chatlab-cli 0.29.0 → 0.30.1-beta.1

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 (104) hide show
  1. package/dist/{chat-command-JKG3FY5B.mjs → chat-command-VBDMWOKL.mjs} +8 -8
  2. package/dist/{chunk-6FPEP2ZX.mjs → chunk-326OCJOT.mjs} +2 -2
  3. package/dist/{chunk-QQK7MLCR.mjs → chunk-36JGKW6K.mjs} +710 -282
  4. package/dist/chunk-36JGKW6K.mjs.map +1 -0
  5. package/dist/{chunk-SKUG5XVD.mjs → chunk-5EE2EFPE.mjs} +5 -4
  6. package/dist/{chunk-KL2NGIMQ.mjs → chunk-D3DPCDII.mjs} +444 -274
  7. package/dist/chunk-D3DPCDII.mjs.map +1 -0
  8. package/dist/{chunk-I35WHWA6.mjs → chunk-KBSAVUFV.mjs} +7 -4
  9. package/dist/chunk-KBSAVUFV.mjs.map +1 -0
  10. package/dist/{chunk-WPI6YSAR.mjs → chunk-LVEJHB3X.mjs} +4570 -4412
  11. package/dist/chunk-LVEJHB3X.mjs.map +1 -0
  12. package/dist/chunk-M2PXDX4G.mjs +2325 -0
  13. package/dist/chunk-M2PXDX4G.mjs.map +1 -0
  14. package/dist/{chunk-CIB23U32.mjs → chunk-TCZHG3G7.mjs} +101 -41
  15. package/dist/chunk-TCZHG3G7.mjs.map +1 -0
  16. package/dist/{chunk-D4WTE7LV.mjs → chunk-TEX44HNL.mjs} +16 -12
  17. package/dist/chunk-TEX44HNL.mjs.map +1 -0
  18. package/dist/{chunk-7RW4CQAO.mjs → chunk-ULNRUS55.mjs} +5 -3
  19. package/dist/chunk-ULNRUS55.mjs.map +1 -0
  20. package/dist/{chunk-2MZ6G4AC.mjs → chunk-WV5GSRQM.mjs} +2 -2
  21. package/dist/cli.mjs +7 -7
  22. package/dist/contacts-worker.mjs +2 -2
  23. package/dist/{http-5E7ARYFW.mjs → http-L4AMELPZ.mjs} +11 -11
  24. package/dist/{import-3KG6VK6O.mjs → import-WWCSXGVS.mjs} +7 -7
  25. package/dist/index.mjs +11 -11
  26. package/dist/{mcp-JBI4CUQT.mjs → mcp-A4M3LT5O.mjs} +8 -8
  27. package/dist/people-relationships-worker.mjs +2 -2
  28. package/dist/semantic-index-worker.mjs +1 -1
  29. package/dist/{src-UTBQAOJ3.mjs → src-MPNBTFOE.mjs} +14 -6
  30. package/dist/{stream-import-7HZ4IRYJ.mjs → stream-import-HIE7PNL4.mjs} +7 -7
  31. package/dist/{worker-runtime-ALS4MR2A.mjs → worker-runtime-BJXO24XX.mjs} +5 -5
  32. package/dist-web/assets/{BufferResource-MsqP5sq3.js → BufferResource-DPkMC1wo.js} +1 -1
  33. package/dist-web/assets/CanvasRenderer-DokMtSX9.js +1 -0
  34. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-Ceg-0WOD.js +110 -0
  35. package/dist-web/assets/{Filter-wRJtdXx9.js → Filter-Bo_p3BVX.js} +1 -1
  36. package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-D_dJuLWJ.js +1 -0
  37. package/dist-web/assets/{MessageExportModal-BCrMf5Kw.js → MessageExportModal-Ch0tqQix.js} +1 -1
  38. package/dist-web/assets/{PageHeader-C_yw54le.js → PageHeader-CRlmZoKw.js} +1 -1
  39. package/dist-web/assets/{RelationshipGalaxyCanvas-DTzJq7q7.js → RelationshipGalaxyCanvas-W_6XfPOG.js} +3 -3
  40. package/dist-web/assets/{RelationshipGalaxyThreeCanvas-DIE-0_iM.js → RelationshipGalaxyThreeCanvas-bB1nCwQ_.js} +1 -1
  41. package/dist-web/assets/{RenderTargetSystem-BSAQFamI.js → RenderTargetSystem-obRi6fMT.js} +1 -1
  42. package/dist-web/assets/{WebGLRenderer-lfzmCZNT.js → WebGLRenderer-xcDlnrCZ.js} +1 -1
  43. package/dist-web/assets/{WebGPURenderer-BnEdfdr_.js → WebGPURenderer-DbAOiiYr.js} +1 -1
  44. package/dist-web/assets/{browserAll-gRNfDyPu.js → browserAll-DVRJimO8.js} +1 -1
  45. package/dist-web/assets/electron-BSGRuxX9.js +1 -0
  46. package/dist-web/assets/{fetch-BW3aO7PS.js → fetch-7UN4YW3A.js} +1 -1
  47. package/dist-web/assets/{fetch-CQt-gb-f.js → fetch-B2alAySK.js} +1 -1
  48. package/dist-web/assets/fetch-B2rVTMYY.js +17 -0
  49. package/dist-web/assets/{fetch-C1fI_KOX.js → fetch-BSShKcMt.js} +1 -1
  50. package/dist-web/assets/{fetch-mFlEBZyB.js → fetch-BmxMf3I6.js} +1 -1
  51. package/dist-web/assets/{fetch-BwF5hNuF.js → fetch-CR5UDFW7.js} +1 -1
  52. package/dist-web/assets/{fetch-CFRoSIAn.js → fetch-ChAsglT5.js} +1 -1
  53. package/dist-web/assets/{fetch-CN91TIwV.js → fetch-DWEjTdcP.js} +1 -1
  54. package/dist-web/assets/{fetch-DPCJ-1l-.js → fetch-DfoxedXR.js} +1 -1
  55. package/dist-web/assets/{fetch-gMB4wuBd.js → fetch-t0WsRKHW.js} +1 -1
  56. package/dist-web/assets/index-B4l3rHie.css +1 -0
  57. package/dist-web/assets/{index-ZdCB7xcf.js → index-BVUTV9_w.js} +4 -4
  58. package/dist-web/assets/index-BmXNdMV5.js +2 -0
  59. package/dist-web/assets/index-BsXljrlg.js +22 -0
  60. package/dist-web/assets/{index-MBRD3wPd.css → index-C1p9CbmN.css} +1 -1
  61. package/dist-web/assets/index-COpza1A4.css +1 -0
  62. package/dist-web/assets/index-CQ5lRCHt.js +173 -0
  63. package/dist-web/assets/index-CZv0ZOID.js +2 -0
  64. package/dist-web/assets/{index-DveHdPfK.js → index-CfgDqd_z.js} +1 -1
  65. package/dist-web/assets/{index-Bg4GGPXv.js → index-CtVR_KuP.js} +1 -1
  66. package/dist-web/assets/index-DT0Bu8mH.js +64 -0
  67. package/dist-web/assets/index-DjcWJv4b.js +1 -0
  68. package/dist-web/assets/{vendor-nuxt-ui-Dr2T8_OZ.js → vendor-nuxt-ui-DaLxYaC4.js} +1 -1
  69. package/dist-web/assets/{web-BakeV58u.js → web-knHSvvNj.js} +1 -1
  70. package/dist-web/assets/{webworkerAll-D_6PgURS.js → webworkerAll-CMKoykfD.js} +1 -1
  71. package/dist-web/index.html +3 -3
  72. package/package.json +1 -1
  73. package/dist/chunk-7RW4CQAO.mjs.map +0 -1
  74. package/dist/chunk-ARZM6HBX.mjs +0 -542
  75. package/dist/chunk-ARZM6HBX.mjs.map +0 -1
  76. package/dist/chunk-CIB23U32.mjs.map +0 -1
  77. package/dist/chunk-D4WTE7LV.mjs.map +0 -1
  78. package/dist/chunk-I35WHWA6.mjs.map +0 -1
  79. package/dist/chunk-KL2NGIMQ.mjs.map +0 -1
  80. package/dist/chunk-QQK7MLCR.mjs.map +0 -1
  81. package/dist/chunk-WPI6YSAR.mjs.map +0 -1
  82. package/dist-web/assets/CanvasRenderer-BKCvcc2p.js +0 -1
  83. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-HGJb0Et1.js +0 -110
  84. package/dist-web/assets/electron-BHaEaaZP.js +0 -1
  85. package/dist-web/assets/fetch-CBZbgZqk.js +0 -17
  86. package/dist-web/assets/index--6C0sK6a.css +0 -1
  87. package/dist-web/assets/index-B6wEGata.js +0 -173
  88. package/dist-web/assets/index-BgVbj5qX.js +0 -12
  89. package/dist-web/assets/index-BkoXaBV_.js +0 -64
  90. package/dist-web/assets/index-C56IuV5U.css +0 -1
  91. package/dist-web/assets/index-CLRaKwpT.js +0 -2
  92. package/dist-web/assets/index-Dj1BSD7O.js +0 -1
  93. package/dist-web/assets/index-RoDgJcHU.js +0 -2
  94. package/dist-web/assets/index-aljYO5ms.js +0 -1
  95. /package/dist/{chat-command-JKG3FY5B.mjs.map → chat-command-VBDMWOKL.mjs.map} +0 -0
  96. /package/dist/{chunk-6FPEP2ZX.mjs.map → chunk-326OCJOT.mjs.map} +0 -0
  97. /package/dist/{chunk-SKUG5XVD.mjs.map → chunk-5EE2EFPE.mjs.map} +0 -0
  98. /package/dist/{chunk-2MZ6G4AC.mjs.map → chunk-WV5GSRQM.mjs.map} +0 -0
  99. /package/dist/{http-5E7ARYFW.mjs.map → http-L4AMELPZ.mjs.map} +0 -0
  100. /package/dist/{import-3KG6VK6O.mjs.map → import-WWCSXGVS.mjs.map} +0 -0
  101. /package/dist/{mcp-JBI4CUQT.mjs.map → mcp-A4M3LT5O.mjs.map} +0 -0
  102. /package/dist/{src-UTBQAOJ3.mjs.map → src-MPNBTFOE.mjs.map} +0 -0
  103. /package/dist/{stream-import-7HZ4IRYJ.mjs.map → stream-import-HIE7PNL4.mjs.map} +0 -0
  104. /package/dist/{worker-runtime-ALS4MR2A.mjs.map → worker-runtime-BJXO24XX.mjs.map} +0 -0
@@ -9,7 +9,7 @@ import {
9
9
  createEmptyContactsDiagnostics,
10
10
  normalizeContactsTimeRangePreset,
11
11
  resolveContactsTimeRange
12
- } from "./chunk-D4WTE7LV.mjs";
12
+ } from "./chunk-TEX44HNL.mjs";
13
13
  import {
14
14
  PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
15
15
  buildPeopleRelationshipsNeighborhoodGraph,
@@ -18,7 +18,7 @@ import {
18
18
  computePeopleRelationshipsSnapshot,
19
19
  normalizePeopleRelationshipsTimeRangePreset,
20
20
  resolvePeopleRelationshipsTimeRange
21
- } from "./chunk-2MZ6G4AC.mjs";
21
+ } from "./chunk-WV5GSRQM.mjs";
22
22
  import {
23
23
  BUILTIN_MODELS,
24
24
  BUILTIN_PROVIDERS,
@@ -32,6 +32,7 @@ import {
32
32
  appLogger,
33
33
  batchSegmentChineseWithStats,
34
34
  batchSegmentWithFrequency,
35
+ buildExcludeKeywordsConditions,
35
36
  buildSessionInfo,
36
37
  clearJiebaInstance,
37
38
  clearSessionIndex,
@@ -79,7 +80,7 @@ import {
79
80
  updateMemberAliases,
80
81
  updateSessionOwnerId,
81
82
  writeAuthProfile
82
- } from "./chunk-WPI6YSAR.mjs";
83
+ } from "./chunk-LVEJHB3X.mjs";
83
84
  import {
84
85
  __commonJS,
85
86
  __export,
@@ -2836,128 +2837,29 @@ function writeParseResultToDb(db, meta, members, messages) {
2836
2837
  return { messageCount, memberCount: members.length, skippedCount };
2837
2838
  }
2838
2839
 
2839
- // ../../packages/node-runtime/src/import/perf-logger.ts
2840
- import * as fs from "fs";
2841
- import * as path from "path";
2842
- var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
2843
- LogLevel2["ERROR"] = "ERROR";
2844
- LogLevel2["INFO"] = "INFO";
2845
- return LogLevel2;
2846
- })(LogLevel || {});
2847
- var lastLogTime = Date.now();
2848
- var lastMessageCount = 0;
2849
- var currentLogFile = null;
2850
- var errorCount = 0;
2851
- function initPerfLog(sessionId, logDir) {
2852
- try {
2853
- if (!fs.existsSync(logDir)) {
2854
- fs.mkdirSync(logDir, { recursive: true });
2855
- }
2856
- currentLogFile = path.join(logDir, `import_${sessionId}_${Date.now()}.log`);
2857
- fs.writeFileSync(currentLogFile, `=== Import Log ===
2858
- Start time: ${(/* @__PURE__ */ new Date()).toISOString()}
2859
-
2860
- `, "utf-8");
2861
- } catch {
2862
- }
2863
- }
2864
- function logPerf(event, messagesProcessed, batchSize) {
2865
- const now = Date.now();
2866
- const duration = now - lastLogTime;
2867
- const messagesDelta = messagesProcessed - lastMessageCount;
2868
- const speed = duration > 0 ? Math.round(messagesDelta / duration * 1e3) : 0;
2869
- let memory = 0;
2870
- try {
2871
- const used = process.memoryUsage();
2872
- memory = Math.round(used.heapUsed / 1024 / 1024);
2873
- } catch {
2874
- }
2875
- const logLine = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${event} | messages: ${messagesProcessed.toLocaleString()} | elapsed: ${duration}ms | speed: ${speed.toLocaleString()}/s | memory: ${memory}MB` + (batchSize ? ` | batch: ${batchSize}` : "") + "\n";
2876
- if (currentLogFile) {
2877
- try {
2878
- fs.appendFileSync(currentLogFile, logLine, "utf-8");
2879
- } catch {
2880
- }
2881
- }
2882
- lastLogTime = now;
2883
- lastMessageCount = messagesProcessed;
2884
- }
2885
- function logPerfDetail(detail) {
2886
- if (currentLogFile) {
2887
- try {
2888
- fs.appendFileSync(currentLogFile, ` ${detail}
2889
- `, "utf-8");
2890
- } catch {
2891
- }
2892
- }
2893
- }
2894
- function resetPerfLog() {
2895
- lastLogTime = Date.now();
2896
- lastMessageCount = 0;
2897
- currentLogFile = null;
2898
- errorCount = 0;
2899
- }
2900
- function getCurrentLogFile() {
2901
- return currentLogFile;
2902
- }
2903
- function writeLogLine(level, message) {
2904
- if (!currentLogFile) return;
2905
- const logLine = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${message}
2906
- `;
2907
- try {
2908
- fs.appendFileSync(currentLogFile, logLine, "utf-8");
2909
- } catch {
2910
- }
2911
- }
2912
- function logError(message, error) {
2913
- errorCount++;
2914
- const errorDetail = error ? `: ${error.message}` : "";
2915
- writeLogLine("ERROR" /* ERROR */, `${message}${errorDetail}`);
2916
- }
2917
- function logInfo(message) {
2918
- writeLogLine("INFO" /* INFO */, message);
2919
- }
2920
- function getErrorCount() {
2921
- return errorCount;
2922
- }
2923
- function logSummary(totalMessages, totalMembers) {
2924
- if (!currentLogFile) return;
2925
- const summary = `
2926
- === Import Summary ===
2927
- End time: ${(/* @__PURE__ */ new Date()).toISOString()}
2928
- Total messages: ${totalMessages.toLocaleString()}
2929
- Total members: ${totalMembers.toLocaleString()}
2930
- Errors: ${errorCount}
2931
- `;
2932
- try {
2933
- fs.appendFileSync(currentLogFile, summary, "utf-8");
2934
- } catch {
2935
- }
2936
- }
2937
-
2938
2840
  // ../../packages/parser/src/index.ts
2939
- import * as fs18 from "fs";
2940
- import * as path14 from "path";
2841
+ import * as fs17 from "fs";
2842
+ import * as path13 from "path";
2941
2843
 
2942
2844
  // ../../packages/parser/src/sniffer.ts
2943
- import * as fs2 from "fs";
2944
- import * as path2 from "path";
2845
+ import * as fs from "fs";
2846
+ import * as path from "path";
2945
2847
  var HEAD_SIZE = 64 * 1024;
2946
2848
  function readFileHead(filePath, size = HEAD_SIZE) {
2947
- const fd = fs2.openSync(filePath, "r");
2849
+ const fd = fs.openSync(filePath, "r");
2948
2850
  const buffer = Buffer.alloc(size);
2949
- const bytesRead = fs2.readSync(fd, buffer, 0, size, 0);
2950
- fs2.closeSync(fd);
2851
+ const bytesRead = fs.readSync(fd, buffer, 0, size, 0);
2852
+ fs.closeSync(fd);
2951
2853
  return buffer.slice(0, bytesRead).toString("utf-8");
2952
2854
  }
2953
2855
  function getExtension(filePath) {
2954
- return path2.extname(filePath).toLowerCase();
2856
+ return path.extname(filePath).toLowerCase();
2955
2857
  }
2956
2858
  function matchHeadSignatures(headContent, patterns) {
2957
2859
  return patterns.some((pattern) => pattern.test(headContent));
2958
2860
  }
2959
2861
  function matchFilenameSignatures(filePath, patterns) {
2960
- const filename = path2.basename(filePath);
2862
+ const filename = path.basename(filePath);
2961
2863
  return patterns.some((pattern) => pattern.test(filename));
2962
2864
  }
2963
2865
  function matchRequiredFields(headContent, fields) {
@@ -3105,18 +3007,18 @@ var import_stream_json = __toESM(require_stream_json(), 1);
3105
3007
  var import_Pick = __toESM(require_Pick(), 1);
3106
3008
  var import_StreamValues = __toESM(require_StreamValues(), 1);
3107
3009
  var import_stream_chain = __toESM(require_stream_chain(), 1);
3108
- import * as fs4 from "fs";
3010
+ import * as fs3 from "fs";
3109
3011
 
3110
3012
  // ../../packages/parser/src/utils.ts
3111
- import * as fs3 from "fs";
3013
+ import * as fs2 from "fs";
3112
3014
  function getFileSize(filePath) {
3113
- return fs3.statSync(filePath).size;
3015
+ return fs2.statSync(filePath).size;
3114
3016
  }
3115
3017
  function readFileHeadBytes(filePath, size) {
3116
- const fd = fs3.openSync(filePath, "r");
3018
+ const fd = fs2.openSync(filePath, "r");
3117
3019
  const buffer = Buffer.alloc(size);
3118
- const bytesRead = fs3.readSync(fd, buffer, 0, size, 0);
3119
- fs3.closeSync(fd);
3020
+ const bytesRead = fs2.readSync(fd, buffer, 0, size, 0);
3021
+ fs2.closeSync(fd);
3120
3022
  return buffer.slice(0, bytesRead).toString("utf-8");
3121
3023
  }
3122
3024
  var createProgress = (stage, bytesRead, totalBytes, messagesProcessed, message) => {
@@ -3146,13 +3048,185 @@ function isValidYear(timestampSeconds) {
3146
3048
  }
3147
3049
 
3148
3050
  // ../../packages/parser/src/formats/chatlab.ts
3149
- import * as path3 from "path";
3051
+ import * as path2 from "path";
3052
+
3053
+ // ../../packages/parser/src/native/loader.ts
3054
+ import { createRequire } from "module";
3055
+ var cachedModule;
3056
+ var cachedLoadError;
3057
+ function isNativeDisabled() {
3058
+ return process.env.CHATLAB_DISABLE_NATIVE_PERF === "1";
3059
+ }
3060
+ function isNativeParserModule(value) {
3061
+ return typeof value?.NativeParser === "function";
3062
+ }
3063
+ function requireNativeModule() {
3064
+ const requireFn = createRequire(import.meta.url);
3065
+ const module = requireFn("@openchatlab/parser-native");
3066
+ if (!isNativeParserModule(module)) {
3067
+ throw new Error("Native parser module missing NativeParser export");
3068
+ }
3069
+ return module;
3070
+ }
3071
+ function loadNativeParser() {
3072
+ if (isNativeDisabled()) return null;
3073
+ if (cachedModule !== void 0) return cachedModule;
3074
+ try {
3075
+ cachedModule = requireNativeModule();
3076
+ } catch (error) {
3077
+ cachedModule = null;
3078
+ cachedLoadError = error instanceof Error ? error.message : String(error);
3079
+ }
3080
+ return cachedModule;
3081
+ }
3082
+ function getNativeParserStatus() {
3083
+ if (isNativeDisabled()) return { available: false, disabled: true };
3084
+ const module = loadNativeParser();
3085
+ return { available: module !== null, disabled: false, error: module ? void 0 : cachedLoadError };
3086
+ }
3087
+
3088
+ // ../../packages/parser/src/native/create-native-parser.ts
3089
+ var PROGRESS_POLL_INTERVAL_MS = 200;
3090
+ async function* pumpNativeParser(adapter, options2, parser11, fallback) {
3091
+ const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
3092
+ const totalBytes = getFileSize(filePath);
3093
+ const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
3094
+ yield { type: "progress", data: initialProgress };
3095
+ onProgress?.(initialProgress);
3096
+ onLog?.(
3097
+ "info",
3098
+ `[NativeParser] Parsing ${adapter.label} with Rust kernel, size: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`
3099
+ );
3100
+ const progressTimer = setInterval(() => {
3101
+ const progress = parser11.progress();
3102
+ onProgress?.(
3103
+ createProgress(
3104
+ "parsing",
3105
+ progress.bytesRead,
3106
+ totalBytes,
3107
+ progress.messagesProcessed,
3108
+ `\u5DF2\u5904\u7406 ${progress.messagesProcessed} \u6761\u6D88\u606F...`
3109
+ )
3110
+ );
3111
+ }, PROGRESS_POLL_INTERVAL_MS);
3112
+ try {
3113
+ await parser11.parse();
3114
+ } catch (error) {
3115
+ onLog?.(
3116
+ "warn",
3117
+ `[NativeParser] Rust parse failed, falling back to TS parser: ${error instanceof Error ? error.message : String(error)}`
3118
+ );
3119
+ yield* fallback(options2);
3120
+ return;
3121
+ } finally {
3122
+ clearInterval(progressTimer);
3123
+ }
3124
+ const metaJson = JSON.parse(parser11.metaJson());
3125
+ yield { type: "meta", data: adapter.mapMeta(metaJson) };
3126
+ const members = adapter.mapMembers(parser11.takeMembers(), metaJson);
3127
+ yield { type: "members", data: members };
3128
+ let messagesProcessed = 0;
3129
+ while (true) {
3130
+ const batch = parser11.takeBatch(batchSize);
3131
+ if (!batch) break;
3132
+ messagesProcessed += batch.length;
3133
+ yield { type: "messages", data: batch.map((message) => adapter.mapMessage(message, metaJson)) };
3134
+ await new Promise((resolve2) => setImmediate(resolve2));
3135
+ }
3136
+ const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
3137
+ yield { type: "progress", data: doneProgress };
3138
+ onProgress?.(doneProgress);
3139
+ onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${members.length} \u4E2A\u6210\u5458`);
3140
+ yield { type: "done", data: { messageCount: messagesProcessed, memberCount: members.length } };
3141
+ }
3142
+ function createNativeFirstParser(adapter, fallback) {
3143
+ return async function* (options2) {
3144
+ const native = loadNativeParser();
3145
+ if (!native) {
3146
+ yield* fallback(options2);
3147
+ return;
3148
+ }
3149
+ let parser11;
3150
+ try {
3151
+ parser11 = new native.NativeParser(
3152
+ adapter.formatId,
3153
+ options2.filePath,
3154
+ options2.formatOptions ? JSON.stringify(options2.formatOptions) : void 0
3155
+ );
3156
+ } catch (error) {
3157
+ options2.onLog?.(
3158
+ "warn",
3159
+ `[NativeParser] Failed to create Rust parser, falling back to TS parser: ${error instanceof Error ? error.message : String(error)}`
3160
+ );
3161
+ yield* fallback(options2);
3162
+ return;
3163
+ }
3164
+ yield* pumpNativeParser(adapter, options2, parser11, fallback);
3165
+ };
3166
+ }
3167
+
3168
+ // ../../packages/parser/src/native/chatlab-native.ts
3169
+ function toParsedMember(member, fromHead) {
3170
+ if (!fromHead) {
3171
+ return {
3172
+ platformId: member.platformId,
3173
+ accountName: member.accountName,
3174
+ groupNickname: member.groupNickname
3175
+ };
3176
+ }
3177
+ return {
3178
+ platformId: member.platformId,
3179
+ accountName: member.accountName,
3180
+ groupNickname: member.groupNickname,
3181
+ avatar: member.avatar,
3182
+ // Role objects pass through with their original key set ({id} or {id, name}).
3183
+ roles: member.roles?.map((role) => role.name !== void 0 ? { id: role.id, name: role.name } : { id: role.id })
3184
+ };
3185
+ }
3186
+ var chatlabAdapter = {
3187
+ formatId: "chatlab",
3188
+ label: "ChatLab JSON",
3189
+ mapMeta(metaJson) {
3190
+ const meta = metaJson;
3191
+ return {
3192
+ name: meta.name,
3193
+ platform: meta.platform,
3194
+ type: meta.chatType,
3195
+ groupId: meta.groupId ?? void 0,
3196
+ groupAvatar: meta.groupAvatar ?? void 0
3197
+ };
3198
+ },
3199
+ mapMembers(members, metaJson) {
3200
+ const fromHead = metaJson.membersFromHead;
3201
+ return members.map((member) => toParsedMember(member, fromHead));
3202
+ },
3203
+ mapMessage(message) {
3204
+ return {
3205
+ senderPlatformId: message.senderPlatformId,
3206
+ senderAccountName: message.senderAccountName,
3207
+ senderGroupNickname: message.senderGroupNickname,
3208
+ // The kernel rejects non-number timestamps, so this is always set.
3209
+ timestamp: message.timestamp,
3210
+ type: message.messageType,
3211
+ // The kernel rejects a missing content key, so undefined here always
3212
+ // means the source value was JSON null.
3213
+ content: message.content ?? null,
3214
+ platformMessageId: message.platformMessageId,
3215
+ replyToMessageId: message.replyToMessageId
3216
+ };
3217
+ }
3218
+ };
3219
+ function withNativeChatlab(fallback) {
3220
+ return createNativeFirstParser(chatlabAdapter, fallback);
3221
+ }
3222
+
3223
+ // ../../packages/parser/src/formats/chatlab.ts
3150
3224
  var { parser } = import_stream_json.default;
3151
3225
  var { pick } = import_Pick.default;
3152
3226
  var { streamValues } = import_StreamValues.default;
3153
3227
  var { chain } = import_stream_chain.default;
3154
3228
  function extractNameFromFilePath(filePath) {
3155
- const basename13 = path3.basename(filePath);
3229
+ const basename13 = path2.basename(filePath);
3156
3230
  const name = basename13.replace(/\.json$/i, "");
3157
3231
  return name || "\u672A\u77E5\u7FA4\u804A";
3158
3232
  }
@@ -3241,7 +3315,7 @@ async function* parseChatLab(options2) {
3241
3315
  const memberMapFromMessages = /* @__PURE__ */ new Map();
3242
3316
  const messageBatch = [];
3243
3317
  await new Promise((resolve2, reject) => {
3244
- const readStream = fs4.createReadStream(filePath, { encoding: "utf-8" });
3318
+ const readStream = fs3.createReadStream(filePath, { encoding: "utf-8" });
3245
3319
  readStream.on("data", (chunk) => {
3246
3320
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
3247
3321
  });
@@ -3310,9 +3384,10 @@ async function* parseChatLab(options2) {
3310
3384
  }
3311
3385
  };
3312
3386
  }
3387
+ var parseChatLabAccelerated = withNativeChatlab(parseChatLab);
3313
3388
  var parser_ = {
3314
3389
  feature,
3315
- parse: parseChatLab
3390
+ parse: parseChatLabAccelerated
3316
3391
  };
3317
3392
  var module_ = {
3318
3393
  feature,
@@ -3321,11 +3396,11 @@ var module_ = {
3321
3396
  var chatlab_default = module_;
3322
3397
 
3323
3398
  // ../../packages/parser/src/formats/chatlab-jsonl.ts
3324
- import * as fs5 from "fs";
3399
+ import * as fs4 from "fs";
3325
3400
  import * as readline from "readline";
3326
- import * as path4 from "path";
3401
+ import * as path3 from "path";
3327
3402
  function extractNameFromFilePath2(filePath) {
3328
- const basename13 = path4.basename(filePath);
3403
+ const basename13 = path3.basename(filePath);
3329
3404
  const name = basename13.replace(/\.jsonl$/i, "");
3330
3405
  return name || "\u672A\u77E5\u7FA4\u804A";
3331
3406
  }
@@ -3366,7 +3441,7 @@ async function* parseChatLabJsonl(options2) {
3366
3441
  const messageBatch = [];
3367
3442
  let meta = null;
3368
3443
  let headerParsed = false;
3369
- const fileStream = fs5.createReadStream(filePath, { encoding: "utf-8" });
3444
+ const fileStream = fs4.createReadStream(filePath, { encoding: "utf-8" });
3370
3445
  const rl = readline.createInterface({
3371
3446
  input: fileStream,
3372
3447
  crlfDelay: Infinity
@@ -3489,16 +3564,16 @@ var import_stream_json3 = __toESM(require_stream_json(), 1);
3489
3564
  var import_Pick3 = __toESM(require_Pick(), 1);
3490
3565
  var import_StreamValues3 = __toESM(require_StreamValues(), 1);
3491
3566
  var import_stream_chain3 = __toESM(require_stream_chain(), 1);
3492
- import * as fs7 from "fs";
3493
- import * as path6 from "path";
3567
+ import * as fs6 from "fs";
3568
+ import * as path5 from "path";
3494
3569
 
3495
3570
  // ../../packages/parser/src/formats/shuakami-qq-preprocessor.ts
3496
3571
  var import_stream_json2 = __toESM(require_stream_json(), 1);
3497
3572
  var import_Pick2 = __toESM(require_Pick(), 1);
3498
3573
  var import_StreamValues2 = __toESM(require_StreamValues(), 1);
3499
3574
  var import_stream_chain2 = __toESM(require_stream_chain(), 1);
3500
- import * as fs6 from "fs";
3501
- import * as path5 from "path";
3575
+ import * as fs5 from "fs";
3576
+ import * as path4 from "path";
3502
3577
  import * as os from "os";
3503
3578
  var { parser: parser3 } = import_stream_json2.default;
3504
3579
  var { pick: pick2 } = import_Pick2.default;
@@ -3506,11 +3581,11 @@ var { streamValues: streamValues2 } = import_StreamValues2.default;
3506
3581
  var { chain: chain2 } = import_stream_chain2.default;
3507
3582
  var PREPROCESS_THRESHOLD = 50 * 1024 * 1024;
3508
3583
  function getTempDir() {
3509
- return path5.join(os.tmpdir(), "chatlab");
3584
+ return path4.join(os.tmpdir(), "chatlab");
3510
3585
  }
3511
3586
  function ensureDir(dir) {
3512
- if (!fs6.existsSync(dir)) {
3513
- fs6.mkdirSync(dir, { recursive: true });
3587
+ if (!fs5.existsSync(dir)) {
3588
+ fs5.mkdirSync(dir, { recursive: true });
3514
3589
  }
3515
3590
  }
3516
3591
  function extractJsonObject(content, key) {
@@ -3552,12 +3627,12 @@ function extractJsonObject(content, key) {
3552
3627
  }
3553
3628
  function readAvatarsFromFile(filePath) {
3554
3629
  try {
3555
- const stats = fs6.statSync(filePath);
3630
+ const stats = fs5.statSync(filePath);
3556
3631
  const tailSize = Math.min(stats.size, 5e6);
3557
- const fd = fs6.openSync(filePath, "r");
3632
+ const fd = fs5.openSync(filePath, "r");
3558
3633
  const buffer = Buffer.alloc(tailSize);
3559
- fs6.readSync(fd, buffer, 0, tailSize, stats.size - tailSize);
3560
- fs6.closeSync(fd);
3634
+ fs5.readSync(fd, buffer, 0, tailSize, stats.size - tailSize);
3635
+ fs5.closeSync(fd);
3561
3636
  const tailContent = buffer.toString("utf-8");
3562
3637
  return extractJsonObject(tailContent, "avatars");
3563
3638
  } catch {
@@ -3616,15 +3691,15 @@ async function preprocessQQJson(inputPath, onProgress) {
3616
3691
  let messagesProcessed = 0;
3617
3692
  const tempDir = getTempDir();
3618
3693
  ensureDir(tempDir);
3619
- const outputFilename = `slim_${Date.now()}_${path5.basename(inputPath)}`;
3620
- const outputPath = path5.join(tempDir, outputFilename);
3694
+ const outputFilename = `slim_${Date.now()}_${path4.basename(inputPath)}`;
3695
+ const outputPath = path4.join(tempDir, outputFilename);
3621
3696
  onProgress?.(createProgress("parsing", 0, totalBytes, 0, ""));
3622
3697
  const avatarsStr = readAvatarsFromFile(inputPath);
3623
3698
  return new Promise((resolve2, reject) => {
3624
3699
  const headChunks = [];
3625
3700
  let headSize = 0;
3626
3701
  const maxHeadSize = 1e5;
3627
- const headStream = fs6.createReadStream(inputPath, { encoding: "utf-8" });
3702
+ const headStream = fs5.createReadStream(inputPath, { encoding: "utf-8" });
3628
3703
  let chatInfo = { name: "\u672A\u77E5\u7FA4\u804A", type: "group" };
3629
3704
  let metadata;
3630
3705
  let statistics;
@@ -3661,8 +3736,8 @@ async function preprocessQQJson(inputPath, onProgress) {
3661
3736
  } catch {
3662
3737
  }
3663
3738
  onProgress?.(createProgress("parsing", 0, totalBytes, 0, ""));
3664
- const readStream = fs6.createReadStream(inputPath, { encoding: "utf-8" });
3665
- const writeStream = fs6.createWriteStream(outputPath, { encoding: "utf-8" });
3739
+ const readStream = fs5.createReadStream(inputPath, { encoding: "utf-8" });
3740
+ const writeStream = fs5.createWriteStream(outputPath, { encoding: "utf-8" });
3666
3741
  readStream.on("data", (chunk) => {
3667
3742
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
3668
3743
  });
@@ -3707,8 +3782,8 @@ async function preprocessQQJson(inputPath, onProgress) {
3707
3782
  });
3708
3783
  pipeline2.on("error", (err) => {
3709
3784
  writeStream.destroy();
3710
- if (fs6.existsSync(outputPath)) {
3711
- fs6.unlinkSync(outputPath);
3785
+ if (fs5.existsSync(outputPath)) {
3786
+ fs5.unlinkSync(outputPath);
3712
3787
  }
3713
3788
  onProgress?.(createProgress("error", bytesRead, totalBytes, messagesProcessed, err.message));
3714
3789
  reject(err);
@@ -3719,8 +3794,8 @@ async function preprocessQQJson(inputPath, onProgress) {
3719
3794
  }
3720
3795
  function cleanupTempFile(filePath) {
3721
3796
  try {
3722
- if (fs6.existsSync(filePath) && filePath.includes(getTempDir())) {
3723
- fs6.unlinkSync(filePath);
3797
+ if (fs5.existsSync(filePath) && filePath.includes(getTempDir())) {
3798
+ fs5.unlinkSync(filePath);
3724
3799
  }
3725
3800
  } catch {
3726
3801
  }
@@ -3787,7 +3862,7 @@ function extractJsonObject2(content, key) {
3787
3862
  return null;
3788
3863
  }
3789
3864
  function extractNameFromFilePath3(filePath) {
3790
- const basename13 = path6.basename(filePath);
3865
+ const basename13 = path5.basename(filePath);
3791
3866
  return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
3792
3867
  }
3793
3868
  function countSenders(content) {
@@ -3872,7 +3947,7 @@ async function* parseV4(options2) {
3872
3947
  const memberMap = /* @__PURE__ */ new Map();
3873
3948
  const messageBatch = [];
3874
3949
  await new Promise((resolve2, reject) => {
3875
- const readStream = fs7.createReadStream(filePath, { encoding: "utf-8" });
3950
+ const readStream = fs6.createReadStream(filePath, { encoding: "utf-8" });
3876
3951
  readStream.on("data", (chunk) => {
3877
3952
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
3878
3953
  });
@@ -3930,12 +4005,12 @@ async function* parseV4(options2) {
3930
4005
  });
3931
4006
  const avatarsMap = /* @__PURE__ */ new Map();
3932
4007
  try {
3933
- const stats = fs7.statSync(filePath);
4008
+ const stats = fs6.statSync(filePath);
3934
4009
  const tailSize = Math.min(stats.size, 5e6);
3935
- const fd = fs7.openSync(filePath, "r");
4010
+ const fd = fs6.openSync(filePath, "r");
3936
4011
  const buffer = Buffer.alloc(tailSize);
3937
- fs7.readSync(fd, buffer, 0, tailSize, stats.size - tailSize);
3938
- fs7.closeSync(fd);
4012
+ fs6.readSync(fd, buffer, 0, tailSize, stats.size - tailSize);
4013
+ fs6.closeSync(fd);
3939
4014
  const tailContent = buffer.toString("utf-8");
3940
4015
  const avatarsStr = extractJsonObject2(tailContent, "avatars");
3941
4016
  if (avatarsStr) {
@@ -3987,8 +4062,8 @@ var module_3 = {
3987
4062
  var shuakami_qq_exporter_default = module_3;
3988
4063
 
3989
4064
  // ../../packages/parser/src/formats/shuakami-qq-exporter-chunked.ts
3990
- import * as fs8 from "fs";
3991
- import * as path7 from "path";
4065
+ import * as fs7 from "fs";
4066
+ import * as path6 from "path";
3992
4067
  import * as readline2 from "readline";
3993
4068
  var feature4 = {
3994
4069
  id: "shuakami-qq-exporter-chunked",
@@ -4037,7 +4112,7 @@ function convertMessageType2(msgType, content, isRecalled) {
4037
4112
  return 0 /* TEXT */;
4038
4113
  }
4039
4114
  function readManifest(manifestPath) {
4040
- const content = fs8.readFileSync(manifestPath, "utf-8");
4115
+ const content = fs7.readFileSync(manifestPath, "utf-8");
4041
4116
  return JSON.parse(content);
4042
4117
  }
4043
4118
  function getChunkRelativePath(chunk) {
@@ -4055,9 +4130,9 @@ function calculateTotalBytes(manifest, baseDir) {
4055
4130
  let total = 0;
4056
4131
  for (const chunk of manifest.chunked.chunks) {
4057
4132
  const relativePath = getChunkRelativePath(chunk);
4058
- const chunkPath = path7.join(baseDir, relativePath);
4059
- if (fs8.existsSync(chunkPath)) {
4060
- total += fs8.statSync(chunkPath).size;
4133
+ const chunkPath = path6.join(baseDir, relativePath);
4134
+ if (fs7.existsSync(chunkPath)) {
4135
+ total += fs7.statSync(chunkPath).size;
4061
4136
  }
4062
4137
  }
4063
4138
  return total;
@@ -4065,10 +4140,10 @@ function calculateTotalBytes(manifest, baseDir) {
4065
4140
  function readAvatars(baseDir, avatarsInfo) {
4066
4141
  const avatarsMap = /* @__PURE__ */ new Map();
4067
4142
  if (!avatarsInfo?.file) return avatarsMap;
4068
- const avatarsPath = path7.join(baseDir, avatarsInfo.file);
4069
- if (!fs8.existsSync(avatarsPath)) return avatarsMap;
4143
+ const avatarsPath = path6.join(baseDir, avatarsInfo.file);
4144
+ if (!fs7.existsSync(avatarsPath)) return avatarsMap;
4070
4145
  try {
4071
- const content = fs8.readFileSync(avatarsPath, "utf-8");
4146
+ const content = fs7.readFileSync(avatarsPath, "utf-8");
4072
4147
  const avatars = JSON.parse(content);
4073
4148
  for (const [uin, avatar] of Object.entries(avatars)) {
4074
4149
  if (avatar && typeof avatar === "string" && avatar.startsWith("data:image/")) {
@@ -4086,7 +4161,7 @@ async function* readJsonlFile(filePath, onParseError) {
4086
4161
  let parseErrorCount = 0;
4087
4162
  const MAX_PARSE_ERRORS_TO_LOG = 10;
4088
4163
  try {
4089
- fileStream = fs8.createReadStream(filePath, { encoding: "utf-8" });
4164
+ fileStream = fs7.createReadStream(filePath, { encoding: "utf-8" });
4090
4165
  rl = readline2.createInterface({
4091
4166
  input: fileStream,
4092
4167
  crlfDelay: Infinity
@@ -4117,7 +4192,7 @@ async function* readJsonlFile(filePath, onParseError) {
4117
4192
  async function* parseChunkedJsonl(options2) {
4118
4193
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
4119
4194
  const manifestPath = filePath;
4120
- const baseDir = path7.dirname(manifestPath);
4195
+ const baseDir = path6.dirname(manifestPath);
4121
4196
  onLog?.("info", `\u5F00\u59CB\u89E3\u6790 manifest: ${manifestPath}`);
4122
4197
  onLog?.("info", `\u57FA\u7840\u76EE\u5F55: ${baseDir}`);
4123
4198
  let manifest;
@@ -4171,13 +4246,13 @@ async function* parseChunkedJsonl(options2) {
4171
4246
  for (let chunkIndex = 0; chunkIndex < manifest.chunked.chunks.length; chunkIndex++) {
4172
4247
  const chunkInfo = manifest.chunked.chunks[chunkIndex];
4173
4248
  const relativePath = getChunkRelativePath(chunkInfo);
4174
- const chunkPath = path7.join(baseDir, relativePath);
4249
+ const chunkPath = path6.join(baseDir, relativePath);
4175
4250
  const chunkMessageCount = getChunkMessageCount(chunkInfo);
4176
- if (!fs8.existsSync(chunkPath)) {
4251
+ if (!fs7.existsSync(chunkPath)) {
4177
4252
  onLog?.("error", `\u5206\u5757\u6587\u4EF6\u4E0D\u5B58\u5728: ${chunkPath}`);
4178
4253
  continue;
4179
4254
  }
4180
- const chunkSize = fs8.statSync(chunkPath).size;
4255
+ const chunkSize = fs7.statSync(chunkPath).size;
4181
4256
  let chunkMessagesRead = 0;
4182
4257
  let chunkParseErrors = 0;
4183
4258
  onLog?.(
@@ -4299,8 +4374,49 @@ var import_stream_json4 = __toESM(require_stream_json(), 1);
4299
4374
  var import_Pick4 = __toESM(require_Pick(), 1);
4300
4375
  var import_StreamValues4 = __toESM(require_StreamValues(), 1);
4301
4376
  var import_stream_chain4 = __toESM(require_stream_chain(), 1);
4302
- import * as fs9 from "fs";
4303
- import * as path8 from "path";
4377
+ import * as fs8 from "fs";
4378
+ import * as path7 from "path";
4379
+
4380
+ // ../../packages/parser/src/native/weflow-native.ts
4381
+ var weflowAdapter = {
4382
+ formatId: "weflow",
4383
+ label: "WeFlow export",
4384
+ mapMeta(metaJson) {
4385
+ const meta = metaJson;
4386
+ return {
4387
+ name: meta.name,
4388
+ platform: KNOWN_PLATFORMS.WECHAT,
4389
+ type: meta.chatType === "private" ? "private" /* PRIVATE */ : "group" /* GROUP */,
4390
+ groupId: meta.groupId ?? void 0,
4391
+ groupAvatar: meta.groupAvatar ?? void 0,
4392
+ ownerId: meta.ownerId ?? void 0
4393
+ };
4394
+ },
4395
+ mapMembers(members) {
4396
+ return members.map((member) => ({
4397
+ platformId: member.platformId,
4398
+ accountName: member.accountName,
4399
+ avatar: member.avatar
4400
+ }));
4401
+ },
4402
+ mapMessage(message) {
4403
+ return {
4404
+ // The kernel always emits an id string (String(localId), JS semantics).
4405
+ platformMessageId: message.platformMessageId,
4406
+ senderPlatformId: message.senderPlatformId,
4407
+ senderAccountName: message.senderAccountName,
4408
+ // WeFlow has no separate group nickname field (same as the TS parser).
4409
+ senderGroupNickname: void 0,
4410
+ // The TS parser passes createTime through as-is, including null.
4411
+ timestamp: message.timestamp ?? null,
4412
+ type: message.messageType,
4413
+ content: message.content ?? null
4414
+ };
4415
+ }
4416
+ };
4417
+ function withNativeWeflow(fallback) {
4418
+ return createNativeFirstParser(weflowAdapter, fallback);
4419
+ }
4304
4420
 
4305
4421
  // ../../packages/parser/src/formats/weflow-preprocessor.ts
4306
4422
  var weflowPreprocessor = {
@@ -4332,7 +4448,7 @@ var { pick: pick4 } = import_Pick4.default;
4332
4448
  var { streamValues: streamValues4 } = import_StreamValues4.default;
4333
4449
  var { chain: chain4 } = import_stream_chain4.default;
4334
4450
  function extractNameFromFilePath4(filePath) {
4335
- const basename13 = path8.basename(filePath);
4451
+ const basename13 = path7.basename(filePath);
4336
4452
  const name = basename13.replace(/\.json$/i, "");
4337
4453
  return name || "\u672A\u77E5\u804A\u5929";
4338
4454
  }
@@ -4401,7 +4517,7 @@ async function* parseWeFlow(options2) {
4401
4517
  let session = null;
4402
4518
  try {
4403
4519
  await new Promise((resolve2) => {
4404
- const sessionStream = fs9.createReadStream(filePath, { encoding: "utf-8" });
4520
+ const sessionStream = fs8.createReadStream(filePath, { encoding: "utf-8" });
4405
4521
  let sessionContent = "";
4406
4522
  let inSession = false;
4407
4523
  let braceDepth = 0;
@@ -4524,7 +4640,7 @@ async function* parseWeFlow(options2) {
4524
4640
  if (avatarsMap.size === 0) {
4525
4641
  try {
4526
4642
  await new Promise((resolve2) => {
4527
- const avatarStream = fs9.createReadStream(filePath, { encoding: "utf-8" });
4643
+ const avatarStream = fs8.createReadStream(filePath, { encoding: "utf-8" });
4528
4644
  let avatarsContent = "";
4529
4645
  let inAvatars = false;
4530
4646
  let braceDepth = 0;
@@ -4591,7 +4707,7 @@ async function* parseWeFlow(options2) {
4591
4707
  let ownerId;
4592
4708
  try {
4593
4709
  await new Promise((resolve2) => {
4594
- const scanStream = fs9.createReadStream(filePath, { encoding: "utf-8" });
4710
+ const scanStream = fs8.createReadStream(filePath, { encoding: "utf-8" });
4595
4711
  const scanPipeline = chain4([scanStream, parser5(), pick4({ filter: /^messages\.\d+$/ }), streamValues4()]);
4596
4712
  scanPipeline.on("data", ({ value }) => {
4597
4713
  if (value.isSend === 1 && value.senderUsername && !value.senderUsername.endsWith("@chatroom")) {
@@ -4617,7 +4733,7 @@ async function* parseWeFlow(options2) {
4617
4733
  const memberMap = /* @__PURE__ */ new Map();
4618
4734
  const messageBatch = [];
4619
4735
  await new Promise((resolve2, reject) => {
4620
- const readStream = fs9.createReadStream(filePath, { encoding: "utf-8" });
4736
+ const readStream = fs8.createReadStream(filePath, { encoding: "utf-8" });
4621
4737
  readStream.on("data", (chunk) => {
4622
4738
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
4623
4739
  });
@@ -4712,9 +4828,10 @@ async function* parseWeFlow(options2) {
4712
4828
  data: { messageCount: messagesProcessed, memberCount: memberMap.size }
4713
4829
  };
4714
4830
  }
4831
+ var parseWeFlowAccelerated = withNativeWeflow(parseWeFlow);
4715
4832
  var parser_4 = {
4716
4833
  feature: feature5,
4717
- parse: parseWeFlow
4834
+ parse: parseWeFlowAccelerated
4718
4835
  };
4719
4836
  var module_5 = {
4720
4837
  feature: feature5,
@@ -4739,7 +4856,7 @@ var feature6 = {
4739
4856
  };
4740
4857
  var parser_5 = {
4741
4858
  feature: feature6,
4742
- parse: parseWeFlow
4859
+ parse: parseWeFlowAccelerated
4743
4860
  };
4744
4861
  var module_6 = {
4745
4862
  feature: feature6,
@@ -4753,7 +4870,7 @@ var import_stream_json5 = __toESM(require_stream_json(), 1);
4753
4870
  var import_Pick5 = __toESM(require_Pick(), 1);
4754
4871
  var import_StreamValues5 = __toESM(require_StreamValues(), 1);
4755
4872
  var import_stream_chain5 = __toESM(require_stream_chain(), 1);
4756
- import * as fs10 from "fs";
4873
+ import * as fs9 from "fs";
4757
4874
  var { parser: parser6 } = import_stream_json5.default;
4758
4875
  var { pick: pick5 } = import_Pick5.default;
4759
4876
  var { streamValues: streamValues5 } = import_StreamValues5.default;
@@ -4903,7 +5020,7 @@ async function* parseDiscordExporter(options2) {
4903
5020
  const memberMap = /* @__PURE__ */ new Map();
4904
5021
  const messageBatch = [];
4905
5022
  await new Promise((resolve2, reject) => {
4906
- const readStream = fs10.createReadStream(filePath, { encoding: "utf-8" });
5023
+ const readStream = fs9.createReadStream(filePath, { encoding: "utf-8" });
4907
5024
  readStream.on("data", (chunk) => {
4908
5025
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
4909
5026
  });
@@ -5019,7 +5136,7 @@ var import_stream_chain6 = __toESM(require_stream_chain(), 1);
5019
5136
  var import_stream_json6 = __toESM(require_stream_json(), 1);
5020
5137
  var import_Pick6 = __toESM(require_Pick(), 1);
5021
5138
  var import_StreamValues6 = __toESM(require_StreamValues(), 1);
5022
- import * as fs11 from "fs";
5139
+ import * as fs10 from "fs";
5023
5140
 
5024
5141
  // ../../packages/parser/src/formats/utils/telegram-utils.ts
5025
5142
  function mapChatType(telegramType) {
@@ -5105,7 +5222,7 @@ var feature8 = {
5105
5222
  async function scanChats(filePath) {
5106
5223
  const chats = [];
5107
5224
  return new Promise((resolve2, reject) => {
5108
- const readStream = fs11.createReadStream(filePath, { encoding: "utf-8" });
5225
+ const readStream = fs10.createReadStream(filePath, { encoding: "utf-8" });
5109
5226
  const pipeline2 = chain6([readStream, parser7(), pick6({ filter: /^chats\.list\.\d+$/ }), streamValues6()]);
5110
5227
  pipeline2.on("data", ({ value }) => {
5111
5228
  const chat = value;
@@ -5138,7 +5255,7 @@ async function* parseTelegram(options2) {
5138
5255
  onLog?.("info", `\u76EE\u6807\u804A\u5929\u7D22\u5F15: ${chatIndex}`);
5139
5256
  const chatPathFilter = `chats.list.${chatIndex}`;
5140
5257
  const chatData = await new Promise((resolve2, reject) => {
5141
- const readStream = fs11.createReadStream(filePath, { encoding: "utf-8" });
5258
+ const readStream = fs10.createReadStream(filePath, { encoding: "utf-8" });
5142
5259
  readStream.on("data", (chunk) => {
5143
5260
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
5144
5261
  });
@@ -5246,7 +5363,7 @@ var telegram_native_default = module_8;
5246
5363
  var import_stream_chain7 = __toESM(require_stream_chain(), 1);
5247
5364
  var import_stream_json7 = __toESM(require_stream_json(), 1);
5248
5365
  var import_StreamValues7 = __toESM(require_StreamValues(), 1);
5249
- import * as fs12 from "fs";
5366
+ import * as fs11 from "fs";
5250
5367
  var { chain: chain7 } = import_stream_chain7.default;
5251
5368
  var { parser: parser8 } = import_stream_json7.default;
5252
5369
  var { streamValues: streamValues7 } = import_StreamValues7.default;
@@ -5276,7 +5393,7 @@ async function* parseTelegramSingle(options2) {
5276
5393
  onProgress?.(initialProgress);
5277
5394
  onLog?.("info", `\u5F00\u59CB\u89E3\u6790 Telegram \u5355\u804A\u5929 JSON \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
5278
5395
  const chatData = await new Promise((resolve2, reject) => {
5279
- const readStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
5396
+ const readStream = fs11.createReadStream(filePath, { encoding: "utf-8" });
5280
5397
  const pipeline2 = chain7([readStream, parser8(), streamValues7()]);
5281
5398
  let found = false;
5282
5399
  pipeline2.on("data", ({ value }) => {
@@ -5374,11 +5491,11 @@ var module_9 = {
5374
5491
  var telegram_native_single_default = module_9;
5375
5492
 
5376
5493
  // ../../packages/parser/src/formats/whatsapp-native-txt.ts
5377
- import * as fs13 from "fs";
5378
- import * as path9 from "path";
5494
+ import * as fs12 from "fs";
5495
+ import * as path8 from "path";
5379
5496
  import * as readline3 from "readline";
5380
5497
  function extractNameFromFilePath5(filePath) {
5381
- const basename13 = path9.basename(filePath);
5498
+ const basename13 = path8.basename(filePath);
5382
5499
  const matchZhCn = basename13.match(/^与(.+?)的\s*WhatsApp\s*聊天\.txt$/i);
5383
5500
  if (matchZhCn) return matchZhCn[1].trim();
5384
5501
  const matchZhTw = basename13.match(/^與(.+?)的\s*WhatsApp\s*對話\.txt$/i);
@@ -5582,7 +5699,7 @@ async function* parseWhatsApp(options2) {
5582
5699
  messagesProcessed++;
5583
5700
  }
5584
5701
  };
5585
- const fileStream = fs13.createReadStream(filePath, { encoding: "utf-8" });
5702
+ const fileStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
5586
5703
  const rl = readline3.createInterface({
5587
5704
  input: fileStream,
5588
5705
  crlfDelay: Infinity
@@ -5702,11 +5819,11 @@ var module_10 = {
5702
5819
  var whatsapp_native_txt_default = module_10;
5703
5820
 
5704
5821
  // ../../packages/parser/src/formats/qq-native-txt.ts
5705
- import * as fs14 from "fs";
5706
- import * as path10 from "path";
5822
+ import * as fs13 from "fs";
5823
+ import * as path9 from "path";
5707
5824
  import * as readline4 from "readline";
5708
5825
  function extractNameFromFilePath6(filePath) {
5709
- const basename13 = path10.basename(filePath);
5826
+ const basename13 = path9.basename(filePath);
5710
5827
  const name = basename13.replace(/\.txt$/i, "");
5711
5828
  return name || "\u672A\u77E5\u7FA4\u804A";
5712
5829
  }
@@ -5788,7 +5905,7 @@ async function* parseTxt(options2) {
5788
5905
  messagesProcessed++;
5789
5906
  }
5790
5907
  };
5791
- const fileStream = fs14.createReadStream(filePath, { encoding: "utf-8" });
5908
+ const fileStream = fs13.createReadStream(filePath, { encoding: "utf-8" });
5792
5909
  const rl = readline4.createInterface({
5793
5910
  input: fileStream,
5794
5911
  crlfDelay: Infinity
@@ -5888,8 +6005,8 @@ var module_11 = {
5888
6005
  var qq_native_txt_default = module_11;
5889
6006
 
5890
6007
  // ../../packages/parser/src/formats/instagram-native.ts
5891
- import * as fs15 from "fs";
5892
- import * as path11 from "path";
6008
+ import * as fs14 from "fs";
6009
+ import * as path10 from "path";
5893
6010
  var feature12 = {
5894
6011
  id: "instagram-native",
5895
6012
  name: "Instagram \u5B98\u65B9\u5BFC\u51FA",
@@ -5915,7 +6032,7 @@ function decodeInstagramText(text) {
5915
6032
  }
5916
6033
  }
5917
6034
  function extractNameFromFilePath7(filePath) {
5918
- const basename13 = path11.basename(filePath);
6035
+ const basename13 = path10.basename(filePath);
5919
6036
  return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u5BF9\u8BDD";
5920
6037
  }
5921
6038
  function isSystemMessage2(content) {
@@ -5984,7 +6101,7 @@ async function* parseInstagram(options2) {
5984
6101
  onLog?.("info", `\u5F00\u59CB\u89E3\u6790 Instagram \u804A\u5929\u8BB0\u5F55\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
5985
6102
  let data;
5986
6103
  try {
5987
- const content = fs15.readFileSync(filePath, "utf-8");
6104
+ const content = fs14.readFileSync(filePath, "utf-8");
5988
6105
  data = JSON.parse(content);
5989
6106
  } catch (error) {
5990
6107
  const err = new Error(`\u65E0\u6CD5\u89E3\u6790 Instagram JSON \u6587\u4EF6: ${error}`);
@@ -6079,8 +6196,8 @@ var import_stream_chain8 = __toESM(require_stream_chain(), 1);
6079
6196
  var import_stream_json8 = __toESM(require_stream_json(), 1);
6080
6197
  var import_Pick7 = __toESM(require_Pick(), 1);
6081
6198
  var import_StreamValues8 = __toESM(require_StreamValues(), 1);
6082
- import * as fs16 from "fs";
6083
- import * as path12 from "path";
6199
+ import * as fs15 from "fs";
6200
+ import * as path11 from "path";
6084
6201
  var { chain: chain8 } = import_stream_chain8.default;
6085
6202
  var { parser: parser9 } = import_stream_json8.default;
6086
6203
  var { pick: pick7 } = import_Pick7.default;
@@ -6163,12 +6280,12 @@ function normalizeIdentity(user) {
6163
6280
  };
6164
6281
  }
6165
6282
  function readJson(filePath) {
6166
- return JSON.parse(fs16.readFileSync(filePath, "utf8"));
6283
+ return JSON.parse(fs15.readFileSync(filePath, "utf8"));
6167
6284
  }
6168
6285
  function resolveManifestFile(manifestDir, relativePath) {
6169
- const resolved = path12.resolve(manifestDir, relativePath);
6170
- const relative2 = path12.relative(manifestDir, resolved);
6171
- if (relative2.startsWith("..") || path12.isAbsolute(relative2)) {
6286
+ const resolved = path11.resolve(manifestDir, relativePath);
6287
+ const relative2 = path11.relative(manifestDir, resolved);
6288
+ if (relative2.startsWith("..") || path11.isAbsolute(relative2)) {
6172
6289
  throw new Error(`Google Chat manifest path escapes its directory: ${relativePath}`);
6173
6290
  }
6174
6291
  return resolved;
@@ -6181,7 +6298,7 @@ function readManifest2(filePath) {
6181
6298
  if (manifest.chatType !== "private" && manifest.chatType !== "group") {
6182
6299
  throw new Error("Invalid Google Chat chat type");
6183
6300
  }
6184
- const manifestDir = path12.dirname(filePath);
6301
+ const manifestDir = path11.dirname(filePath);
6185
6302
  return {
6186
6303
  manifest,
6187
6304
  userInfoPath: resolveManifestFile(manifestDir, manifest.userInfoFile),
@@ -6259,7 +6376,7 @@ async function* parseGoogleChat(options2) {
6259
6376
  });
6260
6377
  }
6261
6378
  yield { type: "members", data: Array.from(memberMap.values()) };
6262
- const readStream = fs16.createReadStream(messagesPath, { encoding: "utf8" });
6379
+ const readStream = fs15.createReadStream(messagesPath, { encoding: "utf8" });
6263
6380
  let bytesRead = 0;
6264
6381
  let messagesProcessed = 0;
6265
6382
  readStream.on("data", (chunk) => {
@@ -6312,8 +6429,8 @@ var module_13 = {
6312
6429
  var google_chat_takeout_default = module_13;
6313
6430
 
6314
6431
  // ../../packages/parser/src/formats/line-native-txt.ts
6315
- import * as fs17 from "fs";
6316
- import * as path13 from "path";
6432
+ import * as fs16 from "fs";
6433
+ import * as path12 from "path";
6317
6434
  var feature14 = {
6318
6435
  id: "line-native-txt",
6319
6436
  name: "LINE \u5B98\u65B9\u5BFC\u51FA TXT",
@@ -6341,7 +6458,7 @@ var feature14 = {
6341
6458
  }
6342
6459
  };
6343
6460
  function extractNameFromFilePath8(filePath) {
6344
- const basename13 = path13.basename(filePath, ".txt");
6461
+ const basename13 = path12.basename(filePath, ".txt");
6345
6462
  const name = basename13.replace(/^\[LINE\]\s*/i, "").trim();
6346
6463
  return name || "\u672A\u77E5\u804A\u5929";
6347
6464
  }
@@ -6521,7 +6638,7 @@ async function* parseLINE(options2) {
6521
6638
  yield { type: "progress", data: initialProgress };
6522
6639
  onProgress?.(initialProgress);
6523
6640
  onLog?.("info", `\u5F00\u59CB\u89E3\u6790 LINE \u5BFC\u51FA\u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
6524
- const content = fs17.readFileSync(filePath, "utf-8");
6641
+ const content = fs16.readFileSync(filePath, "utf-8");
6525
6642
  const lines = content.split("\n").map((line) => line.replace(/\r$/, ""));
6526
6643
  let currentDate = null;
6527
6644
  let chatName = extractNameFromFilePath8(filePath);
@@ -6829,12 +6946,12 @@ function findEntryFileInDirectory(dirPath, maxDepth = 1) {
6829
6946
  if (depth > maxDepth) return null;
6830
6947
  let entries;
6831
6948
  try {
6832
- entries = fs18.readdirSync(current, { withFileTypes: true });
6949
+ entries = fs17.readdirSync(current, { withFileTypes: true });
6833
6950
  } catch {
6834
6951
  return null;
6835
6952
  }
6836
6953
  for (const entry of entries) {
6837
- const full = path14.join(current, entry.name);
6954
+ const full = path13.join(current, entry.name);
6838
6955
  if (entry.isFile() && entry.name.endsWith(".json")) {
6839
6956
  const format = detectFormat(full);
6840
6957
  if (format) return full;
@@ -6842,7 +6959,7 @@ function findEntryFileInDirectory(dirPath, maxDepth = 1) {
6842
6959
  }
6843
6960
  for (const entry of entries) {
6844
6961
  if (entry.isDirectory()) {
6845
- const found = walk(path14.join(current, entry.name), depth + 1);
6962
+ const found = walk(path13.join(current, entry.name), depth + 1);
6846
6963
  if (found) return found;
6847
6964
  }
6848
6965
  }
@@ -6873,8 +6990,124 @@ async function streamParseFile(filePath, callbacks, formatId) {
6873
6990
  }
6874
6991
  }
6875
6992
 
6993
+ // ../../packages/node-runtime/src/import/native-parser-status.ts
6994
+ var logged = false;
6995
+ function logNativeParserStatus() {
6996
+ if (logged) return;
6997
+ logged = true;
6998
+ const status = getNativeParserStatus();
6999
+ if (status.available) {
7000
+ appLogger.info("import", "Native parser kernels available (Rust module loaded)");
7001
+ } else if (status.disabled) {
7002
+ appLogger.info("import", "Native parser kernels disabled via CHATLAB_DISABLE_NATIVE_PERF=1; using TS parsers");
7003
+ } else {
7004
+ appLogger.warn("import", "Native parser kernels unavailable; imports fall back to TS parsers", {
7005
+ reason: status.error ?? "unknown"
7006
+ });
7007
+ }
7008
+ }
7009
+
7010
+ // ../../packages/node-runtime/src/import/perf-logger.ts
7011
+ import * as fs18 from "fs";
7012
+ import * as path14 from "path";
7013
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
7014
+ LogLevel2["ERROR"] = "ERROR";
7015
+ LogLevel2["INFO"] = "INFO";
7016
+ return LogLevel2;
7017
+ })(LogLevel || {});
7018
+ var lastLogTime = Date.now();
7019
+ var lastMessageCount = 0;
7020
+ var currentLogFile = null;
7021
+ var errorCount = 0;
7022
+ function initPerfLog(sessionId, logDir) {
7023
+ try {
7024
+ if (!fs18.existsSync(logDir)) {
7025
+ fs18.mkdirSync(logDir, { recursive: true });
7026
+ }
7027
+ currentLogFile = path14.join(logDir, `import_${sessionId}_${Date.now()}.log`);
7028
+ fs18.writeFileSync(currentLogFile, `=== Import Log ===
7029
+ Start time: ${(/* @__PURE__ */ new Date()).toISOString()}
7030
+
7031
+ `, "utf-8");
7032
+ } catch {
7033
+ }
7034
+ }
7035
+ function logPerf(event, messagesProcessed, batchSize) {
7036
+ const now = Date.now();
7037
+ const duration = now - lastLogTime;
7038
+ const messagesDelta = messagesProcessed - lastMessageCount;
7039
+ const speed = duration > 0 ? Math.round(messagesDelta / duration * 1e3) : 0;
7040
+ let memory = 0;
7041
+ try {
7042
+ const used = process.memoryUsage();
7043
+ memory = Math.round(used.heapUsed / 1024 / 1024);
7044
+ } catch {
7045
+ }
7046
+ const logLine = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${event} | messages: ${messagesProcessed.toLocaleString()} | elapsed: ${duration}ms | speed: ${speed.toLocaleString()}/s | memory: ${memory}MB` + (batchSize ? ` | batch: ${batchSize}` : "") + "\n";
7047
+ if (currentLogFile) {
7048
+ try {
7049
+ fs18.appendFileSync(currentLogFile, logLine, "utf-8");
7050
+ } catch {
7051
+ }
7052
+ }
7053
+ lastLogTime = now;
7054
+ lastMessageCount = messagesProcessed;
7055
+ }
7056
+ function logPerfDetail(detail) {
7057
+ if (currentLogFile) {
7058
+ try {
7059
+ fs18.appendFileSync(currentLogFile, ` ${detail}
7060
+ `, "utf-8");
7061
+ } catch {
7062
+ }
7063
+ }
7064
+ }
7065
+ function resetPerfLog() {
7066
+ lastLogTime = Date.now();
7067
+ lastMessageCount = 0;
7068
+ currentLogFile = null;
7069
+ errorCount = 0;
7070
+ }
7071
+ function getCurrentLogFile() {
7072
+ return currentLogFile;
7073
+ }
7074
+ function writeLogLine(level, message) {
7075
+ if (!currentLogFile) return;
7076
+ const logLine = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${message}
7077
+ `;
7078
+ try {
7079
+ fs18.appendFileSync(currentLogFile, logLine, "utf-8");
7080
+ } catch {
7081
+ }
7082
+ }
7083
+ function logError(message, error) {
7084
+ errorCount++;
7085
+ const errorDetail = error ? `: ${error.message}` : "";
7086
+ writeLogLine("ERROR" /* ERROR */, `${message}${errorDetail}`);
7087
+ }
7088
+ function logInfo(message) {
7089
+ writeLogLine("INFO" /* INFO */, message);
7090
+ }
7091
+ function getErrorCount() {
7092
+ return errorCount;
7093
+ }
7094
+ function logSummary(totalMessages, totalMembers) {
7095
+ if (!currentLogFile) return;
7096
+ const summary = `
7097
+ === Import Summary ===
7098
+ End time: ${(/* @__PURE__ */ new Date()).toISOString()}
7099
+ Total messages: ${totalMessages.toLocaleString()}
7100
+ Total members: ${totalMembers.toLocaleString()}
7101
+ Errors: ${errorCount}
7102
+ `;
7103
+ try {
7104
+ fs18.appendFileSync(currentLogFile, summary, "utf-8");
7105
+ } catch {
7106
+ }
7107
+ }
7108
+
6876
7109
  // ../../packages/node-runtime/src/nlp/word-frequency.ts
6877
- function buildMessageQuery(timeFilter, memberId) {
7110
+ function buildMessageQuery(timeFilter, memberId, excludeKeywords) {
6878
7111
  const conditions = [];
6879
7112
  const params = [];
6880
7113
  if (timeFilter?.startTs !== void 0) {
@@ -6889,6 +7122,11 @@ function buildMessageQuery(timeFilter, memberId) {
6889
7122
  conditions.push("msg.sender_id = ?");
6890
7123
  params.push(memberId);
6891
7124
  }
7125
+ if (excludeKeywords && excludeKeywords.length > 0) {
7126
+ const exclude = buildExcludeKeywordsConditions(excludeKeywords);
7127
+ conditions.push(...exclude.conditions);
7128
+ params.push(...exclude.params);
7129
+ }
6892
7130
  conditions.push("COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'");
6893
7131
  conditions.push("msg.type = 0");
6894
7132
  conditions.push("msg.content IS NOT NULL");
@@ -6910,9 +7148,10 @@ function computeWordFrequency(db, params) {
6910
7148
  customPosTags,
6911
7149
  enableStopwords = true,
6912
7150
  dictType = "default",
6913
- excludeWords
7151
+ excludeWords,
7152
+ excludeKeywords
6914
7153
  } = params;
6915
- const { clause, params: filterParams } = buildMessageQuery(timeFilter, memberId);
7154
+ const { clause, params: filterParams } = buildMessageQuery(timeFilter, memberId, excludeKeywords);
6916
7155
  const messages = db.prepare(`SELECT msg.content FROM message msg JOIN member m ON msg.sender_id = m.id${clause}`).all(...filterParams);
6917
7156
  if (messages.length === 0) {
6918
7157
  return { words: [], totalWords: 0, totalMessages: 0, uniqueWords: 0 };
@@ -7173,6 +7412,7 @@ function searchByFts(db, keywords, limit = 1e3, offset = 0) {
7173
7412
  import * as fs20 from "fs";
7174
7413
  var BATCH_COMMIT_SIZE = 5e4;
7175
7414
  var CHECKPOINT_INTERVAL = 2e5;
7415
+ var yieldToEventLoop = () => new Promise((resolve2) => setImmediate(resolve2));
7176
7416
  function defaultGenerateSessionId() {
7177
7417
  const ts = Date.now();
7178
7418
  const rand = Math.random().toString(36).substring(2, 8);
@@ -7254,7 +7494,13 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
7254
7494
  `INSERT INTO meta (name, platform, type, imported_at, group_id, group_avatar, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?)`
7255
7495
  );
7256
7496
  const insertMember = db.prepare(
7257
- `INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, avatar, roles) VALUES (?, ?, ?, ?, ?)`
7497
+ `INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles)
7498
+ VALUES (?, ?, ?, ?, ?)
7499
+ ON CONFLICT(platform_id) DO UPDATE SET
7500
+ account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name),
7501
+ group_nickname = COALESCE(NULLIF(excluded.group_nickname, ''), group_nickname),
7502
+ avatar = COALESCE(NULLIF(excluded.avatar, ''), avatar),
7503
+ roles = CASE WHEN excluded.roles != '[]' THEN excluded.roles ELSE roles END`
7258
7504
  );
7259
7505
  const getMemberId = db.prepare(`SELECT id FROM member WHERE platform_id = ?`);
7260
7506
  const insertMessage = db.prepare(
@@ -7297,7 +7543,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
7297
7543
  lastCheckpointCount = totalMessageCount;
7298
7544
  }
7299
7545
  onProgress({
7300
- stage: "importing",
7546
+ stage: "saving",
7301
7547
  bytesRead: 0,
7302
7548
  totalBytes: 0,
7303
7549
  messagesProcessed: totalMessageCount,
@@ -7469,13 +7715,14 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
7469
7715
  inTransaction = false;
7470
7716
  }
7471
7717
  onProgress({
7472
- stage: "importing",
7718
+ stage: "saving",
7473
7719
  bytesRead: 0,
7474
7720
  totalBytes: 0,
7475
7721
  messagesProcessed: totalMessageCount,
7476
7722
  percentage: 100,
7477
7723
  message: ""
7478
7724
  });
7725
+ await yieldToEventLoop();
7479
7726
  logger?.perf("Writing nickname history", totalMessageCount);
7480
7727
  db.exec("BEGIN TRANSACTION");
7481
7728
  let historyCount = 0;
@@ -7491,13 +7738,14 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
7491
7738
  db.exec("COMMIT");
7492
7739
  logger?.perf(`Nickname history written (${historyCount} entries)`, totalMessageCount);
7493
7740
  onProgress({
7494
- stage: "importing",
7741
+ stage: "indexing",
7495
7742
  bytesRead: 0,
7496
7743
  totalBytes: 0,
7497
7744
  messagesProcessed: totalMessageCount,
7498
7745
  percentage: 100,
7499
7746
  message: ""
7500
7747
  });
7748
+ await yieldToEventLoop();
7501
7749
  logger?.perf("Creating indexes", totalMessageCount);
7502
7750
  db.exec(CHAT_DB_INDEXES);
7503
7751
  logger?.perf("Indexes created", totalMessageCount);
@@ -7508,13 +7756,14 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
7508
7756
  logger?.error("FTS index build failed (non-fatal)", ftsError instanceof Error ? ftsError : void 0);
7509
7757
  }
7510
7758
  onProgress({
7511
- stage: "importing",
7759
+ stage: "indexing",
7512
7760
  bytesRead: 0,
7513
7761
  totalBytes: 0,
7514
7762
  messagesProcessed: totalMessageCount,
7515
7763
  percentage: 100,
7516
7764
  message: ""
7517
7765
  });
7766
+ await yieldToEventLoop();
7518
7767
  doCheckpoint();
7519
7768
  logger?.perf("WAL checkpoint done", totalMessageCount);
7520
7769
  try {
@@ -10550,45 +10799,65 @@ var defaultLogger4 = {
10550
10799
  warn: () => {
10551
10800
  }
10552
10801
  };
10802
+ function emptyStats(count) {
10803
+ return {
10804
+ input: count,
10805
+ output: count,
10806
+ cleaned: 0,
10807
+ blacklistRemoved: 0,
10808
+ denoiseRemoved: 0,
10809
+ mergeCombined: 0,
10810
+ desensitizeRulesApplied: 0
10811
+ };
10812
+ }
10553
10813
  function preprocessMessages(messages, config, logger = defaultLogger4) {
10554
- if (!config || !hasAnyEnabled(config)) return messages;
10555
- if (messages.length === 0) return messages;
10556
- const inputCount = messages.length;
10814
+ return preprocessMessagesWithStats(messages, config, logger).messages;
10815
+ }
10816
+ function preprocessMessagesWithStats(messages, config, logger = defaultLogger4) {
10817
+ if (!config || !hasAnyEnabled(config)) return { messages, stats: emptyStats(messages.length) };
10818
+ if (messages.length === 0) return { messages, stats: emptyStats(0) };
10819
+ const stats = emptyStats(messages.length);
10557
10820
  let result = [...messages];
10558
10821
  const applied = [];
10559
10822
  if (config.dataCleaning !== false) {
10560
10823
  const cleaned = applyDataCleaning(result);
10561
10824
  if (cleaned.changed > 0) {
10562
10825
  result = cleaned.messages;
10826
+ stats.cleaned = cleaned.changed;
10563
10827
  applied.push(`dataCleaning: ${cleaned.changed} messages cleaned`);
10564
10828
  }
10565
10829
  }
10566
10830
  if (config.blacklistKeywords.length > 0) {
10567
10831
  const before = result.length;
10568
10832
  result = applyBlacklistFilter(result, config.blacklistKeywords);
10833
+ stats.blacklistRemoved = before - result.length;
10569
10834
  applied.push(`blacklist: ${before} \u2192 ${result.length} (-${before - result.length})`);
10570
10835
  }
10571
10836
  if (config.denoise) {
10572
10837
  const before = result.length;
10573
10838
  result = applyDenoise(result);
10839
+ stats.denoiseRemoved = before - result.length;
10574
10840
  applied.push(`denoise: ${before} \u2192 ${result.length} (-${before - result.length})`);
10575
10841
  }
10576
10842
  if (config.mergeConsecutive) {
10577
10843
  const before = result.length;
10578
10844
  result = applyMergeConsecutive(result, config.mergeWindowSeconds ?? MERGE_WINDOW_DEFAULT);
10845
+ stats.mergeCombined = before - result.length;
10579
10846
  applied.push(`merge: ${before} \u2192 ${result.length} (-${before - result.length})`);
10580
10847
  }
10581
10848
  if (config.desensitize) {
10582
10849
  const enabledRules = (config.desensitizeRules || []).filter((r) => r.enabled);
10583
10850
  if (enabledRules.length > 0) {
10584
10851
  result = applyDesensitize(result, enabledRules, logger);
10852
+ stats.desensitizeRulesApplied = enabledRules.length;
10585
10853
  applied.push(`desensitize: ${enabledRules.length} rules applied`);
10586
10854
  }
10587
10855
  }
10588
- logger.info("Preprocess", `Pipeline: ${inputCount} \u2192 ${result.length} messages`, {
10856
+ stats.output = result.length;
10857
+ logger.info("Preprocess", `Pipeline: ${stats.input} \u2192 ${result.length} messages`, {
10589
10858
  strategies: applied
10590
10859
  });
10591
- return result;
10860
+ return { messages: result, stats };
10592
10861
  }
10593
10862
  function hasAnyEnabled(config) {
10594
10863
  return config.dataCleaning !== false || config.mergeConsecutive || config.blacklistKeywords.length > 0 || config.denoise || config.desensitize;
@@ -10683,9 +10952,16 @@ function applyMergeConsecutive(messages, windowSeconds) {
10683
10952
  const sameSender = isSameSender(current, msg);
10684
10953
  const withinWindow = Math.abs(msg.timestamp - current.timestamp) <= windowSeconds;
10685
10954
  if (sameSender && withinWindow) {
10955
+ const mergedIds = [
10956
+ ...current.mergedIds ?? (current.id != null ? [current.id] : []),
10957
+ ...msg.mergedIds ?? (msg.id != null ? [msg.id] : [])
10958
+ ];
10686
10959
  current = {
10687
10960
  ...current,
10688
- content: [current.content, msg.content].filter(Boolean).join("\n")
10961
+ content: [current.content, msg.content].filter(Boolean).join("\n"),
10962
+ // keep first message's id, track last id for [#start-end] range citations
10963
+ mergedEndId: msg.id ?? current.mergedEndId,
10964
+ ...mergedIds.length > 0 ? { mergedIds } : {}
10689
10965
  };
10690
10966
  } else {
10691
10967
  merged.push(current);
@@ -10706,15 +10982,29 @@ function applyDesensitize(messages, rules, logger) {
10706
10982
  if (compiledRules.length === 0) return messages;
10707
10983
  return messages.map((msg) => {
10708
10984
  if (!msg.content) return msg;
10709
- let content = msg.content;
10710
- for (const { regex, replacement } of compiledRules) {
10711
- regex.lastIndex = 0;
10712
- content = content.replace(regex, replacement);
10713
- }
10985
+ const content = applyCompiledRules(msg.content, compiledRules);
10714
10986
  if (content === msg.content) return msg;
10715
10987
  return { ...msg, content };
10716
10988
  });
10717
10989
  }
10990
+ function applyCompiledRules(text, compiledRules) {
10991
+ let result = text;
10992
+ for (const { regex, replacement } of compiledRules) {
10993
+ regex.lastIndex = 0;
10994
+ result = result.replace(regex, replacement);
10995
+ }
10996
+ return result;
10997
+ }
10998
+ function desensitizeText(text, rules, logger = defaultLogger4) {
10999
+ const enabledRules = rules.filter((r) => r.enabled);
11000
+ if (enabledRules.length === 0 || !text) return text;
11001
+ return applyCompiledRules(text, compileRules(enabledRules, logger));
11002
+ }
11003
+ function matchesBlacklist(text, keywords) {
11004
+ if (keywords.length === 0 || !text) return false;
11005
+ const lower = text.toLowerCase();
11006
+ return keywords.some((kw) => lower.includes(kw.toLowerCase()));
11007
+ }
10718
11008
  var regexCache = /* @__PURE__ */ new Map();
10719
11009
  function compileRules(rules, logger) {
10720
11010
  const result = [];
@@ -11040,14 +11330,26 @@ function t(key, locale) {
11040
11330
  return "";
11041
11331
  }
11042
11332
  var MAX_MESSAGE_CONTENT_LENGTH = 200;
11043
- function formatMessageCompact(msg, locale) {
11333
+ function formatMessageCompact(msg, locale, options2) {
11044
11334
  const localeStr = isChineseLocale(locale) ? "zh-CN" : "en-US";
11045
11335
  const time = new Date(msg.timestamp * 1e3).toLocaleString(localeStr);
11046
11336
  let content = msg.content || t("noContent", locale);
11047
- if (content.length > MAX_MESSAGE_CONTENT_LENGTH) {
11048
- content = content.slice(0, MAX_MESSAGE_CONTENT_LENGTH) + "...";
11337
+ const maxLength = options2?.maxContentLength ?? MAX_MESSAGE_CONTENT_LENGTH;
11338
+ if (maxLength > 0 && content.length > maxLength) {
11339
+ content = content.slice(0, maxLength) + "...";
11340
+ }
11341
+ let idPrefix = "";
11342
+ if (options2?.includeMessageId && msg.id != null) {
11343
+ const range = msg.mergedEndId != null && msg.mergedEndId !== msg.id ? `${msg.id}-${msg.mergedEndId}` : `${msg.id}`;
11344
+ const hitMark = isHitMessage(msg, options2.hitIds) ? "*" : "";
11345
+ idPrefix = `[#${range}${hitMark}] `;
11049
11346
  }
11050
- return `${time} ${msg.senderName}: ${content}`;
11347
+ return `${idPrefix}${time} ${msg.senderName}: ${content}`;
11348
+ }
11349
+ function isHitMessage(msg, hitIds) {
11350
+ if (!hitIds) return false;
11351
+ if (msg.id != null && hitIds.has(msg.id)) return true;
11352
+ return msg.mergedIds?.some((id) => hitIds.has(id)) ?? false;
11051
11353
  }
11052
11354
  function formatTimeRange(timeFilter, locale) {
11053
11355
  if (!timeFilter) return t("allTime", locale);
@@ -11081,16 +11383,19 @@ function formatToolResultAsText(details) {
11081
11383
  lines.push("");
11082
11384
  let lastDate = "";
11083
11385
  for (const msg of messages) {
11084
- const spaceIdx = msg.indexOf(" ");
11085
- const secondSpaceIdx = msg.indexOf(" ", spaceIdx + 1);
11386
+ const prefixMatch = /^(\[#[^\]]+\] )/.exec(msg);
11387
+ const prefix = prefixMatch ? prefixMatch[1] : "";
11388
+ const body = prefix ? msg.slice(prefix.length) : msg;
11389
+ const spaceIdx = body.indexOf(" ");
11390
+ const secondSpaceIdx = body.indexOf(" ", spaceIdx + 1);
11086
11391
  if (spaceIdx > 0 && secondSpaceIdx > 0) {
11087
- const date = msg.slice(0, spaceIdx);
11088
- const rest = msg.slice(spaceIdx + 1);
11392
+ const date = body.slice(0, spaceIdx);
11393
+ const rest = body.slice(spaceIdx + 1);
11089
11394
  if (date !== lastDate) {
11090
11395
  lines.push(`--- ${date} ---`);
11091
11396
  lastDate = date;
11092
11397
  }
11093
- lines.push(rest);
11398
+ lines.push(prefix + rest);
11094
11399
  } else {
11095
11400
  lines.push(msg);
11096
11401
  }
@@ -11163,14 +11468,26 @@ function applyPreprocessingPipeline(options2) {
11163
11468
  maxToolResultTokens,
11164
11469
  truncationStrategy = "keep_last",
11165
11470
  extraDetails = {},
11166
- logger
11471
+ logger,
11472
+ includeMessageIds = false,
11473
+ hitIds,
11474
+ maxContentChars
11167
11475
  } = options2;
11168
- const processed = preprocessMessages(rawMessages, preprocessConfig, logger);
11476
+ const { messages: processed, stats: preprocessStats } = preprocessMessagesWithStats(
11477
+ rawMessages,
11478
+ preprocessConfig,
11479
+ logger
11480
+ );
11169
11481
  let nameMapLine = "";
11170
11482
  if (anonymizeNames) {
11171
11483
  nameMapLine = anonymizeMessageNames(processed, ownerPlatformId);
11172
11484
  }
11173
- let formatted = processed.map((m) => formatMessageCompact(m, locale));
11485
+ const formatOptions = includeMessageIds || maxContentChars != null ? {
11486
+ includeMessageId: includeMessageIds,
11487
+ hitIds: hitIds ? new Set(hitIds) : void 0,
11488
+ maxContentLength: maxContentChars
11489
+ } : void 0;
11490
+ let formatted = processed.map((m) => formatMessageCompact(m, locale, formatOptions));
11174
11491
  let wasTruncated = false;
11175
11492
  const originalCount = formatted.length;
11176
11493
  if (maxToolResultTokens && maxToolResultTokens > 0) {
@@ -11190,7 +11507,12 @@ function applyPreprocessingPipeline(options2) {
11190
11507
  if (nameMapLine) {
11191
11508
  textContent = nameMapLine + "\n" + textContent;
11192
11509
  }
11193
- return { text: textContent, details: finalDetails };
11510
+ const stats = {
11511
+ ...preprocessStats,
11512
+ renderedBlocks: formatted.length,
11513
+ truncated: wasTruncated
11514
+ };
11515
+ return { text: textContent, details: finalDetails, stats };
11194
11516
  }
11195
11517
 
11196
11518
  // ../../packages/node-runtime/src/ai/agent/constants.ts
@@ -16731,6 +17053,7 @@ __export(relationships_exports, {
16731
17053
  // ../../packages/node-runtime/src/semantic-index/config.ts
16732
17054
  import fs30 from "fs";
16733
17055
  import path27 from "path";
17056
+ var DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE = "huggingface";
16734
17057
  var SEMANTIC_INDEX_CONFIG_VERSION = 1;
16735
17058
  var SEARCH_MAX_RESULTS_DEFAULT = 10;
16736
17059
  var SEARCH_MAX_RESULTS_MIN = 5;
@@ -16745,7 +17068,7 @@ function defaultSemanticIndexConfig() {
16745
17068
  version: SEMANTIC_INDEX_CONFIG_VERSION,
16746
17069
  enabled: true,
16747
17070
  mode: "local",
16748
- local: { modelId: "" },
17071
+ local: { modelId: "", downloadSource: DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE },
16749
17072
  api: null,
16750
17073
  searchMaxResults: SEARCH_MAX_RESULTS_DEFAULT
16751
17074
  };
@@ -16790,7 +17113,10 @@ function normalizeConfig(raw) {
16790
17113
  // 旧配置无 enabled 字段时默认开启,保证既有用户功能不被意外关闭
16791
17114
  enabled: raw.enabled ?? true,
16792
17115
  mode,
16793
- local: { modelId: raw.local?.modelId ?? base.local.modelId },
17116
+ local: {
17117
+ modelId: raw.local?.modelId ?? base.local.modelId,
17118
+ downloadSource: raw.local?.downloadSource === "hf-mirror" ? "hf-mirror" : DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE
17119
+ },
16794
17120
  api,
16795
17121
  searchMaxResults: SEARCH_MAX_RESULTS_DEFAULT
16796
17122
  };
@@ -16840,7 +17166,7 @@ import path28 from "path";
16840
17166
 
16841
17167
  // ../../packages/node-runtime/src/semantic-index/chunker-config.ts
16842
17168
  import { createHash as createHash2 } from "crypto";
16843
- var CHUNKER_VERSION = "v1.2";
17169
+ var CHUNKER_VERSION = "v1.3";
16844
17170
  var STRATEGY_ID = "balanced";
16845
17171
  var DEFAULT_CHUNKER_CONFIG = {
16846
17172
  parentGapSeconds: 1800,
@@ -16888,6 +17214,44 @@ function parseParentBounds(parentId) {
16888
17214
  return { startMessageId, endMessageId };
16889
17215
  }
16890
17216
 
17217
+ // ../../packages/node-runtime/src/semantic-index/tokens.ts
17218
+ import { Buffer as Buffer2 } from "buffer";
17219
+ var UTF8_BYTES_PER_ESTIMATED_TOKEN = 4;
17220
+ function isCjkCharacter(char) {
17221
+ return /[\u3400-\u9fff\uf900-\ufaff]/u.test(char);
17222
+ }
17223
+ function estimateTokens2(text) {
17224
+ let cjk = 0;
17225
+ let other = 0;
17226
+ for (const ch of text) {
17227
+ if (isCjkCharacter(ch)) cjk++;
17228
+ else other++;
17229
+ }
17230
+ return cjk + Math.ceil(other / 4);
17231
+ }
17232
+ function clampEstimatedTokens(text, maxTokens) {
17233
+ const limit = Math.max(0, Math.floor(maxTokens));
17234
+ if (limit === 0) return "";
17235
+ const byteLimit = limit * UTF8_BYTES_PER_ESTIMATED_TOKEN;
17236
+ if (estimateTokens2(text) <= limit && Buffer2.byteLength(text, "utf8") <= byteLimit) return text;
17237
+ let cjk = 0;
17238
+ let other = 0;
17239
+ let bytes = 0;
17240
+ let end = 0;
17241
+ for (const char of text) {
17242
+ const isCjk = isCjkCharacter(char);
17243
+ const nextCjk = cjk + (isCjk ? 1 : 0);
17244
+ const nextOther = other + (isCjk ? 0 : 1);
17245
+ const nextBytes = bytes + Buffer2.byteLength(char, "utf8");
17246
+ if (nextCjk + Math.ceil(nextOther / 4) > limit || nextBytes > byteLimit) break;
17247
+ cjk = nextCjk;
17248
+ other = nextOther;
17249
+ bytes = nextBytes;
17250
+ end += char.length;
17251
+ }
17252
+ return text.slice(0, end);
17253
+ }
17254
+
16891
17255
  // ../../packages/node-runtime/src/semantic-index/embedding/api.ts
16892
17256
  var API_DOCUMENT_BATCH_SIZE = 8;
16893
17257
  var defaultFetchFn = (url, init) => fetch(url, init);
@@ -16927,7 +17291,19 @@ var OpenAICompatibleEmbeddingProvider = class {
16927
17291
  });
16928
17292
  if (!response.ok) {
16929
17293
  await response.text().catch(() => "");
16930
- throw new Error(`Embedding API request failed: ${response.status}`);
17294
+ const estimatedTokens = texts.map(estimateTokens2);
17295
+ const traceId = response.headers?.get("x-siliconcloud-trace-id")?.trim();
17296
+ const diagnostics = [
17297
+ `model=${this.config.model}`,
17298
+ `batchSize=${texts.length}`,
17299
+ `maxInputTokens=${Math.max(...estimatedTokens)}`,
17300
+ `totalInputTokens=${estimatedTokens.reduce((sum, count) => sum + count, 0)}`,
17301
+ `maxInputChars=${Math.max(...texts.map((text) => text.length))}`
17302
+ ];
17303
+ if (traceId) diagnostics.push(`traceId=${traceId}`);
17304
+ const error = new Error(`Embedding API request failed: ${response.status} (${diagnostics.join(", ")})`);
17305
+ appLogger.error("semantic-index", "embedding API request failed", error);
17306
+ throw error;
16931
17307
  }
16932
17308
  const payload = await response.json();
16933
17309
  const sorted = [...payload.data].sort((a, b) => a.index - b.index);
@@ -16959,6 +17335,11 @@ function clampTextChars(text, maxChars) {
16959
17335
  // ../../packages/node-runtime/src/semantic-index/embedding/local.ts
16960
17336
  var SOCKS_PROXY_PROTOCOLS = /* @__PURE__ */ new Set(["socks:", "socks4:", "socks5:"]);
16961
17337
  var LOCAL_ONNX_SESSION_OPTIONS = { intraOpNumThreads: 1, interOpNumThreads: 1 };
17338
+ var HUGGING_FACE_MODEL_HOST = "https://huggingface.co/";
17339
+ var HF_MIRROR_MODEL_HOST = "https://hf-mirror.com/";
17340
+ function resolveModelDownloadRemoteHost(source) {
17341
+ return source === "hf-mirror" ? HF_MIRROR_MODEL_HOST : HUGGING_FACE_MODEL_HOST;
17342
+ }
16962
17343
  async function createProxyFetch(proxyUrl) {
16963
17344
  const parsed = new URL(proxyUrl);
16964
17345
  if (SOCKS_PROXY_PROTOCOLS.has(parsed.protocol)) {
@@ -16973,8 +17354,15 @@ async function createProxyFetch(proxyUrl) {
16973
17354
  return undiciFetch(input, nextInit);
16974
17355
  });
16975
17356
  }
16976
- var defaultPipelineFactory = async ({ modelId, dtype, cacheDir, modelDownloadProxyUrl }) => {
17357
+ var defaultPipelineFactory = async ({
17358
+ modelId,
17359
+ dtype,
17360
+ cacheDir,
17361
+ modelDownloadProxyUrl,
17362
+ modelDownloadSource
17363
+ }) => {
16977
17364
  const transformers = await import("./transformers.node-PUMHAODN.mjs");
17365
+ transformers.env.remoteHost = resolveModelDownloadRemoteHost(modelDownloadSource);
16978
17366
  if (cacheDir) {
16979
17367
  transformers.env.cacheDir = cacheDir;
16980
17368
  transformers.env.allowRemoteModels = true;
@@ -17015,6 +17403,7 @@ var LocalEmbeddingProvider = class {
17015
17403
  dtype: this.profile.dtype,
17016
17404
  cacheDir: this.options.cacheDir,
17017
17405
  modelDownloadProxyUrl: this.options.modelDownloadProxyUrl,
17406
+ modelDownloadSource: this.options.modelDownloadSource,
17018
17407
  sessionOptions: LOCAL_ONNX_SESSION_OPTIONS
17019
17408
  }).catch((error) => {
17020
17409
  this.extractorPromise = null;
@@ -17108,6 +17497,7 @@ function createEmbedder(config, deps = {}) {
17108
17497
  return new LocalEmbeddingProvider(profile, {
17109
17498
  cacheDir: deps.modelsCacheDir,
17110
17499
  modelDownloadProxyUrl: deps.modelDownloadProxyUrl,
17500
+ modelDownloadSource: config.local.downloadSource,
17111
17501
  pipelineFactory: deps.localPipelineFactory
17112
17502
  });
17113
17503
  }
@@ -17659,19 +18049,6 @@ var SemanticIndexJobQueue = class {
17659
18049
 
17660
18050
  // ../../packages/node-runtime/src/semantic-index/chunker.ts
17661
18051
  import { createHash as createHash3 } from "crypto";
17662
-
17663
- // ../../packages/node-runtime/src/semantic-index/tokens.ts
17664
- function estimateTokens2(text) {
17665
- let cjk = 0;
17666
- let other = 0;
17667
- for (const ch of text) {
17668
- if (/[\u3400-\u9fff\uf900-\ufaff]/u.test(ch)) cjk++;
17669
- else other++;
17670
- }
17671
- return cjk + Math.ceil(other / 4);
17672
- }
17673
-
17674
- // ../../packages/node-runtime/src/semantic-index/chunker.ts
17675
18052
  var VOID_MESSAGE_TYPES = /* @__PURE__ */ new Set([
17676
18053
  1 /* IMAGE */,
17677
18054
  2 /* VOICE */,
@@ -17756,6 +18133,9 @@ function sumEffectiveChars(messages) {
17756
18133
  function sha256Hex2(input) {
17757
18134
  return createHash3("sha256").update(input, "utf-8").digest("hex");
17758
18135
  }
18136
+ var EMBEDDING_HEADER_BUDGET_RATIO = 0.25;
18137
+ var SOURCE_TITLE_HEADER_SHARE = 1 / 3;
18138
+ var SENDER_NAMES_BODY_BUDGET_RATIO = 0.1;
17759
18139
  function pad2(n) {
17760
18140
  return String(n).padStart(2, "0");
17761
18141
  }
@@ -17773,23 +18153,51 @@ function formatTimeRange2(startTs, endTs) {
17773
18153
  const sameDay = start.getFullYear() === end.getFullYear() && start.getMonth() === end.getMonth() && start.getDate() === end.getDate();
17774
18154
  return `${formatDateTime(startTs)} - ${sameDay ? formatTime2(endTs) : formatDateTime(endTs)}`;
17775
18155
  }
17776
- function buildEmbeddingInput(source, messages) {
18156
+ function resolveSenderNameTokenCap(nameTokenCounts, totalLimit) {
18157
+ const total = nameTokenCounts.reduce((sum, count) => sum + count, 0);
18158
+ if (total <= totalLimit) return null;
18159
+ const sorted = [...nameTokenCounts].sort((a, b) => a - b);
18160
+ let consumed = 0;
18161
+ for (let i = 0; i < sorted.length; i++) {
18162
+ const cap = Math.floor((totalLimit - consumed) / (sorted.length - i));
18163
+ if (sorted[i] > cap) return cap;
18164
+ consumed += sorted[i];
18165
+ }
18166
+ return null;
18167
+ }
18168
+ function buildEmbeddingInput(source, messages, maxTokens) {
18169
+ const limit = Math.max(0, Math.floor(maxTokens));
18170
+ if (limit === 0) return "";
17777
18171
  const effective = messages.filter((m) => !isSemanticVoid(m));
17778
18172
  const startTs = messages[0].ts;
17779
18173
  const endTs = messages[messages.length - 1].ts;
17780
- const lines = [];
18174
+ const headerLimit = Math.max(1, Math.floor(limit * EMBEDDING_HEADER_BUDGET_RATIO));
18175
+ const bodyFloor = Math.max(1, limit - headerLimit);
18176
+ const sourceTitleLimit = Math.max(1, Math.floor(headerLimit * SOURCE_TITLE_HEADER_SHARE));
18177
+ const senderNamesTotalLimit = Math.floor(bodyFloor * SENDER_NAMES_BODY_BUDGET_RATIO);
18178
+ const bodySenderNameCap = resolveSenderNameTokenCap(
18179
+ effective.map((m) => estimateTokens2(m.senderName)),
18180
+ senderNamesTotalLimit
18181
+ );
18182
+ const clampHeaderSenderName = (name) => clampEstimatedTokens(name, sourceTitleLimit);
18183
+ const clampBodySenderName = (name) => bodySenderNameCap === null ? name : clampEstimatedTokens(name, bodySenderNameCap);
18184
+ const headerLines = [];
17781
18185
  const kindLabel = source.kind === "group" ? "\u7FA4\u804A" : "\u79C1\u804A";
17782
- lines.push(`[\u6765\u6E90] ${source.title}\uFF08${kindLabel}\uFF09`);
17783
- lines.push(`[\u65F6\u95F4\u8303\u56F4] ${formatTimeRange2(startTs, endTs)}`);
18186
+ headerLines.push(`[\u6765\u6E90] ${clampEstimatedTokens(source.title, sourceTitleLimit)}\uFF08${kindLabel}\uFF09`);
18187
+ headerLines.push(`[\u65F6\u95F4\u8303\u56F4] ${formatTimeRange2(startTs, endTs)}`);
17784
18188
  if (source.kind === "group") {
17785
- const participants = [...new Set(effective.map((m) => m.senderName))];
17786
- lines.push(`[\u53C2\u4E0E\u8005] ${participants.join("\u3001")}`);
17787
- }
17788
- lines.push("");
17789
- for (const m of effective) {
17790
- lines.push(`${m.senderName}: ${(m.content ?? "").trim()}`);
17791
- }
17792
- return lines.join("\n");
18189
+ const participants = [...new Set(effective.map((m) => clampHeaderSenderName(m.senderName)))];
18190
+ headerLines.push(`[\u53C2\u4E0E\u8005] ${participants.join("\u3001")}`);
18191
+ }
18192
+ const header = clampEstimatedTokens(headerLines.join("\n"), headerLimit);
18193
+ const separator = "\n\n";
18194
+ const remaining = Math.max(0, limit - estimateTokens2(header) - estimateTokens2(separator));
18195
+ const body = clampEstimatedTokens(
18196
+ effective.map((m) => `${clampBodySenderName(m.senderName)}: ${(m.content ?? "").trim()}`).join("\n"),
18197
+ remaining
18198
+ );
18199
+ const combined = body ? `${header}${separator}${body}` : header;
18200
+ return clampEstimatedTokens(combined, limit);
17793
18201
  }
17794
18202
  function rawContentHashOf(messages) {
17795
18203
  return sha256Hex2(messages.map((m) => `${m.id}:${m.content ?? ""}`).join("\n"));
@@ -17864,7 +18272,7 @@ function chunkMessages(input) {
17864
18272
  }
17865
18273
  const startMessageId = draft[0].id;
17866
18274
  const endMessageId = draft[draft.length - 1].id;
17867
- const embeddingInput = buildEmbeddingInput(input.source, draft);
18275
+ const embeddingInput = buildEmbeddingInput(input.source, draft, config.childHardMaxTokens);
17868
18276
  chunks.push({
17869
18277
  localChunkId: `${parentId}#${startMessageId}-${endMessageId}`,
17870
18278
  parentId,
@@ -18312,6 +18720,7 @@ var SemanticIndexService = class {
18312
18720
  embedder = null;
18313
18721
  embedderModelId = null;
18314
18722
  modelPreloadStatus = "idle";
18723
+ modelPreloadGeneration = 0;
18315
18724
  /** 当前激活的 chunker 身份(version + 参数 hash),随索引记录用于重建判定 */
18316
18725
  chunkerVersion = CHUNKER_VERSION;
18317
18726
  chunkerConfigHash = computeChunkerConfigHash(DEFAULT_CHUNKER_CONFIG);
@@ -18360,6 +18769,7 @@ var SemanticIndexService = class {
18360
18769
  apiKey: options2?.apiKey,
18361
18770
  writeAuthProfile: this.options.writeAuthProfile
18362
18771
  });
18772
+ const preloadGeneration = ++this.modelPreloadGeneration;
18363
18773
  this.embedder = null;
18364
18774
  this.embedderModelId = null;
18365
18775
  this.modelPreloadStatus = "idle";
@@ -18368,15 +18778,26 @@ var SemanticIndexService = class {
18368
18778
  }
18369
18779
  if (saved.enabled && isSemanticIndexConfigured(saved) && saved.mode === "local") {
18370
18780
  const modelId = saved.local.modelId;
18781
+ const downloadSource = saved.local.downloadSource ?? DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE;
18371
18782
  const cacheDir = this.options.modelsCacheDir;
18372
18783
  this.modelPreloadStatus = "downloading";
18373
- appLogger.info("semantic-index", "local embedding model preload started", { modelId, cacheDir });
18784
+ appLogger.info("semantic-index", "local embedding model preload started", { modelId, downloadSource, cacheDir });
18374
18785
  void this.getEmbedder().preload?.().then(() => {
18786
+ if (preloadGeneration !== this.modelPreloadGeneration) return;
18375
18787
  this.modelPreloadStatus = "ready";
18376
- appLogger.info("semantic-index", "local embedding model preload completed", { modelId, cacheDir });
18788
+ appLogger.info("semantic-index", "local embedding model preload completed", {
18789
+ modelId,
18790
+ downloadSource,
18791
+ cacheDir
18792
+ });
18377
18793
  }).catch((error) => {
18794
+ if (preloadGeneration !== this.modelPreloadGeneration) return;
18378
18795
  this.modelPreloadStatus = "error";
18379
- appLogger.error("semantic-index", `local embedding model preload failed: ${modelId}`, error);
18796
+ appLogger.error(
18797
+ "semantic-index",
18798
+ `local embedding model preload failed: ${modelId} via ${downloadSource}`,
18799
+ error
18800
+ );
18380
18801
  });
18381
18802
  }
18382
18803
  return saved;
@@ -18805,7 +19226,9 @@ ${text}`;
18805
19226
  checkStop
18806
19227
  });
18807
19228
  const currentConfig = this.configStore.get();
18808
- if (result.status === "completed" && result.chunksWritten > 0 && jobConfig.mode === "local" && currentConfig.mode === "local" && currentConfig.local.modelId === jobConfig.local.modelId) {
19229
+ const jobDownloadSource = jobConfig.mode === "local" ? jobConfig.local.downloadSource ?? DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE : null;
19230
+ const currentDownloadSource = currentConfig.mode === "local" ? currentConfig.local.downloadSource ?? DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE : null;
19231
+ if (result.status === "completed" && result.chunksWritten > 0 && jobConfig.mode === "local" && currentConfig.mode === "local" && currentConfig.local.modelId === jobConfig.local.modelId && currentDownloadSource === jobDownloadSource) {
18809
19232
  this.modelPreloadStatus = "ready";
18810
19233
  }
18811
19234
  appLogger.info("semantic-index", "warmup job finished", {
@@ -19245,6 +19668,13 @@ async function pushImport(dbManager, sessionId, payload) {
19245
19668
 
19246
19669
  export {
19247
19670
  writeParseResultToDb,
19671
+ detectFormat,
19672
+ detectAllFormats,
19673
+ getSupportedFormats,
19674
+ getFormatFeatureById,
19675
+ scanMultiChatFile,
19676
+ findEntryFileInDirectory,
19677
+ logNativeParserStatus,
19248
19678
  LogLevel,
19249
19679
  initPerfLog,
19250
19680
  logPerf,
@@ -19255,12 +19685,6 @@ export {
19255
19685
  logInfo,
19256
19686
  getErrorCount,
19257
19687
  logSummary,
19258
- detectFormat,
19259
- detectAllFormats,
19260
- getSupportedFormats,
19261
- getFormatFeatureById,
19262
- scanMultiChatFile,
19263
- findEntryFileInDirectory,
19264
19688
  computeWordFrequency,
19265
19689
  segmentText,
19266
19690
  isDictDownloaded,
@@ -19316,6 +19740,9 @@ export {
19316
19740
  manualCompress,
19317
19741
  createCompressionLlmAdapter,
19318
19742
  preprocessMessages,
19743
+ preprocessMessagesWithStats,
19744
+ desensitizeText,
19745
+ matchesBlacklist,
19319
19746
  DESENSITIZE_RULES_SCHEMA_VERSION,
19320
19747
  BUILTIN_DESENSITIZE_RULES,
19321
19748
  getDefaultRulesForLocale,
@@ -19399,6 +19826,7 @@ export {
19399
19826
  deriveParentId,
19400
19827
  parseParentBounds,
19401
19828
  estimateTokens2 as estimateTokens,
19829
+ DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE,
19402
19830
  SEMANTIC_INDEX_CONFIG_VERSION,
19403
19831
  SEARCH_MAX_RESULTS_DEFAULT,
19404
19832
  SEARCH_MAX_RESULTS_MIN,
@@ -19440,4 +19868,4 @@ export {
19440
19868
  MergeSessionCache,
19441
19869
  pushImport
19442
19870
  };
19443
- //# sourceMappingURL=chunk-QQK7MLCR.mjs.map
19871
+ //# sourceMappingURL=chunk-36JGKW6K.mjs.map