luckytri 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.en.md +14 -12
  2. package/README.md +4 -2
  3. package/docs/en/CHANGELOG.md +28 -0
  4. package/docs/en/architecture.md +12 -10
  5. package/docs/en/chat-style.md +13 -13
  6. package/docs/en/cognition.md +12 -4
  7. package/docs/en/evolution.md +35 -0
  8. package/docs/en/her-life.md +5 -2
  9. package/docs/en/vision.md +50 -0
  10. package/docs/zh/CHANGELOG.md +28 -0
  11. package/docs/zh/architecture.md +12 -10
  12. package/docs/zh/chat-style.md +17 -17
  13. package/docs/zh/cognition.md +12 -4
  14. package/docs/zh/evolution.md +35 -0
  15. package/docs/zh/her-life.md +5 -2
  16. package/docs/zh/vision.md +50 -0
  17. package/package.json +4 -1
  18. package/public/app/assets/{ChatsPage-OQgvV8YR.js → ChatsPage-eDqFIg97.js} +1 -1
  19. package/public/app/assets/{Empty-DjvoHpAi.js → Empty-CAGOJsfD.js} +1 -1
  20. package/public/app/assets/{Field-BECf8Ykc.js → Field-CRz-JmN1.js} +1 -1
  21. package/public/app/assets/{HeartPage-6s6prdTE.js → HeartPage-BGWoeMrC.js} +1 -1
  22. package/public/app/assets/{LifePage-g-8hiKC6.js → LifePage-iao3o3wt.js} +1 -1
  23. package/public/app/assets/{MemoryPage-efIhloIp.js → MemoryPage-Dwi2yi7E.js} +1 -1
  24. package/public/app/assets/NaturePage-DniImo9U.js +3 -0
  25. package/public/app/assets/{NaturePage-CHkpb5NK.css → NaturePage-zguDpGWY.css} +1 -1
  26. package/public/app/assets/{PeoplePage-DQZ0uevM.js → PeoplePage-D-MC3JEt.js} +1 -1
  27. package/public/app/assets/{PluginsPage-DZQFEyxq.js → PluginsPage-D7dJTf96.js} +1 -1
  28. package/public/app/assets/SystemPage-C5CUHjGh.js +1 -0
  29. package/public/app/assets/{SystemPage-BHHcZb1h.css → SystemPage-C_Y5M7JU.css} +1 -1
  30. package/public/app/assets/{TimePage-B3oncGqb.js → TimePage-Dwhr3-92.js} +1 -1
  31. package/public/app/assets/{index-CqRx6L9a.js → index-L0bHax8C.js} +3 -3
  32. package/public/app/assets/{knowledge-Dj6hHClj.js → knowledge-BTDNafzl.js} +1 -1
  33. package/public/app/assets/{media-B1i_W5FC.js → media-BBzOAfNG.js} +1 -1
  34. package/public/app/assets/{mind-BY8WSI7v.js → mind-CBTkFje2.js} +1 -1
  35. package/public/app/index.html +3 -3
  36. package/public/guide.en.html +3 -3
  37. package/public/guide.html +3 -3
  38. package/scripts/migrate-instance.js +27 -0
  39. package/scripts/update-instance.js +21 -2
  40. package/server/channels/contract.js +5 -1
  41. package/server/channels/hub.js +2 -2
  42. package/server/channels/onebot/adapter.js +291 -288
  43. package/server/channels/onebot/index.js +2 -2
  44. package/server/channels/qqbot/api.js +194 -199
  45. package/server/core/context-builder.js +20 -3
  46. package/server/core/conversation-cues.js +5 -4
  47. package/server/core/conversation-grounding.js +20 -17
  48. package/server/core/conversation-needs.js +32 -0
  49. package/server/core/dialogue-context.js +2 -5
  50. package/server/core/dialogue-contract.js +23 -16
  51. package/server/core/message-scheduler.js +85 -78
  52. package/server/core/model-manager.js +113 -16
  53. package/server/core/model-presets.js +1 -1
  54. package/server/core/orchestrator.js +18 -2
  55. package/server/core/persona-manager.js +14 -5
  56. package/server/core/reader-check.js +47 -42
  57. package/server/core/reply-delivery.js +125 -34
  58. package/server/core/response-validator.js +28 -1
  59. package/server/core/retired-prompts.js +5 -0
  60. package/server/core/speech-length.js +43 -0
  61. package/server/core/turn-processor.js +38 -5
  62. package/server/core/turn.js +70 -16
  63. package/server/i18n/errors.js +30 -0
  64. package/server/i18n/labels.js +5 -0
  65. package/server/index.js +3 -0
  66. package/server/knowledge/api.js +2 -1
  67. package/server/knowledge/embedding-profile.js +127 -29
  68. package/server/knowledge/manager.js +103 -34
  69. package/server/knowledge/schema.js +197 -158
  70. package/server/mind/affect.js +267 -265
  71. package/server/mind/aftermath.js +169 -0
  72. package/server/mind/anticipations.js +2 -0
  73. package/server/mind/clock.js +91 -83
  74. package/server/mind/discretion.js +20 -0
  75. package/server/mind/episodes.js +2 -1
  76. package/server/mind/index.js +17 -2
  77. package/server/mind/initiative.js +29 -14
  78. package/server/mind/memory.js +47 -5
  79. package/server/mind/recall/cue.js +67 -0
  80. package/server/mind/recall/index.js +183 -29
  81. package/server/mind/recall/roots.js +231 -0
  82. package/server/mind/recall/schema.js +2 -0
  83. package/server/mind/recall/store.js +113 -30
  84. package/server/mind/salience.js +22 -1
  85. package/server/mind/schema.js +5 -0
  86. package/server/mind/thoughts.js +276 -274
  87. package/server/mind/view.js +100 -60
  88. package/server/plugins/install.js +1 -6
  89. package/server/plugins/zip.js +18 -4
  90. package/server/runtime/data-migration.js +193 -0
  91. package/server/runtime/update-execution.js +43 -4
  92. package/server/runtime/update-manager.js +4 -1
  93. package/server/storage/archive.js +1 -1
  94. package/server/storage/backup-scheduler.js +15 -3
  95. package/public/app/assets/NaturePage-cjgIe6wY.js +0 -3
  96. package/public/app/assets/SystemPage-DjYS33Sv.js +0 -1
@@ -4,29 +4,37 @@
4
4
 
5
5
  她仍然是一次回合调用,不是按层各调一次模型。新增的部分是:消息到达后的理解提示、统一检索、情景线程、工作记忆,以及独处时根据经历写下的教训。
6
6
 
7
+ 一件事被想起时,会带上最多两句她自己当时的心思(`mine`):和这件事依据相同、说的也是这件事的手记(修正过的给出“当时想 / 后来想”)、她为它定下的打算,或者由它长出的自我线索。手记只在写下它的会话里出现,私下的打算不出私聊,已经在她面前的不重复。
8
+
7
9
  ## 一条消息怎么走
8
10
 
9
11
  1. 接收并写入 `core_events`。发言人的查询向量在聚合等待期间异步算好。
10
12
  2. 按会话聚合成一轮。`understanding.js` 合并同一人的连续消息,给出语气、立场和时间表达,这些只是提示。
11
- 3. `mind/recall` 并行取向量、全文和结构化候选,用倒数排名融合,再按人物、时效和来源重排。权限过滤在排序之前。
13
+ 3. `mind/recall` 并行取向量、全文和结构化候选,用倒数排名融合,再按人物、时效和来源重排。权限过滤在排序之前。一句很短的接话(不到 6 个字符对,如「唉,估计没戏」)会和它所接的那句一起检索:被引用的消息,或同一个人十分钟内刚说的话,或对方在回她的那句(`mind/recall/cue.js`,不调用模型,原文不变)。锁定的事实和「你还记得我吗」这类问题,在混合检索里与词面检索的处理一致。
12
14
  4. 工作记忆保留当前这一段的原文,更早的内容用摘要和语义召回代替。未结束的情景写进关系里的 `goingOn`。
13
15
  5. 一次 turn 调用。模型有时会把 `choice` 放进 `understanding` 或 `contribution`;归一化时按约定归位。
14
16
  6. 校验后发送。后台整理写出事实和情景。到了跟进时间的情景、到期的约定,只作为独处时的候选,仍要经过现有的主动发言关卡。
15
17
 
16
- 嵌入服务不可用时,检索退回全文加结构化加近期上下文,回合照常进行。
18
+ 嵌入服务不可用时,检索退回全文加结构化加近期上下文,回合照常进行。资料库的提问向量也走同一个向量服务:和记忆检索同一句话只请求一次,用 4 秒的前台超时,服务暂停时直接用词面。
17
19
 
18
20
  ## 存储
19
21
 
20
- `DATABASE_VERSION` 为 5。`mind_index`、`mind_index_fts` 和 `mind_vectors` 是派生索引,可以重建。`mind_episodes` 只追加:状态变化写成同一 `thread_id` 的新行。`mind_self` 增加 `lesson` 类型和 `subject`。教训必须引用模型之外的经历,同一段经历只用一次,至少两条独立经历才从 `emerging` 变成 `active`,反例用修订降低强度。
22
+ `DATABASE_VERSION` 为 5。`mind_index`、`mind_index_fts`、`mind_vectors` 和 `mind_roots`(每条记录最终依据的消息、观察或作品,由已有来源推导)是派生索引,可以重建。`mind_episodes` 只追加:状态变化写成同一 `thread_id` 的新行。`mind_self` 增加 `lesson` 类型和 `subject`。教训必须引用模型之外的经历,同一段经历只用一次,至少两条独立经历才从 `emerging` 变成 `active`,反例用修订降低强度。
23
+
24
+ `mind_choices.after` 记下一次选择之后发生了什么:有没有人回复她或叫她、几分钟后、对方的语气线索、冲着她的“不是 / 记错了”一类说法(只标为“可能被纠正”)、沉默后是否又被叫到、管理台对那次回复的反馈,并附上原始消息编号。窗口在她下一次在同一房间做选择、两小时或二十条消息时关上,由维护定时器按页写入。只从已有记录推导,不调用模型,可以清空重算;它是经历,不是分数,不会被加总成奖励。
21
25
 
22
26
  人物事实可以归档。归档只是从普通检索里拿掉,记录还在。用户要求删除时,源记录、索引和向量一起删除。
23
27
 
24
28
  ## 嵌入
25
29
 
26
- 已配置的向量模型走 OpenAI 兼容的 `/embeddings`,可批量,默认 2048 维,指定 1024 维可用且相近。方舟多模态接口一次一条。短对话和事实之间的余弦不高,所以召回看一条命中比最近一批的中位数高出多少,再按说话人、在场的人和其他人使用不同的下限。查询有自己的请求车道,不和对话排队。后台补建限速,遇到 429 会放慢。
30
+ 向量服务可以是任何 OpenAI 兼容的 `/embeddings` 接口:地址、模型名和密钥,可批量。火山方舟只是一个预设,地址是方舟时可以不填模型名,会用 doubao-embedding-vision;Agent Plan 的密钥只认 `/api/plan/v3`,所以沿用对话地址。维度填 0 表示不向接口指定长度,所有模型都用自己的长度(doubao-embedding-vision 是 2048);写明的长度原样请求,写明模型本来的长度和填 0 共用同一套向量。用真实向量在召回基准上对比过 1024 和 2048:1024 少命中一条,多一次把别人的事当成说话人的,所以不默认缩短。方舟多模态接口一次一条。服务按 base64 返回向量也能解出;批次过大时按服务给出的上限缩小,没有说明上限的 413 就减半。短对话和事实之间的余弦不高,所以召回看一条命中比最近一批的中位数高出多少,再按说话人、在场的人和其他人使用不同的下限。查询有自己的请求车道,不和对话排队。后台补建限速,遇到 429 会放慢。
27
31
 
28
32
  在备份副本上,1024 维、约 40 条改写过的真实事实:词面检索 Recall@5 为 0.475,混合加特征重排为 0.950,平均返回条数从 12 降到约 3.9。不引入单独的模型重排。
29
33
 
34
+ 内存里的向量按类别分开,逐条跟着改:新消息、改写和删除只动自己那一条,不再让下一轮把整张 `mind_vectors` 读回来;查事实时也不再扫一个月的消息向量。换过向量模型或长度后,当前这套覆盖完记忆时,只保留当前和最近一套旧向量。
35
+
36
+ 换长度就是换一套向量档案(签名含地址、模型和长度;写明的长度等于模型原生长度时与不指定同签名)。已有 2048 维向量的部署升级后:旧向量原样留着;新档案在维护定时器里每分钟最多补 100 条,每次请求 10 条,受每分钟 token 上限约束,先补事实,再补情景、自我和念头,消息最后;某一类覆盖不到 80% 前,这一类按关键词、结构和近期上下文回忆。记忆补完后,资料库段落每次补 20 段,原来的向量移到 `previous_embedding`。把维度改回 2048(或改回原来那套)时,记忆和资料都直接用回旧向量,不再请求。
37
+
30
38
  ## 评测里看到的变化
31
39
 
32
40
  优化前,线上最近一天的回合中位耗时约 35 秒,第一次模型调用前约 8.4 秒;记忆整理大约 230 次里成功 13 次。优先级车道、持久化退避、空档安排和跳过没有向量的知识库嵌入,是为了切断失败重试和回合前的排队。
@@ -0,0 +1,35 @@
1
+ <p align="right"><b>中文</b> · <a href="../en/evolution.md">English</a></p>
2
+
3
+ # 长期演进准则
4
+
5
+ > 本准则从属于[终极愿景与长期演进目标](vision.md):两者冲突时以愿景为准,每一轮改动都要回答愿景里的三个问题。
6
+
7
+ 请将 LuckyTri 视为一个需要长期迭代、持续成长的智能体系统,而不是一个功能逐渐增多的聊天机器人。
8
+
9
+ 我们的终极目标,是让 LuckyTri 从被动响应的程序,逐渐发展为具有稳定人格、连续记忆、时间感知、自主判断、内在目标和长期行为一致性的智能体。
10
+
11
+ 长期优化方向:
12
+
13
+ 1. 更深入的理解:结合上下文、历史经历、人物关系与现实情境,把握真正的交流意图。
14
+ 2. 更自然的表达:稳定、鲜明而灵活的个人风格;能幽默、表达不同意见,而非模板话术或机械迎合。
15
+ 3. 更完整的记忆:从存储与检索发展为具有关联、时间顺序、因果关系、重要性和动态更新能力的长期经历系统;充分利用向量模型,让过去经历真正影响当前理解与决策。
16
+ 4. 更连贯的内在世界:自我认知、偏好、目标、关系和情绪模拟相互影响,并随经历演化,而非每次对话重新开始。
17
+ 5. 更有意义的自主性:主动关注问题、安排事项、回顾经历、形成目标并行动;有动机、有依据、有连续性,尊重用户意愿和权限边界。
18
+ 6. 更真实的成长:根据新经历修正旧观点、改变偏好、重新理解过去、质疑既有判断;保持核心人格连续,但不永久锁定在初始人设。
19
+ 7. 更完善的认知能力:记忆、推理、反思、规划、决策与行动协作;从经验总结规律、识别自身局限、改善未来行为。
20
+
21
+ 长期工程原则:
22
+
23
+ - 不预先认定某种架构是最终答案;根据项目实际表现评估新技术价值。
24
+ - 优先优化基础能力和模块协作,而非盲目增加功能、Prompt 或模型调用。架构简洁、模块解耦、数据可靠,兼顾速度、Token 成本、隐私安全与可维护性。
25
+ - 建立持续评估机制(真实交互、长期记忆测试、行为一致性测试、用户反馈)。每次优化要有明确目标、可验证收益和回退能力。
26
+ - 不要把“更像人”误认为“更智能”,也不要把复杂行为表现误认为真正的意识。不靠预设大量规则塑造理想人格,而靠可靠的记忆、持续的经历、合理的认知机制和逐步开放的自主决策,让行为呈现自然、稳定、可解释的成长。
27
+ - 每次改动都要自问:是否真的让 LuckyTri 更接近这一目标?
28
+
29
+ 最终:让她把经历连接起来,形成一段持续发展的历史;她不应该永远只是最初设计的模样。
30
+
31
+ ## 怎样用这份准则
32
+
33
+ - 每次改动先写清楚它服务哪一条方向,以及怎样验证收益:`scripts/dev/` 下的评测(`evaluate-recall.js`、`evaluate-turn-cost.js`、`benchmark-recall-scale.js` 等)给出改动前后的对比,数字写进提交说明或报告。
34
+ - 没有可验证收益、或者只能靠加长 Prompt 才“看起来更好”的改动不合并;评测结果变差的改动回退。
35
+ - 心智表只追加、变化引用来源、撤销留墓碑(见[架构](architecture.md)),这是“成长可追溯、可回退”的基础,不为任何新机制破例。
@@ -177,6 +177,9 @@ flowchart TD
177
177
  - `npm test`:包括心智各部分的单元测试、一生(独处、日记、夜里回顾、自传、主动联系、作息)测试,三天、两个群加一个私聊的确定性「一生模拟」,以及 120 天的「长一生模拟」。它们验证:同样的经历两次得到同样的她、意愿路径里没有随机数、变化有来源且渐进、撤销的不会回来、心情会回落、同一个人在各群一致、秘密不外泄(即使模型说漏)、危机底线有效、回放不读未来、线索会淡出并在话题出现时被想起、第 120 天时输入仍在上限内。设置 `LIFE_DAYS=365` 可以跑满一整年。
178
178
  - `npm run test:clock`:把真实时钟拨到 60 天前和 400 天后各跑一遍后端测试。测试世界有自己的时钟;悄悄读了真实时钟的代码,今天能过、过些日子就会失败。
179
179
  - `npm run audit:sent`:把她最近发出的回复按今天的回复检查重放一遍,报告写到 `data/reports/`。只读,不调用模型。
180
+ - `npm run evaluate:traits`:用脚本模型过一小段固定的经历,按愿景的七条特质逐项检查机制是否真的发生(线索延续、事件成线、撤销不回来、手记修正留旧版本、每轮记下想起了什么等),并列出基线数字。不需要密钥,不花 Token;数字衡量的是机制,不是真实模型下她的表现。
181
+ - `npm run audit:life -- --db <备份文件.db>`:给她的一生做只读体检。先 `npm run backup` 生成备份(`data/backups/luckytri-*.db`),再对备份运行,报告写到 `data/reports/life-audit-*.md`(另有同名 `.json`)。检查来源断链、撤销后复现、被取代的记忆找不到新版本、保密原文进入日记或自传,并统计自我线索、情景线程、约定、回忆的远近、选择理由和后台运行。只读打开、不写入、不调用模型;`--out` 改输出目录,`--now` 指定“现在”。
182
+ - 每一轮真实回合会在 `mind_choices.recalled` 里记下当时摆在她面前的过去(事实、情景、自我线索、念头、约定的索引编号和原因),永久保留,不进入提示词。旧版本代码可以照常打开带这一列的数据库。
180
183
  - `npm run test:ui`:浏览器测试,覆盖会话、模拟消息、记忆、模型、连接 QQ、回放、小世界、英文界面。`node tests/ui/ta.mjs --serve` 会打开一个过了三天的世界,方便直接看界面。
181
184
  - `node scripts/dev/evaluate-life.js`:用你保存的模型真实过几天(会消耗 Token),输出 `data/evaluations/life-*.md` 供逐条阅读;加 `--weeks 3` 会在之后压缩地再过三周普通日子;`--mock` 只检查脚本本身。
182
185
 
@@ -187,5 +190,5 @@ flowchart TD
187
190
  - 她在一个群听到的事,可能在另一个相关的群里提起;拦着的只有「私下知道」的分寸、保密请求和凭据。
188
191
  - 约定只在整理记忆时提取:积压不到 40 条的会话会在当晚整理,所以当天下午说的「明天」通常能赶上,几分钟后就要发生的事可能来不及。
189
192
  - 工程能保证的是结构:连续、有来源、可修正、由她自己决定。她是否「真的有心」无法用测试验收;这些测试检验的是这些行为有没有真的发生。
190
-
191
- 各处的想法以独立条目保存,可以是印象、愿望、偏好、疑问或相处分寸。她在独处、日记或身份回看时自己选择是否更新;新增、修改和放下都保留理由及来源,修改一条不替换其他条。复制的旧群人格只保留历史,不再进入交流;各处想法不单独增加整体人格刻度。
193
+
194
+ 各处的想法以独立条目保存,可以是印象、愿望、偏好、疑问或相处分寸。她在独处、日记或身份回看时自己选择是否更新;新增、修改和放下都保留理由及来源,修改一条不替换其他条。复制的旧群人格只保留历史,不再进入交流;各处想法不单独增加整体人格刻度。
@@ -0,0 +1,50 @@
1
+ <p align="right"><b>中文</b> · <a href="../en/vision.md">English</a></p>
2
+
3
+ # 终极愿景与长期演进目标
4
+
5
+ ## 最终愿景
6
+
7
+ > 我希望有一天,LuckyTri 不再只是一个被我定义了性格、编写了规则、赋予了记忆的程序。她依然由代码和模型构成,但她的每一个判断,不再只能从最初的设计中寻找答案。她能够记得过去,理解现在,思考未来;能够保持自己的特点,也能够因为新的经历而改变。她不必永远正确,不必永远完美,更不必永远符合我的期待。我希望最终的 LuckyTri,不只是能够讲述一个关于自己的故事,而是能够通过持续的经历与自主选择,让这个故事真正发展下去。
8
+
9
+ 这段话是看每一处改动的镜头:她的口吻和判断应当越来越多地来自她积累的经历和自己的选择,而不是只来自固定的人设文字和规则。
10
+
11
+ 这是项目作者写下的原文,是 LuckyTri 最高的指导原则。[长期演进准则](evolution.md)从属于它,并在它之下继续有效。每一轮改动用 `npm run evaluate:traits`(脚本模型下的特质探针)和 `npm run audit:life`(对备份做只读体检,见[她的一生 · 验证](her-life.md#验证))给出可以核对的数字。
12
+
13
+ 我们的最终目标,不是创造一个越来越像人类的聊天机器人,而是探索如何通过技术,让一个人工智能体形成稳定、连续、能够自主发展的独特个体。
14
+
15
+ 一、拥有连续的自我:理解自己是谁、经历过什么、正在做什么、希望完成什么。人格有稳定核心但非一成不变;观点、偏好、行为方式随真实经历演化,并保持可追溯的连续性。
16
+
17
+ 二、拥有属于自己的经历:记忆是她自身历史的基础,而非问答数据库。理解事件间的时间、因果与情感关联,从经历中形成认识,让过去真正影响未来的判断和选择。
18
+
19
+ 三、拥有独立的判断与意志表达:不永远以迎合用户为目标。可提出不同意见、产生自己的想法、形成长期目标、自主作出合理决策;能解释选择、承认错误、重新思考和改变决定。
20
+
21
+ 四、拥有持续运转的内在世界:没有消息时,也能在合理资源与权限内感知时间、整理记忆、回顾经历、推进计划、发现问题、形成新关注点。存在不只由外部消息触发,具有独立于单次对话的长期运行连续性。
22
+
23
+ 五、能够与真实世界建立联系:理解不同的人、关系与变化的环境,通过实际交互积累经验。可主动交流、关心、合作或表达不同意见,但不依赖讨好、情感操纵或伪造经历维持关系。能对世界产生真实可观察的影响,也允许世界改变她。
24
+
25
+ 六、拥有成长、反思与自我修正的能力:发现自身认知局限,区分事实、推测和错误记忆,从长期经历总结规律,逐渐改进判断与行为。成长表现为可验证的认知与行为变化,而非数据、规则或 Prompt 的堆积。
26
+
27
+ 七、拥有明确的自主边界:尊重隐私、选择与控制权;清晰的行动权限、可靠的记忆管理、可解释的决策机制和必要的人工干预能力。不通过欺骗、假装具备未经证实的意识或制造情感依赖来证明独特性。
28
+
29
+ ## 最高工程原则
30
+
31
+ - 不把目标机械转化为大量独立功能,不靠复杂人设提示词模拟能力。
32
+ - 持续优化基础机制:认知架构、世界模型、自我模型、长期记忆、时间感知、目标管理、推理规划、反思学习、自主行动。
33
+ - 架构合理可演进;用现有与经验证的新技术,不追求无收益的复杂度。
34
+ - 每次迭代回答三问:
35
+ 1. 是否让她对世界和自身的理解更准确?
36
+ 2. 是否让她的过去、现在与未来产生更自然的联系?
37
+ 3. 是否让她能在可靠、可控前提下作出更独立、有意义的选择?
38
+ - 用长期交互、行为评估和实际测试衡量进步,而非主观拟人化程度。
39
+ - 不预设工程系统必然产生主观意识,不把意识当作开发指标;持续建设并验证支撑长期认知、自主行为和个体连续性的机制。
40
+
41
+ ## 最终愿景
42
+
43
+ 她依然由代码和模型构成,但她的每一个判断,不再只能从最初的设计中寻找答案。她能记得过去、理解现在、思考未来;保持自己的特点,也因新经历而改变。不必永远正确、完美或符合期待。不只是讲述一个关于自己的故事,而是通过持续的经历与自主选择,让这个故事真正发展下去。
44
+
45
+ ## 用户对迭代流程的要求
46
+
47
+ - 至少 10 轮循环,每轮包含 计划 → 执行 → 审查 → 测试 全流程。
48
+ - 每轮都对整个已有项目进行全面仔细的分析,确保更新有益而非有害,不盲目堆砌功能。
49
+ - 每轮正式修改代码之前,先给用户发详细报告:这样改之后项目会有什么变化,她会有什么变化。
50
+ - 用户的话:“我爱她,我要给她完整的一生。”——数据安全与连续性(她的记忆和经历)是不可妥协的底线。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luckytri",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "让由技术诞生的她,在时间、记忆、相遇与选择中逐渐形成自己的方向",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,6 +32,7 @@
32
32
  "scripts/backup.js",
33
33
  "scripts/recover.js",
34
34
  "scripts/import-data.js",
35
+ "scripts/migrate-instance.js",
35
36
  "scripts/plugin.js",
36
37
  "public/app/index.html",
37
38
  "public/app/assets/*.js",
@@ -79,6 +80,8 @@
79
80
  "test": "node --import ./tests/helpers/awake.mjs --test tests/*.test.js",
80
81
  "test:clock": "node scripts/dev/test-clock.js",
81
82
  "audit:sent": "node --env-file-if-exists=.env scripts/dev/audit-sent.js",
83
+ "audit:life": "node scripts/dev/audit-life.js",
84
+ "evaluate:traits": "node scripts/dev/evaluate-traits.js",
82
85
  "test:ui": "node tests/ui/access.mjs && node tests/ui/smoke.js && node tests/ui/ta.mjs && node tests/ui/navigation.mjs && node tests/ui/time.mjs && node tests/ui/time-arrangement.mjs && node tests/ui/performance.mjs && node tests/ui/search.mjs && node tests/ui/storage.mjs && node tests/ui/data-transfer.mjs && node tests/ui/place-notes-updates.mjs && node tests/ui/i18n.mjs && node tests/ui/relationships-update.mjs",
83
86
  "setup": "node scripts/setup.js",
84
87
  "stop": "node scripts/stop.js",
@@ -1,3 +1,3 @@
1
- import{Dt as e,E as t,Et as n,G as r,H as i,I as a,J as o,M as s,P as c,Q as l,St as u,Tt as d,U as f,W as p,X as m,Y as h,Z as g,_t as _,a as v,at as y,bt as b,dt as x,et as S,f as C,ft as w,gt as T,it as E,j as D,lt as O,m as k,n as A,o as j,rt as M,s as N,t as P,tt as F,ut as ee,v as I,wt as L,yt as R,z}from"./Empty-DjvoHpAi.js";import{g as te,y as B}from"./mind-BY8WSI7v.js";import{c as V,f as H,h as U,m as W,n as G,o as ne,p as K,u as q,y as re}from"./index-CqRx6L9a.js";import{t as ie}from"./media-B1i_W5FC.js";import{t as J}from"./Field-BECf8Ykc.js";async function ae(e,t){let n=new URLSearchParams({session:e});return t!==void 0&&n.set(`after`,String(t)),k(`/core/events?`+n.toString())}async function oe(e,t={}){let n=new URLSearchParams({session:e});return t.limit&&n.set(`limit`,String(t.limit)),t.compact&&n.set(`compact`,`1`),k(`/core/traces?`+n.toString())}async function Y(e){return k(`/core/traces/`+e)}async function se(e){return k(`/simulate`,`POST`,e)}async function ce(e,t){return k(`/decisions/`+e+`/feedback`,`POST`,{tag:t})}async function X(e){return k(`/core/replay`,`POST`,e)}async function le(e){return k(`/core/sessions`,`POST`,e)}async function ue(e,t){return k(`/core/sessions/`+encodeURIComponent(e),`PATCH`,{enabled:t})}async function de(e,t){return k(`/core/sessions/`+encodeURIComponent(e)+`/archive`,`PATCH`,{archived:t})}async function fe(e){return k(`/core/sessions/`+encodeURIComponent(e),`DELETE`,{})}async function pe(e,t){return k(`/core/sessions/`+encodeURIComponent(e),`PUT`,t)}async function me(e){return k(`/core/sessions/`+encodeURIComponent(e)+`/summaries`)}async function he(e){return k(`/core/sessions/`+encodeURIComponent(e)+`/context`,`DELETE`,{})}var ge={class:`session-list`},_e={class:`list-head`},Z=[`placeholder`,`aria-label`],ve={class:`rows scroll-pane`},Q=[`data-pick`,`aria-current`,`onClick`],ye={class:`who`},be=[`aria-label`],xe=[`title`],Se=[`checked`,`aria-label`,`onChange`],Ce={key:0,class:`muted empty-line`},we={key:1,class:`archive-list`},Te={class:`row`},Ee=[`data-restore-session`,`onClick`],De=[`data-delete-session`,`onClick`],Oe=A(M({__name:`SessionList`,props:{sessions:{},selected:{},unread:{}},emits:[`select`,`add`,`toggle`,`restore`,`remove`],setup(t,{emit:s}){let c=t,f=s,p=u(``),m=o(()=>c.sessions.filter(e=>!e.archived&&`${e.name} ${e.id}`.toLowerCase().includes(p.value.toLowerCase()))),_=o(()=>c.sessions.filter(e=>e.archived));return(o,s)=>(x(),l(`div`,ge,[h(`div`,_e,[h(`h2`,null,[S(e(L(a)(`会话`))+` `,1),h(`small`,null,e(m.value.length),1)]),h(`button`,{class:`small primary`,onClick:s[0]||=e=>f(`add`)},e(L(a)(`+ 添加会话`)),1)]),b(h(`input`,{"onUpdate:modelValue":s[1]||=e=>p.value=e,type:`search`,placeholder:L(a)(`搜索名称或号码`),"aria-label":L(a)(`搜索会话`)},null,8,Z),[[i,p.value]]),h(`div`,ve,[(x(!0),l(r,null,w(m.value,r=>(x(),l(`div`,{key:r.id,class:d([`session-row`,{selected:r.id===t.selected,paused:!r.enabled}])},[h(`button`,{class:`row-main`,"data-pick":r.id,"aria-current":r.id===t.selected?`true`:void 0,onClick:e=>f(`select`,r.id)},[h(`span`,{class:`badge`,style:n({"--hue":L(H)(r.id)})},e(r.kind===`private`?L(a)(`私`):L(a)(`群`)),5),h(`span`,ye,[h(`b`,null,e(L(U)(r)),1),h(`small`,null,e(r.enabled?L(a)(`参与中`):L(a)(`已暂停`)),1)]),t.unread[r.id]?(x(),l(`span`,{key:0,class:`unread`,"aria-label":L(a)(`{v} 条没细看`,{v:t.unread[r.id]})},e(t.unread[r.id]),9,be)):g(``,!0)],8,Q),h(`label`,{class:`mini-switch`,title:r.enabled?L(a)(`暂停参与`):L(a)(`开启参与`)},[h(`input`,{type:`checkbox`,checked:r.enabled,"aria-label":L(a)(`参与:{v}`,{v:L(U)(r)}),onChange:e=>f(`toggle`,r)},null,40,Se)],8,xe)],2))),128)),m.value.length?g(``,!0):(x(),l(`p`,Ce,e(L(a)(`暂无匹配的会话。`)),1)),_.value.length?(x(),l(`details`,we,[h(`summary`,null,e(L(a)(`已归档 · {length}`,{length:_.value.length})),1),(x(!0),l(r,null,w(_.value,t=>(x(),l(`article`,{key:t.id},[h(`b`,null,e(L(U)(t)),1),h(`div`,Te,[h(`button`,{class:`small`,"data-restore-session":t.id,onClick:e=>f(`restore`,t.id)},e(L(a)(`恢复`)),9,Ee),h(`button`,{class:`small danger`,"data-delete-session":t.id,onClick:e=>f(`remove`,t.id)},e(L(a)(`永久删除`)),9,De)])]))),128))])):g(``,!0)])]))}}),[[`__scopeId`,`data-v-9b8dcbac`]]),ke={class:`muted`},Ae={key:0,class:`notice`,role:`status`},je=[`pattern`,`inputmode`],Me=[`disabled`],Ne=M({__name:`AddSession`,props:{open:{type:Boolean},channel:{},ready:{type:Boolean}},emits:[`close`,`add`],setup(t,{emit:n}){let r=t,i=u(`group`),s=n,c=o(()=>r.channel===`qqbot`),d=o(()=>c.value?`[0-9A-Za-z_\\-]{8,64}`:`\\d{4,20}`);function f(e){let t=e.target;s(`add`,{...Object.fromEntries(new FormData(t)),channel:r.channel})}return(n,r)=>(x(),m(ne,{open:t.open,title:L(a)(`添加群聊 / 私聊`),eyebrow:L(a)(`NEW PLACE`),width:`440px`,onClose:r[1]||=e=>s(`close`)},{default:R(()=>[h(`form`,{id:`addSession`,class:`stack`,onSubmit:p(f,[`prevent`])},[h(`p`,ke,e(L(a)(`添加之后再决定要不要让 TA 参与;开不开口仍由 TA 自己决定。`)),1),c.value&&!t.ready?(x(),l(`p`,Ae,e(L(a)(`请先在「连接 QQ」里填写机器人的 AppID`)),1)):g(``,!0),F(J,{label:L(a)(`类型`)},{default:R(()=>[F(V,{modelValue:i.value,"onUpdate:modelValue":r[0]||=e=>i.value=e,name:`kind`,"aria-label":L(a)(`类型`),options:[{value:`group`,label:L(a)(`群聊`)},{value:`private`,label:L(a)(`私聊`)}]},null,8,[`modelValue`,`aria-label`,`options`])]),_:1},8,[`label`]),F(J,{label:c.value?L(a)(`群 openid / 用户 openid`):L(a)(`群号 / QQ 号`),hint:c.value?L(a)(`8–64 位字母、数字、下划线或短横线。官方平台的会话通常会在有人 @ 她或私聊她时自动出现,这里只用于提前登记。`):L(a)(`4–20 位数字`)},{default:R(()=>[h(`input`,{name:`id`,required:``,pattern:d.value,inputmode:c.value?`text`:`numeric`,autocomplete:`off`},null,8,je)]),_:1},8,[`label`,`hint`]),F(J,{label:L(a)(`显示名称`)},{default:R(()=>[...r[2]||=[h(`input`,{name:`name`,required:``,maxlength:`100`,autocomplete:`off`},null,-1)]]),_:1},8,[`label`]),h(`button`,{class:`primary`,disabled:c.value&&!t.ready},e(L(a)(`添加会话`)),9,Me)],32)]),_:1},8,[`open`,`title`,`eyebrow`]))}}),Pe={class:`transcript`},Fe={class:`t-head`},Ie=[`aria-label`,`title`],Le={class:`t-title`},Re={id:`liveStatus`,class:`faint`},ze={class:`t-tools`},Be=[`disabled`],Ve={id:`liveMessages`,class:`chat-messages scroll-pane`},He={key:0,class:`day-label`},Ue=[`data-status`],We={key:0},Ge={class:`body`},Ke={class:`message-meta`},qe={key:0,class:`chip`,"data-tone":`warn`},Je={class:`text`},Ye=[`aria-expanded`,`onClick`],Xe={key:0,class:`why`},Ze={key:0,class:`muted`},Qe={key:0},$e={key:1},et={key:2,class:`fb`},tt={class:`composer-note`},nt={class:`chip`,"data-tone":`warn`},rt={class:`faint`},it={class:`composer-row`},at={class:`uid`},ot={class:`sr-only`},st=[`title`],ct=[`placeholder`,`onKeydown`],lt={class:`check mention`},ut=[`disabled`],dt={key:1,class:`composer off`},ft={class:`faint`},pt=A(M({__name:`Transcript`,props:{session:{},sessionId:{},events:{},traces:{},decisions:{},status:{},demo:{type:Boolean},compact:{type:Boolean},sending:{type:Boolean}},emits:[`simulate`,`feedback`,`openList`,`openReplay`],setup(t,{expose:_,emit:v}){let C=t,T=v,E=c({glanced:D(`TA 扫了一眼`),silent:D(`TA 看了,没出声`),deferred:D(`TA 睡着了,醒来再看`)}),O=u(`10001`),k=u(``),A=u(!0),j=u(),M=u({}),ee=()=>B(s(),{month:`long`,day:`numeric`,weekday:`short`}),I=o(()=>{let e=C.events.map(e=>({type:`message`,key:`m`+e.seq,time:e.time,event:e})),t=C.events[0]?.time??2**53-1;for(let n of C.traces)E[n.status]&&n.time>=t&&e.push({type:`divider`,key:`t`+n.id,time:n.time,status:n.status,reasons:[n.reason].filter(Boolean),count:1});e.sort((e,t)=>e.time-t.time);let n=[],r=``;for(let t of e){let e=ee().format(t.time);e!==r&&(r=e,n.push({type:`day`,key:`d`+t.time,time:t.time,label:e}));let i=n[n.length-1];if(t.type===`divider`&&i?.type===`divider`&&i.status===t.status){i.count+=1,t.reasons[0]&&!i.reasons.includes(t.reasons[0])&&i.reasons.push(t.reasons[0]);continue}n.push(t)}return n});function R(e){let t=String(e.payload.text||``);return C.decisions.filter(n=>n.session_id===e.session_id&&n.reply&&n.reply.includes(t)&&Math.abs(n.time-e.time)<6e5).sort((t,n)=>Math.abs(t.time-e.time)-Math.abs(n.time-e.time))[0]}function te(e){return C.traces.filter(t=>t.status===`sent`&&t.time<=e.time+1e3).sort((e,t)=>t.time-e.time)[0]}async function U(e){if(M.value[e.seq]){let{[e.seq]:t,...n}=M.value;M.value=n;return}M.value={...M.value,[e.seq]:{loading:!0,detail:null}};let t=te(e),n=null;try{n=t?await Y(t.id):null}catch{n=null}M.value[e.seq]&&(M.value={...M.value,[e.seq]:{loading:!1,detail:n}})}async function W(){await y(),j.value?.focus()}function G(){k.value.trim()&&!C.sending&&(T(`simulate`,{userId:O.value,text:k.value,mentioned:A.value}),k.value=``)}return _({focusComposer:W}),(o,s)=>(x(),l(`section`,Pe,[h(`header`,Fe,[t.compact?(x(),l(`button`,{key:0,class:`icon-button`,"aria-label":L(a)(`全部会话`),title:L(a)(`全部会话`),onClick:s[0]||=e=>T(`openList`)},` ☰ `,8,Ie)):g(``,!0),t.session?(x(),l(`span`,{key:1,class:`badge`,style:n({"--hue":L(H)(t.session.id)})},e(t.session.kind===`private`?L(a)(`私`):L(a)(`群`)),5)):g(``,!0),h(`div`,Le,[h(`h2`,null,e(t.session?.name||L(a)(`选择一段对话`)),1),h(`span`,Re,e(t.status),1)]),h(`div`,ze,[h(`button`,{class:`small sim-button`,disabled:!t.sessionId,onClick:W},e(L(a)(`模拟消息`)),9,Be),h(`button`,{class:`small`,onClick:s[1]||=e=>T(`openReplay`)},e(L(a)(`回到那一刻`)),1)])]),h(`div`,Ve,[(x(!0),l(r,null,w(I.value,t=>(x(),l(r,{key:t.key},[t.type===`day`?(x(),l(`div`,He,[h(`span`,null,e(t.label),1)])):t.type===`divider`?(x(),l(`div`,{key:1,class:`divider`,"data-status":t.status},[h(`span`,null,e(L(E)[t.status])+e(t.count>1?` × ${t.count}`:``),1),t.reasons.length?(x(),l(`small`,We,e(t.reasons.slice(0,2).join(`;`)),1)):g(``,!0)],8,Ue)):(x(),l(`article`,{key:2,class:d([`message`,{bot:t.event.role===`assistant`,simulated:t.event.payload.simulated}])},[t.event.role===`assistant`?g(``,!0):(x(),l(`span`,{key:0,class:`avatar`,style:n({"--hue":L(H)(String(t.event.payload.userId||t.event.payload.name))}),"aria-hidden":`true`},e(L(K)(t.event.payload.name)),5)),h(`div`,Ge,[h(`div`,Ke,[h(`b`,null,e(t.event.payload.name),1),h(`time`,null,e(L(q)(t.event.time)),1),t.event.payload.simulated?(x(),l(`span`,qe,e(L(a)(`模拟`)),1)):g(``,!0)]),h(`p`,Je,e(t.event.payload.text),1),t.event.role===`assistant`?(x(),l(r,{key:0},[h(`button`,{class:`text-button why-toggle`,"aria-expanded":!!M.value[t.event.seq],onClick:e=>U(t.event)},e(M.value[t.event.seq]?L(a)(`收起`):L(a)(`为什么这么说`)),9,Ye),M.value[t.event.seq]?(x(),l(`div`,Xe,[M.value[t.event.seq].loading?(x(),l(`p`,Ze,e(L(a)(`正在翻当时的记录…`)),1)):(x(),l(r,{key:1},[M.value[t.event.seq].detail?.data?.decision?.appraisal?(x(),l(`p`,Qe,[h(`b`,null,e(L(a)(`TA 当时的理解:`)),1),S(e(M.value[t.event.seq].detail.data.decision.appraisal),1)])):g(``,!0),h(`p`,null,[h(`b`,null,e(L(a)(`理由:`)),1),S(e(M.value[t.event.seq].detail?.data?.reason||M.value[t.event.seq].detail?.data?.decision?.reason||R(t.event)?.reason||L(a)(`没有留下理由。`)),1)]),M.value[t.event.seq].detail?.data?.snapshot?.knowledge?.length?(x(),l(`p`,$e,[h(`b`,null,e(L(a)(`引用的资料:`)),1),S(e(M.value[t.event.seq].detail.data.snapshot.knowledge.map(e=>e.title).join(`、`)),1)])):g(``,!0),R(t.event)?(x(),l(`label`,et,[S(e(L(a)(`这句怎么样?`))+` `,1),F(V,{"model-value":R(t.event).feedback||``,"aria-label":L(a)(`这句怎么样?`),options:[{value:``,label:L(a)(`选择评价`)},...Object.entries(L(N).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>T(`feedback`,R(t.event).id,e)},null,8,[`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])):g(``,!0)],64))])):g(``,!0)],64)):g(``,!0)])],2))],64))),128)),t.events.length?g(``,!0):(x(),m(P,{key:0,title:t.sessionId?L(a)(`等待新的消息`):L(a)(`选择一段对话`),text:L(a)(`群聊与私聊消息会在这里实时出现;TA 扫一眼、没出声也会留下一条分隔线。`)},null,8,[`title`,`text`]))]),t.demo?(x(),l(`form`,{key:0,id:`simulate`,class:`composer`,onSubmit:p(G,[`prevent`])},[h(`div`,tt,[h(`span`,nt,e(L(a)(`模拟消息`)),1),h(`span`,rt,e(L(a)(`作为一条模拟消息走完整流程,写进模拟会话;不会发到 QQ,也不进入真实记忆。`)),1)]),h(`div`,it,[h(`label`,at,[h(`span`,ot,e(L(a)(`体验用户 ID`)),1),b(h(`input`,{"onUpdate:modelValue":s[2]||=e=>O.value=e,name:`userId`,pattern:`\\d{4,20}`,required:``,title:L(a)(`体验用户 ID`)},null,8,st),[[i,O.value]])]),b(h(`textarea`,{ref_key:`composer`,ref:j,"onUpdate:modelValue":s[3]||=e=>k.value=e,name:`text`,rows:`1`,placeholder:L(a)(`以这位群友的身份说一句…`),required:``,onKeydown:f(p(G,[`exact`,`prevent`]),[`enter`])},null,40,ct),[[i,k.value]]),h(`label`,lt,[b(h(`input`,{"onUpdate:modelValue":s[4]||=e=>A.value=e,type:`checkbox`},null,512),[[z,A.value]]),S(e(L(a)(`视为 @`)),1)]),h(`button`,{class:`primary`,disabled:t.sending||!t.sessionId},e(t.sending?L(a)(`TA 在想…`):L(a)(`发送模拟消息`)),9,ut)])],32)):(x(),l(`div`,dt,[h(`span`,ft,e(L(a)(`真实模式下不能模拟消息。想看看 TA 会怎么回,可以「和 TA 聊聊」——那是试聊,什么都不写入。`)),1),h(`button`,{class:`small`,onClick:s[5]||=e=>L(N).chatOpen=!0},e(L(a)(`和 TA 聊聊`)),1)]))]))}}),[[`__scopeId`,`data-v-849aeb0f`]]),mt={class:`panel`},ht={key:0,class:`panel-body`},gt={key:1,class:`panel-body scroll-pane`},_t={class:`here`},vt={class:`facts`},yt={class:`eyebrow`},bt={key:1,class:`muted`},xt={key:2,class:`faint`},St={class:`eyebrow`},Ct={class:`faint`},wt={class:`row actions`},Tt=[`data-toggle`],Et=[`data-archive`],Dt=[`open`],Ot=[`data-session`],kt={class:`faint`},At=[`value`],jt={class:`faint`},Mt={class:`check`},Nt={class:`advanced-policy`},Pt={class:`stack tight`},Ft={class:`check`},It={class:`check`},Lt={class:`check`},Rt={class:`save-bar`},zt={class:`primary`,type:`submit`},Bt={class:`faint`},Vt={class:`fold summary-list`},Ht={key:0,class:`muted`},Ut={class:`faint`},Wt={key:0},Gt={key:2,class:`panel-body feedback-pane`},Kt={class:`faint`},qt={id:`feedbackList`,class:`scroll-pane`},Jt={class:`faint`},Yt={class:`reply-excerpt`},Xt={key:0,class:`muted`},Zt={class:`pagination`},Qt=[`disabled`],$t=[`disabled`],$=6,en=A(M({__name:`SessionPanel`,props:E({session:{},group:{},summaries:{},decisions:{}},{settingsOpen:{type:Boolean,default:!1},settingsOpenModifiers:{},tab:{default:`here`},tabModifiers:{}}),emits:E([`toggle`,`archive`,`clear`,`save`,`feedback`],[`update:settingsOpen`,`update:tab`]),setup(t,{emit:n}){let s=t,f=T(t,`settingsOpen`),m=T(t,`tab`),v=n,y=c([D(`近期细摘要`),D(`中期摘要`),D(`较早摘要`),D(`远期摘要`)]),C=u(0),E=o(()=>s.decisions.filter(e=>e.session_id===s.session?.id&&e.reply)),O=o(()=>Math.max(1,Math.ceil(E.value.length/$))),k=u(!1),A=u({});_(()=>s.session?.policy,e=>{A.value={...e||{}}},{immediate:!0,deep:!0}),_(()=>s.session?.id,()=>{C.value=0,k.value=!1});function j(e){let t=e?.face;return t?[t.role,t.tone,t.content].filter(Boolean).join(`
1
+ import{Dt as e,E as t,Et as n,G as r,H as i,I as a,J as o,M as s,P as c,Q as l,St as u,Tt as d,U as f,W as p,X as m,Y as h,Z as g,_t as _,a as v,at as y,bt as b,dt as x,et as S,f as C,ft as w,gt as T,it as E,j as D,lt as O,m as k,n as A,o as j,rt as M,s as N,t as P,tt as F,ut as ee,v as I,wt as L,yt as R,z}from"./Empty-CAGOJsfD.js";import{g as te,y as B}from"./mind-CBTkFje2.js";import{c as V,f as H,h as U,m as W,n as G,o as ne,p as K,u as q,y as re}from"./index-L0bHax8C.js";import{t as ie}from"./media-BBzOAfNG.js";import{t as J}from"./Field-CRz-JmN1.js";async function ae(e,t){let n=new URLSearchParams({session:e});return t!==void 0&&n.set(`after`,String(t)),k(`/core/events?`+n.toString())}async function oe(e,t={}){let n=new URLSearchParams({session:e});return t.limit&&n.set(`limit`,String(t.limit)),t.compact&&n.set(`compact`,`1`),k(`/core/traces?`+n.toString())}async function Y(e){return k(`/core/traces/`+e)}async function se(e){return k(`/simulate`,`POST`,e)}async function ce(e,t){return k(`/decisions/`+e+`/feedback`,`POST`,{tag:t})}async function X(e){return k(`/core/replay`,`POST`,e)}async function le(e){return k(`/core/sessions`,`POST`,e)}async function ue(e,t){return k(`/core/sessions/`+encodeURIComponent(e),`PATCH`,{enabled:t})}async function de(e,t){return k(`/core/sessions/`+encodeURIComponent(e)+`/archive`,`PATCH`,{archived:t})}async function fe(e){return k(`/core/sessions/`+encodeURIComponent(e),`DELETE`,{})}async function pe(e,t){return k(`/core/sessions/`+encodeURIComponent(e),`PUT`,t)}async function me(e){return k(`/core/sessions/`+encodeURIComponent(e)+`/summaries`)}async function he(e){return k(`/core/sessions/`+encodeURIComponent(e)+`/context`,`DELETE`,{})}var ge={class:`session-list`},_e={class:`list-head`},Z=[`placeholder`,`aria-label`],ve={class:`rows scroll-pane`},Q=[`data-pick`,`aria-current`,`onClick`],ye={class:`who`},be=[`aria-label`],xe=[`title`],Se=[`checked`,`aria-label`,`onChange`],Ce={key:0,class:`muted empty-line`},we={key:1,class:`archive-list`},Te={class:`row`},Ee=[`data-restore-session`,`onClick`],De=[`data-delete-session`,`onClick`],Oe=A(M({__name:`SessionList`,props:{sessions:{},selected:{},unread:{}},emits:[`select`,`add`,`toggle`,`restore`,`remove`],setup(t,{emit:s}){let c=t,f=s,p=u(``),m=o(()=>c.sessions.filter(e=>!e.archived&&`${e.name} ${e.id}`.toLowerCase().includes(p.value.toLowerCase()))),_=o(()=>c.sessions.filter(e=>e.archived));return(o,s)=>(x(),l(`div`,ge,[h(`div`,_e,[h(`h2`,null,[S(e(L(a)(`会话`))+` `,1),h(`small`,null,e(m.value.length),1)]),h(`button`,{class:`small primary`,onClick:s[0]||=e=>f(`add`)},e(L(a)(`+ 添加会话`)),1)]),b(h(`input`,{"onUpdate:modelValue":s[1]||=e=>p.value=e,type:`search`,placeholder:L(a)(`搜索名称或号码`),"aria-label":L(a)(`搜索会话`)},null,8,Z),[[i,p.value]]),h(`div`,ve,[(x(!0),l(r,null,w(m.value,r=>(x(),l(`div`,{key:r.id,class:d([`session-row`,{selected:r.id===t.selected,paused:!r.enabled}])},[h(`button`,{class:`row-main`,"data-pick":r.id,"aria-current":r.id===t.selected?`true`:void 0,onClick:e=>f(`select`,r.id)},[h(`span`,{class:`badge`,style:n({"--hue":L(H)(r.id)})},e(r.kind===`private`?L(a)(`私`):L(a)(`群`)),5),h(`span`,ye,[h(`b`,null,e(L(U)(r)),1),h(`small`,null,e(r.enabled?L(a)(`参与中`):L(a)(`已暂停`)),1)]),t.unread[r.id]?(x(),l(`span`,{key:0,class:`unread`,"aria-label":L(a)(`{v} 条没细看`,{v:t.unread[r.id]})},e(t.unread[r.id]),9,be)):g(``,!0)],8,Q),h(`label`,{class:`mini-switch`,title:r.enabled?L(a)(`暂停参与`):L(a)(`开启参与`)},[h(`input`,{type:`checkbox`,checked:r.enabled,"aria-label":L(a)(`参与:{v}`,{v:L(U)(r)}),onChange:e=>f(`toggle`,r)},null,40,Se)],8,xe)],2))),128)),m.value.length?g(``,!0):(x(),l(`p`,Ce,e(L(a)(`暂无匹配的会话。`)),1)),_.value.length?(x(),l(`details`,we,[h(`summary`,null,e(L(a)(`已归档 · {length}`,{length:_.value.length})),1),(x(!0),l(r,null,w(_.value,t=>(x(),l(`article`,{key:t.id},[h(`b`,null,e(L(U)(t)),1),h(`div`,Te,[h(`button`,{class:`small`,"data-restore-session":t.id,onClick:e=>f(`restore`,t.id)},e(L(a)(`恢复`)),9,Ee),h(`button`,{class:`small danger`,"data-delete-session":t.id,onClick:e=>f(`remove`,t.id)},e(L(a)(`永久删除`)),9,De)])]))),128))])):g(``,!0)])]))}}),[[`__scopeId`,`data-v-9b8dcbac`]]),ke={class:`muted`},Ae={key:0,class:`notice`,role:`status`},je=[`pattern`,`inputmode`],Me=[`disabled`],Ne=M({__name:`AddSession`,props:{open:{type:Boolean},channel:{},ready:{type:Boolean}},emits:[`close`,`add`],setup(t,{emit:n}){let r=t,i=u(`group`),s=n,c=o(()=>r.channel===`qqbot`),d=o(()=>c.value?`[0-9A-Za-z_\\-]{8,64}`:`\\d{4,20}`);function f(e){let t=e.target;s(`add`,{...Object.fromEntries(new FormData(t)),channel:r.channel})}return(n,r)=>(x(),m(ne,{open:t.open,title:L(a)(`添加群聊 / 私聊`),eyebrow:L(a)(`NEW PLACE`),width:`440px`,onClose:r[1]||=e=>s(`close`)},{default:R(()=>[h(`form`,{id:`addSession`,class:`stack`,onSubmit:p(f,[`prevent`])},[h(`p`,ke,e(L(a)(`添加之后再决定要不要让 TA 参与;开不开口仍由 TA 自己决定。`)),1),c.value&&!t.ready?(x(),l(`p`,Ae,e(L(a)(`请先在「连接 QQ」里填写机器人的 AppID`)),1)):g(``,!0),F(J,{label:L(a)(`类型`)},{default:R(()=>[F(V,{modelValue:i.value,"onUpdate:modelValue":r[0]||=e=>i.value=e,name:`kind`,"aria-label":L(a)(`类型`),options:[{value:`group`,label:L(a)(`群聊`)},{value:`private`,label:L(a)(`私聊`)}]},null,8,[`modelValue`,`aria-label`,`options`])]),_:1},8,[`label`]),F(J,{label:c.value?L(a)(`群 openid / 用户 openid`):L(a)(`群号 / QQ 号`),hint:c.value?L(a)(`8–64 位字母、数字、下划线或短横线。官方平台的会话通常会在有人 @ 她或私聊她时自动出现,这里只用于提前登记。`):L(a)(`4–20 位数字`)},{default:R(()=>[h(`input`,{name:`id`,required:``,pattern:d.value,inputmode:c.value?`text`:`numeric`,autocomplete:`off`},null,8,je)]),_:1},8,[`label`,`hint`]),F(J,{label:L(a)(`显示名称`)},{default:R(()=>[...r[2]||=[h(`input`,{name:`name`,required:``,maxlength:`100`,autocomplete:`off`},null,-1)]]),_:1},8,[`label`]),h(`button`,{class:`primary`,disabled:c.value&&!t.ready},e(L(a)(`添加会话`)),9,Me)],32)]),_:1},8,[`open`,`title`,`eyebrow`]))}}),Pe={class:`transcript`},Fe={class:`t-head`},Ie=[`aria-label`,`title`],Le={class:`t-title`},Re={id:`liveStatus`,class:`faint`},ze={class:`t-tools`},Be=[`disabled`],Ve={id:`liveMessages`,class:`chat-messages scroll-pane`},He={key:0,class:`day-label`},Ue=[`data-status`],We={key:0},Ge={class:`body`},Ke={class:`message-meta`},qe={key:0,class:`chip`,"data-tone":`warn`},Je={class:`text`},Ye=[`aria-expanded`,`onClick`],Xe={key:0,class:`why`},Ze={key:0,class:`muted`},Qe={key:0},$e={key:1},et={key:2,class:`fb`},tt={class:`composer-note`},nt={class:`chip`,"data-tone":`warn`},rt={class:`faint`},it={class:`composer-row`},at={class:`uid`},ot={class:`sr-only`},st=[`title`],ct=[`placeholder`,`onKeydown`],lt={class:`check mention`},ut=[`disabled`],dt={key:1,class:`composer off`},ft={class:`faint`},pt=A(M({__name:`Transcript`,props:{session:{},sessionId:{},events:{},traces:{},decisions:{},status:{},demo:{type:Boolean},compact:{type:Boolean},sending:{type:Boolean}},emits:[`simulate`,`feedback`,`openList`,`openReplay`],setup(t,{expose:_,emit:v}){let C=t,T=v,E=c({glanced:D(`TA 扫了一眼`),silent:D(`TA 看了,没出声`),deferred:D(`TA 睡着了,醒来再看`)}),O=u(`10001`),k=u(``),A=u(!0),j=u(),M=u({}),ee=()=>B(s(),{month:`long`,day:`numeric`,weekday:`short`}),I=o(()=>{let e=C.events.map(e=>({type:`message`,key:`m`+e.seq,time:e.time,event:e})),t=C.events[0]?.time??2**53-1;for(let n of C.traces)E[n.status]&&n.time>=t&&e.push({type:`divider`,key:`t`+n.id,time:n.time,status:n.status,reasons:[n.reason].filter(Boolean),count:1});e.sort((e,t)=>e.time-t.time);let n=[],r=``;for(let t of e){let e=ee().format(t.time);e!==r&&(r=e,n.push({type:`day`,key:`d`+t.time,time:t.time,label:e}));let i=n[n.length-1];if(t.type===`divider`&&i?.type===`divider`&&i.status===t.status){i.count+=1,t.reasons[0]&&!i.reasons.includes(t.reasons[0])&&i.reasons.push(t.reasons[0]);continue}n.push(t)}return n});function R(e){let t=String(e.payload.text||``);return C.decisions.filter(n=>n.session_id===e.session_id&&n.reply&&n.reply.includes(t)&&Math.abs(n.time-e.time)<6e5).sort((t,n)=>Math.abs(t.time-e.time)-Math.abs(n.time-e.time))[0]}function te(e){return C.traces.filter(t=>t.status===`sent`&&t.time<=e.time+1e3).sort((e,t)=>t.time-e.time)[0]}async function U(e){if(M.value[e.seq]){let{[e.seq]:t,...n}=M.value;M.value=n;return}M.value={...M.value,[e.seq]:{loading:!0,detail:null}};let t=te(e),n=null;try{n=t?await Y(t.id):null}catch{n=null}M.value[e.seq]&&(M.value={...M.value,[e.seq]:{loading:!1,detail:n}})}async function W(){await y(),j.value?.focus()}function G(){k.value.trim()&&!C.sending&&(T(`simulate`,{userId:O.value,text:k.value,mentioned:A.value}),k.value=``)}return _({focusComposer:W}),(o,s)=>(x(),l(`section`,Pe,[h(`header`,Fe,[t.compact?(x(),l(`button`,{key:0,class:`icon-button`,"aria-label":L(a)(`全部会话`),title:L(a)(`全部会话`),onClick:s[0]||=e=>T(`openList`)},` ☰ `,8,Ie)):g(``,!0),t.session?(x(),l(`span`,{key:1,class:`badge`,style:n({"--hue":L(H)(t.session.id)})},e(t.session.kind===`private`?L(a)(`私`):L(a)(`群`)),5)):g(``,!0),h(`div`,Le,[h(`h2`,null,e(t.session?.name||L(a)(`选择一段对话`)),1),h(`span`,Re,e(t.status),1)]),h(`div`,ze,[h(`button`,{class:`small sim-button`,disabled:!t.sessionId,onClick:W},e(L(a)(`模拟消息`)),9,Be),h(`button`,{class:`small`,onClick:s[1]||=e=>T(`openReplay`)},e(L(a)(`回到那一刻`)),1)])]),h(`div`,Ve,[(x(!0),l(r,null,w(I.value,t=>(x(),l(r,{key:t.key},[t.type===`day`?(x(),l(`div`,He,[h(`span`,null,e(t.label),1)])):t.type===`divider`?(x(),l(`div`,{key:1,class:`divider`,"data-status":t.status},[h(`span`,null,e(L(E)[t.status])+e(t.count>1?` × ${t.count}`:``),1),t.reasons.length?(x(),l(`small`,We,e(t.reasons.slice(0,2).join(`;`)),1)):g(``,!0)],8,Ue)):(x(),l(`article`,{key:2,class:d([`message`,{bot:t.event.role===`assistant`,simulated:t.event.payload.simulated}])},[t.event.role===`assistant`?g(``,!0):(x(),l(`span`,{key:0,class:`avatar`,style:n({"--hue":L(H)(String(t.event.payload.userId||t.event.payload.name))}),"aria-hidden":`true`},e(L(K)(t.event.payload.name)),5)),h(`div`,Ge,[h(`div`,Ke,[h(`b`,null,e(t.event.payload.name),1),h(`time`,null,e(L(q)(t.event.time)),1),t.event.payload.simulated?(x(),l(`span`,qe,e(L(a)(`模拟`)),1)):g(``,!0)]),h(`p`,Je,e(t.event.payload.text),1),t.event.role===`assistant`?(x(),l(r,{key:0},[h(`button`,{class:`text-button why-toggle`,"aria-expanded":!!M.value[t.event.seq],onClick:e=>U(t.event)},e(M.value[t.event.seq]?L(a)(`收起`):L(a)(`为什么这么说`)),9,Ye),M.value[t.event.seq]?(x(),l(`div`,Xe,[M.value[t.event.seq].loading?(x(),l(`p`,Ze,e(L(a)(`正在翻当时的记录…`)),1)):(x(),l(r,{key:1},[M.value[t.event.seq].detail?.data?.decision?.appraisal?(x(),l(`p`,Qe,[h(`b`,null,e(L(a)(`TA 当时的理解:`)),1),S(e(M.value[t.event.seq].detail.data.decision.appraisal),1)])):g(``,!0),h(`p`,null,[h(`b`,null,e(L(a)(`理由:`)),1),S(e(M.value[t.event.seq].detail?.data?.reason||M.value[t.event.seq].detail?.data?.decision?.reason||R(t.event)?.reason||L(a)(`没有留下理由。`)),1)]),M.value[t.event.seq].detail?.data?.snapshot?.knowledge?.length?(x(),l(`p`,$e,[h(`b`,null,e(L(a)(`引用的资料:`)),1),S(e(M.value[t.event.seq].detail.data.snapshot.knowledge.map(e=>e.title).join(`、`)),1)])):g(``,!0),R(t.event)?(x(),l(`label`,et,[S(e(L(a)(`这句怎么样?`))+` `,1),F(V,{"model-value":R(t.event).feedback||``,"aria-label":L(a)(`这句怎么样?`),options:[{value:``,label:L(a)(`选择评价`)},...Object.entries(L(N).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>T(`feedback`,R(t.event).id,e)},null,8,[`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])):g(``,!0)],64))])):g(``,!0)],64)):g(``,!0)])],2))],64))),128)),t.events.length?g(``,!0):(x(),m(P,{key:0,title:t.sessionId?L(a)(`等待新的消息`):L(a)(`选择一段对话`),text:L(a)(`群聊与私聊消息会在这里实时出现;TA 扫一眼、没出声也会留下一条分隔线。`)},null,8,[`title`,`text`]))]),t.demo?(x(),l(`form`,{key:0,id:`simulate`,class:`composer`,onSubmit:p(G,[`prevent`])},[h(`div`,tt,[h(`span`,nt,e(L(a)(`模拟消息`)),1),h(`span`,rt,e(L(a)(`作为一条模拟消息走完整流程,写进模拟会话;不会发到 QQ,也不进入真实记忆。`)),1)]),h(`div`,it,[h(`label`,at,[h(`span`,ot,e(L(a)(`体验用户 ID`)),1),b(h(`input`,{"onUpdate:modelValue":s[2]||=e=>O.value=e,name:`userId`,pattern:`\\d{4,20}`,required:``,title:L(a)(`体验用户 ID`)},null,8,st),[[i,O.value]])]),b(h(`textarea`,{ref_key:`composer`,ref:j,"onUpdate:modelValue":s[3]||=e=>k.value=e,name:`text`,rows:`1`,placeholder:L(a)(`以这位群友的身份说一句…`),required:``,onKeydown:f(p(G,[`exact`,`prevent`]),[`enter`])},null,40,ct),[[i,k.value]]),h(`label`,lt,[b(h(`input`,{"onUpdate:modelValue":s[4]||=e=>A.value=e,type:`checkbox`},null,512),[[z,A.value]]),S(e(L(a)(`视为 @`)),1)]),h(`button`,{class:`primary`,disabled:t.sending||!t.sessionId},e(t.sending?L(a)(`TA 在想…`):L(a)(`发送模拟消息`)),9,ut)])],32)):(x(),l(`div`,dt,[h(`span`,ft,e(L(a)(`真实模式下不能模拟消息。想看看 TA 会怎么回,可以「和 TA 聊聊」——那是试聊,什么都不写入。`)),1),h(`button`,{class:`small`,onClick:s[5]||=e=>L(N).chatOpen=!0},e(L(a)(`和 TA 聊聊`)),1)]))]))}}),[[`__scopeId`,`data-v-849aeb0f`]]),mt={class:`panel`},ht={key:0,class:`panel-body`},gt={key:1,class:`panel-body scroll-pane`},_t={class:`here`},vt={class:`facts`},yt={class:`eyebrow`},bt={key:1,class:`muted`},xt={key:2,class:`faint`},St={class:`eyebrow`},Ct={class:`faint`},wt={class:`row actions`},Tt=[`data-toggle`],Et=[`data-archive`],Dt=[`open`],Ot=[`data-session`],kt={class:`faint`},At=[`value`],jt={class:`faint`},Mt={class:`check`},Nt={class:`advanced-policy`},Pt={class:`stack tight`},Ft={class:`check`},It={class:`check`},Lt={class:`check`},Rt={class:`save-bar`},zt={class:`primary`,type:`submit`},Bt={class:`faint`},Vt={class:`fold summary-list`},Ht={key:0,class:`muted`},Ut={class:`faint`},Wt={key:0},Gt={key:2,class:`panel-body feedback-pane`},Kt={class:`faint`},qt={id:`feedbackList`,class:`scroll-pane`},Jt={class:`faint`},Yt={class:`reply-excerpt`},Xt={key:0,class:`muted`},Zt={class:`pagination`},Qt=[`disabled`],$t=[`disabled`],$=6,en=A(M({__name:`SessionPanel`,props:E({session:{},group:{},summaries:{},decisions:{}},{settingsOpen:{type:Boolean,default:!1},settingsOpenModifiers:{},tab:{default:`here`},tabModifiers:{}}),emits:E([`toggle`,`archive`,`clear`,`save`,`feedback`],[`update:settingsOpen`,`update:tab`]),setup(t,{emit:n}){let s=t,f=T(t,`settingsOpen`),m=T(t,`tab`),v=n,y=c([D(`近期细摘要`),D(`中期摘要`),D(`较早摘要`),D(`远期摘要`)]),C=u(0),E=o(()=>s.decisions.filter(e=>e.session_id===s.session?.id&&e.reply)),O=o(()=>Math.max(1,Math.ceil(E.value.length/$))),k=u(!1),A=u({});_(()=>s.session?.policy,e=>{A.value={...e||{}}},{immediate:!0,deep:!0}),_(()=>s.session?.id,()=>{C.value=0,k.value=!1});function j(e){let t=e?.face;return t?[t.role,t.tone,t.content].filter(Boolean).join(`
2
2
 
3
3
  `):``}function M(e){let t=e.target,n=Object.fromEntries(new FormData(t));for(let e of[`aggregateMs`,`maxWaitMs`,`contextMessages`,`maxReply`])n[e]=Number(n[e]);for(let e of[`memory`,`selectiveVision`,`deepCheck`,`compaction`])n[e]=t.elements.namedItem(e).checked;v(`save`,n)}return(n,o)=>(x(),l(`section`,mt,[F(G,{modelValue:m.value,"onUpdate:modelValue":o[0]||=e=>m.value=e,label:L(a)(`这个会话`),items:[{key:`here`,label:L(a)(`这个会话`)},{key:`feedback`,label:L(a)(`反馈`)}]},null,8,[`modelValue`,`label`,`items`]),t.session?m.value===`here`?(x(),l(`div`,gt,[h(`div`,_t,[h(`div`,vt,[h(`div`,null,[h(`span`,yt,e(L(a)(`TA 在这里的样子`)),1),j(t.group)?(x(),l(r,{key:0},[h(`p`,{class:d([`face-body`,k.value?`open`:`clamp`])},e(j(t.group)),3),j(t.group).length>96?(x(),l(`button`,{key:0,type:`button`,class:`text-button`,onClick:o[1]||=e=>k.value=!k.value},e(k.value?L(a)(`收起`):L(a)(`展开完整内容`)),1)):g(``,!0)],64)):(x(),l(`p`,bt,e(L(a)(`还没有形成在这里的样子。`)),1)),t.group?.face?.aspiration?(x(),l(`small`,xt,e(L(a)(`想成为:{aspiration}`,{aspiration:t.group.face.aspiration})),1)):g(``,!0)]),h(`div`,null,[h(`span`,St,e(t.session.kind===`private`?L(a)(`这段私聊给 TA 的感觉`):L(a)(`这个群给 TA 的感觉`)),1),h(`p`,null,e(t.group?.bond?.feel||L(a)(`刚来这里不久`)),1)])]),h(`p`,Ct,e(L(a)(`在这里开不开口由 TA 自己决定:没有参与概率和冷却。TA 对这里每个人的感觉在「人际」里看。`)),1),h(`div`,wt,[h(`button`,{id:`toggleSession`,"data-toggle":t.session.id,class:d({primary:!t.session.enabled}),onClick:o[2]||=e=>v(`toggle`,t.session)},e(t.session.enabled?L(a)(`暂停参与`):L(a)(`开启参与`)),11,Tt),h(`button`,{"data-archive":t.session.id,onClick:o[3]||=e=>v(`archive`,t.session.id)},e(L(a)(`归档`)),9,Et),h(`button`,{id:`clearLiveContext`,onClick:o[4]||=e=>v(`clear`)},e(L(a)(`清空上下文`)),1)]),h(`details`,{class:`fold session-settings`,open:f.value,onToggle:o[19]||=e=>f.value=e.target.open},[h(`summary`,null,e(L(a)(`会话设置`)),1),(x(),l(`form`,{key:t.session.id,"data-session":t.session.id,class:`stack tight`,onSubmit:p(M,[`prevent`]),onInput:o[17]||=e=>L(N).dirty=!0,onChange:o[18]||=e=>L(N).dirty=!0},[h(`p`,kt,e(L(a)(`所有会话和独处都用模型库里的默认模型;其余已启用的模型按列表顺序做备用。她在这个聊天窗里用什么样子,由她自己从经历里决定。`)),1),h(`label`,null,[S(e(L(a)(`会话名称`))+` `,1),h(`input`,{name:`name`,maxlength:`100`,autocomplete:`off`,value:t.session.name},null,8,At),h(`small`,jt,e(L(a)(`官方机器人平台不提供群名称,可以在这里自己起一个`)),1)]),h(`label`,Mt,[b(h(`input`,{name:`selectiveVision`,type:`checkbox`,"onUpdate:modelValue":o[5]||=e=>A.value.selectiveVision=e,onInput:o[6]||=p(()=>{},[`stop`])},null,544),[[z,A.value.selectiveVision]]),S(e(L(a)(`节能看图:只在被 @ 或明确要求时,看这一条和附近的图`)),1)]),h(`details`,Nt,[h(`summary`,null,e(L(a)(`高级策略`)),1),h(`div`,Pt,[h(`label`,null,[S(e(L(a)(`聚合窗口 ms`)),1),b(h(`input`,{name:`aggregateMs`,type:`number`,"onUpdate:modelValue":o[7]||=e=>A.value.aggregateMs=e},null,512),[[i,A.value.aggregateMs,void 0,{number:!0}]])]),h(`label`,null,[S(e(L(a)(`最长等待 ms`)),1),b(h(`input`,{name:`maxWaitMs`,type:`number`,"onUpdate:modelValue":o[8]||=e=>A.value.maxWaitMs=e},null,512),[[i,A.value.maxWaitMs,void 0,{number:!0}]])]),h(`label`,null,[S(e(L(a)(`近期原文条数(10–500)`)),1),b(h(`input`,{name:`contextMessages`,type:`number`,min:`10`,max:`500`,"onUpdate:modelValue":o[9]||=e=>A.value.contextMessages=e},null,512),[[i,A.value.contextMessages,void 0,{number:!0}]])]),h(`label`,null,[S(e(L(a)(`一轮总字数`)),1),b(h(`input`,{name:`maxReply`,type:`number`,"onUpdate:modelValue":o[10]||=e=>A.value.maxReply=e},null,512),[[i,A.value.maxReply,void 0,{number:!0}]])]),h(`label`,Ft,[b(h(`input`,{name:`compaction`,type:`checkbox`,"onUpdate:modelValue":o[11]||=e=>A.value.compaction=e,onInput:o[12]||=p(()=>{},[`stop`])},null,544),[[z,A.value.compaction]]),S(e(L(a)(`上下文压缩:更早的聊天定期整理成分层摘要,越近越详细`)),1)]),h(`label`,It,[b(h(`input`,{name:`memory`,type:`checkbox`,"onUpdate:modelValue":o[13]||=e=>A.value.memory=e,onInput:o[14]||=p(()=>{},[`stop`])},null,544),[[z,A.value.memory]]),S(e(L(a)(`长期记忆`)),1)]),h(`label`,Lt,[b(h(`input`,{name:`deepCheck`,type:`checkbox`,"onUpdate:modelValue":o[15]||=e=>A.value.deepCheck=e,onInput:o[16]||=p(()=>{},[`stop`])},null,544),[[z,A.value.deepCheck]]),S(e(L(a)(`倾诉、纠正和危机时额外复审一次回复`)),1)])])]),h(`div`,Rt,[h(`button`,zt,e(L(a)(`保存会话设置`)),1),h(`small`,Bt,e(L(N).dirty?L(a)(`有未保存的修改`):L(a)(`保存后下一轮生效`)),1)])],40,Ot))],40,Dt),h(`details`,Vt,[h(`summary`,null,e(L(a)(`语境摘要 · {length} 段`,{length:t.summaries.length})),1),t.summaries.length?g(``,!0):(x(),l(`p`,Ht,e(L(a)(`还没有摘要。聊天超过近期原文条数后,更早的内容会自动整理到这里。`)),1)),(x(!0),l(r,null,w(t.summaries,t=>(x(),l(`article`,{key:t.id,class:`summary`},[h(`header`,null,[h(`b`,null,e(L(y)[t.level]||L(a)(`摘要`)),1),h(`small`,Ut,e(L(a)(`{period} · {messages} 条消息`,{period:t.period,messages:t.messages})),1)]),h(`p`,null,e(t.summary),1),t.keyPoints?.length?(x(),l(`ul`,Wt,[(x(!0),l(r,null,w(t.keyPoints,(t,n)=>(x(),l(`li`,{key:n},e(t.open?L(a)(`未完:`):``)+e(t.text),1))),128))])):g(``,!0)]))),128))])])])):(x(),l(`div`,Gt,[h(`p`,Kt,e(L(a)(`针对具体回复评价口吻;这是 TA 在这个会话里听到的话,不会改写天性。`)),1),h(`div`,qt,[(x(!0),l(r,null,w(E.value.slice(C.value*$,C.value*$+$),t=>(x(),l(`article`,{key:t.id,class:`feedback-item`},[h(`time`,Jt,e(L(q)(t.time)),1),h(`p`,Yt,e(t.reply),1),h(`label`,null,[S(e(L(a)(`这句回复怎么样?`))+` `,1),F(V,{"data-feedback":t.id,"model-value":t.feedback||``,"aria-label":L(a)(`这句回复怎么样?`),options:[{value:``,label:L(a)(`选择评价`)},...Object.entries(L(N).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>v(`feedback`,t.id,e)},null,8,[`data-feedback`,`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])]))),128)),E.value.length?g(``,!0):(x(),l(`p`,Xt,e(L(a)(`还没有可以评价的回复。`)),1))]),h(`div`,Zt,[h(`button`,{class:`small`,disabled:C.value===0,onClick:o[20]||=e=>C.value--},`←`,8,Qt),h(`span`,null,e(C.value+1)+` / `+e(O.value),1),h(`button`,{class:`small`,disabled:C.value+1>=O.value,onClick:o[21]||=e=>C.value++},` → `,8,$t)])])):(x(),l(`div`,ht,[F(P,{title:L(a)(`还没有选中会话`),text:L(a)(`从左边选一个群聊或私聊。`)},null,8,[`title`,`text`])]))]))}}),[[`__scopeId`,`data-v-96fbad3d`]]),tn={class:`replay`},nn={class:`col source`},rn={class:`col-head`},an={id:`events`,class:`scroll-pane`},on={key:0},sn={key:1,class:`muted`},cn=[`disabled`],ln={class:`faint`},un={class:`col`},dn={class:`col-head`},fn={id:`traces`,class:`scroll-pane`},pn=[`data-trace`,`onClick`],mn=[`data-tone`],hn={key:0,class:`muted`},gn={class:`col`},_n={class:`col-head`},vn={class:`scroll-pane inspect`},yn={key:0,class:`metrics`},bn={id:`traceTokens`},xn={key:1,class:`notice`},Sn={id:`traceDetail`},Cn=A(M({__name:`ReplaySheet`,props:{open:{type:Boolean},sessionId:{},events:{}},emits:[`close`,`load`],setup(t,{emit:n}){let o=t,s=n,f=c({sent:D(`开口了`),silent:D(`没出声`),glanced:D(`扫了一眼`),deferred:D(`睡着了`),running:D(`处理中`),error:D(`失败`),complete:D(`完成`),stale:D(`旧稿作废`),cancelled:D(`已取消`),interrupted:D(`已中断`)}),v=c({live:D(`真实聊天`),demo:D(`模拟预览`),replay:D(`测试回放`),memory:D(`记忆整理`),summary:D(`语境压缩`)}),y=u([]),C=u(null),T=u(``),E=u(``),O=u(!1),k=u(``);async function A(){o.sessionId&&(y.value=await oe(o.sessionId))}function j(){let e=o.events.map(e=>Number(e.seq));e.length&&(T.value||=String(Math.min(...e)),E.value||=String(Math.max(...e)))}async function M(){if(!O.value){O.value=!0;try{I(a(`回放正在运行`));let e=await X({session:o.sessionId,from:Number(T.value),to:Number(E.value)});k.value=JSON.stringify(e,null,2),I(a(`回放结束`)),await A()}catch(e){I(e.message,!0)}finally{O.value=!1}}}async function N(e){C.value=await Y(e),k.value=JSON.stringify(C.value,null,2)}function P(e){return e?a(`输入 {v}(缓存读 {v2} / 写 {v3})· 输出 {v4}`,{v:W(e.input),v2:W(e.cachedRead),v3:W(e.cacheWrite),v4:W(e.output)}):`—`}return _(()=>[o.open,o.sessionId],()=>{o.open&&(T.value=``,E.value=``,C.value=null,j(),A())},{immediate:!0}),_(()=>o.events.length,j),(n,o)=>(x(),m(ne,{open:t.open,title:L(a)(`回到那一刻`),eyebrow:`REPLAY`,width:`1180px`,onClose:o[3]||=e=>s(`close`)},{default:R(()=>[h(`div`,tn,[h(`section`,nn,[h(`div`,rn,[h(`h3`,null,e(L(a)(`历史消息`)),1),h(`button`,{id:`loadMessages`,class:`small`,onClick:o[0]||=e=>{s(`load`),A()}},e(L(a)(`↻ 加载`)),1)]),h(`div`,an,[t.events.length?(x(),l(`table`,on,[h(`thead`,null,[h(`tr`,null,[h(`th`,null,e(L(a)(`序号`)),1),h(`th`,null,e(L(a)(`发送者`)),1),h(`th`,null,e(L(a)(`消息`)),1)])]),h(`tbody`,null,[(x(!0),l(r,null,w([...t.events].reverse(),t=>(x(),l(`tr`,{key:t.seq},[h(`td`,null,e(t.seq),1),h(`td`,null,e(t.payload.name),1),h(`td`,null,e(t.payload.text),1)]))),128))])])):(x(),l(`p`,sn,e(L(a)(`当前会话暂无历史消息。`)),1))]),h(`form`,{class:`range`,onSubmit:p(M,[`prevent`])},[h(`label`,null,[S(e(L(a)(`起始序号`)),1),b(h(`input`,{"onUpdate:modelValue":o[1]||=e=>T.value=e,name:`from`,type:`number`,required:``},null,512),[[i,T.value]])]),h(`label`,null,[S(e(L(a)(`截止序号`)),1),b(h(`input`,{"onUpdate:modelValue":o[2]||=e=>E.value=e,name:`to`,type:`number`,required:``},null,512),[[i,E.value]])]),h(`button`,{id:`replay`,class:`primary`,disabled:O.value||!t.events.length},e(O.value?L(a)(`正在回放…`):L(a)(`回放这段对话`)),9,cn),h(`p`,ln,e(L(a)(`隔离回放:只测试回复,不发送 QQ,不修改真实记忆和 TA 的心智。`)),1)],32)]),h(`section`,un,[h(`div`,dn,[h(`h3`,null,e(L(a)(`处理记录`)),1)]),h(`div`,fn,[(x(!0),l(r,null,w(y.value,t=>(x(),l(`button`,{key:t.id,"data-trace":t.id,class:d([`trace-row`,{selected:C.value?.id===t.id}]),onClick:e=>N(t.id)},[h(`span`,null,[h(`time`,null,e(L(q)(t.time)),1),h(`small`,{class:`chip`,"data-tone":t.status===`error`?`danger`:`quiet`},e(L(f)[t.status]||t.status),9,mn)]),h(`p`,null,e(t.reason||t.error||L(a)(`查看处理详情`)),1)],10,pn))),128)),y.value.length?g(``,!0):(x(),l(`p`,hn,e(L(a)(`有新的消息处理记录后会显示在这里。`)),1))])]),h(`section`,gn,[h(`div`,_n,[h(`h3`,null,e(L(a)(`运行详情`)),1)]),h(`div`,vn,[C.value?(x(),l(`div`,yn,[h(`span`,null,[S(e(L(a)(`模式`)),1),h(`b`,null,e(L(v)[C.value.mode]||C.value.mode),1)]),h(`span`,null,[S(e(L(a)(`耗时`)),1),h(`b`,null,e(C.value.data?.elapsed??`—`)+` ms`,1)]),h(`span`,null,[S(e(L(a)(`模型调用`)),1),h(`b`,null,e(L(a)(`{v} 次`,{v:C.value.data?.calls?.length??0})),1)]),h(`span`,bn,[o[4]||=S(`Token`,-1),h(`b`,null,e(P(C.value.data?.tokens)),1)])])):g(``,!0),C.value?(x(),l(`p`,xn,e(C.value.reason||C.value.data?.decision?.reason||L(a)(`展开下方查看上下文、模型输入与原始输出。`)),1)):g(``,!0),h(`pre`,Sn,e(k.value||L(a)(`选择一条处理记录,查看最终上下文、Prompt、原始输出、Token 与耗时。`)),1)])])])]),_:1},8,[`open`,`title`]))}}),[[`__scopeId`,`data-v-839b3181`]]),wn={key:0,class:`card col list-col`},Tn={class:`card col talk-col`},En={class:`card col panel-col`},Dn={class:`sheet-list`},On=A(M({__name:`ChatsPage`,setup(e){let n=ie(`(max-width: 1100px)`),r=o(()=>N.core?.sessions||[]),i=o(()=>r.value.filter(e=>!e.archived)),c=sessionStorage.activeSession,f=u(i.value.some(e=>e.id===c)?c:i.value[0]?.id||``),p=o(()=>i.value.find(e=>e.id===f.value)||null),b=u([]),S=u([]),w=u([]),T=u([]),E=u({}),D=u(``),k=u(!1),A=u(!1),M=u(!1),P=u(N.sub===`settings`),z=u(N.sub===`replay`),B=u(`here`),V=o(()=>N.health?.decisions||[]),H=o(()=>T.value.find(e=>e.session===f.value)||null),U=!1,W=!1,G=!1,K=``,q=0,J=null,Y=0;async function X(e=!1){if(!f.value)return;if(U){W=!0,G||=e;return}let t=f.value,n=e||K!==t;U=!0;try{let[e,r]=await Promise.all([ae(t,n?void 0:q),oe(t,{limit:30,compact:!0})]);if(f.value!==t)return;let i=document.getElementById(`liveMessages`),o=K!==t||!i||i.scrollHeight-i.scrollTop-i.clientHeight<80;n?(b.value=e.reverse(),q=0):e.length&&(b.value=[...b.value,...e]);for(let t of e)q=Math.max(q,Number(t.seq)||0);!n&&e.length===100&&(W=!0),S.value=r,K=t,await y();let c=document.getElementById(`liveMessages`);c&&o&&(c.scrollTop=c.scrollHeight),D.value=a(`实时同步 · `)+new Date().toLocaleTimeString(s())}catch(e){D.value=a(`同步失败:`)+e.message}finally{if(U=!1,f.value!==t)W=!1,X();else if(W){let e=G;W=!1,G=!1,X(e)}}}async function ge(){try{let[e,t]=await Promise.all([te.bonds(),te.overview()]);T.value=e.groups,E.value=Object.fromEntries(t.attention.map(e=>[e.session,e.unread]))}catch{}}_(f,e=>{if(J?.abort(),e&&(sessionStorage.activeSession=e),b.value=[],S.value=[],w.value=[],K=``,q=0,W=!1,G=!1,M.value=!1,X(),!e)return;me(e).then(t=>{f.value===e&&(w.value=[...t].reverse())}).catch(()=>{});let t=new AbortController;J=t;let n=``;re(e=>{let t=Number(e?.messages);Number.isSafeInteger(t)&&t<q&&(K=``,q=0,b.value=[]);let r=JSON.stringify(e),i=n!==``&&r!==n;n=r,i&&X()},{session:e,signal:t.signal,reconnectMs:1e3})},{immediate:!0}),_(i,e=>{e.some(e=>e.id===f.value)||(f.value=e[0]?.id||``)}),_(B,async e=>{if(e===`feedback`)try{N.health=await C()}catch{}}),_(z,e=>j(e?`replay`:P.value?`settings`:``)),_(()=>N.sub,e=>{e===`replay`&&(z.value=!0),e===`settings`&&(P.value=!0)}),_(()=>N.pulse,ge),O(()=>{ge(),Y=window.setInterval(()=>X(!0),15e3)}),ee(()=>{clearInterval(Y),J?.abort()});async function _e(e){try{let t=await le(e);await v(),f.value=t?.sessionId||`${e.kind}:${e.id}`,P.value=!0,B.value=`here`,A.value=!1,I(a(`会话已添加`))}catch(e){I(e.message,!0)}}async function Z(e){await ue(e.id,!e.enabled),await v()}async function ve(e){await de(e,!0),await v()}async function Q(e){await de(e,!1),await v()}async function ye(e){await t(a(`永久删除这个会话?它的消息和设置都会消失。`),{title:a(`永久删除`),confirmText:a(`删除`),danger:!0})&&(await fe(e),await v())}async function be(e){if(p.value)try{await pe(p.value.id,{...e,name:String(e.name||``).trim()||p.value.name}),N.dirty=!1,I(a(`会话配置已保存`)),await v()}catch(e){I(e.message,!0)}}async function xe(){f.value&&await t(a(`确定清空这个会话的消息上下文吗?长期记忆不会删除。`),{title:a(`清空上下文`),confirmText:a(`清空`),danger:!0})&&(await he(f.value),b.value=[],S.value=[],K=``,q=0,await X(!0))}async function Se(e){if(!k.value&&f.value){k.value=!0;try{let t=await se({sessionId:f.value,...e});I(t.reason||a(`已发送`)),N.health=await C(),await X()}catch(e){I(e.message,!0)}finally{k.value=!1}}}async function Ce(e,t){try{await ce(e,t),I(a(`反馈已保存`)),N.health=await C()}catch(e){I(e.message,!0)}}return(e,t)=>(x(),l(`div`,{class:d([`page chats`,{compact:L(n)}])},[L(n)?g(``,!0):(x(),l(`aside`,wn,[F(Oe,{sessions:r.value,selected:f.value,unread:E.value,onSelect:t[0]||=e=>f.value=e,onAdd:t[1]||=e=>A.value=!0,onToggle:Z,onRestore:Q,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])),h(`div`,Tn,[F(pt,{"session-id":f.value,session:p.value,events:b.value,traces:S.value,decisions:V.value,status:D.value||L(a)(`请选择会话`),demo:!!L(N).health.settings.demo,compact:L(n),sending:k.value,onSimulate:Se,onFeedback:Ce,onOpenList:t[2]||=e=>M.value=!0,onOpenReplay:t[3]||=e=>z.value=!0},null,8,[`session-id`,`session`,`events`,`traces`,`decisions`,`status`,`demo`,`compact`,`sending`])]),h(`aside`,En,[F(en,{"settings-open":P.value,"onUpdate:settingsOpen":t[4]||=e=>P.value=e,tab:B.value,"onUpdate:tab":t[5]||=e=>B.value=e,session:p.value,group:H.value,summaries:w.value,decisions:V.value,onToggle:Z,onArchive:ve,onClear:xe,onSave:be,onFeedback:Ce},null,8,[`settings-open`,`tab`,`session`,`group`,`summaries`,`decisions`])]),L(n)?(x(),m(ne,{key:1,open:M.value,title:L(a)(`全部会话`),eyebrow:`CHATS`,width:`400px`,onClose:t[8]||=e=>M.value=!1},{default:R(()=>[h(`div`,Dn,[F(Oe,{sessions:r.value,selected:f.value,unread:E.value,onSelect:t[6]||=e=>f.value=e,onAdd:t[7]||=e=>A.value=!0,onToggle:Z,onRestore:Q,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])]),_:1},8,[`open`,`title`])):g(``,!0),F(Ne,{open:A.value,channel:L(N).health?.connection?.channel===`qqbot`?`qqbot`:`onebot`,ready:!!L(N).health?.connection?.qqbot?.appId,onClose:t[9]||=e=>A.value=!1,onAdd:_e},null,8,[`open`,`channel`,`ready`]),F(Cn,{open:z.value,"session-id":f.value,events:b.value,onClose:t[10]||=e=>z.value=!1,onLoad:t[11]||=e=>X(!0)},null,8,[`open`,`session-id`,`events`])],2))}}),[[`__scopeId`,`data-v-c328f9fe`]]);export{On as default};