chatlab-cli 0.26.2 → 0.26.3

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 (70) hide show
  1. package/dist/{chat-command-BVIG2LCO.mjs → chat-command-D37MMEJ7.mjs} +4 -4
  2. package/dist/{chunk-6AOEMUCM.mjs → chunk-742TAKEP.mjs} +2 -2
  3. package/dist/{chunk-3XFHIQIW.mjs → chunk-D5MWER7N.mjs} +2 -2
  4. package/dist/{chunk-JFSTXSX2.mjs → chunk-GNGZHBKM.mjs} +3 -3
  5. package/dist/{chunk-GTXX72AQ.mjs → chunk-K5SGXTVJ.mjs} +13 -13
  6. package/dist/{chunk-GBQQJJKK.mjs → chunk-QMXNJRTT.mjs} +372 -173
  7. package/dist/chunk-QMXNJRTT.mjs.map +1 -0
  8. package/dist/{chunk-SCE4YL3M.mjs → chunk-RMITK4IC.mjs} +2 -2
  9. package/dist/{chunk-QKQRV7RV.mjs → chunk-VCN64OD6.mjs} +2 -2
  10. package/dist/{chunk-TMZC3SNX.mjs → chunk-YPTJVTVJ.mjs} +1037 -49
  11. package/dist/chunk-YPTJVTVJ.mjs.map +1 -0
  12. package/dist/cli.mjs +3 -3
  13. package/dist/{http-NLFLQDI7.mjs → http-6F6TYWCI.mjs} +8 -8
  14. package/dist/{import-ILK6MGYR.mjs → import-VYPOMSSA.mjs} +4 -4
  15. package/dist/index.mjs +8 -8
  16. package/dist/{mcp-FOLR5XJL.mjs → mcp-AR7L5FA3.mjs} +4 -4
  17. package/dist/{src-OQN3VE73.mjs → src-6S7JPKHX.mjs} +6 -2
  18. package/dist/{stream-import-F5QI2HTO.mjs → stream-import-KZFC3XT2.mjs} +3 -3
  19. package/dist/{update-checker-JIBA7T4U.mjs → update-checker-3N7HQEP2.mjs} +36 -12
  20. package/dist/update-checker-3N7HQEP2.mjs.map +1 -0
  21. package/dist-web/assets/{DebugToolsPanel-CrqULdXp.css → DebugToolsPanel-DzZmSzIU.css} +1 -1
  22. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-BTdqmm0y.js +107 -0
  23. package/dist-web/assets/{MessageExportModal-ttP_JPqO.js → MessageExportModal-CF6yaor7.js} +1 -1
  24. package/dist-web/assets/{electron-BeJHY587.js → electron-DKVw61Ww.js} +1 -1
  25. package/dist-web/assets/{fetch-H4B1CK_T.js → fetch-BC8jyJ_U.js} +1 -1
  26. package/dist-web/assets/{fetch-4s20GRbb.js → fetch-BMdgfBVS.js} +1 -1
  27. package/dist-web/assets/fetch-BQlEQsqi.js +5 -0
  28. package/dist-web/assets/{fetch-DtXUkazy.js → fetch-Bh6HC-IR.js} +1 -1
  29. package/dist-web/assets/{fetch-DBJ7Xkjt.js → fetch-BvVfWR-P.js} +1 -1
  30. package/dist-web/assets/{fetch-DW8r_JmP.js → fetch-CPeSfqsU.js} +1 -1
  31. package/dist-web/assets/fetch-CUw-PUYP.js +1 -0
  32. package/dist-web/assets/{fetch-B7USXx9R.js → fetch-CpYSD089.js} +1 -1
  33. package/dist-web/assets/{fetch-rtQEc8Bu.js → fetch-D2jNBqs8.js} +1 -1
  34. package/dist-web/assets/{fetch-BETFYYe1.js → fetch-DqEMAea3.js} +1 -1
  35. package/dist-web/assets/{index-Dov-rTKE.js → index-BHQnAj6l.js} +1 -1
  36. package/dist-web/assets/{index-CsxrXMkt.js → index-BwzuCu5R.js} +2 -2
  37. package/dist-web/assets/index-CG746E15.js +64 -0
  38. package/dist-web/assets/index-CXoQ_U22.js +173 -0
  39. package/dist-web/assets/index-DqybGdJH.css +1 -0
  40. package/dist-web/assets/{index-CjnMyejM.css → index-HgIuP1Td.css} +1 -1
  41. package/dist-web/assets/{index-CYMTvtEI.js → index-Qh0XLt3R.js} +1 -1
  42. package/dist-web/assets/{index-Dtm2oajn.css → index-R82N2_0b.css} +1 -1
  43. package/dist-web/assets/index-wPAmuQZY.js +11 -0
  44. package/dist-web/assets/{web-1-WNRDnP.js → web-Bx2TiHDk.js} +1 -1
  45. package/dist-web/favicon.ico +0 -0
  46. package/dist-web/index.html +3 -3
  47. package/package.json +1 -1
  48. package/dist/chunk-GBQQJJKK.mjs.map +0 -1
  49. package/dist/chunk-TMZC3SNX.mjs.map +0 -1
  50. package/dist/update-checker-JIBA7T4U.mjs.map +0 -1
  51. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-D9mCNaYM.js +0 -182
  52. package/dist-web/assets/electron-Dbml-SPN.js +0 -1
  53. package/dist-web/assets/fetch-DcEBAz_3.js +0 -1
  54. package/dist-web/assets/fetch-DsCNr_M0.js +0 -1
  55. package/dist-web/assets/index-C2ts-hTr.js +0 -173
  56. package/dist-web/assets/index-DkownpUj.js +0 -163
  57. package/dist-web/assets/index-LCedyGl2.css +0 -1
  58. package/dist-web/assets/index-m7v4PP7r.js +0 -11
  59. /package/dist/{chat-command-BVIG2LCO.mjs.map → chat-command-D37MMEJ7.mjs.map} +0 -0
  60. /package/dist/{chunk-6AOEMUCM.mjs.map → chunk-742TAKEP.mjs.map} +0 -0
  61. /package/dist/{chunk-3XFHIQIW.mjs.map → chunk-D5MWER7N.mjs.map} +0 -0
  62. /package/dist/{chunk-JFSTXSX2.mjs.map → chunk-GNGZHBKM.mjs.map} +0 -0
  63. /package/dist/{chunk-GTXX72AQ.mjs.map → chunk-K5SGXTVJ.mjs.map} +0 -0
  64. /package/dist/{chunk-SCE4YL3M.mjs.map → chunk-RMITK4IC.mjs.map} +0 -0
  65. /package/dist/{chunk-QKQRV7RV.mjs.map → chunk-VCN64OD6.mjs.map} +0 -0
  66. /package/dist/{http-NLFLQDI7.mjs.map → http-6F6TYWCI.mjs.map} +0 -0
  67. /package/dist/{import-ILK6MGYR.mjs.map → import-VYPOMSSA.mjs.map} +0 -0
  68. /package/dist/{mcp-FOLR5XJL.mjs.map → mcp-AR7L5FA3.mjs.map} +0 -0
  69. /package/dist/{src-OQN3VE73.mjs.map → src-6S7JPKHX.mjs.map} +0 -0
  70. /package/dist/{stream-import-F5QI2HTO.mjs.map → stream-import-KZFC3XT2.mjs.map} +0 -0
@@ -7,16 +7,16 @@ const __dirname = __pathDirname(__filename);
7
7
  import {
8
8
  importData,
9
9
  parseFile
10
- } from "./chunk-3XFHIQIW.mjs";
10
+ } from "./chunk-D5MWER7N.mjs";
11
11
  import {
12
12
  analyzeIncrementalImport,
13
13
  analyzeNewImport,
14
14
  incrementalImport,
15
15
  streamImport
16
- } from "./chunk-QKQRV7RV.mjs";
16
+ } from "./chunk-VCN64OD6.mjs";
17
17
  import {
18
18
  assertCliDataDirCompatible
19
- } from "./chunk-6AOEMUCM.mjs";
19
+ } from "./chunk-742TAKEP.mjs";
20
20
  import {
21
21
  getVersion
22
22
  } from "./chunk-5ADKYYNA.mjs";
@@ -27,11 +27,11 @@ import {
27
27
  closeServerAiLogger,
28
28
  createCliRunAgentStream,
29
29
  initServerAiLogger
30
- } from "./chunk-JFSTXSX2.mjs";
30
+ } from "./chunk-GNGZHBKM.mjs";
31
31
  import {
32
32
  AGENT_TOOL_REGISTRY,
33
33
  CoreDataProvider
34
- } from "./chunk-SCE4YL3M.mjs";
34
+ } from "./chunk-RMITK4IC.mjs";
35
35
  import {
36
36
  AIChatManager,
37
37
  ALL_MIGRATIONS,
@@ -74,29 +74,41 @@ import {
74
74
  getAvailableYears,
75
75
  getBuiltinModelsByProvider,
76
76
  getCatchphraseAnalysis,
77
+ getCheckInAnalysis,
77
78
  getClusterGraph,
78
79
  getDailyActivity,
80
+ getDbFileVersion,
79
81
  getDefaultNodeUserDataDir,
80
82
  getDefaultRulesForLocale,
81
83
  getDictList,
84
+ getDivingAnalysis,
85
+ getDragonKingAnalysis,
82
86
  getHourlyActivity,
83
87
  getLanguagePreferenceAnalysis,
84
88
  getLaughAnalysis,
89
+ getLongMessageCount,
85
90
  getMemberActivity,
91
+ getMemberMonthlyTrend,
86
92
  getMembers,
87
93
  getMembersDetailed,
94
+ getMemeBattleAnalysis,
88
95
  getMentionAnalysis,
89
96
  getMentionGraph,
90
97
  getMessageLengthDistribution,
91
98
  getMessageTypeStats,
92
99
  getMonthlyActivity,
100
+ getNightOwlAnalysis,
101
+ getOrComputeAnalysisCache,
93
102
  getPendingNodeDataDirMigration,
94
103
  getRelationshipStats,
104
+ getRepeatAnalysis,
95
105
  getSchemaDetailed,
96
106
  getSessionInfo,
97
107
  getSessionMeta,
98
108
  getSessionOverview,
99
109
  getSupportedFormats,
110
+ getTextLengthPercentiles,
111
+ getTextStats,
100
112
  getTimeRange,
101
113
  getWeekdayActivity,
102
114
  getYearlyActivity,
@@ -123,10 +135,10 @@ import {
123
135
  verifyCliDataPath,
124
136
  writeAuthProfile,
125
137
  writeConfigField
126
- } from "./chunk-TMZC3SNX.mjs";
138
+ } from "./chunk-YPTJVTVJ.mjs";
127
139
 
128
140
  // src/http/index.ts
129
- import * as fs9 from "fs";
141
+ import * as fs11 from "fs";
130
142
  import * as crypto4 from "crypto";
131
143
 
132
144
  // src/http/server.ts
@@ -451,13 +463,19 @@ function registerSessionRoutes(server2, ctx) {
451
463
  return preferencesInstance;
452
464
  };
453
465
  server2.get("/_web/sessions", async () => {
454
- return session_service_exports.listAnalysisSessions(adapter);
466
+ const aiChatCounts = ctx.aiChatManager?.getAIChatCountsBySession();
467
+ return session_service_exports.listAnalysisSessions(adapter, {
468
+ enrichSession: aiChatCounts?.size ? (dto) => ({ ...dto, aiConversationCount: aiChatCounts.get(dto.id) ?? 0 }) : void 0
469
+ });
455
470
  });
456
471
  server2.get("/_web/sessions/:id", async (request) => {
457
472
  const session = session_service_exports.getAnalysisSession(adapter, request.params.id);
458
473
  if (!session) {
459
474
  throw Object.assign(new Error(`Session not found: ${request.params.id}`), { statusCode: 404 });
460
475
  }
476
+ if (ctx.aiChatManager) {
477
+ session.aiConversationCount = ctx.aiChatManager.getAIChats(request.params.id).length;
478
+ }
461
479
  return session;
462
480
  });
463
481
  server2.delete("/_web/sessions/:id", async (request, reply) => {
@@ -589,6 +607,10 @@ function registerPreferencesRoutes(server2, ctx) {
589
607
  });
590
608
  }
591
609
 
610
+ // ../../packages/http-routes/src/routes/web/analytics.ts
611
+ import * as fs from "fs";
612
+ import * as path2 from "path";
613
+
592
614
  // ../../packages/http-routes/src/helpers.ts
593
615
  function parseTimeFilter(query) {
594
616
  const { startTs, endTs, memberId } = query;
@@ -600,9 +622,32 @@ function parseTimeFilter(query) {
600
622
  return filter;
601
623
  }
602
624
 
625
+ // ../../packages/http-routes/src/analytics-cache.ts
626
+ import * as path from "path";
627
+ function canonical(value) {
628
+ if (value === void 0) return "null";
629
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
630
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
631
+ const obj = value;
632
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
633
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
634
+ }
635
+ function buildAnalyticsCacheKey(namespace, params) {
636
+ return `${namespace}:${canonical(params)}`;
637
+ }
638
+ function withAnalyticsCache(ctx, sessionId, namespace, params, compute, options) {
639
+ const queryCacheDir = path.join(ctx.pathProvider.getCacheDir(), "query");
640
+ const dateStr = options?.dailyInvalidate ? `|date:${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}` : "";
641
+ const extraStr = options?.extraVersion ? `|${options.extraVersion}` : "";
642
+ const version = `${ctx.getVersion()}|${getDbFileVersion(ctx.sessionAdapter.getDbPath(sessionId))}${dateStr}${extraStr}`;
643
+ const key = buildAnalyticsCacheKey(namespace, params);
644
+ return getOrComputeAnalysisCache(sessionId, key, queryCacheDir, version, compute);
645
+ }
646
+
603
647
  // ../../packages/http-routes/src/routes/web/analytics.ts
604
648
  function registerAnalyticsRoutes(server2, ctx) {
605
649
  const { sessionAdapter: adapter } = ctx;
650
+ const cached = (name, sessionId, params, compute, options) => withAnalyticsCache(ctx, sessionId, `analytics.${name}`, params, compute, options);
606
651
  server2.get("/_web/sessions/:id/years", async (request) => {
607
652
  const db = adapter.ensureReadonly(request.params.id);
608
653
  return getAvailableYears(db);
@@ -614,111 +659,252 @@ function registerAnalyticsRoutes(server2, ctx) {
614
659
  server2.get(
615
660
  "/_web/sessions/:id/stats/member-activity",
616
661
  async (request) => {
617
- const db = adapter.ensureReadonly(request.params.id);
618
- return getMemberActivity(db, parseTimeFilter(request.query));
662
+ const id = request.params.id;
663
+ const filter = parseTimeFilter(request.query);
664
+ return cached("member-activity", id, { ...filter }, () => getMemberActivity(adapter.ensureReadonly(id), filter));
619
665
  }
620
666
  );
621
667
  server2.get(
622
668
  "/_web/sessions/:id/stats/hourly",
623
669
  async (request) => {
624
- const db = adapter.ensureReadonly(request.params.id);
625
- return getHourlyActivity(db, parseTimeFilter(request.query));
670
+ const id = request.params.id;
671
+ const filter = parseTimeFilter(request.query);
672
+ return cached("hourly", id, { ...filter }, () => getHourlyActivity(adapter.ensureReadonly(id), filter));
626
673
  }
627
674
  );
628
675
  server2.get(
629
676
  "/_web/sessions/:id/stats/daily",
630
677
  async (request) => {
631
- const db = adapter.ensureReadonly(request.params.id);
632
- return getDailyActivity(db, parseTimeFilter(request.query));
678
+ const id = request.params.id;
679
+ const filter = parseTimeFilter(request.query);
680
+ return cached("daily", id, { ...filter }, () => getDailyActivity(adapter.ensureReadonly(id), filter));
633
681
  }
634
682
  );
635
683
  server2.get(
636
684
  "/_web/sessions/:id/stats/weekday",
637
685
  async (request) => {
638
- const db = adapter.ensureReadonly(request.params.id);
639
- return getWeekdayActivity(db, parseTimeFilter(request.query));
686
+ const id = request.params.id;
687
+ const filter = parseTimeFilter(request.query);
688
+ return cached("weekday", id, { ...filter }, () => getWeekdayActivity(adapter.ensureReadonly(id), filter));
640
689
  }
641
690
  );
642
691
  server2.get(
643
692
  "/_web/sessions/:id/stats/message-types",
644
693
  async (request) => {
645
- const db = adapter.ensureReadonly(request.params.id);
646
- return getMessageTypeStats(db, parseTimeFilter(request.query));
694
+ const id = request.params.id;
695
+ const filter = parseTimeFilter(request.query);
696
+ return cached("message-types", id, { ...filter }, () => getMessageTypeStats(adapter.ensureReadonly(id), filter));
647
697
  }
648
698
  );
649
699
  server2.get(
650
700
  "/_web/sessions/:id/analytics/relationship",
651
701
  async (request) => {
652
- const db = adapter.ensureReadonly(request.params.id);
653
- return getRelationshipStats(db, parseTimeFilter(request.query));
702
+ const id = request.params.id;
703
+ const filter = parseTimeFilter(request.query);
704
+ return cached("relationship", id, { ...filter }, () => getRelationshipStats(adapter.ensureReadonly(id), filter));
654
705
  }
655
706
  );
656
707
  server2.get(
657
708
  "/_web/sessions/:id/analytics/catchphrase",
658
709
  async (request) => {
659
- const db = adapter.ensureReadonly(request.params.id);
660
- return getCatchphraseAnalysis(db, parseTimeFilter(request.query));
710
+ const id = request.params.id;
711
+ const filter = parseTimeFilter(request.query);
712
+ return cached("catchphrase", id, { ...filter }, () => getCatchphraseAnalysis(adapter.ensureReadonly(id), filter));
661
713
  }
662
714
  );
663
715
  server2.get(
664
716
  "/_web/sessions/:id/analytics/mention",
665
717
  async (request) => {
666
- const db = adapter.ensureReadonly(request.params.id);
667
- return getMentionAnalysis(db, parseTimeFilter(request.query));
718
+ const id = request.params.id;
719
+ const filter = parseTimeFilter(request.query);
720
+ return cached("mention", id, { ...filter }, () => getMentionAnalysis(adapter.ensureReadonly(id), filter));
668
721
  }
669
722
  );
670
723
  server2.get(
671
724
  "/_web/sessions/:id/analytics/mention-graph",
672
725
  async (request) => {
673
- const db = adapter.ensureReadonly(request.params.id);
674
- return getMentionGraph(db, parseTimeFilter(request.query));
726
+ const id = request.params.id;
727
+ const filter = parseTimeFilter(request.query);
728
+ return cached("mention-graph", id, { ...filter }, () => getMentionGraph(adapter.ensureReadonly(id), filter));
675
729
  }
676
730
  );
677
731
  server2.get(
678
732
  "/_web/sessions/:id/analytics/laugh",
679
733
  async (request) => {
680
- const db = adapter.ensureReadonly(request.params.id);
681
- return getLaughAnalysis(db, parseTimeFilter(request.query));
682
- }
683
- );
684
- server2.get(
685
- "/_web/sessions/:id/analytics/cluster",
686
- async (request) => {
687
- const db = adapter.ensureReadonly(request.params.id);
734
+ const id = request.params.id;
688
735
  const filter = parseTimeFilter(request.query);
689
- const topEdges = request.query.topEdges ? parseInt(request.query.topEdges, 10) : void 0;
690
- return getClusterGraph(db, filter, topEdges ? { topEdges } : void 0);
736
+ return cached("laugh", id, { ...filter }, () => getLaughAnalysis(adapter.ensureReadonly(id), filter));
691
737
  }
692
738
  );
739
+ server2.get("/_web/sessions/:id/analytics/cluster", async (request) => {
740
+ const id = request.params.id;
741
+ const filter = parseTimeFilter(request.query);
742
+ const options = {};
743
+ if (request.query.topEdges) options.topEdges = parseInt(request.query.topEdges, 10);
744
+ if (request.query.lookAhead) options.lookAhead = parseInt(request.query.lookAhead, 10);
745
+ if (request.query.decaySeconds) options.decaySeconds = parseInt(request.query.decaySeconds, 10);
746
+ const hasOptions = Object.keys(options).length > 0;
747
+ return cached(
748
+ "cluster",
749
+ id,
750
+ { ...filter, ...options },
751
+ () => getClusterGraph(adapter.ensureReadonly(id), filter, hasOptions ? options : void 0)
752
+ );
753
+ });
693
754
  server2.get(
694
755
  "/_web/sessions/:id/analytics/language-preference",
695
756
  async (request) => {
696
- const db = adapter.ensureReadonly(request.params.id);
757
+ const id = request.params.id;
697
758
  const filter = parseTimeFilter(request.query);
698
759
  const locale = request.query.locale || "zh-CN";
699
- const nlpProvider = createJiebaNlpProvider();
700
- return getLanguagePreferenceAnalysis(db, { locale, timeFilter: filter, nlpProvider });
760
+ const zhDictPath = path2.join(ctx.pathProvider.getSystemDir(), "nlp", "zh-CN.dict");
761
+ let zhDictVersion;
762
+ try {
763
+ const st = fs.statSync(zhDictPath);
764
+ zhDictVersion = `${Math.floor(st.mtimeMs)}:${st.size}`;
765
+ } catch {
766
+ zhDictVersion = "-";
767
+ }
768
+ return cached(
769
+ "language-preference",
770
+ id,
771
+ { ...filter, locale },
772
+ () => getLanguagePreferenceAnalysis(adapter.ensureReadonly(id), {
773
+ locale,
774
+ timeFilter: filter,
775
+ nlpProvider: createJiebaNlpProvider()
776
+ }),
777
+ { extraVersion: `dict:${zhDictVersion}` }
778
+ );
701
779
  }
702
780
  );
703
781
  server2.get(
704
782
  "/_web/sessions/:id/analytics/monthly-activity",
705
783
  async (request) => {
706
- const db = adapter.ensureReadonly(request.params.id);
707
- return getMonthlyActivity(db, parseTimeFilter(request.query));
784
+ const id = request.params.id;
785
+ const filter = parseTimeFilter(request.query);
786
+ return cached("monthly-activity", id, { ...filter }, () => getMonthlyActivity(adapter.ensureReadonly(id), filter));
708
787
  }
709
788
  );
710
789
  server2.get(
711
790
  "/_web/sessions/:id/analytics/yearly-activity",
712
791
  async (request) => {
713
- const db = adapter.ensureReadonly(request.params.id);
714
- return getYearlyActivity(db, parseTimeFilter(request.query));
792
+ const id = request.params.id;
793
+ const filter = parseTimeFilter(request.query);
794
+ return cached("yearly-activity", id, { ...filter }, () => getYearlyActivity(adapter.ensureReadonly(id), filter));
715
795
  }
716
796
  );
717
797
  server2.get(
718
798
  "/_web/sessions/:id/analytics/message-length-distribution",
719
799
  async (request) => {
720
- const db = adapter.ensureReadonly(request.params.id);
721
- return getMessageLengthDistribution(db, parseTimeFilter(request.query));
800
+ const id = request.params.id;
801
+ const filter = parseTimeFilter(request.query);
802
+ return cached(
803
+ "message-length-distribution",
804
+ id,
805
+ { ...filter },
806
+ () => getMessageLengthDistribution(adapter.ensureReadonly(id), filter)
807
+ );
808
+ }
809
+ );
810
+ server2.get(
811
+ "/_web/sessions/:id/analytics/text-stats",
812
+ async (request) => {
813
+ const id = request.params.id;
814
+ const filter = parseTimeFilter(request.query);
815
+ return cached("text-stats", id, { ...filter }, () => getTextStats(adapter.ensureReadonly(id), filter));
816
+ }
817
+ );
818
+ server2.get(
819
+ "/_web/sessions/:id/analytics/long-message-count",
820
+ async (request) => {
821
+ const id = request.params.id;
822
+ const filter = parseTimeFilter(request.query);
823
+ const minLength = request.query.minLength ? parseInt(request.query.minLength, 10) : void 0;
824
+ return cached(
825
+ "long-message-count",
826
+ id,
827
+ { ...filter, minLength },
828
+ () => getLongMessageCount(adapter.ensureReadonly(id), filter, minLength)
829
+ );
830
+ }
831
+ );
832
+ server2.get(
833
+ "/_web/sessions/:id/analytics/member-monthly-trend",
834
+ async (request) => {
835
+ const id = request.params.id;
836
+ const filter = parseTimeFilter(request.query);
837
+ return cached(
838
+ "member-monthly-trend",
839
+ id,
840
+ { ...filter },
841
+ () => getMemberMonthlyTrend(adapter.ensureReadonly(id), filter)
842
+ );
843
+ }
844
+ );
845
+ server2.get(
846
+ "/_web/sessions/:id/analytics/text-length-percentiles",
847
+ async (request) => {
848
+ const id = request.params.id;
849
+ const filter = parseTimeFilter(request.query);
850
+ return cached(
851
+ "text-length-percentiles",
852
+ id,
853
+ { ...filter },
854
+ () => getTextLengthPercentiles(adapter.ensureReadonly(id), filter)
855
+ );
856
+ }
857
+ );
858
+ server2.get(
859
+ "/_web/sessions/:id/analytics/dragon-king",
860
+ async (request) => {
861
+ const id = request.params.id;
862
+ const filter = parseTimeFilter(request.query);
863
+ return cached("dragon-king", id, { ...filter }, () => getDragonKingAnalysis(adapter.ensureReadonly(id), filter));
864
+ }
865
+ );
866
+ server2.get(
867
+ "/_web/sessions/:id/analytics/diving",
868
+ async (request) => {
869
+ const id = request.params.id;
870
+ const filter = parseTimeFilter(request.query);
871
+ return cached("diving", id, { ...filter }, () => getDivingAnalysis(adapter.ensureReadonly(id), filter), {
872
+ dailyInvalidate: true
873
+ });
874
+ }
875
+ );
876
+ server2.get(
877
+ "/_web/sessions/:id/analytics/check-in",
878
+ async (request) => {
879
+ const id = request.params.id;
880
+ const filter = parseTimeFilter(request.query);
881
+ return cached("check-in", id, { ...filter }, () => getCheckInAnalysis(adapter.ensureReadonly(id), filter));
882
+ }
883
+ );
884
+ server2.get(
885
+ "/_web/sessions/:id/analytics/meme-battle",
886
+ async (request) => {
887
+ const id = request.params.id;
888
+ const filter = parseTimeFilter(request.query);
889
+ return cached("meme-battle", id, { ...filter }, () => getMemeBattleAnalysis(adapter.ensureReadonly(id), filter));
890
+ }
891
+ );
892
+ server2.get(
893
+ "/_web/sessions/:id/analytics/night-owl",
894
+ async (request) => {
895
+ const id = request.params.id;
896
+ const filter = parseTimeFilter(request.query);
897
+ return cached("night-owl", id, { ...filter }, () => getNightOwlAnalysis(adapter.ensureReadonly(id), filter), {
898
+ dailyInvalidate: true
899
+ });
900
+ }
901
+ );
902
+ server2.get(
903
+ "/_web/sessions/:id/analytics/repeat",
904
+ async (request) => {
905
+ const id = request.params.id;
906
+ const filter = parseTimeFilter(request.query);
907
+ return cached("repeat", id, { ...filter }, () => getRepeatAnalysis(adapter.ensureReadonly(id), filter));
722
908
  }
723
909
  );
724
910
  }
@@ -823,9 +1009,10 @@ function registerExportRoutes(server2, ctx) {
823
1009
  }
824
1010
 
825
1011
  // ../../packages/http-routes/src/routes/web/nlp.ts
826
- import * as path from "path";
1012
+ import * as fs2 from "fs";
1013
+ import * as path3 from "path";
827
1014
  function registerNlpRoutes(server2, ctx) {
828
- const nlpDir = path.join(ctx.pathProvider.getSystemDir(), "nlp");
1015
+ const nlpDir = path3.join(ctx.pathProvider.getSystemDir(), "nlp");
829
1016
  initNlpDir(nlpDir);
830
1017
  ensureDefaultDict(nlpDir).catch((err) => console.warn("[NLP] Auto-download zh-CN dict failed:", err));
831
1018
  server2.get("/_web/nlp/pos-tags", async () => {
@@ -849,7 +1036,19 @@ function registerNlpRoutes(server2, ctx) {
849
1036
  if (!db) {
850
1037
  throw Object.assign(new Error(`Session not found: ${params.sessionId}`), { statusCode: 404 });
851
1038
  }
852
- return computeWordFrequency(db, params);
1039
+ const { sessionId, ...keyParams } = params;
1040
+ const effectiveDictId = !params.dictType || params.dictType === "default" ? "zh-CN" : String(params.dictType);
1041
+ const dictFilePath = path3.join(nlpDir, `${effectiveDictId}.dict`);
1042
+ let dictVersion;
1043
+ try {
1044
+ const st = fs2.statSync(dictFilePath);
1045
+ dictVersion = `${Math.floor(st.mtimeMs)}:${st.size}`;
1046
+ } catch {
1047
+ dictVersion = "-";
1048
+ }
1049
+ return withAnalyticsCache(ctx, sessionId, "nlp.word-frequency", keyParams, () => computeWordFrequency(db, params), {
1050
+ extraVersion: `dict:${dictVersion}`
1051
+ });
853
1052
  });
854
1053
  server2.post(
855
1054
  "/_web/nlp/segment",
@@ -1414,9 +1613,9 @@ function registerAiSummaryRoutes(server2, ctx) {
1414
1613
  }
1415
1614
 
1416
1615
  // ../../packages/http-routes/src/routes/web/merge.ts
1417
- import * as fs from "fs";
1616
+ import * as fs3 from "fs";
1418
1617
  import * as os from "os";
1419
- import * as path2 from "path";
1618
+ import * as path4 from "path";
1420
1619
  function ensureDb2(ctx, sessionId) {
1421
1620
  const db = ctx.dbManager.open(sessionId);
1422
1621
  if (!db) throw sessionNotFound(sessionId);
@@ -1433,31 +1632,31 @@ function registerMergeRoutes(server2, ctx) {
1433
1632
  if (contentType.includes("multipart/form-data")) {
1434
1633
  const data = await request.file();
1435
1634
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1436
- const tmpDir = fs.mkdtempSync(path2.join(os.tmpdir(), "chatlab-merge-"));
1437
- const tmpPath = path2.join(tmpDir, data.filename || "upload");
1635
+ const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merge-"));
1636
+ const tmpPath = path4.join(tmpDir, data.filename || "upload");
1438
1637
  const chunks = [];
1439
1638
  for await (const chunk of data.file) {
1440
1639
  chunks.push(chunk);
1441
1640
  }
1442
- fs.writeFileSync(tmpPath, Buffer.concat(chunks));
1641
+ fs3.writeFileSync(tmpPath, Buffer.concat(chunks));
1443
1642
  filePath = tmpPath;
1444
1643
  cleanupFile = true;
1445
1644
  cleanupDir = tmpDir;
1446
1645
  } else {
1447
1646
  const body = request.body;
1448
1647
  if (!body?.filePath) return reply.code(400).send({ error: "Missing filePath in body" });
1449
- if (!fs.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1648
+ if (!fs3.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1450
1649
  filePath = body.filePath;
1451
1650
  }
1452
1651
  try {
1453
1652
  const result = await streamParseFileInfo(filePath, {
1454
1653
  createTempDatabase(sourceFilePath) {
1455
- return mergeCache.createTempDatabase(path2.basename(sourceFilePath));
1654
+ return mergeCache.createTempDatabase(path4.basename(sourceFilePath));
1456
1655
  },
1457
1656
  onProgress() {
1458
1657
  }
1459
1658
  });
1460
- const filename = path2.basename(filePath);
1659
+ const filename = path4.basename(filePath);
1461
1660
  const handle = mergeCache.store(filename, result.tempDbPath);
1462
1661
  return {
1463
1662
  handle,
@@ -1471,13 +1670,13 @@ function registerMergeRoutes(server2, ctx) {
1471
1670
  } finally {
1472
1671
  if (cleanupFile) {
1473
1672
  try {
1474
- fs.unlinkSync(filePath);
1673
+ fs3.unlinkSync(filePath);
1475
1674
  } catch {
1476
1675
  }
1477
1676
  }
1478
1677
  if (cleanupDir) {
1479
1678
  try {
1480
- fs.rmdirSync(cleanupDir);
1679
+ fs3.rmdirSync(cleanupDir);
1481
1680
  } catch {
1482
1681
  }
1483
1682
  }
@@ -1526,20 +1725,20 @@ function registerMergeRoutes(server2, ctx) {
1526
1725
  return reply.code(501).send({ error: "Import not available on this server" });
1527
1726
  }
1528
1727
  const jsonData = JSON.stringify(merged.chatLabData);
1529
- const tmpDir = fs.mkdtempSync(path2.join(os.tmpdir(), "chatlab-merged-"));
1530
- const safeName = path2.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1531
- const tmpPath = path2.join(tmpDir, `${safeName}.json`);
1532
- fs.writeFileSync(tmpPath, jsonData, "utf-8");
1728
+ const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merged-"));
1729
+ const safeName = path4.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1730
+ const tmpPath = path4.join(tmpDir, `${safeName}.json`);
1731
+ fs3.writeFileSync(tmpPath, jsonData, "utf-8");
1533
1732
  try {
1534
1733
  const importResult = await streamImport2(dbManager2, tmpPath);
1535
1734
  sessionId = importResult.sessionId;
1536
1735
  } finally {
1537
1736
  try {
1538
- fs.unlinkSync(tmpPath);
1737
+ fs3.unlinkSync(tmpPath);
1539
1738
  } catch {
1540
1739
  }
1541
1740
  try {
1542
- fs.rmdirSync(tmpDir);
1741
+ fs3.rmdirSync(tmpDir);
1543
1742
  } catch {
1544
1743
  }
1545
1744
  }
@@ -1613,16 +1812,16 @@ function registerMergeRoutes(server2, ctx) {
1613
1812
  }
1614
1813
 
1615
1814
  // ../../packages/http-routes/src/routes/web/cache.ts
1616
- import * as fs2 from "fs";
1815
+ import * as fs4 from "fs";
1617
1816
  import * as fsp from "fs/promises";
1618
- import * as path3 from "path";
1817
+ import * as path5 from "path";
1619
1818
  async function getDirSize(dirPath) {
1620
1819
  let totalSize = 0;
1621
1820
  try {
1622
- if (!fs2.existsSync(dirPath)) return 0;
1821
+ if (!fs4.existsSync(dirPath)) return 0;
1623
1822
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1624
1823
  for (const file of files) {
1625
- const filePath = path3.join(dirPath, file.name);
1824
+ const filePath = path5.join(dirPath, file.name);
1626
1825
  if (file.isDirectory()) {
1627
1826
  totalSize += await getDirSize(filePath);
1628
1827
  } else {
@@ -1637,10 +1836,10 @@ async function getDirSize(dirPath) {
1637
1836
  async function getFileCount(dirPath) {
1638
1837
  let count = 0;
1639
1838
  try {
1640
- if (!fs2.existsSync(dirPath)) return 0;
1839
+ if (!fs4.existsSync(dirPath)) return 0;
1641
1840
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1642
1841
  for (const file of files) {
1643
- const filePath = path3.join(dirPath, file.name);
1842
+ const filePath = path5.join(dirPath, file.name);
1644
1843
  if (file.isDirectory()) {
1645
1844
  count += await getFileCount(filePath);
1646
1845
  } else {
@@ -1653,10 +1852,10 @@ async function getFileCount(dirPath) {
1653
1852
  }
1654
1853
  function hasChatLabDatabases(dirPath) {
1655
1854
  try {
1656
- const markerPath = path3.join(dirPath, ".chatlab");
1657
- const dbDir = path3.join(dirPath, "databases");
1658
- if (!fs2.existsSync(markerPath) || !fs2.existsSync(dbDir)) return false;
1659
- return fs2.readdirSync(dbDir).some((file) => file.endsWith(".db"));
1855
+ const markerPath = path5.join(dirPath, ".chatlab");
1856
+ const dbDir = path5.join(dirPath, "databases");
1857
+ if (!fs4.existsSync(markerPath) || !fs4.existsSync(dbDir)) return false;
1858
+ return fs4.readdirSync(dbDir).some((file) => file.endsWith(".db"));
1660
1859
  } catch {
1661
1860
  return false;
1662
1861
  }
@@ -1702,7 +1901,7 @@ function registerCacheRoutes(server2, ctx) {
1702
1901
  const results = await Promise.all(
1703
1902
  directories.map(async (dir) => {
1704
1903
  const [size, fileCount] = await Promise.all([getDirSize(dir.path), getFileCount(dir.path)]);
1705
- return { ...dir, size, fileCount, exists: fs2.existsSync(dir.path) };
1904
+ return { ...dir, size, fileCount, exists: fs4.existsSync(dir.path) };
1706
1905
  })
1707
1906
  );
1708
1907
  return {
@@ -1719,10 +1918,10 @@ function registerCacheRoutes(server2, ctx) {
1719
1918
  };
1720
1919
  const dirPath = allowedDirs[cacheId];
1721
1920
  if (!dirPath) return { success: false, error: "Not allowed to clear this directory" };
1722
- if (!fs2.existsSync(dirPath)) return { success: true };
1921
+ if (!fs4.existsSync(dirPath)) return { success: true };
1723
1922
  const files = await fsp.readdir(dirPath);
1724
1923
  for (const file of files) {
1725
- const filePath = path3.join(dirPath, file);
1924
+ const filePath = path5.join(dirPath, file);
1726
1925
  const stat2 = await fsp.stat(filePath);
1727
1926
  if (stat2.isDirectory()) {
1728
1927
  await fsp.rm(filePath, { recursive: true });
@@ -1739,7 +1938,7 @@ function registerCacheRoutes(server2, ctx) {
1739
1938
  defaultPath: ctx.defaultUserDataDir,
1740
1939
  isCustom: ctx.isCustomDataDir ?? false,
1741
1940
  canSetDataDir: ctx.canSetDataDir ?? Boolean(ctx.setDataDir),
1742
- hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path3.resolve(pp.getUserDataDir()) !== path3.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
1941
+ hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path5.resolve(pp.getUserDataDir()) !== path5.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
1743
1942
  pendingMigration: pending ? {
1744
1943
  from: pending.from,
1745
1944
  to: pending.to,
@@ -1756,8 +1955,8 @@ function registerCacheRoutes(server2, ctx) {
1756
1955
  return ctx.setDataDir(targetPath, migrate);
1757
1956
  });
1758
1957
  server2.get("/_web/cache/latest-import-log", async () => {
1759
- const importLogDir = path3.join(pp.getLogsDir(), "import");
1760
- if (!fs2.existsSync(importLogDir)) {
1958
+ const importLogDir = path5.join(pp.getLogsDir(), "import");
1959
+ if (!fs4.existsSync(importLogDir)) {
1761
1960
  return { success: false, error: "Log directory not found" };
1762
1961
  }
1763
1962
  const files = await fsp.readdir(importLogDir);
@@ -1767,7 +1966,7 @@ function registerCacheRoutes(server2, ctx) {
1767
1966
  }
1768
1967
  const fileStats = await Promise.all(
1769
1968
  logFiles.map(async (f) => {
1770
- const filePath = path3.join(importLogDir, f);
1969
+ const filePath = path5.join(importLogDir, f);
1771
1970
  const stat2 = await fsp.stat(filePath);
1772
1971
  return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
1773
1972
  })
@@ -1791,11 +1990,11 @@ function registerCacheRoutes(server2, ctx) {
1791
1990
  const base64Data = dataUrl.replace(/^data:[^,]+,/, "");
1792
1991
  buffer = Buffer.from(base64Data, "base64");
1793
1992
  }
1794
- if (!fs2.existsSync(downloadsDir)) {
1795
- fs2.mkdirSync(downloadsDir, { recursive: true });
1993
+ if (!fs4.existsSync(downloadsDir)) {
1994
+ fs4.mkdirSync(downloadsDir, { recursive: true });
1796
1995
  }
1797
- const filePath = path3.join(downloadsDir, filename);
1798
- fs2.writeFileSync(filePath, buffer);
1996
+ const filePath = path5.join(downloadsDir, filename);
1997
+ fs4.writeFileSync(filePath, buffer);
1799
1998
  return { success: true, filePath };
1800
1999
  });
1801
2000
  server2.post("/_web/cache/open-dir", async (request, reply) => {
@@ -1811,7 +2010,7 @@ function registerCacheRoutes(server2, ctx) {
1811
2010
  };
1812
2011
  const dirPath = dirPaths[cacheId];
1813
2012
  if (!dirPath) return { success: false, error: "Unknown directory" };
1814
- if (!fs2.existsSync(dirPath)) {
2013
+ if (!fs4.existsSync(dirPath)) {
1815
2014
  await fsp.mkdir(dirPath, { recursive: true });
1816
2015
  }
1817
2016
  try {
@@ -1948,19 +2147,19 @@ function createServer() {
1948
2147
 
1949
2148
  // src/http/routes/web/index.ts
1950
2149
  import * as os3 from "os";
1951
- import * as path6 from "path";
2150
+ import * as path8 from "path";
1952
2151
 
1953
2152
  // src/http/routes/web/import.ts
1954
- import * as fs4 from "fs";
2153
+ import * as fs6 from "fs";
1955
2154
  import * as os2 from "os";
1956
- import * as path4 from "path";
2155
+ import * as path6 from "path";
1957
2156
 
1958
2157
  // src/http/routes/web/helpers.ts
1959
- import * as fs3 from "fs";
2158
+ import * as fs5 from "fs";
1960
2159
  function resolveNativeBinding() {
1961
2160
  if (process.versions.electron) return void 0;
1962
2161
  const nativePath = resolveCliPath("native/better_sqlite3.node");
1963
- if (fs3.existsSync(nativePath)) return nativePath;
2162
+ if (fs5.existsSync(nativePath)) return nativePath;
1964
2163
  return void 0;
1965
2164
  }
1966
2165
 
@@ -1969,11 +2168,11 @@ var DEMO_BASE_URL = "https://chatlab.fun/assets/demo";
1969
2168
  function cleanupTemp(...paths) {
1970
2169
  for (const p of paths) {
1971
2170
  try {
1972
- const stat2 = fs4.statSync(p);
2171
+ const stat2 = fs6.statSync(p);
1973
2172
  if (stat2.isDirectory()) {
1974
- fs4.rmSync(p, { recursive: true, force: true });
2173
+ fs6.rmSync(p, { recursive: true, force: true });
1975
2174
  } else {
1976
- fs4.unlinkSync(p);
2175
+ fs6.unlinkSync(p);
1977
2176
  }
1978
2177
  } catch {
1979
2178
  }
@@ -1986,14 +2185,14 @@ function registerImportRoutes(server2, dbManager2) {
1986
2185
  server2.post("/_web/detect-format", async (request, reply) => {
1987
2186
  const data = await request.file();
1988
2187
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1989
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-detect-"));
1990
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2188
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-detect-"));
2189
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
1991
2190
  try {
1992
2191
  const chunks = [];
1993
2192
  for await (const chunk of data.file) {
1994
2193
  chunks.push(chunk);
1995
2194
  }
1996
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2195
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
1997
2196
  const format = detectFormat(tmpPath);
1998
2197
  const allFormats = detectAllFormats(tmpPath);
1999
2198
  return { format, allFormats };
@@ -2004,14 +2203,14 @@ function registerImportRoutes(server2, dbManager2) {
2004
2203
  server2.post("/_web/scan-multi-chat", async (request, reply) => {
2005
2204
  const data = await request.file();
2006
2205
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2007
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-scan-"));
2008
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2206
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-scan-"));
2207
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2009
2208
  try {
2010
2209
  const chunks = [];
2011
2210
  for await (const chunk of data.file) {
2012
2211
  chunks.push(chunk);
2013
2212
  }
2014
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2213
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2015
2214
  const chats = await scanMultiChatFile(tmpPath);
2016
2215
  return { chats };
2017
2216
  } finally {
@@ -2021,13 +2220,13 @@ function registerImportRoutes(server2, dbManager2) {
2021
2220
  server2.post("/_web/import", async (request, reply) => {
2022
2221
  const data = await request.file();
2023
2222
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2024
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-import-"));
2025
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2223
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-import-"));
2224
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2026
2225
  const chunks = [];
2027
2226
  for await (const chunk of data.file) {
2028
2227
  chunks.push(chunk);
2029
2228
  }
2030
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2229
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2031
2230
  const formatId = data.fields?.formatId?.value;
2032
2231
  const chatIndexStr = data.fields?.chatIndex?.value;
2033
2232
  const chatIndex = chatIndexStr !== void 0 ? parseInt(chatIndexStr, 10) : void 0;
@@ -2070,7 +2269,7 @@ data: ${JSON.stringify(eventData)}
2070
2269
  server2.post("/_web/import-directory", async (request, reply) => {
2071
2270
  const parts = request.parts();
2072
2271
  if (!parts) return reply.code(400).send({ error: "No files uploaded" });
2073
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-dir-import-"));
2272
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-dir-import-"));
2074
2273
  const relativePaths = [];
2075
2274
  const fileBuffers = [];
2076
2275
  try {
@@ -2098,10 +2297,10 @@ data: ${JSON.stringify(eventData)}
2098
2297
  if (segments.length > 1) {
2099
2298
  relPath = segments.slice(1).join("/");
2100
2299
  }
2101
- const targetPath = path4.resolve(tmpDir, relPath);
2102
- if (!targetPath.startsWith(tmpDir + path4.sep)) continue;
2103
- fs4.mkdirSync(path4.dirname(targetPath), { recursive: true });
2104
- fs4.writeFileSync(targetPath, fileBuffers[i].data);
2300
+ const targetPath = path6.resolve(tmpDir, relPath);
2301
+ if (!targetPath.startsWith(tmpDir + path6.sep)) continue;
2302
+ fs6.mkdirSync(path6.dirname(targetPath), { recursive: true });
2303
+ fs6.writeFileSync(targetPath, fileBuffers[i].data);
2105
2304
  }
2106
2305
  const entryPath = findEntryFileInDirectory(tmpDir);
2107
2306
  if (!entryPath) {
@@ -2146,13 +2345,13 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
2146
2345
  const sessionId = request.params.id;
2147
2346
  const data = await request.file();
2148
2347
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2149
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-inc-"));
2150
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2348
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-inc-"));
2349
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2151
2350
  const chunks = [];
2152
2351
  for await (const chunk of data.file) {
2153
2352
  chunks.push(chunk);
2154
2353
  }
2155
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2354
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2156
2355
  reply.raw.writeHead(200, {
2157
2356
  "Content-Type": "text/event-stream",
2158
2357
  "Cache-Control": "no-cache",
@@ -2184,13 +2383,13 @@ data: ${JSON.stringify(eventData)}
2184
2383
  const sessionId = request.params.id;
2185
2384
  const data = await request.file();
2186
2385
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2187
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-analyze-"));
2188
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2386
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2387
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2189
2388
  const chunks = [];
2190
2389
  for await (const chunk of data.file) {
2191
2390
  chunks.push(chunk);
2192
2391
  }
2193
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2392
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2194
2393
  try {
2195
2394
  return await analyzeIncrementalImport(dbManager2, sessionId, tmpPath);
2196
2395
  } catch (err) {
@@ -2202,13 +2401,13 @@ data: ${JSON.stringify(eventData)}
2202
2401
  server2.post("/_web/import/analyze", async (request, reply) => {
2203
2402
  const data = await request.file();
2204
2403
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2205
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-analyze-"));
2206
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2404
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2405
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2207
2406
  const chunks = [];
2208
2407
  for await (const chunk of data.file) {
2209
2408
  chunks.push(chunk);
2210
2409
  }
2211
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2410
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2212
2411
  try {
2213
2412
  return await analyzeNewImport(tmpPath);
2214
2413
  } catch (err) {
@@ -2238,17 +2437,17 @@ data: ${JSON.stringify(eventData)}
2238
2437
 
2239
2438
  `);
2240
2439
  }
2241
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-demo-"));
2440
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-demo-"));
2242
2441
  try {
2243
2442
  const localPaths = [];
2244
2443
  for (let i = 0; i < total; i++) {
2245
2444
  sendEvent("progress", { stage: "downloading", current: i + 1, total });
2246
- const localPath = path4.join(tmpDir, DEMO_FILES[i]);
2445
+ const localPath = path6.join(tmpDir, DEMO_FILES[i]);
2247
2446
  const resp = await fetch(`${DEMO_BASE_URL}/${locale}/${DEMO_FILES[i]}`, {
2248
2447
  signal: AbortSignal.timeout(6e4)
2249
2448
  });
2250
2449
  if (!resp.ok) throw new Error(`Download demo failed (${DEMO_FILES[i]}): ${resp.status}`);
2251
- fs4.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
2450
+ fs6.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
2252
2451
  localPaths.push(localPath);
2253
2452
  }
2254
2453
  sendEvent("progress", { stage: "importing", current: 1, total });
@@ -2279,7 +2478,7 @@ data: ${JSON.stringify(eventData)}
2279
2478
  }
2280
2479
 
2281
2480
  // src/http/routes/web/cache.ts
2282
- import * as path5 from "path";
2481
+ import * as path7 from "path";
2283
2482
  import { execFile } from "child_process";
2284
2483
  function runExecFile(file, args, runner = execFile) {
2285
2484
  return new Promise((resolve4, reject) => {
@@ -2307,7 +2506,7 @@ async function showPathInFolder(filePath, platform = process.platform, runner) {
2307
2506
  } else if (platform === "win32") {
2308
2507
  await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
2309
2508
  } else {
2310
- await runExecFile("xdg-open", [path5.dirname(filePath)], runner);
2509
+ await runExecFile("xdg-open", [path7.dirname(filePath)], runner);
2311
2510
  }
2312
2511
  }
2313
2512
 
@@ -2330,26 +2529,26 @@ function registerWebRoutes(server2, dbManager2, options) {
2330
2529
  const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
2331
2530
  mergeCache?.cleanupOrphans();
2332
2531
  const fallbackPathProvider = {
2333
- getSystemDir: () => path6.join(os3.homedir(), ".chatlab"),
2334
- getUserDataDir: () => path6.join(os3.homedir(), ".chatlab", "data"),
2335
- getDatabaseDir: () => path6.join(os3.homedir(), ".chatlab", "data", "databases"),
2336
- getAiDataDir: () => path6.join(os3.homedir(), ".chatlab", "ai"),
2337
- getSettingsDir: () => path6.join(os3.homedir(), ".chatlab", "settings"),
2338
- getCacheDir: () => path6.join(os3.homedir(), ".chatlab", "cache"),
2339
- getTempDir: () => path6.join(os3.homedir(), ".chatlab", "temp"),
2340
- getLogsDir: () => path6.join(os3.homedir(), ".chatlab", "logs"),
2341
- getDownloadsDir: () => path6.join(os3.homedir(), "Downloads")
2532
+ getSystemDir: () => path8.join(os3.homedir(), ".chatlab"),
2533
+ getUserDataDir: () => path8.join(os3.homedir(), ".chatlab", "data"),
2534
+ getDatabaseDir: () => path8.join(os3.homedir(), ".chatlab", "data", "databases"),
2535
+ getAiDataDir: () => path8.join(os3.homedir(), ".chatlab", "ai"),
2536
+ getSettingsDir: () => path8.join(os3.homedir(), ".chatlab", "settings"),
2537
+ getCacheDir: () => path8.join(os3.homedir(), ".chatlab", "cache"),
2538
+ getTempDir: () => path8.join(os3.homedir(), ".chatlab", "temp"),
2539
+ getLogsDir: () => path8.join(os3.homedir(), ".chatlab", "logs"),
2540
+ getDownloadsDir: () => path8.join(os3.homedir(), "Downloads")
2342
2541
  };
2343
2542
  const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
2344
2543
  const ai = options?.aiContext;
2345
2544
  const cliStreamImport = async (dm, filePath) => {
2346
- const { streamImport: streamImport2 } = await import("./stream-import-F5QI2HTO.mjs");
2545
+ const { streamImport: streamImport2 } = await import("./stream-import-KZFC3XT2.mjs");
2347
2546
  const result = await streamImport2(dm, filePath);
2348
2547
  if (!result.sessionId) throw new Error("Import succeeded but no sessionId returned");
2349
2548
  return { sessionId: result.sessionId };
2350
2549
  };
2351
2550
  const defaultUserDataDir = getDefaultNodeUserDataDir();
2352
- const isCustom = path6.resolve(resolvedPathProvider.getUserDataDir()) !== path6.resolve(defaultUserDataDir);
2551
+ const isCustom = path8.resolve(resolvedPathProvider.getUserDataDir()) !== path8.resolve(defaultUserDataDir);
2353
2552
  registerSharedRoutes(
2354
2553
  server2,
2355
2554
  {
@@ -2440,31 +2639,31 @@ function registerProxyRoutes(server2) {
2440
2639
  }
2441
2640
 
2442
2641
  // src/ai/manager-factory.ts
2443
- import * as fs5 from "fs";
2444
- import * as path7 from "path";
2642
+ import * as fs7 from "fs";
2643
+ import * as path9 from "path";
2445
2644
  import { createHash, randomUUID } from "crypto";
2446
2645
  var nodeFs = {
2447
2646
  ensureDir(dir) {
2448
- if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
2647
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2449
2648
  },
2450
2649
  listFiles(dir, ext) {
2451
- if (!fs5.existsSync(dir)) return [];
2452
- return fs5.readdirSync(dir).filter((f) => f.endsWith(ext));
2650
+ if (!fs7.existsSync(dir)) return [];
2651
+ return fs7.readdirSync(dir).filter((f) => f.endsWith(ext));
2453
2652
  },
2454
2653
  readFile(filePath) {
2455
- return fs5.readFileSync(filePath, "utf-8");
2654
+ return fs7.readFileSync(filePath, "utf-8");
2456
2655
  },
2457
2656
  writeFile(filePath, content) {
2458
- fs5.writeFileSync(filePath, content, "utf-8");
2657
+ fs7.writeFileSync(filePath, content, "utf-8");
2459
2658
  },
2460
2659
  deleteFile(filePath) {
2461
- if (fs5.existsSync(filePath)) fs5.unlinkSync(filePath);
2660
+ if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
2462
2661
  },
2463
2662
  fileExists(filePath) {
2464
- return fs5.existsSync(filePath);
2663
+ return fs7.existsSync(filePath);
2465
2664
  },
2466
2665
  joinPath(...parts) {
2467
- return path7.join(...parts);
2666
+ return path9.join(...parts);
2468
2667
  }
2469
2668
  };
2470
2669
  var managerCache = /* @__PURE__ */ new Map();
@@ -2474,13 +2673,13 @@ function getAssistantManager(aiDataDir) {
2474
2673
  cached = {
2475
2674
  assistant: new AssistantManager({
2476
2675
  fs: nodeFs,
2477
- assistantsDir: path7.join(aiDataDir, "assistants"),
2676
+ assistantsDir: path9.join(aiDataDir, "assistants"),
2478
2677
  builtinRawConfigs: [],
2479
2678
  generateId: () => `custom_${randomUUID().replace(/-/g, "").slice(0, 12)}`
2480
2679
  }),
2481
2680
  skill: new SkillManagerCore({
2482
2681
  fs: nodeFs,
2483
- skillsDir: path7.join(aiDataDir, "skills"),
2682
+ skillsDir: path9.join(aiDataDir, "skills"),
2484
2683
  builtinRawSkills: [],
2485
2684
  contentHash: (content) => createHash("md5").update(content).digest("hex")
2486
2685
  })
@@ -2504,8 +2703,8 @@ var noop = () => {
2504
2703
  var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
2505
2704
 
2506
2705
  // ../../packages/sync/src/data-source-manager.ts
2507
- import * as fs6 from "fs";
2508
- import * as path8 from "path";
2706
+ import * as fs8 from "fs";
2707
+ import * as path10 from "path";
2509
2708
  import * as crypto from "crypto";
2510
2709
  var CONFIG_FILE = "data-sources.json";
2511
2710
  function generateId(prefix = "ds") {
@@ -2533,17 +2732,17 @@ var DataSourceManager = class {
2533
2732
  this.logger = logger ?? NOOP_LOGGER;
2534
2733
  }
2535
2734
  getConfigPath() {
2536
- return path8.join(this.configDir, CONFIG_FILE);
2735
+ return path10.join(this.configDir, CONFIG_FILE);
2537
2736
  }
2538
2737
  ensureDir() {
2539
- fs6.mkdirSync(this.configDir, { recursive: true });
2738
+ fs8.mkdirSync(this.configDir, { recursive: true });
2540
2739
  }
2541
2740
  // ==================== Load / Save ====================
2542
2741
  loadAll() {
2543
2742
  try {
2544
2743
  const filePath = this.getConfigPath();
2545
- if (fs6.existsSync(filePath)) {
2546
- const raw = fs6.readFileSync(filePath, "utf-8");
2744
+ if (fs8.existsSync(filePath)) {
2745
+ const raw = fs8.readFileSync(filePath, "utf-8");
2547
2746
  const parsed = JSON.parse(raw);
2548
2747
  if (!isValidDataSourceArray(parsed)) {
2549
2748
  this.logger.warn("[DataSource] Incompatible config format detected, returning empty.");
@@ -2562,7 +2761,7 @@ var DataSourceManager = class {
2562
2761
  saveAll(sources) {
2563
2762
  try {
2564
2763
  this.ensureDir();
2565
- fs6.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
2764
+ fs8.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
2566
2765
  } catch (err) {
2567
2766
  this.logger.error("[DataSource] Failed to save config", err);
2568
2767
  }
@@ -2687,7 +2886,7 @@ function parseRemoteSessionsResponse(body) {
2687
2886
  }
2688
2887
 
2689
2888
  // ../../packages/sync/src/pull-engine.ts
2690
- import * as fs7 from "fs";
2889
+ import * as fs9 from "fs";
2691
2890
  import * as crypto2 from "crypto";
2692
2891
  var MAX_PAGES_PER_PULL = 5e3;
2693
2892
  var PULL_OVERLAP_SECONDS = 60;
@@ -2707,7 +2906,7 @@ function parseSyncFromFile(filePath) {
2707
2906
  try {
2708
2907
  const isJsonl = filePath.endsWith(".jsonl");
2709
2908
  if (isJsonl) {
2710
- const content = fs7.readFileSync(filePath, "utf-8");
2909
+ const content = fs9.readFileSync(filePath, "utf-8");
2711
2910
  const lines = content.trimEnd().split("\n");
2712
2911
  for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
2713
2912
  try {
@@ -2726,7 +2925,7 @@ function parseSyncFromFile(filePath) {
2726
2925
  }
2727
2926
  return null;
2728
2927
  }
2729
- const raw = fs7.readFileSync(filePath, "utf-8");
2928
+ const raw = fs9.readFileSync(filePath, "utf-8");
2730
2929
  const parsed = JSON.parse(raw);
2731
2930
  if (parsed.sync && typeof parsed.sync === "object") {
2732
2931
  const s = parsed.sync;
@@ -2740,7 +2939,7 @@ function parseSyncFromFile(filePath) {
2740
2939
  function fileContainsMessages(filePath) {
2741
2940
  try {
2742
2941
  if (filePath.endsWith(".jsonl")) {
2743
- const content = fs7.readFileSync(filePath, "utf-8");
2942
+ const content = fs9.readFileSync(filePath, "utf-8");
2744
2943
  for (const line of content.split("\n")) {
2745
2944
  const trimmed = line.trim();
2746
2945
  if (!trimmed) continue;
@@ -2753,7 +2952,7 @@ function fileContainsMessages(filePath) {
2753
2952
  }
2754
2953
  return false;
2755
2954
  }
2756
- const raw = fs7.readFileSync(filePath, "utf-8");
2955
+ const raw = fs9.readFileSync(filePath, "utf-8");
2757
2956
  const parsed = JSON.parse(raw);
2758
2957
  return Array.isArray(parsed.messages) && parsed.messages.length > 0;
2759
2958
  } catch {
@@ -2762,7 +2961,7 @@ function fileContainsMessages(filePath) {
2762
2961
  }
2763
2962
  function cleanupTempFile(filePath) {
2764
2963
  try {
2765
- if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
2964
+ if (fs9.existsSync(filePath)) fs9.unlinkSync(filePath);
2766
2965
  } catch {
2767
2966
  }
2768
2967
  }
@@ -2824,7 +3023,7 @@ var PullEngine = class {
2824
3023
  limit: ds.pullLimit
2825
3024
  });
2826
3025
  try {
2827
- const stat2 = fs7.statSync(tempFile);
3026
+ const stat2 = fs9.statSync(tempFile);
2828
3027
  this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat2.size} bytes`);
2829
3028
  const sync0 = parseSyncFromFile(tempFile);
2830
3029
  if (stat2.size < 1024) {
@@ -2842,7 +3041,7 @@ var PullEngine = class {
2842
3041
  since,
2843
3042
  limit: ds.pullLimit
2844
3043
  });
2845
- const retryStat = fs7.statSync(retryFile);
3044
+ const retryStat = fs9.statSync(retryFile);
2846
3045
  this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
2847
3046
  const retrySync = parseSyncFromFile(retryFile);
2848
3047
  if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
@@ -3123,13 +3322,13 @@ function reloadTimer(dsId, skipInitialPull = false) {
3123
3322
  }
3124
3323
 
3125
3324
  // src/sync/adapters.ts
3126
- import * as fs8 from "fs";
3325
+ import * as fs10 from "fs";
3127
3326
  import * as os4 from "os";
3128
- import * as path9 from "path";
3327
+ import * as path11 from "path";
3129
3328
  import * as crypto3 from "crypto";
3130
3329
  function getTempFilePath(ext) {
3131
3330
  const id = crypto3.randomBytes(8).toString("hex");
3132
- return path9.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3331
+ return path11.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3133
3332
  }
3134
3333
  var NodeFetcher = class {
3135
3334
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
@@ -3146,7 +3345,7 @@ var NodeFetcher = class {
3146
3345
  const isJsonl = contentType.includes("ndjson") || contentType.includes("jsonl");
3147
3346
  const tempFile = getTempFilePath(isJsonl ? ".jsonl" : ".json");
3148
3347
  const buffer = Buffer.from(await response.arrayBuffer());
3149
- fs8.writeFileSync(tempFile, buffer);
3348
+ fs10.writeFileSync(tempFile, buffer);
3150
3349
  return tempFile;
3151
3350
  }
3152
3351
  };
@@ -3159,7 +3358,7 @@ var DirectImporter = class {
3159
3358
  }
3160
3359
  sessionExists(sessionId) {
3161
3360
  const dbPath = this.dbManager.getDbPath(sessionId);
3162
- if (!fs8.existsSync(dbPath)) return false;
3361
+ if (!fs10.existsSync(dbPath)) return false;
3163
3362
  try {
3164
3363
  const db = this.dbManager.openRawSessionDatabase(sessionId, { readonly: true });
3165
3364
  const row = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message'").get();
@@ -3167,7 +3366,7 @@ var DirectImporter = class {
3167
3366
  if (row.cnt === 0) {
3168
3367
  this.logger.warn(`[DirectImporter] DB file exists but has no message table: ${sessionId}, removing`);
3169
3368
  try {
3170
- fs8.unlinkSync(dbPath);
3369
+ fs10.unlinkSync(dbPath);
3171
3370
  } catch {
3172
3371
  }
3173
3372
  return false;
@@ -3177,7 +3376,7 @@ var DirectImporter = class {
3177
3376
  if (error instanceof DataDirCompatibilityError) throw error;
3178
3377
  this.logger.warn(`[DirectImporter] Cannot validate DB file: ${sessionId}, removing`);
3179
3378
  try {
3180
- fs8.unlinkSync(dbPath);
3379
+ fs10.unlinkSync(dbPath);
3181
3380
  } catch {
3182
3381
  }
3183
3382
  return false;
@@ -3382,21 +3581,21 @@ function createFileConfigStorage(aiDataDir) {
3382
3581
  return {
3383
3582
  readJson(key) {
3384
3583
  try {
3385
- return JSON.parse(fs9.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
3584
+ return JSON.parse(fs11.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
3386
3585
  } catch {
3387
3586
  return null;
3388
3587
  }
3389
3588
  },
3390
3589
  writeJson(key, data) {
3391
- if (!fs9.existsSync(aiDataDir)) fs9.mkdirSync(aiDataDir, { recursive: true });
3392
- fs9.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
3590
+ if (!fs11.existsSync(aiDataDir)) fs11.mkdirSync(aiDataDir, { recursive: true });
3591
+ fs11.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
3393
3592
  }
3394
3593
  };
3395
3594
  }
3396
3595
  function resolveNativeBinding2() {
3397
3596
  if (process.versions.electron) return void 0;
3398
3597
  const nativePath = resolveCliPath("native/better_sqlite3.node");
3399
- if (fs9.existsSync(nativePath)) return nativePath;
3598
+ if (fs11.existsSync(nativePath)) return nativePath;
3400
3599
  return void 0;
3401
3600
  }
3402
3601
  function ensureToken(config) {
@@ -3482,7 +3681,7 @@ async function startHttpServer(options) {
3482
3681
  }
3483
3682
  });
3484
3683
  initSync(server, dbManager, pathProvider, { port, host, token });
3485
- if (options?.webRoot && fs9.existsSync(options.webRoot)) {
3684
+ if (options?.webRoot && fs11.existsSync(options.webRoot)) {
3486
3685
  registerProxyRoutes(server);
3487
3686
  const fastifyStatic = await import("@fastify/static");
3488
3687
  await server.register(fastifyStatic.default, {
@@ -3523,4 +3722,4 @@ export {
3523
3722
  startHttpServer,
3524
3723
  stopHttpServer
3525
3724
  };
3526
- //# sourceMappingURL=chunk-GBQQJJKK.mjs.map
3725
+ //# sourceMappingURL=chunk-QMXNJRTT.mjs.map