chatlab-cli 0.34.2 → 0.35.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 (176) hide show
  1. package/dist/{chat-command-6IOH2QRF.mjs → chat-command-24RYGJAS.mjs} +8 -8
  2. package/dist/{chunk-INBJ7SND.mjs → chunk-22YJOBA3.mjs} +589 -513
  3. package/dist/chunk-22YJOBA3.mjs.map +1 -0
  4. package/dist/{chunk-4LW6443F.mjs → chunk-4GWWKHWA.mjs} +833 -886
  5. package/dist/chunk-4GWWKHWA.mjs.map +1 -0
  6. package/dist/{chunk-MENKFEPC.mjs → chunk-7IGBPZMI.mjs} +56 -48
  7. package/dist/chunk-7IGBPZMI.mjs.map +1 -0
  8. package/dist/{chunk-72RWABYJ.mjs → chunk-BRZ6YIBR.mjs} +3 -3
  9. package/dist/chunk-BRZ6YIBR.mjs.map +1 -0
  10. package/dist/{chunk-2XCNTG4O.mjs → chunk-DC7IUDNV.mjs} +677 -69
  11. package/dist/chunk-DC7IUDNV.mjs.map +1 -0
  12. package/dist/{chunk-H6D4DZOC.mjs → chunk-DEFEU567.mjs} +12 -18
  13. package/dist/chunk-DEFEU567.mjs.map +1 -0
  14. package/dist/{chunk-4RLZFGO5.mjs → chunk-GQMCDWZQ.mjs} +2 -2
  15. package/dist/{chunk-52P5ZRBD.mjs → chunk-KIMJXKZO.mjs} +2 -2
  16. package/dist/{chunk-652KEAX3.mjs → chunk-KXDJ7N6N.mjs} +2 -2
  17. package/dist/{chunk-U6MVJSXX.mjs → chunk-QKEARRFH.mjs} +15 -9
  18. package/dist/chunk-QKEARRFH.mjs.map +1 -0
  19. package/dist/{chunk-UMBEHBIZ.mjs → chunk-U36TQMAF.mjs} +2 -2
  20. package/dist/{chunk-JX6MFS6F.mjs → chunk-ZBPPLXM4.mjs} +15 -15
  21. package/dist/cli.mjs +9 -9
  22. package/dist/contacts-worker.mjs +2 -2
  23. package/dist/global-insight-worker.mjs +1 -1
  24. package/dist/{http-HSK46XEU.mjs → http-3K4YMWDE.mjs} +12 -12
  25. package/dist/{import-DF2WD2HE.mjs → import-D4LV565A.mjs} +7 -7
  26. package/dist/index.mjs +12 -12
  27. package/dist/{mcp-A2RND7KN.mjs → mcp-KL22FKQ2.mjs} +8 -8
  28. package/dist/people-relationships-worker.mjs +2 -2
  29. package/dist/semantic-index-worker.mjs +1 -1
  30. package/dist/{src-SHFHW2QB.mjs → src-COIB6Z4P.mjs} +24 -38
  31. package/dist/{stream-import-GZ2QDE3Z.mjs → stream-import-FSXCFG4M.mjs} +7 -7
  32. package/dist/{worker-runtime-OWN56GG5.mjs → worker-runtime-DPS2LURI.mjs} +5 -5
  33. package/dist-cli-web/assets/AISettingsTab-rQC2ocFH.js +1 -0
  34. package/dist-cli-web/assets/AboutTab-DMPziKjj.js +1 -0
  35. package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-CnRLt_1j.js +110 -0
  36. package/dist-cli-web/assets/Alert--bTQXRcZ.js +1 -0
  37. package/dist-cli-web/assets/ApiSettingsTab-CtXl4I28.js +2 -0
  38. package/dist-cli-web/assets/BatchManageTab-1KaCPweN.js +1 -0
  39. package/dist-cli-web/assets/{BufferResource-BdKXeRZS.js → BufferResource-BXSJADxx.js} +1 -1
  40. package/dist-cli-web/assets/CanvasRenderer-Cq--8UQ_.js +1 -0
  41. package/dist-cli-web/assets/ChatRecordDrawer-BC0F7ok1.js +11 -0
  42. package/dist-cli-web/assets/ChatRecordDrawer-Tt6UduRC.css +1 -0
  43. package/dist-cli-web/assets/Checkbox-CwajNPGF.js +1 -0
  44. package/dist-cli-web/assets/DataDirCleanupNotice-D0Oruka2.js +1 -0
  45. package/dist-cli-web/assets/DatePicker.vue_vue_type_script_setup_true_lang-C4CsoIlq.js +1 -0
  46. package/dist-cli-web/assets/DebugToolsPanel-DVDwS-wz.js +1 -0
  47. package/dist-cli-web/assets/EChartWordcloud-KV0MdsDE.js +1 -0
  48. package/dist-cli-web/assets/{Filter-B2eEAPxI.js → Filter-DVaqcFmI.js} +1 -1
  49. package/dist-cli-web/assets/GlobalTaskBar-COW_uBX6.css +1 -0
  50. package/dist-cli-web/assets/GlobalTaskBar-DpPMX0jx.js +1 -0
  51. package/dist-cli-web/assets/GroupRelationshipGalaxyView-CLnO3phW.js +1 -0
  52. package/dist-cli-web/assets/Label-Bt2U4lGy.js +1 -0
  53. package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-Cnshyn0f.js +1 -0
  54. package/dist-cli-web/assets/MessageExportModal-BozaR9UZ.js +1 -0
  55. package/dist-cli-web/assets/PageHeader-CEe0xTl3.js +1 -0
  56. package/dist-cli-web/assets/PageTabs.vue_vue_type_script_setup_true_lang-D2DZrrnv.js +1 -0
  57. package/dist-cli-web/assets/{RelationshipGalaxyCanvas-CwQ63TPi.js → RelationshipGalaxyCanvas-6EamagxG.js} +3 -3
  58. package/dist-cli-web/assets/{RelationshipGalaxyThreeCanvas-BLqISQe5.js → RelationshipGalaxyThreeCanvas-B6GB4PWS.js} +166 -166
  59. package/dist-cli-web/assets/{RelationshipGalaxyThreeCanvas-CMuibfiA.css → RelationshipGalaxyThreeCanvas-TYGOUDiv.css} +1 -1
  60. package/dist-cli-web/assets/{RenderTargetSystem-eC_VrDAr.js → RenderTargetSystem-mYuj4uBS.js} +1 -1
  61. package/dist-cli-web/assets/RovingFocusItem-B2y6BG1a.js +1 -0
  62. package/dist-cli-web/assets/ScreenCaptureModal-BvAtSNNR.js +1 -0
  63. package/dist-cli-web/assets/SectionTabs.vue_vue_type_script_setup_true_lang-ueEF4zHE.js +1 -0
  64. package/dist-cli-web/assets/Select-Cm4I8u9n.js +1 -0
  65. package/dist-cli-web/assets/SettingsModal-CjVax3I6.css +1 -0
  66. package/dist-cli-web/assets/SettingsModal-Cwshzj7i.js +2 -0
  67. package/dist-cli-web/assets/StorageTab-D7HzH16Y.js +1 -0
  68. package/dist-cli-web/assets/Switch-BVw4e_19.js +1 -0
  69. package/dist-cli-web/assets/Tabs-Dqwkh3Ea.js +1 -0
  70. package/dist-cli-web/assets/Tabs-OrJkANIA.js +1 -0
  71. package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-DPIvbq75.js +1 -0
  72. package/dist-cli-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-dEdo6CqM.js +1 -0
  73. package/dist-cli-web/assets/VisuallyHiddenInput-BOdkR_GC.js +1 -0
  74. package/dist-cli-web/assets/{WebGLRenderer-CRp_HC84.js → WebGLRenderer-BB8ukBI_.js} +2 -2
  75. package/dist-cli-web/assets/{WebGPURenderer-WIUnEhpi.js → WebGPURenderer-DDOzF1Kc.js} +2 -2
  76. package/dist-cli-web/assets/aiChat-CzFSFuOJ.js +8 -0
  77. package/dist-cli-web/assets/annual-summary-time-range-CmrAqcuj.js +1 -0
  78. package/dist-cli-web/assets/base-CawUl6ET.js +1 -0
  79. package/dist-cli-web/assets/{browserAll-B29Ox2in.js → browserAll-BRqlnNxq.js} +2 -2
  80. package/dist-cli-web/assets/chatlabSiteLocale-CrCNb8QP.js +1 -0
  81. package/dist-cli-web/assets/clamp-jFYhBC76.js +1 -0
  82. package/dist-cli-web/assets/cli-web-ClAJ6Tao.js +1 -0
  83. package/dist-cli-web/assets/{dateFormat-T7kMS5IP.js → dateFormat-DXuDxIye.js} +1 -1
  84. package/dist-cli-web/assets/fetch-3_xX8Yi8.js +1 -0
  85. package/dist-cli-web/assets/{fetch-DsHf-61a.js → fetch-Bcy2T8Fe.js} +1 -1
  86. package/dist-cli-web/assets/{fetch-DvrVGAzR.js → fetch-CGu53cxT.js} +1 -1
  87. package/dist-cli-web/assets/{fetch-BBgZQob_.js → fetch-CP1afnT0.js} +1 -1
  88. package/dist-cli-web/assets/{fetch-D9sn99zX.js → fetch-DWAIk_6_.js} +1 -1
  89. package/dist-cli-web/assets/{fetch-BKBlpBl4.js → fetch-DijSOvop.js} +1 -1
  90. package/dist-cli-web/assets/{fetch-CYaGyohV.js → fetch-Dy38lkAa.js} +1 -1
  91. package/dist-cli-web/assets/{fetch-L6PKD8-N.js → fetch-DzKlTaLz.js} +1 -1
  92. package/dist-cli-web/assets/{fetch-D8hJZa8o.js → fetch-LY-yerw1.js} +1 -1
  93. package/dist-cli-web/assets/{fetch-BLi6Jsjg.js → fetch-q480XYZQ.js} +1 -1
  94. package/dist-cli-web/assets/index-0dp58uL4.css +1 -0
  95. package/dist-cli-web/assets/index-1X7zdLgJ.css +1 -0
  96. package/dist-cli-web/assets/index-4enwdBcq.css +1 -0
  97. package/dist-cli-web/assets/index-AudHHbMs.js +1 -0
  98. package/dist-cli-web/assets/{index-0qcENyvH.js → index-BRV_D8dv.js} +3 -3
  99. package/dist-cli-web/assets/index-CF4DTbvD.js +1 -0
  100. package/dist-cli-web/assets/index-CMLoht5R.js +1 -0
  101. package/dist-cli-web/assets/index-CMPHZH6B.css +1 -0
  102. package/dist-cli-web/assets/index-CNzv7aBW.js +1 -0
  103. package/dist-cli-web/assets/index-CXUTiy-V.js +1 -0
  104. package/dist-cli-web/assets/index-D5m0P4ss.js +36 -0
  105. package/dist-cli-web/assets/index-Di6FmCdk.js +64 -0
  106. package/dist-cli-web/assets/index-DkOrt7_X.js +2 -0
  107. package/dist-cli-web/assets/index-Dlb6fC68.js +1 -0
  108. package/dist-cli-web/assets/index-IA7YUjQX.js +2 -0
  109. package/dist-cli-web/assets/index-kzU381y1.js +1 -0
  110. package/dist-cli-web/assets/index-z8Vogc-b.js +137 -0
  111. package/dist-cli-web/assets/lazy-overlay-visibility-Bd9ZFOgR.js +1 -0
  112. package/dist-cli-web/assets/member-select-utils-BXZ3oHct.js +1 -0
  113. package/dist-cli-web/assets/people-page-header-Bh3KXD4g.js +1 -0
  114. package/dist-cli-web/assets/prompt-DcAcgQiB.js +1 -0
  115. package/dist-cli-web/assets/relationship-galaxy-connections-Cg4G5yYE.js +1 -0
  116. package/dist-cli-web/assets/semantic-index-config-builder-CK6v64Y1.js +1 -0
  117. package/dist-cli-web/assets/service-YZnJyEMq.js +1 -0
  118. package/dist-cli-web/assets/useFormControl-B-aXrJZN.js +1 -0
  119. package/dist-cli-web/assets/useTable-DPEMaGby.js +1 -0
  120. package/dist-cli-web/assets/useToast-B36MzFFy.js +1 -0
  121. package/dist-cli-web/assets/vendor-echarts-DfAMQxdr.js +11 -0
  122. package/dist-cli-web/assets/{vendor-echarts-wordcloud-Ca8n6qS6.js → vendor-echarts-wordcloud-BsaEJTPY.js} +1 -1
  123. package/dist-cli-web/assets/vendor-zrender-PFmZe1EY.js +8 -0
  124. package/dist-cli-web/assets/{webworkerAll-B2qvP2qR.js → webworkerAll-Dwqmy_YO.js} +1 -1
  125. package/dist-cli-web/index.html +2 -2
  126. package/package.json +1 -1
  127. package/dist/chunk-2XCNTG4O.mjs.map +0 -1
  128. package/dist/chunk-4LW6443F.mjs.map +0 -1
  129. package/dist/chunk-72RWABYJ.mjs.map +0 -1
  130. package/dist/chunk-H6D4DZOC.mjs.map +0 -1
  131. package/dist/chunk-INBJ7SND.mjs.map +0 -1
  132. package/dist/chunk-MENKFEPC.mjs.map +0 -1
  133. package/dist/chunk-U6MVJSXX.mjs.map +0 -1
  134. package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-B06LYYS9.js +0 -110
  135. package/dist-cli-web/assets/CanvasRenderer-DDRdIyZ8.js +0 -1
  136. package/dist-cli-web/assets/DataDirCleanupNotice-CP8P1lsL.js +0 -1
  137. package/dist-cli-web/assets/EChartWordcloud-CqknPGui.js +0 -1
  138. package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-BFaXTz1c.js +0 -1
  139. package/dist-cli-web/assets/MessageExportModal-CZge6qub.js +0 -1
  140. package/dist-cli-web/assets/PageHeader-CxKbcLDc.js +0 -1
  141. package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-DVmpDz05.js +0 -1
  142. package/dist-cli-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-CCH1UQGc.js +0 -1
  143. package/dist-cli-web/assets/annual-summary-time-range-R3pLPEuN.js +0 -1
  144. package/dist-cli-web/assets/base-CLwWn3Rs.js +0 -1
  145. package/dist-cli-web/assets/cli-web-CVLb5Pkq.js +0 -1
  146. package/dist-cli-web/assets/fetch-Cm2WR2Cv.js +0 -1
  147. package/dist-cli-web/assets/index-AkF_KqVI.js +0 -1
  148. package/dist-cli-web/assets/index-BVOFyB9E.js +0 -137
  149. package/dist-cli-web/assets/index-BkdOH41D.js +0 -1
  150. package/dist-cli-web/assets/index-C4TuoOLM.js +0 -2
  151. package/dist-cli-web/assets/index-C6Mk-Xqt.js +0 -1
  152. package/dist-cli-web/assets/index-CkgUsaeX.js +0 -1
  153. package/dist-cli-web/assets/index-CuAy1Xmt.js +0 -64
  154. package/dist-cli-web/assets/index-D_0qeKKc.js +0 -53
  155. package/dist-cli-web/assets/index-DeR0Vukh.js +0 -1
  156. package/dist-cli-web/assets/index-DrjoMv9s.css +0 -1
  157. package/dist-cli-web/assets/index-Dx-XvfIK.js +0 -2
  158. package/dist-cli-web/assets/index-Jhwca9kg.css +0 -1
  159. package/dist-cli-web/assets/index-_DrviTnP.js +0 -1
  160. package/dist-cli-web/assets/index-vtF05fh7.css +0 -1
  161. package/dist-cli-web/assets/index-xN0sjOLK.js +0 -1
  162. package/dist-cli-web/assets/people-page-header-CCNJOrmK.js +0 -1
  163. package/dist-cli-web/assets/vendor-echarts-B-i_56Av.js +0 -11
  164. package/dist-cli-web/assets/vendor-zrender-5DcYjSZk.js +0 -8
  165. /package/dist/{chat-command-6IOH2QRF.mjs.map → chat-command-24RYGJAS.mjs.map} +0 -0
  166. /package/dist/{chunk-4RLZFGO5.mjs.map → chunk-GQMCDWZQ.mjs.map} +0 -0
  167. /package/dist/{chunk-52P5ZRBD.mjs.map → chunk-KIMJXKZO.mjs.map} +0 -0
  168. /package/dist/{chunk-652KEAX3.mjs.map → chunk-KXDJ7N6N.mjs.map} +0 -0
  169. /package/dist/{chunk-UMBEHBIZ.mjs.map → chunk-U36TQMAF.mjs.map} +0 -0
  170. /package/dist/{chunk-JX6MFS6F.mjs.map → chunk-ZBPPLXM4.mjs.map} +0 -0
  171. /package/dist/{http-HSK46XEU.mjs.map → http-3K4YMWDE.mjs.map} +0 -0
  172. /package/dist/{import-DF2WD2HE.mjs.map → import-D4LV565A.mjs.map} +0 -0
  173. /package/dist/{mcp-A2RND7KN.mjs.map → mcp-KL22FKQ2.mjs.map} +0 -0
  174. /package/dist/{src-SHFHW2QB.mjs.map → src-COIB6Z4P.mjs.map} +0 -0
  175. /package/dist/{stream-import-GZ2QDE3Z.mjs.map → stream-import-FSXCFG4M.mjs.map} +0 -0
  176. /package/dist/{worker-runtime-OWN56GG5.mjs.map → worker-runtime-DPS2LURI.mjs.map} +0 -0
@@ -40,13 +40,17 @@ var cliConfigSchema = z.object({
40
40
  /** Allow the read-only `sql` query command. */
41
41
  allow_sql: z.boolean().default(true)
42
42
  });
43
+ var desktopConfigSchema = z.object({
44
+ close_behavior: z.enum(["background", "quit"]).default("background")
45
+ });
43
46
  var configSchema = z.object({
44
47
  llm: llmConfigSchema.default({}),
45
48
  data: dataConfigSchema.default({}),
46
49
  api: apiConfigSchema.default({}),
47
50
  locale: localeConfigSchema.default({}),
48
51
  ui: uiConfigSchema.default({}),
49
- cli: cliConfigSchema.default({})
52
+ cli: cliConfigSchema.default({}),
53
+ desktop: desktopConfigSchema.default({})
50
54
  });
51
55
 
52
56
  // ../../packages/config/src/loader.ts
@@ -4108,7 +4112,7 @@ function buildMsgConditions(options) {
4108
4112
 
4109
4113
  // ../../packages/core/src/query/advanced/social.ts
4110
4114
  function getMentionAnalysis(db, filter) {
4111
- const emptyResult = {
4115
+ const emptyResult2 = {
4112
4116
  topMentioners: [],
4113
4117
  topMentioned: [],
4114
4118
  totalMentions: 0
@@ -4117,7 +4121,7 @@ function getMentionAnalysis(db, filter) {
4117
4121
  `SELECT id, platform_id as platformId, COALESCE(group_nickname, account_name, platform_id) as name
4118
4122
  FROM member WHERE COALESCE(account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'`
4119
4123
  ).all();
4120
- if (members.length === 0) return emptyResult;
4124
+ if (members.length === 0) return emptyResult2;
4121
4125
  const nameToMemberId = /* @__PURE__ */ new Map();
4122
4126
  const memberIdToInfo = /* @__PURE__ */ new Map();
4123
4127
  for (const member of members) {
@@ -4158,7 +4162,7 @@ function getMentionAnalysis(db, filter) {
4158
4162
  }
4159
4163
  }
4160
4164
  }
4161
- if (totalMentions === 0) return emptyResult;
4165
+ if (totalMentions === 0) return emptyResult2;
4162
4166
  const topMentioners = [];
4163
4167
  for (const [memberId, count] of mentionerCount.entries()) {
4164
4168
  const info = memberIdToInfo.get(memberId);
@@ -4185,87 +4189,13 @@ function getMentionAnalysis(db, filter) {
4185
4189
  topMentioned.sort((a, b) => b.count - a.count);
4186
4190
  return { topMentioners, topMentioned, totalMentions };
4187
4191
  }
4188
- function getMentionGraph(db, filter) {
4189
- const emptyResult = { nodes: [], links: [], maxLinkValue: 0 };
4190
- const { clause, params } = buildTimeFilter(filter);
4191
- const msgFilterBase = clause ? clause.replace("WHERE", "AND") : "";
4192
- const msgFilterWithSystem = msgFilterBase + " AND COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'";
4193
- const members = db.prepare(
4194
- `SELECT m.id, m.platform_id as platformId, COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,
4195
- COUNT(msg.id) as messageCount
4196
- FROM member m LEFT JOIN message msg ON m.id = msg.sender_id ${msgFilterWithSystem}
4197
- WHERE COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F' GROUP BY m.id`
4198
- ).all(...params);
4199
- if (members.length === 0) return emptyResult;
4200
- const nameToMemberId = /* @__PURE__ */ new Map();
4201
- const memberIdToInfo = /* @__PURE__ */ new Map();
4202
- for (const member of members) {
4203
- memberIdToInfo.set(member.id, { name: member.name, messageCount: member.messageCount });
4204
- nameToMemberId.set(member.name, member.id);
4205
- const history = db.prepare("SELECT name FROM member_name_history WHERE member_id = ?").all(member.id);
4206
- for (const h of history) {
4207
- if (!nameToMemberId.has(h.name)) nameToMemberId.set(h.name, member.id);
4208
- }
4209
- }
4210
- let whereClause = clause;
4211
- if (whereClause.includes("WHERE")) {
4212
- whereClause += " AND COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'";
4213
- } else {
4214
- whereClause = " WHERE COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'";
4215
- }
4216
- const messages = db.prepare(
4217
- `SELECT msg.sender_id as senderId, msg.content FROM message msg JOIN member m ON msg.sender_id = m.id ${whereClause}`
4218
- ).all(...params);
4219
- const mentionMatrix = /* @__PURE__ */ new Map();
4220
- const mentionRegex = /@([^\s@]+)/g;
4221
- for (const msg of messages) {
4222
- const matches = msg.content.matchAll(mentionRegex);
4223
- const mentionedInThisMsg = /* @__PURE__ */ new Set();
4224
- for (const match of matches) {
4225
- const mentionedId = nameToMemberId.get(match[1]);
4226
- if (mentionedId && mentionedId !== msg.senderId && !mentionedInThisMsg.has(mentionedId)) {
4227
- mentionedInThisMsg.add(mentionedId);
4228
- if (!mentionMatrix.has(msg.senderId)) mentionMatrix.set(msg.senderId, /* @__PURE__ */ new Map());
4229
- const fromMap = mentionMatrix.get(msg.senderId);
4230
- fromMap.set(mentionedId, (fromMap.get(mentionedId) || 0) + 1);
4231
- }
4232
- }
4233
- }
4234
- const involvedMemberIds = /* @__PURE__ */ new Set();
4235
- for (const [fromId, toMap] of mentionMatrix.entries()) {
4236
- involvedMemberIds.add(fromId);
4237
- for (const toId of toMap.keys()) involvedMemberIds.add(toId);
4238
- }
4239
- const maxMessageCount = Math.max(...members.filter((m) => involvedMemberIds.has(m.id)).map((m) => m.messageCount), 1);
4240
- const nodes = [];
4241
- for (const memberId of involvedMemberIds) {
4242
- const info = memberIdToInfo.get(memberId);
4243
- if (info) {
4244
- const symbolSize = 20 + info.messageCount / maxMessageCount * 40;
4245
- nodes.push({ id: memberId, name: info.name, value: info.messageCount, symbolSize: Math.round(symbolSize) });
4246
- }
4247
- }
4248
- const links = [];
4249
- let maxLinkValue = 0;
4250
- for (const [fromId, toMap] of mentionMatrix.entries()) {
4251
- const fromInfo = memberIdToInfo.get(fromId);
4252
- if (!fromInfo) continue;
4253
- for (const [toId, count] of toMap.entries()) {
4254
- const toInfo = memberIdToInfo.get(toId);
4255
- if (!toInfo) continue;
4256
- links.push({ source: fromInfo.name, target: toInfo.name, value: count });
4257
- maxLinkValue = Math.max(maxLinkValue, count);
4258
- }
4259
- }
4260
- return { nodes, links, maxLinkValue };
4261
- }
4262
4192
  function keywordToPattern(keyword) {
4263
4193
  const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4264
4194
  if (keyword === "\u54C8\u54C8") return "\u54C8\u54C8+";
4265
4195
  return escaped;
4266
4196
  }
4267
4197
  function getLaughAnalysis(db, filter, keywords) {
4268
- const emptyResult = {
4198
+ const emptyResult2 = {
4269
4199
  rankByRate: [],
4270
4200
  rankByCount: [],
4271
4201
  typeDistribution: [],
@@ -4273,7 +4203,8 @@ function getLaughAnalysis(db, filter, keywords) {
4273
4203
  totalMessages: 0,
4274
4204
  groupLaughRate: 0
4275
4205
  };
4276
- const laughKeywords = keywords && keywords.length > 0 ? keywords : [];
4206
+ const laughKeywords = keywords?.map((keyword) => keyword.trim()).filter(Boolean) ?? [];
4207
+ if (laughKeywords.length === 0) return emptyResult2;
4277
4208
  const patterns = laughKeywords.map(keywordToPattern);
4278
4209
  const laughRegex = new RegExp(`(${patterns.join("|")})`, "gi");
4279
4210
  const { clause, params } = buildTimeFilter(filter);
@@ -4288,7 +4219,7 @@ function getLaughAnalysis(db, filter, keywords) {
4288
4219
  COALESCE(m.group_nickname, m.account_name, m.platform_id) as name
4289
4220
  FROM message msg JOIN member m ON msg.sender_id = m.id ${whereClause}`
4290
4221
  ).all(...params);
4291
- if (messages.length === 0) return emptyResult;
4222
+ if (messages.length === 0) return emptyResult2;
4292
4223
  const memberStats = /* @__PURE__ */ new Map();
4293
4224
  const typeCount = /* @__PURE__ */ new Map();
4294
4225
  let totalLaughs = 0;
@@ -4321,7 +4252,7 @@ function getLaughAnalysis(db, filter, keywords) {
4321
4252
  }
4322
4253
  }
4323
4254
  }
4324
- if (totalLaughs === 0) return emptyResult;
4255
+ if (totalLaughs === 0) return emptyResult2;
4325
4256
  const rankItems = [];
4326
4257
  for (const [memberId, stats] of memberStats.entries()) {
4327
4258
  if (stats.laughCount > 0) {
@@ -4411,7 +4342,7 @@ function accumulateCoOccurrencePairs(messages, options) {
4411
4342
  }
4412
4343
  function getClusterGraph(db, filter, options) {
4413
4344
  const opts = { ...DEFAULT_CLUSTER_OPTIONS, ...options };
4414
- const emptyResult = {
4345
+ const emptyResult2 = {
4415
4346
  nodes: [],
4416
4347
  links: [],
4417
4348
  maxLinkValue: 0,
@@ -4423,7 +4354,7 @@ function getClusterGraph(db, filter, options) {
4423
4354
  (SELECT COUNT(*) FROM message WHERE sender_id = member.id) as messageCount
4424
4355
  FROM member WHERE COALESCE(account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'`
4425
4356
  ).all();
4426
- if (members.length < 2) return { ...emptyResult, stats: { ...emptyResult.stats, totalMembers: members.length } };
4357
+ if (members.length < 2) return { ...emptyResult2, stats: { ...emptyResult2.stats, totalMembers: members.length } };
4427
4358
  const memberInfo = /* @__PURE__ */ new Map();
4428
4359
  for (const m of members)
4429
4360
  memberInfo.set(m.id, { name: m.name, platformId: m.platformId, messageCount: m.messageCount });
@@ -4440,8 +4371,8 @@ function getClusterGraph(db, filter, options) {
4440
4371
  ).all(...params);
4441
4372
  if (messages.length < 2)
4442
4373
  return {
4443
- ...emptyResult,
4444
- stats: { ...emptyResult.stats, totalMembers: members.length, totalMessages: messages.length }
4374
+ ...emptyResult2,
4375
+ stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages: messages.length }
4445
4376
  };
4446
4377
  const memberMsgCount = /* @__PURE__ */ new Map();
4447
4378
  for (const msg of messages) memberMsgCount.set(msg.senderId, (memberMsgCount.get(msg.senderId) || 0) + 1);
@@ -4480,8 +4411,8 @@ function getClusterGraph(db, filter, options) {
4480
4411
  const keptEdges = edges.slice(0, opts.topEdges);
4481
4412
  if (keptEdges.length === 0)
4482
4413
  return {
4483
- ...emptyResult,
4484
- stats: { ...emptyResult.stats, totalMembers: members.length, totalMessages: messages.length }
4414
+ ...emptyResult2,
4415
+ stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages: messages.length }
4485
4416
  };
4486
4417
  const involvedIds = /* @__PURE__ */ new Set();
4487
4418
  for (const edge of keptEdges) {
@@ -4754,7 +4685,7 @@ function getGroupRelationshipGraphFacts(db, ownerMemberId, options = {}) {
4754
4685
  ORDER BY msg.ts ASC, msg.id ASC`
4755
4686
  ).all(...messageTimeFilter.params);
4756
4687
  const edgeStats = /* @__PURE__ */ new Map();
4757
- const ensureEdge = (aId, bId) => {
4688
+ const ensureEdge2 = (aId, bId) => {
4758
4689
  const sourceId = Math.min(aId, bId);
4759
4690
  const targetId = Math.max(aId, bId);
4760
4691
  const key = `${sourceId}:${targetId}`;
@@ -4774,7 +4705,7 @@ function getGroupRelationshipGraphFacts(db, ownerMemberId, options = {}) {
4774
4705
  };
4775
4706
  for (const pair of accumulateCoOccurrencePairs(coOccurrenceRows)) {
4776
4707
  if (!contactById.has(pair.sourceId) || !contactById.has(pair.targetId)) continue;
4777
- const edge = ensureEdge(pair.sourceId, pair.targetId);
4708
+ const edge = ensureEdge2(pair.sourceId, pair.targetId);
4778
4709
  edge.coOccurrenceCount += pair.coOccurrenceCount;
4779
4710
  edge.coOccurrenceRawScore += pair.rawScore;
4780
4711
  edge.lastInteractionTs = Math.max(edge.lastInteractionTs ?? 0, pair.lastOccurrenceTs);
@@ -4796,7 +4727,7 @@ function getGroupRelationshipGraphFacts(db, ownerMemberId, options = {}) {
4796
4727
  for (const row of replyRows) {
4797
4728
  if (!contactById.has(row.replySenderId) || !contactById.has(row.targetSenderId)) continue;
4798
4729
  if (row.replySenderId === row.targetSenderId) continue;
4799
- const edge = ensureEdge(row.replySenderId, row.targetSenderId);
4730
+ const edge = ensureEdge2(row.replySenderId, row.targetSenderId);
4800
4731
  if (row.replySenderId === edge.sourceId) edge.repliesFromSourceToTarget++;
4801
4732
  else edge.repliesFromTargetToSource++;
4802
4733
  edge.lastInteractionTs = Math.max(edge.lastInteractionTs ?? 0, row.replyTs);
@@ -6004,12 +5935,456 @@ function getCatchphraseAnalysis(db, filter) {
6004
5935
  return { members };
6005
5936
  }
6006
5937
 
5938
+ // ../../packages/core/src/query/advanced/group-relationship-galaxy.ts
5939
+ var GROUP_RELATIONSHIP_GALAXY_ALGORITHM_VERSION = "group-relationship-galaxy-v1";
5940
+ var REPLY_WEIGHT = 3;
5941
+ var MENTION_WEIGHT = 2;
5942
+ var CO_OCCURRENCE_COUNT_WEIGHT = 0.05;
5943
+ var MIN_EDGE_WEIGHT = 1e-6;
5944
+ var DEFAULT_LIMITS = {
5945
+ nodeLimit: 400,
5946
+ edgeLimit: 1200,
5947
+ perNodeEdgeLimit: 10
5948
+ };
5949
+ var COMMUNITY_COLORS = [
5950
+ "#7dd3fc",
5951
+ "#c4b5fd",
5952
+ "#f0abfc",
5953
+ "#5eead4",
5954
+ "#fda4af",
5955
+ "#fde68a",
5956
+ "#93c5fd",
5957
+ "#6ee7b7",
5958
+ "#d8b4fe",
5959
+ "#fdba74"
5960
+ ];
5961
+ function getGroupRelationshipGalaxy(db, filter, options = {}) {
5962
+ const limits = normalizeLimits(options);
5963
+ const allMembers = getNonSystemMembersForContacts(db);
5964
+ const memberById = new Map(allMembers.map((member) => [member.id, member]));
5965
+ const messageRows = queryRelationshipMessages(db, filter).filter((message) => memberById.has(message.senderId));
5966
+ const activeMembers = buildActiveMembers(allMembers, messageRows);
5967
+ const activeMemberById = new Map(activeMembers.map((member) => [member.id, member]));
5968
+ if (activeMembers.length < 2) {
5969
+ return emptyResult(allMembers.length, activeMembers.length);
5970
+ }
5971
+ const edgeByPair = /* @__PURE__ */ new Map();
5972
+ for (const pair of accumulateCoOccurrencePairs(messageRows)) {
5973
+ if (!activeMemberById.has(pair.sourceId) || !activeMemberById.has(pair.targetId)) continue;
5974
+ const edge = ensureEdge(edgeByPair, activeMemberById, pair.sourceId, pair.targetId);
5975
+ edge.coOccurrenceCount += pair.coOccurrenceCount;
5976
+ edge.coOccurrenceRawScore += pair.rawScore;
5977
+ edge.lastInteractionTs = maxTimestamp(edge.lastInteractionTs, pair.lastOccurrenceTs);
5978
+ }
5979
+ accumulateReplies(db, filter, activeMemberById, edgeByPair);
5980
+ accumulateMentions(db, filter, activeMembers, activeMemberById, edgeByPair);
5981
+ const weightedEdges = [...edgeByPair.values()].map((edge) => ({ ...edge, weight: roundNum2(computeEdgeWeight(edge)) })).filter((edge) => edge.weight >= MIN_EDGE_WEIGHT).sort(compareEdges);
5982
+ if (weightedEdges.length === 0) {
5983
+ return emptyResult(allMembers.length, activeMembers.length);
5984
+ }
5985
+ const candidateNodeKeys = selectCandidateNodeKeys(weightedEdges, limits.nodeLimit);
5986
+ const candidateEdges = weightedEdges.filter(
5987
+ (edge) => candidateNodeKeys.has(edge.sourceKey) && candidateNodeKeys.has(edge.targetKey)
5988
+ );
5989
+ const keptEdges = selectEdges(candidateEdges, limits.perNodeEdgeLimit, limits.edgeLimit);
5990
+ const displayedNodeKeys = /* @__PURE__ */ new Set();
5991
+ for (const edge of keptEdges) {
5992
+ displayedNodeKeys.add(edge.sourceKey);
5993
+ displayedNodeKeys.add(edge.targetKey);
5994
+ }
5995
+ const displayedMembers = activeMembers.filter((member) => displayedNodeKeys.has(member.key));
5996
+ const degreeByKey = computeDegreeByKey(keptEdges);
5997
+ const rankedMembers = [...displayedMembers].sort((a, b) => {
5998
+ const degreeDiff = (degreeByKey.get(b.key) ?? 0) - (degreeByKey.get(a.key) ?? 0);
5999
+ return degreeDiff || b.messageCount - a.messageCount || a.key.localeCompare(b.key);
6000
+ });
6001
+ const communityAssignments = detectWeightedCommunities(rankedMembers, keptEdges, degreeByKey);
6002
+ const communityByNodeKey = /* @__PURE__ */ new Map();
6003
+ for (const community of communityAssignments) {
6004
+ for (const nodeKey of community.nodeKeys) communityByNodeKey.set(nodeKey, community);
6005
+ }
6006
+ const maxDegree = Math.max(...degreeByKey.values(), 1);
6007
+ const renderNodes = [];
6008
+ const memberDetails = [];
6009
+ const incidentTotals = buildIncidentTotals(weightedEdges);
6010
+ for (const [index, member] of rankedMembers.entries()) {
6011
+ const degree = degreeByKey.get(member.key) ?? 0;
6012
+ const score = degree / maxDegree;
6013
+ const community = communityByNodeKey.get(member.key);
6014
+ const position = layoutNode(member.key, index, community, rankedMembers, communityByNodeKey);
6015
+ const totals = incidentTotals.get(member.key) ?? createIncidentTotals();
6016
+ const rank = index + 1;
6017
+ renderNodes.push({
6018
+ key: member.key,
6019
+ displayName: member.displayName,
6020
+ avatar: member.avatar,
6021
+ score: roundNum2(score),
6022
+ rank,
6023
+ communityId: community.id,
6024
+ x: position.x,
6025
+ y: position.y,
6026
+ size: roundNum2(18 + Math.sqrt(score) * 30, 2),
6027
+ color: community.color,
6028
+ labelVisibility: rank <= 8 ? 2 : rank <= 30 ? 1 : 0
6029
+ });
6030
+ memberDetails.push({
6031
+ key: member.key,
6032
+ memberId: member.id,
6033
+ platformId: member.platformId,
6034
+ displayName: member.displayName,
6035
+ avatar: member.avatar,
6036
+ messageCount: member.messageCount,
6037
+ lastMessageTs: member.lastMessageTs,
6038
+ relationshipScore: roundNum2(score),
6039
+ rank,
6040
+ communityId: community.id,
6041
+ replyInteractionCount: totals.replyInteractionCount,
6042
+ mentionInteractionCount: totals.mentionInteractionCount,
6043
+ coOccurrenceCount: totals.coOccurrenceCount,
6044
+ coOccurrenceRawScore: roundNum2(totals.coOccurrenceRawScore),
6045
+ lastInteractionTs: totals.lastInteractionTs
6046
+ });
6047
+ }
6048
+ const maxEdgeWeight = Math.max(...keptEdges.map((edge) => edge.weight), 1);
6049
+ const renderEdges = keptEdges.map((edge) => ({
6050
+ id: edge.id,
6051
+ sourceKey: edge.sourceKey,
6052
+ targetKey: edge.targetKey,
6053
+ weight: edge.weight,
6054
+ visibility: edge.weight / maxEdgeWeight >= 0.35 ? 2 : 1,
6055
+ lastInteractionTs: edge.lastInteractionTs
6056
+ }));
6057
+ const edgeDetails = keptEdges.map((edge) => ({
6058
+ id: edge.id,
6059
+ sourceKey: edge.sourceKey,
6060
+ targetKey: edge.targetKey,
6061
+ weight: edge.weight,
6062
+ coOccurrenceCount: edge.coOccurrenceCount,
6063
+ coOccurrenceRawScore: roundNum2(edge.coOccurrenceRawScore),
6064
+ replyInteractionCount: edge.replyInteractionCount,
6065
+ mentionInteractionCount: edge.mentionInteractionCount,
6066
+ lastInteractionTs: edge.lastInteractionTs
6067
+ }));
6068
+ const renderCommunities = communityAssignments.map((community, index) => ({
6069
+ id: community.id,
6070
+ label: `community-${index + 1}`,
6071
+ size: community.nodeKeys.length,
6072
+ x: community.x,
6073
+ y: community.y,
6074
+ color: community.color
6075
+ }));
6076
+ return {
6077
+ graph: { nodes: renderNodes, edges: renderEdges, communities: renderCommunities },
6078
+ members: memberDetails,
6079
+ edges: edgeDetails,
6080
+ stats: {
6081
+ totalMembers: allMembers.length,
6082
+ activeMembers: activeMembers.length,
6083
+ displayedMembers: renderNodes.length,
6084
+ displayedEdges: renderEdges.length,
6085
+ communityCount: renderCommunities.length
6086
+ },
6087
+ algorithmVersion: GROUP_RELATIONSHIP_GALAXY_ALGORITHM_VERSION
6088
+ };
6089
+ }
6090
+ function queryRelationshipMessages(db, filter) {
6091
+ const { clause, params } = buildTimeFilter(filter, "msg");
6092
+ const where = appendConditions(clause, ["msg.type NOT IN (80, 81)"]);
6093
+ return db.prepare(`SELECT msg.sender_id as senderId, msg.ts FROM message msg ${where} ORDER BY msg.ts ASC, msg.id ASC`).all(...params);
6094
+ }
6095
+ function buildActiveMembers(allMembers, messages) {
6096
+ const messageStats = /* @__PURE__ */ new Map();
6097
+ for (const message of messages) {
6098
+ const stats = messageStats.get(message.senderId) ?? { count: 0, lastTs: 0 };
6099
+ stats.count++;
6100
+ stats.lastTs = Math.max(stats.lastTs, message.ts);
6101
+ messageStats.set(message.senderId, stats);
6102
+ }
6103
+ const active = allMembers.filter((member) => messageStats.has(member.id)).map((member) => ({
6104
+ ...member,
6105
+ key: memberKey(member.id),
6106
+ displayName: member.name,
6107
+ messageCount: messageStats.get(member.id).count,
6108
+ lastMessageTs: messageStats.get(member.id).lastTs || null
6109
+ }));
6110
+ const nameCounts = /* @__PURE__ */ new Map();
6111
+ for (const member of active) nameCounts.set(member.displayName, (nameCounts.get(member.displayName) ?? 0) + 1);
6112
+ for (const member of active) {
6113
+ if ((nameCounts.get(member.displayName) ?? 0) > 1) {
6114
+ member.displayName = `${member.displayName}#${member.platformId.slice(-4)}`;
6115
+ }
6116
+ }
6117
+ return active;
6118
+ }
6119
+ function accumulateReplies(db, filter, activeMemberById, edgeByPair) {
6120
+ if (!hasColumn(db, "message", "reply_to_message_id") || !hasColumn(db, "message", "platform_message_id")) return;
6121
+ const { clause, params } = buildTimeFilter(filter, "msg");
6122
+ const conditions = ["msg.type NOT IN (80, 81)", "target.type NOT IN (80, 81)", "msg.reply_to_message_id IS NOT NULL"];
6123
+ const targetParams = [];
6124
+ if (filter?.startTs !== void 0) {
6125
+ conditions.push("target.ts >= ?");
6126
+ targetParams.push(filter.startTs);
6127
+ }
6128
+ if (filter?.endTs !== void 0) {
6129
+ conditions.push("target.ts <= ?");
6130
+ targetParams.push(filter.endTs);
6131
+ }
6132
+ const rows = db.prepare(
6133
+ `SELECT msg.sender_id as replySenderId, msg.ts as replyTs, target.sender_id as targetSenderId
6134
+ FROM message msg
6135
+ JOIN message target ON msg.reply_to_message_id = target.platform_message_id
6136
+ ${appendConditions(clause, conditions)}`
6137
+ ).all(...params, ...targetParams);
6138
+ for (const row of rows) {
6139
+ if (row.replySenderId === row.targetSenderId) continue;
6140
+ if (!activeMemberById.has(row.replySenderId) || !activeMemberById.has(row.targetSenderId)) continue;
6141
+ const edge = ensureEdge(edgeByPair, activeMemberById, row.replySenderId, row.targetSenderId);
6142
+ edge.replyInteractionCount++;
6143
+ edge.lastInteractionTs = maxTimestamp(edge.lastInteractionTs, row.replyTs);
6144
+ }
6145
+ }
6146
+ function accumulateMentions(db, filter, activeMembers, activeMemberById, edgeByPair) {
6147
+ const nameToMemberId = buildMentionNameIndex(db, activeMembers);
6148
+ const { clause, params } = buildTimeFilter(filter, "msg");
6149
+ const where = appendConditions(clause, ["msg.type = 0", "msg.content IS NOT NULL", "msg.content LIKE '%@%'"]);
6150
+ const rows = db.prepare(`SELECT msg.sender_id as senderId, msg.ts, msg.content FROM message msg ${where}`).all(...params);
6151
+ const mentionRegex = /@([^\s@]+)/g;
6152
+ for (const row of rows) {
6153
+ if (!activeMemberById.has(row.senderId)) continue;
6154
+ const mentionedInMessage = /* @__PURE__ */ new Set();
6155
+ for (const match of row.content.matchAll(mentionRegex)) {
6156
+ const mentionedId = nameToMemberId.get(match[1]);
6157
+ if (mentionedId === null || mentionedId === void 0 || mentionedId === row.senderId) continue;
6158
+ if (!activeMemberById.has(mentionedId) || mentionedInMessage.has(mentionedId)) continue;
6159
+ mentionedInMessage.add(mentionedId);
6160
+ const edge = ensureEdge(edgeByPair, activeMemberById, row.senderId, mentionedId);
6161
+ edge.mentionInteractionCount++;
6162
+ edge.lastInteractionTs = maxTimestamp(edge.lastInteractionTs, row.ts);
6163
+ }
6164
+ }
6165
+ }
6166
+ function buildMentionNameIndex(db, members) {
6167
+ const index = /* @__PURE__ */ new Map();
6168
+ const addName = (name, memberId) => {
6169
+ const trimmed = name.trim();
6170
+ if (!trimmed) return;
6171
+ const existing = index.get(trimmed);
6172
+ if (existing === void 0 || existing === memberId) index.set(trimmed, memberId);
6173
+ else index.set(trimmed, null);
6174
+ };
6175
+ for (const member of members) {
6176
+ addName(member.name, member.id);
6177
+ for (const alias of member.aliases) addName(alias, member.id);
6178
+ }
6179
+ if (hasTable(db, "member_name_history")) {
6180
+ const memberIds = new Set(members.map((member) => member.id));
6181
+ const rows = db.prepare("SELECT member_id as memberId, name FROM member_name_history").all();
6182
+ for (const row of rows) {
6183
+ if (memberIds.has(row.memberId)) addName(row.name, row.memberId);
6184
+ }
6185
+ }
6186
+ return index;
6187
+ }
6188
+ function ensureEdge(edgeByPair, memberById, aId, bId) {
6189
+ const sourceId = Math.min(aId, bId);
6190
+ const targetId = Math.max(aId, bId);
6191
+ const id = `${memberKey(sourceId)}--${memberKey(targetId)}`;
6192
+ const existing = edgeByPair.get(id);
6193
+ if (existing) return existing;
6194
+ const created = {
6195
+ id,
6196
+ sourceId,
6197
+ targetId,
6198
+ sourceKey: memberById.get(sourceId).key,
6199
+ targetKey: memberById.get(targetId).key,
6200
+ coOccurrenceCount: 0,
6201
+ coOccurrenceRawScore: 0,
6202
+ replyInteractionCount: 0,
6203
+ mentionInteractionCount: 0,
6204
+ lastInteractionTs: null,
6205
+ weight: 0
6206
+ };
6207
+ edgeByPair.set(id, created);
6208
+ return created;
6209
+ }
6210
+ function computeEdgeWeight(edge) {
6211
+ return edge.coOccurrenceRawScore + edge.replyInteractionCount * REPLY_WEIGHT + edge.mentionInteractionCount * MENTION_WEIGHT + edge.coOccurrenceCount * CO_OCCURRENCE_COUNT_WEIGHT;
6212
+ }
6213
+ function selectCandidateNodeKeys(edges, nodeLimit) {
6214
+ const degree = computeDegreeByKey(edges);
6215
+ return new Set(
6216
+ [...degree.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, nodeLimit).map(([key]) => key)
6217
+ );
6218
+ }
6219
+ function selectEdges(edges, perNodeLimit, edgeLimit) {
6220
+ const incident = /* @__PURE__ */ new Map();
6221
+ for (const edge of edges) {
6222
+ for (const key of [edge.sourceKey, edge.targetKey]) {
6223
+ const list = incident.get(key) ?? [];
6224
+ list.push(edge);
6225
+ incident.set(key, list);
6226
+ }
6227
+ }
6228
+ const selectedIds = /* @__PURE__ */ new Set();
6229
+ for (const list of incident.values()) {
6230
+ list.sort(compareEdges);
6231
+ for (const edge of list.slice(0, perNodeLimit)) selectedIds.add(edge.id);
6232
+ }
6233
+ return edges.filter((edge) => selectedIds.has(edge.id)).sort(compareEdges).slice(0, edgeLimit);
6234
+ }
6235
+ function computeDegreeByKey(edges) {
6236
+ const degree = /* @__PURE__ */ new Map();
6237
+ for (const edge of edges) {
6238
+ degree.set(edge.sourceKey, (degree.get(edge.sourceKey) ?? 0) + edge.weight);
6239
+ degree.set(edge.targetKey, (degree.get(edge.targetKey) ?? 0) + edge.weight);
6240
+ }
6241
+ return degree;
6242
+ }
6243
+ function detectWeightedCommunities(members, edges, degreeByKey) {
6244
+ const parent = new Map(members.map((member) => [member.key, member.key]));
6245
+ const strongestByKey = /* @__PURE__ */ new Map();
6246
+ for (const edge of edges) {
6247
+ strongestByKey.set(edge.sourceKey, Math.max(strongestByKey.get(edge.sourceKey) ?? 0, edge.weight));
6248
+ strongestByKey.set(edge.targetKey, Math.max(strongestByKey.get(edge.targetKey) ?? 0, edge.weight));
6249
+ }
6250
+ const find = (key) => {
6251
+ const current = parent.get(key) ?? key;
6252
+ if (current === key) return current;
6253
+ const root = find(current);
6254
+ parent.set(key, root);
6255
+ return root;
6256
+ };
6257
+ const union = (a, b) => {
6258
+ const aRoot = find(a);
6259
+ const bRoot = find(b);
6260
+ if (aRoot === bRoot) return;
6261
+ const [source, target] = aRoot.localeCompare(bRoot) <= 0 ? [aRoot, bRoot] : [bRoot, aRoot];
6262
+ parent.set(target, source);
6263
+ };
6264
+ for (const edge of edges) {
6265
+ const sourceThreshold = (strongestByKey.get(edge.sourceKey) ?? edge.weight) * 0.6;
6266
+ const targetThreshold = (strongestByKey.get(edge.targetKey) ?? edge.weight) * 0.6;
6267
+ if (edge.weight >= sourceThreshold && edge.weight >= targetThreshold) union(edge.sourceKey, edge.targetKey);
6268
+ }
6269
+ const groups = /* @__PURE__ */ new Map();
6270
+ for (const member of members) {
6271
+ const root = find(member.key);
6272
+ const list = groups.get(root) ?? [];
6273
+ list.push(member.key);
6274
+ groups.set(root, list);
6275
+ }
6276
+ const sortedGroups = [...groups.values()].map((keys) => keys.sort()).sort((a, b) => {
6277
+ const aWeight = a.reduce((sum, key) => sum + (degreeByKey.get(key) ?? 0), 0);
6278
+ const bWeight = b.reduce((sum, key) => sum + (degreeByKey.get(key) ?? 0), 0);
6279
+ return bWeight - aWeight || a[0].localeCompare(b[0]);
6280
+ });
6281
+ return sortedGroups.map((nodeKeys, index) => {
6282
+ const center = layoutCommunity(index, sortedGroups.length);
6283
+ return {
6284
+ id: `community:${nodeKeys[0]}`,
6285
+ nodeKeys,
6286
+ color: COMMUNITY_COLORS[stableHash(nodeKeys[0]) % COMMUNITY_COLORS.length],
6287
+ x: center.x,
6288
+ y: center.y
6289
+ };
6290
+ });
6291
+ }
6292
+ function layoutCommunity(index, count) {
6293
+ if (index === 0 || count === 1) return { x: 0, y: 0 };
6294
+ const ringIndex = index - 1;
6295
+ const ring = Math.floor(ringIndex / 6);
6296
+ const position = ringIndex % 6;
6297
+ const radius = 380 + ring * 280;
6298
+ const angle = -Math.PI / 2 + position / 6 * Math.PI * 2 + ring * 0.31;
6299
+ return { x: roundNum2(Math.cos(angle) * radius, 2), y: roundNum2(Math.sin(angle) * radius, 2) };
6300
+ }
6301
+ function layoutNode(nodeKey, globalIndex, community, members, communityByNodeKey) {
6302
+ const communityMembers = members.filter((member) => communityByNodeKey.get(member.key)?.id === community.id);
6303
+ const localIndex = communityMembers.findIndex((member) => member.key === nodeKey);
6304
+ const angle = localIndex * 2.399963229728653 + stableHash(nodeKey) % 1e3 / 1e3;
6305
+ const radius = localIndex === 0 ? 0 : 54 + Math.sqrt(localIndex) * 42;
6306
+ const globalJitter = globalIndex % 3 * 2;
6307
+ return {
6308
+ x: roundNum2(community.x + Math.cos(angle) * (radius + globalJitter), 2),
6309
+ y: roundNum2(community.y + Math.sin(angle) * (radius + globalJitter), 2)
6310
+ };
6311
+ }
6312
+ function buildIncidentTotals(edges) {
6313
+ const totals = /* @__PURE__ */ new Map();
6314
+ for (const edge of edges) {
6315
+ for (const key of [edge.sourceKey, edge.targetKey]) {
6316
+ const item = totals.get(key) ?? createIncidentTotals();
6317
+ item.replyInteractionCount += edge.replyInteractionCount;
6318
+ item.mentionInteractionCount += edge.mentionInteractionCount;
6319
+ item.coOccurrenceCount += edge.coOccurrenceCount;
6320
+ item.coOccurrenceRawScore += edge.coOccurrenceRawScore;
6321
+ item.lastInteractionTs = maxTimestamp(item.lastInteractionTs, edge.lastInteractionTs);
6322
+ totals.set(key, item);
6323
+ }
6324
+ }
6325
+ return totals;
6326
+ }
6327
+ function createIncidentTotals() {
6328
+ return {
6329
+ replyInteractionCount: 0,
6330
+ mentionInteractionCount: 0,
6331
+ coOccurrenceCount: 0,
6332
+ coOccurrenceRawScore: 0,
6333
+ lastInteractionTs: null
6334
+ };
6335
+ }
6336
+ function normalizeLimits(options) {
6337
+ return {
6338
+ nodeLimit: normalizeLimit(options.nodeLimit, DEFAULT_LIMITS.nodeLimit),
6339
+ edgeLimit: normalizeLimit(options.edgeLimit, DEFAULT_LIMITS.edgeLimit),
6340
+ perNodeEdgeLimit: normalizeLimit(options.perNodeEdgeLimit, DEFAULT_LIMITS.perNodeEdgeLimit)
6341
+ };
6342
+ }
6343
+ function normalizeLimit(value, fallback) {
6344
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
6345
+ }
6346
+ function appendConditions(clause, conditions) {
6347
+ const existing = clause.trim().replace(/^WHERE\s+/i, "");
6348
+ return `WHERE ${[existing, ...conditions].filter(Boolean).join(" AND ")}`;
6349
+ }
6350
+ function emptyResult(totalMembers, activeMembers) {
6351
+ return {
6352
+ graph: { nodes: [], edges: [], communities: [] },
6353
+ members: [],
6354
+ edges: [],
6355
+ stats: { totalMembers, activeMembers, displayedMembers: 0, displayedEdges: 0, communityCount: 0 },
6356
+ algorithmVersion: GROUP_RELATIONSHIP_GALAXY_ALGORITHM_VERSION
6357
+ };
6358
+ }
6359
+ function compareEdges(a, b) {
6360
+ return b.weight - a.weight || a.id.localeCompare(b.id);
6361
+ }
6362
+ function memberKey(memberId) {
6363
+ return `member:${memberId}`;
6364
+ }
6365
+ function maxTimestamp(current, next) {
6366
+ if (next === null) return current;
6367
+ return Math.max(current ?? 0, next) || null;
6368
+ }
6369
+ function roundNum2(value, digits = 4) {
6370
+ const factor = 10 ** digits;
6371
+ return Math.round(value * factor) / factor;
6372
+ }
6373
+ function stableHash(value) {
6374
+ let hash = 2166136261;
6375
+ for (let index = 0; index < value.length; index++) {
6376
+ hash ^= value.charCodeAt(index);
6377
+ hash = Math.imul(hash, 16777619);
6378
+ }
6379
+ return hash >>> 0;
6380
+ }
6381
+
6007
6382
  // ../../packages/core/src/query/advanced/relationship.ts
6008
6383
  var ICE_BREAK_THRESHOLD = 24 * 60 * 60;
6009
6384
  var DEFAULT_PERSEVERANCE_THRESHOLD = 300;
6010
6385
  function getRelationshipStats(db, filter, options) {
6011
6386
  const perseveranceThreshold = options?.perseveranceThreshold ?? DEFAULT_PERSEVERANCE_THRESHOLD;
6012
- const emptyResult = {
6387
+ const emptyResult2 = {
6013
6388
  months: [],
6014
6389
  members: [],
6015
6390
  totalSessions: 0,
@@ -6021,7 +6396,7 @@ function getRelationshipStats(db, filter, options) {
6021
6396
  monthlyPerseverance: []
6022
6397
  };
6023
6398
  const sessionCount = db.prepare("SELECT COUNT(*) as count FROM segment").get();
6024
- if (!sessionCount || sessionCount.count === 0) return emptyResult;
6399
+ if (!sessionCount || sessionCount.count === 0) return emptyResult2;
6025
6400
  const timeConditions = [];
6026
6401
  const params = [];
6027
6402
  if (filter?.startTs !== void 0) {
@@ -6223,8 +6598,8 @@ function getJourneyStats(db, filter) {
6223
6598
  const indexAvailable = hasSessionIndex(db) && hasTable(db, "message_context");
6224
6599
  const segmentRows = indexAvailable ? querySegmentRows(db, effectiveFilter) : [];
6225
6600
  const segmentCountByMonth = /* @__PURE__ */ new Map();
6226
- for (const segment2 of segmentRows) {
6227
- segmentCountByMonth.set(segment2.startMonth, (segmentCountByMonth.get(segment2.startMonth) ?? 0) + 1);
6601
+ for (const segment of segmentRows) {
6602
+ segmentCountByMonth.set(segment.startMonth, (segmentCountByMonth.get(segment.startMonth) ?? 0) + 1);
6228
6603
  }
6229
6604
  const firstRow = monthlyRows[0];
6230
6605
  const lastRow = monthlyRows[monthlyRows.length - 1];
@@ -6754,7 +7129,7 @@ function getCheckInAnalysis(db, filter) {
6754
7129
  return computeCheckIn(dailyActivity);
6755
7130
  }
6756
7131
  function computeMemeBattle(messages) {
6757
- const emptyResult = {
7132
+ const emptyResult2 = {
6758
7133
  topBattles: [],
6759
7134
  rankByCount: [],
6760
7135
  rankByImageCount: [],
@@ -6788,7 +7163,7 @@ function computeMemeBattle(messages) {
6788
7163
  }
6789
7164
  }
6790
7165
  processChain();
6791
- if (battles.length === 0) return emptyResult;
7166
+ if (battles.length === 0) return emptyResult2;
6792
7167
  const topBattles = battles.map((battle) => ({
6793
7168
  startTime: battle.startTime,
6794
7169
  endTime: battle.endTime,
@@ -6867,7 +7242,7 @@ function getMemeBattleAnalysis(db, filter) {
6867
7242
  return computeMemeBattle(messages);
6868
7243
  }
6869
7244
  function computeNightOwl(messages) {
6870
- const emptyResult = {
7245
+ const emptyResult2 = {
6871
7246
  nightOwlRank: [],
6872
7247
  lastSpeakerRank: [],
6873
7248
  firstSpeakerRank: [],
@@ -6875,7 +7250,7 @@ function computeNightOwl(messages) {
6875
7250
  champions: [],
6876
7251
  totalDays: 0
6877
7252
  };
6878
- if (messages.length === 0) return emptyResult;
7253
+ if (messages.length === 0) return emptyResult2;
6879
7254
  function getNightOwlTitleByCount(count) {
6880
7255
  if (count === 0) return "\u517B\u751F\u8FBE\u4EBA";
6881
7256
  if (count <= 20) return "\u5076\u5C14\u5931\u7720";
@@ -7702,7 +8077,7 @@ function mergeMembers2(sources, collidingIds) {
7702
8077
  }
7703
8078
 
7704
8079
  // ../../packages/core/src/schema/tables.ts
7705
- var CURRENT_SCHEMA_VERSION = 8;
8080
+ var CURRENT_SCHEMA_VERSION = 9;
7706
8081
  var CHAT_DB_TABLES = `
7707
8082
  CREATE TABLE IF NOT EXISTS meta (
7708
8083
  name TEXT NOT NULL,
@@ -7776,13 +8151,6 @@ var CHAT_DB_INDEXES = `
7776
8151
  CREATE INDEX IF NOT EXISTS idx_context_segment ON message_context(segment_id);
7777
8152
  `;
7778
8153
  var CHAT_DB_SCHEMA = CHAT_DB_TABLES + CHAT_DB_INDEXES;
7779
- var FTS_TABLE_SCHEMA = `
7780
- CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts5(
7781
- content,
7782
- content='',
7783
- content_rowid=id
7784
- );
7785
- `;
7786
8154
 
7787
8155
  // ../../packages/core/src/schema/migrations.ts
7788
8156
  function getSchemaVersion(db) {
@@ -8040,282 +8408,30 @@ var appLogger = {
8040
8408
  getLogPath: () => instance?.getLogPath() ?? null
8041
8409
  };
8042
8410
 
8043
- // ../../packages/node-runtime/src/nlp/segmenter.ts
8044
- import * as fs8 from "fs";
8045
- import * as path13 from "path";
8046
- import { createRequire } from "module";
8047
- var _nlpDir = null;
8048
- var jiebaInstances = /* @__PURE__ */ new Map();
8049
- var require2 = createRequire(import.meta.url);
8050
- function initNlpDir(nlpDir) {
8051
- _nlpDir = nlpDir;
8052
- }
8053
- function getNlpDir() {
8054
- return _nlpDir;
8055
- }
8056
- function existsDictOnDisk(dictId) {
8057
- if (!_nlpDir) return false;
8058
- return fs8.existsSync(path13.join(_nlpDir, `${dictId}.dict`));
8059
- }
8060
- function tryLoadDictFromDisk(dictId) {
8061
- if (!_nlpDir) return null;
8062
- const dictPath = path13.join(_nlpDir, `${dictId}.dict`);
8063
- if (!fs8.existsSync(dictPath)) return null;
8064
- try {
8065
- return fs8.readFileSync(dictPath);
8066
- } catch {
8067
- return null;
8068
- }
8069
- }
8070
- function getJieba(dictType = "default") {
8071
- const effectiveType = dictType === "default" ? "zh-CN" : dictType;
8072
- const cached = jiebaInstances.get(effectiveType);
8073
- if (cached) {
8074
- if (cached.fromDiskDict === existsDictOnDisk(effectiveType)) return cached.instance;
8075
- jiebaInstances.delete(effectiveType);
8076
- console.error(`[NLP] jieba cache invalidated (dict ${cached.fromDiskDict ? "removed" : "added"}): ${effectiveType}`);
8077
- }
8078
- try {
8079
- const { Jieba } = require2("@node-rs/jieba");
8080
- let instance2;
8081
- const diskDict = tryLoadDictFromDisk(effectiveType);
8082
- if (diskDict) {
8083
- instance2 = Jieba.withDict(diskDict);
8084
- console.error(`[NLP] jieba dict loaded: ${effectiveType} (${diskDict.length} bytes)`);
8085
- } else {
8086
- instance2 = new Jieba();
8087
- console.warn(`[NLP] jieba dict missing: ${effectiveType}, fallback to built-in tokenizer`);
8088
- }
8089
- jiebaInstances.set(effectiveType, { instance: instance2, fromDiskDict: !!diskDict });
8090
- return instance2;
8091
- } catch (error) {
8092
- console.error(`[NLP] Failed to load jieba module (dict=${effectiveType}):`, error);
8093
- throw new Error(`jieba \u6A21\u5757\u52A0\u8F7D\u5931\u8D25 (${effectiveType})`);
8094
- }
8095
- }
8096
- function clearJiebaInstance(dictType) {
8097
- jiebaInstances.delete(dictType);
8098
- console.error(`[NLP] jieba instance cleared: ${dictType}`);
8099
- }
8100
- function segmentChinese(text, options = {}) {
8101
- const { posFilterMode = "meaningful", customPosTags, dictType = "default" } = options;
8102
- const cleaned = cleanText(text);
8103
- if (!cleaned) return [];
8104
- try {
8105
- const jieba = getJieba(dictType);
8106
- if (posFilterMode === "all") return jieba.cut(cleaned, false);
8107
- const tagged = jieba.tag(cleaned);
8108
- const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
8109
- return tagged.filter((item) => allowedTags.has(item.tag)).map((item) => item.word);
8110
- } catch (error) {
8111
- console.error("[NLP] Chinese segmentation failed:", error);
8112
- try {
8113
- return getJieba("default").cut(cleanText(text), false);
8114
- } catch {
8115
- return cleaned.split("");
8116
- }
8117
- }
8118
- }
8119
- function segmentEnglish(text) {
8120
- const cleaned = cleanText(text);
8121
- if (!cleaned) return [];
8122
- try {
8123
- const segmenter = new Intl.Segmenter("en", { granularity: "word" });
8124
- return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase());
8125
- } catch {
8126
- return cleaned.toLowerCase().split(/\s+/).filter((w) => w.length > 0);
8127
- }
8128
- }
8129
- function segmentJapanese(text) {
8130
- const cleaned = cleanText(text);
8131
- if (!cleaned) return [];
8132
- try {
8133
- const segmenter = new Intl.Segmenter("ja", { granularity: "word" });
8134
- return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment);
8135
- } catch {
8136
- return cleaned.split("").filter((ch) => ch.trim().length > 0);
8137
- }
8138
- }
8139
- function segment(text, locale, options = {}) {
8140
- const {
8141
- minLength,
8142
- posFilterMode = "meaningful",
8143
- customPosTags,
8144
- enableStopwords = true,
8145
- dictType = "default"
8146
- } = options;
8147
- const isChinese = locale.startsWith("zh");
8148
- const isJapanese = locale === "ja-JP";
8149
- const defaultMinLength = isChinese || isJapanese ? 2 : 3;
8150
- const effectiveMinLength = minLength ?? defaultMinLength;
8151
- let words;
8152
- if (isChinese) {
8153
- words = segmentChinese(text, { posFilterMode, customPosTags, dictType });
8154
- } else if (isJapanese) {
8155
- words = segmentJapanese(text);
8156
- } else {
8157
- words = segmentEnglish(text);
8158
- }
8159
- return words.filter((word) => isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword));
8160
- }
8161
- function batchSegmentWithFrequency(texts, locale, options = {}) {
8162
- const {
8163
- minLength,
8164
- minCount = 2,
8165
- topN = 100,
8166
- posFilterMode,
8167
- customPosTags,
8168
- enableStopwords,
8169
- dictType,
8170
- excludeWords
8171
- } = options;
8172
- const wordFrequency = /* @__PURE__ */ new Map();
8173
- const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
8174
- for (const text of texts) {
8175
- const words = segment(text, locale, { minLength, posFilterMode, customPosTags, enableStopwords, dictType });
8176
- for (const word of words) {
8177
- if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
8178
- wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
8179
- }
8180
- }
8181
- const filtered = /* @__PURE__ */ new Map();
8182
- let totalWords = 0;
8183
- for (const [word, count] of wordFrequency) {
8184
- if (count >= minCount) {
8185
- filtered.set(word, count);
8186
- totalWords += count;
8187
- }
8188
- }
8189
- const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
8190
- return { words: new Map(sorted), uniqueWords: filtered.size, totalWords };
8191
- }
8192
- function batchSegmentChineseWithStats(texts, locale, options = {}) {
8193
- const {
8194
- minLength,
8195
- minCount = 2,
8196
- topN = 100,
8197
- posFilterMode = "meaningful",
8198
- customPosTags,
8199
- enableStopwords = true,
8200
- dictType = "default",
8201
- excludeWords
8202
- } = options;
8203
- const effectiveMinLength = minLength ?? 2;
8204
- const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
8205
- const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
8206
- const wordFrequency = /* @__PURE__ */ new Map();
8207
- const posTagStats = /* @__PURE__ */ new Map();
8208
- try {
8209
- const jieba = getJieba(dictType);
8210
- for (const text of texts) {
8211
- const cleaned = cleanText(text);
8212
- if (!cleaned) continue;
8213
- for (const { tag, word } of jieba.tag(cleaned)) {
8214
- if (!isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword)) continue;
8215
- posTagStats.set(tag, (posTagStats.get(tag) || 0) + 1);
8216
- if (!allowedTags.has(tag)) continue;
8217
- if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
8218
- wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
8219
- }
8220
- }
8221
- } catch (error) {
8222
- console.error("[NLP] Chinese single-pass segmentation failed:", error);
8223
- }
8224
- const filtered = /* @__PURE__ */ new Map();
8225
- let totalWords = 0;
8226
- for (const [word, count] of wordFrequency) {
8227
- if (count >= minCount) {
8228
- filtered.set(word, count);
8229
- totalWords += count;
8230
- }
8231
- }
8232
- const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
8233
- return { words: new Map(sorted), uniqueWords: filtered.size, totalWords, posTagStats };
8234
- }
8235
- function collectPosTagStats(texts, minWordLength = 2, enableStopwords = true, dictType = "default") {
8236
- const posStats = /* @__PURE__ */ new Map();
8237
- try {
8238
- const jieba = getJieba(dictType);
8239
- for (const text of texts) {
8240
- const cleaned = cleanText(text);
8241
- if (!cleaned) continue;
8242
- const tagged = jieba.tag(cleaned);
8243
- for (const item of tagged) {
8244
- if (!isValidWord(item.word, "zh-CN", minWordLength, enableStopwords, isStopword)) continue;
8245
- posStats.set(item.tag, (posStats.get(item.tag) || 0) + 1);
8246
- }
8247
- }
8248
- } catch (error) {
8249
- console.error("[NLP] Failed to collect POS stats:", error);
8250
- }
8251
- return posStats;
8252
- }
8253
- function getPosTagDefinitions() {
8254
- return POS_TAG_DEFINITIONS;
8255
- }
8256
-
8257
- // ../../packages/node-runtime/src/nlp/fts-tokenizer.ts
8258
- function tokenizeForFts(text) {
8259
- if (!text || text.trim().length === 0) return "";
8260
- try {
8261
- const jieba = getJieba();
8262
- const tokens = jieba.cut(text, false);
8263
- return tokens.map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0).join(" ");
8264
- } catch {
8265
- return fallbackTokenize(text);
8266
- }
8267
- }
8268
- function fallbackTokenize(text) {
8269
- return text.toLowerCase().split(/\s+/).filter((t) => t.length > 0).join(" ");
8270
- }
8271
- function escapeToken(token) {
8272
- return `"${token.replace(/"/g, '""')}"`;
8273
- }
8274
- function tokenizeQueryForFts(keywords) {
8275
- if (keywords.length === 0) return "";
8276
- const groups = keywords.map((kw) => {
8277
- const trimmed = kw.trim();
8278
- if (!trimmed) return "";
8279
- try {
8280
- const jieba = getJieba();
8281
- const tokens = jieba.cut(trimmed, false).map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0);
8282
- if (tokens.length === 0) return "";
8283
- if (tokens.length === 1) return escapeToken(tokens[0]);
8284
- return tokens.map(escapeToken).join(" ");
8285
- } catch {
8286
- const simple = trimmed.toLowerCase().trim();
8287
- return simple ? escapeToken(simple) : "";
8288
- }
8289
- }).filter((g) => g.length > 0);
8290
- if (groups.length === 0) return "";
8291
- if (groups.length === 1) return groups[0];
8292
- return groups.map((g) => g.includes(" ") ? `(${g})` : g).join(" OR ");
8293
- }
8294
-
8295
8411
  // ../../packages/node-runtime/src/migrations/electron-data-migration.ts
8296
- import * as fs9 from "fs";
8412
+ import * as fs8 from "fs";
8297
8413
  import * as os3 from "os";
8298
- import * as path14 from "path";
8414
+ import * as path13 from "path";
8299
8415
  var SYSTEM_SUBDIRS = ["ai", "settings", "cache", "logs", "nlp"];
8300
8416
  var STORAGE_CONFIG_FILE = "storage.json";
8301
8417
  function getElectronUserDataDir(options = {}) {
8302
8418
  const home = options.homeDir ?? os3.homedir();
8303
8419
  switch (options.platform ?? process.platform) {
8304
8420
  case "darwin":
8305
- return path14.join(home, "Library", "Application Support", "ChatLab");
8421
+ return path13.join(home, "Library", "Application Support", "ChatLab");
8306
8422
  case "win32": {
8307
- const appData = options.appDataDir ?? process.env.APPDATA ?? path14.join(home, "AppData", "Roaming");
8308
- return path14.join(appData, "ChatLab");
8423
+ const appData = options.appDataDir ?? process.env.APPDATA ?? path13.join(home, "AppData", "Roaming");
8424
+ return path13.join(appData, "ChatLab");
8309
8425
  }
8310
8426
  default:
8311
- return path14.join(home, ".config", "ChatLab");
8427
+ return path13.join(home, ".config", "ChatLab");
8312
8428
  }
8313
8429
  }
8314
8430
  function readElectronStorageConfig(electronUserData) {
8315
- const configPath = path14.join(electronUserData, STORAGE_CONFIG_FILE);
8316
- if (!fs9.existsSync(configPath)) return {};
8431
+ const configPath = path13.join(electronUserData, STORAGE_CONFIG_FILE);
8432
+ if (!fs8.existsSync(configPath)) return {};
8317
8433
  try {
8318
- return JSON.parse(fs9.readFileSync(configPath, "utf-8"));
8434
+ return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
8319
8435
  } catch {
8320
8436
  return {};
8321
8437
  }
@@ -8323,45 +8439,45 @@ function readElectronStorageConfig(electronUserData) {
8323
8439
  function resolveElectronDataDir() {
8324
8440
  const electronUserData = getElectronUserDataDir();
8325
8441
  const storageConfig = readElectronStorageConfig(electronUserData);
8326
- if (storageConfig.dataDir && path14.isAbsolute(storageConfig.dataDir)) {
8442
+ if (storageConfig.dataDir && path13.isAbsolute(storageConfig.dataDir)) {
8327
8443
  return storageConfig.dataDir;
8328
8444
  }
8329
- return path14.join(electronUserData, "data");
8445
+ return path13.join(electronUserData, "data");
8330
8446
  }
8331
8447
  function hasDatabases(dir) {
8332
- const dbDir = path14.join(dir, "databases");
8333
- if (!fs9.existsSync(dbDir)) return false;
8448
+ const dbDir = path13.join(dir, "databases");
8449
+ if (!fs8.existsSync(dbDir)) return false;
8334
8450
  try {
8335
- return fs9.readdirSync(dbDir).some((f) => f.endsWith(".db"));
8451
+ return fs8.readdirSync(dbDir).some((f) => f.endsWith(".db"));
8336
8452
  } catch {
8337
8453
  return false;
8338
8454
  }
8339
8455
  }
8340
8456
  function ensureDir2(dirPath) {
8341
- if (!fs9.existsSync(dirPath)) {
8342
- fs9.mkdirSync(dirPath, { recursive: true });
8457
+ if (!fs8.existsSync(dirPath)) {
8458
+ fs8.mkdirSync(dirPath, { recursive: true });
8343
8459
  }
8344
8460
  }
8345
8461
  function copyDirMerge(src, dest) {
8346
8462
  const stats = { copied: 0, skipped: 0 };
8347
- if (!fs9.existsSync(src)) return stats;
8463
+ if (!fs8.existsSync(src)) return stats;
8348
8464
  ensureDir2(dest);
8349
8465
  let entries;
8350
8466
  try {
8351
- entries = fs9.readdirSync(src, { withFileTypes: true });
8467
+ entries = fs8.readdirSync(src, { withFileTypes: true });
8352
8468
  } catch {
8353
8469
  return stats;
8354
8470
  }
8355
8471
  for (const entry of entries) {
8356
- const srcPath = path14.join(src, entry.name);
8357
- const destPath = path14.join(dest, entry.name);
8472
+ const srcPath = path13.join(src, entry.name);
8473
+ const destPath = path13.join(dest, entry.name);
8358
8474
  try {
8359
8475
  if (entry.isDirectory()) {
8360
8476
  const sub = copyDirMerge(srcPath, destPath);
8361
8477
  stats.copied += sub.copied;
8362
8478
  stats.skipped += sub.skipped;
8363
- } else if (!fs9.existsSync(destPath)) {
8364
- fs9.copyFileSync(srcPath, destPath);
8479
+ } else if (!fs8.existsSync(destPath)) {
8480
+ fs8.copyFileSync(srcPath, destPath);
8365
8481
  stats.copied++;
8366
8482
  } else {
8367
8483
  stats.skipped++;
@@ -8374,22 +8490,22 @@ function copyDirMerge(src, dest) {
8374
8490
  function writeMigrationLog(logDir, message) {
8375
8491
  try {
8376
8492
  ensureDir2(logDir);
8377
- const logPath = path14.join(logDir, "app.log");
8493
+ const logPath = path13.join(logDir, "app.log");
8378
8494
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
8379
- fs9.appendFileSync(logPath, `[${ts}] [MIGRATION] ${message}
8495
+ fs8.appendFileSync(logPath, `[${ts}] [MIGRATION] ${message}
8380
8496
  `, "utf-8");
8381
8497
  } catch {
8382
8498
  }
8383
8499
  }
8384
8500
  function wasElectronUsed() {
8385
8501
  const electronUserData = getElectronUserDataDir();
8386
- if (!fs9.existsSync(electronUserData)) return false;
8387
- return fs9.existsSync(path14.join(electronUserData, "Preferences")) || fs9.existsSync(path14.join(electronUserData, "Local Storage")) || fs9.existsSync(path14.join(electronUserData, "Session Storage")) || fs9.existsSync(path14.join(electronUserData, "data"));
8502
+ if (!fs8.existsSync(electronUserData)) return false;
8503
+ return fs8.existsSync(path13.join(electronUserData, "Preferences")) || fs8.existsSync(path13.join(electronUserData, "Local Storage")) || fs8.existsSync(path13.join(electronUserData, "Session Storage")) || fs8.existsSync(path13.join(electronUserData, "data"));
8388
8504
  }
8389
8505
  function verifyCliDataPath(databaseDir) {
8390
- if (fs9.existsSync(databaseDir)) {
8506
+ if (fs8.existsSync(databaseDir)) {
8391
8507
  try {
8392
- if (fs9.readdirSync(databaseDir).some((f) => f.endsWith(".db"))) return true;
8508
+ if (fs8.readdirSync(databaseDir).some((f) => f.endsWith(".db"))) return true;
8393
8509
  } catch {
8394
8510
  }
8395
8511
  }
@@ -8407,9 +8523,9 @@ function migrateFromElectronIfNeeded(systemDir) {
8407
8523
  let totalSkipped = 0;
8408
8524
  const movedDirs = [];
8409
8525
  for (const subDir of SYSTEM_SUBDIRS) {
8410
- const srcDir = path14.join(electronDataDir, subDir);
8411
- const destDir = path14.join(systemDir, subDir);
8412
- if (!fs9.existsSync(srcDir)) continue;
8526
+ const srcDir = path13.join(electronDataDir, subDir);
8527
+ const destDir = path13.join(systemDir, subDir);
8528
+ if (!fs8.existsSync(srcDir)) continue;
8413
8529
  const { copied, skipped } = copyDirMerge(srcDir, destDir);
8414
8530
  if (copied > 0 || skipped > 0) {
8415
8531
  movedDirs.push(subDir);
@@ -8419,13 +8535,13 @@ function migrateFromElectronIfNeeded(systemDir) {
8419
8535
  }
8420
8536
  }
8421
8537
  for (const subDir of movedDirs) {
8422
- const srcDir = path14.join(electronDataDir, subDir);
8538
+ const srcDir = path13.join(electronDataDir, subDir);
8423
8539
  try {
8424
- fs9.rmSync(srcDir, { recursive: true, force: true });
8540
+ fs8.rmSync(srcDir, { recursive: true, force: true });
8425
8541
  } catch {
8426
8542
  }
8427
8543
  }
8428
- const logsDir = path14.join(systemDir, "logs");
8544
+ const logsDir = path13.join(systemDir, "logs");
8429
8545
  const summary = `CLI first-run: detected Electron data at ${electronDataDir}, pointed user_data_dir there, copied ${totalCopied} system files (${movedDirs.join(", ") || "none"})`;
8430
8546
  writeMigrationLog(logsDir, summary);
8431
8547
  console.log(`[Migration] ${summary}`);
@@ -8439,7 +8555,7 @@ function migrateFromElectronIfNeeded(systemDir) {
8439
8555
  const errorMsg = error instanceof Error ? error.message : String(error);
8440
8556
  console.error("[Migration] Electron data migration failed:", errorMsg);
8441
8557
  try {
8442
- writeMigrationLog(path14.join(systemDir, "logs"), `Electron migration failed: ${errorMsg}`);
8558
+ writeMigrationLog(path13.join(systemDir, "logs"), `Electron migration failed: ${errorMsg}`);
8443
8559
  } catch {
8444
8560
  }
8445
8561
  return { migrated: false, electronDetected: true, error: errorMsg };
@@ -8447,26 +8563,26 @@ function migrateFromElectronIfNeeded(systemDir) {
8447
8563
  }
8448
8564
 
8449
8565
  // ../../packages/node-runtime/src/cache/session-cache.ts
8450
- import * as fs10 from "fs";
8451
- import * as path15 from "path";
8566
+ import * as fs9 from "fs";
8567
+ import * as path14 from "path";
8452
8568
  function getCachePath(sessionId, cacheDir) {
8453
- return path15.join(cacheDir, `${sessionId}.cache.json`);
8569
+ return path14.join(cacheDir, `${sessionId}.cache.json`);
8454
8570
  }
8455
8571
  function readCacheFile(cachePath) {
8456
8572
  try {
8457
- if (!fs10.existsSync(cachePath)) return null;
8458
- return JSON.parse(fs10.readFileSync(cachePath, "utf-8"));
8573
+ if (!fs9.existsSync(cachePath)) return null;
8574
+ return JSON.parse(fs9.readFileSync(cachePath, "utf-8"));
8459
8575
  } catch {
8460
8576
  return null;
8461
8577
  }
8462
8578
  }
8463
8579
  function writeCacheFile(cachePath, content) {
8464
8580
  try {
8465
- const dir = path15.dirname(cachePath);
8466
- if (!fs10.existsSync(dir)) {
8467
- fs10.mkdirSync(dir, { recursive: true });
8581
+ const dir = path14.dirname(cachePath);
8582
+ if (!fs9.existsSync(dir)) {
8583
+ fs9.mkdirSync(dir, { recursive: true });
8468
8584
  }
8469
- fs10.writeFileSync(cachePath, JSON.stringify(content), "utf-8");
8585
+ fs9.writeFileSync(cachePath, JSON.stringify(content), "utf-8");
8470
8586
  } catch {
8471
8587
  }
8472
8588
  }
@@ -8486,7 +8602,7 @@ function invalidateCache(sessionId, cacheDir, key) {
8486
8602
  const cachePath = getCachePath(sessionId, cacheDir);
8487
8603
  try {
8488
8604
  if (!key) {
8489
- if (fs10.existsSync(cachePath)) fs10.unlinkSync(cachePath);
8605
+ if (fs9.existsSync(cachePath)) fs9.unlinkSync(cachePath);
8490
8606
  } else {
8491
8607
  const file = readCacheFile(cachePath);
8492
8608
  if (file && file[key]) {
@@ -8500,7 +8616,7 @@ function invalidateCache(sessionId, cacheDir, key) {
8500
8616
  function deleteSessionCache(sessionId, cacheDir) {
8501
8617
  const cachePath = getCachePath(sessionId, cacheDir);
8502
8618
  try {
8503
- if (fs10.existsSync(cachePath)) fs10.unlinkSync(cachePath);
8619
+ if (fs9.existsSync(cachePath)) fs9.unlinkSync(cachePath);
8504
8620
  } catch {
8505
8621
  }
8506
8622
  }
@@ -8554,8 +8670,8 @@ function computeAndSetMembersCache(db, sessionId, cacheDir) {
8554
8670
  }
8555
8671
 
8556
8672
  // ../../packages/node-runtime/src/data-dir-compat.ts
8557
- import fs11 from "fs";
8558
- import path16 from "path";
8673
+ import fs10 from "fs";
8674
+ import path15 from "path";
8559
8675
  var DataDirCompatibilityError = class extends Error {
8560
8676
  code;
8561
8677
  userDataDir;
@@ -8575,10 +8691,10 @@ var DataDirCompatibilityError = class extends Error {
8575
8691
  };
8576
8692
  function readDataDirCompatibilityMeta(userDataDir) {
8577
8693
  const metaPath = getMetaPath(userDataDir);
8578
- if (!fs11.existsSync(metaPath)) return null;
8694
+ if (!fs10.existsSync(metaPath)) return null;
8579
8695
  let parsed;
8580
8696
  try {
8581
- parsed = JSON.parse(fs11.readFileSync(metaPath, "utf-8"));
8697
+ parsed = JSON.parse(fs10.readFileSync(metaPath, "utf-8"));
8582
8698
  } catch (error) {
8583
8699
  throw createInvalidMetaError(userDataDir, metaPath, "Invalid data directory compatibility meta JSON.", error);
8584
8700
  }
@@ -8661,7 +8777,7 @@ function raiseDataDirMinRuntimeVersion(pathProvider, input) {
8661
8777
  return nextMeta;
8662
8778
  }
8663
8779
  function getMetaPath(userDataDir) {
8664
- return path16.join(userDataDir, ".chatlab-meta.json");
8780
+ return path15.join(userDataDir, ".chatlab-meta.json");
8665
8781
  }
8666
8782
  function validateDataDirCompatibilityMeta(value, userDataDir, metaPath) {
8667
8783
  if (!isRecord2(value)) {
@@ -8732,15 +8848,15 @@ function mergeReasons(existing, next) {
8732
8848
  return [.../* @__PURE__ */ new Set([...existing, next])];
8733
8849
  }
8734
8850
  function writeMetaAtomic(userDataDir, metaPath, meta) {
8735
- fs11.mkdirSync(userDataDir, { recursive: true });
8736
- const tempPath = path16.join(userDataDir, `.chatlab-meta.json.${process.pid}.${Date.now()}.tmp`);
8851
+ fs10.mkdirSync(userDataDir, { recursive: true });
8852
+ const tempPath = path15.join(userDataDir, `.chatlab-meta.json.${process.pid}.${Date.now()}.tmp`);
8737
8853
  try {
8738
- fs11.writeFileSync(tempPath, `${JSON.stringify(meta, null, 2)}
8854
+ fs10.writeFileSync(tempPath, `${JSON.stringify(meta, null, 2)}
8739
8855
  `, "utf-8");
8740
- fs11.renameSync(tempPath, metaPath);
8856
+ fs10.renameSync(tempPath, metaPath);
8741
8857
  } catch (error) {
8742
8858
  try {
8743
- if (fs11.existsSync(tempPath)) fs11.unlinkSync(tempPath);
8859
+ if (fs10.existsSync(tempPath)) fs10.unlinkSync(tempPath);
8744
8860
  } catch {
8745
8861
  }
8746
8862
  throw error;
@@ -8777,7 +8893,7 @@ function raiseChatDbCompatibilityGate(pathProvider, runtime) {
8777
8893
  });
8778
8894
  }
8779
8895
  }
8780
- function getChatDbMigrations(deps) {
8896
+ function getChatDbMigrations() {
8781
8897
  const hasColumn2 = (db, tableName, columnName) => {
8782
8898
  const tableInfo = db.pragma(`table_info(${tableName})`);
8783
8899
  return tableInfo.some((col) => col.name === columnName);
@@ -8856,39 +8972,8 @@ function getChatDbMigrations(deps) {
8856
8972
  },
8857
8973
  {
8858
8974
  version: 4,
8859
- description: "Add FTS5 full-text search index",
8860
- up: (db) => {
8861
- const hasTable2 = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'").get();
8862
- if (hasTable2) return;
8863
- db.exec(`
8864
- CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts5(
8865
- content,
8866
- content='',
8867
- content_rowid=id
8868
- )
8869
- `);
8870
- const tokenize = deps?.tokenizeForFts;
8871
- if (!tokenize) return;
8872
- const BATCH_SIZE = 5e3;
8873
- const insertFts = db.prepare("INSERT INTO message_fts(rowid, content) VALUES (?, ?)");
8874
- const countRow = db.prepare("SELECT COUNT(*) as total FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''").get();
8875
- const total = countRow?.total ?? 0;
8876
- let offset = 0;
8877
- while (offset < total) {
8878
- const rows = db.prepare(
8879
- `SELECT id, content FROM message
8880
- WHERE type = 0 AND content IS NOT NULL AND content != ''
8881
- ORDER BY id ASC LIMIT ? OFFSET ?`
8882
- ).all(BATCH_SIZE, offset);
8883
- if (rows.length === 0) break;
8884
- for (const row of rows) {
8885
- const tokens = tokenize(row.content);
8886
- if (tokens) {
8887
- insertFts.run(row.id, tokens);
8888
- }
8889
- }
8890
- offset += BATCH_SIZE;
8891
- }
8975
+ description: "Preserve the legacy schema migration sequence",
8976
+ up: () => {
8892
8977
  }
8893
8978
  },
8894
8979
  {
@@ -9076,49 +9161,56 @@ function getChatDbMigrations(deps) {
9076
9161
  CREATE INDEX IF NOT EXISTS idx_message_reply_to ON message(reply_to_message_id);
9077
9162
  `);
9078
9163
  }
9164
+ },
9165
+ {
9166
+ version: 9,
9167
+ description: "Remove the obsolete per-session full-text index",
9168
+ up: (db) => {
9169
+ db.exec("DROP TABLE IF EXISTS message_fts");
9170
+ }
9079
9171
  }
9080
9172
  ];
9081
9173
  }
9082
9174
 
9083
9175
  // ../../packages/node-runtime/src/services/contacts/paths.ts
9084
- import path17 from "path";
9176
+ import path16 from "path";
9085
9177
  var CONTACTS_DIR_NAME = "contacts";
9086
9178
  var CONTACTS_FACTS_DIR_NAME = "facts";
9087
9179
  function getContactsDir(userDataDir) {
9088
- return path17.join(userDataDir, CONTACTS_DIR_NAME);
9180
+ return path16.join(userDataDir, CONTACTS_DIR_NAME);
9089
9181
  }
9090
9182
  function getContactsFactsCacheDir(userDataDir) {
9091
- return path17.join(getContactsDir(userDataDir), CONTACTS_FACTS_DIR_NAME);
9183
+ return path16.join(getContactsDir(userDataDir), CONTACTS_FACTS_DIR_NAME);
9092
9184
  }
9093
9185
 
9094
9186
  // ../../packages/node-runtime/src/services/global-insight/paths.ts
9095
- import path18 from "path";
9187
+ import path17 from "path";
9096
9188
  function getGlobalInsightDir(userDataDir) {
9097
- return path18.join(userDataDir, "insight", "annual-summary");
9189
+ return path17.join(userDataDir, "insight", "annual-summary");
9098
9190
  }
9099
9191
  function getGlobalInsightFactsCacheDir(userDataDir) {
9100
- return path18.join(getGlobalInsightDir(userDataDir), "facts");
9192
+ return path17.join(getGlobalInsightDir(userDataDir), "facts");
9101
9193
  }
9102
9194
 
9103
9195
  // ../../packages/node-runtime/src/services/people/relationships/paths.ts
9104
- import path19 from "path";
9196
+ import path18 from "path";
9105
9197
  var PEOPLE_DIR_NAME = "people";
9106
9198
  var RELATIONSHIPS_DIR_NAME = "relationships";
9107
9199
  var RELATIONSHIPS_FACTS_DIR_NAME = "facts";
9108
9200
  function getPeopleRelationshipsDir(userDataDir) {
9109
- return path19.join(userDataDir, PEOPLE_DIR_NAME, RELATIONSHIPS_DIR_NAME);
9201
+ return path18.join(userDataDir, PEOPLE_DIR_NAME, RELATIONSHIPS_DIR_NAME);
9110
9202
  }
9111
9203
  function getPeopleRelationshipsFactsCacheDir(userDataDir) {
9112
- return path19.join(getPeopleRelationshipsDir(userDataDir), RELATIONSHIPS_FACTS_DIR_NAME);
9204
+ return path18.join(getPeopleRelationshipsDir(userDataDir), RELATIONSHIPS_FACTS_DIR_NAME);
9113
9205
  }
9114
9206
 
9115
9207
  // ../../packages/node-runtime/src/database-manager.ts
9116
- import * as fs13 from "fs";
9117
- import * as path21 from "path";
9208
+ import * as fs12 from "fs";
9209
+ import * as path20 from "path";
9118
9210
 
9119
9211
  // ../../packages/node-runtime/src/services/global-insight/snapshot.ts
9120
- import fs12 from "fs";
9121
- import path20 from "path";
9212
+ import fs11 from "fs";
9213
+ import path19 from "path";
9122
9214
 
9123
9215
  // ../../packages/node-runtime/src/services/global-insight/time-range.ts
9124
9216
  function normalizeAnnualSummaryRange(input, nowInput = /* @__PURE__ */ new Date()) {
@@ -9169,17 +9261,17 @@ function toAnnualSummaryLocalDate(range) {
9169
9261
  // ../../packages/node-runtime/src/services/global-insight/snapshot.ts
9170
9262
  var SNAPSHOT_TMP_PREFIX = "annual-summary.tmp-";
9171
9263
  function getAnnualSummarySnapshotPath(snapshotDir, range) {
9172
- return path20.join(snapshotDir, `annual-summary-${toAnnualSummaryRangeKey(range)}.json`);
9264
+ return path19.join(snapshotDir, `annual-summary-${toAnnualSummaryRangeKey(range)}.json`);
9173
9265
  }
9174
9266
  function readAnnualSummarySnapshot(snapshotDir, range, options = {}) {
9175
9267
  const snapshotPath = getAnnualSummarySnapshotPath(snapshotDir, range);
9176
- if (!fs12.existsSync(snapshotPath)) return null;
9268
+ if (!fs11.existsSync(snapshotPath)) return null;
9177
9269
  try {
9178
- return JSON.parse(fs12.readFileSync(snapshotPath, "utf-8"));
9270
+ return JSON.parse(fs11.readFileSync(snapshotPath, "utf-8"));
9179
9271
  } catch (error) {
9180
- const backupPath = path20.join(snapshotDir, `annual-summary.corrupt-${options.now?.() ?? Date.now()}.json`);
9272
+ const backupPath = path19.join(snapshotDir, `annual-summary.corrupt-${options.now?.() ?? Date.now()}.json`);
9181
9273
  try {
9182
- fs12.renameSync(snapshotPath, backupPath);
9274
+ fs11.renameSync(snapshotPath, backupPath);
9183
9275
  } catch (renameError) {
9184
9276
  appLogger.warn("global-insight", "failed to backup corrupt annual summary snapshot", renameError);
9185
9277
  }
@@ -9188,28 +9280,28 @@ function readAnnualSummarySnapshot(snapshotDir, range, options = {}) {
9188
9280
  }
9189
9281
  }
9190
9282
  function writeAnnualSummarySnapshot(snapshotDir, snapshot) {
9191
- fs12.mkdirSync(snapshotDir, { recursive: true });
9192
- const tmpPath = path20.join(snapshotDir, `${SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
9193
- fs12.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
9194
- fs12.renameSync(tmpPath, getAnnualSummarySnapshotPath(snapshotDir, snapshot.range));
9283
+ fs11.mkdirSync(snapshotDir, { recursive: true });
9284
+ const tmpPath = path19.join(snapshotDir, `${SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
9285
+ fs11.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
9286
+ fs11.renameSync(tmpPath, getAnnualSummarySnapshotPath(snapshotDir, snapshot.range));
9195
9287
  }
9196
9288
  function cleanupAnnualSummarySnapshotTempFiles(snapshotDir) {
9197
- if (!fs12.existsSync(snapshotDir)) return;
9198
- for (const name of fs12.readdirSync(snapshotDir)) {
9289
+ if (!fs11.existsSync(snapshotDir)) return;
9290
+ for (const name of fs11.readdirSync(snapshotDir)) {
9199
9291
  if (!name.startsWith(SNAPSHOT_TMP_PREFIX)) continue;
9200
9292
  try {
9201
- fs12.rmSync(path20.join(snapshotDir, name), { force: true });
9293
+ fs11.rmSync(path19.join(snapshotDir, name), { force: true });
9202
9294
  } catch (error) {
9203
9295
  appLogger.warn("global-insight", "failed to remove annual summary snapshot temp file", error);
9204
9296
  }
9205
9297
  }
9206
9298
  }
9207
9299
  function deleteAnnualSummarySnapshots(snapshotDir) {
9208
- if (!fs12.existsSync(snapshotDir)) return;
9209
- for (const name of fs12.readdirSync(snapshotDir)) {
9300
+ if (!fs11.existsSync(snapshotDir)) return;
9301
+ for (const name of fs11.readdirSync(snapshotDir)) {
9210
9302
  if (!name.startsWith("annual-summary-") && !name.startsWith("annual-summary.")) continue;
9211
9303
  try {
9212
- fs12.rmSync(path20.join(snapshotDir, name), { force: true });
9304
+ fs11.rmSync(path19.join(snapshotDir, name), { force: true });
9213
9305
  } catch (error) {
9214
9306
  appLogger.warn("global-insight", "failed to remove annual summary snapshot", error);
9215
9307
  }
@@ -9217,15 +9309,9 @@ function deleteAnnualSummarySnapshots(snapshotDir) {
9217
9309
  }
9218
9310
 
9219
9311
  // ../../packages/node-runtime/src/database-manager.ts
9220
- function createMigrationDeps(overrides) {
9221
- return {
9222
- tokenizeForFts,
9223
- ...overrides
9224
- };
9225
- }
9226
9312
  function listDatabaseCandidateIds(databaseDir) {
9227
- if (!fs13.existsSync(databaseDir)) return [];
9228
- return fs13.readdirSync(databaseDir).filter((fileName) => fileName.endsWith(".db") && !fileName.startsWith("._")).map((fileName) => fileName.slice(0, -3));
9313
+ if (!fs12.existsSync(databaseDir)) return [];
9314
+ return fs12.readdirSync(databaseDir).filter((fileName) => fileName.endsWith(".db") && !fileName.startsWith("._")).map((fileName) => fileName.slice(0, -3));
9229
9315
  }
9230
9316
  var DatabaseManager = class {
9231
9317
  constructor(pathProvider, options = {}) {
@@ -9234,13 +9320,11 @@ var DatabaseManager = class {
9234
9320
  throw new Error("DatabaseManager runtime identity is required for data directory compatibility checks.");
9235
9321
  }
9236
9322
  this.nativeBinding = options?.nativeBinding;
9237
- this.migrationDeps = createMigrationDeps(options?.migrationDeps);
9238
9323
  this.runtime = options?.runtime ?? null;
9239
9324
  }
9240
9325
  pathProvider;
9241
9326
  cache = /* @__PURE__ */ new Map();
9242
9327
  nativeBinding;
9243
- migrationDeps;
9244
9328
  runtime;
9245
9329
  /**
9246
9330
  * Open a session DB (read-only by default, cached).
@@ -9251,7 +9335,7 @@ var DatabaseManager = class {
9251
9335
  return this.cache.get(sessionId);
9252
9336
  }
9253
9337
  const dbPath = this.getDbPath(sessionId);
9254
- if (!fs13.existsSync(dbPath)) return null;
9338
+ if (!fs12.existsSync(dbPath)) return null;
9255
9339
  this.migrateIfNeeded(dbPath);
9256
9340
  this.assertCompatible();
9257
9341
  const adapter = openBetterSqliteDatabase(dbPath, {
@@ -9278,7 +9362,7 @@ var DatabaseManager = class {
9278
9362
  nativeBinding: this.nativeBinding
9279
9363
  });
9280
9364
  try {
9281
- const migrations = getChatDbMigrations(this.migrationDeps);
9365
+ const migrations = getChatDbMigrations();
9282
9366
  this.runMigrations(adapter, migrations);
9283
9367
  } finally {
9284
9368
  adapter.close();
@@ -9297,13 +9381,13 @@ var DatabaseManager = class {
9297
9381
  this.cache.delete(sessionId);
9298
9382
  }
9299
9383
  const dbPath = this.getDbPath(sessionId);
9300
- if (!fs13.existsSync(dbPath)) return null;
9384
+ if (!fs12.existsSync(dbPath)) return null;
9301
9385
  const adapter = openBetterSqliteDatabase(dbPath, {
9302
9386
  readonly: false,
9303
9387
  nativeBinding: this.nativeBinding
9304
9388
  });
9305
9389
  if (needsMigration(adapter, CURRENT_SCHEMA_VERSION)) {
9306
- const migrations = getChatDbMigrations(this.migrationDeps);
9390
+ const migrations = getChatDbMigrations();
9307
9391
  this.runMigrations(adapter, migrations);
9308
9392
  this.assertCompatible();
9309
9393
  } else {
@@ -9365,17 +9449,17 @@ var DatabaseManager = class {
9365
9449
  * 获取数据库文件路径
9366
9450
  */
9367
9451
  getDbPath(sessionId) {
9368
- return path21.join(this.pathProvider.getDatabaseDir(), `${sessionId}.db`);
9452
+ return path20.join(this.pathProvider.getDatabaseDir(), `${sessionId}.db`);
9369
9453
  }
9370
9454
  openRawSessionDatabase(sessionId, options = {}) {
9371
9455
  this.assertCompatible();
9372
9456
  const dbPath = this.getDbPath(sessionId);
9373
- if (!options.create && !fs13.existsSync(dbPath)) {
9457
+ if (!options.create && !fs12.existsSync(dbPath)) {
9374
9458
  throw new Error(`Session database not found: ${sessionId}`);
9375
9459
  }
9376
9460
  if (!options.readonly) {
9377
9461
  this.close(sessionId);
9378
- fs13.mkdirSync(path21.dirname(dbPath), { recursive: true });
9462
+ fs12.mkdirSync(path20.dirname(dbPath), { recursive: true });
9379
9463
  }
9380
9464
  const adapter = openBetterSqliteDatabase(dbPath, {
9381
9465
  readonly: options.readonly ?? false,
@@ -9393,17 +9477,17 @@ var DatabaseManager = class {
9393
9477
  deleteSessionDatabaseFiles(sessionId) {
9394
9478
  this.close(sessionId);
9395
9479
  const dbPath = this.getDbPath(sessionId);
9396
- const existed = ["", "-wal", "-shm"].some((suffix) => fs13.existsSync(dbPath + suffix));
9480
+ const existed = ["", "-wal", "-shm"].some((suffix) => fs12.existsSync(dbPath + suffix));
9397
9481
  for (const suffix of ["", "-wal", "-shm"]) {
9398
9482
  try {
9399
9483
  const filePath = dbPath + suffix;
9400
- if (fs13.existsSync(filePath)) fs13.unlinkSync(filePath);
9484
+ if (fs12.existsSync(filePath)) fs12.unlinkSync(filePath);
9401
9485
  } catch {
9402
9486
  }
9403
9487
  }
9404
9488
  const cacheDir = this.pathProvider.getCacheDir();
9405
9489
  deleteSessionCache(sessionId, cacheDir);
9406
- deleteSessionCache(sessionId, path21.join(cacheDir, "query"));
9490
+ deleteSessionCache(sessionId, path20.join(cacheDir, "query"));
9407
9491
  deleteSessionCache(sessionId, getContactsFactsCacheDir(this.pathProvider.getUserDataDir()));
9408
9492
  deleteSessionCache(sessionId, getGlobalInsightFactsCacheDir(this.pathProvider.getUserDataDir()));
9409
9493
  deleteAnnualSummarySnapshots(getGlobalInsightDir(this.pathProvider.getUserDataDir()));
@@ -9444,12 +9528,12 @@ function shouldRepairCompatibilityGate(schemaVersion) {
9444
9528
  }
9445
9529
 
9446
9530
  // ../../packages/node-runtime/src/cache/analytics-cache.ts
9447
- import * as fs14 from "fs";
9531
+ import * as fs13 from "fs";
9448
9532
  function getDbFileVersion(dbPath) {
9449
9533
  const parts = [];
9450
9534
  for (const p of [dbPath, `${dbPath}-wal`]) {
9451
9535
  try {
9452
- const st = fs14.statSync(p);
9536
+ const st = fs13.statSync(p);
9453
9537
  parts.push(`${Math.floor(st.mtimeMs)}:${st.size}`);
9454
9538
  } catch {
9455
9539
  parts.push("-");
@@ -9605,9 +9689,10 @@ export {
9605
9689
  POS_TAG_DEFINITIONS,
9606
9690
  MEANINGFUL_POS_TAGS,
9607
9691
  isStopword,
9692
+ cleanText,
9693
+ isValidWord,
9608
9694
  buildExcludeKeywordsConditions,
9609
9695
  getMentionAnalysis,
9610
- getMentionGraph,
9611
9696
  getLaughAnalysis,
9612
9697
  getClusterGraph,
9613
9698
  resolveOwnerMember,
@@ -9654,6 +9739,8 @@ export {
9654
9739
  deleteMembers,
9655
9740
  deleteMember,
9656
9741
  getCatchphraseAnalysis,
9742
+ GROUP_RELATIONSHIP_GALAXY_ALGORITHM_VERSION,
9743
+ getGroupRelationshipGalaxy,
9657
9744
  getRelationshipStats,
9658
9745
  getJourneyStats,
9659
9746
  getLanguagePreferenceAnalysis,
@@ -9683,28 +9770,17 @@ export {
9683
9770
  generateMessageKey,
9684
9771
  writeParseResultToDb,
9685
9772
  getCollidingPlatformIds,
9773
+ getCollidingPlatformIdsFromMessages,
9686
9774
  normalizePlatformId,
9687
9775
  detectConflictsInMessages,
9688
9776
  mergeMembers2,
9689
9777
  CHAT_DB_INDEXES,
9690
9778
  CHAT_DB_SCHEMA,
9691
- FTS_TABLE_SCHEMA,
9692
9779
  AiLogger,
9693
9780
  extractErrorInfo,
9694
9781
  extractErrorStack,
9695
9782
  initAppLogger,
9696
9783
  appLogger,
9697
- initNlpDir,
9698
- getNlpDir,
9699
- getJieba,
9700
- clearJiebaInstance,
9701
- segment,
9702
- batchSegmentWithFrequency,
9703
- batchSegmentChineseWithStats,
9704
- collectPosTagStats,
9705
- getPosTagDefinitions,
9706
- tokenizeForFts,
9707
- tokenizeQueryForFts,
9708
9784
  wasElectronUsed,
9709
9785
  verifyCliDataPath,
9710
9786
  migrateFromElectronIfNeeded,
@@ -9747,4 +9823,4 @@ export {
9747
9823
  StaticPathProvider,
9748
9824
  createDatabaseManagerAdapter
9749
9825
  };
9750
- //# sourceMappingURL=chunk-INBJ7SND.mjs.map
9826
+ //# sourceMappingURL=chunk-22YJOBA3.mjs.map