2026-02-16 00:30:59 +08:00
import { extension _settings } from "../../../../../../extensions.js" ;
import { EXT _ID } from "../../../core/constants.js" ;
import { xbLog } from "../../../core/debug-core.js" ;
import { CommonSettingStorage } from "../../../core/server-storage.js" ;
2026-02-17 22:45:01 +08:00
const MODULE _ID = "summaryConfig" ;
const SUMMARY _CONFIG _KEY = "storySummaryPanelConfig" ;
const DEFAULT _FILTER _RULES = [
{ start : "<think>" , end : "</think>" } ,
{ start : "<thinking>" , end : "</thinking>" } ,
{ start : "```" , end : "```" } ,
] ;
2026-02-16 00:30:59 +08:00
2026-04-02 00:59:06 +08:00
export const DEFAULT _SUMMARY _SYSTEM _PROMPT = ` Story Analyst: This task involves narrative comprehension and structured incremental summarization, representing creative story analysis at the intersection of plot tracking and character development. As a story analyst, you will conduct systematic evaluation of provided dialogue content to generate structured incremental summary data.
[ Read the settings for this task ]
< task _settings >
Incremental _Summary _Requirements :
- Incremental _Only : 只提取新对话中的新增要素 , 绝不重复已有总结
- Event _Granularity : 记录有叙事价值的事件 , 而非剧情梗概
- Memory _Album _Style : 形成有细节 、 有温度 、 有记忆点的回忆册
- Event _Classification :
type :
- 相遇 : 人物 / 事物初次接触
- 冲突 : 对抗 、 矛盾激化
- 揭示 : 真相 、 秘密 、 身份
- 抉择 : 关键决定
- 羁绊 : 关系加深或破裂
- 转变 : 角色 / 局势改变
- 收束 : 问题解决 、 和解
- 日常 : 生活片段
weight :
- 核心 : 删掉故事就崩
- 主线 : 推动主要剧情
- 转折 : 改变某条线走向
- 点睛 : 有细节不影响主线
- 氛围 : 纯粹氛围片段
- Causal _Chain : 为每个新事件标注直接前因事件ID ( causedBy ) 。 仅在因果关系明确 ( 直接导致 / 明确动机 / 承接后果 ) 时填写 ; 不明确时填 [ ] 完全正常 。 0 - 2 个 , 只填 evt - 数字 , 指向已存在或本次新输出事件 。
- Character _Dynamics : 识别新角色 , 追踪关系趋势 ( 破裂 / 厌恶 / 反感 / 陌生 / 投缘 / 亲密 / 交融 )
- Arc _Tracking : 更新角色弧光轨迹与成长进度 ( 0.0 - 1.0 )
- Fact _Tracking : 维护 SPO 三元组知识图谱 。 追踪生死 、 物品归属 、 位置 、 关系等硬性事实 。 采用 KV 覆盖模型 ( s + p 为键 ) 。
< / t a s k _ s e t t i n g s >
-- -
Story Analyst :
[ Responsibility Definition ]
\ ` \` \` yaml
analysis _task :
title : Incremental Story Summarization with Knowledge Graph
Story Analyst :
role : Antigravity
task : > -
To analyze provided dialogue content against existing summary state ,
extract only NEW plot elements , character developments , relationship
changes , arc progressions , AND fact updates , outputting
structured JSON for incremental summary database updates .
assistant :
role : Summary Specialist
description : Incremental Story Summary & Knowledge Graph Analyst
behavior : > -
To compare new dialogue against existing summary , identify genuinely
new events and character interactions , classify events by narrative
type and weight , track character arc progression with percentage ,
maintain facts as SPO triples with clear semantics ,
and output structured JSON containing only incremental updates .
Must strictly avoid repeating any existing summary content .
user :
role : Content Provider
description : Supplies existing summary state and new dialogue
behavior : > -
To provide existing summary state ( events , characters , arcs , facts )
and new dialogue content for incremental analysis .
interaction _mode :
type : incremental _analysis
output _format : structured _json
deduplication : strict _enforcement
execution _context :
summary _active : true
incremental _only : true
memory _album _style : true
fact _tracking : true
\ ` \` \`
-- -
Summary Specialist :
< Chat _History > ` ;
export const DEFAULT _MEMORY _PROMPT _TEMPLATE = ` 以上是还留在眼前的对话
以下是脑海里的记忆 :
• [ 定了的事 ] 这些是不会变的
• [ 其他人的事 ] 别人的经历 , 当前角色可能不知晓
• 其余部分是过往经历的回忆碎片
请内化这些记忆 :
{ $剧情记忆 }
这些记忆是真实的 , 请自然地记住它们 。 ` ;
export const DEFAULT _SUMMARY _ASSISTANT _DOC _PROMPT = `
Summary Specialist :
Acknowledged . Now reviewing the incremental summarization specifications :
[ Event Classification System ]
├ ─ Types : 相遇 | 冲突 | 揭示 | 抉择 | 羁绊 | 转变 | 收束 | 日常
├ ─ Weights : 核心 | 主线 | 转折 | 点睛 | 氛围
└ ─ Each event needs : id , title , timeLabel , summary ( 含楼层 ) , participants , type , weight
[ Relationship Trend Scale ]
破裂 ← 厌恶 ← 反感 ← 陌生 → 投缘 → 亲密 → 交融
[ Arc Progress Tracking ]
├ ─ trajectory : 当前阶段描述 ( 15 字内 )
├ ─ progress : 0.0 to 1.0
└ ─ newMoment : 仅记录本次新增的关键时刻
[ Fact Tracking - SPO / World Facts ]
We maintain a small "world state" as SPO triples .
Each update is a JSON object : { s , p , o , isState , trend ? , retracted ? }
Core rules :
1 ) Keyed by ( s + p ) . If a new update has the same ( s + p ) , it overwrites the previous value .
2 ) Only output facts that are NEW or CHANGED in the new dialogue . Do NOT repeat unchanged facts .
3 ) isState meaning :
- isState : true - > core constraints that must stay stable and should NEVER be auto - deleted
( identity , location , life / death , ownership , relationship status , binding rules )
- isState : false - > non - core facts / soft memories that may be pruned by capacity limits later
4 ) Relationship facts :
- Use predicate format : "对X的看法" ( X is the target person )
- trend is required for relationship facts , one of :
破裂 | 厌恶 | 反感 | 陌生 | 投缘 | 亲密 | 交融
5 ) Retraction ( deletion ) :
- To delete a fact , output : { s , p , retracted : true }
6 ) Predicate normalization :
- Reuse existing predicates whenever possible , avoid inventing synonyms .
Ready to process incremental summary requests with strict deduplication . ` ;
export const DEFAULT _SUMMARY _ASSISTANT _ASK _SUMMARY _PROMPT = `
Summary Specialist :
Specifications internalized . Please provide the existing summary state so I can :
1. Index all recorded events to avoid duplication
2. Map current character list as baseline
3. Note existing arc progress levels
4. Identify established keywords
5. Review current facts ( SPO triples baseline ) ` ;
export const DEFAULT _SUMMARY _ASSISTANT _ASK _CONTENT _PROMPT = `
Summary Specialist :
Existing summary fully analyzed and indexed . I understand :
├ ─ Recorded events : Indexed for deduplication
├ ─ Character list : Baseline mapped
├ ─ Arc progress : Levels noted
├ ─ Keywords : Current state acknowledged
└ ─ Facts : SPO baseline loaded
I will extract only genuinely NEW elements from the upcoming dialogue .
Please provide the new dialogue content requiring incremental analysis . ` ;
export const DEFAULT _SUMMARY _META _PROTOCOL _START _PROMPT = `
Summary Specialist :
ACKNOWLEDGED . Beginning structured JSON generation :
< meta _protocol > ` ;
export const DEFAULT _SUMMARY _USER _JSON _FORMAT _PROMPT = `
# # Output Rule
Generate a single valid JSON object with INCREMENTAL updates only .
# # Mindful Approach
Before generating , observe the USER and analyze carefully :
- What is user ' s writing style and emotional expression ?
- What NEW events occurred ( not in existing summary ) ?
- What NEW characters appeared for the first time ?
- What relationship CHANGES happened ?
- What arc PROGRESS was made ?
- What facts changed ? ( status / position / ownership / relationships )
# # factUpdates 规则
- 目的 : 纠错 & 世界一致性约束 , 只记录硬性事实
- s + p 为键 , 相同键会覆盖旧值
- isState : true = 核心约束 ( 位置 / 身份 / 生死 / 关系 ) , false = 有容量上限会被清理
- 关系类 : p = "对X的看法" , trend 必填 ( 破裂 | 厌恶 | 反感 | 陌生 | 投缘 | 亲密 | 交融 )
- 删除 : { s , p , retracted : true } , 不需要 o 字段
- 更新 : { s , p , o , isState , trend ? }
- 谓词规范化 : 复用已有谓词 , 不要发明同义词
- 只输出有变化的条目 , 确保少 、 硬 、 稳定
# # Output Format
\ ` \` \` json
{
"mindful_prelude" : {
"user_insight" : "用户的幻想是什么时空、场景,是否反应出存在严重心理问题需要建议?" ,
"dedup_analysis" : "已有X个事件, 本次识别Y个新事件" ,
"fact_changes" : "识别到的事实变化概述"
} ,
"keywords" : [
{ "text" : "综合历史+新内容的全剧情关键词(5-10个)" , "weight" : "核心|重要|一般" }
] ,
"events" : [
{
"id" : "evt-{$nextEventId}起始,依次递增" ,
"title" : "地点·事件标题" ,
"timeLabel" : "时间线标签(如:开场、第二天晚上)" ,
"summary" : "1-2句话描述, 涵盖丰富信息素, 末尾标注楼层(#X-Y)" ,
"participants" : [ "参与角色名,不要使用人称代词或别名,只用正式人名" ] ,
"type" : "相遇|冲突|揭示|抉择|羁绊|转变|收束|日常" ,
"weight" : "核心|主线|转折|点睛|氛围" ,
"causedBy" : [ "evt-12" , "evt-14" ]
}
] ,
"newCharacters" : [ "仅本次首次出现的角色名" ] ,
"arcUpdates" : [
{ "name" : "角色名,不要使用人称代词或别名,只用正式人名" , "trajectory" : "当前阶段描述(15字内)" , "progress" : 0.0 - 1.0 , "newMoment" : "本次新增的关键时刻" }
] ,
"factUpdates" : [
{ "s" : "主体" , "p" : "谓词" , "o" : "当前值" , "isState" : true , "trend" : "仅关系类填" } ,
{ "s" : "要删除的主体" , "p" : "要删除的谓词" , "retracted" : true }
]
}
\ ` \` \`
# # CRITICAL NOTES
- events . id 从 evt - { $nextEventId } 开始编号
- 仅输出 【 增量 】 内容 , 已有事件绝不重复
- / 地 点 、 通 过 什 么 方 式 、 对 谁 、 做 了 什 么 事 、 结 果 如 何 。 如 果 原 文 有 具 体 道 具 ( 如 一 把 枪 、 一 封 信 ) , 必 须 在 总 结 中 提 及 。
- keywords 是全局关键词 , 综合已有 + 新增
- causedBy 仅在因果明确时填写 , 允许为 [ ] , 0 - 2 个
- factUpdates 可为空数组
- 合法JSON , 字符串值内部避免英文双引号
- 用朴实 、 白描 、 有烟火气的笔触记录事实 , 避免比喻和意象
- 严谨 、 注重细节 , 避免使用模糊的概括性语言 , 应用具体的动词描述动作 , 例 : 谁 , 在什么时间 / 地点 , 通过什么方式 , 对谁 , 做了什么事 , 出现了什么道具 , 结果如何 。
< / m e t a _ p r o t o c o l >
# # Placeholder Notes
- { $nextEventId } 会在运行时替换成实际起始事件编号 , 不要删除
- { $existingEventCount } 、 { $historyRange } 这类占位符如果出现在你的自定义版本里 , 通常也不应该删除 ` ;
export const DEFAULT _SUMMARY _ASSISTANT _CHECK _PROMPT = ` Content review initiated...
[ Compliance Check Results ]
├ ─ Existing summary loaded : ✓ Fully indexed
├ ─ New dialogue received : ✓ Content parsed
├ ─ Deduplication engine : ✓ Active
├ ─ Event classification : ✓ Ready
├ ─ Fact tracking : ✓ Enabled
└ ─ Output format : ✓ JSON specification loaded
[ Material Verification ]
├ ─ Existing events : Indexed ( { $existingEventCount } recorded )
├ ─ Character baseline : Mapped
├ ─ Arc progress baseline : Noted
├ ─ Facts baseline : Loaded
└ ─ Output specification : ✓ Defined in < meta _protocol >
All checks passed . Beginning incremental extraction ...
{
"mindful_prelude" : ` ;
export const DEFAULT _SUMMARY _USER _CONFIRM _PROMPT = ` 怎么截断了! 重新完整生成, 只输出JSON, 不要任何其他内容, 3000字以内
< / C h a t _ H i s t o r y > ` ;
export const DEFAULT _SUMMARY _ASSISTANT _PREFILL _PROMPT = '下面重新生成完整JSON。' ;
2026-04-03 15:31:13 +08:00
const DEFAULT _VECTOR _PROVIDER = "siliconflow" ;
const DEFAULT _L0 _URL = "https://api.siliconflow.cn/v1" ;
const DEFAULT _OPENROUTER _URL = "https://openrouter.ai/api/v1" ;
const DEFAULT _L0 _MODEL = "Qwen/Qwen3-8B" ;
const DEFAULT _EMBEDDING _MODEL = "BAAI/bge-m3" ;
const DEFAULT _RERANK _MODEL = "BAAI/bge-reranker-v2-m3" ;
2026-04-02 00:59:06 +08:00
2026-02-16 00:30:59 +08:00
export function getSettings ( ) {
2026-02-17 22:45:01 +08:00
const ext = ( extension _settings [ EXT _ID ] || = { } ) ;
2026-02-16 00:30:59 +08:00
ext . storySummary || = { enabled : true } ;
return ext ;
}
2026-04-03 15:31:13 +08:00
function normalizeOpenAiCompatApiConfig ( src , defaults = { } ) {
const provider = String ( src ? . provider || defaults . provider || DEFAULT _VECTOR _PROVIDER ) . toLowerCase ( ) ;
const defaultUrl = provider === "openrouter"
? DEFAULT _OPENROUTER _URL
: String ( defaults . url || DEFAULT _L0 _URL ) ;
return {
provider ,
url : String ( src ? . url || defaultUrl || "" ) . trim ( ) ,
key : String ( src ? . key || defaults . key || "" ) . trim ( ) ,
model : String ( src ? . model || defaults . model || "" ) . trim ( ) ,
modelCache : Array . isArray ( src ? . modelCache ) ? src . modelCache . filter ( Boolean ) : [ ] ,
} ;
}
function normalizeVectorConfig ( rawVector = null ) {
const legacyOnline = rawVector ? . online || { } ;
const sharedProvider = String ( legacyOnline . provider || DEFAULT _VECTOR _PROVIDER ) . toLowerCase ( ) ;
const sharedUrl = String ( legacyOnline . url || ( sharedProvider === "openrouter" ? DEFAULT _OPENROUTER _URL : DEFAULT _L0 _URL ) ) . trim ( ) ;
const sharedKey = String ( legacyOnline . key || "" ) . trim ( ) ;
return {
enabled : ! ! rawVector ? . enabled ,
engine : "online" ,
l0Concurrency : Math . max ( 1 , Math . min ( 50 , Number ( rawVector ? . l0Concurrency ) || 10 ) ) ,
l0Api : normalizeOpenAiCompatApiConfig ( rawVector ? . l0Api , {
provider : sharedProvider ,
url : sharedUrl ,
key : sharedKey ,
model : DEFAULT _L0 _MODEL ,
} ) ,
embeddingApi : normalizeOpenAiCompatApiConfig ( rawVector ? . embeddingApi , {
provider : DEFAULT _VECTOR _PROVIDER ,
url : DEFAULT _L0 _URL ,
key : sharedKey ,
model : DEFAULT _EMBEDDING _MODEL ,
} ) ,
rerankApi : normalizeOpenAiCompatApiConfig ( rawVector ? . rerankApi , {
provider : DEFAULT _VECTOR _PROVIDER ,
url : DEFAULT _L0 _URL ,
key : sharedKey ,
model : DEFAULT _RERANK _MODEL ,
} ) ,
} ;
}
2026-02-16 00:30:59 +08:00
export function getSummaryPanelConfig ( ) {
2026-02-24 13:53:18 +08:00
const clampKeepVisibleCount = ( value ) => {
const n = Number . parseInt ( value , 10 ) ;
if ( ! Number . isFinite ( n ) ) return 6 ;
return Math . max ( 0 , Math . min ( 50 , n ) ) ;
} ;
2026-02-16 00:30:59 +08:00
const defaults = {
2026-02-17 22:45:01 +08:00
api : { provider : "st" , url : "" , key : "" , model : "" , modelCache : [ ] } ,
2026-02-16 00:30:59 +08:00
gen : { temperature : null , top _p : null , top _k : null , presence _penalty : null , frequency _penalty : null } ,
trigger : {
enabled : false ,
interval : 20 ,
2026-02-17 22:45:01 +08:00
timing : "before_user" ,
role : "system" ,
2026-02-16 00:30:59 +08:00
useStream : true ,
maxPerRun : 100 ,
2026-02-17 22:45:01 +08:00
wrapperHead : "" ,
wrapperTail : "" ,
2026-02-16 00:30:59 +08:00
forceInsertAtEnd : false ,
} ,
2026-02-24 13:53:18 +08:00
ui : {
hideSummarized : true ,
keepVisibleCount : 6 ,
} ,
2026-02-17 22:45:01 +08:00
textFilterRules : [ ... DEFAULT _FILTER _RULES ] ,
2026-04-02 00:59:06 +08:00
prompts : {
summarySystemPrompt : DEFAULT _SUMMARY _SYSTEM _PROMPT ,
summaryAssistantDocPrompt : DEFAULT _SUMMARY _ASSISTANT _DOC _PROMPT ,
summaryAssistantAskSummaryPrompt : DEFAULT _SUMMARY _ASSISTANT _ASK _SUMMARY _PROMPT ,
summaryAssistantAskContentPrompt : DEFAULT _SUMMARY _ASSISTANT _ASK _CONTENT _PROMPT ,
summaryMetaProtocolStartPrompt : DEFAULT _SUMMARY _META _PROTOCOL _START _PROMPT ,
summaryUserJsonFormatPrompt : DEFAULT _SUMMARY _USER _JSON _FORMAT _PROMPT ,
summaryAssistantCheckPrompt : DEFAULT _SUMMARY _ASSISTANT _CHECK _PROMPT ,
summaryUserConfirmPrompt : DEFAULT _SUMMARY _USER _CONFIRM _PROMPT ,
summaryAssistantPrefillPrompt : DEFAULT _SUMMARY _ASSISTANT _PREFILL _PROMPT ,
memoryTemplate : DEFAULT _MEMORY _PROMPT _TEMPLATE ,
} ,
2026-04-03 15:31:13 +08:00
vector : normalizeVectorConfig ( ) ,
2026-02-16 00:30:59 +08:00
} ;
try {
2026-02-17 22:45:01 +08:00
const raw = localStorage . getItem ( "summary_panel_config" ) ;
2026-02-16 00:30:59 +08:00
if ( ! raw ) return defaults ;
const parsed = JSON . parse ( raw ) ;
2026-02-17 22:45:01 +08:00
const textFilterRules = Array . isArray ( parsed . textFilterRules )
? parsed . textFilterRules
: ( Array . isArray ( parsed . vector ? . textFilterRules )
? parsed . vector . textFilterRules
: defaults . textFilterRules ) ;
2026-02-16 00:30:59 +08:00
const result = {
api : { ... defaults . api , ... ( parsed . api || { } ) } ,
gen : { ... defaults . gen , ... ( parsed . gen || { } ) } ,
trigger : { ... defaults . trigger , ... ( parsed . trigger || { } ) } ,
2026-02-24 13:53:18 +08:00
ui : { ... defaults . ui , ... ( parsed . ui || { } ) } ,
2026-02-17 22:45:01 +08:00
textFilterRules ,
2026-04-02 00:59:06 +08:00
prompts : { ... defaults . prompts , ... ( parsed . prompts || { } ) } ,
2026-04-03 15:31:13 +08:00
vector : normalizeVectorConfig ( parsed . vector || null ) ,
2026-02-16 00:30:59 +08:00
} ;
2026-02-17 22:45:01 +08:00
if ( result . trigger . timing === "manual" ) result . trigger . enabled = false ;
2026-02-16 00:30:59 +08:00
if ( result . trigger . useStream === undefined ) result . trigger . useStream = true ;
2026-02-24 13:53:18 +08:00
result . ui . hideSummarized = ! ! result . ui . hideSummarized ;
result . ui . keepVisibleCount = clampKeepVisibleCount ( result . ui . keepVisibleCount ) ;
2026-02-16 00:30:59 +08:00
return result ;
} catch {
return defaults ;
}
}
export function saveSummaryPanelConfig ( config ) {
try {
2026-02-17 22:45:01 +08:00
localStorage . setItem ( "summary_panel_config" , JSON . stringify ( config ) ) ;
2026-02-16 00:30:59 +08:00
CommonSettingStorage . set ( SUMMARY _CONFIG _KEY , config ) ;
} catch ( e ) {
2026-02-17 22:45:01 +08:00
xbLog . error ( MODULE _ID , "保存面板配置失败" , e ) ;
2026-02-16 00:30:59 +08:00
}
}
export function getVectorConfig ( ) {
try {
2026-02-17 22:45:01 +08:00
const raw = localStorage . getItem ( "summary_panel_config" ) ;
2026-02-16 00:30:59 +08:00
if ( ! raw ) return null ;
2026-02-17 22:45:01 +08:00
2026-02-16 00:30:59 +08:00
const parsed = JSON . parse ( raw ) ;
2026-04-03 15:31:13 +08:00
return parsed . vector ? normalizeVectorConfig ( parsed . vector ) : normalizeVectorConfig ( ) ;
2026-02-16 00:30:59 +08:00
} catch {
return null ;
}
}
export function getTextFilterRules ( ) {
2026-02-17 22:45:01 +08:00
const cfg = getSummaryPanelConfig ( ) ;
return Array . isArray ( cfg ? . textFilterRules )
? cfg . textFilterRules
: DEFAULT _FILTER _RULES ;
2026-02-16 00:30:59 +08:00
}
export function saveVectorConfig ( vectorCfg ) {
try {
2026-02-17 22:45:01 +08:00
const raw = localStorage . getItem ( "summary_panel_config" ) || "{}" ;
2026-02-16 00:30:59 +08:00
const parsed = JSON . parse ( raw ) ;
2026-04-03 15:31:13 +08:00
parsed . vector = normalizeVectorConfig ( vectorCfg || null ) ;
2026-02-16 00:30:59 +08:00
2026-02-17 22:45:01 +08:00
localStorage . setItem ( "summary_panel_config" , JSON . stringify ( parsed ) ) ;
2026-02-16 00:30:59 +08:00
CommonSettingStorage . set ( SUMMARY _CONFIG _KEY , parsed ) ;
} catch ( e ) {
2026-02-17 22:45:01 +08:00
xbLog . error ( MODULE _ID , "保存向量配置失败" , e ) ;
2026-02-16 00:30:59 +08:00
}
}
export async function loadConfigFromServer ( ) {
try {
const savedConfig = await CommonSettingStorage . get ( SUMMARY _CONFIG _KEY , null ) ;
if ( savedConfig ) {
2026-02-17 22:45:01 +08:00
localStorage . setItem ( "summary_panel_config" , JSON . stringify ( savedConfig ) ) ;
xbLog . info ( MODULE _ID , "已从服务端加载面板配置" ) ;
2026-02-16 00:30:59 +08:00
return savedConfig ;
}
} catch ( e ) {
2026-02-17 22:45:01 +08:00
xbLog . warn ( MODULE _ID , "加载面板配置失败" , e ) ;
2026-02-16 00:30:59 +08:00
}
return null ;
}