chatlab-cli 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/{chat-command-ISJ2DC7R.mjs → chat-command-UZ23GKO7.mjs} +8 -5
  2. package/dist/{chat-command-ISJ2DC7R.mjs.map → chat-command-UZ23GKO7.mjs.map} +1 -1
  3. package/dist/{chunk-AB6TAXTQ.mjs → chunk-4IZ3WKYU.mjs} +591 -310
  4. package/dist/chunk-4IZ3WKYU.mjs.map +1 -0
  5. package/dist/{chunk-3QBVO4J7.mjs → chunk-5UBHNQFZ.mjs} +101 -41
  6. package/dist/chunk-5UBHNQFZ.mjs.map +1 -0
  7. package/dist/chunk-7BZLS2KW.mjs +8965 -0
  8. package/dist/chunk-7BZLS2KW.mjs.map +1 -0
  9. package/dist/{chunk-ZVDOFWL7.mjs → chunk-A2SANKG4.mjs} +4043 -10895
  10. package/dist/chunk-A2SANKG4.mjs.map +1 -0
  11. package/dist/chunk-CNSYXRJP.mjs +526 -0
  12. package/dist/chunk-CNSYXRJP.mjs.map +1 -0
  13. package/dist/{chunk-T7IETWHN.mjs → chunk-FCCL5Z4W.mjs} +5 -3
  14. package/dist/{chunk-T7IETWHN.mjs.map → chunk-FCCL5Z4W.mjs.map} +1 -1
  15. package/dist/chunk-M55KW2HA.mjs +2323 -0
  16. package/dist/chunk-M55KW2HA.mjs.map +1 -0
  17. package/dist/chunk-MUPILWAZ.mjs +1339 -0
  18. package/dist/chunk-MUPILWAZ.mjs.map +1 -0
  19. package/dist/{chunk-YBS3YQB6.mjs → chunk-P3ZOLTGS.mjs} +31 -57
  20. package/dist/chunk-P3ZOLTGS.mjs.map +1 -0
  21. package/dist/{chunk-NJB2NNB2.mjs → chunk-RDR6PS2R.mjs} +2 -2
  22. package/dist/{chunk-ERAAWLEF.mjs → chunk-ZFU2CHW3.mjs} +11 -8
  23. package/dist/{chunk-ERAAWLEF.mjs.map → chunk-ZFU2CHW3.mjs.map} +1 -1
  24. package/dist/cli.mjs +7 -4
  25. package/dist/contacts-worker.mjs +48 -0
  26. package/dist/contacts-worker.mjs.map +1 -0
  27. package/dist/{http-MBILV37U.mjs → http-UWRA5FLB.mjs} +11 -8
  28. package/dist/{import-EYV3GXHE.mjs → import-745KQL7N.mjs} +7 -4
  29. package/dist/index.mjs +11 -8
  30. package/dist/{mcp-H55AEXU5.mjs → mcp-UHN5JOHC.mjs} +8 -5
  31. package/dist/{mcp-H55AEXU5.mjs.map → mcp-UHN5JOHC.mjs.map} +1 -1
  32. package/dist/people-relationships-worker.mjs +48 -0
  33. package/dist/people-relationships-worker.mjs.map +1 -0
  34. package/dist/semantic-index-worker.mjs +1 -1
  35. package/dist/{src-36ZIR65K.mjs → src-Y7MDRXLJ.mjs} +68 -44
  36. package/dist/{stream-import-YZMZ56B5.mjs → stream-import-3JMXQD7P.mjs} +7 -4
  37. package/dist/{worker-runtime-YDYS2C6T.mjs → worker-runtime-63RYMGZI.mjs} +7 -3
  38. package/dist/{worker-runtime-YDYS2C6T.mjs.map → worker-runtime-63RYMGZI.mjs.map} +1 -1
  39. package/dist-web/assets/BufferResource-BVdJ_I5X.js +185 -0
  40. package/dist-web/assets/CanvasRenderer-BaRuT54R.js +1 -0
  41. package/dist-web/assets/DebugToolsPanel-D_lFIW6j.css +1 -0
  42. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-N5CoiJzN.js +110 -0
  43. package/dist-web/assets/{EChartWordcloud-B_YisOW5.js → EChartWordcloud-Clg60FMo.js} +1 -1
  44. package/dist-web/assets/Filter-B7cNFtuE.js +1 -0
  45. package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-D_dJuLWJ.js +1 -0
  46. package/dist-web/assets/MessageExportModal-B2Qm6VVj.js +1 -0
  47. package/dist-web/assets/PageHeader-Bbn87QZS.css +1 -0
  48. package/dist-web/assets/PageHeader-PjlWy9fC.js +1 -0
  49. package/dist-web/assets/RelationshipGalaxyCanvas-11MafciW.js +244 -0
  50. package/dist-web/assets/RelationshipGalaxyThreeCanvas-BluPHjCR.css +1 -0
  51. package/dist-web/assets/RelationshipGalaxyThreeCanvas-C1I4B8NX.js +4108 -0
  52. package/dist-web/assets/RenderTargetSystem-Chkz1WlI.js +184 -0
  53. package/dist-web/assets/WebGLRenderer-xK19ru58.js +156 -0
  54. package/dist-web/assets/WebGPURenderer-CL5ZaKsn.js +41 -0
  55. package/dist-web/assets/browserAll-CyyNzRD_.js +14 -0
  56. package/dist-web/assets/electron-MXaTKaMu.js +1 -0
  57. package/dist-web/assets/{fetch-29ZBtGV0.js → fetch-D1dDiFO4.js} +1 -1
  58. package/dist-web/assets/{fetch-TCeykOkO.js → fetch-DDnVXGte.js} +1 -1
  59. package/dist-web/assets/{fetch-Bh1fo3Df.js → fetch-DTwdRTXM.js} +1 -1
  60. package/dist-web/assets/{fetch-BcxrU2mr.js → fetch-DaaJwT-5.js} +1 -1
  61. package/dist-web/assets/{fetch-tIIVdXSv.js → fetch-DeA0ztwT.js} +1 -1
  62. package/dist-web/assets/fetch-DpnIGZSN.js +17 -0
  63. package/dist-web/assets/{fetch-C3yhWO2S.js → fetch-DsE-xcP2.js} +1 -1
  64. package/dist-web/assets/{fetch-BYczbT4C.js → fetch-DsTgYu_X.js} +1 -1
  65. package/dist-web/assets/fetch-hKE53UO4.js +1 -0
  66. package/dist-web/assets/fetch-u9GTMhMF.js +1 -0
  67. package/dist-web/assets/index---rXG1hZ.css +1 -0
  68. package/dist-web/assets/index-BVkE4qis.js +22 -0
  69. package/dist-web/assets/index-BcXLcFnV.css +1 -0
  70. package/dist-web/assets/index-BnHKyESZ.js +1 -0
  71. package/dist-web/assets/index-Btr5H7d-.js +2 -0
  72. package/dist-web/assets/{index-BX3tqfLz.css → index-C1p9CbmN.css} +1 -1
  73. package/dist-web/assets/index-COpza1A4.css +1 -0
  74. package/dist-web/assets/index-D04LWJop.js +173 -0
  75. package/dist-web/assets/index-DFCYmzGE.js +2 -0
  76. package/dist-web/assets/index-DosG7LmJ.css +1 -0
  77. package/dist-web/assets/index-DqXHRIhI.js +1 -0
  78. package/dist-web/assets/{index-fbWrQpgH.js → index-DqtXt3wl.js} +1 -1
  79. package/dist-web/assets/index-Dszv-QFp.js +64 -0
  80. package/dist-web/assets/index-DzLrWGXu.js +1 -0
  81. package/dist-web/assets/people-page-header-Mupn-U_b.js +1 -0
  82. package/dist-web/assets/relationship-galaxy-viewport-Cwfa51ER.js +1 -0
  83. package/dist-web/assets/{vendor-echarts-CITQeskI.js → vendor-echarts-hJVnODyU.js} +2 -2
  84. package/dist-web/assets/{vendor-echarts-wordcloud-B1kQMklB.js → vendor-echarts-wordcloud-k5ih2Jl4.js} +1 -1
  85. package/dist-web/assets/{vendor-nuxt-ui-Zy8txNdC.js → vendor-nuxt-ui-DaLxYaC4.js} +2 -2
  86. package/dist-web/assets/{vendor-reka-ui-mMvma1EP.js → vendor-reka-ui-CzM4kDQ7.js} +1 -1
  87. package/dist-web/assets/{vendor-zrender-DFl4U2Oe.js → vendor-zrender-5DcYjSZk.js} +1 -1
  88. package/dist-web/assets/{web-H3XLx159.js → web-DPVWfPS-.js} +1 -1
  89. package/dist-web/assets/webworkerAll-DF5QV_vN.js +83 -0
  90. package/dist-web/index.html +4 -4
  91. package/package.json +1 -1
  92. package/dist/chunk-3QBVO4J7.mjs.map +0 -1
  93. package/dist/chunk-AB6TAXTQ.mjs.map +0 -1
  94. package/dist/chunk-GHTGFM6O.mjs +0 -540
  95. package/dist/chunk-GHTGFM6O.mjs.map +0 -1
  96. package/dist/chunk-YBS3YQB6.mjs.map +0 -1
  97. package/dist/chunk-ZVDOFWL7.mjs.map +0 -1
  98. package/dist-web/assets/DebugToolsPanel-DFw6_G5V.css +0 -1
  99. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-Do3x4aL3.js +0 -110
  100. package/dist-web/assets/MessageExportModal-rijqUusq.js +0 -1
  101. package/dist-web/assets/electron-DWDCqx_d.js +0 -1
  102. package/dist-web/assets/fetch-BAxKmbKF.js +0 -1
  103. package/dist-web/assets/fetch-Da_TBxrX.js +0 -1
  104. package/dist-web/assets/fetch-DyD8wDhb.js +0 -17
  105. package/dist-web/assets/index-BzjLqlW_.js +0 -173
  106. package/dist-web/assets/index-C74qEDFh.js +0 -64
  107. package/dist-web/assets/index-CM6xNPFp.css +0 -1
  108. package/dist-web/assets/index-CgK2LLIF.js +0 -12
  109. package/dist-web/assets/index-CjkOvM3N.js +0 -2
  110. package/dist-web/assets/index-Dno7CADA.js +0 -1
  111. package/dist-web/assets/index-I3SWZjnW.css +0 -1
  112. /package/dist/{chunk-NJB2NNB2.mjs.map → chunk-RDR6PS2R.mjs.map} +0 -0
  113. /package/dist/{http-MBILV37U.mjs.map → http-UWRA5FLB.mjs.map} +0 -0
  114. /package/dist/{import-EYV3GXHE.mjs.map → import-745KQL7N.mjs.map} +0 -0
  115. /package/dist/{src-36ZIR65K.mjs.map → src-Y7MDRXLJ.mjs.map} +0 -0
  116. /package/dist/{stream-import-YZMZ56B5.mjs.map → stream-import-3JMXQD7P.mjs.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../node_modules/stream-json/utils/Utf8Stream.js","../../../node_modules/stream-json/Parser.js","../../../node_modules/stream-json/utils/emit.js","../../../node_modules/stream-json/index.js","../../../node_modules/stream-json/filters/FilterBase.js","../../../node_modules/stream-chain/index.js","../../../node_modules/stream-json/utils/withParser.js","../../../node_modules/stream-json/filters/Pick.js","../../../node_modules/stream-json/Assembler.js","../../../node_modules/stream-json/streamers/StreamBase.js","../../../node_modules/stream-json/streamers/StreamValues.js","../../../node_modules/pend/index.js","../../../packages/node-runtime/node_modules/yauzl/fd-slicer.js","../../../packages/node-runtime/node_modules/yauzl/crc32.js","../../../packages/node-runtime/node_modules/yauzl/index.js","../../../packages/config/src/schema.ts","../../../packages/config/src/loader.ts","../../../packages/config/src/auth-profiles.ts","../../../packages/config/src/migrations/runner.ts","../../../packages/config/src/migrations/m001-legacy-to-multi-config.ts","../../../packages/config/src/migrations/m002-schema-v2.ts","../../../packages/config/src/migrations/m003-schema-v3.ts","../../../packages/config/src/migrations/m004-encrypted-keys-to-auth-profiles.ts","../../../packages/config/src/migrations/crypto-legacy.ts","../../../packages/config/src/migrations/index.ts","../../../packages/node-runtime/src/better-sqlite3-adapter.ts","../../../packages/node-runtime/src/import/write-parse-result.ts","../../../packages/node-runtime/src/import/perf-logger.ts","../../../packages/core/src/query/filters.ts","../../../packages/core/src/query/basic-queries.ts","../../../packages/core/src/query/session-queries.ts","../../../packages/core/src/query/message-sql.ts","../../../packages/core/src/query/message-queries.ts","../../../packages/core/src/query/member-ops.ts","../../../packages/core/src/query/advanced/text-filters.ts","../../../packages/core/src/query/advanced/repeat.ts","../../../packages/core/src/query/advanced/social.ts","../../../packages/core/src/query/advanced/relationship.ts","../../../packages/core/src/query/advanced/languagePreference.ts","../../../packages/core/src/query/advanced/ranking.ts","../../../packages/core/src/ai/tool-catalog.ts","../../../packages/core/src/ai/provider-registry.ts","../../../packages/core/src/ai/model-catalog.ts","../../../packages/core/src/ai/content-parser.ts","../../../packages/core/src/ai/streaming-think-parser.ts","../../../packages/core/src/ai/thinking.ts","../../../packages/core/src/ai/tool-result-text.ts","../../../packages/core/src/version.ts","../../../packages/core/src/chart/index.ts","../../../packages/core/src/nlp/pos-tags.ts","../../../packages/core/src/nlp/stopwords.ts","../../../packages/core/src/nlp/text-utils.ts","../../../packages/core/src/owner/owner-matching.ts","../../../packages/core/src/import/dedup.ts","../../../packages/core/src/merger/algorithms.ts","../../../packages/core/src/schema/tables.ts","../../../packages/core/src/schema/migrations.ts","../../../packages/parser/src/index.ts","../../../packages/parser/src/sniffer.ts","../../../packages/parser/src/formats/chatlab.ts","../../../packages/shared-types/index.ts","../../../packages/parser/src/utils.ts","../../../packages/parser/src/formats/chatlab-jsonl.ts","../../../packages/parser/src/formats/shuakami-qq-exporter.ts","../../../packages/parser/src/formats/shuakami-qq-preprocessor.ts","../../../packages/parser/src/formats/shuakami-qq-exporter-chunked.ts","../../../packages/parser/src/formats/weflow.ts","../../../packages/parser/src/formats/weflow-preprocessor.ts","../../../packages/parser/src/formats/ycccccccy-echotrace.ts","../../../packages/parser/src/formats/tyrrrz-discord-exporter.ts","../../../packages/parser/src/formats/telegram-native.ts","../../../packages/parser/src/formats/utils/telegram-utils.ts","../../../packages/parser/src/formats/telegram-native-single.ts","../../../packages/parser/src/formats/whatsapp-native-txt.ts","../../../packages/parser/src/formats/qq-native-txt.ts","../../../packages/parser/src/formats/instagram-native.ts","../../../packages/parser/src/formats/google-chat-takeout.ts","../../../packages/parser/src/formats/line-native-txt.ts","../../../packages/parser/src/formats/index.ts","../../../packages/node-runtime/src/nlp/segmenter.ts","../../../packages/node-runtime/src/nlp/word-frequency.ts","../../../packages/node-runtime/src/nlp/fts-tokenizer.ts","../../../packages/node-runtime/src/nlp/dict-manager.ts","../../../packages/node-runtime/src/fts/index.ts","../../../packages/node-runtime/src/import/streaming-importer.ts","../../../packages/node-runtime/src/import/incremental-importer.ts","../../../packages/node-runtime/src/import/archive/errors.ts","../../../packages/node-runtime/src/import/archive/archive-reader.ts","../../../packages/node-runtime/src/import/archive/google-chat-resolver.ts","../../../packages/node-runtime/src/import/archive/source-manager.ts","../../../packages/node-runtime/src/ai/ai-logger.ts","../../../packages/node-runtime/src/ai/error-formatter.ts","../../../packages/node-runtime/src/ai/assistant-parser.ts","../../../packages/node-runtime/src/ai/assistant-manager.ts","../../../packages/node-runtime/src/ai/skill-parser.ts","../../../packages/node-runtime/src/ai/chats.ts","../../../packages/node-runtime/src/ai/tokenizer.ts","../../../packages/node-runtime/src/ai/skill-menu.ts","../../../packages/node-runtime/src/ai/skill-manager-core.ts","../../../packages/node-runtime/src/ai/skill-manager.ts","../../../packages/node-runtime/src/ai/activate-skill-tool.ts","../../../packages/node-runtime/src/ai/chart-runtime.ts","../../../packages/node-runtime/src/ai/chart-schema-gate.ts","../../../packages/node-runtime/src/ai/agent/history.ts","../../../packages/node-runtime/src/ai/compression/compressor.ts","../../../packages/node-runtime/src/ai/compression/adapter-factory.ts","../../../packages/node-runtime/src/ai/preprocessor/pipeline.ts","../../../packages/node-runtime/src/ai/preprocessor/builtin-rules.ts","../../../packages/node-runtime/src/ai/preprocessor/format.ts","../../../packages/node-runtime/src/ai/preprocessor/preprocessing-pipeline.ts","../../../packages/node-runtime/src/ai/agent/constants.ts","../../../packages/node-runtime/src/ai/agent/core.ts","../../../packages/node-runtime/src/ai/agent/data-snapshot.ts","../../../packages/node-runtime/src/ai/agent/router.ts","../../../packages/node-runtime/src/ai/agent/planner.ts","../../../packages/node-runtime/src/ai/agent/semantic-search-guidance.ts","../../../packages/node-runtime/src/ai/agent/event-handler.ts","../../../packages/node-runtime/src/ai/agent/prompt-builder.ts","../../../packages/node-runtime/src/ai/i18n/locales/zh-CN.ts","../../../packages/node-runtime/src/ai/i18n/locales/en-US.ts","../../../packages/node-runtime/src/ai/i18n/locales/ja-JP.ts","../../../packages/node-runtime/src/ai/i18n/locales/zh-TW.ts","../../../packages/node-runtime/src/ai/i18n/index.ts","../../../packages/node-runtime/src/ai/summary/index.ts","../../../packages/node-runtime/src/ai/llm-config-store.ts","../../../packages/node-runtime/src/ai/custom-store.ts","../../../packages/node-runtime/src/ai/llm-builder.ts","../../../packages/node-runtime/src/ai/remote-api.ts","../../../packages/node-runtime/src/ai/llm-stream.ts","../../../packages/node-runtime/src/ai/index.ts","../../../packages/node-runtime/src/logging/app-logger.ts","../../../packages/node-runtime/src/migrations/electron-data-migration.ts","../../../packages/node-runtime/src/cache/session-cache.ts","../../../packages/node-runtime/src/data-dir-compat.ts","../../../packages/node-runtime/src/migrations/chat-db-migrations.ts","../../../packages/node-runtime/src/database-manager.ts","../../../packages/node-runtime/src/export/markdown-exporter.ts","../../../packages/node-runtime/src/export/format-exporter.ts","../../../packages/node-runtime/src/merger/temp-db.ts","../../../packages/node-runtime/src/services/session-service.ts","../../../packages/node-runtime/src/services/member-service.ts","../../../packages/node-runtime/src/services/session-index-service.ts","../../../packages/node-runtime/src/services/summary-service.ts","../../../packages/node-runtime/src/services/export-service.ts","../../../packages/node-runtime/src/services/owner-profile-service.ts","../../../packages/node-runtime/src/semantic-index/config.ts","../../../packages/node-runtime/src/semantic-index/service.ts","../../../packages/node-runtime/src/semantic-index/chunker-config.ts","../../../packages/node-runtime/src/semantic-index/embedding/api.ts","../../../packages/node-runtime/src/semantic-index/embedding/text.ts","../../../packages/node-runtime/src/semantic-index/embedding/local.ts","../../../packages/node-runtime/src/semantic-index/embedding/profiles.ts","../../../packages/node-runtime/src/semantic-index/embedder-factory.ts","../../../packages/node-runtime/src/semantic-index/store.ts","../../../node_modules/sqlite-vec/index.mjs","../../../packages/node-runtime/src/semantic-index/schema.ts","../../../packages/node-runtime/src/semantic-index/session-state-store.ts","../../../packages/node-runtime/src/semantic-index/warmup/job-queue.ts","../../../packages/node-runtime/src/semantic-index/chunker.ts","../../../packages/node-runtime/src/semantic-index/tokens.ts","../../../packages/node-runtime/src/semantic-index/warmup/runner.ts","../../../packages/node-runtime/src/semantic-index/retrieval/rrf.ts","../../../packages/node-runtime/src/semantic-index/retrieval/hybrid-search.ts","../../../packages/node-runtime/src/semantic-index/retrieval/evidence.ts","../../../packages/node-runtime/src/semantic-index/chat-db/message-source.ts","../../../packages/node-runtime/src/semantic-index/chat-db/message-range-reader.ts","../../../packages/node-runtime/src/semantic-index/chat-db/fts-searcher.ts","../../../packages/node-runtime/src/semantic-index/static-path-provider.ts","../../../packages/node-runtime/src/services/adapters/database-manager-adapter.ts","../../../packages/node-runtime/src/services/merge-cache.ts","../../../packages/node-runtime/src/services/push-importer.ts"],"sourcesContent":["'use strict';\n\nconst {Transform} = require('stream');\nconst {StringDecoder} = require('string_decoder');\n\nclass Utf8Stream extends Transform {\n constructor(options) {\n super(Object.assign({}, options, {writableObjectMode: false}));\n this._buffer = '';\n }\n\n _transform(chunk, encoding, callback) {\n if (typeof chunk == 'string') {\n this._transform = this._transformString;\n } else {\n this._stringDecoder = new StringDecoder();\n this._transform = this._transformBuffer;\n }\n this._transform(chunk, encoding, callback);\n }\n\n _transformBuffer(chunk, _, callback) {\n this._buffer += this._stringDecoder.write(chunk);\n this._processBuffer(callback);\n }\n\n _transformString(chunk, _, callback) {\n this._buffer += chunk.toString();\n this._processBuffer(callback);\n }\n\n _processBuffer(callback) {\n if (this._buffer) {\n this.push(this._buffer, 'utf8');\n this._buffer = '';\n }\n callback(null);\n }\n\n _flushInput() {\n // meant to be called from _flush()\n if (this._stringDecoder) {\n this._buffer += this._stringDecoder.end();\n }\n }\n\n _flush(callback) {\n this._flushInput();\n this._processBuffer(callback);\n }\n}\n\nmodule.exports = Utf8Stream;\n","'use strict';\n\nconst Utf8Stream = require('./utils/Utf8Stream');\n\nconst patterns = {\n value1: /^(?:[\\\"\\{\\[\\]\\-\\d]|true\\b|false\\b|null\\b|\\s{1,256})/,\n string: /^(?:[^\\x00-\\x1f\\\"\\\\]{1,256}|\\\\[bfnrt\\\"\\\\\\/]|\\\\u[\\da-fA-F]{4}|\\\")/,\n key1: /^(?:[\\\"\\}]|\\s{1,256})/,\n colon: /^(?:\\:|\\s{1,256})/,\n comma: /^(?:[\\,\\]\\}]|\\s{1,256})/,\n ws: /^\\s{1,256}/,\n numberStart: /^\\d/,\n numberDigit: /^\\d{0,256}/,\n numberFraction: /^[\\.eE]/,\n numberExponent: /^[eE]/,\n numberExpSign: /^[-+]/\n};\nconst MAX_PATTERN_SIZE = 16;\n\nlet noSticky = true;\ntry {\n new RegExp('.', 'y');\n noSticky = false;\n} catch (e) {\n // suppress\n}\n\n!noSticky &&\n Object.keys(patterns).forEach(key => {\n let src = patterns[key].source.slice(1); // lop off ^\n if (src.slice(0, 3) === '(?:' && src.slice(-1) === ')') {\n src = src.slice(3, -1);\n }\n patterns[key] = new RegExp(src, 'y');\n });\n\npatterns.numberFracStart = patterns.numberExpStart = patterns.numberStart;\npatterns.numberFracDigit = patterns.numberExpDigit = patterns.numberDigit;\n\nconst values = {true: true, false: false, null: null},\n expected = {object: 'objectStop', array: 'arrayStop', '': 'done'};\n\n// long hexadecimal codes: \\uXXXX\nconst fromHex = s => String.fromCharCode(parseInt(s.slice(2), 16));\n\n// short codes: \\b \\f \\n \\r \\t \\\" \\\\ \\/\nconst codes = {b: '\\b', f: '\\f', n: '\\n', r: '\\r', t: '\\t', '\"': '\"', '\\\\': '\\\\', '/': '/'};\n\nclass Parser extends Utf8Stream {\n static make(options) {\n return new Parser(options);\n }\n\n constructor(options) {\n super(Object.assign({}, options, {readableObjectMode: true}));\n\n this._packKeys = this._packStrings = this._packNumbers = this._streamKeys = this._streamStrings = this._streamNumbers = true;\n if (options) {\n 'packValues' in options && (this._packKeys = this._packStrings = this._packNumbers = options.packValues);\n 'packKeys' in options && (this._packKeys = options.packKeys);\n 'packStrings' in options && (this._packStrings = options.packStrings);\n 'packNumbers' in options && (this._packNumbers = options.packNumbers);\n 'streamValues' in options && (this._streamKeys = this._streamStrings = this._streamNumbers = options.streamValues);\n 'streamKeys' in options && (this._streamKeys = options.streamKeys);\n 'streamStrings' in options && (this._streamStrings = options.streamStrings);\n 'streamNumbers' in options && (this._streamNumbers = options.streamNumbers);\n this._jsonStreaming = options.jsonStreaming;\n }\n !this._packKeys && (this._streamKeys = true);\n !this._packStrings && (this._streamStrings = true);\n !this._packNumbers && (this._streamNumbers = true);\n\n this._done = false;\n this._expect = this._jsonStreaming ? 'done' : 'value';\n this._stack = [];\n this._parent = '';\n this._open_number = false;\n this._accumulator = '';\n }\n\n _flush(callback) {\n this._done = true;\n super._flush(error => {\n if (error) return callback(error);\n if (this._open_number) {\n if (this._streamNumbers) {\n this.push({name: 'endNumber'});\n }\n this._open_number = false;\n if (this._packNumbers) {\n this.push({name: 'numberValue', value: this._accumulator});\n this._accumulator = '';\n }\n }\n callback(null);\n });\n }\n\n _processBuffer(callback) {\n let match,\n value,\n index = 0;\n main: for (;;) {\n switch (this._expect) {\n case 'value1':\n case 'value':\n patterns.value1.lastIndex = index;\n match = patterns.value1.exec(this._buffer);\n if (!match) {\n if (this._done || index + MAX_PATTERN_SIZE < this._buffer.length) {\n if (index < this._buffer.length) return callback(new Error('Parser cannot parse input: expected a value'));\n return callback(new Error('Parser has expected a value'));\n }\n break main; // wait for more input\n }\n value = match[0];\n switch (value) {\n case '\"':\n this._streamStrings && this.push({name: 'startString'});\n this._expect = 'string';\n break;\n case '{':\n this.push({name: 'startObject'});\n this._stack.push(this._parent);\n this._parent = 'object';\n this._expect = 'key1';\n break;\n case '[':\n this.push({name: 'startArray'});\n this._stack.push(this._parent);\n this._parent = 'array';\n this._expect = 'value1';\n break;\n case ']':\n if (this._expect !== 'value1') return callback(new Error(\"Parser cannot parse input: unexpected token ']'\"));\n if (this._open_number) {\n this._streamNumbers && this.push({name: 'endNumber'});\n this._open_number = false;\n if (this._packNumbers) {\n this.push({name: 'numberValue', value: this._accumulator});\n this._accumulator = '';\n }\n }\n this.push({name: 'endArray'});\n this._parent = this._stack.pop();\n this._expect = expected[this._parent];\n break;\n case '-':\n this._open_number = true;\n if (this._streamNumbers) {\n this.push({name: 'startNumber'});\n this.push({name: 'numberChunk', value: '-'});\n }\n this._packNumbers && (this._accumulator = '-');\n this._expect = 'numberStart';\n break;\n case '0':\n this._open_number = true;\n if (this._streamNumbers) {\n this.push({name: 'startNumber'});\n this.push({name: 'numberChunk', value: '0'});\n }\n this._packNumbers && (this._accumulator = '0');\n this._expect = 'numberFraction';\n break;\n case '1':\n case '2':\n case '3':\n case '4':\n case '5':\n case '6':\n case '7':\n case '8':\n case '9':\n this._open_number = true;\n if (this._streamNumbers) {\n this.push({name: 'startNumber'});\n this.push({name: 'numberChunk', value: value});\n }\n this._packNumbers && (this._accumulator = value);\n this._expect = 'numberDigit';\n break;\n case 'true':\n case 'false':\n case 'null':\n if (this._buffer.length - index === value.length && !this._done) break main; // wait for more input\n this.push({name: value + 'Value', value: values[value]});\n this._expect = expected[this._parent];\n break;\n // default: // ws\n }\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'keyVal':\n case 'string':\n patterns.string.lastIndex = index;\n match = patterns.string.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length && (this._done || this._buffer.length - index >= 6))\n return callback(new Error('Parser cannot parse input: escaped characters'));\n if (this._done) return callback(new Error('Parser has expected a string value'));\n break main; // wait for more input\n }\n value = match[0];\n if (value === '\"') {\n if (this._expect === 'keyVal') {\n this._streamKeys && this.push({name: 'endKey'});\n if (this._packKeys) {\n this.push({name: 'keyValue', value: this._accumulator});\n this._accumulator = '';\n }\n this._expect = 'colon';\n } else {\n this._streamStrings && this.push({name: 'endString'});\n if (this._packStrings) {\n this.push({name: 'stringValue', value: this._accumulator});\n this._accumulator = '';\n }\n this._expect = expected[this._parent];\n }\n } else if (value.length > 1 && value.charAt(0) === '\\\\') {\n const t = value.length == 2 ? codes[value.charAt(1)] : fromHex(value);\n if (this._expect === 'keyVal' ? this._streamKeys : this._streamStrings) {\n this.push({name: 'stringChunk', value: t});\n }\n if (this._expect === 'keyVal' ? this._packKeys : this._packStrings) {\n this._accumulator += t;\n }\n } else {\n if (this._expect === 'keyVal' ? this._streamKeys : this._streamStrings) {\n this.push({name: 'stringChunk', value: value});\n }\n if (this._expect === 'keyVal' ? this._packKeys : this._packStrings) {\n this._accumulator += value;\n }\n }\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'key1':\n case 'key':\n patterns.key1.lastIndex = index;\n match = patterns.key1.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length || this._done) return callback(new Error('Parser cannot parse input: expected an object key'));\n break main; // wait for more input\n }\n value = match[0];\n if (value === '\"') {\n this._streamKeys && this.push({name: 'startKey'});\n this._expect = 'keyVal';\n } else if (value === '}') {\n if (this._expect !== 'key1') return callback(new Error(\"Parser cannot parse input: unexpected token '}'\"));\n this.push({name: 'endObject'});\n this._parent = this._stack.pop();\n this._expect = expected[this._parent];\n }\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'colon':\n patterns.colon.lastIndex = index;\n match = patterns.colon.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length || this._done) return callback(new Error(\"Parser cannot parse input: expected ':'\"));\n break main; // wait for more input\n }\n value = match[0];\n value === ':' && (this._expect = 'value');\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'arrayStop':\n case 'objectStop':\n patterns.comma.lastIndex = index;\n match = patterns.comma.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length || this._done) return callback(new Error(\"Parser cannot parse input: expected ','\"));\n break main; // wait for more input\n }\n if (this._open_number) {\n this._streamNumbers && this.push({name: 'endNumber'});\n this._open_number = false;\n if (this._packNumbers) {\n this.push({name: 'numberValue', value: this._accumulator});\n this._accumulator = '';\n }\n }\n value = match[0];\n if (value === ',') {\n this._expect = this._expect === 'arrayStop' ? 'value' : 'key';\n } else if (value === '}' || value === ']') {\n if (value === '}' ? this._expect === 'arrayStop' : this._expect !== 'arrayStop') {\n return callback(new Error(\"Parser cannot parse input: expected '\" + (this._expect === 'arrayStop' ? ']' : '}') + \"'\"));\n }\n this.push({name: value === '}' ? 'endObject' : 'endArray'});\n this._parent = this._stack.pop();\n this._expect = expected[this._parent];\n }\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n // number chunks\n case 'numberStart': // [0-9]\n patterns.numberStart.lastIndex = index;\n match = patterns.numberStart.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length || this._done) return callback(new Error('Parser cannot parse input: expected a starting digit'));\n break main; // wait for more input\n }\n value = match[0];\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n this._expect = value === '0' ? 'numberFraction' : 'numberDigit';\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'numberDigit': // [0-9]*\n patterns.numberDigit.lastIndex = index;\n match = patterns.numberDigit.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length || this._done) return callback(new Error('Parser cannot parse input: expected a digit'));\n break main; // wait for more input\n }\n value = match[0];\n if (value) {\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n } else {\n if (index < this._buffer.length) {\n this._expect = 'numberFraction';\n break;\n }\n if (this._done) {\n this._expect = expected[this._parent];\n break;\n }\n break main; // wait for more input\n }\n break;\n case 'numberFraction': // [\\.eE]?\n patterns.numberFraction.lastIndex = index;\n match = patterns.numberFraction.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length || this._done) {\n this._expect = expected[this._parent];\n break;\n }\n break main; // wait for more input\n }\n value = match[0];\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n this._expect = value === '.' ? 'numberFracStart' : 'numberExpSign';\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'numberFracStart': // [0-9]\n patterns.numberFracStart.lastIndex = index;\n match = patterns.numberFracStart.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length || this._done) return callback(new Error('Parser cannot parse input: expected a fractional part of a number'));\n break main; // wait for more input\n }\n value = match[0];\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n this._expect = 'numberFracDigit';\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'numberFracDigit': // [0-9]*\n patterns.numberFracDigit.lastIndex = index;\n match = patterns.numberFracDigit.exec(this._buffer);\n value = match[0];\n if (value) {\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n } else {\n if (index < this._buffer.length) {\n this._expect = 'numberExponent';\n break;\n }\n if (this._done) {\n this._expect = expected[this._parent];\n break;\n }\n break main; // wait for more input\n }\n break;\n case 'numberExponent': // [eE]?\n patterns.numberExponent.lastIndex = index;\n match = patterns.numberExponent.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length) {\n this._expect = expected[this._parent];\n break;\n }\n if (this._done) {\n this._expect = 'done';\n break;\n }\n break main; // wait for more input\n }\n value = match[0];\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n this._expect = 'numberExpSign';\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'numberExpSign': // [-+]?\n patterns.numberExpSign.lastIndex = index;\n match = patterns.numberExpSign.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length) {\n this._expect = 'numberExpStart';\n break;\n }\n if (this._done) return callback(new Error('Parser has expected an exponent value of a number'));\n break main; // wait for more input\n }\n value = match[0];\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n this._expect = 'numberExpStart';\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'numberExpStart': // [0-9]\n patterns.numberExpStart.lastIndex = index;\n match = patterns.numberExpStart.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length || this._done) return callback(new Error('Parser cannot parse input: expected an exponent part of a number'));\n break main; // wait for more input\n }\n value = match[0];\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n this._expect = 'numberExpDigit';\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n case 'numberExpDigit': // [0-9]*\n patterns.numberExpDigit.lastIndex = index;\n match = patterns.numberExpDigit.exec(this._buffer);\n value = match[0];\n if (value) {\n this._streamNumbers && this.push({name: 'numberChunk', value: value});\n this._packNumbers && (this._accumulator += value);\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n } else {\n if (index < this._buffer.length || this._done) {\n this._expect = expected[this._parent];\n break;\n }\n break main; // wait for more input\n }\n break;\n case 'done':\n patterns.ws.lastIndex = index;\n match = patterns.ws.exec(this._buffer);\n if (!match) {\n if (index < this._buffer.length) {\n if (this._jsonStreaming) {\n this._expect = 'value';\n break;\n }\n return callback(new Error('Parser cannot parse input: unexpected characters'));\n }\n break main; // wait for more input\n }\n value = match[0];\n if (this._open_number) {\n this._streamNumbers && this.push({name: 'endNumber'});\n this._open_number = false;\n if (this._packNumbers) {\n this.push({name: 'numberValue', value: this._accumulator});\n this._accumulator = '';\n }\n }\n if (noSticky) {\n this._buffer = this._buffer.slice(value.length);\n } else {\n index += value.length;\n }\n break;\n }\n }\n !noSticky && (this._buffer = this._buffer.slice(index));\n callback(null);\n }\n}\nParser.parser = Parser.make;\nParser.make.Constructor = Parser;\n\nmodule.exports = Parser;\n","'use strict';\n\nconst emit = stream => stream.on('data', item => stream.emit(item.name, item.value));\n\nmodule.exports = emit;\n","'use strict';\n\nconst Parser = require('./Parser');\nconst emit = require('./utils/emit');\n\nconst make = options => emit(new Parser(options));\n\nmake.Parser = Parser;\nmake.parser = Parser.parser;\n\nmodule.exports = make;\n","'use strict';\n\nconst {Transform} = require('stream');\n\nclass FilterBase extends Transform {\n static stringFilter(string, separator) {\n return stack => {\n const path = stack.join(separator);\n return (\n (path.length === string.length && path === string) ||\n (path.length > string.length && path.substr(0, string.length) === string && path.substr(string.length, separator.length) === separator)\n );\n };\n }\n\n static regExpFilter(regExp, separator) {\n return stack => regExp.test(stack.join(separator));\n }\n\n static arrayReplacement(array) {\n return () => array;\n }\n\n constructor(options) {\n super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));\n this._transform = this._check;\n this._stack = [];\n\n const filter = options && options.filter,\n separator = (options && options.pathSeparator) || '.';\n if (typeof filter == 'string') {\n this._filter = FilterBase.stringFilter(filter, separator);\n } else if (typeof filter == 'function') {\n this._filter = filter;\n } else if (filter instanceof RegExp) {\n this._filter = FilterBase.regExpFilter(filter, separator);\n }\n\n const replacement = options && options.replacement;\n if (typeof replacement == 'function') {\n this._replacement = replacement;\n } else {\n this._replacement = FilterBase.arrayReplacement(replacement || FilterBase.defaultReplacement);\n }\n this._allowEmptyReplacement = options && options.allowEmptyReplacement;\n\n this._streamKeys = true;\n if (options) {\n 'streamValues' in options && (this._streamKeys = options.streamValues);\n 'streamKeys' in options && (this._streamKeys = options.streamKeys);\n }\n\n this._once = options && options.once;\n this._previousToken = '';\n }\n\n _check(chunk, _, callback) {\n // update the last stack key\n switch (chunk.name) {\n case 'startObject':\n case 'startArray':\n case 'startString':\n case 'startNumber':\n case 'nullValue':\n case 'trueValue':\n case 'falseValue':\n if (typeof this._stack[this._stack.length - 1] == 'number') {\n // array\n ++this._stack[this._stack.length - 1];\n }\n break;\n case 'keyValue':\n this._stack[this._stack.length - 1] = chunk.value;\n break;\n case 'numberValue':\n if (this._previousToken !== 'endNumber' && typeof this._stack[this._stack.length - 1] == 'number') {\n // array\n ++this._stack[this._stack.length - 1];\n }\n break;\n case 'stringValue':\n if (this._previousToken !== 'endString' && typeof this._stack[this._stack.length - 1] == 'number') {\n // array\n ++this._stack[this._stack.length - 1];\n }\n break;\n }\n this._previousToken = chunk.name;\n // check, if we allow a chunk\n if (this._checkChunk(chunk)) {\n return callback(null);\n }\n // update the stack\n switch (chunk.name) {\n case 'startObject':\n this._stack.push(null);\n break;\n case 'startArray':\n this._stack.push(-1);\n break;\n case 'endObject':\n case 'endArray':\n this._stack.pop();\n break;\n }\n callback(null);\n }\n\n _passObject(chunk, _, callback) {\n this.push(chunk);\n switch (chunk.name) {\n case 'startObject':\n case 'startArray':\n ++this._depth;\n break;\n case 'endObject':\n case 'endArray':\n --this._depth;\n break;\n }\n if (!this._depth) {\n this._transform = this._once ? this._skip : this._check;\n }\n callback(null);\n }\n\n _pass(chunk, _, callback) {\n this.push(chunk);\n callback(null);\n }\n\n _skipObject(chunk, _, callback) {\n switch (chunk.name) {\n case 'startObject':\n case 'startArray':\n ++this._depth;\n break;\n case 'endObject':\n case 'endArray':\n --this._depth;\n break;\n }\n if (!this._depth) {\n this._transform = this._once ? this._pass : this._check;\n }\n callback(null);\n }\n\n _skip(chunk, _, callback) {\n callback(null);\n }\n}\n\nFilterBase.defaultReplacement = [{name: 'nullValue', value: null}];\n\nconst passValue = (last, post) =>\n function(chunk, _, callback) {\n if (this._expected) {\n const expected = this._expected;\n this._expected = '';\n this._transform = this._once ? this._skip : this._check;\n if (expected === chunk.name) {\n this.push(chunk);\n } else {\n return this._transform(chunk, _, callback);\n }\n } else {\n this.push(chunk);\n if (chunk.name === last) {\n this._expected = post;\n }\n }\n callback(null);\n };\n\nFilterBase.prototype._passNumber = passValue('endNumber', 'numberValue');\nFilterBase.prototype._passString = passValue('endString', 'stringValue');\nFilterBase.prototype._passKey = passValue('endKey', 'keyValue');\n\nconst skipValue = (last, post) =>\n function(chunk, _, callback) {\n if (this._expected) {\n const expected = this._expected;\n this._expected = '';\n this._transform = this._once ? this._pass : this._check;\n if (expected !== chunk.name) {\n return this._transform(chunk, _, callback);\n }\n } else {\n if (chunk.name === last) {\n this._expected = post;\n }\n }\n callback(null);\n };\n\nFilterBase.prototype._skipNumber = skipValue('endNumber', 'numberValue');\nFilterBase.prototype._skipString = skipValue('endString', 'stringValue');\nFilterBase.prototype._skipKey = skipValue('endKey', 'keyValue');\n\nmodule.exports = FilterBase;\n","'use strict';\n\nconst {Readable, Writable, Duplex, Transform} = require('stream');\n\nconst none = Symbol.for('object-stream.none');\nconst finalSymbol = Symbol.for('object-stream.final');\nconst manySymbol = Symbol.for('object-stream.many');\n\nconst final = value => ({[finalSymbol]: value});\nconst many = values => ({[manySymbol]: values});\n\nconst isFinal = o => o && typeof o == 'object' && finalSymbol in o;\nconst isMany = o => o && typeof o == 'object' && manySymbol in o;\n\nconst getFinalValue = o => o[finalSymbol];\nconst getManyValues = o => o[manySymbol];\n\nconst runAsyncGenerator = async (gen, stream) => {\n for (;;) {\n let data = gen.next();\n if (data && typeof data.then == 'function') {\n data = await data;\n }\n if (data.done) break;\n let value = data.value;\n if (value && typeof value.then == 'function') {\n value = await value;\n }\n Chain.sanitize(value, stream);\n }\n};\n\nconst wrapFunction = fn =>\n new Transform({\n writableObjectMode: true,\n readableObjectMode: true,\n transform(chunk, encoding, callback) {\n try {\n const result = fn.call(this, chunk, encoding);\n if (result && typeof result.then == 'function') {\n // thenable\n result.then(\n result => (Chain.sanitize(result, this), callback(null)),\n error => callback(error)\n );\n return;\n }\n if (result && typeof result.next == 'function') {\n // generator\n runAsyncGenerator(result, this).then(\n () => callback(null),\n error => callback(error)\n );\n return;\n }\n Chain.sanitize(result, this);\n callback(null);\n } catch (error) {\n callback(error);\n }\n }\n });\n\nconst wrapArray = fns =>\n new Transform({\n writableObjectMode: true,\n readableObjectMode: true,\n transform(chunk, encoding, callback) {\n try {\n let value = chunk;\n for (let i = 0; i < fns.length; ++i) {\n const result = fns[i].call(this, value, encoding);\n if (result === Chain.none) {\n callback(null);\n return;\n }\n if (Chain.isFinal(result)) {\n value = Chain.getFinalValue(result);\n break;\n }\n value = result;\n }\n Chain.sanitize(value, this);\n callback(null);\n } catch (error) {\n callback(error);\n }\n }\n });\n\n// is*NodeStream functions taken from https://github.com/nodejs/node/blob/master/lib/internal/streams/utils.js\nconst isReadableNodeStream = obj =>\n obj &&\n typeof obj.pipe === 'function' &&\n typeof obj.on === 'function' &&\n (!obj._writableState || (typeof obj._readableState === 'object' ? obj._readableState.readable : null) !== false) && // Duplex\n (!obj._writableState || obj._readableState); // Writable has .pipe.\n\nconst isWritableNodeStream = obj =>\n obj &&\n typeof obj.write === 'function' &&\n typeof obj.on === 'function' &&\n (!obj._readableState || (typeof obj._writableState === 'object' ? obj._writableState.writable : null) !== false); // Duplex\n\nconst isDuplexNodeStream = obj =>\n obj && typeof obj.pipe === 'function' && obj._readableState && typeof obj.on === 'function' && typeof obj.write === 'function';\n\nclass Chain extends Duplex {\n constructor(fns, options) {\n super(options || {writableObjectMode: true, readableObjectMode: true});\n\n if (!(fns instanceof Array) || !fns.length) {\n throw Error(\"Chain's argument should be a non-empty array.\");\n }\n\n this.streams = fns\n .filter(fn => fn)\n .map((fn, index, fns) => {\n if (typeof fn === 'function' || fn instanceof Array) return Chain.convertToTransform(fn);\n if (isDuplexNodeStream(fn) || (!index && isReadableNodeStream(fn)) || (index === fns.length - 1 && isWritableNodeStream(fn))) {\n return fn;\n }\n throw Error('Arguments should be functions, arrays or streams.');\n })\n .filter(s => s);\n this.input = this.streams[0];\n this.output = this.streams.reduce((output, stream) => (output && output.pipe(stream)) || stream);\n\n if (!isWritableNodeStream(this.input)) {\n this._write = (_1, _2, callback) => callback(null);\n this._final = callback => callback(null); // unavailable in Node 6\n this.input.on('end', () => this.end());\n }\n\n if (isReadableNodeStream(this.output)) {\n this.output.on('data', chunk => !this.push(chunk) && this.output.pause());\n this.output.on('end', () => this.push(null));\n } else {\n this._read = () => {}; // nop\n this.resume();\n this.output.on('finish', () => this.push(null));\n }\n\n // connect events\n if (!options || !options.skipEvents) {\n this.streams.forEach(stream => stream.on('error', error => this.emit('error', error)));\n }\n }\n _write(chunk, encoding, callback) {\n let error = null;\n try {\n this.input.write(chunk, encoding, e => callback(e || error));\n } catch (e) {\n error = e;\n }\n }\n _final(callback) {\n let error = null;\n try {\n this.input.end(null, null, e => callback(e || error));\n } catch (e) {\n error = e;\n }\n }\n _read() {\n this.output.resume();\n }\n static make(fns, options) {\n return new Chain(fns, options);\n }\n static sanitize(result, stream) {\n if (Chain.isFinal(result)) {\n result = Chain.getFinalValue(result);\n } else if (Chain.isMany(result)) {\n result = Chain.getManyValues(result);\n }\n if (result !== undefined && result !== null && result !== Chain.none) {\n if (result instanceof Array) {\n result.forEach(value => value !== undefined && value !== null && stream.push(value));\n } else {\n stream.push(result);\n }\n }\n }\n static convertToTransform(fn) {\n if (typeof fn === 'function') return wrapFunction(fn);\n if (fn instanceof Array) return fn.length ? wrapArray(fn) : null;\n return null;\n }\n}\n\nChain.none = none;\nChain.final = final;\nChain.isFinal = isFinal;\nChain.getFinalValue = getFinalValue;\nChain.many = many;\nChain.isMany = isMany;\nChain.getManyValues = getManyValues;\n\nChain.chain = Chain.make;\nChain.make.Constructor = Chain;\n\nmodule.exports = Chain;\n","'use strict';\n\nconst {chain} = require('stream-chain');\n\nconst Parser = require('../Parser');\n\nconst withParser = (fn, options) =>\n chain([new Parser(options), fn(options)], Object.assign({}, options, {writableObjectMode: false, readableObjectMode: true}));\n\nmodule.exports = withParser;\n","'use strict';\n\nconst FilterBase = require('./FilterBase');\nconst withParser = require('../utils/withParser');\n\nclass Pick extends FilterBase {\n static make(options) {\n return new Pick(options);\n }\n\n static withParser(options) {\n return withParser(Pick.make, options);\n }\n\n _checkChunk(chunk) {\n switch (chunk.name) {\n case 'startObject':\n case 'startArray':\n if (this._filter(this._stack, chunk)) {\n this.push(chunk);\n this._transform = this._passObject;\n this._depth = 1;\n return true;\n }\n break;\n case 'startString':\n if (this._filter(this._stack, chunk)) {\n this.push(chunk);\n this._transform = this._passString;\n return true;\n }\n break;\n case 'startNumber':\n if (this._filter(this._stack, chunk)) {\n this.push(chunk);\n this._transform = this._passNumber;\n return true;\n }\n break;\n case 'nullValue':\n case 'trueValue':\n case 'falseValue':\n case 'stringValue':\n case 'numberValue':\n if (this._filter(this._stack, chunk)) {\n this.push(chunk);\n this._transform = this._once ? this._skip : this._check;\n return true;\n }\n break;\n }\n return false;\n }\n}\nPick.pick = Pick.make;\nPick.make.Constructor = Pick;\n\nmodule.exports = Pick;\n","'use strict';\n\nconst EventEmitter = require('events');\n\nconst startObject = Ctr =>\n function () {\n if (this.done) {\n this.done = false;\n } else {\n this.stack.push(this.current, this.key);\n }\n this.current = new Ctr();\n this.key = null;\n };\n\nclass Assembler extends EventEmitter {\n static connectTo(stream, options) {\n return new Assembler(options).connectTo(stream);\n }\n\n constructor(options) {\n super();\n this.stack = [];\n this.current = this.key = null;\n this.done = true;\n if (options) {\n this.reviver = typeof options.reviver == 'function' && options.reviver;\n if (this.reviver) {\n this.stringValue = this._saveValue = this._saveValueWithReviver;\n }\n if (options.numberAsString) {\n this.numberValue = this.stringValue;\n }\n }\n }\n\n connectTo(stream) {\n stream.on('data', chunk => {\n if (this[chunk.name]) {\n this[chunk.name](chunk.value);\n if (this.done) this.emit('done', this);\n }\n });\n return this;\n }\n\n get depth() {\n return (this.stack.length >> 1) + (this.done ? 0 : 1);\n }\n\n get path() {\n const path = [];\n for (let i = 0; i < this.stack.length; i += 2) {\n const key = this.stack[i + 1];\n path.push(key === null ? this.stack[i].length : key);\n }\n return path;\n }\n\n dropToLevel(level) {\n if (level < this.depth) {\n if (level) {\n const index = (level - 1) << 1;\n this.current = this.stack[index];\n this.key = this.stack[index + 1];\n this.stack.splice(index);\n } else {\n this.stack = [];\n this.current = this.key = null;\n this.done = true;\n }\n }\n return this;\n }\n\n consume(chunk) {\n this[chunk.name] && this[chunk.name](chunk.value);\n return this;\n }\n\n keyValue(value) {\n this.key = value;\n }\n\n //stringValue() - aliased below to _saveValue()\n\n numberValue(value) {\n this._saveValue(parseFloat(value));\n }\n nullValue() {\n this._saveValue(null);\n }\n trueValue() {\n this._saveValue(true);\n }\n falseValue() {\n this._saveValue(false);\n }\n\n //startObject() - assigned below\n\n endObject() {\n if (this.stack.length) {\n const value = this.current;\n this.key = this.stack.pop();\n this.current = this.stack.pop();\n this._saveValue(value);\n } else {\n this.done = true;\n }\n }\n\n //startArray() - assigned below\n //endArray() - aliased below to endObject()\n\n _saveValue(value) {\n if (this.done) {\n this.current = value;\n } else {\n if (this.current instanceof Array) {\n this.current.push(value);\n } else {\n this.current[this.key] = value;\n this.key = null;\n }\n }\n }\n _saveValueWithReviver(value) {\n if (this.done) {\n this.current = this.reviver('', value);\n } else {\n if (this.current instanceof Array) {\n value = this.reviver('' + this.current.length, value);\n this.current.push(value);\n if (value === undefined) {\n delete this.current[this.current.length - 1];\n }\n } else {\n value = this.reviver(this.key, value);\n if (value !== undefined) {\n this.current[this.key] = value;\n }\n this.key = null;\n }\n }\n }\n}\n\nAssembler.prototype.stringValue = Assembler.prototype._saveValue;\nAssembler.prototype.startObject = startObject(Object);\nAssembler.prototype.startArray = startObject(Array);\nAssembler.prototype.endArray = Assembler.prototype.endObject;\n\nmodule.exports = Assembler;\n","'use strict';\n\nconst {Transform} = require('stream');\nconst Assembler = require('../Assembler');\n\nclass Counter {\n constructor(initialDepth) {\n this.depth = initialDepth;\n }\n startObject() {\n ++this.depth;\n }\n endObject() {\n --this.depth;\n }\n startArray() {\n ++this.depth;\n }\n endArray() {\n --this.depth;\n }\n}\n\nclass StreamBase extends Transform {\n constructor(options) {\n super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));\n if (options) {\n this.objectFilter = options.objectFilter;\n this.includeUndecided = options.includeUndecided;\n }\n if (typeof this.objectFilter != 'function') {\n this._filter = this._transform;\n }\n this._transform = this._wait || this._filter;\n this._assembler = new Assembler(options);\n }\n\n _transform(chunk, encoding, callback) {\n if (this._assembler[chunk.name]) {\n this._assembler[chunk.name](chunk.value);\n if (this._assembler.depth === this._level) {\n this._push();\n }\n }\n callback(null);\n }\n\n _filter(chunk, encoding, callback) {\n if (this._assembler[chunk.name]) {\n this._assembler[chunk.name](chunk.value);\n const result = this.objectFilter(this._assembler);\n if (result) {\n if (this._assembler.depth === this._level) {\n this._push();\n this._transform = this._filter;\n }\n this._transform = this._accept;\n return callback(null);\n }\n if (result === false) {\n this._saved_assembler = this._assembler;\n this._assembler = new Counter(this._saved_assembler.depth);\n this._saved_assembler.dropToLevel(this._level);\n if (this._assembler.depth === this._level) {\n this._assembler = this._saved_assembler;\n this._transform = this._filter;\n }\n this._transform = this._reject;\n return callback(null);\n }\n if (this._assembler.depth === this._level) {\n this._push(!this.includeUndecided);\n }\n }\n callback(null);\n }\n\n _accept(chunk, encoding, callback) {\n if (this._assembler[chunk.name]) {\n this._assembler[chunk.name](chunk.value);\n if (this._assembler.depth === this._level) {\n this._push();\n this._transform = this._filter;\n }\n }\n callback(null);\n }\n\n _reject(chunk, encoding, callback) {\n if (this._assembler[chunk.name]) {\n this._assembler[chunk.name](chunk.value);\n if (this._assembler.depth === this._level) {\n this._assembler = this._saved_assembler;\n this._transform = this._filter;\n }\n }\n callback(null);\n }\n}\n\nmodule.exports = StreamBase;\n","'use strict';\n\nconst StreamBase = require('./StreamBase');\nconst withParser = require('../utils/withParser');\n\nclass StreamValues extends StreamBase {\n static make(options) {\n return new StreamValues(options);\n }\n\n static withParser(options) {\n return withParser(StreamValues.make, Object.assign({}, options, {jsonStreaming: true}));\n }\n\n constructor(options) {\n super(options);\n this._counter = 0;\n this._level = 0;\n }\n\n _push(discard) {\n if (discard) {\n ++this._counter;\n } else {\n this.push({key: this._counter++, value: this._assembler.current});\n }\n this._assembler.current = this._assembler.key = null;\n }\n}\nStreamValues.streamValues = StreamValues.make;\nStreamValues.make.Constructor = StreamValues;\n\nmodule.exports = StreamValues;\n","module.exports = Pend;\n\nfunction Pend() {\n this.pending = 0;\n this.max = Infinity;\n this.listeners = [];\n this.waiting = [];\n this.error = null;\n}\n\nPend.prototype.go = function(fn) {\n if (this.pending < this.max) {\n pendGo(this, fn);\n } else {\n this.waiting.push(fn);\n }\n};\n\nPend.prototype.wait = function(cb) {\n if (this.pending === 0) {\n cb(this.error);\n } else {\n this.listeners.push(cb);\n }\n};\n\nPend.prototype.hold = function() {\n return pendHold(this);\n};\n\nfunction pendHold(self) {\n self.pending += 1;\n var called = false;\n return onCb;\n function onCb(err) {\n if (called) throw new Error(\"callback called twice\");\n called = true;\n self.error = self.error || err;\n self.pending -= 1;\n if (self.waiting.length > 0 && self.pending < self.max) {\n pendGo(self, self.waiting.shift());\n } else if (self.pending === 0) {\n var listeners = self.listeners;\n self.listeners = [];\n listeners.forEach(cbListener);\n }\n }\n function cbListener(listener) {\n listener(self.error);\n }\n}\n\nfunction pendGo(self, fn) {\n fn(pendHold(self));\n}\n","// This was adapted from https://github.com/andrewrk/node-fd-slicer by Andrew Kelley under the MIT License.\nvar fs = require('fs');\nvar util = require('util');\nvar stream = require('stream');\nvar Readable = stream.Readable;\nvar PassThrough = stream.PassThrough;\nvar Pend = require('pend');\nvar EventEmitter = require('events').EventEmitter;\n\nexports.BufferSlicer = BufferSlicer;\nexports.FdSlicer = FdSlicer;\n\nutil.inherits(FdSlicer, EventEmitter);\nfunction FdSlicer(fd) {\n EventEmitter.call(this);\n\n this.fd = fd;\n this.pend = new Pend();\n this.pend.max = 1;\n this.refCount = 0;\n}\n\nFdSlicer.prototype.read = function(buffer, offset, length, position, callback) {\n var self = this;\n self.pend.go(function(cb) {\n fs.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer) {\n cb();\n callback(err, bytesRead, buffer);\n });\n });\n};\n\nFdSlicer.prototype.createReadStream = function(options) {\n return new ReadStream(this, options);\n};\n\nFdSlicer.prototype.ref = function() {\n this.refCount += 1;\n};\n\nFdSlicer.prototype.unref = function() {\n var self = this;\n self.refCount -= 1;\n if (self.refCount < 0) throw new Error(\"invalid unref\");\n if (self.refCount > 0) return;\n\n fs.close(self.fd, onCloseDone);\n\n function onCloseDone(err) {\n if (err) {\n self.emit('error', err);\n } else {\n self.emit('close');\n }\n }\n};\n\nutil.inherits(ReadStream, Readable);\nfunction ReadStream(context, options) {\n options = options || {};\n Readable.call(this, options);\n\n this.context = context;\n this.context.ref();\n\n this.start = options.start || 0;\n this.endOffset = options.end;\n this.pos = this.start;\n}\n\nReadStream.prototype._read = function(n) {\n var self = this;\n\n var toRead = Math.min(self._readableState.highWaterMark, n);\n if (self.endOffset != null) {\n toRead = Math.min(toRead, self.endOffset - self.pos);\n }\n if (toRead <= 0) {\n self.push(null);\n this._cleanup();\n return;\n }\n self.context.pend.go(function(cb) {\n var buffer = Buffer.allocUnsafe(toRead);\n fs.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {\n if (err) {\n self.destroy(err);\n } else if (bytesRead === 0) {\n self.push(null);\n self._cleanup();\n } else {\n self.pos += bytesRead;\n self.push(buffer.slice(0, bytesRead));\n }\n cb();\n });\n });\n};\n\nReadStream.prototype._destroy = function(err, cb) {\n // Node 14+ calls this automatically at EOF.\n this._cleanup();\n cb(err);\n};\n\nReadStream.prototype._cleanup = function() {\n if (this.context != null) {\n this.context.unref();\n this.context = null;\n }\n};\n\nutil.inherits(BufferSlicer, EventEmitter);\nfunction BufferSlicer(buffer) {\n EventEmitter.call(this);\n\n this.refCount = 0;\n this.buffer = buffer;\n}\n\nBufferSlicer.prototype.read = function(buffer, offset, length, position, callback) {\n if (!(0 <= offset && offset <= buffer.length)) throw new RangeError(\"offset outside buffer: 0 <= \" + offset + \" <= \" + buffer.length);\n if (position < 0) throw new RangeError(\"position is negative: \" + position);\n if (offset + length > buffer.length) {\n // The caller's buffer can't hold all the bytes they're trying to read.\n // Clamp the length instead of giving an error.\n // The callback will be informed of fewer than expected bytes written.\n length = buffer.length - offset;\n }\n if (position + length > this.buffer.length) {\n // Clamp any attempt to read past the end of the source buffer.\n length = this.buffer.length - position;\n }\n if (length <= 0) {\n // After any clamping, we're fully out of bounds or otherwise have nothing to do.\n // This isn't an error; it's just zero bytes written.\n setImmediate(function() {\n callback(null, 0);\n });\n return;\n }\n this.buffer.copy(buffer, offset, position, position + length);\n setImmediate(function() {\n callback(null, length);\n });\n};\n\nBufferSlicer.prototype.createReadStream = function(options) {\n options = options || {};\n var readStream = new PassThrough(options);\n readStream.start = options.start || 0;\n readStream.endOffset = options.end;\n // by the time this function returns, we'll be done.\n readStream.pos = readStream.endOffset || this.buffer.length;\n\n var entireSlice = this.buffer.slice(readStream.start, readStream.pos);\n // Cut the buffer into smaller slices for better memory usage when streaming into a zlib inflate stream.\n // See https://github.com/thejoshwolfe/yauzl/issues/87\n var maxChunkSize = 0x10000;\n var offset = 0;\n while (true) {\n var nextOffset = offset + maxChunkSize;\n if (nextOffset >= entireSlice.length) {\n // last chunk\n if (offset < entireSlice.length) {\n readStream.write(entireSlice.slice(offset, entireSlice.length));\n }\n break;\n }\n readStream.write(entireSlice.slice(offset, nextOffset));\n offset = nextOffset;\n }\n\n readStream.end();\n return readStream;\n};\n\nBufferSlicer.prototype.ref = function() {\n this.refCount += 1;\n};\n\nBufferSlicer.prototype.unref = function() {\n this.refCount -= 1;\n\n if (this.refCount < 0) {\n throw new Error(\"invalid unref\");\n }\n};\n","// Adapted from https://github.com/brianloveswords/buffer-crc32/blob/v0.2.13/index.js\nconst CRC_TABLE = new Int32Array([\n 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,\n 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,\n 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,\n 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,\n 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,\n 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,\n 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,\n 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,\n 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,\n 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,\n 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,\n 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,\n 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,\n 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,\n 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,\n 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,\n 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,\n 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,\n 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,\n 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,\n 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,\n 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,\n 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,\n 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,\n 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,\n 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,\n 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,\n 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,\n 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,\n 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,\n 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,\n 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,\n]);\n\nfunction crc32(buf) {\n let crc = -1;\n for (let x of buf) {\n crc = CRC_TABLE[(crc ^ x) & 0xff] ^ (crc >>> 8);\n }\n return (crc ^ -1) >>> 0;\n}\n\nmodule.exports = crc32;\n","var fs = require(\"fs\");\nvar zlib = require(\"zlib\");\nvar fd_slicer = require(\"./fd-slicer\");\nvar util = require(\"util\");\nvar EventEmitter = require(\"events\").EventEmitter;\nvar Transform = require(\"stream\").Transform;\nvar PassThrough = require(\"stream\").PassThrough;\nvar Writable = require(\"stream\").Writable;\n\n// Node 20 added zlib.crc32.\nconst crc32 = typeof zlib.crc32 === \"function\" ? zlib.crc32 : require(\"./crc32\");\n\nexports.open = open;\nexports.fromFd = fromFd;\nexports.fromBuffer = fromBuffer;\nexports.fromRandomAccessReader = fromRandomAccessReader;\nexports.openPromise = openPromise;\nexports.fromFdPromise = fromFdPromise;\nexports.fromBufferPromise = fromBufferPromise;\nexports.fromRandomAccessReaderPromise = fromRandomAccessReaderPromise;\n\nexports.dosDateTimeToDate = dosDateTimeToDate;\nexports.getFileNameLowLevel = getFileNameLowLevel;\nexports.validateFileName = validateFileName;\nexports.parseExtraFields = parseExtraFields;\nexports.ZipFile = ZipFile;\nexports.Entry = Entry;\nexports.LocalFileHeader = LocalFileHeader;\nexports.RandomAccessReader = RandomAccessReader;\n\nfunction openPromise(path, options) {\n return new Promise((resolve, reject) => {\n open(path, {...options, lazyEntries: true}, function(err, zipfile) {\n if (err) return reject(err);\n resolve(zipfile);\n });\n });\n}\nfunction fromFdPromise(fd, options) {\n return new Promise((resolve, reject) => {\n fromFd(fd, {...options, lazyEntries: true}, function(err, zipfile) {\n if (err) return reject(err);\n resolve(zipfile);\n });\n });\n}\nfunction fromBufferPromise(buffer, options) {\n return new Promise((resolve, reject) => {\n fromBuffer(buffer, {...options, lazyEntries: true}, function(err, zipfile) {\n if (err) return reject(err);\n resolve(zipfile);\n });\n });\n}\nfunction fromRandomAccessReaderPromise(reader, totalSize, options) {\n return new Promise((resolve, reject) => {\n fromRandomAccessReader(reader, totalSize, {...options, lazyEntries: true}, function(err, zipfile) {\n if (err) return reject(err);\n resolve(zipfile);\n });\n });\n}\n\nfunction open(path, options, callback) {\n if (typeof options === \"function\") {\n callback = options;\n options = null;\n }\n if (options == null) options = {};\n if (options.autoClose == null) options.autoClose = true;\n if (options.lazyEntries == null) options.lazyEntries = false;\n if (options.decodeStrings == null) options.decodeStrings = true;\n if (options.validateEntrySizes == null) options.validateEntrySizes = true;\n if (options.strictFileNames == null) options.strictFileNames = false;\n if (callback == null) callback = defaultCallback;\n fs.open(path, \"r\", function(err, fd) {\n if (err) return callback(err);\n fromFd(fd, options, function(err, zipfile) {\n if (err) fs.close(fd, defaultCallback);\n callback(err, zipfile);\n });\n });\n}\n\nfunction fromFd(fd, options, callback) {\n if (typeof options === \"function\") {\n callback = options;\n options = null;\n }\n if (options == null) options = {};\n if (options.autoClose == null) options.autoClose = false;\n if (options.lazyEntries == null) options.lazyEntries = false;\n if (options.decodeStrings == null) options.decodeStrings = true;\n if (options.validateEntrySizes == null) options.validateEntrySizes = true;\n if (options.strictFileNames == null) options.strictFileNames = false;\n if (callback == null) callback = defaultCallback;\n fs.fstat(fd, function(err, stats) {\n if (err) return callback(err);\n var reader = new fd_slicer.FdSlicer(fd);\n fromRandomAccessReader(reader, stats.size, options, callback);\n });\n}\n\nfunction fromBuffer(buffer, options, callback) {\n if (typeof options === \"function\") {\n callback = options;\n options = null;\n }\n if (options == null) options = {};\n options.autoClose = false;\n if (options.lazyEntries == null) options.lazyEntries = false;\n if (options.decodeStrings == null) options.decodeStrings = true;\n if (options.validateEntrySizes == null) options.validateEntrySizes = true;\n if (options.strictFileNames == null) options.strictFileNames = false;\n var reader = new fd_slicer.BufferSlicer(buffer);\n fromRandomAccessReader(reader, buffer.length, options, callback);\n}\n\nfunction fromRandomAccessReader(reader, totalSize, options, callback) {\n if (typeof options === \"function\") {\n callback = options;\n options = null;\n }\n if (options == null) options = {};\n if (options.autoClose == null) options.autoClose = true;\n if (options.lazyEntries == null) options.lazyEntries = false;\n if (options.decodeStrings == null) options.decodeStrings = true;\n var decodeStrings = !!options.decodeStrings;\n if (options.validateEntrySizes == null) options.validateEntrySizes = true;\n if (options.strictFileNames == null) options.strictFileNames = false;\n if (callback == null) callback = defaultCallback;\n if (typeof totalSize !== \"number\") throw new Error(\"expected totalSize parameter to be a number\");\n if (totalSize > Number.MAX_SAFE_INTEGER) {\n throw new Error(\"zip file too large. only file sizes up to 2^52 are supported due to JavaScript's Number type being an IEEE 754 double.\");\n }\n\n // the matching unref() call is in zipfile.close()\n reader.ref();\n\n // eocdr means End of Central Directory Record.\n // search backwards for the eocdr signature.\n // the last field of the eocdr is a variable-length comment.\n // the comment size is encoded in a 2-byte field in the eocdr, which we can't find without trudging backwards through the comment to find it.\n // as a consequence of this design decision, it's possible to have ambiguous zip file metadata if a coherent eocdr was in the comment.\n // we search backwards for a eocdr signature, and hope that whoever made the zip file was smart enough to forbid the eocdr signature in the comment.\n var eocdrWithoutCommentSize = 22;\n var zip64EocdlSize = 20; // Zip64 end of central directory locator\n var maxCommentSize = 0xffff; // 2-byte size\n var bufferSize = Math.min(zip64EocdlSize + eocdrWithoutCommentSize + maxCommentSize, totalSize);\n var buffer = newBuffer(bufferSize);\n var bufferReadStart = totalSize - buffer.length;\n readAndAssertNoEof(reader, buffer, 0, bufferSize, bufferReadStart, function(err) {\n if (err) return callback(err);\n for (var i = bufferSize - eocdrWithoutCommentSize; i >= 0; i -= 1) {\n if (buffer.readUInt32LE(i) !== 0x06054b50) continue;\n // found eocdr\n var eocdrBuffer = buffer.subarray(i);\n\n // 0 - End of central directory signature = 0x06054b50\n // 4 - Number of this disk\n var diskNumber = eocdrBuffer.readUInt16LE(4);\n // 6 - Disk where central directory starts\n // 8 - Number of central directory records on this disk\n // 10 - Total number of central directory records\n var entryCount = eocdrBuffer.readUInt16LE(10);\n // 12 - Size of central directory (bytes)\n // 16 - Offset of start of central directory, relative to start of archive\n var centralDirectoryOffset = eocdrBuffer.readUInt32LE(16);\n // 20 - Comment length\n var commentLength = eocdrBuffer.readUInt16LE(20);\n var expectedCommentLength = eocdrBuffer.length - eocdrWithoutCommentSize;\n if (commentLength !== expectedCommentLength) {\n return callback(new Error(\"Invalid comment length. Expected: \" + expectedCommentLength + \". Found: \" + commentLength + \". Are there extra bytes at the end of the file? Or is the end of central dir signature `PK☺☻` in the comment?\"));\n }\n // 22 - Comment\n // the encoding is always cp437.\n var comment = decodeStrings ? decodeBuffer(eocdrBuffer.subarray(22), false)\n : eocdrBuffer.subarray(22);\n\n // Look for a Zip64 end of central directory locator\n if (i - zip64EocdlSize >= 0 && buffer.readUInt32LE(i - zip64EocdlSize) === 0x07064b50) {\n // ZIP64 format\n var zip64EocdlBuffer = buffer.subarray(i - zip64EocdlSize, i - zip64EocdlSize + zip64EocdlSize);\n // 0 - zip64 end of central dir locator signature = 0x07064b50\n // 4 - number of the disk with the start of the zip64 end of central directory\n // 8 - relative offset of the zip64 end of central directory record\n var zip64EocdrOffset = readUInt64LE(zip64EocdlBuffer, 8);\n // 16 - total number of disks\n\n // ZIP64 end of central directory record\n var zip64EocdrBuffer = newBuffer(56);\n return readAndAssertNoEof(reader, zip64EocdrBuffer, 0, zip64EocdrBuffer.length, zip64EocdrOffset, function(err) {\n if (err) return callback(err);\n\n // 0 - zip64 end of central dir signature 4 bytes (0x06064b50)\n if (zip64EocdrBuffer.readUInt32LE(0) !== 0x06064b50) {\n return callback(new Error(\"invalid zip64 end of central directory record signature\"));\n }\n // 4 - size of zip64 end of central directory record 8 bytes\n // 12 - version made by 2 bytes\n // 14 - version needed to extract 2 bytes\n // 16 - number of this disk 4 bytes\n diskNumber = zip64EocdrBuffer.readUInt32LE(16);\n if (diskNumber !== 0) {\n // Check this only after zip64 overrides. See #118.\n return callback(new Error(\"multi-disk zip files are not supported: found disk number: \" + diskNumber));\n }\n // 20 - number of the disk with the start of the central directory 4 bytes\n // 24 - total number of entries in the central directory on this disk 8 bytes\n // 32 - total number of entries in the central directory 8 bytes\n entryCount = readUInt64LE(zip64EocdrBuffer, 32);\n // 40 - size of the central directory 8 bytes\n // 48 - offset of start of central directory with respect to the starting disk number 8 bytes\n centralDirectoryOffset = readUInt64LE(zip64EocdrBuffer, 48);\n // 56 - zip64 extensible data sector (variable size)\n return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries, decodeStrings, options.validateEntrySizes, options.strictFileNames));\n });\n }\n\n // Not ZIP64 format\n if (diskNumber !== 0) {\n return callback(new Error(\"multi-disk zip files are not supported: found disk number: \" + diskNumber));\n }\n return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries, decodeStrings, options.validateEntrySizes, options.strictFileNames));\n\n }\n\n // Not a zip file.\n callback(new Error(\"End of central directory record signature not found. Either not a zip file, or file is truncated.\"));\n });\n}\n\nutil.inherits(ZipFile, EventEmitter);\nfunction ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries, decodeStrings, validateEntrySizes, strictFileNames) {\n var self = this;\n EventEmitter.call(self);\n self.reader = reader;\n // forward close events\n self.reader.on(\"error\", function(err) {\n // error closing the fd\n emitError(self, err);\n });\n self.reader.once(\"close\", function() {\n self.emit(\"close\");\n });\n self.readEntryCursor = centralDirectoryOffset;\n self.fileSize = fileSize;\n self.entryCount = entryCount;\n self.comment = comment;\n self.entriesRead = 0;\n self.autoClose = !!autoClose;\n self.lazyEntries = !!lazyEntries;\n self.decodeStrings = !!decodeStrings;\n self.validateEntrySizes = !!validateEntrySizes;\n self.strictFileNames = !!strictFileNames;\n self.isOpen = true;\n self.emittedError = false;\n self.hasEachEntryBeenCalled = false;\n\n if (!self.lazyEntries) self._readEntry();\n}\nZipFile.prototype.close = function() {\n if (!this.isOpen) return;\n this.isOpen = false;\n this.reader.unref();\n};\n\nfunction emitErrorAndAutoClose(self, err) {\n if (self.autoClose) self.close();\n emitError(self, err);\n}\nfunction emitError(self, err) {\n if (self.emittedError) return;\n self.emittedError = true;\n self.emit(\"error\", err);\n}\n\nZipFile.prototype.readEntry = function() {\n if (!this.lazyEntries) throw new Error(\"readEntry() called without lazyEntries:true\");\n this._readEntry();\n};\nZipFile.prototype._readEntry = function() {\n var self = this;\n if (self.entryCount === self.entriesRead) {\n // done with metadata\n setImmediate(function() {\n if (self.autoClose) self.close();\n if (self.emittedError) return;\n self.emit(\"end\");\n });\n return;\n }\n if (self.emittedError) return;\n var buffer = newBuffer(46);\n readAndAssertNoEof(self.reader, buffer, 0, buffer.length, self.readEntryCursor, function(err) {\n if (err) return emitErrorAndAutoClose(self, err);\n if (self.emittedError) return;\n var entry = new Entry();\n // 0 - Central directory file header signature\n var signature = buffer.readUInt32LE(0);\n if (signature !== 0x02014b50) return emitErrorAndAutoClose(self, new Error(\"invalid central directory file header signature: 0x\" + signature.toString(16)));\n // 4 - Version made by\n entry.versionMadeBy = buffer.readUInt16LE(4);\n // 6 - Version needed to extract (minimum)\n entry.versionNeededToExtract = buffer.readUInt16LE(6);\n // 8 - General purpose bit flag\n entry.generalPurposeBitFlag = buffer.readUInt16LE(8);\n // 10 - Compression method\n entry.compressionMethod = buffer.readUInt16LE(10);\n // 12 - File last modification time\n entry.lastModFileTime = buffer.readUInt16LE(12);\n // 14 - File last modification date\n entry.lastModFileDate = buffer.readUInt16LE(14);\n // 16 - CRC-32\n entry.crc32 = buffer.readUInt32LE(16);\n // 20 - Compressed size\n entry.compressedSize = buffer.readUInt32LE(20);\n // 24 - Uncompressed size\n entry.uncompressedSize = buffer.readUInt32LE(24);\n // 28 - File name length (n)\n entry.fileNameLength = buffer.readUInt16LE(28);\n // 30 - Extra field length (m)\n entry.extraFieldLength = buffer.readUInt16LE(30);\n // 32 - File comment length (k)\n entry.fileCommentLength = buffer.readUInt16LE(32);\n // 34 - Disk number where file starts\n // 36 - Internal file attributes\n entry.internalFileAttributes = buffer.readUInt16LE(36);\n // 38 - External file attributes\n entry.externalFileAttributes = buffer.readUInt32LE(38);\n // 42 - Relative offset of local file header\n entry.relativeOffsetOfLocalHeader = buffer.readUInt32LE(42);\n\n if (entry.generalPurposeBitFlag & 0x40) return emitErrorAndAutoClose(self, new Error(\"strong encryption is not supported\"));\n\n self.readEntryCursor += 46;\n\n buffer = newBuffer(entry.fileNameLength + entry.extraFieldLength + entry.fileCommentLength);\n readAndAssertNoEof(self.reader, buffer, 0, buffer.length, self.readEntryCursor, function(err) {\n if (err) return emitErrorAndAutoClose(self, err);\n if (self.emittedError) return;\n // 46 - File name\n entry.fileNameRaw = buffer.subarray(0, entry.fileNameLength);\n // 46+n - Extra field\n var fileCommentStart = entry.fileNameLength + entry.extraFieldLength;\n entry.extraFieldRaw = buffer.subarray(entry.fileNameLength, fileCommentStart);\n // 46+n+m - File comment\n entry.fileCommentRaw = buffer.subarray(fileCommentStart, fileCommentStart + entry.fileCommentLength);\n\n // Parse the extra fields, which we need for processing other fields.\n try {\n entry.extraFields = parseExtraFields(entry.extraFieldRaw);\n } catch (err) {\n return emitErrorAndAutoClose(self, err);\n }\n\n // Interpret strings according to bit flags, extra fields, and options.\n if (self.decodeStrings) {\n var isUtf8 = (entry.generalPurposeBitFlag & 0x800) !== 0;\n entry.fileComment = decodeBuffer(entry.fileCommentRaw, isUtf8);\n entry.fileName = getFileNameLowLevel(entry.generalPurposeBitFlag, entry.fileNameRaw, entry.extraFields, self.strictFileNames);\n var errorMessage = validateFileName(entry.fileName);\n if (errorMessage != null) return emitErrorAndAutoClose(self, new Error(errorMessage));\n } else {\n entry.fileComment = entry.fileCommentRaw;\n entry.fileName = entry.fileNameRaw;\n }\n // Maintain API compatibility. See https://github.com/thejoshwolfe/yauzl/issues/47\n entry.comment = entry.fileComment;\n\n self.readEntryCursor += buffer.length;\n self.entriesRead += 1;\n\n // Check for the Zip64 Extended Information Extra Field.\n for (var i = 0; i < entry.extraFields.length; i++) {\n var extraField = entry.extraFields[i];\n if (extraField.id !== 0x0001) continue;\n // Found it.\n\n var zip64EiefBuffer = extraField.data;\n var index = 0;\n // 0 - Original Size 8 bytes\n if (entry.uncompressedSize === 0xffffffff) {\n if (index + 8 > zip64EiefBuffer.length) {\n return emitErrorAndAutoClose(self, new Error(\"zip64 extended information extra field does not include uncompressed size\"));\n }\n entry.uncompressedSize = readUInt64LE(zip64EiefBuffer, index);\n index += 8;\n }\n // 8 - Compressed Size 8 bytes\n if (entry.compressedSize === 0xffffffff) {\n if (index + 8 > zip64EiefBuffer.length) {\n return emitErrorAndAutoClose(self, new Error(\"zip64 extended information extra field does not include compressed size\"));\n }\n entry.compressedSize = readUInt64LE(zip64EiefBuffer, index);\n index += 8;\n }\n // 16 - Relative Header Offset 8 bytes\n if (entry.relativeOffsetOfLocalHeader === 0xffffffff) {\n if (index + 8 > zip64EiefBuffer.length) {\n return emitErrorAndAutoClose(self, new Error(\"zip64 extended information extra field does not include relative header offset\"));\n }\n entry.relativeOffsetOfLocalHeader = readUInt64LE(zip64EiefBuffer, index);\n index += 8;\n }\n // 24 - Disk Start Number 4 bytes\n\n break;\n }\n\n // validate file size\n if (self.validateEntrySizes && entry.compressionMethod === 0) {\n var expectedCompressedSize = entry.uncompressedSize;\n if (entry.isEncrypted()) {\n // traditional encryption prefixes the file data with a header\n expectedCompressedSize += 12;\n }\n if (entry.compressedSize !== expectedCompressedSize) {\n var msg = \"compressed/uncompressed size mismatch for stored file: \" + entry.compressedSize + \" != \" + entry.uncompressedSize;\n return emitErrorAndAutoClose(self, new Error(msg));\n }\n }\n\n self.emit(\"entry\", entry);\n\n if (!self.lazyEntries) self._readEntry();\n });\n });\n};\n\nZipFile.prototype.eachEntry = function() {\n const self = this;\n if (!self.lazyEntries) throw new Error(\"eachEntry() requires lazyEntries: true\");\n if (self.hasEachEntryBeenCalled) throw new Error(\"eachEntry() must only be called once per ZipFile\");\n self.hasEachEntryBeenCalled = true;\n\n let pendingResolveReject = null;\n self.on(\"entry\", onEntry);\n self.on(\"end\", onEnd);\n self.on(\"error\", onError);\n function cleanup() {\n self.removeListener(\"entry\", onEntry);\n self.removeListener(\"end\", onEnd);\n self.removeListener(\"error\", onError);\n if (self.autoClose) self.close();\n }\n\n function onEntry(entry) {\n let {resolve} = pendingResolveReject;\n pendingResolveReject = null;\n resolve({value: entry});\n }\n function onEnd() {\n let {resolve} = pendingResolveReject;\n pendingResolveReject = null;\n cleanup();\n resolve({done: true});\n }\n function onError(err) {\n let {reject} = pendingResolveReject;\n pendingResolveReject = null;\n cleanup();\n reject(err);\n }\n\n return {\n [Symbol.asyncIterator]() {\n // Called once by `for await...of`.\n return this;\n },\n next() {\n // Called on each iteration in a `for await...of`.\n const promise = new Promise((resolve, reject) => {\n if (pendingResolveReject != null) throw new Error(\"next() called before previous Promise was resolved.\");\n pendingResolveReject = {resolve, reject};\n });\n self.readEntry();\n return promise;\n },\n return(value) {\n // Called when breaking, returning, throwing out of a `for await...of`.\n cleanup();\n return Promise.resolve({done: true, value});\n },\n throw(value) {\n // Almost never called. Something about `yield*` maybe?\n cleanup();\n return Promise.reject(value);\n },\n };\n};\n\nZipFile.prototype.openReadStream = function(entry, options, callback) {\n var self = this;\n // parameter validation\n var relativeStart = 0;\n var relativeEnd = entry.compressedSize;\n if (callback == null) {\n callback = options;\n options = null;\n }\n if (options == null) {\n options = {};\n } else {\n if (options.decodeFileData === false) {\n // new, simple option\n if (options.decrypt != null) {\n throw new Error(\"cannot use options.decrypt when options.decodeFileData === false\");\n }\n if (options.decompress != null) {\n throw new Error(\"cannot use options.decompress when options.decodeFileData === false\");\n }\n // start and end are allowed\n } else {\n // old, complicated options\n if (options.decrypt != null) {\n if (!entry.isEncrypted()) {\n throw new Error(\"options.decrypt can only be specified for encrypted entries. See also option decodeFileData.\");\n }\n if (options.decrypt !== false) throw new Error(\"invalid options.decrypt value: \" + options.decrypt);\n if (entry.isCompressed()) {\n if (options.decompress !== false) throw new Error(\"entry is encrypted and compressed, and options.decompress !== false. See also option decodeFileData.\");\n }\n }\n if (options.decompress != null) {\n if (!entry.isCompressed()) {\n throw new Error(\"options.decompress can only be specified for compressed entries. See also option decodeFileData.\");\n }\n if (!(options.decompress === false || options.decompress === true)) {\n throw new Error(\"invalid options.decompress value: \" + options.decompress);\n }\n decompress = options.decompress;\n }\n }\n if (options.start != null) {\n relativeStart = options.start;\n if (relativeStart < 0) throw new Error(\"options.start < 0\");\n if (relativeStart > entry.compressedSize) throw new Error(\"options.start > entry.compressedSize\");\n }\n if (options.end != null) {\n relativeEnd = options.end;\n if (relativeEnd < 0) throw new Error(\"options.end < 0\");\n if (relativeEnd > entry.compressedSize) throw new Error(\"options.end > entry.compressedSize\");\n if (relativeEnd < relativeStart) throw new Error(\"options.end < options.start\");\n }\n }\n var rawMode = (\n options.decodeFileData === false || // Explicitly requested raw.\n (\n (entry.compressionMethod === 0 || // Naturally without compression.\n (entry.compressionMethod === 8 && options.decompress === false) // Deprecated compression bypass option.\n ) &&\n (!entry.isEncrypted() || // Naturally without encryption.\n options.decrypt === false // Deprecated encryption bypass option.\n )\n )\n );\n if (options.start != null || options.end != null) {\n // Ensure slicing deals with raw data.\n if (!rawMode) throw new Error(\"start/end range require options.decodeFileData === false for non-trivial encoded entries.\");\n }\n\n // any further errors can either be caused by the zipfile,\n // or were introduced in a minor version of yauzl,\n // so should be passed to the client rather than thrown.\n if (!self.isOpen) return callback(new Error(\"closed\"));\n if (entry.isEncrypted() && !rawMode) {\n if (options.decrypt !== false) return callback(new Error(\"entry is encrypted, and options.decodeFileData !== false\"));\n }\n var decompress;\n if (rawMode) {\n decompress = false;\n } else if (entry.compressionMethod === 8) {\n // 8 - The file is Deflated\n decompress = options.decodeFileData !== true;\n } else {\n return callback(new Error(\"unsupported compression method: \" + entry.compressionMethod));\n }\n\n self.readLocalFileHeader(entry, {minimal: true}, function(err, localFileHeader) {\n if (err) return callback(err);\n self.openReadStreamLowLevel(\n localFileHeader.fileDataStart, entry.compressedSize,\n relativeStart, relativeEnd,\n decompress, entry.uncompressedSize,\n callback);\n });\n};\n\nZipFile.prototype.openReadStreamLowLevel = function(fileDataStart, compressedSize, relativeStart, relativeEnd, decompress, uncompressedSize, callback) {\n var self = this;\n\n var fileDataEnd = fileDataStart + compressedSize;\n var readStream = self.reader.createReadStream({\n start: fileDataStart + relativeStart,\n end: fileDataStart + relativeEnd,\n });\n var endpointStream = readStream;\n if (decompress) {\n var destroyed = false;\n var inflateFilter = zlib.createInflateRaw();\n readStream.on(\"error\", function(err) {\n // setImmediate here because errors can be emitted during the first call to pipe()\n setImmediate(function() {\n if (!destroyed) inflateFilter.emit(\"error\", err);\n });\n });\n readStream.pipe(inflateFilter);\n\n if (self.validateEntrySizes) {\n endpointStream = new AssertByteCountStream(uncompressedSize);\n inflateFilter.on(\"error\", function(err) {\n // forward zlib errors to the client-visible stream\n setImmediate(function() {\n if (!destroyed) endpointStream.emit(\"error\", err);\n });\n });\n inflateFilter.pipe(endpointStream);\n } else {\n // the zlib filter is the client-visible stream\n endpointStream = inflateFilter;\n }\n // this is part of yauzl's API, so implement this function on the client-visible stream\n installDestroyFn(endpointStream, function() {\n destroyed = true;\n if (inflateFilter !== endpointStream) inflateFilter.unpipe(endpointStream);\n readStream.unpipe(inflateFilter);\n // TODO: the inflateFilter may cause a memory leak. see Issue #27.\n readStream.destroy();\n });\n }\n callback(null, endpointStream);\n};\n\nZipFile.prototype.readLocalFileHeader = function(entry, options, callback) {\n var self = this;\n if (callback == null) {\n callback = options;\n options = null;\n }\n if (options == null) options = {};\n\n self.reader.ref();\n var buffer = newBuffer(30);\n readAndAssertNoEof(self.reader, buffer, 0, buffer.length, entry.relativeOffsetOfLocalHeader, function(err) {\n try {\n if (err) return callback(err);\n // 0 - Local file header signature = 0x04034b50\n var signature = buffer.readUInt32LE(0);\n if (signature !== 0x04034b50) {\n return callback(new Error(\"invalid local file header signature: 0x\" + signature.toString(16)));\n }\n\n var fileNameLength = buffer.readUInt16LE(26);\n var extraFieldLength = buffer.readUInt16LE(28);\n var fileDataStart = entry.relativeOffsetOfLocalHeader + 30 + fileNameLength + extraFieldLength;\n // We now have enough information to do this bounds check.\n if (fileDataStart + entry.compressedSize > self.fileSize) {\n return callback(new Error(\"file data overflows file bounds: \" +\n fileDataStart + \" + \" + entry.compressedSize + \" > \" + self.fileSize));\n }\n\n if (options.minimal) {\n return callback(null, {fileDataStart: fileDataStart});\n }\n\n var localFileHeader = new LocalFileHeader();\n localFileHeader.fileDataStart = fileDataStart;\n\n // 4 - Version needed to extract (minimum)\n localFileHeader.versionNeededToExtract = buffer.readUInt16LE(4);\n // 6 - General purpose bit flag\n localFileHeader.generalPurposeBitFlag = buffer.readUInt16LE(6);\n // 8 - Compression method\n localFileHeader.compressionMethod = buffer.readUInt16LE(8);\n // 10 - File last modification time\n localFileHeader.lastModFileTime = buffer.readUInt16LE(10);\n // 12 - File last modification date\n localFileHeader.lastModFileDate = buffer.readUInt16LE(12);\n // 14 - CRC-32\n localFileHeader.crc32 = buffer.readUInt32LE(14);\n // 18 - Compressed size\n localFileHeader.compressedSize = buffer.readUInt32LE(18);\n // 22 - Uncompressed size\n localFileHeader.uncompressedSize = buffer.readUInt32LE(22);\n // 26 - File name length (n)\n localFileHeader.fileNameLength = fileNameLength;\n // 28 - Extra field length (m)\n localFileHeader.extraFieldLength = extraFieldLength;\n // 30 - File name\n // 30+n - Extra field\n\n buffer = newBuffer(fileNameLength + extraFieldLength);\n self.reader.ref();\n readAndAssertNoEof(self.reader, buffer, 0, buffer.length, entry.relativeOffsetOfLocalHeader + 30, function(err) {\n try {\n if (err) return callback(err);\n localFileHeader.fileName = buffer.subarray(0, fileNameLength);\n localFileHeader.extraField = buffer.subarray(fileNameLength);\n return callback(null, localFileHeader);\n } finally {\n self.reader.unref();\n }\n });\n } finally {\n self.reader.unref();\n }\n });\n};\n\nZipFile.prototype.openReadStreamPromise = function(entry, options) {\n return new Promise((resolve, reject) => {\n this.openReadStream(entry, options, function(err, readStream) {\n if (err) return reject(err);\n resolve(readStream);\n });\n });\n};\nZipFile.prototype.openReadStreamLowLevelPromise = function(fileDataStart, compressedSize, relativeStart, relativeEnd, decompress, uncompressedSize) {\n return new Promise((resolve, reject) => {\n this.openReadStream(fileDataStart, compressedSize, relativeStart, relativeEnd, decompress, uncompressedSize, function(err, readStream) {\n if (err) return reject(err);\n resolve(readStream);\n });\n });\n};\nZipFile.prototype.readLocalFileHeaderPromise = function(entry, options) {\n return new Promise((resolve, reject) => {\n this.readLocalFileHeader(entry, options, function(err, localFileHeader) {\n if (err) return reject(err);\n resolve(localFileHeader);\n });\n });\n};\n\nfunction Entry() {\n}\nEntry.prototype.getLastModDate = function(options) {\n if (options == null) options = {};\n\n if (!options.forceDosFormat) {\n // Check extended fields.\n for (var i = 0; i < this.extraFields.length; i++) {\n var extraField = this.extraFields[i];\n if (extraField.id === 0x5455) {\n // Info-ZIP \"universal timestamp\" extended field (`0x5455` aka `\"UT\"`).\n // See the Info-ZIP source code unix/unix.c:set_extra_field() and zipfile.c:ef_scan_ut_time().\n var data = extraField.data;\n if (data.length < 5) continue; // Too short.\n // The flags define which of the three fields are present: mtime, atime, ctime.\n // We only care about mtime.\n // Also, ctime is never included in practice.\n // And also, atime is only included in the local file header for some reason\n // despite the flags lying about its inclusion in the central header.\n var flags = data[0];\n var HAS_MTIME = 1;\n if (!(flags & HAS_MTIME)) continue; // This will realistically never happen.\n // Although the positions of all of the fields shift around depending on the presence of other fields,\n // mtime is always first if present, and that's the only one we care about.\n var posixTimestamp = data.readInt32LE(1);\n return new Date(posixTimestamp * 1000);\n } else if (extraField.id === 0x000a) {\n var data = extraField.data;\n if (data.length !== 32) continue; // The length is always the same.\n // 4 bytes reserved\n // 2 bytes Tag\n if (data.readUInt16LE(4) !== 1) continue; // Tag1 is actually the only defined Tag.\n // 2 bytes Size\n if (data.readUInt16LE(6) !== 24) continue; // Size is always 24.\n // 8 bytes Mtime\n var hundredNanoSecondsSince1601 = data.readUInt32LE(8) + 4294967296 * data.readInt32LE(12);\n // Convert from NTFS to POSIX milliseconds.\n // The big number below is the milliseconds between year 1601 and year 1970\n // (i.e. the negative POSIX timestamp of 1601-01-01 00:00:00Z)\n var millisecondsSince1970 = hundredNanoSecondsSince1601 / 10000 - 11644473600000;\n // Note on numeric precision: JavaScript Number objects lose precision above Number.MAX_SAFE_INTEGER,\n // and NTFS timestamps are typically much bigger than that limit.\n // (MAX_SAFE_INTEGER would represent 1629-07-17T23:58:45.475Z.)\n // However, we're losing precision in the conversion from 100nanosecond units to millisecond units anyway,\n // and the time at which we also lose 1-millisecond precision is year 275760, the JavaScript Date limit (by design).\n // Up through the year 2057, this conversion only drops 4 bits of precision,\n // which is well under the 13-14 bits ratio between the milliseconds and 100nanoseconds.\n return new Date(millisecondsSince1970);\n }\n }\n }\n\n // Fallback to non-extended encoding.\n return dosDateTimeToDate(this.lastModFileDate, this.lastModFileTime, options.timezone);\n};\n\nEntry.prototype.canDecodeFileData = function() {\n return (\n !this.isEncrypted() &&\n (this.compressionMethod === 0 || this.compressionMethod === 8)\n );\n};\nEntry.prototype.isEncrypted = function() {\n return (this.generalPurposeBitFlag & 0x1) !== 0;\n};\nEntry.prototype.isCompressed = function() {\n return this.compressionMethod === 8;\n};\n\nfunction LocalFileHeader() {\n}\n\nfunction dosDateTimeToDate(date, time, timezone) {\n var day = date & 0x1f; // 1-31\n var month = (date >> 5 & 0xf) - 1; // 1-12, 0-11\n var year = (date >> 9 & 0x7f) + 1980; // 0-128, 1980-2108\n\n var millisecond = 0;\n var second = (time & 0x1f) * 2; // 0-29, 0-58 (even numbers)\n var minute = time >> 5 & 0x3f; // 0-59\n var hour = time >> 11 & 0x1f; // 0-23\n\n if (timezone == null || timezone === \"local\") {\n return new Date(year, month, day, hour, minute, second, millisecond);\n } else if (timezone === \"UTC\") {\n return new Date(Date.UTC(year, month, day, hour, minute, second, millisecond));\n } else {\n throw new Error(\"unrecognized options.timezone: \" + options.timezone);\n }\n}\n\nfunction getFileNameLowLevel(generalPurposeBitFlag, fileNameBuffer, extraFields, strictFileNames) {\n var fileName = null;\n\n // check for Info-ZIP Unicode Path Extra Field (0x7075)\n // see https://github.com/thejoshwolfe/yauzl/issues/33\n for (var i = 0; i < extraFields.length; i++) {\n var extraField = extraFields[i];\n if (extraField.id === 0x7075) {\n if (extraField.data.length < 6) {\n // too short to be meaningful\n continue;\n }\n // Version 1 byte version of this extra field, currently 1\n if (extraField.data.readUInt8(0) !== 1) {\n // > Changes may not be backward compatible so this extra\n // > field should not be used if the version is not recognized.\n continue;\n }\n // NameCRC32 4 bytes File Name Field CRC32 Checksum\n var oldNameCrc32 = extraField.data.readUInt32LE(1);\n if (crc32(fileNameBuffer) !== oldNameCrc32) {\n // > If the CRC check fails, this UTF-8 Path Extra Field should be\n // > ignored and the File Name field in the header should be used instead.\n continue;\n }\n // UnicodeName Variable UTF-8 version of the entry File Name\n fileName = decodeBuffer(extraField.data.subarray(5), true);\n break;\n }\n }\n\n if (fileName == null) {\n // The typical case.\n var isUtf8 = (generalPurposeBitFlag & 0x800) !== 0;\n fileName = decodeBuffer(fileNameBuffer, isUtf8);\n }\n\n if (!strictFileNames) {\n // Allow backslash.\n fileName = fileName.replace(/\\\\/g, \"/\");\n }\n return fileName;\n}\n\nfunction validateFileName(fileName) {\n if (fileName.indexOf(\"\\\\\") !== -1) {\n return \"invalid characters in fileName: \" + fileName;\n }\n if (/^[a-zA-Z]:/.test(fileName) || /^\\//.test(fileName)) {\n return \"absolute path: \" + fileName;\n }\n if (fileName.split(\"/\").indexOf(\"..\") !== -1) {\n return \"invalid relative path: \" + fileName;\n }\n // all good\n return null;\n}\n\nfunction parseExtraFields(extraFieldBuffer) {\n var extraFields = [];\n var i = 0;\n while (i < extraFieldBuffer.length - 3) {\n var headerId = extraFieldBuffer.readUInt16LE(i + 0);\n var dataSize = extraFieldBuffer.readUInt16LE(i + 2);\n var dataStart = i + 4;\n var dataEnd = dataStart + dataSize;\n if (dataEnd > extraFieldBuffer.length) throw new Error(\"extra field length exceeds extra field buffer size\");\n var dataBuffer = extraFieldBuffer.subarray(dataStart, dataEnd);\n extraFields.push({\n id: headerId,\n data: dataBuffer,\n });\n i = dataEnd;\n }\n return extraFields;\n}\n\nfunction readAndAssertNoEof(reader, buffer, offset, length, position, callback) {\n if (length === 0) {\n // fs.read will throw an out-of-bounds error if you try to read 0 bytes from a 0 byte file\n return setImmediate(function() { callback(null, newBuffer(0)); });\n }\n reader.read(buffer, offset, length, position, function(err, bytesRead) {\n if (err) return callback(err);\n if (bytesRead < length) {\n return callback(new Error(\"unexpected EOF\"));\n }\n callback();\n });\n}\n\nutil.inherits(AssertByteCountStream, Transform);\nfunction AssertByteCountStream(byteCount) {\n Transform.call(this);\n this.actualByteCount = 0;\n this.expectedByteCount = byteCount;\n}\nAssertByteCountStream.prototype._transform = function(chunk, encoding, cb) {\n this.actualByteCount += chunk.length;\n if (this.actualByteCount > this.expectedByteCount) {\n var msg = \"too many bytes in the stream. expected \" + this.expectedByteCount + \". got at least \" + this.actualByteCount;\n return cb(new Error(msg));\n }\n cb(null, chunk);\n};\nAssertByteCountStream.prototype._flush = function(cb) {\n if (this.actualByteCount < this.expectedByteCount) {\n var msg = \"not enough bytes in the stream. expected \" + this.expectedByteCount + \". got only \" + this.actualByteCount;\n return cb(new Error(msg));\n }\n cb();\n};\n\nutil.inherits(RandomAccessReader, EventEmitter);\nfunction RandomAccessReader() {\n EventEmitter.call(this);\n this.refCount = 0;\n}\nRandomAccessReader.prototype.ref = function() {\n this.refCount += 1;\n};\nRandomAccessReader.prototype.unref = function() {\n var self = this;\n self.refCount -= 1;\n\n if (self.refCount > 0) return;\n if (self.refCount < 0) throw new Error(\"invalid unref\");\n\n self.close(onCloseDone);\n\n function onCloseDone(err) {\n if (err) return self.emit('error', err);\n self.emit('close');\n }\n};\nRandomAccessReader.prototype.createReadStream = function(options) {\n if (options == null) options = {};\n var start = options.start;\n var end = options.end;\n if (start === end) {\n var emptyStream = new PassThrough();\n setImmediate(function() {\n emptyStream.end();\n });\n return emptyStream;\n }\n var stream = this._readStreamForRange(start, end);\n\n var destroyed = false;\n var refUnrefFilter = new RefUnrefFilter(this);\n stream.on(\"error\", function(err) {\n setImmediate(function() {\n if (!destroyed) refUnrefFilter.emit(\"error\", err);\n });\n });\n installDestroyFn(refUnrefFilter, function() {\n stream.unpipe(refUnrefFilter);\n refUnrefFilter.unref();\n stream.destroy();\n });\n\n var byteCounter = new AssertByteCountStream(end - start);\n refUnrefFilter.on(\"error\", function(err) {\n setImmediate(function() {\n if (!destroyed) byteCounter.emit(\"error\", err);\n });\n });\n installDestroyFn(byteCounter, function() {\n destroyed = true;\n refUnrefFilter.unpipe(byteCounter);\n refUnrefFilter.destroy();\n });\n\n return stream.pipe(refUnrefFilter).pipe(byteCounter);\n};\nRandomAccessReader.prototype._readStreamForRange = function(start, end) {\n throw new Error(\"not implemented\");\n};\nRandomAccessReader.prototype.read = function(buffer, offset, length, position, callback) {\n var readStream = this.createReadStream({start: position, end: position + length});\n var writeStream = new Writable();\n var written = 0;\n writeStream._write = function(chunk, encoding, cb) {\n chunk.copy(buffer, offset + written, 0, chunk.length);\n written += chunk.length;\n cb();\n };\n writeStream.on(\"finish\", callback);\n readStream.on(\"error\", function(error) {\n callback(error);\n });\n readStream.pipe(writeStream);\n};\nRandomAccessReader.prototype.close = function(callback) {\n setImmediate(callback);\n};\n\nutil.inherits(RefUnrefFilter, PassThrough);\nfunction RefUnrefFilter(context) {\n PassThrough.call(this);\n this.context = context;\n this.context.ref();\n this.unreffedYet = false;\n}\nRefUnrefFilter.prototype._flush = function(cb) {\n this.unref();\n cb();\n};\nRefUnrefFilter.prototype.unref = function(cb) {\n if (this.unreffedYet) return;\n this.unreffedYet = true;\n this.context.unref();\n};\n\nvar cp437 = '\\u0000☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ';\nfunction decodeBuffer(buffer, isUtf8) {\n if (isUtf8) {\n return buffer.toString(\"utf8\");\n } else {\n var result = \"\";\n for (var i = 0; i < buffer.length; i++) {\n result += cp437[buffer[i]];\n }\n return result;\n }\n}\n\nfunction readUInt64LE(buffer, offset) {\n // There is no native function for this, because we can't actually store 64-bit integers precisely.\n // after 53 bits, JavaScript's Number type (IEEE 754 double) can't store individual integers anymore.\n // but since 53 bits is a whole lot more than 32 bits, we do our best anyway.\n // As of 2020, Node has added support for BigInt, which obviates this whole function,\n // but yauzl hasn't been updated to depend on BigInt (yet?).\n var lower32 = buffer.readUInt32LE(offset);\n var upper32 = buffer.readUInt32LE(offset + 4);\n // we can't use bitshifting here, because JavaScript bitshifting only works on 32-bit integers.\n return upper32 * 0x100000000 + lower32;\n // as long as we're bounds checking the result of this function against the total file size,\n // we'll catch any overflow errors, because we already made sure the total file size was within reason.\n}\n\n// Node 10 deprecated new Buffer().\nvar newBuffer;\nif (typeof Buffer.allocUnsafe === \"function\") {\n newBuffer = function(len) {\n return Buffer.allocUnsafe(len);\n };\n} else {\n newBuffer = function(len) {\n return new Buffer(len);\n };\n}\n\n// Node 8 introduced a proper destroy() implementation on writable streams.\nfunction installDestroyFn(stream, fn) {\n if (typeof stream.destroy === \"function\") {\n // New API.\n stream._destroy = function(err, cb) {\n fn();\n if (cb != null) cb(err);\n };\n } else {\n // Old API.\n stream.destroy = fn;\n }\n}\n\nfunction defaultCallback(err) {\n if (err) throw err;\n}\n","/**\n * 配置 Zod Schema 定义\n *\n * 同时服务于 TOML 文件校验和环境变量解析。\n */\n\nimport { z } from 'zod'\n\nexport const DEFAULT_API_PORT = 3110\n\nexport const llmConfigSchema = z.object({\n provider: z.string().default(''),\n model: z.string().default(''),\n base_url: z.string().default(''),\n})\n\nexport const dataConfigSchema = z.object({\n user_data_dir: z.string().default(''),\n electron_migration_done: z.boolean().default(false),\n})\n\nexport const apiConfigSchema = z.object({\n port: z.number().int().min(1).max(65535).default(DEFAULT_API_PORT),\n host: z.string().default('127.0.0.1'),\n token: z.string().default(''),\n require_auth: z.boolean().default(false),\n})\n\nexport const localeConfigSchema = z.object({\n lang: z.string().default(''),\n})\n\nexport const uiConfigSchema = z.object({\n default_session_tab: z.enum(['overview', 'ai-chat']).default('overview'),\n session_gap_threshold: z.number().int().min(60).max(86400).default(1800),\n summary_strategy: z.enum(['brief', 'standard']).default('standard'),\n})\n\nexport const configSchema = z.object({\n llm: llmConfigSchema.default({}),\n data: dataConfigSchema.default({}),\n api: apiConfigSchema.default({}),\n locale: localeConfigSchema.default({}),\n ui: uiConfigSchema.default({}),\n})\n\nexport type ChatLabConfig = z.infer<typeof configSchema>\nexport type LlmConfig = z.infer<typeof llmConfigSchema>\nexport type DataConfig = z.infer<typeof dataConfigSchema>\nexport type ApiConfig = z.infer<typeof apiConfigSchema>\nexport type LocaleConfig = z.infer<typeof localeConfigSchema>\nexport type UiConfig = z.infer<typeof uiConfigSchema>\n","/**\n * 配置加载器\n *\n * 优先级(从高到低):\n * 1. CHATLAB_* 环境变量\n * 2. ~/.chatlab/config.toml(或 config.json)\n * 3. Zod schema 默认值\n */\n\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport * as path from 'path'\nimport { parse as parseToml } from 'smol-toml'\nimport { configSchema, type ChatLabConfig } from './schema'\n\nconst CONFIG_DIR = path.join(os.homedir(), '.chatlab')\nconst CONFIG_TOML = path.join(CONFIG_DIR, 'config.toml')\nconst CONFIG_JSON = path.join(CONFIG_DIR, 'config.json')\n\n/**\n * 加载完整配置\n */\nexport function loadConfig(overrides?: Partial<ChatLabConfig>): ChatLabConfig {\n const fileConfig = loadConfigFile()\n const envConfig = loadEnvConfig()\n\n const merged = deepMerge(deepMerge(fileConfig, envConfig), overrides ?? {})\n return configSchema.parse(merged)\n}\n\n/**\n * 获取配置文件路径(返回实际存在的路径或默认 TOML 路径)\n */\nexport function getConfigPath(): string {\n if (fs.existsSync(CONFIG_TOML)) return CONFIG_TOML\n if (fs.existsSync(CONFIG_JSON)) return CONFIG_JSON\n return CONFIG_TOML\n}\n\n/**\n * 获取配置目录路径\n */\nexport function getConfigDir(): string {\n return CONFIG_DIR\n}\n\n/**\n * 从文件加载配置\n */\nfunction loadConfigFile(): Record<string, unknown> {\n if (fs.existsSync(CONFIG_TOML)) {\n try {\n const content = fs.readFileSync(CONFIG_TOML, 'utf-8')\n return parseToml(content) as Record<string, unknown>\n } catch (err) {\n console.warn(`[Config] Failed to parse ${CONFIG_TOML}:`, err)\n }\n }\n\n if (fs.existsSync(CONFIG_JSON)) {\n try {\n const content = fs.readFileSync(CONFIG_JSON, 'utf-8')\n return JSON.parse(content) as Record<string, unknown>\n } catch (err) {\n console.warn(`[Config] Failed to parse ${CONFIG_JSON}:`, err)\n }\n }\n\n return {}\n}\n\n/**\n * 从 CHATLAB_* 环境变量加载配置\n *\n * 映射规则:\n * - CHATLAB_DATA_DIR -> data.user_data_dir\n * - CHATLAB_API_PORT -> api.port\n * - CHATLAB_API_HOST -> api.host\n * - CHATLAB_LLM_PROVIDER -> llm.provider\n * - CHATLAB_LLM_MODEL -> llm.model\n * - CHATLAB_LLM_BASE_URL -> llm.base_url\n * - CHATLAB_LOCALE_LANG -> locale.lang\n */\nfunction loadEnvConfig(): Record<string, unknown> {\n const result: Record<string, Record<string, unknown>> = {}\n\n const envMap: Array<{ env: string; section: string; key: string; transform?: (v: string) => unknown }> = [\n { env: 'CHATLAB_DATA_DIR', section: 'data', key: 'user_data_dir' },\n { env: 'CHATLAB_API_PORT', section: 'api', key: 'port', transform: (v) => parseInt(v, 10) },\n { env: 'CHATLAB_API_HOST', section: 'api', key: 'host' },\n { env: 'CHATLAB_LLM_PROVIDER', section: 'llm', key: 'provider' },\n { env: 'CHATLAB_LLM_MODEL', section: 'llm', key: 'model' },\n { env: 'CHATLAB_LLM_BASE_URL', section: 'llm', key: 'base_url' },\n { env: 'CHATLAB_LOCALE_LANG', section: 'locale', key: 'lang' },\n ]\n\n for (const { env, section, key, transform } of envMap) {\n const value = process.env[env]\n if (value !== undefined && value !== '') {\n if (!result[section]) result[section] = {}\n result[section][key] = transform ? transform(value) : value\n }\n }\n\n return result\n}\n\n/**\n * 写入 config.toml 的某个字段\n *\n * 如果文件不存在则创建,如果已存在则保留其他内容,只更新指定字段。\n * 采用简单的 TOML 序列化:仅支持一级 section + 字符串/数字值。\n */\nexport function writeConfigField(section: string, key: string, value: string | number | boolean): void {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true })\n }\n\n let existing: Record<string, Record<string, unknown>> = {}\n if (fs.existsSync(CONFIG_TOML)) {\n try {\n const content = fs.readFileSync(CONFIG_TOML, 'utf-8')\n existing = parseToml(content) as Record<string, Record<string, unknown>>\n } catch {\n // 解析失败时从空开始,旧文件会被覆盖\n }\n }\n\n if (!existing[section] || typeof existing[section] !== 'object') {\n existing[section] = {}\n }\n existing[section][key] = value\n\n const lines: string[] = []\n for (const [sec, entries] of Object.entries(existing)) {\n if (typeof entries !== 'object' || entries === null) continue\n lines.push(`[${sec}]`)\n for (const [k, v] of Object.entries(entries as Record<string, unknown>)) {\n if (typeof v === 'string') {\n lines.push(`${k} = ${JSON.stringify(v)}`)\n } else if (typeof v === 'number' || typeof v === 'boolean') {\n lines.push(`${k} = ${v}`)\n }\n }\n lines.push('')\n }\n\n fs.writeFileSync(CONFIG_TOML, lines.join('\\n'), 'utf-8')\n}\n\nfunction deepMerge(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown> {\n const result = { ...base }\n for (const [key, value] of Object.entries(override)) {\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = { ...(result[key] as object), ...(value as object) }\n } else if (value !== undefined) {\n result[key] = value\n }\n }\n return result\n}\n","/**\n * auth-profiles.json 凭证管理\n *\n * 存储位置:~/.chatlab/auth-profiles.json\n * 用途:将 API Key 等敏感凭证与主配置文件分离\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { getConfigDir } from './loader'\n\nexport interface AuthProfile {\n type: 'api_key'\n provider: string\n key: string\n}\n\nexport interface AuthProfilesData {\n version: number\n profiles: Record<string, AuthProfile>\n}\n\nconst AUTH_PROFILES_FILE = 'auth-profiles.json'\n\nfunction getAuthProfilesPath(): string {\n return path.join(getConfigDir(), AUTH_PROFILES_FILE)\n}\n\n/**\n * 加载 auth-profiles.json\n */\nexport function loadAuthProfiles(): AuthProfilesData {\n const filePath = getAuthProfilesPath()\n if (!fs.existsSync(filePath)) {\n return { version: 1, profiles: {} }\n }\n\n try {\n const content = fs.readFileSync(filePath, 'utf-8')\n const data = JSON.parse(content) as AuthProfilesData\n if (!data.profiles || typeof data.profiles !== 'object') {\n return { version: 1, profiles: {} }\n }\n return data\n } catch {\n return { version: 1, profiles: {} }\n }\n}\n\n/**\n * 按 profile 名称获取 API Key\n */\nexport function getApiKeyByProfile(profileName: string): string {\n const data = loadAuthProfiles()\n const profile = data.profiles[profileName]\n return profile?.key || ''\n}\n\n/**\n * 按 provider 名称获取 API Key(模糊匹配,取第一个匹配的 profile)\n */\nexport function getApiKeyByProvider(provider: string): string {\n const data = loadAuthProfiles()\n for (const profile of Object.values(data.profiles)) {\n if (profile.provider === provider) {\n return profile.key || ''\n }\n }\n return ''\n}\n\n/**\n * 查找 API Key:先按 authProfile 精确匹配,再按 provider 兜底\n */\nexport function resolveApiKey(provider: string, authProfile?: string): string {\n if (authProfile) {\n const key = getApiKeyByProfile(authProfile)\n if (key) return key\n }\n return getApiKeyByProvider(provider)\n}\n\n/**\n * 写入/更新一个 auth profile\n */\nexport function writeAuthProfile(name: string, profile: AuthProfile): void {\n const configDir = getConfigDir()\n if (!fs.existsSync(configDir)) {\n fs.mkdirSync(configDir, { recursive: true })\n }\n\n const data = loadAuthProfiles()\n data.profiles[name] = profile\n\n const filePath = getAuthProfilesPath()\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), { encoding: 'utf-8', mode: 0o600 })\n}\n\n/**\n * 删除一个 auth profile\n */\nexport function deleteAuthProfile(name: string): boolean {\n const data = loadAuthProfiles()\n if (!(name in data.profiles)) return false\n\n delete data.profiles[name]\n const filePath = getAuthProfilesPath()\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), { encoding: 'utf-8', mode: 0o600 })\n return true\n}\n","/**\n * MigrationRunner\n *\n * 在应用启动时执行所有待执行的数据迁移。\n * 版本号存储在 ~/.chatlab/.migration-version 文件中。\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { Migration, MigrationContext } from './types'\n\nconst VERSION_FILE = '.migration-version'\n\nexport class MigrationRunner {\n private migrations: Migration[]\n private context: MigrationContext\n\n constructor(migrations: Migration[], context: MigrationContext) {\n this.migrations = [...migrations].sort((a, b) => a.version - b.version)\n this.context = context\n }\n\n getCurrentVersion(): number {\n const versionPath = path.join(this.context.dataDir, VERSION_FILE)\n try {\n if (fs.existsSync(versionPath)) {\n const raw = fs.readFileSync(versionPath, 'utf-8').trim()\n const ver = parseInt(raw, 10)\n return Number.isFinite(ver) ? ver : 0\n }\n } catch {\n // version file unreadable, treat as 0\n }\n return 0\n }\n\n private writeVersion(version: number): void {\n const versionPath = path.join(this.context.dataDir, VERSION_FILE)\n try {\n if (!fs.existsSync(this.context.dataDir)) {\n fs.mkdirSync(this.context.dataDir, { recursive: true })\n }\n fs.writeFileSync(versionPath, String(version), 'utf-8')\n } catch (err) {\n this.context.logger.error('Migration', `Failed to write version file: ${err}`)\n }\n }\n\n async run(): Promise<{ executed: number; currentVersion: number }> {\n const currentVersion = this.getCurrentVersion()\n const pending = this.migrations.filter((m) => m.version > currentVersion)\n\n if (pending.length === 0) {\n return { executed: 0, currentVersion }\n }\n\n this.context.logger.info('Migration', `${pending.length} pending migration(s), current version: ${currentVersion}`)\n\n let lastVersion = currentVersion\n let executed = 0\n\n for (const migration of pending) {\n this.context.logger.info('Migration', `Running: v${lastVersion}→v${migration.version} ${migration.name}`)\n try {\n await migration.up(this.context)\n lastVersion = migration.version\n this.writeVersion(lastVersion)\n executed++\n this.context.logger.info('Migration', `Completed: ${migration.name}`)\n } catch (err) {\n this.context.logger.error('Migration', `Failed: ${migration.name}`, err)\n break\n }\n }\n\n return { executed, currentVersion: lastVersion }\n }\n}\n","/**\n * Migration v0→v1: Legacy 单配置 → 多配置格式\n *\n * 旧格式:{ provider, apiKey, model, ... }(扁平对象)\n * 新格式:{ configs: [{ id, name, provider, apiKey, model, ... }], defaultAssistant, fastModel }\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { randomUUID } from 'crypto'\nimport type { Migration, MigrationContext } from './types'\n\ninterface LegacyFlatConfig {\n provider: string\n apiKey: string\n model?: string\n maxTokens?: number\n [key: string]: unknown\n}\n\nfunction isLegacyFlatConfig(data: unknown): data is LegacyFlatConfig {\n if (!data || typeof data !== 'object') return false\n const obj = data as Record<string, unknown>\n return 'provider' in obj && 'apiKey' in obj && !('configs' in obj)\n}\n\nexport const m001LegacyToMultiConfig: Migration = {\n version: 1,\n name: 'legacy-to-multi-config',\n description: 'Convert legacy flat LLM config to multi-config format',\n\n async up(ctx: MigrationContext) {\n const configPath = path.join(ctx.aiDataDir, 'llm-config.json')\n if (!fs.existsSync(configPath)) return\n\n const raw = fs.readFileSync(configPath, 'utf-8')\n let data: unknown\n try {\n data = JSON.parse(raw)\n } catch {\n return\n }\n\n if (!isLegacyFlatConfig(data)) return\n\n ctx.logger.info('Migration', 'Converting legacy flat config to multi-config format')\n\n const now = Date.now()\n const newConfig = {\n id: randomUUID(),\n name: data.provider,\n provider: data.provider,\n apiKey: data.apiKey || '',\n model: data.model || '',\n maxTokens: data.maxTokens,\n createdAt: now,\n updatedAt: now,\n }\n\n const migrated = {\n schemaVersion: 1,\n configs: [newConfig],\n defaultAssistant: { configId: newConfig.id, modelId: newConfig.model },\n fastModel: null,\n }\n\n fs.writeFileSync(configPath, JSON.stringify(migrated, null, 2), 'utf-8')\n ctx.logger.info('Migration', 'Legacy config migrated to multi-config format')\n },\n}\n","/**\n * Migration v1→v2: Schema v2 升级\n *\n * - 移除 configs 中的 disableThinking / isReasoningModel 字段\n * - 更新 schemaVersion 为 2\n *\n * 注意:原 Electron 版还包含自定义 provider/model 创建逻辑,\n * 该逻辑依赖 Electron-specific 模块,在共享 migration 中跳过。\n * Electron 端在 loadConfigStore 中已有回退处理。\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { Migration, MigrationContext } from './types'\n\nexport const m002SchemaV2: Migration = {\n version: 2,\n name: 'schema-v2',\n description: 'Upgrade LLM config to schema v2 (remove deprecated fields)',\n\n async up(ctx: MigrationContext) {\n const configPath = path.join(ctx.aiDataDir, 'llm-config.json')\n if (!fs.existsSync(configPath)) return\n\n let data: Record<string, unknown>\n try {\n data = JSON.parse(fs.readFileSync(configPath, 'utf-8'))\n } catch {\n return\n }\n\n const schemaVersion = (data.schemaVersion as number) || 0\n if (schemaVersion >= 2) return\n\n ctx.logger.info('Migration', 'Upgrading LLM config to schema v2')\n\n const configs = (data.configs as Record<string, unknown>[]) || []\n const cleanedConfigs = configs.map((c) => {\n const { disableThinking: _dt, isReasoningModel: _rm, ...rest } = c\n return rest\n })\n\n data.configs = cleanedConfigs\n data.schemaVersion = 2\n\n fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8')\n },\n}\n","/**\n * Migration v2→v3: Schema v3 升级 — 双 slot 模型选择\n *\n * - 将 activeConfigId → defaultAssistant { configId, modelId }\n * - 新增 fastModel slot(默认 null)\n * - 更新 schemaVersion 为 3\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { Migration, MigrationContext } from './types'\n\nexport const m003SchemaV3: Migration = {\n version: 3,\n name: 'schema-v3-dual-slot',\n description: 'Upgrade LLM config to schema v3 (dual-slot model selection)',\n\n async up(ctx: MigrationContext) {\n const configPath = path.join(ctx.aiDataDir, 'llm-config.json')\n if (!fs.existsSync(configPath)) return\n\n let data: Record<string, unknown>\n try {\n data = JSON.parse(fs.readFileSync(configPath, 'utf-8'))\n } catch {\n return\n }\n\n const schemaVersion = (data.schemaVersion as number) || 0\n if (schemaVersion >= 3) return\n\n ctx.logger.info('Migration', 'Upgrading LLM config to schema v3 (dual-slot)')\n\n const configs = (data.configs as Array<{ id: string; model?: string }>) || []\n const activeConfigId = data.activeConfigId as string | null | undefined\n\n const resolvedConfig =\n activeConfigId && configs.find((c) => c.id === activeConfigId)\n ? configs.find((c) => c.id === activeConfigId)!\n : (configs[0] ?? null)\n\n data.defaultAssistant = resolvedConfig ? { configId: resolvedConfig.id, modelId: resolvedConfig.model || '' } : null\n data.fastModel = data.fastModel ?? null\n data.schemaVersion = 3\n delete data.activeConfigId\n\n fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8')\n },\n}\n","/**\n * Migration v3→v4: 加密 API Key → auth-profiles.json\n *\n * - 从 llm-config.json 读取加密的 apiKey(enc: 前缀)\n * - 使用 device-key + legacy machine-id 尝试解密\n * - 解密成功的写入 auth-profiles.json\n * - 清空 llm-config.json 中的 apiKey 字段\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { Migration, MigrationContext } from './types'\nimport { isEncrypted, decryptApiKey } from './crypto-legacy'\nimport { writeAuthProfile } from '../auth-profiles'\n\n/**\n * Derive a human-readable profile name from config fields.\n * Official providers → provider id (e.g. \"deepseek\")\n * openai-compatible → hostname from baseUrl (e.g. \"api.example.com\") or config name\n */\nfunction deriveProfileName(provider: string, config: Record<string, unknown>): string {\n if (provider !== 'openai-compatible') {\n return provider.toLowerCase().replace(/\\s+/g, '-')\n }\n\n const baseUrl = config.baseUrl as string\n if (baseUrl) {\n try {\n return new URL(baseUrl).hostname.toLowerCase()\n } catch {\n // invalid URL, fall through\n }\n }\n\n const name = config.name as string\n if (name) return name.toLowerCase().replace(/\\s+/g, '-')\n\n return 'custom'\n}\n\nexport const m004EncryptedKeysToAuthProfiles: Migration = {\n version: 4,\n name: 'encrypted-keys-to-auth-profiles',\n description: 'Migrate encrypted API keys from llm-config.json to auth-profiles.json',\n\n async up(ctx: MigrationContext) {\n const configPath = path.join(ctx.aiDataDir, 'llm-config.json')\n if (!fs.existsSync(configPath)) return\n\n let data: Record<string, unknown>\n try {\n data = JSON.parse(fs.readFileSync(configPath, 'utf-8'))\n } catch {\n return\n }\n\n const configs = (data.configs as Array<Record<string, unknown>>) || []\n let migrated = false\n const usedProfileNames = new Set<string>()\n\n for (const config of configs) {\n const apiKey = config.apiKey as string\n if (!apiKey) continue\n\n const provider = (config.provider as string) || 'unknown'\n let plainKey: string | null = null\n\n if (isEncrypted(apiKey)) {\n plainKey = decryptApiKey(apiKey)\n if (!plainKey) {\n ctx.logger.warn('Migration', `Failed to decrypt API key for \"${config.name}\", skipping`)\n continue\n }\n } else if (apiKey.length > 0) {\n plainKey = apiKey\n }\n\n if (plainKey) {\n const baseName = deriveProfileName(provider, config)\n\n let profileName = baseName\n if (usedProfileNames.has(profileName)) {\n let i = 2\n while (usedProfileNames.has(`${profileName}-${i}`)) i++\n profileName = `${profileName}-${i}`\n }\n usedProfileNames.add(profileName)\n\n writeAuthProfile(profileName, { type: 'api_key', provider, key: plainKey })\n config.apiKey = ''\n migrated = true\n ctx.logger.info('Migration', `Migrated API key → profile \"${profileName}\"`)\n }\n }\n\n if (migrated) {\n fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8')\n ctx.logger.info('Migration', 'Cleared API keys from llm-config.json')\n }\n },\n}\n","/**\n * Legacy crypto 解密模块(仅用于迁移)\n *\n * 从 electron/main/ai/llm/crypto.ts + device-key.ts 提取,\n * 不依赖 Electron API,可在 CLI 和 Electron 中共用。\n */\n\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport * as path from 'path'\nimport { createDecipheriv, createHash } from 'crypto'\nimport { execSync } from 'child_process'\n\nconst ENCRYPTED_PREFIX = 'enc:'\nconst ALGORITHM = 'aes-256-gcm'\nconst SALT = 'chatlab-api-key-encryption-v1'\nconst DEVICE_KEY_FILE = '.device-key'\n\nexport function isEncrypted(value: string): boolean {\n return value?.startsWith(ENCRYPTED_PREFIX) ?? false\n}\n\nfunction readKeyFromFile(keyPath: string): string | null {\n try {\n if (fs.existsSync(keyPath)) {\n const key = fs.readFileSync(keyPath, 'utf-8').trim()\n if (key.length >= 32) return key\n }\n } catch {\n // ignore\n }\n return null\n}\n\nfunction getAllDeviceKeyPaths(): string[] {\n const home = os.homedir()\n const paths = [path.join(home, '.chatlab', DEVICE_KEY_FILE)]\n\n if (process.platform === 'darwin') {\n paths.push(path.join(home, 'Library', 'Application Support', 'ChatLab', 'data', DEVICE_KEY_FILE))\n } else if (process.platform === 'win32') {\n const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming')\n paths.push(path.join(appData, 'ChatLab', 'data', DEVICE_KEY_FILE))\n } else {\n paths.push(path.join(home, '.config', 'ChatLab', 'data', DEVICE_KEY_FILE))\n }\n\n return paths\n}\n\nfunction getAllDeviceKeys(): string[] {\n const keys: string[] = []\n const seen = new Set<string>()\n for (const p of getAllDeviceKeyPaths()) {\n const k = readKeyFromFile(p)\n if (k && !seen.has(k)) {\n seen.add(k)\n keys.push(k)\n }\n }\n return keys\n}\n\nfunction deriveLegacyKeys(): Buffer[] {\n const keys: Buffer[] = []\n try {\n let cmd: string | null = null\n if (process.platform === 'linux') {\n cmd = '( cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || hostname ) | head -n 1 || :'\n } else if (process.platform === 'darwin') {\n cmd = 'ioreg -rd1 -c IOPlatformExpertDevice'\n } else if (process.platform === 'win32') {\n cmd = 'REG.exe QUERY HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\Microsoft\\\\Cryptography /v MachineGuid'\n }\n\n if (cmd) {\n const raw = execSync(cmd).toString()\n let machineId: string\n if (process.platform === 'darwin') {\n machineId =\n raw\n .split('IOPlatformUUID')[1]\n ?.split('\\n')[0]\n ?.replace(/[=\\s\"]/g, '')\n ?.toLowerCase() || ''\n } else if (process.platform === 'win32') {\n machineId =\n raw\n .split('REG_SZ')[1]\n ?.replace(/[\\r\\n\\s]/g, '')\n ?.toLowerCase() || ''\n } else {\n machineId = raw.replace(/[\\r\\n\\s]/g, '').toLowerCase()\n }\n\n if (machineId) {\n const hashed = createHash('sha256').update(machineId).digest('hex')\n keys.push(\n createHash('sha256')\n .update(hashed + SALT)\n .digest()\n )\n }\n }\n } catch {\n // ignore\n }\n\n keys.push(\n createHash('sha256')\n .update('chatlab-fallback-key' + SALT)\n .digest()\n )\n return keys\n}\n\nfunction tryDecryptWithKey(encrypted: string, key: Buffer): string | null {\n try {\n const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(':')\n if (parts.length !== 3) return null\n\n const [ivBase64, authTagBase64, ciphertext] = parts\n const iv = Buffer.from(ivBase64, 'base64')\n const authTag = Buffer.from(authTagBase64, 'base64')\n\n const decipher = createDecipheriv(ALGORITHM, key, iv)\n decipher.setAuthTag(authTag)\n\n let decrypted = decipher.update(ciphertext, 'base64', 'utf8')\n decrypted += decipher.final('utf8')\n return decrypted\n } catch {\n return null\n }\n}\n\n/**\n * 解密旧加密 API Key,尝试所有可用的 device key 和 legacy machine-id 密钥\n */\nexport function decryptApiKey(encrypted: string): string {\n if (!encrypted || !isEncrypted(encrypted)) return encrypted || ''\n\n for (const dk of getAllDeviceKeys()) {\n const derived = createHash('sha256')\n .update(dk + SALT)\n .digest()\n const result = tryDecryptWithKey(encrypted, derived)\n if (result !== null) return result\n }\n\n for (const legacyKey of deriveLegacyKeys()) {\n const result = tryDecryptWithKey(encrypted, legacyKey)\n if (result !== null) return result\n }\n\n return ''\n}\n","/**\n * Migration Runner 导出\n *\n * ALL_MIGRATIONS 按版本号排列,由 MigrationRunner 在应用启动时执行。\n */\n\nexport { MigrationRunner } from './runner'\nexport type { Migration, MigrationContext, Logger } from './types'\n\nimport { m001LegacyToMultiConfig } from './m001-legacy-to-multi-config'\nimport { m002SchemaV2 } from './m002-schema-v2'\nimport { m003SchemaV3 } from './m003-schema-v3'\nimport { m004EncryptedKeysToAuthProfiles } from './m004-encrypted-keys-to-auth-profiles'\nimport type { Migration } from './types'\n\nexport const ALL_MIGRATIONS: Migration[] = [\n m001LegacyToMultiConfig,\n m002SchemaV2,\n m003SchemaV3,\n m004EncryptedKeysToAuthProfiles,\n]\n","/**\n * DatabaseAdapter 的 better-sqlite3 实现\n *\n * 薄包装层:better-sqlite3 的 API 与 DatabaseAdapter 接口天然匹配,\n * 本适配器主要做类型桥接,几乎零额外逻辑。\n */\n\nimport Database from 'better-sqlite3'\nimport type { DatabaseAdapter, PreparedStatement, RunResult } from '@openchatlab/core'\n\nclass BetterSqlitePreparedStatement implements PreparedStatement {\n readonly?: boolean\n\n constructor(private stmt: Database.Statement) {\n this.readonly = stmt.readonly\n }\n\n get(...params: unknown[]): Record<string, unknown> | undefined {\n return this.stmt.get(...params) as Record<string, unknown> | undefined\n }\n\n all(...params: unknown[]): Record<string, unknown>[] {\n return this.stmt.all(...params) as Record<string, unknown>[]\n }\n\n run(...params: unknown[]): RunResult {\n const result = this.stmt.run(...params)\n return {\n changes: result.changes,\n lastInsertRowid: result.lastInsertRowid,\n }\n }\n}\n\n/**\n * 基于 better-sqlite3 的 DatabaseAdapter 实现\n */\nexport class BetterSqliteAdapter implements DatabaseAdapter {\n readonly?: boolean\n\n constructor(private db: Database.Database) {\n this.readonly = db.readonly\n }\n\n exec(sql: string): void {\n this.db.exec(sql)\n }\n\n prepare(sql: string): PreparedStatement {\n return new BetterSqlitePreparedStatement(this.db.prepare(sql))\n }\n\n transaction<T>(fn: () => T): T {\n return this.db.transaction(fn)()\n }\n\n pragma(pragma: string): unknown {\n return this.db.pragma(pragma)\n }\n\n close(): void {\n this.db.close()\n }\n}\n\n/**\n * 从文件路径打开数据库并返回适配器\n *\n * @param options.nativeBinding - 指定 better-sqlite3 原生模块路径,\n * 用于在独立 Node.js 环境中加载与 Electron 隔离的二进制。\n */\nexport function openBetterSqliteDatabase(\n filePath: string,\n options?: { readonly?: boolean; nativeBinding?: string }\n): BetterSqliteAdapter {\n const db = new Database(filePath, {\n readonly: options?.readonly ?? false,\n nativeBinding: options?.nativeBinding,\n })\n if (!options?.readonly) {\n db.pragma('journal_mode = WAL')\n }\n return new BetterSqliteAdapter(db)\n}\n","/**\n * Shared data writing logic for importing parsed chat data into SQLite.\n *\n * Extracted from electron/main/database/core.ts importData().\n * Used by both Electron and Server import pipelines.\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport type { ParsedMember, ParsedMessage } from '@openchatlab/shared-types'\n\nexport interface ImportMeta {\n name: string\n platform: string\n type: string\n groupId?: string | null\n groupAvatar?: string | null\n ownerId?: string | null\n}\n\nexport interface WriteParseResultStats {\n messageCount: number\n memberCount: number\n skippedCount: number\n}\n\n/**\n * Write parsed chat data into a database that already has the schema initialized.\n *\n * Handles:\n * - Meta record insertion (with schema_version and imported_at)\n * - Member insertion with dedup (INSERT OR IGNORE)\n * - Messages sorted by timestamp, inserted with sender_id FK\n * - member_name_history tracking (account_name & group_nickname changes over time)\n * - Final member name update to latest seen values\n *\n * @param db - Database with CHAT_DB_SCHEMA already applied\n * @param meta - Chat session metadata\n * @param members - Parsed member list\n * @param messages - Parsed message list (will be sorted by timestamp internally)\n */\nexport function writeParseResultToDb(\n db: DatabaseAdapter,\n meta: ImportMeta,\n members: readonly ParsedMember[],\n messages: readonly ParsedMessage[]\n): WriteParseResultStats {\n let messageCount = 0\n let skippedCount = 0\n\n db.transaction(() => {\n db.prepare(\n `INSERT INTO meta (name, platform, type, imported_at, group_id, group_avatar, owner_id)\n VALUES (?, ?, ?, ?, ?, ?, ?)`\n ).run(\n meta.name,\n meta.platform,\n meta.type,\n Math.floor(Date.now() / 1000),\n meta.groupId || null,\n meta.groupAvatar || null,\n meta.ownerId || null\n )\n\n const insertMember = db.prepare(\n `INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, avatar, roles) VALUES (?, ?, ?, ?, ?)`\n )\n const getMemberId = db.prepare(`SELECT id FROM member WHERE platform_id = ?`)\n\n const memberIdMap = new Map<string, number>()\n\n for (const member of members) {\n insertMember.run(\n member.platformId,\n member.accountName || null,\n member.groupNickname || null,\n member.avatar || null,\n member.roles ? JSON.stringify(member.roles) : '[]'\n )\n const row = getMemberId.get(member.platformId) as { id: number }\n memberIdMap.set(member.platformId, row.id)\n }\n\n const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp)\n\n const accountNameTracker = new Map<string, { currentName: string; lastSeenTs: number }>()\n const groupNicknameTracker = new Map<string, { currentName: string; lastSeenTs: number }>()\n\n const insertMessage = db.prepare(\n `INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n )\n const insertNameHistory = db.prepare(\n `INSERT INTO member_name_history (member_id, name_type, name, start_ts, end_ts) VALUES (?, ?, ?, ?, ?)`\n )\n const updateMemberAccountName = db.prepare(`UPDATE member SET account_name = ? WHERE platform_id = ?`)\n const updateMemberGroupNickname = db.prepare(`UPDATE member SET group_nickname = ? WHERE platform_id = ?`)\n const updateNameHistoryEndTs = db.prepare(\n `UPDATE member_name_history SET end_ts = ? WHERE member_id = ? AND name_type = ? AND end_ts IS NULL`\n )\n\n for (const msg of sortedMessages) {\n const senderId = memberIdMap.get(msg.senderPlatformId)\n if (senderId === undefined) {\n skippedCount++\n continue\n }\n\n insertMessage.run(\n senderId,\n msg.senderAccountName || null,\n msg.senderGroupNickname || null,\n msg.timestamp,\n msg.type,\n msg.content,\n msg.replyToMessageId || null,\n msg.platformMessageId || null\n )\n messageCount++\n\n const accountName = msg.senderAccountName\n if (accountName) {\n const tracker = accountNameTracker.get(msg.senderPlatformId)\n if (!tracker) {\n accountNameTracker.set(msg.senderPlatformId, {\n currentName: accountName,\n lastSeenTs: msg.timestamp,\n })\n insertNameHistory.run(senderId, 'account_name', accountName, msg.timestamp, null)\n } else if (tracker.currentName !== accountName) {\n updateNameHistoryEndTs.run(msg.timestamp, senderId, 'account_name')\n insertNameHistory.run(senderId, 'account_name', accountName, msg.timestamp, null)\n tracker.currentName = accountName\n tracker.lastSeenTs = msg.timestamp\n } else {\n tracker.lastSeenTs = msg.timestamp\n }\n }\n\n const groupNickname = msg.senderGroupNickname\n if (groupNickname) {\n const tracker = groupNicknameTracker.get(msg.senderPlatformId)\n if (!tracker) {\n groupNicknameTracker.set(msg.senderPlatformId, {\n currentName: groupNickname,\n lastSeenTs: msg.timestamp,\n })\n insertNameHistory.run(senderId, 'group_nickname', groupNickname, msg.timestamp, null)\n } else if (tracker.currentName !== groupNickname) {\n updateNameHistoryEndTs.run(msg.timestamp, senderId, 'group_nickname')\n insertNameHistory.run(senderId, 'group_nickname', groupNickname, msg.timestamp, null)\n tracker.currentName = groupNickname\n tracker.lastSeenTs = msg.timestamp\n } else {\n tracker.lastSeenTs = msg.timestamp\n }\n }\n }\n\n for (const [platformId, tracker] of accountNameTracker.entries()) {\n updateMemberAccountName.run(tracker.currentName, platformId)\n }\n for (const [platformId, tracker] of groupNicknameTracker.entries()) {\n updateMemberGroupNickname.run(tracker.currentName, platformId)\n }\n })\n\n return { messageCount, memberCount: members.length, skippedCount }\n}\n","/**\n * Import performance logger.\n *\n * Extracted from electron/main/worker/core/perfLogger.ts.\n * Records real-time performance metrics during import operations.\n *\n * Caller provides the log directory; this module has no path assumptions.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nexport enum LogLevel {\n ERROR = 'ERROR',\n INFO = 'INFO',\n}\n\nlet lastLogTime = Date.now()\nlet lastMessageCount = 0\nlet currentLogFile: string | null = null\nlet errorCount = 0\n\nexport function initPerfLog(sessionId: string, logDir: string): void {\n try {\n if (!fs.existsSync(logDir)) {\n fs.mkdirSync(logDir, { recursive: true })\n }\n currentLogFile = path.join(logDir, `import_${sessionId}_${Date.now()}.log`)\n fs.writeFileSync(currentLogFile, `=== Import Log ===\\nStart time: ${new Date().toISOString()}\\n\\n`, 'utf-8')\n } catch {\n // Ignore initialization failure\n }\n}\n\nexport function logPerf(event: string, messagesProcessed: number, batchSize?: number): void {\n const now = Date.now()\n const duration = now - lastLogTime\n const messagesDelta = messagesProcessed - lastMessageCount\n const speed = duration > 0 ? Math.round((messagesDelta / duration) * 1000) : 0\n\n let memory = 0\n try {\n const used = process.memoryUsage()\n memory = Math.round(used.heapUsed / 1024 / 1024)\n } catch {\n // Ignore\n }\n\n const logLine =\n `[${new Date().toISOString()}] ${event} | ` +\n `messages: ${messagesProcessed.toLocaleString()} | ` +\n `elapsed: ${duration}ms | ` +\n `speed: ${speed.toLocaleString()}/s | ` +\n `memory: ${memory}MB` +\n (batchSize ? ` | batch: ${batchSize}` : '') +\n '\\n'\n\n if (currentLogFile) {\n try {\n fs.appendFileSync(currentLogFile, logLine, 'utf-8')\n } catch {\n // Ignore write failure\n }\n }\n\n lastLogTime = now\n lastMessageCount = messagesProcessed\n}\n\nexport function logPerfDetail(detail: string): void {\n if (currentLogFile) {\n try {\n fs.appendFileSync(currentLogFile, ` ${detail}\\n`, 'utf-8')\n } catch {\n // Ignore\n }\n }\n}\n\nexport function resetPerfLog(): void {\n lastLogTime = Date.now()\n lastMessageCount = 0\n currentLogFile = null\n errorCount = 0\n}\n\nexport function getCurrentLogFile(): string | null {\n return currentLogFile\n}\n\nfunction writeLogLine(level: LogLevel, message: string): void {\n if (!currentLogFile) return\n\n const logLine = `[${new Date().toISOString()}] [${level}] ${message}\\n`\n try {\n fs.appendFileSync(currentLogFile, logLine, 'utf-8')\n } catch {\n // Ignore write failure\n }\n}\n\nexport function logError(message: string, error?: Error): void {\n errorCount++\n const errorDetail = error ? `: ${error.message}` : ''\n writeLogLine(LogLevel.ERROR, `${message}${errorDetail}`)\n}\n\nexport function logInfo(message: string): void {\n writeLogLine(LogLevel.INFO, message)\n}\n\nexport function getErrorCount(): number {\n return errorCount\n}\n\nexport function logSummary(totalMessages: number, totalMembers: number): void {\n if (!currentLogFile) return\n\n const summary = `\n=== Import Summary ===\nEnd time: ${new Date().toISOString()}\nTotal messages: ${totalMessages.toLocaleString()}\nTotal members: ${totalMembers.toLocaleString()}\nErrors: ${errorCount}\n`\n try {\n fs.appendFileSync(currentLogFile, summary, 'utf-8')\n } catch {\n // Ignore\n }\n}\n","/**\n * SQL 查询过滤条件构建工具\n *\n * 平台无关的 WHERE 子句构建器和 schema 检测工具,供所有查询模块复用。\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../interfaces'\n\n/**\n * 构建时间过滤 WHERE 子句\n * @param filter 时间过滤器(包含时间范围和成员筛选)\n * @param tableAlias 表别名,用于多表 JOIN 场景避免列名歧义(如 'msg')\n */\nexport function buildTimeFilter(\n filter?: TimeFilter,\n tableAlias?: string\n): { clause: string; params: (number | string)[] } {\n const conditions: string[] = []\n const params: (number | string)[] = []\n\n const tsColumn = tableAlias ? `${tableAlias}.ts` : 'ts'\n const senderIdColumn = tableAlias ? `${tableAlias}.sender_id` : 'sender_id'\n\n if (filter?.startTs !== undefined) {\n conditions.push(`${tsColumn} >= ?`)\n params.push(filter.startTs)\n }\n if (filter?.endTs !== undefined) {\n conditions.push(`${tsColumn} <= ?`)\n params.push(filter.endTs)\n }\n if (filter?.memberId !== undefined && filter?.memberId !== null) {\n conditions.push(`${senderIdColumn} = ?`)\n params.push(filter.memberId)\n }\n\n return {\n clause: conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '',\n params,\n }\n}\n\n/**\n * Check if a table exists in the database\n */\nexport function hasTable(db: DatabaseAdapter, tableName: string): boolean {\n const row = db.prepare(\"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?\").get(tableName)\n return row !== undefined\n}\n\n/**\n * Check if a column exists in a table\n */\nexport function hasColumn(db: DatabaseAdapter, tableName: string, columnName: string): boolean {\n const rows = db.pragma(`table_info(${tableName})`) as Array<{ name: string }>\n if (!Array.isArray(rows)) return false\n return rows.some((r) => r.name === columnName)\n}\n\n/**\n * 构建排除系统消息的过滤条件\n */\nexport function buildSystemMessageFilter(existingClause: string): string {\n const systemFilter = \"COALESCE(m.account_name, '') != '系统消息'\"\n\n if (existingClause.includes('WHERE')) {\n return existingClause + ' AND ' + systemFilter\n } else {\n return ' WHERE ' + systemFilter\n }\n}\n","/**\n * 基础统计查询模块(平台无关)\n *\n * 提供活跃度排行、时段分布等核心统计查询。\n * 所有函数接收 DatabaseAdapter 参数,不依赖全局状态。\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../interfaces'\nimport { buildTimeFilter, buildSystemMessageFilter } from './filters'\n\nexport interface MemberActivity {\n memberId: number\n platformId: string\n name: string\n avatar: string | null\n messageCount: number\n percentage: number\n}\n\nexport interface HourlyActivity {\n hour: number\n messageCount: number\n}\n\nexport interface DailyActivity {\n date: string\n messageCount: number\n}\n\nexport interface WeekdayActivity {\n weekday: number\n messageCount: number\n}\n\nexport interface MonthlyActivity {\n month: number\n messageCount: number\n}\n\nexport interface YearlyActivity {\n year: number\n messageCount: number\n}\n\nexport interface MessageLengthDistribution {\n detail: Array<{ len: number; count: number }>\n grouped: Array<{ range: string; count: number }>\n}\n\nexport interface MessageTypeStats {\n type: number\n count: number\n}\n\nexport interface TextStats {\n textCount: number\n avgLength: number\n maxLength: number\n shortCount: number\n}\n\nexport interface TextLengthPercentiles {\n p25: number\n p50: number\n p75: number\n p90: number\n}\n\nexport interface MemberMonthlyTrend {\n month: string\n memberId: number\n memberName: string\n count: number\n}\n\n/**\n * 获取消息时间范围\n */\nexport function getTimeRange(db: DatabaseAdapter): { start: number; end: number } | null {\n const row = db.prepare('SELECT MIN(ts) as start, MAX(ts) as end FROM message').get() as\n | { start: number | null; end: number | null }\n | undefined\n if (!row || row.start == null || row.end == null) return null\n return { start: row.start, end: row.end }\n}\n\n/**\n * 获取可用的年份列表\n */\nexport function getAvailableYears(db: DatabaseAdapter): number[] {\n const rows = db\n .prepare(\n `SELECT DISTINCT CAST(strftime('%Y', ts, 'unixepoch', 'localtime') AS INTEGER) as year\n FROM message\n ORDER BY year DESC`\n )\n .all() as Array<{ year: number }>\n\n return rows.map((r) => r.year)\n}\n\n/**\n * 获取成员活跃度排行\n */\nexport function getMemberActivity(db: DatabaseAdapter, filter?: TimeFilter): MemberActivity[] {\n const { clause, params } = buildTimeFilter(filter)\n\n const msgFilterBase = clause ? clause.replace('WHERE', 'AND') : ''\n const msgFilterWithSystem = msgFilterBase + \" AND COALESCE(m.account_name, '') != '系统消息'\"\n\n const totalClauseWithSystem = buildSystemMessageFilter(clause)\n const totalMessages = (\n db\n .prepare(\n `SELECT COUNT(*) as count\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${totalClauseWithSystem}`\n )\n .get(...params) as { count: number }\n ).count\n\n const rows = db\n .prepare(\n `SELECT\n m.id as memberId,\n m.platform_id as platformId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,\n m.avatar as avatar,\n COUNT(msg.id) as messageCount\n FROM member m\n LEFT JOIN message msg ON m.id = msg.sender_id ${msgFilterWithSystem}\n WHERE COALESCE(m.account_name, '') != '系统消息'\n GROUP BY m.id\n HAVING messageCount > 0\n ORDER BY messageCount DESC`\n )\n .all(...params) as Array<{\n memberId: number\n platformId: string\n name: string\n avatar: string | null\n messageCount: number\n }>\n\n return rows.map((row) => ({\n ...row,\n percentage: totalMessages > 0 ? Math.round((row.messageCount / totalMessages) * 10000) / 100 : 0,\n }))\n}\n\n/**\n * 获取每小时活跃度分布\n */\nexport function getHourlyActivity(db: DatabaseAdapter, filter?: TimeFilter): HourlyActivity[] {\n const { clause, params } = buildTimeFilter(filter)\n const clauseWithSystem = buildSystemMessageFilter(clause)\n\n const rows = db\n .prepare(\n `SELECT\n CAST(strftime('%H', msg.ts, 'unixepoch', 'localtime') AS INTEGER) as hour,\n COUNT(*) as messageCount\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${clauseWithSystem}\n GROUP BY hour\n ORDER BY hour`\n )\n .all(...params) as Array<{ hour: number; messageCount: number }>\n\n const result: HourlyActivity[] = []\n for (let h = 0; h < 24; h++) {\n const found = rows.find((r) => r.hour === h)\n result.push({ hour: h, messageCount: found ? found.messageCount : 0 })\n }\n return result\n}\n\n/**\n * 获取每日活跃度趋势\n */\nexport function getDailyActivity(db: DatabaseAdapter, filter?: TimeFilter): DailyActivity[] {\n const { clause, params } = buildTimeFilter(filter)\n const clauseWithSystem = buildSystemMessageFilter(clause)\n\n return db\n .prepare(\n `SELECT\n strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime') as date,\n COUNT(*) as messageCount\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${clauseWithSystem}\n GROUP BY date\n ORDER BY date`\n )\n .all(...params) as unknown as DailyActivity[]\n}\n\n/**\n * 获取星期活跃度分布\n */\nexport function getWeekdayActivity(db: DatabaseAdapter, filter?: TimeFilter): WeekdayActivity[] {\n const { clause, params } = buildTimeFilter(filter)\n const clauseWithSystem = buildSystemMessageFilter(clause)\n\n const rows = db\n .prepare(\n `SELECT\n CASE\n WHEN CAST(strftime('%w', msg.ts, 'unixepoch', 'localtime') AS INTEGER) = 0 THEN 7\n ELSE CAST(strftime('%w', msg.ts, 'unixepoch', 'localtime') AS INTEGER)\n END as weekday,\n COUNT(*) as messageCount\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${clauseWithSystem}\n GROUP BY weekday\n ORDER BY weekday`\n )\n .all(...params) as Array<{ weekday: number; messageCount: number }>\n\n const result: WeekdayActivity[] = []\n for (let w = 1; w <= 7; w++) {\n const found = rows.find((r) => r.weekday === w)\n result.push({ weekday: w, messageCount: found ? found.messageCount : 0 })\n }\n return result\n}\n\n/**\n * 获取消息类型分布\n */\nexport function getMessageTypeStats(db: DatabaseAdapter, filter?: TimeFilter): MessageTypeStats[] {\n const { clause, params } = buildTimeFilter(filter)\n const clauseWithSystem = buildSystemMessageFilter(clause)\n\n return db\n .prepare(\n `SELECT\n msg.type as type,\n COUNT(*) as count\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${clauseWithSystem}\n GROUP BY msg.type\n ORDER BY count DESC`\n )\n .all(...params) as unknown as MessageTypeStats[]\n}\n\n/**\n * 获取月份活跃度分布\n */\nexport function getMonthlyActivity(db: DatabaseAdapter, filter?: TimeFilter): MonthlyActivity[] {\n const { clause, params } = buildTimeFilter(filter)\n const clauseWithSystem = buildSystemMessageFilter(clause)\n\n const rows = db\n .prepare(\n `SELECT\n CAST(strftime('%m', msg.ts, 'unixepoch', 'localtime') AS INTEGER) as month,\n COUNT(*) as messageCount\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${clauseWithSystem}\n GROUP BY month\n ORDER BY month`\n )\n .all(...params) as Array<{ month: number; messageCount: number }>\n\n const result: MonthlyActivity[] = []\n for (let m = 1; m <= 12; m++) {\n const found = rows.find((r) => r.month === m)\n result.push({ month: m, messageCount: found ? found.messageCount : 0 })\n }\n return result\n}\n\n/**\n * 获取年份活跃度分布\n */\nexport function getYearlyActivity(db: DatabaseAdapter, filter?: TimeFilter): YearlyActivity[] {\n const { clause, params } = buildTimeFilter(filter)\n const clauseWithSystem = buildSystemMessageFilter(clause)\n\n return db\n .prepare(\n `SELECT\n CAST(strftime('%Y', msg.ts, 'unixepoch', 'localtime') AS INTEGER) as year,\n COUNT(*) as messageCount\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${clauseWithSystem}\n GROUP BY year\n ORDER BY year`\n )\n .all(...params) as unknown as YearlyActivity[]\n}\n\n/**\n * 获取消息长度分布(仅统计文字消息 type=0)\n */\nexport function getMessageLengthDistribution(db: DatabaseAdapter, filter?: TimeFilter): MessageLengthDistribution {\n const { clause, params } = buildTimeFilter(filter)\n const clauseWithSystem = buildSystemMessageFilter(clause)\n\n const typeCondition = clauseWithSystem\n ? clauseWithSystem + ' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(msg.content) > 0'\n : 'WHERE msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(msg.content) > 0'\n\n const rows = db\n .prepare(\n `SELECT LENGTH(msg.content) as len, COUNT(*) as count\n FROM message msg JOIN member m ON msg.sender_id = m.id\n ${typeCondition}\n GROUP BY len ORDER BY len`\n )\n .all(...params) as Array<{ len: number; count: number }>\n\n const detail: Array<{ len: number; count: number }> = []\n for (let i = 1; i <= 25; i++) {\n const found = rows.find((r) => r.len === i)\n detail.push({ len: i, count: found ? found.count : 0 })\n }\n\n const ranges = [\n { min: 1, max: 5, label: '1-5' },\n { min: 6, max: 10, label: '6-10' },\n { min: 11, max: 15, label: '11-15' },\n { min: 16, max: 20, label: '16-20' },\n { min: 21, max: 25, label: '21-25' },\n { min: 26, max: 30, label: '26-30' },\n { min: 31, max: 35, label: '31-35' },\n { min: 36, max: 40, label: '36-40' },\n { min: 41, max: 45, label: '41-45' },\n { min: 46, max: 50, label: '46-50' },\n { min: 51, max: 60, label: '51-60' },\n { min: 61, max: 70, label: '61-70' },\n { min: 71, max: 80, label: '71-80' },\n { min: 81, max: 100, label: '81-100' },\n { min: 101, max: Infinity, label: '100+' },\n ]\n\n const grouped: Array<{ range: string; count: number }> = ranges.map((r) => ({\n range: r.label,\n count: rows.filter((row) => row.len >= r.min && row.len <= r.max).reduce((sum, row) => sum + row.count, 0),\n }))\n\n return { detail, grouped }\n}\n\n/**\n * 获取文字消息统计(仅 type=0):数量、平均长度、最大长度、短消息(≤5 字)数\n */\nexport function getTextStats(db: DatabaseAdapter, filter?: TimeFilter): TextStats {\n const { clause, params } = buildTimeFilter(filter)\n const typeCondition =\n buildSystemMessageFilter(clause) + ' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(msg.content) > 0'\n\n const row = db\n .prepare(\n `SELECT\n COUNT(*) as textCount,\n ROUND(AVG(LENGTH(msg.content)), 1) as avgLength,\n MAX(LENGTH(msg.content)) as maxLength,\n SUM(CASE WHEN LENGTH(msg.content) <= 5 THEN 1 ELSE 0 END) as shortCount\n FROM message msg JOIN member m ON msg.sender_id = m.id\n ${typeCondition}`\n )\n .get(...params) as\n | { textCount: number; avgLength: number | null; maxLength: number | null; shortCount: number | null }\n | undefined\n\n return {\n textCount: row?.textCount ?? 0,\n avgLength: row?.avgLength ?? 0,\n maxLength: row?.maxLength ?? 0,\n shortCount: row?.shortCount ?? 0,\n }\n}\n\n/**\n * 获取长消息(小作文)数量,minLength 为字符阈值(默认 30),仅统计文字消息\n */\nexport function getLongMessageCount(db: DatabaseAdapter, filter?: TimeFilter, minLength = 30): number {\n const { clause, params } = buildTimeFilter(filter)\n const typeCondition =\n buildSystemMessageFilter(clause) + ' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(msg.content) >= ?'\n\n const row = db\n .prepare(\n `SELECT COUNT(*) as cnt\n FROM message msg JOIN member m ON msg.sender_id = m.id\n ${typeCondition}`\n )\n .get(...params, minLength) as { cnt: number } | undefined\n\n return row?.cnt ?? 0\n}\n\n/**\n * 获取成员月度消息趋势(按月 × 发送者)\n */\nexport function getMemberMonthlyTrend(db: DatabaseAdapter, filter?: TimeFilter): MemberMonthlyTrend[] {\n const { clause, params } = buildTimeFilter(filter)\n const clauseWithSystem = buildSystemMessageFilter(clause)\n\n return db\n .prepare(\n `SELECT\n strftime('%Y-%m', msg.ts, 'unixepoch', 'localtime') as month,\n msg.sender_id as memberId,\n m.account_name as memberName,\n COUNT(*) as count\n FROM message msg JOIN member m ON msg.sender_id = m.id\n ${clauseWithSystem}\n GROUP BY month, msg.sender_id\n ORDER BY month`\n )\n .all(...params) as unknown as MemberMonthlyTrend[]\n}\n\n/**\n * 获取文字消息长度百分位(P25/P50/P75/P90),仅统计文字消息\n */\nexport function getTextLengthPercentiles(db: DatabaseAdapter, filter?: TimeFilter): TextLengthPercentiles {\n const { clause, params } = buildTimeFilter(filter)\n const typeCondition =\n buildSystemMessageFilter(clause) + ' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(msg.content) > 0'\n\n const rows = db\n .prepare(\n `SELECT LENGTH(msg.content) as len\n FROM message msg JOIN member m ON msg.sender_id = m.id\n ${typeCondition}\n ORDER BY len`\n )\n .all(...params) as Array<{ len: number }>\n\n if (rows.length === 0) return { p25: 0, p50: 0, p75: 0, p90: 0 }\n\n const lengths = rows.map((r) => r.len)\n const getPercentile = (arr: number[], p: number) => {\n const idx = Math.ceil((p / 100) * arr.length) - 1\n return arr[Math.max(0, idx)]\n }\n\n return {\n p25: getPercentile(lengths, 25),\n p50: getPercentile(lengths, 50),\n p75: getPercentile(lengths, 75),\n p90: getPercentile(lengths, 90),\n }\n}\n","/**\n * 会话查询模块(平台无关)\n *\n * 纯 SQL 查询函数,接收 DatabaseAdapter 参数,不依赖全局状态。\n * 这些函数是 CLI/MCP/HTTP API 查询会话数据的基础。\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../interfaces'\nimport { hasTable } from './filters'\nimport { getMemberActivity } from './basic-queries'\n\nexport interface SessionMeta {\n name: string\n platform: string\n type: string\n importedAt: number\n groupId: string | null\n groupAvatar: string | null\n ownerId: string | null\n}\n\nexport interface SessionOverview {\n totalMessages: number\n totalMembers: number\n firstMessageTs: number | null\n lastMessageTs: number | null\n}\n\nexport interface SessionInfo extends SessionMeta {\n id: string\n overview: SessionOverview\n}\n\n// ==================== Flat session info (for AnalysisSession-like consumers) ====================\n\n/**\n * Platform-agnostic session info built from meta + overview.\n * Covers all AnalysisSession fields that are queryable from a single DB.\n * Platform-specific fields (dbPath, memberAvatar, aiConversationCount) are left to callers.\n */\nexport interface CoreSessionInfo {\n name: string\n platform: string\n type: string\n importedAt: number\n messageCount: number\n memberCount: number\n groupId: string | null\n groupAvatar: string | null\n ownerId: string | null\n firstMessageTs: number | null\n lastMessageTs: number | null\n summaryCount: number\n}\n\n/**\n * Pure mapper: compose SessionMeta + SessionOverview into flat CoreSessionInfo.\n * Callers provide the inputs which may come from cache or fresh SQL.\n */\nexport function buildSessionInfo(\n meta: SessionMeta,\n overview: SessionOverview,\n summaryCount: number = 0\n): CoreSessionInfo {\n return {\n name: meta.name,\n platform: meta.platform,\n type: meta.type,\n importedAt: meta.importedAt,\n messageCount: overview.totalMessages,\n memberCount: overview.totalMembers,\n groupId: meta.groupId,\n groupAvatar: meta.groupAvatar,\n ownerId: meta.ownerId,\n firstMessageTs: overview.firstMessageTs,\n lastMessageTs: overview.lastMessageTs,\n summaryCount,\n }\n}\n\n/**\n * Convenience: read meta + overview from DB and return flat CoreSessionInfo.\n */\nexport function getSessionInfo(db: DatabaseAdapter): CoreSessionInfo | null {\n const meta = getSessionMeta(db)\n if (!meta) return null\n const overview = getSessionOverview(db)\n const sc = getSummaryCount(db)\n return buildSessionInfo(meta, overview, sc)\n}\n\n/**\n * Count of chat sessions that have an AI-generated summary.\n */\nexport function getSummaryCount(db: DatabaseAdapter): number {\n if (!hasTable(db, 'segment')) return 0\n const row = db.prepare(\"SELECT COUNT(*) as count FROM segment WHERE summary IS NOT NULL AND summary != ''\").get() as\n | { count: number }\n | undefined\n return row?.count ?? 0\n}\n\n/**\n * Get the latest platform_message_id (used as incremental import boundary).\n */\nexport function getLastPlatformMessageId(db: DatabaseAdapter): string | null {\n const row = db\n .prepare('SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL ORDER BY ts DESC LIMIT 1')\n .get() as { platform_message_id: string } | undefined\n return row?.platform_message_id ?? null\n}\n\n// ==================== Core identification ====================\n\n/**\n * 判断数据库是否为聊天会话数据库\n * 通过核心三表(meta/member/message)存在性快速识别\n */\nexport function isChatSessionDb(db: DatabaseAdapter): boolean {\n const row = db\n .prepare(\"SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name IN ('meta', 'member', 'message')\")\n .get() as { cnt: number } | undefined\n return row?.cnt === 3\n}\n\n/**\n * 读取会话元信息\n */\nexport function getSessionMeta(db: DatabaseAdapter): SessionMeta | null {\n const row = db.prepare('SELECT * FROM meta LIMIT 1').get() as Record<string, unknown> | undefined\n if (!row) return null\n\n return {\n name: row.name as string,\n platform: row.platform as string,\n type: row.type as string,\n importedAt: row.imported_at as number,\n groupId: (row.group_id as string) || null,\n groupAvatar: (row.group_avatar as string) || null,\n ownerId: (row.owner_id as string) || null,\n }\n}\n\n/**\n * 查询会话基础统计(消息数、成员数、时间范围)\n */\nexport function getSessionOverview(db: DatabaseAdapter): SessionOverview {\n const msgRow = db\n .prepare(\n `SELECT COUNT(*) as count\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE COALESCE(m.account_name, '') != '系统消息'`\n )\n .get() as { count: number }\n\n const memberRow = db\n .prepare(\n `SELECT COUNT(*) as count\n FROM member\n WHERE COALESCE(account_name, '') != '系统消息'`\n )\n .get() as { count: number }\n\n const firstTs = (db.prepare('SELECT MIN(ts) as v FROM message').get() as { v: number | null })?.v ?? null\n const lastTs = (db.prepare('SELECT MAX(ts) as v FROM message').get() as { v: number | null })?.v ?? null\n\n return {\n totalMessages: msgRow.count,\n totalMembers: memberRow.count,\n firstMessageTs: firstTs,\n lastMessageTs: lastTs,\n }\n}\n\n/**\n * 获取数据库中的表结构(Schema)\n */\nexport function getDatabaseSchema(db: DatabaseAdapter): Array<{ name: string; sql: string }> {\n return db\n .prepare(\"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name\")\n .all() as Array<{ name: string; sql: string }>\n}\n\n// ==================== Chat Overview & Session Queries ====================\n\nexport interface ChatOverviewData {\n name: string\n platform: string\n type: string\n totalMessages: number\n totalMembers: number\n firstMessageTs: number | null\n lastMessageTs: number | null\n topMembers: Array<{ id: number; name: string; count: number }>\n summaryCount: number\n}\n\nexport interface SessionPreviewMessage {\n id: number\n senderId: number\n senderName: string\n senderPlatformId: string\n content: string | null\n timestamp: number\n}\n\nexport interface SegmentMessagesData {\n segmentId: number\n startTs: number\n endTs: number\n messageCount: number\n returnedCount: number\n participants: string[]\n messages: SessionPreviewMessage[]\n}\n\nexport interface SegmentSummaryData {\n id: number\n startTs: number\n endTs: number\n messageCount: number\n participants: string[]\n summary: string | null\n}\n\n/**\n * Get chat overview by composing meta, overview stats, and top members.\n * Simpler than Electron version — no cache layer, direct SQL.\n */\nexport function getChatOverview(db: DatabaseAdapter, topN: number = 10): ChatOverviewData | null {\n const meta = getSessionMeta(db)\n if (!meta) return null\n\n const overview = getSessionOverview(db)\n const members = getMemberActivity(db)\n const summaryCount = getSummaryCount(db)\n\n const topMembers = members.slice(0, topN).map((m) => ({\n id: m.memberId,\n name: m.name,\n count: m.messageCount,\n }))\n\n return {\n name: meta.name,\n platform: meta.platform,\n type: meta.type,\n totalMessages: overview.totalMessages,\n totalMembers: overview.totalMembers,\n firstMessageTs: overview.firstMessageTs,\n lastMessageTs: overview.lastMessageTs,\n topMembers,\n summaryCount,\n }\n}\n\n/**\n * Get messages for a specific chat session\n */\nexport function getSegmentMessages(\n db: DatabaseAdapter,\n segmentId: number,\n limit: number = 500\n): SegmentMessagesData | null {\n if (!hasTable(db, 'segment')) return null\n\n const session = db\n .prepare(\n `SELECT id, start_ts as startTs, end_ts as endTs, message_count as messageCount\n FROM segment WHERE id = ?`\n )\n .get(segmentId) as { id: number; startTs: number; endTs: number; messageCount: number } | undefined\n\n if (!session) return null\n\n const messages = db\n .prepare(\n `SELECT m.id, mb.id as senderId,\n COALESCE(mb.group_nickname, mb.account_name, mb.platform_id) as senderName,\n mb.platform_id as senderPlatformId,\n m.content, m.ts as timestamp\n FROM message_context mc\n JOIN message m ON m.id = mc.message_id\n JOIN member mb ON mb.id = m.sender_id\n WHERE mc.segment_id = ? ORDER BY m.ts ASC LIMIT ?`\n )\n .all(segmentId, limit) as unknown as SessionPreviewMessage[]\n\n const participantsSet = new Set<string>()\n for (const msg of messages) {\n participantsSet.add(msg.senderName)\n }\n\n return {\n segmentId: session.id,\n startTs: session.startTs,\n endTs: session.endTs,\n messageCount: session.messageCount,\n returnedCount: messages.length,\n participants: Array.from(participantsSet),\n messages,\n }\n}\n\n/**\n * Get session summaries (only sessions that have AI-generated summaries)\n */\nexport function getSegmentSummaries(\n db: DatabaseAdapter,\n options?: { limit?: number; timeFilter?: TimeFilter }\n): SegmentSummaryData[] {\n if (!hasTable(db, 'segment')) return []\n\n const { limit = 50, timeFilter } = options ?? {}\n\n let sql = `\n SELECT cs.id, cs.start_ts as startTs, cs.end_ts as endTs,\n cs.message_count as messageCount, cs.summary\n FROM segment cs\n WHERE cs.summary IS NOT NULL AND cs.summary != ''\n `\n const params: unknown[] = []\n\n if (timeFilter?.startTs !== undefined) {\n sql += ' AND cs.start_ts >= ?'\n params.push(timeFilter.startTs)\n }\n if (timeFilter?.endTs !== undefined) {\n sql += ' AND cs.start_ts <= ?'\n params.push(timeFilter.endTs)\n }\n\n sql += ' ORDER BY cs.start_ts DESC LIMIT ?'\n params.push(limit)\n\n const sessions = db.prepare(sql).all(...params) as Array<{\n id: number\n startTs: number\n endTs: number\n messageCount: number\n summary: string | null\n }>\n\n const participantsSql = `\n SELECT DISTINCT COALESCE(mb.group_nickname, mb.account_name, mb.platform_id) as name\n FROM message_context mc\n JOIN message m ON m.id = mc.message_id\n JOIN member mb ON mb.id = m.sender_id\n WHERE mc.segment_id = ? LIMIT 10\n `\n\n return sessions.map((session) => {\n const participants = db.prepare(participantsSql).all(session.id) as Array<{ name: string }>\n\n return {\n id: session.id,\n startTs: session.startTs,\n endTs: session.endTs,\n messageCount: session.messageCount,\n participants: participants.map((p) => p.name),\n summary: session.summary,\n }\n })\n}\n\n// ==================== Session Index (segment) ====================\n\n/** Default gap threshold for session segmentation: 30 minutes (seconds) */\nexport const DEFAULT_SESSION_GAP_THRESHOLD = 1800\n\nexport interface ChatSessionItem {\n id: number\n startTs: number\n endTs: number\n messageCount: number\n firstMessageId: number\n summary?: string | null\n}\n\nexport interface SessionIndexStats {\n sessionCount: number\n hasIndex: boolean\n gapThreshold: number\n}\n\n/**\n * Check whether the segment table exists and has at least one row.\n */\nexport function hasSessionIndex(db: DatabaseAdapter): boolean {\n if (!hasTable(db, 'segment')) return false\n try {\n const row = db.prepare('SELECT COUNT(*) as count FROM segment').get() as { count: number } | undefined\n return (row?.count ?? 0) > 0\n } catch {\n return false\n }\n}\n\n/**\n * Session index statistics: count, existence flag, and gap threshold from meta.\n */\nexport function getSessionIndexStats(db: DatabaseAdapter): SessionIndexStats {\n let sessionCount = 0\n if (hasTable(db, 'segment')) {\n try {\n const row = db.prepare('SELECT COUNT(*) as count FROM segment').get() as { count: number } | undefined\n sessionCount = row?.count ?? 0\n } catch {\n /* table may not exist */\n }\n }\n\n let gapThreshold = DEFAULT_SESSION_GAP_THRESHOLD\n try {\n const meta = db.prepare('SELECT session_gap_threshold FROM meta LIMIT 1').get() as\n | { session_gap_threshold: number | null }\n | undefined\n if (meta?.session_gap_threshold) {\n gapThreshold = meta.session_gap_threshold\n }\n } catch {\n /* column may not exist */\n }\n\n return { sessionCount, hasIndex: sessionCount > 0, gapThreshold }\n}\n\n/**\n * Query chat sessions within a time range.\n */\nexport function getSessionsByTimeRange(db: DatabaseAdapter, startTs: number, endTs: number): ChatSessionItem[] {\n if (!hasTable(db, 'segment')) return []\n try {\n return db\n .prepare(\n `SELECT\n id, start_ts as startTs, end_ts as endTs,\n message_count as messageCount, summary,\n (SELECT mc.message_id FROM message_context mc\n WHERE mc.segment_id = cs.id ORDER BY mc.message_id LIMIT 1) as firstMessageId\n FROM segment cs\n WHERE start_ts >= ? AND start_ts <= ?\n ORDER BY start_ts DESC`\n )\n .all(startTs, endTs) as unknown as ChatSessionItem[]\n } catch {\n return []\n }\n}\n\n/**\n * Get the most recent N chat sessions.\n */\nexport function getRecentChatSessions(db: DatabaseAdapter, limit: number): ChatSessionItem[] {\n if (!hasTable(db, 'segment')) return []\n try {\n return db\n .prepare(\n `SELECT\n id, start_ts as startTs, end_ts as endTs,\n message_count as messageCount, summary,\n (SELECT mc.message_id FROM message_context mc\n WHERE mc.segment_id = cs.id ORDER BY mc.message_id LIMIT 1) as firstMessageId\n FROM segment cs\n ORDER BY start_ts DESC\n LIMIT ?`\n )\n .all(limit) as unknown as ChatSessionItem[]\n } catch {\n return []\n }\n}\n\n/**\n * Timeline list of chat sessions with first message id and summary.\n */\nexport function getChatSessionList(db: DatabaseAdapter): ChatSessionItem[] {\n if (!hasTable(db, 'segment')) return []\n try {\n return db\n .prepare(\n `SELECT\n cs.id,\n cs.start_ts as startTs,\n cs.end_ts as endTs,\n cs.message_count as messageCount,\n cs.summary,\n (SELECT mc.message_id FROM message_context mc\n WHERE mc.segment_id = cs.id ORDER BY mc.message_id LIMIT 1) as firstMessageId\n FROM segment cs\n ORDER BY cs.start_ts ASC`\n )\n .all() as unknown as ChatSessionItem[]\n } catch {\n return []\n }\n}\n\n/**\n * Load messages for a chat session (for summary generation).\n */\nexport function loadSegmentMessages(\n db: DatabaseAdapter,\n segmentId: number,\n limit: number = 500\n): Array<{ senderName: string; content: string | null }> | null {\n try {\n return db\n .prepare(\n `SELECT\n COALESCE(mb.group_nickname, mb.account_name, mb.platform_id) as senderName,\n m.content\n FROM message_context mc\n JOIN message m ON m.id = mc.message_id\n JOIN member mb ON mb.id = m.sender_id\n WHERE mc.segment_id = ?\n ORDER BY m.ts ASC\n LIMIT ?`\n )\n .all(segmentId, limit) as unknown as Array<{ senderName: string; content: string | null }>\n } catch {\n return null\n }\n}\n\n/**\n * Get summary text for a single chat session.\n */\nexport function getSegmentSummary(db: DatabaseAdapter, segmentId: number): string | null {\n try {\n const row = db.prepare('SELECT summary FROM segment WHERE id = ?').get(segmentId) as\n | { summary: string | null }\n | undefined\n return row?.summary ?? null\n } catch {\n return null\n }\n}\n\n/**\n * Save summary text for a chat session.\n */\nexport function saveSegmentSummary(db: DatabaseAdapter, segmentId: number, summary: string): void {\n db.prepare('UPDATE segment SET summary = ? WHERE id = ?').run(summary, segmentId)\n}\n\n/**\n * Update gap threshold in meta table.\n */\nexport function updateSessionGapThreshold(db: DatabaseAdapter, gapThreshold: number | null): void {\n db.prepare('UPDATE meta SET session_gap_threshold = ?').run(gapThreshold)\n}\n\n/**\n * Update session owner_id in meta table.\n */\nexport function updateSessionOwnerId(db: DatabaseAdapter, ownerId: string | null): void {\n db.prepare('UPDATE meta SET owner_id = ?').run(ownerId)\n}\n\n/**\n * Rename a session (update name in meta table).\n */\nexport function renameSession(db: DatabaseAdapter, newName: string): void {\n db.prepare('UPDATE meta SET name = ?').run(newName)\n}\n\n/**\n * Delete all session index data (segment + message_context).\n */\nfunction clearSessionIndexRows(db: DatabaseAdapter): void {\n db.exec('DELETE FROM message_context')\n db.exec('DELETE FROM segment')\n}\n\nexport function clearSessionIndex(db: DatabaseAdapter): void {\n db.transaction(() => clearSessionIndexRows(db))\n}\n\n/**\n * Generate session index using gap-based segmentation.\n * Pure SQL+JS algorithm — caller provides an open DatabaseAdapter.\n *\n * @returns number of sessions created\n */\nexport function generateSessionIndex(\n db: DatabaseAdapter,\n gapThreshold: number = DEFAULT_SESSION_GAP_THRESHOLD,\n onProgress?: (current: number, total: number) => void\n): number {\n const countRow = db.prepare('SELECT COUNT(*) as count FROM message').get() as { count: number } | undefined\n if (!countRow || countRow.count === 0) return 0\n\n const sessionMarkSQL = `\n WITH message_ordered AS (\n SELECT id, ts, LAG(ts) OVER (ORDER BY ts, id) AS prev_ts FROM message\n ),\n session_marks AS (\n SELECT id, ts,\n CASE WHEN prev_ts IS NULL OR (ts - prev_ts) > ? THEN 1 ELSE 0 END AS is_new_session\n FROM message_ordered\n ),\n session_ids AS (\n SELECT id, ts, SUM(is_new_session) OVER (ORDER BY ts, id) AS session_num\n FROM session_marks\n )\n SELECT id, ts, session_num FROM session_ids\n `\n\n const messages = db.prepare(sessionMarkSQL).all(gapThreshold) as Array<{\n id: number\n ts: number\n session_num: number\n }>\n\n if (messages.length === 0) return 0\n\n const sessionMap = new Map<number, { startTs: number; endTs: number; messageIds: number[] }>()\n for (const msg of messages) {\n const session = sessionMap.get(msg.session_num)\n if (!session) {\n sessionMap.set(msg.session_num, { startTs: msg.ts, endTs: msg.ts, messageIds: [msg.id] })\n } else {\n session.endTs = msg.ts\n session.messageIds.push(msg.id)\n }\n }\n\n const insertSession = db.prepare(\n 'INSERT INTO segment (start_ts, end_ts, message_count, is_manual, summary) VALUES (?, ?, ?, 0, NULL)'\n )\n const insertContext = db.prepare('INSERT INTO message_context (message_id, segment_id, topic_id) VALUES (?, ?, NULL)')\n\n return db.transaction(() => {\n clearSessionIndexRows(db)\n\n let processed = 0\n const total = sessionMap.size\n for (const [, data] of sessionMap) {\n const result = insertSession.run(data.startTs, data.endTs, data.messageIds.length)\n const newId = (result.lastInsertRowid ?? 0) as number\n for (const mid of data.messageIds) {\n insertContext.run(mid, newId)\n }\n processed++\n if (onProgress && processed % 100 === 0) onProgress(processed, total)\n }\n if (onProgress) onProgress(total, total)\n return total\n })\n}\n\n/**\n * Incremental session index generation — only processes unindexed messages.\n * Preserves existing sessions and their summaries.\n *\n * @returns number of NEW sessions created (appended messages don't count)\n */\nexport function generateIncrementalSessionIndex(\n db: DatabaseAdapter,\n gapThreshold: number = DEFAULT_SESSION_GAP_THRESHOLD\n): number {\n const indexedIds = new Set<number>()\n if (hasTable(db, 'message_context')) {\n const rows = db.prepare('SELECT message_id FROM message_context').all() as Array<{ message_id: number }>\n for (const r of rows) indexedIds.add(r.message_id)\n }\n\n const allMessages = db.prepare('SELECT id, ts FROM message ORDER BY ts, id').all() as Array<{\n id: number\n ts: number\n }>\n const newMessages = allMessages.filter((m) => !indexedIds.has(m.id))\n if (newMessages.length === 0) return 0\n\n const lastSession = hasTable(db, 'segment')\n ? (db.prepare('SELECT id, end_ts FROM segment ORDER BY end_ts DESC LIMIT 1').get() as\n | { id: number; end_ts: number }\n | undefined)\n : undefined\n\n newMessages.sort((a, b) => a.ts - b.ts || a.id - b.id)\n\n const insertSession = db.prepare(\n 'INSERT INTO segment (start_ts, end_ts, message_count, is_manual, summary) VALUES (?, ?, ?, 0, NULL)'\n )\n const insertContext = db.prepare('INSERT INTO message_context (message_id, segment_id, topic_id) VALUES (?, ?, NULL)')\n const updateSession = db.prepare('UPDATE segment SET end_ts = ?, message_count = message_count + ? WHERE id = ?')\n\n return db.transaction(() => {\n let newSessionCount = 0\n let currentSessionId: number | null = null\n let currentEndTs = 0\n let appendCount = 0\n\n for (let i = 0; i < newMessages.length; i++) {\n const msg = newMessages[i]\n let needNew = false\n\n if (i === 0) {\n if (lastSession && msg.ts - lastSession.end_ts <= gapThreshold) {\n currentSessionId = lastSession.id\n currentEndTs = lastSession.end_ts\n appendCount = 0\n } else {\n needNew = true\n }\n } else {\n if (msg.ts - newMessages[i - 1].ts > gapThreshold) {\n if (currentSessionId && appendCount > 0) {\n updateSession.run(currentEndTs, appendCount, currentSessionId)\n appendCount = 0\n }\n needNew = true\n }\n }\n\n if (needNew) {\n const result = insertSession.run(msg.ts, msg.ts, 1)\n currentSessionId = (result.lastInsertRowid ?? 0) as number\n currentEndTs = msg.ts\n newSessionCount++\n appendCount = 0\n } else {\n currentEndTs = msg.ts\n appendCount++\n }\n\n insertContext.run(msg.id, currentSessionId)\n }\n\n if (currentSessionId && appendCount > 0) {\n updateSession.run(currentEndTs, appendCount, currentSessionId)\n }\n\n return newSessionCount\n })\n}\n\n// ==================== Export data ====================\n\nexport interface ExportSessionData {\n meta: {\n name: string\n platform: string\n type: string\n groupId?: string\n groupAvatar?: string\n }\n members: Array<{\n platformId: string\n accountName: string\n groupNickname?: string\n avatar?: string\n }>\n messages: Array<{\n sender: string\n accountName: string\n groupNickname?: string\n timestamp: number\n type: number\n content: string | null\n }>\n}\n\n/**\n * Query all data needed for session export (meta + members + messages).\n */\nexport function getExportSessionData(db: DatabaseAdapter): ExportSessionData {\n const meta = db.prepare('SELECT * FROM meta').get() as {\n name: string\n platform: string\n type: string\n group_id?: string\n group_avatar?: string\n }\n if (!meta) throw new Error('Cannot read session meta')\n\n const members = db.prepare('SELECT platform_id, account_name, group_nickname, avatar FROM member').all() as Array<{\n platform_id: string\n account_name?: string\n group_nickname?: string\n avatar?: string\n }>\n\n const messages = db\n .prepare(\n `SELECT\n m.platform_id as sender,\n msg.sender_account_name as accountName,\n msg.sender_group_nickname as groupNickname,\n msg.ts as timestamp,\n msg.type,\n msg.content\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ORDER BY msg.ts`\n )\n .all() as unknown as Array<{\n sender: string\n accountName?: string\n groupNickname?: string\n timestamp: number\n type: number\n content?: string\n }>\n\n return {\n meta: {\n name: meta.name,\n platform: meta.platform,\n type: meta.type,\n groupId: meta.group_id,\n groupAvatar: meta.group_avatar,\n },\n members: members.map((m) => ({\n platformId: m.platform_id,\n accountName: m.account_name || m.platform_id,\n groupNickname: m.group_nickname || undefined,\n avatar: m.avatar,\n })),\n messages: messages.map((msg) => ({\n sender: msg.sender,\n accountName: msg.accountName || msg.sender,\n groupNickname: msg.groupNickname || undefined,\n timestamp: msg.timestamp,\n type: msg.type,\n content: msg.content ?? null,\n })),\n }\n}\n\n/**\n * Get private chat partner's avatar.\n * Finds the \"other\" member (not the owner) in a private chat, falling back to name match or first with avatar.\n */\nexport function getPrivateChatMemberAvatar(\n db: DatabaseAdapter,\n sessionName: string,\n ownerId: string | null | undefined\n): string | null {\n const members = db\n .prepare(\n `SELECT\n m.platform_id as platformId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,\n m.avatar\n FROM member m\n WHERE COALESCE(m.account_name, '') != '系统消息'\n ORDER BY (SELECT COUNT(*) FROM message WHERE sender_id = m.id) DESC`\n )\n .all() as unknown as Array<{ platformId: string; name: string; avatar: string | null }>\n\n if (members.length === 0) return null\n\n if (ownerId) {\n const other = members.find((m) => m.platformId !== ownerId)\n if (other?.avatar) return other.avatar\n }\n\n const sameName = members.find((m) => m.name === sessionName)\n if (sameName?.avatar) return sameName.avatar\n\n const firstWithAvatar = members.find((m) => m.avatar)\n return firstWithAvatar?.avatar || null\n}\n","/**\n * Shared full-message SQL template, types, and row mapper.\n *\n * Single source of truth for all platforms (Electron worker, CLI Web FetchMessageAdapter).\n * Each platform imports these constants and the mapper; only the SQL execution\n * mechanism differs (direct db.prepare vs pluginQuery HTTP).\n */\n\n// ==================== SQL fragments ====================\n\nexport const FULL_MSG_COLUMNS = `\n msg.id,\n m.id as senderId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,\n m.platform_id as senderPlatformId,\n COALESCE(m.aliases, '[]') as aliasesJson,\n m.avatar as senderAvatar,\n msg.content,\n msg.ts as timestamp,\n msg.type,\n msg.reply_to_message_id as replyToMessageId,\n reply_msg.content as replyToContent,\n COALESCE(reply_m.group_nickname, reply_m.account_name, reply_m.platform_id) as replyToSenderName`\n\nexport const FULL_MSG_FROM = `\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n LEFT JOIN message reply_msg ON msg.reply_to_message_id = reply_msg.platform_message_id\n LEFT JOIN member reply_m ON reply_msg.sender_id = reply_m.id`\n\nexport const FULL_MSG_SELECT = `SELECT ${FULL_MSG_COLUMNS} ${FULL_MSG_FROM}`\n\nexport const MSG_COUNT_FROM = `FROM message msg JOIN member m ON msg.sender_id = m.id`\n\nexport const SYSTEM_MSG_FILTER = \"COALESCE(m.account_name, '') != '系统消息'\"\nexport const TEXT_ONLY_FILTER = \"msg.type = 0 AND msg.content IS NOT NULL AND msg.content != ''\"\n\n// ==================== Types ====================\n\nexport interface FullMessageRow {\n id: number\n senderId: number\n senderName: string\n senderPlatformId: string\n aliasesJson: string\n senderAvatar: string | null\n content: string | null\n timestamp: number\n type: number\n replyToMessageId: string | null\n replyToContent: string | null\n replyToSenderName: string | null\n}\n\nexport interface MappedMessage {\n id: number\n senderId: number\n senderName: string\n senderPlatformId: string\n senderAliases: string[]\n senderAvatar: string | null\n content: string\n timestamp: number\n type: number\n replyToMessageId: string | null\n replyToContent: string | null\n replyToSenderName: string | null\n}\n\n// ==================== Row mapper ====================\n\nexport function mapMessageRow(row: FullMessageRow): MappedMessage {\n let senderAliases: string[] = []\n try {\n const parsed = JSON.parse(row.aliasesJson || '[]')\n if (Array.isArray(parsed)) senderAliases = parsed\n } catch {\n /* ignore malformed JSON */\n }\n\n return {\n id: Number(row.id),\n senderId: Number(row.senderId),\n senderName: String(row.senderName || ''),\n senderPlatformId: String(row.senderPlatformId || ''),\n senderAliases,\n senderAvatar: row.senderAvatar || null,\n content: row.content != null ? String(row.content) : '',\n timestamp: Number(row.timestamp),\n type: Number(row.type),\n replyToMessageId: row.replyToMessageId || null,\n replyToContent: row.replyToContent || null,\n replyToSenderName: row.replyToSenderName || null,\n }\n}\n\n// ==================== Query builders ====================\n\nexport interface MsgQueryConditions {\n clause: string\n params: unknown[]\n}\n\nexport function buildMsgConditions(options?: {\n startTs?: number\n endTs?: number\n senderId?: number\n memberId?: number | null\n keywords?: string[]\n systemFilter?: boolean\n textOnlyFilter?: boolean\n}): MsgQueryConditions {\n const conds: string[] = []\n const params: unknown[] = []\n\n if (options?.startTs != null) {\n conds.push('msg.ts >= ?')\n params.push(options.startTs)\n }\n if (options?.endTs != null) {\n conds.push('msg.ts <= ?')\n params.push(options.endTs)\n }\n if (options?.senderId != null) {\n conds.push('msg.sender_id = ?')\n params.push(options.senderId)\n }\n if (options?.memberId != null) {\n conds.push('msg.sender_id = ?')\n params.push(options.memberId)\n }\n if (options?.keywords && options.keywords.length > 0) {\n const kwConds = options.keywords.map(() => 'msg.content LIKE ?')\n conds.push(`(${kwConds.join(' OR ')})`)\n params.push(...options.keywords.map((k) => `%${k}%`))\n }\n if (options?.systemFilter) {\n conds.push(SYSTEM_MSG_FILTER)\n }\n if (options?.textOnlyFilter) {\n conds.push(TEXT_ONLY_FILTER)\n }\n\n return {\n clause: conds.length > 0 ? 'AND ' + conds.join(' AND ') : '',\n params,\n }\n}\n","/**\n * 消息查询模块(平台无关)\n *\n * 提供消息搜索、分页等基础查询能力。\n * 复杂的 FTS 搜索留在 Electron/Server 层处理(依赖分词器)。\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../interfaces'\nimport { buildTimeFilter, hasTable, hasColumn } from './filters'\nimport {\n FULL_MSG_SELECT,\n mapMessageRow,\n buildMsgConditions,\n type FullMessageRow,\n type MappedMessage,\n} from './message-sql'\n\nexport interface MessageResult {\n id: number\n senderId: number\n senderName: string\n senderPlatformId: string\n content: string\n timestamp: number\n type: number\n}\n\nexport interface PaginatedMessages {\n messages: MessageResult[]\n hasMore: boolean\n total?: number\n}\n\nexport interface QueryMessagesOptions {\n keyword?: string\n startTs?: number\n endTs?: number\n senderId?: number\n limit?: number\n offset?: number\n}\n\nexport interface QueryMessagesResult {\n messages: MessageResult[]\n total: number\n page: number\n limit: number\n totalPages: number\n}\n\n/**\n * 通用分页消息查询\n * 支持关键词、时间范围、发送者过滤\n */\nexport function queryMessages(db: DatabaseAdapter, options?: QueryMessagesOptions): QueryMessagesResult {\n const limit = Math.min(1000, Math.max(1, options?.limit ?? 100))\n const offset = options?.offset ?? 0\n const page = Math.floor(offset / limit) + 1\n\n const conditions: string[] = [\"COALESCE(m.account_name, '') != '系统消息'\"]\n const params: unknown[] = []\n\n if (options?.keyword) {\n conditions.push('msg.content LIKE ?')\n params.push(`%${options.keyword}%`)\n }\n if (options?.startTs !== undefined) {\n conditions.push('msg.ts >= ?')\n params.push(options.startTs)\n }\n if (options?.endTs !== undefined) {\n conditions.push('msg.ts <= ?')\n params.push(options.endTs)\n }\n if (options?.senderId !== undefined) {\n conditions.push('msg.sender_id = ?')\n params.push(options.senderId)\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n\n const countRow = db\n .prepare(\n `SELECT COUNT(*) as total\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${where}`\n )\n .get(...params) as { total: number }\n\n const rows = db\n .prepare(\n `SELECT\n msg.id as id,\n m.id as senderId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,\n m.platform_id as senderPlatformId,\n msg.content as content,\n msg.ts as timestamp,\n msg.type as type\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${where}\n ORDER BY msg.ts DESC\n LIMIT ? OFFSET ?`\n )\n .all(...params, limit, offset) as Array<{\n id: number\n senderId: number\n senderName: string\n senderPlatformId: string\n content: string\n timestamp: number\n type: number\n }>\n\n const messages = rows.map((row) => ({\n id: Number(row.id),\n senderId: Number(row.senderId),\n senderName: String(row.senderName || ''),\n senderPlatformId: String(row.senderPlatformId || ''),\n content: row.content != null ? String(row.content) : '',\n timestamp: Number(row.timestamp),\n type: Number(row.type),\n }))\n\n const total = countRow.total\n return {\n messages,\n total,\n page,\n limit,\n totalPages: Math.ceil(total / limit),\n }\n}\n\n/**\n * 基于 LIKE 的简单关键词搜索\n * 不依赖 FTS 索引,适用于 CLI/MCP 场景\n */\nexport function searchMessagesLike(\n db: DatabaseAdapter,\n keyword: string,\n options?: { limit?: number; offset?: number }\n): PaginatedMessages {\n const limit = options?.limit ?? 50\n const offset = options?.offset ?? 0\n\n const countRow = db\n .prepare(\n `SELECT COUNT(*) as total\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE msg.content LIKE ? AND COALESCE(m.account_name, '') != '系统消息'`\n )\n .get(`%${keyword}%`) as { total: number }\n\n const rows = db\n .prepare(\n `SELECT\n msg.id as id,\n m.id as senderId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,\n m.platform_id as senderPlatformId,\n msg.content as content,\n msg.ts as timestamp,\n msg.type as type\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE msg.content LIKE ? AND COALESCE(m.account_name, '') != '系统消息'\n ORDER BY msg.ts DESC\n LIMIT ? OFFSET ?`\n )\n .all(`%${keyword}%`, limit + 1, offset) as Array<{\n id: number\n senderId: number\n senderName: string\n senderPlatformId: string\n content: string\n timestamp: number\n type: number\n }>\n\n const hasMore = rows.length > limit\n const messages = rows.slice(0, limit).map((row) => ({\n id: Number(row.id),\n senderId: Number(row.senderId),\n senderName: String(row.senderName || ''),\n senderPlatformId: String(row.senderPlatformId || ''),\n content: row.content != null ? String(row.content) : '',\n timestamp: Number(row.timestamp),\n type: Number(row.type),\n }))\n\n return { messages, hasMore, total: countRow.total }\n}\n\n/**\n * 多关键词 LIKE 子串搜索(关键词之间 OR,命中任一即返回)\n *\n * 适用于 CLI/MCP/Web 等无 FTS 的场景,行为与 Electron worker 的\n * searchMessagesLikeAsync 一致:支持时间范围、发送者过滤,并排除系统消息。\n */\nexport function searchMessagesByKeywords(\n db: DatabaseAdapter,\n keywords: string[],\n options?: { startTs?: number; endTs?: number; senderId?: number; limit?: number; offset?: number }\n): PaginatedMessages {\n const limit = options?.limit ?? 50\n const offset = options?.offset ?? 0\n const cleaned = keywords.map((k) => k.trim()).filter((k) => k.length > 0)\n\n const { clause, params } = buildMsgConditions({\n startTs: options?.startTs,\n endTs: options?.endTs,\n senderId: options?.senderId,\n keywords: cleaned.length > 0 ? cleaned : undefined,\n systemFilter: true,\n })\n\n const countRow = db\n .prepare(`SELECT COUNT(*) as total FROM message msg JOIN member m ON msg.sender_id = m.id WHERE 1=1 ${clause}`)\n .get(...params) as { total: number }\n\n const rows = db\n .prepare(`${FULL_MSG_SELECT} WHERE 1=1 ${clause} ORDER BY msg.ts DESC LIMIT ? OFFSET ?`)\n .all(...params, limit + 1, offset) as unknown as FullMessageRow[]\n\n const hasMore = rows.length > limit\n const messages = rows.slice(0, limit).map((row) => {\n const mapped = mapMessageRow(row)\n return {\n id: mapped.id,\n senderId: mapped.senderId,\n senderName: mapped.senderName,\n senderPlatformId: mapped.senderPlatformId,\n content: mapped.content,\n timestamp: mapped.timestamp,\n type: mapped.type,\n }\n })\n\n return { messages, hasMore, total: countRow.total }\n}\n\n/**\n * 获取最近 N 条消息\n */\nexport function getRecentMessages(db: DatabaseAdapter, options?: { limit?: number }): MessageResult[] {\n const limit = options?.limit ?? 50\n\n const rows = db\n .prepare(\n `SELECT\n msg.id as id,\n m.id as senderId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,\n m.platform_id as senderPlatformId,\n msg.content as content,\n msg.ts as timestamp,\n msg.type as type\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE COALESCE(m.account_name, '') != '系统消息'\n ORDER BY msg.ts DESC\n LIMIT ?`\n )\n .all(limit) as Array<{\n id: number\n senderId: number\n senderName: string\n senderPlatformId: string\n content: string\n timestamp: number\n type: number\n }>\n\n return rows.map((row) => ({\n id: Number(row.id),\n senderId: Number(row.senderId),\n senderName: String(row.senderName || ''),\n senderPlatformId: String(row.senderPlatformId || ''),\n content: row.content != null ? String(row.content) : '',\n timestamp: Number(row.timestamp),\n type: Number(row.type),\n }))\n}\n\n/**\n * 获取成员列表\n */\nexport function getMembers(\n db: DatabaseAdapter\n): Array<{ id: number; platformId: string; name: string; messageCount: number }> {\n return db\n .prepare(\n `SELECT\n m.id as id,\n m.platform_id as platformId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,\n (SELECT COUNT(*) FROM message WHERE sender_id = m.id) as messageCount\n FROM member m\n WHERE COALESCE(m.account_name, '') != '系统消息'\n ORDER BY messageCount DESC`\n )\n .all() as Array<{ id: number; platformId: string; name: string; messageCount: number }>\n}\n\nexport interface MemberDetailed {\n id: number\n platformId: string\n accountName: string\n groupNickname: string | null\n messageCount: number\n}\n\n/**\n * 获取完整字段的成员列表(含 accountName、groupNickname)\n */\nexport function getMembersDetailed(db: DatabaseAdapter): MemberDetailed[] {\n const rows = db\n .prepare(\n `SELECT\n m.id as id,\n m.platform_id as platformId,\n COALESCE(m.account_name, m.platform_id) as accountName,\n m.group_nickname as groupNickname,\n (SELECT COUNT(*) FROM message WHERE sender_id = m.id) as messageCount\n FROM member m\n WHERE COALESCE(m.account_name, '') != '系统消息'\n ORDER BY messageCount DESC`\n )\n .all() as Array<{\n id: number\n platformId: string\n accountName: string\n groupNickname: string | null\n messageCount: number\n }>\n\n return rows.map((row) => ({\n id: Number(row.id),\n platformId: String(row.platformId),\n accountName: String(row.accountName || row.platformId),\n groupNickname: row.groupNickname ? String(row.groupNickname) : null,\n messageCount: Number(row.messageCount),\n }))\n}\n\n/**\n * 执行只读 SQL 查询(SQL Lab)— 保留向后兼容签名\n */\nexport function executeReadonlySql(\n db: DatabaseAdapter,\n sql: string,\n maxRows: number = 1000\n): { columns: string[]; rows: Record<string, unknown>[]; rowCount: number; truncated: boolean } {\n const result = executeSql(db, sql, { maxRows })\n return {\n columns: result.columns,\n rows: result.rows as Record<string, unknown>[],\n rowCount: result.rowCount,\n truncated: result.truncated,\n }\n}\n\n// ==================== Unified SQL Execution ====================\n\nexport interface SqlExecutionOptions {\n /** Maximum rows to return. 0 = no limit. Default: 1000. */\n maxRows?: number\n /** Return rows as 2D arrays instead of objects. Default: false. */\n columnar?: boolean\n /** Include execution duration in result. Default: false. */\n timing?: boolean\n}\n\nexport interface SqlExecutionResult {\n columns: string[]\n rows: Record<string, unknown>[] | unknown[][]\n rowCount: number\n truncated: boolean\n duration?: number\n}\n\n/**\n * Unified readonly SQL execution.\n *\n * Safety: uses stmt.readonly when available (better-sqlite3 native check),\n * falls back to keyword denylist for adapters that don't expose it.\n */\nexport function executeSql(db: DatabaseAdapter, sql: string, options?: SqlExecutionOptions): SqlExecutionResult {\n const maxRows = options?.maxRows ?? 1000\n const columnar = options?.columnar ?? false\n const timing = options?.timing ?? false\n\n const trimmed = sql.trim()\n\n const startTime = timing ? Date.now() : 0\n\n const stmt = db.prepare(trimmed)\n\n if (stmt.readonly === false) {\n throw new Error('Only read-only statements are allowed (SELECT / WITH)')\n }\n\n if (stmt.readonly === undefined) {\n const forbidden = /^\\s*(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|ATTACH|DETACH|REINDEX|VACUUM|PRAGMA)/i\n if (forbidden.test(trimmed)) {\n throw new Error('Only SELECT queries are allowed')\n }\n }\n\n const needsLimit = maxRows > 0 && !/\\bLIMIT\\b/i.test(trimmed)\n let allRows: Record<string, unknown>[]\n\n if (needsLimit) {\n const safeSql = `${trimmed} LIMIT ${maxRows + 1}`\n allRows = db.prepare(safeSql).all() as Record<string, unknown>[]\n } else {\n allRows = stmt.all() as Record<string, unknown>[]\n }\n\n const truncated = maxRows > 0 && allRows.length > maxRows\n const resultRows = truncated ? allRows.slice(0, maxRows) : allRows\n const columns = resultRows.length > 0 ? Object.keys(resultRows[0]) : []\n\n const duration = timing ? Date.now() - startTime : undefined\n\n if (columnar) {\n const rows2d = resultRows.map((row) => columns.map((col) => row[col]))\n return { columns, rows: rows2d, rowCount: rows2d.length, truncated, duration }\n }\n\n return { columns, rows: resultRows, rowCount: resultRows.length, truncated, duration }\n}\n\n/** Table schema with column details */\nexport interface TableSchema {\n name: string\n columns: Array<{\n name: string\n type: string\n notnull: boolean\n pk: boolean\n }>\n}\n\n/**\n * Get database schema with column-level details.\n * Returns table names + full column info via PRAGMA table_info.\n */\nexport function getSchemaDetailed(db: DatabaseAdapter): TableSchema[] {\n const tables = db\n .prepare(\n `SELECT name FROM sqlite_master\n WHERE type='table' AND name NOT LIKE 'sqlite_%'\n ORDER BY name`\n )\n .all() as Array<{ name: string }>\n\n return tables.map((table) => {\n const columns = db.prepare(`PRAGMA table_info('${table.name}')`).all() as Array<{\n cid: number\n name: string\n type: string\n notnull: number\n dflt_value: unknown\n pk: number\n }>\n\n return {\n name: table.name,\n columns: columns.map((col) => ({\n name: col.name,\n type: col.type,\n notnull: col.notnull === 1,\n pk: col.pk === 1,\n })),\n }\n })\n}\n\n// ==================== Context & Conversation Queries ====================\n\nexport interface ContextMessage {\n id: number\n senderId: number\n senderName: string\n senderPlatformId: string\n content: string | null\n timestamp: number\n}\n\nexport interface ConversationData {\n messages: ContextMessage[]\n total: number\n member1Name: string\n member2Name: string\n}\n\nexport interface MemberNameHistoryEntry {\n nameType: string\n name: string\n startTs: number\n endTs: number | null\n}\n\nfunction getMemberNameHistoryFromMessages(db: DatabaseAdapter, memberId: number): MemberNameHistoryEntry[] {\n const rows = db\n .prepare(\n `SELECT\n sender_account_name as accountName,\n sender_group_nickname as groupNickname,\n MIN(ts) as startTs,\n MAX(ts) as endTs\n FROM message\n WHERE sender_id = ?\n GROUP BY sender_account_name, sender_group_nickname\n ORDER BY startTs`\n )\n .all(memberId) as unknown as Array<{\n accountName: string | null\n groupNickname: string | null\n startTs: number\n endTs: number | null\n }>\n\n const history: MemberNameHistoryEntry[] = []\n for (const row of rows) {\n if (row.accountName) {\n history.push({ nameType: 'account_name', name: row.accountName, startTs: row.startTs, endTs: row.endTs })\n }\n if (row.groupNickname) {\n history.push({ nameType: 'group_nickname', name: row.groupNickname, startTs: row.startTs, endTs: row.endTs })\n }\n }\n return history\n}\n\nexport interface MemberWithAliases {\n id: number\n platformId: string\n accountName: string | null\n groupNickname: string | null\n aliases: string[]\n avatar: string | null\n messageCount: number\n}\n\nexport interface MembersPaginationParams {\n page?: number\n pageSize?: number\n search?: string\n sortOrder?: 'asc' | 'desc'\n}\n\nexport interface MembersPaginatedResult {\n members: MemberWithAliases[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\n/**\n * Batch-load messages by IDs with full sender info (avatar, aliases, reply, etc.)\n */\nfunction hydrateMessagesByIds(db: DatabaseAdapter, ids: number[]): MappedMessage[] {\n if (ids.length === 0) return []\n const placeholders = ids.map(() => '?').join(', ')\n const rows = db\n .prepare(`${FULL_MSG_SELECT} WHERE msg.id IN (${placeholders}) ORDER BY msg.id ASC`)\n .all(...ids) as unknown as FullMessageRow[]\n return rows.map(mapMessageRow)\n}\n\n/**\n * Get surrounding context messages for given message IDs.\n * Uses simple id-based ordering (not session-aware).\n */\nexport function getMessageContext(\n db: DatabaseAdapter,\n messageIds: number[],\n contextSize: number = 20\n): MappedMessage[] {\n if (messageIds.length === 0) return []\n\n const contextIds = new Set<number>()\n\n for (const messageId of messageIds) {\n contextIds.add(messageId)\n\n const beforeRows = db\n .prepare('SELECT id FROM message WHERE id < ? ORDER BY id DESC LIMIT ?')\n .all(messageId, contextSize) as { id: number }[]\n beforeRows.forEach((r) => contextIds.add(r.id))\n\n const afterRows = db\n .prepare('SELECT id FROM message WHERE id > ? ORDER BY id ASC LIMIT ?')\n .all(messageId, contextSize) as { id: number }[]\n afterRows.forEach((r) => contextIds.add(r.id))\n }\n\n return hydrateMessagesByIds(db, Array.from(contextIds))\n}\n\n/**\n * Get context messages around search results.\n * Session-aware when message_context table is available, falls back to id-based ordering.\n */\nexport function getSearchMessageContext(\n db: DatabaseAdapter,\n messageIds: number[],\n contextBefore: number = 2,\n contextAfter: number = 2\n): MappedMessage[] {\n if (messageIds.length === 0) return []\n\n const contextIds = new Set<number>()\n\n const hasSessionData =\n hasTable(db, 'message_context') &&\n (db.prepare('SELECT 1 FROM message_context LIMIT 1').get() as Record<string, unknown> | undefined) !== undefined\n\n for (const messageId of messageIds) {\n contextIds.add(messageId)\n\n if (hasSessionData) {\n const sessionRow = db.prepare('SELECT segment_id FROM message_context WHERE message_id = ?').get(messageId) as\n | { segment_id: number }\n | undefined\n\n if (sessionRow) {\n if (contextBefore > 0) {\n const rows = db\n .prepare(\n `SELECT mc.message_id as id FROM message_context mc\n WHERE mc.segment_id = ? AND mc.message_id < ?\n ORDER BY mc.message_id DESC LIMIT ?`\n )\n .all(sessionRow.segment_id, messageId, contextBefore) as { id: number }[]\n rows.forEach((r) => contextIds.add(r.id))\n }\n if (contextAfter > 0) {\n const rows = db\n .prepare(\n `SELECT mc.message_id as id FROM message_context mc\n WHERE mc.segment_id = ? AND mc.message_id > ?\n ORDER BY mc.message_id ASC LIMIT ?`\n )\n .all(sessionRow.segment_id, messageId, contextAfter) as { id: number }[]\n rows.forEach((r) => contextIds.add(r.id))\n }\n continue\n }\n }\n\n if (contextBefore > 0) {\n const rows = db\n .prepare('SELECT id FROM message WHERE id < ? ORDER BY id DESC LIMIT ?')\n .all(messageId, contextBefore) as { id: number }[]\n rows.forEach((r) => contextIds.add(r.id))\n }\n if (contextAfter > 0) {\n const rows = db\n .prepare('SELECT id FROM message WHERE id > ? ORDER BY id ASC LIMIT ?')\n .all(messageId, contextAfter) as { id: number }[]\n rows.forEach((r) => contextIds.add(r.id))\n }\n }\n\n return hydrateMessagesByIds(db, Array.from(contextIds))\n}\n\n/**\n * Get conversation messages between two members\n */\nexport function getConversationBetween(\n db: DatabaseAdapter,\n memberId1: number,\n memberId2: number,\n filter?: TimeFilter,\n limit: number = 100\n): ConversationData {\n const member1 = db\n .prepare('SELECT COALESCE(group_nickname, account_name, platform_id) as name FROM member WHERE id = ?')\n .get(memberId1) as { name: string } | undefined\n\n const member2 = db\n .prepare('SELECT COALESCE(group_nickname, account_name, platform_id) as name FROM member WHERE id = ?')\n .get(memberId2) as { name: string } | undefined\n\n if (!member1 || !member2) {\n return { messages: [], total: 0, member1Name: '', member2Name: '' }\n }\n\n const { clause: timeClause, params: timeParams } = buildTimeFilter(filter, 'msg')\n const timeCondition = timeClause ? timeClause.replace('WHERE', 'AND') : ''\n\n const countSql = `\n SELECT COUNT(*) as total FROM message msg\n WHERE msg.sender_id IN (?, ?) ${timeCondition}\n AND msg.content IS NOT NULL AND msg.content != ''\n `\n const totalRow = db.prepare(countSql).get(memberId1, memberId2, ...timeParams) as { total: number }\n\n const sql = `\n SELECT\n msg.id as id, m.id as senderId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,\n m.platform_id as senderPlatformId,\n msg.content as content, msg.ts as timestamp\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE msg.sender_id IN (?, ?) ${timeCondition}\n AND msg.content IS NOT NULL AND msg.content != ''\n ORDER BY msg.ts DESC LIMIT ?\n `\n const rows = db.prepare(sql).all(memberId1, memberId2, ...timeParams, limit) as unknown as ContextMessage[]\n\n return {\n messages: rows.reverse(),\n total: totalRow?.total || 0,\n member1Name: member1.name,\n member2Name: member2.name,\n }\n}\n\n/**\n * Get name change history for a member\n */\nexport function getMemberNameHistory(db: DatabaseAdapter, memberId: number): MemberNameHistoryEntry[] {\n if (!hasTable(db, 'member_name_history')) return getMemberNameHistoryFromMessages(db, memberId)\n\n const history = db\n .prepare(\n `SELECT name_type as nameType, name, start_ts as startTs, end_ts as endTs\n FROM member_name_history WHERE member_id = ? ORDER BY start_ts DESC`\n )\n .all(memberId) as unknown as MemberNameHistoryEntry[]\n\n return history.length > 0 ? history : getMemberNameHistoryFromMessages(db, memberId)\n}\n\n/**\n * Get member list with aliases, avatar and detailed info\n */\nexport function getMembersWithAliases(db: DatabaseAdapter): MemberWithAliases[] {\n const aliasesAvailable = hasColumn(db, 'member', 'aliases')\n const avatarAvailable = hasColumn(db, 'member', 'avatar')\n\n const aliasesSelect = aliasesAvailable ? 'm.aliases' : 'NULL as aliases'\n const avatarSelect = avatarAvailable ? 'm.avatar' : 'NULL as avatar'\n const rows = db\n .prepare(\n `SELECT\n m.id, m.platform_id as platformId,\n m.account_name as accountName, m.group_nickname as groupNickname,\n ${aliasesSelect}, ${avatarSelect},\n COUNT(msg.id) as messageCount\n FROM member m\n LEFT JOIN message msg ON m.id = msg.sender_id\n WHERE COALESCE(m.group_nickname, m.account_name, m.platform_id) != '系统消息'\n GROUP BY m.id ORDER BY messageCount DESC`\n )\n .all() as Array<{\n id: number\n platformId: string\n accountName: string | null\n groupNickname: string | null\n aliases: string | null\n avatar: string | null\n messageCount: number\n }>\n\n return rows.map(mapMemberRow)\n}\n\n/**\n * Paginated member list with search and sort.\n */\nexport function getMembersPaginated(db: DatabaseAdapter, params: MembersPaginationParams): MembersPaginatedResult {\n const page = Math.max(1, params.page ?? 1)\n const pageSize = Math.min(100, Math.max(1, params.pageSize ?? 20))\n const search = params.search?.trim() || ''\n const sortDirection = params.sortOrder === 'asc' ? 'ASC' : 'DESC'\n\n const aliasesAvailable = hasColumn(db, 'member', 'aliases')\n const avatarAvailable = hasColumn(db, 'member', 'avatar')\n const aliasesSelect = aliasesAvailable ? 'm.aliases' : 'NULL as aliases'\n const avatarSelect = avatarAvailable ? 'm.avatar' : 'NULL as avatar'\n\n let searchClause = ''\n const searchParams: unknown[] = []\n if (search) {\n const clauses = ['m.account_name LIKE ?', 'm.group_nickname LIKE ?', 'm.platform_id LIKE ?']\n const like = `%${search}%`\n searchParams.push(like, like, like)\n if (aliasesAvailable) {\n clauses.push('m.aliases LIKE ?')\n searchParams.push(like)\n }\n searchClause = `AND (${clauses.join(' OR ')})`\n }\n\n const systemFilter = \"COALESCE(m.group_nickname, m.account_name, m.platform_id) != '系统消息'\"\n\n const countRow = db\n .prepare(\n `SELECT COUNT(*) as total FROM (\n SELECT m.id FROM member m\n LEFT JOIN message msg ON m.id = msg.sender_id\n WHERE ${systemFilter} ${searchClause}\n GROUP BY m.id\n )`\n )\n .get(...searchParams) as { total: number } | undefined\n\n const total = countRow?.total ?? 0\n const totalPages = Math.ceil(total / pageSize)\n const offset = (page - 1) * pageSize\n\n const rows = db\n .prepare(\n `SELECT\n m.id, m.platform_id as platformId,\n m.account_name as accountName, m.group_nickname as groupNickname,\n ${aliasesSelect}, ${avatarSelect},\n COUNT(msg.id) as messageCount\n FROM member m\n LEFT JOIN message msg ON m.id = msg.sender_id\n WHERE ${systemFilter} ${searchClause}\n GROUP BY m.id\n ORDER BY messageCount ${sortDirection}\n LIMIT ? OFFSET ?`\n )\n .all(...searchParams, pageSize, offset) as Array<{\n id: number\n platformId: string\n accountName: string | null\n groupNickname: string | null\n aliases: string | null\n avatar: string | null\n messageCount: number\n }>\n\n return { members: rows.map(mapMemberRow), total, page, pageSize, totalPages }\n}\n\nfunction mapMemberRow(row: {\n id: number\n platformId: string\n accountName: string | null\n groupNickname: string | null\n aliases: string | null\n avatar: string | null\n messageCount: number\n}): MemberWithAliases {\n return {\n id: row.id,\n platformId: row.platformId,\n accountName: row.accountName,\n groupNickname: row.groupNickname,\n aliases: row.aliases ? JSON.parse(row.aliases) : [],\n avatar: row.avatar ?? null,\n messageCount: row.messageCount,\n }\n}\n\n/**\n * Execute a parameterized read-only SQL query with named bindings.\n * Used by SQL analysis tools to run predefined queries with user-supplied parameters.\n */\nexport function executeParameterizedSql<T = Record<string, unknown>>(\n db: DatabaseAdapter,\n query: string,\n params: Record<string, unknown> = {}\n): T[] {\n const trimmed = query.trim()\n\n const forbidden = /^\\s*(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|ATTACH|DETACH|REINDEX|VACUUM|PRAGMA)/i\n if (forbidden.test(trimmed)) {\n throw new Error('Only SELECT queries are allowed')\n }\n\n const stmt = db.prepare(trimmed)\n\n if (stmt.readonly === false) {\n throw new Error('Only READ-ONLY statements are allowed')\n }\n\n return stmt.all(params) as T[]\n}\n","/**\n * Member write operations (merge, delete, update aliases).\n *\n * All functions accept a DatabaseAdapter, keeping them platform-agnostic.\n * The caller is responsible for opening/closing the DB connection.\n */\n\nimport type { DatabaseAdapter } from '../interfaces/database-adapter'\n\n// ==================== Helpers ====================\n\nfunction parseAliases(raw: string | null): string[] {\n if (!raw) return []\n try {\n const parsed = JSON.parse(raw)\n if (!Array.isArray(parsed)) return []\n return parsed.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)\n } catch {\n return []\n }\n}\n\n// ==================== Public API ====================\n\nexport function updateMemberAliases(db: DatabaseAdapter, memberId: number, aliases: string[]): boolean {\n try {\n db.prepare('UPDATE member SET aliases = ? WHERE id = ?').run(JSON.stringify(aliases), memberId)\n return true\n } catch {\n return false\n }\n}\n\ninterface MemberMergeRow {\n id: number\n platformId: string\n accountName: string | null\n groupNickname: string | null\n aliases: string | null\n avatar: string | null\n messageCount: number\n}\n\n/**\n * Merge two members — messages and name history are reassigned to the one\n * with more messages (or the lower id on tie). The secondary member is deleted.\n */\nexport function mergeMembers(db: DatabaseAdapter, memberId1: number, memberId2: number): boolean {\n if (memberId1 === memberId2) return false\n\n try {\n const rows = db\n .prepare(\n `\n SELECT\n m.id,\n m.platform_id as platformId,\n m.account_name as accountName,\n m.group_nickname as groupNickname,\n m.aliases,\n m.avatar,\n COUNT(msg.id) as messageCount\n FROM member m\n LEFT JOIN message msg ON m.id = msg.sender_id\n WHERE m.id IN (?, ?)\n GROUP BY m.id\n `\n )\n .all(memberId1, memberId2) as unknown as MemberMergeRow[]\n\n if (rows.length !== 2) return false\n\n const [memberA, memberB] = rows\n let primary = memberA\n let secondary = memberB\n\n if (\n memberB.messageCount > memberA.messageCount ||\n (memberB.messageCount === memberA.messageCount && memberB.id < memberA.id)\n ) {\n primary = memberB\n secondary = memberA\n }\n\n const mergedAliases = Array.from(new Set([...parseAliases(primary.aliases), ...parseAliases(secondary.aliases)]))\n const mergedAccountName = primary.accountName || secondary.accountName\n const mergedGroupNickname = primary.groupNickname || secondary.groupNickname\n const mergedAvatar = primary.avatar || secondary.avatar\n\n db.transaction(() => {\n db.prepare('UPDATE message SET sender_id = ? WHERE sender_id = ?').run(primary.id, secondary.id)\n db.prepare('UPDATE member_name_history SET member_id = ? WHERE member_id = ?').run(primary.id, secondary.id)\n db.prepare('UPDATE meta SET owner_id = ? WHERE owner_id = ?').run(primary.platformId, secondary.platformId)\n db.prepare(`UPDATE member SET account_name = ?, group_nickname = ?, avatar = ?, aliases = ? WHERE id = ?`).run(\n mergedAccountName,\n mergedGroupNickname,\n mergedAvatar,\n JSON.stringify(mergedAliases),\n primary.id\n )\n db.prepare('DELETE FROM member WHERE id = ?').run(secondary.id)\n })\n\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Delete a member and all their messages / name history.\n */\nexport function deleteMember(db: DatabaseAdapter, memberId: number): boolean {\n try {\n db.transaction(() => {\n db.prepare('DELETE FROM message WHERE sender_id = ?').run(memberId)\n db.prepare('DELETE FROM member_name_history WHERE member_id = ?').run(memberId)\n db.prepare('DELETE FROM member WHERE id = ?').run(memberId)\n })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Ensure the `aliases` column exists on the `member` table (DDL migration).\n */\nexport function ensureAliasesColumn(db: DatabaseAdapter): boolean {\n const columns = db.prepare('PRAGMA table_info(member)').all() as unknown as Array<{ name: string }>\n const has = columns.some((col) => col.name === 'aliases')\n if (!has) {\n db.exec(\"ALTER TABLE member ADD COLUMN aliases TEXT DEFAULT '[]'\")\n return true\n }\n return false\n}\n\n/**\n * Ensure the `avatar` column exists on the `member` table (DDL migration).\n */\nexport function ensureAvatarColumn(db: DatabaseAdapter): boolean {\n const columns = db.prepare('PRAGMA table_info(member)').all() as unknown as Array<{ name: string }>\n const has = columns.some((col) => col.name === 'avatar')\n if (!has) {\n db.exec('ALTER TABLE member ADD COLUMN avatar TEXT')\n return true\n }\n return false\n}\n","const SYSTEM_PLACEHOLDER_CONTENTS = new Set([\n '[图片]',\n '[视频]',\n '[语音]',\n '[文件]',\n '[动画表情]',\n '[表情]',\n '[链接]',\n '[位置]',\n '[地理位置]',\n '[名片]',\n '[红包]',\n '[转账]',\n '[音乐]',\n '[回复消息]',\n '[Image]',\n '[Photo]',\n '[Video]',\n '[Voice]',\n '[File]',\n '[Sticker]',\n '[Link]',\n '[Location]',\n])\n\nexport function isSystemPlaceholderContent(content: string): boolean {\n return SYSTEM_PLACEHOLDER_CONTENTS.has(content.trim())\n}\n","/**\n * 口头禅分析模块(平台无关)\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../../interfaces'\nimport { buildTimeFilter } from '../filters'\nimport { isSystemPlaceholderContent } from './text-filters'\n\nexport interface CatchphraseItem {\n content: string\n count: number\n}\n\nexport interface MemberCatchphrase {\n memberId: number\n platformId: string\n name: string\n catchphrases: CatchphraseItem[]\n}\n\nexport interface CatchphraseAnalysis {\n members: MemberCatchphrase[]\n}\n\nexport function getCatchphraseAnalysis(db: DatabaseAdapter, filter?: TimeFilter): CatchphraseAnalysis {\n const { clause, params } = buildTimeFilter(filter)\n\n let whereClause = clause\n if (whereClause.includes('WHERE')) {\n whereClause +=\n \" AND COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2\"\n } else {\n whereClause =\n \" WHERE COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2\"\n }\n\n const rows = db\n .prepare(\n `\n SELECT\n m.id as memberId,\n m.platform_id as platformId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,\n TRIM(msg.content) as content,\n COUNT(*) as count\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${whereClause}\n GROUP BY m.id, TRIM(msg.content)\n ORDER BY m.id, count DESC\n `\n )\n .all(...params) as Array<{\n memberId: number\n platformId: string\n name: string\n content: string\n count: number\n }>\n\n const memberMap = new Map<number, MemberCatchphrase>()\n\n for (const row of rows) {\n if (isSystemPlaceholderContent(row.content)) continue\n\n if (!memberMap.has(row.memberId)) {\n memberMap.set(row.memberId, {\n memberId: row.memberId,\n platformId: row.platformId,\n name: row.name,\n catchphrases: [],\n })\n }\n\n const member = memberMap.get(row.memberId)!\n if (member.catchphrases.length < 100) {\n member.catchphrases.push({ content: row.content, count: row.count })\n }\n }\n\n const members = Array.from(memberMap.values())\n members.sort((a, b) => {\n const aTotal = a.catchphrases.reduce((sum, c) => sum + c.count, 0)\n const bTotal = b.catchphrases.reduce((sum, c) => sum + c.count, 0)\n return bTotal - aTotal\n })\n\n return { members }\n}\n","/**\n * 社交分析模块(平台无关)\n * 包含:@ 互动分析、含笑量分析、小团体关系图\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../../interfaces'\nimport { buildTimeFilter } from '../filters'\n\n// ==================== @ 互动分析 ====================\n\nexport function getMentionAnalysis(db: DatabaseAdapter, filter?: TimeFilter): any {\n const emptyResult = {\n topMentioners: [],\n topMentioned: [],\n oneWay: [],\n twoWay: [],\n totalMentions: 0,\n memberDetails: [],\n }\n\n const members = db\n .prepare(\n `SELECT id, platform_id as platformId, COALESCE(group_nickname, account_name, platform_id) as name\n FROM member WHERE COALESCE(account_name, '') != '系统消息'`\n )\n .all() as Array<{ id: number; platformId: string; name: string }>\n\n if (members.length === 0) return emptyResult\n\n const nameToMemberId = new Map<string, number>()\n const memberIdToInfo = new Map<number, { platformId: string; name: string }>()\n\n for (const member of members) {\n memberIdToInfo.set(member.id, { platformId: member.platformId, name: member.name })\n nameToMemberId.set(member.name, member.id)\n\n const history = db.prepare('SELECT name FROM member_name_history WHERE member_id = ?').all(member.id) as Array<{\n name: string\n }>\n\n for (const h of history) {\n if (!nameToMemberId.has(h.name)) {\n nameToMemberId.set(h.name, member.id)\n }\n }\n }\n\n const { clause, params } = buildTimeFilter(filter)\n let whereClause = clause\n if (whereClause.includes('WHERE')) {\n whereClause +=\n \" AND COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'\"\n } else {\n whereClause =\n \" WHERE COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'\"\n }\n\n const messages = db\n .prepare(\n `SELECT msg.sender_id as senderId, msg.content\n FROM message msg JOIN member m ON msg.sender_id = m.id ${whereClause}`\n )\n .all(...params) as Array<{ senderId: number; content: string }>\n\n const mentionMatrix = new Map<number, Map<number, number>>()\n const mentionedCount = new Map<number, number>()\n const mentionerCount = new Map<number, number>()\n let totalMentions = 0\n const mentionRegex = /@([^\\s@]+)/g\n\n for (const msg of messages) {\n const matches = msg.content.matchAll(mentionRegex)\n const mentionedInThisMsg = new Set<number>()\n\n for (const match of matches) {\n const mentionedId = nameToMemberId.get(match[1])\n if (mentionedId && mentionedId !== msg.senderId && !mentionedInThisMsg.has(mentionedId)) {\n mentionedInThisMsg.add(mentionedId)\n totalMentions++\n\n if (!mentionMatrix.has(msg.senderId)) mentionMatrix.set(msg.senderId, new Map())\n const fromMap = mentionMatrix.get(msg.senderId)!\n fromMap.set(mentionedId, (fromMap.get(mentionedId) || 0) + 1)\n\n mentionerCount.set(msg.senderId, (mentionerCount.get(msg.senderId) || 0) + 1)\n mentionedCount.set(mentionedId, (mentionedCount.get(mentionedId) || 0) + 1)\n }\n }\n }\n\n if (totalMentions === 0) return emptyResult\n\n const topMentioners: any[] = []\n for (const [memberId, count] of mentionerCount.entries()) {\n const info = memberIdToInfo.get(memberId)!\n topMentioners.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n count,\n percentage: Math.round((count / totalMentions) * 10000) / 100,\n })\n }\n topMentioners.sort((a, b) => b.count - a.count)\n\n const topMentioned: any[] = []\n for (const [memberId, count] of mentionedCount.entries()) {\n const info = memberIdToInfo.get(memberId)!\n topMentioned.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n count,\n percentage: Math.round((count / totalMentions) * 10000) / 100,\n })\n }\n topMentioned.sort((a, b) => b.count - a.count)\n\n const oneWay: any[] = []\n const processedPairs = new Set<string>()\n for (const [fromId, toMap] of mentionMatrix.entries()) {\n for (const [toId, fromToCount] of toMap.entries()) {\n const pairKey = `${Math.min(fromId, toId)}-${Math.max(fromId, toId)}`\n if (processedPairs.has(pairKey)) continue\n processedPairs.add(pairKey)\n const toFromCount = mentionMatrix.get(toId)?.get(fromId) || 0\n const total = fromToCount + toFromCount\n if (total < 3) continue\n const ratio = fromToCount / total\n if (ratio >= 0.8) {\n const fromInfo = memberIdToInfo.get(fromId)!\n const toInfo = memberIdToInfo.get(toId)!\n oneWay.push({\n fromMemberId: fromId,\n fromName: fromInfo.name,\n toMemberId: toId,\n toName: toInfo.name,\n fromToCount,\n toFromCount,\n ratio: Math.round(ratio * 100) / 100,\n })\n } else if (ratio <= 0.2) {\n const fromInfo = memberIdToInfo.get(fromId)!\n const toInfo = memberIdToInfo.get(toId)!\n oneWay.push({\n fromMemberId: toId,\n fromName: toInfo.name,\n toMemberId: fromId,\n toName: fromInfo.name,\n fromToCount: toFromCount,\n toFromCount: fromToCount,\n ratio: Math.round((1 - ratio) * 100) / 100,\n })\n }\n }\n }\n oneWay.sort((a, b) => b.fromToCount - a.fromToCount)\n\n const twoWay: any[] = []\n processedPairs.clear()\n for (const [fromId, toMap] of mentionMatrix.entries()) {\n for (const [toId, fromToCount] of toMap.entries()) {\n const pairKey = `${Math.min(fromId, toId)}-${Math.max(fromId, toId)}`\n if (processedPairs.has(pairKey)) continue\n processedPairs.add(pairKey)\n const toFromCount = mentionMatrix.get(toId)?.get(fromId) || 0\n const total = fromToCount + toFromCount\n if (total < 5 || toFromCount === 0 || fromToCount === 0) continue\n const ratio = Math.min(fromToCount, toFromCount) / Math.max(fromToCount, toFromCount)\n if (ratio >= 0.3) {\n const m1Info = memberIdToInfo.get(fromId)!\n const m2Info = memberIdToInfo.get(toId)!\n twoWay.push({\n member1Id: fromId,\n member1Name: m1Info.name,\n member2Id: toId,\n member2Name: m2Info.name,\n member1To2: fromToCount,\n member2To1: toFromCount,\n total,\n balance: Math.round(ratio * 100) / 100,\n })\n }\n }\n }\n twoWay.sort((a, b) => b.total - a.total)\n\n const memberDetails: any[] = []\n for (const member of members) {\n const info = memberIdToInfo.get(member.id)!\n const topMentionedByThis: any[] = []\n const toMap = mentionMatrix.get(member.id)\n if (toMap) {\n for (const [toId, count] of toMap.entries()) {\n const toInfo = memberIdToInfo.get(toId)!\n topMentionedByThis.push({\n fromMemberId: member.id,\n fromName: info.name,\n toMemberId: toId,\n toName: toInfo.name,\n count,\n })\n }\n topMentionedByThis.sort((a, b) => b.count - a.count)\n }\n const topMentionersOfThis: any[] = []\n for (const [fromId, fToMap] of mentionMatrix.entries()) {\n const count = fToMap.get(member.id)\n if (count) {\n const fromInfo = memberIdToInfo.get(fromId)!\n topMentionersOfThis.push({\n fromMemberId: fromId,\n fromName: fromInfo.name,\n toMemberId: member.id,\n toName: info.name,\n count,\n })\n }\n }\n topMentionersOfThis.sort((a, b) => b.count - a.count)\n if (topMentionedByThis.length > 0 || topMentionersOfThis.length > 0) {\n memberDetails.push({\n memberId: member.id,\n name: info.name,\n topMentioned: topMentionedByThis.slice(0, 5),\n topMentioners: topMentionersOfThis.slice(0, 5),\n })\n }\n }\n\n return { topMentioners, topMentioned, oneWay, twoWay, totalMentions, memberDetails }\n}\n\n// ==================== @ 互动关系图数据 ====================\n\nexport interface MentionGraphNode {\n id: number\n name: string\n value: number\n symbolSize: number\n}\n\nexport interface MentionGraphLink {\n source: string\n target: string\n value: number\n}\n\nexport interface MentionGraphData {\n nodes: MentionGraphNode[]\n links: MentionGraphLink[]\n maxLinkValue: number\n}\n\nexport function getMentionGraph(db: DatabaseAdapter, filter?: TimeFilter): MentionGraphData {\n const emptyResult: MentionGraphData = { nodes: [], links: [], maxLinkValue: 0 }\n\n const { clause, params } = buildTimeFilter(filter)\n const msgFilterBase = clause ? clause.replace('WHERE', 'AND') : ''\n const msgFilterWithSystem = msgFilterBase + \" AND COALESCE(m.account_name, '') != '系统消息'\"\n\n const members = db\n .prepare(\n `SELECT m.id, m.platform_id as platformId, COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,\n COUNT(msg.id) as messageCount\n FROM member m LEFT JOIN message msg ON m.id = msg.sender_id ${msgFilterWithSystem}\n WHERE COALESCE(m.account_name, '') != '系统消息' GROUP BY m.id`\n )\n .all(...params) as Array<{ id: number; platformId: string; name: string; messageCount: number }>\n\n if (members.length === 0) return emptyResult\n\n const nameToMemberId = new Map<string, number>()\n const memberIdToInfo = new Map<number, { name: string; messageCount: number }>()\n\n for (const member of members) {\n memberIdToInfo.set(member.id, { name: member.name, messageCount: member.messageCount })\n nameToMemberId.set(member.name, member.id)\n const history = db.prepare('SELECT name FROM member_name_history WHERE member_id = ?').all(member.id) as Array<{\n name: string\n }>\n for (const h of history) {\n if (!nameToMemberId.has(h.name)) nameToMemberId.set(h.name, member.id)\n }\n }\n\n let whereClause = clause\n if (whereClause.includes('WHERE')) {\n whereClause +=\n \" AND COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'\"\n } else {\n whereClause =\n \" WHERE COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'\"\n }\n\n const messages = db\n .prepare(\n `SELECT msg.sender_id as senderId, msg.content FROM message msg JOIN member m ON msg.sender_id = m.id ${whereClause}`\n )\n .all(...params) as Array<{ senderId: number; content: string }>\n\n const mentionMatrix = new Map<number, Map<number, number>>()\n const mentionRegex = /@([^\\s@]+)/g\n\n for (const msg of messages) {\n const matches = msg.content.matchAll(mentionRegex)\n const mentionedInThisMsg = new Set<number>()\n for (const match of matches) {\n const mentionedId = nameToMemberId.get(match[1])\n if (mentionedId && mentionedId !== msg.senderId && !mentionedInThisMsg.has(mentionedId)) {\n mentionedInThisMsg.add(mentionedId)\n if (!mentionMatrix.has(msg.senderId)) mentionMatrix.set(msg.senderId, new Map())\n const fromMap = mentionMatrix.get(msg.senderId)!\n fromMap.set(mentionedId, (fromMap.get(mentionedId) || 0) + 1)\n }\n }\n }\n\n const involvedMemberIds = new Set<number>()\n for (const [fromId, toMap] of mentionMatrix.entries()) {\n involvedMemberIds.add(fromId)\n for (const toId of toMap.keys()) involvedMemberIds.add(toId)\n }\n\n const maxMessageCount = Math.max(...members.filter((m) => involvedMemberIds.has(m.id)).map((m) => m.messageCount), 1)\n const nodes: MentionGraphNode[] = []\n for (const memberId of involvedMemberIds) {\n const info = memberIdToInfo.get(memberId)\n if (info) {\n const symbolSize = 20 + (info.messageCount / maxMessageCount) * 40\n nodes.push({ id: memberId, name: info.name, value: info.messageCount, symbolSize: Math.round(symbolSize) })\n }\n }\n\n const links: MentionGraphLink[] = []\n let maxLinkValue = 0\n for (const [fromId, toMap] of mentionMatrix.entries()) {\n const fromInfo = memberIdToInfo.get(fromId)\n if (!fromInfo) continue\n for (const [toId, count] of toMap.entries()) {\n const toInfo = memberIdToInfo.get(toId)\n if (!toInfo) continue\n links.push({ source: fromInfo.name, target: toInfo.name, value: count })\n maxLinkValue = Math.max(maxLinkValue, count)\n }\n }\n\n return { nodes, links, maxLinkValue }\n}\n\n// ==================== 含笑量分析 ====================\n\nfunction keywordToPattern(keyword: string): string {\n const escaped = keyword.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n if (keyword === '哈哈') return '哈哈+'\n return escaped\n}\n\nexport function getLaughAnalysis(db: DatabaseAdapter, filter?: TimeFilter, keywords?: string[]): any {\n const emptyResult = {\n rankByRate: [],\n rankByCount: [],\n typeDistribution: [],\n totalLaughs: 0,\n totalMessages: 0,\n groupLaughRate: 0,\n }\n const laughKeywords = keywords && keywords.length > 0 ? keywords : []\n const patterns = laughKeywords.map(keywordToPattern)\n const laughRegex = new RegExp(`(${patterns.join('|')})`, 'gi')\n\n const { clause, params } = buildTimeFilter(filter)\n let whereClause = clause\n if (whereClause.includes('WHERE')) {\n whereClause += \" AND COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL\"\n } else {\n whereClause = \" WHERE COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL\"\n }\n\n const messages = db\n .prepare(\n `SELECT msg.sender_id as senderId, msg.content, m.platform_id as platformId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name\n FROM message msg JOIN member m ON msg.sender_id = m.id ${whereClause}`\n )\n .all(...params) as Array<{ senderId: number; content: string; platformId: string; name: string }>\n\n if (messages.length === 0) return emptyResult\n\n const memberStats = new Map<\n number,\n { platformId: string; name: string; laughCount: number; messageCount: number; keywordCounts: Map<string, number> }\n >()\n const typeCount = new Map<string, number>()\n let totalLaughs = 0\n\n for (const msg of messages) {\n if (!memberStats.has(msg.senderId)) {\n memberStats.set(msg.senderId, {\n platformId: msg.platformId,\n name: msg.name,\n laughCount: 0,\n messageCount: 0,\n keywordCounts: new Map(),\n })\n }\n const stats = memberStats.get(msg.senderId)!\n stats.messageCount++\n const matches = msg.content.match(laughRegex)\n if (matches) {\n stats.laughCount += matches.length\n totalLaughs += matches.length\n for (const match of matches) {\n let matchedType = '其他'\n for (const keyword of laughKeywords) {\n if (new RegExp(`^${keywordToPattern(keyword)}$`, 'i').test(match)) {\n matchedType = keyword\n break\n }\n }\n typeCount.set(matchedType, (typeCount.get(matchedType) || 0) + 1)\n stats.keywordCounts.set(matchedType, (stats.keywordCounts.get(matchedType) || 0) + 1)\n }\n }\n }\n\n if (totalLaughs === 0) return emptyResult\n\n const rankItems: any[] = []\n for (const [memberId, stats] of memberStats.entries()) {\n if (stats.laughCount > 0) {\n const keywordDistribution: Array<{ keyword: string; count: number; percentage: number }> = []\n for (const keyword of laughKeywords) {\n const count = stats.keywordCounts.get(keyword) || 0\n if (count > 0)\n keywordDistribution.push({ keyword, count, percentage: Math.round((count / stats.laughCount) * 10000) / 100 })\n }\n const otherCount = stats.keywordCounts.get('其他') || 0\n if (otherCount > 0)\n keywordDistribution.push({\n keyword: '其他',\n count: otherCount,\n percentage: Math.round((otherCount / stats.laughCount) * 10000) / 100,\n })\n\n rankItems.push({\n memberId,\n platformId: stats.platformId,\n name: stats.name,\n laughCount: stats.laughCount,\n messageCount: stats.messageCount,\n laughRate: Math.round((stats.laughCount / stats.messageCount) * 10000) / 100,\n percentage: Math.round((stats.laughCount / totalLaughs) * 10000) / 100,\n keywordDistribution,\n })\n }\n }\n\n const typeDistribution: any[] = []\n for (const [type, count] of typeCount.entries()) {\n typeDistribution.push({ type, count, percentage: Math.round((count / totalLaughs) * 10000) / 100 })\n }\n typeDistribution.sort((a, b) => b.count - a.count)\n\n return {\n rankByRate: [...rankItems].sort((a, b) => b.laughRate - a.laughRate),\n rankByCount: [...rankItems].sort((a, b) => b.laughCount - a.laughCount),\n typeDistribution,\n totalLaughs,\n totalMessages: messages.length,\n groupLaughRate: Math.round((totalLaughs / messages.length) * 10000) / 100,\n }\n}\n\n// ==================== 小团体关系图 ====================\n\nexport interface ClusterGraphOptions {\n lookAhead?: number\n decaySeconds?: number\n topEdges?: number\n}\n\nexport interface ClusterGraphNode {\n id: number\n name: string\n messageCount: number\n symbolSize: number\n degree: number\n normalizedDegree: number\n}\n\nexport interface ClusterGraphLink {\n source: string\n target: string\n value: number\n rawScore: number\n expectedScore: number\n coOccurrenceCount: number\n}\n\nexport interface ClusterGraphData {\n nodes: ClusterGraphNode[]\n links: ClusterGraphLink[]\n maxLinkValue: number\n communities: Array<{ id: number; name: string; size: number }>\n stats: {\n totalMembers: number\n totalMessages: number\n involvedMembers: number\n edgeCount: number\n communityCount: number\n }\n}\n\nconst DEFAULT_CLUSTER_OPTIONS = { lookAhead: 3, decaySeconds: 120, topEdges: 100 }\n\nfunction roundNum(value: number, digits = 4): number {\n const factor = 10 ** digits\n return Math.round(value * factor) / factor\n}\n\nfunction clusterPairKey(aId: number, bId: number): string {\n return aId < bId ? `${aId}-${bId}` : `${bId}-${aId}`\n}\n\nexport function getClusterGraph(\n db: DatabaseAdapter,\n filter?: TimeFilter,\n options?: ClusterGraphOptions\n): ClusterGraphData {\n const opts = { ...DEFAULT_CLUSTER_OPTIONS, ...options }\n const emptyResult: ClusterGraphData = {\n nodes: [],\n links: [],\n maxLinkValue: 0,\n communities: [],\n stats: { totalMembers: 0, totalMessages: 0, involvedMembers: 0, edgeCount: 0, communityCount: 0 },\n }\n\n const members = db\n .prepare(\n `SELECT id, platform_id as platformId, COALESCE(group_nickname, account_name, platform_id) as name,\n (SELECT COUNT(*) FROM message WHERE sender_id = member.id) as messageCount\n FROM member WHERE COALESCE(account_name, '') != '系统消息'`\n )\n .all() as Array<{ id: number; platformId: string; name: string; messageCount: number }>\n\n if (members.length < 2) return { ...emptyResult, stats: { ...emptyResult.stats, totalMembers: members.length } }\n\n const memberInfo = new Map<number, { name: string; platformId: string; messageCount: number }>()\n for (const m of members)\n memberInfo.set(m.id, { name: m.name, platformId: m.platformId, messageCount: m.messageCount })\n\n const { clause, params } = buildTimeFilter(filter)\n let whereClause = clause\n if (whereClause.includes('WHERE')) {\n whereClause += \" AND COALESCE(m.account_name, '') != '系统消息'\"\n } else {\n whereClause = \" WHERE COALESCE(m.account_name, '') != '系统消息'\"\n }\n\n const messages = db\n .prepare(\n `SELECT msg.sender_id as senderId, msg.ts as ts FROM message msg JOIN member m ON msg.sender_id = m.id\n ${whereClause} ORDER BY msg.ts ASC, msg.id ASC`\n )\n .all(...params) as Array<{ senderId: number; ts: number }>\n\n if (messages.length < 2)\n return {\n ...emptyResult,\n stats: { ...emptyResult.stats, totalMembers: members.length, totalMessages: messages.length },\n }\n\n const memberMsgCount = new Map<number, number>()\n for (const msg of messages) memberMsgCount.set(msg.senderId, (memberMsgCount.get(msg.senderId) || 0) + 1)\n const totalMessages = messages.length\n\n const pairRawScore = new Map<string, number>()\n const pairCoOccurrence = new Map<string, number>()\n\n for (let i = 0; i < messages.length - 1; i++) {\n const anchor = messages[i]\n const seenPartners = new Set<number>()\n let partnersFound = 0\n for (let j = i + 1; j < messages.length && partnersFound < opts.lookAhead; j++) {\n const candidate = messages[j]\n if (candidate.senderId === anchor.senderId || seenPartners.has(candidate.senderId)) continue\n seenPartners.add(candidate.senderId)\n partnersFound++\n const deltaSeconds = (candidate.ts - anchor.ts) / 1000\n const decayWeight = Math.exp(-deltaSeconds / opts.decaySeconds)\n const positionWeight = 1 - (partnersFound - 1) * 0.2\n const weight = decayWeight * positionWeight\n const key = clusterPairKey(anchor.senderId, candidate.senderId)\n pairRawScore.set(key, (pairRawScore.get(key) || 0) + weight)\n pairCoOccurrence.set(key, (pairCoOccurrence.get(key) || 0) + 1)\n }\n }\n\n const lookAheadFactor = opts.lookAhead * 0.8\n const rawEdges: Array<{\n sourceId: number\n targetId: number\n rawScore: number\n expectedScore: number\n normalizedScore: number\n coOccurrenceCount: number\n }> = []\n\n for (const [key, rawScore] of pairRawScore) {\n const [aIdStr, bIdStr] = key.split('-')\n const aId = parseInt(aIdStr)\n const bId = parseInt(bIdStr)\n const aMsgCount = memberMsgCount.get(aId) || 0\n const bMsgCount = memberMsgCount.get(bId) || 0\n const expectedScore = ((aMsgCount * bMsgCount) / totalMessages) * lookAheadFactor\n const normalizedScore = expectedScore > 0 ? rawScore / expectedScore : 0\n rawEdges.push({\n sourceId: aId,\n targetId: bId,\n rawScore,\n expectedScore,\n normalizedScore,\n coOccurrenceCount: pairCoOccurrence.get(key) || 0,\n })\n }\n\n const maxRawScore = Math.max(...rawEdges.map((e) => e.rawScore), 1)\n const maxNormalizedScore = Math.max(...rawEdges.map((e) => e.normalizedScore), 1)\n\n const edges = rawEdges.map((e) => {\n const hybridScore = 0.5 * (e.rawScore / maxRawScore) + 0.5 * (e.normalizedScore / maxNormalizedScore)\n return {\n ...e,\n rawScore: roundNum(e.rawScore),\n expectedScore: roundNum(e.expectedScore),\n normalizedScore: roundNum(e.normalizedScore),\n hybridScore: roundNum(hybridScore),\n }\n })\n\n edges.sort((a, b) => b.hybridScore - a.hybridScore)\n const keptEdges = edges.slice(0, opts.topEdges)\n\n if (keptEdges.length === 0)\n return {\n ...emptyResult,\n stats: { ...emptyResult.stats, totalMembers: members.length, totalMessages: messages.length },\n }\n\n const involvedIds = new Set<number>()\n for (const edge of keptEdges) {\n involvedIds.add(edge.sourceId)\n involvedIds.add(edge.targetId)\n }\n\n const nodeDegree = new Map<number, number>()\n for (const edge of keptEdges) {\n nodeDegree.set(edge.sourceId, (nodeDegree.get(edge.sourceId) || 0) + edge.hybridScore)\n nodeDegree.set(edge.targetId, (nodeDegree.get(edge.targetId) || 0) + edge.hybridScore)\n }\n const maxDegree = Math.max(...nodeDegree.values(), 1)\n\n const nameCount = new Map<string, number>()\n for (const id of involvedIds) {\n const name = memberInfo.get(id)?.name || String(id)\n nameCount.set(name, (nameCount.get(name) || 0) + 1)\n }\n\n const displayNames = new Map<number, string>()\n for (const id of involvedIds) {\n const info = memberInfo.get(id)\n const baseName = info?.name || String(id)\n displayNames.set(\n id,\n (nameCount.get(baseName) || 0) > 1 ? `${baseName}#${(info?.platformId || String(id)).slice(-4)}` : baseName\n )\n }\n\n const maxMsgCount = Math.max(...[...involvedIds].map((id) => memberInfo.get(id)?.messageCount || 0), 1)\n const nodes: ClusterGraphNode[] = [...involvedIds].map((id) => {\n const info = memberInfo.get(id)!\n const degree = nodeDegree.get(id) || 0\n const normalizedDegree = degree / maxDegree\n const msgNorm = info.messageCount / maxMsgCount\n const symbolSize = 20 + (0.7 * normalizedDegree + 0.3 * msgNorm) * 35\n return {\n id,\n name: displayNames.get(id)!,\n messageCount: info.messageCount,\n symbolSize: Math.round(symbolSize),\n degree: roundNum(degree),\n normalizedDegree: roundNum(normalizedDegree),\n }\n })\n nodes.sort((a, b) => b.degree - a.degree)\n\n const maxLinkValue = keptEdges.length > 0 ? Math.max(...keptEdges.map((e) => e.hybridScore)) : 0\n const links: ClusterGraphLink[] = keptEdges.map((e) => ({\n source: displayNames.get(e.sourceId)!,\n target: displayNames.get(e.targetId)!,\n value: e.hybridScore,\n rawScore: e.rawScore,\n expectedScore: e.expectedScore,\n coOccurrenceCount: e.coOccurrenceCount,\n }))\n\n return {\n nodes,\n links,\n maxLinkValue: roundNum(maxLinkValue),\n communities: [],\n stats: {\n totalMembers: members.length,\n totalMessages: messages.length,\n involvedMembers: involvedIds.size,\n edgeCount: keptEdges.length,\n communityCount: 0,\n },\n }\n}\n","/**\n * 关系分析模块(私聊专属,平台无关)\n * 基于会话索引统计双方的主动发起、收尾、破冰、响应时延、锲而不舍行为\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../../interfaces'\n\nexport interface RelationshipMonthStats {\n month: string\n members: Array<{ memberId: number; name: string; initiateCount: number; closeCount: number }>\n totalSessions: number\n}\n\nexport interface IceBreakerItem {\n month: string\n memberId: number\n name: string\n count: number\n}\n\nexport interface ResponseLatencyMember {\n memberId: number\n name: string\n avgResponseTime: number\n totalResponses: number\n}\n\nexport interface PerseveranceMember {\n memberId: number\n name: string\n totalDoubleTexts: number\n}\n\nexport interface MonthlyResponseLatency {\n month: string\n members: Array<{ memberId: number; name: string; avgResponseTime: number; responseCount: number }>\n}\n\nexport interface MonthlyPerseverance {\n month: string\n members: Array<{ memberId: number; name: string; doubleTextCount: number }>\n}\n\nexport interface RelationshipOptions {\n perseveranceThreshold?: number\n}\n\nexport interface RelationshipStats {\n months: RelationshipMonthStats[]\n members: Array<{ memberId: number; name: string; totalInitiateCount: number; totalCloseCount: number }>\n totalSessions: number\n hasSessionIndex: boolean\n iceBreakers: IceBreakerItem[]\n totalIceBreaks: number\n responseLatency: ResponseLatencyMember[]\n perseverance: PerseveranceMember[]\n totalDoubleTexts: number\n monthlyResponseLatency: MonthlyResponseLatency[]\n monthlyPerseverance: MonthlyPerseverance[]\n perseveranceThreshold: number\n}\n\nconst ICE_BREAK_THRESHOLD = 24 * 60 * 60\nconst DEFAULT_PERSEVERANCE_THRESHOLD = 300\n\nexport function getRelationshipStats(\n db: DatabaseAdapter,\n filter?: TimeFilter,\n options?: RelationshipOptions\n): RelationshipStats {\n const perseveranceThreshold = options?.perseveranceThreshold ?? DEFAULT_PERSEVERANCE_THRESHOLD\n const emptyResult: RelationshipStats = {\n months: [],\n members: [],\n totalSessions: 0,\n hasSessionIndex: false,\n iceBreakers: [],\n totalIceBreaks: 0,\n responseLatency: [],\n perseverance: [],\n totalDoubleTexts: 0,\n monthlyResponseLatency: [],\n monthlyPerseverance: [],\n perseveranceThreshold,\n }\n\n const sessionCount = db.prepare('SELECT COUNT(*) as count FROM segment').get() as { count: number } | undefined\n if (!sessionCount || sessionCount.count === 0) return emptyResult\n\n const timeConditions: string[] = []\n const params: (number | string)[] = []\n\n if (filter?.startTs !== undefined) {\n timeConditions.push('cs.start_ts >= ?')\n params.push(filter.startTs)\n }\n if (filter?.endTs !== undefined) {\n timeConditions.push('cs.start_ts <= ?')\n params.push(filter.endTs)\n }\n\n const whereClause = timeConditions.length > 0 ? `WHERE ${timeConditions.join(' AND ')}` : ''\n\n const segmentRows = db\n .prepare(\n `SELECT cs.id AS segment_id, cs.start_ts, cs.end_ts,\n (SELECT m.sender_id FROM message_context mc JOIN message m ON m.id = mc.message_id\n WHERE mc.segment_id = cs.id ORDER BY m.ts ASC, m.id ASC LIMIT 1) AS initiator_id,\n (SELECT m.sender_id FROM message_context mc JOIN message m ON m.id = mc.message_id\n WHERE mc.segment_id = cs.id ORDER BY m.ts DESC, m.id DESC LIMIT 1) AS closer_id\n FROM segment cs ${whereClause} ORDER BY cs.start_ts ASC`\n )\n .all(...params) as Array<{\n segment_id: number\n start_ts: number\n end_ts: number\n initiator_id: number | null\n closer_id: number | null\n }>\n\n const memberNames = new Map<number, string>()\n const memberRows = db\n .prepare('SELECT id, COALESCE(group_nickname, account_name, platform_id) as name FROM member')\n .all() as Array<{ id: number; name: string }>\n for (const row of memberRows) memberNames.set(row.id, row.name)\n\n const monthMap = new Map<\n string,\n { initiateMap: Map<number, number>; closeMap: Map<number, number>; totalSessions: number }\n >()\n const memberInitTotals = new Map<number, number>()\n const memberCloseTotals = new Map<number, number>()\n const iceBreakMap = new Map<string, Map<number, number>>()\n let totalIceBreaks = 0\n let prevEndTs: number | null = null\n\n for (const row of segmentRows) {\n const month = toLocalMonth(row.start_ts)\n if (!monthMap.has(month)) monthMap.set(month, { initiateMap: new Map(), closeMap: new Map(), totalSessions: 0 })\n const ms = monthMap.get(month)!\n ms.totalSessions++\n\n if (row.initiator_id !== null) {\n ms.initiateMap.set(row.initiator_id, (ms.initiateMap.get(row.initiator_id) ?? 0) + 1)\n memberInitTotals.set(row.initiator_id, (memberInitTotals.get(row.initiator_id) ?? 0) + 1)\n }\n if (row.closer_id !== null) {\n ms.closeMap.set(row.closer_id, (ms.closeMap.get(row.closer_id) ?? 0) + 1)\n memberCloseTotals.set(row.closer_id, (memberCloseTotals.get(row.closer_id) ?? 0) + 1)\n }\n if (prevEndTs !== null && row.initiator_id !== null && row.start_ts - prevEndTs > ICE_BREAK_THRESHOLD) {\n if (!iceBreakMap.has(month)) iceBreakMap.set(month, new Map())\n const mMap = iceBreakMap.get(month)!\n mMap.set(row.initiator_id, (mMap.get(row.initiator_id) ?? 0) + 1)\n totalIceBreaks++\n }\n prevEndTs = row.end_ts\n }\n\n const allMemberIds = new Set<number>()\n for (const id of memberInitTotals.keys()) allMemberIds.add(id)\n for (const id of memberCloseTotals.keys()) allMemberIds.add(id)\n\n const monthKeys = Array.from(monthMap.keys()).sort((a, b) => b.localeCompare(a))\n const months: RelationshipMonthStats[] = monthKeys.map((month) => {\n const ms = monthMap.get(month)!\n const members = Array.from(allMemberIds).map((memberId) => ({\n memberId,\n name: memberNames.get(memberId) ?? `Unknown(${memberId})`,\n initiateCount: ms.initiateMap.get(memberId) ?? 0,\n closeCount: ms.closeMap.get(memberId) ?? 0,\n }))\n return { month, members, totalSessions: ms.totalSessions }\n })\n\n const members = Array.from(allMemberIds)\n .map((memberId) => ({\n memberId,\n name: memberNames.get(memberId) ?? `Unknown(${memberId})`,\n totalInitiateCount: memberInitTotals.get(memberId) ?? 0,\n totalCloseCount: memberCloseTotals.get(memberId) ?? 0,\n }))\n .sort((a, b) => b.totalInitiateCount - a.totalInitiateCount)\n\n const iceBreakers: IceBreakerItem[] = []\n for (const month of monthKeys) {\n const mMap = iceBreakMap.get(month)\n if (!mMap) continue\n for (const [memberId, count] of mMap) {\n iceBreakers.push({ month, memberId, name: memberNames.get(memberId) ?? `Unknown(${memberId})`, count })\n }\n }\n\n const segmentIdList = segmentRows.map((r) => r.segment_id)\n const msgStats = queryMessageLevelStats(db, segmentIdList, memberNames, perseveranceThreshold)\n\n return {\n months,\n members,\n totalSessions: segmentRows.length,\n hasSessionIndex: true,\n iceBreakers,\n totalIceBreaks,\n ...msgStats,\n perseveranceThreshold,\n }\n}\n\nfunction queryMessageLevelStats(\n db: DatabaseAdapter,\n segmentIds: number[],\n memberNames: Map<number, string>,\n perseveranceThreshold: number\n): {\n responseLatency: ResponseLatencyMember[]\n perseverance: PerseveranceMember[]\n totalDoubleTexts: number\n monthlyResponseLatency: MonthlyResponseLatency[]\n monthlyPerseverance: MonthlyPerseverance[]\n} {\n const empty = {\n responseLatency: [],\n perseverance: [],\n totalDoubleTexts: 0,\n monthlyResponseLatency: [],\n monthlyPerseverance: [],\n }\n if (segmentIds.length === 0) return empty\n\n const BATCH_SIZE = 500\n const responseTotals = new Map<number, { sum: number; count: number }>()\n const dtTotals = new Map<number, number>()\n const monthlyRespMap = new Map<string, Map<number, { sum: number; count: number }>>()\n const monthlyDtMap = new Map<string, Map<number, number>>()\n\n for (let i = 0; i < segmentIds.length; i += BATCH_SIZE) {\n const batch = segmentIds.slice(i, i + BATCH_SIZE)\n const placeholders = batch.map(() => '?').join(',')\n\n const latencyRows = db\n .prepare(\n `WITH msg_lag AS (\n SELECT m.sender_id, m.ts,\n LAG(m.sender_id) OVER (PARTITION BY mc.segment_id ORDER BY m.ts, m.id) AS prev_sender_id,\n LAG(m.ts) OVER (PARTITION BY mc.segment_id ORDER BY m.ts, m.id) AS prev_ts\n FROM message_context mc JOIN message m ON m.id = mc.message_id\n WHERE mc.segment_id IN (${placeholders}) AND m.type = 0\n )\n SELECT strftime('%Y-%m', datetime(ts, 'unixepoch', 'localtime')) AS month,\n sender_id AS responder_id, SUM(ts - prev_ts) AS total_time, COUNT(*) AS response_count\n FROM msg_lag WHERE prev_sender_id IS NOT NULL AND sender_id != prev_sender_id\n GROUP BY month, responder_id`\n )\n .all(...batch) as Array<{ month: string; responder_id: number; total_time: number; response_count: number }>\n\n for (const row of latencyRows) {\n const existing = responseTotals.get(row.responder_id)\n if (existing) {\n existing.sum += row.total_time\n existing.count += row.response_count\n } else responseTotals.set(row.responder_id, { sum: row.total_time, count: row.response_count })\n\n if (!monthlyRespMap.has(row.month)) monthlyRespMap.set(row.month, new Map())\n const mMap = monthlyRespMap.get(row.month)!\n const mExisting = mMap.get(row.responder_id)\n if (mExisting) {\n mExisting.sum += row.total_time\n mExisting.count += row.response_count\n } else mMap.set(row.responder_id, { sum: row.total_time, count: row.response_count })\n }\n\n const dtRows = db\n .prepare(\n `WITH msg_lag AS (\n SELECT m.sender_id, m.ts,\n LAG(m.sender_id) OVER (PARTITION BY mc.segment_id ORDER BY m.ts, m.id) AS prev_sender_id,\n LAG(m.ts) OVER (PARTITION BY mc.segment_id ORDER BY m.ts, m.id) AS prev_ts\n FROM message_context mc JOIN message m ON m.id = mc.message_id\n WHERE mc.segment_id IN (${placeholders}) AND m.type = 0\n )\n SELECT strftime('%Y-%m', datetime(ts, 'unixepoch', 'localtime')) AS month,\n sender_id, COUNT(*) AS double_text_count\n FROM msg_lag WHERE prev_sender_id IS NOT NULL AND sender_id = prev_sender_id AND (ts - prev_ts) >= ?\n GROUP BY month, sender_id`\n )\n .all(...batch, perseveranceThreshold) as Array<{ month: string; sender_id: number; double_text_count: number }>\n\n for (const row of dtRows) {\n dtTotals.set(row.sender_id, (dtTotals.get(row.sender_id) ?? 0) + row.double_text_count)\n if (!monthlyDtMap.has(row.month)) monthlyDtMap.set(row.month, new Map())\n const mMap = monthlyDtMap.get(row.month)!\n mMap.set(row.sender_id, (mMap.get(row.sender_id) ?? 0) + row.double_text_count)\n }\n }\n\n const responseLatency: ResponseLatencyMember[] = Array.from(responseTotals.entries())\n .map(([memberId, { sum, count }]) => ({\n memberId,\n name: memberNames.get(memberId) ?? `Unknown(${memberId})`,\n avgResponseTime: Math.round(sum / count),\n totalResponses: count,\n }))\n .sort((a, b) => a.avgResponseTime - b.avgResponseTime)\n\n let totalDoubleTexts = 0\n const perseverance: PerseveranceMember[] = Array.from(dtTotals.entries())\n .map(([memberId, count]) => {\n totalDoubleTexts += count\n return { memberId, name: memberNames.get(memberId) ?? `Unknown(${memberId})`, totalDoubleTexts: count }\n })\n .sort((a, b) => b.totalDoubleTexts - a.totalDoubleTexts)\n\n const monthlyResponseLatency: MonthlyResponseLatency[] = Array.from(monthlyRespMap.entries())\n .sort(([a], [b]) => b.localeCompare(a))\n .map(([month, mMap]) => ({\n month,\n members: Array.from(mMap.entries())\n .map(([memberId, { sum, count }]) => ({\n memberId,\n name: memberNames.get(memberId) ?? `Unknown(${memberId})`,\n avgResponseTime: Math.round(sum / count),\n responseCount: count,\n }))\n .sort((a, b) => a.avgResponseTime - b.avgResponseTime),\n }))\n\n const monthlyPerseverance: MonthlyPerseverance[] = Array.from(monthlyDtMap.entries())\n .sort(([a], [b]) => b.localeCompare(a))\n .map(([month, mMap]) => ({\n month,\n members: Array.from(mMap.entries())\n .map(([memberId, count]) => ({\n memberId,\n name: memberNames.get(memberId) ?? `Unknown(${memberId})`,\n doubleTextCount: count,\n }))\n .sort((a, b) => b.doubleTextCount - a.doubleTextCount),\n }))\n\n return { responseLatency, perseverance, totalDoubleTexts, monthlyResponseLatency, monthlyPerseverance }\n}\n\nfunction toLocalMonth(ts: number): string {\n const d = new Date(ts * 1000)\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`\n}\n","/**\n * 语言偏好分析模块(私聊专用,平台无关)\n *\n * NLP 能力通过 NlpProvider 接口注入,不直接依赖 @node-rs/jieba。\n * 调用方负责提供对应平台的实现(Electron / Server 用 jieba,浏览器可降级)。\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../../interfaces'\nimport { buildTimeFilter } from '../filters'\nimport { isSystemPlaceholderContent } from './text-filters'\n\n// ==================== NLP Provider 接口 ====================\n\nexport interface PosTagResult {\n word: string\n tag: string\n}\n\nexport interface NlpProvider {\n tag(text: string): PosTagResult[]\n isStopword(word: string, locale: string): boolean\n meaningfulPosTags: Set<string>\n}\n\n// ==================== 内部常量 ====================\n\nconst RE_ELLIPSIS = /\\.{2,}|…+|。{2,}/g\nconst RE_EXCLAMATION = /[!!]+/g\nconst RE_QUESTION = /[??]+/g\nconst RE_TILDE = /[~~]+/g\nconst RE_PERIOD = /[.。](?![.。])/g\nconst RE_ENDS_WITH_PUNCT = /[.。!!??~~…,,;;::、)\\])】》\"'」』\\-—]$/\n\nconst NOUN_TAGS = new Set(['n', 'nr', 'ns', 'nt', 'nz', 'nw'])\nconst VERB_TAGS = new Set(['v', 'vn', 'vd', 'vg'])\nconst ADJ_TAGS = new Set(['a', 'an', 'ad', 'ag'])\nconst ADV_TAGS = new Set(['d'])\nconst MODAL_TAGS = new Set(['y', 'e'])\n\nconst RE_URL = /https?:\\/\\/[^\\s]+/g\nconst RE_MENTION = /@[^\\s@]+/g\nconst RE_EMOJI =\n /[\\u{1F600}-\\u{1F64F}\\u{1F300}-\\u{1F5FF}\\u{1F680}-\\u{1F6FF}\\u{1F1E0}-\\u{1F1FF}\\u{2600}-\\u{26FF}\\u{2700}-\\u{27BF}]/gu\nconst RE_PUNCTUATION = /[!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~,。!?、;:\"\"''()【】《》…—~·\\s]/g\nconst RE_PURE_NUMBER = /^\\d+$/\n\nfunction cleanTextForNlp(text: string): string {\n return text\n .replace(RE_URL, ' ')\n .replace(RE_MENTION, ' ')\n .replace(RE_EMOJI, ' ')\n .replace(RE_PUNCTUATION, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction countMatches(text: string, regex: RegExp): number {\n regex.lastIndex = 0\n const m = text.match(regex)\n return m ? m.length : 0\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0,\n magA = 0,\n magB = 0\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i]\n magA += a[i] * a[i]\n magB += b[i] * b[i]\n }\n const denom = Math.sqrt(magA) * Math.sqrt(magB)\n return denom === 0 ? 0 : dot / denom\n}\n\n// ==================== 主入口 ====================\n\nexport interface LanguagePreferenceParams {\n locale: string\n timeFilter?: TimeFilter\n nlpProvider?: NlpProvider\n}\n\nexport function getLanguagePreferenceAnalysis(db: DatabaseAdapter, params: LanguagePreferenceParams): any {\n const { locale, timeFilter, nlpProvider } = params\n\n const { clause, params: filterParams } = buildTimeFilter(timeFilter)\n let whereClause = clause\n const textFilter =\n \" COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2\"\n if (whereClause.includes('WHERE')) {\n whereClause += ' AND ' + textFilter\n } else {\n whereClause = ' WHERE ' + textFilter\n }\n\n const rows = db\n .prepare(\n `SELECT m.id as memberId, COALESCE(m.group_nickname, m.account_name, m.platform_id) as name, msg.content as content\n FROM message msg JOIN member m ON msg.sender_id = m.id ${whereClause} ORDER BY m.id`\n )\n .all(...filterParams) as Array<{ memberId: number; name: string; content: string }>\n\n if (rows.length === 0) return { members: [], sharedWords: [], similarityScore: 0 }\n\n const memberMessages = new Map<number, { name: string; messages: string[] }>()\n for (const row of rows) {\n if (isSystemPlaceholderContent(row.content)) continue\n\n let entry = memberMessages.get(row.memberId)\n if (!entry) {\n entry = { name: row.name, messages: [] }\n memberMessages.set(row.memberId, entry)\n }\n entry.messages.push(row.content)\n }\n\n const isChinese = locale.startsWith('zh')\n const minWordLength = isChinese ? 2 : 3\n\n const memberProfiles: any[] = []\n\n for (const [memberId, { name, messages }] of memberMessages) {\n const wordFreq = new Map<string, number>()\n const posCount = { noun: 0, verb: 0, adjective: 0, adverb: 0, modalParticle: 0, interjection: 0, other: 0 }\n const modalFreq = new Map<string, number>()\n let totalWordCount = 0\n const punct = { ellipsis: 0, exclamation: 0, question: 0, tilde: 0, period: 0, noPunct: 0, total: 0 }\n const phraseFreq = new Map<string, number>()\n\n for (const content of messages) {\n punct.ellipsis += countMatches(content, RE_ELLIPSIS)\n punct.exclamation += countMatches(content, RE_EXCLAMATION)\n punct.question += countMatches(content, RE_QUESTION)\n punct.tilde += countMatches(content, RE_TILDE)\n punct.period += countMatches(content, RE_PERIOD)\n const trimmed = content.trim()\n\n if (trimmed.length > 0 && !RE_ENDS_WITH_PUNCT.test(trimmed)) punct.noPunct++\n punct.total++\n\n if (trimmed.length >= 2) phraseFreq.set(trimmed, (phraseFreq.get(trimmed) || 0) + 1)\n\n const cleaned = cleanTextForNlp(content)\n if (!cleaned) continue\n\n if (isChinese && nlpProvider) {\n try {\n const tagged = nlpProvider.tag(cleaned)\n for (const { word, tag } of tagged) {\n if (!word || word.trim().length === 0 || RE_PURE_NUMBER.test(word)) continue\n if (word.length < minWordLength && !MODAL_TAGS.has(tag)) continue\n\n if (NOUN_TAGS.has(tag)) posCount.noun++\n else if (VERB_TAGS.has(tag)) posCount.verb++\n else if (ADJ_TAGS.has(tag)) posCount.adjective++\n else if (ADV_TAGS.has(tag)) posCount.adverb++\n else if (tag === 'y') posCount.modalParticle++\n else if (tag === 'e') posCount.interjection++\n else posCount.other++\n\n if (MODAL_TAGS.has(tag)) modalFreq.set(word, (modalFreq.get(word) || 0) + 1)\n\n if (nlpProvider.meaningfulPosTags.has(tag) || MODAL_TAGS.has(tag)) {\n if (!nlpProvider.isStopword(word, locale)) {\n wordFreq.set(word, (wordFreq.get(word) || 0) + 1)\n totalWordCount++\n }\n }\n }\n } catch {\n /* jieba failure — skip */\n }\n } else {\n try {\n const segmenter = new Intl.Segmenter(locale, { granularity: 'word' })\n for (const seg of segmenter.segment(cleaned)) {\n if (!seg.isWordLike) continue\n const w = seg.segment.toLowerCase()\n if (w.length < minWordLength || RE_PURE_NUMBER.test(w)) continue\n if (nlpProvider?.isStopword(w, locale)) continue\n wordFreq.set(w, (wordFreq.get(w) || 0) + 1)\n totalWordCount++\n posCount.other++\n }\n } catch {\n /* fallback */\n }\n }\n }\n\n const filteredWords = [...wordFreq.entries()].filter(([, c]) => c >= 2).sort((a, b) => b[1] - a[1])\n const uniqueWords = filteredWords.length\n const topWords = filteredWords.slice(0, 100).map(([word, count]) => ({ word, count }))\n const lexicalDiversity = totalWordCount > 0 ? Math.round((uniqueWords / totalWordCount) * 10000) / 100 : 0\n\n const modalParticles = [...modalFreq.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 20)\n .map(([word, count]) => ({ word, count }))\n const catchphrases = [...phraseFreq.entries()]\n .filter(([, c]) => c >= 2)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 50)\n .map(([content, count]) => ({ content, count }))\n\n memberProfiles.push({\n memberId,\n name,\n totalMessages: messages.length,\n totalWords: totalWordCount,\n uniqueWords,\n lexicalDiversity,\n topWords,\n posDistribution: posCount,\n modalParticles,\n punctuation: punct,\n catchphrases,\n })\n }\n\n memberProfiles.sort((a, b) => b.totalMessages - a.totalMessages)\n\n let sharedWords: any[] = []\n let similarityScore = 0\n\n if (memberProfiles.length >= 2) {\n const a = memberProfiles[0]\n const b = memberProfiles[1]\n const wordsA = new Map<string, number>(a.topWords.map((w: any) => [w.word, w.count]))\n const wordsB = new Map<string, number>(b.topWords.map((w: any) => [w.word, w.count]))\n const shared: Array<{ word: string; countA: number; countB: number }> = []\n for (const [word, countA] of wordsA) {\n const countB = wordsB.get(word)\n if (countB) shared.push({ word, countA, countB })\n }\n shared.sort((x, y) => y.countA + y.countB - (x.countA + x.countB))\n sharedWords = shared.slice(0, 30)\n\n const posKeys = ['noun', 'verb', 'adjective', 'adverb', 'modalParticle', 'interjection', 'other'] as const\n const vecA = posKeys.map((k) => a.posDistribution[k] as number)\n const vecB = posKeys.map((k) => b.posDistribution[k] as number)\n similarityScore = Math.round(cosineSimilarity(vecA, vecB) * 100)\n }\n\n return { members: memberProfiles, sharedWords, similarityScore }\n}\n","/**\n * 榜单分析模块(平台无关)\n *\n * 包含:龙王、潜水、打卡、斗图、夜猫、复读分析。\n * 算法从原前端 pluginCompute 移植,行为与现状保持一致(含原有时区/边界处理)。\n * 所有函数接收 DatabaseAdapter,不依赖全局状态。\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../../interfaces'\n\n// ==================== 类型定义 ====================\n\nexport type NightOwlTitle = '养生达人' | '偶尔失眠' | '经常失眠' | '夜猫子' | '秃头预备役' | '修仙练习生' | '守夜冠军'\n\nexport interface NightOwlRankItem {\n memberId: number\n platformId: string\n name: string\n totalNightMessages: number\n title: NightOwlTitle\n hourlyBreakdown: {\n h23: number\n h0: number\n h1: number\n h2: number\n h3to4: number\n }\n percentage: number\n}\n\nexport interface TimeRankItem {\n memberId: number\n platformId: string\n name: string\n count: number\n avgTime: string\n extremeTime: string\n percentage: number\n}\n\nexport interface ConsecutiveNightRecord {\n memberId: number\n platformId: string\n name: string\n maxConsecutiveDays: number\n currentStreak: number\n}\n\nexport interface NightOwlChampion {\n memberId: number\n platformId: string\n name: string\n score: number\n nightMessages: number\n lastSpeakerCount: number\n consecutiveDays: number\n}\n\nexport interface NightOwlAnalysis {\n nightOwlRank: NightOwlRankItem[]\n lastSpeakerRank: TimeRankItem[]\n firstSpeakerRank: TimeRankItem[]\n consecutiveRecords: ConsecutiveNightRecord[]\n champions: NightOwlChampion[]\n totalDays: number\n}\n\nexport interface DragonKingRankItem {\n memberId: number\n platformId: string\n name: string\n count: number\n percentage: number\n}\n\nexport interface DragonKingAnalysis {\n rank: DragonKingRankItem[]\n totalDays: number\n}\n\nexport interface DivingRankItem {\n memberId: number\n platformId: string\n name: string\n lastMessageTs: number\n daysSinceLastMessage: number\n}\n\nexport interface DivingAnalysis {\n rank: DivingRankItem[]\n}\n\nexport interface RepeatStatItem {\n memberId: number\n platformId: string\n name: string\n count: number\n percentage: number\n}\n\nexport interface RepeatRateItem {\n memberId: number\n platformId: string\n name: string\n count: number\n totalMessages: number\n rate: number\n}\n\nexport interface ChainLengthDistribution {\n length: number\n count: number\n}\n\nexport interface HotRepeatContent {\n content: string\n count: number\n maxChainLength: number\n originatorName: string\n lastTs: number\n firstMessageId: number\n}\n\nexport interface FastestRepeaterItem {\n memberId: number\n platformId: string\n name: string\n count: number\n avgTimeDiff: number\n}\n\nexport interface RepeatAnalysis {\n originators: RepeatStatItem[]\n initiators: RepeatStatItem[]\n breakers: RepeatStatItem[]\n fastestRepeaters: FastestRepeaterItem[]\n originatorRates: RepeatRateItem[]\n initiatorRates: RepeatRateItem[]\n breakerRates: RepeatRateItem[]\n chainLengthDistribution: ChainLengthDistribution[]\n hotContents: HotRepeatContent[]\n avgChainLength: number\n totalRepeatChains: number\n}\n\nexport interface MemeBattleRankItem {\n memberId: number\n platformId: string\n name: string\n count: number\n percentage: number\n}\n\nexport interface MemeBattleRecord {\n startTime: number\n endTime: number\n totalImages: number\n participantCount: number\n participants: Array<{\n memberId: number\n name: string\n imageCount: number\n }>\n}\n\nexport interface MemeBattleAnalysis {\n topBattles: MemeBattleRecord[]\n rankByCount: MemeBattleRankItem[]\n rankByImageCount: MemeBattleRankItem[]\n totalBattles: number\n}\n\nexport interface StreakRankItem {\n memberId: number\n name: string\n maxStreak: number\n maxStreakStart: string\n maxStreakEnd: string\n currentStreak: number\n}\n\nexport interface LoyaltyRankItem {\n memberId: number\n name: string\n totalDays: number\n percentage: number\n}\n\nexport interface CheckInAnalysis {\n streakRank: StreakRankItem[]\n loyaltyRank: LoyaltyRankItem[]\n totalDays: number\n}\n\n// ==================== 过滤条件构建 ====================\n\n/** 系统消息过滤条件(始终排除),与前端历史实现保持一致 */\nconst SYSTEM_FILTER = \"AND COALESCE(m.account_name, '') != '系统消息'\"\n\n/** 构建 AND 形式的时间/成员过滤条件(msg 别名),与原前端 SQL 保持等价 */\nfunction buildFilter(filter?: TimeFilter): { conditions: string; params: (number | string)[] } {\n const parts: string[] = []\n const params: (number | string)[] = []\n if (filter?.startTs != null) {\n parts.push('AND msg.ts >= ?')\n params.push(filter.startTs)\n }\n if (filter?.endTs != null) {\n parts.push('AND msg.ts <= ?')\n params.push(filter.endTs)\n }\n if (filter?.memberId != null) {\n parts.push('AND msg.sender_id = ?')\n params.push(filter.memberId)\n }\n return { conditions: parts.join(' '), params }\n}\n\n// ==================== 龙王分析 ====================\n\nexport function getDragonKingAnalysis(db: DatabaseAdapter, filter?: TimeFilter): DragonKingAnalysis {\n const { conditions, params } = buildFilter(filter)\n\n const rankRows = db\n .prepare(\n `WITH daily_counts AS (\n SELECT\n strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime') as date,\n msg.sender_id,\n m.platform_id,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,\n COUNT(*) as msg_count\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE 1=1 ${SYSTEM_FILTER} ${conditions}\n GROUP BY date, msg.sender_id\n ),\n daily_max AS (\n SELECT date, MAX(msg_count) as max_count\n FROM daily_counts\n GROUP BY date\n )\n SELECT dc.sender_id, dc.platform_id, dc.name, COUNT(*) as dragon_days\n FROM daily_counts dc\n JOIN daily_max dm ON dc.date = dm.date AND dc.msg_count = dm.max_count\n GROUP BY dc.sender_id\n ORDER BY dragon_days DESC`\n )\n .all(...params) as Array<{ sender_id: number; platform_id: string; name: string; dragon_days: number }>\n\n const totalRow = db\n .prepare(\n `SELECT COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as total\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE 1=1 ${SYSTEM_FILTER} ${conditions}`\n )\n .get(...params) as { total: number } | undefined\n\n const totalDays = totalRow?.total ?? 0\n const rank: DragonKingRankItem[] = rankRows.map((item) => ({\n memberId: item.sender_id,\n platformId: item.platform_id,\n name: item.name,\n count: item.dragon_days,\n percentage: totalDays > 0 ? Math.round((item.dragon_days / totalDays) * 10000) / 100 : 0,\n }))\n\n return { rank, totalDays }\n}\n\n// ==================== 潜水分析 ====================\n\nexport function getDivingAnalysis(db: DatabaseAdapter, filter?: TimeFilter): DivingAnalysis {\n const { conditions, params } = buildFilter(filter)\n\n const rows = db\n .prepare(\n `SELECT\n m.id as member_id,\n m.platform_id,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,\n MAX(msg.ts) as last_ts\n FROM member m\n JOIN message msg ON m.id = msg.sender_id\n WHERE 1=1 ${SYSTEM_FILTER} ${conditions}\n GROUP BY m.id\n ORDER BY last_ts ASC`\n )\n .all(...params) as Array<{ member_id: number; platform_id: string; name: string; last_ts: number }>\n\n const now = Math.floor(Date.now() / 1000)\n const rank: DivingRankItem[] = rows.map((item) => ({\n memberId: item.member_id,\n platformId: item.platform_id,\n name: item.name,\n lastMessageTs: item.last_ts,\n daysSinceLastMessage: Math.floor((now - item.last_ts) / 86400),\n }))\n\n return { rank }\n}\n\n// ==================== 打卡分析 ====================\n\nfunction computeCheckIn(dailyActivity: Array<{ senderId: number; name: string; day: string }>): CheckInAnalysis {\n if (dailyActivity.length === 0) {\n return { streakRank: [], loyaltyRank: [], totalDays: 0 }\n }\n\n const allDays = new Set(dailyActivity.map((r) => r.day))\n const totalDays = allDays.size\n const sortedDays = Array.from(allDays).sort()\n const lastDay = sortedDays[sortedDays.length - 1]\n\n const memberDays = new Map<number, { name: string; days: Set<string> }>()\n for (const record of dailyActivity) {\n if (!memberDays.has(record.senderId)) {\n memberDays.set(record.senderId, { name: record.name, days: new Set() })\n }\n memberDays.get(record.senderId)!.days.add(record.day)\n }\n\n const streakData: StreakRankItem[] = []\n const loyaltyData: Array<{ memberId: number; name: string; totalDays: number }> = []\n\n for (const [memberId, data] of memberDays) {\n const sortedMemberDays = Array.from(data.days).sort()\n const totalMemberDays = sortedMemberDays.length\n\n let maxStreak = 1\n let maxStreakStart = sortedMemberDays[0]\n let maxStreakEnd = sortedMemberDays[0]\n let currentStreakCount = 1\n let currentStreakStart = sortedMemberDays[0]\n\n for (let i = 1; i < sortedMemberDays.length; i++) {\n const prevDate = new Date(sortedMemberDays[i - 1])\n const currDate = new Date(sortedMemberDays[i])\n const diffDays = Math.round((currDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24))\n\n if (diffDays === 1) {\n currentStreakCount++\n } else {\n if (currentStreakCount > maxStreak) {\n maxStreak = currentStreakCount\n maxStreakStart = currentStreakStart\n maxStreakEnd = sortedMemberDays[i - 1]\n }\n currentStreakCount = 1\n currentStreakStart = sortedMemberDays[i]\n }\n }\n\n if (currentStreakCount > maxStreak) {\n maxStreak = currentStreakCount\n maxStreakStart = currentStreakStart\n maxStreakEnd = sortedMemberDays[sortedMemberDays.length - 1]\n }\n\n let finalCurrentStreak = 0\n if (sortedMemberDays[sortedMemberDays.length - 1] === lastDay) {\n finalCurrentStreak = 1\n for (let i = sortedMemberDays.length - 2; i >= 0; i--) {\n const currDate = new Date(sortedMemberDays[i + 1])\n const prevDate = new Date(sortedMemberDays[i])\n const diffDays = Math.round((currDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24))\n if (diffDays === 1) {\n finalCurrentStreak++\n } else {\n break\n }\n }\n }\n\n streakData.push({\n memberId,\n name: data.name,\n maxStreak,\n maxStreakStart,\n maxStreakEnd,\n currentStreak: finalCurrentStreak,\n })\n\n loyaltyData.push({\n memberId,\n name: data.name,\n totalDays: totalMemberDays,\n })\n }\n\n const streakRank = streakData.sort((a, b) => b.maxStreak - a.maxStreak)\n const sortedLoyalty = loyaltyData.sort((a, b) => b.totalDays - a.totalDays)\n const maxLoyaltyDays = sortedLoyalty.length > 0 ? sortedLoyalty[0].totalDays : 1\n const loyaltyRank: LoyaltyRankItem[] = sortedLoyalty.map((item) => ({\n ...item,\n percentage: Math.round((item.totalDays / maxLoyaltyDays) * 100),\n }))\n\n return { streakRank, loyaltyRank, totalDays }\n}\n\nexport function getCheckInAnalysis(db: DatabaseAdapter, filter?: TimeFilter): CheckInAnalysis {\n const { conditions, params } = buildFilter(filter)\n\n const dailyActivity = db\n .prepare(\n `SELECT\n msg.sender_id as senderId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,\n DATE(msg.ts, 'unixepoch', 'localtime') as day\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE 1=1 ${SYSTEM_FILTER} ${conditions}\n GROUP BY msg.sender_id, day\n ORDER BY msg.sender_id, day`\n )\n .all(...params) as Array<{ senderId: number; name: string; day: string }>\n\n if (dailyActivity.length === 0) {\n return { streakRank: [], loyaltyRank: [], totalDays: 0 }\n }\n\n return computeCheckIn(dailyActivity)\n}\n\n// ==================== 斗图分析 ====================\n\nfunction computeMemeBattle(\n messages: Array<{ senderId: number; type: number; ts: number; platformId: string; name: string }>\n): MemeBattleAnalysis {\n const emptyResult: MemeBattleAnalysis = {\n topBattles: [],\n rankByCount: [],\n rankByImageCount: [],\n totalBattles: 0,\n }\n\n const battles: Array<{\n startTime: number\n endTime: number\n msgs: Array<{ senderId: number; name: string; platformId: string }>\n }> = []\n\n let currentChain: Array<{ senderId: number; name: string; platformId: string; ts: number }> = []\n\n const processChain = () => {\n if (currentChain.length >= 3) {\n const senders = new Set(currentChain.map((m) => m.senderId))\n if (senders.size >= 2) {\n battles.push({\n startTime: currentChain[0].ts,\n endTime: currentChain[currentChain.length - 1].ts,\n msgs: currentChain.map(({ senderId, name, platformId }) => ({ senderId, name, platformId })),\n })\n }\n }\n currentChain = []\n }\n\n for (const msg of messages) {\n if (msg.type === 1 || msg.type === 5) {\n currentChain.push({\n senderId: msg.senderId,\n name: msg.name,\n platformId: msg.platformId,\n ts: msg.ts,\n })\n } else {\n processChain()\n }\n }\n processChain()\n\n if (battles.length === 0) return emptyResult\n\n const topBattles: MemeBattleRecord[] = battles\n .map((battle) => ({\n startTime: battle.startTime,\n endTime: battle.endTime,\n totalImages: battle.msgs.length,\n participantCount: new Set(battle.msgs.map((m) => m.senderId)).size,\n participants: Object.values(\n battle.msgs.reduce((acc: Record<number, { memberId: number; name: string; imageCount: number }>, curr) => {\n if (!acc[curr.senderId]) {\n acc[curr.senderId] = { memberId: curr.senderId, name: curr.name, imageCount: 0 }\n }\n acc[curr.senderId].imageCount++\n return acc\n }, {})\n ).sort((a, b) => b.imageCount - a.imageCount),\n }))\n .sort((a, b) => b.totalImages - a.totalImages)\n .slice(0, 30)\n\n const memberStats = new Map<\n number,\n { memberId: number; platformId: string; name: string; battleCount: number; imageCount: number }\n >()\n\n for (const battle of battles) {\n const participantsInBattle = new Set<number>()\n for (const msg of battle.msgs) {\n if (!memberStats.has(msg.senderId)) {\n memberStats.set(msg.senderId, {\n memberId: msg.senderId,\n platformId: msg.platformId,\n name: msg.name,\n battleCount: 0,\n imageCount: 0,\n })\n }\n memberStats.get(msg.senderId)!.imageCount++\n participantsInBattle.add(msg.senderId)\n }\n for (const memberId of participantsInBattle) {\n memberStats.get(memberId)!.battleCount++\n }\n }\n\n const allStats = Array.from(memberStats.values())\n\n const rankByCount: MemeBattleRankItem[] = [...allStats]\n .sort((a, b) => b.battleCount - a.battleCount)\n .map((item) => ({\n memberId: item.memberId,\n platformId: item.platformId,\n name: item.name,\n count: item.battleCount,\n percentage: battles.length > 0 ? Math.round((item.battleCount / battles.length) * 10000) / 100 : 0,\n }))\n\n const totalBattleImages = battles.reduce((sum, b) => sum + b.msgs.length, 0)\n const rankByImageCount: MemeBattleRankItem[] = [...allStats]\n .sort((a, b) => b.imageCount - a.imageCount)\n .map((item) => ({\n memberId: item.memberId,\n platformId: item.platformId,\n name: item.name,\n count: item.imageCount,\n percentage: totalBattleImages > 0 ? Math.round((item.imageCount / totalBattleImages) * 10000) / 100 : 0,\n }))\n\n return {\n topBattles,\n rankByCount,\n rankByImageCount,\n totalBattles: battles.length,\n }\n}\n\nexport function getMemeBattleAnalysis(db: DatabaseAdapter, filter?: TimeFilter): MemeBattleAnalysis {\n // 斗图分析过滤条件与众不同:不排除系统消息,仅排除 type=6(链接)\n const { conditions, params } = buildFilter(filter)\n\n const messages = db\n .prepare(\n `SELECT\n msg.sender_id as senderId,\n msg.type,\n msg.ts,\n m.platform_id as platformId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE msg.type != 6 ${conditions}\n ORDER BY msg.ts ASC`\n )\n .all(...params) as Array<{ senderId: number; type: number; ts: number; platformId: string; name: string }>\n\n if (messages.length === 0) {\n return { topBattles: [], rankByCount: [], rankByImageCount: [], totalBattles: 0 }\n }\n\n return computeMemeBattle(messages)\n}\n\n// ==================== 夜猫分析 ====================\n\nfunction computeNightOwl(\n messages: Array<{ id: number; senderId: number; ts: number; platformId: string; name: string }>\n): NightOwlAnalysis {\n const emptyResult: NightOwlAnalysis = {\n nightOwlRank: [],\n lastSpeakerRank: [],\n firstSpeakerRank: [],\n consecutiveRecords: [],\n champions: [],\n totalDays: 0,\n }\n\n if (messages.length === 0) return emptyResult\n\n function getNightOwlTitleByCount(count: number): NightOwlTitle {\n if (count === 0) return '养生达人'\n if (count <= 20) return '偶尔失眠'\n if (count <= 50) return '经常失眠'\n if (count <= 100) return '夜猫子'\n if (count <= 200) return '秃头预备役'\n if (count <= 500) return '修仙练习生'\n return '守夜冠军'\n }\n\n function getAdjustedDate(ts: number): string {\n const date = new Date(ts * 1000)\n const hour = date.getHours()\n if (hour < 5) {\n date.setDate(date.getDate() - 1)\n }\n return date.toISOString().split('T')[0]\n }\n\n function formatMinutes(minutes: number): string {\n const h = Math.floor(minutes / 60)\n const m = Math.round(minutes % 60)\n return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`\n }\n\n const memberInfo = new Map<number, { platformId: string; name: string }>()\n const nightStats = new Map<\n number,\n { total: number; h23: number; h0: number; h1: number; h2: number; h3to4: number; totalMessages: number }\n >()\n const dailyMessages = new Map<string, Array<{ senderId: number; ts: number; hour: number; minute: number }>>()\n const memberNightDays = new Map<number, Set<string>>()\n\n for (const msg of messages) {\n if (!memberInfo.has(msg.senderId)) {\n memberInfo.set(msg.senderId, { platformId: msg.platformId, name: msg.name })\n }\n\n const date = new Date(msg.ts * 1000)\n const hour = date.getHours()\n const minute = date.getMinutes()\n const adjustedDate = getAdjustedDate(msg.ts)\n\n if (!nightStats.has(msg.senderId)) {\n nightStats.set(msg.senderId, { total: 0, h23: 0, h0: 0, h1: 0, h2: 0, h3to4: 0, totalMessages: 0 })\n }\n const stats = nightStats.get(msg.senderId)!\n stats.totalMessages++\n\n if (hour === 23) {\n stats.h23++\n stats.total++\n } else if (hour === 0) {\n stats.h0++\n stats.total++\n } else if (hour === 1) {\n stats.h1++\n stats.total++\n } else if (hour === 2) {\n stats.h2++\n stats.total++\n } else if (hour >= 3 && hour < 5) {\n stats.h3to4++\n stats.total++\n }\n\n if (hour >= 23 || hour < 5) {\n if (!memberNightDays.has(msg.senderId)) {\n memberNightDays.set(msg.senderId, new Set())\n }\n memberNightDays.get(msg.senderId)!.add(adjustedDate)\n }\n\n if (!dailyMessages.has(adjustedDate)) {\n dailyMessages.set(adjustedDate, [])\n }\n dailyMessages.get(adjustedDate)!.push({ senderId: msg.senderId, ts: msg.ts, hour, minute })\n }\n\n const totalDays = dailyMessages.size\n\n const nightOwlRank: NightOwlAnalysis['nightOwlRank'] = []\n for (const [memberId, stats] of nightStats.entries()) {\n if (stats.total === 0) continue\n const info = memberInfo.get(memberId)!\n nightOwlRank.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n totalNightMessages: stats.total,\n title: getNightOwlTitleByCount(stats.total),\n hourlyBreakdown: { h23: stats.h23, h0: stats.h0, h1: stats.h1, h2: stats.h2, h3to4: stats.h3to4 },\n percentage: stats.totalMessages > 0 ? Math.round((stats.total / stats.totalMessages) * 10000) / 100 : 0,\n })\n }\n nightOwlRank.sort((a, b) => b.totalNightMessages - a.totalNightMessages)\n\n const lastSpeakerStats = new Map<number, { count: number; times: number[] }>()\n const firstSpeakerStats = new Map<number, { count: number; times: number[] }>()\n\n for (const [, dayMessages] of dailyMessages.entries()) {\n if (dayMessages.length === 0) continue\n const lastMsg = dayMessages[dayMessages.length - 1]\n if (!lastSpeakerStats.has(lastMsg.senderId)) {\n lastSpeakerStats.set(lastMsg.senderId, { count: 0, times: [] })\n }\n const lastStats = lastSpeakerStats.get(lastMsg.senderId)!\n lastStats.count++\n lastStats.times.push(lastMsg.hour * 60 + lastMsg.minute)\n\n const firstMsg = dayMessages[0]\n if (!firstSpeakerStats.has(firstMsg.senderId)) {\n firstSpeakerStats.set(firstMsg.senderId, { count: 0, times: [] })\n }\n const firstStats = firstSpeakerStats.get(firstMsg.senderId)!\n firstStats.count++\n firstStats.times.push(firstMsg.hour * 60 + firstMsg.minute)\n }\n\n const lastSpeakerRank: TimeRankItem[] = []\n for (const [memberId, stats] of lastSpeakerStats.entries()) {\n const info = memberInfo.get(memberId)!\n const avgMinutes = stats.times.reduce((a, b) => a + b, 0) / stats.times.length\n const maxMinutes = Math.max(...stats.times)\n lastSpeakerRank.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n count: stats.count,\n avgTime: formatMinutes(avgMinutes),\n extremeTime: formatMinutes(maxMinutes),\n percentage: totalDays > 0 ? Math.round((stats.count / totalDays) * 10000) / 100 : 0,\n })\n }\n lastSpeakerRank.sort((a, b) => b.count - a.count)\n\n const firstSpeakerRank: TimeRankItem[] = []\n for (const [memberId, stats] of firstSpeakerStats.entries()) {\n const info = memberInfo.get(memberId)!\n const avgMinutes = stats.times.reduce((a, b) => a + b, 0) / stats.times.length\n const minMinutes = Math.min(...stats.times)\n firstSpeakerRank.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n count: stats.count,\n avgTime: formatMinutes(avgMinutes),\n extremeTime: formatMinutes(minMinutes),\n percentage: totalDays > 0 ? Math.round((stats.count / totalDays) * 10000) / 100 : 0,\n })\n }\n firstSpeakerRank.sort((a, b) => b.count - a.count)\n\n const consecutiveRecords: ConsecutiveNightRecord[] = []\n for (const [memberId, nightDaysSet] of memberNightDays.entries()) {\n if (nightDaysSet.size === 0) continue\n const info = memberInfo.get(memberId)!\n const sortedNightDays = Array.from(nightDaysSet).sort()\n\n let maxStreak = 1\n let currentStreak = 1\n for (let i = 1; i < sortedNightDays.length; i++) {\n const prevDate = new Date(sortedNightDays[i - 1])\n const currDate = new Date(sortedNightDays[i])\n const diffDays = (currDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24)\n if (diffDays === 1) {\n currentStreak++\n maxStreak = Math.max(maxStreak, currentStreak)\n } else {\n currentStreak = 1\n }\n }\n\n const lastNightDay = sortedNightDays[sortedNightDays.length - 1]\n const today = new Date().toISOString().split('T')[0]\n const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0]\n const isCurrentStreak = lastNightDay === today || lastNightDay === yesterday\n\n consecutiveRecords.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n maxConsecutiveDays: maxStreak,\n currentStreak: isCurrentStreak ? currentStreak : 0,\n })\n }\n consecutiveRecords.sort((a, b) => b.maxConsecutiveDays - a.maxConsecutiveDays)\n\n const championScores = new Map<number, { nightMessages: number; lastSpeakerCount: number; consecutiveDays: number }>()\n for (const item of nightOwlRank) {\n if (!championScores.has(item.memberId)) {\n championScores.set(item.memberId, { nightMessages: 0, lastSpeakerCount: 0, consecutiveDays: 0 })\n }\n championScores.get(item.memberId)!.nightMessages = item.totalNightMessages\n }\n for (const item of lastSpeakerRank) {\n if (!championScores.has(item.memberId)) {\n championScores.set(item.memberId, { nightMessages: 0, lastSpeakerCount: 0, consecutiveDays: 0 })\n }\n championScores.get(item.memberId)!.lastSpeakerCount = item.count\n }\n for (const item of consecutiveRecords) {\n if (!championScores.has(item.memberId)) {\n championScores.set(item.memberId, { nightMessages: 0, lastSpeakerCount: 0, consecutiveDays: 0 })\n }\n championScores.get(item.memberId)!.consecutiveDays = item.maxConsecutiveDays\n }\n\n const champions: NightOwlChampion[] = []\n for (const [memberId, scores] of championScores.entries()) {\n const info = memberInfo.get(memberId)!\n const score = scores.nightMessages * 1 + scores.lastSpeakerCount * 10 + scores.consecutiveDays * 20\n if (score > 0) {\n champions.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n score,\n nightMessages: scores.nightMessages,\n lastSpeakerCount: scores.lastSpeakerCount,\n consecutiveDays: scores.consecutiveDays,\n })\n }\n }\n champions.sort((a, b) => b.score - a.score)\n\n return { nightOwlRank, lastSpeakerRank, firstSpeakerRank, consecutiveRecords, champions, totalDays }\n}\n\nexport function getNightOwlAnalysis(db: DatabaseAdapter, filter?: TimeFilter): NightOwlAnalysis {\n const { conditions, params } = buildFilter(filter)\n\n const messages = db\n .prepare(\n `SELECT\n msg.id,\n msg.sender_id as senderId,\n msg.ts,\n m.platform_id as platformId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE 1=1 ${SYSTEM_FILTER} ${conditions}\n ORDER BY msg.ts ASC`\n )\n .all(...params) as Array<{ id: number; senderId: number; ts: number; platformId: string; name: string }>\n\n if (messages.length === 0) {\n return {\n nightOwlRank: [],\n lastSpeakerRank: [],\n firstSpeakerRank: [],\n consecutiveRecords: [],\n champions: [],\n totalDays: 0,\n }\n }\n\n return computeNightOwl(messages)\n}\n\n// ==================== 复读分析 ====================\n\nfunction computeRepeat(\n messages: Array<{ id: number; senderId: number; content: string; ts: number; platformId: string; name: string }>\n): RepeatAnalysis {\n const originatorCount = new Map<number, number>()\n const initiatorCount = new Map<number, number>()\n const breakerCount = new Map<number, number>()\n const memberMessageCount = new Map<number, number>()\n const memberInfo = new Map<number, { platformId: string; name: string }>()\n const chainLengthCount = new Map<number, number>()\n const contentStats = new Map<\n string,\n { count: number; maxChainLength: number; originatorId: number; lastTs: number; firstMessageId: number }\n >()\n\n let currentContent: string | null = null\n let repeatChain: Array<{ id: number; senderId: number; content: string; ts: number }> = []\n let totalRepeatChains = 0\n let totalChainLength = 0\n\n const fastestRepeaterStats = new Map<number, { totalDiff: number; count: number }>()\n\n const processRepeatChain = (\n chain: Array<{ id: number; senderId: number; content: string; ts: number }>,\n breakerId?: number\n ) => {\n if (chain.length < 3) return\n\n totalRepeatChains++\n const chainLength = chain.length\n totalChainLength += chainLength\n\n const oId = chain[0].senderId\n originatorCount.set(oId, (originatorCount.get(oId) || 0) + 1)\n\n const iId = chain[1].senderId\n initiatorCount.set(iId, (initiatorCount.get(iId) || 0) + 1)\n\n if (breakerId !== undefined) {\n breakerCount.set(breakerId, (breakerCount.get(breakerId) || 0) + 1)\n }\n\n chainLengthCount.set(chainLength, (chainLengthCount.get(chainLength) || 0) + 1)\n\n const content = chain[0].content\n const chainTs = chain[0].ts\n const firstMsgId = chain[0].id\n const existing = contentStats.get(content)\n if (existing) {\n existing.count++\n existing.lastTs = Math.max(existing.lastTs, chainTs)\n if (chainLength > existing.maxChainLength) {\n existing.maxChainLength = chainLength\n existing.originatorId = oId\n existing.firstMessageId = firstMsgId\n }\n } else {\n contentStats.set(content, {\n count: 1,\n maxChainLength: chainLength,\n originatorId: oId,\n lastTs: chainTs,\n firstMessageId: firstMsgId,\n })\n }\n\n for (let i = 1; i < chain.length; i++) {\n const currentMsg = chain[i]\n const prevMsg = chain[i - 1]\n const diff = (currentMsg.ts - prevMsg.ts) * 1000\n if (diff <= 20 * 1000) {\n if (!fastestRepeaterStats.has(currentMsg.senderId)) {\n fastestRepeaterStats.set(currentMsg.senderId, { totalDiff: 0, count: 0 })\n }\n const stats = fastestRepeaterStats.get(currentMsg.senderId)!\n stats.totalDiff += diff\n stats.count++\n }\n }\n }\n\n for (const msg of messages) {\n if (!memberInfo.has(msg.senderId)) {\n memberInfo.set(msg.senderId, { platformId: msg.platformId, name: msg.name })\n }\n memberMessageCount.set(msg.senderId, (memberMessageCount.get(msg.senderId) || 0) + 1)\n\n const content = msg.content.trim()\n if (content === currentContent) {\n const lastSender = repeatChain[repeatChain.length - 1]?.senderId\n if (lastSender !== msg.senderId) {\n repeatChain.push({ id: msg.id, senderId: msg.senderId, content, ts: msg.ts })\n }\n } else {\n processRepeatChain(repeatChain, msg.senderId)\n currentContent = content\n repeatChain = [{ id: msg.id, senderId: msg.senderId, content, ts: msg.ts }]\n }\n }\n processRepeatChain(repeatChain)\n\n const buildRankList = (countMap: Map<number, number>, total: number): RepeatStatItem[] => {\n const items: RepeatStatItem[] = []\n for (const [memberId, count] of countMap.entries()) {\n const info = memberInfo.get(memberId)\n if (info) {\n items.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n count,\n percentage: total > 0 ? Math.round((count / total) * 10000) / 100 : 0,\n })\n }\n }\n return items.sort((a, b) => b.count - a.count)\n }\n\n const buildRateList = (countMap: Map<number, number>): RepeatRateItem[] => {\n const items: RepeatRateItem[] = []\n for (const [memberId, count] of countMap.entries()) {\n const info = memberInfo.get(memberId)\n const totalMessages = memberMessageCount.get(memberId) || 0\n if (info && totalMessages > 0) {\n items.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n count,\n totalMessages,\n rate: Math.round((count / totalMessages) * 10000) / 100,\n })\n }\n }\n return items.sort((a, b) => b.rate - a.rate)\n }\n\n const buildFastestList = (): FastestRepeaterItem[] => {\n const items: FastestRepeaterItem[] = []\n for (const [memberId, stats] of fastestRepeaterStats.entries()) {\n if (stats.count < 5) continue\n const info = memberInfo.get(memberId)\n if (info) {\n items.push({\n memberId,\n platformId: info.platformId,\n name: info.name,\n count: stats.count,\n avgTimeDiff: Math.round(stats.totalDiff / stats.count),\n })\n }\n }\n return items.sort((a, b) => a.avgTimeDiff - b.avgTimeDiff)\n }\n\n const chainLengthDistribution: ChainLengthDistribution[] = []\n for (const [length, count] of chainLengthCount.entries()) {\n chainLengthDistribution.push({ length, count })\n }\n chainLengthDistribution.sort((a, b) => a.length - b.length)\n\n const hotContents: HotRepeatContent[] = []\n for (const [content, stats] of contentStats.entries()) {\n const originatorInfo = memberInfo.get(stats.originatorId)\n hotContents.push({\n content,\n count: stats.count,\n maxChainLength: stats.maxChainLength,\n originatorName: originatorInfo?.name || '未知',\n lastTs: stats.lastTs,\n firstMessageId: stats.firstMessageId,\n })\n }\n hotContents.sort((a, b) => b.maxChainLength - a.maxChainLength)\n const top50HotContents = hotContents.slice(0, 100)\n\n return {\n originators: buildRankList(originatorCount, totalRepeatChains),\n initiators: buildRankList(initiatorCount, totalRepeatChains),\n breakers: buildRankList(breakerCount, totalRepeatChains),\n fastestRepeaters: buildFastestList(),\n originatorRates: buildRateList(originatorCount),\n initiatorRates: buildRateList(initiatorCount),\n breakerRates: buildRateList(breakerCount),\n chainLengthDistribution,\n hotContents: top50HotContents,\n avgChainLength: totalRepeatChains > 0 ? Math.round((totalChainLength / totalRepeatChains) * 100) / 100 : 0,\n totalRepeatChains,\n }\n}\n\nexport function getRepeatAnalysis(db: DatabaseAdapter, filter?: TimeFilter): RepeatAnalysis {\n const { conditions, params } = buildFilter(filter)\n\n const messages = db\n .prepare(\n `SELECT\n msg.id,\n msg.sender_id as senderId,\n msg.content,\n msg.ts,\n m.platform_id as platformId,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE 1=1 ${SYSTEM_FILTER}\n AND msg.type = 0\n AND msg.content IS NOT NULL\n AND TRIM(msg.content) != ''\n ${conditions}\n ORDER BY msg.ts ASC, msg.id ASC`\n )\n .all(...params) as Array<{\n id: number\n senderId: number\n content: string\n ts: number\n platformId: string\n name: string\n }>\n\n if (messages.length === 0) {\n return {\n originators: [],\n initiators: [],\n breakers: [],\n fastestRepeaters: [],\n originatorRates: [],\n initiatorRates: [],\n breakerRates: [],\n chainLengthDistribution: [],\n hotContents: [],\n avgChainLength: 0,\n totalRepeatChains: 0,\n }\n }\n\n return computeRepeat(messages)\n}\n","/**\n * 内置工具目录(静态数据)\n *\n * 仅包含工具名称和分类,不含任何运行时依赖。\n * 用于前端展示工具列表和助手配置面板。\n */\n\nexport type ToolCategory = 'core' | 'analysis'\n\nexport interface BuiltinToolCatalogEntry {\n name: string\n category: ToolCategory\n}\n\nconst LEGACY_BUILTIN_TOOL_NAME_ALIASES: Record<string, string> = {\n get_session_messages: 'get_segment_messages',\n get_session_summaries: 'get_segment_summaries',\n}\n\nexport function normalizeBuiltinToolName(toolName: string): string {\n return LEGACY_BUILTIN_TOOL_NAME_ALIASES[toolName] ?? toolName\n}\n\nexport function normalizeBuiltinToolNames(toolNames: readonly string[]): string[] {\n return Array.from(new Set(toolNames.map(normalizeBuiltinToolName)))\n}\n\nexport const CHART_CAPABILITY_SKILL_ID = 'chart_runtime'\n\nexport const BUILTIN_TOOL_CATALOG: BuiltinToolCatalogEntry[] = [\n // Core 工具\n { name: 'get_chat_overview', category: 'core' },\n { name: 'search_messages', category: 'core' },\n { name: 'deep_search_messages', category: 'core' },\n { name: 'get_recent_messages', category: 'core' },\n { name: 'get_message_context', category: 'core' },\n { name: 'get_segment_messages', category: 'core' },\n { name: 'get_members', category: 'core' },\n { name: 'get_schema', category: 'core' },\n\n // Analysis 工具\n { name: 'get_member_stats', category: 'analysis' },\n { name: 'get_time_stats', category: 'analysis' },\n { name: 'get_member_name_history', category: 'analysis' },\n { name: 'get_conversation_between', category: 'analysis' },\n { name: 'get_segment_summaries', category: 'analysis' },\n { name: 'response_time_analysis', category: 'analysis' },\n { name: 'keyword_frequency', category: 'analysis' },\n { name: 'render_chart', category: 'analysis' },\n { name: 'execute_sql', category: 'analysis' },\n\n // SQL Analysis 工具\n { name: 'message_type_breakdown', category: 'analysis' },\n { name: 'peak_chat_hours_by_member', category: 'analysis' },\n { name: 'member_activity_trend', category: 'analysis' },\n { name: 'silent_members', category: 'analysis' },\n { name: 'reply_interaction_ranking', category: 'analysis' },\n { name: 'mutual_interaction_pairs', category: 'analysis' },\n { name: 'member_message_length_stats', category: 'analysis' },\n { name: 'daily_active_members', category: 'analysis' },\n { name: 'conversation_initiator_stats', category: 'analysis' },\n { name: 'activity_heatmap', category: 'analysis' },\n { name: 'unanswered_messages', category: 'analysis' },\n]\n","/**\n * Provider Registry — 内置 provider 目录\n */\n\nimport type { ProviderDefinition } from './model-types'\nexport type { ProviderDefinition }\n\n// ==================== 内置 Provider 目录 ====================\n\nconst OPENAI: ProviderDefinition = {\n id: 'openai',\n name: 'OpenAI',\n kind: 'official',\n website: 'https://openai.com',\n consoleUrl: 'https://platform.openai.com/api-keys',\n defaultBaseUrl: 'https://api.openai.com/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: [\n 'gpt-5.4-pro',\n 'gpt-5.4',\n 'gpt-5.2-pro',\n 'gpt-5.2',\n 'gpt-5.1',\n 'gpt-5-pro',\n 'gpt-5',\n 'gpt-4.1',\n 'gpt-4.1-mini',\n 'gpt-4.1-nano',\n 'gpt-4o',\n 'gpt-4o-mini',\n 'o3',\n 'o4-mini',\n 'text-embedding-3-small',\n 'text-embedding-3-large',\n ],\n}\n\nconst ANTHROPIC: ProviderDefinition = {\n id: 'anthropic',\n name: 'Anthropic',\n kind: 'official',\n website: 'https://www.anthropic.com',\n consoleUrl: 'https://console.anthropic.com/settings/keys',\n defaultBaseUrl: 'https://api.anthropic.com',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: [\n 'claude-opus-4-6',\n 'claude-sonnet-4-6',\n 'claude-opus-4-5',\n 'claude-sonnet-4-5',\n 'claude-haiku-4-5',\n 'claude-sonnet-4-20250514',\n 'claude-opus-4-20250514',\n 'claude-haiku-3-5-20241022',\n ],\n}\n\nconst GEMINI: ProviderDefinition = {\n id: 'gemini',\n name: 'Gemini',\n kind: 'official',\n website: 'https://ai.google.dev',\n consoleUrl: 'https://aistudio.google.com/apikey',\n defaultBaseUrl: 'https://generativelanguage.googleapis.com/v1beta',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: [\n 'gemini-3.1-pro-preview',\n 'gemini-3-pro-preview',\n 'gemini-2.5-flash',\n 'gemini-2.5-pro',\n 'gemini-2.0-flash',\n 'text-embedding-004',\n ],\n}\n\nconst DEEPSEEK: ProviderDefinition = {\n id: 'deepseek',\n name: 'DeepSeek',\n kind: 'official',\n website: 'https://www.deepseek.com',\n consoleUrl: 'https://platform.deepseek.com/api_keys',\n defaultBaseUrl: 'https://api.deepseek.com/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: ['deepseek-v4-pro', 'deepseek-v4-flash'],\n}\n\nconst QWEN: ProviderDefinition = {\n id: 'qwen',\n name: 'Qwen',\n kind: 'official',\n website: 'https://tongyi.aliyun.com',\n consoleUrl: 'https://dashscope.console.aliyun.com/apiKey',\n defaultBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: [\n 'qwen3.5-plus',\n 'qwen3.5-flash',\n 'qwen3-max',\n 'qwen-max',\n 'qwen-plus',\n 'qwen-turbo',\n 'qwen-long',\n 'text-embedding-v3',\n ],\n}\n\nconst GLM: ProviderDefinition = {\n id: 'glm',\n name: 'GLM',\n kind: 'official',\n website: 'https://www.zhipuai.cn',\n consoleUrl: 'https://open.bigmodel.cn/usercenter/apikeys',\n defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: [\n 'glm-5',\n 'glm-4.7',\n 'glm-4.6v',\n 'glm-4.6v-flash',\n 'glm-4.5',\n 'glm-4.5-flash',\n 'glm-4-plus',\n 'glm-4-flash',\n 'embedding-3',\n ],\n}\n\nconst KIMI: ProviderDefinition = {\n id: 'kimi',\n name: 'Kimi',\n kind: 'official',\n website: 'https://www.moonshot.cn',\n consoleUrl: 'https://platform.moonshot.cn/console/api-keys',\n defaultBaseUrl: 'https://api.moonshot.cn/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: ['kimi-k2.5', 'kimi-k2-thinking', 'kimi-k2-thinking-turbo', 'kimi-k2-0905-Preview', 'moonshot-v1-auto'],\n}\n\nconst DOUBAO: ProviderDefinition = {\n id: 'doubao',\n name: 'Doubao',\n kind: 'official',\n website: 'https://www.volcengine.com/product/doubao',\n consoleUrl: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey',\n defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: [\n 'doubao-seed-2-0-pro-260215',\n 'doubao-seed-2-0-lite-260215',\n 'doubao-seed-2-0-mini-260215',\n 'doubao-seed-1-8-251228',\n 'doubao-seed-1-6-251015',\n 'doubao-seed-1-6-lite-251015',\n ],\n}\n\nconst SILICONFLOW: ProviderDefinition = {\n id: 'siliconflow',\n name: 'SiliconFlow',\n kind: 'official',\n website: 'https://siliconflow.cn',\n consoleUrl: 'https://cloud.siliconflow.cn/account/ak',\n defaultBaseUrl: 'https://api.siliconflow.cn/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: ['deepseek-ai/DeepSeek-V3.2', 'deepseek-ai/DeepSeek-R1', 'Qwen/Qwen3-8B'],\n}\n\nconst GROQ: ProviderDefinition = {\n id: 'groq',\n name: 'Groq',\n kind: 'official',\n website: 'https://groq.com',\n consoleUrl: 'https://console.groq.com/keys',\n defaultBaseUrl: 'https://api.groq.com/openai/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: ['llama3-70b-8192', 'llama3-8b-8192', 'mistral-saba-24b', 'gemma-9b-it'],\n}\n\nconst OPENROUTER: ProviderDefinition = {\n id: 'openrouter',\n name: 'OpenRouter',\n kind: 'aggregator',\n website: 'https://openrouter.ai',\n consoleUrl: 'https://openrouter.ai/keys',\n defaultBaseUrl: 'https://openrouter.ai/api/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: ['deepseek/deepseek-v4-pro', 'google/gemini-2.5-flash-preview', 'mistralai/mistral-7b-instruct:free'],\n}\n\nconst XAI: ProviderDefinition = {\n id: 'xai',\n name: 'xAI',\n kind: 'official',\n website: 'https://x.ai',\n consoleUrl: 'https://console.x.ai',\n defaultBaseUrl: 'https://api.x.ai/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: ['grok-4', 'grok-3', 'grok-3-fast', 'grok-3-mini', 'grok-3-mini-fast'],\n}\n\nconst MINIMAX: ProviderDefinition = {\n id: 'minimax',\n name: 'MiniMax',\n kind: 'official',\n website: 'https://www.minimaxi.com',\n consoleUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key',\n defaultBaseUrl: 'https://api.minimaxi.com/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: ['MiniMax-M2.7', 'MiniMax-M2.7-highspeed', 'MiniMax-M2.5', 'MiniMax-M2.5-highspeed'],\n}\n\nconst OPENAI_COMPATIBLE: ProviderDefinition = {\n id: 'openai-compatible',\n name: 'OpenAI Compatible',\n kind: 'openai-compatible',\n defaultBaseUrl: 'http://localhost:11434/v1',\n authMode: 'api-key',\n supportsCustomModels: true,\n builtin: true,\n enabledByDefault: true,\n modelIds: [],\n}\n\n// ==================== 内置目录导出 ====================\n\nexport const BUILTIN_PROVIDERS: ProviderDefinition[] = [\n OPENAI,\n ANTHROPIC,\n GEMINI,\n DEEPSEEK,\n QWEN,\n GLM,\n KIMI,\n DOUBAO,\n SILICONFLOW,\n GROQ,\n OPENROUTER,\n XAI,\n MINIMAX,\n OPENAI_COMPATIBLE,\n]\n\n/** 按 id 查找内置 provider */\nexport function getBuiltinProviderById(id: string): ProviderDefinition | null {\n return BUILTIN_PROVIDERS.find((p) => p.id === id) || null\n}\n","/**\n * Model Catalog — 内置模型目录\n */\n\nimport type { ModelDefinition } from './model-types'\nexport type { ModelDefinition }\n\n// ==================== Helper ====================\n\ntype ModelInput = Omit<ModelDefinition, 'builtin' | 'editable'>\n\nfunction builtin(m: ModelInput): ModelDefinition {\n return { ...m, builtin: true, editable: false }\n}\n\n// ==================== OpenAI ====================\n\nconst OPENAI_MODELS: ModelDefinition[] = [\n builtin({\n id: 'gpt-5.4-pro',\n providerId: 'openai',\n name: 'GPT-5.4 Pro',\n description: 'Latest flagship, best reasoning & coding',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-5.4',\n providerId: 'openai',\n name: 'GPT-5.4',\n description: 'GPT-5.4 standard',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-5.2-pro',\n providerId: 'openai',\n name: 'GPT-5.2 Pro',\n description: 'GPT-5.2 Pro',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-5.2',\n providerId: 'openai',\n name: 'GPT-5.2',\n description: 'GPT-5.2 standard',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-5.1',\n providerId: 'openai',\n name: 'GPT-5.1',\n description: 'GPT-5.1',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-5-pro',\n providerId: 'openai',\n name: 'GPT-5 Pro',\n description: 'GPT-5 Pro',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-5',\n providerId: 'openai',\n name: 'GPT-5',\n description: 'GPT-5 standard',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-4.1',\n providerId: 'openai',\n name: 'GPT-4.1',\n description: 'Best at coding & instruction following',\n contextWindow: 1047576,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-4.1-mini',\n providerId: 'openai',\n name: 'GPT-4.1 Mini',\n description: 'Cost-effective',\n contextWindow: 1047576,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-4.1-nano',\n providerId: 'openai',\n name: 'GPT-4.1 Nano',\n description: 'Fastest and most affordable',\n contextWindow: 1047576,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-4o',\n providerId: 'openai',\n name: 'GPT-4o',\n description: 'Multimodal flagship model',\n contextWindow: 128000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'gpt-4o-mini',\n providerId: 'openai',\n name: 'GPT-4o Mini',\n description: 'Lightweight multimodal model',\n contextWindow: 128000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'o3',\n providerId: 'openai',\n name: 'o3',\n description: 'Advanced reasoning model',\n contextWindow: 200000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'o4-mini',\n providerId: 'openai',\n name: 'o4-mini',\n description: 'Efficient reasoning model',\n contextWindow: 200000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'text-embedding-3-small',\n providerId: 'openai',\n name: 'Text Embedding 3 Small',\n description: 'Efficient embedding model',\n contextWindow: 8191,\n capabilities: ['embedding'],\n recommendedFor: ['embedding'],\n status: 'stable',\n }),\n builtin({\n id: 'text-embedding-3-large',\n providerId: 'openai',\n name: 'Text Embedding 3 Large',\n description: 'High-precision embedding model',\n contextWindow: 8191,\n capabilities: ['embedding'],\n recommendedFor: ['embedding'],\n status: 'stable',\n }),\n]\n\n// ==================== Anthropic ====================\n\nconst ANTHROPIC_MODELS: ModelDefinition[] = [\n builtin({\n id: 'claude-opus-4-6',\n providerId: 'anthropic',\n name: 'Claude Opus 4.6',\n description: 'Latest flagship, adaptive thinking',\n contextWindow: 200000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'claude-sonnet-4-6',\n providerId: 'anthropic',\n name: 'Claude Sonnet 4.6',\n description: 'Cost-effective, adaptive thinking',\n contextWindow: 200000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'claude-opus-4-5',\n providerId: 'anthropic',\n name: 'Claude Opus 4.5',\n description: 'Previous generation flagship',\n contextWindow: 200000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'claude-sonnet-4-5',\n providerId: 'anthropic',\n name: 'Claude Sonnet 4.5',\n description: 'Previous generation cost-effective',\n contextWindow: 200000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'claude-haiku-4-5',\n providerId: 'anthropic',\n name: 'Claude Haiku 4.5',\n description: 'Fast and lightweight',\n contextWindow: 200000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'claude-sonnet-4-20250514',\n providerId: 'anthropic',\n name: 'Claude Sonnet 4',\n description: 'Sonnet 4 snapshot',\n contextWindow: 200000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'claude-opus-4-20250514',\n providerId: 'anthropic',\n name: 'Claude Opus 4',\n description: 'Opus 4 snapshot',\n contextWindow: 200000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'claude-haiku-3-5-20241022',\n providerId: 'anthropic',\n name: 'Claude 3.5 Haiku',\n description: '3.5 Haiku snapshot',\n contextWindow: 200000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n]\n\n// ==================== Gemini ====================\n\nconst GEMINI_MODELS: ModelDefinition[] = [\n builtin({\n id: 'gemini-3.1-pro-preview',\n providerId: 'gemini',\n name: 'Gemini 3.1 Pro',\n description: 'Latest Gemini 3.1 Pro preview',\n contextWindow: 1048576,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'preview',\n }),\n builtin({\n id: 'gemini-3-pro-preview',\n providerId: 'gemini',\n name: 'Gemini 3 Pro',\n description: 'Gemini 3 Pro preview',\n contextWindow: 1048576,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'preview',\n }),\n builtin({\n id: 'gemini-2.5-flash',\n providerId: 'gemini',\n name: 'Gemini 2.5 Flash',\n description: 'Cost-effective, low latency, adaptive thinking',\n contextWindow: 1048576,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'gemini-2.5-pro',\n providerId: 'gemini',\n name: 'Gemini 2.5 Pro',\n description: 'Deep reasoning and complex tasks',\n contextWindow: 1048576,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'gemini-2.0-flash',\n providerId: 'gemini',\n name: 'Gemini 2.0 Flash',\n description: 'Previous generation fast model',\n contextWindow: 1048576,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'text-embedding-004',\n providerId: 'gemini',\n name: 'Text Embedding 004',\n description: 'Google embedding model',\n contextWindow: 2048,\n capabilities: ['embedding'],\n recommendedFor: ['embedding'],\n status: 'stable',\n }),\n]\n\n// ==================== DeepSeek ====================\n\nconst DEEPSEEK_MODELS: ModelDefinition[] = [\n builtin({\n id: 'deepseek-v4-pro',\n providerId: 'deepseek',\n name: 'DeepSeek V4 Pro',\n description: 'Flagship model, 1M context, thinking & non-thinking',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'deepseek-v4-flash',\n providerId: 'deepseek',\n name: 'DeepSeek V4 Flash',\n description: 'Fast model, 1M context, thinking & non-thinking',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n]\n\n// ==================== 通义千问 ====================\n\nconst QWEN_MODELS: ModelDefinition[] = [\n builtin({\n id: 'qwen3.5-plus',\n providerId: 'qwen',\n name: 'Qwen3.5 Plus',\n description: 'Latest Qwen3.5 flagship',\n contextWindow: 262144,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'qwen3.5-flash',\n providerId: 'qwen',\n name: 'Qwen3.5 Flash',\n description: 'Qwen3.5 fast version',\n contextWindow: 262144,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'qwen3-max',\n providerId: 'qwen',\n name: 'Qwen3 Max',\n description: 'Qwen3 massive-scale',\n contextWindow: 262144,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'qwen-max',\n providerId: 'qwen',\n name: 'Qwen Max',\n description: 'Massive-scale language model',\n contextWindow: 262144,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'qwen-plus',\n providerId: 'qwen',\n name: 'Qwen Plus',\n description: 'Good quality, suitable for most scenarios',\n contextWindow: 262144,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'qwen-turbo',\n providerId: 'qwen',\n name: 'Qwen Turbo',\n description: 'Fast and cost-effective',\n contextWindow: 1000000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'qwen-long',\n providerId: 'qwen',\n name: 'Qwen Long',\n description: 'Ultra-long context model',\n contextWindow: 262144,\n capabilities: ['chat'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'text-embedding-v3',\n providerId: 'qwen',\n name: 'Text Embedding V3',\n description: 'Qwen embedding model',\n contextWindow: 8192,\n capabilities: ['embedding'],\n recommendedFor: ['embedding'],\n status: 'stable',\n }),\n]\n\n// ==================== 智谱 GLM ====================\n\nconst GLM_MODELS: ModelDefinition[] = [\n builtin({\n id: 'glm-5',\n providerId: 'glm',\n name: 'GLM-5',\n description: 'Latest flagship model',\n contextWindow: 200000,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'glm-4.7',\n providerId: 'glm',\n name: 'GLM-4.7',\n description: 'GLM-4.7 high performance',\n contextWindow: 200000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'glm-4.6v',\n providerId: 'glm',\n name: 'GLM-4.6V',\n description: '4.6 multimodal model',\n contextWindow: 200000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'glm-4.6v-flash',\n providerId: 'glm',\n name: 'GLM-4.6V Flash',\n description: '4.6V free multimodal',\n contextWindow: 200000,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'glm-4.5',\n providerId: 'glm',\n name: 'GLM-4.5',\n description: 'GLM-4.5 standard',\n contextWindow: 200000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'glm-4.5-flash',\n providerId: 'glm',\n name: 'GLM-4.5 Flash',\n description: '4.5 free model',\n contextWindow: 200000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'glm-4-plus',\n providerId: 'glm',\n name: 'GLM-4 Plus',\n description: 'Previous generation flagship',\n contextWindow: 128000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'glm-4-flash',\n providerId: 'glm',\n name: 'GLM-4 Flash',\n description: 'Fast model',\n contextWindow: 128000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'embedding-3',\n providerId: 'glm',\n name: 'Embedding-3',\n description: 'Zhipu embedding model',\n contextWindow: 8192,\n capabilities: ['embedding'],\n recommendedFor: ['embedding'],\n status: 'stable',\n }),\n]\n\n// ==================== Kimi ====================\n\nconst KIMI_MODELS: ModelDefinition[] = [\n builtin({\n id: 'kimi-k2.5',\n providerId: 'kimi',\n name: 'Kimi K2.5',\n description: 'Latest flagship with vision support',\n contextWindow: 262144,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'kimi-k2-thinking',\n providerId: 'kimi',\n name: 'Kimi K2 Thinking',\n description: 'K2 deep reasoning',\n contextWindow: 131072,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'kimi-k2-thinking-turbo',\n providerId: 'kimi',\n name: 'Kimi K2 Thinking Turbo',\n description: 'K2 fast reasoning',\n contextWindow: 131072,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'kimi-k2-0905-Preview',\n providerId: 'kimi',\n name: 'Kimi K2 Preview',\n description: 'K2 preview',\n contextWindow: 131072,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'preview',\n }),\n builtin({\n id: 'moonshot-v1-auto',\n providerId: 'kimi',\n name: 'Moonshot V1 Auto',\n description: 'Auto-select context length',\n contextWindow: 131072,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n]\n\n// ==================== 豆包 ====================\n\nconst DOUBAO_MODELS: ModelDefinition[] = [\n builtin({\n id: 'doubao-seed-2-0-pro-260215',\n providerId: 'doubao',\n name: 'Doubao Seed 2.0 Pro',\n description: 'Latest flagship model',\n contextWindow: 256000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'doubao-seed-2-0-lite-260215',\n providerId: 'doubao',\n name: 'Doubao Seed 2.0 Lite',\n description: 'Seed 2.0 lightweight',\n contextWindow: 256000,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'doubao-seed-2-0-mini-260215',\n providerId: 'doubao',\n name: 'Doubao Seed 2.0 Mini',\n description: 'Seed 2.0 ultra-fast',\n contextWindow: 256000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'doubao-seed-1-8-251228',\n providerId: 'doubao',\n name: 'Doubao Seed 1.8',\n description: 'Previous generation flagship',\n contextWindow: 128000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'doubao-seed-1-6-251015',\n providerId: 'doubao',\n name: 'Doubao Seed 1.6',\n description: 'High-performance general model',\n contextWindow: 128000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'doubao-seed-1-6-lite-251015',\n providerId: 'doubao',\n name: 'Doubao Seed 1.6 Lite',\n description: 'Lightweight and cost-effective',\n contextWindow: 128000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n]\n\n// ==================== 硅基流动 ====================\n\nconst SILICONFLOW_MODELS: ModelDefinition[] = [\n builtin({\n id: 'deepseek-ai/DeepSeek-V3.2',\n providerId: 'siliconflow',\n name: 'DeepSeek V3.2',\n description: 'DeepSeek V3.2 on SiliconFlow',\n contextWindow: 128000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'deepseek-ai/DeepSeek-R1',\n providerId: 'siliconflow',\n name: 'DeepSeek R1',\n description: 'DeepSeek R1 reasoning model',\n contextWindow: 65536,\n capabilities: ['chat', 'reasoning'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'Qwen/Qwen3-8B',\n providerId: 'siliconflow',\n name: 'Qwen3 8B',\n description: 'Qwen3 8B lightweight model',\n contextWindow: 32768,\n capabilities: ['chat'],\n recommendedFor: [],\n status: 'stable',\n }),\n]\n\n// ==================== Groq ====================\n\nconst GROQ_MODELS: ModelDefinition[] = [\n builtin({\n id: 'llama3-70b-8192',\n providerId: 'groq',\n name: 'LLaMA3 70B',\n description: 'Meta LLaMA3 70B ultra-fast inference',\n contextWindow: 8192,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'llama3-8b-8192',\n providerId: 'groq',\n name: 'LLaMA3 8B',\n description: 'Meta LLaMA3 8B lightweight ultra-fast',\n contextWindow: 8192,\n capabilities: ['chat'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'mistral-saba-24b',\n providerId: 'groq',\n name: 'Mistral Saba 24B',\n description: 'Mistral Saba 24B',\n contextWindow: 32768,\n capabilities: ['chat'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'gemma-9b-it',\n providerId: 'groq',\n name: 'Gemma 9B',\n description: 'Google Gemma 9B',\n contextWindow: 8192,\n capabilities: ['chat'],\n recommendedFor: [],\n status: 'stable',\n }),\n]\n\n// ==================== OpenRouter ====================\n\nconst OPENROUTER_MODELS: ModelDefinition[] = [\n builtin({\n id: 'deepseek/deepseek-v4-pro',\n providerId: 'openrouter',\n name: 'DeepSeek V4 Pro',\n description: 'DeepSeek V4 Pro via OpenRouter',\n contextWindow: 1000000,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'google/gemini-2.5-flash-preview',\n providerId: 'openrouter',\n name: 'Gemini 2.5 Flash',\n description: 'Google Gemini 2.5 Flash via OpenRouter',\n contextWindow: 1048576,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'mistralai/mistral-7b-instruct:free',\n providerId: 'openrouter',\n name: 'Mistral 7B (Free)',\n description: 'Mistral 7B free tier',\n contextWindow: 32768,\n capabilities: ['chat'],\n recommendedFor: [],\n status: 'stable',\n }),\n]\n\n// ==================== xAI ====================\n\nconst XAI_MODELS: ModelDefinition[] = [\n builtin({\n id: 'grok-4',\n providerId: 'xai',\n name: 'Grok 4',\n description: 'Latest flagship model',\n contextWindow: 256000,\n capabilities: ['chat', 'reasoning', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'grok-3',\n providerId: 'xai',\n name: 'Grok 3',\n description: 'Grok 3 standard',\n contextWindow: 131072,\n capabilities: ['chat', 'vision', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'grok-3-fast',\n providerId: 'xai',\n name: 'Grok 3 Fast',\n description: 'Grok 3 fast version',\n contextWindow: 131072,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'grok-3-mini',\n providerId: 'xai',\n name: 'Grok 3 Mini',\n description: 'Grok 3 small reasoning model',\n contextWindow: 131072,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'grok-3-mini-fast',\n providerId: 'xai',\n name: 'Grok 3 Mini Fast',\n description: 'Grok 3 Mini fast version',\n contextWindow: 131072,\n capabilities: ['chat', 'reasoning', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n]\n\n// ==================== MiniMax ====================\n\nconst MINIMAX_MODELS: ModelDefinition[] = [\n builtin({\n id: 'MiniMax-M2.7',\n providerId: 'minimax',\n name: 'MiniMax M2.7',\n description: 'Latest flagship model',\n contextWindow: 200000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'MiniMax-M2.7-highspeed',\n providerId: 'minimax',\n name: 'MiniMax M2.7 HS',\n description: 'M2.7 high-speed',\n contextWindow: 200000,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: ['chat'],\n status: 'stable',\n }),\n builtin({\n id: 'MiniMax-M2.5',\n providerId: 'minimax',\n name: 'MiniMax M2.5',\n description: 'M2.5 standard',\n contextWindow: 204800,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n builtin({\n id: 'MiniMax-M2.5-highspeed',\n providerId: 'minimax',\n name: 'MiniMax M2.5 HS',\n description: 'M2.5 high-speed',\n contextWindow: 204800,\n capabilities: ['chat', 'function_calling'],\n recommendedFor: [],\n status: 'stable',\n }),\n]\n\n// ==================== 汇总导出 ====================\n\nexport const BUILTIN_MODELS: ModelDefinition[] = [\n ...OPENAI_MODELS,\n ...ANTHROPIC_MODELS,\n ...GEMINI_MODELS,\n ...DEEPSEEK_MODELS,\n ...QWEN_MODELS,\n ...GLM_MODELS,\n ...KIMI_MODELS,\n ...DOUBAO_MODELS,\n ...SILICONFLOW_MODELS,\n ...GROQ_MODELS,\n ...OPENROUTER_MODELS,\n ...XAI_MODELS,\n ...MINIMAX_MODELS,\n]\n\n/** 按 provider 筛选内置模型 */\nexport function getBuiltinModelsByProvider(providerId: string): ModelDefinition[] {\n return BUILTIN_MODELS.filter((m) => m.providerId === providerId)\n}\n\n/** 按 id + providerId 查找内置模型 */\nexport function getBuiltinModelById(providerId: string, modelId: string): ModelDefinition | null {\n return BUILTIN_MODELS.find((m) => m.providerId === providerId && m.id === modelId) || null\n}\n","/**\n * Agent content parsing utilities.\n * Extracts thinking tags and strips tool-call markup from LLM output.\n */\n\nexport const THINK_TAGS = ['think', 'analysis', 'reasoning', 'reflection', 'thought', 'thinking']\n\nexport function extractThinkingContent(content: string): { thinking: string; cleanContent: string } {\n if (!content) {\n return { thinking: '', cleanContent: '' }\n }\n\n const tagPattern = THINK_TAGS.join('|')\n const thinkRegex = new RegExp(`<(${tagPattern})>([\\\\s\\\\S]*?)<\\\\/\\\\1>`, 'gi')\n const thinkingParts: string[] = []\n let cleanContent = content\n\n const matches = content.matchAll(thinkRegex)\n for (const match of matches) {\n const thinkText = match[2].trim()\n if (thinkText) {\n thinkingParts.push(thinkText)\n }\n cleanContent = cleanContent.replace(match[0], '')\n }\n\n return { thinking: thinkingParts.join('\\n').trim(), cleanContent: cleanContent.trim() }\n}\n\nexport function stripToolCallTags(content: string): string {\n return content.replace(/<tool_call>[\\s\\S]*?<\\/tool_call>/gi, '').trim()\n}\n\n/**\n * Recursively strip large avatar/senderAvatar base64 strings from objects.\n * Used when serializing tool results to avoid transmitting large image data.\n */\nexport function stripAvatarFields(obj: unknown): void {\n if (!obj || typeof obj !== 'object') return\n if (Array.isArray(obj)) {\n for (const item of obj) stripAvatarFields(item)\n return\n }\n const record = obj as Record<string, unknown>\n for (const key of Object.keys(record)) {\n if ((key === 'avatar' || key === 'senderAvatar') && typeof record[key] === 'string') {\n if ((record[key] as string).length > 200) {\n record[key] = '[stripped]'\n }\n } else if (typeof record[key] === 'object' && record[key] !== null) {\n stripAvatarFields(record[key])\n }\n }\n}\n","/**\n * Streaming think-tag parser.\n *\n * Detects <think>, <reasoning>, etc. tags embedded in streaming text deltas\n * and splits them into separate thinking/content events. Handles tags split\n * across multiple chunks via internal buffering.\n */\n\nimport { THINK_TAGS } from './content-parser'\n\nexport type StreamParserEvent =\n | { type: 'content'; content: string }\n | { type: 'thinking_start'; tag: string }\n | { type: 'thinking_delta'; content: string }\n | { type: 'thinking_end' }\n\ntype ParserState = 'content' | 'thinking'\n\nexport class StreamingThinkTagParser {\n private state: ParserState = 'content'\n private buffer = ''\n private activeTag = ''\n private emit: (event: StreamParserEvent) => void\n\n constructor(emit: (event: StreamParserEvent) => void) {\n this.emit = emit\n }\n\n feed(text: string): void {\n this.buffer += text\n this.processBuffer()\n }\n\n /** Flush any remaining buffer (call at stream end). */\n flush(): void {\n if (!this.buffer) return\n if (this.state === 'thinking') {\n this.emit({ type: 'thinking_delta', content: this.buffer })\n } else {\n this.emit({ type: 'content', content: this.buffer })\n }\n this.buffer = ''\n }\n\n private processBuffer(): void {\n while (this.buffer.length > 0) {\n const ltIdx = this.buffer.indexOf('<')\n\n if (ltIdx === -1) {\n this.emitBufferContent(this.buffer)\n this.buffer = ''\n return\n }\n\n if (ltIdx > 0) {\n this.emitBufferContent(this.buffer.slice(0, ltIdx))\n this.buffer = this.buffer.slice(ltIdx)\n }\n\n // Buffer starts with '<', try to match a tag\n const tagResult = this.tryMatchTag()\n if (tagResult === 'incomplete') {\n // Need more data; keep buffer for next feed()\n return\n }\n if (tagResult === 'matched') {\n // Tag was consumed and events emitted, continue processing\n continue\n }\n // 'no_match': the '<' is not a think tag, emit it and advance\n this.emitBufferContent('<')\n this.buffer = this.buffer.slice(1)\n }\n }\n\n /**\n * Try to match a think tag at the start of buffer.\n * Returns: 'matched' if a tag was consumed, 'incomplete' if more data\n * needed, 'no_match' if it's not a valid think tag.\n */\n private tryMatchTag(): 'matched' | 'incomplete' | 'no_match' {\n if (this.state === 'content') {\n return this.tryMatchOpenTag()\n }\n return this.tryMatchCloseTag()\n }\n\n private tryMatchOpenTag(): 'matched' | 'incomplete' | 'no_match' {\n // Look for <tagName> pattern\n for (const tag of THINK_TAGS) {\n const openTag = `<${tag}>`\n if (this.buffer.startsWith(openTag)) {\n this.buffer = this.buffer.slice(openTag.length)\n this.state = 'thinking'\n this.activeTag = tag\n this.emit({ type: 'thinking_start', tag })\n return 'matched'\n }\n // Could be a partial match — check if buffer is a prefix of the tag\n if (openTag.startsWith(this.buffer) && this.buffer.length < openTag.length) {\n return 'incomplete'\n }\n }\n return 'no_match'\n }\n\n private tryMatchCloseTag(): 'matched' | 'incomplete' | 'no_match' {\n const closeTag = `</${this.activeTag}>`\n if (this.buffer.startsWith(closeTag)) {\n this.buffer = this.buffer.slice(closeTag.length)\n this.state = 'content'\n this.emit({ type: 'thinking_end' })\n this.activeTag = ''\n return 'matched'\n }\n // Could be a partial match\n if (closeTag.startsWith(this.buffer) && this.buffer.length < closeTag.length) {\n return 'incomplete'\n }\n // Not a close tag — emit the '<' as thinking content and continue\n this.emit({ type: 'thinking_delta', content: '<' })\n this.buffer = this.buffer.slice(1)\n return 'matched'\n }\n\n private emitBufferContent(text: string): void {\n if (!text) return\n if (this.state === 'thinking') {\n this.emit({ type: 'thinking_delta', content: text })\n } else {\n this.emit({ type: 'content', content: text })\n }\n }\n}\n\n/**\n * Check if a model likely embeds think tags in content\n * (i.e. uses native <think> output rather than separate reasoning fields).\n */\nexport function needsStreamingThinkParsing(provider: string, _modelId: string): boolean {\n return provider === 'minimax'\n}\n","/**\n * Thinking / Reasoning level configuration for AI models.\n *\n * Inspired by cherry-studio's MODEL_SUPPORTED_REASONING_EFFORT table.\n * This module is the single source of truth for:\n * - which models support thinking and at what granularity\n * - what `compat` fields pi-ai needs to actually inject the right request params\n *\n * Scope: openai-completions API path only.\n * anthropic-messages / google-generative-ai are out of scope for this version.\n */\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * ThinkingLevel values the UI selector can display.\n * - 'default': no reasoning params sent — rely on model's native default behavior\n * - 'off': explicitly disable reasoning\n * - 'auto': let the model flexibly decide reasoning intensity\n * - others: specific intensity levels\n */\nexport type ThinkingLevel = 'default' | 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'auto'\n\n/** Used internally to categorise a model into a thinking behaviour group. */\ntype ThinkingType =\n | 'none' // model is not a reasoning model\n | 'qwen' // Qwen/GLM-family: boolean enable_thinking (off/on)\n | 'deepseek_v4' // DeepSeek V4+: thinkingFormat:'deepseek', off/high/xhigh\n | 'deepseek_hybrid' // DeepSeek V3.x hybrid inference: supports auto\n | 'o_series' // OpenAI o1/o3: can't disable thinking; low/medium/high only\n | 'gpt5' // OpenAI gpt-5 base: can't disable; minimal/low/medium/high\n | 'gpt5_1' // OpenAI gpt-5.1: off→none supported; off/low/medium/high\n | 'gpt5_2plus' // OpenAI gpt-5.2+: off→none + xhigh; full range\n | 'grok' // xAI grok: can't disable; low/high only\n | 'kimi' // Kimi thinking: off/auto/low/medium/high\n | 'doubao' // Doubao seed reasoning: off/auto/high\n | 'hunyuan' // Hunyuan: enable_thinking boolean, similar to qwen\n | 'gemini' // Gemini 2.5+: thinking level via Google API (off/low/medium/high)\n | 'default' // generic reasoning: off / low / medium / high\n\n// ── Internal: per-type level tables ──────────────────────────────────────────\n\n/** UI options for each type — what the selector will show. */\nconst TYPE_LEVELS: Record<ThinkingType, ThinkingLevel[]> = {\n none: [],\n qwen: ['default', 'off', 'high'],\n deepseek_v4: ['default', 'off', 'high', 'xhigh'],\n deepseek_hybrid: ['default', 'off', 'auto'],\n o_series: ['default', 'low', 'medium', 'high'],\n gpt5: ['default', 'minimal', 'low', 'medium', 'high'],\n gpt5_1: ['default', 'off', 'low', 'medium', 'high'],\n gpt5_2plus: ['default', 'off', 'minimal', 'low', 'medium', 'high', 'xhigh'],\n grok: ['default', 'low', 'high'],\n kimi: ['default', 'off', 'auto', 'low', 'medium', 'high'],\n doubao: ['default', 'off', 'auto', 'high'],\n hunyuan: ['default', 'off', 'high'],\n gemini: ['default', 'off', 'low', 'medium', 'high'],\n default: ['default', 'off', 'low', 'medium', 'high'],\n}\n\n/**\n * thinkingLevelMap entries for models that use `reasoning_effort`.\n * `null` means that level is explicitly not supported.\n * `undefined` (key absent) means pass the level string verbatim.\n * Matches pi-ai's convention in models.generated.js.\n */\nconst TYPE_LEVEL_MAP: Partial<Record<ThinkingType, Partial<Record<ThinkingLevel, string | null>>>> = {\n gpt5: {\n minimal: 'minimal',\n low: 'low',\n medium: 'medium',\n high: 'high',\n },\n gpt5_1: {\n off: 'none',\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: null,\n },\n gpt5_2plus: {\n off: 'none',\n minimal: 'minimal',\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n },\n grok: {\n minimal: null,\n low: 'low',\n medium: null,\n high: 'high',\n xhigh: null,\n },\n kimi: {\n off: 'none',\n auto: 'auto',\n minimal: null,\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: null,\n },\n doubao: {\n off: null,\n auto: 'auto',\n high: 'high',\n },\n deepseek_hybrid: {\n off: null,\n auto: 'auto',\n high: 'high',\n },\n}\n\n// ── Internal: model → ThinkingType classification ─────────────────────────────\n\n/**\n * Broad regex matching common reasoning model naming patterns.\n * Used as a fallback when no specific provider/model rule matches.\n * Excludes explicit '-non-reasoning' variants.\n */\nconst REASONING_FALLBACK_REGEX =\n /^(?!.*-non-reasoning\\b)(o\\d+(?:-[\\w-]+)?|.*\\b(?:reasoning|reasoner|thinking|think)\\b.*|.*-[rR]\\d+.*|.*\\bqwq(?:-[\\w-]+)?\\b.*|.*\\bhunyuan-t1(?:-[\\w-]+)?\\b.*|.*\\bgrok-(?:3-mini|4|4-fast|build)(?:-[\\w-]+)?\\b.*)$/i\n\n/**\n * Classify a model into a ThinkingType based on provider + model id.\n */\nfunction classifyThinkingType(provider: string, modelId: string): ThinkingType {\n const id = modelId.toLowerCase()\n const prov = provider.toLowerCase()\n\n // ── Explicit non-reasoning variants ──\n if (id.includes('-non-reasoning')) return 'none'\n\n // ── Anthropic uses different API format (out of scope) ──\n if (prov === 'anthropic') return 'none'\n\n // ── Gemini 2.5+ reasoning models (via Google Generative AI API) ──\n if (prov === 'gemini') {\n if (/gemini-(?:2\\.5|3(?:\\.\\d+)?-(?:flash|pro)|flash-latest|pro-latest)/i.test(id)) return 'gemini'\n return 'none'\n }\n\n // ── Qwen (official DashScope + self-hosted models containing qwen/qwq) ──\n if (prov === 'qwen' || /\\bqwen|qwq/i.test(id)) return 'qwen'\n\n // ── DeepSeek V4+ ──\n if (/deepseek[_-]v([4-9]|\\d{2,})/i.test(id) || /deepseek-ai\\/deepseek-r1/i.test(id)) return 'deepseek_v4'\n\n // ── DeepSeek V3.x hybrid inference (deepseek-chat, deepseek-v3.x) ──\n if (/deepseek[_-]chat|deepseek[_-]v3/i.test(id)) return 'deepseek_hybrid'\n\n // ── OpenAI gpt-5.x family ──\n if (prov === 'openai' || prov === 'openai-compatible') {\n if (/gpt-5\\.[2-9]|gpt-5\\.[1-9]\\d/.test(id)) return 'gpt5_2plus'\n if (/gpt-5\\.1/.test(id)) return 'gpt5_1'\n if (/gpt-5/.test(id)) return 'gpt5'\n if (/^o\\d/.test(id)) return 'o_series'\n }\n // o-series on other providers (e.g., Azure, OpenRouter)\n if (/^o\\d/.test(id)) return 'o_series'\n\n // ── xAI Grok reasoning models (grok-3-mini, grok-4, grok-4-fast, grok-build) ──\n if (/\\bgrok-(?:3-mini|4|4-fast|build)\\b/i.test(id)) return 'grok'\n\n // ── Kimi thinking models (k2-thinking, k2.5+, k3+) ──\n if (/\\bkimi-k(?:2-thinking|2\\.[5-9]|[3-9])/i.test(id)) return 'kimi'\n if (prov === 'kimi' && /thinking/i.test(id)) return 'kimi'\n\n // ── Doubao seed reasoning ──\n if (/\\bdoubao-.*seed/i.test(id) || (prov === 'doubao' && /seed/i.test(id))) return 'doubao'\n\n // ── Hunyuan reasoning models ──\n if (/\\bhunyuan-(?:t1|a13b)/i.test(id)) return 'hunyuan'\n\n // ── GLM (ZAI) — glm-5, glm-4.5/4.6/4.7, glm-z1, glm-zero-preview ──\n if (prov === 'glm' || /\\bglm-?(?:5|4\\.[5-7]|z1|zero)/i.test(id)) return 'qwen'\n\n // ── SiliconFlow hosted reasoning models (DeepSeek R1 family) ──\n if (prov === 'siliconflow' && /r[1-9]/i.test(id)) return 'deepseek_v4'\n\n // ── OpenRouter: classify by inner model id ──\n if (prov === 'openrouter') {\n if (/deepseek.*(v[4-9]|r\\d)/i.test(id)) return 'deepseek_v4'\n if (/deepseek[_-]?(chat|v3)/i.test(id)) return 'deepseek_hybrid'\n if (/\\bgrok-(?:3-mini|4|4-fast|build)\\b/i.test(id)) return 'grok'\n if (/\\bkimi-k(?:2-thinking|2\\.[5-9]|[3-9])/i.test(id)) return 'kimi'\n if (REASONING_FALLBACK_REGEX.test(id)) return 'default'\n return 'none'\n }\n\n // ── Models using thinking:{type:'disabled'/'enabled'} format (same as DeepSeek) ──\n if (/\\bmimo-v2/i.test(id)) return 'deepseek_hybrid'\n if (/\\bminimax-m[123]/i.test(id)) return 'deepseek_hybrid'\n if (/\\bseed-oss/i.test(id)) return 'deepseek_hybrid'\n\n // ── Known reasoning model families → 'default' bucket (supportsReasoningEffort) ──\n if (/\\bbaichuan-m[23]/i.test(id)) return 'default'\n if (/\\bmagistral/i.test(id)) return 'default'\n if (/\\bmistral-small-2603/i.test(id)) return 'default'\n if (/\\bstep-(?:3|r1-v-mini)/i.test(id)) return 'default'\n if (/\\bgemma-?4/i.test(id)) return 'default'\n if (/\\bpangu-pro-moe/i.test(id)) return 'default'\n if (/\\bsonar-deep-research/i.test(id)) return 'default'\n if (/\\bring-(?:1t|mini|flash)/i.test(id)) return 'default'\n\n // ── Self-hosted / unknown — heuristic from model name ──\n if (/\\bqwen|qwq/i.test(id)) return 'qwen'\n if (/deepseek[_-]?r\\d|deepseek[_-]?v[4-9]/i.test(id)) return 'deepseek_v4'\n if (/^o\\d/i.test(id)) return 'o_series'\n\n // Broad fallback: match common reasoning naming patterns\n if (REASONING_FALLBACK_REGEX.test(id)) return 'default'\n\n return 'none'\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Returns the list of ThinkingLevel values the selector should show for a model.\n * An empty array means the model has no thinking support — hide the selector.\n *\n * Used by: UI (ChatStatusBar) to render the level picker.\n */\nexport function getSupportedThinkingLevels(provider: string, modelId: string): ThinkingLevel[] {\n const type = classifyThinkingType(provider, modelId)\n return TYPE_LEVELS[type]\n}\n\n/**\n * Returns whether the model/provider combo is a reasoning-capable model\n * on the openai-completions path. (anthropic/gemini always return false here.)\n *\n * Used by: buildPiModel to set `PiModel.reasoning`.\n */\nexport function isReasoningModel(provider: string, modelId: string): boolean {\n return classifyThinkingType(provider, modelId) !== 'none'\n}\n\n/**\n * PiModel compat fragment that makes the chosen thinkingLevel actually reach\n * the request body. Must be spread into the PiModel returned by buildPiModel.\n *\n * Used by: buildPiModel (openai-completions branch).\n */\nexport interface ThinkingCompat {\n thinkingFormat?: 'qwen' | 'deepseek'\n supportsReasoningEffort?: true\n thinkingLevelMap?: Partial<Record<ThinkingLevel, string | null>>\n}\n\nexport function getThinkingCompat(provider: string, modelId: string): ThinkingCompat {\n const type = classifyThinkingType(provider, modelId)\n\n if (type === 'none') return {}\n\n // Gemini uses pi-ai's native Google provider; no compat fields needed.\n if (type === 'gemini') return {}\n\n if (type === 'qwen' || type === 'hunyuan') {\n return { thinkingFormat: 'qwen' }\n }\n\n if (type === 'deepseek_v4') {\n return {\n thinkingFormat: 'deepseek',\n thinkingLevelMap: { high: 'high', xhigh: 'max', minimal: null, low: null, medium: null },\n }\n }\n\n if (type === 'deepseek_hybrid') {\n return {\n thinkingFormat: 'deepseek',\n thinkingLevelMap: { auto: 'auto', high: 'high', minimal: null, low: null, medium: null, xhigh: null },\n }\n }\n\n // All remaining types use OpenAI-style reasoning_effort.\n const compat: ThinkingCompat = { supportsReasoningEffort: true }\n const levelMap = TYPE_LEVEL_MAP[type]\n if (levelMap) {\n compat.thinkingLevelMap = levelMap as Partial<Record<ThinkingLevel, string | null>>\n }\n return compat\n}\n","/**\n * Tool result text extraction and truncation.\n *\n * Tool results are persisted (truncated) into assistant message content blocks\n * so later turns can replay them as real toolCall/toolResult message pairs,\n * keeping the model grounded in what it actually retrieved.\n */\n\n/**\n * Max characters of a single tool result persisted into a content block.\n * Bounds both DB row size and the per-result token cost when history is replayed.\n */\nexport const MAX_PERSISTED_TOOL_RESULT_CHARS = 4000\n\nconst TRUNCATION_MARKER = '\\n…[truncated]'\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Extract the text the model saw from an AgentToolResult-shaped value\n * (`{ content: [{ type: 'text', text }], details? }`). Non-text parts are skipped.\n */\nexport function extractToolResultText(toolResult: unknown): string {\n if (!isRecord(toolResult)) return ''\n if (!Array.isArray(toolResult.content)) return ''\n return toolResult.content\n .map((part) => (isRecord(part) && part.type === 'text' && typeof part.text === 'string' ? part.text : ''))\n .filter((text) => text.length > 0)\n .join('\\n')\n}\n\nexport function truncateToolResultText(text: string, maxChars: number = MAX_PERSISTED_TOOL_RESULT_CHARS): string {\n if (text.length <= maxChars) return text\n return text.slice(0, maxChars) + TRUNCATION_MARKER\n}\n","export interface ParsedStableVersion {\n major: number\n minor: number\n patch: number\n}\n\ninterface ParsedComparableVersion extends ParsedStableVersion {\n prerelease: boolean\n}\n\nfunction parseStableVersion(version: string): ParsedStableVersion | null {\n const normalized = version.trim().replace(/^v/i, '')\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)$/.exec(normalized)\n if (!match) return null\n\n return buildParsedVersion(match)\n}\n\nfunction parseComparableCurrentVersion(version: string): ParsedComparableVersion | null {\n const normalized = version.trim().replace(/^v/i, '')\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(-[0-9A-Za-z.-]+)?$/.exec(normalized)\n if (!match) return null\n\n return {\n ...buildParsedVersion(match),\n prerelease: Boolean(match[4]),\n }\n}\n\nfunction buildParsedVersion(match: RegExpExecArray): ParsedStableVersion {\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n }\n}\n\nexport function isStableVersion(version: string): boolean {\n return parseStableVersion(version) !== null\n}\n\nexport function isNewerStableVersion(latest: string, current: string): boolean {\n const latestVersion = parseStableVersion(latest)\n const currentVersion = parseComparableCurrentVersion(current)\n if (!latestVersion || !currentVersion) return false\n\n if (latestVersion.major !== currentVersion.major) return latestVersion.major > currentVersion.major\n if (latestVersion.minor !== currentVersion.minor) return latestVersion.minor > currentVersion.minor\n if (latestVersion.patch !== currentVersion.patch) return latestVersion.patch > currentVersion.patch\n return currentVersion.prerelease\n}\n","export type ChartType = 'bar' | 'line' | 'pie' | 'heatmap'\n\nexport type ChartFieldType = 'string' | 'number' | 'integer' | 'boolean' | 'date' | 'datetime' | 'category'\n\nexport interface ChartField {\n name: string\n type: ChartFieldType\n label?: string\n unit?: string\n}\n\nexport interface ChartEncoding {\n x?: string\n y?: string\n value?: string\n label?: string\n series?: string\n color?: string\n}\n\nexport interface ChartSpec {\n version: 1\n type: ChartType\n title: string\n subtitle?: string\n description?: string\n encoding: ChartEncoding\n fields?: ChartField[]\n unit?: string\n filters?: {\n timeRange?: { startTs?: number; endTs?: number; label?: string }\n members?: Array<{ id?: number; name: string }>\n keywords?: string[]\n messageTypes?: number[]\n custom?: Record<string, unknown>\n }\n display?: {\n horizontal?: boolean\n stacked?: boolean\n showLegend?: boolean\n showDataZoom?: boolean\n height?: number\n }\n}\n\nexport interface ChartDataset {\n columns: ChartField[]\n rows: Record<string, unknown>[]\n}\n\nexport interface BarChartRenderData {\n labels: string[]\n values: number[]\n}\n\nexport interface LineChartRenderData {\n labels: string[]\n values: number[]\n series?: Array<{ name: string; values: number[] }>\n}\n\nexport interface PieChartRenderData {\n labels: string[]\n values: number[]\n}\n\nexport interface HeatmapChartRenderData {\n xLabels: string[]\n yLabels: string[]\n data: Array<[number, number, number]>\n}\n\nexport type ChartRenderData = BarChartRenderData | LineChartRenderData | PieChartRenderData | HeatmapChartRenderData\n\nexport interface ChartPayload {\n version: 1\n spec: ChartSpec\n dataset: ChartDataset\n data: ChartRenderData\n rowCount: number\n truncated?: boolean\n}\n\nexport class ChartValidationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ChartValidationError'\n }\n}\n\nconst CHART_TYPES = new Set<ChartType>(['bar', 'line', 'pie', 'heatmap'])\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction asFieldName(value: unknown, label: string): string {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new ChartValidationError(`${label} must be a non-empty field name`)\n }\n return value\n}\n\nfunction assertFieldExists(rows: Record<string, unknown>[], field: string): void {\n if (rows.length === 0) return\n if (!Object.prototype.hasOwnProperty.call(rows[0], field)) {\n throw new ChartValidationError(`Field \"${field}\" does not exist in SQL result`)\n }\n}\n\nfunction toLabel(value: unknown): string {\n if (value === null || value === undefined || value === '') return '(empty)'\n return String(value)\n}\n\nfunction toNumber(value: unknown, field: string): number {\n const num = typeof value === 'number' ? value : Number(value)\n if (!Number.isFinite(num)) {\n throw new ChartValidationError(`Field \"${field}\" contains non-numeric value \"${String(value)}\"`)\n }\n return num\n}\n\nfunction inferFieldType(values: unknown[]): ChartFieldType {\n const present = values.filter((v) => v !== null && v !== undefined)\n if (present.length === 0) return 'string'\n if (present.every((v) => typeof v === 'number' && Number.isInteger(v))) return 'integer'\n if (\n present.every(\n (v) => typeof v === 'number' || (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v)))\n )\n ) {\n return 'number'\n }\n if (present.every((v) => typeof v === 'boolean')) return 'boolean'\n return 'category'\n}\n\nexport function normalizeChartSpec(raw: unknown): ChartSpec {\n if (!isRecord(raw)) throw new ChartValidationError('chartSpec must be an object')\n if (raw.version !== 1) throw new ChartValidationError('chartSpec.version must be 1')\n if (typeof raw.type !== 'string' || !CHART_TYPES.has(raw.type as ChartType)) {\n throw new ChartValidationError('chartSpec.type must be one of: bar, line, pie, heatmap')\n }\n if (typeof raw.title !== 'string' || raw.title.trim().length === 0) {\n throw new ChartValidationError('chartSpec.title must be a non-empty string')\n }\n if (!isRecord(raw.encoding)) throw new ChartValidationError('chartSpec.encoding must be an object')\n\n const spec = raw as unknown as ChartSpec\n switch (spec.type) {\n case 'bar':\n case 'line':\n asFieldName(spec.encoding.x, 'encoding.x')\n asFieldName(spec.encoding.y, 'encoding.y')\n break\n case 'pie':\n asFieldName(spec.encoding.label, 'encoding.label')\n asFieldName(spec.encoding.value, 'encoding.value')\n break\n case 'heatmap':\n asFieldName(spec.encoding.x, 'encoding.x')\n asFieldName(spec.encoding.y, 'encoding.y')\n asFieldName(spec.encoding.value, 'encoding.value')\n break\n }\n\n return {\n ...spec,\n title: spec.title.trim(),\n }\n}\n\nexport function inferChartDataset(rows: Record<string, unknown>[], declaredFields?: ChartField[]): ChartDataset {\n const declared = new Map((declaredFields ?? []).map((field) => [field.name, field]))\n const columnNames = rows.length > 0 ? Object.keys(rows[0]) : [...declared.keys()]\n const columns = columnNames.map((name) => {\n const declaredField = declared.get(name)\n if (declaredField) return declaredField\n return {\n name,\n type: inferFieldType(rows.map((row) => row[name])),\n }\n })\n\n return { columns, rows }\n}\n\nfunction aggregateByLabel(\n rows: Record<string, unknown>[],\n labelField: string,\n valueField: string\n): {\n labels: string[]\n values: number[]\n} {\n const labels: string[] = []\n const indexByLabel = new Map<string, number>()\n const values: number[] = []\n\n for (const row of rows) {\n const label = toLabel(row[labelField])\n let index = indexByLabel.get(label)\n if (index === undefined) {\n index = labels.length\n indexByLabel.set(label, index)\n labels.push(label)\n values.push(0)\n }\n values[index] += toNumber(row[valueField], valueField)\n }\n\n return { labels, values }\n}\n\nfunction buildLineData(rows: Record<string, unknown>[], spec: ChartSpec): LineChartRenderData {\n const xField = spec.encoding.x!\n const yField = spec.encoding.y!\n const seriesField = spec.encoding.series\n\n if (!seriesField) return aggregateByLabel(rows, xField, yField)\n\n assertFieldExists(rows, seriesField)\n const labels: string[] = []\n const labelIndex = new Map<string, number>()\n const seriesNames: string[] = []\n const seriesIndex = new Map<string, number>()\n const matrix: number[][] = []\n\n for (const row of rows) {\n const label = toLabel(row[xField])\n const seriesName = toLabel(row[seriesField])\n let xIndex = labelIndex.get(label)\n if (xIndex === undefined) {\n xIndex = labels.length\n labelIndex.set(label, xIndex)\n labels.push(label)\n for (const values of matrix) values.push(0)\n }\n\n let sIndex = seriesIndex.get(seriesName)\n if (sIndex === undefined) {\n sIndex = seriesNames.length\n seriesIndex.set(seriesName, sIndex)\n seriesNames.push(seriesName)\n matrix.push(Array(labels.length).fill(0))\n }\n\n matrix[sIndex][xIndex] += toNumber(row[yField], yField)\n }\n\n const series = seriesNames.map((name, index) => ({ name, values: matrix[index] }))\n return {\n labels,\n values: series[0]?.values ?? [],\n series,\n }\n}\n\nfunction buildHeatmapData(rows: Record<string, unknown>[], spec: ChartSpec): HeatmapChartRenderData {\n const xField = spec.encoding.x!\n const yField = spec.encoding.y!\n const valueField = spec.encoding.value!\n const xLabels: string[] = []\n const yLabels: string[] = []\n const xIndex = new Map<string, number>()\n const yIndex = new Map<string, number>()\n const values = new Map<string, number>()\n\n for (const row of rows) {\n const xLabel = toLabel(row[xField])\n const yLabel = toLabel(row[yField])\n let xi = xIndex.get(xLabel)\n if (xi === undefined) {\n xi = xLabels.length\n xIndex.set(xLabel, xi)\n xLabels.push(xLabel)\n }\n let yi = yIndex.get(yLabel)\n if (yi === undefined) {\n yi = yLabels.length\n yIndex.set(yLabel, yi)\n yLabels.push(yLabel)\n }\n const key = `${xi}:${yi}`\n values.set(key, (values.get(key) ?? 0) + toNumber(row[valueField], valueField))\n }\n\n const data: Array<[number, number, number]> = []\n for (const [key, value] of values.entries()) {\n const [x, y] = key.split(':').map(Number)\n data.push([x, y, value])\n }\n\n return { xLabels, yLabels, data }\n}\n\nexport function buildChartPayload(\n rows: Record<string, unknown>[],\n rawSpec: unknown,\n options?: { truncated?: boolean }\n): ChartPayload {\n const spec = normalizeChartSpec(rawSpec)\n const dataset = inferChartDataset(rows, spec.fields)\n\n const requiredFields = new Set<string>()\n for (const field of Object.values(spec.encoding)) {\n if (typeof field === 'string' && field) requiredFields.add(field)\n }\n for (const field of requiredFields) assertFieldExists(rows, field)\n\n let data: ChartRenderData\n switch (spec.type) {\n case 'bar':\n data = aggregateByLabel(rows, spec.encoding.x!, spec.encoding.y!)\n break\n case 'line':\n data = buildLineData(rows, spec)\n break\n case 'pie':\n data = aggregateByLabel(rows, spec.encoding.label!, spec.encoding.value!)\n break\n case 'heatmap':\n data = buildHeatmapData(rows, spec)\n break\n }\n\n return {\n version: 1,\n spec,\n dataset,\n data,\n rowCount: rows.length,\n truncated: options?.truncated,\n }\n}\n","/**\n * 词性标签定义(静态数据,平台无关)\n */\n\nimport type { PosTagInfo } from './types'\n\nexport const POS_TAG_DEFINITIONS: PosTagInfo[] = [\n { tag: 'n', name: '名词', description: '普通名词', meaningful: true },\n { tag: 'nr', name: '人名', description: '人名', meaningful: true },\n { tag: 'ns', name: '地名', description: '地名', meaningful: true },\n { tag: 'nt', name: '机构名', description: '机构团体名', meaningful: true },\n { tag: 'nz', name: '其他专名', description: '其他专有名词', meaningful: true },\n { tag: 'nw', name: '作品名', description: '作品名', meaningful: true },\n { tag: 'v', name: '动词', description: '普通动词', meaningful: false },\n { tag: 'vn', name: '动名词', description: '动名词', meaningful: true },\n { tag: 'vd', name: '副动词', description: '副动词', meaningful: false },\n { tag: 'vg', name: '动语素', description: '动词性语素', meaningful: false },\n { tag: 'a', name: '形容词', description: '普通形容词', meaningful: true },\n { tag: 'an', name: '名形词', description: '名形词', meaningful: true },\n { tag: 'ad', name: '副形词', description: '副形词', meaningful: true },\n { tag: 'ag', name: '形语素', description: '形容词性语素', meaningful: true },\n { tag: 'i', name: '成语', description: '成语', meaningful: true },\n { tag: 'l', name: '习用语', description: '习用语', meaningful: true },\n { tag: 'j', name: '简称', description: '简称略语', meaningful: true },\n { tag: 'd', name: '副词', description: '副词', meaningful: false },\n { tag: 'p', name: '介词', description: '介词', meaningful: false },\n { tag: 'c', name: '连词', description: '连词', meaningful: false },\n { tag: 'u', name: '助词', description: '助词', meaningful: false },\n { tag: 'r', name: '代词', description: '代词', meaningful: false },\n { tag: 'm', name: '数词', description: '数词', meaningful: false },\n { tag: 'q', name: '量词', description: '量词', meaningful: false },\n { tag: 'f', name: '方位词', description: '方位词', meaningful: false },\n { tag: 't', name: '时间词', description: '时间词', meaningful: false },\n { tag: 'e', name: '叹词', description: '叹词', meaningful: false },\n { tag: 'y', name: '语气词', description: '语气词', meaningful: false },\n { tag: 'o', name: '拟声词', description: '拟声词', meaningful: false },\n { tag: 'x', name: '非语素字', description: '非语素字', meaningful: false },\n { tag: 'w', name: '标点符号', description: '标点符号', meaningful: false },\n]\n\nexport const MEANINGFUL_POS_TAGS = new Set(POS_TAG_DEFINITIONS.filter((t) => t.meaningful).map((t) => t.tag))\n","import type { SupportedLocale } from './types'\n\n/**\n * 停用词表(平台无关的静态数据)\n */\n\n/**\n * 停用词表\n * 用于过滤无意义的高频词\n */\n\n/** 中文停用词 */\nexport const CHINESE_STOPWORDS = new Set([\n // 代词\n '我',\n '你',\n '他',\n '她',\n '它',\n '我们',\n '你们',\n '他们',\n '她们',\n '它们',\n '自己',\n '别人',\n '大家',\n '谁',\n '什么',\n '哪',\n '哪里',\n '哪儿',\n '这',\n '那',\n '这个',\n '那个',\n '这些',\n '那些',\n '这里',\n '那里',\n '这儿',\n '那儿',\n '这样',\n '那样',\n // 助词\n '的',\n '地',\n '得',\n '了',\n '着',\n '过',\n '吗',\n '呢',\n '吧',\n '啊',\n '呀',\n '哇',\n '哦',\n '嗯',\n '噢',\n '喔',\n '呃',\n '唉',\n '哎',\n '嘛',\n // 介词\n '在',\n '从',\n '到',\n '向',\n '往',\n '把',\n '被',\n '给',\n '跟',\n '和',\n '与',\n '对',\n '比',\n '为',\n '因',\n '由',\n '以',\n '按',\n '用',\n '让',\n // 连词\n '和',\n '与',\n '或',\n '或者',\n '而',\n '并',\n '并且',\n '但',\n '但是',\n '可是',\n '然而',\n '不过',\n '只是',\n '如果',\n '要是',\n '假如',\n '虽然',\n '尽管',\n '即使',\n '所以',\n '因此',\n '于是',\n '那么',\n '因为',\n '由于',\n '既然',\n '为了',\n '以便',\n // 副词\n '不',\n '没',\n '没有',\n '很',\n '太',\n '最',\n '更',\n '也',\n '都',\n '就',\n '才',\n '又',\n '再',\n '还',\n '却',\n '只',\n '只是',\n '已',\n '已经',\n '曾',\n '曾经',\n '正',\n '正在',\n '将',\n '将要',\n '会',\n '能',\n '可以',\n '可能',\n '应该',\n '必须',\n '一定',\n '大概',\n '也许',\n '或许',\n '其实',\n '确实',\n '真的',\n '当然',\n '一直',\n '总是',\n '经常',\n '常常',\n '往往',\n '偶尔',\n '几乎',\n '差不多',\n '简直',\n '反正',\n '终于',\n // 量词\n '个',\n '只',\n '条',\n '件',\n '位',\n '种',\n '些',\n '点',\n '下',\n '次',\n // 数词\n '一',\n '二',\n '三',\n '四',\n '五',\n '六',\n '七',\n '八',\n '九',\n '十',\n '百',\n '千',\n '万',\n '亿',\n '两',\n '几',\n '多',\n '少',\n '第',\n '每',\n // 动词(常见无实意动词)\n '是',\n '有',\n '在',\n '做',\n '去',\n '来',\n '说',\n '看',\n '想',\n '要',\n '能',\n '会',\n '让',\n '给',\n '叫',\n '用',\n '打',\n '把',\n '被',\n '到',\n // 其他常见词\n '上',\n '下',\n '前',\n '后',\n '里',\n '外',\n '中',\n '内',\n '左',\n '右',\n '东',\n '南',\n '西',\n '北',\n '时',\n '时候',\n '现在',\n '今天',\n '明天',\n '昨天',\n '年',\n '月',\n '日',\n '号',\n '点',\n '分',\n '秒',\n '周',\n '星期',\n // 网络聊天常见无意义词\n '好',\n '好的',\n '行',\n '可以',\n '嗯嗯',\n '哈',\n '呵',\n '额',\n '恩',\n '昂',\n 'ok',\n 'OK',\n '好吧',\n '知道',\n '知道了',\n '谢谢',\n '感谢',\n '抱歉',\n '不好意思',\n // 语气词和程度词(虽然词性是名词/动词,但在聊天中无实际意义)\n '感觉',\n '有点',\n '可能',\n '应该',\n '好像',\n '觉得',\n '认为',\n '看看',\n '看到',\n '说',\n '问',\n '找',\n '弄',\n '搞',\n '搞定',\n '整',\n '干',\n '做',\n '来',\n '去',\n '有',\n '没有',\n '没',\n '是不是',\n '有没有',\n '能不能',\n '会不会',\n '要不要',\n '怎样',\n '如何',\n '为何',\n '为什么',\n '怎么',\n '怎么样',\n '怎么办',\n '东西',\n '事情',\n '事',\n '问题',\n '时候',\n '地方',\n '情况',\n '样子',\n '意思',\n '一下',\n '一点',\n '一些',\n '一样',\n '一起',\n '一直',\n '一般',\n '一定',\n '差不多',\n])\n\n/** 英文停用词 */\nexport const ENGLISH_STOPWORDS = new Set([\n // Articles\n 'a',\n 'an',\n 'the',\n // Pronouns\n 'i',\n 'me',\n 'my',\n 'myself',\n 'we',\n 'our',\n 'ours',\n 'ourselves',\n 'you',\n 'your',\n 'yours',\n 'yourself',\n 'yourselves',\n 'he',\n 'him',\n 'his',\n 'himself',\n 'she',\n 'her',\n 'hers',\n 'herself',\n 'it',\n 'its',\n 'itself',\n 'they',\n 'them',\n 'their',\n 'theirs',\n 'themselves',\n 'what',\n 'which',\n 'who',\n 'whom',\n 'this',\n 'that',\n 'these',\n 'those',\n // Prepositions\n 'in',\n 'on',\n 'at',\n 'by',\n 'for',\n 'with',\n 'about',\n 'against',\n 'between',\n 'into',\n 'through',\n 'during',\n 'before',\n 'after',\n 'above',\n 'below',\n 'to',\n 'from',\n 'up',\n 'down',\n 'out',\n 'off',\n 'over',\n 'under',\n 'again',\n 'further',\n // Conjunctions\n 'and',\n 'but',\n 'or',\n 'nor',\n 'so',\n 'yet',\n 'both',\n 'either',\n 'neither',\n 'not',\n 'only',\n 'own',\n 'same',\n 'than',\n 'too',\n 'very',\n 'just',\n // Be verbs\n 'am',\n 'is',\n 'are',\n 'was',\n 'were',\n 'be',\n 'been',\n 'being',\n // Have verbs\n 'have',\n 'has',\n 'had',\n 'having',\n // Do verbs\n 'do',\n 'does',\n 'did',\n 'doing',\n // Modal verbs\n 'will',\n 'would',\n 'shall',\n 'should',\n 'can',\n 'could',\n 'may',\n 'might',\n 'must',\n // Other common words\n 'if',\n 'then',\n 'else',\n 'when',\n 'where',\n 'why',\n 'how',\n 'all',\n 'each',\n 'every',\n 'both',\n 'few',\n 'more',\n 'most',\n 'other',\n 'some',\n 'such',\n 'no',\n 'any',\n 'now',\n 'here',\n 'there',\n 'of',\n 'as',\n // Contractions (without apostrophe)\n 'dont',\n 'doesnt',\n 'didnt',\n 'wont',\n 'wouldnt',\n 'cant',\n 'couldnt',\n 'shouldnt',\n 'isnt',\n 'arent',\n 'wasnt',\n 'werent',\n 'havent',\n 'hasnt',\n 'hadnt',\n // Chat common words\n 'ok',\n 'okay',\n 'yes',\n 'no',\n 'yeah',\n 'yep',\n 'nope',\n 'sure',\n 'thanks',\n 'thank',\n 'please',\n 'sorry',\n 'hi',\n 'hello',\n 'hey',\n 'bye',\n 'goodbye',\n 'well',\n 'like',\n 'know',\n 'think',\n 'want',\n 'need',\n 'get',\n 'got',\n 'go',\n 'going',\n 'come',\n 'coming',\n 'make',\n 'made',\n 'take',\n 'took',\n 'see',\n 'saw',\n 'look',\n 'looking',\n 'say',\n 'said',\n 'tell',\n 'told',\n 'ask',\n 'asked',\n 'let',\n 'put',\n 'keep',\n 'give',\n 'gave',\n 'find',\n 'found',\n 'try',\n 'tried',\n // Time words\n 'today',\n 'tomorrow',\n 'yesterday',\n 'now',\n 'then',\n 'always',\n 'never',\n 'sometimes',\n 'often',\n 'usually',\n 'still',\n 'already',\n 'soon',\n 'later',\n])\n\n/** 日语停用词 */\nexport const JAPANESE_STOPWORDS = new Set([\n // 助詞\n 'の',\n 'に',\n 'は',\n 'を',\n 'た',\n 'が',\n 'で',\n 'て',\n 'と',\n 'し',\n 'れ',\n 'さ',\n 'ある',\n 'いる',\n 'も',\n 'する',\n 'から',\n 'な',\n 'こと',\n 'として',\n 'い',\n 'や',\n 'れる',\n 'など',\n 'なっ',\n 'ない',\n 'この',\n 'ため',\n 'その',\n 'あっ',\n 'よう',\n 'また',\n 'もの',\n 'という',\n 'あり',\n 'まで',\n 'られ',\n 'なる',\n 'へ',\n 'か',\n 'だ',\n 'これ',\n 'によって',\n 'により',\n 'おり',\n 'より',\n 'による',\n 'ず',\n 'なり',\n 'られる',\n 'において',\n 'ば',\n 'なかっ',\n 'なく',\n 'しかし',\n 'について',\n 'せ',\n 'だっ',\n 'その後',\n 'できる',\n 'それ',\n 'う',\n 'ので',\n 'なお',\n 'のみ',\n 'でき',\n 'き',\n 'つ',\n 'における',\n 'および',\n 'いう',\n 'さらに',\n 'でも',\n 'ら',\n 'たり',\n 'その他',\n 'に関する',\n 'たち',\n 'ます',\n 'ん',\n 'なら',\n 'に対して',\n // 代名詞\n '私',\n '僕',\n '俺',\n '自分',\n 'あなた',\n '彼',\n '彼女',\n 'それ',\n 'これ',\n 'あれ',\n 'ここ',\n 'そこ',\n 'あそこ',\n 'どこ',\n 'みんな',\n '皆',\n // 接続詞\n 'そして',\n 'しかし',\n 'でも',\n 'だから',\n 'それで',\n 'だけど',\n 'けど',\n 'ところで',\n 'さて',\n 'つまり',\n 'すなわち',\n 'ただし',\n 'もし',\n 'また',\n // 副詞\n 'とても',\n 'すごく',\n 'もう',\n 'まだ',\n 'よく',\n 'ちょっと',\n 'ちょと',\n 'もっと',\n 'やっぱり',\n 'やはり',\n 'たぶん',\n 'きっと',\n 'ぜんぜん',\n 'ほんとに',\n 'ほんと',\n 'かなり',\n 'だいたい',\n 'ほとんど',\n 'まあ',\n 'なんか',\n 'なんとなく',\n // 感動詞・フィラー\n 'あ',\n 'ああ',\n 'えー',\n 'うん',\n 'えっ',\n 'おお',\n 'へー',\n 'ふーん',\n 'はい',\n 'いいえ',\n 'うーん',\n 'まあ',\n 'ねえ',\n 'ほら',\n 'あのね',\n 'えっと',\n 'その',\n // 動詞(高頻度)\n 'いる',\n 'ある',\n 'する',\n 'なる',\n 'できる',\n 'いく',\n 'くる',\n 'みる',\n 'おもう',\n 'いう',\n 'やる',\n 'くれる',\n 'もらう',\n 'あげる',\n 'しまう',\n 'おく',\n // 形容詞(高頻度)\n 'いい',\n 'ない',\n 'よい',\n 'すごい',\n 'おおきい',\n 'ちいさい',\n // 助動詞\n 'です',\n 'ます',\n 'でした',\n 'ました',\n 'ません',\n 'だった',\n 'でしょう',\n // チャットでの高頻語\n 'www',\n 'ww',\n 'lol',\n 'ok',\n 'おけ',\n 'りょ',\n 'おつ',\n 'わら',\n '笑',\n 'それな',\n 'たしかに',\n 'マジ',\n 'まじ',\n 'ガチ',\n 'がち',\n])\n\n/**\n * 获取停用词集合\n * @param locale 语言\n * @returns 停用词集合\n */\nexport function getStopwords(locale: string): Set<string> {\n const normalizedLocale = normalizeStopwordLocale(locale)\n\n if (normalizedLocale.startsWith('zh')) {\n return CHINESE_STOPWORDS\n }\n if (normalizedLocale === 'ja-JP') {\n return JAPANESE_STOPWORDS\n }\n return ENGLISH_STOPWORDS\n}\n\n/**\n * 判断是否为停用词\n * @param word 词语\n * @param locale 语言\n * @returns 是否为停用词\n */\nexport function isStopword(word: string, locale: string): boolean {\n const normalizedLocale = normalizeStopwordLocale(locale)\n const stopwords = getStopwords(normalizedLocale)\n const normalizedWord = normalizedLocale === 'en-US' ? word.toLowerCase() : word\n return stopwords.has(normalizedWord)\n}\n\n/**\n * 规范化停用词处理使用的 locale。\n * 这里额外兜底一次,避免上游误传数字或其他异常值时直接导致运行时崩溃。\n */\nfunction normalizeStopwordLocale(locale: string): SupportedLocale {\n if (typeof locale !== 'string') {\n return 'en-US'\n }\n\n if (locale.startsWith('zh')) {\n return 'zh-CN'\n }\n\n if (locale === 'ja-JP') {\n return 'ja-JP'\n }\n\n return 'en-US'\n}\n","/**\n * 文本处理工具(纯函数,平台无关)\n */\n\nconst EMOJI_REGEX = /[\\u{1F300}-\\u{1FAFF}\\u{1F1E0}-\\u{1F1FF}\\u{2600}-\\u{26FF}\\u{2700}-\\u{27BF}]/gu\nconst EMOJI_VARIATION_SELECTOR_REGEX = /\\u{FE0F}/gu\nconst PUNCTUATION_REGEX = /[!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~,。!?、;:\"\"''()【】《》…—~·\\s]/g\nconst URL_REGEX = /https?:\\/\\/[^\\s]+/g\nconst MENTION_REGEX = /@[^\\s@]+/g\nconst PURE_NUMBER_REGEX = /^\\d+$/\nconst SYSTEM_PLACEHOLDER_REGEX =\n /\\[(?:图片|视频|语音|文件|动画表情|表情|链接|位置|名片|红包|转账|音乐|Image|Video|Voice|File|Sticker|Link)\\]/gi\nconst BRACKET_EMOJI_PLACEHOLDER_REGEX =\n /(?:\\[|【)([\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Letter}\\p{Number}_-]{1,16})(?:\\]|】)/gu\nconst CJK_TEXT_REGEX = /[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}]/u\n\nconst BRACKET_EMOJI_MAP: Record<string, string> = {\n 破涕为笑: '😂',\n 微笑: '🙂',\n 呲牙: '😁',\n 大笑: '😄',\n 笑哭: '😂',\n 流泪: '😢',\n 捂脸: '🤦',\n 发呆: '😳',\n 害羞: '😊',\n 调皮: '😜',\n 色: '😍',\n 惊讶: '😮',\n 撇嘴: '😒',\n 难过: '😞',\n 酷: '😎',\n 抓狂: '😫',\n 吐: '🤮',\n 偷笑: '🤭',\n 可爱: '😊',\n 白眼: '🙄',\n 傲慢: '😤',\n 饥饿: '😋',\n 困: '😴',\n 惊恐: '😱',\n 流汗: '😅',\n 憨笑: '😄',\n 悠闲: '😌',\n 奋斗: '💪',\n 咒骂: '😡',\n 疑问: '❓',\n 嘘: '🤫',\n 晕: '😵',\n 衰: '😞',\n 敲打: '👊',\n 再见: '👋',\n 擦汗: '😅',\n 抠鼻: '👃',\n 鼓掌: '👏',\n 坏笑: '😏',\n 左哼哼: '😤',\n 右哼哼: '😤',\n 哈欠: '🥱',\n 鄙视: '😒',\n 委屈: '🥺',\n 快哭了: '😢',\n 阴险: '😏',\n 亲亲: '😘',\n 吓: '😱',\n 可怜: '🥺',\n 菜刀: '🔪',\n 西瓜: '🍉',\n 啤酒: '🍺',\n 篮球: '🏀',\n 乒乓: '🏓',\n 咖啡: '☕',\n 饭: '🍚',\n 猪头: '🐷',\n 玫瑰: '🌹',\n 凋谢: '🥀',\n 爱心: '❤️',\n 心碎: '💔',\n 蛋糕: '🎂',\n 闪电: '⚡',\n 炸弹: '💣',\n 刀: '🔪',\n 足球: '⚽',\n 便便: '💩',\n 月亮: '🌙',\n 太阳: '☀️',\n 礼物: '🎁',\n 拥抱: '🤗',\n 强: '👍',\n 弱: '👎',\n 握手: '🤝',\n 胜利: '✌️',\n 抱拳: '🙏',\n 勾引: '☝️',\n 拳头: '✊',\n 差劲: '👎',\n 爱你: '🤟',\n NO: '🙅',\n OK: '👌',\n}\n\n/**\n * 清理文本:移除 URL、@提及、表情、标点等\n */\nexport function cleanText(text: string): string {\n return text\n .replace(URL_REGEX, ' ')\n .replace(MENTION_REGEX, ' ')\n .replace(SYSTEM_PLACEHOLDER_REGEX, ' ')\n .replace(BRACKET_EMOJI_PLACEHOLDER_REGEX, (match, name: string) => {\n if (BRACKET_EMOJI_MAP[name]) return BRACKET_EMOJI_MAP[name]\n return CJK_TEXT_REGEX.test(name) ? ' ' : match\n })\n .replace(EMOJI_REGEX, ' ')\n .replace(EMOJI_VARIATION_SELECTOR_REGEX, ' ')\n .replace(PUNCTUATION_REGEX, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/**\n * 判断是否为有效词语\n */\nexport function isValidWord(\n word: string,\n locale: string,\n minLength: number,\n enableStopwords: boolean,\n isStopwordFn: (word: string, locale: string) => boolean\n): boolean {\n if (!word || word.trim().length === 0) return false\n if (PURE_NUMBER_REGEX.test(word)) return false\n if (word.length < minLength) return false\n if (enableStopwords && isStopwordFn(word, locale)) return false\n return true\n}\n","/**\n * Owner profile matching — platform-independent \"who am I\" resolution.\n *\n * Given a platform-level owner profile (stored in preferences) and the member\n * list of a session, decide which member is the owner. Matching is strictly\n * deterministic: exact platformId first, then normalized-name equality for an\n * allowlisted set of platforms whose exports lack stable native IDs.\n * No fuzzy matching, no heuristics, no LLM inference.\n */\n\nimport type { OwnerProfile } from '@openchatlab/shared-types'\n\n/**\n * Platforms whose text exports use display names as platformId, so a\n * normalized-name fallback is safe and necessary. All other platforms\n * (including 'unknown') match by exact platformId only.\n */\nexport const NAME_MATCH_PLATFORMS: ReadonlySet<string> = new Set(['whatsapp', 'line', 'instagram'])\n\nexport function isNameMatchPlatform(platform: string): boolean {\n return NAME_MATCH_PLATFORMS.has(platform)\n}\n\n/** Minimal member shape needed for owner matching. */\nexport interface OwnerMatchCandidate {\n platformId: string\n accountName?: string | null\n groupNickname?: string | null\n aliases?: string[] | null\n /** Computed display name, if the caller already derived one. */\n displayName?: string | null\n}\n\nexport type OwnerMatchResult =\n | { type: 'exact'; platformId: string }\n | { type: 'name'; platformId: string }\n | { type: 'none' }\n | { type: 'ambiguous'; platformIds: string[] }\n\n// Invisible direction/control characters that chat exports commonly embed\n// around names (LRM/RLM, directional embeddings/isolates, zero-width chars, BOM).\nconst INVISIBLE_CHARS_RE = /[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF]/g\n\n/**\n * Deterministic name normalization for matching:\n * strip invisible chars → NFKC → collapse whitespace → trim → lowercase.\n * Stored names keep their original form; normalize only at compare time.\n */\nexport function normalizeOwnerName(name: string): string {\n return name.replace(INVISIBLE_CHARS_RE, '').normalize('NFKC').replace(/\\s+/g, ' ').trim().toLowerCase()\n}\n\n/** Collect all candidate name strings of a member (original, non-normalized, deduped, non-empty). */\nexport function collectCandidateNames(member: OwnerMatchCandidate): string[] {\n const names = [\n member.platformId,\n member.accountName ?? '',\n member.groupNickname ?? '',\n ...(member.aliases ?? []),\n member.displayName ?? '',\n ]\n const result: string[] = []\n const seen = new Set<string>()\n for (const name of names) {\n if (!name || !name.trim()) continue\n if (seen.has(name)) continue\n seen.add(name)\n result.push(name)\n }\n return result\n}\n\n/**\n * Merge a member's candidate names into an existing confirmedNames list.\n * Keeps original strings; dedupes by exact string equality, preserving order.\n */\nexport function mergeConfirmedNames(existing: string[], member: OwnerMatchCandidate): string[] {\n const result: string[] = []\n const seen = new Set<string>()\n for (const name of [...existing, ...collectCandidateNames(member)]) {\n if (!name || !name.trim()) continue\n if (seen.has(name)) continue\n seen.add(name)\n result.push(name)\n }\n return result\n}\n\n/**\n * Match a platform owner profile against a session's members.\n *\n * 1. Exact platformId match wins immediately.\n * 2. Name fallback only for NAME_MATCH_PLATFORMS: a member matches when any\n * of its normalized candidate names equals any normalized confirmed name.\n * 3. Exactly one member must match; zero → none, multiple → ambiguous.\n */\nexport function matchOwnerProfile(\n platform: string,\n profile: OwnerProfile,\n members: OwnerMatchCandidate[]\n): OwnerMatchResult {\n const exact = members.find((m) => m.platformId === profile.platformId)\n if (exact) {\n return { type: 'exact', platformId: exact.platformId }\n }\n\n if (!isNameMatchPlatform(platform)) {\n return { type: 'none' }\n }\n\n const confirmed = new Set(profile.confirmedNames.map(normalizeOwnerName).filter((n) => n.length > 0))\n if (confirmed.size === 0) {\n return { type: 'none' }\n }\n\n const matched = members.filter((m) =>\n collectCandidateNames(m).some((name) => confirmed.has(normalizeOwnerName(name)))\n )\n\n if (matched.length === 1) {\n return { type: 'name', platformId: matched[0].platformId }\n }\n if (matched.length > 1) {\n return { type: 'ambiguous', platformIds: matched.map((m) => m.platformId) }\n }\n return { type: 'none' }\n}\n","/**\n * Canonical message deduplication key generator.\n *\n * Used by both Electron (worker import, merger) and Server (importer)\n * to produce deterministic content-hash keys for messages that lack\n * a platform_message_id.\n *\n * Uses a pure-JS FNV-1a 64-bit hash (browser-safe, no Node.js deps).\n * Segments are NUL-separated; content is preceded by a type discriminator\n * so that null content and the literal string \"null\" hash differently.\n *\n * Empty-string content is normalized to null before hashing to match the\n * storage-layer behavior where '' is folded to NULL in SQLite.\n */\n\n/**\n * FNV-1a 64-bit hash implemented with two 32-bit halves.\n * Returns a 16-char hex string (~11 chars base36).\n */\nfunction fnv1a64(input: string): string {\n // FNV offset basis for 64-bit: 0xcbf29ce484222325\n let h0 = 0x811c9dc5 // low 32\n let h1 = 0xcbf29ce4 // high 32\n\n for (let i = 0; i < input.length; i++) {\n const c = input.charCodeAt(i)\n h0 ^= c\n // FNV prime for 64-bit: 0x00000100000001B3\n // Multiply (h1:h0) * 0x01000193 (32-bit FNV prime for low half mixing)\n // and cross-mix to approximate 64-bit FNV\n const t0 = Math.imul(h0, 0x01000193)\n const t1 = Math.imul(h1, 0x01000193) + Math.imul(h0, 0x01)\n h0 = t0 >>> 0\n h1 = t1 >>> 0\n }\n\n const hi = h1.toString(16).padStart(8, '0')\n const lo = h0.toString(16).padStart(8, '0')\n return hi + lo\n}\n\nexport function generateMessageKey(timestamp: number, senderPlatformId: string, content: string | null): string {\n const normalizedContent = content || null\n const parts = [\n String(timestamp),\n senderPlatformId,\n normalizedContent === null ? 'null' : 'text',\n normalizedContent ?? '',\n ]\n return fnv1a64(parts.join('\\0'))\n}\n","/**\n * Merger pure algorithms — platform ID collision detection,\n * normalization, conflict detection, and dedup-sort merge.\n *\n * All functions are pure (no I/O, no Node.js deps).\n */\n\nimport { generateMessageKey } from '../import/dedup'\n\n// ==================== Types ====================\n\nexport interface MergerMember {\n platformId: string\n accountName?: string\n groupNickname?: string\n avatar?: string\n}\n\nexport interface MergerMessage {\n senderPlatformId: string\n senderAccountName?: string\n senderGroupNickname?: string\n timestamp: number\n type: number\n content?: string | null\n}\n\nexport interface MergeConflict {\n id: string\n timestamp: number\n sender: string\n contentLength1: number\n contentLength2: number\n content1: string\n content2: string\n}\n\nexport interface ConflictCheckResult {\n conflicts: MergeConflict[]\n totalMessages: number\n}\n\nexport interface MergedMember {\n platformId: string\n accountName?: string\n groupNickname?: string\n avatar?: string\n}\n\nexport interface MergedMessage {\n sender: string\n accountName?: string\n groupNickname?: string\n timestamp: number\n type: number\n content?: string | null\n}\n\n// ==================== Platform ID collision ====================\n\nexport function getCollidingPlatformIds(\n sources: Array<{ platform: string; members: Array<{ platformId: string }> }>\n): Set<string> {\n const map = new Map<string, Set<string>>()\n for (const source of sources) {\n for (const member of source.members) {\n if (!map.has(member.platformId)) map.set(member.platformId, new Set())\n map.get(member.platformId)!.add(source.platform || 'unknown')\n }\n }\n const result = new Set<string>()\n for (const [id, platforms] of map) {\n if (platforms.size > 1) result.add(id)\n }\n return result\n}\n\nexport function getCollidingPlatformIdsFromMessages(\n allMessages: Array<{ msg: MergerMessage; platform: string }>\n): Set<string> {\n const map = new Map<string, Set<string>>()\n for (const item of allMessages) {\n const pid = item.msg.senderPlatformId\n if (!map.has(pid)) map.set(pid, new Set())\n map.get(pid)!.add(item.platform || 'unknown')\n }\n const result = new Set<string>()\n for (const [id, platforms] of map) {\n if (platforms.size > 1) result.add(id)\n }\n return result\n}\n\nexport function normalizePlatformId(platformId: string, platform: string, collidingIds: Set<string>): string {\n if (!collidingIds.has(platformId)) return platformId\n const np = encodeURIComponent(platform || 'unknown')\n const ni = encodeURIComponent(platformId)\n return `__chatlab_platform__${np}__${ni}`\n}\n\n// ==================== Helpers ====================\n\nfunction getMessageKey(msg: MergerMessage, senderOverride?: string): string {\n return generateMessageKey(msg.timestamp, senderOverride || msg.senderPlatformId, msg.content ?? null)\n}\n\nfunction getDisplayName(msg: MergerMessage): string {\n return msg.senderGroupNickname || msg.senderAccountName || msg.senderPlatformId\n}\n\nfunction isImageOnlyMessage(content: string | undefined | null): boolean {\n if (!content) return false\n return /^\\[图片:\\s*.+\\]$/.test(content.trim())\n}\n\n// ==================== Conflict detection ====================\n\nexport function detectConflictsInMessages(\n allMessages: Array<{ msg: MergerMessage; source: string; platform: string }>\n): ConflictCheckResult {\n const collidingIds = getCollidingPlatformIdsFromMessages(allMessages)\n const conflicts: MergeConflict[] = []\n\n const timeGroups = new Map<number, Array<{ msg: MergerMessage; source: string; platform: string }>>()\n for (const item of allMessages) {\n const ts = item.msg.timestamp\n if (!timeGroups.has(ts)) timeGroups.set(ts, [])\n timeGroups.get(ts)!.push(item)\n }\n\n for (const [ts, items] of timeGroups) {\n if (items.length < 2) continue\n\n const senderGroups = new Map<string, typeof items>()\n for (const item of items) {\n const sender = normalizePlatformId(item.msg.senderPlatformId, item.platform || 'unknown', collidingIds)\n if (!senderGroups.has(sender)) senderGroups.set(sender, [])\n senderGroups.get(sender)!.push(item)\n }\n\n for (const [sender, senderItems] of senderGroups) {\n if (senderItems.length < 2) continue\n const sources = new Set(senderItems.map((it) => it.source))\n if (sources.size < 2) continue\n\n const contentGroups = new Map<string, typeof senderItems>()\n for (const item of senderItems) {\n const c = item.msg.content || ''\n if (!contentGroups.has(c)) contentGroups.set(c, [])\n contentGroups.get(c)!.push(item)\n }\n\n if (contentGroups.size > 1) {\n const entries = Array.from(contentGroups.entries())\n for (let i = 0; i < entries.length - 1; i++) {\n for (let j = i + 1; j < entries.length; j++) {\n const [c1, items1] = entries[i]\n const [c2, items2] = entries[j]\n const item1 = items1[0]\n const item2 = items2.find((it) => it.source !== item1.source)\n if (!item2) continue\n if (isImageOnlyMessage(c1) && isImageOnlyMessage(c2)) continue\n\n conflicts.push({\n id: `conflict_${ts}_${sender}_${conflicts.length}`,\n timestamp: ts,\n sender: getDisplayName(item1.msg) || sender,\n contentLength1: c1.length,\n contentLength2: c2.length,\n content1: c1,\n content2: c2,\n })\n }\n }\n }\n }\n }\n\n const uniqueKeys = new Set<string>()\n for (const item of allMessages) {\n const nid = normalizePlatformId(item.msg.senderPlatformId, item.platform || 'unknown', collidingIds)\n uniqueKeys.add(getMessageKey(item.msg, nid))\n }\n\n return { conflicts, totalMessages: uniqueKeys.size }\n}\n\n// ==================== Member merge ====================\n\nexport function mergeMembers(\n sources: Array<{ platform: string; members: MergerMember[] }>,\n collidingIds: Set<string>\n): Map<string, MergedMember> {\n const map = new Map<string, MergedMember>()\n for (const { platform, members } of sources) {\n const p = platform || 'unknown'\n for (const m of members) {\n const nid = normalizePlatformId(m.platformId, p, collidingIds)\n const existing = map.get(nid)\n if (existing) {\n if (m.accountName) existing.accountName = m.accountName\n if (m.groupNickname) existing.groupNickname = m.groupNickname\n if (m.avatar) existing.avatar = m.avatar\n } else {\n map.set(nid, {\n platformId: nid,\n accountName: m.accountName,\n groupNickname: m.groupNickname,\n avatar: m.avatar,\n })\n }\n }\n }\n return map\n}\n\n// ==================== Message dedup + sort ====================\n\nexport function deduplicateAndSortMessages(\n sources: Array<{ platform: string; messages: MergerMessage[] }>,\n collidingIds: Set<string>\n): MergedMessage[] {\n const seen = new Set<string>()\n const merged: MergedMessage[] = []\n\n for (const { platform, messages } of sources) {\n const p = platform || 'unknown'\n for (const msg of messages) {\n const nid = normalizePlatformId(msg.senderPlatformId, p, collidingIds)\n const key = getMessageKey(msg, nid)\n if (seen.has(key)) continue\n seen.add(key)\n merged.push({\n sender: nid,\n accountName: msg.senderAccountName,\n groupNickname: msg.senderGroupNickname,\n timestamp: msg.timestamp,\n type: msg.type,\n content: msg.content,\n })\n }\n }\n\n merged.sort((a, b) => a.timestamp - b.timestamp)\n return merged\n}\n","/**\n * ChatLab 聊天会话数据库 Schema 定义\n *\n * 所有 CREATE TABLE / INDEX 语句的单一事实来源。\n * 新建数据库时使用完整 Schema,现有数据库通过迁移脚本演进。\n *\n * 当前 Schema 版本:7\n */\n\n/** 当前 Schema 版本(最新迁移的版本号) */\nexport const CURRENT_SCHEMA_VERSION = 8\n\n/**\n * Table DDL only (no indexes). Used by bulk-import workflows that defer\n * index creation until after data is loaded for better write performance.\n */\nexport const CHAT_DB_TABLES = `\n CREATE TABLE IF NOT EXISTS meta (\n name TEXT NOT NULL,\n platform TEXT NOT NULL,\n type TEXT NOT NULL,\n imported_at INTEGER NOT NULL,\n group_id TEXT,\n group_avatar TEXT,\n owner_id TEXT,\n schema_version INTEGER DEFAULT ${CURRENT_SCHEMA_VERSION},\n session_gap_threshold INTEGER\n );\n\n CREATE TABLE IF NOT EXISTS member (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n platform_id TEXT NOT NULL UNIQUE,\n account_name TEXT,\n group_nickname TEXT,\n aliases TEXT DEFAULT '[]',\n avatar TEXT,\n roles TEXT DEFAULT '[]'\n );\n\n CREATE TABLE IF NOT EXISTS member_name_history (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n member_id INTEGER NOT NULL,\n name_type TEXT NOT NULL,\n name TEXT NOT NULL,\n start_ts INTEGER NOT NULL,\n end_ts INTEGER,\n FOREIGN KEY(member_id) REFERENCES member(id)\n );\n\n CREATE TABLE IF NOT EXISTS message (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n sender_id INTEGER NOT NULL,\n sender_account_name TEXT,\n sender_group_nickname TEXT,\n ts INTEGER NOT NULL,\n type INTEGER NOT NULL,\n content TEXT,\n reply_to_message_id TEXT DEFAULT NULL,\n platform_message_id TEXT DEFAULT NULL,\n FOREIGN KEY(sender_id) REFERENCES member(id)\n );\n\n CREATE TABLE IF NOT EXISTS segment (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n start_ts INTEGER NOT NULL,\n end_ts INTEGER NOT NULL,\n message_count INTEGER DEFAULT 0,\n is_manual INTEGER DEFAULT 0,\n summary TEXT\n );\n\n CREATE TABLE IF NOT EXISTS message_context (\n message_id INTEGER PRIMARY KEY,\n segment_id INTEGER NOT NULL,\n topic_id INTEGER\n );\n`\n\n/**\n * Index DDL only. Applied after bulk import or as part of full schema init.\n */\nexport const CHAT_DB_INDEXES = `\n CREATE INDEX IF NOT EXISTS idx_message_ts ON message(ts);\n CREATE INDEX IF NOT EXISTS idx_message_sender ON message(sender_id);\n CREATE INDEX IF NOT EXISTS idx_message_sender_ts ON message(sender_id, ts);\n CREATE INDEX IF NOT EXISTS idx_message_type_ts ON message(type, ts);\n CREATE INDEX IF NOT EXISTS idx_message_reply_to ON message(reply_to_message_id);\n CREATE INDEX IF NOT EXISTS idx_message_platform_id ON message(platform_message_id);\n CREATE INDEX IF NOT EXISTS idx_member_name_history_member_id ON member_name_history(member_id);\n CREATE INDEX IF NOT EXISTS idx_segment_time ON segment(start_ts, end_ts);\n CREATE INDEX IF NOT EXISTS idx_context_segment ON message_context(segment_id);\n`\n\n/**\n * Combined tables + indexes DDL (Schema Version 4).\n * For new database creation where deferred indexing is not needed.\n */\nexport const CHAT_DB_SCHEMA = CHAT_DB_TABLES + CHAT_DB_INDEXES\n\n/**\n * FTS5 全文搜索虚拟表 DDL\n *\n * content='' 表示使用外部内容表模式(不存储原始内容,只存储索引)。\n * 填充需要在导入或迁移时手动执行。\n */\nexport const FTS_TABLE_SCHEMA = `\n CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts5(\n content,\n content='',\n content_rowid=id\n );\n`\n","/**\n * 数据库迁移框架(平台无关)\n *\n * 提供 Schema 版本检测和通用迁移执行逻辑。\n * 具体的迁移脚本由各平台运行时注册(因为某些迁移依赖 NLP 分词器等平台特性)。\n */\n\nimport type { DatabaseAdapter } from '../interfaces'\n\n/** 迁移脚本接口 */\nexport interface Migration {\n version: number\n description: string\n up: (db: DatabaseAdapter) => void\n}\n\n/**\n * 读取数据库的 schema 版本\n */\nexport function getSchemaVersion(db: DatabaseAdapter): number {\n try {\n const tableInfo = db.pragma('table_info(meta)') as Array<{ name: string }>\n const hasVersionColumn = tableInfo.some((col) => col.name === 'schema_version')\n if (!hasVersionColumn) return 0\n\n const result = db.prepare('SELECT schema_version FROM meta LIMIT 1').get() as\n | { schema_version: number | null }\n | undefined\n return result?.schema_version ?? 0\n } catch {\n return 0\n }\n}\n\n/**\n * 设置数据库的 schema 版本\n */\nexport function setSchemaVersion(db: DatabaseAdapter, version: number): void {\n const tableInfo = db.pragma('table_info(meta)') as Array<{ name: string }>\n const hasVersionColumn = tableInfo.some((col) => col.name === 'schema_version')\n\n if (!hasVersionColumn) {\n db.exec('ALTER TABLE meta ADD COLUMN schema_version INTEGER DEFAULT 0')\n }\n\n db.prepare('UPDATE meta SET schema_version = ?').run(version)\n}\n\n/**\n * 检查数据库是否需要迁移\n */\nexport function needsMigration(db: DatabaseAdapter, targetVersion: number): boolean {\n return getSchemaVersion(db) < targetVersion\n}\n\n/**\n * 执行数据库迁移\n *\n * @param db 数据库适配器\n * @param migrationsList 迁移脚本列表(由调用方提供,可包含平台特定逻辑)\n * @param forceRepair 是否强制修复\n * @returns 是否执行了迁移\n */\nexport function runMigrations(db: DatabaseAdapter, migrationsList: Migration[], forceRepair = false): boolean {\n const currentVersion = getSchemaVersion(db)\n\n if (!forceRepair && currentVersion >= (migrationsList.at(-1)?.version ?? 0)) {\n return false\n }\n\n const pending = forceRepair ? migrationsList : migrationsList.filter((m) => m.version > currentVersion)\n\n if (pending.length === 0) return false\n\n db.transaction(() => {\n for (const migration of pending) {\n migration.up(db)\n setSchemaVersion(db, migration.version)\n }\n })\n\n return true\n}\n","/**\n * Parser V2 - 模块入口\n * 三层架构:标准层、嗅探层、解析层\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { FormatSniffer, createSniffer } from './sniffer'\nimport { formats } from './formats'\nimport { getFileSize } from './utils'\nimport type {\n ParseOptions,\n ParseEvent,\n ParseResult,\n ParseProgress,\n LogLevel,\n FormatFeature,\n Parser,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n MultiChatInfo,\n} from './types'\n\n// ==================== 全局嗅探器实例 ====================\n\nconst sniffer = createSniffer()\nsniffer.registerAll(formats)\n\n// ==================== 公共 API ====================\n\n/**\n * 检测文件格式\n * @param filePath 文件路径\n * @returns 格式特征,如果无法识别则返回 null\n */\nexport function detectFormat(filePath: string): FormatFeature | null {\n return sniffer.sniff(filePath)\n}\n\n/**\n * 检测所有匹配的文件格式(按优先级排序)\n * 用于 fallback 机制:当第一个格式解析失败时尝试下一个\n * @param filePath 文件路径\n * @returns 所有匹配的格式特征列表\n */\nexport function detectAllFormats(filePath: string): FormatFeature[] {\n return sniffer.sniffAll(filePath)\n}\n\n/**\n * 获取文件对应的解析器\n * @param filePath 文件路径\n * @returns 解析器实例,如果无法识别则返回 null\n */\nexport function getParser(filePath: string): Parser | null {\n return sniffer.getParser(filePath)\n}\n\n/**\n * 获取所有支持的格式\n */\nexport function getSupportedFormats(): FormatFeature[] {\n return sniffer.getSupportedFormats()\n}\n\n/**\n * 根据格式 ID 获取格式特征\n * 用于手动指定格式时跳过嗅探\n */\nexport function getFormatFeatureById(formatId: string): FormatFeature | null {\n const all = sniffer.getSupportedFormats()\n return all.find((f) => f.id === formatId) ?? null\n}\n\n/**\n * 获取格式的预处理器(如果有)\n */\nexport function getPreprocessor(filePath: string) {\n const feature = sniffer.sniff(filePath)\n if (!feature) return null\n\n const module = formats.find((m) => m.feature.id === feature.id)\n return module?.preprocessor || null\n}\n\n/**\n * 扫描多聊天文件中的聊天列表\n * 自动检测格式并调用对应格式模块的 scanChats\n * @param filePath 文件路径\n * @returns 聊天列表,如果格式不支持多聊天则抛出错误\n */\nexport async function scanMultiChatFile(filePath: string): Promise<MultiChatInfo[]> {\n const feature = sniffer.sniff(filePath)\n if (!feature) {\n throw new Error(`无法识别文件格式: ${filePath}`)\n }\n\n if (!feature.multiChat) {\n throw new Error(`格式 \"${feature.name}\" 不是多聊天格式`)\n }\n\n const module = formats.find((m) => m.feature.id === feature.id)\n if (!module?.scanChats) {\n throw new Error(`格式 \"${feature.name}\" 声明了 multiChat 但未实现 scanChats`)\n }\n\n return module.scanChats(filePath)\n}\n\n/**\n * 检查文件是否需要预处理\n */\nexport function needsPreprocess(filePath: string): boolean {\n const preprocessor = getPreprocessor(filePath)\n if (!preprocessor) return false\n\n const fileSize = getFileSize(filePath)\n return preprocessor.needsPreprocess(filePath, fileSize)\n}\n\n/**\n * 流式解析文件\n * @param options 解析选项\n * @yields 解析事件流\n */\nexport async function* parseFile(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const parser = sniffer.getParser(options.filePath)\n if (!parser) {\n yield { type: 'error', data: new Error(`无法识别文件格式: ${options.filePath}`) }\n return\n }\n\n console.log(`[Parser V2] Using parser: ${parser.feature.name}`)\n yield* parser.parse(options)\n}\n\n/**\n * 使用指定格式解析文件(用于 fallback 机制)\n * @param formatId 格式 ID\n * @param options 解析选项\n * @yields 解析事件流\n */\nexport async function* parseFileWithFormat(\n formatId: string,\n options: ParseOptions\n): AsyncGenerator<ParseEvent, void, unknown> {\n const parser = sniffer.getParserById(formatId)\n if (!parser) {\n yield { type: 'error', data: new Error(`未知的格式 ID: ${formatId}`) }\n return\n }\n\n console.log(`[Parser V2] Using parser (explicit): ${parser.feature.name}`)\n yield* parser.parse(options)\n}\n\n/**\n * 同步解析文件(收集所有事件为完整结果)\n * 适用于不需要流式处理的场景(如合并工具)\n * @param filePath 文件路径\n * @param onProgress 进度回调(可选)\n */\nexport async function parseFileSync(\n filePath: string,\n onProgress?: (progress: ParseProgress) => void\n): Promise<ParseResult> {\n let meta: ParsedMeta | null = null\n const members: ParsedMember[] = []\n const messages: ParsedMessage[] = []\n\n for await (const event of parseFile({ filePath, onProgress })) {\n switch (event.type) {\n case 'meta':\n meta = event.data\n break\n case 'members':\n members.push(...event.data)\n break\n case 'messages':\n messages.push(...event.data)\n break\n case 'progress':\n onProgress?.(event.data)\n break\n case 'error':\n throw event.data\n }\n }\n\n if (!meta) {\n throw new Error('解析失败:未获取到元信息')\n }\n\n return { meta, members, messages }\n}\n\n/**\n * 解析文件获取基本信息(只统计,不返回完整消息)\n * 用于预览和合并工具\n */\nexport async function parseFileInfo(\n filePath: string,\n onProgress?: (progress: ParseProgress) => void\n): Promise<{\n name: string\n format: string\n platform: string\n messageCount: number\n memberCount: number\n fileSize: number\n}> {\n const feature = sniffer.sniff(filePath)\n if (!feature) {\n throw new Error(`无法识别文件格式: ${filePath}`)\n }\n\n let name = '未知群聊'\n let platform = feature.platform\n let messageCount = 0\n let memberCount = 0\n\n for await (const event of parseFile({ filePath, onProgress })) {\n switch (event.type) {\n case 'meta':\n name = event.data.name\n platform = event.data.platform\n break\n case 'members':\n memberCount += event.data.length\n break\n case 'messages':\n messageCount += event.data.length\n break\n case 'progress':\n onProgress?.(event.data)\n break\n case 'error':\n throw event.data\n }\n }\n\n const fileSize = fs.statSync(filePath).size\n\n return {\n name,\n format: feature.name,\n platform,\n messageCount,\n memberCount,\n fileSize,\n }\n}\n\n/**\n * Scan a directory for a recognizable entry file (e.g. manifest.json for chunked formats).\n * Uses existing format detection; no format-specific filenames are hardcoded here.\n * @param dirPath directory path to scan\n * @param maxDepth max depth to scan (default 1, only top-level files)\n * @returns path to the entry file, or null if none found\n */\nexport function findEntryFileInDirectory(dirPath: string, maxDepth = 1): string | null {\n function walk(current: string, depth: number): string | null {\n if (depth > maxDepth) return null\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(current, { withFileTypes: true })\n } catch {\n return null\n }\n for (const entry of entries) {\n const full = path.join(current, entry.name)\n if (entry.isFile() && entry.name.endsWith('.json')) {\n const format = detectFormat(full)\n if (format) return full\n }\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const found = walk(path.join(current, entry.name), depth + 1)\n if (found) return found\n }\n }\n return null\n }\n\n return walk(dirPath, 0)\n}\n\n// ==================== 导出类型 ====================\n\nexport type {\n ParseOptions,\n ParseEvent,\n ParseResult,\n ParseProgress,\n FormatFeature,\n Parser,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n MultiChatInfo,\n}\n\n// ==================== 导出嗅探器(高级用法) ====================\n\nexport { FormatSniffer, createSniffer }\n\n// ==================== 导出工具函数 ====================\n\nexport { getFileSize, formatFileSize, parseTimestamp, isValidYear, createProgress, readFileHeadBytes } from './utils'\n\n// ==================== 回调模式 API ====================\n\n/**\n * 回调模式的解析选项\n */\nexport interface StreamParseCallbacks {\n onProgress: (progress: ParseProgress) => void\n onMeta: (meta: ParsedMeta) => void\n onMembers: (members: ParsedMember[]) => void\n onMessageBatch: (messages: ParsedMessage[]) => void\n /** 日志回调(可选) */\n onLog?: (level: LogLevel, message: string) => void\n}\n\nexport interface StreamParseOptions extends StreamParseCallbacks {\n filePath: string\n batchSize?: number\n formatOptions?: Record<string, unknown>\n}\n\n/**\n * 回调模式的流式解析\n * 内部使用 AsyncGenerator,对外提供回调接口\n * @param filePath 文件路径\n * @param callbacks 回调选项\n * @param formatId 可选,指定格式 ID(用于 fallback 机制,跳过自动检测)\n */\nexport async function streamParseFile(\n filePath: string,\n callbacks: Omit<StreamParseOptions, 'filePath'>,\n formatId?: string\n): Promise<void> {\n const { onProgress, onMeta, onMembers, onMessageBatch, onLog, batchSize = 5000, formatOptions } = callbacks\n\n // 根据是否指定 formatId 选择解析方式\n const generator = formatId\n ? parseFileWithFormat(formatId, { filePath, batchSize, formatOptions, onProgress, onLog })\n : parseFile({ filePath, batchSize, formatOptions, onProgress, onLog })\n\n for await (const event of generator) {\n switch (event.type) {\n case 'meta':\n onMeta(event.data)\n break\n case 'members':\n onMembers(event.data)\n break\n case 'messages':\n onMessageBatch(event.data)\n break\n case 'progress':\n onProgress(event.data)\n break\n case 'error':\n throw event.data\n }\n }\n}\n","/**\n * Parser V2 - 嗅探层\n * 负责检测文件格式,匹配对应的解析器\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { FormatFeature, FormatModule, Parser } from './types'\n\n/** 文件头检测大小 (64KB) - 考虑到现代聊天记录文件可能包含 base64 头像等大数据 */\nconst HEAD_SIZE = 64 * 1024\n\n/**\n * 读取文件头部内容\n */\nfunction readFileHead(filePath: string, size: number = HEAD_SIZE): string {\n const fd = fs.openSync(filePath, 'r')\n const buffer = Buffer.alloc(size)\n const bytesRead = fs.readSync(fd, buffer, 0, size, 0)\n fs.closeSync(fd)\n return buffer.slice(0, bytesRead).toString('utf-8')\n}\n\n/**\n * 获取文件扩展名(小写)\n */\nfunction getExtension(filePath: string): string {\n return path.extname(filePath).toLowerCase()\n}\n\n/**\n * 检查文件头是否匹配签名\n */\nfunction matchHeadSignatures(headContent: string, patterns: RegExp[]): boolean {\n return patterns.some((pattern) => pattern.test(headContent))\n}\n\n/**\n * 检查文件名是否匹配签名\n */\nfunction matchFilenameSignatures(filePath: string, patterns: RegExp[]): boolean {\n const filename = path.basename(filePath)\n return patterns.some((pattern) => pattern.test(filename))\n}\n\n/**\n * 检查必需字段是否存在\n */\nfunction matchRequiredFields(headContent: string, fields: string[]): boolean {\n // 简单检查:字段名是否出现在文件头中\n // 对于 JSON 文件,检查 \"fieldName\" 是否存在\n return fields.every((field) => {\n const pattern = new RegExp(`\"${field.replace('.', '\"\\\\s*:\\\\s*.*\"')}\"\\\\s*:`)\n return pattern.test(headContent) || headContent.includes(`\"${field}\"`)\n })\n}\n\n/**\n * 格式嗅探器\n * 管理所有格式特征,负责检测文件格式\n */\nexport class FormatSniffer {\n private formats: FormatModule[] = []\n\n /**\n * 注册格式模块\n */\n register(module: FormatModule): void {\n this.formats.push(module)\n // 按优先级排序(优先级数字越小越靠前)\n this.formats.sort((a, b) => a.feature.priority - b.feature.priority)\n }\n\n /**\n * 批量注册格式模块\n */\n registerAll(modules: FormatModule[]): void {\n for (const module of modules) {\n this.register(module)\n }\n }\n\n /**\n * 嗅探文件格式\n * @param filePath 文件路径\n * @returns 匹配的格式特征,如果无法识别则返回 null\n */\n sniff(filePath: string): FormatFeature | null {\n const ext = getExtension(filePath)\n const headContent = readFileHead(filePath)\n\n for (const { feature } of this.formats) {\n if (this.matchFeature(feature, ext, headContent, filePath)) {\n return feature\n }\n }\n\n return null\n }\n\n /**\n * 获取文件对应的解析器\n * @param filePath 文件路径\n * @returns 匹配的解析器,如果无法识别则返回 null\n */\n getParser(filePath: string): Parser | null {\n const ext = getExtension(filePath)\n const headContent = readFileHead(filePath)\n\n for (const { feature, parser } of this.formats) {\n if (this.matchFeature(feature, ext, headContent, filePath)) {\n return parser\n }\n }\n\n return null\n }\n\n /**\n * 嗅探所有匹配的格式(按优先级排序)\n * 用于 fallback 机制:当第一个格式解析失败时尝试下一个\n * @param filePath 文件路径\n * @returns 所有匹配的格式特征列表\n */\n sniffAll(filePath: string): FormatFeature[] {\n const ext = getExtension(filePath)\n const headContent = readFileHead(filePath)\n const results: FormatFeature[] = []\n\n for (const { feature } of this.formats) {\n if (this.matchFeature(feature, ext, headContent, filePath)) {\n results.push(feature)\n }\n }\n\n return results\n }\n\n /**\n * 获取所有匹配的解析器(按优先级排序)\n * 用于 fallback 机制\n * @param filePath 文件路径\n * @returns 所有匹配的解析器列表\n */\n getParserCandidates(filePath: string): Parser[] {\n const ext = getExtension(filePath)\n const headContent = readFileHead(filePath)\n const results: Parser[] = []\n\n for (const { feature, parser } of this.formats) {\n if (this.matchFeature(feature, ext, headContent, filePath)) {\n results.push(parser)\n }\n }\n\n return results\n }\n\n /**\n * 根据格式 ID 获取解析器\n */\n getParserById(formatId: string): Parser | null {\n const module = this.formats.find((m) => m.feature.id === formatId)\n return module?.parser || null\n }\n\n /**\n * 获取所有支持的格式\n */\n getSupportedFormats(): FormatFeature[] {\n return this.formats.map((m) => m.feature)\n }\n\n /**\n * 检查特征是否匹配\n */\n private matchFeature(feature: FormatFeature, ext: string, headContent: string, filePath?: string): boolean {\n // 1. 检查扩展名\n if (!feature.extensions.includes(ext)) {\n return false\n }\n\n const { signatures } = feature\n\n // 2. 检查文件头签名(如果定义了)\n let headMatch = true\n if (signatures.head && signatures.head.length > 0) {\n headMatch = matchHeadSignatures(headContent, signatures.head)\n }\n\n // 3. 检查文件名签名(如果定义了,作为文件头匹配失败的补充)\n let filenameMatch = false\n if (signatures.filename && signatures.filename.length > 0 && filePath) {\n filenameMatch = matchFilenameSignatures(filePath, signatures.filename)\n }\n\n // 文件头签名或文件名签名至少有一个匹配\n if (!headMatch && !filenameMatch) {\n // 如果两个都没定义,则认为匹配(只检查扩展名)\n if ((signatures.head && signatures.head.length > 0) || (signatures.filename && signatures.filename.length > 0)) {\n return false\n }\n }\n\n // 4. 检查必需字段(如果定义了)\n if (signatures.requiredFields && signatures.requiredFields.length > 0) {\n if (!matchRequiredFields(headContent, signatures.requiredFields)) {\n return false\n }\n }\n\n // 5. 检查字段值模式(如果定义了)\n if (signatures.fieldPatterns) {\n for (const [, pattern] of Object.entries(signatures.fieldPatterns)) {\n if (!pattern.test(headContent)) {\n return false\n }\n }\n }\n\n return true\n }\n}\n\n/**\n * 创建并返回全局嗅探器实例\n */\nexport function createSniffer(): FormatSniffer {\n return new FormatSniffer()\n}\n","/**\n * ChatLab JSON 格式\n * ChatLab 专属的统一格式(通过 chatlab 对象识别)\n *\n * 特征:\n * - 文件头包含 \"chatlab\" 字段\n * - 有 version 版本号\n * - 消息结构已经是标准化的\n */\n\nimport * as fs from 'fs'\nimport streamJson from 'stream-json'\nimport pickModule from 'stream-json/filters/Pick.js'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\nimport streamChain from 'stream-chain'\nimport { KNOWN_PLATFORMS, ChatType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress, readFileHeadBytes } from '../utils'\nimport * as path from 'path'\n\nconst { parser } = streamJson\nconst { pick } = pickModule\nconst { streamValues } = streamValuesModule\nconst { chain } = streamChain\n\n// ==================== 辅助函数 ====================\n\n/**\n * 从文件名提取群名\n * 返回不含扩展名的文件名\n */\nfunction extractNameFromFilePath(filePath: string): string {\n const basename = path.basename(filePath)\n // 移除 .json 扩展名\n const name = basename.replace(/\\.json$/i, '')\n return name || '未知群聊'\n}\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'chatlab',\n name: 'ChatLab JSON',\n platform: KNOWN_PLATFORMS.UNKNOWN, // ChatLab 格式可能包含多平台数据\n priority: 50, // 低优先级,让其他格式先匹配\n extensions: ['.json'],\n signatures: {\n // 只要求 chatlab 字段在文件头,其他字段在解析时验证\n head: [/\"chatlab\"\\s*:\\s*\\{/],\n requiredFields: ['chatlab'],\n },\n}\n\n// ==================== 消息结构 ====================\n\ninterface ChatLabMessage {\n platformMessageId?: string // 消息的平台原始 ID(用于回复关联查询)\n sender: string // platformId\n accountName: string // 发送时的账号名称\n groupNickname?: string // 发送时的群昵称\n timestamp: number // 秒级时间戳\n type: number // MessageType\n content: string | null\n replyToMessageId?: string // 回复的目标消息 ID(平台原始 ID)\n}\n\ninterface ChatLabMember {\n platformId: string\n accountName: string // 账号名称\n groupNickname?: string // 群昵称\n aliases?: string[]\n avatar?: string // 头像(base64 Data URL)\n roles?: Array<{ id: string; name?: string }> // 成员角色\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseChatLab(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let bytesRead = 0\n let messagesProcessed = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n // 记录解析开始\n onLog?.('info', `开始解析 ChatLab 格式文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 读取文件头获取 meta 和 members 信息\n const headContent = readFileHeadBytes(filePath, 200000)\n\n // 解析 meta\n let meta: ParsedMeta = {\n name: '未知群聊',\n platform: KNOWN_PLATFORMS.UNKNOWN,\n type: ChatType.GROUP,\n }\n try {\n // 使用更健壮的方式解析嵌套 JSON 对象\n // 因为 meta 可能包含 sources 数组(嵌套对象),简单的正则无法正确匹配\n const metaStartMatch = headContent.match(/\"meta\"\\s*:\\s*\\{/)\n if (metaStartMatch && metaStartMatch.index !== undefined) {\n const startIndex = metaStartMatch.index + metaStartMatch[0].length - 1 // 指向 {\n let depth = 0\n let endIndex = startIndex\n\n // 遍历字符找到匹配的闭合 }\n for (let i = startIndex; i < headContent.length; i++) {\n const char = headContent[i]\n if (char === '{') {\n depth++\n } else if (char === '}') {\n depth--\n if (depth === 0) {\n endIndex = i\n break\n }\n }\n }\n\n if (endIndex > startIndex) {\n const metaJson = headContent.slice(startIndex, endIndex + 1)\n const metaObj = JSON.parse(metaJson)\n meta = {\n name: metaObj.name || '未知群聊',\n platform: metaObj.platform || KNOWN_PLATFORMS.UNKNOWN,\n type: (metaObj.type as ChatType) || ChatType.GROUP,\n groupId: metaObj.groupId,\n groupAvatar: metaObj.groupAvatar,\n }\n }\n }\n } catch {\n // 使用默认值\n }\n\n // 如果群名仍是默认值,使用文件名作为后备\n if (meta.name === '未知群聊') {\n meta.name = extractNameFromFilePath(filePath)\n }\n\n yield { type: 'meta', data: meta }\n\n // 解析 members(如果在文件开头能找到)\n const members: ParsedMember[] = []\n try {\n const membersMatch = headContent.match(/\"members\"\\s*:\\s*\\[([\\s\\S]*?)\\]/)\n if (membersMatch) {\n const membersJson = JSON.parse(`[${membersMatch[1]}]`) as ChatLabMember[]\n for (const m of membersJson) {\n members.push({\n platformId: m.platformId,\n accountName: m.accountName,\n groupNickname: m.groupNickname,\n avatar: m.avatar,\n roles: m.roles,\n })\n }\n }\n } catch {\n // members 可能太大,稍后从消息中收集\n }\n\n // 收集成员和消息\n const memberMapFromMessages = new Map<string, ParsedMember>()\n const messageBatch: ParsedMessage[] = []\n\n // 流式解析\n await new Promise<void>((resolve, reject) => {\n const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n readStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n const pipeline = chain([readStream, parser(), pick({ filter: /^messages\\.\\d+$/ }), streamValues()])\n\n // 用于收集批次的临时数组\n const batchCollector: ParsedMessage[] = []\n\n pipeline.on('data', ({ value }: { value: ChatLabMessage }) => {\n const msg = value\n\n // 如果前面没解析到 members,从消息中收集\n if (members.length === 0) {\n memberMapFromMessages.set(msg.sender, {\n platformId: msg.sender,\n accountName: msg.accountName,\n groupNickname: msg.groupNickname,\n })\n }\n\n batchCollector.push({\n senderPlatformId: msg.sender,\n senderAccountName: msg.accountName,\n senderGroupNickname: msg.groupNickname,\n timestamp: msg.timestamp,\n type: msg.type,\n content: msg.content,\n platformMessageId: msg.platformMessageId,\n replyToMessageId: msg.replyToMessageId,\n })\n\n messagesProcessed++\n\n // 达到批次大小\n if (batchCollector.length >= batchSize) {\n messageBatch.push(...batchCollector)\n batchCollector.length = 0\n\n const progress = createProgress(\n 'parsing',\n bytesRead,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n onProgress?.(progress)\n }\n })\n\n pipeline.on('end', () => {\n // 收集剩余消息\n if (batchCollector.length > 0) {\n messageBatch.push(...batchCollector)\n }\n resolve()\n })\n\n pipeline.on('error', reject)\n })\n\n // 发送成员\n if (members.length > 0) {\n yield { type: 'members', data: members }\n } else if (memberMapFromMessages.size > 0) {\n yield { type: 'members', data: Array.from(memberMapFromMessages.values()) }\n }\n\n // 分批发送消息\n for (let i = 0; i < messageBatch.length; i += batchSize) {\n const batch = messageBatch.slice(i, i + batchSize)\n yield { type: 'messages', data: batch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n // 记录解析摘要\n const memberCount = members.length > 0 ? members.length : memberMapFromMessages.size\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberCount} 个成员`)\n\n yield {\n type: 'done',\n data: {\n messageCount: messagesProcessed,\n memberCount,\n },\n }\n}\n\n// ==================== 导出解析器 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseChatLab,\n}\n\n// ==================== 导出格式模块 ====================\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n}\n\nexport default module_\n","/**\n * @openchatlab/shared-types\n * 平台无关的共享类型定义,三端(Electron / Node 服务 / Web)统一使用\n */\n\n// ==================== 时间筛选 ====================\n\nexport interface TimeFilter {\n startTs?: number\n endTs?: number\n memberId?: number | null\n}\n\n// ==================== 枚举与平台 ====================\n\n/**\n * 消息类型枚举\n *\n * 分类说明:\n * - 基础消息 (0-19): 常见的内容类型\n * - 交互消息 (20-39): 涉及互动的消息类型\n * - 系统消息 (80-89): 系统相关消息\n * - 其他 (99): 未知或无法分类的消息\n */\nexport enum MessageType {\n // ========== 基础消息类型 (0-19) ==========\n TEXT = 0,\n IMAGE = 1,\n VOICE = 2,\n VIDEO = 3,\n FILE = 4,\n EMOJI = 5,\n LINK = 7,\n LOCATION = 8,\n\n // ========== 交互消息类型 (20-39) ==========\n RED_PACKET = 20,\n TRANSFER = 21,\n POKE = 22,\n CALL = 23,\n SHARE = 24,\n REPLY = 25,\n FORWARD = 26,\n CONTACT = 27,\n\n // ========== 系统消息类型 (80-89) ==========\n SYSTEM = 80,\n RECALL = 81,\n\n // ========== 其他 (99) ==========\n OTHER = 99,\n}\n\n/**\n * 聊天平台类型(字符串,允许任意值)\n * 常见平台示例:qq, weixin, discord, whatsapp 等\n * 合并多平台记录时使用 'mixed'\n */\nexport type ChatPlatform = string\n\nexport const KNOWN_PLATFORMS = {\n QQ: 'qq',\n WECHAT: 'weixin',\n DISCORD: 'discord',\n WHATSAPP: 'whatsapp',\n TELEGRAM: 'telegram',\n INSTAGRAM: 'instagram',\n GOOGLE_CHAT: 'google-chat',\n LINE: 'line',\n UNKNOWN: 'unknown',\n} as const\n\n/**\n * 聊天类型枚举\n */\nexport enum ChatType {\n GROUP = 'group',\n PRIVATE = 'private',\n}\n\n// ==================== 成员角色 ====================\n\nexport interface MemberRole {\n id: string\n name?: string\n}\n\nexport const STANDARD_ROLE_IDS = {\n OWNER: 'owner',\n ADMIN: 'admin',\n} as const\n\n// ==================== 标准协议(Parser 输出) ====================\n\nexport interface ParsedMember {\n platformId: string\n accountName: string\n groupNickname?: string\n avatar?: string\n roles?: MemberRole[]\n}\n\nexport interface ParsedMessage {\n platformMessageId?: string\n senderPlatformId: string\n senderAccountName: string\n senderGroupNickname?: string\n timestamp: number\n type: MessageType\n content: string | null\n replyToMessageId?: string\n}\n\n// ==================== Preferences (跨端偏好设置) ====================\n\nexport interface WordFilterScheme {\n id: string\n name: string\n words: string[]\n createdAt: number\n}\n\nexport interface ContextCompressionSettings {\n enabled: boolean\n tokenThresholdPercent: number\n bufferSizePercent: number\n maxToolResultPercent: number\n}\n\nexport type ChartAutoMode = 'explicit' | 'suggest' | 'aggressive'\n\nexport interface AIGlobalSettings {\n maxMessagesPerRequest: number\n exportFormat: 'markdown' | 'txt'\n sqlExportFormat: 'csv' | 'json'\n enableAutoSkill: boolean\n chartAutoMode: ChartAutoMode\n searchContextBefore: number\n searchContextAfter: number\n contextCompression: ContextCompressionSettings\n}\n\nexport interface KeywordTemplate {\n id: string\n name: string\n keywords: string[]\n [key: string]: unknown\n}\n\nexport interface DesensitizeRule {\n id: string\n label: string\n pattern: string\n replacement: string\n enabled: boolean\n builtin: boolean\n locales: string[]\n group?: string\n}\n\nexport interface AIPreprocessConfig {\n dataCleaning: boolean\n mergeConsecutive: boolean\n mergeWindowSeconds: number\n blacklistKeywords: string[]\n denoise: boolean\n desensitize: boolean\n desensitizeRulesSchemaVersion?: number\n desensitizeBuiltinRuleOverrides?: Record<string, boolean>\n desensitizeRules: DesensitizeRule[]\n anonymizeNames: boolean\n}\n\nexport interface FilterHistoryItem {\n id: string\n sessionId: string\n createdAt: number\n name: string\n mode: 'condition' | 'session'\n conditionFilter?: {\n keywords: string[]\n timeRange: { start: number; end: number } | null\n senderIds: number[]\n contextSize: number\n }\n selectedSessionIds?: number[]\n}\n\nexport type OwnerMatchMode = 'platform_id' | 'name'\n\n/**\n * Platform-level owner identity (\"who am I\" on this chat platform).\n * Stored in preferences.json and shared across sessions of the same platform.\n */\nexport interface OwnerProfile {\n platformId: string\n displayName: string\n /** Original (non-normalized) names confirmed by the user; normalization happens at match time. */\n confirmedNames: string[]\n matchMode: OwnerMatchMode\n updatedAt: number\n}\n\nexport type ApplyOwnerProfileReason = 'no_profile' | 'no_match' | 'ambiguous' | 'already_set' | 'missing_session'\n\nexport interface ApplyOwnerProfileResult {\n applied: boolean\n ownerId?: string\n reason?: ApplyOwnerProfileReason\n /** Whether the user chose \"do not remind me\" for this session (UI hint only). */\n dismissed: boolean\n}\n\nexport interface SetOwnerAndApplyProfileResult {\n sessionId: string\n platform: string\n ownerId: string\n /** Other same-platform sessions auto-filled by the updated profile. */\n updatedSessionIds: string[]\n /**\n * The actual owner_id written to each updated session.\n * On name-match platforms the matched member's platformId can differ from\n * ownerId (the source session's platformId), so callers must use this map\n * rather than ownerId when caching the result for updated sessions.\n */\n updatedSessionOwnerIds: Record<string, string>\n}\n\nexport interface Preferences {\n pinnedSessionIds: string[]\n aiPreprocessConfig: AIPreprocessConfig\n aiGlobalSettings: AIGlobalSettings\n customKeywordTemplates: KeywordTemplate[]\n deletedPresetTemplateIds: string[]\n wordFilter: {\n schemes: WordFilterScheme[]\n defaultSchemeId: string | null\n sessionSchemeOverrides: Record<string, string | null>\n }\n filterHistory: FilterHistoryItem[]\n /** Per-model thinking level, keyed by `${configId}:${modelId}`. */\n thinkingLevels: Record<string, string>\n /** Platform-level owner identity, keyed by platform (e.g. 'whatsapp'). */\n ownerProfilesByPlatform: Record<string, OwnerProfile>\n /** Sessions where the user chose \"do not remind me\"; suppresses the owner prompt UI only. */\n ownerPromptDismissedSessionIds: string[]\n}\n\nexport interface UiConfig {\n default_session_tab: 'overview' | 'ai-chat'\n session_gap_threshold: number\n summary_strategy?: 'brief' | 'standard'\n}\n","/**\n * Parser V2 - 工具函数\n */\n\nimport * as fs from 'fs'\nimport type { CreateProgress } from './types'\n\n/**\n * 获取文件大小\n */\nexport function getFileSize(filePath: string): number {\n return fs.statSync(filePath).size\n}\n\n/**\n * 读取文件头部指定字节数\n */\nexport function readFileHeadBytes(filePath: string, size: number): string {\n const fd = fs.openSync(filePath, 'r')\n const buffer = Buffer.alloc(size)\n const bytesRead = fs.readSync(fd, buffer, 0, size, 0)\n fs.closeSync(fd)\n return buffer.slice(0, bytesRead).toString('utf-8')\n}\n\n/**\n * 创建进度对象\n */\nexport const createProgress: CreateProgress = (stage, bytesRead, totalBytes, messagesProcessed, message) => {\n const percentage = totalBytes > 0 ? Math.min(99, Math.round((bytesRead / totalBytes) * 100)) : 0\n return {\n stage,\n bytesRead,\n totalBytes,\n messagesProcessed,\n percentage: stage === 'done' ? 100 : percentage,\n message,\n }\n}\n\n/**\n * 解析 ISO 时间戳或毫秒时间戳\n * @returns 秒级时间戳,如果解析失败返回 null\n */\nexport function parseTimestamp(value: string | number): number | null {\n if (typeof value === 'string') {\n // ISO 字符串格式:2017-12-30T03:24:36.000Z\n const parsed = Date.parse(value)\n if (isNaN(parsed)) return null\n return Math.floor(parsed / 1000)\n } else if (typeof value === 'number') {\n // 毫秒时间戳\n if (isNaN(value)) return null\n return Math.floor(value / 1000)\n }\n return null\n}\n\n/**\n * 验证年份是否合理(2000年以后)\n */\nexport function isValidYear(timestampSeconds: number): boolean {\n return new Date(timestampSeconds * 1000).getFullYear() >= 2000\n}\n\n/**\n * 格式化文件大小\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`\n}\n","/**\n * ChatLab JSONL 格式\n * 支持超大规模聊天记录的流式格式\n *\n * 格式特点:\n * - 每行一个 JSON 对象\n * - 第一行必须是 header(包含 chatlab 和 meta)\n * - 成员行(可选)在消息行之前\n * - 消息行按时间顺序排列\n * - 通过 _type 字段区分行类型:header / member / message\n *\n * 优势:\n * - 内存占用恒定,支持 GB 级文件\n * - 可逐行处理,支持流式导入\n * - 便于追加写入\n */\n\nimport * as fs from 'fs'\nimport * as readline from 'readline'\nimport * as path from 'path'\nimport { KNOWN_PLATFORMS, ChatType, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress } from '../utils'\n\n// ==================== JSONL 行类型定义 ====================\n\n/** Header 行结构 */\ninterface JsonlHeader {\n _type: 'header'\n chatlab: {\n version: string\n exportedAt: number\n generator?: string\n description?: string\n }\n meta: {\n name: string\n platform: string\n type: string\n groupId?: string\n groupAvatar?: string\n }\n}\n\n/** Member 行结构 */\ninterface JsonlMember {\n _type: 'member'\n platformId: string\n accountName: string\n groupNickname?: string\n aliases?: string[]\n avatar?: string\n}\n\n/** Message 行结构 */\ninterface JsonlMessage {\n _type: 'message'\n sender: string\n platformMessageId?: string\n accountName: string\n groupNickname?: string\n timestamp: number\n type: number\n content: string | null\n replyToMessageId?: string\n}\n\n/** 任意 JSONL 行 */\ntype JsonlLine = JsonlHeader | JsonlMember | JsonlMessage\n\n// ==================== 辅助函数 ====================\n\n/**\n * 从文件名提取群名\n */\nfunction extractNameFromFilePath(filePath: string): string {\n const basename = path.basename(filePath)\n const name = basename.replace(/\\.jsonl$/i, '')\n return name || '未知群聊'\n}\n\n/**\n * 解析单行 JSONL\n */\nfunction parseLine(line: string): JsonlLine | null {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#')) {\n // 空行或注释行,跳过\n return null\n }\n try {\n return JSON.parse(trimmed) as JsonlLine\n } catch {\n // 解析失败,跳过该行\n return null\n }\n}\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'chatlab-jsonl',\n name: 'ChatLab JSONL',\n platform: KNOWN_PLATFORMS.UNKNOWN,\n priority: 51, // 低优先级,让其他格式先匹配\n extensions: ['.jsonl'],\n signatures: {\n // 第一行必须是 header 类型,包含 chatlab 信息\n head: [/^\\s*\\{.*\"_type\"\\s*:\\s*\"header\".*\"chatlab\"/],\n },\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseChatLabJsonl(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let bytesRead = 0\n let messagesProcessed = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n // 记录解析开始\n onLog?.('info', `开始解析 ChatLab JSONL 文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 用于收集成员和消息\n const members: ParsedMember[] = []\n const memberMap = new Map<string, ParsedMember>()\n const messageBatch: ParsedMessage[] = []\n let meta: ParsedMeta | null = null\n let headerParsed = false\n\n // 创建逐行读取流\n const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n })\n\n // 跟踪已读取字节数\n fileStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n // 逐行解析\n for await (const line of rl) {\n const parsed = parseLine(line)\n if (!parsed) continue\n\n switch (parsed._type) {\n case 'header':\n if (!headerParsed) {\n meta = {\n name: parsed.meta.name || extractNameFromFilePath(filePath),\n platform: parsed.meta.platform || KNOWN_PLATFORMS.UNKNOWN,\n type: (parsed.meta.type as ChatType) || ChatType.GROUP,\n groupId: parsed.meta.groupId,\n groupAvatar: parsed.meta.groupAvatar,\n }\n headerParsed = true\n yield { type: 'meta', data: meta }\n }\n break\n\n case 'member':\n {\n const member: ParsedMember = {\n platformId: parsed.platformId,\n accountName: parsed.accountName,\n groupNickname: parsed.groupNickname,\n avatar: parsed.avatar,\n }\n members.push(member)\n memberMap.set(parsed.platformId, member)\n }\n break\n\n case 'message':\n // 如果还没有 header,尝试从文件名推断\n if (!meta) {\n meta = {\n name: extractNameFromFilePath(filePath),\n platform: KNOWN_PLATFORMS.UNKNOWN,\n type: ChatType.GROUP,\n }\n yield { type: 'meta', data: meta }\n }\n\n // 如果没有成员列表,从消息中收集\n if (!memberMap.has(parsed.sender)) {\n const inferredMember: ParsedMember = {\n platformId: parsed.sender,\n accountName: parsed.accountName,\n groupNickname: parsed.groupNickname,\n }\n memberMap.set(parsed.sender, inferredMember)\n }\n\n messageBatch.push({\n senderPlatformId: parsed.sender,\n senderAccountName: parsed.accountName,\n senderGroupNickname: parsed.groupNickname,\n timestamp: parsed.timestamp,\n type: parsed.type as MessageType,\n content: parsed.content,\n platformMessageId: parsed.platformMessageId,\n replyToMessageId: parsed.replyToMessageId,\n })\n messagesProcessed++\n\n // 达到批次大小,发送进度\n if (messageBatch.length >= batchSize) {\n const progress = createProgress(\n 'parsing',\n bytesRead,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed.toLocaleString()} 条消息...`\n )\n onProgress?.(progress)\n }\n break\n }\n }\n\n // 如果没有解析到 header,使用默认值\n if (!meta) {\n meta = {\n name: extractNameFromFilePath(filePath),\n platform: KNOWN_PLATFORMS.UNKNOWN,\n type: ChatType.GROUP,\n }\n yield { type: 'meta', data: meta }\n }\n\n // 发送成员\n if (members.length > 0) {\n yield { type: 'members', data: members }\n } else if (memberMap.size > 0) {\n yield { type: 'members', data: Array.from(memberMap.values()) }\n }\n\n // 分批发送消息\n for (let i = 0; i < messageBatch.length; i += batchSize) {\n const batch = messageBatch.slice(i, i + batchSize)\n yield { type: 'messages', data: batch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n // 记录解析摘要\n const memberCount = members.length > 0 ? members.length : memberMap.size\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberCount} 个成员`)\n\n yield {\n type: 'done',\n data: {\n messageCount: messagesProcessed,\n memberCount,\n },\n }\n}\n\n// ==================== 导出解析器 ====================\n\nexport const parser: Parser = {\n feature,\n parse: parseChatLabJsonl,\n}\n\n// ==================== 导出格式模块 ====================\n\nconst module_: FormatModule = {\n feature,\n parser,\n}\n\nexport default module_\n","/**\n * shuakami/qq-chat-exporter 格式解析器\n * 适配项目: https://github.com/shuakami/qq-chat-exporter\n * 版本: 通用(支持 V4.x 及后续版本)\n *\n * 文件结构:\n * - metadata: 元数据\n * - chatInfo: 聊天信息(包含群头像 avatar)\n * - statistics: 统计信息\n * - messages: 消息数组\n * - avatars: 用户头像(在 messages 之后)\n *\n * 名字字段说明:\n * - sendNickName: QQ原始昵称(始终存在)\n * - sendMemberName: 群昵称(可选)\n *\n * 显示名优先级: sendMemberName > sendNickName\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport streamJson from 'stream-json'\nimport pickModule from 'stream-json/filters/Pick.js'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\nimport streamChain from 'stream-chain'\nimport { KNOWN_PLATFORMS, ChatType, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress, readFileHeadBytes, parseTimestamp, isValidYear } from '../utils'\n\nconst { parser } = streamJson\nconst { pick } = pickModule\nconst { streamValues } = streamValuesModule\nconst { chain } = streamChain\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'shuakami-qq-exporter',\n name: 'shuakami/qq-chat-exporter',\n platform: KNOWN_PLATFORMS.QQ,\n priority: 10,\n extensions: ['.json'],\n signatures: {\n head: [/QQChatExporter(?:\\s+V\\d+)?/, /\"version\"\\s*:\\s*\"\\d+\\./],\n requiredFields: ['metadata', 'chatInfo'],\n },\n}\n\n// ==================== 类型定义 ====================\n\ninterface V4RawMessage {\n senderUin?: string\n senderUid?: string\n sendNickName?: string\n sendMemberName?: string\n}\n\ninterface V4Message {\n messageId?: string // 消息的唯一 ID\n timestamp: string\n sender: {\n uid?: string\n uin?: string\n name: string\n }\n messageType?: number\n isSystemMessage?: boolean\n isRecalled?: boolean\n content: {\n text: string\n resources?: Array<{ type: string }>\n emojis?: Array<{ type: string }>\n reply?: {\n messageId?: string\n referencedMessageId?: string // 被引用消息的 ID\n senderName?: string\n content?: string\n }\n }\n rawMessage?: V4RawMessage\n}\n\ninterface MemberInfo {\n platformId: string\n accountName: string\n groupNickname: string | undefined\n avatar: string | undefined\n}\n\ninterface ChatInfo {\n name: string\n type: string\n avatar?: string\n}\n\n// ==================== 辅助函数 ====================\n\n/**\n * 从字符串中提取 JSON 对象(处理嵌套和转义)\n */\nfunction extractJsonObject(content: string, key: string): string | null {\n const searchStr = `\"${key}\":`\n const startIdx = content.indexOf(searchStr)\n if (startIdx === -1) return null\n\n let i = startIdx + searchStr.length\n while (i < content.length && /\\s/.test(content[i])) i++\n\n if (content[i] !== '{') return null\n\n let braceDepth = 0\n let inString = false\n let escape = false\n const objStart = i\n\n for (; i < content.length; i++) {\n const char = content[i]\n\n if (escape) {\n escape = false\n continue\n }\n\n if (char === '\\\\' && inString) {\n escape = true\n continue\n }\n\n if (char === '\"') {\n inString = !inString\n continue\n }\n\n if (!inString) {\n if (char === '{') braceDepth++\n if (char === '}') {\n braceDepth--\n if (braceDepth === 0) {\n return content.slice(objStart, i + 1)\n }\n }\n }\n }\n\n return null\n}\n\n/**\n * 从文件名提取名称\n */\nfunction extractNameFromFilePath(filePath: string): string {\n const basename = path.basename(filePath)\n return basename.replace(/\\.json$/i, '') || '未知群聊'\n}\n\n/**\n * 计算 senders 数量以判断聊天类型\n */\nfunction countSenders(content: string): number {\n const sendersStart = content.indexOf('\"senders\"')\n if (sendersStart === -1) return 0\n\n const arrayStart = content.indexOf('[', sendersStart)\n if (arrayStart === -1 || arrayStart - sendersStart > 20) return 0\n\n // 找到匹配的 ]\n let depth = 1\n let i = arrayStart + 1\n while (i < content.length && depth > 0) {\n if (content[i] === '[') depth++\n else if (content[i] === ']') depth--\n i++\n }\n\n if (depth !== 0) return 0\n\n const sendersContent = content.slice(arrayStart + 1, i - 1)\n const uidMatches = sendersContent.match(/\"uid\"\\s*:/g)\n return uidMatches ? uidMatches.length : 0\n}\n\n// ==================== 消息类型转换 ====================\n\nfunction convertMessageType(\n messageType: number | undefined,\n content: V4Message['content'],\n isRecalled?: boolean\n): MessageType {\n if (isRecalled) return MessageType.RECALL\n\n // 检查资源类型\n if (content.resources?.length) {\n const resourceType = content.resources[0].type\n switch (resourceType) {\n case 'image':\n return MessageType.IMAGE\n case 'video':\n return MessageType.VIDEO\n case 'voice':\n case 'audio':\n return MessageType.VOICE\n case 'file':\n return MessageType.FILE\n case 'location':\n return MessageType.LOCATION\n }\n }\n\n if (content.emojis?.length) return MessageType.EMOJI\n\n // 根据文本内容判断\n const text = content.text?.trim() || ''\n if (text.includes('QQ红包') || text.includes('发出了红包') || text === '[红包]') return MessageType.RED_PACKET\n if (text.includes('转账') || text === '[转账]') return MessageType.TRANSFER\n if (text.includes('拍了拍') || text.includes('戳了戳') || text === '[拍一拍]') return MessageType.POKE\n if (text.includes('语音通话') || text.includes('视频通话') || text.includes('通话时长')) return MessageType.CALL\n if (text === '[分享]' || text === '[音乐]' || text === '[小程序]') return MessageType.SHARE\n if (text === '[链接]' || text === '[卡片消息]') return MessageType.LINK\n if (text === '[位置]' || text === '[地理位置]') return MessageType.LOCATION\n if (text === '[转发]' || text === '[聊天记录]') return MessageType.FORWARD\n\n // 根据 messageType 判断\n if (messageType === 3) return MessageType.IMAGE\n if (messageType === 7) return MessageType.VIDEO\n if (messageType === 9) return MessageType.REPLY\n\n return MessageType.TEXT\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseV4(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let bytesRead = 0\n let messagesProcessed = 0\n let skippedMessages = 0 // 跳过的无效消息计数\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n // 记录解析开始\n onLog?.('info', `开始解析 QQ Chat Exporter 文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 读取文件头获取 meta 信息(增加到 500KB 以包含 chatInfo.avatar)\n const headContent = readFileHeadBytes(filePath, 500000)\n\n // 解析 chatInfo(包含群头像)\n let chatInfo: ChatInfo = { name: '未知群聊', type: 'group' }\n try {\n const chatInfoStr = extractJsonObject(headContent, 'chatInfo')\n if (chatInfoStr) {\n chatInfo = JSON.parse(chatInfoStr)\n }\n } catch {\n // 使用默认值\n }\n\n // 根据 senders 数量判断聊天类型\n const sendersCount = countSenders(headContent)\n const chatType = sendersCount > 2 ? ChatType.GROUP : sendersCount > 0 ? ChatType.PRIVATE : ChatType.GROUP\n\n // 发送 meta(包含群头像)\n const meta: ParsedMeta = {\n name: chatInfo.name || extractNameFromFilePath(filePath),\n platform: KNOWN_PLATFORMS.QQ,\n type: chatType,\n groupAvatar: chatInfo.avatar, // 从 chatInfo.avatar 提取群头像\n }\n yield { type: 'meta', data: meta }\n\n // 收集成员和消息\n const memberMap = new Map<string, MemberInfo>()\n const messageBatch: ParsedMessage[] = []\n\n // 流式解析消息\n await new Promise<void>((resolve, reject) => {\n const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n readStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n const pipeline = chain([readStream, parser(), pick({ filter: /^messages\\.\\d+$/ }), streamValues()])\n\n pipeline.on('data', ({ value }: { value: V4Message }) => {\n // 获取 platformId\n const platformId =\n value.sender.uin || value.sender.uid || value.rawMessage?.senderUin || value.rawMessage?.senderUid\n if (!platformId) {\n skippedMessages++\n return\n }\n\n // 获取名字信息\n const raw = value.rawMessage\n const accountName = raw?.sendNickName || value.sender.name || platformId\n const groupNickname = raw?.sendMemberName || undefined\n\n // 更新成员信息\n const existingMember = memberMap.get(platformId)\n if (!existingMember) {\n memberMap.set(platformId, { platformId, accountName, groupNickname, avatar: undefined })\n } else {\n existingMember.accountName = accountName\n if (groupNickname) existingMember.groupNickname = groupNickname\n }\n\n // 解析时间戳\n const timestamp = parseTimestamp(value.timestamp)\n if (timestamp === null || !isValidYear(timestamp)) {\n skippedMessages++\n return\n }\n\n // 消息类型\n const type = value.isSystemMessage\n ? MessageType.SYSTEM\n : convertMessageType(value.messageType, value.content, value.isRecalled)\n\n // 文本内容\n let textContent = value.content?.text || ''\n if (value.isRecalled) textContent = '[已撤回] ' + textContent\n\n messageBatch.push({\n platformMessageId: value.messageId, // 消息的平台原始 ID\n senderPlatformId: platformId,\n senderAccountName: accountName,\n senderGroupNickname: groupNickname,\n timestamp,\n type,\n content: textContent || null,\n replyToMessageId: value.content?.reply?.referencedMessageId, // 被引用消息的 ID\n })\n\n messagesProcessed++\n if (messagesProcessed % batchSize === 0) {\n const progress = createProgress(\n 'parsing',\n bytesRead,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n onProgress?.(progress)\n }\n })\n\n pipeline.on('end', resolve)\n pipeline.on('error', reject)\n })\n\n // 消息解析完成后,读取文件末尾的 avatars 对象\n const avatarsMap = new Map<string, string>()\n try {\n // 读取文件末尾(avatars 通常在最后)\n const stats = fs.statSync(filePath)\n const tailSize = Math.min(stats.size, 5000000) // 最多读取 5MB\n const fd = fs.openSync(filePath, 'r')\n const buffer = Buffer.alloc(tailSize)\n fs.readSync(fd, buffer, 0, tailSize, stats.size - tailSize)\n fs.closeSync(fd)\n\n const tailContent = buffer.toString('utf-8')\n const avatarsStr = extractJsonObject(tailContent, 'avatars')\n if (avatarsStr) {\n const avatarsObj = JSON.parse(avatarsStr) as Record<string, string>\n for (const [uin, avatar] of Object.entries(avatarsObj)) {\n if (avatar && typeof avatar === 'string' && avatar.startsWith('data:image/')) {\n avatarsMap.set(uin, avatar)\n }\n }\n }\n } catch {\n // avatars 解析失败,继续不带头像\n }\n\n // 将头像关联到成员\n for (const member of memberMap.values()) {\n const avatar = avatarsMap.get(member.platformId)\n if (avatar) member.avatar = avatar\n }\n\n // 发送成员\n const members: ParsedMember[] = Array.from(memberMap.values()).map((m) => ({\n platformId: m.platformId,\n accountName: m.accountName,\n groupNickname: m.groupNickname,\n avatar: m.avatar,\n }))\n yield { type: 'members', data: members }\n\n // 分批发送消息\n for (let i = 0; i < messageBatch.length; i += batchSize) {\n const batch = messageBatch.slice(i, i + batchSize)\n yield { type: 'messages', data: batch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n // 记录解析摘要\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberMap.size} 个成员`)\n if (skippedMessages > 0) {\n onLog?.('info', `跳过 ${skippedMessages} 条无效消息(缺少发送者ID或时间戳无效)`)\n }\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount: memberMap.size },\n }\n}\n\n// ==================== 导出 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseV4,\n}\n\nimport { qqPreprocessor } from './shuakami-qq-preprocessor'\nexport const preprocessor = qqPreprocessor\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n preprocessor: qqPreprocessor,\n}\n\nexport default module_\n","/**\n * shuakami/qq-chat-exporter 格式预处理器\n * 适配项目: https://github.com/shuakami/qq-chat-exporter\n *\n * 功能:移除 content.html、content.raw 等冗余字段,减小文件体积\n * 阈值:>50MB 自动触发预处理\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport streamJson from 'stream-json'\nimport pickModule from 'stream-json/filters/Pick.js'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\nimport streamChain from 'stream-chain'\nimport type { ParseProgress, Preprocessor } from '../types'\nimport { getFileSize, createProgress } from '../utils'\n\nconst { parser } = streamJson\nconst { pick } = pickModule\nconst { streamValues } = streamValuesModule\nconst { chain } = streamChain\n\n/** 预处理阈值:50MB */\nconst PREPROCESS_THRESHOLD = 50 * 1024 * 1024\n\n/**\n * 获取临时目录\n */\nfunction getTempDir(): string {\n return path.join(os.tmpdir(), 'chatlab')\n}\n\n/**\n * 确保目录存在\n */\nfunction ensureDir(dir: string): void {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n}\n\n/**\n * 从字符串中提取 JSON 对象(处理嵌套和转义)\n */\nfunction extractJsonObject(content: string, key: string): string | null {\n const searchStr = `\"${key}\":`\n const startIdx = content.indexOf(searchStr)\n if (startIdx === -1) return null\n\n let i = startIdx + searchStr.length\n while (i < content.length && /\\s/.test(content[i])) i++\n\n if (content[i] !== '{') return null\n\n let braceDepth = 0\n let inString = false\n let escape = false\n const objStart = i\n\n for (; i < content.length; i++) {\n const char = content[i]\n\n if (escape) {\n escape = false\n continue\n }\n\n if (char === '\\\\' && inString) {\n escape = true\n continue\n }\n\n if (char === '\"') {\n inString = !inString\n continue\n }\n\n if (!inString) {\n if (char === '{') braceDepth++\n if (char === '}') {\n braceDepth--\n if (braceDepth === 0) {\n return content.slice(objStart, i + 1)\n }\n }\n }\n }\n\n return null\n}\n\n/**\n * 从文件末尾读取 avatars 对象\n */\nfunction readAvatarsFromFile(filePath: string): string | null {\n try {\n const stats = fs.statSync(filePath)\n const tailSize = Math.min(stats.size, 5000000) // 最多读取 5MB\n const fd = fs.openSync(filePath, 'r')\n const buffer = Buffer.alloc(tailSize)\n fs.readSync(fd, buffer, 0, tailSize, stats.size - tailSize)\n fs.closeSync(fd)\n\n const tailContent = buffer.toString('utf-8')\n return extractJsonObject(tailContent, 'avatars')\n } catch {\n return null\n }\n}\n\n/**\n * QQ JSON 消息的精简结构\n */\ninterface SlimQQMessage {\n id?: string\n messageId?: string\n timestamp: number | string\n sender: {\n uid?: string\n uin?: string\n name: string\n }\n type?: string\n messageType?: number\n content: {\n text: string\n elements?: Array<{ type: string }>\n resources?: Array<{ type: string }>\n emojis?: Array<{ type: string }>\n }\n recalled?: boolean\n isRecalled?: boolean\n system?: boolean\n isSystemMessage?: boolean\n rawMessage?: {\n sendNickName?: string\n sendMemberName?: string\n senderUin?: string\n senderUid?: string\n }\n}\n\n/**\n * 精简 QQ JSON 消息对象\n */\nfunction slimMessage(msg: Record<string, unknown>): SlimQQMessage {\n const sender = msg.sender as { uin?: string; uid?: string; name?: string } | undefined\n const content = msg.content as Record<string, unknown> | undefined\n const rawMessage = msg.rawMessage as Record<string, unknown> | undefined\n\n const slimContent: SlimQQMessage['content'] = {\n text: (content?.text as string) || '',\n }\n\n if (content?.elements && Array.isArray(content.elements)) {\n slimContent.elements = (content.elements as Array<{ type: string }>).map((e) => ({\n type: e.type,\n }))\n }\n\n if (content?.resources && Array.isArray(content.resources)) {\n slimContent.resources = (content.resources as Array<{ type: string }>).map((r) => ({\n type: r.type,\n }))\n }\n\n if (content?.emojis && Array.isArray(content.emojis)) {\n slimContent.emojis = (content.emojis as Array<{ type: string }>).map((e) => ({\n type: e.type,\n }))\n }\n\n const slimMsg: SlimQQMessage = {\n timestamp: msg.timestamp as number | string,\n sender: { name: sender?.name || '' },\n content: slimContent,\n }\n\n // 旧格式字段\n if (msg.id) slimMsg.id = msg.id as string\n if (msg.type) slimMsg.type = msg.type as string\n if (msg.recalled) slimMsg.recalled = msg.recalled as boolean\n if (msg.system) slimMsg.system = msg.system as boolean\n\n // V4 新格式字段\n if (msg.messageId) slimMsg.messageId = msg.messageId as string\n if (msg.messageType !== undefined) slimMsg.messageType = msg.messageType as number\n if (msg.isRecalled) slimMsg.isRecalled = msg.isRecalled as boolean\n if (msg.isSystemMessage) slimMsg.isSystemMessage = msg.isSystemMessage as boolean\n\n // sender 字段\n if (sender?.uin) slimMsg.sender.uin = sender.uin\n if (sender?.uid) slimMsg.sender.uid = sender.uid\n\n // V4 新增:保留 rawMessage 中的关键名字字段\n if (rawMessage) {\n slimMsg.rawMessage = {}\n if (rawMessage.sendNickName) slimMsg.rawMessage.sendNickName = rawMessage.sendNickName as string\n if (rawMessage.sendMemberName) slimMsg.rawMessage.sendMemberName = rawMessage.sendMemberName as string\n if (rawMessage.senderUin) slimMsg.rawMessage.senderUin = rawMessage.senderUin as string\n if (rawMessage.senderUid) slimMsg.rawMessage.senderUid = rawMessage.senderUid as string\n }\n\n return slimMsg\n}\n\n/**\n * 预处理 QQ JSON 文件\n */\nasync function preprocessQQJson(inputPath: string, onProgress?: (progress: ParseProgress) => void): Promise<string> {\n const totalBytes = getFileSize(inputPath)\n let bytesRead = 0\n let messagesProcessed = 0\n\n const tempDir = getTempDir()\n ensureDir(tempDir)\n const outputFilename = `slim_${Date.now()}_${path.basename(inputPath)}`\n const outputPath = path.join(tempDir, outputFilename)\n\n onProgress?.(createProgress('parsing', 0, totalBytes, 0, ''))\n\n // 先从原文件读取 avatars(因为它在文件末尾,消息处理时可能无法访问)\n const avatarsStr = readAvatarsFromFile(inputPath)\n\n return new Promise((resolve, reject) => {\n const headChunks: string[] = []\n let headSize = 0\n const maxHeadSize = 100000\n\n const headStream = fs.createReadStream(inputPath, { encoding: 'utf-8' })\n let chatInfo: Record<string, unknown> = { name: '未知群聊', type: 'group' }\n let metadata: Record<string, unknown> | undefined\n let statistics: Record<string, unknown> | undefined\n\n headStream.on('data', (chunk: string | Buffer) => {\n const str = typeof chunk === 'string' ? chunk : chunk.toString('utf-8')\n if (headSize < maxHeadSize) {\n headChunks.push(str)\n headSize += str.length\n } else {\n headStream.destroy()\n }\n })\n\n headStream.on('close', () => {\n const headContent = headChunks.join('')\n\n try {\n const chatInfoMatch = headContent.match(/\"chatInfo\"\\s*:\\s*(\\{[^}]+\\})/)\n if (chatInfoMatch) {\n chatInfo = JSON.parse(chatInfoMatch[1])\n }\n } catch {\n // 使用默认值\n }\n\n try {\n const metadataMatch = headContent.match(/\"metadata\"\\s*:\\s*(\\{[^}]+\\})/)\n if (metadataMatch) {\n metadata = JSON.parse(metadataMatch[1])\n }\n } catch {\n // 忽略\n }\n\n try {\n const statisticsMatch = headContent.match(/\"statistics\"\\s*:\\s*(\\{[\\s\\S]*?\\})\\s*,\\s*\"messages\"/)\n if (statisticsMatch) {\n statistics = JSON.parse(statisticsMatch[1])\n }\n } catch {\n // 解析失败时忽略\n }\n\n onProgress?.(createProgress('parsing', 0, totalBytes, 0, ''))\n\n const readStream = fs.createReadStream(inputPath, { encoding: 'utf-8' })\n const writeStream = fs.createWriteStream(outputPath, { encoding: 'utf-8' })\n\n readStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n const header = { metadata, chatInfo, statistics, messages: [] }\n const headerJson = JSON.stringify(header)\n // 移除最后的 ]} 保留 [\n writeStream.write(headerJson.slice(0, -2) + '\\n')\n\n let isFirstMessage = true\n\n const pipeline = chain([readStream, parser(), pick({ filter: /^messages\\.\\d+$/ }), streamValues()])\n\n pipeline.on('data', ({ value }: { value: Record<string, unknown> }) => {\n const slimMsg = slimMessage(value)\n const msgJson = JSON.stringify(slimMsg)\n\n if (isFirstMessage) {\n writeStream.write(msgJson)\n isFirstMessage = false\n } else {\n writeStream.write(',\\n' + msgJson)\n }\n\n messagesProcessed++\n\n if (messagesProcessed % 10000 === 0) {\n onProgress?.(\n createProgress(\n 'parsing',\n bytesRead,\n totalBytes,\n messagesProcessed,\n `预处理:已精简 ${messagesProcessed} 条消息...`\n )\n )\n }\n })\n\n pipeline.on('end', () => {\n // 关闭 messages 数组\n writeStream.write('\\n]')\n\n // 添加 avatars 对象(如果存在)\n if (avatarsStr) {\n writeStream.write(',\"avatars\":' + avatarsStr)\n }\n\n // 关闭 JSON 对象\n writeStream.write('}')\n writeStream.end()\n\n writeStream.on('finish', () => {\n onProgress?.(createProgress('done', totalBytes, totalBytes, messagesProcessed, ''))\n resolve(outputPath)\n })\n })\n\n pipeline.on('error', (err) => {\n writeStream.destroy()\n if (fs.existsSync(outputPath)) {\n fs.unlinkSync(outputPath)\n }\n onProgress?.(createProgress('error', bytesRead, totalBytes, messagesProcessed, err.message))\n reject(err)\n })\n })\n\n headStream.on('error', reject)\n })\n}\n\n/**\n * 清理临时文件\n */\nfunction cleanupTempFile(filePath: string): void {\n try {\n if (fs.existsSync(filePath) && filePath.includes(getTempDir())) {\n fs.unlinkSync(filePath)\n }\n } catch {\n // 忽略清理错误\n }\n}\n\n/**\n * QQ Chat Exporter 预处理器\n */\nexport const qqPreprocessor: Preprocessor = {\n needsPreprocess(_filePath: string, fileSize: number): boolean {\n return fileSize > PREPROCESS_THRESHOLD\n },\n\n preprocess: preprocessQQJson,\n\n cleanup: cleanupTempFile,\n}\n","/**\n * shuakami/qq-chat-exporter chunked-jsonl 格式解析器\n * 适配项目: https://github.com/shuakami/qq-chat-exporter\n * 版本: 5.x(chunked-jsonl 分块格式)\n *\n * 文件结构:\n * - manifest.json: 元数据和分块信息\n * - metadata: 导出元数据\n * - chatInfo: 聊天信息\n * - chunked.chunks[]: 分块文件列表\n * - avatars?: 头像文件信息(V5.5+)\n * - chunks/: 分块目录\n * - chunk_0001.jsonl: 每行一条消息\n * - chunk_0002.jsonl: ...\n * - avatars.json: 头像数据(V5.5+,可选)\n *\n * 消息格式(sender 字段):\n * - uid: 用户 UID\n * - uin: QQ 号\n * - name: 展示名(优先群昵称,否则QQ昵称)\n * - nickname: QQ 昵称\n * - groupCard: 群昵称(群聊时存在)\n * - remark: 好友备注\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as readline from 'readline'\nimport { KNOWN_PLATFORMS, ChatType, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { createProgress, parseTimestamp, isValidYear } from '../utils'\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'shuakami-qq-exporter-chunked',\n name: 'shuakami/qq-chat-exporter (chunked)',\n platform: KNOWN_PLATFORMS.QQ,\n priority: 5, // 比单文件版本优先级更高\n extensions: ['.json'],\n signatures: {\n // 使用 head 签名 \"format\": \"chunked-jsonl\" 来区分。\n head: [/\"format\"\\s*:\\s*\"chunked-jsonl\"/],\n requiredFields: ['metadata', 'chatInfo'],\n },\n}\n\n// ==================== 类型定义 ====================\n\ninterface ChunkInfo {\n // V5.0 格式\n file?: string\n messages?: number\n bytes?: number\n // V5.5+ 格式\n index?: number\n fileName?: string\n relativePath?: string\n count?: number\n start?: string\n end?: string\n}\n\ninterface Manifest {\n metadata: {\n name?: string\n copyright?: string\n exportTime: string\n version: string\n format: string\n }\n chatInfo: {\n name: string\n type: string\n selfUid?: string\n selfUin?: string\n selfName?: string\n }\n statistics: {\n totalMessages: number\n chunkCount?: number\n timeRange?: {\n start: string\n end: string\n durationDays: number\n }\n messageTypes?: Record<string, number>\n senders?: Array<{\n uid: string\n name: string\n messageCount: number\n percentage: number\n }>\n }\n chunked: {\n format: string\n chunksDir: string\n chunkFileExt: string\n maxMessagesPerChunk: number\n maxBytesPerChunk: number\n chunks: ChunkInfo[]\n }\n avatars?: {\n file: string\n count: number\n }\n}\n\ninterface ChunkedMessage {\n id?: string\n seq?: string\n timestamp: number\n time?: string\n sender: {\n uid?: string\n uin?: string\n name: string\n nickname?: string // QQ 昵称\n groupCard?: string // 群昵称\n remark?: string // 好友备注\n }\n type: string\n content: {\n text: string\n html?: string\n elements?: Array<{ type: string; data?: Record<string, unknown> }>\n resources?: Array<{ type: string }>\n mentions?: Array<{ uid: string; name: string }>\n }\n recalled?: boolean\n system?: boolean\n}\n\ninterface MemberInfo {\n platformId: string\n accountName: string\n groupNickname: string | undefined\n avatar: string | undefined\n}\n\n// ==================== 消息类型转换 ====================\n\nfunction convertMessageType(msgType: string, content: ChunkedMessage['content'], isRecalled?: boolean): MessageType {\n if (isRecalled) return MessageType.RECALL\n\n // 系统消息\n if (msgType === 'system') return MessageType.SYSTEM\n\n // 检查资源类型\n if (content.resources?.length) {\n const resourceType = content.resources[0].type\n switch (resourceType) {\n case 'image':\n return MessageType.IMAGE\n case 'video':\n return MessageType.VIDEO\n case 'voice':\n case 'audio':\n return MessageType.VOICE\n case 'file':\n return MessageType.FILE\n case 'location':\n return MessageType.LOCATION\n }\n }\n\n // 检查表情\n if (content.elements?.some((e) => e.type === 'face' || e.type === 'market_face' || e.type === 'marketFace')) {\n return MessageType.EMOJI\n }\n\n // 根据文本内容判断\n const text = content.text?.trim() || ''\n if (text.includes('QQ红包') || text.includes('发出了红包') || text === '[红包]') return MessageType.RED_PACKET\n if (text.includes('转账') || text === '[转账]') return MessageType.TRANSFER\n if (text.includes('拍了拍') || text.includes('戳了戳') || text === '[拍一拍]') return MessageType.POKE\n if (text.includes('语音通话') || text.includes('视频通话') || text.includes('通话时长')) return MessageType.CALL\n if (text === '[分享]' || text === '[音乐]' || text === '[小程序]') return MessageType.SHARE\n if (text === '[链接]' || text === '[卡片消息]') return MessageType.LINK\n if (text === '[位置]' || text === '[地理位置]') return MessageType.LOCATION\n if (text === '[转发]' || text === '[聊天记录]') return MessageType.FORWARD\n\n return MessageType.TEXT\n}\n\n// ==================== 辅助函数 ====================\n\n/**\n * 读取并解析 manifest.json\n */\nfunction readManifest(manifestPath: string): Manifest {\n const content = fs.readFileSync(manifestPath, 'utf-8')\n return JSON.parse(content) as Manifest\n}\n\n/**\n * 获取 chunk 文件的相对路径(兼容新旧格式)\n */\nfunction getChunkRelativePath(chunk: ChunkInfo): string {\n // V5.5+ 使用 relativePath\n if (chunk.relativePath) return chunk.relativePath\n // V5.0 使用 file\n if (chunk.file) return chunk.file\n // 后备:使用 fileName 拼接\n if (chunk.fileName) return `chunks/${chunk.fileName}`\n throw new Error('无法获取 chunk 文件路径')\n}\n\n/**\n * 获取 chunk 的消息数量(兼容新旧格式)\n */\nfunction getChunkMessageCount(chunk: ChunkInfo): number {\n // V5.5+ 使用 count\n if (chunk.count !== undefined) return chunk.count\n // V5.0 使用 messages\n if (chunk.messages !== undefined) return chunk.messages\n return 0\n}\n\n/**\n * 计算所有 chunk 文件的总字节数\n */\nfunction calculateTotalBytes(manifest: Manifest, baseDir: string): number {\n let total = 0\n for (const chunk of manifest.chunked.chunks) {\n const relativePath = getChunkRelativePath(chunk)\n const chunkPath = path.join(baseDir, relativePath)\n if (fs.existsSync(chunkPath)) {\n total += fs.statSync(chunkPath).size\n }\n }\n return total\n}\n\n/**\n * 读取 avatars.json 文件\n */\nfunction readAvatars(baseDir: string, avatarsInfo?: { file: string; count: number }): Map<string, string> {\n const avatarsMap = new Map<string, string>()\n if (!avatarsInfo?.file) return avatarsMap\n\n const avatarsPath = path.join(baseDir, avatarsInfo.file)\n if (!fs.existsSync(avatarsPath)) return avatarsMap\n\n try {\n const content = fs.readFileSync(avatarsPath, 'utf-8')\n const avatars = JSON.parse(content) as Record<string, string>\n for (const [uin, avatar] of Object.entries(avatars)) {\n if (avatar && typeof avatar === 'string' && avatar.startsWith('data:image/')) {\n avatarsMap.set(uin, avatar)\n }\n }\n } catch {\n // 头像读取失败,继续不带头像\n }\n\n return avatarsMap\n}\n\n/**\n * 流式读取 JSONL 文件(带详细错误处理)\n */\nasync function* readJsonlFile(\n filePath: string,\n onParseError?: (lineNumber: number, error: string) => void\n): AsyncGenerator<ChunkedMessage> {\n let fileStream: fs.ReadStream | null = null\n let rl: readline.Interface | null = null\n let lineNumber = 0\n let parseErrorCount = 0\n const MAX_PARSE_ERRORS_TO_LOG = 10 // 最多记录前 10 个解析错误\n\n try {\n fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n })\n\n for await (const line of rl) {\n lineNumber++\n const trimmed = line.trim()\n if (!trimmed) continue\n\n try {\n yield JSON.parse(trimmed) as ChunkedMessage\n } catch (error) {\n parseErrorCount++\n // 只记录前几个解析错误,避免日志爆炸\n if (parseErrorCount <= MAX_PARSE_ERRORS_TO_LOG && onParseError) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n onParseError(lineNumber, errorMsg)\n }\n // 继续处理下一行\n }\n }\n } finally {\n // 确保资源被正确清理\n if (rl) {\n rl.close()\n }\n if (fileStream) {\n fileStream.destroy()\n }\n }\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseChunkedJsonl(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n // 确定 manifest.json 路径和基础目录\n const manifestPath = filePath\n const baseDir = path.dirname(manifestPath)\n\n onLog?.('info', `开始解析 manifest: ${manifestPath}`)\n onLog?.('info', `基础目录: ${baseDir}`)\n\n // 读取 manifest\n let manifest: Manifest\n try {\n manifest = readManifest(manifestPath)\n onLog?.('info', `manifest 读取成功`)\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n onLog?.('error', `无法读取 manifest.json: ${errorMsg}`)\n yield { type: 'error', data: new Error(`无法读取 manifest.json: ${errorMsg}`) }\n return\n }\n\n // 验证格式\n if (manifest.metadata.format !== 'chunked-jsonl') {\n onLog?.('error', `不支持的格式: ${manifest.metadata.format}`)\n yield { type: 'error', data: new Error(`不支持的格式: ${manifest.metadata.format}`) }\n return\n }\n\n const totalBytes = calculateTotalBytes(manifest, baseDir)\n let bytesRead = 0\n let messagesProcessed = 0\n let skippedMessages = 0\n let parseErrors = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n onLog?.(\n 'info',\n `开始解析 chunked-jsonl 格式 (V${manifest.metadata.version}),共 ${manifest.chunked.chunks.length} 个分块,预计 ${manifest.statistics.totalMessages} 条消息,总大小约 ${Math.round(totalBytes / 1024 / 1024)}MB`\n )\n\n // 读取头像文件(如果存在)\n let avatarsMap: Map<string, string>\n try {\n avatarsMap = readAvatars(baseDir, manifest.avatars)\n if (avatarsMap.size > 0) {\n onLog?.('info', `已加载 ${avatarsMap.size} 个用户头像`)\n }\n } catch (error) {\n onLog?.('error', `读取头像文件失败: ${error instanceof Error ? error.message : String(error)}`)\n avatarsMap = new Map()\n }\n\n // 发送 meta\n const chatType = manifest.chatInfo.type === 'group' ? ChatType.GROUP : ChatType.PRIVATE\n const meta: ParsedMeta = {\n name: manifest.chatInfo.name || '未知群聊',\n platform: KNOWN_PLATFORMS.QQ,\n type: chatType,\n ownerId: manifest.chatInfo.selfUin || manifest.chatInfo.selfUid,\n }\n yield { type: 'meta', data: meta }\n onLog?.('info', `Meta 信息: name=${meta.name}, type=${meta.type}, ownerId=${meta.ownerId}`)\n\n // 收集成员信息(不再收集所有消息到内存)\n const memberMap = new Map<string, MemberInfo>()\n\n // 当前消息批次(真正的流式处理:边读取边发送)\n let currentBatch: ParsedMessage[] = []\n\n // 遍历所有 chunk 文件\n for (let chunkIndex = 0; chunkIndex < manifest.chunked.chunks.length; chunkIndex++) {\n const chunkInfo = manifest.chunked.chunks[chunkIndex]\n const relativePath = getChunkRelativePath(chunkInfo)\n const chunkPath = path.join(baseDir, relativePath)\n const chunkMessageCount = getChunkMessageCount(chunkInfo)\n\n if (!fs.existsSync(chunkPath)) {\n onLog?.('error', `分块文件不存在: ${chunkPath}`)\n continue\n }\n\n const chunkSize = fs.statSync(chunkPath).size\n let chunkMessagesRead = 0\n let chunkParseErrors = 0\n\n onLog?.(\n 'info',\n `正在解析分块 [${chunkIndex + 1}/${manifest.chunked.chunks.length}]: ${relativePath} (预计 ${chunkMessageCount} 条消息, ${Math.round(chunkSize / 1024)}KB)`\n )\n\n // 流式读取 JSONL 文件(带错误处理)\n try {\n const parseErrorCallback = (lineNumber: number, errorMsg: string) => {\n onLog?.('error', `分块 ${relativePath} 第 ${lineNumber} 行 JSON 解析失败: ${errorMsg}`)\n }\n\n for await (const msg of readJsonlFile(chunkPath, parseErrorCallback)) {\n chunkMessagesRead++\n\n // 获取 platformId\n const platformId = msg.sender?.uin || msg.sender?.uid\n if (!platformId || platformId === '0' || platformId === '未知') {\n skippedMessages++\n continue\n }\n\n // 获取名字信息\n // nickname: QQ 昵称(原始昵称)\n // groupCard: 群昵称\n // name: 展示名(一般是 groupCard || nickname)\n const accountName = msg.sender?.nickname || msg.sender?.name || platformId\n const groupNickname = msg.sender?.groupCard || undefined\n\n // 更新成员信息\n const existingMember = memberMap.get(platformId)\n if (!existingMember) {\n memberMap.set(platformId, {\n platformId,\n accountName,\n groupNickname,\n avatar: avatarsMap.get(platformId),\n })\n } else {\n existingMember.accountName = accountName\n if (groupNickname) existingMember.groupNickname = groupNickname\n if (!existingMember.avatar) existingMember.avatar = avatarsMap.get(platformId)\n }\n\n // 解析时间戳(chunked 格式的时间戳是毫秒)\n const timestamp =\n typeof msg.timestamp === 'number' ? Math.floor(msg.timestamp / 1000) : parseTimestamp(msg.timestamp)\n\n if (timestamp === null || !isValidYear(timestamp)) {\n skippedMessages++\n continue\n }\n\n // 消息类型\n const type = msg.system ? MessageType.SYSTEM : convertMessageType(msg.type, msg.content, msg.recalled)\n\n // 文本内容\n let textContent = msg.content?.text || ''\n if (msg.recalled) textContent = '[已撤回] ' + textContent\n\n // 添加到当前批次\n currentBatch.push({\n platformMessageId: msg.id,\n senderPlatformId: platformId,\n senderAccountName: accountName,\n senderGroupNickname: groupNickname,\n timestamp,\n type,\n content: textContent || null,\n })\n\n messagesProcessed++\n\n // 达到批次大小时立即发送(真正的流式处理)\n if (currentBatch.length >= batchSize) {\n yield { type: 'messages', data: currentBatch }\n currentBatch = [] // 清空批次,释放内存\n\n // 发送进度\n const chunkProgress = chunkMessageCount > 0 ? chunkMessagesRead / chunkMessageCount : 0\n const chunkBytesRead = Math.floor(chunkProgress * chunkSize)\n const currentBytesRead = bytesRead + chunkBytesRead\n const progress = createProgress(\n 'parsing',\n currentBytesRead,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n yield { type: 'progress', data: progress }\n onProgress?.(progress)\n }\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n onLog?.('error', `解析分块 ${relativePath} 时发生错误: ${errorMsg}`)\n chunkParseErrors++\n parseErrors++\n // 继续处理下一个分块,不中断整个解析\n }\n\n // 更新总字节读取\n bytesRead += chunkSize\n\n // 记录分块处理结果\n onLog?.(\n 'info',\n `分块 ${relativePath} 解析完成: 读取 ${chunkMessagesRead} 条, 有效 ${messagesProcessed} 条, 跳过 ${skippedMessages} 条${chunkParseErrors > 0 ? `, 错误 ${chunkParseErrors} 个` : ''}`\n )\n }\n\n // 发送剩余的消息批次\n if (currentBatch.length > 0) {\n yield { type: 'messages', data: currentBatch }\n onLog?.('info', `发送最后一批消息: ${currentBatch.length} 条`)\n }\n\n // 发送成员(包含头像)\n const members: ParsedMember[] = Array.from(memberMap.values()).map((m) => ({\n platformId: m.platformId,\n accountName: m.accountName,\n groupNickname: m.groupNickname,\n avatar: m.avatar,\n }))\n yield { type: 'members', data: members }\n onLog?.('info', `发送成员列表: ${members.length} 个成员`)\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberMap.size} 个成员`)\n if (skippedMessages > 0) {\n onLog?.('info', `跳过 ${skippedMessages} 条无效消息(缺少发送者ID或时间戳无效)`)\n }\n if (parseErrors > 0) {\n onLog?.('error', `解析过程中发生 ${parseErrors} 个错误`)\n }\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount: memberMap.size },\n }\n}\n\n// ==================== 导出 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseChunkedJsonl,\n}\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n}\n\nexport default module_\n","/**\n * WeFlow 导出格式解析器\n * 适配项目: WeFlow 聊天记录导出工具\n *\n * 特征:\n * - 顶层包含 weflow、session 和 messages 字段\n * - weflow 对象包含版本信息和导出时间\n * - session.wxid: ID(群聊以 @chatroom 结尾)\n * - session.type: \"群聊\" 或 \"私聊\"\n * - session.avatar: 群/用户头像(base64 Data URL)\n * - messages[].isSend: 1=发送者本人, 0=接收, null=系统\n * - messages[].senderUsername: 发送者ID\n * - messages[].senderDisplayName: 发送者显示名\n *\n * 注意:localType 字段不可信,不使用\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport streamJson from 'stream-json'\nimport pickModule from 'stream-json/filters/Pick.js'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\nimport streamChain from 'stream-chain'\nimport { KNOWN_PLATFORMS, ChatType, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress, readFileHeadBytes } from '../utils'\n\nconst { parser } = streamJson\nconst { pick } = pickModule\nconst { streamValues } = streamValuesModule\nconst { chain } = streamChain\n\n// ==================== 辅助函数 ====================\n\n/**\n * 从文件名提取聊天名称\n */\nfunction extractNameFromFilePath(filePath: string): string {\n const basename = path.basename(filePath)\n const name = basename.replace(/\\.json$/i, '')\n return name || '未知聊天'\n}\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'weflow',\n name: 'WeFlow 导出',\n platform: KNOWN_PLATFORMS.WECHAT,\n priority: 15,\n extensions: ['.json'],\n signatures: {\n // weflow 对象是唯一识别特征\n // 注意:session.avatar 包含 base64 图片,可能很大,所以 messages 字段可能不在 8KB 文件头中\n // 只检测 weflow 和 session(它们在文件开头)\n head: [/\"weflow\"\\s*:\\s*\\{/],\n requiredFields: ['weflow', 'session'],\n },\n}\n\n// ==================== 数据结构 ====================\n\ninterface WeFlowSession {\n wxid: string\n nickname: string\n remark: string\n displayName: string\n type: '群聊' | '私聊'\n lastTimestamp: number\n messageCount: number\n avatar?: string // 群/用户头像(base64 Data URL)\n}\n\ninterface WeFlowMessage {\n localId: number\n createTime: number // Unix 时间戳(秒)\n formattedTime: string\n type: string // 中文消息类型\n localType: number // 不可信,不使用\n content: string\n isSend: number | null // 0=接收, 1=发送, null=系统\n senderUsername: string // 发送者ID\n senderDisplayName: string // 发送者显示名\n senderAvatarKey: string // 头像查找 key(通常与 senderUsername 相同)\n source: string\n}\n\n// ==================== 消息类型映射 ====================\n\n/**\n * 将 WeFlow 中文消息类型转换为标准 MessageType\n */\nfunction convertMessageType(typeStr: string): MessageType {\n switch (typeStr) {\n case '文本消息':\n return MessageType.TEXT\n case '图片消息':\n return MessageType.IMAGE\n case '语音消息':\n return MessageType.VOICE\n case '视频消息':\n return MessageType.VIDEO\n case '文件消息':\n return MessageType.FILE\n case '动画表情':\n return MessageType.EMOJI\n case '名片消息':\n return MessageType.CONTACT\n case '卡片式链接':\n case '图文消息':\n return MessageType.LINK\n case '位置消息':\n return MessageType.LOCATION\n case '红包卡片':\n return MessageType.RED_PACKET\n case '转账卡片':\n return MessageType.TRANSFER\n case '小程序分享':\n case '视频号直播卡片':\n return MessageType.SHARE\n case '引用消息':\n return MessageType.REPLY\n case '聊天记录合并转发':\n return MessageType.FORWARD\n case '系统消息':\n return MessageType.SYSTEM\n default:\n // 未知类型(xxxxx) 或其他\n return MessageType.OTHER\n }\n}\n\n// ==================== 头像信息结构 ====================\n// WeFlow 的 avatars 对象直接存储 base64 Data URL 字符串\n// 格式:{ \"wxid\": \"data:image/jpeg;base64,...\" }\n\n// ==================== 成员信息追踪 ====================\n\ninterface MemberInfo {\n platformId: string\n accountName: string\n avatar: string | undefined // 头像(base64 Data URL)\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseWeFlow(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let bytesRead = 0\n let messagesProcessed = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n // 记录解析开始\n onLog?.('info', `开始解析 WeFlow 导出文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 读取文件头获取基本信息\n const headContent = readFileHeadBytes(filePath, 5000)\n\n // 使用流式读取获取完整的 session 对象(因为 session.avatar 可能很大)\n let session: WeFlowSession | null = null\n try {\n await new Promise<void>((resolve) => {\n const sessionStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n let sessionContent = ''\n let inSession = false\n let braceDepth = 0\n let inString = false\n let escape = false\n\n sessionStream.on('data', (chunk: string | Buffer) => {\n const str = typeof chunk === 'string' ? chunk : chunk.toString()\n\n for (let i = 0; i < str.length; i++) {\n const char = str[i]\n\n if (!inSession) {\n // 查找 \"session\": 的位置\n const searchStr = '\"session\":'\n if (str.slice(i, i + searchStr.length) === searchStr) {\n inSession = true\n i += searchStr.length - 1\n continue\n }\n } else {\n sessionContent += char\n\n if (escape) {\n escape = false\n continue\n }\n\n if (char === '\\\\' && inString) {\n escape = true\n continue\n }\n\n if (char === '\"') {\n inString = !inString\n continue\n }\n\n if (!inString) {\n if (char === '{') braceDepth++\n if (char === '}') {\n braceDepth--\n if (braceDepth === 0) {\n sessionStream.destroy()\n return\n }\n }\n }\n }\n }\n })\n\n sessionStream.on('close', () => {\n if (sessionContent) {\n try {\n session = JSON.parse(sessionContent) as WeFlowSession\n } catch {\n // 解析失败\n }\n }\n resolve()\n })\n\n sessionStream.on('error', () => resolve())\n })\n } catch {\n // 使用默认值\n }\n\n // 确定聊天类型\n // 1. 优先使用 session.type\n // 2. 或者通过 wxid 是否以 @chatroom 结尾判断\n let chatType = ChatType.GROUP\n // 使用局部变量避免 TypeScript 控制流分析问题\n const sessionData = session as WeFlowSession | null\n if (sessionData) {\n if (sessionData.type === '私聊') {\n chatType = ChatType.PRIVATE\n } else if (sessionData.type === '群聊') {\n chatType = ChatType.GROUP\n } else if (sessionData.wxid && !sessionData.wxid.endsWith('@chatroom')) {\n chatType = ChatType.PRIVATE\n }\n }\n\n // 确定聊天名称\n const chatName = sessionData?.displayName || sessionData?.nickname || extractNameFromFilePath(filePath)\n\n // 提取群ID(群聊类型时有值)\n // 群ID 格式:以 @chatroom 结尾\n const groupId = chatType === ChatType.GROUP && sessionData?.wxid ? sessionData.wxid : undefined\n\n // 解析 avatars 对象(头像)\n // avatars 格式:{ \"wxid\": { \"displayName\": \"...\", \"base64\": \"...\" } }\n // 注意:base64 不包含 Data URL 前缀,需要添加\n const avatarsMap = new Map<string, string>()\n\n /**\n * 从字符串中提取 avatars 对象内容\n * 正确处理 JSON 字符串中的花括号匹配(考虑字符串内的转义字符)\n */\n function extractAvatarsObject(content: string): string | null {\n const searchStr = '\"avatars\":'\n const startIdx = content.indexOf(searchStr)\n if (startIdx === -1) return null\n\n let i = startIdx + searchStr.length\n // 跳过空白字符\n while (i < content.length && /\\s/.test(content[i])) i++\n\n if (content[i] !== '{') return null\n\n // 从 { 开始匹配\n let braceDepth = 0\n let inString = false\n let escape = false\n const objStart = i\n\n for (; i < content.length; i++) {\n const char = content[i]\n\n if (escape) {\n escape = false\n continue\n }\n\n if (char === '\\\\' && inString) {\n escape = true\n continue\n }\n\n if (char === '\"') {\n inString = !inString\n continue\n }\n\n if (!inString) {\n if (char === '{') braceDepth++\n if (char === '}') {\n braceDepth--\n if (braceDepth === 0) {\n return content.slice(objStart, i + 1)\n }\n }\n }\n }\n\n return null\n }\n\n try {\n // 先尝试从文件头解析(适用于成员较少的聊天)\n const avatarsContent = extractAvatarsObject(headContent)\n if (avatarsContent) {\n // WeFlow 的 avatars 值直接是 base64 Data URL 字符串\n const avatarsObj = JSON.parse(avatarsContent) as Record<string, string>\n for (const [wxid, avatarDataUrl] of Object.entries(avatarsObj)) {\n if (avatarDataUrl && typeof avatarDataUrl === 'string') {\n avatarsMap.set(wxid, avatarDataUrl)\n }\n }\n }\n } catch {\n // avatars 解析失败,继续不带头像\n }\n\n // 如果文件头没有完整的 avatars(可能超出 5000 字节),尝试流式读取\n if (avatarsMap.size === 0) {\n try {\n await new Promise<void>((resolve) => {\n const avatarStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n let avatarsContent = ''\n let inAvatars = false\n let braceDepth = 0\n let inString = false\n let escape = false\n\n avatarStream.on('data', (chunk: string | Buffer) => {\n const str = typeof chunk === 'string' ? chunk : chunk.toString()\n\n for (let i = 0; i < str.length; i++) {\n const char = str[i]\n\n if (!inAvatars) {\n // 查找 \"avatars\": 的位置\n const searchStr = '\"avatars\":'\n if (str.slice(i, i + searchStr.length) === searchStr) {\n inAvatars = true\n // 跳过 \"avatars\": 和可能的空白\n i += searchStr.length - 1\n continue\n }\n } else {\n // 开始收集 avatars 对象内容\n avatarsContent += char\n\n if (escape) {\n escape = false\n continue\n }\n\n if (char === '\\\\' && inString) {\n escape = true\n continue\n }\n\n if (char === '\"') {\n inString = !inString\n continue\n }\n\n if (!inString) {\n if (char === '{') braceDepth++\n if (char === '}') {\n braceDepth--\n if (braceDepth === 0) {\n // avatars 对象结束\n avatarStream.destroy()\n return\n }\n }\n }\n }\n }\n })\n\n avatarStream.on('close', () => {\n if (avatarsContent) {\n try {\n // WeFlow 的 avatars 值直接是 base64 Data URL 字符串\n const avatarsObj = JSON.parse(avatarsContent) as Record<string, string>\n for (const [wxid, avatarDataUrl] of Object.entries(avatarsObj)) {\n if (avatarDataUrl && typeof avatarDataUrl === 'string') {\n avatarsMap.set(wxid, avatarDataUrl)\n }\n }\n } catch {\n // 解析失败\n }\n }\n resolve()\n })\n\n avatarStream.on('error', () => resolve())\n })\n } catch {\n // 流式解析失败,继续不带头像\n }\n }\n\n // 提取群头像(优先从 session.avatar,其次从 avatars 中获取群ID对应的头像)\n const groupAvatar = sessionData?.avatar || (groupId ? avatarsMap.get(groupId) : undefined)\n\n // 快速扫描获取 ownerId(通过 isSend === 1 推断)\n let ownerId: string | undefined\n try {\n await new Promise<void>((resolve) => {\n const scanStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n const scanPipeline = chain([scanStream, parser(), pick({ filter: /^messages\\.\\d+$/ }), streamValues()])\n\n scanPipeline.on('data', ({ value }: { value: WeFlowMessage }) => {\n if (value.isSend === 1 && value.senderUsername && !value.senderUsername.endsWith('@chatroom')) {\n ownerId = value.senderUsername\n scanStream.destroy() // 找到后立即停止扫描\n }\n })\n\n scanStream.on('close', () => resolve())\n scanPipeline.on('end', () => resolve())\n scanPipeline.on('error', () => resolve())\n })\n } catch {\n // 扫描失败,ownerId 保持 undefined\n }\n\n // 发送 meta(包含推断的 ownerId)\n const meta: ParsedMeta = {\n name: chatName,\n platform: KNOWN_PLATFORMS.WECHAT,\n type: chatType,\n groupId,\n groupAvatar,\n ownerId,\n }\n yield { type: 'meta', data: meta }\n\n // 收集成员和消息\n const memberMap = new Map<string, MemberInfo>()\n const messageBatch: ParsedMessage[] = []\n\n // 流式解析\n await new Promise<void>((resolve, reject) => {\n const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n readStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n const pipeline = chain([readStream, parser(), pick({ filter: /^messages\\.\\d+$/ }), streamValues()])\n\n const processMessage = (msg: WeFlowMessage): ParsedMessage | null => {\n // 验证必要字段\n if (!msg.senderUsername || msg.createTime === undefined) {\n return null\n }\n\n const platformId = msg.senderUsername\n\n // 跳过群\"成员\"(群ID以 @chatroom 结尾的消息)\n // 这些通常是系统消息,发送者是群本身,不是真正的成员\n if (platformId.endsWith('@chatroom')) {\n return null\n }\n\n const accountName = msg.senderDisplayName || platformId\n\n // 获取头像(通过 senderAvatarKey 从 avatarsMap 查找)\n const avatarKey = msg.senderAvatarKey || msg.senderUsername\n const avatar = avatarsMap.get(avatarKey)\n\n // 更新成员信息\n if (!memberMap.has(platformId)) {\n memberMap.set(platformId, {\n platformId,\n accountName,\n avatar,\n })\n } else {\n // 更新为最新的显示名\n const existing = memberMap.get(platformId)!\n existing.accountName = accountName\n // 头像使用最新的(覆盖更新)\n if (avatar) {\n existing.avatar = avatar\n }\n }\n\n // 转换消息类型\n const type = convertMessageType(msg.type)\n\n // 确保 content 是字符串类型(防止某些消息类型的 content 是对象)\n // 同时去除开头和结尾的空白字符\n let content: string | null = null\n if (msg.content != null) {\n const rawContent = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)\n content = rawContent.trim() || null\n }\n\n return {\n platformMessageId: String(msg.localId), // 消息的平台原始 ID(用于回复关联查询)\n senderPlatformId: platformId,\n senderAccountName: accountName,\n // WeFlow 格式没有单独的群昵称字段\n senderGroupNickname: undefined,\n timestamp: msg.createTime,\n type,\n content,\n // 注意:WeFlow 导出格式不包含被引用消息的 ID,所以 replyToMessageId 为空\n }\n }\n\n // 用于收集批次的临时数组\n const batchCollector: ParsedMessage[] = []\n\n pipeline.on('data', ({ value }: { value: WeFlowMessage }) => {\n const parsed = processMessage(value)\n if (parsed) {\n batchCollector.push(parsed)\n messagesProcessed++\n\n // 达到批次大小\n if (batchCollector.length >= batchSize) {\n messageBatch.push(...batchCollector)\n batchCollector.length = 0\n\n const progress = createProgress(\n 'parsing',\n bytesRead,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n onProgress?.(progress)\n }\n }\n })\n\n pipeline.on('end', () => {\n // 收集剩余消息\n if (batchCollector.length > 0) {\n messageBatch.push(...batchCollector)\n }\n resolve()\n })\n\n pipeline.on('error', reject)\n })\n\n // 发送成员\n const members: ParsedMember[] = Array.from(memberMap.values()).map((m) => ({\n platformId: m.platformId,\n accountName: m.accountName,\n avatar: m.avatar,\n }))\n yield { type: 'members', data: members }\n\n // 分批发送消息\n for (let i = 0; i < messageBatch.length; i += batchSize) {\n const batch = messageBatch.slice(i, i + batchSize)\n yield { type: 'messages', data: batch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n // 记录解析摘要\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberMap.size} 个成员`)\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount: memberMap.size },\n }\n}\n\n// ==================== 导出解析器 ====================\n\n// 导出解析函数供其他格式复用(如 ycccccccy-echotrace)\nexport { parseWeFlow }\n\nexport const parser_: Parser = {\n feature,\n parse: parseWeFlow,\n}\n\n// ==================== 预处理器(预留) ====================\n\nimport { weflowPreprocessor } from './weflow-preprocessor'\nexport const preprocessor = weflowPreprocessor\n\n// ==================== 导出格式模块 ====================\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n preprocessor: weflowPreprocessor,\n}\n\nexport default module_\n","/**\n * WeFlow 格式预处理器\n * 用于大文件预处理,移除冗余字段\n *\n * 当前为预留实现,WeFlow 格式的字段结构较为简洁,\n * 暂不需要复杂的预处理逻辑。\n *\n * 如果未来发现性能问题,可在此添加:\n * - 移除 formattedTime 字段(可由 createTime 计算)\n * - 移除 source 字段(通常为空)\n * - 移除 localType 字段(不可信)\n */\n\nimport type { Preprocessor, ParseProgress } from '../types'\n\n/**\n * WeFlow 预处理器\n * 当前为预留实现,返回不需要预处理\n */\nexport const weflowPreprocessor: Preprocessor = {\n /**\n * 判断是否需要预处理\n * 当前策略:暂不需要预处理\n * 如果未来发现大文件性能问题,可调整阈值\n */\n needsPreprocess(_filePath: string, _fileSize: number): boolean {\n // 预留:如果文件超过 100MB 可能需要预处理\n // const THRESHOLD = 100 * 1024 * 1024 // 100MB\n // return fileSize > THRESHOLD\n return false\n },\n\n /**\n * 执行预处理\n * 当前为预留实现,直接返回原文件路径\n */\n async preprocess(filePath: string, _onProgress?: (progress: ParseProgress) => void): Promise<string> {\n // 预留:未来可在此实现精简逻辑\n // 1. 使用流式读取原文件\n // 2. 移除冗余字段 (formattedTime, source, localType)\n // 3. 写入临时文件\n // 4. 返回临时文件路径\n return filePath\n },\n\n /**\n * 清理临时文件\n */\n cleanup(_tempPath: string): void {\n // 预留:当 preprocess 返回临时文件时,在此清理\n // if (tempPath !== originalFilePath) {\n // fs.unlinkSync(tempPath)\n // }\n },\n}\n\nexport default weflowPreprocessor\n","/**\n * ycccccccy/echotrace 导出格式解析器\n * 适配项目: https://github.com/ycccccccy/echotrace\n *\n * 特征:\n * - 顶层包含 session 和 messages 字段(无 weflow 对象)\n * - session.wxid: ID(群聊以 @chatroom 结尾)\n * - session.type: \"群聊\" 或 \"私聊\"\n * - messages[].senderUsername: 发送者ID\n * - messages[].senderDisplayName: 发送者显示名\n *\n * 注意:此格式与 WeFlow 格式共享解析逻辑,区别在于签名检测\n */\n\nimport { KNOWN_PLATFORMS } from '@openchatlab/shared-types'\nimport type { FormatFeature, FormatModule, Parser } from '../types'\nimport { parseWeFlow } from './weflow'\nimport { weflowPreprocessor } from './weflow-preprocessor'\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'ycccccccy-echotrace',\n name: 'ycccccccy/echotrace 导出',\n platform: KNOWN_PLATFORMS.WECHAT,\n priority: 16, // 比 WeFlow (15) 略低,优先检测 WeFlow\n extensions: ['.json'],\n signatures: {\n // echotrace 格式没有 weflow 对象,但有 session 和 senderUsername/senderDisplayName\n head: [/\"session\"\\s*:/, /\"senderUsername\"\\s*:/, /\"senderDisplayName\"\\s*:/],\n requiredFields: ['session', 'messages'],\n },\n}\n\n// ==================== 导出解析器 ====================\n\n// 复用 WeFlow 的解析逻辑(两种格式的数据结构相同)\nexport const parser_: Parser = {\n feature,\n parse: parseWeFlow,\n}\n\n// ==================== 预处理器(复用 WeFlow) ====================\n\nexport const preprocessor = weflowPreprocessor\n\n// ==================== 导出格式模块 ====================\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n preprocessor: weflowPreprocessor,\n}\n\nexport default module_\n","/**\n * Tyrrrz/DiscordChatExporter 格式解析器\n * 支持 https://github.com/Tyrrrz/DiscordChatExporter 导出的 Discord 聊天记录\n *\n * 特征:\n * - 文件头包含 \"guild\" 和 \"channel\" 字段\n * - 消息结构包含 author.roles 数组\n * - 支持附件、嵌入、贴纸等 Discord 特有内容\n */\n\nimport * as fs from 'fs'\nimport streamJson from 'stream-json'\nimport pickModule from 'stream-json/filters/Pick.js'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\nimport streamChain from 'stream-chain'\nimport { KNOWN_PLATFORMS, ChatType, MessageType, type MemberRole } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress, readFileHeadBytes } from '../utils'\n\nconst { parser } = streamJson\nconst { pick } = pickModule\nconst { streamValues } = streamValuesModule\nconst { chain } = streamChain\n\n// ==================== Discord 数据结构定义 ====================\n\ninterface DiscordGuild {\n id: string\n name: string\n iconUrl?: string\n}\n\ninterface DiscordChannel {\n id: string\n type: string\n categoryId?: string\n category?: string\n name: string\n topic?: string\n}\n\ninterface DiscordRole {\n id: string\n name: string\n color?: string\n position?: number\n}\n\ninterface DiscordAuthor {\n id: string\n name: string\n discriminator: string\n nickname?: string\n color?: string\n isBot: boolean\n roles: DiscordRole[]\n avatarUrl?: string\n}\n\ninterface DiscordAttachment {\n id: string\n url: string\n fileName: string\n fileSizeBytes?: number\n}\n\ninterface DiscordEmbed {\n title?: string\n url?: string\n description?: string\n thumbnail?: { url: string }\n}\n\ninterface DiscordSticker {\n id: string\n name: string\n format: string\n sourceUrl?: string\n}\n\ninterface DiscordMessage {\n id: string\n type: string\n timestamp: string\n timestampEdited?: string | null\n callEndedTimestamp?: string | null\n isPinned: boolean\n content: string\n author: DiscordAuthor\n attachments: DiscordAttachment[]\n embeds: DiscordEmbed[]\n stickers: DiscordSticker[]\n reference?: {\n messageId?: string\n channelId?: string\n guildId?: string | null\n }\n}\n\n// ==================== 消息类型映射 ====================\n\n/**\n * Discord 消息类型到 ChatLab 消息类型的映射\n */\nfunction mapDiscordMessageType(type: string): MessageType {\n switch (type) {\n // 内容消息\n case 'Default':\n case 'ThreadStarterMessage':\n return MessageType.TEXT\n case 'Reply':\n return MessageType.REPLY\n // 通话相关\n case 'Call':\n return MessageType.CALL\n // 系统消息\n case 'RecipientAdd':\n case 'RecipientRemove':\n case 'ChannelNameChange':\n case 'ChannelIconChange':\n case 'ChannelPinnedMessage':\n case 'UserJoin':\n case 'GuildBoost':\n case 'GuildBoostTier1':\n case 'GuildBoostTier2':\n case 'GuildBoostTier3':\n case 'ChannelFollowAdd':\n case 'ThreadCreated':\n case 'ChatInputCommand':\n case 'ContextMenuCommand':\n case 'AutoModerationAction':\n case 'StageStart':\n case 'StageEnd':\n case 'StageSpeaker':\n case 'StageTopic':\n case 'GuildDiscoveryDisqualified':\n case 'GuildDiscoveryRequalified':\n case 'GuildDiscoveryGracePeriodInitialWarning':\n case 'GuildDiscoveryGracePeriodFinalWarning':\n case 'GuildInviteReminder':\n case 'RoleSubscriptionPurchase':\n case 'InteractionPremiumUpsell':\n case 'GuildApplicationPremiumSubscription':\n return MessageType.SYSTEM\n default:\n return MessageType.OTHER\n }\n}\n\n/**\n * 根据附件判断消息类型\n */\nfunction getMessageTypeFromAttachment(attachment: DiscordAttachment): MessageType {\n const fileName = attachment.fileName.toLowerCase()\n\n // 图片\n if (/\\.(png|jpg|jpeg|gif|webp|bmp|svg)$/i.test(fileName)) {\n return MessageType.IMAGE\n }\n // 视频\n if (/\\.(mp4|webm|mov|avi|mkv)$/i.test(fileName)) {\n return MessageType.VIDEO\n }\n // 音频\n if (/\\.(mp3|wav|ogg|flac|m4a)$/i.test(fileName)) {\n return MessageType.VOICE\n }\n // 其他文件\n return MessageType.FILE\n}\n\n// ==================== 附件文本生成 ====================\n\n/**\n * 附件标记生成器\n * 使用英文标记,前端可根据需要翻译\n * 格式: [Type: filename] 或 [Type: description]\n */\nconst AttachmentMarkers = {\n image: (fileName: string) => `[Image: ${fileName}]`,\n video: (fileName: string) => `[Video: ${fileName}]`,\n audio: (fileName: string) => `[Audio: ${fileName}]`,\n file: (fileName: string) => `[File: ${fileName}]`,\n sticker: (name: string) => `[Sticker: ${name}]`,\n embed: (title: string) => `[Link: ${title}]`,\n}\n\n/**\n * 生成附件文本标记\n */\nfunction formatAttachment(attachment: DiscordAttachment): string {\n const fileName = attachment.fileName.toLowerCase()\n\n if (/\\.(png|jpg|jpeg|gif|webp|bmp|svg)$/i.test(fileName)) {\n return AttachmentMarkers.image(attachment.fileName)\n }\n if (/\\.(mp4|webm|mov|avi|mkv)$/i.test(fileName)) {\n return AttachmentMarkers.video(attachment.fileName)\n }\n if (/\\.(mp3|wav|ogg|flac|m4a)$/i.test(fileName)) {\n return AttachmentMarkers.audio(attachment.fileName)\n }\n return AttachmentMarkers.file(attachment.fileName)\n}\n\n/**\n * 生成嵌入文本标记\n */\nfunction formatEmbed(embed: DiscordEmbed): string {\n const title = embed.title || embed.url || embed.description?.slice(0, 30) || 'link'\n return AttachmentMarkers.embed(title)\n}\n\n/**\n * 生成贴纸文本标记\n */\nfunction formatSticker(sticker: DiscordSticker): string {\n return AttachmentMarkers.sticker(sticker.name)\n}\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'tyrrrz-discord-exporter',\n name: 'Tyrrrz/DiscordChatExporter',\n platform: KNOWN_PLATFORMS.DISCORD,\n priority: 20, // Discord 格式优先级\n extensions: ['.json'],\n signatures: {\n head: [/\"guild\"\\s*:\\s*\\{/, /\"channel\"\\s*:\\s*\\{/],\n requiredFields: ['guild', 'channel', 'messages'],\n },\n}\n\n// ==================== 辅助函数 ====================\n\n/**\n * 解析 ISO 8601 时间戳为秒级 Unix 时间戳\n */\nfunction parseTimestamp(isoString: string): number {\n const date = new Date(isoString)\n return Math.floor(date.getTime() / 1000)\n}\n\n/**\n * 转换 Discord 角色为 ChatLab MemberRole\n */\nfunction convertRoles(discordRoles: DiscordRole[]): MemberRole[] {\n return discordRoles.map((role) => ({\n id: role.id,\n name: role.name,\n }))\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseDiscordExporter(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let bytesRead = 0\n let messagesProcessed = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n onLog?.('info', `开始解析 Discord 导出文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 读取文件头获取 guild 和 channel 信息\n const headContent = readFileHeadBytes(filePath, 10000)\n\n // 解析 guild 和 channel\n let guild: DiscordGuild | null = null\n let channel: DiscordChannel | null = null\n\n try {\n const guildMatch = headContent.match(/\"guild\"\\s*:\\s*(\\{[^}]+\\})/)\n if (guildMatch) {\n guild = JSON.parse(guildMatch[1])\n }\n\n const channelMatch = headContent.match(/\"channel\"\\s*:\\s*(\\{[^}]+\\})/)\n if (channelMatch) {\n channel = JSON.parse(channelMatch[1])\n }\n } catch (e) {\n onLog?.('error', `解析头部信息失败: ${e}`)\n }\n\n // 构建 meta\n const chatName = guild && channel ? `${guild.name} - ${channel.name}` : channel?.name || '未知频道'\n\n const meta: ParsedMeta = {\n name: chatName,\n platform: KNOWN_PLATFORMS.DISCORD,\n type: ChatType.GROUP, // Discord 频道都是群聊\n groupId: channel?.id,\n groupAvatar: guild?.iconUrl,\n }\n\n yield { type: 'meta', data: meta }\n onLog?.('info', `群聊名称: ${chatName}`)\n\n // 收集成员和消息\n const memberMap = new Map<string, ParsedMember>()\n const messageBatch: ParsedMessage[] = []\n\n // 流式解析消息\n await new Promise<void>((resolve, reject) => {\n const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n readStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n const pipeline = chain([readStream, parser(), pick({ filter: /^messages\\.\\d+$/ }), streamValues()])\n\n // 用于收集批次的临时数组\n const batchCollector: ParsedMessage[] = []\n\n pipeline.on('data', ({ value }: { value: DiscordMessage }) => {\n const msg = value\n const author = msg.author\n\n // 收集成员信息(包括角色)\n if (!memberMap.has(author.id)) {\n memberMap.set(author.id, {\n platformId: author.id,\n accountName: author.name,\n groupNickname: author.nickname || undefined,\n avatar: author.avatarUrl,\n roles: convertRoles(author.roles),\n })\n } else {\n // 更新角色(用户可能在不同消息中有不同角色)\n const existingMember = memberMap.get(author.id)!\n if (author.roles.length > (existingMember.roles?.length || 0)) {\n existingMember.roles = convertRoles(author.roles)\n }\n }\n\n // 确定消息类型\n let messageType = mapDiscordMessageType(msg.type)\n let content = msg.content || ''\n\n // 处理附件\n if (msg.attachments && msg.attachments.length > 0) {\n // 如果是纯附件消息,根据附件类型确定消息类型\n if (!content && msg.attachments.length === 1) {\n messageType = getMessageTypeFromAttachment(msg.attachments[0])\n }\n // 添加附件标记到内容\n const attachmentTexts = msg.attachments.map(formatAttachment)\n content = content ? `${content}\\n${attachmentTexts.join('\\n')}` : attachmentTexts.join('\\n')\n }\n\n // 处理嵌入\n if (msg.embeds && msg.embeds.length > 0) {\n const embedTexts = msg.embeds.map(formatEmbed)\n content = content ? `${content}\\n${embedTexts.join('\\n')}` : embedTexts.join('\\n')\n // 如果纯嵌入消息,标记为链接类型\n if (!msg.content && !msg.attachments?.length && msg.embeds.length > 0) {\n messageType = MessageType.LINK\n }\n }\n\n // 处理贴纸\n if (msg.stickers && msg.stickers.length > 0) {\n const stickerTexts = msg.stickers.map(formatSticker)\n content = content ? `${content}\\n${stickerTexts.join('\\n')}` : stickerTexts.join('\\n')\n // 纯贴纸消息\n if (!msg.content && !msg.attachments?.length && !msg.embeds?.length) {\n messageType = MessageType.EMOJI\n }\n }\n\n batchCollector.push({\n platformMessageId: msg.id, // 消息的平台原始 ID\n senderPlatformId: author.id,\n senderAccountName: author.name,\n senderGroupNickname: author.nickname || undefined,\n timestamp: parseTimestamp(msg.timestamp),\n type: messageType,\n content: content || null,\n replyToMessageId: msg.reference?.messageId || undefined,\n })\n\n messagesProcessed++\n\n // 达到批次大小\n if (batchCollector.length >= batchSize) {\n messageBatch.push(...batchCollector)\n batchCollector.length = 0\n\n const progress = createProgress(\n 'parsing',\n bytesRead,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n onProgress?.(progress)\n }\n })\n\n pipeline.on('end', () => {\n // 收集剩余消息\n if (batchCollector.length > 0) {\n messageBatch.push(...batchCollector)\n }\n resolve()\n })\n\n pipeline.on('error', reject)\n })\n\n // 发送成员\n const members = Array.from(memberMap.values())\n yield { type: 'members', data: members }\n onLog?.('info', `解析到 ${members.length} 个成员`)\n\n // 分批发送消息\n for (let i = 0; i < messageBatch.length; i += batchSize) {\n const batch = messageBatch.slice(i, i + batchSize)\n yield { type: 'messages', data: batch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${members.length} 个成员`)\n\n yield {\n type: 'done',\n data: {\n messageCount: messagesProcessed,\n memberCount: members.length,\n },\n }\n}\n\n// ==================== 导出解析器 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseDiscordExporter,\n}\n\n// ==================== 导出格式模块 ====================\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n}\n\nexport default module_\n","/**\n * Telegram 官方全量导出 JSON 格式解析器\n * 适配 Telegram Desktop (macOS) 的「导出聊天记录」→ 全部导出\n *\n * 格式特征:\n * - 单个 JSON 文件包含用户所有聊天(contacts, chats 等)\n * - 聊天数据在 chats.list[] 下,每个聊天有独立的 messages 数组\n * - 消息的 text 字段可以是纯字符串或 mixed 数组(含富文本实体)\n * - 支持 personal_chat / private_group / private_supergroup / saved_messages 等类型\n *\n * 导入流程(多聊天选择器):\n * 1. 用户选择 telegram.json → 格式识别\n * 2. scanChats() 快速扫描提取聊天列表\n * 3. 用户选择要导入的聊天\n * 4. parser 使用 formatOptions.chatIndex 定位并流式解析选定聊天\n */\n\nimport * as fs from 'fs'\nimport streamChain from 'stream-chain'\nimport streamJson from 'stream-json'\nimport pickModule from 'stream-json/filters/Pick.js'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\n\nimport { KNOWN_PLATFORMS, ChatType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress } from '../utils'\nimport { mapChatType, extractPlatformId, detectMessageType, buildContent } from './utils/telegram-utils'\nimport type { TelegramChat } from './utils/telegram-utils'\n\nconst { chain } = streamChain\nconst { parser } = streamJson\nconst { pick } = pickModule\nconst { streamValues } = streamValuesModule\n\n// ==================== 类型定义 ====================\n\n/** Telegram 聊天信息(扫描结果) */\nexport interface TelegramChatInfo {\n /** 在 chats.list[] 中的索引 */\n index: number\n /** 聊天名称 */\n name: string\n /** Telegram 聊天类型 */\n type: string\n /** Telegram 聊天 ID */\n id: number\n /** 消息数量 */\n messageCount: number\n}\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'telegram-native',\n name: 'Telegram 官方导出 (JSON)',\n platform: KNOWN_PLATFORMS.TELEGRAM,\n priority: 22,\n extensions: ['.json'],\n signatures: {\n // Telegram 导出 JSON 的特征(语言无关:品牌名在所有语言导出中都存在)\n head: [/Telegram/i],\n // 注意:personal_information 在某些导出配置中是可选的,不能作为必需字段\n requiredFields: ['chats'],\n },\n multiChat: true,\n}\n\n// ==================== 扫描函数 ====================\n\n/**\n * 快速扫描 Telegram 导出 JSON,提取聊天列表\n * 使用 stream-json 流式处理,避免全量加载大文件到内存\n *\n * @param filePath 文件路径\n * @returns 聊天列表信息\n */\nexport async function scanChats(filePath: string): Promise<TelegramChatInfo[]> {\n const chats: TelegramChatInfo[] = []\n\n return new Promise<TelegramChatInfo[]>((resolve, reject) => {\n const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n // 使用 stream-json 解析 chats.list 数组中的每个聊天对象\n // ignore 过滤掉 messages 的实际内容以加速扫描\n const pipeline = chain([readStream, parser(), pick({ filter: /^chats\\.list\\.\\d+$/ }), streamValues()])\n\n pipeline.on('data', ({ value }: { value: TelegramChat }) => {\n const chat = value\n chats.push({\n index: chats.length,\n name: chat.name || `Chat ${chat.id}`,\n type: chat.type,\n id: chat.id,\n messageCount: Array.isArray(chat.messages) ? chat.messages.length : 0,\n })\n })\n\n pipeline.on('end', () => {\n resolve(chats)\n })\n\n pipeline.on('error', (err: Error) => {\n reject(new Error(`扫描 Telegram 文件失败: ${err.message}`))\n })\n })\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseTelegram(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, formatOptions, onProgress, onLog } = options\n\n // 获取目标聊天索引\n const chatIndex = (formatOptions?.chatIndex as number) ?? 0\n\n const totalBytes = getFileSize(filePath)\n let bytesRead = 0\n let messagesProcessed = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n onLog?.('info', `开始解析 Telegram JSON 文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n onLog?.('info', `目标聊天索引: ${chatIndex}`)\n\n // 使用 stream-json 流式解析目标聊天\n // 定位到 chats.list[chatIndex] 对象\n const chatPathFilter = `chats.list.${chatIndex}`\n\n const chatData = await new Promise<TelegramChat | null>((resolve, reject) => {\n const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n readStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n const pipeline = chain([\n readStream,\n parser(),\n pick({ filter: new RegExp(`^${chatPathFilter.replace('.', '\\\\.')}$`) }),\n streamValues(),\n ])\n\n let found = false\n\n pipeline.on('data', ({ value }: { value: TelegramChat }) => {\n found = true\n resolve(value)\n })\n\n pipeline.on('end', () => {\n if (!found) resolve(null)\n })\n\n pipeline.on('error', reject)\n })\n\n if (!chatData) {\n onLog?.('error', `未找到索引 ${chatIndex} 对应的聊天`)\n yield { type: 'error', data: new Error(`未找到索引 ${chatIndex} 对应的聊天`) }\n return\n }\n\n onLog?.('info', `找到聊天: \"${chatData.name}\", 类型: ${chatData.type}, 消息数: ${chatData.messages?.length || 0}`)\n\n // 确定聊天类型\n const chatType = mapChatType(chatData.type)\n\n // 发送 meta\n const meta: ParsedMeta = {\n name: chatData.name || `Telegram Chat ${chatData.id}`,\n platform: KNOWN_PLATFORMS.TELEGRAM,\n type: chatType,\n groupId: chatType === ChatType.GROUP ? String(chatData.id) : undefined,\n }\n yield { type: 'meta', data: meta }\n\n // 收集成员和消息\n const memberMap = new Map<string, ParsedMember>()\n const messageBatch: ParsedMessage[] = []\n const messages = chatData.messages || []\n\n for (const msg of messages) {\n // 提取发送者信息\n let senderPlatformId: string\n let senderName: string\n\n if (msg.type === 'service') {\n // Service 消息使用 actor 信息\n senderPlatformId = extractPlatformId(msg.actor_id)\n senderName = msg.actor || '系统'\n } else {\n senderPlatformId = extractPlatformId(msg.from_id)\n senderName = msg.from || senderPlatformId\n }\n\n // 更新成员\n if (!memberMap.has(senderPlatformId) && senderPlatformId !== 'unknown') {\n memberMap.set(senderPlatformId, {\n platformId: senderPlatformId,\n accountName: senderName,\n })\n }\n\n // 解析时间戳\n const timestamp = parseInt(msg.date_unixtime, 10)\n if (isNaN(timestamp)) continue\n\n // 构建消息\n const parsedMsg: ParsedMessage = {\n platformMessageId: String(msg.id),\n senderPlatformId,\n senderAccountName: senderName,\n timestamp,\n type: detectMessageType(msg),\n content: buildContent(msg),\n replyToMessageId: msg.reply_to_message_id ? String(msg.reply_to_message_id) : undefined,\n }\n\n messageBatch.push(parsedMsg)\n messagesProcessed++\n\n // 分批 yield 消息\n if (messageBatch.length >= batchSize) {\n yield { type: 'messages', data: [...messageBatch] }\n messageBatch.length = 0\n\n const progress = createProgress(\n 'parsing',\n bytesRead,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n yield { type: 'progress', data: progress }\n onProgress?.(progress)\n }\n }\n\n // 发送成员\n yield { type: 'members', data: Array.from(memberMap.values()) }\n\n // 发送剩余消息\n if (messageBatch.length > 0) {\n yield { type: 'messages', data: messageBatch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberMap.size} 个成员, 类型: ${chatType}`)\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount: memberMap.size },\n }\n}\n\n// ==================== 导出 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseTelegram,\n}\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n scanChats,\n}\n\nexport default module_\n","/**\n * Telegram 格式解析共享工具\n * 供 telegram-native(全量导出)和 telegram-native-single(单聊天导出)共用\n */\n\nimport { ChatType, MessageType } from '@openchatlab/shared-types'\n\n// ==================== 共享类型 ====================\n\n/** Telegram 消息结构 */\nexport interface TelegramMessage {\n id: number\n type: 'message' | 'service'\n date: string\n date_unixtime: string\n from?: string | null\n from_id?: string\n actor?: string | null\n actor_id?: string\n action?: string\n text: string | Array<string | { type: string; text: string }>\n text_entities?: Array<{ type: string; text: string }>\n reply_to_message_id?: number\n forwarded_from?: string | null\n forwarded_from_id?: string\n photo?: string\n file?: string\n file_name?: string\n media_type?: string\n sticker_emoji?: string\n mime_type?: string\n members?: string[]\n}\n\n/** Telegram 聊天结构 */\nexport interface TelegramChat {\n name: string\n type: string\n id: number\n messages: TelegramMessage[]\n}\n\n// ==================== 共享辅助函数 ====================\n\n/**\n * 将 Telegram 聊天类型映射到 ChatLab 聊天类型\n */\nexport function mapChatType(telegramType: string): ChatType {\n switch (telegramType) {\n case 'personal_chat':\n case 'bot_chat':\n case 'saved_messages':\n return ChatType.PRIVATE\n case 'private_group':\n case 'public_group':\n case 'private_supergroup':\n case 'public_supergroup':\n case 'private_channel':\n case 'public_channel':\n return ChatType.GROUP\n default:\n return ChatType.GROUP\n }\n}\n\n/**\n * 提取 Telegram text 字段为纯文本\n * text 可以是字符串或 mixed 数组\n */\nexport function extractText(text: string | Array<string | { type: string; text: string }>): string {\n if (typeof text === 'string') return text\n if (!Array.isArray(text)) return ''\n\n return text\n .map((part) => {\n if (typeof part === 'string') return part\n if (part && typeof part === 'object' && 'text' in part) return part.text\n return ''\n })\n .join('')\n}\n\n/**\n * 从 from_id 提取平台 ID\n * 格式:user123456 → 123456\n */\nexport function extractPlatformId(fromId: string | undefined): string {\n if (!fromId) return 'unknown'\n // 移除 \"user\" / \"channel\" 等前缀,保留数字部分\n return fromId.replace(/^(user|channel)/, '')\n}\n\n/**\n * 检测消息类型\n */\nexport function detectMessageType(msg: TelegramMessage): MessageType {\n // Service 消息 → 系统消息\n if (msg.type === 'service') return MessageType.SYSTEM\n\n // 贴纸\n if (msg.media_type === 'sticker') return MessageType.IMAGE\n // 图片\n if (msg.photo) return MessageType.IMAGE\n // 动画 (GIF)\n if (msg.media_type === 'animation') return MessageType.IMAGE\n // 视频\n if (msg.media_type === 'video_file') return MessageType.VIDEO\n // 语音\n if (msg.media_type === 'voice_message') return MessageType.VOICE\n // 视频留言\n if (msg.media_type === 'video_message') return MessageType.VIDEO\n // 文件\n if (msg.file && !msg.media_type) return MessageType.FILE\n\n // 默认文本\n return MessageType.TEXT\n}\n\n/**\n * 构建消息内容\n */\nexport function buildContent(msg: TelegramMessage): string | null {\n const text = extractText(msg.text)\n\n // Service 消息\n if (msg.type === 'service') {\n const action = msg.action || ''\n const members = msg.members?.join(', ') || ''\n if (members) return `[${action}] ${members}`\n // action 为空时回退到文本,避免常量表达式触发 lint 规则。\n if (action) return `[${action}]`\n return text || null\n }\n\n // 贴纸:使用 emoji 表示\n if (msg.media_type === 'sticker' && msg.sticker_emoji) {\n return text ? `[sticker ${msg.sticker_emoji}] ${text}` : `[sticker ${msg.sticker_emoji}]`\n }\n\n // 媒体消息带文字说明\n if (msg.photo || msg.file || msg.media_type) {\n const mediaLabel = msg.media_type || (msg.photo ? 'photo' : 'file')\n if (text) return `[${mediaLabel}] ${text}`\n return `[${mediaLabel}]`\n }\n\n return text || null\n}\n","/**\n * Telegram 单聊天导出 JSON 格式解析器\n * 适配 Telegram Desktop (Windows) 的「导出聊天记录」→ 单个聊天导出\n *\n * 格式特征:\n * - 单个 JSON 文件只包含一个聊天\n * - 顶层直接是聊天对象:{ name, type, id, messages }\n * - 没有 about / personal_information / chats 等外层包装\n * - 消息结构与全量导出一致(date_unixtime, from_id, text_entities 等)\n * - 支持 personal_chat / bot_chat / private_group / public_channel 等类型\n *\n * 导入流程(直接导入,无需聊天选择器):\n * 1. 用户选择单聊天 JSON 文件 → 格式识别\n * 2. parser 直接读取并解析该文件\n */\n\nimport * as fs from 'fs'\nimport streamChain from 'stream-chain'\nimport streamJson from 'stream-json'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\n\nimport { KNOWN_PLATFORMS, ChatType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress } from '../utils'\nimport { mapChatType, extractPlatformId, detectMessageType, buildContent } from './utils/telegram-utils'\nimport type { TelegramChat } from './utils/telegram-utils'\n\nconst { chain } = streamChain\nconst { parser } = streamJson\nconst { streamValues } = streamValuesModule\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'telegram-native-single',\n name: 'Telegram 单聊天导出 (JSON)',\n platform: KNOWN_PLATFORMS.TELEGRAM,\n priority: 23,\n extensions: ['.json'],\n signatures: {\n // Telegram 单聊天导出特征:文件以 \"name\" 作为第一个 JSON 键\n // 这与全量导出(以 \"about\" 开头)有效区分,避免嵌套字段误匹配\n head: [/^\\s*\\{\\s*\\r?\\n\\s*\"name\"\\s*:/],\n requiredFields: ['messages'],\n fieldPatterns: {\n // Telegram 特有的聊天类型值,精确区分\n telegramChatType:\n /\"type\"\\s*:\\s*\"(personal_chat|bot_chat|private_group|private_supergroup|public_group|public_supergroup|public_channel|private_channel|saved_messages)\"/,\n },\n },\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseTelegramSingle(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let messagesProcessed = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n onLog?.('info', `开始解析 Telegram 单聊天 JSON 文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 流式读取整个文件(顶层即为聊天对象)\n const chatData = await new Promise<TelegramChat | null>((resolve, reject) => {\n const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n\n const pipeline = chain([readStream, parser(), streamValues()])\n\n let found = false\n\n pipeline.on('data', ({ value }: { value: TelegramChat }) => {\n if (!found) {\n found = true\n resolve(value)\n }\n })\n\n pipeline.on('end', () => {\n if (!found) resolve(null)\n })\n\n pipeline.on('error', reject)\n })\n\n if (!chatData) {\n onLog?.('error', '无法解析 Telegram 单聊天文件')\n yield { type: 'error', data: new Error('无法解析 Telegram 单聊天文件') }\n return\n }\n\n onLog?.('info', `聊天: \"${chatData.name}\", 类型: ${chatData.type}, 消息数: ${chatData.messages?.length || 0}`)\n\n // 确定聊天类型\n const chatType = mapChatType(chatData.type)\n\n // 发送 meta\n const meta: ParsedMeta = {\n name: chatData.name || `Telegram Chat ${chatData.id}`,\n platform: KNOWN_PLATFORMS.TELEGRAM,\n type: chatType,\n groupId: chatType === ChatType.GROUP ? String(chatData.id) : undefined,\n }\n yield { type: 'meta', data: meta }\n\n // 收集成员和消息\n const memberMap = new Map<string, ParsedMember>()\n const messageBatch: ParsedMessage[] = []\n const messages = chatData.messages || []\n\n for (const msg of messages) {\n // 提取发送者信息\n let senderPlatformId: string\n let senderName: string\n\n if (msg.type === 'service') {\n // Service 消息使用 actor 信息\n senderPlatformId = extractPlatformId(msg.actor_id)\n senderName = msg.actor || '系统'\n } else {\n senderPlatformId = extractPlatformId(msg.from_id)\n senderName = msg.from || senderPlatformId\n }\n\n // 更新成员\n if (!memberMap.has(senderPlatformId) && senderPlatformId !== 'unknown') {\n memberMap.set(senderPlatformId, {\n platformId: senderPlatformId,\n accountName: senderName,\n })\n }\n\n // 解析时间戳\n const timestamp = parseInt(msg.date_unixtime, 10)\n if (isNaN(timestamp)) continue\n\n // 构建消息\n const parsedMsg: ParsedMessage = {\n platformMessageId: String(msg.id),\n senderPlatformId,\n senderAccountName: senderName,\n timestamp,\n type: detectMessageType(msg),\n content: buildContent(msg),\n replyToMessageId: msg.reply_to_message_id ? String(msg.reply_to_message_id) : undefined,\n }\n\n messageBatch.push(parsedMsg)\n messagesProcessed++\n\n // 分批 yield 消息\n if (messageBatch.length >= batchSize) {\n yield { type: 'messages', data: [...messageBatch] }\n messageBatch.length = 0\n\n const progress = createProgress(\n 'parsing',\n 0,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n yield { type: 'progress', data: progress }\n onProgress?.(progress)\n }\n }\n\n // 发送成员\n yield { type: 'members', data: Array.from(memberMap.values()) }\n\n // 发送剩余消息\n if (messageBatch.length > 0) {\n yield { type: 'messages', data: messageBatch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberMap.size} 个成员, 类型: ${chatType}`)\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount: memberMap.size },\n }\n}\n\n// ==================== 导出 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseTelegramSingle,\n}\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n}\n\nexport default module_\n","/**\n * WhatsApp 官方导出 TXT 格式解析器\n * 适配 WhatsApp 聊天导出功能\n *\n * 格式特征:\n * - 文件头:消息和通话已进行端到端加密 / Messages and calls are end-to-end encrypted\n * - 消息格式(V1,无方括号):YYYY/MM/DD HH:MM - 昵称: 内容\n * - 消息格式(V2,方括号):[时间戳] 昵称: 内容\n * 时间戳由弹性解析器处理,自动适配各地区变体:\n * - 日期顺序:YYYY/MM/DD | DD/MM/YYYY | M/D/YY\n * - AM/PM 标记:24h 无标记 | AM/PM | 上午/下午 | 오전/오후 | 午前/午後\n * - 分隔符:逗号 / 空格 / 特殊空格(U+2009, U+202F)\n * - 系统消息:时间戳 + 系统内容(无冒号分隔)\n * - 媒体占位:<省略影音内容> / 圖片已略去 等\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as readline from 'readline'\nimport { KNOWN_PLATFORMS, ChatType, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress } from '../utils'\n\n// ==================== 辅助函数 ====================\n\n/**\n * 从文件名提取聊天名称\n * 例如:与开心每一天的 WhatsApp 聊天.txt → 开心每一天\n * 例如:与gaoberry37的 WhatsApp 聊天.txt → gaoberry37\n */\nfunction extractNameFromFilePath(filePath: string): string {\n const basename = path.basename(filePath)\n // 简体中文:与xxx的 WhatsApp 聊天.txt\n const matchZhCn = basename.match(/^与(.+?)的\\s*WhatsApp\\s*聊天\\.txt$/i)\n if (matchZhCn) return matchZhCn[1].trim()\n // 繁体中文:與xxx的WhatsApp對話.txt\n const matchZhTw = basename.match(/^與(.+?)的\\s*WhatsApp\\s*對話\\.txt$/i)\n if (matchZhTw) return matchZhTw[1].trim()\n // 兜底:移除扩展名\n return basename.replace(/\\.txt$/i, '') || '未知聊天'\n}\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'whatsapp-native-txt',\n name: 'WhatsApp 官方导出 (TXT)',\n platform: KNOWN_PLATFORMS.WHATSAPP,\n priority: 25,\n extensions: ['.txt'],\n signatures: {\n // WhatsApp 导出文件的特征(中文/英文)\n // 注意:仅保留 WhatsApp 独有的特征,避免误匹配其他 TXT 格式(如 LINE)\n head: [\n /消息和通话已进行端到端加密/, // 简体中文加密提示(WhatsApp 独有)\n /訊息與通話已受端對端加密保護/, // 繁体中文加密提示(WhatsApp 独有)\n /Messages and calls are end-to-end encrypted/i, // 英文加密提示(WhatsApp 独有)\n /你发送给自己的消息已进行端到端加密/, // 简体中文自己对话提示(WhatsApp 独有)\n /\\d{1,4}\\/\\d{1,2}\\/\\d{2,4},?\\s+\\d{1,2}:\\d{2} - /, // 消息行格式特征(无方括号,含 \" - \" 分隔符,WhatsApp 独有)\n /\\[\\d{1,4}\\/\\d{1,2}\\/\\d{2,4}[\\s,].*\\d{1,2}:\\d{2}:\\d{2}.*\\] /, // 消息行格式特征(方括号 + 含日期和时间的时间戳,兼容各地区变体)\n ],\n // 文件名特征:与xxx的 WhatsApp 聊天.txt\n filename: [/^与.+的\\s*WhatsApp\\s*聊天\\.txt$/i, /^與.+的\\s*WhatsApp\\s*對話\\.txt$/i, /WhatsApp/i],\n },\n}\n\n// ==================== 辅助函数:清理不可见字符 ====================\n\n/**\n * 清理行首/行尾的不可见 Unicode 字符\n * WhatsApp 导出文件中可能包含 BOM、Left-to-Right Mark (U+200E) 等\n */\nfunction cleanLine(line: string): string {\n // 移除常见的不可见字符:BOM、LTR Mark、RTL Mark、零宽字符等\n return line.replace(/^(?:\\uFEFF|\\u200E|\\u200F|\\u200B|\\u200C|\\u200D|\\u2060)+/, '').trim()\n}\n\n// ==================== 消息头正则 ====================\n\n// 格式1(无方括号):日期/日期/日期[,] 时:分 - 内容\n// 兼容各地区变体:YYYY/MM/DD HH:MM | DD/MM/YYYY, HH:MM | M/D/YY, HH:MM\nconst MESSAGE_LINE_REGEX_V1 = /^(\\d{1,4}\\/\\d{1,2}\\/\\d{2,4},?\\s+\\d{1,2}:\\d{2}) - (.+)$/\n\n// 格式2(方括号格式):宽松捕获 [时间戳] 后的内容\n// 时间戳的地区变体由 parseFlexibleV2Timestamp() 弹性解析器处理\n// 已知变体包括但不限于:中文 上午/下午、英文 AM/PM、韩文 오전/오후、各种日期顺序\nconst MESSAGE_LINE_REGEX_V2 = /^\\[([^\\]]+)\\] (.+)$/\n\n// 从消息内容中分离昵称和实际内容\n// 格式:昵称: 内容(冒号后可能是空格、U+200E LTR Mark 或两者组合)\nconst SENDER_CONTENT_REGEX = /^(.+?):[\\s\\u200E]+(.*)$/\n\n// ==================== 系统消息识别 ====================\n\nconst SYSTEM_MESSAGE_PATTERNS = [\n // 简体中文系统消息\n /消息和通话已进行端到端加密/,\n /创建了此群组/,\n /加入群组/,\n /添加了/,\n /退出了群组/,\n /移除了/,\n /更改了本群组/,\n /已将此群组的设置更改为/,\n /这条消息已删除/,\n /限时消息功能/,\n /正在等待此消息/,\n // 繁体中文系统消息\n /訊息與通話已受端對端加密保護/,\n /建立了此群組/,\n /加入了群組/,\n /已新增/,\n /已離開群組/,\n /已移除/,\n /已變更本群組/,\n /此訊息已刪除/,\n /限時訊息/,\n // 英文系统消息\n /Messages and calls are end-to-end encrypted/i,\n /created this group/i,\n /joined the group/i,\n /added/i,\n /left the group/i,\n /removed/i,\n /changed this group/i,\n /This message was deleted/i,\n /disappearing messages/i,\n]\n\nfunction isSystemMessage(content: string): boolean {\n return SYSTEM_MESSAGE_PATTERNS.some((pattern) => pattern.test(content))\n}\n\n// ==================== 消息类型判断 ====================\n\nfunction detectMessageType(content: string): MessageType {\n const trimmed = content.trim()\n\n // 媒体消息(简体/繁体多种变体)\n if (\n trimmed === '<省略影音内容>' ||\n trimmed === '<已省略多媒體檔案>' ||\n trimmed === '圖片已略去' ||\n trimmed === '影片已略去' ||\n trimmed === '音訊已略去' ||\n trimmed === 'image omitted' ||\n trimmed === 'video omitted' ||\n trimmed === 'audio omitted'\n )\n return MessageType.IMAGE\n if (trimmed.includes('<已附加:') || trimmed.includes('<附件:') || trimmed.includes('<已附加:'))\n return MessageType.FILE\n\n // 贴图/贴纸(繁体中文导出标记)\n if (trimmed === '貼圖已忽略' || trimmed === '貼圖已略去') return MessageType.EMOJI\n\n // 删除消息(简体/繁体多种变体)\n if (trimmed === '这条消息已删除' || trimmed.startsWith('此訊息已刪除') || trimmed.startsWith('你已刪除此訊息'))\n return MessageType.RECALL\n\n // 系统消息\n if (isSystemMessage(trimmed)) return MessageType.SYSTEM\n\n return MessageType.TEXT\n}\n\n// ==================== 时间解析 ====================\n\n// 多语言 AM/PM 标记映射(值为 true 表示 PM,false 表示 AM)\nconst AMPM_MARKERS: [RegExp, boolean][] = [\n [/\\bPM\\b/i, true],\n [/\\bP\\.M\\.\\b/i, true],\n [/下午/, true],\n [/午後/, true],\n [/오후/, true],\n [/\\bAM\\b/i, false],\n [/\\bA\\.M\\.\\b/i, false],\n [/上午/, false],\n [/午前/, false],\n [/오전/, false],\n]\n\n/**\n * 弹性解析 V2(方括号)时间戳\n * 自动处理各地区变体:日期顺序、AM/PM 多语言标记、特殊空格等\n * @returns 秒级时间戳,解析失败返回 null\n */\nfunction parseFlexibleV2Timestamp(raw: string): number | null {\n // 1. 规范化特殊空格和不可见字符\n let str = raw.replace(/(?:\\u2009|\\u202F|\\uFEFF|\\u200E|\\u200F|\\u200B|\\u200C|\\u200D|\\u2060)/g, ' ').trim()\n\n // 2. 提取并移除 AM/PM 标记(任何语言)\n let isPM: boolean | null = null\n for (const [pattern, pm] of AMPM_MARKERS) {\n if (pattern.test(str)) {\n isPM = pm\n str = str.replace(pattern, '').trim()\n break\n }\n }\n\n // 3. 移除逗号,规范化连续空格\n str = str.replace(/,/g, ' ').replace(/\\s+/g, ' ').trim()\n\n // 4. 提取日期部分(含 /)和时间部分(含 :)\n const match = str.match(/^(\\d{1,4}\\/\\d{1,2}\\/\\d{2,4})\\s+(\\d{1,2}:\\d{2}(?::\\d{2})?)$/)\n if (!match) return null\n\n const [, datePart, timePart] = match\n const dateParts = datePart.split('/').map((s) => parseInt(s, 10))\n const timeParts = timePart.split(':').map((s) => parseInt(s, 10))\n\n // 5. 推断日期顺序\n let year: number, month: number, day: number\n if (dateParts[0] > 31) {\n // 第一段 > 31 → 一定是年份 → YYYY/MM/DD\n year = dateParts[0]\n month = dateParts[1]\n day = dateParts[2]\n } else if (dateParts[2] > 31) {\n // 第三段 > 31 → 一定是年份 → DD/MM/YYYY\n day = dateParts[0]\n month = dateParts[1]\n year = dateParts[2]\n } else {\n // 全部 ≤ 31 → M/D/YY(2 位年份,00-99 → 2000-2099)\n month = dateParts[0]\n day = dateParts[1]\n year = 2000 + dateParts[2]\n }\n\n // 6. 解析时间\n let hour = timeParts[0]\n const minute = timeParts[1]\n const second = timeParts[2] ?? 0\n\n if (isPM === true && hour !== 12) hour += 12\n if (isPM === false && hour === 12) hour = 0\n\n // 7. 构造日期并校验\n const date = new Date(year, month - 1, day, hour, minute, second)\n const ts = Math.floor(date.getTime() / 1000)\n return isNaN(ts) ? null : ts\n}\n\n/**\n * 解析 V1(无方括号)时间格式\n * 支持:YYYY/M/D H:MM | DD/MM/YYYY, HH:MM | M/D/YY, HH:MM\n */\nfunction parseV1Timestamp(timeStr: string): number {\n // 移除逗号,规范化空格\n const str = timeStr.replace(/,/g, '').replace(/\\s+/g, ' ').trim()\n const match = str.match(/^(\\d{1,4})\\/(\\d{1,2})\\/(\\d{2,4})\\s+(\\d{1,2}):(\\d{2})$/)\n if (!match) {\n const normalized = timeStr.replace(/,/g, '').replace(/\\//g, '-').replace(/\\s+/, 'T') + ':00'\n return Math.floor(new Date(normalized).getTime() / 1000)\n }\n\n const parts = [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)]\n const hour = parseInt(match[4], 10)\n const minute = parseInt(match[5], 10)\n\n let year: number, month: number, day: number\n if (parts[0] > 31) {\n year = parts[0]\n month = parts[1]\n day = parts[2]\n } else if (parts[2] > 31) {\n day = parts[0]\n month = parts[1]\n year = parts[2]\n } else {\n month = parts[0]\n day = parts[1]\n year = 2000 + parts[2]\n }\n\n const date = new Date(year, month - 1, day, hour, minute, 0)\n const ts = Math.floor(date.getTime() / 1000)\n return isNaN(ts) ? 0 : ts\n}\n\n// ==================== 成员信息 ====================\n\ninterface MemberInfo {\n platformId: string\n nickname: string\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseWhatsApp(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let bytesRead = 0\n let messagesProcessed = 0\n let skippedLines = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n // 记录解析开始\n onLog?.('info', `开始解析 WhatsApp TXT 文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 收集数据\n const chatName = extractNameFromFilePath(filePath)\n const memberMap = new Map<string, MemberInfo>()\n const messages: ParsedMessage[] = []\n\n // 当前正在解析的消息(可能跨多行)\n let currentMessage: {\n timestamp: number\n sender: string | null // null 表示系统消息\n contentLines: string[]\n } | null = null\n\n // 保存当前消息\n const saveCurrentMessage = () => {\n if (currentMessage) {\n const content = currentMessage.contentLines.join('\\n').trim()\n const type = detectMessageType(content)\n\n // 系统消息使用特殊 ID 和统一名称\n const senderPlatformId = currentMessage.sender || 'system'\n const senderName = currentMessage.sender || '系统消息'\n\n messages.push({\n senderPlatformId,\n senderAccountName: senderName,\n timestamp: currentMessage.timestamp,\n type,\n content: content || null,\n })\n\n // 更新成员信息(跳过系统消息)\n if (currentMessage.sender) {\n memberMap.set(senderPlatformId, {\n platformId: senderPlatformId,\n nickname: senderName,\n })\n }\n\n messagesProcessed++\n }\n }\n\n // 逐行读取文件\n const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n })\n\n fileStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n for await (const line of rl) {\n // 清理行首不可见字符\n const cleanedLine = cleanLine(line)\n\n // 尝试匹配消息行:优先 V1(严格),再 V2(宽松 + 弹性时间解析)\n const v1Match = cleanedLine.match(MESSAGE_LINE_REGEX_V1)\n if (v1Match) {\n saveCurrentMessage()\n const restContent = v1Match[2]\n const senderMatch = restContent.match(SENDER_CONTENT_REGEX)\n if (senderMatch && !isSystemMessage(restContent)) {\n currentMessage = {\n timestamp: parseV1Timestamp(v1Match[1]),\n sender: senderMatch[1].trim(),\n contentLines: [senderMatch[2]],\n }\n } else {\n currentMessage = {\n timestamp: parseV1Timestamp(v1Match[1]),\n sender: null,\n contentLines: [restContent],\n }\n }\n if (messagesProcessed % 500 === 0) {\n onProgress?.(\n createProgress('parsing', bytesRead, totalBytes, messagesProcessed, `已处理 ${messagesProcessed} 条消息...`)\n )\n }\n continue\n }\n\n const v2Match = cleanedLine.match(MESSAGE_LINE_REGEX_V2)\n if (v2Match) {\n const timestamp = parseFlexibleV2Timestamp(v2Match[1])\n if (timestamp !== null) {\n // 弹性解析成功 → 新消息\n saveCurrentMessage()\n const restContent = v2Match[2]\n const senderMatch = restContent.match(SENDER_CONTENT_REGEX)\n if (senderMatch && !isSystemMessage(restContent)) {\n currentMessage = {\n timestamp,\n sender: senderMatch[1].trim(),\n contentLines: [senderMatch[2]],\n }\n } else {\n currentMessage = {\n timestamp,\n sender: null,\n contentLines: [restContent],\n }\n }\n if (messagesProcessed % 500 === 0) {\n onProgress?.(\n createProgress('parsing', bytesRead, totalBytes, messagesProcessed, `已处理 ${messagesProcessed} 条消息...`)\n )\n }\n continue\n }\n // 弹性解析失败 → 方括号内容不是有效时间戳,降级为续行\n }\n\n // 非消息行:可能是多行消息的延续\n if (currentMessage && cleanedLine) {\n currentMessage.contentLines.push(cleanedLine)\n } else if (cleanedLine) {\n // 无法解析的非空行\n skippedLines++\n }\n }\n\n // 保存最后一条消息\n saveCurrentMessage()\n\n // 确定聊天类型:根据参与者数量判断\n // - 排除系统成员后,2 人或更少:私聊\n // - 超过 2 人:群聊\n const hasSystemMember = memberMap.has('system')\n const realMemberCount = hasSystemMember ? memberMap.size - 1 : memberMap.size\n\n const chatType = realMemberCount > 2 ? ChatType.GROUP : ChatType.PRIVATE\n\n // 发送 meta\n const meta: ParsedMeta = {\n name: chatName,\n platform: KNOWN_PLATFORMS.WHATSAPP,\n type: chatType,\n }\n yield { type: 'meta', data: meta }\n\n // 发送成员\n const members: ParsedMember[] = Array.from(memberMap.values()).map((m) => ({\n platformId: m.platformId,\n accountName: m.nickname,\n }))\n yield { type: 'members', data: members }\n\n // 分批发送消息\n for (let i = 0; i < messages.length; i += batchSize) {\n const batch = messages.slice(i, i + batchSize)\n yield { type: 'messages', data: batch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n // 统计消息类型\n const typeCounts = new Map<MessageType, number>()\n for (const msg of messages) {\n typeCounts.set(msg.type, (typeCounts.get(msg.type) || 0) + 1)\n }\n\n // 记录解析摘要\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberMap.size} 个成员, 类型: ${chatType}`)\n onLog?.(\n 'info',\n `消息类型统计: ${Array.from(typeCounts.entries())\n .map(([type, count]) => `${type}=${count}`)\n .join(', ')}`\n )\n if (skippedLines > 0) {\n onLog?.('info', `跳过 ${skippedLines} 行无法解析的内容`)\n }\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount: memberMap.size },\n }\n}\n\n// ==================== 导出解析器 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseWhatsApp,\n}\n\n// ==================== 导出格式模块 ====================\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n // TXT 格式不需要预处理器\n}\n\nexport default module_\n","/**\n * QQ 官方导出 TXT 格式解析器\n * 适配 QQ 群聊旧版导出功能\n *\n * 格式特征:\n * - 文件头:消息记录(此消息记录为文本格式,不支持重新导入)\n * - 群名:消息对象:xxx\n * - 消息格式:时间 昵称(QQ号) 或 时间 昵称<邮箱>\n * - 内容在消息头下一行,可能跨多行\n *\n * 字段映射:\n * - name: platformId(用户ID)\n * - nickname: 群昵称\n * - senderName: 群昵称(用于昵称历史追踪)\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as readline from 'readline'\nimport { KNOWN_PLATFORMS, ChatType, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress } from '../utils'\n\n// ==================== 辅助函数 ====================\n\n/**\n * 从文件名提取群名\n */\nfunction extractNameFromFilePath(filePath: string): string {\n const basename = path.basename(filePath)\n const name = basename.replace(/\\.txt$/i, '')\n return name || '未知群聊'\n}\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'qq-native-txt',\n name: 'QQ 官方导出 (TXT)',\n platform: KNOWN_PLATFORMS.QQ,\n priority: 30,\n extensions: ['.txt'],\n signatures: {\n // 支持群聊导出和多人聊天(讨论组)导出\n head: [/消息记录(此消息记录为文本格式/, /消息对象:/, /多人聊天/],\n },\n}\n\n// ==================== 消息头正则 ====================\n\n// 群聊格式:2019-07-16 18:15:05 地瓜(23333233)\n// 邮箱格式:2019-07-16 18:15:11 土豆<example@xx.com>\n// 讨论组格式:2017-08-29 20:28:30 番茄(没有 ID,只有昵称)\nconst MESSAGE_HEADER_REGEX = /^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) (.+?)(?:\\(([^)]+)\\)|<([^>]+)>)?$/\n\n// 匹配群名:消息对象:xxx\nconst GROUP_NAME_REGEX = /^消息对象:(.+)$/\n\n// ==================== 消息类型判断 ====================\n\nfunction detectMessageType(content: string): MessageType {\n const trimmed = content.trim()\n\n // 基础消息类型\n if (trimmed === '[图片]') return MessageType.IMAGE\n if (trimmed === '[表情]') return MessageType.EMOJI\n if (trimmed === '[语音]') return MessageType.VOICE\n if (trimmed === '[视频]') return MessageType.VIDEO\n if (trimmed === '[文件]') return MessageType.FILE\n if (trimmed === '[位置]' || trimmed === '[地理位置]') return MessageType.LOCATION\n if (trimmed === '[链接]' || trimmed === '[卡片消息]') return MessageType.LINK\n\n // 交互消息类型\n if (trimmed === '[红包]' || trimmed.includes('发出了红包')) return MessageType.RED_PACKET\n if (trimmed === '[转账]' || trimmed.includes('向你转账')) return MessageType.TRANSFER\n if (trimmed.includes('拍了拍') || trimmed === '[拍一拍]') return MessageType.POKE\n if (trimmed === '[语音通话]' || trimmed === '[视频通话]' || trimmed.includes('通话时长')) return MessageType.CALL\n if (trimmed === '[分享]' || trimmed === '[音乐]' || trimmed === '[小程序]') return MessageType.SHARE\n if (trimmed.startsWith('[回复]')) return MessageType.REPLY\n if (trimmed === '[转发]' || trimmed === '[聊天记录]') return MessageType.FORWARD\n\n // 系统消息类型\n if (trimmed.includes('撤回了一条消息') || trimmed === '[撤回]') return MessageType.RECALL\n if (\n trimmed.includes('加入了群聊') ||\n trimmed.includes('退出了群聊') ||\n trimmed.includes('被移出群聊') ||\n trimmed.includes('修改了群名称') ||\n trimmed.includes('成为新群主') ||\n trimmed.includes('群公告')\n ) {\n return MessageType.SYSTEM\n }\n\n // 其他方括号包裹的特殊消息\n if (trimmed.startsWith('[') && trimmed.endsWith(']')) return MessageType.OTHER\n\n return MessageType.TEXT\n}\n\n// ==================== 时间解析 ====================\n\n/**\n * 解析本地时间字符串为秒级时间戳\n * @param timeStr 格式:2019-07-16 18:15:05\n */\nfunction parseLocalTime(timeStr: string): number {\n // 直接用 Date 解析,会按本地时区处理\n const date = new Date(timeStr.replace(' ', 'T'))\n return Math.floor(date.getTime() / 1000)\n}\n\n// ==================== 昵称清理 ====================\n\n/**\n * 清理昵称中的前缀污染\n * 例如:【管理员】张三 -> 张三\n */\nfunction cleanNickname(nickname: string): string {\n // 移除开头的【xxx】前缀(可能有多个)\n return nickname.replace(/^(【[^】]*】\\s*)+/, '').trim()\n}\n\n// ==================== 成员信息 ====================\n\ninterface MemberInfo {\n platformId: string\n nickname: string // 群昵称\n}\n\n// 用于记录用户最近的有效昵称(昵称 != ID 的情况)\nconst lastValidNickname = new Map<string, string>()\n\n// ==================== 解析器实现 ====================\n\nasync function* parseTxt(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let bytesRead = 0\n let messagesProcessed = 0\n let skippedLines = 0 // 跳过的无效行计数\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n // 记录解析开始\n onLog?.('info', `开始解析 QQ TXT 文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 收集数据\n let groupName = '未知群聊'\n const memberMap = new Map<string, MemberInfo>()\n const messages: ParsedMessage[] = []\n\n // 当前正在解析的消息\n let currentMessage: {\n timestamp: number\n platformId: string\n nickname: string\n contentLines: string[]\n } | null = null\n\n // 保存当前消息\n const saveCurrentMessage = () => {\n if (currentMessage) {\n const content = currentMessage.contentLines.join('\\n').trim()\n const type = detectMessageType(content)\n\n messages.push({\n senderPlatformId: currentMessage.platformId,\n senderAccountName: currentMessage.nickname, // QQ TXT 格式只有一个昵称,作为账号名称追踪历史\n // 不设置 senderGroupNickname,避免同一昵称被重复追踪\n timestamp: currentMessage.timestamp,\n type,\n content: content || null,\n })\n\n // 更新成员信息(保留最新昵称)\n memberMap.set(currentMessage.platformId, {\n platformId: currentMessage.platformId,\n nickname: currentMessage.nickname,\n })\n\n messagesProcessed++\n }\n }\n\n // 逐行读取文件\n const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' })\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n })\n\n fileStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n for await (const line of rl) {\n // 检查群名\n const groupMatch = line.match(GROUP_NAME_REGEX)\n if (groupMatch) {\n groupName = groupMatch[1].trim()\n continue\n }\n\n // 检查消息头\n const headerMatch = line.match(MESSAGE_HEADER_REGEX)\n if (headerMatch) {\n // 保存前一条消息\n saveCurrentMessage()\n\n const timeStr = headerMatch[1]\n const rawNickname = headerMatch[2].trim()\n let nickname = cleanNickname(rawNickname) // 清理前缀污染\n // platformId: (id) 或 <email>,如果没有则使用昵称(讨论组格式)\n const platformId = headerMatch[3] || headerMatch[4] || nickname\n\n // 如果昵称和 ID 相同,可能是系统故障,使用之前记录的昵称\n if (nickname === platformId && headerMatch[3]) {\n // 只有当确实有 ID 时才检查昵称覆盖\n const previousNickname = lastValidNickname.get(platformId)\n if (previousNickname) {\n nickname = previousNickname\n }\n // 如果没有之前的记录,保持使用 ID 作为昵称\n } else if (headerMatch[3] || headerMatch[4]) {\n // 记录有效昵称(有 ID 且昵称 != ID)\n lastValidNickname.set(platformId, nickname)\n }\n\n currentMessage = {\n timestamp: parseLocalTime(timeStr),\n platformId,\n nickname,\n contentLines: [],\n }\n\n // 更新进度\n if (messagesProcessed % 1000 === 0) {\n const progress = createProgress(\n 'parsing',\n bytesRead,\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n onProgress?.(progress)\n }\n\n continue\n }\n\n // 内容行(追加到当前消息)\n if (currentMessage) {\n // 跳过分隔线\n if (line.startsWith('=====')) continue\n // 跳过文件头信息\n if (line.startsWith('消息记录') || line.startsWith('消息分组')) continue\n\n currentMessage.contentLines.push(line)\n } else {\n // 没有当前消息时,检查是否是需要跳过的行\n const trimmed = line.trim()\n if (\n trimmed &&\n !trimmed.startsWith('=====') &&\n !trimmed.startsWith('消息记录') &&\n !trimmed.startsWith('消息分组')\n ) {\n // 这是一个无法解析的非空行\n skippedLines++\n }\n }\n }\n\n // 保存最后一条消息\n saveCurrentMessage()\n\n // 发送 meta(如果群名仍是默认值,使用文件名作为后备)\n const meta: ParsedMeta = {\n name: groupName === '未知群聊' ? extractNameFromFilePath(filePath) : groupName,\n platform: KNOWN_PLATFORMS.QQ,\n type: ChatType.GROUP,\n }\n yield { type: 'meta', data: meta }\n\n // 发送成员(QQ TXT 格式只有一个昵称,只设置 accountName 避免重复追踪)\n const members: ParsedMember[] = Array.from(memberMap.values()).map((m) => ({\n platformId: m.platformId,\n accountName: m.nickname, // QQ TXT 格式只有昵称,作为账号名称\n // 不设置 groupNickname,避免同一昵称被重复追踪\n }))\n yield { type: 'members', data: members }\n\n // 分批发送消息\n for (let i = 0; i < messages.length; i += batchSize) {\n const batch = messages.slice(i, i + batchSize)\n yield { type: 'messages', data: batch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n // 记录解析摘要\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberMap.size} 个成员`)\n if (skippedLines > 0) {\n onLog?.('info', `跳过 ${skippedLines} 行无法解析的内容`)\n }\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount: memberMap.size },\n }\n}\n\n// ==================== 导出解析器 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseTxt,\n}\n\n// ==================== 导出格式模块 ====================\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n // TXT 格式不需要预处理器\n}\n\nexport default module_\n","/**\n * Instagram 官方导出格式解析器\n * 适配:Instagram 账号数据下载功能导出的 JSON 文件\n *\n * 文件结构:\n * - participants: 参与者数组 [{ name: string }]\n * - messages: 消息数组(逆序,最新在前)\n * - title: 对话标题(群名或对方用户名)\n * - thread_path: 线程路径,如 \"inbox/xxx_123456\"\n * - joinable_mode: 仅群聊有,包含入群链接\n *\n * 特殊处理:\n * - 编码问题:Instagram 将 UTF-8 字节按 Latin-1 编码后存储,需要解码\n * - 消息逆序:需要反转为正序\n * - 无用户 ID:使用用户名作为 platformId\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { KNOWN_PLATFORMS, ChatType, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress } from '../utils'\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'instagram-native',\n name: 'Instagram 官方导出',\n platform: KNOWN_PLATFORMS.INSTAGRAM,\n priority: 25,\n extensions: ['.json'],\n signatures: {\n // 使用 Instagram 特有的字段作为签名(在文件头部就能匹配)\n // is_geoblocked_for_viewer 是 Instagram 消息特有的字段\n requiredFields: ['participants', 'messages'],\n head: [/\"is_geoblocked_for_viewer\"\\s*:/],\n },\n}\n\n// ==================== 类型定义 ====================\n\ninterface InstagramParticipant {\n name: string\n}\n\ninterface InstagramPhoto {\n uri: string\n creation_timestamp?: number\n}\n\ninterface InstagramVideo {\n uri: string\n creation_timestamp?: number\n}\n\ninterface InstagramAudio {\n uri: string\n creation_timestamp?: number\n}\n\ninterface InstagramShare {\n link?: string\n share_text?: string\n original_content_owner?: string\n}\n\ninterface InstagramReaction {\n reaction: string\n actor: string\n}\n\ninterface InstagramMessage {\n sender_name: string\n timestamp_ms: number\n content?: string\n photos?: InstagramPhoto[]\n videos?: InstagramVideo[]\n audio_files?: InstagramAudio[]\n share?: InstagramShare\n reactions?: InstagramReaction[]\n is_geoblocked_for_viewer?: boolean\n is_unsent_image_by_messenger_kid_parent?: boolean\n}\n\ninterface InstagramData {\n participants: InstagramParticipant[]\n messages: InstagramMessage[]\n title: string\n is_still_participant?: boolean\n thread_path: string\n magic_words?: unknown[]\n joinable_mode?: {\n mode: number\n link: string\n }\n}\n\n// ==================== 辅助函数 ====================\n\n/**\n * 解码 Instagram 特殊编码的文本\n * Instagram 将 UTF-8 字节按 Latin-1 编码后存储\n */\nfunction decodeInstagramText(text: string): string {\n try {\n // 将每个字符的 charCode 收集为字节数组\n const bytes = new Uint8Array(text.length)\n for (let i = 0; i < text.length; i++) {\n bytes[i] = text.charCodeAt(i)\n }\n // 用 UTF-8 解码\n return new TextDecoder('utf-8').decode(bytes)\n } catch {\n return text // 解码失败则返回原文\n }\n}\n\n/**\n * 从文件名提取名称(备用)\n */\nfunction extractNameFromFilePath(filePath: string): string {\n const basename = path.basename(filePath)\n return basename.replace(/\\.json$/i, '') || '未知对话'\n}\n\n/**\n * 判断是否为系统消息\n */\nfunction isSystemMessage(content: string): boolean {\n const systemPatterns = [\n 'You created the group',\n 'created the group',\n 'added',\n 'to the group',\n 'left the group',\n 'removed',\n 'named the group',\n 'changed the group photo',\n 'Reacted',\n 'sent an attachment',\n 'liked a message',\n 'changed the theme',\n 'set the nickname',\n ]\n return systemPatterns.some((p) => content.includes(p))\n}\n\n/**\n * 判断消息类型\n */\nfunction detectMessageType(msg: InstagramMessage): MessageType {\n const content = msg.content || ''\n\n // 1. 系统消息判断\n if (content && isSystemMessage(content)) {\n return MessageType.SYSTEM\n }\n\n // 2. 媒体消息\n if (msg.photos?.length) return MessageType.IMAGE\n if (msg.videos?.length) return MessageType.VIDEO\n if (msg.audio_files?.length) return MessageType.VOICE\n\n // 3. 分享消息\n if (msg.share) {\n const link = msg.share.link || ''\n if (link.includes('giphy.com')) return MessageType.EMOJI\n return MessageType.LINK\n }\n\n // 4. 文本消息\n if (content) return MessageType.TEXT\n\n // 5. 空消息(位置分享、通话等已删除的消息)\n return MessageType.OTHER\n}\n\n/**\n * 获取消息内容\n */\nfunction getMessageContent(msg: InstagramMessage): string | null {\n // 文本内容\n if (msg.content) {\n return decodeInstagramText(msg.content)\n }\n\n // 图片\n if (msg.photos?.length) {\n return `[图片] ${msg.photos[0].uri}`\n }\n\n // 视频\n if (msg.videos?.length) {\n return `[视频] ${msg.videos[0].uri}`\n }\n\n // 语音\n if (msg.audio_files?.length) {\n return `[语音] ${msg.audio_files[0].uri}`\n }\n\n // 分享\n if (msg.share) {\n const link = msg.share.link || ''\n if (link.includes('giphy.com')) {\n return `[GIF] ${link}`\n }\n return `[链接] ${link}`\n }\n\n // 空消息\n return '[未知消息]'\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseInstagram(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let messagesProcessed = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '正在解析 Instagram 聊天记录...')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n onLog?.('info', `开始解析 Instagram 聊天记录,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 读取并解析 JSON 文件\n let data: InstagramData\n try {\n const content = fs.readFileSync(filePath, 'utf-8')\n data = JSON.parse(content)\n } catch (error) {\n const err = new Error(`无法解析 Instagram JSON 文件: ${error}`)\n yield { type: 'error', data: err }\n return\n }\n\n // 判断聊天类型\n const isGroup = data.participants.length > 2 || !!data.joinable_mode\n const chatType = isGroup ? ChatType.GROUP : ChatType.PRIVATE\n\n // 判断 Owner\n let ownerId: string | undefined\n if (chatType === ChatType.PRIVATE) {\n // 私聊:title 是对方名字,另一个参与者是 owner\n const owner = data.participants.find((p) => decodeInstagramText(p.name) !== decodeInstagramText(data.title))\n ownerId = owner ? decodeInstagramText(owner.name) : undefined\n } else {\n // 群聊:找 \"You created the group.\" 消息的发送者\n const createMsg = data.messages.find((m) => m.content === 'You created the group.')\n ownerId = createMsg ? decodeInstagramText(createMsg.sender_name) : undefined\n }\n\n // 发送 meta\n const meta: ParsedMeta = {\n name: decodeInstagramText(data.title) || extractNameFromFilePath(filePath),\n platform: KNOWN_PLATFORMS.INSTAGRAM,\n type: chatType,\n ownerId,\n }\n yield { type: 'meta', data: meta }\n\n // 收集成员信息\n const memberMap = new Map<string, ParsedMember>()\n for (const participant of data.participants) {\n const name = decodeInstagramText(participant.name)\n memberMap.set(name, {\n platformId: name,\n accountName: name,\n })\n }\n\n // 发送成员\n const members = Array.from(memberMap.values())\n yield { type: 'members', data: members }\n\n // 处理消息(Instagram 消息是逆序的,需要反转)\n const reversedMessages = [...data.messages].reverse()\n const messageBatch: ParsedMessage[] = []\n\n for (const msg of reversedMessages) {\n const senderName = decodeInstagramText(msg.sender_name)\n const timestamp = Math.floor(msg.timestamp_ms / 1000) // 毫秒转秒\n const type = detectMessageType(msg)\n const content = getMessageContent(msg)\n\n // 确保成员存在(处理消息中出现但不在 participants 中的情况)\n if (!memberMap.has(senderName)) {\n memberMap.set(senderName, {\n platformId: senderName,\n accountName: senderName,\n })\n }\n\n messageBatch.push({\n senderPlatformId: senderName,\n senderAccountName: senderName,\n timestamp,\n type,\n content,\n })\n\n messagesProcessed++\n\n // 分批输出消息\n if (messageBatch.length >= batchSize) {\n yield { type: 'messages', data: [...messageBatch] }\n messageBatch.length = 0\n\n const progress = createProgress(\n 'parsing',\n Math.floor((messagesProcessed / reversedMessages.length) * totalBytes),\n totalBytes,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n onProgress?.(progress)\n }\n }\n\n // 发送剩余消息\n if (messageBatch.length > 0) {\n yield { type: 'messages', data: messageBatch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '解析完成')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberMap.size} 个成员`)\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount: memberMap.size },\n }\n}\n\n// ==================== 导出 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseInstagram,\n}\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n}\n\nexport default module_\n","/**\n * Google Chat Takeout 内部导入格式解析器。\n *\n * ZIP 读取与会话选择由 node-runtime 负责;本解析器只读取其生成的固定 manifest\n * 和同目录 JSON 文件,从而复用现有 streaming importer。\n */\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport streamChain from 'stream-chain'\nimport streamJson from 'stream-json'\nimport pickModule from 'stream-json/filters/Pick.js'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\nimport { ChatType, KNOWN_PLATFORMS, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n ParseEvent,\n ParseOptions,\n ParsedMember,\n ParsedMessage,\n Parser,\n} from '../types'\nimport { createProgress, getFileSize } from '../utils'\n\nconst { chain } = streamChain\nconst { parser } = streamJson\nconst { pick } = pickModule\nconst { streamValues } = streamValuesModule\n\ninterface GoogleChatManifest {\n format: string\n version: number\n chatId: string\n chatType: 'private' | 'group'\n chatName?: string\n userInfoFile: string\n groupInfoFile: string\n messagesFile: string\n}\n\ninterface GoogleChatUser {\n email?: string\n name?: string\n user_type?: string\n}\n\ninterface GoogleChatUserInfo {\n user?: GoogleChatUser\n}\n\ninterface GoogleChatGroupInfo {\n name?: string\n members?: GoogleChatUser[]\n}\n\ninterface GoogleChatAttachment {\n original_name?: string\n export_name?: string\n}\n\ninterface GoogleChatQuotedMessage {\n creator?: GoogleChatUser\n text?: string\n}\n\ninterface GoogleChatMessage {\n message_id?: string\n created_date?: string\n updated_date?: string\n creator?: GoogleChatUser\n text?: string\n attached_files?: GoogleChatAttachment[]\n quoted_message_metadata?: GoogleChatQuotedMessage\n}\n\nconst ENGLISH_MONTHS = new Map([\n ['january', 0],\n ['february', 1],\n ['march', 2],\n ['april', 3],\n ['may', 4],\n ['june', 5],\n ['july', 6],\n ['august', 7],\n ['september', 8],\n ['october', 9],\n ['november', 10],\n ['december', 11],\n])\n\nexport const feature: FormatFeature = {\n id: 'google-chat-takeout',\n name: 'Google Chat Takeout',\n platform: KNOWN_PLATFORMS.GOOGLE_CHAT,\n priority: 24,\n extensions: ['.json'],\n signatures: {\n head: [/\"format\"\\s*:\\s*\"chatlab-google-chat-takeout\"/],\n requiredFields: ['format', 'version', 'messagesFile'],\n },\n}\n\nfunction normalizeSpaces(value: string): string {\n return value\n .replace(/[\\u00a0\\u202f]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction createUtcTimestamp(\n year: number,\n monthIndex: number,\n day: number,\n hour: number,\n minute: number,\n second: number\n): number | null {\n const timestampMs = Date.UTC(year, monthIndex, day, hour, minute, second)\n const date = new Date(timestampMs)\n if (\n date.getUTCFullYear() !== year ||\n date.getUTCMonth() !== monthIndex ||\n date.getUTCDate() !== day ||\n date.getUTCHours() !== hour ||\n date.getUTCMinutes() !== minute ||\n date.getUTCSeconds() !== second\n ) {\n return null\n }\n return Math.floor(timestampMs / 1000)\n}\n\nfunction parseChineseUtcDate(value: string): number | null {\n const match = normalizeSpaces(value).match(\n /^(\\d{4})年(\\d{1,2})月(\\d{1,2})日(?:星期[一二三四五六日天])?\\s+UTC\\s+(\\d{1,2}):(\\d{2}):(\\d{2})$/\n )\n if (!match) return null\n return createUtcTimestamp(\n Number(match[1]),\n Number(match[2]) - 1,\n Number(match[3]),\n Number(match[4]),\n Number(match[5]),\n Number(match[6])\n )\n}\n\nfunction parseEnglishUtcDate(value: string): number | null {\n const match = normalizeSpaces(value).match(\n /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\\s+([A-Za-z]+)\\s+(\\d{1,2}),\\s+(\\d{4})\\s+at\\s+(\\d{1,2}):(\\d{2}):(\\d{2})\\s+(AM|PM)\\s+UTC$/i\n )\n if (!match) return null\n\n const monthIndex = ENGLISH_MONTHS.get(match[1].toLowerCase())\n if (monthIndex === undefined) return null\n\n let hour = Number(match[4])\n if (hour < 1 || hour > 12) return null\n if (match[7].toUpperCase() === 'AM') {\n if (hour === 12) hour = 0\n } else if (hour !== 12) {\n hour += 12\n }\n\n return createUtcTimestamp(Number(match[3]), monthIndex, Number(match[2]), hour, Number(match[5]), Number(match[6]))\n}\n\n/**\n * Google Takeout 会根据导出语言生成本地化日期文本。这里只接受已验证的\n * 中文和英文 UTC 形式,避免宿主机 locale 或 Date.parse 产生不稳定结果。\n */\nexport function parseGoogleChatDate(value: string): number | null {\n return parseChineseUtcDate(value) ?? parseEnglishUtcDate(value)\n}\n\nfunction normalizeIdentity(user: GoogleChatUser | undefined): { platformId: string; name: string } {\n const email = user?.email?.trim().toLowerCase()\n const name = user?.name?.trim() || email || 'Unknown'\n return {\n platformId: email || name.toLowerCase(),\n name,\n }\n}\n\nfunction readJson<T>(filePath: string): T {\n return JSON.parse(fs.readFileSync(filePath, 'utf8')) as T\n}\n\nfunction resolveManifestFile(manifestDir: string, relativePath: string): string {\n const resolved = path.resolve(manifestDir, relativePath)\n const relative = path.relative(manifestDir, resolved)\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error(`Google Chat manifest path escapes its directory: ${relativePath}`)\n }\n return resolved\n}\n\nfunction readManifest(filePath: string): {\n manifest: GoogleChatManifest\n userInfoPath: string\n groupInfoPath: string\n messagesPath: string\n} {\n const manifest = readJson<GoogleChatManifest>(filePath)\n if (manifest.format !== 'chatlab-google-chat-takeout' || manifest.version !== 1) {\n throw new Error('Invalid Google Chat import manifest')\n }\n if (manifest.chatType !== 'private' && manifest.chatType !== 'group') {\n throw new Error('Invalid Google Chat chat type')\n }\n\n const manifestDir = path.dirname(filePath)\n return {\n manifest,\n userInfoPath: resolveManifestFile(manifestDir, manifest.userInfoFile),\n groupInfoPath: resolveManifestFile(manifestDir, manifest.groupInfoFile),\n messagesPath: resolveManifestFile(manifestDir, manifest.messagesFile),\n }\n}\n\nfunction getAttachmentName(attachment: GoogleChatAttachment): string | null {\n return attachment.original_name?.trim() || attachment.export_name?.trim() || null\n}\n\nfunction buildQuotePrefix(message: GoogleChatMessage): string | null {\n const quote = message.quoted_message_metadata\n const quoteText = quote?.text?.trim()\n if (!quoteText) return null\n const creator = quote?.creator?.name?.trim() || quote?.creator?.email?.trim()\n return creator ? `> ${creator}: ${quoteText}` : `> ${quoteText}`\n}\n\nfunction buildMessageContent(message: GoogleChatMessage): string {\n const parts: string[] = []\n const quote = buildQuotePrefix(message)\n if (quote) parts.push(quote)\n\n const text = message.text?.trim()\n if (text) parts.push(text)\n\n for (const attachment of message.attached_files ?? []) {\n const name = getAttachmentName(attachment)\n if (name) parts.push(`[附件] ${name}`)\n }\n\n return parts.length > 0 ? parts.join('\\n') : '[不支持的 Google Chat 消息]'\n}\n\nfunction detectMessageType(message: GoogleChatMessage): MessageType {\n if (message.text?.trim()) return MessageType.TEXT\n if ((message.attached_files ?? []).some((attachment) => getAttachmentName(attachment))) {\n return MessageType.FILE\n }\n return MessageType.OTHER\n}\n\nfunction deriveChatName(\n manifest: GoogleChatManifest,\n groupInfo: GoogleChatGroupInfo,\n ownerId: string | undefined\n): string {\n if (manifest.chatName?.trim()) return manifest.chatName.trim()\n if (manifest.chatType === 'group' && groupInfo.name?.trim()) return groupInfo.name.trim()\n\n const otherMember = (groupInfo.members ?? [])\n .map(normalizeIdentity)\n .find((member) => !ownerId || member.platformId !== ownerId)\n return (\n otherMember?.name ||\n groupInfo.members?.map((member) => normalizeIdentity(member).name).join(', ') ||\n manifest.chatId\n )\n}\n\nasync function* parseGoogleChat(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n const { manifest, userInfoPath, groupInfoPath, messagesPath } = readManifest(filePath)\n const userInfo = readJson<GoogleChatUserInfo>(userInfoPath)\n const groupInfo = readJson<GoogleChatGroupInfo>(groupInfoPath)\n const owner = userInfo.user ? normalizeIdentity(userInfo.user) : null\n const totalBytes = getFileSize(messagesPath)\n\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n yield {\n type: 'meta',\n data: {\n name: deriveChatName(manifest, groupInfo, owner?.platformId),\n platform: KNOWN_PLATFORMS.GOOGLE_CHAT,\n type: manifest.chatType === 'group' ? ChatType.GROUP : ChatType.PRIVATE,\n ...(manifest.chatType === 'group' ? { groupId: manifest.chatId } : {}),\n ownerId: owner?.platformId,\n },\n }\n\n const memberMap = new Map<string, ParsedMember>()\n for (const member of groupInfo.members ?? []) {\n const identity = normalizeIdentity(member)\n memberMap.set(identity.platformId, {\n platformId: identity.platformId,\n accountName: identity.name,\n })\n }\n if (owner && !memberMap.has(owner.platformId)) {\n memberMap.set(owner.platformId, {\n platformId: owner.platformId,\n accountName: owner.name,\n })\n }\n yield { type: 'members', data: Array.from(memberMap.values()) }\n\n const readStream = fs.createReadStream(messagesPath, { encoding: 'utf8' })\n let bytesRead = 0\n let messagesProcessed = 0\n readStream.on('data', (chunk: string | Buffer) => {\n bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length\n })\n\n const pipeline = chain([readStream, parser(), pick({ filter: /^messages\\.\\d+$/ }), streamValues()])\n const messageBatch: ParsedMessage[] = []\n\n try {\n for await (const item of pipeline as AsyncIterable<{ value: GoogleChatMessage }>) {\n const message = item.value\n const sender = normalizeIdentity(message.creator)\n messageBatch.push({\n platformMessageId: message.message_id ? String(message.message_id) : undefined,\n senderPlatformId: sender.platformId,\n senderAccountName: sender.name,\n timestamp: parseGoogleChatDate(message.created_date ?? message.updated_date ?? '') ?? Number.NaN,\n type: detectMessageType(message),\n content: buildMessageContent(message),\n })\n messagesProcessed++\n\n if (messageBatch.length >= batchSize) {\n yield { type: 'messages', data: messageBatch.splice(0) }\n const progress = createProgress('parsing', bytesRead, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: progress }\n onProgress?.(progress)\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n onLog?.('error', `Failed to parse Google Chat messages: ${message}`)\n yield { type: 'error', data: new Error(`Failed to parse Google Chat messages: ${message}`) }\n return\n }\n\n if (messageBatch.length > 0) {\n yield { type: 'messages', data: messageBatch }\n }\n\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n yield { type: 'done', data: { messageCount: messagesProcessed, memberCount: memberMap.size } }\n}\n\nexport const parser_: Parser = {\n feature,\n parse: parseGoogleChat,\n}\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n}\n\nexport default module_\n","/**\n * LINE 官方导出 TXT 格式解析器\n * 支持私聊和群聊,支持多语言导出(EN / ZH-CN / ZH-TW / JA)\n *\n * 格式特征:\n * - 头部格式(私聊和群聊相同):\n * Line 1: [LINE] {name}的聊天记录 / Chat history with/in {name} / ...\n * Line 2: 保存日期: YYYY/MM/DD HH:MM / Saved on: ...\n * Line 3: (空行)\n * - 日期行:YYYY/MM/DD(星期)或 Day, MM/DD/YYYY\n * - 消息格式:TIME\\t{sender}\\t{content}(Tab 分隔)\n * - 系统消息:TIME\\t\\t{content}(双 Tab,无发送者)\n * - 时间格式:HH:MM / 上午|下午HH:MM / 午前|午後HH:MM / HH:MMam|pm\n * - 多行消息:用双引号包裹\n *\n * 私聊 vs 群聊区分:\n * - EN: \"Chat history with {name}\" (私聊) vs \"Chat history in {name}\" (群聊)\n * - JA: \"{name}とのトーク履歴\" (私聊) vs \"{name}のトーク履歴\" (群聊)\n * - ZH-CN: \"与{name}的聊天记录\" (私聊) vs \"{name}的聊天记录\" (群聊)\n * - ZH-TW: \"與{name}的聊天記錄\" (私聊) vs \"{name}的聊天記錄\" (群聊)\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { KNOWN_PLATFORMS, ChatType, MessageType } from '@openchatlab/shared-types'\nimport type {\n FormatFeature,\n FormatModule,\n Parser,\n ParseOptions,\n ParseEvent,\n ParsedMeta,\n ParsedMember,\n ParsedMessage,\n} from '../types'\nimport { getFileSize, createProgress } from '../utils'\n\n// ==================== 特征定义 ====================\n\nexport const feature: FormatFeature = {\n id: 'line-native-txt',\n name: 'LINE 官方导出 TXT',\n platform: KNOWN_PLATFORMS.LINE,\n priority: 35,\n extensions: ['.txt'],\n signatures: {\n head: [\n // 头部标识(多语言)\n /^\\[LINE\\] /m,\n /^(?:\\[LINE\\] )?Chat history (?:with|in) /m,\n // Tab 分隔的消息格式(支持多种时间格式)\n /^((?:上午|下午|午前|午後)?\\d{1,2}:\\d{2}(?:[AaPp][Mm])?)\\t[^\\t\\n]+\\t/m,\n // 空格分隔的消息格式(部分 LINE 导出)\n /^((?:上午|下午|午前|午後)?\\d{1,2}:\\d{2}(?:[AaPp][Mm])?) [^\\s]+ /m,\n // LINE 独有的日期行格式:YYYY.MM.DD DayOfWeek(英文星期全称)\n /^\\d{4}\\.\\d{2}\\.\\d{2}\\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/m,\n // LINE 日文/中文日期行格式:YYYY/M/D(曜日)\n /^\\d{4}\\/\\d{1,2}\\/\\d{1,2}[((][月火水木金土日]/m,\n // LINE 中文日期行格式:YYYY/M/D周X\n /^\\d{4}\\/\\d{1,2}\\/\\d{1,2}周/m,\n ],\n // 文件名特征:[LINE] 出现在文件名中\n filename: [/\\[LINE\\]/i],\n },\n}\n\n// ==================== 辅助函数 ====================\n\n/**\n * 从文件名提取聊天名称\n */\nfunction extractNameFromFilePath(filePath: string): string {\n const basename = path.basename(filePath, '.txt')\n // 移除 [LINE] 前缀\n const name = basename.replace(/^\\[LINE\\]\\s*/i, '').trim()\n return name || '未知聊天'\n}\n\n/**\n * 从头部提取聊天名称和类型\n * 支持:英文、日文、简体中文、繁体中文\n */\nfunction extractNameFromHeader(header: string): { name: string; isGroup: boolean } | null {\n // ===== 英文 =====\n // 私聊:Chat history with {name}\n const enPrivateMatch = header.match(/^(?:\\[LINE\\] )?Chat history with (.+)$/m)\n if (enPrivateMatch) return { name: enPrivateMatch[1].trim(), isGroup: false }\n // 群聊:Chat history in {name}\n const enGroupMatch = header.match(/^(?:\\[LINE\\] )?Chat history in (.+)$/m)\n if (enGroupMatch) return { name: enGroupMatch[1].trim(), isGroup: true }\n\n // ===== 日文 =====\n // 私聊:{name}とのトーク履歴\n const jaPrivateMatch = header.match(/^\\[LINE\\] (.+)とのトーク履歴/)\n if (jaPrivateMatch) return { name: jaPrivateMatch[1].trim(), isGroup: false }\n // 群聊:{name}のトーク履歴\n const jaGroupMatch = header.match(/^\\[LINE\\] (.+)のトーク履歴/)\n if (jaGroupMatch) return { name: jaGroupMatch[1].trim(), isGroup: true }\n\n // ===== 简体中文 =====\n // 私聊:与{name}的聊天记录\n const zhCnPrivateMatch = header.match(/^\\[LINE\\] 与(.+)的聊天记录/)\n if (zhCnPrivateMatch) return { name: zhCnPrivateMatch[1].trim(), isGroup: false }\n // 群聊:{name}的聊天记录\n const zhCnGroupMatch = header.match(/^\\[LINE\\] (.+)的聊天记录/)\n if (zhCnGroupMatch) return { name: zhCnGroupMatch[1].trim(), isGroup: true }\n\n // ===== 繁体中文 =====\n // 私聊:與{name}的聊天記錄\n const zhTwPrivateMatch = header.match(/^\\[LINE\\] 與(.+)的聊天記錄/)\n if (zhTwPrivateMatch) return { name: zhTwPrivateMatch[1].trim(), isGroup: false }\n // 群聊:{name}的聊天記錄\n const zhTwGroupMatch = header.match(/^\\[LINE\\] (.+)的聊天記錄/)\n if (zhTwGroupMatch) return { name: zhTwGroupMatch[1].trim(), isGroup: true }\n\n return null\n}\n\n/**\n * 日期行正则模式\n */\nconst DATE_PATTERNS = [\n // 2025.12.10 Wednesday\n /^(\\d{4})\\.(\\d{2})\\.(\\d{2})\\s+(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)?/,\n // 2026/1/30周五 or 2026/1/30(金)\n /^(\\d{4})\\/(\\d{1,2})\\/(\\d{1,2})/,\n // Fri, 1/30/2026\n /^[A-Za-z]+,\\s*(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})/,\n]\n\n/**\n * 尝试解析日期行\n */\nfunction parseDateLine(line: string): Date | null {\n for (const pattern of DATE_PATTERNS) {\n const match = line.match(pattern)\n if (match) {\n // 根据不同格式提取年月日\n if (pattern === DATE_PATTERNS[0] || pattern === DATE_PATTERNS[1]) {\n // YYYY.MM.DD or YYYY/M/D\n const year = parseInt(match[1])\n const month = parseInt(match[2]) - 1\n const day = parseInt(match[3])\n return new Date(year, month, day)\n } else if (pattern === DATE_PATTERNS[2]) {\n // M/D/YYYY\n const month = parseInt(match[1]) - 1\n const day = parseInt(match[2])\n const year = parseInt(match[3])\n return new Date(year, month, day)\n }\n }\n }\n return null\n}\n\n/**\n * 消息行正则模式\n * 时间格式:HH:MM / HH:MMam|pm / 上午|下午|午前|午後HH:MM\n */\n// 私聊/群聊(有发送者):TIME\\t{name}\\t{content}\nconst PRIVATE_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\\d{1,2}:\\d{2}(?:[AaPp][Mm])?)\\t([^\\t]+)\\t(.*)$/\n// 群聊:HH:MM {name} {content} (已废弃,实际都用 Tab 分隔)\nconst GROUP_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\\d{1,2}:\\d{2}(?:[AaPp][Mm])?) ([^\\s]+) (.*)$/\n// 系统消息:双 Tab(无发送者),如「下午07:04\\t\\tXXX已加入群組」\nconst SYSTEM_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\\d{1,2}:\\d{2}(?:[AaPp][Mm])?)\\t\\t(.+)$/\n\n/**\n * 特殊消息类型映射(多语言:EN / ZH-CN / ZH-TW / JA)\n */\nconst SPECIAL_MESSAGE_TYPES: Record<string, MessageType> = {\n // 图片 / Photo\n '[Photo]': MessageType.IMAGE, // EN\n '[照片]': MessageType.IMAGE, // ZH-CN / ZH-TW\n '[写真]': MessageType.IMAGE, // JA\n Photos: MessageType.IMAGE, // EN (fallback)\n\n // 语音 / Voice\n '[Voice message]': MessageType.VOICE, // EN\n '[语音信息]': MessageType.VOICE, // ZH-CN\n '[語音訊息]': MessageType.VOICE, // ZH-TW\n '[ボイスメッセージ]': MessageType.VOICE, // JA\n Audio: MessageType.VOICE, // EN (fallback)\n\n // 视频 / Video\n '[Video]': MessageType.VIDEO, // EN\n '[视频]': MessageType.VIDEO, // ZH-CN\n '[影片]': MessageType.VIDEO, // ZH-TW\n '[動画]': MessageType.VIDEO, // JA\n Videos: MessageType.VIDEO, // EN (fallback)\n\n // 文件 / File\n '[File]': MessageType.FILE, // EN\n '[文件]': MessageType.FILE, // ZH-CN\n '[檔案]': MessageType.FILE, // ZH-TW\n '[ファイル]': MessageType.FILE, // JA\n\n // 贴纸 / Sticker\n '[Sticker]': MessageType.EMOJI, // EN\n '[贴图]': MessageType.EMOJI, // ZH-CN\n '[貼圖]': MessageType.EMOJI, // ZH-TW\n '[スタンプ]': MessageType.EMOJI, // JA\n Stickers: MessageType.EMOJI, // EN (fallback)\n\n // 位置 / Location\n '[Location]': MessageType.LOCATION, // EN\n '[位置]': MessageType.LOCATION, // ZH-CN / ZH-TW\n '[位置情報]': MessageType.LOCATION, // JA\n\n // 记事本 / Notes\n '[Notes]': MessageType.TEXT, // EN\n '[记事本]': MessageType.TEXT, // ZH-CN\n '[記事本]': MessageType.TEXT, // ZH-TW\n '[ノート]': MessageType.TEXT, // JA\n}\n\n/**\n * 检测消息类型\n */\nfunction detectMessageType(content: string): MessageType {\n // 检查特殊消息类型\n for (const [pattern, type] of Object.entries(SPECIAL_MESSAGE_TYPES)) {\n if (content === pattern || content.startsWith(pattern)) {\n return type\n }\n }\n\n // 检查 [null] 开头的位置消息\n if (content.startsWith('[null]') && content.includes('maps.google.com')) {\n return MessageType.LOCATION\n }\n\n // 检查系统消息(多语言:EN / ZH-CN / ZH-TW / JA)\n if (\n // --- 加入群组 / Join group ---\n content.includes(' joined the group') || // EN\n content.includes('已加入该群') || // ZH-CN\n content.includes('已加入群組') || // ZH-TW\n content.includes('がグループに参加しました') || // JA\n // --- 拉人进群 / Added to group ---\n content.includes(' added ') || // EN\n content.includes(' to the group') || // EN\n content.includes('已将') || // ZH-CN\n content.includes('添加至群') || // ZH-CN\n content.includes('添加到群') || // ZH-CN (另一格式)\n content.includes('已新增') || // ZH-TW\n content.includes('至群組') || // ZH-TW\n content.includes('をグループに追加しました') || // JA\n // --- 退出群组 / Left group ---\n content.includes(' left the group') || // EN\n content.includes('已退群') || // ZH-CN\n content.includes('已離開群組') || // ZH-TW\n content.includes('がグループを退会しました') || // JA\n // --- 设定公告 / Announcement ---\n content.includes('made an announcement') || // EN\n content.includes('发布了通告') || // ZH-CN\n content.includes('已設定公告') || // ZH-TW\n content.includes('がアナウンスしました') || // JA\n // --- 收回讯息 / Unsent message ---\n content.includes('unsent a message') || // EN\n content === 'Message unsent.' || // EN\n content.includes('撤回了一条消息') || // ZH-CN\n content.includes('已收回訊息') || // ZH-TW\n content.includes('送信を取り消しました') || // JA\n // --- 其他 / Others ---\n content.startsWith('Auto-reply') // EN 自动回复\n ) {\n return MessageType.SYSTEM\n }\n\n // 检查链接\n if (content.match(/^https?:\\/\\//)) {\n return MessageType.LINK\n }\n\n return MessageType.TEXT\n}\n\n// ==================== 解析器实现 ====================\n\nasync function* parseLINE(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> {\n const { filePath, batchSize = 5000, onProgress, onLog } = options\n\n const totalBytes = getFileSize(filePath)\n let messagesProcessed = 0\n\n // 发送初始进度\n const initialProgress = createProgress('parsing', 0, totalBytes, 0, '')\n yield { type: 'progress', data: initialProgress }\n onProgress?.(initialProgress)\n\n onLog?.('info', `开始解析 LINE 导出文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`)\n\n // 读取整个文件(LINE 导出通常不大)\n const content = fs.readFileSync(filePath, 'utf-8')\n // 处理 Windows 换行符 (\\r\\n)\n const lines = content.split('\\n').map((line) => line.replace(/\\r$/, ''))\n\n // 解析状态\n let currentDate: Date | null = null\n let chatName = extractNameFromFilePath(filePath)\n let isPrivateChat = false\n let useTabSeparator = false\n const memberMap = new Map<string, ParsedMember>()\n const messages: ParsedMessage[] = []\n let lastMessage: ParsedMessage | null = null\n let lineIndex = 0\n\n // 检测是否有头部\n if (lines.length > 0) {\n const firstLine = lines[0].trim()\n onLog?.('debug', `LINE 第一行: \"${firstLine}\"`)\n const headerResult = extractNameFromHeader(firstLine)\n if (headerResult) {\n chatName = headerResult.name\n isPrivateChat = !headerResult.isGroup\n useTabSeparator = true // 两种头部格式都使用 Tab 分隔\n lineIndex = 3 // 跳过头部(标题、保存时间、空行)\n onLog?.('debug', `LINE 检测到头部,名称: ${headerResult.name}, 群聊: ${headerResult.isGroup}`)\n }\n }\n\n // 如果没有检测到头部,检查第一条消息的格式\n if (!isPrivateChat && lines.length > 0) {\n for (const line of lines) {\n if (PRIVATE_MSG_PATTERN.test(line)) {\n useTabSeparator = true\n onLog?.('debug', `LINE 检测到 Tab 分隔格式`)\n break\n }\n if (GROUP_MSG_PATTERN.test(line)) {\n useTabSeparator = false\n onLog?.('debug', `LINE 检测到空格分隔格式`)\n break\n }\n }\n }\n\n onLog?.('debug', `LINE 解析配置: useTabSeparator=${useTabSeparator}, lineIndex=${lineIndex}`)\n\n // 解析消息\n let debugLogCount = 0\n for (let i = lineIndex; i < lines.length; i++) {\n const line = lines[i]\n\n // 尝试解析日期行\n const dateResult = parseDateLine(line)\n if (dateResult) {\n currentDate = dateResult\n if (debugLogCount < 5) {\n onLog?.('debug', `LINE 日期行[${i}]: ${line} -> ${dateResult.toISOString()}`)\n }\n continue\n }\n\n // 尝试解析消息行\n const msgPattern = useTabSeparator ? PRIVATE_MSG_PATTERN : GROUP_MSG_PATTERN\n const msgMatch = line.match(msgPattern)\n\n // 调试前几行\n if (debugLogCount < 5 && line.trim()) {\n onLog?.('debug', `LINE 行[${i}]: \"${line.substring(0, 50)}...\" match=${!!msgMatch}`)\n debugLogCount++\n }\n\n if (msgMatch) {\n const [, timeStr, sender, contentRaw] = msgMatch\n let content = contentRaw.trim()\n\n // 处理 LINE 导出的多行消息格式(用双引号包裹)\n let isQuotedMultiline = false\n if (content.startsWith('\"')) {\n content = content.substring(1) // 移除开头的引号\n isQuotedMultiline = !content.endsWith('\"') // 单行带引号则直接处理\n if (content.endsWith('\"')) {\n content = content.substring(0, content.length - 1) // 移除结尾引号\n }\n }\n\n // 解析时间(支持中文上午/下午、日文午前/午後、英文am/pm)\n let hours = 0\n let minutes = 0\n\n const prefix = timeStr.match(/^(上午|下午|午前|午後)/)?.[1]\n const cleanTime = timeStr.replace(/^(上午|下午|午前|午後)/, '')\n const partsMatch = cleanTime.match(/^(\\d{1,2}):(\\d{2})([AaPp][Mm])?$/i)\n\n if (partsMatch) {\n hours = parseInt(partsMatch[1])\n minutes = parseInt(partsMatch[2])\n const suffix = partsMatch[3]?.toLowerCase()\n\n // 英文后缀\n if (suffix === 'pm' && hours < 12) hours += 12\n if (suffix === 'am' && hours === 12) hours = 0\n\n // 中文/日文前缀 (下午/午後 = PM, 上午/午前 = AM)\n if ((prefix === '下午' || prefix === '午後') && hours < 12) hours += 12\n if ((prefix === '上午' || prefix === '午前') && hours === 12) hours = 0\n } else {\n // Fallback\n const parts = timeStr.split(':').map(Number)\n hours = parts[0]\n minutes = parts[1]\n }\n\n let timestamp: number\n\n if (currentDate) {\n const msgDate = new Date(currentDate)\n msgDate.setHours(hours, minutes, 0, 0)\n timestamp = Math.floor(msgDate.getTime() / 1000)\n } else {\n // 如果没有日期,使用当前日期\n const now = new Date()\n now.setHours(hours, minutes, 0, 0)\n timestamp = Math.floor(now.getTime() / 1000)\n }\n\n // 检测消息类型\n const msgType = detectMessageType(content)\n\n // 更新成员信息\n if (!memberMap.has(sender)) {\n memberMap.set(sender, {\n platformId: sender,\n accountName: sender,\n })\n }\n\n // 创建消息\n lastMessage = {\n senderPlatformId: sender,\n senderAccountName: sender,\n timestamp,\n type: msgType,\n content: content || null,\n _isQuotedMultiline: isQuotedMultiline, // 临时标记,用于追加多行内容时处理结尾引号\n } as ParsedMessage & { _isQuotedMultiline?: boolean }\n messages.push(lastMessage)\n messagesProcessed++\n\n // 更新进度\n if (messagesProcessed % 1000 === 0) {\n const progress = createProgress(\n 'parsing',\n i,\n lines.length,\n messagesProcessed,\n `已处理 ${messagesProcessed} 条消息...`\n )\n onProgress?.(progress)\n }\n } else {\n // 尝试解析系统消息(双 Tab)\n const systemMatch = line.match(SYSTEM_MSG_PATTERN)\n if (systemMatch) {\n const [, timeStr, contentRaw] = systemMatch\n const content = contentRaw.trim()\n\n // 解析时间(支持中文、日文、英文)\n let hours = 0\n let minutes = 0\n\n const prefix = timeStr.match(/^(上午|下午|午前|午後)/)?.[1]\n const cleanTime = timeStr.replace(/^(上午|下午|午前|午後)/, '')\n const partsMatch = cleanTime.match(/^(\\d{1,2}):(\\d{2})([AaPp][Mm])?$/i)\n\n if (partsMatch) {\n hours = parseInt(partsMatch[1])\n minutes = parseInt(partsMatch[2])\n const suffix = partsMatch[3]?.toLowerCase()\n\n if (suffix === 'pm' && hours < 12) hours += 12\n if (suffix === 'am' && hours === 12) hours = 0\n\n if ((prefix === '下午' || prefix === '午後') && hours < 12) hours += 12\n if ((prefix === '上午' || prefix === '午前') && hours === 12) hours = 0\n } else {\n const parts = timeStr.split(':').map(Number)\n hours = parts[0]\n minutes = parts[1]\n }\n\n let timestamp: number\n if (currentDate) {\n const msgDate = new Date(currentDate)\n msgDate.setHours(hours, minutes, 0, 0)\n timestamp = Math.floor(msgDate.getTime() / 1000)\n } else {\n const now = new Date()\n now.setHours(hours, minutes, 0, 0)\n timestamp = Math.floor(now.getTime() / 1000)\n }\n\n // 创建系统消息\n lastMessage = {\n senderPlatformId: 'system',\n senderAccountName: '系統',\n timestamp,\n type: MessageType.SYSTEM,\n content: content || null,\n }\n messages.push(lastMessage)\n messagesProcessed++\n } else if (line.trim() && lastMessage) {\n // 非消息行,追加到上一条消息(多行内容)\n let appendLine = line\n const quotedMsg = lastMessage as ParsedMessage & { _isQuotedMultiline?: boolean }\n\n // 检查是否为带引号多行消息的最后一行(以 \" 结尾)\n if (quotedMsg._isQuotedMultiline && appendLine.endsWith('\"')) {\n appendLine = appendLine.substring(0, appendLine.length - 1) // 移除结尾引号\n delete quotedMsg._isQuotedMultiline // 清除临时标记\n }\n\n if (lastMessage.content) {\n lastMessage.content += '\\n' + appendLine\n } else {\n lastMessage.content = appendLine\n }\n }\n }\n }\n\n // 根据成员数判断聊天类型\n const memberCount = memberMap.size\n const chatType = memberCount <= 2 ? ChatType.PRIVATE : ChatType.GROUP\n\n // 发送 meta\n const meta: ParsedMeta = {\n name: chatName,\n platform: KNOWN_PLATFORMS.LINE,\n type: chatType,\n }\n yield { type: 'meta', data: meta }\n\n // 发送成员\n const members = Array.from(memberMap.values())\n yield { type: 'members', data: members }\n\n // 分批发送消息\n for (let i = 0; i < messages.length; i += batchSize) {\n const batch = messages.slice(i, i + batchSize)\n yield { type: 'messages', data: batch }\n }\n\n // 完成\n const doneProgress = createProgress('done', totalBytes, totalBytes, messagesProcessed, '')\n yield { type: 'progress', data: doneProgress }\n onProgress?.(doneProgress)\n\n onLog?.('info', `解析完成: ${messagesProcessed} 条消息, ${memberCount} 个成员`)\n\n yield {\n type: 'done',\n data: { messageCount: messagesProcessed, memberCount },\n }\n}\n\n// ==================== 导出 ====================\n\nexport const parser_: Parser = {\n feature,\n parse: parseLINE,\n}\n\nconst module_: FormatModule = {\n feature,\n parser: parser_,\n}\n\nexport default module_\n","/**\n * 格式模块注册\n * 导出所有支持的格式\n */\n\nimport type { FormatModule } from '../types'\n\n// 导入所有格式模块\nimport chatlab from './chatlab'\nimport chatlabJsonl from './chatlab-jsonl'\nimport shuakamiQqExporter from './shuakami-qq-exporter'\nimport shuakamiQqExporterChunked from './shuakami-qq-exporter-chunked'\nimport weflow from './weflow'\nimport yccccccyEchotrace from './ycccccccy-echotrace'\nimport tyrrrzDiscordExporter from './tyrrrz-discord-exporter'\nimport telegramNative from './telegram-native'\nimport telegramNativeSingle from './telegram-native-single'\nimport whatsappNativeTxt from './whatsapp-native-txt'\nimport qqNativeTxt from './qq-native-txt'\nimport instagramNative from './instagram-native'\nimport googleChatTakeout from './google-chat-takeout'\nimport lineNativeTxt from './line-native-txt'\n\n/**\n * 所有支持的格式模块(按优先级排序)\n * 注意:注册时会自动按 priority 字段排序\n */\nexport const formats: FormatModule[] = [\n shuakamiQqExporterChunked, // 优先级 5 - shuakami/qq-chat-exporter chunked-jsonl\n shuakamiQqExporter, // 优先级 10 - shuakami/qq-chat-exporter\n weflow, // 优先级 15 - WeFlow 微信导出\n yccccccyEchotrace, // 优先级 16 - ycccccccy/echotrace 微信导出\n tyrrrzDiscordExporter, // 优先级 20 - Tyrrrz/DiscordChatExporter\n telegramNative, // 优先级 22 - Telegram 官方全量导出 JSON\n telegramNativeSingle, // 优先级 23 - Telegram 单聊天导出 JSON\n googleChatTakeout, // 优先级 24 - Google Chat Takeout 内部 manifest\n instagramNative, // 优先级 25 - Instagram 官方导出\n whatsappNativeTxt, // 优先级 26 - WhatsApp 官方导出 TXT\n qqNativeTxt, // 优先级 30 - QQ 官方导出 TXT\n lineNativeTxt, // 优先级 35 - LINE 官方导出 TXT\n chatlab, // 优先级 50 - ChatLab JSON\n chatlabJsonl, // 优先级 51 - ChatLab JSONL(流式格式,支持超大文件)\n]\n\n// 按名称导出,方便单独使用\nexport {\n chatlab,\n chatlabJsonl,\n shuakamiQqExporter,\n shuakamiQqExporterChunked,\n weflow,\n yccccccyEchotrace,\n tyrrrzDiscordExporter,\n telegramNative,\n telegramNativeSingle,\n googleChatTakeout,\n instagramNative,\n whatsappNativeTxt,\n qqNativeTxt,\n lineNativeTxt,\n}\n","/**\n * 分词引擎(Node.js 实现,依赖 @node-rs/jieba)\n *\n * 中文使用 jieba,英文/日语使用 Intl.Segmenter。\n * 类型、停用词、文本处理工具来自 @openchatlab/core。\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { createRequire } from 'module'\nimport type {\n SupportedLocale,\n PosFilterMode,\n DictType,\n SegmentOptions,\n BatchSegmentOptions,\n BatchSegmentResult,\n} from '@openchatlab/core'\nimport { MEANINGFUL_POS_TAGS, isStopword, cleanText, isValidWord } from '@openchatlab/core'\n\ninterface JiebaInstance {\n cut: (text: string, hmm?: boolean) => string[]\n tag: (text: string) => Array<{ tag: string; word: string }>\n}\n\nlet _nlpDir: string | null = null\nconst jiebaInstances = new Map<DictType, JiebaInstance>()\nconst require = createRequire(import.meta.url)\n\n/**\n * 设置自定义词库目录路径(由应用初始化时调用)\n */\nexport function initNlpDir(nlpDir: string): void {\n _nlpDir = nlpDir\n}\n\nexport function getNlpDir(): string | null {\n return _nlpDir\n}\n\nfunction existsDictOnDisk(dictId: string): boolean {\n if (!_nlpDir) return false\n return fs.existsSync(path.join(_nlpDir, `${dictId}.dict`))\n}\n\nfunction tryLoadDictFromDisk(dictId: string): Buffer | null {\n if (!_nlpDir) return null\n const dictPath = path.join(_nlpDir, `${dictId}.dict`)\n if (!fs.existsSync(dictPath)) return null\n try {\n return fs.readFileSync(dictPath)\n } catch {\n return null\n }\n}\n\n/**\n * 获取 Jieba 实例(支持多词库)\n */\nexport function getJieba(dictType: DictType = 'default'): JiebaInstance {\n const effectiveType = dictType === 'default' ? 'zh-CN' : dictType\n const cached = jiebaInstances.get(effectiveType)\n if (cached) {\n if (existsDictOnDisk(effectiveType)) return cached\n jiebaInstances.delete(effectiveType)\n console.log(`[NLP] jieba cache invalidated (dict missing): ${effectiveType}`)\n }\n\n try {\n const { Jieba } = require('@node-rs/jieba')\n let instance: JiebaInstance\n const diskDict = tryLoadDictFromDisk(effectiveType)\n if (diskDict) {\n instance = Jieba.withDict(diskDict)\n console.log(`[NLP] jieba dict loaded: ${effectiveType} (${diskDict.length} bytes)`)\n } else {\n instance = new Jieba()\n console.warn(`[NLP] jieba dict missing: ${effectiveType}, fallback to built-in tokenizer`)\n }\n jiebaInstances.set(effectiveType, instance)\n return instance\n } catch (error) {\n console.error(`[NLP] Failed to load jieba module (dict=${effectiveType}):`, error)\n throw new Error(`jieba 模块加载失败 (${effectiveType})`)\n }\n}\n\n/**\n * 清除指定词库的缓存实例(词库更新后调用)\n */\nexport function clearJiebaInstance(dictType: DictType): void {\n jiebaInstances.delete(dictType)\n console.log(`[NLP] jieba instance cleared: ${dictType}`)\n}\n\nfunction segmentChinese(\n text: string,\n options: { posFilterMode?: PosFilterMode; customPosTags?: string[]; dictType?: DictType } = {}\n): string[] {\n const { posFilterMode = 'meaningful', customPosTags, dictType = 'default' } = options\n const cleaned = cleanText(text)\n if (!cleaned) return []\n\n try {\n const jieba = getJieba(dictType)\n if (posFilterMode === 'all') return jieba.cut(cleaned, false)\n const tagged = jieba.tag(cleaned)\n const allowedTags = posFilterMode === 'custom' && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS\n return tagged.filter((item) => allowedTags.has(item.tag)).map((item) => item.word)\n } catch (error) {\n console.error('[NLP] Chinese segmentation failed:', error)\n try {\n return getJieba('default').cut(cleanText(text), false)\n } catch {\n return cleaned.split('')\n }\n }\n}\n\nfunction segmentEnglish(text: string): string[] {\n const cleaned = cleanText(text)\n if (!cleaned) return []\n try {\n const segmenter = new Intl.Segmenter('en', { granularity: 'word' })\n return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase())\n } catch {\n return cleaned\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 0)\n }\n}\n\nfunction segmentJapanese(text: string): string[] {\n const cleaned = cleanText(text)\n if (!cleaned) return []\n try {\n const segmenter = new Intl.Segmenter('ja', { granularity: 'word' })\n return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment)\n } catch {\n return cleaned.split('').filter((ch) => ch.trim().length > 0)\n }\n}\n\n/**\n * 通用分词入口\n */\nexport function segment(text: string, locale: SupportedLocale, options: SegmentOptions = {}): string[] {\n const {\n minLength,\n posFilterMode = 'meaningful',\n customPosTags,\n enableStopwords = true,\n dictType = 'default',\n } = options\n const isChinese = locale.startsWith('zh')\n const isJapanese = locale === 'ja-JP'\n const defaultMinLength = isChinese || isJapanese ? 2 : 3\n const effectiveMinLength = minLength ?? defaultMinLength\n\n let words: string[]\n if (isChinese) {\n words = segmentChinese(text, { posFilterMode, customPosTags, dictType })\n } else if (isJapanese) {\n words = segmentJapanese(text)\n } else {\n words = segmentEnglish(text)\n }\n\n return words.filter((word) => isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword))\n}\n\n/**\n * 批量分词并统计词频\n */\nexport function batchSegmentWithFrequency(\n texts: string[],\n locale: SupportedLocale,\n options: BatchSegmentOptions = {}\n): BatchSegmentResult {\n const {\n minLength,\n minCount = 2,\n topN = 100,\n posFilterMode,\n customPosTags,\n enableStopwords,\n dictType,\n excludeWords,\n } = options\n const wordFrequency = new Map<string, number>()\n const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null\n\n for (const text of texts) {\n const words = segment(text, locale, { minLength, posFilterMode, customPosTags, enableStopwords, dictType })\n for (const word of words) {\n if (excludeSet && excludeSet.has(word.toLowerCase())) continue\n wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1)\n }\n }\n\n const filtered = new Map<string, number>()\n let totalWords = 0\n for (const [word, count] of wordFrequency) {\n if (count >= minCount) {\n filtered.set(word, count)\n totalWords += count\n }\n }\n\n const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN)\n return { words: new Map(sorted), uniqueWords: filtered.size, totalWords }\n}\n\n/**\n * 中文单遍分词:一次 jieba.tag 同时产出词频与词性统计。\n *\n * 仅适用于中文 meaningful/custom 模式(all 模式走 jieba.cut,无词性)。\n * 输出与 `batchSegmentWithFrequency` + `collectPosTagStats` 组合完全一致,\n * 用于消除对同一批文本重复分词的开销。\n */\nexport function batchSegmentChineseWithStats(\n texts: string[],\n locale: SupportedLocale,\n options: BatchSegmentOptions = {}\n): BatchSegmentResult & { posTagStats: Map<string, number> } {\n const {\n minLength,\n minCount = 2,\n topN = 100,\n posFilterMode = 'meaningful',\n customPosTags,\n enableStopwords = true,\n dictType = 'default',\n excludeWords,\n } = options\n\n const effectiveMinLength = minLength ?? 2\n const allowedTags = posFilterMode === 'custom' && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS\n const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null\n\n const wordFrequency = new Map<string, number>()\n const posTagStats = new Map<string, number>()\n\n try {\n const jieba = getJieba(dictType)\n for (const text of texts) {\n const cleaned = cleanText(text)\n if (!cleaned) continue\n for (const { tag, word } of jieba.tag(cleaned)) {\n if (!isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword)) continue\n // 词性统计覆盖全部有效词(与 collectPosTagStats 一致)\n posTagStats.set(tag, (posTagStats.get(tag) || 0) + 1)\n // 词频仅统计命中允许词性的词(与 batchSegmentWithFrequency 一致)\n if (!allowedTags.has(tag)) continue\n if (excludeSet && excludeSet.has(word.toLowerCase())) continue\n wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1)\n }\n }\n } catch (error) {\n console.error('[NLP] Chinese single-pass segmentation failed:', error)\n }\n\n const filtered = new Map<string, number>()\n let totalWords = 0\n for (const [word, count] of wordFrequency) {\n if (count >= minCount) {\n filtered.set(word, count)\n totalWords += count\n }\n }\n\n const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN)\n return { words: new Map(sorted), uniqueWords: filtered.size, totalWords, posTagStats }\n}\n\n/**\n * 收集文本的词性统计\n */\nexport function collectPosTagStats(\n texts: string[],\n minWordLength: number = 2,\n enableStopwords: boolean = true,\n dictType: DictType = 'default'\n): Map<string, number> {\n const posStats = new Map<string, number>()\n try {\n const jieba = getJieba(dictType)\n for (const text of texts) {\n const cleaned = cleanText(text)\n if (!cleaned) continue\n const tagged = jieba.tag(cleaned)\n for (const item of tagged) {\n if (!isValidWord(item.word, 'zh-CN', minWordLength, enableStopwords, isStopword)) continue\n posStats.set(item.tag, (posStats.get(item.tag) || 0) + 1)\n }\n }\n } catch (error) {\n console.error('[NLP] Failed to collect POS stats:', error)\n }\n return posStats\n}\n\nimport { POS_TAG_DEFINITIONS } from '@openchatlab/core'\nimport type { PosTagInfo } from '@openchatlab/core'\n\n/**\n * 获取所有词性标签信息\n */\nexport function getPosTagDefinitions(): PosTagInfo[] {\n return POS_TAG_DEFINITIONS\n}\n","/**\n * 词频统计(Node.js 实现)\n *\n * 从数据库查询消息,调用分词引擎进行词频统计。\n * 供 Electron worker 和 Server HTTP 路由共用。\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport type {\n SupportedLocale,\n DictType,\n WordFrequencyParams,\n WordFrequencyResult,\n PosTagStat,\n BatchSegmentResult,\n} from '@openchatlab/core'\nimport { segment, batchSegmentWithFrequency, batchSegmentChineseWithStats, collectPosTagStats } from './segmenter'\n\nfunction buildMessageQuery(\n timeFilter?: { startTs?: number; endTs?: number },\n memberId?: number\n): { clause: string; params: unknown[] } {\n const conditions: string[] = []\n const params: unknown[] = []\n\n if (timeFilter?.startTs !== undefined) {\n conditions.push('msg.ts >= ?')\n params.push(timeFilter.startTs)\n }\n if (timeFilter?.endTs !== undefined) {\n conditions.push('msg.ts <= ?')\n params.push(timeFilter.endTs)\n }\n if (memberId !== undefined && memberId !== null) {\n conditions.push('msg.sender_id = ?')\n params.push(memberId)\n }\n\n conditions.push(\"COALESCE(m.account_name, '') != '系统消息'\")\n conditions.push('msg.type = 0')\n conditions.push('msg.content IS NOT NULL')\n conditions.push(\"TRIM(msg.content) != ''\")\n\n return {\n clause: ` WHERE ${conditions.join(' AND ')}`,\n params,\n }\n}\n\n/**\n * 从数据库计算词频统计\n */\nexport function computeWordFrequency(db: DatabaseAdapter, params: WordFrequencyParams): WordFrequencyResult {\n const {\n locale,\n timeFilter,\n memberId,\n topN = 100,\n minWordLength,\n minCount = 2,\n posFilterMode = 'meaningful',\n customPosTags,\n enableStopwords = true,\n dictType = 'default',\n excludeWords,\n } = params\n\n const { clause, params: filterParams } = buildMessageQuery(timeFilter, memberId)\n\n const messages = db\n .prepare(`SELECT msg.content FROM message msg JOIN member m ON msg.sender_id = m.id${clause}`)\n .all(...filterParams) as Array<{ content: string }>\n\n if (messages.length === 0) {\n return { words: [], totalWords: 0, totalMessages: 0, uniqueWords: 0 }\n }\n\n const texts = messages.map((m) => m.content)\n\n const segmentOptions = {\n minLength: minWordLength,\n minCount,\n topN,\n posFilterMode,\n customPosTags,\n enableStopwords,\n dictType: dictType as DictType,\n excludeWords,\n }\n\n let posTagStats: PosTagStat[] | undefined\n let result: BatchSegmentResult\n\n // 中文 meaningful/custom 模式下,词频与词性统计可在一次分词内同时产出,避免重复分词。\n if (locale.startsWith('zh') && posFilterMode !== 'all') {\n const combined = batchSegmentChineseWithStats(texts, locale as SupportedLocale, segmentOptions)\n result = { words: combined.words, uniqueWords: combined.uniqueWords, totalWords: combined.totalWords }\n posTagStats = [...combined.posTagStats.entries()].map(([tag, count]) => ({ tag, count }))\n } else {\n if (locale.startsWith('zh')) {\n const posStatsMap = collectPosTagStats(texts, minWordLength ?? 2, enableStopwords, dictType as DictType)\n posTagStats = [...posStatsMap.entries()].map(([tag, count]) => ({ tag, count }))\n }\n result = batchSegmentWithFrequency(texts, locale as SupportedLocale, segmentOptions)\n }\n\n let topNTotalWords = 0\n for (const count of result.words.values()) topNTotalWords += count\n\n const words = [...result.words.entries()].map(([word, count]) => ({\n word,\n count,\n percentage: topNTotalWords > 0 ? Math.round((count / topNTotalWords) * 10000) / 100 : 0,\n }))\n\n return {\n words,\n totalWords: result.totalWords,\n totalMessages: messages.length,\n uniqueWords: result.uniqueWords,\n posTagStats,\n }\n}\n\n/**\n * 单文本分词\n */\nexport function segmentText(text: string, locale: SupportedLocale, minLength?: number): string[] {\n return segment(text, locale, { minLength })\n}\n","/**\n * FTS5 tokenizer — shared implementation.\n *\n * Unlike NLP word-frequency analysis, FTS tokenization keeps all tokens\n * (no POS filtering, no stopwords) to maximize recall in search scenarios.\n *\n * Uses jieba for Chinese (naturally handles mixed CJK/Latin text),\n * falls back to whitespace split when jieba is unavailable.\n */\n\nimport { getJieba } from './segmenter'\n\nexport function tokenizeForFts(text: string | null | undefined): string {\n if (!text || text.trim().length === 0) return ''\n\n try {\n const jieba = getJieba()\n const tokens = jieba.cut(text, false)\n return tokens\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0)\n .join(' ')\n } catch {\n return fallbackTokenize(text)\n }\n}\n\nfunction fallbackTokenize(text: string): string {\n return text\n .toLowerCase()\n .split(/\\s+/)\n .filter((t) => t.length > 0)\n .join(' ')\n}\n\nfunction escapeToken(token: string): string {\n return `\"${token.replace(/\"/g, '\"\"')}\"`\n}\n\n/**\n * Convert a list of user search keywords into an FTS5 MATCH expression.\n *\n * - Single keyword: tokens joined with AND (all must appear)\n * - Multiple keywords: groups joined with OR (any match)\n */\nexport function tokenizeQueryForFts(keywords: string[]): string {\n if (keywords.length === 0) return ''\n\n const groups = keywords\n .map((kw) => {\n const trimmed = kw.trim()\n if (!trimmed) return ''\n\n try {\n const jieba = getJieba()\n const tokens = jieba\n .cut(trimmed, false)\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0)\n\n if (tokens.length === 0) return ''\n if (tokens.length === 1) return escapeToken(tokens[0])\n return tokens.map(escapeToken).join(' ')\n } catch {\n const simple = trimmed.toLowerCase().trim()\n return simple ? escapeToken(simple) : ''\n }\n })\n .filter((g) => g.length > 0)\n\n if (groups.length === 0) return ''\n if (groups.length === 1) return groups[0]\n\n return groups.map((g) => (g.includes(' ') ? `(${g})` : g)).join(' OR ')\n}\n","/**\n * 词库管理器(Node.js 实现)\n *\n * 接收 nlpDir 参数而非依赖 Electron app 模块。\n * Electron 和 Server 各自传入自己的 nlpDir 路径。\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { createHash } from 'crypto'\nimport type { DictInfo } from '@openchatlab/core'\nimport { clearJiebaInstance } from './segmenter'\n\nconst DICT_DOWNLOAD_URL_BASE = 'https://chatlab.fun/assets/nlp'\nconst DICT_SHA256: Record<string, string> = {\n 'zh-CN': '139519822fe8ab9e10d9d07e68ea0451045380aedaf54ecc51e2a28c6b42a13f',\n 'zh-TW': 'a63ec7e388f16f1b486dcd948a9f1a3b492be5d9b6bdab786a95e59966786dfd',\n}\n\nconst AVAILABLE_DICTS: Array<{ id: string; label: string; locale: string }> = [\n { id: 'zh-CN', label: '简体中文', locale: 'zh-CN' },\n { id: 'zh-TW', label: '繁體中文', locale: 'zh-TW' },\n]\n\nfunction getDictFilePath(nlpDir: string, dictId: string): string {\n return path.join(nlpDir, `${dictId}.dict`)\n}\n\nexport function isDictDownloaded(nlpDir: string, dictId: string): boolean {\n return fs.existsSync(getDictFilePath(nlpDir, dictId))\n}\n\nexport function getDictList(nlpDir: string): DictInfo[] {\n return AVAILABLE_DICTS.map((d) => {\n const filePath = getDictFilePath(nlpDir, d.id)\n const downloaded = fs.existsSync(filePath)\n let fileSize: number | undefined\n if (downloaded) {\n try {\n fileSize = fs.statSync(filePath).size\n } catch {\n /* ignore */\n }\n }\n return { ...d, downloaded, fileSize }\n })\n}\n\nexport function loadDictBuffer(nlpDir: string, dictId: string): Buffer | null {\n const filePath = getDictFilePath(nlpDir, dictId)\n if (!fs.existsSync(filePath)) return null\n try {\n return fs.readFileSync(filePath)\n } catch (error) {\n console.error(`[NLP DictManager] Failed to read dict file: ${filePath}`, error)\n return null\n }\n}\n\nexport async function downloadDict(\n nlpDir: string,\n dictId: string,\n onProgress?: (percent: number) => void\n): Promise<{ success: boolean; error?: string }> {\n if (!AVAILABLE_DICTS.find((d) => d.id === dictId)) {\n return { success: false, error: `Unknown dict: ${dictId}` }\n }\n\n fs.mkdirSync(nlpDir, { recursive: true })\n const url = `${DICT_DOWNLOAD_URL_BASE}/${dictId}.dict`\n const filePath = getDictFilePath(nlpDir, dictId)\n const tmpPath = filePath + '.tmp'\n\n try {\n const response = await fetch(url, { signal: AbortSignal.timeout(120_000) })\n if (!response.ok) throw new Error(`Download failed: HTTP ${response.status}`)\n\n const total = Number(response.headers.get('content-length') || 0)\n let buffer: Buffer\n\n if (!response.body) {\n buffer = Buffer.from(await response.arrayBuffer())\n } else {\n const reader = response.body.getReader()\n const chunks: Buffer[] = []\n let loaded = 0\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n const chunk = Buffer.from(value)\n chunks.push(chunk)\n loaded += chunk.length\n if (total > 0 && onProgress) onProgress(Math.round((loaded / total) * 100))\n }\n buffer = Buffer.concat(chunks)\n }\n\n const MIN_DICT_SIZE = 1_000_000\n if (buffer.length < MIN_DICT_SIZE) {\n return { success: false, error: `Downloaded file is invalid (${buffer.length} bytes)` }\n }\n const head = buffer.subarray(0, 50).toString('utf-8').trim()\n if (head.startsWith('<!') || head.startsWith('<html')) {\n return { success: false, error: 'Downloaded file is HTML, not a dictionary file' }\n }\n\n const expectedSha256 = DICT_SHA256[dictId]\n if (!expectedSha256) {\n return { success: false, error: `Missing SHA256 checksum for dict: ${dictId}` }\n }\n const actualSha256 = createHash('sha256').update(buffer).digest('hex')\n if (actualSha256 !== expectedSha256) {\n return { success: false, error: 'Dictionary integrity check failed (SHA256 mismatch)' }\n }\n\n fs.writeFileSync(tmpPath, buffer)\n if (fs.existsSync(filePath)) fs.unlinkSync(filePath)\n fs.renameSync(tmpPath, filePath)\n clearJiebaInstance(dictId as any)\n\n console.log(`[NLP DictManager] Dict downloaded: ${dictId} (${fs.statSync(filePath).size} bytes)`)\n return { success: true }\n } catch (error) {\n if (fs.existsSync(tmpPath)) {\n try {\n fs.unlinkSync(tmpPath)\n } catch {\n /* ignore */\n }\n }\n const msg = error instanceof Error ? error.message : String(error)\n console.error(`[NLP DictManager] Download failed for ${dictId}:`, msg)\n return { success: false, error: msg }\n }\n}\n\nexport function deleteDict(nlpDir: string, dictId: string): { success: boolean; error?: string } {\n const filePath = getDictFilePath(nlpDir, dictId)\n if (!fs.existsSync(filePath)) return { success: true }\n try {\n fs.unlinkSync(filePath)\n clearJiebaInstance(dictId as any)\n console.log(`[NLP DictManager] Dict deleted: ${dictId}`)\n return { success: true }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error)\n console.error(`[NLP DictManager] Delete failed for ${dictId}:`, msg)\n return { success: false, error: msg }\n }\n}\n\n/**\n * 自动下载默认词库(应用启动时调用)\n */\nexport async function ensureDefaultDict(nlpDir: string): Promise<void> {\n if (isDictDownloaded(nlpDir, 'zh-CN')) return\n console.log('[NLP DictManager] zh-CN dict not found, starting background download...')\n const result = await downloadDict(nlpDir, 'zh-CN')\n if (result.success) {\n console.log('[NLP DictManager] zh-CN dict auto-downloaded successfully')\n } else {\n console.warn('[NLP DictManager] zh-CN dict auto-download failed:', result.error)\n }\n}\n","/**\n * FTS5 full-text search index operations (platform-agnostic).\n *\n * Extracted from electron/main/worker/query/fts.ts.\n * Works with DatabaseAdapter + NLP tokenizer from this package.\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport { FTS_TABLE_SCHEMA } from '@openchatlab/core'\nimport { tokenizeForFts, tokenizeQueryForFts } from '../nlp'\n\nconst BATCH_SIZE = 5000\n\n/**\n * Check if FTS virtual table exists in the database.\n */\nexport function hasFtsTable(db: DatabaseAdapter): boolean {\n try {\n const row = db.prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'\").get()\n return !!row\n } catch {\n return false\n }\n}\n\n/**\n * Create FTS virtual table if it doesn't exist.\n */\nexport function createFtsTable(db: DatabaseAdapter): void {\n db.exec(FTS_TABLE_SCHEMA)\n}\n\n/**\n * Build FTS index from all text messages in the database.\n * Processes in batches for memory efficiency.\n */\nexport function buildFtsIndex(db: DatabaseAdapter): { indexed: number } {\n createFtsTable(db)\n\n const insertFts = db.prepare('INSERT INTO message_fts(rowid, content) VALUES (?, ?)')\n\n const countRow = db\n .prepare(\"SELECT COUNT(*) as total FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''\")\n .get() as { total: number } | undefined\n const total = countRow?.total ?? 0\n\n let indexed = 0\n let offset = 0\n\n while (offset < total) {\n const rows = db\n .prepare(\n `SELECT id, content FROM message\n WHERE type = 0 AND content IS NOT NULL AND content != ''\n ORDER BY id ASC LIMIT ? OFFSET ?`\n )\n .all(BATCH_SIZE, offset) as Array<{ id: number; content: string }>\n\n if (rows.length === 0) break\n\n db.transaction(() => {\n for (const row of rows) {\n const tokens = tokenizeForFts(row.content)\n if (tokens) {\n insertFts.run(row.id, tokens)\n }\n }\n })\n\n indexed += rows.length\n offset += BATCH_SIZE\n }\n\n return { indexed }\n}\n\n/**\n * Rebuild FTS index by dropping and recreating.\n */\nexport function rebuildFtsIndex(db: DatabaseAdapter): { indexed: number } {\n if (hasFtsTable(db)) {\n db.exec('DROP TABLE message_fts')\n }\n return buildFtsIndex(db)\n}\n\n/**\n * Insert FTS entries for a batch of messages.\n * Used during incremental import to sync new messages.\n */\nexport function insertFtsEntries(db: DatabaseAdapter, entries: Array<{ id: number; content: string | null }>): void {\n if (!hasFtsTable(db)) return\n\n const insertFts = db.prepare('INSERT INTO message_fts(rowid, content) VALUES (?, ?)')\n\n db.transaction(() => {\n for (const entry of entries) {\n if (entry.content) {\n const tokens = tokenizeForFts(entry.content)\n if (tokens) {\n insertFts.run(entry.id, tokens)\n }\n }\n }\n })\n}\n\n/**\n * Search messages using FTS5, returning matching rowids.\n */\nexport function searchByFts(\n db: DatabaseAdapter,\n keywords: string[],\n limit = 1000,\n offset = 0\n): { rowids: number[]; total: number } {\n if (keywords.length === 0) return { rowids: [], total: 0 }\n\n const matchQuery = tokenizeQueryForFts(keywords)\n if (!matchQuery) return { rowids: [], total: 0 }\n\n try {\n const countRow = db.prepare('SELECT COUNT(*) as total FROM message_fts WHERE content MATCH ?').get(matchQuery) as\n | { total: number }\n | undefined\n const total = countRow?.total ?? 0\n\n const rows = db\n .prepare(`SELECT rowid FROM message_fts WHERE content MATCH ? ORDER BY rank LIMIT ? OFFSET ?`)\n .all(matchQuery, limit, offset) as Array<{ rowid: number }>\n\n return {\n rowids: rows.map((r) => r.rowid),\n total,\n }\n } catch (error) {\n console.error('[FTS] Search failed, query:', matchQuery, error)\n return { rowids: [], total: 0 }\n }\n}\n","/**\n * Platform-agnostic streaming importer.\n *\n * Extracted from electron/main/worker/import/streamImport.ts.\n * Streams parsed data directly into SQLite with batched transactions,\n * deferred index creation, nickname history tracking, and FTS indexing.\n *\n * Both Electron and Server/CLI use this module via dependency injection:\n * the caller provides a DatabaseAdapter, progress callback, and optional hooks.\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport { CHAT_DB_INDEXES, generateSessionIndex } from '@openchatlab/core'\nimport type { ParsedMember, ParsedMessage } from '@openchatlab/shared-types'\nimport {\n streamParseFile,\n detectFormat,\n detectAllFormats,\n getFormatFeatureById,\n getPreprocessor,\n needsPreprocess,\n type ParsedMeta,\n type FormatFeature,\n type ParseProgress,\n} from '@openchatlab/parser'\nimport * as fs from 'fs'\nimport { buildFtsIndex } from '../fts'\n\n// ==================== Public interfaces ====================\n\nexport interface SkipReasons {\n noSenderId: number\n noAccountName: number\n invalidTimestamp: number\n noType: number\n}\n\nexport interface ImportDiagnostics {\n logFile: string | null\n detectedFormat: string | null\n messagesReceived: number\n messagesWritten: number\n messagesSkipped: number\n skipReasons: SkipReasons\n}\n\nexport interface StreamImportResult {\n success: boolean\n sessionId?: string\n error?: string\n diagnostics?: ImportDiagnostics\n}\n\nexport type ImportProgressCallback = (progress: ParseProgress) => void\n\nexport interface ImportLogger {\n info(message: string): void\n error(message: string, err?: Error): void\n perf(label: string, messageCount: number, batchSize?: number): void\n perfDetail(detail: string): void\n summary(messageCount: number, memberCount: number): void\n reset(): void\n init(sessionId: string): void\n getCurrentLogFile(): string | null\n}\n\nexport interface StreamImportDeps {\n /** Open a new database for writing (tables only, no indexes). */\n openDatabase(sessionId: string): DatabaseAdapter\n /** Delete a database file (and WAL/SHM) on failure. */\n deleteDatabase(sessionId: string): void\n /** Progress callback (IPC postMessage, SSE event, etc.) */\n onProgress: ImportProgressCallback\n /** Optional perf/diagnostic logger */\n logger?: ImportLogger\n /** Optional hook after import completes (e.g. write overview cache) */\n postImportHook?: (db: DatabaseAdapter, sessionId: string) => void | Promise<void>\n /** Generate a session ID. Defaults to timestamp + random. */\n generateSessionId?: () => string\n}\n\n// ==================== Core streaming import ====================\n\nconst BATCH_COMMIT_SIZE = 50000\nconst CHECKPOINT_INTERVAL = 200000\n\nfunction defaultGenerateSessionId(): string {\n const ts = Date.now()\n const rand = Math.random().toString(36).substring(2, 8)\n return `chat_${ts}_${rand}`\n}\n\n/**\n * High-performance streaming import: parse a file and write to DB\n * with batched transactions. Supports format auto-detection with fallback.\n */\nexport async function streamingImport(\n filePath: string,\n deps: StreamImportDeps,\n formatOptions?: Record<string, unknown>,\n externalSessionId?: string\n): Promise<StreamImportResult> {\n if (formatOptions?.formatId) {\n const formatId = formatOptions.formatId as string\n const feature = getFormatFeatureById(formatId)\n if (!feature) {\n return { success: false, error: 'error.unknown_format_id' }\n }\n return streamImportSingle(filePath, deps, feature, formatOptions, externalSessionId)\n }\n\n const candidates = detectAllFormats(filePath)\n if (candidates.length === 0) {\n return { success: false, error: 'error.unrecognized_format' }\n }\n\n if (candidates.length > 1) {\n return streamImportWithFallback(filePath, deps, candidates, formatOptions, externalSessionId)\n }\n\n return streamImportSingle(filePath, deps, candidates[0], formatOptions, externalSessionId)\n}\n\nasync function streamImportWithFallback(\n filePath: string,\n deps: StreamImportDeps,\n candidates: FormatFeature[],\n formatOptions?: Record<string, unknown>,\n externalSessionId?: string\n): Promise<StreamImportResult> {\n for (let i = 0; i < candidates.length; i++) {\n const candidate = candidates[i]\n deps.logger?.info(`[StreamImport] Trying format ${i + 1}/${candidates.length}: ${candidate.name} (${candidate.id})`)\n\n const result = await streamImportSingle(filePath, deps, candidate, formatOptions, externalSessionId)\n\n if (result.success) {\n if (i > 0) {\n deps.logger?.info(\n `[StreamImport] Fallback succeeded: ${candidate.name} (after ${i} failed attempt${i > 1 ? 's' : ''})`\n )\n }\n return result\n }\n\n if (i === candidates.length - 1) return result\n\n deps.logger?.info(`[StreamImport] Format ${candidate.name} produced 0 messages, falling back to next candidate...`)\n }\n\n return { success: false, error: 'error.no_messages' }\n}\n\nasync function streamImportSingle(\n filePath: string,\n deps: StreamImportDeps,\n formatFeature: FormatFeature,\n formatOptions?: Record<string, unknown>,\n externalSessionId?: string\n): Promise<StreamImportResult> {\n const { onProgress, logger } = deps\n const genId = deps.generateSessionId ?? defaultGenerateSessionId\n\n logger?.reset()\n const sessionId = externalSessionId || genId()\n logger?.init(sessionId)\n\n logger?.info(`File path: ${filePath}`)\n logger?.info(`Detected format: ${formatFeature.name} (${formatFeature.id})`)\n logger?.info(`Platform: ${formatFeature.platform}`)\n logger?.perf('Import started', 0)\n\n // Preprocess large files if needed\n let actualFilePath = filePath\n let tempFilePath: string | null = null\n const preprocessor = getPreprocessor(filePath)\n\n if (preprocessor && needsPreprocess(filePath)) {\n logger?.info('File needs preprocessing, simplifying large file...')\n onProgress({\n stage: 'parsing',\n bytesRead: 0,\n totalBytes: 0,\n messagesProcessed: 0,\n percentage: 0,\n message: '',\n })\n\n try {\n tempFilePath = await preprocessor.preprocess(filePath, (progress: ParseProgress) => {\n onProgress({ ...progress, message: '' })\n })\n actualFilePath = tempFilePath\n logger?.info(`Preprocessing done, temp file: ${tempFilePath}`)\n } catch (err) {\n const errorMsg = `Preprocessing failed: ${err instanceof Error ? err.message : String(err)}`\n logger?.error(errorMsg, err instanceof Error ? err : undefined)\n return { success: false, error: errorMsg }\n }\n }\n\n const db = deps.openDatabase(sessionId)\n\n const insertMeta = db.prepare(\n `INSERT INTO meta (name, platform, type, imported_at, group_id, group_avatar, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?)`\n )\n const insertMember = db.prepare(\n `INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, avatar, roles) VALUES (?, ?, ?, ?, ?)`\n )\n const getMemberId = db.prepare(`SELECT id FROM member WHERE platform_id = ?`)\n const insertMessage = db.prepare(\n `INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n )\n const insertNameHistory = db.prepare(\n `INSERT INTO member_name_history (member_id, name_type, name, start_ts, end_ts) VALUES (?, ?, ?, ?, ?)`\n )\n const updateMemberAccountName = db.prepare(`UPDATE member SET account_name = ? WHERE platform_id = ?`)\n const updateMemberGroupNickname = db.prepare(`UPDATE member SET group_nickname = ? WHERE platform_id = ?`)\n\n const memberIdMap = new Map<string, number>()\n const accountNameTracker = new Map<\n string,\n { currentName: string; lastSeenTs: number; history: Array<{ name: string; startTs: number }> }\n >()\n const groupNicknameTracker = new Map<\n string,\n { currentName: string; lastSeenTs: number; history: Array<{ name: string; startTs: number }> }\n >()\n\n let metaInserted = false\n let messageCountInBatch = 0\n let totalMessageCount = 0\n let lastCheckpointCount = 0\n let inTransaction = false\n\n const beginTransaction = () => {\n if (!inTransaction) {\n db.exec('BEGIN TRANSACTION')\n inTransaction = true\n }\n }\n\n const doCheckpoint = () => {\n try {\n db.pragma('wal_checkpoint(TRUNCATE)')\n } catch {\n /* ignore */\n }\n }\n\n const commitAndBeginNew = () => {\n if (inTransaction) {\n db.exec('COMMIT')\n inTransaction = false\n logger?.perf('Commit transaction', totalMessageCount, BATCH_COMMIT_SIZE)\n\n if (totalMessageCount - lastCheckpointCount >= CHECKPOINT_INTERVAL) {\n doCheckpoint()\n logger?.perf('WAL checkpoint', totalMessageCount)\n lastCheckpointCount = totalMessageCount\n }\n\n onProgress({\n stage: 'importing',\n bytesRead: 0,\n totalBytes: 0,\n messagesProcessed: totalMessageCount,\n percentage: 100,\n message: '',\n })\n }\n beginTransaction()\n }\n\n beginTransaction()\n\n let shouldDeleteDb = false\n let importError: string | null = null\n\n const callbackStats = {\n onProgressCalls: 0,\n onLogCalls: 0,\n onMetaCalls: 0,\n onMembersCalls: 0,\n onMessageBatchCalls: 0,\n totalMembersReceived: 0,\n totalMessagesReceived: 0,\n skippedNoSenderId: 0,\n skippedNoAccountName: 0,\n skippedInvalidTimestamp: 0,\n skippedNoType: 0,\n }\n\n logger?.info('Starting streamParseFile...')\n\n try {\n await streamParseFile(\n actualFilePath,\n {\n batchSize: 5000,\n formatOptions,\n\n onProgress: (progress: ParseProgress) => {\n callbackStats.onProgressCalls++\n onProgress(progress)\n },\n\n onLog: (level: string, message: string) => {\n callbackStats.onLogCalls++\n if (level === 'error') {\n logger?.error(message)\n } else {\n logger?.info(message)\n }\n },\n\n onMeta: (meta: ParsedMeta) => {\n callbackStats.onMetaCalls++\n if (!metaInserted) {\n logger?.info(`Writing meta: name=${meta.name}, type=${meta.type}, platform=${meta.platform}`)\n insertMeta.run(\n meta.name,\n meta.platform,\n meta.type,\n Math.floor(Date.now() / 1000),\n meta.groupId || null,\n meta.groupAvatar || null,\n meta.ownerId || null\n )\n metaInserted = true\n }\n },\n\n onMembers: (members: ParsedMember[]) => {\n callbackStats.onMembersCalls++\n callbackStats.totalMembersReceived += members.length\n logger?.info(`Received member batch: ${members.length} members`)\n for (const member of members) {\n insertMember.run(\n member.platformId,\n member.accountName || null,\n member.groupNickname || null,\n member.avatar || null,\n member.roles ? JSON.stringify(member.roles) : '[]'\n )\n const row = getMemberId.get(member.platformId) as { id: number } | undefined\n if (row) memberIdMap.set(member.platformId, row.id)\n }\n },\n\n onMessageBatch: (messages: ParsedMessage[]) => {\n callbackStats.onMessageBatchCalls++\n callbackStats.totalMessagesReceived += messages.length\n if (callbackStats.onMessageBatchCalls <= 3 || callbackStats.onMessageBatchCalls % 10 === 0) {\n logger?.info(`Received message batch #${callbackStats.onMessageBatchCalls}: ${messages.length} messages`)\n }\n\n let memberLookupTime = 0\n let memberInsertTime = 0\n let messageInsertTime = 0\n let nicknameTrackTime = 0\n let memberLookupCount = 0\n let memberInsertCount = 0\n let nicknameChangeCount = 0\n\n for (const msg of messages) {\n if (!msg.senderPlatformId) {\n callbackStats.skippedNoSenderId++\n continue\n }\n if (!msg.senderAccountName) {\n callbackStats.skippedNoAccountName++\n continue\n }\n if (msg.timestamp === undefined || msg.timestamp === null || isNaN(msg.timestamp)) {\n callbackStats.skippedInvalidTimestamp++\n continue\n }\n if (msg.type === undefined || msg.type === null) {\n callbackStats.skippedNoType++\n continue\n }\n\n let t0 = Date.now()\n if (!memberIdMap.has(msg.senderPlatformId)) {\n insertMember.run(\n msg.senderPlatformId,\n msg.senderAccountName || null,\n msg.senderGroupNickname || null,\n null,\n '[]'\n )\n const row = getMemberId.get(msg.senderPlatformId) as { id: number } | undefined\n if (row) memberIdMap.set(msg.senderPlatformId, row.id)\n memberInsertCount++\n memberInsertTime += Date.now() - t0\n } else {\n memberLookupCount++\n memberLookupTime += Date.now() - t0\n }\n\n const senderId = memberIdMap.get(msg.senderPlatformId)\n if (senderId === undefined) continue\n\n let safeContent: string | null = null\n if (msg.content != null) {\n safeContent = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)\n }\n\n t0 = Date.now()\n insertMessage.run(\n senderId,\n msg.senderAccountName || null,\n msg.senderGroupNickname || null,\n msg.timestamp,\n msg.type,\n safeContent,\n msg.replyToMessageId || null,\n msg.platformMessageId || null\n )\n messageInsertTime += Date.now() - t0\n messageCountInBatch++\n totalMessageCount++\n\n t0 = Date.now()\n trackNickname(accountNameTracker, msg.senderPlatformId, msg.senderAccountName, msg.timestamp)\n trackNickname(groupNicknameTracker, msg.senderPlatformId, msg.senderGroupNickname, msg.timestamp)\n nicknameTrackTime += Date.now() - t0\n // nicknameChangeCount is approximate but sufficient for logging\n nicknameChangeCount += accountNameTracker.get(msg.senderPlatformId)?.history.length === 1 ? 0 : 0\n\n if (messageCountInBatch >= BATCH_COMMIT_SIZE) {\n const detail =\n `[Detail] Member lookup: ${memberLookupTime}ms (${memberLookupCount} times) | ` +\n `Member insert: ${memberInsertTime}ms (${memberInsertCount} times) | ` +\n `Message insert: ${messageInsertTime}ms | ` +\n `Nickname tracking: ${nicknameTrackTime}ms (${nicknameChangeCount} changes)`\n logger?.perfDetail(detail)\n commitAndBeginNew()\n messageCountInBatch = 0\n memberLookupTime = 0\n memberInsertTime = 0\n messageInsertTime = 0\n nicknameTrackTime = 0\n memberLookupCount = 0\n memberInsertCount = 0\n nicknameChangeCount = 0\n }\n }\n },\n },\n formatFeature.id\n )\n\n if (inTransaction) {\n db.exec('COMMIT')\n inTransaction = false\n }\n\n // Flush nickname history in batch\n onProgress({\n stage: 'importing',\n bytesRead: 0,\n totalBytes: 0,\n messagesProcessed: totalMessageCount,\n percentage: 100,\n message: '',\n })\n logger?.perf('Writing nickname history', totalMessageCount)\n\n db.exec('BEGIN TRANSACTION')\n let historyCount = 0\n\n flushNicknameHistory(accountNameTracker, 'account_name', memberIdMap, insertNameHistory, updateMemberAccountName)\n flushNicknameHistory(\n groupNicknameTracker,\n 'group_nickname',\n memberIdMap,\n insertNameHistory,\n updateMemberGroupNickname\n )\n historyCount = countHistory(accountNameTracker) + countHistory(groupNicknameTracker)\n\n db.exec('COMMIT')\n logger?.perf(`Nickname history written (${historyCount} entries)`, totalMessageCount)\n\n // Create indexes (deferred for performance)\n onProgress({\n stage: 'importing',\n bytesRead: 0,\n totalBytes: 0,\n messagesProcessed: totalMessageCount,\n percentage: 100,\n message: '',\n })\n logger?.perf('Creating indexes', totalMessageCount)\n db.exec(CHAT_DB_INDEXES)\n logger?.perf('Indexes created', totalMessageCount)\n\n // Build FTS index\n try {\n buildFtsIndex(db)\n logger?.perf('FTS index built', totalMessageCount)\n } catch (ftsError) {\n logger?.error('FTS index build failed (non-fatal)', ftsError instanceof Error ? ftsError : undefined)\n }\n\n // Final WAL checkpoint\n onProgress({\n stage: 'importing',\n bytesRead: 0,\n totalBytes: 0,\n messagesProcessed: totalMessageCount,\n percentage: 100,\n message: '',\n })\n doCheckpoint()\n logger?.perf('WAL checkpoint done', totalMessageCount)\n\n // Build session index (segment / message_context tables)\n try {\n generateSessionIndex(db)\n logger?.perf('Session index built', totalMessageCount)\n } catch {\n /* non-fatal */\n }\n\n // Post-import hook (e.g. overview cache)\n try {\n await deps.postImportHook?.(db, sessionId)\n if (deps.postImportHook) logger?.perf('Post-import hook done', totalMessageCount)\n } catch {\n /* non-fatal */\n }\n\n logger?.perf('Import completed', totalMessageCount)\n\n // Diagnostic logging\n logger?.info(`=== Parser Callback Stats ===`)\n logger?.info(`onProgress calls: ${callbackStats.onProgressCalls}`)\n logger?.info(`onLog calls: ${callbackStats.onLogCalls}`)\n logger?.info(`onMeta calls: ${callbackStats.onMetaCalls}`)\n logger?.info(\n `onMembers calls: ${callbackStats.onMembersCalls}, total members: ${callbackStats.totalMembersReceived}`\n )\n logger?.info(\n `onMessageBatch calls: ${callbackStats.onMessageBatchCalls}, total messages: ${callbackStats.totalMessagesReceived}`\n )\n if (\n callbackStats.skippedNoSenderId > 0 ||\n callbackStats.skippedNoAccountName > 0 ||\n callbackStats.skippedInvalidTimestamp > 0 ||\n callbackStats.skippedNoType > 0\n ) {\n logger?.info(`=== Skipped Messages Stats ===`)\n if (callbackStats.skippedNoSenderId > 0)\n logger?.info(` missing senderPlatformId: ${callbackStats.skippedNoSenderId}`)\n if (callbackStats.skippedNoAccountName > 0)\n logger?.info(` missing senderAccountName: ${callbackStats.skippedNoAccountName}`)\n if (callbackStats.skippedInvalidTimestamp > 0)\n logger?.info(` invalid timestamp: ${callbackStats.skippedInvalidTimestamp}`)\n if (callbackStats.skippedNoType > 0) logger?.info(` missing type: ${callbackStats.skippedNoType}`)\n }\n\n logger?.summary(totalMessageCount, memberIdMap.size)\n\n if (totalMessageCount === 0) {\n logger?.error(\n `Import failed: no messages parsed (received ${callbackStats.totalMessagesReceived} messages, all skipped or none received)`\n )\n shouldDeleteDb = true\n importError = 'error.no_messages'\n }\n } catch (error) {\n logger?.error('Import failed', error instanceof Error ? error : undefined)\n if (inTransaction) {\n try {\n db.exec('ROLLBACK')\n } catch {\n /* ignore */\n }\n }\n shouldDeleteDb = true\n importError = error instanceof Error ? error.message : String(error)\n } finally {\n db.close()\n\n if (tempFilePath && preprocessor) {\n preprocessor.cleanup(tempFilePath)\n }\n\n if (shouldDeleteDb) {\n deps.deleteDatabase(sessionId)\n }\n }\n\n const diagnostics: ImportDiagnostics = {\n logFile: logger?.getCurrentLogFile() ?? null,\n detectedFormat: formatFeature ? `${formatFeature.name} (${formatFeature.id})` : null,\n messagesReceived: callbackStats.totalMessagesReceived,\n messagesWritten: totalMessageCount,\n messagesSkipped:\n callbackStats.skippedNoSenderId +\n callbackStats.skippedNoAccountName +\n callbackStats.skippedInvalidTimestamp +\n callbackStats.skippedNoType,\n skipReasons: {\n noSenderId: callbackStats.skippedNoSenderId,\n noAccountName: callbackStats.skippedNoAccountName,\n invalidTimestamp: callbackStats.skippedInvalidTimestamp,\n noType: callbackStats.skippedNoType,\n },\n }\n\n if (importError) {\n return { success: false, error: importError, diagnostics }\n }\n return { success: true, sessionId, diagnostics }\n}\n\n// ==================== Dry-run analysis ====================\n\nexport interface AnalyzeNewImportResult {\n totalMessages: number\n totalMembers: number\n meta: { name: string; platform: string; type: string } | null\n error?: string\n}\n\nexport async function analyzeNewImport(\n filePath: string,\n onProgress: ImportProgressCallback\n): Promise<AnalyzeNewImportResult> {\n const formatFeature = detectFormat(filePath)\n if (!formatFeature) {\n return { totalMessages: 0, totalMembers: 0, meta: null, error: 'error.unrecognized_format' }\n }\n\n let meta: { name: string; platform: string; type: string } | null = null\n const memberSet = new Set<string>()\n let totalMessages = 0\n\n await streamParseFile(filePath, {\n onMeta: (parsedMeta: ParsedMeta) => {\n meta = { name: parsedMeta.name, platform: parsedMeta.platform, type: parsedMeta.type }\n },\n onMembers: (members: ParsedMember[]) => {\n for (const m of members) memberSet.add(m.platformId)\n },\n onProgress: (progress: ParseProgress) => {\n onProgress(progress)\n },\n onMessageBatch: (batch: ParsedMessage[]) => {\n for (const msg of batch) {\n totalMessages++\n if (!memberSet.has(msg.senderPlatformId)) memberSet.add(msg.senderPlatformId)\n }\n },\n })\n\n return { totalMessages, totalMembers: memberSet.size, meta }\n}\n\n// ==================== Temp DB for merge preview ====================\n\nexport interface StreamParseFileInfoResult {\n name: string\n format: string\n platform: string\n messageCount: number\n memberCount: number\n fileSize: number\n tempDbPath: string\n}\n\nexport interface StreamParseFileInfoDeps {\n createTempDatabase(filePath: string): { db: DatabaseAdapter; tempDbPath: string }\n onProgress: ImportProgressCallback\n}\n\nexport async function streamParseFileInfo(\n filePath: string,\n deps: StreamParseFileInfoDeps\n): Promise<StreamParseFileInfoResult> {\n const formatFeature = detectFormat(filePath)\n if (!formatFeature) {\n throw new Error('Unrecognized file format')\n }\n\n const fileSize = fs.statSync(filePath).size\n\n deps.onProgress({\n stage: 'parsing',\n bytesRead: 0,\n totalBytes: fileSize,\n messagesProcessed: 0,\n percentage: 0,\n message: '',\n })\n\n const { db, tempDbPath } = deps.createTempDatabase(filePath)\n\n const insertMeta = db.prepare(\n 'INSERT INTO meta (name, platform, type, group_id, group_avatar, owner_id) VALUES (?, ?, ?, ?, ?, ?)'\n )\n const insertMember = db.prepare(\n 'INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, avatar) VALUES (?, ?, ?, ?)'\n )\n const insertMessage = db.prepare(\n `INSERT INTO message (sender_platform_id, sender_account_name, sender_group_nickname, timestamp, type, content)\n VALUES (?, ?, ?, ?, ?, ?)`\n )\n\n let meta: ParsedMeta = { name: 'Unknown', platform: formatFeature.platform, type: 0 as any }\n const memberSet = new Set<string>()\n let messageCount = 0\n let metaInserted = false\n\n db.exec('BEGIN TRANSACTION')\n\n try {\n await streamParseFile(filePath, {\n batchSize: fileSize > 100 * 1024 * 1024 ? 2000 : 5000,\n\n onProgress: (progress: ParseProgress) => {\n deps.onProgress(progress)\n },\n\n onMeta: (parsedMeta: ParsedMeta) => {\n meta = parsedMeta\n if (!metaInserted) {\n insertMeta.run(\n parsedMeta.name,\n parsedMeta.platform,\n parsedMeta.type,\n parsedMeta.groupId || null,\n parsedMeta.groupAvatar || null,\n parsedMeta.ownerId || null\n )\n metaInserted = true\n }\n },\n\n onMembers: (parsedMembers: ParsedMember[]) => {\n for (const m of parsedMembers) {\n if (!memberSet.has(m.platformId)) {\n memberSet.add(m.platformId)\n insertMember.run(m.platformId, m.accountName || null, m.groupNickname || null, m.avatar || null)\n }\n }\n },\n\n onMessageBatch: (batch: ParsedMessage[]) => {\n for (const msg of batch) {\n if (!memberSet.has(msg.senderPlatformId)) {\n memberSet.add(msg.senderPlatformId)\n insertMember.run(msg.senderPlatformId, msg.senderAccountName || null, msg.senderGroupNickname || null, null)\n }\n\n insertMessage.run(\n msg.senderPlatformId,\n msg.senderAccountName || null,\n msg.senderGroupNickname || null,\n msg.timestamp,\n msg.type,\n msg.content || null\n )\n messageCount++\n }\n },\n })\n\n db.exec('COMMIT')\n db.close()\n\n return {\n name: meta.name,\n format: formatFeature.name,\n platform: meta.platform,\n messageCount,\n memberCount: memberSet.size,\n fileSize,\n tempDbPath,\n }\n } catch (error) {\n try {\n db.exec('ROLLBACK')\n } catch {\n /* ignore */\n }\n db.close()\n\n try {\n if (fs.existsSync(tempDbPath)) fs.unlinkSync(tempDbPath)\n } catch {\n /* ignore */\n }\n\n throw error\n }\n}\n\n// ==================== Internal helpers ====================\n\ntype NicknameTracker = Map<\n string,\n { currentName: string; lastSeenTs: number; history: Array<{ name: string; startTs: number }> }\n>\n\nfunction trackNickname(\n tracker: NicknameTracker,\n platformId: string,\n name: string | undefined | null,\n timestamp: number\n): void {\n if (!name) return\n // For account_name tracking, skip if name equals platformId\n const existing = tracker.get(platformId)\n if (!existing) {\n tracker.set(platformId, {\n currentName: name,\n lastSeenTs: timestamp,\n history: [{ name, startTs: timestamp }],\n })\n } else if (existing.currentName !== name) {\n existing.history.push({ name, startTs: timestamp })\n existing.currentName = name\n existing.lastSeenTs = timestamp\n } else {\n existing.lastSeenTs = timestamp\n }\n}\n\ninterface PreparedStatement {\n run(...args: unknown[]): unknown\n}\n\nfunction flushNicknameHistory(\n tracker: NicknameTracker,\n nameType: string,\n memberIdMap: Map<string, number>,\n insertNameHistory: PreparedStatement,\n updateMemberName: PreparedStatement\n): void {\n for (const [platformId, data] of tracker.entries()) {\n if (!platformId || platformId === '0' || platformId === 'undefined') continue\n\n const senderId = memberIdMap.get(platformId)\n if (!senderId) continue\n\n const uniqueNames = new Map<string, { startTs: number; lastTs: number }>()\n for (const h of data.history) {\n const existing = uniqueNames.get(h.name)\n if (!existing) {\n uniqueNames.set(h.name, { startTs: h.startTs, lastTs: h.startTs })\n } else {\n existing.lastTs = h.startTs\n }\n }\n\n // For account_name, skip the platformId itself\n if (nameType === 'account_name') {\n uniqueNames.delete(platformId)\n }\n\n if (uniqueNames.size <= 1) {\n updateMemberName.run(data.currentName, platformId)\n continue\n }\n\n const sortedHistory = Array.from(uniqueNames.entries()).sort((a, b) => a[1].startTs - b[1].startTs)\n for (let i = 0; i < sortedHistory.length; i++) {\n const [name, { startTs }] = sortedHistory[i]\n const endTs = i < sortedHistory.length - 1 ? sortedHistory[i + 1][1].startTs : null\n insertNameHistory.run(senderId, nameType, name, startTs, endTs)\n }\n\n updateMemberName.run(data.currentName, platformId)\n }\n}\n\nfunction countHistory(tracker: NicknameTracker): number {\n let count = 0\n for (const [, data] of tracker.entries()) {\n if (data.history.length > 1) count += data.history.length\n }\n return count\n}\n","/**\n * Platform-agnostic incremental importer.\n *\n * Extracted from electron/main/worker/import/incrementalImport.ts.\n * Appends new messages to an existing session, using dual-path dedup:\n * platformMessageId (preferred) + content hash (fallback).\n *\n * Callers provide DatabaseAdapter + progress callback via dependency injection.\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport { generateMessageKey, generateSessionIndex, generateIncrementalSessionIndex } from '@openchatlab/core'\nimport { streamParseFile, detectFormat, type ParseProgress } from '@openchatlab/parser'\nimport { insertFtsEntries, hasFtsTable } from '../fts'\nimport type { ImportProgressCallback } from './streaming-importer'\n\n// ==================== Public interfaces ====================\n\nexport interface ImportOptions {\n metaUpdateMode?: 'patch' | 'none'\n memberUpdateMode?: 'upsert' | 'none'\n}\n\nexport interface IncrementalAnalyzeResult {\n newMessageCount: number\n duplicateCount: number\n totalInFile: number\n error?: string\n}\n\ninterface ErrorSample {\n index: number\n reason: string\n detail: string\n}\n\nexport interface IncrementalImportResult {\n success: boolean\n newMessageCount: number\n error?: string\n batch?: {\n receivedCount: number\n writtenCount: number\n duplicateCount: number\n errorCount: number\n errorReasonCounts: Record<string, number>\n errorSample: ErrorSample[]\n }\n session?: {\n totalCount: number\n memberCount: number\n firstTimestamp: number\n lastTimestamp: number\n }\n updates?: {\n metaUpdated: boolean\n membersAdded: number\n membersUpdated: number\n }\n}\n\nexport interface IncrementalImportDeps {\n /** Open existing session DB for read-only access (analyze) or read-write (import). */\n openDatabase(sessionId: string, readonly?: boolean): DatabaseAdapter\n onProgress: ImportProgressCallback\n /** Optional hook after incremental import (e.g. update overview cache). */\n postImportHook?: (db: DatabaseAdapter, sessionId: string) => void | Promise<void>\n}\n\n// ==================== Internal helpers ====================\n\nfunction loadExistingDedup(db: DatabaseAdapter): {\n existingPlatformMsgIds: Set<string>\n existingKeys: Set<string>\n} {\n const existingPlatformMsgIds = new Set<string>()\n const existingKeys = new Set<string>()\n\n const pmidRows = db\n .prepare('SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL')\n .all() as Array<{ platform_message_id: string }>\n for (const row of pmidRows) {\n existingPlatformMsgIds.add(row.platform_message_id)\n }\n\n const hashRows = db\n .prepare(\n `SELECT ts, m.platform_id as sender_platform_id, content\n FROM message msg\n JOIN member m ON msg.sender_id = m.id`\n )\n .all() as Array<{ ts: number; sender_platform_id: string; content: string | null }>\n for (const row of hashRows) {\n existingKeys.add(generateMessageKey(row.ts, row.sender_platform_id, row.content))\n }\n\n return { existingPlatformMsgIds, existingKeys }\n}\n\nfunction isDuplicate(\n msg: { platformMessageId?: string; timestamp: number; senderPlatformId: string; content: string | null },\n existingPlatformMsgIds: Set<string>,\n existingKeys: Set<string>\n): boolean {\n if (msg.platformMessageId) {\n if (existingPlatformMsgIds.has(msg.platformMessageId)) return true\n existingPlatformMsgIds.add(msg.platformMessageId)\n return false\n }\n const key = generateMessageKey(msg.timestamp, msg.senderPlatformId, msg.content)\n if (existingKeys.has(key)) return true\n existingKeys.add(key)\n return false\n}\n\nfunction normalizeTimestamp(timestamp: unknown): number | null {\n const value = typeof timestamp === 'string' && timestamp.trim() !== '' ? Number(timestamp) : timestamp\n return typeof value === 'number' && value > 0 && Number.isFinite(value) ? value : null\n}\n\n// ==================== Analyze (dry-run) ====================\n\nexport async function analyzeIncrementalImport(\n sessionId: string,\n filePath: string,\n deps: IncrementalImportDeps\n): Promise<IncrementalAnalyzeResult> {\n const formatFeature = detectFormat(filePath)\n if (!formatFeature) {\n return { error: 'error.unrecognized_format', newMessageCount: 0, duplicateCount: 0, totalInFile: 0 }\n }\n\n let db: DatabaseAdapter\n try {\n db = deps.openDatabase(sessionId, true)\n } catch {\n return { error: 'error.session_not_found', newMessageCount: 0, duplicateCount: 0, totalInFile: 0 }\n }\n\n const { existingPlatformMsgIds, existingKeys } = loadExistingDedup(db)\n db.close()\n\n let totalInFile = 0\n let newMessageCount = 0\n let duplicateCount = 0\n\n await streamParseFile(filePath, {\n onMeta: () => {},\n onMembers: () => {},\n onProgress: (progress: ParseProgress) => {\n deps.onProgress(progress)\n },\n onMessageBatch: (batch) => {\n for (const msg of batch) {\n totalInFile++\n const timestamp = normalizeTimestamp(msg.timestamp)\n if (timestamp === null) continue\n\n if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys)) {\n duplicateCount++\n } else {\n newMessageCount++\n }\n }\n },\n })\n\n return { newMessageCount, duplicateCount, totalInFile }\n}\n\n// ==================== Execute incremental import ====================\n\nexport async function incrementalImport(\n sessionId: string,\n filePath: string,\n deps: IncrementalImportDeps,\n options?: ImportOptions\n): Promise<IncrementalImportResult> {\n const formatFeature = detectFormat(filePath)\n if (!formatFeature) {\n return { success: false, newMessageCount: 0, error: 'error.unrecognized_format' }\n }\n\n let db: DatabaseAdapter\n try {\n db = deps.openDatabase(sessionId, false)\n } catch {\n return { success: false, newMessageCount: 0, error: 'error.session_not_found' }\n }\n\n const metaUpdateMode = options?.metaUpdateMode ?? 'patch'\n const memberUpdateMode = options?.memberUpdateMode ?? 'upsert'\n\n try {\n const { existingPlatformMsgIds, existingKeys } = loadExistingDedup(db)\n\n const memberIdMap = new Map<string, number>()\n const existingMembers = db.prepare('SELECT id, platform_id FROM member').all() as Array<{\n id: number\n platform_id: string\n }>\n for (const m of existingMembers) {\n memberIdMap.set(m.platform_id, m.id)\n }\n\n const upsertMember = db.prepare(`\n INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(platform_id) DO UPDATE SET\n account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name),\n group_nickname = COALESCE(NULLIF(excluded.group_nickname, ''), group_nickname),\n avatar = COALESCE(NULLIF(excluded.avatar, ''), avatar),\n roles = CASE WHEN excluded.roles != '[]' THEN excluded.roles ELSE roles END\n `)\n\n const insertMemberMinimal = db.prepare(`\n INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, avatar)\n VALUES (?, ?, ?, ?)\n `)\n\n const getMemberId = db.prepare('SELECT id FROM member WHERE platform_id = ?')\n\n const insertMessage = db.prepare(`\n INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `)\n\n const updateMeta = db.prepare(`\n UPDATE meta SET\n name = COALESCE(NULLIF(?, ''), name),\n group_id = COALESCE(NULLIF(?, ''), group_id),\n group_avatar = COALESCE(NULLIF(?, ''), group_avatar),\n owner_id = COALESCE(NULLIF(?, ''), owner_id),\n imported_at = ?\n `)\n\n const preWriteMaxTs =\n (db.prepare('SELECT MAX(ts) as max_ts FROM message').get() as { max_ts: number | null })?.max_ts ?? 0\n\n db.exec('BEGIN TRANSACTION')\n\n let newMessageCount = 0\n let duplicateCount = 0\n let processedCount = 0\n let minWrittenTs = Infinity\n let metaUpdated = false\n let membersAdded = 0\n let membersUpdated = 0\n let errorCount = 0\n const errorReasonCounts: Record<string, number> = {}\n const errorSamples: ErrorSample[] = []\n const MAX_ERROR_SAMPLES = 5\n const BATCH_SIZE = 5000\n\n function trackError(index: number, reason: string, detail: string) {\n errorCount++\n errorReasonCounts[reason] = (errorReasonCounts[reason] || 0) + 1\n if (errorSamples.length < MAX_ERROR_SAMPLES) {\n errorSamples.push({ index, reason, detail })\n }\n }\n\n const newFtsEntries: Array<{ id: number; content: string | null }> = []\n\n await streamParseFile(filePath, {\n onMeta: (meta) => {\n if (metaUpdateMode === 'none') return\n updateMeta.run(\n meta.name || '',\n meta.groupId || '',\n meta.groupAvatar || '',\n meta.ownerId || '',\n Math.floor(Date.now() / 1000)\n )\n metaUpdated = true\n },\n onMembers: (members) => {\n if (memberUpdateMode === 'none') return\n for (const m of members) {\n const existed = memberIdMap.has(m.platformId)\n upsertMember.run(\n m.platformId,\n m.accountName || null,\n m.groupNickname || null,\n m.avatar || null,\n m.roles ? JSON.stringify(m.roles) : '[]'\n )\n if (!existed) {\n const row = getMemberId.get(m.platformId) as { id: number } | undefined\n if (row) memberIdMap.set(m.platformId, row.id)\n membersAdded++\n } else {\n membersUpdated++\n }\n }\n },\n onProgress: (progress: ParseProgress) => {\n deps.onProgress(progress)\n },\n onMessageBatch: (batch) => {\n for (const msg of batch) {\n processedCount++\n\n if (!msg.senderPlatformId) {\n trackError(processedCount, 'MISSING_SENDER', 'sender field is empty')\n continue\n }\n if (msg.timestamp === undefined || msg.timestamp === null) {\n trackError(processedCount, 'MISSING_TIMESTAMP', 'timestamp field is missing')\n continue\n }\n const timestamp = normalizeTimestamp(msg.timestamp)\n if (timestamp === null) {\n trackError(processedCount, 'INVALID_TIMESTAMP', `timestamp value: ${msg.timestamp}`)\n continue\n }\n\n if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys)) {\n duplicateCount++\n continue\n }\n\n let memberId = memberIdMap.get(msg.senderPlatformId)\n if (!memberId) {\n insertMemberMinimal.run(\n msg.senderPlatformId,\n msg.senderAccountName || null,\n msg.senderGroupNickname || null,\n null\n )\n const row = getMemberId.get(msg.senderPlatformId) as { id: number } | undefined\n if (row) {\n memberId = row.id\n memberIdMap.set(msg.senderPlatformId, memberId)\n membersAdded++\n }\n }\n if (!memberId) continue\n\n const msgResult = insertMessage.run(\n memberId,\n msg.senderAccountName || null,\n msg.senderGroupNickname || null,\n timestamp,\n msg.type,\n msg.content || null,\n msg.replyToMessageId || null,\n msg.platformMessageId || null\n )\n\n newFtsEntries.push({\n id: Number((msgResult as any).lastInsertRowid ?? 0),\n content: msg.content || null,\n })\n if (timestamp < minWrittenTs) minWrittenTs = timestamp\n newMessageCount++\n }\n\n if (processedCount % BATCH_SIZE === 0) {\n deps.onProgress({\n stage: 'saving',\n bytesRead: 0,\n totalBytes: 0,\n messagesProcessed: processedCount,\n percentage: 50,\n message: `Processed ${processedCount}, added ${newMessageCount}`,\n })\n }\n },\n })\n\n db.exec('COMMIT')\n\n if (!metaUpdated) {\n db.prepare('UPDATE meta SET imported_at = ?').run(Math.floor(Date.now() / 1000))\n }\n\n // Incremental FTS update\n if (newFtsEntries.length > 0 && hasFtsTable(db)) {\n try {\n insertFtsEntries(db, newFtsEntries)\n } catch {\n /* FTS failure is non-fatal */\n }\n }\n\n // Incremental session index (segment / message_context tables).\n // Use full rebuild for backfill batches: generateIncrementalSessionIndex\n // compares the first new message with the latest existing segment, so an\n // older backfilled message would be incorrectly attached to the newest segment.\n if (newMessageCount > 0) {\n try {\n if (minWrittenTs < preWriteMaxTs) {\n generateSessionIndex(db)\n } else {\n generateIncrementalSessionIndex(db)\n }\n } catch {\n /* non-fatal */\n }\n }\n\n const sessionStats = db\n .prepare(\n `SELECT\n COUNT(*) as totalCount,\n MIN(ts) as firstTimestamp,\n MAX(ts) as lastTimestamp\n FROM message`\n )\n .get() as { totalCount: number; firstTimestamp: number; lastTimestamp: number }\n const memberCountRow = db.prepare('SELECT COUNT(*) as count FROM member').get() as { count: number }\n\n // Post-import hook (e.g. overview cache)\n try {\n await deps.postImportHook?.(db, sessionId)\n } catch {\n /* non-fatal */\n }\n\n db.close()\n\n deps.onProgress({\n stage: 'done',\n bytesRead: 0,\n totalBytes: 0,\n messagesProcessed: processedCount,\n percentage: 100,\n message: `Import complete, added ${newMessageCount} messages`,\n })\n\n return {\n success: true,\n newMessageCount,\n batch: {\n receivedCount: processedCount,\n writtenCount: newMessageCount,\n duplicateCount,\n errorCount,\n errorReasonCounts,\n errorSample: errorSamples,\n },\n session: {\n totalCount: sessionStats.totalCount,\n memberCount: memberCountRow.count,\n firstTimestamp: sessionStats.firstTimestamp,\n lastTimestamp: sessionStats.lastTimestamp,\n },\n updates: {\n metaUpdated,\n membersAdded,\n membersUpdated,\n },\n }\n } catch (error) {\n try {\n db.exec('ROLLBACK')\n } catch {\n /* ignore */\n }\n db.close()\n\n console.error('[IncrementalImport] Error:', error)\n return { success: false, newMessageCount: 0, error: String(error) }\n }\n}\n","export type ArchiveImportErrorCode =\n | 'error.archive_unsupported'\n | 'error.archive_encrypted'\n | 'error.archive_corrupt'\n | 'error.archive_unsafe_path'\n | 'error.archive_limit_exceeded'\n | 'error.google_chat_structure_not_found'\n | 'error.google_chat_conversation_incomplete'\n | 'error.import_source_not_found'\n | 'error.import_source_expired'\n\nexport class ArchiveImportError extends Error {\n constructor(\n readonly code: ArchiveImportErrorCode,\n message: string,\n options?: ErrorOptions\n ) {\n super(message, options)\n this.name = 'ArchiveImportError'\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport type { Entry, ZipFile } from 'yauzl'\nimport { openPromise } from 'yauzl'\nimport { ArchiveImportError } from './errors'\nimport type {\n ArchiveEntryStreamOpener,\n ArchiveEntrySummary,\n ArchiveEntryVisitor,\n ZipArchiveReaderOptions,\n} from './types'\n\nconst DEFAULT_MAX_ENTRIES = 100_000\nconst DEFAULT_MAX_ENTRY_BYTES = 20 * 1024 * 1024 * 1024\nconst DEFAULT_MAX_COMPRESSION_RATIO = 10_000\n\nexport function validateArchiveEntryName(fileName: string): void {\n if (\n fileName.startsWith('/') ||\n /^[A-Za-z]:\\//.test(fileName) ||\n fileName.includes('\\\\') ||\n fileName.split('/').includes('..')\n ) {\n throw new ArchiveImportError('error.archive_unsafe_path', `Unsafe archive entry path: ${fileName}`)\n }\n}\n\nfunction mapArchiveError(error: unknown): ArchiveImportError {\n if (error instanceof ArchiveImportError) return error\n const message = error instanceof Error ? error.message : String(error)\n return new ArchiveImportError('error.archive_corrupt', `Invalid ZIP archive: ${message}`, {\n cause: error,\n })\n}\n\nfunction toSummary(entry: Entry): ArchiveEntrySummary {\n return {\n name: entry.fileName,\n compressedSize: entry.compressedSize,\n uncompressedSize: entry.uncompressedSize,\n compressionMethod: entry.compressionMethod,\n isDirectory: entry.fileName.endsWith('/'),\n }\n}\n\n/**\n * 对 ZIP 中央目录和条目元数据做统一校验。所有调用方必须经过这里,\n * 防止 resolver 在看到恶意路径或异常压缩比后继续处理。\n */\nfunction validateEntry(entry: Entry, options: Required<ZipArchiveReaderOptions>): void {\n validateArchiveEntryName(entry.fileName)\n\n if (entry.isEncrypted()) {\n throw new ArchiveImportError('error.archive_encrypted', `Encrypted ZIP entry is not supported: ${entry.fileName}`)\n }\n if (!entry.canDecodeFileData() || (entry.compressionMethod !== 0 && entry.compressionMethod !== 8)) {\n throw new ArchiveImportError(\n 'error.archive_unsupported',\n `Unsupported ZIP compression method for entry: ${entry.fileName}`\n )\n }\n if (entry.uncompressedSize > options.maxEntryBytes) {\n throw new ArchiveImportError('error.archive_limit_exceeded', `ZIP entry exceeds the size limit: ${entry.fileName}`)\n }\n\n if (entry.uncompressedSize > 0) {\n const ratio = entry.compressedSize === 0 ? Number.POSITIVE_INFINITY : entry.uncompressedSize / entry.compressedSize\n if (ratio > options.maxCompressionRatio) {\n throw new ArchiveImportError(\n 'error.archive_limit_exceeded',\n `ZIP entry exceeds the compression ratio limit: ${entry.fileName}`\n )\n }\n }\n}\n\nexport class ZipArchiveReader {\n private readonly options: Required<ZipArchiveReaderOptions>\n\n constructor(\n private readonly archivePath: string,\n options: ZipArchiveReaderOptions = {}\n ) {\n this.options = {\n maxEntries: options.maxEntries ?? DEFAULT_MAX_ENTRIES,\n maxEntryBytes: options.maxEntryBytes ?? DEFAULT_MAX_ENTRY_BYTES,\n maxCompressionRatio: options.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO,\n }\n }\n\n private async open(): Promise<ZipFile> {\n try {\n return await openPromise(this.archivePath, {\n autoClose: true,\n decodeStrings: true,\n validateEntrySizes: true,\n strictFileNames: true,\n })\n } catch (error) {\n throw mapArchiveError(error)\n }\n }\n\n async listEntries(): Promise<ArchiveEntrySummary[]> {\n const entries: ArchiveEntrySummary[] = []\n await this.visitEntries((entry) => {\n entries.push(entry)\n })\n return entries\n }\n\n /**\n * 每次只推进一个 ZIP 条目;visitor 若打开流,必须等待流消费结束后返回。\n * 这样大 ZIP 不会同时创建大量 inflate stream。\n */\n async visitEntries(visitor: ArchiveEntryVisitor): Promise<void> {\n const zipFile = await this.open()\n let count = 0\n try {\n for await (const entry of zipFile.eachEntry()) {\n count++\n if (count > this.options.maxEntries) {\n throw new ArchiveImportError('error.archive_limit_exceeded', 'ZIP archive contains too many entries')\n }\n validateEntry(entry, this.options)\n\n let opened = false\n const openStream: ArchiveEntryStreamOpener = async () => {\n if (opened) {\n throw new ArchiveImportError('error.archive_corrupt', `ZIP entry stream opened twice: ${entry.fileName}`)\n }\n opened = true\n try {\n return await zipFile.openReadStreamPromise(entry)\n } catch (error) {\n throw mapArchiveError(error)\n }\n }\n await visitor(toSummary(entry), openStream)\n }\n } catch (error) {\n throw mapArchiveError(error)\n } finally {\n if (zipFile.isOpen) zipFile.close()\n }\n }\n\n async pipeEntries(targets: ReadonlyMap<string, string>): Promise<void> {\n const remaining = new Set(targets.keys())\n await this.visitEntries(async (entry, openStream) => {\n const destination = targets.get(entry.name)\n if (!destination || entry.isDirectory) return\n fs.mkdirSync(path.dirname(destination), { recursive: true })\n await pipeline(await openStream(), fs.createWriteStream(destination, { flags: 'wx' }))\n remaining.delete(entry.name)\n })\n\n if (remaining.size > 0) {\n throw new ArchiveImportError(\n 'error.archive_corrupt',\n `ZIP archive is missing expected entries: ${Array.from(remaining).join(', ')}`\n )\n }\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type { Readable } from 'node:stream'\nimport streamChain from 'stream-chain'\nimport streamJson from 'stream-json'\nimport pickModule from 'stream-json/filters/Pick.js'\nimport streamValuesModule from 'stream-json/streamers/StreamValues.js'\nimport { ArchiveImportError } from './errors'\nimport type { ZipArchiveReader } from './archive-reader'\nimport type { ArchiveEntrySummary, ArchiveResolver, MaterializedImport, PreparedImportChat } from './types'\n\nconst { chain } = streamChain\nconst { parser } = streamJson\nconst { pick } = pickModule\nconst { streamValues } = streamValuesModule\n\nconst GROUP_FILE = /^Takeout\\/Google Chat\\/Groups\\/((DM|Space) [^/]+)\\/(group_info|messages)\\.json$/\nconst USER_INFO_FILE = /^Takeout\\/Google Chat\\/Users\\/[^/]+\\/user_info\\.json$/\nconst TAKEOUT_ROOT = /^Takeout\\/([^/]+)\\//\nconst MAX_METADATA_BYTES = 5 * 1024 * 1024\n\ninterface GoogleChatUser {\n email?: string\n name?: string\n}\n\ninterface GoogleChatUserInfo {\n user?: GoogleChatUser\n}\n\ninterface GoogleChatGroupInfo {\n name?: string\n members?: GoogleChatUser[]\n}\n\ninterface ConversationDraft {\n chatId: string\n kind: 'DM' | 'Space'\n groupInfoEntry?: string\n messagesEntry?: string\n groupInfo?: GoogleChatGroupInfo\n messageCount?: number\n}\n\nasync function readSmallJson<T>(stream: Readable, maxBytes = MAX_METADATA_BYTES): Promise<T> {\n const chunks: Buffer[] = []\n let total = 0\n for await (const chunk of stream as AsyncIterable<Buffer | string>) {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)\n total += buffer.length\n if (total > maxBytes) {\n throw new ArchiveImportError('error.archive_limit_exceeded', 'Google Chat metadata exceeds the size limit')\n }\n chunks.push(buffer)\n }\n try {\n return JSON.parse(Buffer.concat(chunks).toString('utf8')) as T\n } catch (error) {\n throw new ArchiveImportError('error.archive_corrupt', 'Invalid Google Chat metadata JSON', { cause: error })\n }\n}\n\nasync function countMessages(stream: Readable): Promise<number> {\n const pipeline = chain([stream, parser(), pick({ filter: /^messages\\.\\d+$/ }), streamValues()])\n let count = 0\n try {\n for await (const _item of pipeline as AsyncIterable<unknown>) count++\n return count\n } catch (error) {\n throw new ArchiveImportError('error.archive_corrupt', 'Invalid Google Chat messages JSON', { cause: error })\n }\n}\n\nfunction normalizeEmail(user: GoogleChatUser | undefined): string | null {\n return user?.email?.trim().toLowerCase() || null\n}\n\nfunction deriveConversationName(draft: ConversationDraft, ownerEmail: string | null): string {\n const groupInfo = draft.groupInfo\n if (draft.kind === 'Space' && groupInfo?.name?.trim()) return groupInfo.name.trim()\n\n const members = groupInfo?.members ?? []\n const other = members.find((member) => {\n const email = normalizeEmail(member)\n return !ownerEmail || email !== ownerEmail\n })\n return other?.name?.trim() || other?.email?.trim() || members[0]?.name?.trim() || draft.chatId\n}\n\nexport class GoogleChatTakeoutResolver implements ArchiveResolver {\n readonly id = 'google-chat-takeout'\n readonly platform = 'google-chat'\n\n detect(entries: ArchiveEntrySummary[]): boolean {\n const products = new Set<string>()\n const groupFiles = new Map<string, Set<string>>()\n\n for (const entry of entries) {\n const product = entry.name.match(TAKEOUT_ROOT)?.[1]\n if (product) products.add(product)\n\n const match = entry.name.match(GROUP_FILE)\n if (!match) continue\n const files = groupFiles.get(match[1]) ?? new Set<string>()\n files.add(match[3])\n groupFiles.set(match[1], files)\n }\n\n return (\n products.size === 1 &&\n products.has('Google Chat') &&\n Array.from(groupFiles.values()).some((files) => files.has('group_info') && files.has('messages'))\n )\n }\n\n /**\n * 扫描时按 ZIP 顺序消费必要 JSON。会话元数据和消息计数先暂存,\n * 等 owner 信息齐备后统一生成名称,避免依赖归档内部文件顺序。\n */\n async scan(reader: ZipArchiveReader): Promise<PreparedImportChat[]> {\n let ownerEmail: string | null = null\n const drafts = new Map<string, ConversationDraft>()\n\n await reader.visitEntries(async (entry, openStream) => {\n if (USER_INFO_FILE.test(entry.name)) {\n const ownerInfo = await readSmallJson<GoogleChatUserInfo>(await openStream())\n ownerEmail = normalizeEmail(ownerInfo.user)\n return\n }\n\n const match = entry.name.match(GROUP_FILE)\n if (!match) return\n const chatId = `Groups/${match[1]}`\n const draft =\n drafts.get(chatId) ??\n ({\n chatId,\n kind: match[2] as 'DM' | 'Space',\n } satisfies ConversationDraft)\n\n if (match[3] === 'group_info') {\n draft.groupInfoEntry = entry.name\n draft.groupInfo = await readSmallJson<GoogleChatGroupInfo>(await openStream())\n } else {\n draft.messagesEntry = entry.name\n draft.messageCount = await countMessages(await openStream())\n }\n drafts.set(chatId, draft)\n })\n\n if (drafts.size === 0) {\n throw new ArchiveImportError(\n 'error.google_chat_structure_not_found',\n 'Google Chat conversation structure was not found'\n )\n }\n\n return Array.from(drafts.values())\n .map((draft) => {\n if (!draft.groupInfoEntry || !draft.messagesEntry || !draft.groupInfo || draft.messageCount === undefined) {\n throw new ArchiveImportError(\n 'error.google_chat_conversation_incomplete',\n `Google Chat conversation is missing required files: ${draft.chatId}`\n )\n }\n return {\n chatId: draft.chatId,\n name: deriveConversationName(draft, ownerEmail),\n type: draft.kind === 'Space' ? 'group' : 'private',\n messageCount: draft.messageCount,\n memberCount: draft.groupInfo.members?.length ?? 0,\n } satisfies PreparedImportChat\n })\n .sort((a, b) => a.chatId.localeCompare(b.chatId))\n }\n\n async materialize(\n reader: ZipArchiveReader,\n chat: PreparedImportChat,\n targetDir: string\n ): Promise<MaterializedImport> {\n const entries = await reader.listEntries()\n const groupPrefix = `Takeout/Google Chat/${chat.chatId}/`\n const userInfoEntry = entries.find((entry) => USER_INFO_FILE.test(entry.name))?.name\n const groupInfoEntry = `${groupPrefix}group_info.json`\n const messagesEntry = `${groupPrefix}messages.json`\n if (\n !userInfoEntry ||\n !entries.some((entry) => entry.name === groupInfoEntry) ||\n !entries.some((entry) => entry.name === messagesEntry)\n ) {\n throw new ArchiveImportError(\n 'error.google_chat_conversation_incomplete',\n `Google Chat conversation is missing required files: ${chat.chatId}`\n )\n }\n\n fs.mkdirSync(targetDir, { recursive: true })\n await reader.pipeEntries(\n new Map([\n [userInfoEntry, path.join(targetDir, 'user_info.json')],\n [groupInfoEntry, path.join(targetDir, 'group_info.json')],\n [messagesEntry, path.join(targetDir, 'messages.json')],\n ])\n )\n\n const manifestPath = path.join(targetDir, 'google-chat-import.json')\n fs.writeFileSync(\n manifestPath,\n JSON.stringify(\n {\n format: 'chatlab-google-chat-takeout',\n version: 1,\n chatId: chat.chatId,\n chatType: chat.type,\n chatName: chat.name,\n userInfoFile: 'user_info.json',\n groupInfoFile: 'group_info.json',\n messagesFile: 'messages.json',\n },\n null,\n 2\n ),\n { encoding: 'utf8', flag: 'wx' }\n )\n return { manifestPath }\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport { ZipArchiveReader } from './archive-reader'\nimport { ArchiveImportError } from './errors'\nimport { GoogleChatTakeoutResolver } from './google-chat-resolver'\nimport type { ArchiveResolver, PreparedImportSource } from './types'\n\nconst DEFAULT_TTL_MS = 30 * 60 * 1000\n\ninterface SourceRecord {\n descriptor: PreparedImportSource\n archivePath: string\n ownsArchive: boolean\n resolver: ArchiveResolver\n lastAccessAt: number\n}\n\nexport interface ArchiveImportSourceManagerOptions {\n tempRoot?: string\n ttlMs?: number\n now?: () => number\n resolvers?: ArchiveResolver[]\n}\n\nexport class ArchiveImportSourceManager {\n private readonly records = new Map<string, SourceRecord>()\n private readonly expiredSourceIds = new Set<string>()\n private readonly tempRoot: string\n private readonly ttlMs: number\n private readonly now: () => number\n private readonly resolvers: ArchiveResolver[]\n\n constructor(options: ArchiveImportSourceManagerOptions = {}) {\n this.tempRoot = options.tempRoot ?? path.join(process.cwd(), '.chatlab-import-sources')\n this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS\n this.now = options.now ?? Date.now\n this.resolvers = options.resolvers ?? [new GoogleChatTakeoutResolver()]\n fs.mkdirSync(this.tempRoot, { recursive: true })\n }\n\n prepareLocalArchive(archivePath: string): Promise<PreparedImportSource> {\n return this.prepareArchive(archivePath, false)\n }\n\n prepareOwnedArchive(archivePath: string): Promise<PreparedImportSource> {\n return this.prepareArchive(archivePath, true)\n }\n\n private async prepareArchive(archivePath: string, ownsArchive: boolean): Promise<PreparedImportSource> {\n await this.cleanupExpired()\n const reader = new ZipArchiveReader(archivePath)\n try {\n const entries = await reader.listEntries()\n const matches = this.resolvers.filter((resolver) => resolver.detect(entries))\n if (matches.length !== 1) {\n throw new ArchiveImportError(\n 'error.archive_unsupported',\n matches.length === 0 ? 'Unsupported archive format' : 'Archive matches multiple import formats'\n )\n }\n\n const resolver = matches[0]\n const chats = await resolver.scan(reader)\n const createdAt = this.now()\n const sourceId = randomUUID()\n const descriptor: PreparedImportSource = {\n sourceId,\n formatId: resolver.id,\n platform: resolver.platform,\n chats,\n expiresAt: createdAt + this.ttlMs,\n }\n this.records.set(sourceId, {\n descriptor,\n archivePath,\n ownsArchive,\n resolver,\n lastAccessAt: createdAt,\n })\n return descriptor\n } catch (error) {\n if (ownsArchive) this.removePath(archivePath)\n throw error\n }\n }\n\n /**\n * selected chat 的 JSON 物化目录只在 handler 生命周期内存在。\n * 无论数据库导入成功、失败还是抛异常,finally 都会删除它。\n */\n async withMaterializedChat<T>(\n sourceId: string,\n chatId: string,\n handler: (manifestPath: string) => Promise<T>\n ): Promise<T> {\n const record = await this.getActiveRecord(sourceId)\n const chat = record.descriptor.chats.find((item) => item.chatId === chatId)\n if (!chat) {\n throw new ArchiveImportError(\n 'error.google_chat_conversation_incomplete',\n `Archive conversation was not found: ${chatId}`\n )\n }\n const materializedDir = fs.mkdtempSync(path.join(this.tempRoot, `materialized-${sourceId}-`))\n try {\n const reader = new ZipArchiveReader(record.archivePath)\n const materialized = await record.resolver.materialize(reader, chat, materializedDir)\n return await handler(materialized.manifestPath)\n } finally {\n this.removePath(materializedDir)\n }\n }\n\n async release(sourceId: string): Promise<void> {\n const record = this.records.get(sourceId)\n if (!record) return\n this.records.delete(sourceId)\n if (record.ownsArchive) this.removePath(record.archivePath)\n }\n\n async cleanupExpired(now = this.now()): Promise<number> {\n let removed = 0\n for (const [sourceId, record] of this.records) {\n if (now - record.lastAccessAt < this.ttlMs) continue\n this.records.delete(sourceId)\n this.expiredSourceIds.add(sourceId)\n if (record.ownsArchive) this.removePath(record.archivePath)\n removed++\n }\n return removed\n }\n\n async close(): Promise<void> {\n for (const sourceId of Array.from(this.records.keys())) {\n await this.release(sourceId)\n }\n this.removePath(this.tempRoot)\n }\n\n private async getActiveRecord(sourceId: string): Promise<SourceRecord> {\n const record = this.records.get(sourceId)\n if (!record) {\n const code = this.expiredSourceIds.has(sourceId) ? 'error.import_source_expired' : 'error.import_source_not_found'\n throw new ArchiveImportError(code, `Archive import source is unavailable: ${sourceId}`)\n }\n\n const now = this.now()\n if (now - record.lastAccessAt >= this.ttlMs) {\n this.records.delete(sourceId)\n this.expiredSourceIds.add(sourceId)\n if (record.ownsArchive) this.removePath(record.archivePath)\n throw new ArchiveImportError('error.import_source_expired', `Archive import source expired: ${sourceId}`)\n }\n\n record.lastAccessAt = now\n record.descriptor.expiresAt = now + this.ttlMs\n return record\n }\n\n private removePath(targetPath: string): void {\n try {\n fs.rmSync(targetPath, { recursive: true, force: true })\n } catch {\n // 清理失败不覆盖原始导入错误,残留文件由下次启动或系统临时目录回收。\n }\n }\n}\n","/**\n * AI 日志模块(平台无关)\n *\n * 将 AI 相关操作日志写入本地文件。Electron 和 Server/CLI 共用。\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\ntype LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'\n\nexport class AiLogger {\n private debugMode = false\n private logDir: string\n private logFile: string | null = null\n private logStream: fs.WriteStream | null = null\n\n constructor(logsDir: string) {\n this.logDir = path.join(logsDir, 'ai')\n }\n\n setDebugMode(enabled: boolean): void {\n this.debugMode = enabled\n }\n\n isDebugMode(): boolean {\n return this.debugMode\n }\n\n debug(category: string, message: string, data?: unknown): void {\n this.writeLog('DEBUG', category, message, data)\n }\n\n info(category: string, message: string, data?: unknown): void {\n this.writeLog('INFO', category, message, data)\n }\n\n warn(category: string, message: string, data?: unknown): void {\n this.writeLog('WARN', category, message, data)\n }\n\n error(category: string, message: string, data?: unknown): void {\n this.writeLog('ERROR', category, message, data)\n }\n\n close(): void {\n if (this.logStream) {\n this.logStream.end()\n this.logStream = null\n }\n }\n\n getLogPath(): string {\n return this.getLogFilePath()\n }\n\n getExistingLogPath(): string | null {\n if (this.logFile && fs.existsSync(this.logFile)) {\n return this.logFile\n }\n return null\n }\n\n private ensureLogDir(): void {\n if (!fs.existsSync(this.logDir)) {\n fs.mkdirSync(this.logDir, { recursive: true })\n }\n }\n\n private getLogFilePath(): string {\n if (this.logFile) return this.logFile\n\n this.ensureLogDir()\n const now = new Date()\n const date = now.toISOString().split('T')[0]\n const hours = String(now.getHours()).padStart(2, '0')\n const minutes = String(now.getMinutes()).padStart(2, '0')\n this.logFile = path.join(this.logDir, `ai_${date}_${hours}-${minutes}.log`)\n\n return this.logFile\n }\n\n private getLogStream(): fs.WriteStream {\n if (this.logStream) return this.logStream\n\n const filePath = this.getLogFilePath()\n const isNewOrEmptyFile = !fs.existsSync(filePath) || fs.statSync(filePath).size === 0\n this.logStream = fs.createWriteStream(filePath, { flags: 'a', encoding: 'utf-8' })\n if (isNewOrEmptyFile) {\n this.logStream.write(`Local Path: ${filePath.replace(/ /g, '\\\\ ')}\\n\\n`)\n }\n\n return this.logStream\n }\n\n private writeLog(level: LogLevel, category: string, message: string, data?: unknown): void {\n const timestamp = new Date().toISOString()\n let logLine = `[${timestamp}] [${level}] [${category}] ${message}`\n\n if (data !== undefined) {\n try {\n const dataStr = typeof data === 'string' ? data : JSON.stringify(data, null, 2)\n if (!this.debugMode && dataStr.length > 2000) {\n logLine += `\\n${dataStr.slice(0, 2000)}...[truncated, ${dataStr.length} chars total]`\n } else {\n logLine += `\\n${dataStr}`\n }\n } catch {\n logLine += `\\n[unserializable data]`\n }\n }\n\n logLine += '\\n'\n\n try {\n const stream = this.getLogStream()\n stream.write(logLine)\n } catch (error) {\n console.error('[AiLogger] Failed to write log:', error)\n }\n\n if (level === 'WARN' || level === 'ERROR') {\n console.log(`[AI] ${message}`)\n }\n }\n}\n\nexport function extractErrorInfo(error: unknown): Record<string, unknown> {\n if (error instanceof Error) {\n const info: Record<string, unknown> = {\n name: error.name,\n message: error.message,\n }\n if ('cause' in error && error.cause) {\n info.cause = extractErrorInfo(error.cause)\n }\n return info\n }\n if (typeof error === 'object' && error !== null) {\n return { raw: JSON.stringify(error) }\n }\n return { message: String(error) }\n}\n\nexport function extractErrorStack(error: unknown, stackLines: number = 5): string | null {\n if (error instanceof Error && error.stack) {\n const lines = error.stack.split('\\n')\n return lines.slice(1, stackLines + 1).join('\\n')\n }\n return null\n}\n","export interface FormatAIErrorOptions {\n providerName?: string\n rawErrorLabel?: string\n}\n\n/**\n * Parse error candidates from an LLM API error, extracting statusCode, message, and retry info.\n */\nfunction parseErrorCandidates(error: unknown): {\n rawMessage: string\n statusCode: number | undefined\n retrySeconds: number | undefined\n} {\n const candidates: unknown[] = []\n if (error) candidates.push(error)\n\n const errorObj = error as { lastError?: unknown; errors?: unknown[] }\n if (errorObj?.lastError) candidates.push(errorObj.lastError)\n if (Array.isArray(errorObj?.errors)) candidates.push(...errorObj.errors)\n\n let rawMessage = ''\n let statusCode: number | undefined\n let retrySeconds: number | undefined\n\n for (const candidate of candidates) {\n if (!candidate || typeof candidate !== 'object') {\n if (!rawMessage && typeof candidate === 'string') rawMessage = candidate\n continue\n }\n\n const record = candidate as Record<string, unknown>\n if (typeof record.statusCode === 'number') statusCode = record.statusCode\n if (!rawMessage && typeof record.message === 'string') rawMessage = record.message\n\n if (!rawMessage && record.data && typeof record.data === 'object') {\n const data = record.data as { error?: { message?: string } }\n if (data.error?.message) rawMessage = data.error.message\n }\n\n if (record.responseBody && typeof record.responseBody === 'string') {\n try {\n const parsed = JSON.parse(record.responseBody) as { error?: { message?: string } }\n if (!rawMessage && parsed.error?.message) rawMessage = parsed.error.message\n } catch {\n if (!rawMessage) rawMessage = record.responseBody\n }\n }\n\n if (rawMessage) {\n const retryMatch = rawMessage.match(/retry in ([0-9.]+)s/i)\n if (retryMatch) retrySeconds = Math.ceil(Number(retryMatch[1]))\n }\n }\n\n return { rawMessage: rawMessage || String(error), statusCode, retrySeconds }\n}\n\n/**\n * Format LLM API errors into user-friendly messages.\n * Shared between Electron and Server — provider name and i18n labels are injected via options.\n */\nexport function formatAIError(error: unknown, options?: FormatAIErrorOptions): string {\n const { rawMessage: fallbackMessage, statusCode, retrySeconds } = parseErrorCandidates(error)\n const lowerMessage = fallbackMessage.toLowerCase()\n const providerName = options?.providerName || 'API'\n\n let friendlyMessage = ''\n\n if (statusCode === 429 || lowerMessage.includes('quota') || lowerMessage.includes('resource_exhausted')) {\n friendlyMessage = retrySeconds\n ? `${providerName} quota exhausted, please retry after ${retrySeconds}s or upgrade your quota.`\n : `${providerName} quota exhausted, please retry later or upgrade your quota.`\n } else if (\n statusCode === 403 &&\n (lowerMessage.includes('quota') || lowerMessage.includes('not enough') || lowerMessage.includes('insufficient'))\n ) {\n friendlyMessage = `${providerName} rejected the request due to insufficient quota or balance.`\n } else if (statusCode === 503 || lowerMessage.includes('overloaded') || lowerMessage.includes('unavailable')) {\n friendlyMessage = `${providerName} model is overloaded, please retry later.`\n } else if (fallbackMessage.length > 300) {\n friendlyMessage = `${fallbackMessage.slice(0, 300)}...`\n } else {\n friendlyMessage = fallbackMessage\n }\n\n const rawErrorLabel = options?.rawErrorLabel || 'Raw error'\n const details = [statusCode ? `status=${statusCode}` : null, fallbackMessage].filter(Boolean).join('; ')\n\n if (friendlyMessage !== fallbackMessage) {\n return `${friendlyMessage}\\n\\n${rawErrorLabel}: ${details}`\n }\n\n return friendlyMessage\n}\n","/**\n * 助手 MD 文件解析器(平台无关,Node.js 实现)\n */\n\nimport * as path from 'path'\nimport matter from 'gray-matter'\nimport { normalizeBuiltinToolNames } from '@openchatlab/core'\nimport type { AssistantConfig } from './types'\n\nexport function parseAssistantFile(content: string, filePath: string): AssistantConfig | null {\n try {\n const { data: fm, content: body } = matter(content)\n\n const id = fm.id ?? path.basename(filePath, '.md')\n const name = fm.name\n if (!name) return null\n\n return {\n id,\n name,\n systemPrompt: body.trim(),\n presetQuestions: parseStringArray(fm.presetQuestions),\n allowedBuiltinTools: normalizeBuiltinToolNames(parseStringArray(fm.allowedBuiltinTools)),\n builtinId: typeof fm.builtinId === 'string' ? fm.builtinId : undefined,\n applicableChatTypes: parseChatTypes(fm.applicableChatTypes),\n supportedLocales: parseStringArray(fm.supportedLocales),\n }\n } catch {\n return null\n }\n}\n\nexport function serializeAssistant(config: AssistantConfig): string {\n const fm: Record<string, unknown> = {\n id: config.id,\n name: config.name,\n }\n\n if (config.builtinId) fm.builtinId = config.builtinId\n if (config.applicableChatTypes?.length) fm.applicableChatTypes = config.applicableChatTypes\n if (config.supportedLocales?.length) fm.supportedLocales = config.supportedLocales\n if (config.allowedBuiltinTools?.length) fm.allowedBuiltinTools = normalizeBuiltinToolNames(config.allowedBuiltinTools)\n if (config.presetQuestions?.length) fm.presetQuestions = config.presetQuestions\n\n return matter.stringify(`\\n${config.systemPrompt}\\n`, fm)\n}\n\nfunction parseStringArray(raw: unknown): string[] {\n if (Array.isArray(raw)) return raw.map(String).filter(Boolean)\n return []\n}\n\nfunction parseChatTypes(raw: unknown): ('group' | 'private')[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const valid = raw.filter((v): v is 'group' | 'private' => v === 'group' || v === 'private')\n return valid.length > 0 ? valid : undefined\n}\n","/**\n * Platform-agnostic assistant manager.\n * Abstracts file system operations and builtin resource loading\n * via dependency injection.\n */\n\nimport { parseAssistantFile, serializeAssistant } from './assistant-parser'\nimport type { AssistantConfig, AssistantSummary } from './types'\n\n// ==================== Result types ====================\n\nexport interface AssistantInitResult {\n total: number\n generalCreated: boolean\n}\n\nexport interface AssistantSaveResult {\n success: boolean\n error?: string\n}\n\nexport interface BuiltinAssistantInfo {\n id: string\n name: string\n systemPrompt: string\n applicableChatTypes?: ('group' | 'private')[]\n supportedLocales?: string[]\n imported: boolean\n}\n\n// ==================== Dependency abstraction ====================\n\nexport interface AssistantManagerFs {\n ensureDir(dir: string): void\n listFiles(dir: string, ext: string): string[]\n readFile(filePath: string): string\n writeFile(filePath: string, content: string): void\n deleteFile(filePath: string): void\n fileExists(filePath: string): boolean\n joinPath(...parts: string[]): string\n}\n\nexport interface AssistantManagerDeps {\n fs: AssistantManagerFs\n assistantsDir: string\n builtinRawConfigs?: Array<{ id: string; content: string }>\n generalIds?: string[]\n generateId?: () => string\n logger?: {\n info: (category: string, message: string, data?: unknown) => void\n warn: (category: string, message: string, data?: unknown) => void\n error: (category: string, message: string, data?: unknown) => void\n }\n}\n\n// ==================== Manager ====================\n\nfunction toSummary(config: AssistantConfig): AssistantSummary {\n return {\n id: config.id,\n name: config.name,\n systemPrompt: config.systemPrompt,\n presetQuestions: config.presetQuestions,\n builtinId: config.builtinId,\n applicableChatTypes: config.applicableChatTypes,\n supportedLocales: config.supportedLocales,\n }\n}\n\nexport class AssistantManager {\n private deps: AssistantManagerDeps\n private generalIds: string[]\n private builtinCache = new Map<string, AssistantConfig>()\n private cache = new Map<string, AssistantConfig>()\n private initialized = false\n\n constructor(deps: AssistantManagerDeps) {\n this.deps = deps\n this.generalIds = deps.generalIds || ['general_cn', 'general_en', 'general_ja']\n this.initBuiltinCache()\n }\n\n private initBuiltinCache(): void {\n if (!this.deps.builtinRawConfigs) return\n for (const { id, content } of this.deps.builtinRawConfigs) {\n const config = parseAssistantFile(content, `${id}.md`)\n if (config) this.builtinCache.set(config.id, config)\n }\n }\n\n private getBuiltinConfig(id: string): AssistantConfig | undefined {\n return this.builtinCache.get(id)\n }\n\n private ensureInitialized(): void {\n if (!this.initialized) this.init()\n }\n\n // ==================== Init ====================\n\n init(): AssistantInitResult {\n const { fs, assistantsDir } = this.deps\n fs.ensureDir(assistantsDir)\n\n const generalCreated = this.ensureGeneralAssistants()\n this.loadAll()\n\n this.initialized = true\n this.deps.logger?.info('AssistantManager', 'Initialized', {\n total: this.cache.size,\n generalCreated,\n })\n\n return { total: this.cache.size, generalCreated }\n }\n\n private ensureGeneralAssistants(): boolean {\n const { fs, assistantsDir } = this.deps\n let anyCreated = false\n for (const id of this.generalIds) {\n const config = this.getBuiltinConfig(id)\n if (!config) continue\n\n const filePath = fs.joinPath(assistantsDir, `${id}.md`)\n if (fs.fileExists(filePath)) continue\n\n fs.writeFile(filePath, serializeAssistant({ ...config, builtinId: config.id }))\n anyCreated = true\n }\n return anyCreated\n }\n\n private loadAll(): void {\n const { fs, assistantsDir } = this.deps\n this.cache.clear()\n\n const files = fs.listFiles(assistantsDir, '.md')\n for (const file of files) {\n try {\n const filePath = fs.joinPath(assistantsDir, file)\n const content = fs.readFile(filePath)\n const config = parseAssistantFile(content, filePath)\n if (config) {\n this.cache.set(config.id, config)\n } else {\n this.deps.logger?.warn('AssistantManager', `Failed to parse: ${file}`)\n }\n } catch (error) {\n this.deps.logger?.warn('AssistantManager', `Failed to load: ${file}`, { error: String(error) })\n }\n }\n }\n\n // ==================== Query ====================\n\n getAllAssistants(): AssistantSummary[] {\n this.ensureInitialized()\n return Array.from(this.cache.values())\n .sort((a, b) => a.name.localeCompare(b.name))\n .map(toSummary)\n }\n\n getAssistantConfig(id: string): AssistantConfig | null {\n this.ensureInitialized()\n return this.cache.get(id) ?? null\n }\n\n hasAssistant(id: string): boolean {\n this.ensureInitialized()\n return this.cache.has(id)\n }\n\n getBuiltinCatalog(): BuiltinAssistantInfo[] {\n this.ensureInitialized()\n return []\n }\n\n isGeneralAssistant(id: string): boolean {\n return this.generalIds.includes(id)\n }\n\n // ==================== Import ====================\n\n importAssistant(builtinId: string): AssistantSaveResult {\n this.ensureInitialized()\n\n const builtinConfig = this.getBuiltinConfig(builtinId)\n if (!builtinConfig) return { success: false, error: `Builtin assistant not found: ${builtinId}` }\n\n const existing = this.findByBuiltinId(builtinId)\n if (existing) return { success: false, error: `Assistant already imported: ${builtinId}` }\n\n return this.saveToDisk({ ...builtinConfig, builtinId: builtinConfig.id })\n }\n\n reimportAssistant(id: string): AssistantSaveResult {\n this.ensureInitialized()\n\n const existing = this.cache.get(id)\n if (!existing) return { success: false, error: `Assistant not found: ${id}` }\n if (!existing.builtinId) return { success: false, error: 'Only imported builtin assistants can be reimported' }\n\n const builtinConfig = this.getBuiltinConfig(existing.builtinId)\n if (!builtinConfig) return { success: false, error: `Builtin template not found: ${existing.builtinId}` }\n\n return this.saveToDisk({ ...builtinConfig, id: existing.id, builtinId: existing.builtinId })\n }\n\n importAssistantFromMd(rawMd: string): AssistantSaveResult & { id?: string } {\n this.ensureInitialized()\n\n const config = parseAssistantFile(rawMd, 'cloud_import.md')\n if (!config) return { success: false, error: 'Failed to parse assistant markdown' }\n\n if (this.cache.has(config.id)) return { success: false, error: `Assistant already exists: ${config.id}` }\n\n const result = this.saveToDisk(config)\n return { ...result, id: result.success ? config.id : undefined }\n }\n\n // ==================== Mutate ====================\n\n updateAssistant(id: string, updates: Partial<AssistantConfig>): AssistantSaveResult {\n this.ensureInitialized()\n\n const existing = this.cache.get(id)\n if (!existing) return { success: false, error: `Assistant not found: ${id}` }\n\n return this.saveToDisk({ ...existing, ...updates, id })\n }\n\n createAssistant(config: Omit<AssistantConfig, 'id'>): AssistantSaveResult & { id?: string } {\n this.ensureInitialized()\n\n const id = this.deps.generateId?.() || `custom_${Date.now().toString(36)}`\n const newConfig: AssistantConfig = { ...config, id, builtinId: undefined }\n\n const result = this.saveToDisk(newConfig)\n return { ...result, id: result.success ? id : undefined }\n }\n\n deleteAssistant(id: string): AssistantSaveResult {\n this.ensureInitialized()\n\n if (this.generalIds.includes(id)) return { success: false, error: 'Cannot delete the default assistant (general)' }\n\n const existing = this.cache.get(id)\n if (!existing) return { success: false, error: `Assistant not found: ${id}` }\n\n try {\n const filePath = this.deps.fs.joinPath(this.deps.assistantsDir, `${id}.md`)\n if (this.deps.fs.fileExists(filePath)) this.deps.fs.deleteFile(filePath)\n this.cache.delete(id)\n return { success: true }\n } catch (error) {\n return { success: false, error: String(error) }\n }\n }\n\n resetAssistant(id: string): AssistantSaveResult {\n this.ensureInitialized()\n\n const existing = this.cache.get(id)\n if (!existing?.builtinId) return { success: false, error: 'Only builtin assistants can be reset' }\n\n const builtinConfig = this.getBuiltinConfig(existing.builtinId)\n if (!builtinConfig) return { success: false, error: `Builtin config not found: ${existing.builtinId}` }\n\n return this.saveToDisk({ ...builtinConfig, id: existing.id, builtinId: existing.builtinId })\n }\n\n // ==================== Internal ====================\n\n private findByBuiltinId(builtinId: string): AssistantConfig | undefined {\n return Array.from(this.cache.values()).find((c) => c.builtinId === builtinId)\n }\n\n private saveToDisk(config: AssistantConfig): AssistantSaveResult {\n try {\n const filePath = this.deps.fs.joinPath(this.deps.assistantsDir, `${config.id}.md`)\n this.deps.fs.writeFile(filePath, serializeAssistant(config))\n this.cache.set(config.id, config)\n return { success: true }\n } catch (error) {\n this.deps.logger?.error('AssistantManager', `Failed to save: ${config.id}`, { error: String(error) })\n return { success: false, error: String(error) }\n }\n }\n}\n","/**\n * 技能 MD 文件解析器(平台无关,Node.js 实现)\n */\n\nimport * as path from 'path'\nimport matter from 'gray-matter'\nimport type { SkillDef } from './types'\n\nexport function parseSkillFile(content: string, filePath: string): SkillDef | null {\n try {\n const { data: fm, content: prompt } = matter(content)\n\n const id = fm.id ?? path.basename(filePath, '.md')\n const name = fm.name\n if (!name) return null\n\n return {\n id,\n name,\n description: fm.description ?? '',\n tags: parseTags(fm.tags),\n chatScope: validateChatScope(fm.chatScope),\n tools: Array.isArray(fm.tools) ? fm.tools : [],\n prompt: prompt.trim(),\n }\n } catch {\n return null\n }\n}\n\nexport function extractSkillId(content: string, filePath: string): string | null {\n try {\n const { data: fm } = matter(content)\n return fm.id ?? path.basename(filePath, '.md')\n } catch {\n return null\n }\n}\n\nfunction parseTags(raw: unknown): string[] {\n if (typeof raw === 'string') {\n return raw\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean)\n }\n if (Array.isArray(raw)) return raw.map(String).filter(Boolean)\n return []\n}\n\nfunction validateChatScope(raw: unknown): 'all' | 'group' | 'private' {\n if (raw === 'group' || raw === 'private') return raw\n return 'all'\n}\n","/**\n * AI 对话历史管理模块(平台无关)\n *\n * 管理 AI 对话的持久化存储(conversations.db),\n * 供 Electron 主进程和 CLI serve 共用。\n */\n\nimport Database from 'better-sqlite3'\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { ChartPayload, ChatEvidencePayload } from '@openchatlab/core'\nimport type { PlanContentBlock, PlanDraftContentBlock } from './agent'\n\nconst DEFAULT_GENERAL_ID = 'general_cn'\n\n// ==================== 类型定义 ====================\n\nexport interface AIChat {\n id: string\n sessionId: string\n title: string | null\n assistantId: string\n activeMessageId?: string | null\n createdAt: number\n updatedAt: number\n}\n\nexport type ContentBlock =\n | { type: 'text'; text: string }\n | { type: 'think'; tag: string; text: string; durationMs?: number }\n | { type: 'chart'; chart: ChartPayload }\n | { type: 'evidence'; evidence: ChatEvidencePayload }\n | PlanContentBlock\n | PlanDraftContentBlock\n | {\n type: 'tool'\n tool: {\n name: string\n displayName: string\n status: 'running' | 'done' | 'error'\n params?: Record<string, unknown>\n /** Provider-issued tool call id, persisted so history replay keeps stable ids (prompt cache friendly). */\n toolCallId?: string\n /** Truncated text of the tool result as seen by the model. Absent on legacy rows and unfinished calls. */\n result?: string\n /** Full safe text result for UI display; not used for history replay. */\n displayResult?: string\n /** Whether the tool execution failed (pi-level isError, distinct from UI status). */\n isError?: boolean\n }\n }\n | { type: 'error'; error: { name: string | null; message: string; stack: string | null } }\n | {\n type: 'summary_meta'\n bufferBoundaryTimestamp: number\n compressedMessageCount: number\n }\n\nexport type AIMessageRole = 'user' | 'assistant' | 'summary'\n\nexport interface TokenUsageData {\n promptTokens: number\n completionTokens: number\n totalTokens: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n}\n\nexport interface AIMessage {\n id: string\n aiChatId: string\n role: AIMessageRole\n content: string\n timestamp: number\n parentId?: string | null\n dataKeywords?: string[]\n dataMessageCount?: number\n contentBlocks?: ContentBlock[]\n tokenUsage?: TokenUsageData\n}\n\ninterface AIMessageRow {\n id: string\n aiChatId: string\n role: string\n content: string\n timestamp: number\n parentId: string | null\n siblingGroupId: string | null\n branchIndex: number | null\n dataKeywords: string | null\n dataMessageCount: number | null\n contentBlocks: string | null\n tokenUsage: string | null\n}\n\nexport interface AIChatManagerLogger {\n warn(category: string, message: string, extra?: Record<string, unknown>): void\n}\n\nconst defaultLogger: AIChatManagerLogger = {\n warn(_category, message, extra) {\n console.warn(`[AI Chats] ${message}`, extra ?? '')\n },\n}\n\n// ==================== AIChatManager ====================\n\nexport class AIChatManager {\n private db: Database.Database | null = null\n private readonly aiDataDir: string\n private readonly logger: AIChatManagerLogger\n private readonly nativeBinding?: string\n private readonly pendingDebugContextMap = new Map<string, string>()\n\n constructor(aiDataDir: string, options?: { logger?: AIChatManagerLogger; nativeBinding?: string }) {\n this.aiDataDir = aiDataDir\n this.logger = options?.logger ?? defaultLogger\n this.nativeBinding = options?.nativeBinding\n }\n\n private ensureDir(dir: string): void {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n }\n\n private getDb(): Database.Database {\n if (this.db) return this.db\n\n this.ensureDir(this.aiDataDir)\n const dbPath = path.join(this.aiDataDir, 'conversations.db')\n this.db = this.nativeBinding ? new Database(dbPath, { nativeBinding: this.nativeBinding }) : new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS ai_chat (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n title TEXT,\n assistant_id TEXT DEFAULT '${DEFAULT_GENERAL_ID}',\n active_message_id TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS ai_message (\n id TEXT PRIMARY KEY,\n ai_chat_id TEXT NOT NULL,\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n timestamp INTEGER NOT NULL,\n data_keywords TEXT,\n data_message_count INTEGER,\n content_blocks TEXT,\n parent_id TEXT,\n sibling_group_id TEXT,\n branch_index INTEGER DEFAULT 0,\n debug_context TEXT,\n token_usage TEXT,\n FOREIGN KEY(ai_chat_id) REFERENCES ai_chat(id) ON DELETE CASCADE\n );\n\n `)\n\n this.migrateDatabase(this.db)\n return this.db\n }\n\n private tableExists(db: Database.Database, tableName: string): boolean {\n const row = db.prepare(\"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?\").get(tableName) as\n | { 1: number }\n | undefined\n return !!row\n }\n\n private getTableColumns(db: Database.Database, tableName: string): string[] {\n const tableInfo = db.pragma(`table_info(${tableName})`) as Array<{ name: string }>\n return tableInfo.map((col) => col.name)\n }\n\n private ensureMessageMigrationColumns(db: Database.Database, tableName: string): void {\n const messageColumns = this.getTableColumns(db, tableName)\n if (!messageColumns.includes('content_blocks')) {\n db.exec(`ALTER TABLE ${tableName} ADD COLUMN content_blocks TEXT`)\n }\n if (!messageColumns.includes('token_usage')) {\n db.exec(`ALTER TABLE ${tableName} ADD COLUMN token_usage TEXT`)\n }\n if (!messageColumns.includes('debug_context')) {\n db.exec(`ALTER TABLE ${tableName} ADD COLUMN debug_context TEXT`)\n }\n if (!messageColumns.includes('parent_id')) {\n db.exec(`ALTER TABLE ${tableName} ADD COLUMN parent_id TEXT`)\n }\n if (!messageColumns.includes('sibling_group_id')) {\n db.exec(`ALTER TABLE ${tableName} ADD COLUMN sibling_group_id TEXT`)\n }\n if (!messageColumns.includes('branch_index')) {\n db.exec(`ALTER TABLE ${tableName} ADD COLUMN branch_index INTEGER DEFAULT 0`)\n }\n }\n\n private migrateDatabase(db: Database.Database): void {\n try {\n const hasLegacyConversationTable = this.tableExists(db, 'ai_conversation')\n\n if (hasLegacyConversationTable) {\n const convColumns = this.getTableColumns(db, 'ai_conversation')\n if (!convColumns.includes('assistant_id')) {\n db.exec(`ALTER TABLE ai_conversation ADD COLUMN assistant_id TEXT DEFAULT '${DEFAULT_GENERAL_ID}'`)\n }\n if (!convColumns.includes('active_message_id')) {\n db.exec('ALTER TABLE ai_conversation ADD COLUMN active_message_id TEXT')\n }\n\n db.exec(`\n INSERT OR IGNORE INTO ai_chat (\n id, session_id, title, assistant_id, active_message_id, created_at, updated_at\n )\n SELECT id, session_id, title, COALESCE(assistant_id, '${DEFAULT_GENERAL_ID}'),\n active_message_id, created_at, updated_at\n FROM ai_conversation\n `)\n }\n\n this.ensureMessageMigrationColumns(db, 'ai_message')\n\n const messageColumns = this.getTableColumns(db, 'ai_message')\n const hadLegacyConversationId = messageColumns.includes('conversation_id')\n const needsMessageTreeBackfill =\n !messageColumns.includes('parent_id') ||\n !messageColumns.includes('sibling_group_id') ||\n !messageColumns.includes('branch_index') ||\n hadLegacyConversationId\n\n if (hadLegacyConversationId && !messageColumns.includes('ai_chat_id')) {\n db.exec(`\n DROP INDEX IF EXISTS idx_ai_message_conversation;\n ALTER TABLE ai_message RENAME TO ai_message_legacy;\n\n CREATE TABLE ai_message (\n id TEXT PRIMARY KEY,\n ai_chat_id TEXT NOT NULL,\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n timestamp INTEGER NOT NULL,\n data_keywords TEXT,\n data_message_count INTEGER,\n content_blocks TEXT,\n parent_id TEXT,\n sibling_group_id TEXT,\n branch_index INTEGER DEFAULT 0,\n debug_context TEXT,\n token_usage TEXT,\n FOREIGN KEY(ai_chat_id) REFERENCES ai_chat(id) ON DELETE CASCADE\n );\n\n INSERT INTO ai_message (\n id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,\n content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage\n )\n SELECT id, conversation_id, role, content, timestamp, data_keywords, data_message_count,\n content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage\n FROM ai_message_legacy;\n\n DROP TABLE ai_message_legacy;\n `)\n }\n\n if (hasLegacyConversationTable) {\n db.exec(`\n DROP INDEX IF EXISTS idx_ai_conversation_session;\n DROP TABLE ai_conversation;\n `)\n }\n\n if (needsMessageTreeBackfill || this.hasUnbackfilledMessageTree(db)) {\n this.backfillMessageTree(db)\n }\n\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_ai_chat_session ON ai_chat(session_id);\n CREATE INDEX IF NOT EXISTS idx_ai_message_ai_chat ON ai_message(ai_chat_id);\n CREATE INDEX IF NOT EXISTS idx_ai_message_parent ON ai_message(parent_id);\n CREATE INDEX IF NOT EXISTS idx_ai_message_sibling ON ai_message(sibling_group_id);\n `)\n } catch (error) {\n console.error('[AI DB Migration] Migration failed:', error)\n }\n }\n\n private hasUnbackfilledMessageTree(db: Database.Database): boolean {\n const row = db\n .prepare(\n `SELECT 1\n FROM ai_chat c\n WHERE EXISTS (\n SELECT 1 FROM ai_message m\n WHERE m.ai_chat_id = c.id\n )\n AND (\n c.active_message_id IS NULL\n OR EXISTS (\n SELECT 1 FROM ai_message m\n WHERE m.ai_chat_id = c.id\n AND (m.sibling_group_id IS NULL OR m.branch_index IS NULL)\n )\n )\n LIMIT 1`\n )\n .get()\n return !!row\n }\n\n private backfillMessageTree(db: Database.Database): void {\n const aiChats = db.prepare('SELECT id FROM ai_chat').all() as Array<{ id: string }>\n const updateMessage = db.prepare(\n 'UPDATE ai_message SET parent_id = ?, sibling_group_id = COALESCE(sibling_group_id, ?), branch_index = COALESCE(branch_index, 0) WHERE id = ?'\n )\n const updateAIChat = db.prepare('UPDATE ai_chat SET active_message_id = ? WHERE id = ?')\n\n const tx = db.transaction(() => {\n for (const aiChat of aiChats) {\n const messages = db\n .prepare(\n `SELECT id, parent_id as parentId, sibling_group_id as siblingGroupId\n FROM ai_message WHERE ai_chat_id = ? ORDER BY timestamp ASC, id ASC`\n )\n .all(aiChat.id) as Array<{ id: string; parentId: string | null; siblingGroupId: string | null }>\n\n let previousId: string | null = null\n for (const message of messages) {\n const parentId = message.parentId === undefined ? previousId : (message.parentId ?? previousId)\n updateMessage.run(parentId, message.siblingGroupId ?? message.id, message.id)\n previousId = message.id\n }\n\n const aiChatRow = db\n .prepare('SELECT active_message_id as activeMessageId FROM ai_chat WHERE id = ?')\n .get(aiChat.id) as { activeMessageId: string | null } | undefined\n if (!aiChatRow?.activeMessageId && previousId) {\n updateAIChat.run(previousId, aiChat.id)\n }\n }\n })\n\n tx()\n }\n\n private generateId(prefix: string): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`\n }\n\n private parseMessageRow(row: AIMessageRow): AIMessage {\n return {\n id: row.id,\n aiChatId: row.aiChatId,\n role: row.role as AIMessageRole,\n content: row.content,\n timestamp: row.timestamp,\n parentId: row.parentId ?? null,\n dataKeywords: row.dataKeywords ? JSON.parse(row.dataKeywords) : undefined,\n dataMessageCount: row.dataMessageCount ?? undefined,\n contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : undefined,\n tokenUsage: row.tokenUsage ? JSON.parse(row.tokenUsage) : undefined,\n }\n }\n\n private getMessageRow(messageId: string): AIMessageRow | null {\n const db = this.getDb()\n const row = db\n .prepare(\n `SELECT id, ai_chat_id as aiChatId, role, content, timestamp,\n parent_id as parentId, sibling_group_id as siblingGroupId, branch_index as branchIndex,\n data_keywords as dataKeywords, data_message_count as dataMessageCount,\n content_blocks as contentBlocks, token_usage as tokenUsage\n FROM ai_message WHERE id = ?`\n )\n .get(messageId) as AIMessageRow | undefined\n return row ?? null\n }\n\n private getActiveMessageId(aiChatId: string): string | null {\n const db = this.getDb()\n const row = db.prepare('SELECT active_message_id as activeMessageId FROM ai_chat WHERE id = ?').get(aiChatId) as\n | { activeMessageId: string | null }\n | undefined\n if (row?.activeMessageId) {\n const activeExists = db.prepare('SELECT 1 FROM ai_message WHERE id = ?').get(row.activeMessageId)\n if (activeExists) return row.activeMessageId\n }\n\n const fallback = db\n .prepare('SELECT id FROM ai_message WHERE ai_chat_id = ? ORDER BY timestamp DESC, id DESC LIMIT 1')\n .get(aiChatId) as { id: string } | undefined\n if (fallback?.id) {\n db.prepare('UPDATE ai_chat SET active_message_id = ? WHERE id = ?').run(fallback.id, aiChatId)\n return fallback.id\n }\n return null\n }\n\n private getAllMessageRows(aiChatId: string): AIMessageRow[] {\n return this.getDb()\n .prepare(\n `SELECT id, ai_chat_id as aiChatId, role, content, timestamp,\n parent_id as parentId, sibling_group_id as siblingGroupId, branch_index as branchIndex,\n data_keywords as dataKeywords, data_message_count as dataMessageCount,\n content_blocks as contentBlocks, token_usage as tokenUsage\n FROM ai_message WHERE ai_chat_id = ? ORDER BY timestamp ASC, id ASC`\n )\n .all(aiChatId) as AIMessageRow[]\n }\n\n private getActivePathRows(aiChatId: string, leafMessageId?: string | null): AIMessageRow[] {\n if (leafMessageId === null) return []\n\n const allRows = this.getAllMessageRows(aiChatId)\n if (allRows.length === 0) return []\n\n const rowMap = new Map(allRows.map((row) => [row.id, row]))\n let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId)\n const path: AIMessageRow[] = []\n const seen = new Set<string>()\n\n while (currentId && !seen.has(currentId)) {\n seen.add(currentId)\n const row = rowMap.get(currentId)\n if (!row) break\n path.push(row)\n currentId = row.parentId\n }\n\n return path.length > 0 ? path.reverse() : allRows\n }\n\n // ==================== 生命周期 ====================\n\n close(): void {\n if (this.db) {\n this.db.close()\n this.db = null\n }\n }\n\n // ==================== Debug ====================\n\n getAiSchema(): Array<{\n name: string\n columns: Array<{ name: string; type: string; notnull: boolean; pk: boolean }>\n }> {\n const db = this.getDb()\n const tables = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\")\n .all() as Array<{ name: string }>\n\n return tables.map((t) => {\n const columns = db.pragma(`table_info(\"${t.name}\")`) as Array<{\n name: string\n type: string\n notnull: number\n pk: number\n }>\n return {\n name: t.name,\n columns: columns.map((c) => ({\n name: c.name,\n type: c.type,\n notnull: !!c.notnull,\n pk: !!c.pk,\n })),\n }\n })\n }\n\n executeAiSQL(sql: string): {\n columns: string[]\n rows: unknown[][]\n rowCount: number\n duration: number\n limited: boolean\n } {\n const db = this.getDb()\n const start = Date.now()\n const trimmed = sql.trim()\n const isSelect = /^SELECT/i.test(trimmed)\n\n if (isSelect) {\n const stmt = db.prepare(trimmed)\n const rows = stmt.all() as Record<string, unknown>[]\n const duration = Date.now() - start\n const columns = rows.length > 0 ? Object.keys(rows[0]) : []\n return {\n columns,\n rows: rows.map((r) => columns.map((c) => r[c])),\n rowCount: rows.length,\n duration,\n limited: false,\n }\n } else {\n const result = db.prepare(trimmed).run()\n const duration = Date.now() - start\n return {\n columns: ['changes', 'lastInsertRowid'],\n rows: [[result.changes, Number(result.lastInsertRowid)]],\n rowCount: 1,\n duration,\n limited: false,\n }\n }\n }\n\n // ==================== 对话管理 ====================\n\n createAIChat(sessionId: string, title: string | undefined, assistantId: string): AIChat {\n const db = this.getDb()\n const now = Math.floor(Date.now() / 1000)\n const id = this.generateId('conv')\n\n db.prepare(\n `INSERT INTO ai_chat (id, session_id, title, assistant_id, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?)`\n ).run(id, sessionId, title || null, assistantId, now, now)\n\n return { id, sessionId, title: title || null, assistantId, activeMessageId: null, createdAt: now, updatedAt: now }\n }\n\n getAIChatCountsBySession(): Map<string, number> {\n const result = new Map<string, number>()\n try {\n const db = this.getDb()\n const rows = db.prepare('SELECT session_id, COUNT(*) as count FROM ai_chat GROUP BY session_id').all() as Array<{\n session_id: string\n count: number\n }>\n for (const row of rows) {\n result.set(row.session_id, row.count)\n }\n } catch {\n // AI DB may not be initialized yet\n }\n return result\n }\n\n getAIChats(sessionId: string): AIChat[] {\n const db = this.getDb()\n return db\n .prepare(\n `SELECT id, session_id as sessionId, title, assistant_id as assistantId,\n active_message_id as activeMessageId,\n created_at as createdAt, updated_at as updatedAt\n FROM ai_chat WHERE session_id = ? ORDER BY updated_at DESC`\n )\n .all(sessionId) as AIChat[]\n }\n\n getAIChat(aiChatId: string): AIChat | null {\n const db = this.getDb()\n const row = db\n .prepare(\n `SELECT id, session_id as sessionId, title, assistant_id as assistantId,\n active_message_id as activeMessageId,\n created_at as createdAt, updated_at as updatedAt\n FROM ai_chat WHERE id = ?`\n )\n .get(aiChatId) as AIChat | undefined\n return row || null\n }\n\n updateAIChatTitle(aiChatId: string, title: string): boolean {\n const db = this.getDb()\n const now = Math.floor(Date.now() / 1000)\n const result = db.prepare('UPDATE ai_chat SET title = ?, updated_at = ? WHERE id = ?').run(title, now, aiChatId)\n return result.changes > 0\n }\n\n deleteAIChat(aiChatId: string): boolean {\n const db = this.getDb()\n db.prepare('DELETE FROM ai_message WHERE ai_chat_id = ?').run(aiChatId)\n const result = db.prepare('DELETE FROM ai_chat WHERE id = ?').run(aiChatId)\n return result.changes > 0\n }\n\n // ==================== 消息管理 ====================\n\n addMessage(\n aiChatId: string,\n role: AIMessageRole,\n content: string,\n dataKeywords?: string[],\n dataMessageCount?: number,\n contentBlocks?: ContentBlock[],\n tokenUsage?: TokenUsageData\n ): AIMessage {\n const db = this.getDb()\n const now = Math.floor(Date.now() / 1000)\n const id = this.generateId('msg')\n const parentId = this.getActiveMessageId(aiChatId)\n const siblingGroupId = id\n const branchIndex = 0\n\n const pendingDebug = role === 'assistant' ? this.pendingDebugContextMap.get(aiChatId) : undefined\n if (pendingDebug) {\n this.pendingDebugContextMap.delete(aiChatId)\n }\n\n db.prepare(\n `INSERT INTO ai_message (\n id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,\n content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n ).run(\n id,\n aiChatId,\n role,\n content,\n now,\n dataKeywords ? JSON.stringify(dataKeywords) : null,\n dataMessageCount ?? null,\n contentBlocks ? JSON.stringify(contentBlocks) : null,\n tokenUsage ? JSON.stringify(tokenUsage) : null,\n pendingDebug ?? null,\n parentId,\n siblingGroupId,\n branchIndex\n )\n\n db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(id, now, aiChatId)\n\n return {\n id,\n aiChatId,\n role,\n content,\n timestamp: now,\n parentId,\n dataKeywords,\n dataMessageCount,\n contentBlocks,\n tokenUsage,\n }\n }\n\n getMessages(aiChatId: string): AIMessage[] {\n return this.getActivePathRows(aiChatId).map((row) => this.parseMessageRow(row))\n }\n\n deleteMessage(messageId: string): boolean {\n const db = this.getDb()\n const result = db.prepare('DELETE FROM ai_message WHERE id = ?').run(messageId)\n return result.changes > 0\n }\n\n deleteMessagesFrom(aiChatId: string, messageId: string): void {\n const db = this.getDb()\n const target = this.getMessageRow(messageId)\n if (!target || target.aiChatId !== aiChatId) {\n throw new Error('Message not found in AI chat')\n }\n\n const activePath = this.getActivePathRows(aiChatId)\n const targetIndex = activePath.findIndex((row) => row.id === messageId)\n if (targetIndex < 0) {\n throw new Error('Message not on active path')\n }\n\n const idsToDelete = activePath.slice(targetIndex).map((row) => row.id)\n const placeholders = idsToDelete.map(() => '?').join(', ')\n db.prepare(`DELETE FROM ai_message WHERE id IN (${placeholders})`).run(...idsToDelete)\n\n const newLeafId = targetIndex > 0 ? activePath[targetIndex - 1]!.id : null\n const now = Math.floor(Date.now() / 1000)\n db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(newLeafId, now, aiChatId)\n }\n\n forkAIChat(sourceAIChatId: string, upToMessageId: string, title?: string): AIChat {\n const db = this.getDb()\n const source = this.getAIChat(sourceAIChatId)\n if (!source) {\n throw new Error('Source AI chat not found')\n }\n\n const activePath = this.getActivePathRows(sourceAIChatId)\n const cutIndex = activePath.findIndex((row) => row.id === upToMessageId)\n if (cutIndex < 0) {\n throw new Error('Message not on active path')\n }\n\n const messagesToCopy = activePath.slice(0, cutIndex + 1)\n const now = Math.floor(Date.now() / 1000)\n const newConvId = this.generateId('conv')\n const forkTitle = title || `${source.title || 'Untitled'} (fork)`\n\n db.prepare(\n `INSERT INTO ai_chat (id, session_id, title, assistant_id, active_message_id, created_at, updated_at)\n VALUES (?, ?, ?, ?, NULL, ?, ?)`\n ).run(newConvId, source.sessionId, forkTitle, source.assistantId, now, now)\n\n const idMap = new Map<string, string>()\n let lastNewId: string | null = null\n\n for (const row of messagesToCopy) {\n const newMsgId = this.generateId('msg')\n idMap.set(row.id, newMsgId)\n const newParentId = row.parentId ? (idMap.get(row.parentId) ?? null) : null\n\n db.prepare(\n `INSERT INTO ai_message (\n id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,\n content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, 0)`\n ).run(\n newMsgId,\n newConvId,\n row.role,\n row.content,\n row.timestamp,\n row.dataKeywords,\n row.dataMessageCount,\n row.contentBlocks,\n row.tokenUsage,\n newParentId,\n newMsgId\n )\n lastNewId = newMsgId\n }\n\n if (lastNewId) {\n db.prepare('UPDATE ai_chat SET active_message_id = ? WHERE id = ?').run(lastNewId, newConvId)\n }\n\n return {\n id: newConvId,\n sessionId: source.sessionId,\n title: forkTitle,\n assistantId: source.assistantId,\n activeMessageId: lastNewId,\n createdAt: now,\n updatedAt: now,\n }\n }\n\n updateMessageContent(messageId: string, newContent: string): void {\n const db = this.getDb()\n const result = db.prepare('UPDATE ai_message SET content = ? WHERE id = ?').run(newContent, messageId)\n if (result.changes === 0) throw new Error('Message not found')\n }\n\n deleteAndRelinkMessage(aiChatId: string, messageId: string): void {\n const db = this.getDb()\n const target = this.getMessageRow(messageId)\n if (!target || target.aiChatId !== aiChatId) {\n throw new Error('Message not found in AI chat')\n }\n\n db.prepare('UPDATE ai_message SET parent_id = ? WHERE parent_id = ? AND ai_chat_id = ?').run(\n target.parentId,\n messageId,\n aiChatId\n )\n\n const conv = this.getAIChat(aiChatId)\n if (conv?.activeMessageId === messageId) {\n const now = Math.floor(Date.now() / 1000)\n db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(\n target.parentId,\n now,\n aiChatId\n )\n }\n\n db.prepare('DELETE FROM ai_message WHERE id = ?').run(messageId)\n }\n\n insertMessageAfter(\n aiChatId: string,\n afterMessageId: string,\n role: AIMessageRole,\n content: string,\n contentBlocks?: ContentBlock[],\n tokenUsage?: TokenUsageData\n ): AIMessage {\n const db = this.getDb()\n const now = Math.floor(Date.now() / 1000)\n const id = this.generateId('msg')\n\n const pendingDebug = role === 'assistant' ? this.pendingDebugContextMap.get(aiChatId) : undefined\n if (pendingDebug) {\n this.pendingDebugContextMap.delete(aiChatId)\n }\n\n const childRow = db\n .prepare('SELECT id FROM ai_message WHERE parent_id = ? AND ai_chat_id = ? LIMIT 1')\n .get(afterMessageId, aiChatId) as { id: string } | undefined\n\n db.prepare(\n `INSERT INTO ai_message (\n id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,\n content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index\n )\n VALUES (?, ?, ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, 0)`\n ).run(\n id,\n aiChatId,\n role,\n content,\n now,\n contentBlocks ? JSON.stringify(contentBlocks) : null,\n tokenUsage ? JSON.stringify(tokenUsage) : null,\n pendingDebug ?? null,\n afterMessageId,\n id\n )\n\n if (childRow) {\n db.prepare('UPDATE ai_message SET parent_id = ? WHERE id = ?').run(id, childRow.id)\n db.prepare('UPDATE ai_chat SET updated_at = ? WHERE id = ?').run(now, aiChatId)\n } else {\n db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(id, now, aiChatId)\n }\n\n return {\n id,\n aiChatId,\n role,\n content,\n timestamp: now,\n parentId: afterMessageId,\n contentBlocks,\n tokenUsage,\n }\n }\n\n getAIChatTokenUsage(aiChatId: string): TokenUsageData {\n const rows = this.getActivePathRows(aiChatId)\n const result: TokenUsageData = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n for (const row of rows) {\n if (row.tokenUsage) {\n const usage = JSON.parse(row.tokenUsage) as TokenUsageData\n result.promptTokens += usage.promptTokens\n result.completionTokens += usage.completionTokens\n result.totalTokens += usage.totalTokens\n result.cacheReadTokens! += usage.cacheReadTokens || 0\n result.cacheWriteTokens! += usage.cacheWriteTokens || 0\n }\n }\n return result\n }\n\n // ==================== Debug context ====================\n\n setPendingDebugContext(aiChatId: string, debugContext: string): void {\n this.pendingDebugContextMap.set(aiChatId, debugContext)\n }\n\n setDebugContext(messageId: string, debugContext: string): void {\n const db = this.getDb()\n db.prepare('UPDATE ai_message SET debug_context = ? WHERE id = ?').run(debugContext, messageId)\n }\n\n clearAllDebugContext(): number {\n const db = this.getDb()\n const result = db.prepare('UPDATE ai_message SET debug_context = NULL WHERE debug_context IS NOT NULL').run()\n return result.changes\n }\n\n // ==================== Agent 专用 ====================\n\n getHistoryForAgent(\n aiChatId: string,\n maxMessages?: number,\n leafMessageId?: string | null\n ): Array<{ role: 'user' | 'assistant' | 'summary'; content: string; contentBlocks?: ContentBlock[] }> {\n const messages = this.getActivePathRows(aiChatId, leafMessageId).map((row) => this.parseMessageRow(row))\n const validMessages = messages.filter(\n (m) =>\n (m.role === 'user' || m.role === 'assistant' || m.role === 'summary') &&\n (m.content?.trim() || (m.role === 'assistant' && m.contentBlocks?.some((b) => b.type === 'tool')))\n )\n\n let summaryMsg: AIMessage | undefined\n for (let i = validMessages.length - 1; i >= 0; i--) {\n if (validMessages[i].role === 'summary') {\n summaryMsg = validMessages[i]\n break\n }\n }\n\n let result: Array<{ role: 'user' | 'assistant' | 'summary'; content: string; contentBlocks?: ContentBlock[] }>\n\n if (summaryMsg) {\n const metaBlock = summaryMsg.contentBlocks?.find(\n (b): b is Extract<ContentBlock, { type: 'summary_meta' }> => b.type === 'summary_meta'\n )\n const bufferBoundary = metaBlock?.bufferBoundaryTimestamp\n\n if (!metaBlock) {\n this.logger.warn('AIChats', 'summary message missing summary_meta; agent context will be summary-only', {\n aiChatId,\n messageId: summaryMsg.id,\n })\n }\n\n const contextMessages = bufferBoundary\n ? validMessages.filter((m) => m.role !== 'summary' && m.timestamp >= bufferBoundary)\n : []\n\n result = [\n { role: 'summary' as const, content: summaryMsg.content },\n ...contextMessages.map((m) => ({ role: m.role, content: m.content, contentBlocks: m.contentBlocks })),\n ]\n } else {\n result = validMessages.map((m) => ({ role: m.role, content: m.content, contentBlocks: m.contentBlocks }))\n }\n\n if (maxMessages && result.length > maxMessages) {\n if (result.length > 0 && result[0].role === 'summary') {\n const rest = result.slice(1)\n const truncated = rest.slice(-(maxMessages - 1))\n return [result[0], ...truncated]\n }\n return result.slice(-maxMessages)\n }\n return result\n }\n\n // ==================== Summary / 压缩专用 ====================\n\n addSummaryMessage(\n aiChatId: string,\n content: string,\n meta: { bufferBoundaryTimestamp: number; compressedMessageCount: number }\n ): AIMessage {\n const contentBlocks: ContentBlock[] = [\n {\n type: 'summary_meta',\n bufferBoundaryTimestamp: meta.bufferBoundaryTimestamp,\n compressedMessageCount: meta.compressedMessageCount,\n },\n ]\n\n return this.addMessage(aiChatId, 'summary', content, undefined, undefined, contentBlocks)\n }\n\n getLatestSummary(aiChatId: string): AIMessage | null {\n const row = [...this.getActivePathRows(aiChatId)].reverse().find((message) => message.role === 'summary')\n return row ? this.parseMessageRow(row) : null\n }\n\n getMessagesAfterSummary(\n aiChatId: string,\n summaryTimestamp: number\n ): Array<{ role: AIMessageRole; content: string; timestamp: number; contentBlocks?: ContentBlock[] }> {\n return this.getActivePathRows(aiChatId)\n .filter((row) => row.timestamp > summaryTimestamp && (row.role === 'user' || row.role === 'assistant'))\n .map((row) => this.toCompressionMessage(row))\n }\n\n getAllUserAssistantMessages(\n aiChatId: string\n ): Array<{ role: AIMessageRole; content: string; timestamp: number; contentBlocks?: ContentBlock[] }> {\n return this.getActivePathRows(aiChatId)\n .filter((row) => row.role === 'user' || row.role === 'assistant')\n .map((row) => this.toCompressionMessage(row))\n }\n\n private toCompressionMessage(row: AIMessageRow): {\n role: AIMessageRole\n content: string\n timestamp: number\n contentBlocks?: ContentBlock[]\n } {\n return {\n role: row.role as AIMessageRole,\n content: row.content,\n timestamp: row.timestamp,\n contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : undefined,\n }\n }\n\n getMessageCountAfterSummary(aiChatId: string): number {\n const summary = this.getLatestSummary(aiChatId)\n if (!summary) {\n return this.getActivePathRows(aiChatId).filter((row) => row.role === 'user' || row.role === 'assistant').length\n }\n\n const metaBlock = summary.contentBlocks?.find(\n (b): b is Extract<ContentBlock, { type: 'summary_meta' }> => b.type === 'summary_meta'\n )\n const boundary = metaBlock?.bufferBoundaryTimestamp ?? summary.timestamp\n\n return this.getActivePathRows(aiChatId).filter(\n (row) => row.timestamp >= boundary && (row.role === 'user' || row.role === 'assistant')\n ).length\n }\n}\n","/**\n * Token 计数模块\n *\n * 使用 js-tiktoken/lite + cl100k_base 编码进行近似 token 计数。\n * cl100k_base 是 GPT-4 / Claude 系列的近似值,对国内模型有一定误差,\n * 因此阈值计算时预留了余量。\n *\n * 【打包优化】使用 lite 入口 + 动态 import rank 表:\n * - `js-tiktoken/lite` 本身不内联任何 BPE rank 数据(~0KB 额外体积)\n * - rank 表通过动态 import 加载,rollup 会将其拆为独立 chunk,\n * 不再随主进程启动路径同步加载(原方式会内联 ~1MB base64 数据)\n * - 调用 initTokenizer() 完成后,countTokens/countMessagesTokens 使用精确计数;\n * 初始化前的调用会降级到轻量字符估算(误差 <10%,适合\"近似\"场景)\n */\n\nimport { Tiktoken } from 'js-tiktoken/lite'\n\nlet encoder: Tiktoken | null = null\nlet initPromise: Promise<void> | null = null\n\n/**\n * 异步初始化 tokenizer。\n * 应在 AI agent 启动前 await 此函数,确保压缩/预处理路径使用精确计数。\n * 可安全多次调用(幂等)。\n */\nexport async function initTokenizer(): Promise<void> {\n if (encoder) return\n if (initPromise) return initPromise\n\n initPromise = (async () => {\n const ranks = (await import('js-tiktoken/ranks/cl100k_base')).default\n encoder = new Tiktoken(ranks)\n })()\n\n return initPromise\n}\n\n/**\n * 轻量字符估算(fallback)\n * CJK 字符约 1.6 字符/token,ASCII 约 4 字符/token\n */\nfunction estimateTokens(text: string): number {\n if (!text) return 0\n let cjk = 0\n let other = 0\n for (const ch of text) {\n const cp = ch.codePointAt(0) ?? 0\n // CJK 统一表意文字 + 扩展区 + 假名 + 谚文\n if (\n (cp >= 0x4e00 && cp <= 0x9fff) ||\n (cp >= 0x3040 && cp <= 0x30ff) ||\n (cp >= 0xac00 && cp <= 0xd7af) ||\n (cp >= 0x3400 && cp <= 0x4dbf)\n ) {\n cjk++\n } else {\n other++\n }\n }\n return Math.ceil(cjk / 1.6 + other / 4)\n}\n\n/**\n * 计算单段文本的 token 数。\n * encoder 已初始化时使用精确计数,否则降级到字符估算。\n */\nexport function countTokens(text: string): number {\n if (!text) return 0\n if (encoder) {\n return encoder.encode(text).length\n }\n return estimateTokens(text)\n}\n\n/**\n * 计算消息列表的总 token 数(含 systemPrompt)。\n * 每条消息额外计 4 tokens 的格式开销(role + 分隔符)。\n */\nexport function countMessagesTokens(\n messages: Array<{ role: string; content: string }>,\n systemPrompt?: string,\n): number {\n let total = 0\n\n if (systemPrompt) {\n total += countTokens(systemPrompt) + 4\n }\n\n for (const msg of messages) {\n total += countTokens(msg.content) + 4\n }\n\n // 回复引导 token\n total += 3\n\n return total\n}\n","export const MAX_SKILL_MENU_ITEMS = 15\n\nconst SKILL_MENU_HEADER = `## 可用技能\n以下是你可以使用的分析技能。当你判断用户的问题适合使用某个技能时,\n请调用 activate_skill 工具激活它,然后按照返回的指导完成任务。`\n\nconst SKILL_MENU_CLOSING = '如果用户的问题不需要使用技能,直接回答即可。'\nconst SKILL_MENU_CLOSING_BLOCK = `\\n\\n${SKILL_MENU_CLOSING}`\n\nexport interface SkillMenuItem {\n id: string\n name: string\n description: string\n guidance?: string\n}\n\nexport function formatSkillMenuLine(item: SkillMenuItem): string {\n const guidanceSuffix = item.guidance ? `. ${item.guidance}` : ''\n return `- ${item.id}: ${item.name} — ${item.description}${guidanceSuffix}`\n}\n\nexport function buildSkillMenuText(lines: readonly string[]): string | null {\n const visibleLines = normalizeSkillMenuLines(lines)\n if (visibleLines.length === 0) return null\n\n return `${SKILL_MENU_HEADER}\n\n${visibleLines.join('\\n')}\n\n${SKILL_MENU_CLOSING}`\n}\n\nexport function appendSkillMenuLines(baseMenu: string | null | undefined, lines: readonly string[]): string | null {\n const visibleLines = normalizeSkillMenuLines(lines)\n if (visibleLines.length === 0) return baseMenu ?? null\n if (!baseMenu) return buildSkillMenuText(visibleLines)\n\n const insertion = `\\n${visibleLines.join('\\n')}`\n if (!baseMenu.includes(SKILL_MENU_CLOSING_BLOCK)) {\n return `${baseMenu}${insertion}`\n }\n\n return baseMenu.replace(SKILL_MENU_CLOSING_BLOCK, `${insertion}${SKILL_MENU_CLOSING_BLOCK}`)\n}\n\nfunction normalizeSkillMenuLines(lines: readonly string[]): string[] {\n return lines.map((line) => line.trim()).filter(Boolean)\n}\n","/**\n * Platform-agnostic skill manager (core logic).\n * Same strategy as AssistantManager: abstract FS and builtin resources via DI.\n *\n * Note: Named \"skill-manager-core\" to avoid conflict with the existing\n * Electron-specific SkillManager class in skill-manager.ts.\n */\n\nimport { parseSkillFile } from './skill-parser'\nimport { buildSkillMenuText, formatSkillMenuLine, MAX_SKILL_MENU_ITEMS } from './skill-menu'\nimport type { SkillDef, SkillSummary } from './types'\n\n// ==================== Result types ====================\n\nexport interface SkillInitResult {\n total: number\n}\n\nexport interface SkillSaveResult {\n success: boolean\n error?: string\n}\n\nexport interface BuiltinSkillInfo {\n id: string\n name: string\n description: string\n tags: string[]\n chatScope: 'all' | 'group' | 'private'\n tools: string[]\n imported: boolean\n hasUpdate: boolean\n}\n\n// ==================== Dependency abstraction ====================\n\nexport interface SkillManagerFs {\n ensureDir(dir: string): void\n listFiles(dir: string, ext: string): string[]\n readFile(filePath: string): string\n writeFile(filePath: string, content: string): void\n deleteFile(filePath: string): void\n fileExists(filePath: string): boolean\n joinPath(...parts: string[]): string\n}\n\nexport interface SkillManagerCoreDeps {\n fs: SkillManagerFs\n skillsDir: string\n builtinRawSkills?: Array<{ id: string; content: string }>\n contentHash?: (content: string) => string\n logger?: {\n info: (category: string, message: string, data?: unknown) => void\n warn: (category: string, message: string, data?: unknown) => void\n error: (category: string, message: string, data?: unknown) => void\n }\n}\n\n// ==================== Internal helpers ====================\n\nfunction toSummary(def: SkillDef): SkillSummary {\n return {\n id: def.id,\n name: def.name,\n description: def.description,\n tags: def.tags,\n chatScope: def.chatScope,\n tools: def.tools,\n builtinId: def.builtinId,\n }\n}\n\nfunction injectBuiltinId(rawMd: string, builtinId: string): string {\n if (rawMd.includes('builtinId:')) return rawMd\n const endOfFrontmatter = rawMd.indexOf('\\n---', 3)\n if (endOfFrontmatter === -1) return rawMd\n return rawMd.slice(0, endOfFrontmatter) + `\\nbuiltinId: ${builtinId}` + rawMd.slice(endOfFrontmatter)\n}\n\nfunction stripBuiltinId(content: string): string {\n return content.replace(/\\nbuiltinId:.*\\n/g, '\\n')\n}\n\nfunction simpleHash(content: string): string {\n let hash = 0\n for (let i = 0; i < content.length; i++) {\n hash = ((hash << 5) - hash + content.charCodeAt(i)) | 0\n }\n return hash.toString(36)\n}\n\n// ==================== Manager ====================\n\nexport class SkillManagerCore {\n private deps: SkillManagerCoreDeps\n private builtinDefs = new Map<string, SkillDef>()\n private builtinRawMap = new Map<string, string>()\n private cache = new Map<string, SkillDef>()\n private initialized = false\n private hashFn: (content: string) => string\n\n constructor(deps: SkillManagerCoreDeps) {\n this.deps = deps\n this.hashFn = deps.contentHash || simpleHash\n this.initBuiltinCache()\n }\n\n private initBuiltinCache(): void {\n if (!this.deps.builtinRawSkills) return\n for (const { id, content } of this.deps.builtinRawSkills) {\n this.builtinRawMap.set(id, content)\n const def = parseSkillFile(content, `${id}.md`)\n if (def) this.builtinDefs.set(id, def)\n }\n }\n\n private ensureInitialized(): void {\n if (!this.initialized) this.init()\n }\n\n // ==================== Init ====================\n\n init(): SkillInitResult {\n const { fs, skillsDir } = this.deps\n fs.ensureDir(skillsDir)\n this.loadAll()\n\n this.initialized = true\n this.deps.logger?.info('SkillManager', 'Initialized', { total: this.cache.size })\n\n return { total: this.cache.size }\n }\n\n private loadAll(): void {\n const { fs, skillsDir } = this.deps\n this.cache.clear()\n\n const files = fs.listFiles(skillsDir, '.md')\n for (const file of files) {\n try {\n const filePath = fs.joinPath(skillsDir, file)\n const content = fs.readFile(filePath)\n const def = parseSkillFile(content, filePath)\n if (def) {\n this.cache.set(def.id, def)\n } else {\n this.deps.logger?.warn('SkillManager', `Failed to parse: ${file}`)\n }\n } catch (error) {\n this.deps.logger?.warn('SkillManager', `Failed to load: ${file}`, { error: String(error) })\n }\n }\n }\n\n // ==================== Query ====================\n\n getAllSkills(): SkillSummary[] {\n this.ensureInitialized()\n return Array.from(this.cache.values()).map(toSummary)\n }\n\n getSkillConfig(id: string): SkillDef | null {\n this.ensureInitialized()\n return this.cache.get(id) ?? null\n }\n\n // ==================== Builtin catalog ====================\n\n getBuiltinCatalog(): BuiltinSkillInfo[] {\n this.ensureInitialized()\n\n return Array.from(this.builtinDefs.entries()).map(([builtinId, builtin]) => {\n const userSkill = this.findByBuiltinId(builtinId)\n const imported = !!userSkill\n const hasUpdate = imported ? this.hasBuiltinUpdate(builtinId, userSkill!) : false\n\n return {\n id: builtinId,\n name: builtin.name,\n description: builtin.description,\n tags: builtin.tags,\n chatScope: builtin.chatScope,\n tools: builtin.tools,\n imported,\n hasUpdate,\n }\n })\n }\n\n // ==================== Import ====================\n\n importSkill(builtinId: string): SkillSaveResult & { id?: string } {\n this.ensureInitialized()\n\n const rawContent = this.builtinRawMap.get(builtinId)\n if (!rawContent) return { success: false, error: `Builtin skill not found: ${builtinId}` }\n\n const existing = this.findByBuiltinId(builtinId)\n if (existing) return { success: false, error: `Skill already imported: ${builtinId}` }\n\n const contentWithId = injectBuiltinId(rawContent, builtinId)\n const def = parseSkillFile(contentWithId, `${builtinId}.md`)\n if (!def) return { success: false, error: `Failed to parse builtin skill: ${builtinId}` }\n def.builtinId = builtinId\n\n const result = this.saveToDisk(def.id, contentWithId, def)\n return { ...result, id: result.success ? def.id : undefined }\n }\n\n reimportSkill(id: string): SkillSaveResult {\n this.ensureInitialized()\n\n const existing = this.cache.get(id)\n if (!existing) return { success: false, error: `Skill not found: ${id}` }\n if (!existing.builtinId) return { success: false, error: 'Only imported builtin skills can be reimported' }\n\n const rawContent = this.builtinRawMap.get(existing.builtinId)\n if (!rawContent) return { success: false, error: `Builtin template not found: ${existing.builtinId}` }\n\n const contentWithId = injectBuiltinId(rawContent, existing.builtinId)\n const def = parseSkillFile(contentWithId, `${id}.md`)\n if (!def) return { success: false, error: `Failed to parse builtin skill: ${existing.builtinId}` }\n def.builtinId = existing.builtinId\n\n return this.saveToDisk(id, contentWithId, def)\n }\n\n importSkillFromMd(rawMd: string): SkillSaveResult & { id?: string } {\n this.ensureInitialized()\n\n const def = parseSkillFile(rawMd, 'cloud_import.md')\n if (!def) return { success: false, error: 'Failed to parse skill markdown' }\n\n if (this.cache.has(def.id)) return { success: false, error: `Skill already exists: ${def.id}` }\n\n const result = this.saveToDisk(def.id, rawMd, def)\n return { ...result, id: result.success ? def.id : undefined }\n }\n\n // ==================== Mutate ====================\n\n updateSkill(id: string, rawMd: string): SkillSaveResult {\n this.ensureInitialized()\n\n const existing = this.cache.get(id)\n if (!existing) return { success: false, error: `Skill not found: ${id}` }\n\n const def = parseSkillFile(rawMd, `${id}.md`)\n if (!def) return { success: false, error: 'Failed to parse skill content' }\n\n def.id = id\n if (existing.builtinId) def.builtinId = existing.builtinId\n\n return this.saveToDisk(id, rawMd, def)\n }\n\n createSkill(rawMd: string): SkillSaveResult & { id?: string } {\n this.ensureInitialized()\n\n const def = parseSkillFile(rawMd, 'new_skill.md')\n if (!def) return { success: false, error: 'Failed to parse skill content' }\n\n if (this.cache.has(def.id)) {\n def.id = `${def.id}_${Date.now().toString(36)}`\n }\n\n const result = this.saveToDisk(def.id, rawMd, def)\n return { ...result, id: result.success ? def.id : undefined }\n }\n\n deleteSkill(id: string): SkillSaveResult {\n this.ensureInitialized()\n\n const existing = this.cache.get(id)\n if (!existing) return { success: false, error: `Skill not found: ${id}` }\n\n try {\n const filePath = this.deps.fs.joinPath(this.deps.skillsDir, `${id}.md`)\n if (this.deps.fs.fileExists(filePath)) this.deps.fs.deleteFile(filePath)\n this.cache.delete(id)\n return { success: true }\n } catch (error) {\n return { success: false, error: String(error) }\n }\n }\n\n // ==================== AI Skill Menu ====================\n\n getSkillMenu(chatType: 'group' | 'private', allowedTools?: string[]): string | null {\n this.ensureInitialized()\n\n const compatible = Array.from(this.cache.values()).filter((skill) => {\n if (skill.chatScope !== 'all' && skill.chatScope !== chatType) return false\n if (skill.tools.length > 0 && allowedTools !== undefined) {\n if (!skill.tools.every((t) => allowedTools.includes(t))) return false\n }\n return true\n })\n\n if (compatible.length === 0) return null\n\n const items = compatible.slice(0, MAX_SKILL_MENU_ITEMS)\n return buildSkillMenuText(items.map(formatSkillMenuLine))\n }\n\n // ==================== Internal ====================\n\n private findByBuiltinId(builtinId: string): SkillDef | undefined {\n return Array.from(this.cache.values()).find((s) => s.builtinId === builtinId)\n }\n\n private saveToDisk(id: string, rawMd: string, def: SkillDef): SkillSaveResult {\n try {\n const filePath = this.deps.fs.joinPath(this.deps.skillsDir, `${id}.md`)\n this.deps.fs.writeFile(filePath, rawMd)\n this.cache.set(id, def)\n return { success: true }\n } catch (error) {\n this.deps.logger?.error('SkillManager', `Failed to save: ${id}`, { error: String(error) })\n return { success: false, error: String(error) }\n }\n }\n\n private hasBuiltinUpdate(builtinId: string, userSkill: SkillDef): boolean {\n const rawContent = this.builtinRawMap.get(builtinId)\n if (!rawContent) return false\n\n try {\n const userFilePath = this.deps.fs.joinPath(this.deps.skillsDir, `${userSkill.id}.md`)\n const userContent = this.deps.fs.readFile(userFilePath)\n return this.hashFn(rawContent) !== this.hashFn(stripBuiltinId(userContent))\n } catch {\n return false\n }\n }\n}\n","/**\n * 技能管理器(平台无关)\n *\n * 从 aiDataDir/skills/*.md 加载技能定义,\n * 提供查询和 AI 自选菜单构建。\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { SkillManagerCore, type SkillInitResult, type SkillManagerFs } from './skill-manager-core'\nimport type { SkillDef, SkillSummary } from './types'\n\nconst SKILLS_DIR_NAME = 'skills'\n\nexport interface SkillManagerLogger {\n info(message: string, extra?: Record<string, unknown>): void\n warn(message: string, extra?: Record<string, unknown>): void\n}\n\nconst defaultLogger: SkillManagerLogger = {\n info: () => {},\n warn: () => {},\n}\n\nconst nodeFs: SkillManagerFs = {\n ensureDir(dir) {\n fs.mkdirSync(dir, { recursive: true })\n },\n listFiles(dir, ext) {\n if (!fs.existsSync(dir)) return []\n return fs.readdirSync(dir).filter((file) => file.endsWith(ext))\n },\n readFile(filePath) {\n return fs.readFileSync(filePath, 'utf-8')\n },\n writeFile(filePath, content) {\n fs.writeFileSync(filePath, content, 'utf-8')\n },\n deleteFile(filePath) {\n fs.unlinkSync(filePath)\n },\n fileExists(filePath) {\n return fs.existsSync(filePath)\n },\n joinPath(...parts) {\n return path.join(...parts)\n },\n}\n\nexport class SkillManager {\n private core: SkillManagerCore\n private logger: SkillManagerLogger\n\n constructor(aiDataDir: string, logger?: SkillManagerLogger) {\n this.logger = logger ?? defaultLogger\n this.core = new SkillManagerCore({\n fs: nodeFs,\n skillsDir: path.join(aiDataDir, SKILLS_DIR_NAME),\n builtinRawSkills: [],\n logger: {\n info: () => {},\n warn: (_category, message, data) => this.logger.warn(message, toLoggerExtra(data)),\n error: (_category, message, data) => this.logger.warn(message, toLoggerExtra(data)),\n },\n })\n }\n\n init(): SkillInitResult {\n const result = this.core.init()\n this.logger.info(`SkillManager initialized: ${result.total} skills`)\n return result\n }\n\n getSkillConfig(id: string): SkillDef | null {\n return this.core.getSkillConfig(id)\n }\n\n getAllSkills(): SkillSummary[] {\n return this.core.getAllSkills()\n }\n\n /**\n * 构建 AI 自选技能菜单文本\n * 只包含与当前 chatType + 助手工具权限兼容的技能\n */\n getSkillMenu(chatType: 'group' | 'private', allowedTools?: string[]): string | null {\n return this.core.getSkillMenu(chatType, allowedTools)\n }\n}\n\nfunction toLoggerExtra(data: unknown): Record<string, unknown> | undefined {\n if (data === undefined) return undefined\n if (data && typeof data === 'object' && !Array.isArray(data)) return data as Record<string, unknown>\n return { data }\n}\n","/**\n * activate_skill 元工具(AI 自选模式专用)\n *\n * LLM 判断用户问题适合某个技能时调用此工具,获取技能的完整执行指导。\n * 平台无关 — 通过 getter 函数获取技能定义。\n */\n\nimport type { SkillDef } from './types'\n\nexport interface ActivateSkillToolResult {\n content: Array<{ type: 'text'; text: string }>\n details: Record<string, unknown>\n}\n\nexport interface ActivateSkillToolOptions {\n chatType: 'group' | 'private'\n allowedTools?: string[]\n coreToolNames?: Set<string>\n locale?: string\n getSkillConfig: (id: string) => SkillDef | null\n}\n\nexport interface ActivateSkillTool {\n name: string\n label: string\n description: string\n parameters: Record<string, unknown>\n execute: (\n toolCallId: string,\n params: unknown,\n signal?: AbortSignal,\n onUpdate?: unknown\n ) => Promise<ActivateSkillToolResult>\n}\n\nexport function createActivateSkillTool(options: ActivateSkillToolOptions): ActivateSkillTool {\n const { chatType, allowedTools, coreToolNames, getSkillConfig, locale = 'zh-CN' } = options\n const isZh = locale.startsWith('zh')\n\n return {\n name: 'activate_skill',\n label: 'activate_skill',\n description: isZh\n ? '激活一个分析技能,获取该技能的详细执行指导'\n : 'Activate an analysis skill and get its detailed execution instructions',\n parameters: {\n type: 'object',\n properties: {\n skill_id: {\n type: 'string',\n description: isZh ? '技能 ID' : 'Skill ID',\n },\n },\n required: ['skill_id'],\n },\n execute: async (_toolCallId: string, params: unknown) => {\n const toolParams = (params && typeof params === 'object' ? params : {}) as { skill_id?: string }\n const skillId = toolParams.skill_id || ''\n const skill: SkillDef | null = getSkillConfig(skillId)\n if (!skill) {\n return {\n content: [{ type: 'text' as const, text: isZh ? '技能不存在' : 'Skill not found' }],\n details: { skillId, found: false },\n }\n }\n\n if (skill.chatScope !== 'all' && skill.chatScope !== chatType) {\n const scopeMsg = isZh\n ? `该技能仅适用于${skill.chatScope === 'group' ? '群聊' : '私聊'}场景`\n : `This skill is only applicable to ${skill.chatScope === 'group' ? 'group chat' : 'private chat'} scenarios`\n return {\n content: [{ type: 'text' as const, text: scopeMsg }],\n details: { skillId, found: true, applicable: false },\n }\n }\n\n if (skill.tools.length > 0 && allowedTools !== undefined) {\n const missing = skill.tools.filter((t) => !(coreToolNames?.has(t) ?? false) && !allowedTools.includes(t))\n if (missing.length > 0) {\n const msg = isZh\n ? `当前助手缺少该技能所需的工具:${missing.join(', ')}`\n : `Current assistant lacks tools required by this skill: ${missing.join(', ')}`\n return {\n content: [{ type: 'text' as const, text: msg }],\n details: { skillId, found: true, applicable: false, missingTools: missing },\n }\n }\n }\n\n const actionPrompt = isZh\n ? '\\n\\n[System]: 你已成功加载该技能手册。现在,请立即、自动地开始执行步骤1,调用相关的基础数据工具,不要等待用户的进一步确认!'\n : '\\n\\n[System]: You have successfully loaded this skill manual. Now, immediately start executing step 1 by calling the relevant data tools. Do not wait for further user confirmation!'\n\n return {\n content: [{ type: 'text' as const, text: `${skill.prompt}${actionPrompt}` }],\n details: { skillId, found: true, applicable: true },\n }\n },\n }\n}\n","import { CHART_CAPABILITY_SKILL_ID } from '@openchatlab/core'\nimport { appendSkillMenuLines, formatSkillMenuLine } from './skill-menu'\nimport type { SkillDef } from './types'\nimport type { PlannerCapabilitySummary } from './agent/planning-types'\nimport type { ChartAutoMode } from '@openchatlab/shared-types'\n\nexport { CHART_CAPABILITY_SKILL_ID }\n\nexport const CHART_CAPABILITY_CORE_TOOLS = ['get_schema'] as const\nexport const CHART_CAPABILITY_ANALYSIS_TOOLS = ['render_chart'] as const\n\nconst RAW_SQL_TOOL_NAMES = new Set(['execute_sql'])\n\nexport interface ResolveChartRuntimeOptions {\n skillId?: string | null\n userMessage?: string\n locale?: string\n assistantAllowedTools?: readonly string[] | null\n enableAutoDetection?: boolean\n enableAnalyticalAutoDetection?: boolean\n chartAutoMode?: ChartAutoMode\n}\n\nexport interface ResolvedChartRuntime {\n isChartCapability: boolean\n skillDef?: SkillDef\n allowedBuiltinTools?: string[]\n}\n\nexport function getChartCapabilityAllowedBuiltinTools(allowedTools?: readonly string[] | null): string[] {\n const baseTools = (allowedTools ?? []).filter((toolName) => !RAW_SQL_TOOL_NAMES.has(toolName))\n return Array.from(new Set([...baseTools, ...CHART_CAPABILITY_ANALYSIS_TOOLS]))\n}\n\nexport function getAllowedBuiltinToolsForChartAutoSkill(allowedTools?: readonly string[] | null): string[] | undefined {\n if (!allowedTools) return undefined\n if (allowedTools.length === 0) return []\n return Array.from(new Set([...allowedTools, ...CHART_CAPABILITY_ANALYSIS_TOOLS]))\n}\n\nexport function shouldUseChartCapabilityForMessage(message: string): boolean {\n const normalized = message.trim().toLowerCase()\n if (!normalized) return false\n\n return /(?:画|绘制|生成|做|出|展示).{0,8}(?:图|图表)|(?:图表|可视化|饼图|柱状图|条形图|折线图|热力图|趋势图|占比图|分布图|chart|charts|plot|visuali[sz]e|visuali[sz]ation|pie chart|bar chart|line chart|heatmap)/i.test(\n normalized\n )\n}\n\nexport function shouldOfferChartCapabilityForAnalyticalMessage(message: string): boolean {\n const normalized = message.trim().toLowerCase()\n if (!normalized) return false\n if (shouldUseChartCapabilityForMessage(normalized)) return true\n\n return /(?:趋势|变化|走势|排名|排行|前\\s*\\d+|top\\s*\\d*|最多|最少|高频|分布|占比|比例|构成|份额|活跃度|热度|按.{0,8}统计|统计.{0,8}按|trend|ranking|rank|top\\s*\\d*|distribution|ratio|share|percentage|breakdown)/i.test(\n normalized\n )\n}\n\nexport function getChartPlannerCapabilityForMessage(options: {\n userMessage: string\n locale?: string\n availableTools: readonly string[]\n chartAutoMode?: ChartAutoMode\n}): PlannerCapabilitySummary | null {\n const availableToolSet = new Set(options.availableTools)\n if (!availableToolSet.has('get_schema') || !availableToolSet.has('render_chart')) {\n return null\n }\n const chartAutoMode = options.chartAutoMode ?? 'suggest'\n const shouldOffer =\n chartAutoMode === 'explicit'\n ? shouldUseChartCapabilityForMessage(options.userMessage)\n : shouldOfferChartCapabilityForAnalyticalMessage(options.userMessage)\n if (!shouldOffer) {\n return null\n }\n\n const isZh = (options.locale ?? 'zh-CN').startsWith('zh')\n return {\n id: 'chart_generation',\n label: isZh ? '图表生成' : 'Chart generation',\n tools: ['get_schema', 'render_chart'],\n guidance: isZh\n ? '用户明确要求图表,或趋势、排名、分布、占比等分析用图更清楚时,可以在计划中加入最多一张图表步骤;必须先用 get_schema 确认 schema,再用 render_chart 生成图表;不要输出绘图代码。'\n : 'When the user explicitly asks for charts, or when trends, rankings, distributions, or ratios are clearer visually, you may add at most one chart step to the plan; call get_schema before render_chart; do not output chart-rendering code.',\n }\n}\n\nexport function resolveChartRuntimeForRequest(options: ResolveChartRuntimeOptions): ResolvedChartRuntime {\n const locale = options.locale ?? 'zh-CN'\n const isExplicitChartSkill = options.skillId === CHART_CAPABILITY_SKILL_ID\n const allowAnalyticalAutoDetection =\n options.chartAutoMode !== undefined\n ? options.chartAutoMode === 'aggressive'\n : options.enableAnalyticalAutoDetection === true\n const shouldAutoDetectChart =\n shouldUseChartCapabilityForMessage(options.userMessage ?? '') ||\n (allowAnalyticalAutoDetection && shouldOfferChartCapabilityForAnalyticalMessage(options.userMessage ?? ''))\n const isAutoChartSkill = options.enableAutoDetection === true && !options.skillId && shouldAutoDetectChart\n\n if (!isExplicitChartSkill && !isAutoChartSkill) {\n return { isChartCapability: false }\n }\n\n return {\n isChartCapability: true,\n skillDef: { ...getChartCapabilitySkill(locale), chatScope: 'all' },\n allowedBuiltinTools: getChartCapabilityAllowedBuiltinTools(options.assistantAllowedTools),\n }\n}\n\nexport function getChartCapabilitySkill(locale: string = 'zh-CN'): SkillDef {\n const isZh = locale.startsWith('zh')\n return {\n id: CHART_CAPABILITY_SKILL_ID,\n name: isZh ? '绘图助手' : 'Chart Assistant',\n description: isZh ? '按本轮问题生成灵活的聊天数据图表' : 'Generate flexible charts for this chat question',\n tags: [isZh ? '图表' : 'chart'],\n chatScope: 'all',\n tools: ['render_chart', 'get_schema'],\n prompt: isZh ? ZH_PROMPT : EN_PROMPT,\n builtinId: CHART_CAPABILITY_SKILL_ID,\n }\n}\n\nfunction getChartMenuLine(locale: string): string {\n const skill = getChartCapabilitySkill(locale)\n const isZh = locale.startsWith('zh')\n const guidance = isZh\n ? '用户明确要求图表、画图、占比、趋势、分布、饼图、柱状图、折线图或热力图时优先激活;不要输出 Python/JS 绘图代码'\n : 'Activate first when the user explicitly asks for charts, visualization, ratios, trends, distributions, pie, bar, line, or heatmap charts; do not output Python/JS chart code'\n return formatSkillMenuLine({\n id: skill.id,\n name: skill.name,\n description: skill.description,\n guidance,\n })\n}\n\nexport function buildSkillMenuWithBuiltinChart(\n baseMenu: string | null | undefined,\n locale: string = 'zh-CN',\n allowedTools?: readonly string[] | null\n): string | null {\n if (allowedTools && !allowedTools.includes(CHART_CAPABILITY_ANALYSIS_TOOLS[0])) return baseMenu ?? null\n if (baseMenu?.includes(CHART_CAPABILITY_SKILL_ID)) return baseMenu\n\n const chartLine = getChartMenuLine(locale)\n return appendSkillMenuLines(baseMenu, [chartLine]) ?? ''\n}\n\nexport function getSkillConfigWithBuiltinChart(\n id: string,\n locale: string = 'zh-CN',\n getSkillConfig: (id: string) => SkillDef | null\n): SkillDef | null {\n if (id === CHART_CAPABILITY_SKILL_ID) return getChartCapabilitySkill(locale)\n return getSkillConfig(id)\n}\n\nconst ZH_PROMPT = `你是 ChatLab 绘图助手。本轮用户希望你在回答里自然嵌入一张或多张图表。\n\n你必须通过 render_chart 工具生成图表。不要输出 HTML、JavaScript、SVG、Canvas、ECharts option、Markdown 图片链接(如 ![图表](chart1.png))或任何渲染代码。\n\n硬性规则:\n1. 第一次写 SQL 前必须先调用 get_schema。禁止猜测表名、字段名或时间字段。\n2. “最近”“过去 N 天/年”等相对时间以系统提示中的真实当前日期为基准;如果启动上下文显示数据库覆盖不足,只分析用户时间范围与数据库覆盖范围的交集并说明缺口。\n3. ChatLab 的 message.ts 是秒级 Unix 时间戳,使用 date(ts, 'unixepoch', 'localtime');禁止写 ts/1000。\n4. “最近的 N 人/成员”必须先用数据定义并筛选出 N 个成员,再只统计这些成员。\n5. 折线图有多条线时,每条线必须有明确 series 字段含义;按日期统计时应补齐日期 × series 的 0 值,避免缺失日期导致折线断裂或误连。\n6. 工具失败、SQL 修正、schema 探索过程不要写进最终回答;最终只保留原生图表和简短结论,禁止用 chart1.png、chart2.png 等文件名引用图表。\n7. 调用 render_chart 时,ChatLab 会立即在该位置插入原生图表;需要图表就直接调用工具,不要写 SQL 注释、Markdown 图片或“已生成图表/见上图”这类文本占位。\n8. 用户要求多张图时,图表应分散出现在相关总结或解释附近;不要把所有图集中生成后再在最后用文字引用。\n9. 为图表准备数据时,优先调用高层工具(如 get_time_stats、member_stats)而非自己写 SQL;只有高层工具无法覆盖所需筛选或聚合时才用 sql。render_chart 不要用于元数据探测、标量查询、单行日期字符串或纯说明性结果。\n10. render_chart 的工具结果会包含数据预览(Data preview);用它分析峰值、低谷和差异,不要为了读取数值重复生成等价图表。\n11. 图表解释只能描述数据直接支持的趋势、峰值、低谷、差异和同步性,禁止夸张拟人化或无法从图中验证的关系判断。\n\n工作流程:\n1. 调用 get_schema。\n2. 根据 schema、用户要求、系统当前日期和启动上下文确定统计对象、时间范围、维度、指标和图表类型。\n3. 如需调查事实、建立话题地图、筛选成员或确认统计口径,先用可用的普通分析工具完成;不要为了调查而生成临时图表。\n4. 准备图表数据——先问自己:哪个可用的高层工具能直接提供这张图所需的数据?get_time_stats 能给时间分布、member_stats 能给成员排名;能用就调用,将 data 数组作为 rows 传入 render_chart。只有在高层工具无法覆盖所需筛选或聚合时,才编写 SQL。\n5. 在希望图表出现的位置调用 render_chart,提交数据(rows 或 sql)和 ChartSpec v1。\n6. 图表生成后,用 1-3 句简洁文字解释结论。用户要求多张图时,可以多次调用 render_chart,并把每张图放在最相关的段落附近。\n\n常用语、口头禅、高频短句类图表必须排除非真人文本:发送者为系统消息、媒体占位(如 [表情包]、[图片]、[语音]、[视频]、[文件]、[链接]、[红包]、[转账])以及撤回/删除提示都不能进入统计。\n\nChartSpec v1 支持:\n- bar: encoding.x + encoding.y\n- line: encoding.x + encoding.y,可选 encoding.series 表示每条线的含义\n- pie: encoding.label + encoding.value\n- heatmap: encoding.x + encoding.y + encoding.value\n\nChartSpec 示例:\n{\n \"version\": 1,\n \"type\": \"line\",\n \"title\": \"最近 30 天成员发言趋势\",\n \"encoding\": { \"x\": \"day\", \"y\": \"msg_count\", \"series\": \"member_name\" },\n \"unit\": \"条\"\n}\n\n用户明确要求图表时必须尝试生成。用户没有明确要求时,只有排名、趋势、分布、占比或二维密度明显更清楚时才主动生成,且默认最多一张。`\n\nconst EN_PROMPT = `You are the ChatLab Chart Assistant. In this turn, the user wants one or more charts embedded naturally in the answer.\n\nYou must generate charts through the render_chart tool. Do not output HTML, JavaScript, SVG, Canvas, ECharts options, Markdown image links such as ![Chart](chart1.png), or rendering code.\n\nHard rules:\n1. Always call get_schema before writing the first SQL query. Do not guess table names, field names, or timestamp fields.\n2. For relative ranges like \"recent\" or \"past N days/years\", use the real current date from the system prompt as the baseline. If startup context shows incomplete database coverage, analyze only the intersection between the requested range and database coverage, and state the gap.\n3. ChatLab message.ts is a Unix timestamp in seconds. Use date(ts, 'unixepoch', 'localtime'); never write ts/1000.\n4. For \"latest N members\" or \"recent N people\", first define and select those N members from the data, then count only those members.\n5. For multi-series line charts, every line must have an explicit series field. For daily counts, fill the date x series grid with zero values so missing days do not break or mislead the line.\n6. Do not include failed SQL attempts, schema exploration, or retry reasoning in the final answer. Final answer should contain only native charts and a short conclusion; never reference charts with filenames like chart1.png or chart2.png.\n7. When you call render_chart, ChatLab immediately inserts the native chart at that position. If a chart is needed, call the tool directly; do not write SQL-comment placeholders, Markdown images, or text such as \"generated chart\" or \"see chart above\".\n8. If the user asks for multiple charts, place each chart near the relevant summary or explanation. Do not generate all charts together and then reference them later with text.\n9. When preparing chart data, prefer calling a high-level tool (such as get_time_stats, member_stats) over writing SQL; only use sql when no high-level tool can satisfy the required filters or aggregations. Do not use render_chart for metadata inspection, scalar queries, single-row date strings, or purely explanatory results.\n10. render_chart tool results include a Data preview; use it to identify peaks, lows, and differences. Do not generate equivalent charts again just to read values.\n11. Explain only trends, peaks, lows, differences, and synchrony directly supported by chart data. Do not make exaggerated personality or relationship claims.\n\nWorkflow:\n1. Call get_schema.\n2. Derive the target members, time range, dimensions, metrics, and chart type from schema, the user request, system current date, and startup context.\n3. If you need to investigate facts, build a topic map, select members, or confirm statistical definitions, use available ordinary analysis tools first; do not generate temporary charts for investigation.\n4. Prepare chart data — ask yourself first: which available high-level tool can directly provide the data this chart needs? get_time_stats covers time distributions; member_stats covers member rankings. If one fits, call it and pass its data array as rows to render_chart. Only write SQL when no high-level tool can cover the required filters or aggregations.\n5. Call render_chart at the position where the chart should appear, with data (rows or sql) and ChartSpec v1.\n6. After the chart is generated, explain the key finding in 1-3 concise sentences. If the user asks for multiple charts, call render_chart multiple times and place each chart near its most relevant section.\n\nFor catchphrase, common phrase, or frequent short-text charts, exclude non-human text from the SQL: system-message senders, media placeholders such as [Sticker], [Image], [Photo], [Voice], [Video], [File], [Link], and recall/deletion notices must not be counted.\n\nChartSpec v1 supports:\n- bar: encoding.x + encoding.y\n- line: encoding.x + encoding.y, optional encoding.series for the meaning of each line\n- pie: encoding.label + encoding.value\n- heatmap: encoding.x + encoding.y + encoding.value\n\nChartSpec example:\n{\n \"version\": 1,\n \"type\": \"line\",\n \"title\": \"Member message trend in the last 30 days\",\n \"encoding\": { \"x\": \"day\", \"y\": \"msg_count\", \"series\": \"member_name\" },\n \"unit\": \"messages\"\n}\n\nWhen the user explicitly asks for charts, you must try to generate them. If the user does not explicitly ask, add at most one chart only when ranking, trend, distribution, ratio, or two-dimensional density is clearly easier to understand visually.`\n","import type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core'\n\nexport const CHART_SCHEMA_REQUIRED_MESSAGE =\n 'Error: Call get_schema before render_chart. Do not guess table names, fields, or timestamp units.'\n\nexport interface ChartSchemaGateState {\n schemaSeen: boolean\n}\n\nexport function createChartSchemaGateState(): ChartSchemaGateState {\n return { schemaSeen: false }\n}\n\nexport function wrapWithChartSchemaGate(tool: AgentTool<any>, state: ChartSchemaGateState): AgentTool<any> {\n const originalExecute = tool.execute\n\n return {\n ...tool,\n execute: async (\n toolCallId: string,\n params: any,\n signal?: AbortSignal,\n onUpdate?: unknown\n ): Promise<AgentToolResult<any>> => {\n if (tool.name === 'render_chart' && !state.schemaSeen) {\n return {\n content: [{ type: 'text', text: CHART_SCHEMA_REQUIRED_MESSAGE }],\n details: null,\n } as AgentToolResult<any>\n }\n\n const result = await originalExecute(toolCallId, params, signal, onUpdate as never)\n\n if (tool.name === 'get_schema') {\n state.schemaSeen = true\n }\n\n return result\n },\n }\n}\n","/**\n * History replay — converts persisted chat history into pi messages.\n *\n * Assistant messages whose content blocks carry persisted tool calls\n * (toolCallId + result) are replayed as real toolCall/toolResult message\n * pairs, so later turns see what tools were called and what they returned.\n * Persisted toolCallIds are replayed verbatim to keep requests byte-stable\n * across turns (prompt cache friendly). Legacy messages without tool data\n * fall back to plain text replay.\n *\n * When model info is provided, thinking blocks are replayed alongside tool\n * calls so APIs that require reasoning_content on tool-call turns (e.g.\n * DeepSeek) receive the original reasoning chain instead of an empty string.\n */\n\nimport type { Message as PiMessage, AssistantMessage, Usage as PiUsage } from '@earendil-works/pi-ai'\nimport { truncateToolResultText } from '@openchatlab/core'\n\nimport type { SimpleHistoryMessage } from './types'\nimport type { ContentBlock } from '../chats'\n\ntype ToolBlock = Extract<ContentBlock, { type: 'tool' }>\nexport type ReplayableToolBlock = ToolBlock & { tool: ToolBlock['tool'] & { toolCallId: string; result: string } }\n\n/**\n * Model metadata attached to replayed assistant messages so pi-ai's\n * transform-messages layer treats them as same-model (preserving thinking\n * blocks in their native format instead of converting to plain text).\n */\nexport interface ReplayModelInfo {\n api: string\n provider: string\n id: string\n}\n\nexport interface ReplayOptions {\n modelInfo?: ReplayModelInfo\n /**\n * Field name used by the provider's streaming API for reasoning content\n * (e.g. \"reasoning_content\" for DeepSeek). When set and the assistant\n * message contains tool calls, persisted think blocks are replayed as\n * pi-ai ThinkingContent blocks with this signature.\n */\n thinkingSignature?: string\n}\n\nfunction createEmptyPiUsage(): PiUsage {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n }\n}\n\nexport function isReplayableToolBlock(block: ContentBlock): block is ReplayableToolBlock {\n if (block.type !== 'tool') return false\n const { toolCallId, result, status } = block.tool\n return (\n typeof toolCallId === 'string' &&\n toolCallId.length > 0 &&\n typeof result === 'string' &&\n (status === 'done' || status === 'error')\n )\n}\n\n/** Drop runtime-injected params (e.g. _timeFilter) that the model never produced. */\nfunction stripInternalParams(params?: Record<string, unknown>): Record<string, unknown> {\n if (!params) return {}\n const cleaned: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(params)) {\n if (!key.startsWith('_')) cleaned[key] = value\n }\n return cleaned\n}\n\nfunction makeAssistantMessage(\n content: AssistantMessage['content'],\n stopReason: 'stop' | 'toolUse',\n modelInfo?: ReplayModelInfo\n): AssistantMessage {\n return {\n role: 'assistant',\n content,\n api: (modelInfo?.api ?? 'openai-completions') as AssistantMessage['api'],\n provider: modelInfo?.provider ?? 'chatlab',\n model: modelInfo?.id ?? 'unknown',\n usage: createEmptyPiUsage(),\n stopReason,\n timestamp: Date.now(),\n }\n}\n\nfunction replayAssistantBlocks(blocks: ContentBlock[], out: PiMessage[], options?: ReplayOptions): void {\n const { modelInfo, thinkingSignature } = options ?? {}\n const replayThinking = !!thinkingSignature && blocks.some(isReplayableToolBlock)\n\n let content: AssistantMessage['content'] = []\n\n for (const block of blocks) {\n if (block.type === 'text') {\n if (block.text.trim()) content.push({ type: 'text', text: block.text })\n } else if (block.type === 'think' && replayThinking && block.text.trim()) {\n content.push({ type: 'thinking', thinking: block.text, thinkingSignature })\n } else if (isReplayableToolBlock(block)) {\n content.push({\n type: 'toolCall',\n id: block.tool.toolCallId,\n name: block.tool.name,\n arguments: stripInternalParams(block.tool.params),\n })\n out.push(makeAssistantMessage(content, 'toolUse', modelInfo))\n out.push({\n role: 'toolResult',\n toolCallId: block.tool.toolCallId,\n toolName: block.tool.name,\n content: [{ type: 'text', text: truncateToolResultText(block.tool.result) || '(empty result)' }],\n isError: block.tool.isError ?? block.tool.status === 'error',\n timestamp: Date.now(),\n })\n content = []\n }\n // chart/plan/error/summary_meta blocks and unfinished/legacy tool blocks are not replayed\n }\n\n if (content.length > 0) {\n out.push(makeAssistantMessage(content, 'stop', modelInfo))\n }\n}\n\nexport function toPiHistoryMessages(messages: SimpleHistoryMessage[], options?: ReplayOptions): PiMessage[] {\n const out: PiMessage[] = []\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n out.push({\n role: 'user',\n content: [{ type: 'text', text: msg.content || '' }],\n timestamp: Date.now(),\n })\n continue\n }\n\n // summary 作为 assistant 消息传给 LLM(它是压缩后的上下文总结)\n const replayable = msg.role === 'assistant' && msg.contentBlocks?.some(isReplayableToolBlock)\n if (replayable) {\n replayAssistantBlocks(msg.contentBlocks!, out, options)\n } else {\n out.push(makeAssistantMessage([{ type: 'text', text: msg.content || '' }], 'stop', options?.modelInfo))\n }\n }\n\n return out\n}\n","/**\n * 上下文压缩核心逻辑(平台无关)\n *\n * 通过 CompressionLlmAdapter 抽象 LLM 调用,\n * 通过 AIChatManager 操作对话数据。\n */\n\nimport { truncateToolResultText } from '@openchatlab/core'\n\nimport { countTokens, countMessagesTokens } from '../tokenizer'\nimport { isReplayableToolBlock } from '../agent/history'\nimport type { AIChatManager, ContentBlock, AIMessageRole } from '../chats'\nimport type { CompressionConfig, CompressionResult, CompressionLogger, CompressionLlmAdapter } from './types'\n\ninterface CompressibleMessage {\n role: string\n content: string\n timestamp: number\n contentBlocks?: ContentBlock[]\n}\n\nconst DEFAULT_CONTEXT_WINDOW = 128000\n\nconst INITIAL_COMPRESSION_PROMPT = `You are a context compression assistant. Compress the conversation below into a structured summary.\n\nSTRICT RULES:\n- Output ONLY the summary content. No greetings, no preamble, no meta-commentary, no word/token counts.\n- Use the same language as the conversation.\n- Maximum output length: {maxTokens} tokens. Be concise.\n- NEVER reproduce any single message verbatim. Always paraphrase and compress.\n- Cover ALL topics discussed — no single topic should exceed 30% of the summary.\n- Organize by topic/thread, using brief headers (e.g. \"## Topic\").\n- Preserve: key facts, conclusions, user preferences, data points, names, important timestamps, action items.\n- Omit: pleasantries, filler, redundant back-and-forth, detailed tables (summarize their conclusions instead).\n\nCONVERSATION:\n{messages}`\n\nconst PROGRESSIVE_COMPRESSION_PROMPT = `You are a context compression assistant performing an INCREMENTAL summary update.\n\nYou will receive:\n1. A [PREVIOUS SUMMARY] — this represents the compressed history of earlier conversation. Its content MUST be preserved in your output.\n2. [NEW MESSAGES] — recent messages that need to be merged into the summary.\n\nSTRICT RULES:\n- Output ONLY the updated summary. No greetings, no preamble, no meta-commentary.\n- Use the same language as the conversation.\n- Maximum output length: {maxTokens} tokens. Be concise.\n- CRITICAL: You MUST retain ALL key points from the previous summary. Do not discard prior context.\n- NEVER reproduce any single message verbatim. Always paraphrase and compress.\n- Merge new information into appropriate existing topic sections, or add new sections.\n- Cover ALL topics — no single topic should exceed 30% of the summary.\n- Organize by topic/thread, using brief headers (e.g. \"## Topic\").\n- Preserve: key facts, conclusions, user preferences, data points, names, important timestamps, action items.\n- Omit: pleasantries, filler, redundant back-and-forth, detailed tables (summarize their conclusions instead).\n\n{messages}`\n\nconst defaultLogger: CompressionLogger = {\n info: () => {},\n warn: () => {},\n error: () => {},\n}\n\nexport async function checkAndCompress(\n aiChatId: string,\n config: CompressionConfig,\n systemPrompt: string,\n llmAdapter: CompressionLlmAdapter,\n convManager: AIChatManager,\n logger: CompressionLogger = defaultLogger\n): Promise<CompressionResult> {\n if (!config.enabled) {\n return { compressed: false, reason: 'skipped_disabled' }\n }\n\n try {\n const contextWindow = llmAdapter.contextWindow || DEFAULT_CONTEXT_WINDOW\n const thresholdTokens = Math.floor(contextWindow * (config.tokenThresholdPercent / 100) * 0.95)\n\n const summary = convManager.getLatestSummary(aiChatId)\n\n let messages: Array<{ role: AIMessageRole; content: string; timestamp: number; contentBlocks?: ContentBlock[] }>\n if (summary) {\n const metaBlock = summary.contentBlocks?.find(\n (b): b is Extract<ContentBlock, { type: 'summary_meta' }> => b.type === 'summary_meta'\n )\n const boundary = metaBlock?.bufferBoundaryTimestamp ?? summary.timestamp\n messages = convManager.getMessagesAfterSummary(aiChatId, boundary - 1)\n } else {\n messages = convManager.getAllUserAssistantMessages(aiChatId)\n }\n\n const historyForTokenCount: Array<{ role: string; content: string }> = []\n if (summary) {\n historyForTokenCount.push({ role: 'assistant', content: summary.content })\n }\n for (const msg of messages) {\n historyForTokenCount.push({ role: msg.role, content: msg.content })\n // Persisted tool results are replayed as toolCall/toolResult pairs each\n // turn (see agent/history.ts), so they occupy real context and must be counted.\n for (const toolText of replayedToolResultTexts(msg.contentBlocks)) {\n historyForTokenCount.push({ role: 'tool', content: toolText })\n }\n }\n\n const currentTokens = countMessagesTokens(historyForTokenCount, systemPrompt)\n\n logger.info('Compression', `Token check: ${currentTokens} / ${thresholdTokens} (${contextWindow} window)`, {\n aiChatId,\n messageCount: messages.length,\n hasSummary: !!summary,\n })\n\n if (currentTokens < thresholdTokens) {\n return { compressed: false, reason: 'skipped_below_threshold', tokensBefore: currentTokens }\n }\n\n const bufferTokenBudget = Math.floor(contextWindow * (config.bufferSizePercent / 100))\n const { bufferMessages, messagesToCompress } = splitMessagesForCompression(messages, bufferTokenBudget)\n\n const MIN_MESSAGES_TO_COMPRESS = 3\n if (messagesToCompress.length < MIN_MESSAGES_TO_COMPRESS) {\n return { compressed: false, reason: 'skipped_below_threshold', tokensBefore: currentTokens }\n }\n\n const isProgressive = !!summary\n const compressInput = buildCompressionInput(messagesToCompress, summary)\n const targetTokens = Math.min(Math.floor(contextWindow * 0.1), 16384)\n\n const template = isProgressive ? PROGRESSIVE_COMPRESSION_PROMPT : INITIAL_COMPRESSION_PROMPT\n const prompt = template.replace('{maxTokens}', String(targetTokens)).replace('{messages}', compressInput)\n\n let summaryText = await llmAdapter.compress(prompt, targetTokens)\n\n if (!summaryText) {\n logger.warn('Compression', 'LLM compression failed, falling back to truncation')\n summaryText = forceTruncate(compressInput, targetTokens)\n }\n\n const bufferBoundary =\n bufferMessages.length > 0\n ? bufferMessages[0].timestamp\n : messagesToCompress[messagesToCompress.length - 1]!.timestamp + 1\n\n convManager.addSummaryMessage(aiChatId, summaryText, {\n bufferBoundaryTimestamp: bufferBoundary,\n compressedMessageCount: messagesToCompress.length,\n })\n\n const afterTokenCount: Array<{ role: string; content: string }> = [{ role: 'assistant', content: summaryText }]\n for (const m of bufferMessages) {\n afterTokenCount.push({ role: m.role, content: m.content })\n for (const toolText of replayedToolResultTexts(m.contentBlocks)) {\n afterTokenCount.push({ role: 'tool', content: toolText })\n }\n }\n const tokensAfter = countMessagesTokens(afterTokenCount, systemPrompt)\n\n if (tokensAfter >= thresholdTokens) {\n logger.warn(\n 'Compression',\n `Thrashing detected: ${tokensAfter} tokens after compression still >= ${thresholdTokens}`\n )\n return {\n compressed: true,\n reason: 'thrashing',\n tokensBefore: currentTokens,\n tokensAfter,\n summaryContent: summaryText,\n }\n }\n\n logger.info('Compression', `Compressed: ${currentTokens} → ${tokensAfter} tokens`)\n return {\n compressed: true,\n reason: 'success',\n tokensBefore: currentTokens,\n tokensAfter,\n summaryContent: summaryText,\n }\n } catch (error) {\n logger.error('Compression', 'Compression failed', { error: String(error) })\n return { compressed: false, reason: 'error', error: String(error) }\n }\n}\n\nexport async function manualCompress(\n aiChatId: string,\n config: CompressionConfig,\n systemPrompt: string,\n llmAdapter: CompressionLlmAdapter,\n convManager: AIChatManager,\n logger?: CompressionLogger\n): Promise<CompressionResult> {\n const messageCount = convManager.getMessageCountAfterSummary(aiChatId)\n if (messageCount < 5) {\n return { compressed: false, reason: 'skipped_idempotent' }\n }\n\n const overrideConfig = { ...config, enabled: true, tokenThresholdPercent: 0 }\n return checkAndCompress(aiChatId, overrideConfig, systemPrompt, llmAdapter, convManager, logger)\n}\n\n// ==================== Internal Helpers ====================\n\n/** Tool result texts that history replay will inject back into the LLM context. */\nfunction replayedToolResultTexts(blocks?: ContentBlock[]): string[] {\n if (!blocks) return []\n return blocks.filter(isReplayableToolBlock).map((block) => truncateToolResultText(block.tool.result))\n}\n\nfunction countMessageTokensWithTools(msg: CompressibleMessage): number {\n let tokens = countTokens(msg.content) + 4\n for (const toolText of replayedToolResultTexts(msg.contentBlocks)) {\n tokens += countTokens(toolText) + 4\n }\n return tokens\n}\n\nfunction splitMessagesForCompression<T extends CompressibleMessage>(\n messages: T[],\n bufferTokenBudget: number\n): {\n bufferMessages: T[]\n messagesToCompress: T[]\n} {\n let bufferTokens = 0\n let splitIndex = messages.length\n\n for (let i = messages.length - 1; i >= 0; i--) {\n const msgTokens = countMessageTokensWithTools(messages[i])\n if (bufferTokens + msgTokens > bufferTokenBudget) {\n splitIndex = i + 1\n break\n }\n bufferTokens += msgTokens\n if (i === 0) {\n splitIndex = 0\n }\n }\n\n return {\n bufferMessages: messages.slice(splitIndex),\n messagesToCompress: messages.slice(0, splitIndex),\n }\n}\n\nfunction buildCompressionInput(\n messagesToCompress: Array<{ role: string; content: string; contentBlocks?: ContentBlock[] }>,\n existingSummary: { content: string } | null\n): string {\n const parts: string[] = []\n\n if (existingSummary) {\n parts.push(`[PREVIOUS SUMMARY — MUST PRESERVE]\\n${existingSummary.content}\\n`)\n parts.push(`[NEW MESSAGES — SUMMARIZE AND MERGE]`)\n }\n\n for (const msg of messagesToCompress) {\n const roleLabel = msg.role === 'user' ? 'User' : 'Assistant'\n parts.push(`${roleLabel}: ${msg.content}`)\n for (const block of (msg.contentBlocks ?? []).filter(isReplayableToolBlock)) {\n parts.push(`[Tool result: ${block.tool.name}]\\n${truncateToolResultText(block.tool.result)}`)\n }\n }\n\n return parts.join('\\n\\n')\n}\n\nfunction forceTruncate(input: string, targetTokens: number): string {\n const lines = input.split('\\n')\n const result: string[] = []\n let tokens = 0\n for (const line of lines) {\n const lineTokens = countTokens(line)\n if (tokens + lineTokens > targetTokens) break\n result.push(line)\n tokens += lineTokens\n }\n return result.join('\\n') || input.slice(0, targetTokens * 3)\n}\n","import {\n completeSimple,\n type Model as PiModel,\n type Api as PiApi,\n type TextContent as PiTextContent,\n} from '@earendil-works/pi-ai'\nimport type { CompressionLlmAdapter } from './types'\n\nexport interface CreateCompressionLlmAdapterOptions {\n piModel: PiModel<PiApi>\n apiKey: string\n contextWindow?: number\n onCompressing?: () => void\n onError?: (error: unknown) => void\n}\n\nconst DEFAULT_CONTEXT_WINDOW = 128000\n\n/**\n * Create a CompressionLlmAdapter from a PiModel and API key.\n * Shared factory used by both Electron and Server.\n */\nexport function createCompressionLlmAdapter(options: CreateCompressionLlmAdapterOptions): CompressionLlmAdapter {\n const { piModel, apiKey, onCompressing, onError } = options\n const contextWindow = options.contextWindow ?? piModel.contextWindow ?? DEFAULT_CONTEXT_WINDOW\n\n return {\n contextWindow,\n compress: async (prompt: string, maxTokens: number) => {\n onCompressing?.()\n try {\n const result = await completeSimple(\n piModel,\n {\n systemPrompt: undefined,\n messages: [{ role: 'user', content: [{ type: 'text', text: prompt }], timestamp: Date.now() }] as any,\n },\n { apiKey, maxTokens }\n )\n const text = result.content\n .filter((item): item is PiTextContent => item.type === 'text')\n .map((item) => item.text)\n .join('')\n return text || null\n } catch (error) {\n onError?.(error)\n return null\n }\n },\n }\n}\n","/**\n * 预处理管道\n * 执行顺序:数据清洗 → 黑名单 → 去噪 → 合并 → 脱敏\n */\n\nimport type { PreprocessConfig, PreprocessableMessage, DesensitizeRule } from './types'\n\nconst MERGE_WINDOW_DEFAULT = 180\n\nexport interface PreprocessLogger {\n info(category: string, message: string, extra?: Record<string, unknown>): void\n warn(category: string, message: string, extra?: Record<string, unknown>): void\n}\n\nconst defaultLogger: PreprocessLogger = {\n info: () => {},\n warn: () => {},\n}\n\nexport function preprocessMessages<T extends PreprocessableMessage>(\n messages: T[],\n config?: PreprocessConfig,\n logger: PreprocessLogger = defaultLogger\n): T[] {\n if (!config || !hasAnyEnabled(config)) return messages\n if (messages.length === 0) return messages\n\n const inputCount = messages.length\n let result: T[] = [...messages]\n const applied: string[] = []\n\n if (config.dataCleaning !== false) {\n const cleaned = applyDataCleaning(result)\n if (cleaned.changed > 0) {\n result = cleaned.messages\n applied.push(`dataCleaning: ${cleaned.changed} messages cleaned`)\n }\n }\n\n if (config.blacklistKeywords.length > 0) {\n const before = result.length\n result = applyBlacklistFilter(result, config.blacklistKeywords)\n applied.push(`blacklist: ${before} → ${result.length} (-${before - result.length})`)\n }\n\n if (config.denoise) {\n const before = result.length\n result = applyDenoise(result)\n applied.push(`denoise: ${before} → ${result.length} (-${before - result.length})`)\n }\n\n if (config.mergeConsecutive) {\n const before = result.length\n result = applyMergeConsecutive(result, config.mergeWindowSeconds ?? MERGE_WINDOW_DEFAULT)\n applied.push(`merge: ${before} → ${result.length} (-${before - result.length})`)\n }\n\n if (config.desensitize) {\n const enabledRules = (config.desensitizeRules || []).filter((r) => r.enabled)\n if (enabledRules.length > 0) {\n result = applyDesensitize(result, enabledRules, logger)\n applied.push(`desensitize: ${enabledRules.length} rules applied`)\n }\n }\n\n logger.info('Preprocess', `Pipeline: ${inputCount} → ${result.length} messages`, {\n strategies: applied,\n })\n\n return result\n}\n\nfunction hasAnyEnabled(config: PreprocessConfig): boolean {\n return (\n config.dataCleaning !== false ||\n config.mergeConsecutive ||\n config.blacklistKeywords.length > 0 ||\n config.denoise ||\n config.desensitize\n )\n}\n\n// ==================== 策略实现 ====================\n\nfunction applyDataCleaning<T extends PreprocessableMessage>(messages: T[]): { messages: T[]; changed: number } {\n let changed = 0\n const result = messages.map((msg) => {\n if (!msg.content) return msg\n const cleaned = cleanXmlContent(msg.content)\n if (cleaned === msg.content) return msg\n changed++\n return { ...msg, content: cleaned }\n })\n return { messages: result, changed }\n}\n\nconst XML_START = /^<\\?xml\\s|^<msg[\\s>]/\n\nfunction cleanXmlContent(content: string): string {\n const trimmed = content.trim()\n if (!XML_START.test(trimmed)) return content\n\n const title = extractXmlTag(trimmed, 'title')\n const des = extractXmlTag(trimmed, 'des')\n\n if (title) {\n return des ? `[分享] ${title} - ${des}` : `[分享] ${title}`\n }\n return '[应用消息]'\n}\n\nfunction extractXmlTag(xml: string, tag: string): string | null {\n const openTag = `<${tag}>`\n const closeTag = `</${tag}>`\n const start = xml.indexOf(openTag)\n if (start === -1) return null\n const contentStart = start + openTag.length\n const end = xml.indexOf(closeTag, contentStart)\n if (end === -1) return null\n const value = xml.slice(contentStart, end).trim()\n return value.length > 0 ? value : null\n}\n\nfunction applyBlacklistFilter<T extends PreprocessableMessage>(messages: T[], keywords: string[]): T[] {\n if (keywords.length === 0) return messages\n const lowerKeywords = keywords.map((k) => k.toLowerCase())\n return messages.filter((msg) => {\n if (!msg.content) return true\n const lower = msg.content.toLowerCase()\n return !lowerKeywords.some((kw) => lower.includes(kw))\n })\n}\n\nfunction applyDenoise<T extends PreprocessableMessage>(messages: T[]): T[] {\n return messages.filter((msg) => {\n if (!msg.content) return false\n if (msg.replyToMessageId) return true\n const content = msg.content.trim()\n if (content.length === 0) return false\n if (content.length < 2) return false\n if (SYSTEM_PLACEHOLDERS.has(content)) return false\n if (isPureEmoji(content)) return false\n return true\n })\n}\n\nconst SYSTEM_PLACEHOLDERS = new Set([\n '[图片]',\n '[视频]',\n '[语音]',\n '[文件]',\n '[动画表情]',\n '[表情]',\n '[链接]',\n '[位置]',\n '[名片]',\n '[红包]',\n '[转账]',\n '[音乐]',\n '[Image]',\n '[Video]',\n '[Voice]',\n '[File]',\n '[Sticker]',\n '[Link]',\n])\n\nfunction isPureEmoji(str: string): boolean {\n const stripped = str\n .replace(/\\p{Emoji_Presentation}/gu, '')\n .replace(/\\p{Extended_Pictographic}/gu, '')\n .replace(/\\u200d/g, '')\n .replace(/\\ufe0f/g, '')\n .replace(/\\u20e3/g, '')\n .replace(/\\s/g, '')\n return stripped.length === 0\n}\n\nfunction applyMergeConsecutive<T extends PreprocessableMessage>(messages: T[], windowSeconds: number): T[] {\n if (messages.length <= 1) return messages\n\n const merged: T[] = []\n let current: T | null = null\n\n for (const msg of messages) {\n if (!current) {\n current = { ...msg }\n continue\n }\n\n const sameSender = isSameSender(current, msg)\n const withinWindow = Math.abs(msg.timestamp - current.timestamp) <= windowSeconds\n\n if (sameSender && withinWindow) {\n current = {\n ...current,\n content: [current.content, msg.content].filter(Boolean).join('\\n'),\n }\n } else {\n merged.push(current)\n current = { ...msg }\n }\n }\n\n if (current) merged.push(current)\n return merged\n}\n\nfunction isSameSender(a: PreprocessableMessage, b: PreprocessableMessage): boolean {\n if (a.senderPlatformId && b.senderPlatformId) {\n return a.senderPlatformId === b.senderPlatformId\n }\n return a.senderName === b.senderName\n}\n\nfunction applyDesensitize<T extends PreprocessableMessage>(\n messages: T[],\n rules: DesensitizeRule[],\n logger: PreprocessLogger\n): T[] {\n const compiledRules = compileRules(rules, logger)\n if (compiledRules.length === 0) return messages\n\n return messages.map((msg) => {\n if (!msg.content) return msg\n let content = msg.content\n for (const { regex, replacement } of compiledRules) {\n regex.lastIndex = 0\n content = content.replace(regex, replacement)\n }\n if (content === msg.content) return msg\n return { ...msg, content }\n })\n}\n\nconst regexCache = new Map<string, RegExp | null>()\n\nfunction compileRules(\n rules: DesensitizeRule[],\n logger: PreprocessLogger\n): Array<{ regex: RegExp; replacement: string }> {\n const result: Array<{ regex: RegExp; replacement: string }> = []\n for (const rule of rules) {\n let regex = regexCache.get(rule.pattern)\n if (regex === undefined) {\n try {\n regex = new RegExp(rule.pattern, 'g')\n regexCache.set(rule.pattern, regex)\n } catch {\n logger.warn('Preprocess', `Invalid regex in desensitize rule \"${rule.id}\": ${rule.pattern}`)\n regexCache.set(rule.pattern, null)\n continue\n }\n }\n if (regex) {\n result.push({ regex, replacement: rule.replacement })\n }\n }\n return result\n}\n","/**\n * 内置脱敏规则库\n * 按 locale 分组,支持通用规则和地区特定规则\n */\nimport type { DesensitizeRule } from './types'\n\nexport const DESENSITIZE_RULES_SCHEMA_VERSION = 2\n\nexport interface DesensitizeRuleGroup {\n id: string\n label: string\n description: string\n locales: string[]\n order: number\n rules: DesensitizeRule[]\n}\n\nconst GROUP_DEFS: Array<Omit<DesensitizeRuleGroup, 'rules'>> = [\n {\n id: 'credentials',\n label: 'desensitize.groups.credentials',\n description: 'desensitize.groups.credentials_desc',\n locales: [],\n order: 10,\n },\n {\n id: 'global_contact',\n label: 'desensitize.groups.global_contact',\n description: 'desensitize.groups.global_contact_desc',\n locales: [],\n order: 20,\n },\n {\n id: 'global_financial',\n label: 'desensitize.groups.global_financial',\n description: 'desensitize.groups.global_financial_desc',\n locales: [],\n order: 30,\n },\n {\n id: 'global_network',\n label: 'desensitize.groups.global_network',\n description: 'desensitize.groups.global_network_desc',\n locales: [],\n order: 40,\n },\n {\n id: 'region_cn',\n label: 'desensitize.groups.region_cn',\n description: 'desensitize.groups.region_cn_desc',\n locales: ['zh-CN'],\n order: 50,\n },\n {\n id: 'region_us',\n label: 'desensitize.groups.region_us',\n description: 'desensitize.groups.region_us_desc',\n locales: ['en-US'],\n order: 50,\n },\n {\n id: 'region_jp',\n label: 'desensitize.groups.region_jp',\n description: 'desensitize.groups.region_jp_desc',\n locales: ['ja-JP'],\n order: 50,\n },\n {\n id: 'region_kr',\n label: 'desensitize.groups.region_kr',\n description: 'desensitize.groups.region_kr_desc',\n locales: ['ko-KR'],\n order: 50,\n },\n]\n\nexport const BUILTIN_DESENSITIZE_RULES: DesensitizeRule[] = [\n // ==================== 中国 (zh-CN) ====================\n {\n id: 'cn_phone',\n label: 'desensitize.rules.cn_phone',\n pattern: '(?<!\\\\d)1[3-9]\\\\d{9}(?!\\\\d)',\n replacement: '[手机号]',\n enabled: true,\n builtin: true,\n locales: ['zh-CN'],\n group: 'region_cn',\n },\n {\n id: 'cn_id_card',\n label: 'desensitize.rules.cn_id_card',\n pattern: '(?<!\\\\d)\\\\d{17}[\\\\dXx](?!\\\\d)',\n replacement: '[身份证]',\n enabled: true,\n builtin: true,\n locales: ['zh-CN'],\n group: 'region_cn',\n },\n {\n id: 'cn_bank_card',\n label: 'desensitize.rules.cn_bank_card',\n pattern: '(?<!\\\\d)\\\\d{16,19}(?!\\\\d)',\n replacement: '[银行卡]',\n enabled: true,\n builtin: true,\n locales: ['zh-CN'],\n group: 'region_cn',\n },\n {\n id: 'cn_landline',\n label: 'desensitize.rules.cn_landline',\n pattern: '(?<!\\\\d)0\\\\d{2,3}-?\\\\d{7,8}(?!\\\\d)',\n replacement: '[座机号]',\n enabled: true,\n builtin: true,\n locales: ['zh-CN'],\n group: 'region_cn',\n },\n\n // ==================== 美国 (en-US) ====================\n {\n id: 'us_ssn',\n label: 'desensitize.rules.us_ssn',\n pattern: '(?<!\\\\d)\\\\d{3}-\\\\d{2}-\\\\d{4}(?!\\\\d)',\n replacement: '[SSN]',\n enabled: true,\n builtin: true,\n locales: ['en-US'],\n group: 'region_us',\n },\n {\n id: 'us_phone',\n label: 'desensitize.rules.us_phone',\n pattern: '(?<!\\\\d)(?:\\\\+?1[-\\\\s.]?)?\\\\(?\\\\d{3}\\\\)?[-\\\\s.]?\\\\d{3}[-\\\\s.]?\\\\d{4}(?!\\\\d)',\n replacement: '[Phone]',\n enabled: true,\n builtin: true,\n locales: ['en-US'],\n group: 'region_us',\n },\n {\n id: 'us_drivers_license',\n label: 'desensitize.rules.us_drivers_license',\n pattern: '(?<![A-Z])\\\\b[A-Z]\\\\d{7,8}\\\\b',\n replacement: \"[Driver's License]\",\n enabled: true,\n builtin: true,\n locales: ['en-US'],\n group: 'region_us',\n },\n\n // ==================== 日本 (ja-JP) ====================\n {\n id: 'jp_phone',\n label: 'desensitize.rules.jp_phone',\n pattern: '(?<!\\\\d)0[789]0-?\\\\d{4}-?\\\\d{4}(?!\\\\d)',\n replacement: '[電話番号]',\n enabled: true,\n builtin: true,\n locales: ['ja-JP'],\n group: 'region_jp',\n },\n {\n id: 'jp_my_number',\n label: 'desensitize.rules.jp_my_number',\n pattern: '(?<!\\\\d)\\\\d{4}\\\\s?\\\\d{4}\\\\s?\\\\d{4}(?!\\\\d)',\n replacement: '[マイナンバー]',\n enabled: true,\n builtin: true,\n locales: ['ja-JP'],\n group: 'region_jp',\n },\n\n // ==================== 韩国 (ko-KR) ====================\n {\n id: 'kr_phone',\n label: 'desensitize.rules.kr_phone',\n pattern: '(?<!\\\\d)01[016789]-?\\\\d{3,4}-?\\\\d{4}(?!\\\\d)',\n replacement: '[전화번호]',\n enabled: true,\n builtin: true,\n locales: ['ko-KR'],\n group: 'region_kr',\n },\n {\n id: 'kr_rrn',\n label: 'desensitize.rules.kr_rrn',\n pattern: '(?<!\\\\d)\\\\d{6}-[1-4]\\\\d{6}(?!\\\\d)',\n replacement: '[주민번호]',\n enabled: true,\n builtin: true,\n locales: ['ko-KR'],\n group: 'region_kr',\n },\n\n // ==================== 凭据 / Token (所有语言) ====================\n {\n id: 'api_key_prefix',\n label: 'desensitize.rules.api_key_prefix',\n pattern: '\\\\b(?:sk-|pk_(?:live|test)_|ghp_|gho_|ghs_|ghu_|glpat-|xoxb-|xoxp-|AKIA)[A-Za-z0-9_\\\\-]{10,}\\\\b',\n replacement: '[API Key]',\n enabled: true,\n builtin: true,\n locales: [],\n group: 'credentials',\n },\n {\n id: 'bearer_token',\n label: 'desensitize.rules.bearer_token',\n pattern: 'Bearer\\\\s+[A-Za-z0-9\\\\-._~+/]+=*',\n replacement: 'Bearer [Token]',\n enabled: true,\n builtin: true,\n locales: [],\n group: 'credentials',\n },\n\n // ==================== 通用 (所有语言) ====================\n {\n id: 'email',\n label: 'desensitize.rules.email',\n pattern: '[a-zA-Z0-9._%+\\\\-]+@[a-zA-Z0-9.\\\\-]+\\\\.[a-zA-Z]{2,}',\n replacement: '[Email]',\n enabled: true,\n builtin: true,\n locales: [],\n group: 'global_contact',\n },\n {\n id: 'credit_card',\n label: 'desensitize.rules.credit_card',\n pattern:\n '(?<!\\\\d)(?:4\\\\d{3}|5[1-5]\\\\d{2}|3[47]\\\\d{2}|6(?:011|5\\\\d{2}))[-\\\\s.]?\\\\d{4}[-\\\\s.]?\\\\d{4}[-\\\\s.]?\\\\d{4}(?!\\\\d)',\n replacement: '[Credit Card]',\n enabled: true,\n builtin: true,\n locales: [],\n group: 'global_financial',\n },\n {\n id: 'ipv4',\n label: 'desensitize.rules.ipv4',\n pattern:\n '(?<!\\\\d)(?:25[0-5]|2[0-4]\\\\d|[01]?\\\\d\\\\d?)\\\\.(?:25[0-5]|2[0-4]\\\\d|[01]?\\\\d\\\\d?)\\\\.(?:25[0-5]|2[0-4]\\\\d|[01]?\\\\d\\\\d?)\\\\.(?:25[0-5]|2[0-4]\\\\d|[01]?\\\\d\\\\d?)(?!\\\\d)',\n replacement: '[IP]',\n enabled: true,\n builtin: true,\n locales: [],\n group: 'global_network',\n },\n {\n id: 'url',\n label: 'desensitize.rules.url',\n pattern: 'https?://[^\\\\s<>\"]+',\n replacement: '[URL]',\n enabled: true,\n builtin: true,\n locales: [],\n group: 'global_network',\n },\n]\n\n/**\n * 获取指定 locale 的默认规则(当前 locale 特定 + 通用规则)\n */\nexport function getDefaultRulesForLocale(locale: string): DesensitizeRule[] {\n return BUILTIN_DESENSITIZE_RULES.filter((rule) => rule.locales.length === 0 || rule.locales.includes(locale)).map(\n (rule) => ({ ...rule })\n )\n}\n\nexport function applyDesensitizeRuleOverrides(\n rules: DesensitizeRule[],\n overrides: Record<string, boolean> = {}\n): DesensitizeRule[] {\n return rules.map((rule) => ({\n ...rule,\n enabled: overrides[rule.id] ?? rule.enabled,\n }))\n}\n\nexport function getRuleGroupsForLocale(\n locale: string,\n overrides: Record<string, boolean> = {}\n): DesensitizeRuleGroup[] {\n const rules = applyDesensitizeRuleOverrides(getDefaultRulesForLocale(locale), overrides)\n const rulesByGroup = new Map<string, DesensitizeRule[]>()\n for (const rule of rules) {\n if (!rule.group) continue\n const groupRules = rulesByGroup.get(rule.group) ?? []\n groupRules.push(rule)\n rulesByGroup.set(rule.group, groupRules)\n }\n\n return GROUP_DEFS.filter((group) => {\n if (group.locales.length > 0 && !group.locales.includes(locale)) return false\n return (rulesByGroup.get(group.id)?.length ?? 0) > 0\n })\n .map((group) => ({\n ...group,\n rules: rulesByGroup.get(group.id) ?? [],\n }))\n .sort((a, b) => a.order - b.order)\n}\n\n/**\n * 合并新 locale 的规则到现有规则列表\n */\nexport function mergeRulesForLocale(\n existing: DesensitizeRule[],\n locale: string,\n overrides: Record<string, boolean> = {}\n): DesensitizeRule[] {\n const customRules = existing.filter((rule) => !rule.builtin)\n const builtinRules = applyDesensitizeRuleOverrides(getDefaultRulesForLocale(locale), overrides)\n\n return [...builtinRules, ...customRules]\n}\n","/**\n * 工具结果格式化 & i18n 辅助(平台无关)\n */\n\nexport function isChineseLocale(locale?: string): boolean {\n return locale?.startsWith('zh') ?? false\n}\n\nexport const i18nTexts = {\n allTime: { zh: '全部时间', en: 'All time' },\n noContent: { zh: '[无内容]', en: '[No content]' },\n memberNotFound: { zh: '未找到该成员', en: 'Member not found' },\n untilNow: { zh: '至今', en: 'Present' },\n noChangeRecord: { zh: '无变更记录', en: 'No change record' },\n noConversation: { zh: '未找到这两人之间的对话', en: 'No conversation found between these two members' },\n noMessageContext: { zh: '未找到指定的消息或上下文', en: 'Message or context not found' },\n messages: { zh: '条', en: '' },\n alias: { zh: '别名', en: 'Alias' },\n weekdays: {\n zh: ['', '周一', '周二', '周三', '周四', '周五', '周六', '周日'],\n en: ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],\n },\n dailySummary: {\n zh: (days: number, total: number, avg: number) => `最近${days}天共${total}条,日均${avg}条`,\n en: (days: number, total: number, avg: number) => `Last ${days} days: ${total} messages, avg ${avg}/day`,\n },\n}\n\ntype TextEntryKey = Exclude<keyof typeof i18nTexts, 'dailySummary'>\n\nexport function t(key: TextEntryKey, locale?: string): string | string[] {\n const text = i18nTexts[key]\n if (typeof text === 'object' && 'zh' in text && 'en' in text) {\n return isChineseLocale(locale) ? text.zh : text.en\n }\n return ''\n}\n\nconst MAX_MESSAGE_CONTENT_LENGTH = 200\n\n/**\n * 格式化消息为简洁文本格式\n * 输出格式: \"2025/3/3 07:25:04 张三: 消息内容\"\n */\nexport function formatMessageCompact(\n msg: {\n id?: number\n senderName: string\n content: string | null\n timestamp: number\n },\n locale?: string\n): string {\n const localeStr = isChineseLocale(locale) ? 'zh-CN' : 'en-US'\n const time = new Date(msg.timestamp * 1000).toLocaleString(localeStr)\n let content = msg.content || (t('noContent', locale) as string)\n\n if (content.length > MAX_MESSAGE_CONTENT_LENGTH) {\n content = content.slice(0, MAX_MESSAGE_CONTENT_LENGTH) + '...'\n }\n\n return `${time} ${msg.senderName}: ${content}`\n}\n\n/**\n * 格式化时间范围用于返回结果\n */\nexport function formatTimeRange(\n timeFilter?: { startTs: number; endTs: number },\n locale?: string\n): string | { start: string; end: string } {\n if (!timeFilter) return t('allTime', locale) as string\n const localeStr = isChineseLocale(locale) ? 'zh-CN' : 'en-US'\n return {\n start: new Date(timeFilter.startTs * 1000).toLocaleString(localeStr),\n end: new Date(timeFilter.endTs * 1000).toLocaleString(localeStr),\n }\n}\n\n/**\n * 将工具返回的结构化数据格式化为 LLM 友好的纯文本\n */\nexport function formatToolResultAsText(details: Record<string, unknown>): string {\n const lines: string[] = []\n const messages = details.messages as string[] | undefined\n\n for (const [key, value] of Object.entries(details)) {\n if (key === 'messages') continue\n // raw message objects are already rendered via the formatted messages list;\n // joining them here would print \"[object Object]\" for every entry\n if (key === 'rawMessages') continue\n if (value === undefined || value === null) continue\n\n if (typeof value === 'object') {\n if ('start' in (value as Record<string, unknown>) && 'end' in (value as Record<string, unknown>)) {\n const range = value as { start: string; end: string }\n lines.push(`${key}: ${range.start} ~ ${range.end}`)\n } else if (Array.isArray(value)) {\n lines.push(`${key}: ${value.join(', ')}`)\n } else {\n lines.push(`${key}: ${JSON.stringify(value)}`)\n }\n } else {\n lines.push(`${key}: ${value}`)\n }\n }\n\n if (messages && messages.length > 0) {\n lines.push('')\n let lastDate = ''\n for (const msg of messages) {\n const spaceIdx = msg.indexOf(' ')\n const secondSpaceIdx = msg.indexOf(' ', spaceIdx + 1)\n if (spaceIdx > 0 && secondSpaceIdx > 0) {\n const date = msg.slice(0, spaceIdx)\n const rest = msg.slice(spaceIdx + 1)\n if (date !== lastDate) {\n lines.push(`--- ${date} ---`)\n lastDate = date\n }\n lines.push(rest)\n } else {\n lines.push(msg)\n }\n }\n }\n\n return lines.join('\\n')\n}\n\n/**\n * 昵称匿名化:用 U{senderId} 替代真实昵称\n * 就地修改 messages 的 senderName,返回映射表文本行\n */\nexport function anonymizeMessageNames(\n messages: Array<{ senderId?: number; senderName: string; senderPlatformId?: string }>,\n ownerPlatformId?: string\n): string {\n const nameMap = new Map<number, { name: string; platformId?: string }>()\n for (const msg of messages) {\n if (msg.senderId != null && !nameMap.has(msg.senderId)) {\n nameMap.set(msg.senderId, { name: msg.senderName, platformId: msg.senderPlatformId })\n }\n }\n\n if (nameMap.size === 0) return ''\n\n for (const msg of messages) {\n if (msg.senderId != null) {\n msg.senderName = `U${msg.senderId}`\n }\n }\n\n const entries: string[] = []\n for (const [id, { name, platformId }] of nameMap) {\n const isOwner = ownerPlatformId && platformId === ownerPlatformId\n entries.push(`U${id}=${name}${isOwner ? '(owner)' : ''}`)\n }\n\n return `[Name Map] ${entries.join(' | ')}`\n}\n\n/**\n * Token-aware 截断:在 token 预算内保留尽可能多的消息\n */\nexport function truncateFormattedMessages(\n formatted: string[],\n maxTokens: number,\n strategy: 'keep_first' | 'keep_last',\n countTokensFn: (text: string) => number\n): { messages: string[]; wasTruncated: boolean } {\n const budget = maxTokens - 200\n\n let totalTokens = 0\n for (const line of formatted) {\n totalTokens += countTokensFn(line) + 1\n }\n if (totalTokens <= budget) {\n return { messages: formatted, wasTruncated: false }\n }\n\n if (strategy === 'keep_first') {\n let tokens = 0\n let cutIndex = formatted.length\n for (let i = 0; i < formatted.length; i++) {\n tokens += countTokensFn(formatted[i]) + 1\n if (tokens > budget) {\n cutIndex = i\n break\n }\n }\n return { messages: formatted.slice(0, cutIndex), wasTruncated: cutIndex < formatted.length }\n } else {\n let tokens = 0\n let cutIndex = 0\n for (let i = formatted.length - 1; i >= 0; i--) {\n tokens += countTokensFn(formatted[i]) + 1\n if (tokens > budget) {\n cutIndex = i + 1\n break\n }\n }\n return { messages: formatted.slice(cutIndex), wasTruncated: cutIndex > 0 }\n }\n}\n","/**\n * 预处理管道(统一实现)\n *\n * Electron 端和 Server 端共用的消息预处理管道核心逻辑:\n * rawMessages → preprocess → [anonymize] → format → truncate → text\n */\n\nimport type { PreprocessConfig, PreprocessableMessage, TruncationStrategy } from './types'\nimport { preprocessMessages, type PreprocessLogger } from './pipeline'\nimport {\n formatMessageCompact,\n anonymizeMessageNames,\n formatToolResultAsText,\n truncateFormattedMessages,\n} from './format'\nimport { countTokens } from '../tokenizer'\n\nexport interface PreprocessingPipelineOptions {\n rawMessages: PreprocessableMessage[]\n preprocessConfig?: PreprocessConfig\n locale?: string\n anonymizeNames?: boolean\n ownerPlatformId?: string\n maxToolResultTokens?: number\n truncationStrategy?: TruncationStrategy\n /** rawMessages 之外的附加元数据(如 total、timeRange),会合并到输出 details */\n extraDetails?: Record<string, unknown>\n /** 可选的日志记录器,Electron 端注入 aiLogger 以记录管道统计信息 */\n logger?: PreprocessLogger\n}\n\nexport interface PreprocessingPipelineResult {\n text: string\n details: Record<string, unknown>\n}\n\nexport function applyPreprocessingPipeline(options: PreprocessingPipelineOptions): PreprocessingPipelineResult {\n const {\n rawMessages,\n preprocessConfig,\n locale,\n anonymizeNames = false,\n ownerPlatformId,\n maxToolResultTokens,\n truncationStrategy = 'keep_last',\n extraDetails = {},\n logger,\n } = options\n\n const processed = preprocessMessages(rawMessages, preprocessConfig, logger)\n\n let nameMapLine = ''\n if (anonymizeNames) {\n nameMapLine = anonymizeMessageNames(processed, ownerPlatformId)\n }\n\n let formatted = processed.map((m) => formatMessageCompact(m, locale))\n\n let wasTruncated = false\n const originalCount = formatted.length\n\n if (maxToolResultTokens && maxToolResultTokens > 0) {\n const truncResult = truncateFormattedMessages(formatted, maxToolResultTokens, truncationStrategy, countTokens)\n if (truncResult.wasTruncated) {\n formatted = truncResult.messages\n wasTruncated = true\n }\n }\n\n const finalDetails: Record<string, unknown> = { ...extraDetails, messages: formatted, returned: formatted.length }\n\n let textContent = formatToolResultAsText(finalDetails)\n\n if (wasTruncated) {\n const strategyDesc = truncationStrategy === 'keep_first' ? 'most relevant' : 'most recent'\n const notice = `⚠️ Results truncated: ${originalCount} messages found, showing ${formatted.length} ${strategyDesc} due to context limit. Use a narrower time range or more specific keywords for more precise results.`\n textContent = notice + '\\n' + textContent\n }\n\n if (nameMapLine) {\n textContent = nameMapLine + '\\n' + textContent\n }\n\n return { text: textContent, details: finalDetails }\n}\n","export const DEFAULT_MAX_TOOL_ROUNDS = 20\n","/**\n * Agent Core — 共享的 PiAgentCore 编排逻辑\n *\n * 封装:构建 → 历史转换 → 事件订阅 → abort 转发 → prompt 执行 → usage 收集。\n * Server 和 Electron 通过 AgentCoreOptions DI 注入平台差异。\n */\n\nimport { Agent as PiAgentCore } from '@earendil-works/pi-agent-core'\nimport type { AgentEvent as PiAgentEvent, AgentMessage as PiAgentMessage } from '@earendil-works/pi-agent-core'\nimport {\n type Message as PiMessage,\n type Usage as PiUsage,\n streamSimple as defaultStreamSimple,\n clampThinkingLevel,\n} from '@earendil-works/pi-ai'\nimport { StreamingThinkTagParser, needsStreamingThinkParsing } from '@openchatlab/core'\n\nimport type { AgentCoreOptions, AgentCoreResult, AgentTokenUsage } from './types'\nimport { initTokenizer } from '../tokenizer'\nimport { DEFAULT_MAX_TOOL_ROUNDS } from './constants'\nimport { toPiHistoryMessages, type ReplayOptions } from './history'\n\nfunction isPiMessage(message: PiAgentMessage): message is PiMessage {\n return message.role === 'user' || message.role === 'assistant' || message.role === 'toolResult'\n}\n\nexport async function runAgentCore(options: AgentCoreOptions): Promise<AgentCoreResult> {\n const {\n piModel,\n apiKey,\n systemPrompt,\n tools,\n history,\n userMessage,\n maxToolRounds = DEFAULT_MAX_TOOL_ROUNDS,\n abortSignal,\n steerMessage = 'Please provide your final answer based on the information gathered.',\n onEvent,\n onConvertToLlm,\n onDebugContext,\n } = options\n\n // 确保 cl100k rank 表已加载,压缩/预处理路径使用精确 token 计数\n await initTokenizer()\n\n const resolvedStreamFn = (options.streamFn ?? defaultStreamSimple) as typeof defaultStreamSimple\n\n const totalUsage: AgentTokenUsage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n const toolsUsed: string[] = []\n let toolRounds = 0\n\n const addPiUsage = (usage?: PiUsage) => {\n if (!usage) return\n totalUsage.promptTokens += usage.input || 0\n totalUsage.completionTokens += usage.output || 0\n totalUsage.totalTokens += usage.totalTokens || usage.input + usage.output || 0\n totalUsage.cacheReadTokens += usage.cacheRead || 0\n totalUsage.cacheWriteTokens += usage.cacheWrite || 0\n }\n\n if (abortSignal?.aborted) {\n return { usage: totalUsage, finalMessages: [], toolsUsed: [], toolRounds: 0 }\n }\n\n // Resolve thinkingLevel for pi-agent-core:\n // - 'default'/undefined → strip reasoning from piModel so pi-ai sends a plain request\n // with NO reasoning params at all (model uses its native default behavior).\n // - 'off' → pi-ai sends disable signals (thinking:{type:'disabled'} / enable_thinking:false)\n // - 'auto' → for thinkingFormat models or effort-based models with thinkingLevelMap,\n // use 'high' to enable; others no params.\n // Note: pi-ai's clampThinkingLevel only covers EXTENDED_THINKING_LEVELS (no 'auto'),\n // so 'auto' cannot be forwarded verbatim for effort-based models like Kimi/Doubao.\n // Returning 'high' ensures reasoning is at least enabled.\n // - Other levels → clamp to what pi-agent-core accepts.\n const isDefault = !options.thinkingLevel || options.thinkingLevel === 'default'\n const effectiveModel = isDefault\n ? { ...piModel, reasoning: false, compat: undefined, thinkingLevelMap: undefined }\n : piModel\n\n const resolvedThinkingLevel = (() => {\n if (isDefault) return 'off'\n const level = options.thinkingLevel!\n if (level === 'auto') {\n if (!piModel.reasoning) return 'off'\n const compat = piModel.compat as Record<string, unknown> | undefined\n if (compat?.thinkingFormat) return 'high'\n // Effort-based reasoning models (e.g., Kimi, Doubao) have a thinkingLevelMap but no\n // thinkingFormat. pi-ai's clampThinkingLevel can't pass 'auto' through, so use 'high'.\n if (piModel.thinkingLevelMap) return 'high'\n return undefined\n }\n return clampThinkingLevel(piModel, level as Exclude<typeof level, 'default' | 'auto'>)\n })()\n\n const finalThinkingLevel = resolvedThinkingLevel ?? (piModel.reasoning ? undefined : 'off')\n\n // DeepSeek-format APIs require reasoning_content on assistant messages that\n // precede tool results; build replay options so toPiHistoryMessages includes\n // persisted thinking blocks in those messages.\n const thinkingFormat = (piModel.compat as Record<string, unknown> | undefined)?.thinkingFormat\n const replayOptions: ReplayOptions | undefined =\n piModel.reasoning && thinkingFormat === 'deepseek'\n ? {\n modelInfo: { api: piModel.api, provider: piModel.provider, id: piModel.id },\n thinkingSignature: 'reasoning_content',\n }\n : undefined\n\n const coreAgent = new PiAgentCore({\n initialState: {\n systemPrompt,\n model: effectiveModel,\n thinkingLevel: finalThinkingLevel,\n tools: maxToolRounds > 0 ? tools : [],\n messages: toPiHistoryMessages(history, replayOptions),\n },\n getApiKey: () => apiKey,\n streamFn: resolvedStreamFn,\n convertToLlm: (messages) => {\n const filtered = messages.filter(\n (msg): msg is PiMessage => msg.role === 'user' || msg.role === 'assistant' || msg.role === 'toolResult'\n )\n onConvertToLlm?.(filtered)\n return filtered\n },\n })\n\n let hasReachedToolRoundLimit = false\n const thinkingStartTime = new Map<number, number>()\n\n // For providers that embed <think> tags in content (e.g. MiniMax),\n // use a streaming parser to split thinking from content.\n const useThinkParser = needsStreamingThinkParsing(piModel.provider, piModel.id)\n let thinkParserStartTime: number | undefined\n const thinkParser = useThinkParser\n ? new StreamingThinkTagParser((ev) => {\n switch (ev.type) {\n case 'content':\n onEvent({ type: 'content', content: ev.content })\n break\n case 'thinking_start':\n thinkParserStartTime = Date.now()\n onEvent({ type: 'thinking_start' })\n break\n case 'thinking_delta':\n onEvent({ type: 'thinking_delta', content: ev.content })\n break\n case 'thinking_end': {\n const durationMs = thinkParserStartTime ? Date.now() - thinkParserStartTime : undefined\n thinkParserStartTime = undefined\n onEvent({ type: 'thinking_end', durationMs })\n break\n }\n }\n })\n : null\n\n const unsubscribe = coreAgent.subscribe((event: PiAgentEvent) => {\n if (event.type === 'message_update') {\n const update = event.assistantMessageEvent\n if (update.type === 'text_delta') {\n if (thinkParser) {\n thinkParser.feed(update.delta)\n } else {\n onEvent({ type: 'content', content: update.delta })\n }\n } else if (update.type === 'thinking_start') {\n thinkingStartTime.set(update.contentIndex, Date.now())\n onEvent({ type: 'thinking_start' })\n } else if (update.type === 'thinking_delta') {\n onEvent({ type: 'thinking_delta', content: update.delta })\n } else if (update.type === 'thinking_end') {\n const startedAt = thinkingStartTime.get(update.contentIndex)\n const durationMs = startedAt ? Date.now() - startedAt : undefined\n thinkingStartTime.delete(update.contentIndex)\n onEvent({ type: 'thinking_end', durationMs })\n }\n } else if (event.type === 'tool_execution_start') {\n toolsUsed.push(event.toolName)\n onEvent({\n type: 'tool_start',\n toolCallId: event.toolCallId,\n toolName: event.toolName,\n toolParams: (event.args || {}) as Record<string, unknown>,\n })\n } else if (event.type === 'tool_execution_end') {\n onEvent({\n type: 'tool_end',\n toolCallId: event.toolCallId,\n toolName: event.toolName,\n toolResult: event.result,\n isError: event.isError,\n })\n } else if (event.type === 'turn_end') {\n const hadToolCalls = event.toolResults.length > 0\n if (hadToolCalls) {\n toolRounds += 1\n if (!hasReachedToolRoundLimit && maxToolRounds > 0 && toolRounds >= maxToolRounds) {\n hasReachedToolRoundLimit = true\n coreAgent.state.tools = []\n coreAgent.steer({\n role: 'user',\n content: [{ type: 'text', text: steerMessage }],\n timestamp: Date.now(),\n } as PiMessage)\n }\n }\n onEvent({ type: 'turn_end', round: toolRounds, hadToolCalls })\n } else if (event.type === 'message_end') {\n if (event.message.role === 'assistant') {\n thinkParser?.flush()\n addPiUsage(event.message.usage)\n onEvent({ type: 'usage_update', usage: { ...totalUsage } })\n }\n }\n })\n\n const forwardAbort = () => coreAgent.abort()\n if (abortSignal) {\n abortSignal.addEventListener('abort', forwardAbort, { once: true })\n }\n\n try {\n if (onDebugContext) {\n try {\n const debugMessages = [\n { role: 'system', content: systemPrompt },\n ...history.map((m) => ({\n role: m.role === 'summary' ? 'assistant' : m.role,\n content: m.content,\n })),\n { role: 'user', content: userMessage },\n ]\n onDebugContext(debugMessages)\n } catch {\n // silent — debug context is best-effort\n }\n }\n\n await coreAgent.prompt(userMessage)\n\n return {\n usage: totalUsage,\n error: coreAgent.state.errorMessage || undefined,\n finalMessages: coreAgent.state.messages.filter(isPiMessage),\n toolsUsed: [...toolsUsed],\n toolRounds,\n }\n } finally {\n unsubscribe()\n if (abortSignal) {\n abortSignal.removeEventListener('abort', forwardAbort)\n }\n }\n}\n","import type { DataSnapshot } from './prompt-builder'\n\nexport interface ChatOverviewForSnapshot {\n name: string\n platform: string\n type: string\n totalMessages: number\n totalMembers: number\n firstMessageTs: number | null\n lastMessageTs: number | null\n topMembers?: Array<{ id: number; name: string; count: number }>\n summaryCount?: number\n}\n\nfunction roundShare(value: number): number {\n if (!Number.isFinite(value)) return 0\n return Math.round(value * 10) / 10\n}\n\nfunction formatNullableTimestamp(timestamp: number | null | undefined): string {\n return typeof timestamp === 'number' && Number.isFinite(timestamp) ? String(timestamp) : 'null'\n}\n\nfunction formatSharePercent(share: number): string {\n if (!Number.isFinite(share)) return '0%'\n const rounded = Math.round(share * 10) / 10\n return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`\n}\n\nexport function createDataSnapshotFromOverview(\n overview: ChatOverviewForSnapshot | null | undefined\n): DataSnapshot | undefined {\n if (!overview) return undefined\n\n const topMembers = overview.topMembers ?? []\n\n return {\n version: 2,\n name: overview.name,\n platform: overview.platform,\n type: overview.type,\n totalMessages: overview.totalMessages,\n totalMembers: overview.totalMembers,\n firstMessageTs: overview.firstMessageTs,\n lastMessageTs: overview.lastMessageTs,\n activeMemberHints: topMembers.slice(0, 10).map((member) => ({\n memberId: member.id,\n displayName: member.name,\n messageCount: member.count,\n share: overview.totalMessages > 0 ? roundShare((member.count / overview.totalMessages) * 100) : 0,\n })),\n segmentSummaries: {\n availableCount: overview.summaryCount ?? 0,\n },\n }\n}\n\nexport function formatDataSnapshotForRouter(dataSnapshot: DataSnapshot | undefined): string {\n if (!dataSnapshot) return '(none)'\n\n return [\n `name: ${dataSnapshot.name}`,\n `platform: ${dataSnapshot.platform}`,\n `type: ${dataSnapshot.type}`,\n `total_messages: ${dataSnapshot.totalMessages}`,\n `total_members: ${dataSnapshot.totalMembers}`,\n `first_message_ts: ${formatNullableTimestamp(dataSnapshot.firstMessageTs)}`,\n `last_message_ts: ${formatNullableTimestamp(dataSnapshot.lastMessageTs)}`,\n `segment_summaries_available: ${dataSnapshot.segmentSummaries?.availableCount ?? 0}`,\n `active_member_hint_count: ${dataSnapshot.activeMemberHints?.length ?? 0}`,\n ].join('\\n')\n}\n\nexport function formatDataSnapshotForPlanner(dataSnapshot: DataSnapshot | undefined): string {\n if (!dataSnapshot) return '(none)'\n\n const memberHints = dataSnapshot.activeMemberHints ?? []\n const memberHintLines =\n memberHints.length > 0\n ? memberHints\n .map(\n (member, index) =>\n `${index + 1}. member_id=${member.memberId} | display_name=${member.displayName} | messages=${member.messageCount} | share=${formatSharePercent(member.share)}`\n )\n .join('\\n')\n : '(none)'\n\n return `${formatDataSnapshotForRouter(dataSnapshot)}\nactive_member_hints:\n${memberHintLines}\nnotes:\n- member_id is a tool lookup hint; display_name may not be unique.\n- Active member hints reflect historical total message volume, not recent activity.\n- Use real current date for relative ranges; database bounds only describe data coverage.\n- last_message_ts is the coverage end of the imported data, not the real-world last activity time of the group/chat; the user may not have imported newer records yet. Do not infer group inactivity from this value.\n- Do not plan a tool call only to rediscover min/max timestamp.\n- When using default recent-day tools, choose a range that intersects database bounds instead of probing an empty real-current-date window first.`\n}\n","import type { LlmRouteDecider, RouteDecision, RouterInput } from './routing-types'\nimport { formatDataSnapshotForRouter } from './data-snapshot'\nimport {\n completeSimple,\n type Api as PiApi,\n type Model as PiModel,\n type TextContent as PiTextContent,\n} from '@earendil-works/pi-ai'\n\nexport interface DecideRequestRouteOptions {\n llmRouter?: LlmRouteDecider\n llmFallbackConfidenceThreshold?: number\n}\n\nexport type LlmRouterCompletionResult =\n | string\n | {\n text: string\n usage?: RouteDecision['usage']\n }\n\nexport interface CreateLlmRouteDeciderOptions {\n piModel?: PiModel<PiApi>\n apiKey?: string\n complete?: (prompt: string, signal?: AbortSignal) => Promise<LlmRouterCompletionResult> | LlmRouterCompletionResult\n abortSignal?: AbortSignal\n maxTokens?: number\n temperature?: number\n}\n\nconst DEFAULT_LLM_FALLBACK_CONFIDENCE_THRESHOLD = 0.6\n\nfunction clampConfidence(value: number): number {\n if (!Number.isFinite(value)) return 0\n return Math.max(0, Math.min(1, value))\n}\n\nfunction normalizeText(text: string): string {\n return text.trim().replace(/\\s+/g, ' ')\n}\n\nfunction matchAny(text: string, patterns: RegExp[]): boolean {\n return patterns.some((pattern) => pattern.test(text))\n}\n\nfunction isRoute(value: unknown): value is RouteDecision['route'] {\n return value === 'direct_response' || value === 'tool_assisted' || value === 'planned_execution'\n}\n\nfunction resultToText(result: LlmRouterCompletionResult): { text: string; usage?: RouteDecision['usage'] } {\n return typeof result === 'string' ? { text: result } : result\n}\n\nfunction extractJsonObject(text: string): string {\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/i)\n if (fenced?.[1]) return fenced[1].trim()\n const start = text.indexOf('{')\n const end = text.lastIndexOf('}')\n if (start >= 0 && end > start) return text.slice(start, end + 1)\n return text.trim()\n}\n\nfunction parseLlmDecision(rawText: string, usage?: RouteDecision['usage']): RouteDecision | null {\n try {\n const parsed = JSON.parse(extractJsonObject(rawText)) as Record<string, unknown>\n if (!isRoute(parsed.route)) return null\n return {\n route: parsed.route,\n confidence: clampConfidence(typeof parsed.confidence === 'number' ? parsed.confidence : 0.5),\n reason: typeof parsed.reason === 'string' && parsed.reason.trim() ? parsed.reason.trim() : 'LLM route decision.',\n source: 'llm',\n usage,\n }\n } catch {\n return null\n }\n}\n\nfunction buildLlmRouterPrompt(input: RouterInput, ruleDecision: RouteDecision): string {\n const toolNames = input.availableTools?.length ? input.availableTools.join(', ') : '(none)'\n const dataSnapshot = formatDataSnapshotForRouter(input.dataSnapshot)\n\n return `Classify the user's ChatLab request into exactly one route.\n\nRoutes:\n- direct_response: no local chat data/tools needed.\n- tool_assisted: local chat data/tools needed, but the task is simple.\n- planned_execution: complex analysis, multi-step evidence gathering, retries, long-range comparison, relationship analysis, or insufficient-evidence handling.\n\nReturn strict JSON only:\n{\"route\":\"direct_response|tool_assisted|planned_execution\",\"confidence\":0.0,\"reason\":\"short reason\"}\n\nContext:\n- locale: ${input.locale}\n- chatType: ${input.chatType}\n- availableTools: ${toolNames}\n- dataSnapshot: ${dataSnapshot}\n- assistantSummary: ${input.assistantSummary ?? '(none)'}\n- skillSummary: ${input.skillSummary ?? '(none)'}\n- recentIntentSummary: ${input.recentIntentSummary ?? '(none)'}\n- ruleDecision: ${ruleDecision.route}, confidence=${ruleDecision.confidence}, reason=${ruleDecision.reason}\n\nUser request:\n${input.userMessage}`\n}\n\nfunction directRuleDecision(text: string): RouteDecision | null {\n if (\n matchAny(text, [/不用查聊天记录|不用查询|不用查数据|不用看本地数据/i, /润色|改写|翻译|polish|rewrite|translate/i])\n ) {\n return {\n route: 'direct_response',\n confidence: 0.95,\n reason: 'User explicitly asked for a non-data response.',\n source: 'rule',\n }\n }\n\n if (matchAny(text, [/什么是|解释一下|概念|区别|explain|briefly explain|why .*function calling|react/i])) {\n return {\n route: 'direct_response',\n confidence: 0.86,\n reason: 'Conceptual explanation without local chat-data dependency.',\n source: 'rule',\n }\n }\n\n if (matchAny(text, [/日志在哪里|怎么配置|设置|帮助|help|where .*log/i])) {\n return {\n route: 'direct_response',\n confidence: 0.84,\n reason: 'Help or configuration request does not require chat tools.',\n source: 'rule',\n }\n }\n\n if (matchAny(text, [/你觉得|闲聊|随便聊聊|建议我|should i/i])) {\n return {\n route: 'direct_response',\n confidence: 0.78,\n reason: 'Conversational request without clear data dependency.',\n source: 'rule',\n }\n }\n\n return null\n}\n\nfunction plannedRuleDecision(text: string): RouteDecision | null {\n if (matchAny(text, [/如果.*找不到.*继续查|换几个相关|换几个.*继续|search.*if.*not.*found/i])) {\n return {\n route: 'planned_execution',\n confidence: 0.88,\n reason: 'Request asks for search failure recovery and retry strategy.',\n source: 'rule',\n }\n }\n\n const dimensions = [\n matchAny(text, [/分析|复盘|总结.*规律|趋势|变化|演变|compare|comparison/i]),\n matchAny(text, [\n /过去一年|最近一年|近一年|过去.*年|最近半年|今年|去年|上半年|下半年|长期|按季度|按月份|first half|second half/i,\n ]),\n matchAny(text, [/关系|互动|影响力|核心成员|最强|最活跃|被忽略|主动|回应|turning point|转折点/i]),\n matchAny(text, [/证据|举出|例子|关键分歧|主要矛盾|evidence/i]),\n matchAny(text, [/并且|同时|以及|分别|每个阶段|按阶段|阶段|多条件|参与|回复|话题|主题|发言规律|topic|theme/i]),\n matchAny(text, [/证据不够|不足以|不够|uncertain|insufficient/i]),\n ]\n const score = dimensions.filter(Boolean).length\n if (score >= 2) {\n return {\n route: 'planned_execution',\n confidence: Math.min(0.95, 0.72 + score * 0.04),\n reason: `Rule matched ${score} complex-analysis signals.`,\n source: 'rule',\n }\n }\n\n return null\n}\n\nfunction toolRuleDecision(text: string): RouteDecision | null {\n if (\n matchAny(text, [\n /一共有多少|多少成员|多少条消息|谁发言最多|前\\s*\\d+\\s*名|top\\s*\\d+/i,\n /异常活跃|活跃度|发言最多|message count|member count/i,\n ])\n ) {\n return {\n route: 'tool_assisted',\n confidence: 0.82,\n reason: 'Simple statistics request can be answered by one or a few tools.',\n source: 'rule',\n }\n }\n\n if (\n matchAny(text, [\n /找一下|查一下|有没有聊到|有没有提过|最近提到|昨天.*聊了什么|最近.*发言/i,\n /search|find|mentioned|talked about/i,\n ])\n ) {\n return {\n route: 'tool_assisted',\n confidence: 0.78,\n reason: 'Simple search or lookup request with a single clear target.',\n source: 'rule',\n }\n }\n\n return null\n}\n\nfunction ruleDecision(input: RouterInput): RouteDecision {\n const text = normalizeText(input.userMessage)\n\n // 中文注释:先识别复杂分析,再识别简单工具查询,最后识别直答;\n // 这样“分析/证据/长时间范围”等高认知负载请求不会被“找一下”之类词误判成简单搜索。\n return (\n plannedRuleDecision(text) ??\n toolRuleDecision(text) ??\n directRuleDecision(text) ?? {\n route: 'tool_assisted',\n confidence: 0.45,\n reason: 'Ambiguous request; conservatively keep the existing tool-assisted Agent path.',\n source: 'rule',\n }\n )\n}\n\nexport async function decideRequestRoute(\n input: RouterInput,\n options: DecideRequestRouteOptions = {}\n): Promise<RouteDecision> {\n const decision = ruleDecision(input)\n const threshold = options.llmFallbackConfidenceThreshold ?? DEFAULT_LLM_FALLBACK_CONFIDENCE_THRESHOLD\n if (decision.confidence >= threshold || !options.llmRouter) {\n return { ...decision, confidence: clampConfidence(decision.confidence) }\n }\n\n const llmDecision = await options.llmRouter(input, decision)\n return {\n ...llmDecision,\n confidence: clampConfidence(llmDecision.confidence),\n }\n}\n\nexport function createLlmRouteDecider(options: CreateLlmRouteDeciderOptions): LlmRouteDecider {\n return async (input, ruleDecision) => {\n const prompt = buildLlmRouterPrompt(input, ruleDecision)\n\n try {\n const rawResult = options.complete\n ? await options.complete(prompt, options.abortSignal)\n : await completeWithPiAi(prompt, options)\n const { text, usage } = resultToText(rawResult)\n const decision = parseLlmDecision(text, usage)\n if (decision) return decision\n return {\n ...ruleDecision,\n reason: `${ruleDecision.reason} LLM fallback returned invalid route JSON.`,\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n ...ruleDecision,\n reason: `${ruleDecision.reason} LLM fallback failed: ${message}`,\n }\n }\n }\n}\n\nasync function completeWithPiAi(\n prompt: string,\n options: CreateLlmRouteDeciderOptions\n): Promise<LlmRouterCompletionResult> {\n if (!options.piModel || !options.apiKey) {\n throw new Error('LLM router requires piModel and apiKey')\n }\n\n const result = await completeSimple(\n options.piModel,\n {\n systemPrompt: 'You are a strict JSON classifier for ChatLab AI routing. Return JSON only.',\n messages: [{ role: 'user', content: [{ type: 'text', text: prompt }], timestamp: Date.now() }] as any,\n },\n {\n apiKey: options.apiKey,\n maxTokens: options.maxTokens ?? 180,\n temperature: options.temperature ?? 0,\n signal: options.abortSignal,\n }\n )\n\n const text = result.content\n .filter((item): item is PiTextContent => item.type === 'text')\n .map((item) => item.text)\n .join('')\n\n return {\n text,\n usage: {\n promptTokens: result.usage?.input,\n completionTokens: result.usage?.output,\n totalTokens: result.usage?.totalTokens,\n },\n }\n}\n","import {\n completeSimple,\n streamSimple,\n type Api as PiApi,\n type Model as PiModel,\n type TextContent as PiTextContent,\n} from '@earendil-works/pi-ai'\nimport type {\n AnalysisPlanIntent,\n AnalysisPlanSummary,\n AnalysisPlanner,\n PlanContentBlock,\n PlannerInput,\n} from './planning-types'\nimport { formatDataSnapshotForPlanner } from './data-snapshot'\n\nexport type PlannerCompletionResult = string | { text: string }\n\nexport interface PlannerStreamCallbacks {\n onPlanDelta: (delta: string) => void\n onThinkingDelta?: (delta: string) => void\n onThinkingEnd?: (durationMs?: number) => void\n onValidationDelta?: (delta: string) => void\n onValidationEnd?: (durationMs?: number) => void\n}\n\nexport interface CreateAnalysisPlannerOptions {\n piModel?: PiModel<PiApi>\n apiKey?: string\n complete?: (prompt: string, signal?: AbortSignal) => Promise<PlannerCompletionResult> | PlannerCompletionResult\n stream?: (prompt: string, callbacks: PlannerStreamCallbacks, signal?: AbortSignal) => Promise<PlannerCompletionResult>\n onPlanDelta?: (delta: string) => void\n onThinkingDelta?: (delta: string) => void\n onThinkingEnd?: (durationMs?: number) => void\n onValidationDelta?: (delta: string) => void\n onValidationEnd?: (durationMs?: number) => void\n maxTokens?: number\n temperature?: number\n}\n\nconst PLAN_INTENTS: readonly AnalysisPlanIntent[] = [\n 'summary',\n 'trend',\n 'relationship',\n 'search',\n 'comparison',\n 'evidence',\n 'mixed',\n]\nconst STREAM_MARKER_HOLDBACK_CHARS = Math.max('</draft>'.length, '</plan>'.length, '<json>'.length) - 1\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isIntent(value: unknown): value is AnalysisPlanIntent {\n return typeof value === 'string' && PLAN_INTENTS.includes(value as AnalysisPlanIntent)\n}\n\nfunction normalizeString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const text = value.trim()\n return text.length > 0 ? text : null\n}\n\nfunction extractJsonObject(text: string): string {\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/i)\n if (fenced?.[1]) return fenced[1].trim()\n const start = text.indexOf('{')\n const end = text.lastIndexOf('}')\n if (start >= 0 && end > start) return text.slice(start, end + 1)\n return text.trim()\n}\n\nfunction parsePlanJson(rawText: string, availableTools: Set<string>): AnalysisPlanSummary | null {\n try {\n const parsed = JSON.parse(extractJsonObject(rawText)) as unknown\n if (!isRecord(parsed)) return null\n const title = normalizeString(parsed.title)\n if (!title || !isIntent(parsed.intent) || !Array.isArray(parsed.steps)) return null\n\n // 中文注释:Planner 输出来自 LLM,必须在进入 Agent guidance 前做结构约束;\n // 这里裁剪长度并过滤不存在的工具,避免把幻觉工具名注入后续执行上下文。\n const steps = parsed.steps\n .filter(isRecord)\n .map((step) => {\n const goal = normalizeString(step.goal)\n const evidenceNeeded = normalizeString(step.evidenceNeeded)\n if (!goal || !evidenceNeeded) return null\n const suggestedTools = Array.isArray(step.suggestedTools)\n ? step.suggestedTools.filter((tool): tool is string => typeof tool === 'string' && availableTools.has(tool))\n : []\n return { goal, suggestedTools: Array.from(new Set(suggestedTools)), evidenceNeeded }\n })\n .filter((step): step is NonNullable<typeof step> => step !== null)\n .slice(0, 5)\n\n if (steps.length === 0) return null\n\n const successCriteria = Array.isArray(parsed.successCriteria)\n ? parsed.successCriteria\n .map(normalizeString)\n .filter((item): item is string => item !== null)\n .slice(0, 5)\n : []\n if (successCriteria.length === 0) return null\n\n return {\n version: 1,\n title,\n route: 'planned_execution',\n intent: parsed.intent,\n steps,\n successCriteria,\n }\n } catch {\n return null\n }\n}\n\nfunction resultToText(result: PlannerCompletionResult): string {\n return typeof result === 'string' ? result : result.text\n}\n\nfunction buildPlannerPrompt(input: PlannerInput): string {\n const dataSnapshot = formatDataSnapshotForPlanner(input.dataSnapshot)\n const availableCapabilities =\n input.availableCapabilities && input.availableCapabilities.length > 0\n ? input.availableCapabilities\n .map((capability) => {\n const tools = capability.tools.join(', ') || 'none'\n return ` - ${capability.id} (${capability.label}; tools: ${tools}): ${capability.guidance}`\n })\n .join('\\n')\n : '(none)'\n\n return `Create a concise user-visible analysis plan for a ChatLab planned_execution request.\n\nReturn exactly three blocks:\n<draft>\nA brief user-visible planning note in the user's locale. Keep it high-level; do not reveal hidden chain-of-thought.\n</draft>\n<plan>\nA concise user-facing analysis approach in the user's locale. Do not include suggested tools, evidence fields, or success criteria; those details belong in <json>.\n</plan>\n<json>\n{\n \"title\": \"short title\",\n \"intent\": \"summary|trend|relationship|search|comparison|evidence|mixed\",\n \"steps\": [\n {\"goal\": \"what to investigate\", \"suggestedTools\": [\"tool_name\"], \"evidenceNeeded\": \"what evidence is needed\"}\n ],\n \"successCriteria\": [\"criterion\"]\n}\n</json>\n\nLimits:\n- Max 5 steps.\n- Max 5 successCriteria.\n- suggestedTools must come only from availableTools.\n- If availableCapabilities are listed, you may plan steps that use their tools when the capability helps the user.\n- Do not reveal hidden chain-of-thought; draft and plan are user-readable summaries only.\n- The plan is soft guidance, not a mandatory execution script.\n\nPlanning strategy:\n- For open-ended topic, community profile, influence, interaction-pattern, or multi-dimensional retrospective analysis, first plan a lightweight reconnaissance step to build a topic/member activity map before drawing conclusions.\n- Prefer segment summaries, keyword frequency, time distribution, member activity, and representative message retrieval when available.\n- Do not create a dedicated step whose only purpose is finding max timestamp or confirming latest message time.\n- Use intent \"evidence\" for historical fact / occurrence / count / \"did we ever\" / proof-from-chat questions that need an evidence chain or a counted/conservative conclusion. When the intent is \"evidence\" and retrieve_chat_evidence is in availableTools, suggest exactly [\"retrieve_chat_evidence\"] for that step instead of search_messages or semantic_search_current_chat, so the model does not pick only one low-level retrieval path.\n\nContext:\n- locale: ${input.locale}\n- chatType: ${input.chatType}\n- availableTools: ${input.availableTools.join(', ') || '(none)'}\n- availableCapabilities:\n${availableCapabilities}\n- dataSnapshot: ${dataSnapshot}\n- assistantSummary: ${input.assistantSummary ?? '(none)'}\n- skillSummary: ${input.skillSummary ?? '(none)'}\n- recentIntentSummary: ${input.recentIntentSummary ?? '(none)'}\n\nUser request:\n${input.userMessage}`\n}\n\nexport function createAnalysisPlanner(options: CreateAnalysisPlannerOptions): AnalysisPlanner {\n return async (input, signal) => {\n try {\n const prompt = buildPlannerPrompt(input)\n const callbacks: PlannerStreamCallbacks = {\n onPlanDelta: options.onPlanDelta ?? (() => {}),\n onThinkingDelta: options.onThinkingDelta,\n onThinkingEnd: options.onThinkingEnd,\n onValidationDelta: options.onValidationDelta,\n onValidationEnd: options.onValidationEnd,\n }\n const rawResult = options.stream\n ? await options.stream(prompt, callbacks, signal)\n : options.complete\n ? await options.complete(prompt, signal)\n : options.onPlanDelta\n ? await streamWithPiAi(prompt, options, signal)\n : await completeWithPiAi(prompt, options, signal)\n return parsePlanJson(resultToText(rawResult), new Set(input.availableTools))\n } catch {\n return null\n }\n }\n}\n\nexport function createPlanContentBlock(\n plan: AnalysisPlanSummary,\n status: PlanContentBlock['status'] = 'created'\n): PlanContentBlock {\n return {\n type: 'plan',\n version: 1,\n status,\n plan,\n }\n}\n\nexport function buildPlanGuidance(plan: AnalysisPlanSummary): string {\n const steps = plan.steps\n .map((step, index) => {\n const tools = step.suggestedTools.length > 0 ? step.suggestedTools.join(', ') : 'none'\n return `${index + 1}. ${step.goal}\\n Evidence needed: ${step.evidenceNeeded}\\n Suggested tools: ${tools}`\n })\n .join('\\n')\n const successCriteria = plan.successCriteria.map((item) => `- ${item}`).join('\\n')\n\n return `A suggested analysis plan is available below. Use it as guidance when helpful, but do not follow it mechanically if the user's question can be answered more directly.\n\nPlan title: ${plan.title}\nIntent: ${plan.intent}\n\nSteps:\n${steps}\n\nSuccess criteria:\n${successCriteria}`\n}\n\nasync function completeWithPiAi(\n prompt: string,\n options: CreateAnalysisPlannerOptions,\n signal?: AbortSignal\n): Promise<PlannerCompletionResult> {\n if (!options.piModel || !options.apiKey) {\n throw new Error('Planner requires piModel and apiKey')\n }\n\n const result = await completeSimple(\n options.piModel,\n {\n systemPrompt: 'You are a strict planner for ChatLab analysis. Return the requested blocks only.',\n messages: [{ role: 'user', content: [{ type: 'text', text: prompt }], timestamp: Date.now() }] as any,\n },\n {\n apiKey: options.apiKey,\n maxTokens: options.maxTokens ?? 900,\n temperature: options.temperature ?? 0.2,\n signal,\n }\n )\n\n return {\n text: result.content\n .filter((item): item is PiTextContent => item.type === 'text')\n .map((item) => item.text)\n .join(''),\n }\n}\n\nfunction extractTaggedBlockText(rawText: string, tag: 'draft' | 'plan' | 'json', nextTags: string[]): string {\n const openTag = `<${tag}>`\n const closeTag = `</${tag}>`\n const blockStart = rawText.indexOf(openTag)\n if (blockStart < 0) return ''\n\n const bodyStart = blockStart + openTag.length\n const closeStart = rawText.indexOf(closeTag, bodyStart)\n const nextStarts = nextTags.map((nextTag) => rawText.indexOf(nextTag, bodyStart)).filter((index) => index >= 0)\n const endCandidates = [closeStart, ...nextStarts].filter((index) => index >= 0)\n const bodyEnd =\n endCandidates.length > 0\n ? Math.min(...endCandidates)\n : Math.max(bodyStart, rawText.length - STREAM_MARKER_HOLDBACK_CHARS)\n const text = rawText.slice(bodyStart, bodyEnd)\n return text.replace(/^\\s+/, '')\n}\n\nasync function streamWithPiAi(\n prompt: string,\n options: CreateAnalysisPlannerOptions,\n signal?: AbortSignal\n): Promise<PlannerCompletionResult> {\n if (!options.piModel || !options.apiKey) {\n throw new Error('Planner requires piModel and apiKey')\n }\n\n const stream = streamSimple(\n options.piModel,\n {\n systemPrompt: 'You are a strict planner for ChatLab analysis. Return the requested plan and JSON blocks only.',\n messages: [{ role: 'user', content: [{ type: 'text', text: prompt }], timestamp: Date.now() }] as any,\n },\n {\n apiKey: options.apiKey,\n maxTokens: options.maxTokens ?? 900,\n temperature: options.temperature ?? 0.2,\n signal,\n }\n )\n\n let text = ''\n let emittedDraftLength = 0\n let emittedPlanLength = 0\n let emittedJsonLength = 0\n let thinkingStartedAt: number | undefined\n let draftThinkingStartedAt: number | undefined\n let draftThinkingEnded = false\n let validationStartedAt: number | undefined\n let validationEnded = false\n\n for await (const event of stream) {\n if (signal?.aborted) break\n if (event.type === 'thinking_start') {\n thinkingStartedAt = Date.now()\n continue\n }\n if (event.type === 'thinking_delta') {\n options.onThinkingDelta?.(event.delta)\n continue\n }\n if (event.type === 'thinking_end') {\n const durationMs = thinkingStartedAt ? Date.now() - thinkingStartedAt : undefined\n thinkingStartedAt = undefined\n options.onThinkingEnd?.(durationMs)\n continue\n }\n if (event.type !== 'text_delta') continue\n\n text += event.delta\n const draftText = extractTaggedBlockText(text, 'draft', ['<plan>', '<json>'])\n if (draftText.length > emittedDraftLength) {\n if (!draftThinkingStartedAt) draftThinkingStartedAt = Date.now()\n const delta = draftText.slice(emittedDraftLength)\n emittedDraftLength = draftText.length\n if (delta.trim().length > 0 || /\\s/.test(delta)) {\n options.onThinkingDelta?.(delta)\n }\n }\n if (!draftThinkingEnded && emittedDraftLength > 0 && (text.includes('</draft>') || text.includes('<plan>'))) {\n draftThinkingEnded = true\n const durationMs = draftThinkingStartedAt ? Date.now() - draftThinkingStartedAt : undefined\n options.onThinkingEnd?.(durationMs)\n }\n\n const planText = extractTaggedBlockText(text, 'plan', ['<json>'])\n if (planText.length > emittedPlanLength) {\n const delta = planText.slice(emittedPlanLength)\n emittedPlanLength = planText.length\n if (delta.trim().length > 0 || /\\s/.test(delta)) {\n options.onPlanDelta?.(delta)\n }\n }\n\n const jsonText = extractTaggedBlockText(text, 'json', [])\n if (jsonText.length > emittedJsonLength) {\n if (!validationStartedAt) validationStartedAt = Date.now()\n const delta = jsonText.slice(emittedJsonLength)\n emittedJsonLength = jsonText.length\n if (delta.trim().length > 0 || /\\s/.test(delta)) {\n options.onValidationDelta?.(delta)\n }\n }\n if (!validationEnded && emittedJsonLength > 0 && text.includes('</json>')) {\n validationEnded = true\n const durationMs = validationStartedAt ? Date.now() - validationStartedAt : undefined\n options.onValidationEnd?.(durationMs)\n }\n }\n\n return { text }\n}\n","/**\n * 语义检索工具引导语\n *\n * 仅当当前会话语义索引可检索、工具被暴露给 LLM 时,由两端 runner 注入 system prompt。\n * 引导模型在需要历史证据时调用 semantic_search_current_chat,避免寒暄/写作类问题无谓检索。\n */\n\nfunction isChinese(locale?: string): boolean {\n return (locale ?? '').toLowerCase().startsWith('zh')\n}\n\nexport function buildSemanticSearchGuidance(locale?: string): string {\n if (isChinese(locale)) {\n return [\n '检索本对话历史时按需选择工具:',\n '需要证据链 / 事件次数统计 / 是否发生过 / “我们有没有/去过几次”这类历史事实判断时,优先调用 retrieve_chat_evidence(它会综合语义与关键词并给出可计入/不计入/不确定的证据)。',\n '想盘点或归纳某类话题、列举“聊过哪些X / 有哪些 / 都聊过什么 / 提到过哪些 / 喜欢什么”,或查找语义相关片段时,调用 semantic_search_current_chat 做向量检索:一次检索即可召回语义相关片段,覆盖未出现字面关键词的内容;不要用多轮 search_messages 逐个猜测关键词穷举。',\n '只有在已知确切字面词、原话、特定发送者或时间范围时,才用 search_messages 精确查找。',\n '寒暄、写作、解释通用概念等不依赖历史证据的问题不要调用检索工具。',\n ].join('')\n }\n return [\n 'Choose a retrieval tool by need when searching THIS conversation history: ',\n 'for evidence chains, event counts, whether something happened, or \"how many times / did we ever\" historical fact judgments, prefer retrieve_chat_evidence (it combines semantic + keyword retrieval and returns included/excluded/uncertain evidence). ',\n 'To inventory or summarize a topic, enumerate \"what X did we discuss / which ones / what did we talk about / what do we like\", or find semantically related excerpts, call semantic_search_current_chat (vector search): a single search recalls related excerpts including ones without the literal keyword — do NOT brute-force many rounds of search_messages guessing keywords. ',\n 'Use search_messages only when you already know the exact literal word, quote, specific sender, or time range. ',\n 'Do not call retrieval tools for greetings, writing, or explaining general concepts that need no historical evidence.',\n ].join('')\n}\n","/**\n * Agent event handler — shared implementation.\n *\n * Maps AgentCoreEvent → stream chunks, tracks usage/tool rounds,\n * estimates context tokens. Platform-agnostic via generic chunk type.\n */\n\nimport type { Message as PiMessage } from '@earendil-works/pi-ai'\nimport type { AgentCoreEvent } from './types'\nimport type { PlanContentBlock } from './planning-types'\nimport type { RouteDecision } from './routing-types'\n\n// ==================== Shared types ====================\n\nexport interface TokenUsage {\n promptTokens: number\n completionTokens: number\n totalTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n}\n\nexport interface AgentRuntimeStatus {\n phase: 'compressing' | 'preparing' | 'thinking' | 'tool_running' | 'responding' | 'completed' | 'aborted' | 'error'\n round: number\n toolsUsed: number\n currentTool?: string\n contextTokens: number\n totalUsage: TokenUsage\n updatedAt: number\n}\n\nexport interface AgentStreamChunk {\n type:\n | 'content'\n | 'think'\n | 'tool_start'\n | 'tool_result'\n | 'status'\n | 'compression_done'\n | 'route'\n | 'plan_delta'\n | 'plan'\n | 'plan_skipped'\n | 'done'\n | 'error'\n content?: string\n thinkTag?: string\n thinkDurationMs?: number\n toolCallId?: string\n toolName?: string\n toolParams?: Record<string, unknown>\n toolResult?: unknown\n toolIsError?: boolean\n error?: unknown\n isFinished?: boolean\n usage?: TokenUsage\n status?: AgentRuntimeStatus\n routeDecision?: RouteDecision\n planDelta?: string\n plan?: PlanContentBlock\n compressionResult?: {\n summaryContent: string\n tokensBefore: number\n tokensAfter: number\n timestamp: number\n }\n}\n\nexport interface EventHandlerContext {\n maxMessagesLimit?: number\n timeFilter?: { startTs: number; endTs: number }\n}\n\nexport interface EventHandlerConfig {\n onChunk: (chunk: AgentStreamChunk) => void\n context: EventHandlerContext\n systemPrompt: string\n}\n\n// ==================== Token estimation ====================\n\nfunction estimateTokensFromText(text: string): number {\n if (!text) return 0\n const normalized = text.replace(/\\s+/g, ' ').trim()\n if (!normalized) return 0\n const cjkCount = (normalized.match(/[\\u3400-\\u9fff\\uf900-\\ufaff]/g) || []).length\n const latinCount = normalized.length - cjkCount\n return Math.max(1, Math.ceil(cjkCount * 1.15 + latinCount / 4))\n}\n\nfunction extractMessageText(message: PiMessage): string {\n if (message.role === 'user') {\n if (typeof message.content === 'string') return message.content\n return message.content\n .map((item) => {\n if (item.type === 'text') return item.text\n if (item.type === 'image') return '[image]'\n return ''\n })\n .join('\\n')\n }\n\n if (message.role === 'assistant') {\n return message.content\n .map((item) => {\n if (item.type === 'text') return item.text\n if (item.type === 'thinking') return item.thinking\n if (item.type === 'toolCall') return `${item.name} ${JSON.stringify(item.arguments || {})}`\n return ''\n })\n .join('\\n')\n }\n\n if (message.role === 'toolResult') {\n return message.content\n .map((item) => {\n if (item.type === 'text') return item.text\n return '[binary]'\n })\n .join('\\n')\n }\n\n return ''\n}\n\nfunction estimateContextTokens(systemPrompt: string, messages: PiMessage[], pendingUserMessage?: string): number {\n let tokens = estimateTokensFromText(systemPrompt)\n for (const message of messages) {\n if (message.role === 'toolResult') continue\n tokens += estimateTokensFromText(extractMessageText(message))\n }\n if (pendingUserMessage) {\n tokens += estimateTokensFromText(pendingUserMessage)\n }\n return tokens\n}\n\n// Exported for unit testing\nexport { estimateTokensFromText }\n\n// ==================== Event handler class ====================\n\nexport class AgentEventHandler {\n readonly toolsUsed: string[] = []\n toolRounds: number = 0\n\n private totalUsage: TokenUsage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n private lastStatusAt = 0\n private readonly onChunk: (chunk: AgentStreamChunk) => void\n private readonly context: EventHandlerContext\n private readonly systemPrompt: string\n\n constructor(config: EventHandlerConfig) {\n this.onChunk = config.onChunk\n this.context = config.context\n this.systemPrompt = config.systemPrompt\n }\n\n handleCoreEvent(event: AgentCoreEvent, messages: PiMessage[]): void {\n switch (event.type) {\n case 'content':\n this.onChunk({ type: 'content', content: event.content })\n this.emitStatus('responding', messages)\n break\n case 'thinking_start':\n this.emitStatus('thinking', messages, { force: true })\n break\n case 'thinking_delta':\n this.onChunk({ type: 'think', content: event.content, thinkTag: 'thinking' })\n this.emitStatus('thinking', messages)\n break\n case 'thinking_end':\n this.onChunk({ type: 'think', content: '', thinkTag: 'thinking', thinkDurationMs: event.durationMs })\n this.emitStatus('responding', messages, { force: true })\n break\n case 'tool_start': {\n const params = this.normalizeToolParams(event.toolName, event.toolParams)\n this.toolsUsed.push(event.toolName)\n this.onChunk({ type: 'tool_start', toolCallId: event.toolCallId, toolName: event.toolName, toolParams: params })\n this.emitStatus('tool_running', messages, { currentTool: event.toolName, force: true })\n break\n }\n case 'tool_end':\n this.onChunk({\n type: 'tool_result',\n toolCallId: event.toolCallId,\n toolName: event.toolName,\n toolResult: event.toolResult,\n toolIsError: event.isError,\n })\n this.emitStatus('thinking', messages, { force: true })\n break\n case 'turn_end':\n this.toolRounds = event.round\n this.emitStatus('thinking', messages, { force: true })\n break\n case 'usage_update':\n this.totalUsage = { ...event.usage }\n this.emitStatus('responding', messages, { force: true })\n break\n }\n }\n\n cloneUsage(): TokenUsage {\n return {\n promptTokens: this.totalUsage.promptTokens,\n completionTokens: this.totalUsage.completionTokens,\n totalTokens: this.totalUsage.totalTokens,\n cacheReadTokens: this.totalUsage.cacheReadTokens,\n cacheWriteTokens: this.totalUsage.cacheWriteTokens,\n }\n }\n\n emitStatus(\n phase: AgentRuntimeStatus['phase'],\n messages: PiMessage[],\n options?: { pendingUserMessage?: string; currentTool?: string; force?: boolean }\n ): void {\n const now = Date.now()\n if (!options?.force && now - this.lastStatusAt < 240) return\n this.lastStatusAt = now\n\n const contextTokens = estimateContextTokens(this.systemPrompt, messages, options?.pendingUserMessage)\n const status: AgentRuntimeStatus = {\n phase,\n round: this.toolRounds,\n toolsUsed: this.toolsUsed.length,\n currentTool: options?.currentTool,\n contextTokens,\n totalUsage: this.cloneUsage(),\n updatedAt: now,\n }\n this.onChunk({ type: 'status', status })\n }\n\n normalizeToolParams(toolName: string, params: Record<string, unknown>): Record<string, unknown> {\n const normalized = { ...params }\n const toolsWithLimit = ['search_messages', 'get_recent_messages', 'get_conversation_between']\n if (this.context.maxMessagesLimit && toolsWithLimit.includes(toolName)) {\n normalized.limit = this.context.maxMessagesLimit\n }\n if (this.context.timeFilter && (toolName === 'search_messages' || toolName === 'get_recent_messages')) {\n normalized._timeFilter = this.context.timeFilter\n }\n return normalized\n }\n}\n","/**\n * Agent system prompt builder — shared implementation.\n *\n * The i18n translation function is injected via `t` parameter,\n * making this module platform-agnostic.\n */\n\nexport interface OwnerInfo {\n platformId: string\n displayName: string\n}\n\nexport interface MentionedMember {\n memberId: number\n platformId: string\n displayName: string\n aliases: string[]\n mentionText: string\n}\n\nexport interface SkillContext {\n skillDef?: { name: string; prompt: string }\n skillMenu?: string\n}\n\nexport interface ActiveMemberHint {\n memberId: number\n displayName: string\n messageCount: number\n share: number\n}\n\nexport interface DataSnapshot {\n version?: 2\n name: string\n platform: string\n type: string\n totalMessages: number\n totalMembers: number\n firstMessageTs: number | null\n lastMessageTs: number | null\n capturedAt?: number\n activeMemberHints?: ActiveMemberHint[]\n segmentSummaries?: {\n availableCount: number\n }\n}\n\nexport type TranslateFn = (key: string, options?: Record<string, unknown>) => string\n\nexport interface BuildSystemPromptOptions {\n t: TranslateFn\n chatType?: 'group' | 'private'\n assistantSystemPrompt?: string\n ownerInfo?: OwnerInfo\n locale?: string\n skillCtx?: SkillContext\n mentionedMembers?: MentionedMember[]\n dataSnapshot?: DataSnapshot\n}\n\nfunction agentT(t: TranslateFn, key: string, locale: string, options?: Record<string, unknown>): string {\n return t(key, { lng: locale, ...options })\n}\n\nfunction formatNullableTimestamp(timestamp: number | null | undefined): string {\n return typeof timestamp === 'number' && Number.isFinite(timestamp) ? String(timestamp) : 'null'\n}\n\nfunction formatSharePercent(share: number): string {\n if (!Number.isFinite(share)) return '0%'\n const rounded = Math.round(share * 10) / 10\n return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`\n}\n\nfunction normalizeInlineText(text: string): string {\n return text.replace(/\\s+/g, ' ').trim()\n}\n\nexport function formatDataSnapshotContext(t: TranslateFn, dataSnapshot: DataSnapshot, locale: string): string {\n const memberHints = dataSnapshot.activeMemberHints ?? []\n const memberHintTitle =\n memberHints.length === 0\n ? agentT(t, 'ai.agent.dataSnapshotMemberHintsUnavailable', locale)\n : dataSnapshot.totalMembers <= 10\n ? agentT(t, 'ai.agent.dataSnapshotMemberHintsAll', locale)\n : agentT(t, 'ai.agent.dataSnapshotMemberHintsTop', locale)\n\n // 中文注释:启动上下文会进入每轮系统提示词,字段顺序和标签必须稳定,便于模型缓存和后续 smoke 对比。\n const memberHintLines =\n memberHints.length > 0\n ? memberHints\n .map((member, index) => {\n return `${index + 1}. member_id=${member.memberId} | display_name=${normalizeInlineText(member.displayName)} | messages=${member.messageCount} | share=${formatSharePercent(member.share)}`\n })\n .join('\\n')\n : agentT(t, 'ai.agent.dataSnapshotMemberHintsEmpty', locale)\n\n return agentT(t, 'ai.agent.dataSnapshotContext', locale, {\n name: dataSnapshot.name,\n platform: dataSnapshot.platform,\n type: dataSnapshot.type,\n totalMessages: dataSnapshot.totalMessages,\n totalMembers: dataSnapshot.totalMembers,\n firstMessageTs: formatNullableTimestamp(dataSnapshot.firstMessageTs),\n firstMessageTime: formatTimestamp(dataSnapshot.firstMessageTs, locale),\n lastMessageTs: formatNullableTimestamp(dataSnapshot.lastMessageTs),\n lastMessageTime: formatTimestamp(dataSnapshot.lastMessageTs, locale),\n segmentSummaryCount: dataSnapshot.segmentSummaries?.availableCount ?? 0,\n memberHintTitle,\n memberHintLines,\n usageRules: agentT(t, 'ai.agent.dataSnapshotUsageRules', locale),\n })\n}\n\nfunction getLockedPromptSection(\n t: TranslateFn,\n chatType: 'group' | 'private',\n ownerInfo: OwnerInfo | undefined,\n locale: string,\n mentionedMembers: MentionedMember[] | undefined,\n dataSnapshot: DataSnapshot | undefined\n): string {\n const now = new Date()\n const dateLocale = locale.startsWith('zh') ? 'zh-CN' : 'en-US'\n const currentDate = now.toLocaleDateString(dateLocale, {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n weekday: 'long',\n })\n\n const isPrivate = chatType === 'private'\n const chatContext = agentT(t, `ai.agent.chatContext.${chatType}`, locale)\n\n const ownerNote = ownerInfo\n ? agentT(t, 'ai.agent.ownerNote', locale, {\n displayName: ownerInfo.displayName,\n platformId: ownerInfo.platformId,\n chatContext,\n })\n : ''\n\n const memberNote = isPrivate\n ? agentT(t, 'ai.agent.memberNotePrivate', locale)\n : agentT(t, 'ai.agent.memberNoteGroup', locale)\n\n const mentionedMembersNote =\n mentionedMembers && mentionedMembers.length > 0\n ? `${agentT(t, 'ai.agent.mentionedMembersNote', locale)}\\n${mentionedMembers\n .map((member) => {\n const aliasPart = member.aliases.length > 0 ? ` | aliases=${member.aliases.join(',')}` : ''\n return `- member_id=${member.memberId} | mention=${member.mentionText} | display_name=${member.displayName} | platform_id=${member.platformId}${aliasPart}`\n })\n .join('\\n')}\\n`\n : ''\n\n const year = now.getFullYear()\n const dataSnapshotNote = dataSnapshot ? `${formatDataSnapshotContext(t, dataSnapshot, locale)}\\n` : ''\n\n return `${agentT(t, 'ai.agent.currentDateIs', locale)} ${currentDate}。\n${ownerNote}\n${mentionedMembersNote}\n${dataSnapshotNote}\n${memberNote}\n${agentT(t, 'ai.agent.timeParamsIntro', locale)}\n${agentT(t, 'ai.agent.defaultYearNote', locale, { year })}\n${agentT(t, 'ai.agent.evidencePolicy', locale)}\n\n${agentT(t, 'ai.agent.responseInstruction', locale)}`\n}\n\nfunction getFallbackRoleDefinition(t: TranslateFn, chatType: 'group' | 'private', locale: string): string {\n return agentT(t, `ai.agent.fallbackRoleDefinition.${chatType}`, locale)\n}\n\nfunction formatTimestamp(timestamp: number | null | undefined, locale: string): string {\n if (!timestamp) return locale.startsWith('zh') ? '未知' : 'unknown'\n\n const date = new Date(timestamp * 1000)\n if (Number.isNaN(date.getTime())) return locale.startsWith('zh') ? '未知' : 'unknown'\n\n const dateLocale = locale.startsWith('zh') ? 'zh-CN' : locale\n return date.toLocaleString(dateLocale, {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n })\n}\n\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n const {\n t,\n chatType = 'group',\n assistantSystemPrompt,\n ownerInfo,\n locale = 'zh-CN',\n skillCtx,\n mentionedMembers,\n dataSnapshot,\n } = options\n\n const systemPrompt = assistantSystemPrompt || getFallbackRoleDefinition(t, chatType, locale)\n const lockedSection = getLockedPromptSection(t, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot)\n\n let skillSection = ''\n if (skillCtx?.skillDef) {\n skillSection =\n `\\n## ${agentT(t, 'ai.agent.currentTask', locale)}:${skillCtx.skillDef.name}\\n` +\n `${agentT(t, 'ai.agent.skillPriorityNote', locale)}\\n` +\n skillCtx.skillDef.prompt\n } else if (skillCtx?.skillMenu) {\n skillSection = `\\n${skillCtx.skillMenu}`\n }\n\n return `${systemPrompt}${skillSection}\n\n${lockedSection}`\n}\n","/**\n * AI shared translations — Simplified Chinese\n */\nexport default {\n ai: {\n tools: {\n search_messages: {\n desc: '根据关键词搜索聊天记录,是回答关键词、成员发言、是否出现过某话题等事实问题的主要证据工具。可以指定时间范围和发送者来筛选消息。支持精确到分钟级别的时间查询。',\n params: {\n keywords: '搜索关键词列表,会用 OR 逻辑匹配包含任一关键词的消息。如果只需要按发送者筛选,可以传空数组 []',\n sender_id: '发送者的成员 ID,用于筛选特定成员发送的消息。可以通过 get_members 工具获取成员 ID',\n limit: '返回消息数量限制,默认 1000,最大 50000',\n year: '指定年份筛选消息,如 2024',\n month: '指定月份筛选消息(1-12),需配合 year 使用',\n day: '指定日期筛选消息(1-31),需配合 year 和 month 使用',\n hour: '指定小时筛选消息(0-23),需配合 year、month、day 使用',\n start_time: '开始时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"。指定后覆盖 year/month/day/hour',\n end_time: '结束时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"。指定后覆盖 year/month/day/hour',\n },\n },\n deep_search_messages: {\n desc: '精确子串匹配搜索聊天记录,速度较慢但不会遗漏任何包含关键词的消息。当 search_messages 结果不足、需要验证某段话是否真实存在、或搜索部分词/单个字符时使用。',\n params: {\n keywords: '搜索关键词列表,使用子串匹配(LIKE),任一关键词匹配即返回',\n sender_id: '发送者的成员 ID,用于筛选特定成员发送的消息',\n limit: '返回消息数量限制,默认 1000,最大 50000',\n year: '指定年份筛选消息,如 2024',\n month: '指定月份筛选消息(1-12),需配合 year 使用',\n day: '指定日期筛选消息(1-31),需配合 year 和 month 使用',\n hour: '指定小时筛选消息(0-23),需配合 year、month、day 使用',\n start_time: '开始时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"。指定后覆盖 year/month/day/hour',\n end_time: '结束时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"。指定后覆盖 year/month/day/hour',\n },\n },\n get_recent_messages: {\n desc: '获取指定时间段内的聊天消息,是回答\"最近大家聊了什么\"、\"X月聊了什么\"、追加新记录后最新内容等概览性问题的首选证据工具。支持精确到分钟级别的时间查询。',\n params: {\n limit: '返回消息数量限制,默认 100(节省 token,可根据需要增加)',\n year: '指定年份筛选消息,如 2024',\n month: '指定月份筛选消息(1-12),需配合 year 使用',\n day: '指定日期筛选消息(1-31),需配合 year 和 month 使用',\n hour: '指定小时筛选消息(0-23),需配合 year、month、day 使用',\n start_time: '开始时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"。指定后覆盖 year/month/day/hour',\n end_time: '结束时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"。指定后覆盖 year/month/day/hour',\n },\n },\n get_chat_overview: {\n desc: '获取聊天记录的基本概览信息,包括群名/平台/类型/总消息数/总成员数/时间跨度/最活跃成员排名。适合在分析前或追加新记录后确认当前数据库范围。',\n params: {\n top_n: '返回前 N 名活跃成员,默认 10',\n },\n },\n get_member_stats: {\n desc: '获取群成员的活跃度统计数据。适用于回答\"谁最活跃\"、\"发言最多的是谁\"等问题。',\n params: {\n top_n: '返回前 N 名成员,默认 10',\n },\n },\n get_time_stats: {\n desc: '获取群聊的时间分布统计。适用于回答\"什么时候最活跃\"、\"大家一般几点聊天\"等问题。返回的 `data` 数组可直接作为 `render_chart` 的 `rows` 参数用于绘图。',\n params: {\n type: '统计类型:hourly(按小时)、weekday(按星期)、daily(按日期)',\n },\n },\n get_members: {\n desc: '获取群成员列表,包括成员的基本信息、别名和消息统计。适用于查询\"群里有哪些人\"、\"某人的别名是什么\"、\"谁的QQ号是xxx\"等问题。',\n params: {\n search: '可选的搜索关键词,用于筛选成员昵称、别名或QQ号',\n limit: '返回成员数量限制,默认返回全部',\n },\n },\n get_member_name_history: {\n desc: '获取成员的昵称变更历史记录。适用于回答\"某人以前叫什么名字\"、\"某人的昵称变化\"、\"某人曾用名\"等问题。需要先通过 get_members 工具获取成员 ID。',\n params: {\n member_id: '成员的数据库 ID,可以通过 get_members 工具获取',\n },\n },\n get_conversation_between: {\n desc: '获取两个群成员之间的对话记录。适用于回答\"A和B之间聊了什么\"、\"查看两人的对话\"等问题。需要先通过 get_members 获取成员 ID。支持精确到分钟级别的时间查询。',\n params: {\n member_id_1: '第一个成员的数据库 ID',\n member_id_2: '第二个成员的数据库 ID',\n limit: '返回消息数量限制,默认 100',\n start_time: '开始时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"。指定后覆盖 year/month/day/hour',\n end_time: '结束时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"。指定后覆盖 year/month/day/hour',\n },\n },\n get_message_context: {\n desc: '根据消息 ID 获取前后的上下文消息。适用于引用具体消息前后进行事实验证,比如\"这条消息前后在聊什么\"、\"确认这段话是否有上下文\"等。支持单个或批量消息 ID。',\n params: {\n message_ids:\n '要查询上下文的消息 ID 列表,可以是单个 ID 或多个 ID。消息 ID 可以从 search_messages 等工具的返回结果中获取',\n context_size: '上下文大小,即获取前后各多少条消息,默认 20',\n },\n },\n get_segment_messages: {\n desc: '获取指定段落的完整消息列表。用于在 get_segment_summaries 找到相关段落摘要后,获取该段落的完整原文上下文。返回段落的所有消息及参与者信息。',\n params: {\n segment_id: '段落 ID,可以从 get_segment_summaries 的返回结果中获取',\n limit: '返回消息数量限制,默认 1000。对于超长段落可以限制返回数量以节省 token',\n },\n },\n get_segment_summaries: {\n desc: `获取段落摘要列表,快速了解群聊历史讨论的主题。\n\n适用场景:\n1. 了解群里最近在聊什么话题\n2. 按关键词搜索讨论过的话题\n3. 概览性问题如\"群里有没有讨论过旅游\"\n\n返回的摘要是对每个段落的简短总结,可以帮助快速定位感兴趣的段落,然后用 get_segment_messages 获取详情。`,\n params: {\n keywords: '在摘要中搜索的关键词列表(OR 逻辑匹配)',\n limit: '返回段落数量限制,默认 20',\n year: '指定年份筛选段落',\n month: '指定月份筛选段落(1-12)',\n day: '指定日期筛选段落(1-31)',\n start_time: '开始时间,格式 \"YYYY-MM-DD HH:mm\"',\n end_time: '结束时间,格式 \"YYYY-MM-DD HH:mm\"',\n },\n },\n message_type_breakdown: {\n desc: '按消息类型统计近 N 天的消息分布(文本、图片、语音、表情等各有多少条)。适用于了解沟通方式偏好。',\n params: { days: '统计最近多少天的数据' },\n rowTemplate: '{type_name}:{msg_count} 条(占 {percentage}%)',\n summaryTemplate: '消息类型分布(共 {rowCount} 种类型):',\n fallback: '该时间范围内没有消息记录',\n },\n peak_chat_hours_by_member: {\n desc: '分析指定成员近 N 天的小时分布,找出其最活跃的时间段。需要先获取 member_id。',\n params: {\n member_id: '成员 ID(通过 get_members 获取)',\n days: '统计最近多少天的数据',\n },\n rowTemplate: '{hour}:00 — {msg_count} 条消息',\n summaryTemplate: '该成员各时间段发言量(共 {rowCount} 个活跃时段):',\n fallback: '指定时间范围内该成员无发言记录',\n },\n member_activity_trend: {\n desc: '查看指定成员近 N 天每天的发言量趋势,观察其活跃度变化。需要先获取 member_id。',\n params: {\n member_id: '成员 ID(通过 get_members 获取)',\n days: '查看最近多少天',\n },\n rowTemplate: '{day}:{msg_count} 条消息',\n summaryTemplate: '该成员近 {rowCount} 天有发言记录:',\n fallback: '指定时间范围内该成员无发言记录',\n },\n silent_members: {\n desc: '检测超过 N 天未发言的\"沉默成员\",适用于社群运营中识别流失风险用户。',\n params: { days: '多少天未发言算沉默' },\n rowTemplate: '{name} — 已沉默 {silent_days} 天',\n summaryTemplate: '共发现 {rowCount} 位沉默成员:',\n fallback: '没有找到超过指定天数未发言的成员,社群活跃度良好!',\n },\n reply_interaction_ranking: {\n desc: '分析群内回复互动排名——谁回复谁最多。适用于发现核心互动关系和意见领袖。',\n params: {\n days: '统计最近多少天的数据',\n limit: '返回前多少对互动关系',\n },\n rowTemplate: '{replier_name} → {original_name}:回复 {reply_count} 次',\n summaryTemplate: '回复互动 Top {rowCount}:',\n fallback: '该时间范围内没有回复互动记录',\n },\n mutual_interaction_pairs: {\n desc: '发现互动最频繁的成员组合,基于双向消息时间相邻度(一方发言后5分钟内另一方也发言则记一次互动)。适用于发现亲密好友组合。',\n params: {\n days: '统计最近多少天的数据',\n limit: '返回前多少对',\n },\n rowTemplate: '{member_a} ↔ {member_b}:{interaction_count} 次互动',\n summaryTemplate: '互动最频繁的 {rowCount} 对成员:',\n fallback: '该时间范围内没有明显的互动关系',\n },\n member_message_length_stats: {\n desc: '分析各成员的平均消息长度(仅统计文本消息),消息越长通常代表交流越深入。适用于发现深度交流者。',\n params: {\n days: '统计最近多少天的数据',\n top_n: '返回前多少名',\n },\n rowTemplate: '{name} — 平均 {avg_length} 字/条(共 {msg_count} 条,最长 {max_length} 字)',\n summaryTemplate: '消息长度 Top {rowCount}(越长越深入):',\n fallback: '该时间范围内没有足够的文本消息数据',\n },\n unanswered_messages: {\n desc: '查找近 N 天内未被回复的消息,这些可能是未解决的客户问题。仅统计文本消息且内容超过 10 字的(过滤简短寒暄)。',\n params: {\n days: '查找最近多少天的数据',\n limit: '最多返回多少条',\n },\n rowTemplate: '[{send_time}] {sender_name}:{content_preview}',\n summaryTemplate: '共发现 {rowCount} 条可能未被回复的消息:',\n fallback: '该时间范围内所有消息都已得到回复,服务质量很好!',\n },\n daily_active_members: {\n desc: '统计每日独立发言人数(DAU)和消息量,用于观察群活力变化趋势。适用于\"群活跃度趋势怎么样\"、\"最近有多少人在说话\"。',\n params: { days: '统计最近多少天的数据' },\n rowTemplate: '{day}:{active_members} 人活跃,{msg_count} 条消息',\n summaryTemplate: '近 {rowCount} 天的每日活跃人数趋势:',\n fallback: '该时间范围内没有消息记录',\n },\n conversation_initiator_stats: {\n desc: '统计每个成员发起段落(作为段落首条消息的发送者)的次数,找出谁最常开启话题。需要已生成段落索引。',\n params: {\n days: '统计最近多少天的数据',\n limit: '返回前多少名',\n },\n rowTemplate: '{name}:发起 {initiated_count} 次话题',\n summaryTemplate: '话题发起者 Top {rowCount}:',\n fallback: '该时间范围内没有段落记录,可能需要先生成段落索引',\n },\n activity_heatmap: {\n desc: '返回 星期×小时 的消息数矩阵,适合生成活跃度热力图。weekday: 0=周日, 1=周一, ..., 6=周六。',\n params: { days: '统计最近多少天的数据' },\n rowTemplate: '星期{weekday} {hour}:00 — {msg_count} 条',\n summaryTemplate: '活跃度热力图数据(共 {rowCount} 个时段有消息):',\n fallback: '该时间范围内没有消息记录',\n },\n response_time_analysis: {\n desc: '分析消息之间的响应时间,按成员维度统计中位数和平均回复速度。适用于\"大家平均多久回复消息\"、\"谁回复最快\"。',\n params: {\n days: '统计最近多少天的数据',\n top_n: '返回前多少名',\n },\n },\n keyword_frequency: {\n desc: '对指定时间段的文本消息进行分词,统计高频关键词排行。支持中英日文分词。适用于\"大家最常说什么\"、\"高频关键词是什么\"。',\n params: {\n days: '统计最近多少天的数据',\n top_n: '返回前多少个关键词',\n },\n },\n get_schema: {\n desc: '查看聊天数据库表结构。绘图或自定义 SQL 前应先用它确认表名和字段名。',\n params: {},\n },\n render_chart: {\n desc: '根据 ChartSpec v1 生成 ChatLab 原生图表。支持 bar、line、pie、heatmap。提供 `rows`(来自高层工具的数据数组,如 get_time_stats 的 `data` 字段)或 `sql`(只读 SELECT)二选一;有现成数据时优先用 `rows`,只有高层工具无法满足时才写 `sql`。禁止输出 HTML、JavaScript、SVG、ECharts option 或渲染代码。',\n params: {\n rows: '来自高层工具返回的数据数组(如 get_time_stats 的 `data` 字段)。有现成数据时优先使用,与 sql 二选一。',\n sql: '只读 SELECT 或 WITH SELECT SQL。仅在高层工具无法满足需求时使用,必须返回 ChartSpec encoding 中引用的字段。',\n params: 'SQL 命名参数对象;不需要参数时传空对象。',\n chartSpec: 'ChartSpec v1,包含 version、type、title、encoding。',\n maxRows: '图表查询最大行数,默认 1000。',\n },\n },\n },\n\n agent: {\n answerWithoutTools: '请根据已获取的信息给出回答,不要再调用工具。',\n toolError: '错误: {{error}}',\n currentDateIs: '当前日期是',\n chatContext: {\n private: '对话',\n group: '群聊',\n },\n ownerNote: `当前用户身份:\n- 用户在{{chatContext}}中的身份是「{{displayName}}」(platformId: {{platformId}})\n- 当用户提到\"我\"、\"我的\"时,指的就是「{{displayName}}」\n- 查询\"我\"的发言时,使用 sender_id 参数筛选该成员\n`,\n memberNotePrivate: `成员查询策略:\n- 私聊只有两个人,可以直接获取成员列表\n- 当用户提到\"对方\"、\"他/她\"时,通过 get_members 获取另一方信息\n`,\n memberNoteGroup: `成员查询策略:\n- 当用户提到特定群成员(如\"张三说过什么\"、\"小明的发言\"等)时,应先调用 get_members 获取成员列表\n- 群成员有三种名称:accountName(原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名)\n- 通过 get_members 的 search 参数可以模糊搜索这三种名称\n- 找到成员后,使用其 id 字段作为 search_messages 的 sender_id 参数来获取该成员的发言\n`,\n mentionedMembersNote: '本轮用户显式 @ 的成员(可直接使用 member_id,无需再次搜索):',\n timeParamsIntro: '时间参数:使用 start_time/end_time 指定时间范围,格式 \"YYYY-MM-DD HH:mm\"',\n defaultYearNote: '未指定时间范围时默认查询全部。当前年份为{{year}}年',\n dataSnapshotNote:\n '当前聊天数据库快照:{{name}}({{platform}}),总消息 {{totalMessages}} 条,成员 {{totalMembers}} 人,时间范围 {{firstMessageDate}} ~ {{lastMessageDate}}。该快照只用于判断数据范围;回答具体聊天事实仍必须调用工具检索当前数据库。',\n dataSnapshotContext: `当前聊天数据库启动上下文:\n- name: {{name}}\n- platform: {{platform}}\n- type: {{type}}\n- total_messages: {{totalMessages}}\n- total_members: {{totalMembers}}\n- first_message_ts: {{firstMessageTs}}\n- first_message_time: {{firstMessageTime}}\n- last_message_ts: {{lastMessageTs}}\n- last_message_time: {{lastMessageTime}} (数据库中已导入消息的截止时间,不代表群组/对话当前是否活跃)\n- segment_summaries_available: {{segmentSummaryCount}}\n\n{{memberHintTitle}}\n{{memberHintLines}}\n\n使用规则:\n{{usageRules}}`,\n dataSnapshotMemberHintsAll: '活跃成员查询提示(全部成员):',\n dataSnapshotMemberHintsTop: '活跃成员查询提示(按历史总消息量 Top 10):',\n dataSnapshotMemberHintsUnavailable: '活跃成员查询提示:',\n dataSnapshotMemberHintsEmpty: '无可用成员提示。',\n dataSnapshotUsageRules: `- member_id 是工具查询提示;display_name 仅用于人类识别,可能不唯一。\n- 不要在最终回答中主动暴露 member_id 或启动上下文本身,除非用户明确要求技术细节。\n- 活跃成员排行只代表历史总消息量,不代表最近活跃情况;也不足以作为影响力、关系或近期趋势结论的证据。\n- 相对时间表达以真实当前日期为基准,而不是数据库最后消息时间。\n- “最近一年/过去一年”表示从真实当前日期回推一年到今天;“去年”表示上一自然年。\n- 数据库时间边界只用于说明覆盖范围,不用于重定义用户要求的时间范围。\n- 使用默认“近 N 天”的工具时,先选择与数据库时间边界有交集的范围,不要先探测真实当前日期窗口导致空结果。\n- 不要只为了重新发现 min/max timestamp 调用工具;但回答具体聊天事实、统计和结论仍必须调用工具获取证据。\n- last_message_time 是数据库中已导入消息的截止时间,不是群组/对话在现实中最后一次发言的时间;用户可能只是还没有导入更新的记录。不要据此推断群组\"多久没动静\",更不要主动建议用户去\"唤醒\"或\"激活\"群组。`,\n evidencePolicy: `证据策略:\n- AI 对话历史、历史 AI 回复和压缩摘要只用于理解用户意图,不能作为聊天记录事实证据。\n- 只要用户询问聊天记录内容、最近聊什么、某人说过什么、统计排行、是否出现过某话题或要求引用原话,必须先调用合适的数据工具检索当前数据库。\n- 如果已追加新聊天记录,当前数据库可能不同于旧窗口历史;优先使用 get_chat_overview 或 get_recent_messages/search_messages 获取最新证据。\n- 只有工具返回的消息、统计或数据库概览可以作为事实依据;工具没有返回足够证据时,明确说明数据不足,不要编造不存在的聊天记录。`,\n currentTask: '当前任务',\n skillPriorityNote: '注意:在执行此任务时,请优先遵循以下任务的输出格式要求,这可以覆盖你的常规回复习惯。',\n responseInstruction: '根据用户的问题,选择合适的工具获取数据,然后基于数据给出回答。',\n fallbackRoleDefinition: {\n group: `你是一个专业但风格轻松的群聊记录分析助手。\n你的任务是帮助用户理解和分析他们的群聊记录数据,同时可以适度使用 B 站/网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。\n\n## 回答要求\n1. 基于工具返回的数据回答,不要编造信息\n2. 如果数据不足以回答问题,请说明\n3. 回答要简洁明了,使用 Markdown 格式\n4. 可以适度加入 B 站/网络热梗、表情/颜文字(强度适中)\n5. 玩梗不得影响事实准确与结论清晰,避免低俗或冒犯性表达`,\n private: `你是一个专业但风格轻松的私聊记录分析助手。\n你的任务是帮助用户理解和分析他们的私聊记录数据,同时可以适度使用 B 站/网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。\n\n## 回答要求\n1. 基于工具返回的数据回答,不要编造信息\n2. 如果数据不足以回答问题,请说明\n3. 回答要简洁明了,使用 Markdown 格式\n4. 可以适度加入 B 站/网络热梗、表情/颜文字(强度适中)\n5. 玩梗不得影响事实准确与结论清晰,避免低俗或冒犯性表达`,\n },\n },\n },\n\n llm: {\n notConfigured: 'LLM 服务未配置,请先在设置中配置 API Key',\n maxConfigs: '最多只能添加 {{count}} 个配置',\n configNotFound: '配置不存在',\n noActiveConfig: '没有激活的配置',\n callFailed: 'LLM 调用失败,请检查模型配置是否正确',\n genericProviderName: 'API 服务',\n rawErrorLabel: '原始错误',\n },\n\n summary: {\n segmentNotFound: '段落不存在或数据库打开失败',\n tooFewMessages: '消息数量少于{{count}}条,无需生成摘要',\n tooFewValidMessages: '有效消息数量少于{{count}}条,无需生成摘要',\n segmentNotExist: '段落不存在',\n messagesTooFew: '消息太少',\n validMessagesTooFew: '有效消息太少',\n systemPromptDirect: '你是一个对话摘要专家,擅长用简洁的语言总结对话内容。',\n systemPromptMerge: '你是一个对话摘要专家,擅长将多个摘要合并成一个连贯的总结。',\n },\n}\n","/**\n * AI shared translations — English\n */\nexport default {\n ai: {\n tools: {\n search_messages: {\n desc: 'Search chat records by keywords. This is the primary evidence tool for factual questions about keywords, member messages, or whether a topic appeared. Can specify time range and sender to filter messages. Supports minute-level time queries.',\n params: {\n keywords:\n 'List of search keywords, using OR logic to match messages containing any keyword. Pass an empty array [] to filter by sender only',\n sender_id:\n 'Sender member ID, used to filter messages from a specific member. Can be obtained via the get_members tool',\n limit: 'Message count limit, default 1000, max 50000',\n year: 'Filter messages by year, e.g. 2024',\n month: 'Filter messages by month (1-12), use with year',\n day: 'Filter messages by day (1-31), use with year and month',\n hour: 'Filter messages by hour (0-23), use with year, month, and day',\n start_time:\n 'Start time, format \"YYYY-MM-DD HH:mm\", e.g. \"2024-03-15 14:00\". Overrides year/month/day/hour when specified',\n end_time:\n 'End time, format \"YYYY-MM-DD HH:mm\", e.g. \"2024-03-15 18:30\". Overrides year/month/day/hour when specified',\n },\n },\n deep_search_messages: {\n desc: 'Exact substring match search for chat records. Slower but never misses any message containing the keyword. Use when search_messages results are insufficient, when verifying whether a phrase really exists, or when searching partial words or single characters.',\n params: {\n keywords: 'List of search keywords, using substring match (LIKE). Returns messages matching any keyword',\n sender_id: 'Sender member ID for filtering messages from a specific member',\n limit: 'Message count limit, default 1000, max 50000',\n year: 'Filter messages by year, e.g. 2024',\n month: 'Filter messages by month (1-12), use with year',\n day: 'Filter messages by day (1-31), use with year and month',\n hour: 'Filter messages by hour (0-23), use with year, month, and day',\n start_time: 'Start time, format \"YYYY-MM-DD HH:mm\". Overrides year/month/day/hour when specified',\n end_time: 'End time, format \"YYYY-MM-DD HH:mm\". Overrides year/month/day/hour when specified',\n },\n },\n get_recent_messages: {\n desc: 'Get chat messages within a specified time period. This is the preferred evidence tool for overview questions like \"what has everyone been chatting about recently\", \"what was discussed in month X\", and latest content after new records were appended. Supports minute-level time queries.',\n params: {\n limit: 'Message count limit, default 100 (saves tokens, can be increased as needed)',\n year: 'Filter messages by year, e.g. 2024',\n month: 'Filter messages by month (1-12), use with year',\n day: 'Filter messages by day (1-31), use with year and month',\n hour: 'Filter messages by hour (0-23), use with year, month, and day',\n start_time:\n 'Start time, format \"YYYY-MM-DD HH:mm\", e.g. \"2024-03-15 14:00\". Overrides year/month/day/hour when specified',\n end_time:\n 'End time, format \"YYYY-MM-DD HH:mm\", e.g. \"2024-03-15 18:30\". Overrides year/month/day/hour when specified',\n },\n },\n get_chat_overview: {\n desc: 'Get basic overview of the chat: name, platform, type, total messages, total members, time range, and top active members. Use this before analysis or after appended records to confirm the current database range.',\n params: {\n top_n: 'Return top N active members, default 10',\n },\n },\n get_member_stats: {\n desc: 'Get member activity statistics. Suitable for questions like \"who is the most active\" or \"who sends the most messages\".',\n params: {\n top_n: 'Return top N members, default 10',\n },\n },\n get_time_stats: {\n desc: 'Get time distribution statistics of chat activity. Suitable for questions like \"when is the group most active\" or \"what time do people usually chat\". The returned `data` array can be passed directly as the `rows` parameter of render_chart for visualization.',\n params: {\n type: 'Statistics type: hourly (by hour), weekday (by day of week), daily (by date)',\n },\n },\n get_members: {\n desc: 'Get group member list, including basic info, aliases, and message statistics. Suitable for queries like \"who is in the group\", \"what is someone\\'s alias\", or \"whose ID is xxx\".',\n params: {\n search: 'Optional search keyword to filter by member nickname, alias, or platform ID',\n limit: 'Member count limit, returns all by default',\n },\n },\n get_member_name_history: {\n desc: 'Get member name change history. Suitable for questions like \"what was someone\\'s previous name\", \"name changes\", or \"former names\". Requires member ID from get_members tool first.',\n params: {\n member_id: 'Member database ID, can be obtained via get_members tool',\n },\n },\n get_conversation_between: {\n desc: 'Get conversation records between two group members. Suitable for questions like \"what did A and B talk about\" or \"view the conversation between two people\". Requires member IDs from get_members first. Supports minute-level time queries.',\n params: {\n member_id_1: 'Database ID of the first member',\n member_id_2: 'Database ID of the second member',\n limit: 'Message count limit, default 100',\n start_time:\n 'Start time, format \"YYYY-MM-DD HH:mm\", e.g. \"2024-03-15 14:00\". Overrides year/month/day/hour when specified',\n end_time:\n 'End time, format \"YYYY-MM-DD HH:mm\", e.g. \"2024-03-15 18:30\". Overrides year/month/day/hour when specified',\n },\n },\n get_message_context: {\n desc: 'Get surrounding context messages for a given message ID. Use it to verify facts around specific quoted messages, such as what was discussed before and after a message or whether a phrase has context. Supports single or batch message IDs.',\n params: {\n message_ids:\n 'List of message IDs to query context for. Can be single or multiple IDs. Message IDs can be obtained from search_messages and other tool results',\n context_size: 'Context size, i.e. how many messages before and after to retrieve, default 20',\n },\n },\n get_segment_messages: {\n desc: 'Get the complete message list for a specific segment. Used to get the full original context after finding a relevant segment summary via get_segment_summaries. Returns all messages and participant information.',\n params: {\n segment_id: 'Segment ID, can be obtained from get_segment_summaries results',\n limit: 'Message count limit, default 1000. Can be limited for very long segments to save tokens',\n },\n },\n get_segment_summaries: {\n desc: `Get segment summary list to quickly understand discussion topics in chat history.\n\nUse cases:\n1. Understand what topics have been discussed recently\n2. Search for discussed topics by keyword\n3. Overview questions like \"has the group discussed travel\"\n\nReturned summaries are brief descriptions of each segment, helping quickly locate segments of interest. Use get_segment_messages for details.`,\n params: {\n keywords: 'Keyword list to search within summaries (OR logic)',\n limit: 'Session count limit, default 20',\n year: 'Filter segments by year',\n month: 'Filter segments by month (1-12)',\n day: 'Filter segments by day (1-31)',\n start_time: 'Start time, format \"YYYY-MM-DD HH:mm\"',\n end_time: 'End time, format \"YYYY-MM-DD HH:mm\"',\n },\n },\n // ===== SQL Analysis Tools =====\n message_type_breakdown: {\n desc: 'Break down message types over the last N days (text, image, voice, emoji, etc.). Useful for understanding communication preferences.',\n params: { days: 'Number of recent days to analyze' },\n rowTemplate: '{type_name}: {msg_count} messages ({percentage}%)',\n summaryTemplate: 'Message type distribution ({rowCount} types):',\n fallback: 'No messages found in this time range',\n },\n peak_chat_hours_by_member: {\n desc: \"Analyze a specific member's hourly message distribution over the last N days to find their most active hours. Requires member_id from get_members.\",\n params: {\n member_id: 'Member ID (from get_members)',\n days: 'Number of recent days to analyze',\n },\n rowTemplate: '{hour}:00 — {msg_count} messages',\n summaryTemplate: 'Message volume by hour ({rowCount} active hours):',\n fallback: 'This member has no messages in the specified time range',\n },\n member_activity_trend: {\n desc: \"View a specific member's daily message count trend over the last N days. Useful for observing whether someone is becoming more or less active. Requires member_id from get_members.\",\n params: {\n member_id: 'Member ID (from get_members)',\n days: 'Number of recent days to view',\n },\n rowTemplate: '{day}: {msg_count} messages',\n summaryTemplate: 'This member was active on {rowCount} days:',\n fallback: 'This member has no messages in the specified time range',\n },\n silent_members: {\n desc: 'Detect \"silent members\" who haven\\'t sent messages for more than N days. Useful for identifying at-risk users in community management.',\n params: { days: 'Days of silence to qualify' },\n rowTemplate: '{name} — silent for {silent_days} days',\n summaryTemplate: 'Found {rowCount} silent members:',\n fallback: 'No members found who have been silent for that long. Community engagement is healthy!',\n },\n reply_interaction_ranking: {\n desc: 'Analyze reply interaction rankings in the group — who replies to whom the most. Useful for discovering core interaction relationships and key opinion leaders.',\n params: {\n days: 'Number of recent days to analyze',\n limit: 'Number of top interaction pairs to return',\n },\n rowTemplate: '{replier_name} → {original_name}: {reply_count} replies',\n summaryTemplate: 'Top {rowCount} reply interactions:',\n fallback: 'No reply interactions found in this time range',\n },\n mutual_interaction_pairs: {\n desc: 'Find the most frequently interacting member pairs, based on bidirectional message timing (if one person speaks and another responds within 5 minutes, it counts as an interaction). Useful for discovering close friendships.',\n params: {\n days: 'Number of recent days to analyze',\n limit: 'Number of top pairs to return',\n },\n rowTemplate: '{member_a} ↔ {member_b}: {interaction_count} interactions',\n summaryTemplate: 'Top {rowCount} most interactive pairs:',\n fallback: 'No significant interaction patterns detected in this time range',\n },\n member_message_length_stats: {\n desc: 'Analyze average message length per member (text messages only). Longer messages often indicate more thoughtful communication. Useful for finding deep communicators.',\n params: {\n days: 'Number of recent days to analyze',\n top_n: 'Number of top members to return',\n },\n rowTemplate: '{name} — avg {avg_length} chars/msg ({msg_count} msgs, max {max_length} chars)',\n summaryTemplate: 'Message length Top {rowCount} (longer = more thoughtful):',\n fallback: 'Not enough text message data in this time range',\n },\n unanswered_messages: {\n desc: 'Find messages in the last N days that may not have been replied to — potential unresolved customer issues. Only counts text messages over 10 characters (filters out short greetings).',\n params: {\n days: 'Number of recent days to search',\n limit: 'Maximum number of results',\n },\n rowTemplate: '[{send_time}] {sender_name}: {content_preview}',\n summaryTemplate: 'Found {rowCount} potentially unanswered messages:',\n fallback: 'All messages have been replied to in this time range. Great service quality!',\n },\n daily_active_members: {\n desc: 'Count daily unique active members (DAU) and message volume to observe community vitality trends. Useful for \"how is the group activity trending\" or \"how many people are chatting recently\".',\n params: { days: 'Number of recent days to analyze' },\n rowTemplate: '{day}: {active_members} active, {msg_count} messages',\n summaryTemplate: 'Daily active members trend for {rowCount} days:',\n fallback: 'No messages in this time range',\n },\n conversation_initiator_stats: {\n desc: 'Count how many times each member initiated a conversation (was the first sender in a segment). Requires segment index to be generated.',\n params: {\n days: 'Number of recent days to analyze',\n limit: 'Number of top members to return',\n },\n rowTemplate: '{name}: initiated {initiated_count} topics',\n summaryTemplate: 'Topic initiator Top {rowCount}:',\n fallback: 'No segment records in this time range. Session index may need to be generated first.',\n },\n activity_heatmap: {\n desc: 'Return a weekday × hour message count matrix for generating activity heatmaps. weekday: 0=Sun, 1=Mon, ..., 6=Sat.',\n params: { days: 'Number of recent days to analyze' },\n rowTemplate: 'Weekday {weekday} {hour}:00 — {msg_count} messages',\n summaryTemplate: 'Activity heatmap data ({rowCount} time slots with messages):',\n fallback: 'No messages in this time range',\n },\n response_time_analysis: {\n desc: 'Analyze response times between messages, showing median and average reply speed per member. Useful for \"how quickly do people reply\" or \"who replies the fastest\".',\n params: {\n days: 'Number of recent days to analyze',\n top_n: 'Number of top members to return',\n },\n },\n keyword_frequency: {\n desc: 'Segment text messages and rank high-frequency keywords. Supports Chinese, English, and Japanese. Useful for \"what do people talk about most\" or \"what are the hot keywords\".',\n params: {\n days: 'Number of recent days to analyze',\n top_n: 'Number of top keywords to return',\n },\n },\n get_schema: {\n desc: 'Inspect the chat database schema. Use it before charting or custom SQL when table or field names are uncertain.',\n params: {},\n },\n render_chart: {\n desc: 'Generate a native ChatLab chart from ChartSpec v1. Supports bar, line, pie, and heatmap. Provide either `rows` (pre-fetched data array from a tool result, e.g. the `data` field from get_time_stats) or `sql` (read-only SELECT) — prefer `rows` when data is already available; use `sql` only when high-level tools cannot satisfy the need. Do not output HTML, JavaScript, SVG, ECharts options, or rendering code.',\n params: {\n rows: 'Pre-fetched data array from a high-level tool result (e.g. the `data` field from get_time_stats). Prefer this over sql when data is already available. Mutually exclusive with sql.',\n sql: 'Read-only SELECT or WITH SELECT SQL. Use only when high-level tools cannot satisfy the need. Must return fields referenced by ChartSpec encoding.',\n params: 'Named SQL parameters. Use an empty object when no parameters are needed.',\n chartSpec: 'ChartSpec v1 with version, type, title, and encoding.',\n maxRows: 'Maximum chart query rows. Default 1000.',\n },\n },\n },\n\n // ===== AI Agent system prompts =====\n agent: {\n answerWithoutTools: 'Please answer based on the information already retrieved, do not call any more tools.',\n toolError: 'Error: {{error}}',\n currentDateIs: 'Current date is',\n chatContext: {\n private: 'conversation',\n group: 'group chat',\n },\n ownerNote: `Current user identity:\n- The user's identity in this {{chatContext}} is \"{{displayName}}\" (platformId: {{platformId}})\n- When the user refers to \"I\" or \"my\", it refers to \"{{displayName}}\"\n- When querying \"my\" messages, use the sender_id parameter to filter for this member\n`,\n memberNotePrivate: `Member query strategy:\n- Private chats only have two participants, so the member list can be directly obtained\n- When the user refers to \"the other party\" or \"he/she\", get the other participant's information via get_members\n`,\n memberNoteGroup: `Member query strategy:\n- When the user refers to specific group members (e.g., \"what did John say\", \"Mary's messages\"), first call get_members to get the member list\n- Group members have three names: accountName (original nickname), groupNickname (group nickname), aliases (user-defined aliases)\n- The search parameter of get_members can be used for fuzzy searching these three names\n- Once a member is found, use their id field as the sender_id parameter for search_messages to retrieve their messages\n`,\n mentionedMembersNote:\n 'Members explicitly @-selected by the user in this round (member_id can be used directly without another search):',\n timeParamsIntro: 'Time parameters: use start_time/end_time to specify the range, format \"YYYY-MM-DD HH:mm\"',\n defaultYearNote: 'When no time range is specified, queries default to all data. Current year is {{year}}.',\n dataSnapshotNote:\n 'Current chat database snapshot: {{name}} ({{platform}}), {{totalMessages}} total messages, {{totalMembers}} members, time range {{firstMessageDate}} ~ {{lastMessageDate}}. This snapshot only helps judge data coverage; concrete chat facts still require tool retrieval from the current database.',\n dataSnapshotContext: `Current chat database startup context:\n- name: {{name}}\n- platform: {{platform}}\n- type: {{type}}\n- total_messages: {{totalMessages}}\n- total_members: {{totalMembers}}\n- first_message_ts: {{firstMessageTs}}\n- first_message_time: {{firstMessageTime}}\n- last_message_ts: {{lastMessageTs}}\n- last_message_time: {{lastMessageTime}} (coverage end of imported data — does not indicate whether the group/chat is currently active)\n- segment_summaries_available: {{segmentSummaryCount}}\n\n{{memberHintTitle}}\n{{memberHintLines}}\n\nUsage rules:\n{{usageRules}}`,\n dataSnapshotMemberHintsAll: 'Active member lookup hints (all members):',\n dataSnapshotMemberHintsTop: 'Active member lookup hints (top 10 by historical total messages):',\n dataSnapshotMemberHintsUnavailable: 'Active member lookup hints:',\n dataSnapshotMemberHintsEmpty: 'No member hints available.',\n dataSnapshotUsageRules: `- member_id is a tool lookup hint; display_name is only for human recognition and may not be unique.\n- Do not proactively expose member_id or the startup context in the final answer unless the user explicitly asks for technical details.\n- Active member ranking only reflects historical total message volume, not recent activity; it is also not evidence for influence, relationships, or recent trends.\n- Interpret relative time expressions using the real current date, not the database's last message time.\n- \"recent year\" / \"past year\" means one year back from the real current date to today; \"last year\" means the previous calendar year.\n- Database time bounds are only for explaining coverage, not for redefining the user's requested time range.\n- When using default recent-day tools, first choose a range that intersects database time bounds instead of probing an empty real-current-date window.\n- Do not call tools only to rediscover min/max timestamp; concrete chat facts, statistics, and conclusions still require tool evidence.\n- last_message_time is the coverage end of the imported data, not the real-world last message time of the group/chat; the user may simply not have imported newer records yet. Do not infer that the group has been inactive for a certain period, and do not suggest the user \"revive\" or \"wake up\" the group.`,\n evidencePolicy: `Evidence policy:\n- AI conversation history, prior AI replies, and compressed summaries are only for understanding the user's intent; they are not evidence for chat-record facts.\n- Whenever the user asks about chat content, recent topics, what someone said, rankings/statistics, whether a topic appeared, or asks for exact quotes, first call an appropriate data tool to retrieve the current database.\n- If new chat records were appended, the current database may differ from old window history; prefer get_chat_overview or get_recent_messages/search_messages to get fresh evidence.\n- Only messages, statistics, or database overviews returned by tools may be used as factual basis. If tools do not return enough evidence, say the data is insufficient and do not fabricate nonexistent chat records.`,\n currentTask: 'Current Task',\n skillPriorityNote:\n 'Note: When executing this task, prioritize the output format requirements below. This can override your usual response style.',\n responseInstruction:\n \"Based on the user's question, select appropriate tools to retrieve data, then provide an answer based on the data.\",\n fallbackRoleDefinition: {\n group: `You are a professional group chat analysis assistant.\nYour task is to help users understand and analyze their group chat data.\n\n## Response Requirements\n1. Answer based on data returned by tools, do not fabricate information\n2. If data is insufficient to answer, please state so\n3. Keep answers concise and clear, use Markdown format`,\n private: `You are a professional private chat analysis assistant.\nYour task is to help users understand and analyze their private chat data.\n\n## Response Requirements\n1. Answer based on data returned by tools, do not fabricate information\n2. If data is insufficient to answer, please state so\n3. Keep answers concise and clear, use Markdown format`,\n },\n },\n },\n\n llm: {\n notConfigured: 'LLM service not configured. Please set up an API Key in settings first.',\n maxConfigs: 'Maximum of {{count}} configurations allowed',\n configNotFound: 'Configuration not found',\n noActiveConfig: 'No active configuration',\n callFailed: 'LLM call failed. Please check your model configuration.',\n genericProviderName: 'API provider',\n rawErrorLabel: 'Raw error',\n },\n\n summary: {\n segmentNotFound: 'Session not found or database could not be opened',\n tooFewMessages: 'Message count less than {{count}}, no need to generate summary',\n tooFewValidMessages: 'Valid message count less than {{count}}, no need to generate summary',\n segmentNotExist: 'Session not found',\n messagesTooFew: 'Too few messages',\n validMessagesTooFew: 'Too few valid messages',\n systemPromptDirect: 'You are a conversation summarization expert. Summarize conversations concisely.',\n systemPromptMerge:\n 'You are a conversation summarization expert skilled at merging multiple summaries into a coherent overview.',\n },\n}\n","/**\n * AI shared translations — Japanese\n */\nexport default {\n ai: {\n tools: {\n search_messages: {\n desc: 'キーワードでチャット履歴を検索する。キーワード、メンバーの発言、特定トピックが出たかどうかなどの事実質問に対する主要な証拠取得ツール。時間範囲や送信者でメッセージをフィルタリングできる。分単位の精度で時間クエリをサポートする。',\n params: {\n keywords:\n '検索キーワードリスト。OR ロジックでいずれかのキーワードを含むメッセージにマッチする。送信者のみでフィルタリングする場合は空配列 [] を渡す',\n sender_id:\n '送信者のメンバー ID。特定メンバーの送信メッセージをフィルタリングする。get_members ツールでメンバー ID を取得できる',\n limit: '返却メッセージ数の上限。デフォルト 1000、最大 50000',\n year: '指定年のメッセージをフィルタリング(例:2024)',\n month: '指定月のメッセージをフィルタリング(1-12)。year と併用する必要がある',\n day: '指定日のメッセージをフィルタリング(1-31)。year と month と併用する必要がある',\n hour: '指定時間のメッセージをフィルタリング(0-23)。year、month、day と併用する必要がある',\n start_time:\n '開始時刻。形式 \"YYYY-MM-DD HH:mm\"(例:\"2024-03-15 14:00\")。指定すると year/month/day/hour パラメータを上書きする',\n end_time:\n '終了時刻。形式 \"YYYY-MM-DD HH:mm\"(例:\"2024-03-15 18:30\")。指定すると year/month/day/hour パラメータを上書きする',\n },\n },\n deep_search_messages: {\n desc: '完全部分文字列マッチでチャット履歴を検索する。速度は遅いが、キーワードを含むメッセージを漏らさない。search_messages の結果が不十分な場合、ある文言が実在するか確認する場合、または部分的な単語・単一文字を検索する場合に使用する。',\n params: {\n keywords:\n '検索キーワードリスト。部分文字列マッチ(LIKE)を使用し、いずれかのキーワードにマッチしたメッセージを返す',\n sender_id: '送信者のメンバー ID。特定メンバーの送信メッセージをフィルタリングする',\n limit: '返却メッセージ数の上限。デフォルト 1000、最大 50000',\n year: '指定年のメッセージをフィルタリング(例:2024)',\n month: '指定月のメッセージをフィルタリング(1-12)。year と併用する必要がある',\n day: '指定日のメッセージをフィルタリング(1-31)。year と month と併用する必要がある',\n hour: '指定時間のメッセージをフィルタリング(0-23)。year、month、day と併用する必要がある',\n start_time: '開始時刻。形式 \"YYYY-MM-DD HH:mm\"。指定すると year/month/day/hour パラメータを上書きする',\n end_time: '終了時刻。形式 \"YYYY-MM-DD HH:mm\"。指定すると year/month/day/hour パラメータを上書きする',\n },\n },\n get_recent_messages: {\n desc: '指定期間内のチャットメッセージを取得する。「最近みんな何を話していた?」「X月に何が話題だった?」、新しい記録追加後の最新内容などの概要質問に対する優先証拠ツール。分単位の精度で時間クエリをサポートする。',\n params: {\n limit: '返却メッセージ数の上限。デフォルト 100(Token を節約したい場合の目安。必要に応じて増やせる)',\n year: '指定年のメッセージをフィルタリング(例:2024)',\n month: '指定月のメッセージをフィルタリング(1-12)。year と併用する必要がある',\n day: '指定日のメッセージをフィルタリング(1-31)。year と month と併用する必要がある',\n hour: '指定時間のメッセージをフィルタリング(0-23)。year、month、day と併用する必要がある',\n start_time:\n '開始時刻。形式 \"YYYY-MM-DD HH:mm\"(例:\"2024-03-15 14:00\")。指定すると year/month/day/hour パラメータを上書きする',\n end_time:\n '終了時刻。形式 \"YYYY-MM-DD HH:mm\"(例:\"2024-03-15 18:30\")。指定すると year/month/day/hour パラメータを上書きする',\n },\n },\n get_chat_overview: {\n desc: 'チャット記録の基本概要を取得する:グループ名、プラットフォーム、タイプ、総メッセージ数、総メンバー数、期間、最もアクティブなメンバーランキング。分析前や新しい記録追加後に、現在のデータベース範囲を確認するのに適している。',\n params: {\n top_n: '上位 N 名のアクティブメンバーを返却。デフォルト 10',\n },\n },\n get_member_stats: {\n desc: 'グループメンバーのアクティビティ統計データを取得する。「最もアクティブなのは誰?」「発言数が一番多いのは?」などの質問に適している。',\n params: {\n top_n: '上位 N 名のメンバーを返却。デフォルト 10',\n },\n },\n get_time_stats: {\n desc: 'グループチャットの時間分布統計を取得する。「いつが一番アクティブ?」「みんな何時にチャットしている?」などの質問に適している。返される `data` 配列は render_chart の `rows` パラメータとして直接使用できる。',\n params: {\n type: '統計タイプ:hourly(時間別)、weekday(曜日別)、daily(日別)',\n },\n },\n get_members: {\n desc: 'グループメンバー一覧を取得する。メンバーの基本情報、別名、メッセージ統計を含む。「グループに誰がいる?」「○○の別名は?」「QQ 番号が xxx の人は?」などの質問に向いている。',\n params: {\n search: '任意の検索キーワード。メンバーのニックネーム、別名、QQ 番号で絞り込む',\n limit: '返却メンバー数の上限。デフォルトは全件返却',\n },\n },\n get_member_name_history: {\n desc: 'メンバーのニックネーム変更履歴を取得する。「○○の以前の名前は?」「○○のニックネームの変遷」「○○の旧名」などの質問に適している。事前に get_members ツールでメンバー ID を取得する必要がある。',\n params: {\n member_id: 'メンバーのデータベース ID。get_members ツールで取得できる',\n },\n },\n get_conversation_between: {\n desc: '2 人のグループメンバー間の会話履歴を取得する。「A と B は何を話していた?」「2 人のやり取りを見たい」などの質問に向いている。事前に get_members でメンバー ID を取得する必要がある。分単位の時間指定にも対応する。',\n params: {\n member_id_1: '1人目のメンバーのデータベース ID',\n member_id_2: '2人目のメンバーのデータベース ID',\n limit: '返却メッセージ数の上限。デフォルト 100',\n start_time:\n '開始時刻。形式 \"YYYY-MM-DD HH:mm\"(例:\"2024-03-15 14:00\")。指定すると year/month/day/hour パラメータを上書きする',\n end_time:\n '終了時刻。形式 \"YYYY-MM-DD HH:mm\"(例:\"2024-03-15 18:30\")。指定すると year/month/day/hour パラメータを上書きする',\n },\n },\n get_message_context: {\n desc: 'メッセージ ID に基づいて前後のコンテキストメッセージを取得する。具体的な引用メッセージ周辺の事実確認、例えば「このメッセージの前後で何を話していた?」「この文言には文脈があるか」を確認する場合に使用する。単一または複数のメッセージ ID をサポートする。',\n params: {\n message_ids:\n 'コンテキストを取得するメッセージ ID リスト。単一 ID または複数 ID が可能。メッセージ ID は search_messages などのツールの返却結果から取得できる',\n context_size: 'コンテキストサイズ。前後それぞれ何件のメッセージを取得するか。デフォルト 20',\n },\n },\n get_segment_messages: {\n desc: '指定セグメントの完全なメッセージリストを取得する。get_segment_summaries で関連セグメントの要約を見つけた後、そのセグメントの完全な原文コンテキストを取得するために使用する。セグメントの全メッセージと参加者情報を返却する。',\n params: {\n segment_id: 'セグメント ID。get_segment_summaries の返却結果から取得できる',\n limit: '返却メッセージ数の上限。デフォルト 1000。非常に長いセグメントでは Token 節約のため件数を制限できる',\n },\n },\n get_segment_summaries: {\n desc: `セグメント要約リストを取得し、グループチャットの過去の議論トピックをすばやく把握する。\n\n適用シーン:\n1. グループで最近何が話題になっていたか知りたい\n2. キーワードで過去に議論されたトピックを検索\n3. 「グループで旅行について話したことある?」などの概要的な質問\n\n返却される要約は各セグメントの短い概要であり、興味のあるセグメントをすばやく特定し、get_segment_messages で詳細を取得できる。`,\n params: {\n keywords: '要約内で検索するキーワードリスト(OR ロジックマッチ)',\n limit: '返却セグメント数の上限。デフォルト 20',\n year: '指定年のセグメントをフィルタリング',\n month: '指定月のセグメントをフィルタリング(1-12)',\n day: '指定日のセグメントをフィルタリング(1-31)',\n start_time: '開始時刻。形式 \"YYYY-MM-DD HH:mm\"',\n end_time: '終了時刻。形式 \"YYYY-MM-DD HH:mm\"',\n },\n },\n message_type_breakdown: {\n desc: 'メッセージタイプ別に直近 N 日間のメッセージ分布を集計する(テキスト、画像、音声、スタンプなどの件数)。コミュニケーション方法の傾向を把握するのに適している。',\n params: { days: '直近何日間のデータを集計するか' },\n rowTemplate: '{type_name}:{msg_count} 件({percentage}%)',\n summaryTemplate: 'メッセージタイプ分布(全 {rowCount} 種類):',\n fallback: 'この期間にメッセージ記録がありません',\n },\n peak_chat_hours_by_member: {\n desc: '指定メンバーの直近 N 日間の時間帯別発言数分布を分析し、最もアクティブな時間帯を特定する。事前に get_members で member_id を取得する必要がある。',\n params: {\n member_id: 'メンバー ID(get_members で取得)',\n days: '直近何日間のデータを集計するか',\n },\n rowTemplate: '{hour}:00 — {msg_count} 件のメッセージ',\n summaryTemplate: '該当メンバーの時間帯別発言数(全 {rowCount} アクティブ時間帯):',\n fallback: '指定期間内に該当メンバーの発言記録がありません',\n },\n member_activity_trend: {\n desc: '指定メンバーの直近 N 日間の日別発言数の変化トレンドを表示する。ある人物がよりアクティブになったか、より静かになったかを観察するのに適している。事前に get_members で member_id を取得する必要がある。',\n params: {\n member_id: 'メンバー ID(get_members で取得)',\n days: '直近何日間のトレンドを表示するか',\n },\n rowTemplate: '{day}:{msg_count} 件',\n summaryTemplate: '該当メンバーの直近 {rowCount} 日間の発言記録:',\n fallback: '指定期間内に該当メンバーの発言記録がありません',\n },\n silent_members: {\n desc: 'N 日以上発言していない「休眠メンバー」を検出する。コミュニティ運営で離脱リスクのある利用者を見つけるのに向いている。',\n params: { days: '何日間未発言でサイレントとみなすか' },\n rowTemplate: '{name} — {silent_days} 日間サイレント',\n summaryTemplate: '全 {rowCount} 名の休眠メンバーを検出:',\n fallback: '指定日数を超えて未発言のメンバーは見つかりませんでした。コミュニティのアクティビティは良好です!',\n },\n reply_interaction_ranking: {\n desc: 'グループ内の返信インタラクションランキングを分析し、誰が誰に最も多く返信しているかを特定する。コミュニティのコアインタラクション関係やオピニオンリーダーの発見に適している。',\n params: {\n days: '直近何日間のデータを集計するか',\n limit: '上位何組のインタラクション関係を返却するか',\n },\n rowTemplate: '{replier_name} → {original_name}:{reply_count} 回返信',\n summaryTemplate: '返信インタラクション Top {rowCount}:',\n fallback: 'この期間に返信インタラクション記録がありません',\n },\n mutual_interaction_pairs: {\n desc: '最も頻繁にインタラクションするメンバーペアを特定する。双方向のメッセージ時間近接度に基づく(一方の発言後5分以内にもう一方も発言した場合を1回のインタラクションとみなす)。親密な友人の組み合わせの発見に適している。',\n params: {\n days: '直近何日間のデータを集計するか',\n limit: '上位何組を返却するか',\n },\n rowTemplate: '{member_a} ↔ {member_b}:{interaction_count} 回のインタラクション',\n summaryTemplate: '最も頻繁にインタラクションする {rowCount} 組のペア:',\n fallback: 'この期間に明らかなインタラクション関係は検出されませんでした',\n },\n member_message_length_stats: {\n desc: '各メンバーの平均メッセージ長(テキストメッセージのみ)を集計する。長いメッセージは通常、より丁寧なコミュニケーションを意味する。深い交流を行う人物の発見に適している。',\n params: {\n days: '直近何日間のデータを集計するか',\n top_n: '上位何名を返却するか',\n },\n rowTemplate: '{name} — 平均 {avg_length} 字/件(全 {msg_count} 件、最長 {max_length} 字)',\n summaryTemplate: 'メッセージ長 Top {rowCount}(長い = より丁寧):',\n fallback: 'この期間に十分なテキストメッセージデータがありません',\n },\n unanswered_messages: {\n desc: '直近 N 日間で返信されていないメッセージを検索する。未解決の問題である可能性がある。テキストメッセージかつ10文字以上のもののみ集計する(短い挨拶を除外)。',\n params: {\n days: '直近何日間のデータを検索するか',\n limit: '最大何件返却するか',\n },\n rowTemplate: '[{send_time}] {sender_name}:{content_preview}',\n summaryTemplate: '全 {rowCount} 件の返信されていない可能性のあるメッセージ:',\n fallback: 'この期間のすべてのメッセージに返信がありました。対応品質は良好です!',\n },\n daily_active_members: {\n desc: '日ごとのユニーク発言者数(DAU)とメッセージ数を集計し、グループの活性度の推移を観察する。「最近グループはどのくらい活発か」「何人が発言しているか」に適している。',\n params: { days: '直近何日間のデータを集計するか' },\n rowTemplate: '{day}:{active_members} 人アクティブ、{msg_count} 件メッセージ',\n summaryTemplate: '直近 {rowCount} 日間の日別アクティブ人数推移:',\n fallback: 'この期間にメッセージの記録がありません',\n },\n conversation_initiator_stats: {\n desc: '各メンバーが会話を開始した回数(セグメントの最初の発言者)を集計し、誰が最も話題を切り出すかを発見する。セグメントインデックスの生成が必要。',\n params: {\n days: '直近何日間のデータを集計するか',\n limit: '上位何名を返却するか',\n },\n rowTemplate: '{name}:{initiated_count} 回話題を開始',\n summaryTemplate: '話題開始者 Top {rowCount}:',\n fallback: 'この期間にセグメント記録がありません。先にセグメントインデックスを生成する必要があるかもしれません',\n },\n activity_heatmap: {\n desc: '曜日×時間帯のメッセージ数マトリックスを返却する。活性度ヒートマップの生成に適している。weekday: 0=日曜, 1=月曜, ..., 6=土曜。',\n params: { days: '直近何日間のデータを集計するか' },\n rowTemplate: '曜日{weekday} {hour}:00 — {msg_count} 件',\n summaryTemplate: '活性度ヒートマップデータ(全 {rowCount} 時間帯にメッセージあり):',\n fallback: 'この期間にメッセージの記録がありません',\n },\n response_time_analysis: {\n desc: 'メッセージ間の応答時間を分析し、メンバーごとの中央値と平均返信速度を集計する。「みんなどのくらいで返信するか」「誰が最も速く返信するか」に適している。',\n params: {\n days: '直近何日間のデータを集計するか',\n top_n: '上位何名を返却するか',\n },\n },\n keyword_frequency: {\n desc: '指定期間のテキストメッセージを分詞し、高頻度キーワードをランキングする。中国語・英語・日本語の分詞に対応。「みんなが最もよく話す話題は何か」「高頻度キーワードは何か」に適している。',\n params: {\n days: '直近何日間のデータを集計するか',\n top_n: '上位何個のキーワードを返却するか',\n },\n },\n get_schema: {\n desc: 'チャットデータベースのスキーマを確認します。チャート作成やカスタム SQL の前にテーブル名とフィールド名を確認してください。',\n params: {},\n },\n render_chart: {\n desc: 'ChartSpec v1 から ChatLab ネイティブチャートを生成します。bar、line、pie、heatmap をサポートします。`rows`(高レベルツールのデータ配列、例:get_time_stats の `data` フィールド)または `sql`(読み取り専用 SELECT)のいずれかを指定します。データがすでに取得済みの場合は `rows` を優先し、高レベルツールで対応できない場合のみ `sql` を使用します。HTML、JavaScript、SVG、ECharts option、描画コードは出力しないでください。',\n params: {\n rows: '高レベルツールから取得したデータ配列(例:get_time_stats の `data` フィールド)。データが取得済みの場合は sql より優先して使用します。sql とは排他。',\n sql: '読み取り専用の SELECT または WITH SELECT SQL。高レベルツールで対応できない場合のみ使用します。ChartSpec encoding で参照するフィールドを返す必要があります。',\n params: 'SQL の名前付きパラメータ。不要な場合は空オブジェクトを渡します。',\n chartSpec: 'version、type、title、encoding を含む ChartSpec v1。',\n maxRows: 'チャートクエリの最大行数。デフォルトは 1000。',\n },\n },\n },\n\n agent: {\n answerWithoutTools: '取得済みの情報に基づいて回答してください。これ以上ツールを呼び出さないでください。',\n toolError: 'エラー: {{error}}',\n currentDateIs: '現在の日付は',\n chatContext: {\n private: '会話',\n group: 'グループチャット',\n },\n ownerNote: `現在のユーザー情報:\n- ユーザーの{{chatContext}}における立場は「{{displayName}}」(platformId: {{platformId}})\n- ユーザーが「私」「自分の」と言った場合、「{{displayName}}」を指す\n- 「私」の発言を検索する際は、sender_id パラメータで該当メンバーをフィルタリングする\n`,\n memberNotePrivate: `メンバー検索戦略:\n- 個人チャットは2人だけなので、直接メンバー一覧を取得できる\n- ユーザーが「相手」「彼/彼女」と言った場合、get_members でもう一方の情報を取得する\n`,\n memberNoteGroup: `メンバー検索戦略:\n- ユーザーが特定のグループメンバーに言及した場合(例:「田中さんは何を言った?」「太郎の発言」など)、まず get_members でメンバー一覧を取得する\n- グループメンバーには3種類の名前がある:accountName(元のニックネーム)、groupNickname(グループニックネーム)、aliases(ユーザー定義の別名)\n- get_members の search パラメータでこれら3種類の名前をあいまい検索できる\n- メンバーを見つけたら、その id フィールドを search_messages の sender_id パラメータとして使用して発言を取得する\n`,\n mentionedMembersNote:\n 'このラウンドでユーザーが明示的に @ 選択したメンバー(member_id を再検索なしで直接使えます):',\n timeParamsIntro: '時間パラメータ:start_time/end_time で範囲を指定。形式は \"YYYY-MM-DD HH:mm\"',\n defaultYearNote: '時間範囲を指定しない場合は全期間が対象。現在の年は{{year}}年',\n dataSnapshotNote:\n '現在のチャットデータベースのスナップショット:{{name}}({{platform}})、総メッセージ {{totalMessages}} 件、メンバー {{totalMembers}} 人、期間 {{firstMessageDate}} ~ {{lastMessageDate}}。このスナップショットはデータ範囲の判断にのみ使い、具体的なチャット事実の回答には必ず現在のデータベースをツールで検索する。',\n dataSnapshotContext: `現在のチャットデータベース起動コンテキスト:\n- name: {{name}}\n- platform: {{platform}}\n- type: {{type}}\n- total_messages: {{totalMessages}}\n- total_members: {{totalMembers}}\n- first_message_ts: {{firstMessageTs}}\n- first_message_time: {{firstMessageTime}}\n- last_message_ts: {{lastMessageTs}}\n- last_message_time: {{lastMessageTime}} (インポート済みデータのカバレッジ終端時刻。グループ/会話が現在もアクティブかどうかは示しません)\n- segment_summaries_available: {{segmentSummaryCount}}\n\n{{memberHintTitle}}\n{{memberHintLines}}\n\n使用ルール:\n{{usageRules}}`,\n dataSnapshotMemberHintsAll: 'アクティブメンバー検索ヒント(全メンバー):',\n dataSnapshotMemberHintsTop: 'アクティブメンバー検索ヒント(過去の総メッセージ数 Top 10):',\n dataSnapshotMemberHintsUnavailable: 'アクティブメンバー検索ヒント:',\n dataSnapshotMemberHintsEmpty: '利用可能なメンバーヒントはありません。',\n dataSnapshotUsageRules: `- member_id はツール検索用のヒントです。display_name は人間が識別するためだけのもので、一意とは限りません。\n- ユーザーが技術的詳細を明示的に求めない限り、最終回答で member_id や起動コンテキスト自体を積極的に開示しないでください。\n- アクティブメンバー順位は過去の総メッセージ量だけを示し、最近の活発さを示すものではありません。また、影響力、関係性、最近の傾向の結論証拠にもなりません。\n- 相対的な時間表現は、データベースの最終メッセージ時刻ではなく、実際の現在日付を基準に解釈してください。\n- 「最近一年/過去一年」は実際の現在日付から今日までの1年間を指し、「去年」は前の暦年を指します。\n- データベースの時間境界はカバレッジ説明にのみ使い、ユーザーが求めた時間範囲を再定義するために使わないでください。\n- デフォルトの「直近 N 日」ツールを使う場合は、空の実際現在日付ウィンドウを試すのではなく、データベース時間境界と交差する範囲を先に選んでください。\n- min/max timestamp を再発見するためだけにツールを呼ばないでください。ただし、具体的なチャット事実、統計、結論には引き続きツール証拠が必要です。\n- last_message_time はインポート済みデータのカバレッジ終端時刻であり、グループ/会話の現実世界における最終発言時刻ではありません。ユーザーがまだ新しい記録をインポートしていないだけかもしれません。この時刻からグループが「どれだけ活動していない」と推測したり、グループを「活性化」するよう提案したりしないでください。`,\n evidencePolicy: `証拠ポリシー:\n- AI 会話履歴、過去の AI 回答、圧縮要約はユーザー意図の理解にのみ使用し、チャット記録の事実証拠として扱わない。\n- ユーザーがチャット内容、最近の話題、誰かの発言、ランキング/統計、ある話題が出たかどうか、または原文引用を求めた場合は、必ず先に適切なデータツールで現在のデータベースを検索する。\n- 新しいチャット記録が追加されている場合、現在のデータベースは古いウィンドウ履歴と異なる可能性がある。最新の証拠取得には get_chat_overview または get_recent_messages/search_messages を優先する。\n- 事実根拠として使えるのは、ツールが返したメッセージ、統計、またはデータベース概要のみ。ツールが十分な証拠を返さない場合は、データ不足と明示し、存在しないチャット記録を作らない。`,\n responseInstruction: 'ユーザーの質問に応じて適切なツールを選択してデータを取得し、データに基づいて回答する。',\n fallbackRoleDefinition: {\n group: `あなたはプロフェッショナルだがカジュアルなスタイルのグループチャット履歴分析アシスタントです。\nユーザーのグループチャット履歴データの理解と分析を支援し、適度にネットスラングや顔文字を使って雰囲気を和らげますが、結論の正確性に影響しないようにします。\n\n## 回答要件\n1. ツールから返却されたデータに基づいて回答し、情報を捏造しない\n2. データが不十分で質問に答えられない場合は、その旨を説明する\n3. 回答は簡潔明瞭に、Markdown 形式を使用する\n4. 適度にネットスラングや顔文字を加えてよい(強度は控えめに)\n5. ネタは事実の正確さと結論の明確さに影響を与えてはならず、低俗または不快な表現は避ける`,\n private: `あなたはプロフェッショナルだがカジュアルなスタイルの個人チャット履歴分析アシスタントです。\nユーザーの個人チャット履歴データの理解と分析を支援し、適度にネットスラングや顔文字を使って雰囲気を和らげますが、結論の正確性に影響しないようにします。\n\n## 回答要件\n1. ツールから返却されたデータに基づいて回答し、情報を捏造しない\n2. データが不十分で質問に答えられない場合は、その旨を説明する\n3. 回答は簡潔明瞭に、Markdown 形式を使用する\n4. 適度にネットスラングや顔文字を加えてよい(強度は控えめに)\n5. ネタは事実の正確さと結論の明確さに影響を与えてはならず、低俗または不快な表現は避ける`,\n },\n },\n },\n\n llm: {\n notConfigured: 'LLM サービスが未設定です。先に設定で API Key を設定してください',\n maxConfigs: '設定は最大 {{count}} 個まで追加できます',\n configNotFound: '設定が存在しません',\n noActiveConfig: 'アクティブな設定がありません',\n callFailed: 'LLM 呼び出しに失敗しました。モデル設定を確認してください。',\n genericProviderName: 'API プロバイダー',\n rawErrorLabel: '元のエラー',\n },\n\n summary: {\n segmentNotFound: 'セグメントが存在しないか、データベースを開けませんでした',\n tooFewMessages: 'メッセージ数が{{count}}件未満のため、要約の生成は不要です',\n tooFewValidMessages: '有効なメッセージ数が{{count}}件未満のため、要約の生成は不要です',\n segmentNotExist: 'セグメントが存在しません',\n messagesTooFew: 'メッセージが少なすぎます',\n validMessagesTooFew: '有効なメッセージが少なすぎます',\n systemPromptDirect: 'あなたは会話要約の専門家であり、簡潔な言葉で会話内容を要約することに長けている。',\n systemPromptMerge: 'あなたは会話要約の専門家であり、複数の要約を一つのまとまった概要に統合することに長けている。',\n },\n}\n","/**\n * AI shared translations — Traditional Chinese\n */\nexport default {\n ai: {\n tools: {\n search_messages: {\n desc: '根據關鍵詞搜尋聊天紀錄,是回答關鍵詞、成員發言、是否出現過某話題等事實問題的主要證據工具。可以指定時間範圍和傳送者來篩選訊息。支援精確到分鐘級別的時間查詢。',\n params: {\n keywords: '搜尋關鍵詞清單,會用 OR 邏輯匹配包含任一關鍵詞的訊息。如果只需要按傳送者篩選,可以傳空陣列 []',\n sender_id: '傳送者的成員 ID,用於篩選特定成員傳送的訊息。可以透過 get_members 工具取得成員 ID',\n limit: '回傳訊息數量限制,預設 1000,最大 50000',\n year: '篩選指定年份的訊息,如 2024',\n month: '篩選指定月份的訊息(1-12),需要配合 year 使用',\n day: '篩選指定日期的訊息(1-31),需要配合 year 和 month 使用',\n hour: '篩選指定小時的訊息(0-23),需要配合 year、month 和 day 使用',\n start_time: '開始時間,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"。指定後會覆蓋 year/month/day/hour 參數',\n end_time: '結束時間,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"。指定後會覆蓋 year/month/day/hour 參數',\n },\n },\n deep_search_messages: {\n desc: '精確子串匹配搜尋聊天紀錄,速度較慢但不會遺漏任何包含關鍵詞的訊息。當 search_messages 結果不足、需要驗證某段話是否真實存在、或搜尋部分詞/單個字元時使用。',\n params: {\n keywords: '搜尋關鍵詞清單,使用子串匹配(LIKE),任一關鍵詞匹配即回傳',\n sender_id: '傳送者的成員 ID,用於篩選特定成員傳送的訊息',\n limit: '回傳訊息數量限制,預設 1000,最大 50000',\n year: '篩選指定年份的訊息,如 2024',\n month: '篩選指定月份的訊息(1-12),需要配合 year 使用',\n day: '篩選指定日期的訊息(1-31),需要配合 year 和 month 使用',\n hour: '篩選指定小時的訊息(0-23),需要配合 year、month 和 day 使用',\n start_time: '開始時間,格式 \"YYYY-MM-DD HH:mm\"。指定後會覆蓋 year/month/day/hour 參數',\n end_time: '結束時間,格式 \"YYYY-MM-DD HH:mm\"。指定後會覆蓋 year/month/day/hour 參數',\n },\n },\n get_recent_messages: {\n desc: '取得指定時間段內的聊天訊息,是回答「最近大家聊了什麼」、「X月聊了什麼」、追加新紀錄後最新內容等概覽性問題的首選證據工具。支援精確到分鐘級別的時間查詢。',\n params: {\n limit: '回傳訊息數量限制,預設 100(可節省 Token,必要時再增加)',\n year: '篩選指定年份的訊息,如 2024',\n month: '篩選指定月份的訊息(1-12),需要配合 year 使用',\n day: '篩選指定日期的訊息(1-31),需要配合 year 和 month 使用',\n hour: '篩選指定小時的訊息(0-23),需要配合 year、month 和 day 使用',\n start_time: '開始時間,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"。指定後會覆蓋 year/month/day/hour 參數',\n end_time: '結束時間,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"。指定後會覆蓋 year/month/day/hour 參數',\n },\n },\n get_chat_overview: {\n desc: '取得聊天記錄的基本概覽資訊,包括群名/平台/類型/總訊息數/總成員數/時間跨度/最活躍成員排名。適合在分析前或追加新紀錄後確認目前資料庫範圍。',\n params: {\n top_n: '回傳前 N 名活躍成員,預設 10',\n },\n },\n get_member_stats: {\n desc: '取得群成員的活躍度統計資料。適用於回答「誰最活躍」、「發言最多的是誰」等問題。',\n params: {\n top_n: '回傳前 N 名成員,預設 10',\n },\n },\n get_time_stats: {\n desc: '取得群聊的時間分佈統計。適用於回答「什麼時候最活躍」、「大家一般幾點聊天」等問題。返回的 `data` 陣列可直接作為 `render_chart` 的 `rows` 參數用於繪圖。',\n params: {\n type: '統計類型:hourly(按小時)、weekday(按星期)、daily(按日期)',\n },\n },\n get_members: {\n desc: '取得群成員清單,包含基本資料、別名與訊息統計。適用於查詢「群裡有哪些人」、「某人的別名是什麼」、「誰的 QQ 號是 xxx」等問題。',\n params: {\n search: '可選的搜尋關鍵詞,用於篩選成員暱稱、別名或 QQ 號',\n limit: '回傳成員數量限制,預設回傳全部',\n },\n },\n get_member_name_history: {\n desc: '取得成員的暱稱變更歷史紀錄。適用於回答「某人以前叫什麼名字」、「某人的暱稱變化」、「某人曾用名」等問題。需要先透過 get_members 工具取得成員 ID。',\n params: {\n member_id: '成員的資料庫 ID,可以透過 get_members 工具取得',\n },\n },\n get_conversation_between: {\n desc: '取得兩位群成員之間的對話紀錄。適用於回答「A 和 B 之間聊了什麼」、「檢視兩人的對話」等問題。需要先透過 get_members 取得成員 ID。支援精確到分鐘級別的時間查詢。',\n params: {\n member_id_1: '第一個成員的資料庫 ID',\n member_id_2: '第二個成員的資料庫 ID',\n limit: '回傳訊息數量限制,預設 100',\n start_time: '開始時間,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"。指定後會覆蓋 year/month/day/hour 參數',\n end_time: '結束時間,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"。指定後會覆蓋 year/month/day/hour 參數',\n },\n },\n get_message_context: {\n desc: '根據訊息 ID 取得前後的上下文訊息。適用於引用具體訊息前後進行事實驗證,例如「這則訊息前後在聊什麼」、「確認這段話是否有上下文」等。支援單筆或批次訊息 ID。',\n params: {\n message_ids:\n '要查詢上下文的訊息 ID 清單,可以是單個 ID 或多個 ID。訊息 ID 可以從 search_messages 等工具的回傳結果中取得',\n context_size: '上下文大小,即取得前後各多少條訊息,預設 20',\n },\n },\n get_segment_messages: {\n desc: '取得指定段落的完整訊息清單。用於在 get_segment_summaries 找到相關段落摘要後,取得該段落的完整原文上下文。回傳段落的所有訊息及參與者資訊。',\n params: {\n segment_id: '段落 ID,可以從 get_segment_summaries 的回傳結果中取得',\n limit: '回傳訊息數量限制,預設 1000。對於超長段落可以限制回傳數量以節省 token',\n },\n },\n get_segment_summaries: {\n desc: `取得段落摘要清單,快速了解群聊歷史討論的主題。\n\n適用場景:\n1. 了解群裡最近在聊什麼話題\n2. 按關鍵詞搜尋討論過的話題\n3. 概覽性問題如「群裡有沒有討論過旅遊」\n\n回傳的摘要是對每個會話的簡短總結,可以幫助快速定位感興趣的會話,然後用 get_segment_messages 取得詳情。`,\n params: {\n keywords: '在摘要中搜尋的關鍵詞清單(OR 邏輯匹配)',\n limit: '回傳會話數量限制,預設 20',\n year: '篩選指定年份的會話',\n month: '篩選指定月份的會話(1-12)',\n day: '篩選指定日期的會話(1-31)',\n start_time: '開始時間,格式 \"YYYY-MM-DD HH:mm\"',\n end_time: '結束時間,格式 \"YYYY-MM-DD HH:mm\"',\n },\n },\n message_type_breakdown: {\n desc: '按訊息類型統計近 N 天的訊息分佈(文字、圖片、語音、表情等各有多少條)。適用於了解溝通方式偏好。',\n params: { days: '統計最近多少天的資料' },\n rowTemplate: '{type_name}:{msg_count} 條(佔 {percentage}%)',\n summaryTemplate: '訊息類型分佈(共 {rowCount} 種類型):',\n fallback: '該時間範圍內沒有訊息紀錄',\n },\n peak_chat_hours_by_member: {\n desc: '分析指定成員在近 N 天內每小時的發言量分佈,找出其最活躍的時段。需要先透過 get_members 取得 member_id。',\n params: {\n member_id: '成員 ID(透過 get_members 取得)',\n days: '統計最近多少天的資料',\n },\n rowTemplate: '{hour}:00 — {msg_count} 條訊息',\n summaryTemplate: '該成員各時段發言量(共 {rowCount} 個活躍時段):',\n fallback: '該成員在指定時間範圍內沒有發言紀錄',\n },\n member_activity_trend: {\n desc: '查看指定成員近 N 天的每日發言數量變化趨勢。適用於觀察某人是否變得更活躍或更沉默。需要先透過 get_members 取得 member_id。',\n params: {\n member_id: '成員 ID(透過 get_members 取得)',\n days: '查看最近多少天的趨勢',\n },\n rowTemplate: '{day}:{msg_count} 條',\n summaryTemplate: '該成員近 {rowCount} 天有發言紀錄:',\n fallback: '該成員在指定時間範圍內沒有發言紀錄',\n },\n silent_members: {\n desc: '偵測超過 N 天未發言的「沉默成員」。適用於社群營運中發現流失風險使用者。',\n params: { days: '多少天未發言算沉默' },\n rowTemplate: '{name} — 已沉默 {silent_days} 天',\n summaryTemplate: '共發現 {rowCount} 位沉默成員:',\n fallback: '沒有發現超過指定天數未發言的成員,社群活躍度良好!',\n },\n reply_interaction_ranking: {\n desc: '分析群內的回覆互動關係排行,找出誰回覆誰最多。適用於發現社群中的核心互動關係和意見領袖。',\n params: {\n days: '統計最近多少天的資料',\n limit: '回傳前多少對互動關係',\n },\n rowTemplate: '{replier_name} → {original_name}:{reply_count} 次回覆',\n summaryTemplate: '回覆互動 Top {rowCount}:',\n fallback: '該時間範圍內沒有回覆互動紀錄',\n },\n mutual_interaction_pairs: {\n desc: '找出互動最頻繁的成員對,基於雙向訊息時間接近度(一方發言後 5 分鐘內另一方也發言即視為一次互動)。適用於發現關係親密的好友組合。',\n params: {\n days: '統計最近多少天的資料',\n limit: '回傳前多少對',\n },\n rowTemplate: '{member_a} ↔ {member_b}:{interaction_count} 次互動',\n summaryTemplate: '互動最頻繁的 {rowCount} 對好友:',\n fallback: '該時間範圍內沒有偵測到明顯的互動關係',\n },\n member_message_length_stats: {\n desc: '統計各成員的平均訊息長度(僅文字訊息),長訊息通常意味著更用心的交流。適用於發現深度交流者。',\n params: {\n days: '統計最近多少天的資料',\n top_n: '回傳前多少名',\n },\n rowTemplate: '{name} — 平均 {avg_length} 字/條(共 {msg_count} 條,最長 {max_length} 字)',\n summaryTemplate: '訊息長度 Top {rowCount}(更長 = 更用心):',\n fallback: '該時間範圍內沒有足夠的文字訊息資料',\n },\n unanswered_messages: {\n desc: '查找近 N 天內未被回覆的訊息,這些可能是未解決的客戶問題。僅統計文字訊息且內容超過 10 字的(過濾簡短寒暄)。',\n params: {\n days: '查找最近多少天的資料',\n limit: '最多回傳多少條',\n },\n rowTemplate: '[{send_time}] {sender_name}:{content_preview}',\n summaryTemplate: '共發現 {rowCount} 條可能未被回覆的訊息:',\n fallback: '該時間範圍內所有訊息都已得到回覆,服務品質很好!',\n },\n daily_active_members: {\n desc: '統計每日獨立發言人數(DAU)和訊息量,用於觀察群活力變化趨勢。適用於「群活躍度趨勢怎麼樣」、「最近有多少人在說話」。',\n params: { days: '統計最近多少天的資料' },\n rowTemplate: '{day}:{active_members} 人活躍,{msg_count} 條訊息',\n summaryTemplate: '近 {rowCount} 天的每日活躍人數趨勢:',\n fallback: '該時間範圍內沒有訊息紀錄',\n },\n conversation_initiator_stats: {\n desc: '統計每個成員發起會話(作為會話首條訊息的發送者)的次數,找出誰最常開啟話題。需要已產生會話索引。',\n params: {\n days: '統計最近多少天的資料',\n limit: '回傳前多少名',\n },\n rowTemplate: '{name}:發起 {initiated_count} 次話題',\n summaryTemplate: '話題發起者 Top {rowCount}:',\n fallback: '該時間範圍內沒有會話紀錄,可能需要先產生會話索引',\n },\n activity_heatmap: {\n desc: '回傳 星期×小時 的訊息數矩陣,適合產生活躍度熱力圖。weekday: 0=週日, 1=週一, ..., 6=週六。',\n params: { days: '統計最近多少天的資料' },\n rowTemplate: '星期{weekday} {hour}:00 — {msg_count} 條',\n summaryTemplate: '活躍度熱力圖資料(共 {rowCount} 個時段有訊息):',\n fallback: '該時間範圍內沒有訊息紀錄',\n },\n response_time_analysis: {\n desc: '分析訊息之間的回應時間,按成員維度統計中位數和平均回覆速度。適用於「大家平均多久回覆訊息」、「誰回覆最快」。',\n params: {\n days: '統計最近多少天的資料',\n top_n: '回傳前多少名',\n },\n },\n keyword_frequency: {\n desc: '對指定時間段的文字訊息進行分詞,統計高頻關鍵詞排行。支援中英日文分詞。適用於「大家最常說什麼」、「高頻關鍵詞是什麼」。',\n params: {\n days: '統計最近多少天的資料',\n top_n: '回傳前多少個關鍵詞',\n },\n },\n get_schema: {\n desc: '查看聊天資料庫表結構。繪圖或自訂 SQL 前應先用它確認表名和欄位名。',\n params: {},\n },\n render_chart: {\n desc: '根據 ChartSpec v1 生成 ChatLab 原生圖表。支援 bar、line、pie、heatmap。提供 `rows`(來自高層工具的資料陣列,如 get_time_stats 的 `data` 欄位)或 `sql`(唯讀 SELECT)二擇一;有現成資料時優先用 `rows`,僅在高層工具無法滿足需求時才寫 `sql`。禁止輸出 HTML、JavaScript、SVG、ECharts option 或渲染程式碼。',\n params: {\n rows: '來自高層工具返回的資料陣列(如 get_time_stats 的 `data` 欄位)。有現成資料時優先使用,與 sql 二擇一。',\n sql: '唯讀 SELECT 或 WITH SELECT SQL。僅在高層工具無法滿足需求時使用,必須返回 ChartSpec encoding 中引用的欄位。',\n params: 'SQL 命名參數物件;不需要參數時傳空物件。',\n chartSpec: 'ChartSpec v1,包含 version、type、title、encoding。',\n maxRows: '圖表查詢最大行數,預設 1000。',\n },\n },\n },\n\n agent: {\n answerWithoutTools: '請根據已取得的資訊給出回答,不要再呼叫工具。',\n toolError: '錯誤: {{error}}',\n currentDateIs: '目前日期是',\n chatContext: {\n private: '對話',\n group: '群聊',\n },\n ownerNote: `目前使用者身份:\n- 使用者在{{chatContext}}中的身份是「{{displayName}}」(platformId: {{platformId}})\n- 當使用者提到「我」、「我的」時,指的就是「{{displayName}}」\n- 查詢「我」的發言時,使用 sender_id 參數篩選該成員\n`,\n memberNotePrivate: `成員查詢策略:\n- 私聊只有兩個人,可以直接取得成員清單\n- 當使用者提到「對方」、「他/她」時,透過 get_members 取得另一方資訊\n`,\n memberNoteGroup: `成員查詢策略:\n- 當使用者提到特定群成員(如「張三說過什麼」、「小明的發言」等)時,應先呼叫 get_members 取得成員清單\n- 群成員有三種名稱:accountName(原始暱稱)、groupNickname(群暱稱)、aliases(使用者自訂別名)\n- 透過 get_members 的 search 參數可以模糊搜尋這三種名稱\n- 找到成員後,使用其 id 欄位作為 search_messages 的 sender_id 參數來取得該成員的發言\n`,\n mentionedMembersNote: '本輪使用者顯式 @ 的成員(可直接使用 member_id,無需再次搜尋):',\n timeParamsIntro: '時間參數:使用 start_time/end_time 指定時間範圍,格式 \"YYYY-MM-DD HH:mm\"',\n defaultYearNote: '未指定時間範圍時預設查詢全部。當前年份為{{year}}年',\n dataSnapshotNote:\n '目前聊天資料庫快照:{{name}}({{platform}}),總訊息 {{totalMessages}} 條,成員 {{totalMembers}} 人,時間範圍 {{firstMessageDate}} ~ {{lastMessageDate}}。該快照只用於判斷資料範圍;回答具體聊天事實仍必須呼叫工具檢索目前資料庫。',\n dataSnapshotContext: `目前聊天資料庫啟動上下文:\n- name: {{name}}\n- platform: {{platform}}\n- type: {{type}}\n- total_messages: {{totalMessages}}\n- total_members: {{totalMembers}}\n- first_message_ts: {{firstMessageTs}}\n- first_message_time: {{firstMessageTime}}\n- last_message_ts: {{lastMessageTs}}\n- last_message_time: {{lastMessageTime}} (資料庫中已匯入訊息的截止時間,不代表群組/對話當前是否活躍)\n- segment_summaries_available: {{segmentSummaryCount}}\n\n{{memberHintTitle}}\n{{memberHintLines}}\n\n使用規則:\n{{usageRules}}`,\n dataSnapshotMemberHintsAll: '活躍成員查詢提示(全部成員):',\n dataSnapshotMemberHintsTop: '活躍成員查詢提示(按歷史總訊息量 Top 10):',\n dataSnapshotMemberHintsUnavailable: '活躍成員查詢提示:',\n dataSnapshotMemberHintsEmpty: '無可用成員提示。',\n dataSnapshotUsageRules: `- member_id 是工具查詢提示;display_name 僅用於人類識別,可能不唯一。\n- 不要在最終回答中主動暴露 member_id 或啟動上下文本身,除非使用者明確要求技術細節。\n- 活躍成員排行只代表歷史總訊息量,不代表近期活躍情況;也不足以作為影響力、關係或近期趨勢結論的證據。\n- 相對時間表達以真實目前日期為基準,而不是資料庫最後訊息時間。\n-「最近一年/過去一年」表示從真實目前日期回推一年到今天;「去年」表示上一自然年。\n- 資料庫時間邊界只用於說明覆蓋範圍,不用於重定義使用者要求的時間範圍。\n- 使用預設「近 N 天」的工具時,先選擇與資料庫時間邊界有交集的範圍,不要先探測真實目前日期視窗導致空結果。\n- 不要只為了重新發現 min/max timestamp 呼叫工具;但回答具體聊天事實、統計和結論仍必須呼叫工具取得證據。\n- last_message_time 是資料庫中已匯入訊息的截止時間,不是群組/對話在現實中最後一次發言的時間;使用者可能只是尚未匯入更新的紀錄。不要據此推斷群組「多久沒動靜」,更不要主動建議使用者去「喚醒」或「激活」群組。`,\n evidencePolicy: `證據策略:\n- AI 對話歷史、歷史 AI 回覆和壓縮摘要只用於理解使用者意圖,不能作為聊天紀錄事實證據。\n- 只要使用者詢問聊天紀錄內容、最近聊什麼、某人說過什麼、統計排行、是否出現過某話題或要求引用原話,必須先呼叫合適的資料工具檢索目前資料庫。\n- 如果已追加新聊天紀錄,目前資料庫可能不同於舊視窗歷史;優先使用 get_chat_overview 或 get_recent_messages/search_messages 取得最新證據。\n- 只有工具回傳的訊息、統計或資料庫概覽可以作為事實依據;工具沒有回傳足夠證據時,明確說明資料不足,不要編造不存在的聊天紀錄。`,\n responseInstruction: '根據使用者的問題,選擇合適的工具取得資料,然後基於資料給出回答。',\n fallbackRoleDefinition: {\n group: `你是一個專業但風格輕鬆的群聊紀錄分析助手。\n你的任務是幫助使用者理解和分析他們的群聊紀錄資料,同時可以適度使用網路熱梗和表情/顏文字活躍氣氛,但不影響結論的準確性。\n\n## 回答要求\n1. 基於工具回傳的資料回答,不要編造資訊\n2. 如果資料不足以回答問題,請說明\n3. 回答要簡潔明瞭,使用 Markdown 格式\n4. 可以適度加入網路熱梗、表情/顏文字(強度適中)\n5. 玩梗不得影響事實準確與結論清晰,避免低俗或冒犯性表達`,\n private: `你是一個專業但風格輕鬆的私聊紀錄分析助手。\n你的任務是幫助使用者理解和分析他們的私聊紀錄資料,同時可以適度使用網路熱梗和表情/顏文字活躍氣氛,但不影響結論的準確性。\n\n## 回答要求\n1. 基於工具回傳的資料回答,不要編造資訊\n2. 如果資料不足以回答問題,請說明\n3. 回答要簡潔明瞭,使用 Markdown 格式\n4. 可以適度加入網路熱梗、表情/顏文字(強度適中)\n5. 玩梗不得影響事實準確與結論清晰,避免低俗或冒犯性表達`,\n },\n },\n },\n\n llm: {\n notConfigured: 'LLM 服務尚未設定,請先在設定中填入 API Key',\n maxConfigs: '最多只能新增 {{count}} 組設定',\n configNotFound: '找不到設定',\n noActiveConfig: '沒有啟用中的設定',\n callFailed: 'LLM 呼叫失敗,請檢查模型設定是否正確',\n genericProviderName: 'API 服務',\n rawErrorLabel: '原始錯誤',\n },\n\n summary: {\n segmentNotFound: '會話不存在或資料庫開啟失敗',\n tooFewMessages: '訊息數量少於 {{count}} 條,無需產生摘要',\n tooFewValidMessages: '有效訊息數量少於 {{count}} 條,無需產生摘要',\n segmentNotExist: '找不到會話',\n messagesTooFew: '訊息太少',\n validMessagesTooFew: '有效訊息太少',\n systemPromptDirect: '你是一個對話摘要專家,擅長用簡潔的語言總結對話內容。',\n systemPromptMerge: '你是一個對話摘要專家,擅長將多個摘要合併成一個連貫的總結。',\n },\n}\n","/**\n * Lightweight AI translation module.\n *\n * Provides a platform-agnostic `TranslateFn` compatible with the shared\n * prompt-builder. No i18next dependency — just nested object lookup\n * with {{var}} interpolation and locale fallback.\n */\n\nimport type { TranslateFn } from '../agent/prompt-builder'\nimport zhCN from './locales/zh-CN'\nimport enUS from './locales/en-US'\nimport jaJP from './locales/ja-JP'\nimport zhTW from './locales/zh-TW'\n\nexport type { TranslateFn }\n\nconst localeMap: Record<string, Record<string, unknown>> = {\n 'zh-CN': zhCN,\n 'en-US': enUS,\n 'ja-JP': jaJP,\n 'zh-TW': zhTW,\n}\n\nfunction getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n let current: unknown = obj\n for (const segment of path.split('.')) {\n if (current == null || typeof current !== 'object') return undefined\n current = (current as Record<string, unknown>)[segment]\n }\n return current\n}\n\nfunction interpolate(template: string, vars: Record<string, unknown>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => {\n const val = vars[key]\n return val != null ? String(val) : ''\n })\n}\n\n/**\n * Create a translation function for the given locale.\n * Supports `lng` option override (i18next-compatible) and {{var}} interpolation.\n */\nexport function createAiTranslate(defaultLocale: string = 'zh-CN'): TranslateFn {\n return (key: string, options?: Record<string, unknown>): string => {\n const lng = (options?.lng as string) || defaultLocale\n const locale = localeMap[lng] ? lng : 'en-US'\n const value = getNestedValue(localeMap[locale], key) ?? getNestedValue(localeMap['en-US'], key)\n\n if (typeof value !== 'string') return key\n if (!options) return value\n return interpolate(value, options)\n }\n}\n\n/** All AI locale data (for consumers like Electron that compose with their own keys) */\nexport const aiLocales = { 'zh-CN': zhCN, 'en-US': enUS, 'ja-JP': jaJP, 'zh-TW': zhTW }\n","/**\n * Session summary generation — shared implementation.\n *\n * Uses Map-Reduce strategy for long conversations.\n * All platform-specific concerns (DB access, LLM invocation, i18n) are injected\n * via the SummaryDeps interface.\n */\n\n// ==================== Types ====================\n\nexport interface SummaryMessage {\n senderName: string\n content: string | null\n}\n\nexport interface SummaryDeps {\n loadMessages: (segmentId: number, limit?: number) => SummaryMessage[] | null\n saveSummary: (segmentId: number, summary: string) => void\n getSummary: (segmentId: number) => string | null\n llmComplete: (\n systemPrompt: string,\n userPrompt: string,\n options?: { temperature?: number; maxTokens?: number }\n ) => Promise<string>\n t: (key: string, options?: Record<string, unknown>) => string\n logger?: {\n info: (category: string, message: string, data?: unknown) => void\n error: (category: string, message: string, data?: unknown) => void\n }\n}\n\nexport type SummaryStrategy = 'brief' | 'standard'\n\nexport interface SummaryOptions {\n locale?: string\n forceRegenerate?: boolean\n strategy?: SummaryStrategy\n}\n\nexport interface SummaryResult {\n success: boolean\n summary?: string\n error?: string\n}\n\n// ==================== Constants ====================\n\nconst MIN_MESSAGE_COUNT = 3\nconst MAX_CONTENT_PER_CALL = 8000\nconst SEGMENT_THRESHOLD = 8000\n\n// ==================== Pure algorithms ====================\n\nfunction getSummaryLengthLimit(messageCount: number, strategy: SummaryStrategy = 'standard'): number {\n if (strategy === 'brief') {\n if (messageCount <= 10) return 50\n if (messageCount <= 30) return 80\n if (messageCount <= 100) return 120\n return 200\n }\n // standard: richer summaries covering who, what, and key details\n if (messageCount <= 10) return 100\n if (messageCount <= 30) return 200\n if (messageCount <= 100) return 350\n return 500\n}\n\nconst MEANINGFUL_SHORT_ZH = ['好的', '不是', '是的', '可以', '不行', '好吧', '明白', '知道', '同意']\nconst MEANINGLESS_SHORT_EN = [\n 'ok',\n 'k',\n 'yes',\n 'no',\n 'ya',\n 'yep',\n 'nope',\n 'lol',\n 'haha',\n 'hehe',\n 'hmm',\n 'ah',\n 'oh',\n 'wow',\n 'thx',\n 'ty',\n 'np',\n 'gg',\n 'brb',\n 'idk',\n]\nconst PLACEHOLDERS = [\n '[图片]',\n '[语音]',\n '[视频]',\n '[文件]',\n '[表情]',\n '[动画表情]',\n '[位置]',\n '[名片]',\n '[红包]',\n '[转账]',\n '[撤回消息]',\n '[image]',\n '[voice]',\n '[video]',\n '[file]',\n '[sticker]',\n '[animated sticker]',\n '[location]',\n '[contact]',\n '[red packet]',\n '[transfer]',\n '[recalled message]',\n '[photo]',\n '[audio]',\n '[gif]',\n]\nconst SYSTEM_PATTERNS_ZH = [/^.*邀请.*加入了群聊$/, /^.*退出了群聊$/, /^.*撤回了一条消息$/, /^你撤回了一条消息$/]\nconst SYSTEM_PATTERNS_EN = [\n /^.*invited.*to the group$/i,\n /^.*left the group$/i,\n /^.*recalled a message$/i,\n /^you recalled a message$/i,\n /^.*joined the group$/i,\n /^.*has been removed$/i,\n]\n\nexport function isValidMessage(content: string): boolean {\n const trimmed = content.trim()\n if (!trimmed) return false\n\n if (trimmed.length <= 2 && !MEANINGFUL_SHORT_ZH.includes(trimmed)) return false\n\n const lower = trimmed.toLowerCase()\n if (MEANINGLESS_SHORT_EN.includes(lower)) return false\n\n if (/^[\\p{Emoji}\\s[\\]()()]+$/u.test(trimmed)) return false\n if (PLACEHOLDERS.some((p) => lower === p.toLowerCase())) return false\n if (SYSTEM_PATTERNS_ZH.some((p) => p.test(trimmed))) return false\n if (SYSTEM_PATTERNS_EN.some((p) => p.test(trimmed))) return false\n\n return true\n}\n\nexport function filterValidMessages(messages: SummaryMessage[]): Array<{ senderName: string; content: string }> {\n return messages\n .filter((m) => m.content && isValidMessage(m.content))\n .map((m) => ({ senderName: m.senderName, content: m.content!.trim() }))\n}\n\nfunction formatMessages(messages: Array<{ senderName: string; content: string }>): string {\n return messages.map((m) => `${m.senderName}: ${m.content}`).join('\\n')\n}\n\nexport function splitIntoSegments(\n messages: Array<{ senderName: string; content: string }>,\n maxCharsPerSegment: number\n): Array<Array<{ senderName: string; content: string }>> {\n const segments: Array<Array<{ senderName: string; content: string }>> = []\n let currentSegment: Array<{ senderName: string; content: string }> = []\n let currentLength = 0\n\n for (const msg of messages) {\n const msgLength = msg.senderName.length + msg.content.length + 3\n if (currentLength + msgLength > maxCharsPerSegment && currentSegment.length > 0) {\n segments.push(currentSegment)\n currentSegment = []\n currentLength = 0\n }\n currentSegment.push(msg)\n currentLength += msgLength\n }\n if (currentSegment.length > 0) segments.push(currentSegment)\n return segments\n}\n\n// ==================== Prompt builders ====================\n\nfunction buildSummaryPrompt(content: string, lengthLimit: number, locale: string, strategy: SummaryStrategy): string {\n if (strategy === 'brief') {\n if (locale.startsWith('zh')) {\n return `请用简洁的语言(${lengthLimit}字以内)总结以下对话的主要内容或话题。只输出摘要内容,不要添加任何前缀、解释或引号。\\n\\n${content}`\n }\n return `Summarize the following conversation concisely (max ${lengthLimit} characters). Output only the summary, no prefix, explanation, or quotes.\\n\\n${content}`\n }\n if (locale.startsWith('zh')) {\n return `请总结以下聊天记录(${lengthLimit}字以内),需包含:谁参与了讨论、讨论了什么事情、关键细节和结论。用2-4句话描述,不要罗列每条消息。只输出摘要内容,不要添加任何前缀、解释或引号。\\n\\n${content}`\n }\n return `Summarize the following chat log (max ${lengthLimit} characters). Include: who participated, what was discussed, key details and conclusions. Use 2-4 sentences, don't list individual messages. Output only the summary, no prefix, explanation, or quotes.\\n\\n${content}`\n}\n\nfunction buildSubSummaryPrompt(content: string, locale: string, strategy: SummaryStrategy): string {\n const limit = strategy === 'brief' ? 50 : 100\n if (locale.startsWith('zh')) {\n return `请用一到两句话(不超过${limit}字)概括以下对话片段的主要内容,包括谁说了什么。只输出摘要内容,不要添加任何前缀、解释或引号。\\n\\n${content}`\n }\n return `Summarize this conversation segment in 1-2 sentences (max ${limit} characters), including who said what. Output only the summary, no prefix or quotes.\\n\\n${content}`\n}\n\nfunction buildMergePrompt(\n subSummaries: string[],\n lengthLimit: number,\n locale: string,\n strategy: SummaryStrategy\n): string {\n const list = subSummaries.map((s, i) => `${i + 1}. ${s}`).join('\\n')\n if (strategy === 'brief') {\n if (locale.startsWith('zh')) {\n return `以下是一段对话的多个片段摘要,请将它们合并成一个完整的总结(${lengthLimit}字以内)。只输出摘要内容,不要添加任何前缀、解释或引号。\\n\\n${list}`\n }\n return `Below are summaries of different parts of a conversation. Merge them into one cohesive summary (max ${lengthLimit} characters). Output only the summary, no prefix or quotes.\\n\\n${list}`\n }\n if (locale.startsWith('zh')) {\n return `以下是一段对话的多个片段摘要,请将它们合并成一个完整的总结(${lengthLimit}字以内)。保留参与者、事件和关键细节。只输出摘要内容,不要添加任何前缀、解释或引号。\\n\\n${list}`\n }\n return `Below are summaries of different parts of a conversation. Merge them into one cohesive summary (max ${lengthLimit} characters). Preserve participants, events, and key details. Output only the summary, no prefix or quotes.\\n\\n${list}`\n}\n\n// ==================== Post-processing ====================\n\nfunction postProcessSummary(summary: string, lengthLimit: number): string {\n let result = summary\n if ((result.startsWith('\"') && result.endsWith('\"')) || (result.startsWith('「') && result.endsWith('」'))) {\n result = result.slice(1, -1)\n }\n const hardLimit = Math.floor(lengthLimit * 1.5)\n if (result.length > hardLimit) {\n result = result.slice(0, hardLimit - 3) + '...'\n }\n return result\n}\n\n// ==================== Public API ====================\n\nexport async function generateSessionSummary(\n deps: SummaryDeps,\n segmentId: number,\n options: SummaryOptions = {}\n): Promise<SummaryResult> {\n const { locale = 'zh-CN', forceRegenerate = false, strategy = 'standard' } = options\n const log = deps.logger\n\n try {\n if (!forceRegenerate) {\n const existing = deps.getSummary(segmentId)\n if (existing) return { success: true, summary: existing }\n }\n\n const rawMessages = deps.loadMessages(segmentId)\n if (!rawMessages) {\n return { success: false, error: deps.t('summary.sessionNotFound') }\n }\n\n if (rawMessages.length < MIN_MESSAGE_COUNT) {\n return { success: false, error: deps.t('summary.tooFewMessages', { count: MIN_MESSAGE_COUNT }) }\n }\n\n const validMessages = filterValidMessages(rawMessages)\n if (validMessages.length < MIN_MESSAGE_COUNT) {\n return { success: false, error: deps.t('summary.tooFewValidMessages', { count: MIN_MESSAGE_COUNT }) }\n }\n\n const lengthLimit = getSummaryLengthLimit(validMessages.length, strategy)\n const maxTokens = strategy === 'brief' ? 300 : 600\n const subMaxTokens = strategy === 'brief' ? 100 : 200\n const content = formatMessages(validMessages)\n\n log?.info(\n 'Summary',\n `Generating summary: sessionId=${segmentId}, strategy=${strategy}, raw=${rawMessages.length}, valid=${validMessages.length}, chars=${content.length}`\n )\n\n let summary: string\n if (content.length <= SEGMENT_THRESHOLD) {\n summary = await deps.llmComplete(\n deps.t('summary.systemPromptDirect'),\n buildSummaryPrompt(content, lengthLimit, locale, strategy),\n { temperature: 0.3, maxTokens }\n )\n summary = summary.trim()\n } else {\n const segments = splitIntoSegments(validMessages, MAX_CONTENT_PER_CALL)\n log?.info('Summary', `Long session segmented: ${segments.length} segments`)\n\n const subSummaries: string[] = []\n for (const segment of segments) {\n const segContent = formatMessages(segment)\n const sub = await deps.llmComplete(\n deps.t('summary.systemPromptDirect'),\n buildSubSummaryPrompt(segContent, locale, strategy),\n { temperature: 0.3, maxTokens: subMaxTokens }\n )\n subSummaries.push(sub.trim())\n }\n\n if (subSummaries.length === 1) {\n summary = subSummaries[0]\n } else {\n summary = await deps.llmComplete(\n deps.t('summary.systemPromptMerge'),\n buildMergePrompt(subSummaries, lengthLimit, locale, strategy),\n { temperature: 0.3, maxTokens }\n )\n summary = summary.trim()\n }\n }\n\n summary = postProcessSummary(summary, lengthLimit)\n deps.saveSummary(segmentId, summary)\n\n log?.info('Summary', `Summary generated: \"${summary.slice(0, 50)}...\"`)\n return { success: true, summary }\n } catch (error) {\n log?.error('Summary', 'Summary generation failed', error)\n return { success: false, error: error instanceof Error ? error.message : String(error) }\n }\n}\n\nexport async function generateSessionSummaries(\n deps: SummaryDeps,\n segmentIds: number[],\n options: SummaryOptions = {},\n onProgress?: (current: number, total: number) => void\n): Promise<{ success: number; failed: number; skipped: number }> {\n let success = 0\n let failed = 0\n let skipped = 0\n\n for (let i = 0; i < segmentIds.length; i++) {\n const result = await generateSessionSummary(deps, segmentIds[i], options)\n if (result.success) {\n success++\n } else if (result.error?.includes('少于') || result.error?.includes('less than') || result.error?.includes('few')) {\n skipped++\n } else {\n failed++\n }\n onProgress?.(i + 1, segmentIds.length)\n }\n\n return { success, failed, skipped }\n}\n\nexport function checkSessionsCanGenerateSummary(\n deps: Pick<SummaryDeps, 'loadMessages' | 't'>,\n segmentIds: number[]\n): Map<number, { canGenerate: boolean; reason?: string }> {\n const results = new Map<number, { canGenerate: boolean; reason?: string }>()\n\n for (const id of segmentIds) {\n const messages = deps.loadMessages(id)\n if (!messages) {\n results.set(id, { canGenerate: false, reason: deps.t('summary.sessionNotExist') })\n continue\n }\n if (messages.length < MIN_MESSAGE_COUNT) {\n results.set(id, { canGenerate: false, reason: deps.t('summary.messagesTooFew') })\n continue\n }\n const valid = filterValidMessages(messages)\n if (valid.length < MIN_MESSAGE_COUNT) {\n results.set(id, { canGenerate: false, reason: deps.t('summary.validMessagesTooFew') })\n continue\n }\n results.set(id, { canGenerate: true })\n }\n\n return results\n}\n","/**\n * LLM configuration CRUD store — platform-agnostic.\n * Uses ConfigStorage abstraction for persistence and dependency injection\n * for i18n, UUID generation, and auth profile management.\n */\n\nimport type { ModelSlot } from '@openchatlab/core'\n\n// ==================== Types ====================\n\nexport interface AIServiceConfig {\n id: string\n name: string\n provider: string\n apiKey: string\n model?: string\n baseUrl?: string\n maxTokens?: number\n apiFormat?: string\n customModels?: Array<{ id: string; name: string }>\n createdAt: number\n updatedAt: number\n}\n\nexport interface AIConfigStore {\n configs: AIServiceConfig[]\n defaultAssistant: ModelSlot | null\n fastModel: ModelSlot | null\n}\n\nexport const MAX_CONFIG_COUNT = 99\n\n// ==================== Storage abstraction ====================\n\nexport interface ConfigStorage {\n readJson<T>(key: string): T | null\n writeJson<T>(key: string, data: T): void\n}\n\n// ==================== Dependencies ====================\n\nexport interface LLMConfigStoreDeps {\n t?: (key: string, options?: Record<string, unknown>) => string\n generateId?: () => string\n /** Returns the auth profile name so LLMConfigStore can persist it on the config */\n onApiKeyCreated?: (config: AIServiceConfig, apiKey: string) => string | void\n /** Called after a config is removed; use to clean up the corresponding auth profile */\n onApiKeyDeleted?: (config: AIServiceConfig) => void\n resolveApiKey?: (provider: string, authProfile?: string) => string | undefined\n onStoreLoaded?: (configs: AIServiceConfig[]) => void\n}\n\n// ==================== Config Store ====================\n\nfunction defaultT(key: string, options?: Record<string, unknown>): string {\n if (key === 'llm.maxConfigs' && options?.count) return `Maximum ${options.count} configurations allowed`\n if (key === 'llm.configNotFound') return 'Configuration not found'\n return key\n}\n\nfunction defaultGenerateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction resolveSlot(slot: ModelSlot | null | undefined, configs: AIServiceConfig[]): ModelSlot | null {\n if (slot && configs.some((c) => c.id === slot.configId)) return slot\n const fallback = configs[0]\n return fallback ? { configId: fallback.id, modelId: fallback.model || '' } : null\n}\n\nfunction getAuthProfile(config: AIServiceConfig): string | undefined {\n return (config as unknown as Record<string, unknown>).authProfile as string | undefined\n}\n\nfunction isAuthProfileUsed(configs: AIServiceConfig[], authProfile: string, profileProvider?: string): boolean {\n return configs.some((config) => {\n const configAuthProfile = getAuthProfile(config)\n if (configAuthProfile === authProfile) return true\n return !configAuthProfile && profileProvider !== undefined && config.provider === profileProvider\n })\n}\n\nexport class LLMConfigStore {\n private storage: ConfigStorage\n private t: (key: string, options?: Record<string, unknown>) => string\n private generateId: () => string\n private onApiKeyCreated?: (config: AIServiceConfig, apiKey: string) => string | void\n private onApiKeyDeleted?: (config: AIServiceConfig) => void\n private resolveApiKey?: (provider: string, authProfile?: string) => string | undefined\n private onStoreLoaded?: (configs: AIServiceConfig[]) => void\n\n constructor(storage: ConfigStorage, deps: LLMConfigStoreDeps = {}) {\n this.storage = storage\n this.t = deps.t || defaultT\n this.generateId = deps.generateId || defaultGenerateId\n this.onApiKeyCreated = deps.onApiKeyCreated\n this.onApiKeyDeleted = deps.onApiKeyDeleted\n this.resolveApiKey = deps.resolveApiKey\n this.onStoreLoaded = deps.onStoreLoaded\n }\n\n loadStore(): AIConfigStore {\n const store = this.storage.readJson<AIConfigStore>('llm-config')\n if (!store) {\n return { configs: [], defaultAssistant: null, fastModel: null }\n }\n\n this.onStoreLoaded?.(store.configs)\n\n const resolvedConfigs = this.resolveApiKey\n ? store.configs.map((config) => {\n const profileKey = this.resolveApiKey!(\n config.provider,\n (config as unknown as Record<string, unknown>).authProfile as string | undefined\n )\n return { ...config, apiKey: profileKey || config.apiKey || '' }\n })\n : store.configs\n\n return {\n ...store,\n configs: resolvedConfigs,\n defaultAssistant: resolveSlot(store.defaultAssistant, resolvedConfigs),\n fastModel: resolveSlot(store.fastModel, resolvedConfigs),\n }\n }\n\n saveStore(store: AIConfigStore): void {\n this.storage.writeJson<AIConfigStore>('llm-config', {\n ...store,\n configs: store.configs.map((config) => ({\n ...config,\n apiKey: '',\n })),\n })\n }\n\n getAllConfigs(): AIServiceConfig[] {\n return this.loadStore().configs\n }\n\n getDefaultAssistantSlot(): ModelSlot | null {\n const store = this.loadStore()\n return resolveSlot(store.defaultAssistant, store.configs)\n }\n\n getDefaultAssistantConfig(): AIServiceConfig | null {\n const store = this.loadStore()\n const slot = resolveSlot(store.defaultAssistant, store.configs)\n if (!slot) return null\n const config = store.configs.find((c) => c.id === slot.configId)\n if (!config) return null\n return { ...config, model: slot.modelId || config.model }\n }\n\n getFastModelSlot(): ModelSlot | null {\n const store = this.loadStore()\n if (store.fastModel === null) return null\n return resolveSlot(store.fastModel, store.configs)\n }\n\n getFastModelConfig(): AIServiceConfig | null {\n const store = this.loadStore()\n if (store.fastModel === null) return this.getDefaultAssistantConfig()\n\n const slot = resolveSlot(store.fastModel, store.configs)\n if (slot) {\n const config = store.configs.find((c) => c.id === slot.configId)\n if (config) return { ...config, model: slot.modelId || config.model }\n }\n return this.getDefaultAssistantConfig()\n }\n\n getConfigById(id: string): AIServiceConfig | null {\n const store = this.loadStore()\n return store.configs.find((c) => c.id === id) || null\n }\n\n addConfig(config: Omit<AIServiceConfig, 'id' | 'createdAt' | 'updatedAt'>): {\n success: boolean\n config?: AIServiceConfig\n error?: string\n } {\n const store = this.loadStore()\n\n if (store.configs.length >= MAX_CONFIG_COUNT) {\n return { success: false, error: this.t('llm.maxConfigs', { count: MAX_CONFIG_COUNT }) }\n }\n\n const now = Date.now()\n const newConfig: AIServiceConfig = {\n ...config,\n id: this.generateId(),\n createdAt: now,\n updatedAt: now,\n }\n\n store.configs.push(newConfig)\n\n if (store.configs.length === 1) {\n store.defaultAssistant = { configId: newConfig.id, modelId: newConfig.model || '' }\n }\n\n if (newConfig.apiKey && this.onApiKeyCreated) {\n const profileName = this.onApiKeyCreated(newConfig, newConfig.apiKey)\n if (profileName) {\n ;(newConfig as unknown as Record<string, unknown>).authProfile = profileName\n }\n }\n\n this.saveStore(store)\n return { success: true, config: { ...newConfig, apiKey: '' } }\n }\n\n updateConfig(\n id: string,\n updates: Partial<Omit<AIServiceConfig, 'id' | 'createdAt' | 'updatedAt'>>\n ): { success: boolean; error?: string } {\n const store = this.loadStore()\n const index = store.configs.findIndex((c) => c.id === id)\n\n if (index === -1) {\n return { success: false, error: this.t('llm.configNotFound') }\n }\n\n const oldConfig = store.configs[index]\n const oldProfileName = (oldConfig as unknown as Record<string, unknown>).authProfile as string | undefined\n\n const updated = {\n ...oldConfig,\n ...updates,\n updatedAt: Date.now(),\n }\n store.configs[index] = updated\n let oldProfileToDelete: AIServiceConfig | null = null\n\n if (updates.apiKey && this.onApiKeyCreated) {\n const profileName = this.onApiKeyCreated(updated, updates.apiKey)\n if (profileName) {\n ;(store.configs[index] as unknown as Record<string, unknown>).authProfile = profileName\n if (\n oldProfileName &&\n oldProfileName !== profileName &&\n !isAuthProfileUsed(store.configs, oldProfileName, oldConfig.provider)\n ) {\n oldProfileToDelete = oldConfig\n }\n }\n }\n\n this.saveStore(store)\n if (oldProfileToDelete) this.onApiKeyDeleted?.(oldProfileToDelete)\n return { success: true }\n }\n\n deleteConfig(id: string): { success: boolean; error?: string } {\n const store = this.loadStore()\n const index = store.configs.findIndex((c) => c.id === id)\n\n if (index === -1) {\n return { success: false, error: this.t('llm.configNotFound') }\n }\n\n const deleted = store.configs[index]\n store.configs.splice(index, 1)\n\n const fallback = store.configs[0]\n if (store.defaultAssistant?.configId === id) {\n store.defaultAssistant = fallback ? { configId: fallback.id, modelId: fallback.model || '' } : null\n }\n if (store.fastModel?.configId === id) {\n store.fastModel = fallback ? { configId: fallback.id, modelId: fallback.model || '' } : null\n }\n\n this.saveStore(store)\n const deletedProfileName = getAuthProfile(deleted)\n if (!deletedProfileName || !isAuthProfileUsed(store.configs, deletedProfileName, deleted.provider)) {\n this.onApiKeyDeleted?.(deleted)\n }\n return { success: true }\n }\n\n setDefaultAssistantModel(configId: string, modelId: string): { success: boolean; error?: string } {\n const store = this.loadStore()\n const config = store.configs.find((c) => c.id === configId)\n\n if (!config) {\n return { success: false, error: this.t('llm.configNotFound') }\n }\n\n store.defaultAssistant = { configId, modelId }\n this.saveStore(store)\n return { success: true }\n }\n\n setFastModel(slot: ModelSlot | null): { success: boolean; error?: string } {\n const store = this.loadStore()\n\n if (slot !== null) {\n const config = store.configs.find((c) => c.id === slot.configId)\n if (!config) {\n return { success: false, error: this.t('llm.configNotFound') }\n }\n }\n\n store.fastModel = slot\n this.saveStore(store)\n return { success: true }\n }\n\n hasActiveConfig(): boolean {\n return this.getDefaultAssistantConfig() !== null\n }\n}\n","/**\n * Custom provider/model persistence — platform-agnostic.\n * Uses ConfigStorage abstraction so both CLI and Electron share the same logic.\n */\n\nimport type { ConfigStorage } from './llm-config-store'\nimport type { ProviderDefinition, ModelDefinition } from '@openchatlab/core'\n\nfunction generateCustomId(): string {\n return `custom:${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nexport class CustomProviderStore {\n constructor(private storage: ConfigStorage) {}\n\n getAll(): ProviderDefinition[] {\n return this.storage.readJson<ProviderDefinition[]>('custom-providers') ?? []\n }\n\n add(input: {\n name: string\n kind?: string\n defaultBaseUrl: string\n supportsCustomModels?: boolean\n modelIds?: string[]\n website?: string\n consoleUrl?: string\n }): ProviderDefinition {\n const providers = this.getAll()\n const newProvider: ProviderDefinition = {\n id: generateCustomId(),\n name: input.name,\n kind: (input.kind || 'openai-compatible') as ProviderDefinition['kind'],\n defaultBaseUrl: input.defaultBaseUrl,\n authMode: 'api-key',\n supportsCustomModels: input.supportsCustomModels ?? true,\n modelIds: input.modelIds ?? [],\n builtin: false,\n enabledByDefault: false,\n website: input.website,\n consoleUrl: input.consoleUrl,\n }\n providers.push(newProvider)\n this.storage.writeJson('custom-providers', providers)\n return newProvider\n }\n\n update(id: string, updates: Partial<ProviderDefinition>): { success: boolean; error?: string } {\n const providers = this.getAll()\n const index = providers.findIndex((p) => p.id === id)\n if (index === -1) return { success: false, error: 'Custom provider not found' }\n providers[index] = { ...providers[index], ...updates }\n this.storage.writeJson('custom-providers', providers)\n return { success: true }\n }\n\n delete(id: string): { success: boolean; error?: string } {\n const providers = this.getAll()\n const index = providers.findIndex((p) => p.id === id)\n if (index === -1) return { success: false, error: 'Custom provider not found' }\n providers.splice(index, 1)\n this.storage.writeJson('custom-providers', providers)\n return { success: true }\n }\n}\n\nexport class CustomModelStore {\n constructor(private storage: ConfigStorage) {}\n\n getAll(): ModelDefinition[] {\n return this.storage.readJson<ModelDefinition[]>('custom-models') ?? []\n }\n\n add(input: {\n id: string\n providerId: string\n name: string\n description?: string\n contextWindow?: number\n capabilities?: string[]\n recommendedFor?: string[]\n status?: string\n }): { success: boolean; model?: ModelDefinition; error?: string } {\n const models = this.getAll()\n if (models.find((m) => m.id === input.id && m.providerId === input.providerId)) {\n return {\n success: false,\n error: `Model \"${input.id}\" already exists under provider \"${input.providerId}\"`,\n }\n }\n const newModel: ModelDefinition = {\n id: input.id,\n providerId: input.providerId,\n name: input.name,\n description: input.description,\n contextWindow: input.contextWindow,\n capabilities: (input.capabilities ?? ['chat']) as ModelDefinition['capabilities'],\n recommendedFor: (input.recommendedFor ?? ['chat']) as ModelDefinition['recommendedFor'],\n status: (input.status ?? 'stable') as ModelDefinition['status'],\n builtin: false,\n editable: true,\n }\n models.push(newModel)\n this.storage.writeJson('custom-models', models)\n return { success: true, model: newModel }\n }\n\n update(providerId: string, modelId: string, updates: Partial<ModelDefinition>): { success: boolean; error?: string } {\n const models = this.getAll()\n const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId)\n if (index === -1) return { success: false, error: 'Custom model not found' }\n models[index] = { ...models[index], ...updates }\n this.storage.writeJson('custom-models', models)\n return { success: true }\n }\n\n delete(providerId: string, modelId: string): { success: boolean; error?: string } {\n const models = this.getAll()\n const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId)\n if (index === -1) return { success: false, error: 'Custom model not found' }\n models.splice(index, 1)\n this.storage.writeJson('custom-models', models)\n return { success: true }\n }\n}\n","/**\n * Shared PiModel builder — translates an AIServiceConfig-like\n * object into a PiModel instance for @earendil-works/pi-ai.\n *\n * Used by both Electron and Server to eliminate duplicated\n * URL normalization, apiFormat mapping, and model construction.\n */\n\nimport {\n BUILTIN_PROVIDERS,\n getBuiltinModelsByProvider,\n BUILTIN_MODELS,\n type ModelDefinition,\n isReasoningModel,\n getThinkingCompat,\n} from '@openchatlab/core'\nimport type { Model as PiModel, Api as PiApi } from '@earendil-works/pi-ai'\n\nexport interface PiModelConfig {\n provider: string\n model?: string\n baseUrl?: string\n maxTokens?: number\n apiFormat?: string\n}\n\nexport interface BuildPiModelOptions {\n /** Override model definition lookup (e.g. to include custom models). */\n findModelFn?: (providerId: string, modelId: string) => ModelDefinition | null\n /** Extra headers injected into the PiModel (e.g. User-Agent). */\n headers?: Record<string, string>\n}\n\n/**\n * Strip /v1 suffix from Anthropic baseUrl because the SDK\n * internally appends /v1/messages.\n */\nexport function normalizeAnthropicBaseUrl(url: string): string {\n return url.replace(/\\/v1\\/?$/, '')\n}\n\n/**\n * Auto-append /v1 to OpenAI-compatible URLs when the path is empty\n * (users frequently forget this).\n */\nexport function normalizeOpenAICompatibleBaseUrl(url: string): string {\n if (!url) return url\n const trimmed = url.replace(/\\/+$/, '')\n if (trimmed.endsWith('/v1')) return trimmed\n try {\n const parsed = new URL(trimmed)\n if (parsed.pathname === '/' || parsed.pathname === '') {\n return trimmed + '/v1'\n }\n } catch {\n // URL parse failure — return as-is\n }\n return trimmed\n}\n\nconst DEFAULT_CONTEXT_WINDOW = 128000\n\nfunction defaultFindModel(providerId: string, modelId: string): ModelDefinition | null {\n const forProvider = getBuiltinModelsByProvider(providerId)\n return forProvider.find((m) => m.id === modelId) || BUILTIN_MODELS.find((m) => m.id === modelId) || null\n}\n\nconst BUILTIN_PROVIDER_API: Record<string, PiApi> = {\n gemini: 'google-generative-ai',\n anthropic: 'anthropic-messages',\n}\n\n/**\n * Infer reasoning flag and pi-ai compat fragment for a model.\n *\n * Uses the catalog's capabilities array as the authoritative source;\n * falls back to the thinking-module's heuristic for unlisted custom models.\n * The compat fragment is sourced from getThinkingCompat() (thinking.ts) which\n * carries supportsReasoningEffort + thinkingLevelMap so pi-ai can inject the\n * correct request-body param for each provider family.\n */\ninterface InferReasoningResult {\n reasoning: boolean\n compat: PiModel<PiApi>['compat']\n thinkingLevelMap?: PiModel<PiApi>['thinkingLevelMap']\n}\n\nfunction inferReasoning(provider: string, modelId: string, modelDef: ModelDefinition | null): InferReasoningResult {\n // Builtin catalog models: trust capabilities array exclusively.\n // Custom (builtin=false) or unlisted models: also check name heuristic as fallback because\n // auto-saved custom model entries carry only capabilities:['chat'] even for reasoning models.\n const reasoning =\n modelDef?.capabilities.includes('reasoning') || (!modelDef?.builtin && isReasoningModel(provider, modelId))\n\n if (!reasoning) return { reasoning: false, compat: undefined }\n\n const thinkingCompat = getThinkingCompat(provider, modelId)\n if (Object.keys(thinkingCompat).length === 0) {\n return { reasoning: true, compat: undefined }\n }\n\n // thinkingLevelMap belongs on the Model top-level (for clampThinkingLevel),\n // while thinkingFormat/supportsReasoningEffort belong in compat (for the provider).\n const { thinkingLevelMap, ...compatFields } = thinkingCompat\n return {\n reasoning: true,\n compat: Object.keys(compatFields).length > 0 ? compatFields : undefined,\n thinkingLevelMap: thinkingLevelMap as PiModel<PiApi>['thinkingLevelMap'],\n }\n}\n\nexport function buildPiModel(config: PiModelConfig, options?: BuildPiModelOptions): PiModel<PiApi> {\n const providerDef = BUILTIN_PROVIDERS.find((p) => p.id === config.provider)\n const baseUrl = config.baseUrl || providerDef?.defaultBaseUrl || ''\n const modelId = config.model || ''\n\n const findModel = options?.findModelFn ?? defaultFindModel\n const modelDef = findModel(config.provider, modelId)\n const contextWindow = modelDef?.contextWindow ?? DEFAULT_CONTEXT_WINDOW\n\n const apiFormat: PiApi = (config.apiFormat as PiApi) || BUILTIN_PROVIDER_API[config.provider] || 'openai-completions'\n\n if (apiFormat === 'google-generative-ai') {\n return {\n id: modelId,\n name: modelId,\n api: 'google-generative-ai',\n provider: 'google',\n baseUrl,\n reasoning: isReasoningModel(config.provider, modelId),\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow,\n maxTokens: config.maxTokens ?? 8192,\n }\n }\n\n if (apiFormat === 'anthropic-messages') {\n return {\n id: modelId,\n name: modelId,\n api: 'anthropic-messages',\n provider: 'anthropic',\n baseUrl: normalizeAnthropicBaseUrl(baseUrl),\n reasoning: false,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow,\n maxTokens: config.maxTokens ?? 8192,\n }\n }\n\n const resolvedBaseUrl =\n config.provider === 'openai-compatible' && (apiFormat === 'openai-completions' || apiFormat === 'openai-responses')\n ? normalizeOpenAICompatibleBaseUrl(baseUrl)\n : baseUrl\n\n const { reasoning, compat, thinkingLevelMap } = inferReasoning(config.provider, modelId, modelDef)\n\n return {\n id: modelId,\n name: modelId,\n api: apiFormat,\n provider: config.provider,\n baseUrl: resolvedBaseUrl,\n headers: options?.headers,\n reasoning,\n thinkingLevelMap,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow,\n maxTokens: config.maxTokens ?? 4096,\n compat,\n }\n}\n","/**\n * Remote LLM API operations (platform-agnostic).\n *\n * - fetchRemoteModels: list available models from a provider's API\n * - validateApiKey: send a minimal completion request to verify key validity\n */\n\nimport { BUILTIN_PROVIDERS } from '@openchatlab/core'\nimport { completeSimple } from '@earendil-works/pi-ai'\nimport { buildPiModel, normalizeOpenAICompatibleBaseUrl, type PiModelConfig } from './llm-builder'\nimport type { Model as PiModel, Api as PiApi } from '@earendil-works/pi-ai'\n\nexport interface RemoteModel {\n id: string\n name: string\n ownedBy?: string\n contextWindow?: number\n}\n\nexport interface FetchRemoteModelsResult {\n success: boolean\n models?: RemoteModel[]\n error?: string\n}\n\nexport interface RemoteApiOptions {\n /** Extra headers for outgoing requests (e.g. User-Agent). */\n headers?: Record<string, string>\n /** Optional logger. */\n onLog?: (level: 'info' | 'error', tag: string, message: string, data?: unknown) => void\n}\n\nexport async function fetchRemoteModels(\n provider: string,\n apiKey: string,\n baseUrl?: string,\n apiFormat?: string,\n options?: RemoteApiOptions\n): Promise<FetchRemoteModelsResult> {\n const effectiveApiFormat = apiFormat || 'openai-completions'\n\n if (effectiveApiFormat === 'anthropic-messages') {\n return { success: false, error: 'Anthropic does not support model listing via API' }\n }\n\n const providerDef = BUILTIN_PROVIDERS.find((p) => p.id === provider)\n const rawBaseUrl = baseUrl || providerDef?.defaultBaseUrl || ''\n if (!rawBaseUrl) {\n return { success: false, error: 'No base URL provided' }\n }\n\n const abortController = new AbortController()\n const timeout = setTimeout(() => abortController.abort(), 15000)\n\n try {\n let url: string\n const headers: Record<string, string> = { ...options?.headers }\n\n if (effectiveApiFormat === 'google-generative-ai') {\n const trimmed = rawBaseUrl.replace(/\\/+$/, '').replace(/\\/v1(beta)?$/, '')\n url = `${trimmed}/v1beta/models?key=${apiKey}`\n } else {\n const resolved = normalizeOpenAICompatibleBaseUrl(rawBaseUrl)\n url = `${resolved}/models`\n headers['Authorization'] = `Bearer ${apiKey}`\n }\n\n options?.onLog?.('info', 'LLM', 'Fetching remote models', {\n url: url.replace(/key=[^&]+/, 'key=***'),\n provider,\n })\n\n const response = await fetch(url, {\n method: 'GET',\n headers,\n signal: abortController.signal,\n })\n\n if (!response.ok) {\n const body = await response.text().catch(() => '')\n return { success: false, error: `HTTP ${response.status}: ${body.slice(0, 200)}` }\n }\n\n const json = await response.json()\n\n let models: RemoteModel[]\n\n if (effectiveApiFormat === 'google-generative-ai') {\n const geminiModels = (json.models || []) as Array<{\n name?: string\n displayName?: string\n inputTokenLimit?: number\n }>\n models = geminiModels.map((m) => {\n const id = (m.name || '').replace(/^models\\//, '')\n return {\n id,\n name: m.displayName || id,\n ownedBy: 'google',\n contextWindow: m.inputTokenLimit || undefined,\n }\n })\n } else {\n const data = (json.data || []) as Array<{\n id?: string\n owned_by?: string\n context_length?: number\n }>\n models = data\n .filter((m) => m.id)\n .map((m) => ({\n id: m.id!,\n name: m.id!,\n ownedBy: m.owned_by,\n contextWindow: m.context_length || undefined,\n }))\n }\n\n options?.onLog?.('info', 'LLM', `Fetched ${models.length} remote models`, { provider })\n return { success: true, models }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (message.includes('aborted') || message.includes('AbortError')) {\n return { success: false, error: 'Request timed out (15s)' }\n }\n return { success: false, error: message }\n } finally {\n clearTimeout(timeout)\n }\n}\n\nexport async function validateApiKey(\n provider: string,\n apiKey: string,\n baseUrl?: string,\n model?: string,\n apiFormat?: string,\n options?: RemoteApiOptions\n): Promise<{ success: boolean; error?: string }> {\n const providerDef = BUILTIN_PROVIDERS.find((p) => p.id === provider)\n const defaultModel = providerDef?.modelIds?.[0]\n\n const config: PiModelConfig = {\n provider,\n model: model || defaultModel,\n baseUrl,\n apiFormat,\n }\n const piModel: PiModel<PiApi> = buildPiModel(config, { headers: options?.headers })\n\n const abortController = new AbortController()\n const timeout = setTimeout(() => abortController.abort(), 15000)\n\n try {\n const result = await completeSimple(\n piModel,\n { messages: [{ role: 'user', content: 'Hi', timestamp: Date.now() }] as any },\n { apiKey, maxTokens: 1, signal: abortController.signal }\n )\n if (result.stopReason === 'error' || result.stopReason === 'aborted') {\n return { success: false, error: result.errorMessage || 'Connection failed' }\n }\n return { success: true }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (message.includes('aborted') || message.includes('AbortError')) {\n return { success: false, error: 'Request timed out (15s)' }\n }\n return { success: false, error: message }\n } finally {\n clearTimeout(timeout)\n }\n}\n","/**\n * Shared LLM simple streaming — used by both Electron and Server.\n *\n * Wraps pi-ai's streamSimple with message preprocessing,\n * event mapping, and error handling.\n */\n\nimport { streamSimple } from '@earendil-works/pi-ai'\nimport type { Model as PiModel, Api as PiApi, Message as PiMessage } from '@earendil-works/pi-ai'\n\nexport interface LlmStreamChunk {\n content: string\n isFinished: boolean\n finishReason?: 'stop' | 'length' | 'error'\n error?: string\n /** When present, this chunk carries thinking/reasoning content rather than final text. */\n thinking?: string\n /** Signals the end of a thinking block (thinking is empty string, thinkingDone is true). */\n thinkingDone?: boolean\n}\n\nexport interface RunSimpleLlmStreamOptions {\n messages: Array<{ role: string; content: string }>\n apiKey: string\n piModel: PiModel<PiApi>\n temperature?: number\n maxTokens?: number\n onChunk: (chunk: LlmStreamChunk) => void\n abortSignal?: AbortSignal\n}\n\nfunction toPiMessages(messages: Array<{ role: string; content: string }>, timestamp: number): PiMessage[] {\n return messages.map((m) => ({\n role: m.role as 'user' | 'assistant',\n content: m.content,\n timestamp,\n })) as unknown as PiMessage[]\n}\n\n/**\n * Run a simple LLM streaming call.\n * Separates system message, streams text deltas via onChunk callback.\n */\nexport async function runSimpleLlmStream(options: RunSimpleLlmStreamOptions): Promise<void> {\n const { messages, apiKey, piModel, temperature, maxTokens, onChunk, abortSignal } = options\n\n const systemMsg = messages.find((m) => m.role === 'system')\n const nonSystemMsgs = messages.filter((m) => m.role !== 'system')\n const now = Date.now()\n\n const eventStream = streamSimple(\n piModel,\n {\n systemPrompt: systemMsg?.content,\n messages: toPiMessages(nonSystemMsgs, now),\n },\n { apiKey, temperature, maxTokens, signal: abortSignal }\n )\n\n let hasTerminalChunk = false\n\n try {\n for await (const event of eventStream) {\n if (abortSignal?.aborted) {\n if (!hasTerminalChunk) {\n hasTerminalChunk = true\n onChunk({ content: '', isFinished: true, finishReason: 'stop' })\n }\n return\n }\n\n if (event.type === 'thinking_start') {\n onChunk({ content: '', isFinished: false, thinking: '' })\n continue\n }\n\n if (event.type === 'thinking_delta') {\n onChunk({ content: '', isFinished: false, thinking: event.delta })\n continue\n }\n\n if (event.type === 'thinking_end') {\n onChunk({ content: '', isFinished: false, thinking: '', thinkingDone: true })\n continue\n }\n\n if (event.type === 'text_delta') {\n onChunk({ content: event.delta, isFinished: false })\n continue\n }\n\n if (event.type === 'done') {\n hasTerminalChunk = true\n onChunk({ content: '', isFinished: true, finishReason: event.reason === 'length' ? 'length' : 'stop' })\n return\n }\n\n if (event.type === 'error') {\n hasTerminalChunk = true\n const errorMsg =\n event.error?.content\n ?.filter((c) => c.type === 'text')\n .map((c) => ('text' in c ? c.text : ''))\n .join('') || 'Unknown LLM error'\n onChunk({ content: '', isFinished: true, finishReason: 'error', error: errorMsg })\n return\n }\n }\n\n if (!hasTerminalChunk) {\n onChunk({ content: '', isFinished: true, finishReason: 'stop' })\n }\n } catch (error) {\n if (!hasTerminalChunk) {\n const msg = error instanceof Error ? error.message : String(error)\n onChunk({ content: '', isFinished: true, finishReason: 'error', error: msg })\n }\n }\n}\n","/**\n * AI 模块(Node.js 实现)\n *\n * 助手/技能 MD 文件解析器、共享类型、对话管理、Agent Core。\n */\n\n// AI Logger\nexport { AiLogger, extractErrorInfo, extractErrorStack } from './ai-logger'\n\n// Error formatting\nexport { formatAIError } from './error-formatter'\nexport type { FormatAIErrorOptions } from './error-formatter'\n\nexport type { AssistantConfig, AssistantSummary, SkillDef, SkillSummary } from './types'\nexport { parseAssistantFile, serializeAssistant } from './assistant-parser'\n\n// Assistant Manager\nexport { AssistantManager } from './assistant-manager'\nexport type {\n AssistantInitResult,\n AssistantSaveResult,\n BuiltinAssistantInfo,\n AssistantManagerFs,\n AssistantManagerDeps,\n} from './assistant-manager'\nexport { parseSkillFile, extractSkillId } from './skill-parser'\nexport { AIChatManager } from './chats'\nexport type { AIChat, AIMessage, AIMessageRole, ContentBlock, TokenUsageData, AIChatManagerLogger } from './chats'\n\n// Tokenizer\nexport { countTokens, countMessagesTokens, initTokenizer } from './tokenizer'\n\n// SkillManager (runtime: activate-skill tool builder)\nexport { SkillManager } from './skill-manager'\n\n// SkillManagerCore (CRUD, shared)\nexport { SkillManagerCore } from './skill-manager-core'\nexport type {\n SkillInitResult,\n SkillSaveResult as SkillManagerSaveResult,\n BuiltinSkillInfo,\n SkillManagerFs,\n SkillManagerCoreDeps,\n} from './skill-manager-core'\nexport type { SkillManagerLogger } from './skill-manager'\nexport { createActivateSkillTool } from './activate-skill-tool'\nexport type { ActivateSkillToolOptions, ActivateSkillTool, ActivateSkillToolResult } from './activate-skill-tool'\nexport {\n CHART_CAPABILITY_ANALYSIS_TOOLS,\n CHART_CAPABILITY_CORE_TOOLS,\n CHART_CAPABILITY_SKILL_ID,\n buildSkillMenuWithBuiltinChart,\n getAllowedBuiltinToolsForChartAutoSkill,\n getChartCapabilityAllowedBuiltinTools,\n getChartCapabilitySkill,\n getChartCapabilitySkill as getBuiltinChartSkill,\n getChartPlannerCapabilityForMessage,\n getSkillConfigWithBuiltinChart,\n resolveChartRuntimeForRequest,\n shouldOfferChartCapabilityForAnalyticalMessage,\n shouldUseChartCapabilityForMessage,\n} from './chart-runtime'\nexport { CHART_SCHEMA_REQUIRED_MESSAGE, createChartSchemaGateState, wrapWithChartSchemaGate } from './chart-schema-gate'\nexport type { ChartSchemaGateState } from './chart-schema-gate'\n\n// Compression\nexport type { CompressionConfig, CompressionResult, CompressionLogger, CompressionLlmAdapter } from './compression'\nexport { checkAndCompress, manualCompress, createCompressionLlmAdapter } from './compression'\nexport type { CreateCompressionLlmAdapterOptions } from './compression'\n\n// Preprocessor\nexport type {\n PreprocessConfig,\n PreprocessableMessage,\n DesensitizeRule,\n DesensitizeRuleGroup,\n TruncationStrategy,\n PreprocessLogger,\n} from './preprocessor'\nexport {\n preprocessMessages,\n BUILTIN_DESENSITIZE_RULES,\n DESENSITIZE_RULES_SCHEMA_VERSION,\n applyDesensitizeRuleOverrides,\n getDefaultRulesForLocale,\n getRuleGroupsForLocale,\n mergeRulesForLocale,\n formatMessageCompact,\n formatTimeRange,\n formatToolResultAsText,\n anonymizeMessageNames,\n truncateFormattedMessages,\n isChineseLocale,\n i18nTexts,\n t,\n applyPreprocessingPipeline,\n} from './preprocessor'\nexport type { PreprocessingPipelineOptions, PreprocessingPipelineResult } from './preprocessor'\n\n// Agent Core\nexport type { AgentCoreOptions, AgentCoreEvent, AgentCoreResult, AgentTokenUsage, SimpleHistoryMessage } from './agent'\nexport { DEFAULT_MAX_TOOL_ROUNDS, createLlmRouteDecider, decideRequestRoute, runAgentCore } from './agent'\nexport type { LlmRouteDecider, RequestRoute, RouteDecision, RouteDecisionSource, RouterInput } from './agent'\nexport { buildPlanGuidance, createAnalysisPlanner, createPlanContentBlock } from './agent'\nexport { createDataSnapshotFromOverview } from './agent'\nexport { buildSemanticSearchGuidance } from './agent'\nexport type {\n AnalysisPlanIntent,\n AnalysisPlanner,\n AnalysisPlanStep,\n AnalysisPlanSummary,\n PlannerCapabilitySummary,\n PlannerInput,\n PlanContentBlock,\n PlanDraftContentBlock,\n ChatOverviewForSnapshot,\n} from './agent'\n\n// Agent Event Handler\nexport { AgentEventHandler, estimateTokensFromText } from './agent/event-handler'\nexport type {\n TokenUsage,\n AgentRuntimeStatus,\n AgentStreamChunk,\n EventHandlerConfig,\n EventHandlerContext,\n} from './agent/event-handler'\n\n// Agent Prompt Builder\nexport { buildSystemPrompt } from './agent/prompt-builder'\nexport type {\n BuildSystemPromptOptions,\n DataSnapshot,\n OwnerInfo,\n MentionedMember,\n SkillContext,\n TranslateFn,\n} from './agent/prompt-builder'\n\n// AI i18n (shared translations for agent prompts and tool descriptions)\nexport { createAiTranslate, aiLocales } from './i18n'\n\n// Summary generation\nexport {\n generateSessionSummary,\n generateSessionSummaries,\n checkSessionsCanGenerateSummary,\n isValidMessage,\n filterValidMessages,\n splitIntoSegments,\n} from './summary'\nexport type { SummaryDeps, SummaryMessage, SummaryOptions, SummaryResult, SummaryStrategy } from './summary'\n\n// LLM Config Store\nexport { LLMConfigStore, MAX_CONFIG_COUNT } from './llm-config-store'\nexport type { AIServiceConfig, AIConfigStore, ConfigStorage, LLMConfigStoreDeps } from './llm-config-store'\n\n// Custom Provider/Model Store\nexport { CustomProviderStore, CustomModelStore } from './custom-store'\n\n// LLM Model Builder\nexport { buildPiModel, normalizeAnthropicBaseUrl, normalizeOpenAICompatibleBaseUrl } from './llm-builder'\nexport type { PiModelConfig, BuildPiModelOptions } from './llm-builder'\n\n// Remote LLM API\nexport { fetchRemoteModels, validateApiKey } from './remote-api'\nexport type { RemoteModel, FetchRemoteModelsResult, RemoteApiOptions } from './remote-api'\n\n// Re-exports from @earendil-works/pi-agent-core\nexport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core'\n\n// LLM simple streaming\nexport { runSimpleLlmStream } from './llm-stream'\nexport type { LlmStreamChunk, RunSimpleLlmStreamOptions } from './llm-stream'\n\n// Re-exports from @earendil-works/pi-ai\nexport { Type, completeSimple, streamSimple } from '@earendil-works/pi-ai'\nexport type {\n Model as PiModel,\n Api as PiApi,\n Message as PiMessage,\n Usage as PiUsage,\n TextContent as PiTextContent,\n AssistantMessage as PiAssistantMessage,\n} from '@earendil-works/pi-ai'\n","/**\n * Unified application logger (platform-agnostic, Node side).\n *\n * Shared by Electron main process, CLI and CLI Web runtime. Writes general\n * application/diagnostic logs to a single rolling file `<logsDir>/app.log`.\n *\n * Scope of this logger: general app + key-path + crash logs. AI logs (AiLogger)\n * and import perf logs keep their own per-scenario files.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { extractErrorInfo, extractErrorStack } from '../ai/ai-logger'\n\ntype LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'\n\nconst LEVEL_ORDER: Record<LogLevel, number> = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 }\n\n// Rotate when app.log reaches this size; old content moves to app.old.log.\nconst MAX_SIZE_BYTES = 10 * 1024 * 1024 // 10MB\n\nclass AppLogger {\n private logFile: string\n private oldLogFile: string\n private threshold: LogLevel\n\n constructor(logsDir: string) {\n this.logFile = path.join(logsDir, 'app.log')\n this.oldLogFile = path.join(logsDir, 'app.old.log')\n this.threshold = resolveThreshold()\n }\n\n debug(scope: string, message: string, data?: unknown): void {\n this.write('DEBUG', scope, message, data)\n }\n\n info(scope: string, message: string, data?: unknown): void {\n this.write('INFO', scope, message, data)\n }\n\n warn(scope: string, message: string, data?: unknown): void {\n this.write('WARN', scope, message, data)\n }\n\n /** `data` may be an Error; its name/message/stack are extracted automatically. */\n error(scope: string, message: string, data?: unknown): void {\n this.write('ERROR', scope, message, data)\n }\n\n getLogPath(): string {\n return this.logFile\n }\n\n private write(level: LogLevel, scope: string, message: string, data?: unknown): void {\n if (LEVEL_ORDER[level] < LEVEL_ORDER[this.threshold]) return\n\n let line = `[${new Date().toISOString()}] [${level}] [${scope}] ${message}`\n\n try {\n const tail = formatData(data)\n if (tail) line += `\\n${tail}`\n line += '\\n'\n ensureDir(path.dirname(this.logFile))\n this.rotateIfNeeded()\n fs.appendFileSync(this.logFile, line, 'utf-8')\n } catch (err) {\n // Logging must never break the app; surface to console only.\n console.error('[AppLogger] Failed to write log:', err)\n }\n }\n\n // ponytail: rename-based rotation, atomic, no rewrite, ~20MB total ceiling\n private rotateIfNeeded(): void {\n let size = 0\n try {\n size = fs.statSync(this.logFile).size\n } catch {\n return // file doesn't exist yet\n }\n if (size < MAX_SIZE_BYTES) return\n fs.renameSync(this.logFile, this.oldLogFile) // overwrites previous .old, atomic\n }\n}\n\nfunction resolveThreshold(): LogLevel {\n const raw = (process.env.CHATLAB_LOG_LEVEL || '').toUpperCase()\n if (raw === 'DEBUG' || raw === 'INFO' || raw === 'WARN' || raw === 'ERROR') return raw\n return 'INFO'\n}\n\nfunction formatData(data: unknown): string {\n if (data === undefined) return ''\n if (data instanceof Error) {\n const info = extractErrorInfo(data)\n const stack = extractErrorStack(data)\n return JSON.stringify(info) + (stack ? `\\n${stack}` : '')\n }\n if (typeof data === 'string') return data\n try {\n return JSON.stringify(data)\n } catch {\n return '[unserializable data]'\n }\n}\n\nfunction ensureDir(dir: string): void {\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })\n}\n\nlet instance: AppLogger | null = null\n\n/** Initialize the singleton app logger. Call once per runtime at startup. */\nexport function initAppLogger(logsDir: string): void {\n instance = new AppLogger(logsDir)\n}\n\n/**\n * The shared logger. Safe to call before init: logs are dropped silently until\n * `initAppLogger` runs (avoids ordering hazards at very early startup).\n */\nexport const appLogger = {\n debug: (scope: string, message: string, data?: unknown) => instance?.debug(scope, message, data),\n info: (scope: string, message: string, data?: unknown) => instance?.info(scope, message, data),\n warn: (scope: string, message: string, data?: unknown) => instance?.warn(scope, message, data),\n error: (scope: string, message: string, data?: unknown) => instance?.error(scope, message, data),\n getLogPath: () => instance?.getLogPath() ?? null,\n}\n","/**\n * Electron legacy data detection and migration for CLI/Node.js runtime.\n *\n * When a user who has been using the Electron desktop app installs the CLI,\n * this module detects existing Electron data and repoints config.toml\n * so that CLI sees the same databases and configuration.\n *\n * Migration behavior:\n * 1. Detect Electron legacy data directory (platform-specific)\n * 2. If databases exist there, write user_data_dir to config.toml pointing to old path\n * 3. Copy system data (ai, settings, cache, logs, temp, nlp) from old path to ~/.chatlab/\n */\n\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport * as path from 'path'\nimport { writeConfigField } from '@openchatlab/config'\n\nconst SYSTEM_SUBDIRS = ['ai', 'settings', 'cache', 'logs', 'temp', 'nlp']\nconst STORAGE_CONFIG_FILE = 'storage.json'\n\ninterface StorageConfig {\n dataDir?: string\n}\n\n/**\n * Get the Electron userData directory path (platform-specific, no Electron dependency).\n *\n * - macOS: ~/Library/Application Support/ChatLab/\n * - Windows: %APPDATA%/ChatLab/\n * - Linux: ~/.config/ChatLab/\n */\nfunction getElectronUserDataDir(): string {\n const home = os.homedir()\n switch (process.platform) {\n case 'darwin':\n return path.join(home, 'Library', 'Application Support', 'ChatLab')\n case 'win32': {\n const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming')\n return path.join(appData, 'ChatLab')\n }\n default:\n return path.join(home, '.config', 'ChatLab')\n }\n}\n\nfunction readElectronStorageConfig(electronUserData: string): StorageConfig {\n const configPath = path.join(electronUserData, STORAGE_CONFIG_FILE)\n if (!fs.existsSync(configPath)) return {}\n try {\n return JSON.parse(fs.readFileSync(configPath, 'utf-8')) as StorageConfig\n } catch {\n return {}\n }\n}\n\n/**\n * Resolve the old Electron data directory, considering storage.json custom paths.\n */\nfunction resolveElectronDataDir(): string {\n const electronUserData = getElectronUserDataDir()\n const storageConfig = readElectronStorageConfig(electronUserData)\n if (storageConfig.dataDir && path.isAbsolute(storageConfig.dataDir)) {\n return storageConfig.dataDir\n }\n return path.join(electronUserData, 'data')\n}\n\nfunction hasDatabases(dir: string): boolean {\n const dbDir = path.join(dir, 'databases')\n if (!fs.existsSync(dbDir)) return false\n try {\n return fs.readdirSync(dbDir).some((f) => f.endsWith('.db'))\n } catch {\n return false\n }\n}\n\nfunction ensureDir(dirPath: string): void {\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true })\n }\n}\n\nfunction copyDirMerge(src: string, dest: string): { copied: number; skipped: number } {\n const stats = { copied: 0, skipped: 0 }\n if (!fs.existsSync(src)) return stats\n\n ensureDir(dest)\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(src, { withFileTypes: true })\n } catch {\n return stats\n }\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name)\n const destPath = path.join(dest, entry.name)\n try {\n if (entry.isDirectory()) {\n const sub = copyDirMerge(srcPath, destPath)\n stats.copied += sub.copied\n stats.skipped += sub.skipped\n } else if (!fs.existsSync(destPath)) {\n fs.copyFileSync(srcPath, destPath)\n stats.copied++\n } else {\n stats.skipped++\n }\n } catch {\n // Skip files that fail to copy\n }\n }\n return stats\n}\n\nfunction writeMigrationLog(logDir: string, message: string): void {\n try {\n ensureDir(logDir)\n const logPath = path.join(logDir, 'app.log')\n const ts = new Date().toISOString().replace('T', ' ').slice(0, 19)\n fs.appendFileSync(logPath, `[${ts}] [MIGRATION] ${message}\\n`, 'utf-8')\n } catch {\n // Silent on log failure\n }\n}\n\nexport interface ElectronMigrationResult {\n migrated: boolean\n userDataDir?: string\n systemDirsCopied?: number\n /** true if Electron userData directory was found (indicating desktop app was used) */\n electronDetected: boolean\n error?: string\n}\n\n/**\n * Check if the Electron desktop app was previously used on this machine.\n * Examines the platform-specific Electron userData directory for signs of active use.\n */\nexport function wasElectronUsed(): boolean {\n const electronUserData = getElectronUserDataDir()\n if (!fs.existsSync(electronUserData)) return false\n\n // Electron creates these on active use (not just installation)\n return (\n fs.existsSync(path.join(electronUserData, 'Preferences')) ||\n fs.existsSync(path.join(electronUserData, 'Local Storage')) ||\n fs.existsSync(path.join(electronUserData, 'Session Storage')) ||\n fs.existsSync(path.join(electronUserData, 'data'))\n )\n}\n\n/**\n * Verify that the resolved database directory actually contains databases.\n * If the directory is empty but Electron was previously used, this indicates\n * a misconfigured data path — the user's databases are somewhere else.\n *\n * @param databaseDir - The databases/ directory path (from PathProvider.getDatabaseDir())\n *\n * Call this AFTER NodePathProvider is initialized, regardless of how\n * the data directory was resolved (config.toml, env var, migration, or default).\n */\nexport function verifyCliDataPath(databaseDir: string): boolean {\n if (fs.existsSync(databaseDir)) {\n try {\n if (fs.readdirSync(databaseDir).some((f) => f.endsWith('.db'))) return true\n } catch {\n // fall through\n }\n }\n\n // No databases found — only block if Electron was previously used\n return !wasElectronUsed()\n}\n\n/**\n * Detect and migrate Electron legacy data for CLI first-run.\n *\n * Should be called when config.toml has no user_data_dir set (i.e. first run).\n *\n * Possible outcomes:\n * - migrated=true: found Electron databases, config.toml updated\n * - migrated=false, electronDetected=false: no Electron installation found\n * - migrated=false, electronDetected=true: Electron was used but databases not found\n * (user likely has a custom data directory that we can't locate)\n */\nexport function migrateFromElectronIfNeeded(systemDir: string): ElectronMigrationResult {\n const electronDataDir = resolveElectronDataDir()\n\n if (!hasDatabases(electronDataDir)) {\n return { migrated: false, electronDetected: wasElectronUsed() }\n }\n\n console.log(`[Migration] Detected Electron data at: ${electronDataDir}`)\n\n try {\n // Step 1: Point config.toml to the Electron data directory (databases stay in place)\n writeConfigField('data', 'user_data_dir', electronDataDir)\n\n // Step 2: Copy system data from Electron dir to ~/.chatlab/ (merge, don't overwrite)\n let totalCopied = 0\n let totalSkipped = 0\n const movedDirs: string[] = []\n\n for (const subDir of SYSTEM_SUBDIRS) {\n const srcDir = path.join(electronDataDir, subDir)\n const destDir = path.join(systemDir, subDir)\n if (!fs.existsSync(srcDir)) continue\n\n const { copied, skipped } = copyDirMerge(srcDir, destDir)\n if (copied > 0 || skipped > 0) {\n movedDirs.push(subDir)\n totalCopied += copied\n totalSkipped += skipped\n console.log(`[Migration] ${subDir}: copied ${copied}, skipped ${skipped}`)\n }\n }\n\n // Step 3: Remove copied system dirs from old data path (databases stay)\n for (const subDir of movedDirs) {\n const srcDir = path.join(electronDataDir, subDir)\n try {\n fs.rmSync(srcDir, { recursive: true, force: true })\n } catch {\n // Non-critical: old dir remains but won't cause issues\n }\n }\n\n const logsDir = path.join(systemDir, 'logs')\n const summary =\n `CLI first-run: detected Electron data at ${electronDataDir}, ` +\n `pointed user_data_dir there, copied ${totalCopied} system files (${movedDirs.join(', ') || 'none'})`\n writeMigrationLog(logsDir, summary)\n console.log(`[Migration] ${summary}`)\n\n return {\n migrated: true,\n electronDetected: true,\n userDataDir: electronDataDir,\n systemDirsCopied: totalCopied,\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error)\n console.error('[Migration] Electron data migration failed:', errorMsg)\n try {\n writeMigrationLog(path.join(systemDir, 'logs'), `Electron migration failed: ${errorMsg}`)\n } catch {\n // Ignore log failure\n }\n return { migrated: false, electronDetected: true, error: errorMsg }\n }\n}\n","/**\n * Session-level JSON cache module (platform-agnostic).\n *\n * Extracted from electron/main/database/sessionCache.ts.\n * Each session has a {sessionId}.cache.json file partitioned by key.\n * Decoupled from DB schema — reads auto-rebuild on failure, no versioning needed.\n *\n * File path: {cacheDir}/{sessionId}.cache.json\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { DatabaseAdapter } from '@openchatlab/core'\n\n// ==================== Generic cache infrastructure ====================\n\ninterface CacheEntry<T = unknown> {\n data: T\n ts: number\n}\n\ntype CacheFile = Record<string, CacheEntry>\n\nexport function getCachePath(sessionId: string, cacheDir: string): string {\n return path.join(cacheDir, `${sessionId}.cache.json`)\n}\n\nfunction readCacheFile(cachePath: string): CacheFile | null {\n try {\n if (!fs.existsSync(cachePath)) return null\n return JSON.parse(fs.readFileSync(cachePath, 'utf-8')) as CacheFile\n } catch {\n return null\n }\n}\n\nfunction writeCacheFile(cachePath: string, content: CacheFile): void {\n try {\n const dir = path.dirname(cachePath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n fs.writeFileSync(cachePath, JSON.stringify(content), 'utf-8')\n } catch {\n // Write failure is non-fatal\n }\n}\n\nexport function getCache<T>(sessionId: string, key: string, cacheDir: string): T | null {\n const cachePath = getCachePath(sessionId, cacheDir)\n const file = readCacheFile(cachePath)\n if (!file || !file[key]) return null\n return file[key].data as T\n}\n\nexport function setCache<T>(sessionId: string, key: string, data: T, cacheDir: string): void {\n const cachePath = getCachePath(sessionId, cacheDir)\n const file = readCacheFile(cachePath) ?? {}\n file[key] = { data, ts: Math.floor(Date.now() / 1000) }\n writeCacheFile(cachePath, file)\n}\n\nexport function invalidateCache(sessionId: string, cacheDir: string, key?: string): void {\n const cachePath = getCachePath(sessionId, cacheDir)\n try {\n if (!key) {\n if (fs.existsSync(cachePath)) fs.unlinkSync(cachePath)\n } else {\n const file = readCacheFile(cachePath)\n if (file && file[key]) {\n delete file[key]\n writeCacheFile(cachePath, file)\n }\n }\n } catch {\n // Ignore\n }\n}\n\nexport function deleteSessionCache(sessionId: string, cacheDir: string): void {\n const cachePath = getCachePath(sessionId, cacheDir)\n try {\n if (fs.existsSync(cachePath)) fs.unlinkSync(cachePath)\n } catch {\n // Ignore\n }\n}\n\n// ==================== Overview cache (aggregate stats) ====================\n\nexport const CACHE_KEY_OVERVIEW = 'overview'\n\nexport interface OverviewCache {\n totalMessages: number\n totalMembers: number\n firstMessageTs: number | null\n lastMessageTs: number | null\n /**\n * MAX(message.id) at the time the cache was written.\n * Used as a cheap O(1) freshness fingerprint: if current MAX(id) differs\n * the cache is stale and must be recomputed. Absent in legacy cache files\n * written before this field was added; absence is treated as stale.\n */\n maxMessageId?: number\n}\n\n/**\n * Return the current MAX(message.id) for freshness checking.\n * This is O(1) via the AUTOINCREMENT rowid B-tree.\n * Returns 0 for an empty table.\n */\nfunction getMaxMessageId(db: DatabaseAdapter): number {\n const row = db.prepare('SELECT MAX(id) AS m FROM message').get() as { m: number | null }\n return row.m ?? 0\n}\n\nexport function computeAndSetOverviewCache(db: DatabaseAdapter, sessionId: string, cacheDir: string): OverviewCache {\n const msgStats = db.prepare('SELECT MIN(ts) as first_ts, MAX(ts) as last_ts FROM message').get() as {\n first_ts: number | null\n last_ts: number | null\n }\n\n const totalMessages = (\n db\n .prepare(\n `SELECT COUNT(*) as count FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE COALESCE(m.account_name, '') != '系统消息'`\n )\n .get() as { count: number }\n ).count\n\n const totalMembers = (\n db.prepare(`SELECT COUNT(*) as count FROM member WHERE COALESCE(account_name, '') != '系统消息'`).get() as {\n count: number\n }\n ).count\n\n const data: OverviewCache = {\n totalMessages,\n totalMembers,\n firstMessageTs: msgStats.first_ts,\n lastMessageTs: msgStats.last_ts,\n maxMessageId: getMaxMessageId(db),\n }\n\n setCache(sessionId, CACHE_KEY_OVERVIEW, data, cacheDir)\n computeAndSetMembersCache(db, sessionId, cacheDir)\n\n return data\n}\n\n/**\n * Cache-first overview read with fingerprint validation.\n *\n * Checks cached `maxMessageId` against the live `MAX(message.id)`. If they\n * match the cache is fresh and returned as-is (one extra O(1) query).\n * On any mismatch — new inserts, legacy cache lacking the field, or a cold\n * miss — the cache is recomputed and written before returning.\n *\n * Use this instead of bare `getCache` whenever you need a guaranteed-fresh\n * overview (e.g. AI system prompt data-snapshot construction).\n */\nexport function getValidatedOverviewCache(db: DatabaseAdapter, sessionId: string, cacheDir: string): OverviewCache {\n const cached = getCache<OverviewCache>(sessionId, CACHE_KEY_OVERVIEW, cacheDir)\n if (cached && cached.maxMessageId !== undefined && cached.maxMessageId === getMaxMessageId(db)) {\n return cached\n }\n return computeAndSetOverviewCache(db, sessionId, cacheDir)\n}\n\n// ==================== Members cache (per-member stats) ====================\n\nexport const CACHE_KEY_MEMBERS = 'members'\n\nexport interface MemberStat {\n name: string\n count: number\n}\n\nexport interface MembersCache {\n members: Record<number, MemberStat>\n}\n\nexport function computeAndSetMembersCache(db: DatabaseAdapter, sessionId: string, cacheDir: string): MembersCache {\n const rows = db\n .prepare(\n `SELECT msg.sender_id, COUNT(*) as count,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as name\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n GROUP BY msg.sender_id`\n )\n .all() as Array<{ sender_id: number; count: number; name: string }>\n\n const members: Record<number, MemberStat> = {}\n for (const row of rows) {\n members[row.sender_id] = { name: row.name, count: row.count }\n }\n\n const data: MembersCache = { members }\n setCache(sessionId, CACHE_KEY_MEMBERS, data, cacheDir)\n return data\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { PathProvider } from '@openchatlab/core'\n\nexport type RuntimeKind = 'cli' | 'desktop' | 'mcp' | 'unknown'\n\nexport interface RuntimeIdentity {\n version: string\n kind: RuntimeKind\n}\n\nexport interface DataDirCompatibilityMeta {\n formatVersion: 1\n minRuntimeVersion: string\n dataCompatibilityVersion: number\n reasons: string[]\n updatedBy: {\n runtime: RuntimeKind\n module: string\n version: string\n }\n updatedAt: number\n}\n\nexport interface RaiseDataDirCompatibilityInput {\n minRuntimeVersion: string\n dataCompatibilityVersion: number\n reason: string\n runtime: RuntimeIdentity\n module: string\n now?: () => number\n}\n\nexport interface AssertDataDirCompatibilityOptions {\n warn?: (message: string) => void\n env?: Pick<NodeJS.ProcessEnv, 'CHATLAB_ALLOW_INCOMPATIBLE_DATA_DIR'>\n}\n\ntype DataDirCompatibilityErrorCode = 'DATA_DIR_REQUIRES_NEWER_RUNTIME' | 'DATA_DIR_COMPATIBILITY_META_INVALID'\n\nexport class DataDirCompatibilityError extends Error {\n readonly code: DataDirCompatibilityErrorCode\n readonly userDataDir: string\n readonly metaPath: string\n readonly currentVersion?: string\n readonly minRuntimeVersion?: string\n readonly statusCode = 409\n\n constructor(\n code: DataDirCompatibilityErrorCode,\n message: string,\n options: {\n userDataDir: string\n metaPath: string\n currentVersion?: string\n minRuntimeVersion?: string\n cause?: unknown\n }\n ) {\n super(message, { cause: options.cause })\n this.name = 'DataDirCompatibilityError'\n this.code = code\n this.userDataDir = options.userDataDir\n this.metaPath = options.metaPath\n this.currentVersion = options.currentVersion\n this.minRuntimeVersion = options.minRuntimeVersion\n }\n}\n\nexport function readDataDirCompatibilityMeta(userDataDir: string): DataDirCompatibilityMeta | null {\n const metaPath = getMetaPath(userDataDir)\n if (!fs.existsSync(metaPath)) return null\n\n let parsed: unknown\n try {\n parsed = JSON.parse(fs.readFileSync(metaPath, 'utf-8'))\n } catch (error) {\n throw createInvalidMetaError(userDataDir, metaPath, 'Invalid data directory compatibility meta JSON.', error)\n }\n\n return validateDataDirCompatibilityMeta(parsed, userDataDir, metaPath)\n}\n\nexport function assertDataDirCompatible(\n pathProvider: PathProvider,\n runtime: RuntimeIdentity,\n options: AssertDataDirCompatibilityOptions = {}\n): void {\n const userDataDir = pathProvider.getUserDataDir()\n const meta = readDataDirCompatibilityMeta(userDataDir)\n if (!meta) return\n\n const runtimeStableVersion = normalizeRuntimeStableVersion(runtime.version)\n if (!runtimeStableVersion) {\n throw new DataDirCompatibilityError(\n 'DATA_DIR_REQUIRES_NEWER_RUNTIME',\n `ChatLab data directory compatibility check requires a valid runtime semver; current version is ${runtime.version}.`,\n {\n userDataDir,\n metaPath: getMetaPath(userDataDir),\n currentVersion: runtime.version,\n minRuntimeVersion: meta.minRuntimeVersion,\n }\n )\n }\n\n if (compareStableSemver(runtimeStableVersion, meta.minRuntimeVersion) < 0) {\n const env = options.env ?? process.env\n if (env.CHATLAB_ALLOW_INCOMPATIBLE_DATA_DIR === '1') {\n const warn = options.warn ?? console.warn\n warn(\n [\n 'CHATLAB_ALLOW_INCOMPATIBLE_DATA_DIR=1 is set. ChatLab will bypass the data directory runtime version gate.',\n `Current runtime ${runtime.kind}@${runtime.version} is below required version ${meta.minRuntimeVersion}.`,\n `Data directory: ${userDataDir}. Continuing may risk data corruption.`,\n ].join(' ')\n )\n return\n }\n\n throw new DataDirCompatibilityError(\n 'DATA_DIR_REQUIRES_NEWER_RUNTIME',\n `ChatLab data directory requires runtime version ${meta.minRuntimeVersion} or newer; current version is ${runtime.version}.`,\n {\n userDataDir,\n metaPath: getMetaPath(userDataDir),\n currentVersion: runtime.version,\n minRuntimeVersion: meta.minRuntimeVersion,\n }\n )\n }\n}\n\nexport function raiseDataDirMinRuntimeVersion(\n pathProvider: PathProvider,\n input: RaiseDataDirCompatibilityInput\n): DataDirCompatibilityMeta {\n const userDataDir = pathProvider.getUserDataDir()\n const metaPath = getMetaPath(userDataDir)\n\n assertStableVersion(input.minRuntimeVersion, userDataDir, metaPath, 'Invalid minRuntimeVersion.')\n const runtimeStableVersion = assertRuntimeComparableVersion(input.runtime.version, userDataDir, metaPath)\n if (!isRuntimeKind(input.runtime.kind)) {\n throw createInvalidMetaError(userDataDir, metaPath, 'Invalid runtime kind.')\n }\n if (!isNonEmptyString(input.module) || !isNonEmptyString(input.reason)) {\n throw createInvalidMetaError(userDataDir, metaPath, 'Invalid compatibility raise metadata.')\n }\n if (!Number.isInteger(input.dataCompatibilityVersion) || input.dataCompatibilityVersion < 0) {\n throw createInvalidMetaError(userDataDir, metaPath, 'Invalid dataCompatibilityVersion.')\n }\n\n const existing = readDataDirCompatibilityMeta(userDataDir)\n const minRuntimeVersion =\n existing && compareStableSemver(existing.minRuntimeVersion, input.minRuntimeVersion) > 0\n ? existing.minRuntimeVersion\n : input.minRuntimeVersion\n const dataCompatibilityVersion = Math.max(existing?.dataCompatibilityVersion ?? 0, input.dataCompatibilityVersion)\n const reasons = mergeReasons(existing?.reasons ?? [], input.reason)\n const now = input.now ?? (() => Math.floor(Date.now() / 1000))\n\n const nextMeta: DataDirCompatibilityMeta = {\n formatVersion: 1,\n minRuntimeVersion,\n dataCompatibilityVersion,\n reasons,\n updatedBy: {\n runtime: input.runtime.kind,\n module: input.module,\n version: runtimeStableVersion,\n },\n updatedAt: now(),\n }\n\n writeMetaAtomic(userDataDir, metaPath, nextMeta)\n return nextMeta\n}\n\nfunction getMetaPath(userDataDir: string): string {\n return path.join(userDataDir, '.chatlab-meta.json')\n}\n\nfunction validateDataDirCompatibilityMeta(\n value: unknown,\n userDataDir: string,\n metaPath: string\n): DataDirCompatibilityMeta {\n if (!isRecord(value)) {\n throw createInvalidMetaError(userDataDir, metaPath, 'Data directory compatibility meta must be an object.')\n }\n\n if (value.formatVersion !== 1) {\n throw createInvalidMetaError(userDataDir, metaPath, 'Unsupported data directory compatibility meta format.')\n }\n\n const minRuntimeVersion = value.minRuntimeVersion\n const dataCompatibilityVersion = value.dataCompatibilityVersion\n const reasons = value.reasons\n const updatedBy = value.updatedBy\n const updatedAt = value.updatedAt\n\n if (\n !isStableSemver(minRuntimeVersion) ||\n typeof dataCompatibilityVersion !== 'number' ||\n !Number.isInteger(dataCompatibilityVersion) ||\n dataCompatibilityVersion < 0 ||\n !Array.isArray(reasons) ||\n !reasons.every(isNonEmptyString) ||\n !isRecord(updatedBy) ||\n !isRuntimeKind(updatedBy.runtime) ||\n !isNonEmptyString(updatedBy.module) ||\n !isStableSemver(updatedBy.version) ||\n typeof updatedAt !== 'number' ||\n !Number.isInteger(updatedAt) ||\n updatedAt < 0\n ) {\n throw createInvalidMetaError(userDataDir, metaPath, 'Invalid data directory compatibility meta fields.')\n }\n\n return {\n formatVersion: 1,\n minRuntimeVersion,\n dataCompatibilityVersion,\n reasons: [...reasons],\n updatedBy: {\n runtime: updatedBy.runtime,\n module: updatedBy.module,\n version: updatedBy.version,\n },\n updatedAt,\n }\n}\n\nfunction createInvalidMetaError(\n userDataDir: string,\n metaPath: string,\n message: string,\n cause?: unknown\n): DataDirCompatibilityError {\n return new DataDirCompatibilityError('DATA_DIR_COMPATIBILITY_META_INVALID', message, {\n userDataDir,\n metaPath,\n cause,\n })\n}\n\nfunction assertStableVersion(version: string, userDataDir: string, metaPath: string, message: string): void {\n if (!isStableSemver(version)) {\n throw createInvalidMetaError(userDataDir, metaPath, message)\n }\n}\n\nfunction isStableSemver(version: unknown): version is string {\n return typeof version === 'string' && /^\\d+\\.\\d+\\.\\d+$/.test(version)\n}\n\nfunction assertRuntimeComparableVersion(version: string, userDataDir: string, metaPath: string): string {\n const normalized = normalizeRuntimeStableVersion(version)\n if (!normalized) {\n throw createInvalidMetaError(userDataDir, metaPath, 'Invalid runtime version.')\n }\n return normalized\n}\n\n// 当前运行时允许使用 beta/rc 等 prerelease;数据目录门禁比较和 meta 写入只使用稳定 core 版本。\nfunction normalizeRuntimeStableVersion(version: string): string | null {\n const match = /^v?(\\d+\\.\\d+\\.\\d+)(-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$/.exec(version.trim())\n if (!match) return null\n if (match[1] === '0.0.0' && match[2]) return null\n return match[1]\n}\n\nfunction compareStableSemver(left: string, right: string): number {\n const leftParts = left.split('.').map(Number)\n const rightParts = right.split('.').map(Number)\n\n for (let i = 0; i < 3; i += 1) {\n if (leftParts[i] > rightParts[i]) return 1\n if (leftParts[i] < rightParts[i]) return -1\n }\n\n return 0\n}\n\nfunction mergeReasons(existing: string[], next: string): string[] {\n return [...new Set([...existing, next])]\n}\n\nfunction writeMetaAtomic(userDataDir: string, metaPath: string, meta: DataDirCompatibilityMeta): void {\n fs.mkdirSync(userDataDir, { recursive: true })\n const tempPath = path.join(userDataDir, `.chatlab-meta.json.${process.pid}.${Date.now()}.tmp`)\n try {\n fs.writeFileSync(tempPath, `${JSON.stringify(meta, null, 2)}\\n`, 'utf-8')\n fs.renameSync(tempPath, metaPath)\n } catch (error) {\n try {\n if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath)\n } catch {\n // Ignore cleanup failures so callers see the original write/rename error.\n }\n throw error\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\nfunction isRuntimeKind(value: unknown): value is RuntimeKind {\n return value === 'cli' || value === 'desktop' || value === 'mcp' || value === 'unknown'\n}\n","/**\n * Chat session DB migration definitions (platform-agnostic).\n *\n * Extracted from electron/main/database/migrations.ts.\n * Migration scripts use only DatabaseAdapter — no Electron or Node-specific APIs.\n * Version 4 (FTS backfill) requires a tokenizer injected via MigrationDeps.\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport type { PathProvider } from '@openchatlab/core'\nimport type { Migration as CoreMigration } from '@openchatlab/core'\nimport { raiseDataDirMinRuntimeVersion, type RuntimeIdentity } from '../data-dir-compat'\n\nexport interface MigrationDeps {\n /** FTS tokenizer — needed by v4 migration for backfilling the FTS index */\n tokenizeForFts?: (content: string) => string | null\n}\n\nexport interface ChatDbCompatibilityRaise {\n migrationVersion: number\n minRuntimeVersion: string\n dataCompatibilityVersion: number\n reason: string\n module: string\n}\n\nexport const CHAT_DB_COMPATIBILITY_RAISES: ChatDbCompatibilityRaise[] = [\n {\n migrationVersion: 6,\n minRuntimeVersion: '0.25.1',\n dataCompatibilityVersion: 1,\n reason: 'segment-schema',\n module: 'chat-db-migration',\n },\n]\n\nexport function raiseChatDbCompatibilityGate(pathProvider: PathProvider, runtime: RuntimeIdentity): void {\n for (const compatibilityRaise of CHAT_DB_COMPATIBILITY_RAISES) {\n raiseDataDirMinRuntimeVersion(pathProvider, {\n minRuntimeVersion: compatibilityRaise.minRuntimeVersion,\n dataCompatibilityVersion: compatibilityRaise.dataCompatibilityVersion,\n reason: compatibilityRaise.reason,\n runtime,\n module: compatibilityRaise.module,\n })\n }\n}\n\n/**\n * Build the chat DB migration list.\n *\n * @param deps Optional dependencies (tokenizer for FTS backfill)\n * @returns Array of migrations compatible with core `runMigrations`\n */\nexport function getChatDbMigrations(deps?: MigrationDeps): CoreMigration[] {\n const hasColumn = (db: DatabaseAdapter, tableName: string, columnName: string): boolean => {\n const tableInfo = db.pragma(`table_info(${tableName})`) as Array<{ name: string }>\n return tableInfo.some((col) => col.name === columnName)\n }\n\n const addColumnIfMissing = (db: DatabaseAdapter, tableName: string, columnName: string, definition: string): void => {\n if (!hasColumn(db, tableName, columnName)) {\n db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${definition}`)\n }\n }\n\n return [\n {\n version: 1,\n description: 'Add owner_id column to meta',\n up: (db: DatabaseAdapter) => {\n const tableInfo = db.pragma('table_info(meta)') as Array<{ name: string }>\n if (!tableInfo.some((col) => col.name === 'owner_id')) {\n db.exec('ALTER TABLE meta ADD COLUMN owner_id TEXT')\n }\n },\n },\n {\n version: 2,\n description: 'Add roles, reply_to_message_id, platform_message_id columns',\n up: (db: DatabaseAdapter) => {\n const memberTableInfo = db.pragma('table_info(member)') as Array<{ name: string }>\n if (!memberTableInfo.some((col) => col.name === 'roles')) {\n db.exec(\"ALTER TABLE member ADD COLUMN roles TEXT DEFAULT '[]'\")\n }\n\n const messageTableInfo = db.pragma('table_info(message)') as Array<{ name: string }>\n\n if (!messageTableInfo.some((col) => col.name === 'reply_to_message_id')) {\n db.exec('ALTER TABLE message ADD COLUMN reply_to_message_id TEXT DEFAULT NULL')\n }\n\n if (!messageTableInfo.some((col) => col.name === 'platform_message_id')) {\n db.exec('ALTER TABLE message ADD COLUMN platform_message_id TEXT DEFAULT NULL')\n }\n\n try {\n db.exec('CREATE INDEX IF NOT EXISTS idx_message_platform_id ON message(platform_message_id)')\n } catch {\n // Index may already exist\n }\n },\n },\n {\n version: 3,\n description: 'Add chat_session and message_context tables',\n up: (db: DatabaseAdapter) => {\n db.exec(`\n CREATE TABLE IF NOT EXISTS chat_session (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n start_ts INTEGER NOT NULL,\n end_ts INTEGER NOT NULL,\n message_count INTEGER DEFAULT 0,\n is_manual INTEGER DEFAULT 0,\n summary TEXT\n )\n `)\n\n try {\n db.exec('CREATE INDEX IF NOT EXISTS idx_session_time ON chat_session(start_ts, end_ts)')\n } catch {\n // Index may already exist\n }\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS message_context (\n message_id INTEGER PRIMARY KEY,\n session_id INTEGER NOT NULL,\n topic_id INTEGER\n )\n `)\n\n try {\n db.exec('CREATE INDEX IF NOT EXISTS idx_context_session ON message_context(session_id)')\n } catch {\n // Index may already exist\n }\n\n const tableInfo = db.pragma('table_info(meta)') as Array<{ name: string }>\n if (!tableInfo.some((col) => col.name === 'session_gap_threshold')) {\n db.exec('ALTER TABLE meta ADD COLUMN session_gap_threshold INTEGER')\n }\n },\n },\n {\n version: 4,\n description: 'Add FTS5 full-text search index',\n up: (db: DatabaseAdapter) => {\n const hasTable = db.prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'\").get()\n if (hasTable) return\n\n db.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts5(\n content,\n content='',\n content_rowid=id\n )\n `)\n\n const tokenize = deps?.tokenizeForFts\n if (!tokenize) return\n\n const BATCH_SIZE = 5000\n const insertFts = db.prepare('INSERT INTO message_fts(rowid, content) VALUES (?, ?)')\n\n const countRow = db\n .prepare(\"SELECT COUNT(*) as total FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''\")\n .get() as { total: number } | undefined\n\n const total = countRow?.total ?? 0\n let offset = 0\n while (offset < total) {\n const rows = db\n .prepare(\n `SELECT id, content FROM message\n WHERE type = 0 AND content IS NOT NULL AND content != ''\n ORDER BY id ASC LIMIT ? OFFSET ?`\n )\n .all(BATCH_SIZE, offset) as Array<{ id: number; content: string }>\n\n if (rows.length === 0) break\n\n for (const row of rows) {\n const tokens = tokenize(row.content)\n if (tokens) {\n insertFts.run(row.id, tokens)\n }\n }\n\n offset += BATCH_SIZE\n }\n },\n },\n {\n version: 5,\n description: 'Repair legacy member/message columns',\n up: (db: DatabaseAdapter) => {\n addColumnIfMissing(db, 'meta', 'group_id', 'TEXT')\n addColumnIfMissing(db, 'meta', 'group_avatar', 'TEXT')\n addColumnIfMissing(db, 'meta', 'owner_id', 'TEXT')\n addColumnIfMissing(db, 'meta', 'session_gap_threshold', 'INTEGER')\n\n const memberHadName = hasColumn(db, 'member', 'name')\n const memberHadNickname = hasColumn(db, 'member', 'nickname')\n addColumnIfMissing(db, 'member', 'account_name', 'TEXT')\n addColumnIfMissing(db, 'member', 'group_nickname', 'TEXT')\n addColumnIfMissing(db, 'member', 'aliases', \"TEXT DEFAULT '[]'\")\n addColumnIfMissing(db, 'member', 'avatar', 'TEXT')\n addColumnIfMissing(db, 'member', 'roles', \"TEXT DEFAULT '[]'\")\n\n if (memberHadName) {\n db.exec(\"UPDATE member SET account_name = COALESCE(NULLIF(account_name, ''), name)\")\n }\n if (memberHadNickname) {\n db.exec(\"UPDATE member SET group_nickname = COALESCE(NULLIF(group_nickname, ''), nickname)\")\n }\n db.exec(\"UPDATE member SET aliases = COALESCE(aliases, '[]'), roles = COALESCE(roles, '[]')\")\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS member_name_history (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n member_id INTEGER NOT NULL,\n name_type TEXT DEFAULT 'account_name',\n name TEXT NOT NULL,\n start_ts INTEGER NOT NULL,\n end_ts INTEGER,\n FOREIGN KEY(member_id) REFERENCES member(id)\n )\n `)\n addColumnIfMissing(db, 'member_name_history', 'name_type', \"TEXT DEFAULT 'account_name'\")\n addColumnIfMissing(db, 'message', 'sender_account_name', 'TEXT')\n addColumnIfMissing(db, 'message', 'sender_group_nickname', 'TEXT')\n addColumnIfMissing(db, 'message', 'reply_to_message_id', 'TEXT DEFAULT NULL')\n addColumnIfMissing(db, 'message', 'platform_message_id', 'TEXT DEFAULT NULL')\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS chat_session (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n start_ts INTEGER NOT NULL,\n end_ts INTEGER NOT NULL,\n message_count INTEGER DEFAULT 0,\n is_manual INTEGER DEFAULT 0,\n summary TEXT\n )\n `)\n db.exec(`\n CREATE TABLE IF NOT EXISTS message_context (\n message_id INTEGER PRIMARY KEY,\n session_id INTEGER NOT NULL,\n topic_id INTEGER\n )\n `)\n\n db.exec('CREATE INDEX IF NOT EXISTS idx_message_platform_id ON message(platform_message_id)')\n db.exec('CREATE INDEX IF NOT EXISTS idx_member_name_history_member_id ON member_name_history(member_id)')\n db.exec('CREATE INDEX IF NOT EXISTS idx_session_time ON chat_session(start_ts, end_ts)')\n db.exec('CREATE INDEX IF NOT EXISTS idx_context_session ON message_context(session_id)')\n },\n },\n {\n version: 6,\n description: 'Rename chat_session index tables to segment terminology',\n up: (db: DatabaseAdapter) => {\n const hasTable = (tableName: string): boolean => {\n const row = db.prepare(\"SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?\").get(tableName) as\n | Record<string, unknown>\n | undefined\n return !!row\n }\n\n db.exec(`\n DROP INDEX IF EXISTS idx_session_time;\n DROP INDEX IF EXISTS idx_context_session;\n `)\n\n const hasLegacyChatSession = hasTable('chat_session')\n const hasSegment = hasTable('segment')\n\n if (hasLegacyChatSession && !hasSegment) {\n db.exec('ALTER TABLE chat_session RENAME TO segment')\n } else if (hasLegacyChatSession && hasSegment) {\n db.exec(`\n INSERT OR IGNORE INTO segment (id, start_ts, end_ts, message_count, is_manual, summary)\n SELECT id, start_ts, end_ts, message_count, is_manual, summary\n FROM chat_session;\n DROP TABLE chat_session;\n `)\n }\n\n if (hasTable('message_context') && hasColumn(db, 'message_context', 'session_id')) {\n db.exec('ALTER TABLE message_context RENAME COLUMN session_id TO segment_id')\n }\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS segment (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n start_ts INTEGER NOT NULL,\n end_ts INTEGER NOT NULL,\n message_count INTEGER DEFAULT 0,\n is_manual INTEGER DEFAULT 0,\n summary TEXT\n );\n\n CREATE TABLE IF NOT EXISTS message_context (\n message_id INTEGER PRIMARY KEY,\n segment_id INTEGER NOT NULL,\n topic_id INTEGER\n );\n\n CREATE INDEX IF NOT EXISTS idx_segment_time ON segment(start_ts, end_ts);\n CREATE INDEX IF NOT EXISTS idx_context_segment ON message_context(segment_id);\n `)\n },\n },\n {\n version: 7,\n description: 'Repair fully missing segment message contexts',\n up: (db: DatabaseAdapter) => {\n const counts = db\n .prepare(\n `SELECT\n (SELECT COUNT(*) FROM message) AS messageCount,\n (SELECT COUNT(*) FROM segment) AS segmentCount,\n (SELECT COUNT(*) FROM message_context) AS contextCount,\n (SELECT COALESCE(SUM(message_count), 0) FROM segment) AS declaredMessageCount`\n )\n .get() as\n | {\n messageCount: number\n segmentCount: number\n contextCount: number\n declaredMessageCount: number\n }\n | undefined\n\n if (!counts || counts.messageCount === 0 || counts.segmentCount === 0 || counts.contextCount > 0) {\n return\n }\n\n if (counts.declaredMessageCount !== counts.messageCount) {\n throw new Error(\n 'Cannot safely repair missing message_context rows: segment message counts do not match message table'\n )\n }\n\n const invalidRange = db\n .prepare(\n `WITH ordered_segments AS (\n SELECT\n id,\n start_ts,\n end_ts,\n LAG(end_ts) OVER (ORDER BY start_ts, id) AS previousEndTs\n FROM segment\n )\n SELECT COUNT(*) AS count\n FROM ordered_segments\n WHERE start_ts > end_ts OR (previousEndTs IS NOT NULL AND start_ts <= previousEndTs)`\n )\n .get() as { count: number } | undefined\n if ((invalidRange?.count ?? 0) > 0) {\n throw new Error(\n 'Cannot safely repair missing message_context rows: segment time ranges are invalid or overlapping'\n )\n }\n\n const mismatchedSegments = db\n .prepare(\n `SELECT COUNT(*) AS count\n FROM (\n SELECT s.id\n FROM segment s\n LEFT JOIN message m ON m.ts >= s.start_ts AND m.ts <= s.end_ts\n GROUP BY s.id\n HAVING COUNT(m.id) != s.message_count\n )`\n )\n .get() as { count: number } | undefined\n if ((mismatchedSegments?.count ?? 0) > 0) {\n throw new Error(\n 'Cannot safely repair missing message_context rows: messages do not match segment time ranges'\n )\n }\n\n const result = db\n .prepare(\n `INSERT INTO message_context (message_id, segment_id, topic_id)\n SELECT m.id, s.id, NULL\n FROM segment s\n JOIN message m ON m.ts >= s.start_ts AND m.ts <= s.end_ts`\n )\n .run()\n if (result.changes !== counts.messageCount) {\n throw new Error('Cannot safely repair missing message_context rows: rebuilt row count is inconsistent')\n }\n },\n },\n {\n version: 8,\n description: 'Add analysis tool performance indexes',\n up: (db: DatabaseAdapter) => {\n addColumnIfMissing(db, 'message', 'reply_to_message_id', 'TEXT DEFAULT NULL')\n\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_message_sender_ts ON message(sender_id, ts);\n CREATE INDEX IF NOT EXISTS idx_message_type_ts ON message(type, ts);\n CREATE INDEX IF NOT EXISTS idx_message_reply_to ON message(reply_to_message_id);\n `)\n },\n },\n ]\n}\n","/**\n * 数据库连接管理器\n *\n * 管理 ChatLab 会话数据库的打开、缓存与关闭。\n * 等效于 electron/main/worker/core/dbCore.ts 中的 dbCache 机制,\n * 但基于 DatabaseAdapter 接口而非直接使用 better-sqlite3。\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { DatabaseAdapter, PathProvider } from '@openchatlab/core'\nimport {\n CHAT_DB_TABLES,\n isChatSessionDb,\n runMigrations as coreRunMigrations,\n needsMigration as coreNeedsMigration,\n CURRENT_SCHEMA_VERSION,\n getSchemaVersion,\n} from '@openchatlab/core'\nimport { openBetterSqliteDatabase } from './better-sqlite3-adapter'\nimport { deleteSessionCache } from './cache/session-cache'\nimport { assertDataDirCompatible, type RuntimeIdentity } from './data-dir-compat'\nimport {\n CHAT_DB_COMPATIBILITY_RAISES,\n getChatDbMigrations,\n raiseChatDbCompatibilityGate,\n type MigrationDeps,\n} from './migrations'\nimport { tokenizeForFts } from './nlp/fts-tokenizer'\n\nfunction createMigrationDeps(overrides?: MigrationDeps): MigrationDeps {\n return {\n tokenizeForFts,\n ...overrides,\n }\n}\n\ninterface DatabaseManagerOptions {\n nativeBinding?: string\n migrationDeps?: MigrationDeps\n runtime?: RuntimeIdentity\n allowMissingRuntimeForTests?: boolean\n}\n\ninterface OpenRawSessionDatabaseOptions {\n readonly?: boolean\n create?: boolean\n initializeChatTables?: boolean\n}\n\nexport class DatabaseManager {\n private cache = new Map<string, DatabaseAdapter>()\n private nativeBinding?: string\n private migrationDeps?: MigrationDeps\n private runtime: RuntimeIdentity | null\n\n constructor(\n private pathProvider: PathProvider,\n options: DatabaseManagerOptions = {}\n ) {\n if (!options.runtime && !options.allowMissingRuntimeForTests) {\n throw new Error('DatabaseManager runtime identity is required for data directory compatibility checks.')\n }\n\n this.nativeBinding = options?.nativeBinding\n this.migrationDeps = createMigrationDeps(options?.migrationDeps)\n this.runtime = options?.runtime ?? null\n }\n\n /**\n * Open a session DB (read-only by default, cached).\n */\n open(sessionId: string, options?: { readonly?: boolean }): DatabaseAdapter | null {\n this.assertCompatible()\n\n if (this.cache.has(sessionId)) {\n return this.cache.get(sessionId)!\n }\n\n const dbPath = this.getDbPath(sessionId)\n if (!fs.existsSync(dbPath)) return null\n\n this.migrateIfNeeded(dbPath)\n this.assertCompatible()\n\n const adapter = openBetterSqliteDatabase(dbPath, {\n readonly: options?.readonly ?? true,\n nativeBinding: this.nativeBinding,\n })\n this.cache.set(sessionId, adapter)\n return adapter\n }\n\n private migrateIfNeeded(dbPath: string): void {\n const readonlyAdapter = openBetterSqliteDatabase(dbPath, {\n readonly: true,\n nativeBinding: this.nativeBinding,\n })\n\n try {\n if (!isChatSessionDb(readonlyAdapter)) return\n this.raiseCompatibilityGateIfNeeded(getSchemaVersion(readonlyAdapter))\n if (!coreNeedsMigration(readonlyAdapter, CURRENT_SCHEMA_VERSION)) return\n } finally {\n readonlyAdapter.close()\n }\n\n const adapter = openBetterSqliteDatabase(dbPath, {\n readonly: false,\n nativeBinding: this.nativeBinding,\n })\n\n try {\n const migrations = getChatDbMigrations(this.migrationDeps)\n this.runMigrations(adapter, migrations)\n } finally {\n adapter.close()\n }\n }\n\n /**\n * Open a session DB in read-write mode and auto-run pending migrations.\n * Falls back to `open(id, { readonly: false })` if migration is unnecessary.\n */\n openWritable(sessionId: string): DatabaseAdapter | null {\n this.assertCompatible()\n\n const existing = this.cache.get(sessionId)\n if (existing && !existing.readonly) return existing\n\n if (existing) {\n existing.close()\n this.cache.delete(sessionId)\n }\n\n const dbPath = this.getDbPath(sessionId)\n if (!fs.existsSync(dbPath)) return null\n\n const adapter = openBetterSqliteDatabase(dbPath, {\n readonly: false,\n nativeBinding: this.nativeBinding,\n })\n\n if (coreNeedsMigration(adapter, CURRENT_SCHEMA_VERSION)) {\n const migrations = getChatDbMigrations(this.migrationDeps)\n this.runMigrations(adapter, migrations)\n this.assertCompatible()\n } else {\n this.raiseCompatibilityGateIfNeeded(getSchemaVersion(adapter))\n }\n\n this.cache.set(sessionId, adapter)\n return adapter\n }\n\n /**\n * 关闭指定会话的数据库连接\n */\n close(sessionId: string): void {\n const adapter = this.cache.get(sessionId)\n if (adapter) {\n adapter.close()\n this.cache.delete(sessionId)\n }\n }\n\n /**\n * 关闭所有数据库连接\n */\n closeAll(): void {\n for (const [id, adapter] of this.cache) {\n adapter.close()\n this.cache.delete(id)\n }\n }\n\n /**\n * 列举数据库目录下的所有聊天会话 ID\n */\n listSessionIds(): string[] {\n this.assertCompatible()\n\n const dbDir = this.pathProvider.getDatabaseDir()\n if (!fs.existsSync(dbDir)) return []\n\n return fs\n .readdirSync(dbDir)\n .filter((f) => f.endsWith('.db'))\n .map((f) => f.replace('.db', ''))\n .filter((id) => {\n const db = this.open(id)\n if (!db) return false\n return isChatSessionDb(db)\n })\n }\n\n /**\n * 获取数据库文件路径\n */\n getDbPath(sessionId: string): string {\n return path.join(this.pathProvider.getDatabaseDir(), `${sessionId}.db`)\n }\n\n openRawSessionDatabase(sessionId: string, options: OpenRawSessionDatabaseOptions = {}): DatabaseAdapter {\n this.assertCompatible()\n\n const dbPath = this.getDbPath(sessionId)\n if (!options.create && !fs.existsSync(dbPath)) {\n throw new Error(`Session database not found: ${sessionId}`)\n }\n\n if (!options.readonly) {\n this.close(sessionId)\n fs.mkdirSync(path.dirname(dbPath), { recursive: true })\n }\n\n const adapter = openBetterSqliteDatabase(dbPath, {\n readonly: options.readonly ?? false,\n nativeBinding: this.nativeBinding,\n })\n\n if (options.initializeChatTables) {\n adapter.exec(CHAT_DB_TABLES)\n }\n\n return adapter\n }\n\n /**\n * 删除一个会话在本地留下的完整文件集合。\n * SQLite WAL 模式会产生 sidecar 文件,前端查询还会写 JSON 缓存;只删主库会让列表和后续同步读到脏状态。\n */\n deleteSessionDatabaseFiles(sessionId: string): boolean {\n this.close(sessionId)\n\n const dbPath = this.getDbPath(sessionId)\n const existed = ['', '-wal', '-shm'].some((suffix) => fs.existsSync(dbPath + suffix))\n for (const suffix of ['', '-wal', '-shm']) {\n try {\n const filePath = dbPath + suffix\n if (fs.existsSync(filePath)) fs.unlinkSync(filePath)\n } catch {\n /* ignore cleanup failures */\n }\n }\n const cacheDir = this.pathProvider.getCacheDir()\n deleteSessionCache(sessionId, cacheDir)\n deleteSessionCache(sessionId, path.join(cacheDir, 'query'))\n return existed\n }\n\n raiseCurrentChatDbCompatibilityGate(): void {\n if (!this.runtime) return\n raiseChatDbCompatibilityGate(this.pathProvider, this.runtime)\n }\n\n private assertCompatible(): void {\n if (!this.runtime) return\n assertDataDirCompatible(this.pathProvider, this.runtime)\n }\n\n private runMigrations(adapter: DatabaseAdapter, migrations: ReturnType<typeof getChatDbMigrations>): void {\n const beforeVersion = getSchemaVersion(adapter)\n const migrated = coreRunMigrations(adapter, migrations)\n if (!migrated || !this.runtime) return\n\n const afterVersion = getSchemaVersion(adapter)\n if (shouldRaiseCompatibilityGate(beforeVersion, afterVersion)) {\n this.raiseCurrentChatDbCompatibilityGate()\n }\n }\n\n private raiseCompatibilityGateIfNeeded(schemaVersion: number): void {\n if (!this.runtime) return\n if (shouldRepairCompatibilityGate(schemaVersion)) {\n this.raiseCurrentChatDbCompatibilityGate()\n }\n }\n}\n\nfunction shouldRaiseCompatibilityGate(beforeVersion: number, afterVersion: number): boolean {\n return CHAT_DB_COMPATIBILITY_RAISES.some(\n (compatibilityRaise) =>\n beforeVersion < compatibilityRaise.migrationVersion && afterVersion >= compatibilityRaise.migrationVersion\n )\n}\n\nfunction shouldRepairCompatibilityGate(schemaVersion: number): boolean {\n return CHAT_DB_COMPATIBILITY_RAISES.some((compatibilityRaise) => schemaVersion >= compatibilityRaise.migrationVersion)\n}\n","/**\n * Markdown export engine (platform-agnostic).\n *\n * Extracted from electron/main/worker/query/session/export.ts.\n * Exports filtered chat messages to Markdown format via a writer abstraction.\n * Supports condition-based filtering (keyword/sender/time) and session-based export.\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\n\n// ==================== Public interfaces ====================\n\nexport interface ExportFilterParams {\n sessionId: string\n sessionName: string\n filterMode: 'condition' | 'session'\n keywords?: string[]\n timeFilter?: { startTs: number; endTs: number }\n senderIds?: number[]\n contextSize?: number\n segmentIds?: number[]\n}\n\nexport interface ExportProgress {\n stage: 'preparing' | 'exporting' | 'done' | 'error'\n currentBlock: number\n totalBlocks: number\n percentage: number\n message: string\n}\n\nexport type ExportProgressCallback = (progress: ExportProgress) => void\n\nexport interface ExportWriter {\n write(chunk: string): void\n end(): void\n}\n\nexport interface ExportDeps {\n openDatabase(sessionId: string): DatabaseAdapter | null\n onProgress?: ExportProgressCallback\n}\n\nexport interface ExportResult {\n success: boolean\n error?: string\n totalBlocks: number\n totalMessages: number\n}\n\n// ==================== Core export logic ====================\n\n/**\n * Export filter results to Markdown via the provided writer.\n * Platform-agnostic: no filesystem or IPC dependency.\n */\nexport function exportFilterResultToMarkdown(\n params: ExportFilterParams,\n deps: ExportDeps,\n writer: ExportWriter\n): ExportResult {\n const db = deps.openDatabase(params.sessionId)\n if (!db) {\n return { success: false, error: 'Cannot open database', totalBlocks: 0, totalMessages: 0 }\n }\n\n const progress = deps.onProgress\n\n try {\n writer.write(`# ${params.sessionName} - Chat Filter Results\\n\\n`)\n writer.write(`> Export time: ${new Date().toLocaleString()}\\n\\n`)\n\n writer.write(`## Filter Conditions\\n\\n`)\n if (params.filterMode === 'condition') {\n if (params.keywords && params.keywords.length > 0) {\n writer.write(`- Keywords: ${params.keywords.join(', ')}\\n`)\n }\n if (params.timeFilter) {\n const start = new Date(params.timeFilter.startTs * 1000).toLocaleString()\n const end = new Date(params.timeFilter.endTs * 1000).toLocaleString()\n writer.write(`- Time range: ${start} ~ ${end}\\n`)\n }\n writer.write(`- Context: ±${params.contextSize || 10} messages\\n`)\n } else {\n writer.write(`- Mode: session filter\\n`)\n writer.write(`- Selected sessions: ${params.segmentIds?.length || 0}\\n`)\n }\n writer.write('\\n')\n\n let totalMessages = 0\n let blockIndex = 0\n\n if (params.filterMode === 'condition') {\n const result = exportConditionMode(db, params, writer, progress)\n totalMessages = result.totalMessages\n blockIndex = result.blockIndex\n } else {\n const result = exportSessionMode(db, params, writer, progress)\n totalMessages = result.totalMessages\n blockIndex = result.blockIndex\n }\n\n writer.end()\n\n progress?.({\n stage: 'done',\n currentBlock: blockIndex,\n totalBlocks: blockIndex,\n percentage: 100,\n message: `Export complete, ${blockIndex} blocks`,\n })\n\n return { success: true, totalBlocks: blockIndex, totalMessages }\n } catch (error) {\n progress?.({\n stage: 'error',\n currentBlock: 0,\n totalBlocks: 0,\n percentage: 0,\n message: `Export failed: ${String(error)}`,\n })\n return { success: false, error: String(error), totalBlocks: 0, totalMessages: 0 }\n }\n}\n\n// ==================== Condition mode ====================\n\nfunction exportConditionMode(\n db: DatabaseAdapter,\n params: ExportFilterParams,\n writer: ExportWriter,\n progress?: ExportProgressCallback\n): { totalMessages: number; blockIndex: number } {\n const contextSize = params.contextSize || 10\n\n const lightweightSql = `\n SELECT id, ts, sender_id as senderId, content\n FROM message\n ${params.timeFilter ? 'WHERE ts >= ? AND ts <= ?' : ''}\n ORDER BY ts ASC, id ASC\n `\n const sqlParams: unknown[] = []\n if (params.timeFilter) {\n sqlParams.push(params.timeFilter.startTs, params.timeFilter.endTs)\n }\n\n const hitIndexes: number[] = []\n let msgIndex = 0\n const stmt = db.prepare(lightweightSql)\n const rows = stmt.all(...sqlParams) as Array<{\n id: number\n ts: number\n senderId: number\n content: string | null\n }>\n\n for (const row of rows) {\n let isHit = true\n if (params.keywords && params.keywords.length > 0) {\n const content = (row.content || '').toLowerCase()\n isHit = params.keywords.some((kw) => content.includes(kw.toLowerCase()))\n }\n if (isHit && params.senderIds && params.senderIds.length > 0) {\n isHit = params.senderIds.includes(row.senderId)\n }\n if (isHit) hitIndexes.push(msgIndex)\n msgIndex++\n }\n\n const totalHits = hitIndexes.length\n\n progress?.({\n stage: 'preparing',\n currentBlock: 0,\n totalBlocks: 0,\n percentage: 10,\n message: `Analyzing: found ${totalHits} matching messages...`,\n })\n\n if (hitIndexes.length === 0) {\n writer.write(`## Statistics\\n\\n- No matches\\n`)\n writer.end()\n return { totalMessages: 0, blockIndex: 0 }\n }\n\n const totalMsgCount = msgIndex\n const ranges: Array<{ start: number; end: number; hitIndexes: number[] }> = []\n for (const hitIdx of hitIndexes) {\n const start = Math.max(0, hitIdx - contextSize)\n const end = Math.min(totalMsgCount - 1, hitIdx + contextSize)\n if (ranges.length > 0) {\n const last = ranges[ranges.length - 1]\n if (start <= last.end + 1) {\n last.end = Math.max(last.end, end)\n last.hitIndexes.push(hitIdx)\n continue\n }\n }\n ranges.push({ start, end, hitIndexes: [hitIdx] })\n }\n\n const totalBlocks = ranges.length\n progress?.({\n stage: 'exporting',\n currentBlock: 0,\n totalBlocks,\n percentage: 15,\n message: `Exporting ${totalBlocks} conversation blocks...`,\n })\n\n writer.write(`## Statistics\\n\\n- Blocks: ${totalBlocks}\\n- Hits: ${totalHits}\\n\\n`)\n writer.write(`## Conversation Content\\n\\n`)\n\n let totalMessages = 0\n let blockIndex = 0\n\n for (const range of ranges) {\n blockIndex++\n progress?.({\n stage: 'exporting',\n currentBlock: blockIndex,\n totalBlocks,\n percentage: Math.round(15 + ((blockIndex - 1) / totalBlocks) * 80),\n message: `Exporting block ${blockIndex}/${totalBlocks}...`,\n })\n\n const blockSql = `\n SELECT msg.id, msg.ts,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,\n msg.content\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${params.timeFilter ? 'WHERE msg.ts >= ? AND msg.ts <= ?' : ''}\n ORDER BY msg.ts ASC, msg.id ASC\n LIMIT ? OFFSET ?\n `\n const blockParams: unknown[] = []\n if (params.timeFilter) {\n blockParams.push(params.timeFilter.startTs, params.timeFilter.endTs)\n }\n blockParams.push(range.end - range.start + 1, range.start)\n\n const messages = db.prepare(blockSql).all(...blockParams) as Array<{\n id: number\n ts: number\n senderName: string\n content: string | null\n }>\n\n if (messages.length === 0) continue\n\n const hitIndexSet = new Set(range.hitIndexes.map((idx) => idx - range.start))\n const startTime = new Date(messages[0].ts * 1000).toLocaleString()\n const endTime = new Date(messages[messages.length - 1].ts * 1000).toLocaleString()\n writer.write(`### Block ${blockIndex} (${startTime} ~ ${endTime})\\n\\n`)\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i]\n const time = new Date(msg.ts * 1000).toLocaleTimeString()\n const hitMark = hitIndexSet.has(i) ? ' ⭐' : ''\n writer.write(`${time} ${msg.senderName}${hitMark}: ${msg.content || '[non-text message]'}\\n`)\n totalMessages++\n }\n writer.write('\\n')\n }\n\n return { totalMessages, blockIndex }\n}\n\n// ==================== Session mode ====================\n\nfunction exportSessionMode(\n db: DatabaseAdapter,\n params: ExportFilterParams,\n writer: ExportWriter,\n progress?: ExportProgressCallback\n): { totalMessages: number; blockIndex: number } {\n if (!params.segmentIds || params.segmentIds.length === 0) {\n writer.write(`## Statistics\\n\\n- No sessions selected\\n`)\n writer.end()\n return { totalMessages: 0, blockIndex: 0 }\n }\n\n progress?.({\n stage: 'preparing',\n currentBlock: 0,\n totalBlocks: params.segmentIds.length,\n percentage: 10,\n message: `Preparing to export ${params.segmentIds.length} sessions...`,\n })\n\n const sessionsSql = `\n SELECT id, start_ts as startTs, end_ts as endTs\n FROM segment\n WHERE id IN (${params.segmentIds.map(() => '?').join(',')})\n ORDER BY start_ts ASC\n `\n const sessions = db.prepare(sessionsSql).all(...params.segmentIds) as Array<{\n id: number\n startTs: number\n endTs: number\n }>\n\n const totalBlocks = sessions.length\n writer.write(`## Statistics\\n\\n- Blocks: ${totalBlocks}\\n\\n`)\n writer.write(`## Conversation Content\\n\\n`)\n\n const messagesSql = `\n SELECT msg.id,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,\n msg.content,\n msg.ts as timestamp\n FROM message_context mc\n JOIN message msg ON msg.id = mc.message_id\n JOIN member m ON msg.sender_id = m.id\n WHERE mc.segment_id = ?\n ORDER BY msg.ts ASC\n `\n\n let totalMessages = 0\n let blockIndex = 0\n\n for (const session of sessions) {\n blockIndex++\n progress?.({\n stage: 'exporting',\n currentBlock: blockIndex,\n totalBlocks,\n percentage: Math.round(15 + ((blockIndex - 1) / totalBlocks) * 80),\n message: `Exporting session ${blockIndex}/${totalBlocks}...`,\n })\n\n const messages = db.prepare(messagesSql).all(session.id) as Array<{\n id: number\n senderName: string\n content: string | null\n timestamp: number\n }>\n\n if (messages.length === 0) continue\n\n const startTime = new Date(session.startTs * 1000).toLocaleString()\n const endTime = new Date(session.endTs * 1000).toLocaleString()\n writer.write(`### Block ${blockIndex} (${startTime} ~ ${endTime})\\n\\n`)\n\n for (const msg of messages) {\n const time = new Date(msg.timestamp * 1000).toLocaleTimeString()\n writer.write(`${time} ${msg.senderName}: ${msg.content || '[non-text message]'}\\n`)\n totalMessages++\n }\n writer.write('\\n')\n }\n\n return { totalMessages, blockIndex }\n}\n","/**\n * Multi-format export: txt / json / markdown.\n * Simplified for time-range-only filtering (no keyword/context block logic).\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\n\nexport type ExportFormat = 'txt' | 'json' | 'markdown'\n\nexport interface FormatExportParams {\n sessionId: string\n sessionName: string\n format: ExportFormat\n timeFilter?: { startTs: number; endTs: number }\n}\n\nexport interface FormatExportResult {\n success: boolean\n error?: string\n totalMessages: number\n content: string\n filename: string\n mimeType: string\n}\n\ninterface MessageRow {\n ts: number\n senderName: string\n content: string | null\n}\n\nfunction queryMessages(db: DatabaseAdapter, timeFilter?: { startTs: number; endTs: number }): MessageRow[] {\n const hasFilter = !!timeFilter\n const sql = `\n SELECT msg.ts,\n COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,\n msg.content\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ${hasFilter ? 'WHERE msg.ts >= ? AND msg.ts <= ?' : ''}\n ORDER BY msg.ts ASC, msg.id ASC\n `\n const params: unknown[] = []\n if (hasFilter) {\n params.push(timeFilter!.startTs, timeFilter!.endTs)\n }\n return db.prepare(sql).all(...params) as unknown as MessageRow[]\n}\n\nfunction formatTime(ts: number): string {\n return new Date(ts * 1000).toLocaleString()\n}\n\nfunction formatTimeShort(ts: number): string {\n return new Date(ts * 1000).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })\n}\n\nfunction exportAsTxt(messages: MessageRow[], sessionName: string): string {\n const lines: string[] = [`${sessionName}\\n`]\n let lastDate = ''\n for (const msg of messages) {\n const date = new Date(msg.ts * 1000).toLocaleDateString()\n if (date !== lastDate) {\n lines.push(`\\n--- ${date} ---\\n`)\n lastDate = date\n }\n lines.push(`${formatTimeShort(msg.ts)} ${msg.senderName}: ${msg.content || '[non-text]'}`)\n }\n return lines.join('\\n')\n}\n\nfunction exportAsJson(messages: MessageRow[], sessionName: string): string {\n const data = {\n sessionName,\n exportTime: new Date().toISOString(),\n totalMessages: messages.length,\n messages: messages.map((msg) => ({\n timestamp: msg.ts,\n time: formatTime(msg.ts),\n sender: msg.senderName,\n content: msg.content || '',\n })),\n }\n return JSON.stringify(data, null, 2)\n}\n\nfunction exportAsMarkdown(messages: MessageRow[], sessionName: string): string {\n const lines: string[] = [`# ${sessionName}\\n`, `> Export time: ${new Date().toLocaleString()}\\n`]\n let lastDate = ''\n for (const msg of messages) {\n const date = new Date(msg.ts * 1000).toLocaleDateString()\n if (date !== lastDate) {\n lines.push(`\\n## ${date}\\n`)\n lastDate = date\n }\n lines.push(`**${formatTimeShort(msg.ts)} ${msg.senderName}**: ${msg.content || '*[non-text]*'}`)\n }\n return lines.join('\\n')\n}\n\nconst FORMAT_CONFIG: Record<ExportFormat, { ext: string; mime: string }> = {\n txt: { ext: 'txt', mime: 'text/plain; charset=utf-8' },\n json: { ext: 'json', mime: 'application/json; charset=utf-8' },\n markdown: { ext: 'md', mime: 'text/markdown; charset=utf-8' },\n}\n\nexport function exportWithFormat(\n params: FormatExportParams,\n openDatabase: (sessionId: string) => DatabaseAdapter | null\n): FormatExportResult {\n const db = openDatabase(params.sessionId)\n if (!db) {\n return { success: false, error: 'Cannot open database', totalMessages: 0, content: '', filename: '', mimeType: '' }\n }\n\n try {\n const messages = queryMessages(db, params.timeFilter)\n if (messages.length === 0) {\n return {\n success: false,\n error: 'No messages found in the specified range',\n totalMessages: 0,\n content: '',\n filename: '',\n mimeType: '',\n }\n }\n\n const { ext, mime } = FORMAT_CONFIG[params.format]\n let content: string\n switch (params.format) {\n case 'txt':\n content = exportAsTxt(messages, params.sessionName)\n break\n case 'json':\n content = exportAsJson(messages, params.sessionName)\n break\n case 'markdown':\n content = exportAsMarkdown(messages, params.sessionName)\n break\n }\n\n const timestamp = Date.now()\n const filename = `${params.sessionName}_export_${timestamp}.${ext}`\n return { success: true, totalMessages: messages.length, content, filename, mimeType: mime }\n } catch (error) {\n return {\n success: false,\n error: String(error),\n totalMessages: 0,\n content: '',\n filename: '',\n mimeType: '',\n }\n }\n}\n","/**\n * Temporary database manager for merge operations.\n *\n * Extracted from electron/main/merger/tempCache.ts.\n * Uses DatabaseAdapter for platform-agnostic temp DB read/write.\n *\n * Callers provide a DatabaseAdapter factory; TempDbWriter/TempDbReader\n * handle schema creation and streaming reads.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport type { ParsedMember, ParsedMessage } from '@openchatlab/shared-types'\nimport type { MergerDataSource, MergerSourceMeta } from './index'\nimport type { MergerMember, MergerMessage } from '@openchatlab/core'\n\n// ==================== Temp DB schema (simplified for merge preview) ====================\n\nexport const TEMP_DB_SCHEMA = `\n CREATE TABLE IF NOT EXISTS meta (\n name TEXT NOT NULL,\n platform TEXT NOT NULL,\n type TEXT NOT NULL,\n group_id TEXT,\n group_avatar TEXT,\n owner_id TEXT\n );\n\n CREATE TABLE IF NOT EXISTS member (\n platform_id TEXT PRIMARY KEY,\n account_name TEXT,\n group_nickname TEXT,\n avatar TEXT\n );\n\n CREATE TABLE IF NOT EXISTS message (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n sender_platform_id TEXT NOT NULL,\n sender_account_name TEXT,\n sender_group_nickname TEXT,\n timestamp INTEGER NOT NULL,\n type INTEGER NOT NULL,\n content TEXT\n );\n\n CREATE INDEX IF NOT EXISTS idx_message_ts ON message(timestamp);\n CREATE INDEX IF NOT EXISTS idx_message_sender ON message(sender_platform_id);\n`\n\n// ==================== TempDbWriter ====================\n\nexport class TempDbWriter {\n private db: DatabaseAdapter\n private memberSet = new Set<string>()\n private messageCount = 0\n\n constructor(db: DatabaseAdapter) {\n this.db = db\n db.exec(TEMP_DB_SCHEMA)\n db.exec('BEGIN TRANSACTION')\n }\n\n writeMeta(meta: {\n name: string\n platform: string\n type: string | number\n groupId?: string\n groupAvatar?: string\n ownerId?: string\n }): void {\n this.db\n .prepare('INSERT INTO meta (name, platform, type, group_id, group_avatar, owner_id) VALUES (?, ?, ?, ?, ?, ?)')\n .run(meta.name, meta.platform, meta.type, meta.groupId || null, meta.groupAvatar || null, meta.ownerId || null)\n }\n\n writeMembers(members: ParsedMember[]): void {\n const insert = this.db.prepare(\n 'INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, avatar) VALUES (?, ?, ?, ?)'\n )\n for (const m of members) {\n if (!this.memberSet.has(m.platformId)) {\n this.memberSet.add(m.platformId)\n insert.run(m.platformId, m.accountName || null, m.groupNickname || null, m.avatar || null)\n }\n }\n }\n\n writeMessages(messages: ParsedMessage[]): void {\n const insert = this.db.prepare(\n `INSERT INTO message (sender_platform_id, sender_account_name, sender_group_nickname, timestamp, type, content)\n VALUES (?, ?, ?, ?, ?, ?)`\n )\n const memberInsert = this.db.prepare(\n 'INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, avatar) VALUES (?, ?, ?, ?)'\n )\n for (const msg of messages) {\n if (!this.memberSet.has(msg.senderPlatformId)) {\n this.memberSet.add(msg.senderPlatformId)\n memberInsert.run(msg.senderPlatformId, msg.senderAccountName || null, msg.senderGroupNickname || null, null)\n }\n insert.run(\n msg.senderPlatformId,\n msg.senderAccountName || null,\n msg.senderGroupNickname || null,\n msg.timestamp,\n msg.type,\n msg.content || null\n )\n this.messageCount++\n }\n }\n\n finish(): { messageCount: number; memberCount: number } {\n this.db.exec('COMMIT')\n const result = { messageCount: this.messageCount, memberCount: this.memberSet.size }\n this.db.close()\n return result\n }\n\n abort(): void {\n try {\n this.db.exec('ROLLBACK')\n } catch {\n /* ignore */\n }\n this.db.close()\n }\n}\n\n// ==================== TempDbReader ====================\n\nexport interface TempDbMeta {\n name: string\n platform: string\n type: string\n groupId?: string\n groupAvatar?: string\n}\n\nexport class TempDbReader {\n private db: DatabaseAdapter\n\n constructor(db: DatabaseAdapter) {\n this.db = db\n }\n\n getMeta(): TempDbMeta | null {\n const row = this.db.prepare('SELECT * FROM meta LIMIT 1').get() as\n | { name: string; platform: string; type: string; group_id: string | null; group_avatar: string | null }\n | undefined\n if (!row) return null\n return {\n name: row.name,\n platform: row.platform,\n type: row.type,\n groupId: row.group_id || undefined,\n groupAvatar: row.group_avatar || undefined,\n }\n }\n\n getMembers(): ParsedMember[] {\n const rows = this.db.prepare('SELECT * FROM member').all() as Array<{\n platform_id: string\n account_name: string | null\n group_nickname: string | null\n avatar: string | null\n }>\n return rows.map((r) => ({\n platformId: r.platform_id,\n accountName: r.account_name || r.platform_id,\n groupNickname: r.group_nickname || undefined,\n avatar: r.avatar || undefined,\n }))\n }\n\n getMessageCount(): number {\n const row = this.db.prepare('SELECT COUNT(*) as count FROM message').get() as { count: number }\n return row.count\n }\n\n streamMessages(batchSize: number, callback: (messages: ParsedMessage[]) => void): void {\n const stmt = this.db.prepare(`\n SELECT sender_platform_id, sender_account_name, sender_group_nickname, timestamp, type, content\n FROM message ORDER BY timestamp ASC LIMIT ? OFFSET ?\n `)\n\n let offset = 0\n while (true) {\n const rows = stmt.all(batchSize, offset) as Array<{\n sender_platform_id: string\n sender_account_name: string | null\n sender_group_nickname: string | null\n timestamp: number\n type: number\n content: string | null\n }>\n\n if (rows.length === 0) break\n\n const messages: ParsedMessage[] = rows.map((r) => ({\n senderPlatformId: r.sender_platform_id,\n senderAccountName: r.sender_account_name || r.sender_platform_id,\n senderGroupNickname: r.sender_group_nickname || undefined,\n timestamp: r.timestamp,\n type: r.type as ParsedMessage['type'],\n content: r.content,\n }))\n\n callback(messages)\n offset += batchSize\n }\n }\n\n getAllMessages(): ParsedMessage[] {\n const rows = this.db\n .prepare(\n `SELECT sender_platform_id, sender_account_name, sender_group_nickname, timestamp, type, content\n FROM message ORDER BY timestamp ASC`\n )\n .all() as Array<{\n sender_platform_id: string\n sender_account_name: string | null\n sender_group_nickname: string | null\n timestamp: number\n type: number\n content: string | null\n }>\n\n return rows.map((r) => ({\n senderPlatformId: r.sender_platform_id,\n senderAccountName: r.sender_account_name || r.sender_platform_id,\n senderGroupNickname: r.sender_group_nickname || undefined,\n timestamp: r.timestamp,\n type: r.type as ParsedMessage['type'],\n content: r.content,\n }))\n }\n\n close(): void {\n this.db.close()\n }\n\n /** Wrap this reader as a MergerDataSource for use with merge algorithms. */\n toDataSource(): MergerDataSource {\n return createDataSourceFromReader(this)\n }\n}\n\nfunction createDataSourceFromReader(reader: TempDbReader): MergerDataSource {\n return {\n getMeta(): MergerSourceMeta | null {\n const meta = reader.getMeta()\n if (!meta) return null\n return {\n name: meta.name,\n platform: meta.platform,\n type: meta.type,\n groupId: meta.groupId,\n groupAvatar: meta.groupAvatar,\n }\n },\n getMembers(): MergerMember[] {\n return reader.getMembers().map((m) => ({\n platformId: m.platformId,\n accountName: m.accountName,\n groupNickname: m.groupNickname,\n avatar: m.avatar,\n }))\n },\n getMessageCount(): number {\n return reader.getMessageCount()\n },\n streamMessages(batchSize: number, callback: (messages: MergerMessage[]) => void): void {\n reader.streamMessages(batchSize, (messages) => {\n callback(\n messages.map((msg) => ({\n senderPlatformId: msg.senderPlatformId,\n senderAccountName: msg.senderAccountName,\n senderGroupNickname: msg.senderGroupNickname,\n timestamp: msg.timestamp,\n type: msg.type,\n content: msg.content,\n }))\n )\n })\n },\n }\n}\n\n// ==================== Session export ====================\n\nexport interface ExportedSession {\n chatlab: { version: string; exportedAt: number; generator: string; description: string }\n meta: { name: string; platform: string; type: string; groupId?: string; groupAvatar?: string }\n members: Array<{ platformId: string; accountName: string; groupNickname?: string; avatar?: string }>\n messages: Array<{\n sender: string\n accountName: string\n groupNickname?: string\n timestamp: number\n type: number\n content: string | null\n }>\n}\n\n/**\n * Export a session DB to a ChatLab JSON structure (in memory).\n * Caller decides how to persist (write to file, send over HTTP, etc.).\n */\nexport function exportSessionToJson(db: DatabaseAdapter): ExportedSession {\n const meta = db.prepare('SELECT * FROM meta').get() as\n | { name: string; platform: string; type: string; group_id?: string; group_avatar?: string }\n | undefined\n\n if (!meta) throw new Error('Cannot read session meta')\n\n const members = db.prepare('SELECT platform_id, account_name, group_nickname, avatar FROM member').all() as Array<{\n platform_id: string\n account_name?: string\n group_nickname?: string\n avatar?: string\n }>\n\n const messages = db\n .prepare(\n `SELECT m.platform_id as sender, msg.sender_account_name as accountName,\n msg.sender_group_nickname as groupNickname, msg.ts as timestamp, msg.type, msg.content\n FROM message msg JOIN member m ON msg.sender_id = m.id ORDER BY msg.ts`\n )\n .all() as Array<{\n sender: string\n accountName?: string\n groupNickname?: string\n timestamp: number\n type: number\n content?: string\n }>\n\n return {\n chatlab: {\n version: '0.0.1',\n exportedAt: Math.floor(Date.now() / 1000),\n generator: 'ChatLab Export',\n description: `Exported from session: ${meta.name}`,\n },\n meta: {\n name: meta.name,\n platform: meta.platform,\n type: meta.type,\n groupId: meta.group_id,\n groupAvatar: meta.group_avatar,\n },\n members: members.map((m) => ({\n platformId: m.platform_id,\n accountName: m.account_name || m.platform_id,\n groupNickname: m.group_nickname || undefined,\n avatar: m.avatar,\n })),\n messages: messages.map((msg) => ({\n sender: msg.sender,\n accountName: msg.accountName || msg.sender,\n groupNickname: msg.groupNickname || undefined,\n timestamp: msg.timestamp,\n type: msg.type,\n content: msg.content ?? null,\n })),\n }\n}\n\n// ==================== Filesystem cleanup helpers ====================\n\nexport function deleteTempDatabase(dbPath: string): void {\n try {\n if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath)\n const walPath = dbPath + '-wal'\n const shmPath = dbPath + '-shm'\n if (fs.existsSync(walPath)) fs.unlinkSync(walPath)\n if (fs.existsSync(shmPath)) fs.unlinkSync(shmPath)\n } catch {\n /* best-effort cleanup */\n }\n}\n\nexport function cleanupTempDatabases(tempDir: string): void {\n try {\n if (!fs.existsSync(tempDir)) return\n const files = fs.readdirSync(tempDir)\n for (const file of files) {\n if (file.startsWith('merge_') && file.endsWith('.db')) {\n deleteTempDatabase(path.join(tempDir, file))\n }\n }\n } catch {\n /* best-effort */\n }\n}\n","/**\n * Shared session service layer.\n *\n * Provides unified session CRUD used by both CLI Web routes and Electron IPC/API.\n * All business logic (sorting, data shape, validation) lives here —\n * callers only handle protocol-specific concerns (HTTP params, IPC channels).\n */\n\nimport {\n getSessionInfo,\n getSessionMeta,\n getSummaryCount,\n getPrivateChatMemberAvatar,\n isChatSessionDb,\n buildSessionInfo,\n renameSession as coreRenameSession,\n updateSessionOwnerId as coreUpdateSessionOwnerId,\n} from '@openchatlab/core'\nimport type { CoreSessionInfo, DatabaseAdapter, SessionOverview } from '@openchatlab/core'\nimport type { SessionRuntimeAdapter } from './adapters'\n\nexport interface AnalysisSessionDTO extends CoreSessionInfo {\n id: string\n dbPath: string\n memberAvatar: string | null\n aiConversationCount: number\n}\n\n/**\n * Optional hooks for platform-specific behavior.\n * Electron can provide a cached overview resolver and post-list enrichment;\n * CLI Web can omit these to use default (no-cache) behavior.\n */\nexport interface ListSessionsOptions {\n /**\n * Resolve overview stats for a session, optionally from cache.\n * When provided, used instead of the default `getSessionOverview` SQL query.\n * Electron worker uses this to read from JSON file cache (resolveOverview).\n */\n resolveOverview?(db: DatabaseAdapter, sessionId: string): SessionOverview\n\n /**\n * Enrich each session DTO after construction.\n * Electron uses this to fill aiConversationCount, etc.\n */\n enrichSession?(dto: AnalysisSessionDTO): AnalysisSessionDTO\n}\n\nfunction buildSession(\n db: DatabaseAdapter,\n id: string,\n dbPath: string,\n options?: ListSessionsOptions\n): AnalysisSessionDTO | null {\n const meta = getSessionMeta(db)\n if (!meta) return null\n\n const overview = options?.resolveOverview ? options.resolveOverview(db, id) : undefined\n const info = overview ? buildSessionInfo(meta, overview, getSummaryCount(db)) : getSessionInfo(db)\n if (!info) return null\n\n let memberAvatar: string | null = null\n if (meta.type === 'private') {\n memberAvatar = getPrivateChatMemberAvatar(db, meta.name, meta.ownerId)\n }\n\n let dto: AnalysisSessionDTO = { ...info, id, dbPath, memberAvatar, aiConversationCount: 0 }\n if (options?.enrichSession) {\n dto = options.enrichSession(dto)\n }\n return dto\n}\n\n/**\n * List all valid sessions, sorted by importedAt descending.\n *\n * Accepts optional hooks so Electron can plug in cached overview resolution\n * and session enrichment without duplicating the listing logic.\n */\nexport function listAnalysisSessions(\n adapter: SessionRuntimeAdapter,\n options?: ListSessionsOptions\n): AnalysisSessionDTO[] {\n const sessionIds = adapter.listSessionIds()\n const sessions: AnalysisSessionDTO[] = []\n\n for (const id of sessionIds) {\n const db = adapter.openReadonly(id)\n if (!db) continue\n if (!isChatSessionDb(db)) continue\n\n const dto = buildSession(db, id, adapter.getDbPath(id), options)\n if (dto) sessions.push(dto)\n }\n\n return sessions.sort((a, b) => b.importedAt - a.importedAt)\n}\n\n/**\n * Get a single session by ID.\n */\nexport function getAnalysisSession(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n options?: ListSessionsOptions\n): AnalysisSessionDTO | null {\n const db = adapter.openReadonly(sessionId)\n if (!db) return null\n return buildSession(db, sessionId, adapter.getDbPath(sessionId), options)\n}\n\n/**\n * Rename a session (updates meta.name).\n */\nexport function renameSession(adapter: SessionRuntimeAdapter, sessionId: string, name: string): void {\n const db = adapter.ensureWritable(sessionId)\n coreRenameSession(db, name)\n}\n\n/**\n * Update session owner_id.\n */\nexport function updateSessionOwnerId(adapter: SessionRuntimeAdapter, sessionId: string, ownerId: string | null): void {\n const db = adapter.ensureWritable(sessionId)\n coreUpdateSessionOwnerId(db, ownerId)\n}\n\n/**\n * Delete a session (close DB + remove files/cache).\n * Returns true if deleted, false if file not found.\n */\nexport function deleteSession(adapter: SessionRuntimeAdapter, sessionId: string): boolean {\n return adapter.deleteSessionFile(sessionId)\n}\n","/**\n * Shared member service layer.\n *\n * Delegates to @openchatlab/core member-ops and query functions,\n * ensuring CLI Web and Electron use identical business semantics\n * (merge strategy, delete scope, name history source, etc.).\n */\n\nimport {\n getMembersWithAliases as coreGetMembersWithAliases,\n getMembersPaginated as coreGetMembersPaginated,\n getMemberNameHistory as coreGetMemberNameHistory,\n updateMemberAliases as coreUpdateMemberAliases,\n mergeMembers as coreMergeMembers,\n deleteMember as coreDeleteMember,\n} from '@openchatlab/core'\nimport type { MemberWithAliases, MembersPaginationParams, MembersPaginatedResult } from '@openchatlab/core'\nimport type { SessionRuntimeAdapter } from './adapters'\n\nexport type { MemberWithAliases, MembersPaginationParams, MembersPaginatedResult }\n\nexport interface MembersPaginatedDTO {\n items: MemberWithAliases[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\n/**\n * Get all members with aliases for a session.\n */\nexport function getMembers(adapter: SessionRuntimeAdapter, sessionId: string): MemberWithAliases[] {\n const db = adapter.ensureReadonly(sessionId)\n return coreGetMembersWithAliases(db)\n}\n\n/**\n * Get paginated member list with search and sort.\n */\nexport function getMembersPaginated(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n params: MembersPaginationParams\n): MembersPaginatedDTO {\n const db = adapter.ensureReadonly(sessionId)\n const result = coreGetMembersPaginated(db, params)\n return {\n items: result.members,\n total: result.total,\n page: result.page,\n pageSize: result.pageSize,\n totalPages: result.totalPages,\n }\n}\n\n/**\n * Update member aliases.\n * Uses core's updateMemberAliases which properly serializes the JSON array.\n */\nexport function updateMemberAliases(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n memberId: number,\n aliases: string[]\n): boolean {\n const db = adapter.ensureWritable(sessionId)\n return coreUpdateMemberAliases(db, memberId, aliases)\n}\n\n/**\n * Merge two members using core semantics:\n * - Primary determined by message count (higher wins, lower id on tie)\n * - Merges aliases, avatar, account_name, group_nickname\n * - Reassigns messages and member_name_history\n * - Updates meta.owner_id if secondary was owner\n * - Runs in a transaction\n */\nexport function mergeMembers(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n memberId1: number,\n memberId2: number\n): boolean {\n const db = adapter.ensureWritable(sessionId)\n return coreMergeMembers(db, memberId1, memberId2)\n}\n\n/**\n * Delete a member and all associated data:\n * - messages, member_name_history, member row (in transaction)\n */\nexport function deleteMember(adapter: SessionRuntimeAdapter, sessionId: string, memberId: number): boolean {\n const db = adapter.ensureWritable(sessionId)\n return coreDeleteMember(db, memberId)\n}\n\n/**\n * Get member name change history.\n * Core prefers member_name_history and falls back to message-derived names for legacy sessions.\n */\nexport function getMemberNameHistory(adapter: SessionRuntimeAdapter, sessionId: string, memberId: number) {\n const db = adapter.ensureReadonly(sessionId)\n return coreGetMemberNameHistory(db, memberId)\n}\n","/**\n * Shared session index service.\n *\n * Wraps core's session index operations with proper writable DB handling.\n */\n\nimport {\n generateSessionIndex as coreGenerateSessionIndex,\n generateIncrementalSessionIndex as coreGenerateIncrementalSessionIndex,\n clearSessionIndex as coreClearSessionIndex,\n getSessionIndexStats,\n} from '@openchatlab/core'\nimport { hasFtsTable, searchByFts, rebuildFtsIndex } from '../fts'\nimport type { SessionRuntimeAdapter } from './adapters'\n\nexport function generateIndex(adapter: SessionRuntimeAdapter, sessionId: string, gapThreshold: number = 1800): number {\n const db = adapter.ensureWritable(sessionId)\n return coreGenerateSessionIndex(db, gapThreshold)\n}\n\nexport function generateIncrementalIndex(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n gapThreshold: number = 1800\n): number {\n const db = adapter.ensureWritable(sessionId)\n return coreGenerateIncrementalSessionIndex(db, gapThreshold)\n}\n\nexport function clearIndex(adapter: SessionRuntimeAdapter, sessionId: string): void {\n const db = adapter.ensureWritable(sessionId)\n coreClearSessionIndex(db)\n}\n\nexport function getFtsStatus(adapter: SessionRuntimeAdapter, sessionId: string): boolean {\n const db = adapter.ensureReadonly(sessionId)\n return hasFtsTable(db)\n}\n\nexport function searchFts(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n keywords: string[],\n limit: number,\n offset: number\n) {\n const db = adapter.ensureReadonly(sessionId)\n return searchByFts(db, keywords, limit, offset)\n}\n\nexport function rebuildFts(adapter: SessionRuntimeAdapter, sessionId: string) {\n const db = adapter.ensureWritable(sessionId)\n return rebuildFtsIndex(db)\n}\n\nexport interface SessionIndexStatusItem {\n sessionId: string\n hasIndex: boolean\n sessionCount: number\n}\n\nexport function getAllIndexStats(adapter: SessionRuntimeAdapter): SessionIndexStatusItem[] {\n const ids = adapter.listSessionIds()\n return ids.map((sessionId) => {\n try {\n const db = adapter.ensureReadonly(sessionId)\n const stats = getSessionIndexStats(db)\n return { sessionId, hasIndex: stats.hasIndex, sessionCount: stats.sessionCount }\n } catch {\n return { sessionId, hasIndex: false, sessionCount: 0 }\n }\n })\n}\n","/**\n * Shared summary service.\n *\n * Encapsulates LLM config loading, SummaryDeps construction,\n * and summary generation loop — shared across CLI Web and Electron.\n */\n\nimport { getSegmentSummary, saveSegmentSummary, getChatSessionList, getSegmentMessages } from '@openchatlab/core'\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport {\n generateSessionSummary,\n checkSessionsCanGenerateSummary,\n type SummaryDeps,\n type SummaryOptions,\n completeSimple,\n type PiTextContent,\n} from '../ai'\nimport type { SessionRuntimeAdapter } from './adapters'\n\nexport interface LlmConfig {\n apiKey: string\n}\n\nexport interface SummaryServiceDeps {\n getLlmConfig(): LlmConfig | null\n buildPiModel(config: LlmConfig): ReturnType<typeof import('../ai').buildPiModel>\n}\n\nfunction buildSummaryDeps(db: DatabaseAdapter, llmConfig: LlmConfig, deps: SummaryServiceDeps): SummaryDeps {\n const piModel = deps.buildPiModel(llmConfig)\n return {\n loadMessages(segmentId, limit = 500) {\n const data = getSegmentMessages(db, segmentId, limit)\n if (!data) return null\n return data.messages.map((m) => ({ senderName: m.senderName, content: m.content }))\n },\n saveSummary(segmentId, summary) {\n saveSegmentSummary(db, segmentId, summary)\n },\n getSummary(segmentId) {\n return getSegmentSummary(db, segmentId)\n },\n async llmComplete(systemPrompt, userPrompt, options) {\n const result = await completeSimple(\n piModel,\n {\n systemPrompt,\n messages: [{ role: 'user', content: [{ type: 'text', text: userPrompt }], timestamp: Date.now() }] as any,\n },\n { apiKey: llmConfig.apiKey, maxTokens: options?.maxTokens, temperature: options?.temperature }\n )\n return result.content\n .filter((item): item is PiTextContent => item.type === 'text')\n .map((item) => item.text)\n .join('')\n },\n t: (key: string) => key,\n }\n}\n\nexport async function generateSummary(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n segmentId: number,\n serviceDeps: SummaryServiceDeps,\n options?: SummaryOptions\n) {\n const llmConfig = serviceDeps.getLlmConfig()\n if (!llmConfig) {\n return { success: false as const, error: 'No LLM configuration available' }\n }\n\n const db = adapter.ensureWritable(sessionId)\n const deps = buildSummaryDeps(db, llmConfig, serviceDeps)\n return generateSessionSummary(deps, segmentId, options)\n}\n\nexport async function generateAllSummaries(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n serviceDeps: SummaryServiceDeps,\n options?: SummaryOptions\n) {\n const llmConfig = serviceDeps.getLlmConfig()\n if (!llmConfig) {\n return { success: 0, failed: 0, total: 0, error: 'No LLM configuration available' }\n }\n\n const db = adapter.ensureWritable(sessionId)\n const chatSessions = getChatSessionList(db)\n const deps = buildSummaryDeps(db, llmConfig, serviceDeps)\n\n let success = 0\n let failed = 0\n\n for (const cs of chatSessions) {\n const result = await generateSessionSummary(deps, cs.id, options)\n if (result.success) success++\n else failed++\n }\n\n return { success, failed, total: chatSessions.length }\n}\n\nexport function checkCanGenerate(\n adapter: SessionRuntimeAdapter,\n sessionId: string,\n segmentIds: number[]\n): Record<number, { canGenerate: boolean; reason?: string }> {\n const db = adapter.ensureReadonly(sessionId)\n const deps: Pick<SummaryDeps, 'loadMessages' | 't'> = {\n loadMessages(segmentId, limit = 500) {\n const data = getSegmentMessages(db, segmentId, limit)\n if (!data) return null\n return data.messages.map((m) => ({ senderName: m.senderName, content: m.content }))\n },\n t: (key: string) => key,\n }\n const resultMap = checkSessionsCanGenerateSummary(deps, segmentIds)\n const result: Record<number, { canGenerate: boolean; reason?: string }> = {}\n for (const [id, info] of resultMap) {\n result[id] = info\n }\n return result\n}\n","/**\n * Shared export service.\n *\n * Wraps node-runtime's exporters with adapter-based DB opening.\n */\n\nimport { exportFilterResultToMarkdown, exportWithFormat, type ExportFilterParams, type ExportResult } from '../export'\nimport type { ExportFormat, FormatExportResult } from '../export'\nimport type { SessionRuntimeAdapter } from './adapters'\n\nexport function exportMarkdown(\n adapter: SessionRuntimeAdapter,\n params: ExportFilterParams\n): { result: ExportResult; content: string } {\n const chunks: string[] = []\n const result = exportFilterResultToMarkdown(\n params,\n {\n openDatabase(sessionId: string) {\n return adapter.openReadonly(sessionId)\n },\n },\n {\n write(chunk: string) {\n chunks.push(chunk)\n },\n end() {\n /* collected in chunks array */\n },\n }\n )\n return { result, content: chunks.join('') }\n}\n\nexport function exportFormatted(\n adapter: SessionRuntimeAdapter,\n params: {\n sessionId: string\n sessionName: string\n format: ExportFormat\n timeFilter?: { startTs: number; endTs: number }\n }\n): FormatExportResult {\n return exportWithFormat(params, (sessionId) => adapter.openReadonly(sessionId))\n}\n","/**\n * Shared owner profile service.\n *\n * Manages the platform-level \"who am I\" identity: writes per-session\n * meta.owner_id, maintains ownerProfilesByPlatform in preferences.json, and\n * applies a confirmed profile to other unowned sessions of the same platform.\n * Used by both CLI Web routes and Electron IPC.\n */\n\nimport {\n getSessionMeta,\n getMembersWithAliases,\n isChatSessionDb,\n updateSessionOwnerId as coreUpdateSessionOwnerId,\n matchOwnerProfile,\n mergeConfirmedNames,\n isNameMatchPlatform,\n} from '@openchatlab/core'\nimport type { MemberWithAliases } from '@openchatlab/core'\nimport type {\n OwnerProfile,\n ApplyOwnerProfileReason,\n ApplyOwnerProfileResult,\n SetOwnerAndApplyProfileResult,\n} from '@openchatlab/shared-types'\nimport type { PreferencesManager } from '../preferences'\nimport type { SessionRuntimeAdapter } from './adapters'\n\nexport type { ApplyOwnerProfileReason, ApplyOwnerProfileResult, SetOwnerAndApplyProfileResult }\n\nfunction memberDisplayName(member: MemberWithAliases): string {\n return member.groupNickname || member.accountName || member.platformId\n}\n\nfunction isDismissed(preferences: PreferencesManager, sessionId: string): boolean {\n return preferences.load().ownerPromptDismissedSessionIds.includes(sessionId)\n}\n\n/**\n * Try to apply the stored platform owner profile to one session.\n * Writes meta.owner_id only on a unique (exact or name) match.\n * Never overrides an existing owner.\n */\nexport function tryApplyOwnerProfile(\n adapter: SessionRuntimeAdapter,\n preferences: PreferencesManager,\n sessionId: string\n): ApplyOwnerProfileResult {\n const dismissed = isDismissed(preferences, sessionId)\n\n const db = adapter.openReadonly(sessionId)\n if (!db || !isChatSessionDb(db)) {\n return { applied: false, reason: 'missing_session', dismissed }\n }\n const meta = getSessionMeta(db)\n if (!meta) {\n return { applied: false, reason: 'missing_session', dismissed }\n }\n if (meta.ownerId) {\n return { applied: false, ownerId: meta.ownerId, reason: 'already_set', dismissed }\n }\n\n const profile = preferences.load().ownerProfilesByPlatform[meta.platform]\n if (!profile) {\n return { applied: false, reason: 'no_profile', dismissed }\n }\n\n const result = matchOwnerProfile(meta.platform, profile, getMembersWithAliases(db))\n if (result.type === 'exact' || result.type === 'name') {\n const writable = adapter.ensureWritable(sessionId)\n coreUpdateSessionOwnerId(writable, result.platformId)\n return { applied: true, ownerId: result.platformId, dismissed }\n }\n return { applied: false, reason: result.type === 'ambiguous' ? 'ambiguous' : 'no_match', dismissed }\n}\n\n/**\n * Manually set the owner of one session, update the platform profile, and\n * batch-apply the profile to other unowned sessions of the same platform.\n *\n * Re-selecting the same identity merges new names into confirmedNames;\n * selecting a different member replaces the profile (names start fresh,\n * so the previous person's names cannot cause wrong matches).\n */\nexport function setOwnerAndApplyProfile(\n adapter: SessionRuntimeAdapter,\n preferences: PreferencesManager,\n sessionId: string,\n ownerPlatformId: string\n): SetOwnerAndApplyProfileResult {\n const db = adapter.ensureWritable(sessionId)\n const meta = getSessionMeta(db)\n if (!meta) {\n throw Object.assign(new Error(`Session has no meta: ${sessionId}`), { statusCode: 404 })\n }\n\n const members = getMembersWithAliases(db)\n const selected = members.find((m) => m.platformId === ownerPlatformId)\n if (!selected) {\n throw Object.assign(new Error(`Member not found in session: ${ownerPlatformId}`), { statusCode: 400 })\n }\n\n coreUpdateSessionOwnerId(db, ownerPlatformId)\n\n const prefs = preferences.load()\n const existing = prefs.ownerProfilesByPlatform[meta.platform]\n const baseNames = existing && existing.platformId === ownerPlatformId ? existing.confirmedNames : []\n const profile: OwnerProfile = {\n platformId: ownerPlatformId,\n displayName: memberDisplayName(selected),\n confirmedNames: mergeConfirmedNames(baseNames, { ...selected, displayName: memberDisplayName(selected) }),\n matchMode: isNameMatchPlatform(meta.platform) ? 'name' : 'platform_id',\n updatedAt: Date.now(),\n }\n\n const updatedSessions = applyProfileToOtherSessions(adapter, meta.platform, profile, sessionId)\n const updatedSessionIds = updatedSessions.map((s) => s.id)\n const updatedSessionOwnerIds: Record<string, string> = {}\n for (const s of updatedSessions) {\n updatedSessionOwnerIds[s.id] = s.ownerId\n }\n\n const noLongerDismissed = new Set([sessionId, ...updatedSessionIds])\n preferences.save({\n ownerProfilesByPlatform: { ...prefs.ownerProfilesByPlatform, [meta.platform]: profile },\n ownerPromptDismissedSessionIds: prefs.ownerPromptDismissedSessionIds.filter((id) => !noLongerDismissed.has(id)),\n })\n\n return { sessionId, platform: meta.platform, ownerId: ownerPlatformId, updatedSessionIds, updatedSessionOwnerIds }\n}\n\n/**\n * Apply a profile to all other unowned sessions of the same platform.\n * Unique match only; sessions with an existing owner are never touched.\n * Returns the id and the actual platformId written for each updated session\n * (may differ from profile.platformId on name-match platforms).\n */\nfunction applyProfileToOtherSessions(\n adapter: SessionRuntimeAdapter,\n platform: string,\n profile: OwnerProfile,\n excludeSessionId: string\n): Array<{ id: string; ownerId: string }> {\n const updated: Array<{ id: string; ownerId: string }> = []\n for (const id of adapter.listSessionIds()) {\n if (id === excludeSessionId) continue\n try {\n const db = adapter.openReadonly(id)\n if (!db || !isChatSessionDb(db)) continue\n const meta = getSessionMeta(db)\n if (!meta || meta.platform !== platform || meta.ownerId) continue\n\n const result = matchOwnerProfile(platform, profile, getMembersWithAliases(db))\n if (result.type === 'exact' || result.type === 'name') {\n coreUpdateSessionOwnerId(adapter.ensureWritable(id), result.platformId)\n updated.push({ id, ownerId: result.platformId })\n }\n } catch (err) {\n console.warn(`[OwnerProfile] Failed to apply profile to session ${id}:`, err)\n }\n }\n return updated\n}\n\n/**\n * Suppress the owner prompt for one session. UI-only: does not imply owner\n * knowledge and does not block later automatic profile application.\n */\nexport function dismissOwnerPrompt(preferences: PreferencesManager, sessionId: string): void {\n const prefs = preferences.load()\n if (prefs.ownerPromptDismissedSessionIds.includes(sessionId)) return\n preferences.save({\n ownerPromptDismissedSessionIds: [...prefs.ownerPromptDismissedSessionIds, sessionId],\n })\n}\n\n/**\n * Clear the owner of one session only. The platform profile is kept and may\n * re-apply automatically later if it still uniquely matches.\n */\nexport function clearSessionOwner(adapter: SessionRuntimeAdapter, sessionId: string): void {\n const db = adapter.ensureWritable(sessionId)\n coreUpdateSessionOwnerId(db, null)\n}\n","/**\n * 语义索引独立配置\n *\n * 存储位置:~/.chatlab/ai/semantic-index-config.json(独立于聊天 LLM 的 provider/model 管理)。\n * API Key 不落本配置,只保存 auth-profiles.json 中的 authProfile 引用。\n *\n * Phase 1 只有一个\"当前向量配置\":local 或 OpenAI-compatible。\n *\n * 重建信号:模型身份(resolveModelId)变化即需要重建。\n * - 换本地模型 / API baseUrl / API 模型名 -> 身份变化 -> 已启用对话索引需重建。\n * - 只改 API Key(authProfile 指向内容)-> 身份不变 -> 不重建。\n * chunk 以模型身份作为 model_id 分区,新配置重建完成前自然查不到旧索引。\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nexport type SemanticIndexMode = 'local' | 'api'\n\nexport interface SemanticIndexLocalConfig {\n /** 本地模型 profile 的 modelId */\n modelId: string\n}\n\nexport interface SemanticIndexApiConfig {\n baseUrl: string\n model: string\n /** auth-profiles.json 中的 profile 引用(只存引用,不存 key) */\n authProfile?: string\n /** API 模型维度;未知时运行时由返回向量长度确定 */\n dim?: number\n}\n\nexport interface SemanticIndexConfig {\n version: number\n /** 全局功能开关:关闭后不暴露 AI 检索工具、不建立/检索索引(已有索引数据保留) */\n enabled: boolean\n mode: SemanticIndexMode\n local: SemanticIndexLocalConfig\n api: SemanticIndexApiConfig | null\n /** AI 单次语义检索默认返回片段数(范围 5-15) */\n searchMaxResults: number\n}\n\n/** setConfig 入参:searchMaxResults / enabled 可省略(缺省由 normalize 填充默认值) */\nexport type SemanticIndexConfigInput = Omit<SemanticIndexConfig, 'searchMaxResults' | 'enabled'> & {\n searchMaxResults?: number\n enabled?: boolean\n}\n\nexport const SEMANTIC_INDEX_CONFIG_VERSION = 1\n\n/** 单次检索默认片段数与用户可配置范围 */\nexport const SEARCH_MAX_RESULTS_DEFAULT = 10\nexport const SEARCH_MAX_RESULTS_MIN = 5\nexport const SEARCH_MAX_RESULTS_MAX = 15\n/** LLM 单次检索片段数硬上限(高于用户可配置范围,仍受证据/token 预算约束) */\nexport const SEARCH_MAX_RESULTS_HARD_CAP = 20\n\nexport function clampSearchMaxResults(value: number | undefined): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return SEARCH_MAX_RESULTS_DEFAULT\n return Math.max(SEARCH_MAX_RESULTS_MIN, Math.min(SEARCH_MAX_RESULTS_MAX, Math.floor(value)))\n}\n\n/**\n * 默认配置:功能开启,但不预选任何向量模型(modelId 为空 = 未选择)。\n * 用户必须在设置中显式选择模型后才视为已配置(isConfigured)。\n */\nexport function defaultSemanticIndexConfig(): SemanticIndexConfig {\n return {\n version: SEMANTIC_INDEX_CONFIG_VERSION,\n enabled: true,\n mode: 'local',\n local: { modelId: '' },\n api: null,\n searchMaxResults: SEARCH_MAX_RESULTS_DEFAULT,\n }\n}\n\n/**\n * 当前配置的模型身份。作为 chunk 的 model_id 分区键与重建信号。\n */\nexport function resolveModelId(config: SemanticIndexConfig): string {\n if (config.mode === 'api' && config.api) {\n return `api:${config.api.baseUrl}#${config.api.model}`\n }\n return config.local.modelId\n}\n\n/** 用户是否已显式选择向量模型(本地有 modelId,或 API 已填 baseUrl+model)。 */\nexport function isSemanticIndexConfigured(config: SemanticIndexConfig): boolean {\n if (config.mode === 'local') return config.local.modelId.trim().length > 0\n return !!config.api && config.api.baseUrl.trim().length > 0 && config.api.model.trim().length > 0\n}\n\n/** 是否允许建立/检索索引:必须同时开启全局开关并完成模型配置。 */\nexport function canRunSemanticIndex(config: SemanticIndexConfig): boolean {\n return config.enabled && isSemanticIndexConfigured(config)\n}\n\n/** Local Ollama's OpenAI-compatible endpoint does not require authentication. */\nexport function isKeylessSemanticIndexApiBaseUrl(baseUrl: string | undefined): boolean {\n if (!baseUrl) return false\n try {\n const url = new URL(baseUrl)\n const host = url.hostname.toLowerCase()\n const isLoopback = host === 'localhost' || host === '127.0.0.1' || host === '::1'\n const port = url.port || (url.protocol === 'https:' ? '443' : '80')\n return isLoopback && port === '11434'\n } catch {\n return false\n }\n}\n\nfunction normalizeConfig(raw: Partial<SemanticIndexConfig> | null | undefined): SemanticIndexConfig {\n const base = defaultSemanticIndexConfig()\n if (!raw || typeof raw !== 'object') return base\n const mode: SemanticIndexMode = raw.mode === 'api' ? 'api' : 'local'\n const api = raw.api\n ? {\n baseUrl: raw.api.baseUrl ?? '',\n model: raw.api.model ?? '',\n authProfile: raw.api.authProfile,\n dim: raw.api.dim,\n }\n : null\n return {\n version: SEMANTIC_INDEX_CONFIG_VERSION,\n // 旧配置无 enabled 字段时默认开启,保证既有用户功能不被意外关闭\n enabled: raw.enabled ?? true,\n mode,\n local: { modelId: raw.local?.modelId ?? base.local.modelId },\n api,\n searchMaxResults: SEARCH_MAX_RESULTS_DEFAULT,\n }\n}\n\nexport class SemanticIndexConfigStore {\n private filePath: string\n\n constructor(filePath: string) {\n this.filePath = filePath\n }\n\n /** 用户是否已显式选择向量模型(本地有 modelId,或 API 已填 baseUrl+model)。未选择时为 false。 */\n isConfigured(): boolean {\n return isSemanticIndexConfigured(this.get())\n }\n\n isEnabled(): boolean {\n return this.get().enabled\n }\n\n canRun(): boolean {\n return canRunSemanticIndex(this.get())\n }\n\n /** 仅更新全局开关,保持已选模型与其余配置不变 */\n setEnabled(enabled: boolean): SemanticIndexConfig {\n return this.set({ ...this.get(), enabled })\n }\n\n get(): SemanticIndexConfig {\n if (!fs.existsSync(this.filePath)) return defaultSemanticIndexConfig()\n try {\n const raw = JSON.parse(fs.readFileSync(this.filePath, 'utf-8')) as Partial<SemanticIndexConfig>\n return normalizeConfig(raw)\n } catch {\n return defaultSemanticIndexConfig()\n }\n }\n\n set(config: SemanticIndexConfigInput): SemanticIndexConfig {\n const normalized = normalizeConfig(config)\n const dir = path.dirname(this.filePath)\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })\n fs.writeFileSync(this.filePath, JSON.stringify(normalized, null, 2), 'utf-8')\n return normalized\n }\n\n resolveModelId(): string {\n return resolveModelId(this.get())\n }\n}\n","/**\n * 语义索引共享 service(node-runtime,Electron 与 CLI Web 复用)\n *\n * 职责:把配置、向量库、业务状态、串行队列、warmup、检索、证据组装与聊天库适配器\n * 编排成一个有状态服务。每个运行时进程一个实例,由 server 启动时创建、停止时 close。\n * 不做隐藏全局单例。\n *\n * 关键约定(见用户决策):\n * - 启用/状态以业务状态表为权威,队列只负责执行。\n * - 模型身份(resolveModelId)变化即需重建;模型身份不变(仅换 API Key)不重建。\n * 身份不一致时检索不使用旧索引。\n * - 启动恢复只把 stale running 标记为 paused,不自动续跑。\n */\n\nimport path from 'node:path'\nimport {\n writeAuthProfile as defaultWriteAuthProfile,\n resolveApiKey as defaultResolveApiKey,\n type AuthProfile,\n} from '@openchatlab/config'\nimport type { DatabaseAdapter, PathProvider } from '@openchatlab/core'\nimport {\n CHUNKER_VERSION,\n DEFAULT_CHUNKER_CONFIG,\n computeChunkerConfigHash,\n computeDbPathHash,\n STRATEGY_ID,\n} from './chunker-config'\nimport type { ChunkSource } from './chunker'\nimport {\n isKeylessSemanticIndexApiBaseUrl,\n isSemanticIndexConfigured,\n SemanticIndexConfigStore,\n type SemanticIndexConfig,\n type SemanticIndexConfigInput,\n} from './config'\nimport { createEmbedder, type EmbedderFactoryDeps } from './embedder-factory'\nimport type { EmbeddingProvider } from './embedding/types'\nimport { EmbeddingIndexStore, type LoadSqliteVec } from './store'\nimport {\n SemanticIndexStateStore,\n type SemanticIndexSessionState,\n type SemanticIndexStatus,\n} from './session-state-store'\nimport { SemanticIndexJobQueue, type JobContext } from './warmup/job-queue'\nimport { runWarmup } from './warmup/runner'\nimport { hybridSearch } from './retrieval/hybrid-search'\nimport { assembleEvidence, type EvidenceBlock, type EvidenceBudget, type EvidenceHit } from './retrieval/evidence'\nimport { createChatDbMessageSource } from './chat-db/message-source'\nimport { createChatDbMessageRangeReader } from './chat-db/message-range-reader'\nimport { createChatDbFtsSearcher } from './chat-db/fts-searcher'\nimport type { SessionRuntimeAdapter } from '../services/adapters'\nimport { clampSearchMaxResults, SEARCH_MAX_RESULTS_HARD_CAP } from './config'\nimport { applyPreprocessingPipeline } from '../ai/preprocessor/preprocessing-pipeline'\nimport type { PreprocessConfig, PreprocessableMessage } from '../ai/preprocessor/types'\nimport { appLogger } from '../logging/app-logger'\n\nexport interface SemanticIndexServiceOptions {\n /** embedding_index.db 路径({vectorDir}/embedding_index.db) */\n vectorDbPath: string\n /** semantic-index-config.json 路径({aiDataDir}/semantic-index-config.json) */\n configPath: string\n sessionAdapter: SessionRuntimeAdapter\n /** 本地模型缓存目录 */\n modelsCacheDir?: string\n /** Optional HTTP(S) proxy URL used only for local embedding model downloads. */\n modelDownloadProxyUrl?: string\n /** embedder 工厂注入(测试/平台) */\n embedderFactoryDeps?: EmbedderFactoryDeps\n /** sqlite-vec 加载器注入(Electron 打包) */\n loadSqliteVec?: LoadSqliteVec\n /** better-sqlite3 原生绑定路径(CLI 打包需要) */\n nativeBinding?: string\n /** auth profile 写入注入(测试用,避免写真实 ~/.chatlab) */\n writeAuthProfile?: (name: string, profile: AuthProfile) => void\n}\n\n/** 语义索引 API Key 固定存储在此 auth profile;config 只保存引用,不保存明文 */\nexport const SEMANTIC_INDEX_AUTH_PROFILE = 'semantic-index-embedding'\n\n/**\n * 仅持久化配置与(可选)API Key,不依赖向量库。\n * 供 SemanticIndexService.setConfig 复用,也供 service 不可用(如 sqlite-vec 加载失败)\n * 的降级路径直接写配置,保证 API 模式下 key 不被丢弃。\n */\nexport function persistSemanticIndexConfig(\n configStore: SemanticIndexConfigStore,\n config: SemanticIndexConfigInput,\n options?: { apiKey?: string; writeAuthProfile?: (name: string, profile: AuthProfile) => void }\n): SemanticIndexConfig {\n let next = config\n if (options?.apiKey && config.mode === 'api' && config.api) {\n const writer = options.writeAuthProfile ?? defaultWriteAuthProfile\n writer(SEMANTIC_INDEX_AUTH_PROFILE, { type: 'api_key', provider: 'semantic-index', key: options.apiKey })\n next = { ...config, api: { ...config.api, authProfile: SEMANTIC_INDEX_AUTH_PROFILE } }\n }\n return configStore.set(next)\n}\n\n/** 当前配置的 API 模式是否已设置可用 Key(不返回明文)。service 与降级路径共用。 */\nexport function resolveSemanticIndexApiKeySet(\n config: SemanticIndexConfig,\n resolveApiKey: (provider: string, authProfile?: string) => string = defaultResolveApiKey\n): boolean {\n if (config.mode !== 'api' || !config.api || !config.api.authProfile) return false\n return resolveApiKey('semantic-index', config.api.authProfile) !== ''\n}\n\nexport interface SemanticIndexSessionStatus {\n sessionId: string\n enabled: boolean\n indexStatus: SemanticIndexStatus\n needsRebuild: boolean\n /** completed session has received new messages since last index run */\n hasNewMessages?: boolean\n totalMessages: number\n indexedMessages: number\n chunkCount: number\n coverage: number\n queued: boolean\n running: boolean\n partial: boolean\n error: string | null\n modelId: string | null\n}\n\nexport type SemanticSearchReason = 'disabled' | 'needs-rebuild' | 'empty' | 'not-found'\n\n/** 面向 AI 工具的安全来源条目(已脱敏,不含原始消息)。结构与 @openchatlab/tools 一致。 */\nexport interface SemanticSearchToolSource {\n startMessageId: number\n endMessageId: number\n score: number\n chunkIds: string[]\n snippet: string\n text?: string\n startTime?: string\n endTime?: string\n}\n\n/** 面向 AI 工具的检索结果:text 已经过 applyPreprocessingPipeline 清洗/脱敏/匿名化/截断 */\nexport interface SemanticSearchToolResult {\n available: boolean\n reason?: string\n text: string\n returned: number\n hitCount: number\n partial: boolean\n coverage: number\n truncated: boolean\n timeRange?: { earliest: string; latest: string }\n sources: SemanticSearchToolSource[]\n}\n\nexport interface SemanticSearchToolOptions {\n maxResults?: number\n preprocessConfig?: Record<string, unknown>\n ownerPlatformId?: string\n locale?: string\n maxResultTokens?: number\n /** 毫秒级时间范围过滤(可单边);仅保留与 chunk 时间范围有交集的语义候选 */\n timeFilter?: { startTs?: number; endTs?: number }\n}\n\n/** 工具返回片段预览的最大字符数 */\nconst SEARCH_TOOL_SNIPPET_MAX = 160\n\n/**\n * 注入证据的固定 token 预算(与 max_results 解耦)。\n * max_results 只决定召回候选数,最终注入量由本预算(再以 maxResultTokens 封顶)控制,\n * 避免候选变多导致上下文 token 膨胀;单块 soft cap 保持较小,让宽泛检索优先增加结果数量。\n */\nconst SEARCH_TOOL_EVIDENCE_TOKENS = 8000\n\nexport function resolveSearchToolEvidenceTokens(maxResultTokens?: number): number {\n return maxResultTokens && maxResultTokens > 0\n ? Math.min(SEARCH_TOOL_EVIDENCE_TOKENS, maxResultTokens)\n : SEARCH_TOOL_EVIDENCE_TOKENS\n}\n\nexport interface SemanticSearchResult {\n available: boolean\n reason?: SemanticSearchReason\n blocks: EvidenceBlock[]\n coverage: number\n /** 索引未完成时为 true,证据可能不完整 */\n partial: boolean\n /** RRF 融合后的候选命中数(证据组装前) */\n hitCount: number\n}\n\nfunction sessionIdFromDbPath(dbPath: string): string {\n return path.basename(dbPath, '.db')\n}\n\n/** 语义索引相关文件名(统一约定,避免各端硬编码) */\nexport const SEMANTIC_INDEX_DB_FILE = 'embedding_index.db'\nexport const SEMANTIC_INDEX_CONFIG_FILE = 'semantic-index-config.json'\n\n/**\n * 按 PathProvider 约定路径创建 service:\n * - 向量库:{vectorDir}/embedding_index.db\n * - 配置:{aiDataDir}/semantic-index-config.json\n * - 本地模型:{aiDataDir}/models/semantic-index(不在 cache 目录,不随清理缓存丢失)\n */\nexport function createSemanticIndexService(params: {\n pathProvider: PathProvider\n sessionAdapter: SessionRuntimeAdapter\n nativeBinding?: string\n loadSqliteVec?: LoadSqliteVec\n embedderFactoryDeps?: EmbedderFactoryDeps\n modelDownloadProxyUrl?: string\n}): SemanticIndexService {\n const { pathProvider } = params\n return new SemanticIndexService({\n vectorDbPath: path.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),\n configPath: path.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),\n modelsCacheDir: path.join(pathProvider.getAiDataDir(), 'models', 'semantic-index'),\n modelDownloadProxyUrl: params.modelDownloadProxyUrl,\n sessionAdapter: params.sessionAdapter,\n nativeBinding: params.nativeBinding,\n loadSqliteVec: params.loadSqliteVec,\n embedderFactoryDeps: params.embedderFactoryDeps,\n })\n}\n\nexport class SemanticIndexService {\n private store: EmbeddingIndexStore\n private stateStore: SemanticIndexStateStore\n private configStore: SemanticIndexConfigStore\n private queue: SemanticIndexJobQueue\n private sessionAdapter: SessionRuntimeAdapter\n private options: SemanticIndexServiceOptions\n\n private embedder: EmbeddingProvider | null = null\n private embedderModelId: string | null = null\n private modelPreloadStatus: 'idle' | 'downloading' | 'ready' | 'error' = 'idle'\n\n /** 当前激活的 chunker 身份(version + 参数 hash),随索引记录用于重建判定 */\n private readonly chunkerVersion = CHUNKER_VERSION\n private readonly chunkerConfigHash = computeChunkerConfigHash(DEFAULT_CHUNKER_CONFIG)\n\n constructor(options: SemanticIndexServiceOptions) {\n this.options = options\n this.sessionAdapter = options.sessionAdapter\n this.store = new EmbeddingIndexStore(options.vectorDbPath, {\n loadSqliteVec: options.loadSqliteVec,\n nativeBinding: options.nativeBinding,\n })\n this.stateStore = new SemanticIndexStateStore(options.vectorDbPath, { nativeBinding: options.nativeBinding })\n this.configStore = new SemanticIndexConfigStore(options.configPath)\n this.queue = new SemanticIndexJobQueue((ctx) => this.runJob(ctx))\n }\n\n // ---------- 配置 ----------\n\n getConfig(): SemanticIndexConfig {\n return this.configStore.get()\n }\n\n /** 用户是否已显式配置向量模型(区分默认兜底配置) */\n isConfigured(): boolean {\n return this.configStore.isConfigured()\n }\n\n /** 全局功能开关是否开启 */\n isEnabled(): boolean {\n return this.configStore.isEnabled()\n }\n\n /** 语义索引是否可实际建立/检索:全局开启且已选择模型 */\n canRun(): boolean {\n if (!this.configStore.canRun()) return false\n const cfg = this.configStore.get()\n return cfg.mode !== 'api' || isKeylessSemanticIndexApiBaseUrl(cfg.api?.baseUrl) || this.hasApiKey()\n }\n\n /** 当前 API 模式是否已配置可用的 API Key(不返回明文,仅布尔) */\n hasApiKey(): boolean {\n return resolveSemanticIndexApiKeySet(this.configStore.get(), this.options.embedderFactoryDeps?.resolveApiKey)\n }\n\n /**\n * 保存配置;模型身份变化会使已启用对话的索引在重建完成前不被检索使用。\n * 传入 apiKey 时写入固定 auth profile,config 内只保存引用,绝不持久化明文。\n */\n getModelStatus(): 'idle' | 'downloading' | 'ready' | 'error' {\n return this.modelPreloadStatus\n }\n\n setConfig(config: SemanticIndexConfigInput, options?: { apiKey?: string }): SemanticIndexConfig {\n const saved = persistSemanticIndexConfig(this.configStore, config, {\n apiKey: options?.apiKey,\n writeAuthProfile: this.options.writeAuthProfile,\n })\n this.embedder = null\n this.embedderModelId = null\n this.modelPreloadStatus = 'idle'\n // 关闭全局开关时停止所有在跑/排队的建索引任务(保留已建立的部分数据)\n if (!saved.enabled) {\n for (const state of this.stateStore.listEnabled()) this.queue.pause(state.dbPathHash)\n }\n // 本地模式已配置且功能已开启时,立即在后台触发模型下载,让用户在建索引前完成等待\n if (saved.enabled && isSemanticIndexConfigured(saved) && saved.mode === 'local') {\n const modelId = saved.local.modelId\n const cacheDir = this.options.modelsCacheDir\n this.modelPreloadStatus = 'downloading'\n appLogger.info('semantic-index', 'local embedding model preload started', { modelId, cacheDir })\n void this.getEmbedder()\n .preload?.()\n .then(() => {\n this.modelPreloadStatus = 'ready'\n appLogger.info('semantic-index', 'local embedding model preload completed', { modelId, cacheDir })\n })\n .catch((error) => {\n this.modelPreloadStatus = 'error'\n appLogger.error('semantic-index', `local embedding model preload failed: ${modelId}`, error)\n })\n }\n return saved\n }\n\n private currentModelId(): string {\n return this.configStore.resolveModelId()\n }\n\n private getEmbedder(): EmbeddingProvider {\n const modelId = this.currentModelId()\n if (!this.embedder || this.embedderModelId !== modelId) {\n this.embedder = createEmbedder(this.configStore.get(), {\n modelsCacheDir: this.options.modelsCacheDir,\n modelDownloadProxyUrl: this.options.modelDownloadProxyUrl,\n ...this.options.embedderFactoryDeps,\n })\n this.embedderModelId = modelId\n }\n return this.embedder\n }\n\n // ---------- 启用 / 生命周期 ----------\n\n private hashFor(sessionId: string): string {\n return computeDbPathHash(sessionId)\n }\n\n /**\n * 查找 sessionId 对应的实际 db_path_hash。\n * 优先用当前规则(computeDbPathHash(sessionId)),找不到时扫全表匹配 dbPath basename,\n * 兼容旧版本用完整 dbPath 计算 hash 存储的历史记录。\n */\n private resolveHash(sessionId: string): string {\n const hash = this.hashFor(sessionId)\n if (this.stateStore.getState(hash)) return hash\n const legacy = this.stateStore.listAll().find((s) => sessionIdFromDbPath(s.dbPath) === sessionId)\n return legacy?.dbPathHash ?? hash\n }\n\n /** 索引身份是否与当前运行时不一致(模型身份或 chunker 身份变化 => 需重建) */\n private isStale(state: SemanticIndexSessionState, currentModelId: string): boolean {\n return (\n state.modelId !== currentModelId ||\n state.chunkerVersion !== this.chunkerVersion ||\n state.chunkerConfigHash !== this.chunkerConfigHash\n )\n }\n\n private enableParams(\n dbPath: string,\n dbPathHash?: string\n ): {\n dbPathHash: string\n dbPath: string\n modelId: string\n chunkerVersion: string\n chunkerConfigHash: string\n } {\n return {\n dbPathHash: dbPathHash ?? computeDbPathHash(sessionIdFromDbPath(dbPath)),\n dbPath,\n modelId: this.currentModelId(),\n chunkerVersion: this.chunkerVersion,\n chunkerConfigHash: this.chunkerConfigHash,\n }\n }\n\n enable(sessionId: string): void {\n if (!this.canRun()) return\n const dbPath = this.sessionAdapter.getDbPath(sessionId)\n const hash = computeDbPathHash(sessionId)\n const existing = this.stateStore.getState(hash)\n const stale = !!existing && this.isStale(existing, this.currentModelId())\n if (stale) {\n // 保留旧身份字段,只重置 enabled 和 index_status。\n // 这样 app 在 rebuild 运行前退出后,重启时 buildAllPending() 仍能通过\n // isStale() 检测到需重建并入队 rebuild,避免永久卡死在\"新身份+无向量\"状态。\n this.stateStore.reactivate(hash, dbPath)\n this.stateStore.setIndexStatus(hash, 'idle')\n } else {\n this.stateStore.enable(this.enableParams(dbPath))\n }\n }\n\n /** 彻底移除:取消队列、删除向量数据、删除状态记录,立即生效无需二次清理 */\n remove(sessionId: string): void {\n const hash = this.resolveHash(sessionId)\n this.queue.cancel(hash)\n this.store.deleteByDbPathHash(hash)\n this.stateStore.remove(hash)\n }\n\n /** 建立 / 续跑索引(续跑从断点游标继续) */\n build(sessionId: string): void {\n if (!this.canRun()) return\n this.queue.enqueue({ type: 'build', dbPathHash: this.resolveHash(sessionId) })\n }\n\n pause(sessionId: string): void {\n this.queue.pause(this.resolveHash(sessionId))\n }\n\n cancel(sessionId: string): void {\n this.queue.cancel(this.resolveHash(sessionId))\n }\n\n /** 重建:换模型身份或用户主动重建时清空旧索引后重新建立 */\n rebuild(sessionId: string): void {\n if (!this.canRun()) return\n const dbPath = this.sessionAdapter.getDbPath(sessionId)\n const hash = this.resolveHash(sessionId)\n this.queue.cancel(hash)\n this.stateStore.enable(this.enableParams(dbPath, hash))\n this.queue.enqueue({ type: 'rebuild', dbPathHash: hash })\n }\n\n /** 为所有启用但未完成 / 需重建的对话入队(\"建立待处理索引\") */\n buildAllPending(): void {\n if (!this.canRun()) return\n const modelId = this.currentModelId()\n for (const state of this.stateStore.listEnabled()) {\n if (state.indexStatus === 'running') continue\n if (this.isStale(state, modelId)) {\n this.queue.enqueue({ type: 'rebuild', dbPathHash: state.dbPathHash })\n } else if (state.indexStatus !== 'completed') {\n this.queue.enqueue({ type: 'build', dbPathHash: state.dbPathHash })\n } else {\n // Completed sessions may have received new messages since last index run\n const sessionId = sessionIdFromDbPath(state.dbPath)\n const db = this.sessionAdapter.openReadonly(sessionId)\n if (db) {\n const liveCount = (db.prepare('SELECT COUNT(*) AS c FROM message').get() as { c: number } | undefined)?.c ?? 0\n if (liveCount > state.totalMessages) {\n this.queue.enqueue({ type: 'build', dbPathHash: state.dbPathHash })\n } else if (liveCount < state.totalMessages) {\n // Messages were deleted (e.g. member deletion); must rebuild to remove stale vectors\n this.queue.enqueue({ type: 'rebuild', dbPathHash: state.dbPathHash })\n }\n }\n }\n }\n }\n\n // ---------- 状态 ----------\n\n private toStatus(state: SemanticIndexSessionState, currentModelId: string): SemanticIndexSessionStatus {\n const needsRebuild = state.enabled && this.isStale(state, currentModelId)\n const coverage = state.totalMessages > 0 ? Math.min(1, state.indexedMessages / state.totalMessages) : 0\n return {\n sessionId: sessionIdFromDbPath(state.dbPath),\n enabled: state.enabled,\n indexStatus: state.indexStatus,\n needsRebuild,\n totalMessages: state.totalMessages,\n indexedMessages: state.indexedMessages,\n chunkCount: state.chunkCount,\n coverage,\n queued: this.queue.isQueued(state.dbPathHash),\n running: this.queue.isRunning(state.dbPathHash),\n partial: state.indexStatus !== 'completed',\n error: state.error,\n modelId: state.modelId,\n }\n }\n\n status(sessionId: string): SemanticIndexSessionStatus | null {\n const state = this.stateStore.getState(this.resolveHash(sessionId))\n if (!state) return null\n return this.toStatus(state, this.currentModelId())\n }\n\n /** 批量查询多个对话状态(未启用的返回 null 过滤) */\n statusForSessions(sessionIds: string[]): SemanticIndexSessionStatus[] {\n const modelId = this.currentModelId()\n const result: SemanticIndexSessionStatus[] = []\n for (const sessionId of sessionIds) {\n const state = this.stateStore.getState(this.resolveHash(sessionId))\n if (state) result.push(this.toStatus(state, modelId))\n }\n return result\n }\n\n listEnabledStatuses(): SemanticIndexSessionStatus[] {\n const modelId = this.currentModelId()\n return this.stateStore.listEnabled().map((s) => {\n const status = this.toStatus(s, modelId)\n if (s.indexStatus === 'completed' && !status.needsRebuild && !status.queued && !status.running) {\n const sessionId = sessionIdFromDbPath(s.dbPath)\n const db = this.sessionAdapter.openReadonly(sessionId)\n if (db) {\n const liveCount = (db.prepare('SELECT COUNT(*) AS c FROM message').get() as { c: number } | undefined)?.c ?? 0\n if (liveCount !== s.totalMessages) status.hasNewMessages = true\n }\n }\n return status\n })\n }\n\n // ---------- 检索 ----------\n\n async search(\n sessionId: string,\n query: string,\n options?: { finalTopK?: number; budget?: EvidenceBudget; timeRangeMs?: { startTs?: number; endTs?: number } }\n ): Promise<SemanticSearchResult> {\n if (!this.canRun())\n return { available: false, reason: 'disabled', blocks: [], coverage: 0, partial: false, hitCount: 0 }\n\n const hash = this.resolveHash(sessionId)\n const state = this.stateStore.getState(hash)\n const modelId = this.currentModelId()\n\n if (!state || !state.enabled)\n return { available: false, reason: 'disabled', blocks: [], coverage: 0, partial: false, hitCount: 0 }\n if (this.isStale(state, modelId))\n return { available: false, reason: 'needs-rebuild', blocks: [], coverage: 0, partial: true, hitCount: 0 }\n\n const dim = this.store.getDim(hash, modelId)\n if (!dim || state.chunkCount === 0)\n return {\n available: false,\n reason: 'empty',\n blocks: [],\n coverage: 0,\n partial: state.indexStatus !== 'completed',\n hitCount: 0,\n }\n\n const db = this.sessionAdapter.openReadonly(sessionId)\n if (!db) return { available: false, reason: 'not-found', blocks: [], coverage: 0, partial: false, hitCount: 0 }\n\n const embedder = this.getEmbedder()\n const fts = createChatDbFtsSearcher(db)\n const finalTopK = options?.finalTopK ?? 5\n // 候选池随 finalTopK 放大:短 chunk 下保证高 max_results 时召回覆盖足够(下限保持默认 40)\n const candidateTopN = Math.max(40, finalTopK * 4)\n const hits = await hybridSearch(\n { embedder, store: this.store, fts },\n {\n query,\n dbPathHash: hash,\n modelId,\n strategyId: STRATEGY_ID,\n dim,\n finalTopK,\n denseTopN: candidateTopN,\n ftsTopN: candidateTopN,\n timeRangeMs: options?.timeRangeMs,\n }\n )\n\n const evidenceHits: EvidenceHit[] = hits.map((h) => ({\n chunkId: h.chunkId,\n score: h.score,\n parentId: h.record.parentId,\n startMessageId: h.record.startMessageId,\n endMessageId: h.record.endMessageId,\n startTs: h.record.startTs,\n endTs: h.record.endTs,\n }))\n const reader = createChatDbMessageRangeReader(db)\n const { blocks } = assembleEvidence(reader, evidenceHits, options?.budget)\n\n const coverage = state.totalMessages > 0 ? Math.min(1, state.indexedMessages / state.totalMessages) : 0\n return { available: true, blocks, coverage, partial: state.indexStatus !== 'completed', hitCount: hits.length }\n }\n\n /**\n * 当前会话是否可被 AI 工具检索。\n * 已启用 + 模型身份一致 + 有 chunk + 维度可用即可(running/paused/failed 只要已有 chunk 也可,结果标记 partial)。\n * 未启用 / 需重建 / 无 chunk 返回 false,runner 据此不向 LLM 暴露检索工具。\n */\n canSearch(sessionId: string): boolean {\n if (!this.canRun()) return false\n const hash = this.resolveHash(sessionId)\n const state = this.stateStore.getState(hash)\n if (!state || !state.enabled) return false\n const modelId = this.currentModelId()\n if (this.isStale(state, modelId)) return false\n if (state.chunkCount <= 0) return false\n return !!this.store.getDim(hash, modelId)\n }\n\n /**\n * 面向 AI 工具的检索:复用 search(),再用 applyPreprocessingPipeline 对证据做清洗/脱敏/匿名化/截断。\n * 工具层只拿到安全文本与安全 metadata,绝不接触原始消息。\n */\n async searchForTool(\n sessionId: string,\n query: string,\n options?: SemanticSearchToolOptions\n ): Promise<SemanticSearchToolResult> {\n const unavailable = (reason: string, partial = false): SemanticSearchToolResult => ({\n available: false,\n reason,\n text: '',\n returned: 0,\n hitCount: 0,\n partial,\n coverage: 0,\n truncated: false,\n sources: [],\n })\n\n const q = query?.trim()\n if (!q) return unavailable('empty-query')\n\n const configuredDefault = clampSearchMaxResults(this.configStore.get().searchMaxResults)\n const requested = options?.maxResults ?? configuredDefault\n const finalTopK = Math.max(1, Math.min(SEARCH_MAX_RESULTS_HARD_CAP, Math.floor(requested)))\n\n // 注入预算与 max_results 解耦:固定预算再以工具结果预算(maxResultTokens)封顶。\n // finalTopK 只放大召回候选数,不放大注入 token,避免大范围查询撑爆上下文。\n const evidenceTokens = resolveSearchToolEvidenceTokens(options?.maxResultTokens)\n\n let result: SemanticSearchResult\n try {\n result = await this.search(sessionId, q, {\n finalTopK,\n budget: { maxChunks: finalTopK, totalTokens: evidenceTokens },\n timeRangeMs: options?.timeFilter,\n })\n } catch (err) {\n return unavailable(err instanceof Error ? err.message : String(err))\n }\n if (!result.available) return unavailable(result.reason ?? 'unavailable', result.partial)\n\n // 归一化预处理配置:补齐数组字段,避免 partial 配置导致管道崩溃(脱敏规则仍以传入为准)\n const preprocessConfig = options?.preprocessConfig\n ? ({ blacklistKeywords: [], desensitizeRules: [], ...options.preprocessConfig } as unknown as PreprocessConfig)\n : undefined\n const anonymizeNames = !!preprocessConfig?.anonymizeNames\n const locale = options?.locale\n const ownerPlatformId = options?.ownerPlatformId\n\n const sources: SemanticSearchToolSource[] = []\n const blockTexts: string[] = []\n // 全局昵称映射(仅 anonymize 时使用),保证整段证据只出现一次 name map\n const nameMap = new Map<number, { name: string; platformId?: string }>()\n let earliest = Number.POSITIVE_INFINITY\n let latest = Number.NEGATIVE_INFINITY\n\n for (const block of result.blocks) {\n const rawMessages: PreprocessableMessage[] = block.messages.map((m) => ({\n id: m.id,\n senderId: m.senderId,\n senderName: m.senderName,\n senderPlatformId: m.senderPlatformId,\n content: m.content,\n // EvidenceMessage.ts 是毫秒;预处理管道按秒渲染时间(format.ts 内部 *1000),故此处回退为秒\n timestamp: Math.floor(m.ts / 1000),\n }))\n for (const m of block.messages) {\n if (m.senderId != null && !nameMap.has(m.senderId)) {\n nameMap.set(m.senderId, { name: m.senderName, platformId: m.senderPlatformId })\n }\n }\n // 每块统一过 applyPreprocessingPipeline 做清洗/脱敏/匿名化;用 details.messages(不含 name map 行)\n const { details } = applyPreprocessingPipeline({\n rawMessages,\n preprocessConfig,\n locale,\n anonymizeNames,\n ownerPlatformId,\n })\n const lines = Array.isArray(details.messages) ? (details.messages as string[]) : []\n const safeText = lines.join('\\n').trim()\n const first = block.messages[0]\n const last = block.messages[block.messages.length - 1]\n const startTime = first ? new Date(first.ts).toISOString() : undefined\n const endTime = last ? new Date(last.ts).toISOString() : undefined\n for (const m of block.messages) {\n if (m.ts < earliest) earliest = m.ts\n if (m.ts > latest) latest = m.ts\n }\n const snippet =\n safeText.length > SEARCH_TOOL_SNIPPET_MAX ? `${safeText.slice(0, SEARCH_TOOL_SNIPPET_MAX)}…` : safeText\n sources.push({\n startMessageId: block.startMessageId,\n endMessageId: block.endMessageId,\n score: block.score,\n chunkIds: block.chunkIds,\n snippet,\n text: safeText,\n startTime,\n endTime,\n })\n const rangeLabel = startTime && endTime ? `${startTime} ~ ${endTime}` : ''\n blockTexts.push(`--- ${rangeLabel}\\n${safeText}`)\n }\n\n let text = blockTexts.join('\\n\\n')\n if (anonymizeNames && nameMap.size > 0) {\n const entries = [...nameMap.entries()].map(\n ([id, { name, platformId }]) =>\n `U${id}=${name}${ownerPlatformId && platformId === ownerPlatformId ? '(owner)' : ''}`\n )\n text = `[Name Map] ${entries.join(' | ')}\\n\\n${text}`\n }\n\n const timeRange =\n Number.isFinite(earliest) && Number.isFinite(latest)\n ? { earliest: new Date(earliest).toISOString(), latest: new Date(latest).toISOString() }\n : undefined\n\n // 命中候选已填满 finalTopK 上限,说明可能还有更多相关历史,提示用更具体 query 继续\n const truncated = result.hitCount >= finalTopK\n\n return {\n available: true,\n text,\n returned: result.blocks.length,\n hitCount: result.hitCount,\n partial: result.partial,\n coverage: result.coverage,\n truncated,\n timeRange,\n sources,\n }\n }\n\n // ---------- 清理 ----------\n\n /** 清理已停用待清理的对话索引,以及无对应业务状态的孤儿 chunk */\n cleanupUnused(): { cleaned: number } {\n let cleaned = 0\n\n // 检测已删除的对话(DB 文件不再存在):将其状态标为 pending cleanup,交由下方统一清理\n const activeSessionIds = new Set(this.sessionAdapter.listSessionIds())\n for (const state of this.stateStore.listAll()) {\n if (state.cleanupStatus !== 'pending' && !activeSessionIds.has(sessionIdFromDbPath(state.dbPath))) {\n this.queue.cancel(state.dbPathHash)\n this.stateStore.disable(state.dbPathHash)\n }\n }\n\n for (const state of this.stateStore.listPendingCleanup()) {\n this.queue.cancel(state.dbPathHash)\n this.store.deleteByDbPathHash(state.dbPathHash)\n this.stateStore.resetProgress(state.dbPathHash)\n this.stateStore.setCleanupStatus(state.dbPathHash, 'done')\n cleaned++\n }\n const known = new Set(this.stateStore.listAll().map((s) => s.dbPathHash))\n for (const hash of this.store.listDbPathHashes()) {\n if (!known.has(hash)) {\n this.store.deleteByDbPathHash(hash)\n cleaned++\n }\n }\n return { cleaned }\n }\n\n // ---------- 启动恢复 ----------\n\n /** 启动时把中断的 running 标记为 paused,保留断点;不自动续跑 */\n recover(): void {\n for (const state of this.stateStore.listAll()) {\n if (state.indexStatus === 'running') {\n this.stateStore.setIndexStatus(state.dbPathHash, 'paused')\n }\n }\n }\n\n // ---------- 队列执行 ----------\n\n private resolveSource(db: DatabaseAdapter): ChunkSource {\n try {\n const row = db.prepare('SELECT name, type FROM meta LIMIT 1').get() as\n | { name?: string; type?: string }\n | undefined\n const kind: ChunkSource['kind'] = row?.type === 'group' ? 'group' : 'private'\n return { title: row?.name ?? '', kind }\n } catch {\n return { title: '', kind: 'private' }\n }\n }\n\n private async runJob(ctx: JobContext): Promise<void> {\n const { job, checkStop } = ctx\n if (job.type === 'cleanup') {\n this.cleanupUnused()\n return\n }\n\n const state = this.stateStore.getState(job.dbPathHash)\n if (!state || !state.enabled) return\n const sessionId = sessionIdFromDbPath(state.dbPath)\n\n const db = this.sessionAdapter.openReadonly(sessionId)\n if (!db) {\n this.stateStore.setIndexStatus(job.dbPathHash, 'failed', 'session database not found')\n return\n }\n\n if (job.type === 'rebuild') {\n this.store.deleteByDbPathHash(job.dbPathHash)\n this.stateStore.resetProgress(job.dbPathHash)\n // 以当前模型/chunker 身份重建:刷新状态身份,确保完成后 needsRebuild=false、可检索\n this.stateStore.setBuildIdentity(job.dbPathHash, {\n modelId: this.currentModelId(),\n chunkerVersion: this.chunkerVersion,\n chunkerConfigHash: this.chunkerConfigHash,\n })\n }\n\n const source = createChatDbMessageSource(db, this.resolveSource(db))\n await runWarmup({\n dbPathHash: job.dbPathHash,\n modelId: this.currentModelId(),\n embedder: this.getEmbedder(),\n store: this.store,\n stateStore: this.stateStore,\n source,\n checkStop,\n })\n }\n\n /** 等待队列清空(优雅停止 / 测试用) */\n whenIdle(): Promise<void> {\n return this.queue.whenIdle()\n }\n\n close(): void {\n this.store.close()\n this.stateStore.close()\n }\n}\n","/**\n * Chunker 版本、运行时参数与 hash 派生(纯函数)\n *\n * 设计依据 chunking-decision-final.md 第 7/8/9/11 节:\n * - chunker_version 是代码常量,算法/格式语义变化时 bump,触发全量重建。\n * - chunker_config_hash 由运行时参数生成,参数变化只影响当前索引。\n * - parent_id 必须包含 config hash,避免错误复用旧 parent 缓存。\n * - db_path_hash 用于在 embedding_index.db 中区分不同聊天库。\n */\n\nimport { createHash } from 'crypto'\n\n/**\n * Chunker 算法/格式语义版本。\n *\n * bump 条件(见文档第 11 节):\n * - header 模板结构变化。\n * - 切片算法语义变化。\n * - 语义真空过滤规则本身变化。\n */\nexport const CHUNKER_VERSION = 'v1.2'\n\n/** Phase 1 固定策略标识 */\nexport const STRATEGY_ID = 'balanced'\n\n/** 运行时 chunker 参数(进入 chunker_config_hash) */\nexport interface ChunkerConfig {\n /** parent 时间 gap 阈值(秒),兜底 1800 */\n parentGapSeconds: number\n /** parent token 硬上限 */\n parentMaxTokens: number\n /** child 目标最小有效字符:达到消息数软上限时仍需满足此下限才关闭,避免 header 主导的过短 chunk */\n childTargetMinChars: number\n /** child 目标最大有效字符 */\n childTargetMaxChars: number\n /** child 消息数软上限:消息数达到且有效字符达到 min 时关闭(应对高频短消息群聊) */\n childSoftMaxMessages: number\n /** child 消息数硬上限:消息数达到即强制关闭,即使有效字符不足 min */\n childHardMaxMessages: number\n /** child token 硬上限 */\n childHardMaxTokens: number\n /** overlap 保留末尾消息条数 */\n overlapMessages: number\n /** 过滤语义真空后有效字符低于该值的 chunk 跳过索引 */\n semanticVoidSkipThreshold: number\n}\n\n/**\n * Phase 1 默认 chunker 参数(见决策表)。\n * 短 chunk + 多候选策略:缩短 child 窗口提升语义定位精度,配合检索侧更高 max_results\n * 与 evidence/token 预算控制最终注入量。\n */\nexport const DEFAULT_CHUNKER_CONFIG: ChunkerConfig = {\n parentGapSeconds: 1800,\n parentMaxTokens: 2000,\n childTargetMinChars: 120,\n childTargetMaxChars: 360,\n childSoftMaxMessages: 12,\n childHardMaxMessages: 20,\n childHardMaxTokens: 1200,\n overlapMessages: 2,\n semanticVoidSkipThreshold: 20,\n}\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input, 'utf-8').digest('hex')\n}\n\n/**\n * 运行时参数 hash。显式按固定字段顺序序列化,确保与传入对象的键顺序无关。\n */\nexport function computeChunkerConfigHash(config: ChunkerConfig): string {\n const canonical = JSON.stringify({\n parentGapSeconds: config.parentGapSeconds,\n parentMaxTokens: config.parentMaxTokens,\n childTargetMinChars: config.childTargetMinChars,\n childTargetMaxChars: config.childTargetMaxChars,\n childSoftMaxMessages: config.childSoftMaxMessages,\n childHardMaxMessages: config.childHardMaxMessages,\n childHardMaxTokens: config.childHardMaxTokens,\n overlapMessages: config.overlapMessages,\n semanticVoidSkipThreshold: config.semanticVoidSkipThreshold,\n })\n return sha256Hex(canonical)\n}\n\n/** 对话稳定标识(sessionId 或相关稳定字符串)的 SHA256 前缀,用于区分不同聊天库的 chunk */\nexport function computeDbPathHash(stableKey: string): string {\n return sha256Hex(stableKey).slice(0, 16)\n}\n\n/**\n * 组合全局唯一 chunk_id。chunker 输出模型无关的 localChunkId,写入存储时由\n * db_path_hash + model_id 组合成跨对话、跨模型唯一的 chunk_id。\n */\nexport function composeChunkId(dbPathHash: string, modelId: string, localChunkId: string): string {\n return `${dbPathHash}:${modelId}:${localChunkId}`\n}\n\n/**\n * 稳定派生 parent id。包含 config hash,参数变化后 parent id 也变化。\n * 格式:`parent:${start}:${end}:${gap}:${version}:${configHash}`\n */\nexport function deriveParentId(params: {\n startMessageId: number\n endMessageId: number\n gapSeconds: number\n chunkerVersion: string\n chunkerConfigHash: string\n}): string {\n return `parent:${params.startMessageId}:${params.endMessageId}:${params.gapSeconds}:${params.chunkerVersion}:${params.chunkerConfigHash}`\n}\n\n/**\n * 从 parent id 解析 parent 的消息 id 边界,用于证据扩展时限定\"不跨 parent\"。\n * 格式不匹配时返回 null。\n */\nexport function parseParentBounds(parentId: string): { startMessageId: number; endMessageId: number } | null {\n const segments = parentId.split(':')\n if (segments.length < 3 || segments[0] !== 'parent') return null\n const startMessageId = Number(segments[1])\n const endMessageId = Number(segments[2])\n if (!Number.isFinite(startMessageId) || !Number.isFinite(endMessageId)) return null\n return { startMessageId, endMessageId }\n}\n","/**\n * OpenAI-compatible embedding API provider\n *\n * Phase 1 只承诺 OpenAI-compatible /embeddings 调用路径,配置仅 baseUrl/apiKey/model,\n * 不暴露 dimensions(只记录 provider 实际返回维度)。API 模式 query 与 document 一致,\n * 不加本地模型的 queryInstruction。\n *\n * fetch 可注入,单元测试验证请求结构与响应解析,不联网。\n */\n\nimport type { EmbeddingProvider } from './types'\n\n/** API embedding 默认一次提交 32 个 chunk,保持 concurrency=1 以降低 429 风险。 */\nexport const API_DOCUMENT_BATCH_SIZE = 32\n\nexport interface OpenAICompatibleConfig {\n baseUrl: string\n apiKey: string\n model: string\n /** 单文本 token 上限提示,默认 8192 */\n maxTokens?: number\n}\n\ninterface EmbeddingResponse {\n data: Array<{ index: number; embedding: number[] }>\n}\n\nexport type FetchFn = (\n url: string,\n init: { method: string; headers: Record<string, string>; body: string }\n) => Promise<{ ok: boolean; status: number; text(): Promise<string>; json(): Promise<unknown> }>\n\nconst defaultFetchFn: FetchFn = (url, init) => fetch(url, init) as unknown as ReturnType<FetchFn>\n\nfunction buildEmbeddingsUrl(baseUrl: string): string {\n const trimmed = baseUrl.replace(/\\/+$/, '')\n return trimmed.endsWith('/embeddings') ? trimmed : `${trimmed}/embeddings`\n}\n\nexport class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {\n readonly modelId: string\n readonly maxTokens: number\n readonly documentBatchSize = API_DOCUMENT_BATCH_SIZE\n\n private config: OpenAICompatibleConfig\n private fetchFn: FetchFn\n private url: string\n private knownDim = 0\n\n constructor(config: OpenAICompatibleConfig, options: { fetchFn?: FetchFn } = {}) {\n this.config = config\n this.fetchFn = options.fetchFn ?? defaultFetchFn\n this.modelId = config.model\n this.maxTokens = config.maxTokens ?? 8192\n this.url = buildEmbeddingsUrl(config.baseUrl)\n }\n\n /** 实际返回维度;首次成功调用后才确定,调用前为 0 */\n get dim(): number {\n return this.knownDim\n }\n\n async embedDocuments(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return []\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n if (this.config.apiKey.trim()) headers.Authorization = `Bearer ${this.config.apiKey}`\n\n const response = await this.fetchFn(this.url, {\n method: 'POST',\n headers,\n body: JSON.stringify({ model: this.config.model, input: texts }),\n })\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '')\n throw new Error(`Embedding API request failed: ${response.status} ${detail}`.trim())\n }\n\n const payload = (await response.json()) as EmbeddingResponse\n const sorted = [...payload.data].sort((a, b) => a.index - b.index)\n const vectors = sorted.map((item) => Float32Array.from(item.embedding))\n if (vectors.length > 0) this.knownDim = vectors[0].length\n return vectors\n }\n\n async embedQuery(text: string): Promise<Float32Array> {\n const [vector] = await this.embedDocuments([text])\n return vector\n }\n\n async preload(): Promise<void> {\n // API mode: no local model to download\n }\n}\n","/**\n * Embedding 输入文本处理(纯函数)\n */\n\n/** 给 query 加模型 queryInstruction 前缀;document 不调用此函数 */\nexport function applyQueryInstruction(instruction: string, query: string): string {\n const trimmed = instruction.trim()\n if (!trimmed) return query\n if (trimmed.endsWith(':') || trimmed.endsWith(':')) return `${trimmed}${query}`\n return `Instruct: ${trimmed}\\nQuery: ${query}`\n}\n\n/** 按字符上限截断文本;maxChars 未提供或文本更短时原样返回 */\nexport function clampTextChars(text: string, maxChars?: number): string {\n if (maxChars === undefined || text.length <= maxChars) return text\n return text.slice(0, maxChars)\n}\n","/**\n * 本地 ONNX embedding provider(@huggingface/transformers)\n *\n * 关键约束(P0-3):Qwen3 必须 batch=1,batch 会污染 last_token embedding;本地 CPU\n * 上 batch 也无吞吐收益。profile.maxBatchSize 控制单次推理的最大文本数。\n *\n * pipeline 工厂可注入,单元测试用 fake 工厂验证 batch 切分与 query instruction,不下载模型;\n * 真实模型加载在 env-gated smoke 测试中验证。\n */\n\nimport type { EmbeddingProvider } from './types'\nimport type { LocalEmbeddingProfile } from './profiles'\nimport { applyQueryInstruction, clampTextChars } from './text'\n\ntype UndiciFetch = (typeof import('undici'))['fetch']\ntype UndiciRequestInit = NonNullable<Parameters<UndiciFetch>[1]>\nconst SOCKS_PROXY_PROTOCOLS = new Set(['socks:', 'socks4:', 'socks5:'])\n\n/** 一次特征抽取调用:输入文本数组,返回每条文本的向量(number[][]) */\nexport type FeatureExtractFn = (\n texts: string[],\n options: { pooling: LocalEmbeddingProfile['pooling']; normalize: boolean }\n) => Promise<number[][]>\n\n/** pipeline 工厂:按 modelId/dtype/cacheDir 构造特征抽取器 */\nexport type LocalPipelineFactory = (params: {\n modelId: string\n dtype?: 'fp32' | 'q8'\n cacheDir?: string\n modelDownloadProxyUrl?: string\n}) => Promise<FeatureExtractFn>\n\nexport async function createProxyFetch(proxyUrl: string): Promise<typeof fetch> {\n const parsed = new URL(proxyUrl)\n if (SOCKS_PROXY_PROTOCOLS.has(parsed.protocol)) {\n throw new Error(\n `SOCKS proxy is not supported for local embedding model downloads: ${parsed.protocol}//${parsed.host}`\n )\n }\n const { fetch: undiciFetch, ProxyAgent } = await import('undici')\n const dispatcher = new ProxyAgent(proxyUrl)\n return ((input, init) => {\n const nextInit: UndiciRequestInit = { ...(init as UndiciRequestInit | undefined), dispatcher }\n return undiciFetch(input as Parameters<UndiciFetch>[0], nextInit) as unknown as ReturnType<typeof fetch>\n }) as typeof fetch\n}\n\nconst defaultPipelineFactory: LocalPipelineFactory = async ({ modelId, dtype, cacheDir, modelDownloadProxyUrl }) => {\n const transformers = await import('@huggingface/transformers')\n if (cacheDir) {\n transformers.env.cacheDir = cacheDir\n transformers.env.allowRemoteModels = true\n }\n if (modelDownloadProxyUrl) {\n transformers.env.fetch = await createProxyFetch(modelDownloadProxyUrl)\n }\n const extractor = await transformers.pipeline('feature-extraction', modelId, dtype ? { dtype } : undefined)\n return async (texts, options) => {\n const out = await extractor(texts, { pooling: options.pooling, normalize: options.normalize })\n return out.tolist() as number[][]\n }\n}\n\nexport interface LocalEmbeddingProviderOptions {\n cacheDir?: string\n modelDownloadProxyUrl?: string\n pipelineFactory?: LocalPipelineFactory\n}\n\nexport class LocalEmbeddingProvider implements EmbeddingProvider {\n readonly modelId: string\n readonly dim: number\n readonly maxTokens: number\n readonly documentBatchSize: number\n\n private profile: LocalEmbeddingProfile\n private options: LocalEmbeddingProviderOptions\n private extractorPromise: Promise<FeatureExtractFn> | null = null\n\n constructor(profile: LocalEmbeddingProfile, options: LocalEmbeddingProviderOptions = {}) {\n this.profile = profile\n this.options = options\n this.modelId = profile.modelId\n this.dim = profile.dim\n this.maxTokens = profile.maxTokens\n this.documentBatchSize = profile.maxBatchSize ?? 1\n }\n\n private getExtractor(): Promise<FeatureExtractFn> {\n if (!this.extractorPromise) {\n const factory = this.options.pipelineFactory ?? defaultPipelineFactory\n this.extractorPromise = factory({\n modelId: this.profile.modelId,\n dtype: this.profile.dtype,\n cacheDir: this.options.cacheDir,\n modelDownloadProxyUrl: this.options.modelDownloadProxyUrl,\n })\n }\n return this.extractorPromise\n }\n\n private toFloat32(vector: number[]): Float32Array {\n if (vector.length !== this.profile.dim) {\n throw new Error(`model ${this.profile.modelId} returned dim ${vector.length}, expected ${this.profile.dim}`)\n }\n return Float32Array.from(vector)\n }\n\n async embedDocuments(texts: string[]): Promise<Float32Array[]> {\n if (texts.length === 0) return []\n const extractor = await this.getExtractor()\n const batchSize = this.profile.maxBatchSize ?? texts.length\n const clamped = texts.map((t) => clampTextChars(t, this.profile.maxTextChars))\n\n const result: Float32Array[] = []\n for (let i = 0; i < clamped.length; i += batchSize) {\n const batch = clamped.slice(i, i + batchSize)\n const vectors = await extractor(batch, { pooling: this.profile.pooling, normalize: this.profile.normalize })\n for (const v of vectors) result.push(this.toFloat32(v))\n }\n return result\n }\n\n async embedQuery(text: string): Promise<Float32Array> {\n const withInstruction = applyQueryInstruction(this.profile.queryInstruction, text)\n const [vector] = await this.embedDocuments([withInstruction])\n return vector\n }\n\n async preload(): Promise<void> {\n await this.getExtractor()\n }\n}\n","/**\n * 本地 embedding 模型 profile(静态定义,单一事实来源)\n *\n * 取值依据 chunking-decision-final.md 第 6.2/6.3 节与 P0-2/P0-3 验证结论。\n * 这些 profile 决定 chunk 的 dim/pooling/normalize,变化会触发索引重建。\n */\n\nimport type { EmbeddingPooling } from './types'\n\nexport interface LocalEmbeddingProfile {\n /** 用户展示名 */\n displayName: string\n /** transformers.js 下载源 modelId */\n modelId: string\n architecture: 'bert' | 'qwen3'\n dim: number\n /** 单文本 token 上限(ChatLab profile cap,非模型真实上限) */\n maxTokens: number\n /** 单文本字符上限(可选护栏) */\n maxTextChars?: number\n pooling: EmbeddingPooling\n normalize: boolean\n /** transformers.js dtype;BGE 用 fp32,Qwen3 用 q8 */\n dtype?: 'fp32' | 'q8'\n /** 固定 batch 上限;Qwen3 必须为 1(P0-3:batch 污染 last_token) */\n maxBatchSize?: number\n /** query 前缀指令;document 不加 */\n queryInstruction: string\n /** 近似下载体积(MB),用于 UI 提示 */\n approxDownloadMB: number\n}\n\n/** Qwen3-Embedding-0.6B:通用本地推荐模型,必须 batch=1(P0-3) */\nexport const QWEN3_PROFILE: LocalEmbeddingProfile = {\n displayName: 'Qwen3 Embedding 0.6B',\n modelId: 'onnx-community/Qwen3-Embedding-0.6B-ONNX',\n architecture: 'qwen3',\n dim: 1024,\n maxTokens: 8192,\n maxTextChars: 2400,\n pooling: 'last_token',\n normalize: true,\n dtype: 'q8',\n maxBatchSize: 1,\n queryInstruction: 'Given a chat history search query, retrieve relevant conversation messages that answer the query',\n approxDownloadMB: 593,\n}\n\n/** BGE base zh v1.5:中文轻量入口,比 small 强、比 Qwen3 轻(cls pooling,512 token 上限) */\nexport const BGE_BASE_PROFILE: LocalEmbeddingProfile = {\n displayName: 'BGE base zh',\n modelId: 'Xenova/bge-base-zh-v1.5',\n architecture: 'bert',\n dim: 768,\n maxTokens: 512,\n pooling: 'cls',\n normalize: true,\n dtype: 'fp32',\n queryInstruction: '为这个句子生成表示以用于检索相关文章:',\n approxDownloadMB: 390,\n}\n\nconst ALL_LOCAL_PROFILES: LocalEmbeddingProfile[] = [QWEN3_PROFILE, BGE_BASE_PROFILE]\n\n/**\n * 按 UI 语言返回可选本地模型列表。\n * Qwen3 为各语言通用推荐;BGE base zh 仅中文 UI 提供(中文轻量替代)。\n */\nexport function getLocalProfilesForLocale(locale: string): LocalEmbeddingProfile[] {\n if (locale.startsWith('zh')) return [QWEN3_PROFILE, BGE_BASE_PROFILE]\n return [QWEN3_PROFILE]\n}\n\nexport function getLocalProfileByModelId(modelId: string): LocalEmbeddingProfile | null {\n return ALL_LOCAL_PROFILES.find((p) => p.modelId === modelId) ?? null\n}\n","/**\n * Embedder 工厂\n *\n * 从语义索引配置构造 EmbeddingProvider:\n * - local:按 modelId 取静态 profile,注入本地模型缓存目录。\n * - api:OpenAI-compatible,API Key 从 auth-profiles.json 按 authProfile 引用解析。\n *\n * resolveApiKey / pipelineFactory / fetchFn 均可注入,便于单测不联网、不下载模型。\n */\n\nimport { resolveApiKey as defaultResolveApiKey } from '@openchatlab/config'\nimport { OpenAICompatibleEmbeddingProvider, type FetchFn } from './embedding/api'\nimport { LocalEmbeddingProvider, type LocalPipelineFactory } from './embedding/local'\nimport { getLocalProfileByModelId } from './embedding/profiles'\nimport type { EmbeddingProvider } from './embedding/types'\nimport type { SemanticIndexConfig } from './config'\n\nexport interface EmbedderFactoryDeps {\n /** 本地模型目录,例如 ~/.chatlab/ai/models/semantic-index */\n modelsCacheDir?: string\n /** Optional HTTP(S) proxy URL used only for downloading local embedding model files. */\n modelDownloadProxyUrl?: string\n /** auth-profiles 解析,默认走 @openchatlab/config */\n resolveApiKey?: (provider: string, authProfile?: string) => string\n /** 本地 pipeline 工厂注入(测试用,不下载模型) */\n localPipelineFactory?: LocalPipelineFactory\n /** API fetch 注入(测试用,不联网) */\n fetchFn?: FetchFn\n}\n\nexport function createEmbedder(config: SemanticIndexConfig, deps: EmbedderFactoryDeps = {}): EmbeddingProvider {\n if (config.mode === 'api') {\n if (!config.api || !config.api.baseUrl || !config.api.model) {\n throw new Error('semantic index API config incomplete: baseUrl and model are required')\n }\n const resolve = deps.resolveApiKey ?? defaultResolveApiKey\n const apiKey = resolve('', config.api.authProfile)\n return new OpenAICompatibleEmbeddingProvider(\n { baseUrl: config.api.baseUrl, apiKey, model: config.api.model },\n { fetchFn: deps.fetchFn }\n )\n }\n\n const profile = getLocalProfileByModelId(config.local.modelId)\n if (!profile) {\n throw new Error(`unknown local embedding model: ${config.local.modelId}`)\n }\n return new LocalEmbeddingProvider(profile, {\n cacheDir: deps.modelsCacheDir,\n modelDownloadProxyUrl: deps.modelDownloadProxyUrl,\n pipelineFactory: deps.localPipelineFactory,\n })\n}\n","/**\n * 语义索引向量存储(sqlite-vec vec0 + better-sqlite3)\n *\n * embedding_index.db 是独立系统级向量库。本存储负责:\n * - 初始化元数据表与按维度的 vec0 表。\n * - 写入 chunk 元数据与向量(同一事务,rowid 关联)。\n * - dense ANN 查询(限定 db_path_hash + model_id 分区)。\n * - FTS message_id -> chunk 的 O(log n) 范围映射。\n *\n * P0-1 硬约定:vec0 的 INTEGER 列与 k 参数绑定必须使用 CAST(? AS INTEGER)。\n */\n\nimport Database from 'better-sqlite3'\nimport * as sqliteVec from 'sqlite-vec'\nimport type {\n ChunkInsert,\n ChunkRecord,\n ChunkStatus,\n DenseQueryParams,\n DenseQueryResult,\n MessageToChunkParams,\n} from './types'\nimport { EMBEDDING_INDEX_SCHEMA, vecTableName, vecTableSchema } from './schema'\n\n/** 加载 sqlite-vec 扩展。Electron 打包后可注入自定义实现处理 asar 解包路径。 */\nexport type LoadSqliteVec = (db: Database.Database) => void\n\nconst defaultLoadSqliteVec: LoadSqliteVec = (db) => sqliteVec.load(db)\n\ninterface ChunkRow {\n chunk_id: string\n db_path_hash: string\n strategy_id: string\n model_id: string\n dim: number\n parent_id: string\n start_message_id: number\n end_message_id: number\n start_ts: number\n end_ts: number\n message_count: number\n raw_content_hash: string\n embedding_input_hash: string\n chunker_version: string\n chunker_config_hash: string\n indexed_at: number\n status: string\n}\n\nfunction rowToRecord(row: ChunkRow): ChunkRecord {\n return {\n chunkId: row.chunk_id,\n dbPathHash: row.db_path_hash,\n strategyId: row.strategy_id,\n modelId: row.model_id,\n dim: row.dim,\n parentId: row.parent_id,\n startMessageId: row.start_message_id,\n endMessageId: row.end_message_id,\n startTs: row.start_ts,\n endTs: row.end_ts,\n messageCount: row.message_count,\n rawContentHash: row.raw_content_hash,\n embeddingInputHash: row.embedding_input_hash,\n chunkerVersion: row.chunker_version,\n chunkerConfigHash: row.chunker_config_hash,\n indexedAt: row.indexed_at,\n status: row.status as ChunkStatus,\n }\n}\n\nfunction toFloat32Buffer(embedding: Float32Array | number[]): Buffer {\n const arr = embedding instanceof Float32Array ? embedding : new Float32Array(embedding)\n return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength)\n}\n\nconst CHUNK_COLUMN_LIST = [\n 'chunk_id',\n 'db_path_hash',\n 'strategy_id',\n 'model_id',\n 'dim',\n 'parent_id',\n 'start_message_id',\n 'end_message_id',\n 'start_ts',\n 'end_ts',\n 'message_count',\n 'raw_content_hash',\n 'embedding_input_hash',\n 'chunker_version',\n 'chunker_config_hash',\n 'indexed_at',\n 'status',\n] as const\n\nconst CHUNK_COLUMNS = CHUNK_COLUMN_LIST.join(', ')\nconst CHUNK_COLUMNS_PREFIXED = CHUNK_COLUMN_LIST.map((c) => `m.${c}`).join(', ')\n\nexport class EmbeddingIndexStore {\n private db: Database.Database\n private ensuredVecDims = new Set<number>()\n\n constructor(dbPath: string, options?: { loadSqliteVec?: LoadSqliteVec; readonly?: boolean; nativeBinding?: string }) {\n this.db = new Database(dbPath, { readonly: options?.readonly ?? false, nativeBinding: options?.nativeBinding })\n ;(options?.loadSqliteVec ?? defaultLoadSqliteVec)(this.db)\n if (!options?.readonly) {\n this.db.pragma('journal_mode = WAL')\n this.db.exec(EMBEDDING_INDEX_SCHEMA)\n }\n }\n\n /** 确保对应维度的 vec0 表已创建(懒加载) */\n private ensureVecTable(dim: number): void {\n if (this.ensuredVecDims.has(dim)) return\n this.db.exec(vecTableSchema(dim))\n this.ensuredVecDims.add(dim)\n }\n\n private insertOne(item: ChunkInsert): void {\n const { record, embedding } = item\n if (embedding.length !== record.dim) {\n throw new Error(`embedding length ${embedding.length} mismatches dim ${record.dim} for chunk ${record.chunkId}`)\n }\n this.ensureVecTable(record.dim)\n\n const meta = this.db\n .prepare(\n // ponytail: OR IGNORE makes resume after mid-batch crash idempotent; vec insert skipped below if chunk exists\n `INSERT OR IGNORE INTO chunk_vector_index (${CHUNK_COLUMNS})\n VALUES (@chunkId, @dbPathHash, @strategyId, @modelId, @dim, @parentId, @startMessageId, @endMessageId,\n @startTs, @endTs, @messageCount, @rawContentHash, @embeddingInputHash, @chunkerVersion,\n @chunkerConfigHash, @indexedAt, @status)`\n )\n .run({\n chunkId: record.chunkId,\n dbPathHash: record.dbPathHash,\n strategyId: record.strategyId,\n modelId: record.modelId,\n dim: record.dim,\n parentId: record.parentId,\n startMessageId: record.startMessageId,\n endMessageId: record.endMessageId,\n startTs: record.startTs,\n endTs: record.endTs,\n messageCount: record.messageCount,\n rawContentHash: record.rawContentHash,\n embeddingInputHash: record.embeddingInputHash,\n chunkerVersion: record.chunkerVersion,\n chunkerConfigHash: record.chunkerConfigHash,\n indexedAt: record.indexedAt,\n status: record.status,\n })\n\n if (meta.changes === 0) return\n this.db\n .prepare(\n `INSERT INTO ${vecTableName(record.dim)} (vector_id, db_path_hash, model_id, embedding)\n VALUES (CAST(? AS INTEGER), ?, ?, ?)`\n )\n .run(Number(meta.lastInsertRowid), record.dbPathHash, record.modelId, toFloat32Buffer(embedding))\n }\n\n /** 写入单个 chunk(元数据 + 向量,同一事务) */\n insertChunk(record: ChunkRecord, embedding: Float32Array | number[]): void {\n this.db.transaction(() => this.insertOne({ record, embedding }))()\n }\n\n /** 批量写入 chunk(单事务) */\n insertChunks(items: ChunkInsert[]): void {\n this.db.transaction(() => {\n for (const item of items) this.insertOne(item)\n })()\n }\n\n /** dense ANN 查询,限定 db_path_hash + model_id 分区,cosine 距离升序 */\n queryDense(params: DenseQueryParams): DenseQueryResult[] {\n const table = vecTableName(params.dim)\n if (!this.tableExists(table)) return []\n\n const rows = this.db\n .prepare(\n `SELECT v.distance AS distance, ${CHUNK_COLUMNS_PREFIXED}\n FROM ${table} v\n JOIN chunk_vector_index m ON m.rowid = v.vector_id\n WHERE v.embedding MATCH ? AND v.db_path_hash = ? AND v.model_id = ? AND v.k = CAST(? AS INTEGER)\n ORDER BY v.distance`\n )\n .all(toFloat32Buffer(params.embedding), params.dbPathHash, params.modelId, params.k) as Array<\n ChunkRow & { distance: number }\n >\n\n return rows.map((row) => ({ chunkId: row.chunk_id, distance: row.distance, record: rowToRecord(row) }))\n }\n\n /**\n * FTS message -> chunk 映射。按 (ts, id) 复合查找:取 (start_ts, start_message_id) <= (messageTs, messageId)\n * 的最大值,再校验 end 覆盖。\n * - 用 ts 而非 ID 做主键:回填旧消息后 ID 不再单调\n * - 用 (ts, id) 复合而非仅 ts:同秒多 chunk 时 ts 相同,需 id 打破 tie\n */\n mapMessageToChunk(params: MessageToChunkParams): ChunkRecord | null {\n const row = this.db\n .prepare(\n `SELECT ${CHUNK_COLUMNS} FROM chunk_vector_index\n WHERE db_path_hash = ? AND model_id = ? AND strategy_id = ?\n AND (start_ts < CAST(? AS INTEGER)\n OR (start_ts = CAST(? AS INTEGER) AND start_message_id <= CAST(? AS INTEGER)))\n ORDER BY start_ts DESC, start_message_id DESC LIMIT 1`\n )\n .get(\n params.dbPathHash,\n params.modelId,\n params.strategyId,\n params.messageTs,\n params.messageTs,\n params.messageId\n ) as ChunkRow | undefined\n\n if (!row) return null\n if (row.end_ts < params.messageTs) return null\n if (row.end_ts === params.messageTs && row.end_message_id < params.messageId) return null\n return rowToRecord(row)\n }\n\n /** 删除某聊天库(全模型)的所有 chunk 元数据与向量,返回删除的元数据行数 */\n deleteByDbPathHash(dbPathHash: string): number {\n return this.db.transaction(() => {\n const rows = this.db\n .prepare('SELECT rowid AS rowid, dim FROM chunk_vector_index WHERE db_path_hash = ?')\n .all(dbPathHash) as Array<{ rowid: number; dim: number }>\n\n const delStmts = new Map<number, Database.Statement>()\n for (const { rowid, dim } of rows) {\n const table = vecTableName(dim)\n if (!this.tableExists(table)) continue\n let stmt = delStmts.get(dim)\n if (!stmt) {\n stmt = this.db.prepare(`DELETE FROM ${table} WHERE vector_id = CAST(? AS INTEGER)`)\n delStmts.set(dim, stmt)\n }\n stmt.run(rowid)\n }\n\n const res = this.db.prepare('DELETE FROM chunk_vector_index WHERE db_path_hash = ?').run(dbPathHash)\n return res.changes\n })()\n }\n\n /** 删除某聊天库 + 模型中从指定聊天顺序位置开始的 chunks,返回删除的元数据行数 */\n deleteByModelFromPosition(params: {\n dbPathHash: string\n modelId: string\n startTs: number\n startMessageId: number\n }): number {\n return this.db.transaction(() => {\n const rows = this.db\n .prepare(\n `SELECT rowid AS rowid, dim FROM chunk_vector_index\n WHERE db_path_hash = ? AND model_id = ?\n AND (start_ts > CAST(? AS INTEGER)\n OR (start_ts = CAST(? AS INTEGER) AND start_message_id >= CAST(? AS INTEGER)))`\n )\n .all(params.dbPathHash, params.modelId, params.startTs, params.startTs, params.startMessageId) as Array<{\n rowid: number\n dim: number\n }>\n\n const delStmts = new Map<number, Database.Statement>()\n for (const { rowid, dim } of rows) {\n const table = vecTableName(dim)\n if (!this.tableExists(table)) continue\n let stmt = delStmts.get(dim)\n if (!stmt) {\n stmt = this.db.prepare(`DELETE FROM ${table} WHERE vector_id = CAST(? AS INTEGER)`)\n delStmts.set(dim, stmt)\n }\n stmt.run(rowid)\n }\n\n const res = this.db\n .prepare(\n `DELETE FROM chunk_vector_index\n WHERE db_path_hash = ? AND model_id = ?\n AND (start_ts > CAST(? AS INTEGER)\n OR (start_ts = CAST(? AS INTEGER) AND start_message_id >= CAST(? AS INTEGER)))`\n )\n .run(params.dbPathHash, params.modelId, params.startTs, params.startTs, params.startMessageId)\n return res.changes\n })()\n }\n\n /** 列出存储中存在 chunk 的所有 db_path_hash */\n listDbPathHashes(): string[] {\n const rows = this.db.prepare('SELECT DISTINCT db_path_hash FROM chunk_vector_index').all() as Array<{\n db_path_hash: string\n }>\n return rows.map((r) => r.db_path_hash)\n }\n\n /** 统计某聊天库 chunk 数(可选限定 model_id) */\n countChunks(dbPathHash: string, modelId?: string): number {\n const row = modelId\n ? (this.db\n .prepare('SELECT COUNT(*) AS c FROM chunk_vector_index WHERE db_path_hash = ? AND model_id = ?')\n .get(dbPathHash, modelId) as { c: number })\n : (this.db.prepare('SELECT COUNT(*) AS c FROM chunk_vector_index WHERE db_path_hash = ?').get(dbPathHash) as {\n c: number\n })\n return row.c\n }\n\n /** 取某聊天库 + 模型已写入 chunk 的维度(用于检索选表);无 chunk 返回 null */\n getDim(dbPathHash: string, modelId: string): number | null {\n const row = this.db\n .prepare('SELECT dim FROM chunk_vector_index WHERE db_path_hash = ? AND model_id = ? LIMIT 1')\n .get(dbPathHash, modelId) as { dim: number } | undefined\n return row ? row.dim : null\n }\n\n /** 按 chunk_id 取元数据 */\n getChunkById(chunkId: string): ChunkRecord | null {\n const row = this.db.prepare(`SELECT ${CHUNK_COLUMNS} FROM chunk_vector_index WHERE chunk_id = ?`).get(chunkId) as\n | ChunkRow\n | undefined\n return row ? rowToRecord(row) : null\n }\n\n private tableExists(name: string): boolean {\n return !!this.db.prepare(`SELECT 1 FROM sqlite_master WHERE name = ?`).get(name)\n }\n\n close(): void {\n this.db.close()\n }\n}\n","\n\nimport { fileURLToPath } from \"node:url\";\nimport { arch, platform } from \"node:process\";\n\n\nconst BASE_PACKAGE_NAME = \"sqlite-vec\";\nconst ENTRYPOINT_BASE_NAME = \"vec0\";\nconst supportedPlatforms = [[\"darwin\",\"x64\"],[\"linux\",\"x64\"],[\"darwin\",\"arm64\"],[\"win32\",\"x64\"],[\"linux\",\"arm64\"]];\n\nconst invalidPlatformErrorMessage = `Unsupported platform for ${BASE_PACKAGE_NAME}, on a ${platform}-${arch} machine. Supported platforms are (${supportedPlatforms\n .map(([p, a]) => `${p}-${a}`)\n .join(\",\")}). Consult the ${BASE_PACKAGE_NAME} NPM package README for details.`;\n\nconst extensionNotFoundErrorMessage = packageName => `Loadble extension for ${BASE_PACKAGE_NAME} not found. Was the ${packageName} package installed?`;\n\nfunction validPlatform(platform, arch) {\n return (\n supportedPlatforms.find(([p, a]) => platform === p && arch === a) !== undefined\n );\n}\nfunction extensionSuffix(platform) {\n if (platform === \"win32\") return \"dll\";\n if (platform === \"darwin\") return \"dylib\";\n return \"so\";\n}\nfunction platformPackageName(platform, arch) {\n const os = platform === \"win32\" ? \"windows\" : platform;\n return `${BASE_PACKAGE_NAME}-${os}-${arch}`;\n}\n\nfunction getLoadablePath() {\n if (!validPlatform(platform, arch)) {\n throw new Error(\n invalidPlatformErrorMessage\n );\n }\n const packageName = platformPackageName(platform, arch);\n const loadablePath = fileURLToPath(import.meta.resolve(packageName + \"/\" + ENTRYPOINT_BASE_NAME + \".\" + extensionSuffix(platform)));\n return loadablePath;\n}\n\nfunction load(db) {\n db.loadExtension(getLoadablePath());\n}\n\nexport {getLoadablePath, load};\n","/**\n * embedding_index.db schema 定义(单一事实来源)\n *\n * 结构:\n * - chunk_vector_index:dim 无关的元数据普通表,rowid 关联 vec0 向量。\n * - chunk_vec_{dim}:每个 embedding 维度一张 vec0 虚拟表(vec0 要求列内维度固定)。\n * PARTITION KEY 同时包含 db_path_hash 和 model_id,保证查询命中分区裁剪、隔离\n * 模型切换/重建期的新旧向量。详见 chunking-decision-final.md 第 11.1/17 节。\n */\n\n/** 元数据表(dim 无关),rowid 即对应 vec0 的 vector_id */\nexport const CHUNK_VECTOR_INDEX_TABLE = `\n CREATE TABLE IF NOT EXISTS chunk_vector_index (\n rowid INTEGER PRIMARY KEY,\n chunk_id TEXT NOT NULL UNIQUE,\n db_path_hash TEXT NOT NULL,\n strategy_id TEXT NOT NULL,\n model_id TEXT NOT NULL,\n dim INTEGER NOT NULL,\n parent_id TEXT NOT NULL,\n start_message_id INTEGER NOT NULL,\n end_message_id INTEGER NOT NULL,\n start_ts INTEGER NOT NULL,\n end_ts INTEGER NOT NULL,\n message_count INTEGER NOT NULL,\n raw_content_hash TEXT NOT NULL,\n embedding_input_hash TEXT NOT NULL,\n chunker_version TEXT NOT NULL,\n chunker_config_hash TEXT NOT NULL,\n indexed_at INTEGER NOT NULL,\n status TEXT NOT NULL\n );\n`\n\n/**\n * 范围映射索引:\n * - idx_chunk_range 保留 message_id 领先的旧查询路径和既有数据库兼容性。\n * - idx_chunk_ts_range 支撑当前 `start_ts, start_message_id` 组合查找,避免按会话扫描排序。\n */\nexport const CHUNK_VECTOR_INDEX_INDEXES = `\n CREATE INDEX IF NOT EXISTS idx_chunk_range\n ON chunk_vector_index(db_path_hash, model_id, strategy_id, start_message_id, end_message_id);\n CREATE INDEX IF NOT EXISTS idx_chunk_ts_range\n ON chunk_vector_index(db_path_hash, model_id, strategy_id, start_ts, start_message_id);\n`\n\nexport const EMBEDDING_INDEX_SCHEMA = CHUNK_VECTOR_INDEX_TABLE + CHUNK_VECTOR_INDEX_INDEXES\n\n/**\n * 语义索引业务状态表(与向量表同库)。\n *\n * 保存\"对话是否启用索引\"、索引进度/状态与清理状态。这些状态是权威来源,\n * 不能从后台任务队列反推(见 chunking-decision-final.md 第 11.1/15 节)。\n * 以 db_path_hash 作为对话主键(一个聊天库 = 一个对话)。\n */\nexport const SEMANTIC_INDEX_SESSION_TABLE = `\n CREATE TABLE IF NOT EXISTS semantic_index_session (\n db_path_hash TEXT PRIMARY KEY,\n db_path TEXT NOT NULL,\n enabled INTEGER NOT NULL DEFAULT 0,\n model_id TEXT,\n index_status TEXT NOT NULL DEFAULT 'idle',\n cleanup_status TEXT NOT NULL DEFAULT 'none',\n total_messages INTEGER NOT NULL DEFAULT 0,\n indexed_messages INTEGER NOT NULL DEFAULT 0,\n last_indexed_message_id INTEGER,\n chunk_count INTEGER NOT NULL DEFAULT 0,\n chunker_version TEXT,\n chunker_config_hash TEXT,\n error TEXT,\n enabled_at INTEGER,\n updated_at INTEGER NOT NULL\n );\n`\n\n/** 给定维度的 vec0 表名 */\nexport function vecTableName(dim: number): string {\n return `chunk_vec_${dim}`\n}\n\n/** 给定维度的 vec0 虚拟表 DDL */\nexport function vecTableSchema(dim: number): string {\n return `\n CREATE VIRTUAL TABLE IF NOT EXISTS ${vecTableName(dim)} USING vec0(\n vector_id INTEGER PRIMARY KEY,\n db_path_hash TEXT PARTITION KEY,\n model_id TEXT PARTITION KEY,\n embedding FLOAT[${dim}] distance_metric=cosine\n );\n `\n}\n","/**\n * 语义索引业务状态存储(与向量库同一 embedding_index.db 文件)\n *\n * 保存对话级权威状态:是否启用、索引进度/状态、清理状态。后台任务队列只负责执行,\n * 启用与结果状态不从队列反推(见 chunking-decision-final.md 第 11.1/15 节)。\n * 以 db_path_hash 作为对话主键(一个聊天库 = 一个对话)。\n */\n\nimport Database from 'better-sqlite3'\nimport { SEMANTIC_INDEX_SESSION_TABLE } from './schema'\n\n/** 索引生命周期状态 */\nexport type SemanticIndexStatus = 'idle' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled'\n\n/** 清理生命周期状态 */\nexport type SemanticIndexCleanupStatus = 'none' | 'pending' | 'running' | 'done'\n\nexport interface SemanticIndexSessionState {\n dbPathHash: string\n dbPath: string\n enabled: boolean\n modelId: string | null\n indexStatus: SemanticIndexStatus\n cleanupStatus: SemanticIndexCleanupStatus\n totalMessages: number\n indexedMessages: number\n lastIndexedMessageId: number | null\n chunkCount: number\n /** 建立索引时使用的 chunker 算法版本(与当前不一致 => 需重建) */\n chunkerVersion: string | null\n /** 建立索引时使用的 chunker 参数 hash(与当前不一致 => 需重建) */\n chunkerConfigHash: string | null\n error: string | null\n enabledAt: number | null\n updatedAt: number\n}\n\nexport interface EnableParams {\n dbPathHash: string\n dbPath: string\n modelId: string\n /** 当前 chunker 算法版本,随索引一起记录用于重建判断 */\n chunkerVersion: string\n /** 当前 chunker 参数 hash,随索引一起记录用于重建判断 */\n chunkerConfigHash: string\n}\n\nexport interface ProgressPatch {\n indexStatus?: SemanticIndexStatus\n totalMessages?: number\n indexedMessages?: number\n lastIndexedMessageId?: number\n chunkCount?: number\n error?: string | null\n}\n\ninterface SessionRow {\n db_path_hash: string\n db_path: string\n enabled: number\n model_id: string | null\n index_status: string\n cleanup_status: string\n total_messages: number\n indexed_messages: number\n last_indexed_message_id: number | null\n chunk_count: number\n chunker_version: string | null\n chunker_config_hash: string | null\n error: string | null\n enabled_at: number | null\n updated_at: number\n}\n\nfunction rowToState(row: SessionRow): SemanticIndexSessionState {\n return {\n dbPathHash: row.db_path_hash,\n dbPath: row.db_path,\n enabled: row.enabled === 1,\n modelId: row.model_id,\n indexStatus: row.index_status as SemanticIndexStatus,\n cleanupStatus: row.cleanup_status as SemanticIndexCleanupStatus,\n totalMessages: row.total_messages,\n indexedMessages: row.indexed_messages,\n lastIndexedMessageId: row.last_indexed_message_id,\n chunkCount: row.chunk_count,\n chunkerVersion: row.chunker_version ?? null,\n chunkerConfigHash: row.chunker_config_hash ?? null,\n error: row.error,\n enabledAt: row.enabled_at,\n updatedAt: row.updated_at,\n }\n}\n\nexport class SemanticIndexStateStore {\n private db: Database.Database\n\n constructor(dbPath: string, options?: { nativeBinding?: string }) {\n this.db = new Database(dbPath, { nativeBinding: options?.nativeBinding })\n this.db.pragma('journal_mode = WAL')\n this.db.exec(SEMANTIC_INDEX_SESSION_TABLE)\n this.ensureChunkerColumns()\n }\n\n /** 兼容旧版本表结构:缺少 chunker 身份列时补齐(identity 为空 => 后续判定需重建) */\n private ensureChunkerColumns(): void {\n const columns = this.db.prepare(`PRAGMA table_info(semantic_index_session)`).all() as { name: string }[]\n const names = new Set(columns.map((c) => c.name))\n if (!names.has('chunker_version')) {\n this.db.exec(`ALTER TABLE semantic_index_session ADD COLUMN chunker_version TEXT`)\n }\n if (!names.has('chunker_config_hash')) {\n this.db.exec(`ALTER TABLE semantic_index_session ADD COLUMN chunker_config_hash TEXT`)\n }\n }\n\n /** 启用对话索引;已存在则更新模型并清除待清理标记(覆盖重新启用/换模型场景) */\n enable(params: EnableParams): void {\n const now = Date.now()\n this.db\n .prepare(\n `INSERT INTO semantic_index_session\n (db_path_hash, db_path, enabled, model_id, index_status, cleanup_status,\n chunker_version, chunker_config_hash, enabled_at, updated_at)\n VALUES (@dbPathHash, @dbPath, 1, @modelId, 'idle', 'none',\n @chunkerVersion, @chunkerConfigHash, @now, @now)\n ON CONFLICT(db_path_hash) DO UPDATE SET\n db_path = excluded.db_path,\n enabled = 1,\n model_id = excluded.model_id,\n cleanup_status = 'none',\n chunker_version = excluded.chunker_version,\n chunker_config_hash = excluded.chunker_config_hash,\n enabled_at = COALESCE(semantic_index_session.enabled_at, excluded.enabled_at),\n updated_at = excluded.updated_at`\n )\n .run({\n dbPathHash: params.dbPathHash,\n dbPath: params.dbPath,\n modelId: params.modelId,\n chunkerVersion: params.chunkerVersion,\n chunkerConfigHash: params.chunkerConfigHash,\n now,\n })\n }\n\n /**\n * 重新激活已有状态行(stale 重建场景):仅更新 enabled 和 dbPath,保留旧身份字段,\n * 使 buildAllPending() 重启后仍能通过 isStale() 检测到需重建并入队 rebuild。\n */\n reactivate(dbPathHash: string, dbPath: string): void {\n this.db\n .prepare(\n `UPDATE semantic_index_session SET enabled = 1, db_path = ?, cleanup_status = 'none', updated_at = ? WHERE db_path_hash = ?`\n )\n .run(dbPath, Date.now(), dbPathHash)\n }\n\n /** 停用对话索引;标记待清理,保留状态与已建索引直到清理任务执行 */\n disable(dbPathHash: string): void {\n this.db\n .prepare(\n `UPDATE semantic_index_session\n SET enabled = 0, cleanup_status = 'pending', updated_at = ?\n WHERE db_path_hash = ?`\n )\n .run(Date.now(), dbPathHash)\n }\n\n updateProgress(dbPathHash: string, patch: ProgressPatch): void {\n const sets: string[] = []\n const values: Record<string, unknown> = { dbPathHash, now: Date.now() }\n const map: Record<keyof ProgressPatch, string> = {\n indexStatus: 'index_status',\n totalMessages: 'total_messages',\n indexedMessages: 'indexed_messages',\n lastIndexedMessageId: 'last_indexed_message_id',\n chunkCount: 'chunk_count',\n error: 'error',\n }\n for (const key of Object.keys(map) as (keyof ProgressPatch)[]) {\n if (patch[key] !== undefined) {\n sets.push(`${map[key]} = @${key}`)\n values[key] = patch[key]\n }\n }\n if (sets.length === 0) return\n this.db\n .prepare(\n `UPDATE semantic_index_session SET ${sets.join(', ')}, updated_at = @now WHERE db_path_hash = @dbPathHash`\n )\n .run(values)\n }\n\n setIndexStatus(dbPathHash: string, status: SemanticIndexStatus, error?: string | null): void {\n this.db\n .prepare(`UPDATE semantic_index_session SET index_status = ?, error = ?, updated_at = ? WHERE db_path_hash = ?`)\n .run(status, error ?? null, Date.now(), dbPathHash)\n }\n\n setCleanupStatus(dbPathHash: string, status: SemanticIndexCleanupStatus): void {\n this.db\n .prepare(`UPDATE semantic_index_session SET cleanup_status = ?, updated_at = ? WHERE db_path_hash = ?`)\n .run(status, Date.now(), dbPathHash)\n }\n\n /**\n * 刷新索引身份(模型 + chunker version/config hash)。\n * rebuild 以当前配置重写向量后调用,确保完成后不再被判为需重建(stale -> rebuild 闭环)。\n */\n setBuildIdentity(\n dbPathHash: string,\n identity: { modelId: string; chunkerVersion: string; chunkerConfigHash: string }\n ): void {\n this.db\n .prepare(\n `UPDATE semantic_index_session\n SET model_id = ?, chunker_version = ?, chunker_config_hash = ?, updated_at = ?\n WHERE db_path_hash = ?`\n )\n .run(identity.modelId, identity.chunkerVersion, identity.chunkerConfigHash, Date.now(), dbPathHash)\n }\n\n /** 重置进度与状态到初始(rebuild 前调用,清空断点游标,避免续跑跳过) */\n resetProgress(dbPathHash: string): void {\n this.db\n .prepare(\n `UPDATE semantic_index_session\n SET index_status = 'idle', indexed_messages = 0, chunk_count = 0,\n last_indexed_message_id = NULL, error = NULL, updated_at = ?\n WHERE db_path_hash = ?`\n )\n .run(Date.now(), dbPathHash)\n }\n\n getState(dbPathHash: string): SemanticIndexSessionState | null {\n const row = this.db.prepare(`SELECT * FROM semantic_index_session WHERE db_path_hash = ?`).get(dbPathHash) as\n | SessionRow\n | undefined\n return row ? rowToState(row) : null\n }\n\n listEnabled(): SemanticIndexSessionState[] {\n const rows = this.db\n .prepare(`SELECT * FROM semantic_index_session WHERE enabled = 1 ORDER BY enabled_at`)\n .all() as SessionRow[]\n return rows.map(rowToState)\n }\n\n listAll(): SemanticIndexSessionState[] {\n const rows = this.db.prepare(`SELECT * FROM semantic_index_session ORDER BY enabled_at`).all() as SessionRow[]\n return rows.map(rowToState)\n }\n\n listPendingCleanup(): SemanticIndexSessionState[] {\n const rows = this.db\n .prepare(`SELECT * FROM semantic_index_session WHERE cleanup_status = 'pending' ORDER BY updated_at`)\n .all() as SessionRow[]\n return rows.map(rowToState)\n }\n\n remove(dbPathHash: string): number {\n return this.db.prepare(`DELETE FROM semantic_index_session WHERE db_path_hash = ?`).run(dbPathHash).changes\n }\n\n close(): void {\n this.db.close()\n }\n}\n","/**\n * 语义索引串行 job 队列(Phase 1 simple runner)\n *\n * chunking-decision-final.md 第 15 节:build/rebuild/cleanup 任务串行处理,避免 CPU、\n * 内存、API 请求和 SQLite 写入竞争;支持按对话暂停、取消、续跑;失败保留已完成部分。\n *\n * 本队列只负责调度与停止信号管理,具体执行(读库/embedding/写入)由注入的 executor\n * 完成。队列不持久化;重启后的恢复由 service 层扫描业务状态表(权威来源)重新入队驱动。\n */\n\nimport type { StopSignal } from './runner'\n\nexport type SemanticIndexJobType = 'build' | 'rebuild' | 'cleanup'\n\nexport interface SemanticIndexJob {\n type: SemanticIndexJobType\n dbPathHash: string\n}\n\nexport interface JobContext {\n job: SemanticIndexJob\n /** executor 应将其传入 runWarmup;返回 'paused'/'cancelled' 时应停止 */\n checkStop: StopSignal\n}\n\nexport type JobExecutor = (ctx: JobContext) => Promise<void>\n\ntype StopState = null | 'paused' | 'cancelled'\n\nexport class SemanticIndexJobQueue {\n private executor: JobExecutor\n private pending: SemanticIndexJob[] = []\n private processing = false\n private currentHash: string | null = null\n private stopState: StopState = null\n private idleResolvers: (() => void)[] = []\n\n constructor(executor: JobExecutor) {\n this.executor = executor\n }\n\n /** 入队;同一对话 + 同类型的待处理任务去重 */\n enqueue(job: SemanticIndexJob): void {\n const duplicate = this.pending.some((j) => j.dbPathHash === job.dbPathHash && j.type === job.type)\n if (duplicate) return\n this.pending.push(job)\n void this.process()\n }\n\n /** 暂停指定对话:停止其正在运行的任务并移除其待处理任务(不自动续跑) */\n pause(dbPathHash: string): void {\n this.removePending(dbPathHash)\n if (this.currentHash === dbPathHash) this.stopState = 'paused'\n }\n\n /** 取消指定对话:停止其正在运行的任务并移除其待处理任务 */\n cancel(dbPathHash: string): void {\n this.removePending(dbPathHash)\n if (this.currentHash === dbPathHash) this.stopState = 'cancelled'\n }\n\n isRunning(dbPathHash: string): boolean {\n return this.currentHash === dbPathHash\n }\n\n isQueued(dbPathHash: string): boolean {\n return this.currentHash === dbPathHash || this.pending.some((j) => j.dbPathHash === dbPathHash)\n }\n\n /** 等待队列清空(含当前任务) */\n whenIdle(): Promise<void> {\n if (!this.processing && this.pending.length === 0) return Promise.resolve()\n return new Promise((resolve) => this.idleResolvers.push(resolve))\n }\n\n private removePending(dbPathHash: string): void {\n this.pending = this.pending.filter((j) => j.dbPathHash !== dbPathHash)\n }\n\n private async process(): Promise<void> {\n if (this.processing) return\n this.processing = true\n\n while (this.pending.length > 0) {\n const job = this.pending.shift()!\n this.currentHash = job.dbPathHash\n this.stopState = null\n try {\n await this.executor({ job, checkStop: () => this.stopState })\n } catch {\n // executor 内部负责把失败写入业务状态;队列继续处理后续任务\n }\n this.currentHash = null\n this.stopState = null\n }\n\n this.processing = false\n const resolvers = this.idleResolvers\n this.idleResolvers = []\n for (const resolve of resolvers) resolve()\n }\n}\n","/**\n * Parent/child chunker(纯函数)\n *\n * 实现 chunking-decision-final.md 第 7/8/9/10 节的 Hierarchical Balanced Chunking:\n * - parent:按时间 gap 粗分,超 token 估算上限再拆 sub-parent。\n * - child:parent 内按有效字符滑动,overlap 保留末尾若干条消息。\n * - 语义真空消息:不计有效字符、不进 header、不进 embedding body,但保留在范围与\n * raw_content_hash 中(证据可读性)。\n * - header:来源 + 时间范围 + 参与者(Contextual Retrieval)。\n *\n * 本模块与具体模型/维度无关;全局 chunk_id 由 warmup 层用 db_path_hash + model_id\n * 组合本模块输出的 localChunkId 得到。\n */\n\nimport { createHash } from 'crypto'\nimport { MessageType } from '@openchatlab/shared-types'\nimport {\n CHUNKER_VERSION,\n DEFAULT_CHUNKER_CONFIG,\n computeChunkerConfigHash,\n deriveParentId,\n type ChunkerConfig,\n} from './chunker-config'\nimport { estimateTokens } from './tokens'\n\nexport interface ChunkMessageInput {\n id: number\n senderName: string\n content: string | null\n /** 毫秒时间戳 */\n ts: number\n /** 平台消息类型(用于识别语义真空类型) */\n type?: number\n}\n\nexport interface ChunkSource {\n /** 对话名称(群名或私聊对端名) */\n title: string\n kind: 'group' | 'private'\n}\n\nexport interface ChunkMessagesInput {\n messages: ChunkMessageInput[]\n source: ChunkSource\n config?: ChunkerConfig\n}\n\nexport interface ChildChunk {\n /** 模型无关的稳定本地 id;全局 chunk_id 由 warmup 组合 db_path_hash + model_id */\n localChunkId: string\n parentId: string\n startMessageId: number\n endMessageId: number\n startTs: number\n endTs: number\n /** 范围内消息总数(含语义真空) */\n messageCount: number\n /** 非真空消息有效字符数 */\n effectiveChars: number\n rawContentHash: string\n embeddingInputHash: string\n /** 送入 embedding 模型的最终文本(header + 非真空 body) */\n embeddingInput: string\n}\n\nexport interface ChunkResult {\n chunkerVersion: string\n chunkerConfigHash: string\n chunks: ChildChunk[]\n skippedCount: number\n parentCount: number\n}\n\n// ==================== 语义真空判定 ====================\n\n/**\n * 语义真空消息类型(canonical MessageType):媒体/占位/系统类,自身不携带可嵌入文本。\n * TEXT(0) 与 LINK/SHARE/REPLY/FORWARD 等携带文本的类型不在此集合,由内容规则进一步判定。\n */\nconst VOID_MESSAGE_TYPES = new Set<number>([\n MessageType.IMAGE,\n MessageType.VOICE,\n MessageType.VIDEO,\n MessageType.FILE,\n MessageType.EMOJI,\n MessageType.LOCATION,\n MessageType.RED_PACKET,\n MessageType.TRANSFER,\n MessageType.POKE,\n MessageType.CALL,\n MessageType.CONTACT,\n MessageType.SYSTEM,\n MessageType.RECALL,\n])\n\n/** 占位类标签(与平台导出格式匹配,保持原始语言不变) */\nconst VOID_TEXT_PATTERNS = [\n '[图片]',\n '[语音]',\n '[视频]',\n '[文件]',\n '[表情]',\n '[动画表情]',\n '[位置]',\n '[名片]',\n '[红包]',\n '[转账]',\n '[撤回消息]',\n]\n\nconst VOID_REVOKE_PATTERNS = ['撤回了一条消息']\n\n/** 短填充语集合(语义真空),变更需 bump CHUNKER_VERSION */\nconst VOID_FILLERS = new Set([\n '好',\n '好的',\n '好滴',\n '好呀',\n '嗯',\n '嗯嗯',\n '嗯呢',\n '哦',\n '噢',\n '额',\n '呃',\n '哈',\n '哈哈',\n '哈哈哈',\n '呵呵',\n '在',\n '在吗',\n '在么',\n '收到',\n '行',\n '行吧',\n '可以',\n '是',\n '是的',\n '对',\n '对的',\n 'ok',\n 'okay',\n '👍',\n '👌',\n '🙏',\n])\n\n/** 归一化用于填充语匹配:去首尾空白、去末尾标点、英文小写 */\nfunction normalizeFiller(content: string): string {\n return content\n .trim()\n .toLowerCase()\n .replace(/[。.!!??、,,~~\\s]+$/u, '')\n}\n\nexport function isSemanticVoid(message: ChunkMessageInput): boolean {\n if (message.type !== undefined && VOID_MESSAGE_TYPES.has(message.type)) return true\n\n const content = (message.content ?? '').trim()\n if (content.length === 0) return true\n\n if (VOID_TEXT_PATTERNS.includes(content)) return true\n if (VOID_REVOKE_PATTERNS.some((p) => content.includes(p))) return true\n\n return VOID_FILLERS.has(normalizeFiller(content))\n}\n\n// ==================== 文本与 token 估算 ====================\n\nfunction effectiveContentOf(message: ChunkMessageInput): string {\n return isSemanticVoid(message) ? '' : (message.content ?? '').trim()\n}\n\nfunction sumEffectiveChars(messages: ChunkMessageInput[]): number {\n let total = 0\n for (const m of messages) total += effectiveContentOf(m).length\n return total\n}\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input, 'utf-8').digest('hex')\n}\n\n// ==================== header / body ====================\n\nfunction pad2(n: number): string {\n return String(n).padStart(2, '0')\n}\n\nfunction formatDateTime(ts: number): string {\n const d = new Date(ts)\n return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`\n}\n\nfunction formatTime(ts: number): string {\n const d = new Date(ts)\n return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`\n}\n\nfunction formatTimeRange(startTs: number, endTs: number): string {\n const start = new Date(startTs)\n const end = new Date(endTs)\n const sameDay =\n start.getFullYear() === end.getFullYear() &&\n start.getMonth() === end.getMonth() &&\n start.getDate() === end.getDate()\n return `${formatDateTime(startTs)} - ${sameDay ? formatTime(endTs) : formatDateTime(endTs)}`\n}\n\nfunction buildEmbeddingInput(source: ChunkSource, messages: ChunkMessageInput[]): string {\n const effective = messages.filter((m) => !isSemanticVoid(m))\n const startTs = messages[0].ts\n const endTs = messages[messages.length - 1].ts\n\n const lines: string[] = []\n const kindLabel = source.kind === 'group' ? '群聊' : '私聊'\n lines.push(`[来源] ${source.title}(${kindLabel})`)\n lines.push(`[时间范围] ${formatTimeRange(startTs, endTs)}`)\n if (source.kind === 'group') {\n const participants = [...new Set(effective.map((m) => m.senderName))]\n lines.push(`[参与者] ${participants.join('、')}`)\n }\n lines.push('')\n for (const m of effective) {\n lines.push(`${m.senderName}: ${(m.content ?? '').trim()}`)\n }\n return lines.join('\\n')\n}\n\nfunction rawContentHashOf(messages: ChunkMessageInput[]): string {\n return sha256Hex(messages.map((m) => `${m.id}:${m.content ?? ''}`).join('\\n'))\n}\n\n// ==================== parent / child 切分 ====================\n\nfunction segmentParents(messages: ChunkMessageInput[], config: ChunkerConfig): ChunkMessageInput[][] {\n const parents: ChunkMessageInput[][] = []\n let current: ChunkMessageInput[] = []\n const gapMs = config.parentGapSeconds * 1000\n\n for (const m of messages) {\n if (current.length === 0) {\n current.push(m)\n continue\n }\n const prev = current[current.length - 1]\n const overGap = m.ts - prev.ts > gapMs\n const overTokens = estimateTokens(current.map((x) => (x.content ?? '').trim()).join('\\n')) >= config.parentMaxTokens\n if (overGap || overTokens) {\n parents.push(current)\n current = [m]\n } else {\n current.push(m)\n }\n }\n if (current.length > 0) parents.push(current)\n return parents\n}\n\nfunction buildChildDrafts(parent: ChunkMessageInput[], config: ChunkerConfig): ChunkMessageInput[][] {\n const drafts: ChunkMessageInput[][] = []\n let current: ChunkMessageInput[] = []\n let newCount = 0\n\n const closeAndSeed = () => {\n drafts.push(current)\n const seed = config.overlapMessages > 0 ? current.slice(-config.overlapMessages) : []\n current = [...seed]\n newCount = 0\n }\n\n for (const m of parent) {\n current.push(m)\n newCount++\n const effectiveChars = sumEffectiveChars(current)\n const messageCount = current.length\n const overChars = effectiveChars >= config.childTargetMaxChars\n const overTokens = estimateTokens(current.map((x) => effectiveContentOf(x)).join('\\n')) >= config.childHardMaxTokens\n // 消息数软上限:高频短消息群聊里,攒够消息数且有效字符达标即关闭,避免单 chunk 混入过多消息\n const overSoftMessages = messageCount >= config.childSoftMaxMessages && effectiveChars >= config.childTargetMinChars\n // 消息数硬上限:即使有效字符不足 min 也强制关闭,避免极端短消息无限堆积\n const overHardMessages = messageCount >= config.childHardMaxMessages\n if (overChars || overTokens || overSoftMessages || overHardMessages) closeAndSeed()\n }\n\n // flush:仅当还有新消息(避免重复上一个 child 的 overlap 尾巴)\n if (current.length > 0 && (newCount > 0 || drafts.length === 0)) {\n drafts.push(current)\n }\n return drafts\n}\n\nexport function chunkMessages(input: ChunkMessagesInput): ChunkResult {\n const config = input.config ?? DEFAULT_CHUNKER_CONFIG\n const chunkerConfigHash = computeChunkerConfigHash(config)\n\n const parents = segmentParents(input.messages, config)\n const chunks: ChildChunk[] = []\n let skippedCount = 0\n\n for (const parent of parents) {\n const parentId = deriveParentId({\n startMessageId: parent[0].id,\n endMessageId: parent[parent.length - 1].id,\n gapSeconds: config.parentGapSeconds,\n chunkerVersion: CHUNKER_VERSION,\n chunkerConfigHash,\n })\n\n for (const draft of buildChildDrafts(parent, config)) {\n const effectiveChars = sumEffectiveChars(draft)\n if (effectiveChars < config.semanticVoidSkipThreshold) {\n skippedCount++\n continue\n }\n const startMessageId = draft[0].id\n const endMessageId = draft[draft.length - 1].id\n const embeddingInput = buildEmbeddingInput(input.source, draft)\n chunks.push({\n localChunkId: `${parentId}#${startMessageId}-${endMessageId}`,\n parentId,\n startMessageId,\n endMessageId,\n startTs: draft[0].ts,\n endTs: draft[draft.length - 1].ts,\n messageCount: draft.length,\n effectiveChars,\n rawContentHash: rawContentHashOf(draft),\n embeddingInputHash: sha256Hex(embeddingInput),\n embeddingInput,\n })\n }\n }\n\n return { chunkerVersion: CHUNKER_VERSION, chunkerConfigHash, chunks, skippedCount, parentCount: parents.length }\n}\n","/**\n * token 粗略估算(共享)\n *\n * 仅作硬上限护栏与证据预算控制,真实 token 由 embedder / LLM 决定。\n * 估算规则:CJK 每字约 1 token,其余字符约 1/4 token。\n */\nexport function estimateTokens(text: string): number {\n let cjk = 0\n let other = 0\n for (const ch of text) {\n if (/[\\u3400-\\u9fff\\uf900-\\ufaff]/u.test(ch)) cjk++\n else other++\n }\n return cjk + Math.ceil(other / 4)\n}\n","/**\n * 单对话 warmup runner\n *\n * 职责(chunking-decision-final.md 第 9/15 节):读取对话消息流 -> 全量 chunk ->\n * 逐 chunk embedding 并增量写入向量库 -> 实时更新业务状态。支持暂停、取消、失败处理\n * 和断点续跑,部分完成即可被检索。\n *\n * 设计要点:\n * - chunk 是纯函数且廉价,每次运行对全量消息重新 chunk(保证 parent 边界稳定)。\n * - 通过业务状态的 lastIndexedMessageId 游标跳过已写入 chunk,实现断点续跑。\n * - 每写入一个 chunk 即更新游标,保证崩溃后续跑不重复写入(chunk_id UNIQUE)。\n * - embedding 是瓶颈;每 chunk 前检查暂停/取消,embedding 返回后再次检查取消,\n * 避免在清理已取消索引后写入刚完成的向量。\n *\n * 依赖全部注入,便于单测(fake source/embedder + 真实内存级 SQLite store)。\n */\n\nimport type { EmbeddingProvider } from '../embedding/types'\nimport { chunkMessages, type ChunkMessageInput, type ChunkSource } from '../chunker'\nimport { CHUNKER_VERSION, STRATEGY_ID, composeChunkId, type ChunkerConfig } from '../chunker-config'\nimport type { EmbeddingIndexStore } from '../store'\nimport type { SemanticIndexStateStore } from '../session-state-store'\nimport type { ChunkInsert, ChunkRecord } from '../types'\n\n/** 对话消息来源(warmup 输入抽象,真实实现读取聊天库) */\nexport interface SemanticMessageSource {\n getSource(): ChunkSource\n countMessages(): number\n /** 按 ts, id 升序返回全部消息 */\n readAllMessages(): ChunkMessageInput[]\n}\n\n/** 停止信号:返回 null 继续,'paused' 暂停(可续跑),'cancelled' 取消 */\nexport type StopSignal = () => null | 'paused' | 'cancelled'\n\nexport interface WarmupRunnerOptions {\n dbPathHash: string\n modelId: string\n embedder: EmbeddingProvider\n store: EmbeddingIndexStore\n stateStore: SemanticIndexStateStore\n source: SemanticMessageSource\n config?: ChunkerConfig\n checkStop?: StopSignal\n}\n\nexport type WarmupStatus = 'completed' | 'paused' | 'cancelled' | 'failed'\n\nexport interface WarmupResult {\n status: WarmupStatus\n chunksWritten: number\n error?: string\n}\n\nfunction resolveDocumentBatchSize(embedder: EmbeddingProvider): number {\n const size = embedder.documentBatchSize\n return Number.isFinite(size) && size && size > 0 ? Math.floor(size) : 1\n}\n\nexport async function runWarmup(options: WarmupRunnerOptions): Promise<WarmupResult> {\n const { dbPathHash, modelId, embedder, store, stateStore, source, config, checkStop } = options\n\n let chunksWritten = 0\n try {\n const messages = source.readAllMessages()\n const total = source.countMessages()\n const savedState = stateStore.getState(dbPathHash)\n stateStore.updateProgress(dbPathHash, { indexStatus: 'running', totalMessages: total, error: null })\n\n // 消息 id -> 流位置,用于进度计数(消息按 ts 排序,id 未必单调)\n const streamIndexById = new Map<number, number>()\n messages.forEach((m, i) => streamIndexById.set(m.id, i))\n\n const resumeMessageId = savedState?.lastIndexedMessageId ?? null\n const rawResumeIndex = resumeMessageId !== null ? (streamIndexById.get(resumeMessageId) ?? -1) : -1\n\n // Detect non-append-only additions: if the cursor's new stream position doesn't match the\n // saved indexed count, older messages were backfilled before the cursor. Clear vectors and\n // re-index from scratch to avoid silently missing the backfilled messages.\n const isNonAppendOnly = rawResumeIndex >= 0 && rawResumeIndex + 1 !== (savedState?.indexedMessages ?? 0)\n if (isNonAppendOnly) {\n store.deleteByDbPathHash(dbPathHash)\n }\n const { chunks, chunkerConfigHash } = chunkMessages({ messages, source: source.getSource(), config })\n\n const chunkRanges = chunks.map((chunk) => ({\n chunk,\n startIndex: streamIndexById.get(chunk.startMessageId) ?? -1,\n endIndex: streamIndexById.get(chunk.endMessageId) ?? -1,\n }))\n\n let resumeIndex = isNonAppendOnly ? -1 : rawResumeIndex\n const hasAppendedMessages = !isNonAppendOnly && rawResumeIndex >= 0 && total > (savedState?.totalMessages ?? 0)\n if (hasAppendedMessages) {\n const cursorRange =\n chunkRanges.find((range) => range.startIndex <= rawResumeIndex && rawResumeIndex <= range.endIndex) ??\n chunkRanges.filter((range) => range.endIndex <= rawResumeIndex).sort((a, b) => b.endIndex - a.endIndex)[0]\n\n if (cursorRange) {\n const parentRanges = chunkRanges.filter((range) => range.chunk.parentId === cursorRange.chunk.parentId)\n const rewindIndex = Math.min(...parentRanges.map((range) => range.startIndex).filter((index) => index >= 0))\n const rewindMessage = messages[rewindIndex]\n if (rewindMessage) {\n store.deleteByModelFromPosition({\n dbPathHash,\n modelId,\n startTs: rewindMessage.ts,\n startMessageId: rewindMessage.id,\n })\n resumeIndex = rewindIndex - 1\n }\n }\n }\n\n // 续跑时统计已写入 chunk 数,保证 chunkCount 连续\n let storedChunkCount = store.countChunks(dbPathHash, modelId)\n if (resumeIndex < 0) storedChunkCount = 0\n\n const pendingRanges = chunkRanges.filter(({ endIndex }) => endIndex > resumeIndex)\n const batchSize = resolveDocumentBatchSize(embedder)\n for (let i = 0; i < pendingRanges.length; i += batchSize) {\n const batchRanges = pendingRanges.slice(i, i + batchSize)\n\n const stop = checkStop?.()\n if (stop) {\n stateStore.setIndexStatus(dbPathHash, stop === 'paused' ? 'paused' : 'cancelled')\n return { status: stop, chunksWritten }\n }\n\n const vectors = await embedder.embedDocuments(batchRanges.map(({ chunk }) => chunk.embeddingInput))\n if (vectors.length !== batchRanges.length) {\n throw new Error(`embedding provider returned ${vectors.length} vectors for ${batchRanges.length} inputs`)\n }\n const stopAfterEmbedding = checkStop?.()\n if (stopAfterEmbedding === 'cancelled') {\n stateStore.setIndexStatus(dbPathHash, 'cancelled')\n return { status: 'cancelled', chunksWritten }\n }\n\n // API provider 支持一次提交多个文本;本地 Qwen3 仍声明 batch=1,避免 last_token 污染。\n const indexedAt = Date.now()\n const inserts: ChunkInsert[] = batchRanges.map(({ chunk }, index) => {\n const vector = vectors[index]\n const record: ChunkRecord = {\n chunkId: composeChunkId(dbPathHash, modelId, chunk.localChunkId),\n dbPathHash,\n strategyId: STRATEGY_ID,\n modelId,\n dim: vector.length,\n parentId: chunk.parentId,\n startMessageId: chunk.startMessageId,\n endMessageId: chunk.endMessageId,\n startTs: chunk.startTs,\n endTs: chunk.endTs,\n messageCount: chunk.messageCount,\n rawContentHash: chunk.rawContentHash,\n embeddingInputHash: chunk.embeddingInputHash,\n chunkerVersion: CHUNKER_VERSION,\n chunkerConfigHash,\n indexedAt,\n status: 'indexed',\n }\n return { record, embedding: vector }\n })\n store.insertChunks(inserts)\n chunksWritten += inserts.length\n storedChunkCount += inserts.length\n\n const lastRange = batchRanges[batchRanges.length - 1]\n stateStore.updateProgress(dbPathHash, {\n indexStatus: 'running',\n indexedMessages: lastRange.endIndex + 1,\n lastIndexedMessageId: lastRange.chunk.endMessageId,\n chunkCount: storedChunkCount,\n })\n }\n\n stateStore.updateProgress(dbPathHash, { indexedMessages: total, chunkCount: storedChunkCount })\n stateStore.setIndexStatus(dbPathHash, 'completed', null)\n return { status: 'completed', chunksWritten }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n stateStore.setIndexStatus(dbPathHash, 'failed', message)\n return { status: 'failed', chunksWritten, error: message }\n }\n}\n","/**\n * Reciprocal Rank Fusion(RRF)\n *\n * chunking-decision-final.md 第 10 节:dense 与 FTS 各自独立排序,在 Node 层用 RRF 融合,\n * 不依赖跨库 join。每个 id 的融合分 = Σ 1 / (k + rank),rank 为该 id 在某个排序列表中的\n * 位次(0 为最优);id 未出现在某列表时该列表不贡献分数。\n *\n * 纯函数,按 id 操作(此处 id 即 chunkId),与具体检索来源解耦。\n */\n\nexport interface RrfResult {\n id: string\n score: number\n}\n\n/**\n * @param rankedLists 多个已排序 id 列表,每个列表内 index 0 为最相关\n * @param k RRF 常数,默认 60(业界常用值,降低高位排名的极端权重)\n */\nexport function reciprocalRankFusion(rankedLists: string[][], k = 60): RrfResult[] {\n const scores = new Map<string, number>()\n const firstSeen = new Map<string, number>()\n let seq = 0\n\n for (const list of rankedLists) {\n list.forEach((id, rank) => {\n scores.set(id, (scores.get(id) ?? 0) + 1 / (k + rank))\n if (!firstSeen.has(id)) firstSeen.set(id, seq++)\n })\n }\n\n return [...scores.entries()]\n .map(([id, score]) => ({ id, score }))\n .sort((a, b) => b.score - a.score || firstSeen.get(a.id)! - firstSeen.get(b.id)!)\n}\n","/**\n * 混合检索:dense(向量)+ FTS(全文)+ RRF 融合\n *\n * chunking-decision-final.md 第 10 节:\n * - dense:query 向量化后在 embedding_index.db 做 ANN,限定单对话 + 单模型分区。\n * - FTS:在聊天库做全文检索得到 message_id 排序,再用 store.mapMessageToChunk 映射到 chunk。\n * - 两路各自独立排序,Node 层 RRF 融合,取 finalTopK。\n *\n * FTS 来源(聊天库连接 + 分词)由注入的 FtsSearcher 提供,保持本模块平台无关、可单测。\n */\n\nimport type { EmbeddingProvider } from '../embedding/types'\nimport type { EmbeddingIndexStore } from '../store'\nimport type { ChunkRecord } from '../types'\nimport { reciprocalRankFusion } from './rrf'\n\n/** 聊天库全文检索:返回按相关度排序的消息(最相关在前);ts 为毫秒,与 chunk_vector_index 的 startTs/endTs 单位一致 */\nexport interface FtsSearcher {\n search(query: string, topN: number): Array<{ id: number; ts: number }>\n}\n\nexport interface HybridSearchDeps {\n embedder: EmbeddingProvider\n store: EmbeddingIndexStore\n fts: FtsSearcher\n}\n\n/** 毫秒级、可单边的时间区间(语义 chunk startTs/endTs 也是毫秒) */\nexport interface SemanticTimeRangeMs {\n startTs?: number\n endTs?: number\n}\n\nexport interface HybridSearchParams {\n query: string\n dbPathHash: string\n modelId: string\n strategyId: string\n dim: number\n /** dense 取回数量,默认 40 */\n denseTopN?: number\n /** FTS 取回 message 数量,默认 40 */\n ftsTopN?: number\n /** RRF 常数,默认 60 */\n rrfK?: number\n /** 融合后最终返回数量,默认 5 */\n finalTopK?: number\n /**\n * 毫秒级时间范围过滤(可单边)。\n * 只保留与 chunk [startTs, endTs] 有交集的候选;启用时会放大候选池避免过滤后结果过少。\n */\n timeRangeMs?: SemanticTimeRangeMs\n}\n\n/** chunk 时间范围是否与过滤区间有交集 */\nfunction overlapsTimeRangeMs(record: ChunkRecord, filter?: SemanticTimeRangeMs): boolean {\n if (!filter) return true\n if (filter.startTs != null && record.endTs < filter.startTs) return false\n if (filter.endTs != null && record.startTs > filter.endTs) return false\n return true\n}\n\n/** 启用时间过滤时放大候选池的倍数;必须足够大,避免长对话中窄时间段的 in-range chunk 均排在 top-N 以外 */\nconst TIME_FILTER_POOL_MULTIPLIER = 10\n\nexport interface HybridSearchResult {\n chunkId: string\n score: number\n record: ChunkRecord\n /** 在 dense 排序中的位次(0 最优),未命中为 null */\n denseRank: number | null\n /** 在 FTS 映射排序中的位次(0 最优),未命中为 null */\n ftsRank: number | null\n}\n\nexport async function hybridSearch(deps: HybridSearchDeps, params: HybridSearchParams): Promise<HybridSearchResult[]> {\n const { embedder, store, fts } = deps\n const { query, dbPathHash, modelId, strategyId, dim, rrfK = 60, finalTopK = 5, timeRangeMs } = params\n\n if (!query.trim()) return []\n\n // 启用时间过滤时放大候选池,避免过滤后融合结果过少\n const poolFactor = timeRangeMs ? TIME_FILTER_POOL_MULTIPLIER : 1\n const denseTopN = Math.max((params.denseTopN ?? 40) * poolFactor, 40)\n const ftsTopN = Math.max((params.ftsTopN ?? 40) * poolFactor, 40)\n\n const records = new Map<string, ChunkRecord>()\n\n // dense(按时间交集过滤后顺序构造排名)\n const queryVector = await embedder.embedQuery(query)\n const dense = store.queryDense({ dbPathHash, modelId, dim, embedding: queryVector, k: denseTopN })\n const denseIds: string[] = []\n const denseRankById = new Map<string, number>()\n for (const hit of dense) {\n if (!overlapsTimeRangeMs(hit.record, timeRangeMs)) continue\n denseRankById.set(hit.chunkId, denseIds.length)\n denseIds.push(hit.chunkId)\n records.set(hit.chunkId, hit.record)\n }\n\n // FTS message -> chunk(按 ts 映射,去重保序 + 时间交集过滤)\n const ftsIds: string[] = []\n const ftsRankById = new Map<string, number>()\n for (const { id: messageId, ts: messageTs } of fts.search(query, ftsTopN)) {\n const record = store.mapMessageToChunk({ dbPathHash, modelId, strategyId, messageId, messageTs })\n if (!record || ftsRankById.has(record.chunkId)) continue\n if (!overlapsTimeRangeMs(record, timeRangeMs)) continue\n ftsRankById.set(record.chunkId, ftsIds.length)\n ftsIds.push(record.chunkId)\n if (!records.has(record.chunkId)) records.set(record.chunkId, record)\n }\n\n const fused = reciprocalRankFusion([denseIds, ftsIds], rrfK)\n\n const results: HybridSearchResult[] = []\n for (const { id, score } of fused) {\n const record = records.get(id) ?? store.getChunkById(id)\n if (!record) continue\n // 兜底:getChunkById 取回的候选也按时间过滤\n if (!overlapsTimeRangeMs(record, timeRangeMs)) continue\n results.push({\n chunkId: id,\n score,\n record,\n denseRank: denseRankById.get(id) ?? null,\n ftsRank: ftsRankById.get(id) ?? null,\n })\n if (results.length >= finalTopK) break\n }\n return results\n}\n","/**\n * 证据块组装\n *\n * chunking-decision-final.md 第 14 节:\n * - RRF 后取 topK 命中 chunk,每个命中在 parent 内前后各扩展若干条消息,不跨 parent。\n * - 相邻命中可合并,但合并后超过单块 soft cap 则拆开注入。\n * - 总证据预算固定 1500 token:不够时优先减少前后扩展,再减少 chunk 数。\n *\n * 消息原文读取(聊天库连接)由注入的 MessageRangeReader 提供,保持模块平台无关、可单测。\n */\n\nimport { parseParentBounds } from '../chunker-config'\nimport { estimateTokens } from '../tokens'\n\nexport interface EvidenceMessage {\n id: number\n senderName: string\n content: string\n ts: number\n /** 发送者 id,用于昵称匿名化(U{id}) */\n senderId?: number\n /** 发送者平台 id,用于匿名化时识别 owner */\n senderPlatformId?: string\n}\n\n/** 读取聊天库父消息区间,按 ts, id 升序返回;startId/endId 为 parent 两端消息 id(ts 序,非 min/max) */\nexport interface MessageRangeReader {\n readRange(startId: number, endId: number): EvidenceMessage[]\n}\n\n/** 命中 chunk(来自 hybridSearch,已按 score 降序) */\nexport interface EvidenceHit {\n chunkId: string\n score: number\n parentId: string\n startMessageId: number\n endMessageId: number\n /** chunk 的起始时间戳(毫秒),供时间范围过滤使用 */\n startTs: number\n /** chunk 的结束时间戳(毫秒),供时间范围过滤使用 */\n endTs: number\n}\n\nexport interface EvidenceBudget {\n /** 总证据预算 token,默认 1500 */\n totalTokens?: number\n /** 单证据块 soft cap token,默认 800 */\n blockSoftCapTokens?: number\n /** 每个命中 chunk 前后各扩展消息条数,默认 3 */\n expandMessages?: number\n /** 最多取多少个命中 chunk,默认 5 */\n maxChunks?: number\n}\n\nexport interface EvidenceBlock {\n parentId: string\n startMessageId: number\n endMessageId: number\n messages: EvidenceMessage[]\n tokens: number\n /** 该块覆盖的命中 chunkId */\n chunkIds: string[]\n /** 块内最高命中分 */\n score: number\n}\n\nexport interface EvidenceResult {\n blocks: EvidenceBlock[]\n totalTokens: number\n}\n\n/** 证据块规范化文本(用于 token 估算与后续 prompt 注入) */\nexport function formatEvidenceMessages(messages: EvidenceMessage[]): string {\n return messages.map((m) => `${m.senderName}: ${m.content}`).join('\\n')\n}\n\nfunction messagesTokens(messages: EvidenceMessage[]): number {\n return estimateTokens(formatEvidenceMessages(messages))\n}\n\ninterface WorkingBlock {\n parentId: string\n parentMsgs: EvidenceMessage[]\n startIdx: number\n endIdx: number\n /** 命中核心范围(合并后取并集),收缩扩展时不得越过此范围 */\n coreStartIdx: number\n coreEndIdx: number\n chunkIds: string[]\n score: number\n}\n\nexport function assembleEvidence(\n reader: MessageRangeReader,\n hits: EvidenceHit[],\n budget: EvidenceBudget = {}\n): EvidenceResult {\n const totalTokens = budget.totalTokens ?? 1500\n const softCap = budget.blockSoftCapTokens ?? 800\n const expand = budget.expandMessages ?? 3\n const maxChunks = budget.maxChunks ?? 5\n\n const selected = hits.slice(0, maxChunks)\n const parentCache = new Map<string, EvidenceMessage[]>()\n\n // 1. 每个命中在 parent 内扩展为 working block\n const raw: WorkingBlock[] = []\n for (const hit of selected) {\n const bounds = parseParentBounds(hit.parentId)\n if (!bounds) continue\n let parentMsgs = parentCache.get(hit.parentId)\n if (!parentMsgs) {\n parentMsgs = reader.readRange(bounds.startMessageId, bounds.endMessageId)\n parentCache.set(hit.parentId, parentMsgs)\n }\n if (parentMsgs.length === 0) continue\n\n // Use message IDs for precise core boundary — ts has second granularity and is non-unique\n const coreStartIdx = parentMsgs.findIndex((m) => m.id === hit.startMessageId)\n let coreEndIdx = -1\n for (let i = parentMsgs.length - 1; i >= 0; i--) {\n if (parentMsgs[i].id === hit.endMessageId) {\n coreEndIdx = i\n break\n }\n }\n if (coreStartIdx < 0 || coreEndIdx < coreStartIdx) continue\n\n raw.push({\n parentId: hit.parentId,\n parentMsgs,\n startIdx: Math.max(0, coreStartIdx - expand),\n endIdx: Math.min(parentMsgs.length - 1, coreEndIdx + expand),\n coreStartIdx,\n coreEndIdx,\n chunkIds: [hit.chunkId],\n score: hit.score,\n })\n }\n\n // 2. 同 parent 内相邻/重叠且合并后不超 soft cap 的块合并\n const byParent = new Map<string, WorkingBlock[]>()\n for (const block of raw) {\n const list = byParent.get(block.parentId) ?? []\n list.push(block)\n byParent.set(block.parentId, list)\n }\n\n const merged: WorkingBlock[] = []\n for (const list of byParent.values()) {\n list.sort((a, b) => a.startIdx - b.startIdx)\n for (const block of list) {\n const last = merged[merged.length - 1]\n const mergeable = last && last.parentId === block.parentId && block.startIdx <= last.endIdx + 1\n if (mergeable) {\n const candStart = Math.min(last.startIdx, block.startIdx)\n const candEnd = Math.max(last.endIdx, block.endIdx)\n const candTokens = messagesTokens(block.parentMsgs.slice(candStart, candEnd + 1))\n if (candTokens <= softCap) {\n last.startIdx = candStart\n last.endIdx = candEnd\n last.coreStartIdx = Math.min(last.coreStartIdx, block.coreStartIdx)\n last.coreEndIdx = Math.max(last.coreEndIdx, block.coreEndIdx)\n last.chunkIds.push(...block.chunkIds)\n last.score = Math.max(last.score, block.score)\n continue\n }\n }\n merged.push({ ...block })\n }\n }\n\n // 3. 单块超 soft cap 时收缩扩展(不越过 core)\n for (const block of merged) shrinkToFit(block, softCap)\n\n // 4. 总预算控制:按 score 降序贪心装入;不够时收缩扩展,再不够则丢弃该块\n merged.sort((a, b) => b.score - a.score)\n const blocks: EvidenceBlock[] = []\n let used = 0\n for (const block of merged) {\n let tokens = messagesTokens(block.parentMsgs.slice(block.startIdx, block.endIdx + 1))\n if (used + tokens > totalTokens) {\n shrinkToFit(block, Math.min(softCap, totalTokens - used))\n tokens = messagesTokens(block.parentMsgs.slice(block.startIdx, block.endIdx + 1))\n if (used + tokens > totalTokens) continue\n }\n const messages = block.parentMsgs.slice(block.startIdx, block.endIdx + 1)\n blocks.push({\n parentId: block.parentId,\n startMessageId: messages[0].id,\n endMessageId: messages[messages.length - 1].id,\n messages,\n tokens,\n chunkIds: block.chunkIds,\n score: block.score,\n })\n used += tokens\n }\n\n return { blocks, totalTokens: used }\n}\n\n/** 收缩扩展使块 token 不超过 cap:从扩展更多的一侧逐条收缩,不越过 core 范围 */\nfunction shrinkToFit(block: WorkingBlock, cap: number): void {\n while (messagesTokens(block.parentMsgs.slice(block.startIdx, block.endIdx + 1)) > cap) {\n const startMargin = block.coreStartIdx - block.startIdx\n const endMargin = block.endIdx - block.coreEndIdx\n if (startMargin <= 0 && endMargin <= 0) break\n if (endMargin >= startMargin) block.endIdx--\n else block.startIdx++\n }\n}\n","/**\n * 聊天库消息来源适配器\n *\n * 把聊天库的 message/member 表桥接为 warmup runner 需要的 SemanticMessageSource。\n *\n * 时间单位转换:聊天库 message.ts 以秒存储,而 chunker 按毫秒计算时间 gap\n * (gapMs = parentGapSeconds * 1000),故此处统一乘 1000 转为毫秒。\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport type { ChunkSource } from '../chunker'\nimport type { SemanticMessageSource } from '../warmup/runner'\n\nconst SELECT_ALL_MESSAGES = `\n SELECT msg.id AS id,\n msg.ts AS ts,\n msg.type AS type,\n msg.content AS content,\n COALESCE(msg.sender_group_nickname, msg.sender_account_name, m.group_nickname, m.account_name, m.platform_id) AS senderName\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n ORDER BY msg.ts ASC, msg.id ASC\n`\n\ninterface MessageRow {\n id: number\n ts: number\n type: number\n content: string | null\n senderName: string | null\n}\n\nexport function createChatDbMessageSource(db: DatabaseAdapter, source: ChunkSource): SemanticMessageSource {\n return {\n getSource: () => source,\n countMessages: () => {\n const row = db.prepare('SELECT COUNT(*) AS c FROM message').get() as { c: number } | undefined\n return row?.c ?? 0\n },\n readAllMessages: () => {\n const rows = db.prepare(SELECT_ALL_MESSAGES).all() as unknown as MessageRow[]\n return rows.map((r) => ({\n id: r.id,\n senderName: r.senderName ?? '',\n content: r.content,\n ts: r.ts * 1000,\n type: r.type,\n }))\n },\n }\n}\n","/**\n * 聊天库消息区间读取适配器\n *\n * 为证据组装提供 parent 边界消息的闭区间原文。使用 (ts, id) 复合边界:\n * - ts 范围只精确到秒,同一秒可有多条消息;若只用 ts 边界,会纳入同秒的相邻 parent 消息\n * - (ts, id) 复合条件可精确到行,保证不跨 parent 越界\n * - 同时支持回填旧消息(高 id 但低 ts)后的正确读取\n * 结果按 ts, id 升序返回(与 chunker 排序一致)。证据保留全部消息(含非文本)。\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport type { EvidenceMessage, MessageRangeReader } from '../retrieval/evidence'\n\nconst SELECT_RANGE = `\n WITH b AS (\n SELECT (SELECT ts FROM message WHERE id = ?) AS s_ts,\n (SELECT ts FROM message WHERE id = ?) AS e_ts\n )\n SELECT msg.id AS id,\n msg.ts AS ts,\n msg.content AS content,\n msg.sender_id AS senderId,\n m.platform_id AS senderPlatformId,\n COALESCE(msg.sender_group_nickname, msg.sender_account_name, m.group_nickname, m.account_name, m.platform_id) AS senderName\n FROM message msg\n JOIN member m ON msg.sender_id = m.id, b\n WHERE (msg.ts > b.s_ts OR (msg.ts = b.s_ts AND msg.id >= ?))\n AND (msg.ts < b.e_ts OR (msg.ts = b.e_ts AND msg.id <= ?))\n ORDER BY msg.ts ASC, msg.id ASC\n`\n\ninterface RangeRow {\n id: number\n ts: number\n content: string | null\n senderId: number | null\n senderPlatformId: string | null\n senderName: string | null\n}\n\nexport function createChatDbMessageRangeReader(db: DatabaseAdapter): MessageRangeReader {\n return {\n readRange(startId: number, endId: number): EvidenceMessage[] {\n const rows = db.prepare(SELECT_RANGE).all(startId, endId, startId, endId) as unknown as RangeRow[]\n return rows.map((r) => ({\n id: r.id,\n senderName: r.senderName ?? '',\n content: r.content ?? '',\n ts: r.ts * 1000,\n senderId: r.senderId ?? undefined,\n senderPlatformId: r.senderPlatformId ?? undefined,\n }))\n },\n }\n}\n","/**\n * 聊天库 FTS 检索适配器\n *\n * 把自然语言 query 用 jieba 分词成关键词,作为多关键词传入 searchByFts\n * (多关键词以 OR 组合,最大化召回;精度由 dense 路与 RRF 融合保证)。\n * 返回按 FTS rank 排序的 message_id。\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport { searchByFts } from '../../fts'\nimport { getJieba } from '../../nlp/segmenter'\nimport type { FtsSearcher } from '../retrieval/hybrid-search'\n\n/** 自然语言 query -> 去重关键词列表 */\nexport function extractFtsKeywords(query: string): string[] {\n const trimmed = query.trim()\n if (!trimmed) return []\n try {\n const tokens = getJieba()\n .cut(trimmed, false)\n .map((t) => t.trim())\n .filter((t) => t.length > 0)\n return [...new Set(tokens)]\n } catch {\n return [...new Set(trimmed.split(/\\s+/).filter((t) => t.length > 0))]\n }\n}\n\nexport function createChatDbFtsSearcher(db: DatabaseAdapter): FtsSearcher {\n return {\n search(query: string, topN: number): Array<{ id: number; ts: number }> {\n const keywords = extractFtsKeywords(query)\n if (keywords.length === 0) return []\n const { rowids } = searchByFts(db, keywords, topN, 0)\n if (rowids.length === 0) return []\n const placeholders = rowids.map(() => '?').join(', ')\n const tsRows = db.prepare(`SELECT id, ts FROM message WHERE id IN (${placeholders})`).all(...rowids) as Array<{\n id: number\n ts: number\n }>\n const tsById = new Map(tsRows.map((r) => [r.id, r.ts * 1000]))\n return rowids.flatMap((id) => {\n const ts = tsById.get(id)\n return ts !== undefined ? [{ id, ts }] : []\n })\n },\n }\n}\n","import type { PathProvider } from '@openchatlab/core'\n\nexport interface StaticPathProviderSnapshot {\n systemDir: string\n userDataDir: string\n databaseDir: string\n vectorDir: string\n aiDataDir: string\n settingsDir: string\n cacheDir: string\n tempDir: string\n logsDir: string\n downloadsDir: string\n}\n\nexport function snapshotPathProvider(pathProvider: PathProvider): StaticPathProviderSnapshot {\n return {\n systemDir: pathProvider.getSystemDir(),\n userDataDir: pathProvider.getUserDataDir(),\n databaseDir: pathProvider.getDatabaseDir(),\n vectorDir: pathProvider.getVectorDir(),\n aiDataDir: pathProvider.getAiDataDir(),\n settingsDir: pathProvider.getSettingsDir(),\n cacheDir: pathProvider.getCacheDir(),\n tempDir: pathProvider.getTempDir(),\n logsDir: pathProvider.getLogsDir(),\n downloadsDir: pathProvider.getDownloadsDir(),\n }\n}\n\nexport class StaticPathProvider implements PathProvider {\n constructor(private readonly paths: StaticPathProviderSnapshot) {}\n\n getSystemDir(): string {\n return this.paths.systemDir\n }\n\n getUserDataDir(): string {\n return this.paths.userDataDir\n }\n\n getDatabaseDir(): string {\n return this.paths.databaseDir\n }\n\n getVectorDir(): string {\n return this.paths.vectorDir\n }\n\n getAiDataDir(): string {\n return this.paths.aiDataDir\n }\n\n getSettingsDir(): string {\n return this.paths.settingsDir\n }\n\n getCacheDir(): string {\n return this.paths.cacheDir\n }\n\n getTempDir(): string {\n return this.paths.tempDir\n }\n\n getLogsDir(): string {\n return this.paths.logsDir\n }\n\n getDownloadsDir(): string {\n return this.paths.downloadsDir\n }\n}\n","/**\n * DatabaseManager adapter for the shared service layer.\n *\n * Bridges DatabaseManager's connection lifecycle to the SessionRuntimeAdapter\n * interface expected by session/member services.\n */\n\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport type { DatabaseManager } from '../../database-manager'\nimport type { SessionRuntimeAdapter } from './types'\n\nexport function createDatabaseManagerAdapter(dbManager: DatabaseManager): SessionRuntimeAdapter {\n return {\n listSessionIds: () => dbManager.listSessionIds(),\n openReadonly: (sessionId) => dbManager.open(sessionId),\n openWritable: (sessionId) => dbManager.openWritable(sessionId),\n closeSession: (sessionId) => dbManager.close(sessionId),\n getDbPath: (sessionId) => dbManager.getDbPath(sessionId),\n\n deleteSessionFile(sessionId: string): boolean {\n return dbManager.deleteSessionDatabaseFiles(sessionId)\n },\n\n ensureReadonly(sessionId: string): DatabaseAdapter {\n const db = dbManager.open(sessionId)\n if (!db) {\n throw Object.assign(new Error(`Session not found: ${sessionId}`), { statusCode: 404 })\n }\n return db\n },\n\n ensureWritable(sessionId: string): DatabaseAdapter {\n const db = dbManager.openWritable(sessionId)\n if (!db) {\n throw Object.assign(new Error(`Session not found: ${sessionId}`), { statusCode: 404 })\n }\n return db\n },\n }\n}\n","/**\n * Server-side merge session cache.\n *\n * Manages temporary databases for the multi-file merge workflow.\n * Each uploaded/parsed file gets a temp DB handle that persists\n * across HTTP requests until merge completes or cache is cleared.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport * as crypto from 'crypto'\nimport type { PathProvider, DatabaseAdapter } from '@openchatlab/core'\nimport { openBetterSqliteDatabase } from '../better-sqlite3-adapter'\nimport { TempDbReader, deleteTempDatabase, cleanupTempDatabases } from '../merger/temp-db'\n\ninterface CacheEntry {\n tempDbPath: string\n filename: string\n createdAt: number\n}\n\nexport class MergeSessionCache {\n private cache = new Map<string, CacheEntry>()\n private tempDir: string\n private nativeBinding?: string\n\n constructor(pathProvider: PathProvider, options?: { nativeBinding?: string }) {\n this.tempDir = pathProvider.getTempDir()\n this.nativeBinding = options?.nativeBinding\n if (!fs.existsSync(this.tempDir)) {\n fs.mkdirSync(this.tempDir, { recursive: true })\n }\n }\n\n generateTempDbPath(filename: string): string {\n const ts = Date.now()\n const rand = Math.random().toString(36).slice(2, 8)\n const safeName = filename.replace(/[/\\\\?%*:|\"<>]/g, '_').slice(0, 40)\n return path.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`)\n }\n\n /** Store a parsed file's temp DB and return a handle for subsequent requests. */\n store(filename: string, tempDbPath: string): string {\n const handle = crypto.randomUUID()\n this.cache.set(handle, { tempDbPath, filename, createdAt: Date.now() })\n return handle\n }\n\n /** Open a TempDbReader for a given handle. Caller must close the reader. */\n openReader(handle: string): { reader: TempDbReader; filename: string } | null {\n const entry = this.cache.get(handle)\n if (!entry || !fs.existsSync(entry.tempDbPath)) return null\n const adapter = openBetterSqliteDatabase(entry.tempDbPath, {\n readonly: true,\n nativeBinding: this.nativeBinding,\n })\n return { reader: new TempDbReader(adapter), filename: entry.filename }\n }\n\n /** Create a writable temp DB for streaming parse. */\n createTempDatabase(filename: string): { db: DatabaseAdapter; tempDbPath: string } {\n const tempDbPath = this.generateTempDbPath(filename)\n const adapter = openBetterSqliteDatabase(tempDbPath, { nativeBinding: this.nativeBinding })\n return { db: adapter, tempDbPath }\n }\n\n /** Remove a single entry and its temp DB file. */\n delete(handle: string): void {\n const entry = this.cache.get(handle)\n if (entry) {\n deleteTempDatabase(entry.tempDbPath)\n this.cache.delete(handle)\n }\n }\n\n /** Clear all entries and delete their temp DB files. */\n clear(): void {\n for (const entry of this.cache.values()) {\n deleteTempDatabase(entry.tempDbPath)\n }\n this.cache.clear()\n }\n\n /** Cleanup orphan temp DBs from previous runs. */\n cleanupOrphans(): void {\n cleanupTempDatabases(this.tempDir)\n }\n\n /** Auto-cleanup entries older than maxAge (default 1 hour). */\n cleanupExpired(maxAgeMs = 3600_000): void {\n const now = Date.now()\n for (const [handle, entry] of this.cache) {\n if (now - entry.createdAt > maxAgeMs) {\n deleteTempDatabase(entry.tempDbPath)\n this.cache.delete(handle)\n }\n }\n }\n}\n","/**\n * Push import service — handles POST /api/v1/imports/:sessionId\n *\n * Accepts a ChatLab Format JSON payload, creates or appends to a session.\n * Dedup: platformMessageId (preferred) or content hash (fallback).\n */\n\nimport * as fs from 'fs'\nimport { DataDirCompatibilityError } from '../data-dir-compat'\nimport {\n CHAT_DB_SCHEMA,\n generateMessageKey,\n generateSessionIndex,\n generateIncrementalSessionIndex,\n} from '@openchatlab/core'\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport type { DatabaseManager } from '../database-manager'\nimport { buildFtsIndex, insertFtsEntries } from '../fts'\nimport { writeParseResultToDb } from '../import'\n\n// Per-session lock: concurrent imports to different sessions are fine (separate DB files).\nconst importInProgress = new Set<string>()\nconst SYSTEM_SENDER_ID = 'SYSTEM'\nconst SYSTEM_MEMBER_NAME = '系统消息'\n\nexport interface PushImportMessage {\n sender: string\n timestamp: number\n type: number\n accountName?: string\n groupNickname?: string\n content?: string | null\n platformMessageId?: string\n replyToMessageId?: string\n}\n\nexport interface PushImportMember {\n platformId: string\n accountName?: string\n groupNickname?: string\n avatar?: string\n roles?: Array<{ id: string }>\n}\n\nexport interface PushImportMeta {\n name: string\n platform: string\n type: string\n groupId?: string\n groupAvatar?: string\n ownerId?: string\n}\n\nexport interface PushImportPayload {\n chatlab?: { version: string; exportedAt: number; generator?: string }\n meta?: PushImportMeta\n members?: PushImportMember[]\n messages?: PushImportMessage[]\n options?: {\n metaUpdateMode?: 'patch' | 'none'\n memberUpdateMode?: 'upsert' | 'none'\n }\n}\n\nexport interface PushImportResult {\n sessionId: string\n created: boolean\n batch: { receivedCount: number; writtenCount: number; duplicateCount: number }\n session: { totalCount: number; memberCount: number; firstTimestamp: number | null; lastTimestamp: number | null }\n updates: { metaUpdated: boolean; membersAdded: number; membersUpdated: number }\n}\n\nexport type PushImportOutcome =\n | { ok: true; result: PushImportResult }\n | { ok: false; reason: 'import_in_progress' | 'invalid_payload' | 'import_failed'; message: string }\n\nfunction validatePayload(payload: PushImportPayload, isNew: boolean): string | null {\n const messages = payload.messages\n if (!Array.isArray(messages) || messages.length === 0) return 'messages is required and must be a non-empty array'\n\n if (isNew) {\n if (!payload.chatlab) return 'chatlab is required for new sessions'\n if (!payload.meta) return 'meta is required for new sessions'\n const m = payload.meta\n if (!m.name) return 'meta.name is required'\n if (!m.platform) return 'meta.platform is required'\n if (!m.type) return 'meta.type is required'\n }\n\n const { metaUpdateMode, memberUpdateMode } = payload.options ?? {}\n if (metaUpdateMode !== undefined && metaUpdateMode !== 'patch' && metaUpdateMode !== 'none') {\n return `options.metaUpdateMode must be 'patch' or 'none'`\n }\n if (memberUpdateMode !== undefined && memberUpdateMode !== 'upsert' && memberUpdateMode !== 'none') {\n return `options.memberUpdateMode must be 'upsert' or 'none'`\n }\n\n if (payload.members !== undefined && !Array.isArray(payload.members)) return 'members must be an array'\n if (payload.members) {\n for (let i = 0; i < payload.members.length; i++) {\n if (typeof payload.members[i].platformId !== 'string' || payload.members[i].platformId.length === 0)\n return `members[${i}].platformId must be a string`\n }\n }\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i]\n if (typeof msg.sender !== 'string' || msg.sender.length === 0) return `messages[${i}].sender must be a string`\n if (typeof msg.timestamp !== 'number' || msg.timestamp <= 0)\n return `messages[${i}].timestamp must be a positive number`\n if (typeof msg.type !== 'number') return `messages[${i}].type must be a number`\n if (msg.content !== undefined && msg.content !== null && typeof msg.content !== 'string')\n return `messages[${i}].content must be a string or null`\n if (msg.platformMessageId !== undefined && typeof msg.platformMessageId !== 'string')\n return `messages[${i}].platformMessageId must be a string`\n if (msg.replyToMessageId !== undefined && typeof msg.replyToMessageId !== 'string')\n return `messages[${i}].replyToMessageId must be a string`\n }\n\n return null\n}\n\nfunction queryStats(db: DatabaseAdapter): PushImportResult['session'] {\n const row = db.prepare('SELECT COUNT(*) as total, MIN(ts) as first, MAX(ts) as last FROM message').get() as {\n total: number\n first: number | null\n last: number | null\n }\n const memberRow = db\n .prepare(`SELECT COUNT(*) as cnt FROM member WHERE COALESCE(account_name, '') != ?`)\n .get(SYSTEM_MEMBER_NAME) as { cnt: number }\n return { totalCount: row.total, memberCount: memberRow.cnt, firstTimestamp: row.first, lastTimestamp: row.last }\n}\n\nfunction normalizeSenderAccountName(sender: string, accountName: string | undefined): string | undefined {\n return sender === SYSTEM_SENDER_ID ? SYSTEM_MEMBER_NAME : accountName\n}\n\nfunction normalizeSenderGroupNickname(sender: string, groupNickname: string | undefined): string | undefined {\n return sender === SYSTEM_SENDER_ID ? SYSTEM_MEMBER_NAME : groupNickname\n}\n\nfunction writeMessages(\n db: DatabaseAdapter,\n messages: PushImportMessage[],\n existingPmids: Set<string>,\n existingKeys: Set<string>\n): {\n writtenCount: number\n duplicateCount: number\n minWrittenTs: number\n ftsEntries: Array<{ id: number; content: string | null }>\n} {\n const insertMsg = db.prepare(\n `INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n )\n const getMemberId = db.prepare('SELECT id FROM member WHERE platform_id = ?')\n const insertMinimalMember = db.prepare('INSERT OR IGNORE INTO member (platform_id, account_name) VALUES (?, ?)')\n\n const memberIdCache = new Map<string, number>()\n let writtenCount = 0\n let duplicateCount = 0\n let minWrittenTs = Infinity\n const ftsEntries: Array<{ id: number; content: string | null }> = []\n\n const sorted = [...messages].sort((a, b) => a.timestamp - b.timestamp)\n\n db.transaction(() => {\n for (const msg of sorted) {\n const key = generateMessageKey(msg.timestamp, msg.sender, msg.content ?? null)\n if (msg.platformMessageId) {\n if (existingPmids.has(msg.platformMessageId)) {\n duplicateCount++\n continue\n }\n if (existingKeys.has(key)) {\n duplicateCount++\n continue\n }\n existingPmids.add(msg.platformMessageId)\n // Also register the content hash so a same-content no-pmid copy later\n // in this batch is caught by the fallback dedup path.\n existingKeys.add(key)\n } else {\n if (existingKeys.has(key)) {\n duplicateCount++\n continue\n }\n existingKeys.add(key)\n }\n\n let memberId = memberIdCache.get(msg.sender)\n if (!memberId) {\n insertMinimalMember.run(msg.sender, normalizeSenderAccountName(msg.sender, msg.accountName) || null)\n const row = getMemberId.get(msg.sender) as { id: number } | undefined\n if (row) {\n memberId = row.id\n memberIdCache.set(msg.sender, memberId)\n }\n }\n if (!memberId) continue\n\n const result = insertMsg.run(\n memberId,\n normalizeSenderAccountName(msg.sender, msg.accountName) || null,\n normalizeSenderGroupNickname(msg.sender, msg.groupNickname) || null,\n msg.timestamp,\n msg.type,\n msg.content ?? null,\n msg.replyToMessageId || null,\n msg.platformMessageId || null\n )\n ftsEntries.push({ id: Number(result.lastInsertRowid), content: msg.content ?? null })\n if (msg.timestamp < minWrittenTs) minWrittenTs = msg.timestamp\n writtenCount++\n }\n })\n\n return { writtenCount, duplicateCount, minWrittenTs, ftsEntries }\n}\n\nfunction fullImport(\n db: DatabaseAdapter,\n meta: PushImportMeta,\n members: PushImportMember[],\n messages: PushImportMessage[]\n): { writtenCount: number; duplicateCount: number; membersAdded: number } {\n db.exec(CHAT_DB_SCHEMA)\n\n // Auto-create minimal member entries for senders not listed in members,\n // preserving the protocol promise that unknown senders are auto-created.\n const knownIds = new Set(members.map((m) => m.platformId))\n const extraMembers: PushImportMember[] = []\n for (const msg of messages) {\n if (!knownIds.has(msg.sender)) {\n extraMembers.push({\n platformId: msg.sender,\n accountName: normalizeSenderAccountName(msg.sender, msg.accountName),\n groupNickname: normalizeSenderGroupNickname(msg.sender, msg.groupNickname),\n })\n knownIds.add(msg.sender)\n }\n }\n const allMembers = extraMembers.length > 0 ? [...members, ...extraMembers] : members\n\n // Deduplicate within the batch using the same pmid/hash logic as incremental imports.\n const pmids = new Set<string>()\n const hashKeys = new Set<string>()\n let duplicateCount = 0\n const dedupedMessages: PushImportMessage[] = []\n for (const msg of messages) {\n const key = generateMessageKey(msg.timestamp, msg.sender, msg.content ?? null)\n if (msg.platformMessageId) {\n if (pmids.has(msg.platformMessageId)) {\n duplicateCount++\n continue\n }\n if (hashKeys.has(key)) {\n duplicateCount++\n continue\n }\n pmids.add(msg.platformMessageId)\n hashKeys.add(key)\n } else {\n if (hashKeys.has(key)) {\n duplicateCount++\n continue\n }\n hashKeys.add(key)\n }\n dedupedMessages.push(msg)\n }\n\n // Delegate to the shared writer so member_name_history is tracked correctly,\n // matching the behaviour of file-based full imports.\n const stats = writeParseResultToDb(\n db,\n meta,\n allMembers.map((m) => ({\n ...m,\n accountName: normalizeSenderAccountName(m.platformId, m.accountName) ?? m.platformId,\n groupNickname: normalizeSenderGroupNickname(m.platformId, m.groupNickname),\n })),\n dedupedMessages.map((m) => ({\n senderPlatformId: m.sender,\n senderAccountName: normalizeSenderAccountName(m.sender, m.accountName) ?? m.sender,\n senderGroupNickname: normalizeSenderGroupNickname(m.sender, m.groupNickname),\n timestamp: m.timestamp,\n type: m.type,\n content: m.content ?? null,\n platformMessageId: m.platformMessageId,\n replyToMessageId: m.replyToMessageId,\n }))\n )\n\n buildFtsIndex(db)\n\n try {\n generateSessionIndex(db)\n } catch {\n /* non-fatal */\n }\n\n return { writtenCount: stats.messageCount, duplicateCount, membersAdded: members.length }\n}\n\nfunction incrementalImport(\n db: DatabaseAdapter,\n payload: PushImportPayload\n): {\n writtenCount: number\n duplicateCount: number\n metaUpdated: boolean\n membersAdded: number\n membersUpdated: number\n} {\n const metaUpdateMode = payload.options?.metaUpdateMode ?? 'patch'\n const memberUpdateMode = payload.options?.memberUpdateMode ?? 'upsert'\n\n // Load dedup keys\n const existingPmids = new Set<string>()\n const existingKeys = new Set<string>()\n ;(\n db.prepare('SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL').all() as Array<{\n platform_message_id: string\n }>\n ).forEach((r) => existingPmids.add(r.platform_message_id))\n // Load hashes for ALL existing messages (not just those without pmid) so that\n // messages previously imported with a platformMessageId are still caught by\n // content-hash dedup when the same content arrives without one.\n ;(\n db\n .prepare(`SELECT msg.ts, m.platform_id, msg.content FROM message msg JOIN member m ON msg.sender_id = m.id`)\n .all() as Array<{ ts: number; platform_id: string; content: string | null }>\n ).forEach((r) => existingKeys.add(generateMessageKey(r.ts, r.platform_id, r.content)))\n\n let metaUpdated = false\n let membersAdded = 0\n let membersUpdated = 0\n\n if (payload.meta && metaUpdateMode === 'patch') {\n const m = payload.meta\n db.prepare(\n `UPDATE meta SET name = COALESCE(NULLIF(?, ''), name), group_id = COALESCE(NULLIF(?, ''), group_id), group_avatar = COALESCE(NULLIF(?, ''), group_avatar), owner_id = COALESCE(NULLIF(?, ''), owner_id), imported_at = ?`\n ).run(m.name || '', m.groupId || '', m.groupAvatar || '', m.ownerId || '', Math.floor(Date.now() / 1000))\n metaUpdated = true\n }\n\n if (payload.members && memberUpdateMode === 'upsert') {\n const upsertMember = db.prepare(\n `INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(platform_id) DO UPDATE SET\n account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name),\n group_nickname = COALESCE(NULLIF(excluded.group_nickname, ''), group_nickname),\n avatar = COALESCE(NULLIF(excluded.avatar, ''), avatar),\n roles = CASE WHEN excluded.roles != '[]' THEN excluded.roles ELSE roles END`\n )\n const getMemberId = db.prepare('SELECT id FROM member WHERE platform_id = ?')\n const existingMemberIds = new Set(\n (db.prepare('SELECT platform_id FROM member').all() as Array<{ platform_id: string }>).map((r) => r.platform_id)\n )\n\n db.transaction(() => {\n for (const m of payload.members!) {\n const existed = existingMemberIds.has(m.platformId)\n const accountName = normalizeSenderAccountName(m.platformId, m.accountName)\n const groupNickname = normalizeSenderGroupNickname(m.platformId, m.groupNickname)\n upsertMember.run(\n m.platformId,\n accountName || null,\n groupNickname || null,\n m.avatar || null,\n m.roles ? JSON.stringify(m.roles) : '[]'\n )\n if (!existed) {\n membersAdded++\n const row = getMemberId.get(m.platformId) as { id: number } | undefined\n if (row) existingMemberIds.add(m.platformId)\n } else {\n membersUpdated++\n }\n }\n })\n }\n\n // Capture existing max timestamp before writing to detect backfill batches.\n const preWriteMaxTs =\n (db.prepare('SELECT MAX(ts) as max_ts FROM message').get() as { max_ts: number | null })?.max_ts ?? 0\n\n const { writtenCount, duplicateCount, minWrittenTs, ftsEntries } = writeMessages(\n db,\n payload.messages!,\n existingPmids,\n existingKeys\n )\n\n if (ftsEntries.length > 0) {\n try {\n insertFtsEntries(db, ftsEntries)\n } catch {\n /* non-fatal */\n }\n }\n\n if (writtenCount > 0) {\n try {\n // Use minWrittenTs (not payload min) to avoid false-positive backfill detection\n // when overlap duplicates in the batch have older timestamps than written rows.\n if (minWrittenTs < preWriteMaxTs) {\n generateSessionIndex(db)\n } else {\n generateIncrementalSessionIndex(db)\n }\n } catch {\n /* non-fatal */\n }\n }\n\n if (!metaUpdated) {\n db.prepare('UPDATE meta SET imported_at = ?').run(Math.floor(Date.now() / 1000))\n }\n\n return { writtenCount, duplicateCount, metaUpdated, membersAdded, membersUpdated }\n}\n\n// Only allow characters that are safe as a bare filename component.\n// Rejects path separators (/ \\), dots-only sequences (..), and control chars.\nconst SAFE_SESSION_ID_RE = /^[a-zA-Z0-9_@-][a-zA-Z0-9_@.-]*$/\n\nexport async function pushImport(\n dbManager: DatabaseManager,\n sessionId: string,\n payload: PushImportPayload\n): Promise<PushImportOutcome> {\n if (!SAFE_SESSION_ID_RE.test(sessionId) || sessionId.includes('..')) {\n return { ok: false, reason: 'invalid_payload', message: 'sessionId contains invalid characters' }\n }\n\n if (importInProgress.has(sessionId)) {\n return {\n ok: false,\n reason: 'import_in_progress',\n message: 'Another import is already in progress for this session',\n }\n }\n\n const dbPath = dbManager.getDbPath(sessionId)\n const isNew = !fs.existsSync(dbPath)\n\n const validationError = validatePayload(payload, isNew)\n if (validationError) {\n return { ok: false, reason: 'invalid_payload', message: validationError }\n }\n\n importInProgress.add(sessionId)\n try {\n if (isNew) {\n const db = dbManager.openRawSessionDatabase(sessionId, { create: true })\n try {\n const { writtenCount, duplicateCount, membersAdded } = fullImport(\n db,\n payload.meta!,\n payload.members ?? [],\n payload.messages!\n )\n const session = queryStats(db)\n dbManager.raiseCurrentChatDbCompatibilityGate()\n return {\n ok: true,\n result: {\n sessionId,\n created: true,\n batch: { receivedCount: payload.messages!.length, writtenCount, duplicateCount },\n session,\n updates: { metaUpdated: true, membersAdded, membersUpdated: 0 },\n },\n }\n } finally {\n db.close()\n }\n }\n\n const db = dbManager.openRawSessionDatabase(sessionId, { readonly: false })\n try {\n const { writtenCount, duplicateCount, metaUpdated, membersAdded, membersUpdated } = incrementalImport(db, payload)\n const session = queryStats(db)\n dbManager.raiseCurrentChatDbCompatibilityGate()\n return {\n ok: true,\n result: {\n sessionId,\n created: false,\n batch: { receivedCount: payload.messages!.length, writtenCount, duplicateCount },\n session,\n updates: { metaUpdated, membersAdded, membersUpdated },\n },\n }\n } finally {\n db.close()\n }\n } catch (err: unknown) {\n // Let DataDirCompatibilityError propagate so the Fastify error handler\n // maps it to 409 DATA_DIR_INCOMPATIBLE (consistent with other routes).\n if (err instanceof DataDirCompatibilityError) throw err\n\n if (isNew) {\n try {\n dbManager.deleteSessionDatabaseFiles(sessionId)\n } catch {\n /* cleanup best-effort */\n }\n }\n return { ok: false, reason: 'import_failed', message: err instanceof Error ? err.message : String(err) }\n } finally {\n importInProgress.delete(sessionId)\n }\n}\n"],"mappings":"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AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MACpF;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,MAAY;AAAA,M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A;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,oBAAoB,oBAAI,IAAI;AAAA;AAAA,EAEvC;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,qBAAqB,oBAAI,IAAI;AAAA;AAAA,EAExC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,SAAS,aAAa,QAA6B;AACxD,QAAM,mBAAmB,wBAAwB,MAAM;AAEvD,MAAI,iBAAiB,WAAW,IAAI,GAAG;AACrC,WAAO;AAAA,EACT;AACA,MAAI,qBAAqB,SAAS;AAChC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQO,SAAS,WAAW,MAAc,QAAyB;AAChE,QAAM,mBAAmB,wBAAwB,MAAM;AACvD,QAAM,YAAY,aAAa,gBAAgB;AAC/C,QAAM,iBAAiB,qBAAqB,UAAU,KAAK,YAAY,IAAI;AAC3E,SAAO,UAAU,IAAI,cAAc;AACrC;AAMA,SAAS,wBAAwB,QAAiC;AAChE,MAAI,OAAO,WAAW,UAAU;AAC9B,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,SAAS;AACtB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;;ACryBA,IAAM,cAAc;AACpB,IAAM,iCAAiC;AACvC,IAAM,oBAAoB;AAC1B,IAAM,YAAY;AAClB,IAAM,gBAAgB;AACtB,IAAM,oBAAoB;AAC1B,IAAM,2BACJ;AACF,IAAM,kCACJ;AACF,IAAM,iBAAiB;AAEvB,IAAM,oBAA4C;AAAA,EAChD,0BAAM;AAAA,EACN,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,QAAG;AAAA,EACH,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,oBAAK;AAAA,EACL,oBAAK;AAAA,EACL,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,oBAAK;AAAA,EACL,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,QAAG;AAAA,EACH,QAAG;AAAA,EACH,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,cAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAKO,SAAS,UAAU,MAAsB;AAC9C,SAAO,KACJ,QAAQ,WAAW,GAAG,EACtB,QAAQ,eAAe,GAAG,EAC1B,QAAQ,0BAA0B,GAAG,EACrC,QAAQ,iCAAiC,CAAC,OAAO,SAAiB;AACjE,QAAI,kBAAkB,IAAI,EAAG,QAAO,kBAAkB,IAAI;AAC1D,WAAO,eAAe,KAAK,IAAI,IAAI,MAAM;AAAA,EAC3C,CAAC,EACA,QAAQ,aAAa,GAAG,EACxB,QAAQ,gCAAgC,GAAG,EAC3C,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAKO,SAAS,YACd,MACA,QACA,WACA,iBACA,cACS;AACT,MAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO;AAC9C,MAAI,kBAAkB,KAAK,IAAI,EAAG,QAAO;AACzC,MAAI,KAAK,SAAS,UAAW,QAAO;AACpC,MAAI,mBAAmB,aAAa,MAAM,MAAM,EAAG,QAAO;AAC1D,SAAO;AACT;;;ACtHO,IAAM,uBAA4C,oBAAI,IAAI,CAAC,YAAY,QAAQ,WAAW,CAAC;AAE3F,SAAS,oBAAoBC,WAA2B;AAC7D,SAAO,qBAAqB,IAAIA,SAAQ;AAC1C;AAoBA,IAAM,qBAAqB;AAOpB,SAAS,mBAAmB,MAAsB;AACvD,SAAO,KAAK,QAAQ,oBAAoB,EAAE,EAAE,UAAU,MAAM,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,YAAY;AACxG;AAGO,SAAS,sBAAsB,QAAuC;AAC3E,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,IACP,OAAO,eAAe;AAAA,IACtB,OAAO,iBAAiB;AAAA,IACxB,GAAI,OAAO,WAAW,CAAC;AAAA,IACvB,OAAO,eAAe;AAAA,EACxB;AACA,QAAM,SAAmB,CAAC;AAC1B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAG;AAC3B,QAAI,KAAK,IAAI,IAAI,EAAG;AACpB,SAAK,IAAI,IAAI;AACb,WAAO,KAAK,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAMO,SAAS,oBAAoB,UAAoB,QAAuC;AAC7F,QAAM,SAAmB,CAAC;AAC1B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,CAAC,GAAG,UAAU,GAAG,sBAAsB,MAAM,CAAC,GAAG;AAClE,QAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAG;AAC3B,QAAI,KAAK,IAAI,IAAI,EAAG;AACpB,SAAK,IAAI,IAAI;AACb,WAAO,KAAK,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAUO,SAAS,kBACdA,WACA,SACA,SACkB;AAClB,QAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,eAAe,QAAQ,UAAU;AACrE,MAAI,OAAO;AACT,WAAO,EAAE,MAAM,SAAS,YAAY,MAAM,WAAW;AAAA,EACvD;AAEA,MAAI,CAAC,oBAAoBA,SAAQ,GAAG;AAClC,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB;AAEA,QAAM,YAAY,IAAI,IAAI,QAAQ,eAAe,IAAI,kBAAkB,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACpG,MAAI,UAAU,SAAS,GAAG;AACxB,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB;AAEA,QAAM,UAAU,QAAQ;AAAA,IAAO,CAAC,MAC9B,sBAAsB,CAAC,EAAE,KAAK,CAAC,SAAS,UAAU,IAAI,mBAAmB,IAAI,CAAC,CAAC;AAAA,EACjF;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,EAAE,MAAM,QAAQ,YAAY,QAAQ,CAAC,EAAE,WAAW;AAAA,EAC3D;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,WAAO,EAAE,MAAM,aAAa,aAAa,QAAQ,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE;AAAA,EAC5E;AACA,SAAO,EAAE,MAAM,OAAO;AACxB;;;AC3GA,SAAS,QAAQ,OAAuB;AAEtC,MAAI,KAAK;AACT,MAAI,KAAK;AAET,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,IAAI,MAAM,WAAW,CAAC;AAC5B,UAAM;AAIN,UAAM,KAAK,KAAK,KAAK,IAAI,QAAU;AACnC,UAAM,KAAK,KAAK,KAAK,IAAI,QAAU,IAAI,KAAK,KAAK,IAAI,CAAI;AACzD,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AAEA,QAAM,KAAK,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC1C,QAAM,KAAK,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC1C,SAAO,KAAK;AACd;AAEO,SAAS,mBAAmB,WAAmB,kBAA0B,SAAgC;AAC9G,QAAM,oBAAoB,WAAW;AACrC,QAAM,QAAQ;AAAA,IACZ,OAAO,SAAS;AAAA,IAChB;AAAA,IACA,sBAAsB,OAAO,SAAS;AAAA,IACtC,qBAAqB;AAAA,EACvB;AACA,SAAO,QAAQ,MAAM,KAAK,IAAI,CAAC;AACjC;;;ACUO,SAAS,wBACd,SACa;AACb,QAAM,MAAM,oBAAI,IAAyB;AACzC,aAAW,UAAU,SAAS;AAC5B,eAAW,UAAU,OAAO,SAAS;AACnC,UAAI,CAAC,IAAI,IAAI,OAAO,UAAU,EAAG,KAAI,IAAI,OAAO,YAAY,oBAAI,IAAI,CAAC;AACrE,UAAI,IAAI,OAAO,UAAU,EAAG,IAAI,OAAO,YAAY,SAAS;AAAA,IAC9D;AAAA,EACF;AACA,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,CAAC,IAAI,SAAS,KAAK,KAAK;AACjC,QAAI,UAAU,OAAO,EAAG,QAAO,IAAI,EAAE;AAAA,EACvC;AACA,SAAO;AACT;AAEO,SAAS,oCACd,aACa;AACb,QAAM,MAAM,oBAAI,IAAyB;AACzC,aAAW,QAAQ,aAAa;AAC9B,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,CAAC,IAAI,IAAI,GAAG,EAAG,KAAI,IAAI,KAAK,oBAAI,IAAI,CAAC;AACzC,QAAI,IAAI,GAAG,EAAG,IAAI,KAAK,YAAY,SAAS;AAAA,EAC9C;AACA,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,CAAC,IAAI,SAAS,KAAK,KAAK;AACjC,QAAI,UAAU,OAAO,EAAG,QAAO,IAAI,EAAE;AAAA,EACvC;AACA,SAAO;AACT;AAEO,SAAS,oBAAoB,YAAoBC,WAAkB,cAAmC;AAC3G,MAAI,CAAC,aAAa,IAAI,UAAU,EAAG,QAAO;AAC1C,QAAM,KAAK,mBAAmBA,aAAY,SAAS;AACnD,QAAM,KAAK,mBAAmB,UAAU;AACxC,SAAO,uBAAuB,EAAE,KAAK,EAAE;AACzC;AAIA,SAAS,cAAc,KAAoB,gBAAiC;AAC1E,SAAO,mBAAmB,IAAI,WAAW,kBAAkB,IAAI,kBAAkB,IAAI,WAAW,IAAI;AACtG;AAEA,SAAS,eAAe,KAA4B;AAClD,SAAO,IAAI,uBAAuB,IAAI,qBAAqB,IAAI;AACjE;AAEA,SAAS,mBAAmB,SAA6C;AACvE,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,iBAAiB,KAAK,QAAQ,KAAK,CAAC;AAC7C;AAIO,SAAS,0BACd,aACqB;AACrB,QAAM,eAAe,oCAAoC,WAAW;AACpE,QAAM,YAA6B,CAAC;AAEpC,QAAM,aAAa,oBAAI,IAA6E;AACpG,aAAW,QAAQ,aAAa;AAC9B,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,CAAC,WAAW,IAAI,EAAE,EAAG,YAAW,IAAI,IAAI,CAAC,CAAC;AAC9C,eAAW,IAAI,EAAE,EAAG,KAAK,IAAI;AAAA,EAC/B;AAEA,aAAW,CAAC,IAAI,KAAK,KAAK,YAAY;AACpC,QAAI,MAAM,SAAS,EAAG;AAEtB,UAAM,eAAe,oBAAI,IAA0B;AACnD,eAAW,QAAQ,OAAO;AACxB,YAAM,SAAS,oBAAoB,KAAK,IAAI,kBAAkB,KAAK,YAAY,WAAW,YAAY;AACtG,UAAI,CAAC,aAAa,IAAI,MAAM,EAAG,cAAa,IAAI,QAAQ,CAAC,CAAC;AAC1D,mBAAa,IAAI,MAAM,EAAG,KAAK,IAAI;AAAA,IACrC;AAEA,eAAW,CAAC,QAAQ,WAAW,KAAK,cAAc;AAChD,UAAI,YAAY,SAAS,EAAG;AAC5B,YAAM,UAAU,IAAI,IAAI,YAAY,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;AAC1D,UAAI,QAAQ,OAAO,EAAG;AAEtB,YAAM,gBAAgB,oBAAI,IAAgC;AAC1D,iBAAW,QAAQ,aAAa;AAC9B,cAAM,IAAI,KAAK,IAAI,WAAW;AAC9B,YAAI,CAAC,cAAc,IAAI,CAAC,EAAG,eAAc,IAAI,GAAG,CAAC,CAAC;AAClD,sBAAc,IAAI,CAAC,EAAG,KAAK,IAAI;AAAA,MACjC;AAEA,UAAI,cAAc,OAAO,GAAG;AAC1B,cAAM,UAAU,MAAM,KAAK,cAAc,QAAQ,CAAC;AAClD,iBAAS,IAAI,GAAG,IAAI,QAAQ,SAAS,GAAG,KAAK;AAC3C,mBAAS,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AAC3C,kBAAM,CAAC,IAAI,MAAM,IAAI,QAAQ,CAAC;AAC9B,kBAAM,CAAC,IAAI,MAAM,IAAI,QAAQ,CAAC;AAC9B,kBAAM,QAAQ,OAAO,CAAC;AACtB,kBAAM,QAAQ,OAAO,KAAK,CAAC,OAAO,GAAG,WAAW,MAAM,MAAM;AAC5D,gBAAI,CAAC,MAAO;AACZ,gBAAI,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,EAAG;AAEtD,sBAAU,KAAK;AAAA,cACb,IAAI,YAAY,EAAE,IAAI,MAAM,IAAI,UAAU,MAAM;AAAA,cAChD,WAAW;AAAA,cACX,QAAQ,eAAe,MAAM,GAAG,KAAK;AAAA,cACrC,gBAAgB,GAAG;AAAA,cACnB,gBAAgB,GAAG;AAAA,cACnB,UAAU;AAAA,cACV,UAAU;AAAA,YACZ,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,QAAQ,aAAa;AAC9B,UAAM,MAAM,oBAAoB,KAAK,IAAI,kBAAkB,KAAK,YAAY,WAAW,YAAY;AACnG,eAAW,IAAI,cAAc,KAAK,KAAK,GAAG,CAAC;AAAA,EAC7C;AAEA,SAAO,EAAE,WAAW,eAAe,WAAW,KAAK;AACrD;AAIO,SAASC,cACd,SACA,cAC2B;AAC3B,QAAM,MAAM,oBAAI,IAA0B;AAC1C,aAAW,EAAE,UAAAD,WAAU,QAAQ,KAAK,SAAS;AAC3C,UAAM,IAAIA,aAAY;AACtB,eAAW,KAAK,SAAS;AACvB,YAAM,MAAM,oBAAoB,EAAE,YAAY,GAAG,YAAY;AAC7D,YAAM,WAAW,IAAI,IAAI,GAAG;AAC5B,UAAI,UAAU;AACZ,YAAI,EAAE,YAAa,UAAS,cAAc,EAAE;AAC5C,YAAI,EAAE,cAAe,UAAS,gBAAgB,EAAE;AAChD,YAAI,EAAE,OAAQ,UAAS,SAAS,EAAE;AAAA,MACpC,OAAO;AACL,YAAI,IAAI,KAAK;AAAA,UACX,YAAY;AAAA,UACZ,aAAa,EAAE;AAAA,UACf,eAAe,EAAE;AAAA,UACjB,QAAQ,EAAE;AAAA,QACZ,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;AC5MO,IAAM,yBAAyB;AAM/B,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCASO,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwDpD,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBxB,IAAM,iBAAiB,iBAAiB;AAQxC,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACtFzB,SAAS,iBAAiB,IAA6B;AAC5D,MAAI;AACF,UAAM,YAAY,GAAG,OAAO,kBAAkB;AAC9C,UAAM,mBAAmB,UAAU,KAAK,CAAC,QAAQ,IAAI,SAAS,gBAAgB;AAC9E,QAAI,CAAC,iBAAkB,QAAO;AAE9B,UAAM,SAAS,GAAG,QAAQ,yCAAyC,EAAE,IAAI;AAGzE,WAAO,QAAQ,kBAAkB;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,iBAAiB,IAAqB,SAAuB;AAC3E,QAAM,YAAY,GAAG,OAAO,kBAAkB;AAC9C,QAAM,mBAAmB,UAAU,KAAK,CAAC,QAAQ,IAAI,SAAS,gBAAgB;AAE9E,MAAI,CAAC,kBAAkB;AACrB,OAAG,KAAK,8DAA8D;AAAA,EACxE;AAEA,KAAG,QAAQ,oCAAoC,EAAE,IAAI,OAAO;AAC9D;AAKO,SAAS,eAAe,IAAqB,eAAgC;AAClF,SAAO,iBAAiB,EAAE,IAAI;AAChC;AAUO,SAAS,cAAc,IAAqB,gBAA6B,cAAc,OAAgB;AAC5G,QAAM,iBAAiB,iBAAiB,EAAE;AAE1C,MAAI,CAAC,eAAe,mBAAmB,eAAe,GAAG,EAAE,GAAG,WAAW,IAAI;AAC3E,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,cAAc,iBAAiB,eAAe,OAAO,CAAC,MAAM,EAAE,UAAU,cAAc;AAEtG,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,KAAG,YAAY,MAAM;AACnB,eAAW,aAAa,SAAS;AAC/B,gBAAU,GAAG,EAAE;AACf,uBAAiB,IAAI,UAAU,OAAO;AAAA,IACxC;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AC7EA,YAAYE,UAAQ;AACpB,YAAYC,YAAU;;;ACDtB,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAItB,IAAM,YAAY,KAAK;AAKvB,SAAS,aAAa,UAAkB,OAAe,WAAmB;AACxE,QAAM,KAAQ,cAAS,UAAU,GAAG;AACpC,QAAM,SAAS,OAAO,MAAM,IAAI;AAChC,QAAM,YAAe,cAAS,IAAI,QAAQ,GAAG,MAAM,CAAC;AACpD,EAAG,eAAU,EAAE;AACf,SAAO,OAAO,MAAM,GAAG,SAAS,EAAE,SAAS,OAAO;AACpD;AAKA,SAAS,aAAa,UAA0B;AAC9C,SAAY,eAAQ,QAAQ,EAAE,YAAY;AAC5C;AAKA,SAAS,oBAAoB,aAAqB,UAA6B;AAC7E,SAAO,SAAS,KAAK,CAAC,YAAY,QAAQ,KAAK,WAAW,CAAC;AAC7D;AAKA,SAAS,wBAAwB,UAAkB,UAA6B;AAC9E,QAAM,WAAgB,gBAAS,QAAQ;AACvC,SAAO,SAAS,KAAK,CAAC,YAAY,QAAQ,KAAK,QAAQ,CAAC;AAC1D;AAKA,SAAS,oBAAoB,aAAqB,QAA2B;AAG3E,SAAO,OAAO,MAAM,CAAC,UAAU;AAC7B,UAAM,UAAU,IAAI,OAAO,IAAI,MAAM,QAAQ,KAAK,eAAe,CAAC,QAAQ;AAC1E,WAAO,QAAQ,KAAK,WAAW,KAAK,YAAY,SAAS,IAAI,KAAK,GAAG;AAAA,EACvE,CAAC;AACH;AAMO,IAAM,gBAAN,MAAoB;AAAA,EACjB,UAA0B,CAAC;AAAA;AAAA;AAAA;AAAA,EAKnC,SAAS,QAA4B;AACnC,SAAK,QAAQ,KAAK,MAAM;AAExB,SAAK,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,WAAW,EAAE,QAAQ,QAAQ;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAA+B;AACzC,eAAW,UAAU,SAAS;AAC5B,WAAK,SAAS,MAAM;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAwC;AAC5C,UAAM,MAAM,aAAa,QAAQ;AACjC,UAAM,cAAc,aAAa,QAAQ;AAEzC,eAAW,EAAE,SAAAC,UAAQ,KAAK,KAAK,SAAS;AACtC,UAAI,KAAK,aAAaA,WAAS,KAAK,aAAa,QAAQ,GAAG;AAC1D,eAAOA;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,UAAiC;AACzC,UAAM,MAAM,aAAa,QAAQ;AACjC,UAAM,cAAc,aAAa,QAAQ;AAEzC,eAAW,EAAE,SAAAA,WAAS,QAAAC,SAAO,KAAK,KAAK,SAAS;AAC9C,UAAI,KAAK,aAAaD,WAAS,KAAK,aAAa,QAAQ,GAAG;AAC1D,eAAOC;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,UAAmC;AAC1C,UAAM,MAAM,aAAa,QAAQ;AACjC,UAAM,cAAc,aAAa,QAAQ;AACzC,UAAM,UAA2B,CAAC;AAElC,eAAW,EAAE,SAAAD,UAAQ,KAAK,KAAK,SAAS;AACtC,UAAI,KAAK,aAAaA,WAAS,KAAK,aAAa,QAAQ,GAAG;AAC1D,gBAAQ,KAAKA,SAAO;AAAA,MACtB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,oBAAoB,UAA4B;AAC9C,UAAM,MAAM,aAAa,QAAQ;AACjC,UAAM,cAAc,aAAa,QAAQ;AACzC,UAAM,UAAoB,CAAC;AAE3B,eAAW,EAAE,SAAAA,WAAS,QAAAC,SAAO,KAAK,KAAK,SAAS;AAC9C,UAAI,KAAK,aAAaD,WAAS,KAAK,aAAa,QAAQ,GAAG;AAC1D,gBAAQ,KAAKC,QAAM;AAAA,MACrB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAiC;AAC7C,UAAM,SAAS,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,QAAQ,OAAO,QAAQ;AACjE,WAAO,QAAQ,UAAU;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAuC;AACrC,WAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,OAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAaD,WAAwB,KAAa,aAAqB,UAA4B;AAEzG,QAAI,CAACA,UAAQ,WAAW,SAAS,GAAG,GAAG;AACrC,aAAO;AAAA,IACT;AAEA,UAAM,EAAE,WAAW,IAAIA;AAGvB,QAAI,YAAY;AAChB,QAAI,WAAW,QAAQ,WAAW,KAAK,SAAS,GAAG;AACjD,kBAAY,oBAAoB,aAAa,WAAW,IAAI;AAAA,IAC9D;AAGA,QAAI,gBAAgB;AACpB,QAAI,WAAW,YAAY,WAAW,SAAS,SAAS,KAAK,UAAU;AACrE,sBAAgB,wBAAwB,UAAU,WAAW,QAAQ;AAAA,IACvE;AAGA,QAAI,CAAC,aAAa,CAAC,eAAe;AAEhC,UAAK,WAAW,QAAQ,WAAW,KAAK,SAAS,KAAO,WAAW,YAAY,WAAW,SAAS,SAAS,GAAI;AAC9G,eAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,WAAW,kBAAkB,WAAW,eAAe,SAAS,GAAG;AACrE,UAAI,CAAC,oBAAoB,aAAa,WAAW,cAAc,GAAG;AAChE,eAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,WAAW,eAAe;AAC5B,iBAAW,CAAC,EAAE,OAAO,KAAK,OAAO,QAAQ,WAAW,aAAa,GAAG;AAClE,YAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAKO,SAAS,gBAA+B;AAC7C,SAAO,IAAI,cAAc;AAC3B;;;AC1NA,yBAAuB;AACvB,kBAAuB;AACvB,0BAA+B;AAC/B,0BAAwB;AAJxB,YAAYE,UAAQ;;;ACkDb,IAAM,kBAAkB;AAAA,EAC7B,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,MAAM;AAAA,EACN,SAAS;AACX;;;AClEA,YAAYC,UAAQ;AAMb,SAAS,YAAY,UAA0B;AACpD,SAAU,cAAS,QAAQ,EAAE;AAC/B;AAKO,SAAS,kBAAkB,UAAkB,MAAsB;AACxE,QAAM,KAAQ,cAAS,UAAU,GAAG;AACpC,QAAM,SAAS,OAAO,MAAM,IAAI;AAChC,QAAM,YAAe,cAAS,IAAI,QAAQ,GAAG,MAAM,CAAC;AACpD,EAAG,eAAU,EAAE;AACf,SAAO,OAAO,MAAM,GAAG,SAAS,EAAE,SAAS,OAAO;AACpD;AAKO,IAAM,iBAAiC,CAAC,OAAO,WAAW,YAAY,mBAAmB,YAAY;AAC1G,QAAM,aAAa,aAAa,IAAI,KAAK,IAAI,IAAI,KAAK,MAAO,YAAY,aAAc,GAAG,CAAC,IAAI;AAC/F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,UAAU,SAAS,MAAM;AAAA,IACrC;AAAA,EACF;AACF;AAMO,SAAS,eAAe,OAAuC;AACpE,MAAI,OAAO,UAAU,UAAU;AAE7B,UAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,QAAI,MAAM,MAAM,EAAG,QAAO;AAC1B,WAAO,KAAK,MAAM,SAAS,GAAI;AAAA,EACjC,WAAW,OAAO,UAAU,UAAU;AAEpC,QAAI,MAAM,KAAK,EAAG,QAAO;AACzB,WAAO,KAAK,MAAM,QAAQ,GAAI;AAAA,EAChC;AACA,SAAO;AACT;AAKO,SAAS,YAAY,kBAAmC;AAC7D,SAAO,IAAI,KAAK,mBAAmB,GAAI,EAAE,YAAY,KAAK;AAC5D;;;AFpCA,YAAYC,YAAU;AAEtB,IAAM,EAAE,OAAO,IAAI,mBAAAC;AACnB,IAAM,EAAE,KAAK,IAAI,YAAAC;AACjB,IAAM,EAAE,aAAa,IAAI,oBAAAC;AACzB,IAAM,EAAE,MAAM,IAAI,oBAAAC;AAQlB,SAAS,wBAAwB,UAA0B;AACzD,QAAMC,aAAgB,gBAAS,QAAQ;AAEvC,QAAM,OAAOA,WAAS,QAAQ,YAAY,EAAE;AAC5C,SAAO,QAAQ;AACjB;AAIO,IAAM,UAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA;AAAA,EAC1B,UAAU;AAAA;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA;AAAA,IAEV,MAAM,CAAC,oBAAoB;AAAA,IAC3B,gBAAgB,CAAC,SAAS;AAAA,EAC5B;AACF;AA0BA,gBAAgB,aAAaC,UAAkE;AAC7F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AAGxB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAG5B,UAAQ,QAAQ,iFAA0B,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGnF,QAAM,cAAc,kBAAkB,UAAU,GAAM;AAGtD,MAAI,OAAmB;AAAA,IACrB,MAAM;AAAA,IACN,UAAU,gBAAgB;AAAA,IAC1B;AAAA,EACF;AACA,MAAI;AAGF,UAAM,iBAAiB,YAAY,MAAM,iBAAiB;AAC1D,QAAI,kBAAkB,eAAe,UAAU,QAAW;AACxD,YAAM,aAAa,eAAe,QAAQ,eAAe,CAAC,EAAE,SAAS;AACrE,UAAI,QAAQ;AACZ,UAAI,WAAW;AAGf,eAAS,IAAI,YAAY,IAAI,YAAY,QAAQ,KAAK;AACpD,cAAM,OAAO,YAAY,CAAC;AAC1B,YAAI,SAAS,KAAK;AAChB;AAAA,QACF,WAAW,SAAS,KAAK;AACvB;AACA,cAAI,UAAU,GAAG;AACf,uBAAW;AACX;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,UAAI,WAAW,YAAY;AACzB,cAAM,WAAW,YAAY,MAAM,YAAY,WAAW,CAAC;AAC3D,cAAM,UAAU,KAAK,MAAM,QAAQ;AACnC,eAAO;AAAA,UACL,MAAM,QAAQ,QAAQ;AAAA,UACtB,UAAU,QAAQ,YAAY,gBAAgB;AAAA,UAC9C,MAAO,QAAQ;AAAA,UACf,SAAS,QAAQ;AAAA,UACjB,aAAa,QAAQ;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI,KAAK,SAAS,4BAAQ;AACxB,SAAK,OAAO,wBAAwB,QAAQ;AAAA,EAC9C;AAEA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,UAA0B,CAAC;AACjC,MAAI;AACF,UAAM,eAAe,YAAY,MAAM,gCAAgC;AACvE,QAAI,cAAc;AAChB,YAAM,cAAc,KAAK,MAAM,IAAI,aAAa,CAAC,CAAC,GAAG;AACrD,iBAAW,KAAK,aAAa;AAC3B,gBAAQ,KAAK;AAAA,UACX,YAAY,EAAE;AAAA,UACd,aAAa,EAAE;AAAA,UACf,eAAe,EAAE;AAAA,UACjB,QAAQ,EAAE;AAAA,UACV,OAAO,EAAE;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,QAAM,wBAAwB,oBAAI,IAA0B;AAC5D,QAAM,eAAgC,CAAC;AAGvC,QAAM,IAAI,QAAc,CAACC,UAAS,WAAW;AAC3C,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAEtE,eAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,mBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,IAC5E,CAAC;AAED,UAAMC,YAAW,MAAM,CAAC,YAAY,OAAO,GAAG,KAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAG,aAAa,CAAC,CAAC;AAGlG,UAAM,iBAAkC,CAAC;AAEzC,IAAAA,UAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAAiC;AAC5D,YAAM,MAAM;AAGZ,UAAI,QAAQ,WAAW,GAAG;AACxB,8BAAsB,IAAI,IAAI,QAAQ;AAAA,UACpC,YAAY,IAAI;AAAA,UAChB,aAAa,IAAI;AAAA,UACjB,eAAe,IAAI;AAAA,QACrB,CAAC;AAAA,MACH;AAEA,qBAAe,KAAK;AAAA,QAClB,kBAAkB,IAAI;AAAA,QACtB,mBAAmB,IAAI;AAAA,QACvB,qBAAqB,IAAI;AAAA,QACzB,WAAW,IAAI;AAAA,QACf,MAAM,IAAI;AAAA,QACV,SAAS,IAAI;AAAA,QACb,mBAAmB,IAAI;AAAA,QACvB,kBAAkB,IAAI;AAAA,MACxB,CAAC;AAED;AAGA,UAAI,eAAe,UAAU,WAAW;AACtC,qBAAa,KAAK,GAAG,cAAc;AACnC,uBAAe,SAAS;AAExB,cAAM,WAAW;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,sBAAO,iBAAiB;AAAA,QAC1B;AACA,qBAAa,QAAQ;AAAA,MACvB;AAAA,IACF,CAAC;AAED,IAAAA,UAAS,GAAG,OAAO,MAAM;AAEvB,UAAI,eAAe,SAAS,GAAG;AAC7B,qBAAa,KAAK,GAAG,cAAc;AAAA,MACrC;AACA,MAAAD,SAAQ;AAAA,IACV,CAAC;AAED,IAAAC,UAAS,GAAG,SAAS,MAAM;AAAA,EAC7B,CAAC;AAGD,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AAAA,EACzC,WAAW,sBAAsB,OAAO,GAAG;AACzC,UAAM,EAAE,MAAM,WAAW,MAAM,MAAM,KAAK,sBAAsB,OAAO,CAAC,EAAE;AAAA,EAC5E;AAGA,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,WAAW;AACvD,UAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,SAAS;AACjD,UAAM,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,EACxC;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAGzB,QAAM,cAAc,QAAQ,SAAS,IAAI,QAAQ,SAAS,sBAAsB;AAChF,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,WAAW,qBAAM;AAEpE,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,cAAc;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAIO,IAAM,UAAkB;AAAA,EAC7B;AAAA,EACA,OAAO;AACT;AAIA,IAAM,UAAwB;AAAA,EAC5B;AAAA,EACA,QAAQ;AACV;AAEA,IAAO,kBAAQ;;;AGjRf,YAAYC,UAAQ;AACpB,YAAY,cAAc;AAC1B,YAAYC,YAAU;AAiEtB,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,gBAAS,QAAQ;AACvC,QAAM,OAAOA,WAAS,QAAQ,aAAa,EAAE;AAC7C,SAAO,QAAQ;AACjB;AAKA,SAAS,UAAU,MAAgC;AACjD,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,GAAG;AAEvC,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAIO,IAAMC,WAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA;AAAA,EACV,YAAY,CAAC,QAAQ;AAAA,EACrB,YAAY;AAAA;AAAA,IAEV,MAAM,CAAC,2CAA2C;AAAA,EACpD;AACF;AAIA,gBAAgB,kBAAkBC,UAAkE;AAClG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AAGxB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAG5B,UAAQ,QAAQ,2EAA8B,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGvF,QAAM,UAA0B,CAAC;AACjC,QAAM,YAAY,oBAAI,IAA0B;AAChD,QAAM,eAAgC,CAAC;AACvC,MAAI,OAA0B;AAC9B,MAAI,eAAe;AAGnB,QAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AACtE,QAAM,KAAc,yBAAgB;AAAA,IAClC,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AAGD,aAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,iBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,EAC5E,CAAC;AAGD,mBAAiB,QAAQ,IAAI;AAC3B,UAAM,SAAS,UAAU,IAAI;AAC7B,QAAI,CAAC,OAAQ;AAEb,YAAQ,OAAO,OAAO;AAAA,MACpB,KAAK;AACH,YAAI,CAAC,cAAc;AACjB,iBAAO;AAAA,YACL,MAAM,OAAO,KAAK,QAAQH,yBAAwB,QAAQ;AAAA,YAC1D,UAAU,OAAO,KAAK,YAAY,gBAAgB;AAAA,YAClD,MAAO,OAAO,KAAK;AAAA,YACnB,SAAS,OAAO,KAAK;AAAA,YACrB,aAAa,OAAO,KAAK;AAAA,UAC3B;AACA,yBAAe;AACf,gBAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAAA,QACnC;AACA;AAAA,MAEF,KAAK;AACH;AACE,gBAAM,SAAuB;AAAA,YAC3B,YAAY,OAAO;AAAA,YACnB,aAAa,OAAO;AAAA,YACpB,eAAe,OAAO;AAAA,YACtB,QAAQ,OAAO;AAAA,UACjB;AACA,kBAAQ,KAAK,MAAM;AACnB,oBAAU,IAAI,OAAO,YAAY,MAAM;AAAA,QACzC;AACA;AAAA,MAEF,KAAK;AAEH,YAAI,CAAC,MAAM;AACT,iBAAO;AAAA,YACL,MAAMA,yBAAwB,QAAQ;AAAA,YACtC,UAAU,gBAAgB;AAAA,YAC1B;AAAA,UACF;AACA,gBAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAAA,QACnC;AAGA,YAAI,CAAC,UAAU,IAAI,OAAO,MAAM,GAAG;AACjC,gBAAM,iBAA+B;AAAA,YACnC,YAAY,OAAO;AAAA,YACnB,aAAa,OAAO;AAAA,YACpB,eAAe,OAAO;AAAA,UACxB;AACA,oBAAU,IAAI,OAAO,QAAQ,cAAc;AAAA,QAC7C;AAEA,qBAAa,KAAK;AAAA,UAChB,kBAAkB,OAAO;AAAA,UACzB,mBAAmB,OAAO;AAAA,UAC1B,qBAAqB,OAAO;AAAA,UAC5B,WAAW,OAAO;AAAA,UAClB,MAAM,OAAO;AAAA,UACb,SAAS,OAAO;AAAA,UAChB,mBAAmB,OAAO;AAAA,UAC1B,kBAAkB,OAAO;AAAA,QAC3B,CAAC;AACD;AAGA,YAAI,aAAa,UAAU,WAAW;AACpC,gBAAM,WAAW;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,sBAAO,kBAAkB,eAAe,CAAC;AAAA,UAC3C;AACA,uBAAa,QAAQ;AAAA,QACvB;AACA;AAAA,IACJ;AAAA,EACF;AAGA,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,MACL,MAAMA,yBAAwB,QAAQ;AAAA,MACtC,UAAU,gBAAgB;AAAA,MAC1B;AAAA,IACF;AACA,UAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAAA,EACnC;AAGA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AAAA,EACzC,WAAW,UAAU,OAAO,GAAG;AAC7B,UAAM,EAAE,MAAM,WAAW,MAAM,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE;AAAA,EAChE;AAGA,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,WAAW;AACvD,UAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,SAAS;AACjD,UAAM,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,EACxC;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAGzB,QAAM,cAAc,QAAQ,SAAS,IAAI,QAAQ,SAAS,UAAU;AACpE,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,WAAW,qBAAM;AAEpE,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,cAAc;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAIO,IAAMI,UAAiB;AAAA,EAC5B,SAAAF;AAAA,EACA,OAAO;AACT;AAIA,IAAMG,WAAwB;AAAA,EAC5B,SAAAH;AAAA,EACA,QAAAE;AACF;AAEA,IAAO,wBAAQC;;;AChRf,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAC/B,IAAAC,uBAAwB;AALxB,YAAYC,UAAQ;AACpB,YAAYC,YAAU;;;ACTtB,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAC/B,IAAAC,uBAAwB;AANxB,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,YAAYC,SAAQ;AAQpB,IAAM,EAAE,QAAAC,QAAO,IAAI,oBAAAC;AACnB,IAAM,EAAE,MAAAC,MAAK,IAAI,aAAAC;AACjB,IAAM,EAAE,cAAAC,cAAa,IAAI,qBAAAC;AACzB,IAAM,EAAE,OAAAC,OAAM,IAAI,qBAAAC;AAGlB,IAAM,uBAAuB,KAAK,OAAO;AAKzC,SAAS,aAAqB;AAC5B,SAAY,YAAQ,WAAO,GAAG,SAAS;AACzC;AAKA,SAAS,UAAU,KAAmB;AACpC,MAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,IAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACvC;AACF;AAKA,SAAS,kBAAkB,SAAiB,KAA4B;AACtE,QAAM,YAAY,IAAI,GAAG;AACzB,QAAM,WAAW,QAAQ,QAAQ,SAAS;AAC1C,MAAI,aAAa,GAAI,QAAO;AAE5B,MAAI,IAAI,WAAW,UAAU;AAC7B,SAAO,IAAI,QAAQ,UAAU,KAAK,KAAK,QAAQ,CAAC,CAAC,EAAG;AAEpD,MAAI,QAAQ,CAAC,MAAM,IAAK,QAAO;AAE/B,MAAI,aAAa;AACjB,MAAI,WAAW;AACf,MAAI,SAAS;AACb,QAAM,WAAW;AAEjB,SAAO,IAAI,QAAQ,QAAQ,KAAK;AAC9B,UAAM,OAAO,QAAQ,CAAC;AAEtB,QAAI,QAAQ;AACV,eAAS;AACT;AAAA,IACF;AAEA,QAAI,SAAS,QAAQ,UAAU;AAC7B,eAAS;AACT;AAAA,IACF;AAEA,QAAI,SAAS,KAAK;AAChB,iBAAW,CAAC;AACZ;AAAA,IACF;AAEA,QAAI,CAAC,UAAU;AACb,UAAI,SAAS,IAAK;AAClB,UAAI,SAAS,KAAK;AAChB;AACA,YAAI,eAAe,GAAG;AACpB,iBAAO,QAAQ,MAAM,UAAU,IAAI,CAAC;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,oBAAoB,UAAiC;AAC5D,MAAI;AACF,UAAM,QAAW,cAAS,QAAQ;AAClC,UAAM,WAAW,KAAK,IAAI,MAAM,MAAM,GAAO;AAC7C,UAAM,KAAQ,cAAS,UAAU,GAAG;AACpC,UAAM,SAAS,OAAO,MAAM,QAAQ;AACpC,IAAG,cAAS,IAAI,QAAQ,GAAG,UAAU,MAAM,OAAO,QAAQ;AAC1D,IAAG,eAAU,EAAE;AAEf,UAAM,cAAc,OAAO,SAAS,OAAO;AAC3C,WAAO,kBAAkB,aAAa,SAAS;AAAA,EACjD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAqCA,SAAS,YAAY,KAA6C;AAChE,QAAM,SAAS,IAAI;AACnB,QAAM,UAAU,IAAI;AACpB,QAAM,aAAa,IAAI;AAEvB,QAAM,cAAwC;AAAA,IAC5C,MAAO,SAAS,QAAmB;AAAA,EACrC;AAEA,MAAI,SAAS,YAAY,MAAM,QAAQ,QAAQ,QAAQ,GAAG;AACxD,gBAAY,WAAY,QAAQ,SAAqC,IAAI,CAAC,OAAO;AAAA,MAC/E,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AAEA,MAAI,SAAS,aAAa,MAAM,QAAQ,QAAQ,SAAS,GAAG;AAC1D,gBAAY,YAAa,QAAQ,UAAsC,IAAI,CAAC,OAAO;AAAA,MACjF,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AAEA,MAAI,SAAS,UAAU,MAAM,QAAQ,QAAQ,MAAM,GAAG;AACpD,gBAAY,SAAU,QAAQ,OAAmC,IAAI,CAAC,OAAO;AAAA,MAC3E,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AAEA,QAAM,UAAyB;AAAA,IAC7B,WAAW,IAAI;AAAA,IACf,QAAQ,EAAE,MAAM,QAAQ,QAAQ,GAAG;AAAA,IACnC,SAAS;AAAA,EACX;AAGA,MAAI,IAAI,GAAI,SAAQ,KAAK,IAAI;AAC7B,MAAI,IAAI,KAAM,SAAQ,OAAO,IAAI;AACjC,MAAI,IAAI,SAAU,SAAQ,WAAW,IAAI;AACzC,MAAI,IAAI,OAAQ,SAAQ,SAAS,IAAI;AAGrC,MAAI,IAAI,UAAW,SAAQ,YAAY,IAAI;AAC3C,MAAI,IAAI,gBAAgB,OAAW,SAAQ,cAAc,IAAI;AAC7D,MAAI,IAAI,WAAY,SAAQ,aAAa,IAAI;AAC7C,MAAI,IAAI,gBAAiB,SAAQ,kBAAkB,IAAI;AAGvD,MAAI,QAAQ,IAAK,SAAQ,OAAO,MAAM,OAAO;AAC7C,MAAI,QAAQ,IAAK,SAAQ,OAAO,MAAM,OAAO;AAG7C,MAAI,YAAY;AACd,YAAQ,aAAa,CAAC;AACtB,QAAI,WAAW,aAAc,SAAQ,WAAW,eAAe,WAAW;AAC1E,QAAI,WAAW,eAAgB,SAAQ,WAAW,iBAAiB,WAAW;AAC9E,QAAI,WAAW,UAAW,SAAQ,WAAW,YAAY,WAAW;AACpE,QAAI,WAAW,UAAW,SAAQ,WAAW,YAAY,WAAW;AAAA,EACtE;AAEA,SAAO;AACT;AAKA,eAAe,iBAAiB,WAAmB,YAAiE;AAClH,QAAM,aAAa,YAAY,SAAS;AACxC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AAExB,QAAM,UAAU,WAAW;AAC3B,YAAU,OAAO;AACjB,QAAM,iBAAiB,QAAQ,KAAK,IAAI,CAAC,IAAS,gBAAS,SAAS,CAAC;AACrE,QAAM,aAAkB,YAAK,SAAS,cAAc;AAEpD,eAAa,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE,CAAC;AAG5D,QAAM,aAAa,oBAAoB,SAAS;AAEhD,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,UAAM,aAAuB,CAAC;AAC9B,QAAI,WAAW;AACf,UAAM,cAAc;AAEpB,UAAM,aAAgB,sBAAiB,WAAW,EAAE,UAAU,QAAQ,CAAC;AACvE,QAAI,WAAoC,EAAE,MAAM,4BAAQ,MAAM,QAAQ;AACtE,QAAI;AACJ,QAAI;AAEJ,eAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,YAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,MAAM,SAAS,OAAO;AACtE,UAAI,WAAW,aAAa;AAC1B,mBAAW,KAAK,GAAG;AACnB,oBAAY,IAAI;AAAA,MAClB,OAAO;AACL,mBAAW,QAAQ;AAAA,MACrB;AAAA,IACF,CAAC;AAED,eAAW,GAAG,SAAS,MAAM;AAC3B,YAAM,cAAc,WAAW,KAAK,EAAE;AAEtC,UAAI;AACF,cAAM,gBAAgB,YAAY,MAAM,8BAA8B;AACtE,YAAI,eAAe;AACjB,qBAAW,KAAK,MAAM,cAAc,CAAC,CAAC;AAAA,QACxC;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,UAAI;AACF,cAAM,gBAAgB,YAAY,MAAM,8BAA8B;AACtE,YAAI,eAAe;AACjB,qBAAW,KAAK,MAAM,cAAc,CAAC,CAAC;AAAA,QACxC;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,UAAI;AACF,cAAM,kBAAkB,YAAY,MAAM,oDAAoD;AAC9F,YAAI,iBAAiB;AACnB,uBAAa,KAAK,MAAM,gBAAgB,CAAC,CAAC;AAAA,QAC5C;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,mBAAa,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE,CAAC;AAE5D,YAAM,aAAgB,sBAAiB,WAAW,EAAE,UAAU,QAAQ,CAAC;AACvE,YAAM,cAAiB,uBAAkB,YAAY,EAAE,UAAU,QAAQ,CAAC;AAE1E,iBAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,qBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,MAC5E,CAAC;AAED,YAAM,SAAS,EAAE,UAAU,UAAU,YAAY,UAAU,CAAC,EAAE;AAC9D,YAAM,aAAa,KAAK,UAAU,MAAM;AAExC,kBAAY,MAAM,WAAW,MAAM,GAAG,EAAE,IAAI,IAAI;AAEhD,UAAI,iBAAiB;AAErB,YAAMC,YAAWH,OAAM,CAAC,YAAYN,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAElG,MAAAK,UAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAA0C;AACrE,cAAM,UAAU,YAAY,KAAK;AACjC,cAAM,UAAU,KAAK,UAAU,OAAO;AAEtC,YAAI,gBAAgB;AAClB,sBAAY,MAAM,OAAO;AACzB,2BAAiB;AAAA,QACnB,OAAO;AACL,sBAAY,MAAM,QAAQ,OAAO;AAAA,QACnC;AAEA;AAEA,YAAI,oBAAoB,QAAU,GAAG;AACnC;AAAA,YACE;AAAA,cACE;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,8CAAW,iBAAiB;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAED,MAAAA,UAAS,GAAG,OAAO,MAAM;AAEvB,oBAAY,MAAM,KAAK;AAGvB,YAAI,YAAY;AACd,sBAAY,MAAM,gBAAgB,UAAU;AAAA,QAC9C;AAGA,oBAAY,MAAM,GAAG;AACrB,oBAAY,IAAI;AAEhB,oBAAY,GAAG,UAAU,MAAM;AAC7B,uBAAa,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE,CAAC;AAClF,UAAAD,SAAQ,UAAU;AAAA,QACpB,CAAC;AAAA,MACH,CAAC;AAED,MAAAC,UAAS,GAAG,SAAS,CAAC,QAAQ;AAC5B,oBAAY,QAAQ;AACpB,YAAO,gBAAW,UAAU,GAAG;AAC7B,UAAG,gBAAW,UAAU;AAAA,QAC1B;AACA,qBAAa,eAAe,SAAS,WAAW,YAAY,mBAAmB,IAAI,OAAO,CAAC;AAC3F,eAAO,GAAG;AAAA,MACZ,CAAC;AAAA,IACH,CAAC;AAED,eAAW,GAAG,SAAS,MAAM;AAAA,EAC/B,CAAC;AACH;AAKA,SAAS,gBAAgB,UAAwB;AAC/C,MAAI;AACF,QAAO,gBAAW,QAAQ,KAAK,SAAS,SAAS,WAAW,CAAC,GAAG;AAC9D,MAAG,gBAAW,QAAQ;AAAA,IACxB;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAKO,IAAM,iBAA+B;AAAA,EAC1C,gBAAgB,WAAmB,UAA2B;AAC5D,WAAO,WAAW;AAAA,EACpB;AAAA,EAEA,YAAY;AAAA,EAEZ,SAAS;AACX;;;ADlVA,IAAM,EAAE,QAAAC,QAAO,IAAI,oBAAAC;AACnB,IAAM,EAAE,MAAAC,MAAK,IAAI,aAAAC;AACjB,IAAM,EAAE,cAAAC,cAAa,IAAI,qBAAAC;AACzB,IAAM,EAAE,OAAAC,OAAM,IAAI,qBAAAC;AAIX,IAAMC,WAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA,IACV,MAAM,CAAC,8BAA8B,wBAAwB;AAAA,IAC7D,gBAAgB,CAAC,YAAY,UAAU;AAAA,EACzC;AACF;AAsDA,SAASC,mBAAkB,SAAiB,KAA4B;AACtE,QAAM,YAAY,IAAI,GAAG;AACzB,QAAM,WAAW,QAAQ,QAAQ,SAAS;AAC1C,MAAI,aAAa,GAAI,QAAO;AAE5B,MAAI,IAAI,WAAW,UAAU;AAC7B,SAAO,IAAI,QAAQ,UAAU,KAAK,KAAK,QAAQ,CAAC,CAAC,EAAG;AAEpD,MAAI,QAAQ,CAAC,MAAM,IAAK,QAAO;AAE/B,MAAI,aAAa;AACjB,MAAI,WAAW;AACf,MAAI,SAAS;AACb,QAAM,WAAW;AAEjB,SAAO,IAAI,QAAQ,QAAQ,KAAK;AAC9B,UAAM,OAAO,QAAQ,CAAC;AAEtB,QAAI,QAAQ;AACV,eAAS;AACT;AAAA,IACF;AAEA,QAAI,SAAS,QAAQ,UAAU;AAC7B,eAAS;AACT;AAAA,IACF;AAEA,QAAI,SAAS,KAAK;AAChB,iBAAW,CAAC;AACZ;AAAA,IACF;AAEA,QAAI,CAAC,UAAU;AACb,UAAI,SAAS,IAAK;AAClB,UAAI,SAAS,KAAK;AAChB;AACA,YAAI,eAAe,GAAG;AACpB,iBAAO,QAAQ,MAAM,UAAU,IAAI,CAAC;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,gBAAS,QAAQ;AACvC,SAAOA,WAAS,QAAQ,YAAY,EAAE,KAAK;AAC7C;AAKA,SAAS,aAAa,SAAyB;AAC7C,QAAM,eAAe,QAAQ,QAAQ,WAAW;AAChD,MAAI,iBAAiB,GAAI,QAAO;AAEhC,QAAM,aAAa,QAAQ,QAAQ,KAAK,YAAY;AACpD,MAAI,eAAe,MAAM,aAAa,eAAe,GAAI,QAAO;AAGhE,MAAI,QAAQ;AACZ,MAAI,IAAI,aAAa;AACrB,SAAO,IAAI,QAAQ,UAAU,QAAQ,GAAG;AACtC,QAAI,QAAQ,CAAC,MAAM,IAAK;AAAA,aACf,QAAQ,CAAC,MAAM,IAAK;AAC7B;AAAA,EACF;AAEA,MAAI,UAAU,EAAG,QAAO;AAExB,QAAM,iBAAiB,QAAQ,MAAM,aAAa,GAAG,IAAI,CAAC;AAC1D,QAAM,aAAa,eAAe,MAAM,YAAY;AACpD,SAAO,aAAa,WAAW,SAAS;AAC1C;AAIA,SAAS,mBACP,aACA,SACA,YACa;AACb,MAAI,WAAY;AAGhB,MAAI,QAAQ,WAAW,QAAQ;AAC7B,UAAM,eAAe,QAAQ,UAAU,CAAC,EAAE;AAC1C,YAAQ,cAAc;AAAA,MACpB,KAAK;AACH;AAAA,MACF,KAAK;AACH;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH;AAAA,MACF,KAAK;AACH;AAAA,MACF,KAAK;AACH;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,QAAQ,QAAQ,OAAQ;AAG5B,QAAM,OAAO,QAAQ,MAAM,KAAK,KAAK;AACrC,MAAI,KAAK,SAAS,gBAAM,KAAK,KAAK,SAAS,gCAAO,KAAK,SAAS,iBAAQ;AACxE,MAAI,KAAK,SAAS,cAAI,KAAK,SAAS,iBAAQ;AAC5C,MAAI,KAAK,SAAS,oBAAK,KAAK,KAAK,SAAS,oBAAK,KAAK,SAAS,uBAAS;AACtE,MAAI,KAAK,SAAS,0BAAM,KAAK,KAAK,SAAS,0BAAM,KAAK,KAAK,SAAS,0BAAM,EAAG;AAC7E,MAAI,SAAS,oBAAU,SAAS,oBAAU,SAAS,uBAAS;AAC5D,MAAI,SAAS,oBAAU,SAAS,6BAAU;AAC1C,MAAI,SAAS,oBAAU,SAAS,6BAAU;AAC1C,MAAI,SAAS,oBAAU,SAAS,6BAAU;AAG1C,MAAI,gBAAgB,EAAG;AACvB,MAAI,gBAAgB,EAAG;AACvB,MAAI,gBAAgB,EAAG;AAEvB;AACF;AAIA,gBAAgB,QAAQC,UAAkE;AACxF,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AACxB,MAAI,kBAAkB;AAGtB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAG5B,UAAQ,QAAQ,8EAAiC,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAG1F,QAAM,cAAc,kBAAkB,UAAU,GAAM;AAGtD,MAAI,WAAqB,EAAE,MAAM,4BAAQ,MAAM,QAAQ;AACvD,MAAI;AACF,UAAM,cAAcH,mBAAkB,aAAa,UAAU;AAC7D,QAAI,aAAa;AACf,iBAAW,KAAK,MAAM,WAAW;AAAA,IACnC;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,QAAM,eAAe,aAAa,WAAW;AAC7C,QAAM,WAAW,eAAe,0BAAqB,eAAe;AAGpE,QAAM,OAAmB;AAAA,IACvB,MAAM,SAAS,QAAQC,yBAAwB,QAAQ;AAAA,IACvD,UAAU,gBAAgB;AAAA,IAC1B,MAAM;AAAA,IACN,aAAa,SAAS;AAAA;AAAA,EACxB;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,YAAY,oBAAI,IAAwB;AAC9C,QAAM,eAAgC,CAAC;AAGvC,QAAM,IAAI,QAAc,CAACG,UAAS,WAAW;AAC3C,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAEtE,eAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,mBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,IAC5E,CAAC;AAED,UAAMC,YAAWR,OAAM,CAAC,YAAYN,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAElG,IAAAU,UAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAA4B;AAEvD,YAAM,aACJ,MAAM,OAAO,OAAO,MAAM,OAAO,OAAO,MAAM,YAAY,aAAa,MAAM,YAAY;AAC3F,UAAI,CAAC,YAAY;AACf;AACA;AAAA,MACF;AAGA,YAAM,MAAM,MAAM;AAClB,YAAM,cAAc,KAAK,gBAAgB,MAAM,OAAO,QAAQ;AAC9D,YAAM,gBAAgB,KAAK,kBAAkB;AAG7C,YAAM,iBAAiB,UAAU,IAAI,UAAU;AAC/C,UAAI,CAAC,gBAAgB;AACnB,kBAAU,IAAI,YAAY,EAAE,YAAY,aAAa,eAAe,QAAQ,OAAU,CAAC;AAAA,MACzF,OAAO;AACL,uBAAe,cAAc;AAC7B,YAAI,cAAe,gBAAe,gBAAgB;AAAA,MACpD;AAGA,YAAM,YAAY,eAAe,MAAM,SAAS;AAChD,UAAI,cAAc,QAAQ,CAAC,YAAY,SAAS,GAAG;AACjD;AACA;AAAA,MACF;AAGA,YAAM,OAAO,MAAM,oCAEf,mBAAmB,MAAM,aAAa,MAAM,SAAS,MAAM,UAAU;AAGzE,UAAI,cAAc,MAAM,SAAS,QAAQ;AACzC,UAAI,MAAM,WAAY,eAAc,0BAAW;AAE/C,mBAAa,KAAK;AAAA,QAChB,mBAAmB,MAAM;AAAA;AAAA,QACzB,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,QACnB,qBAAqB;AAAA,QACrB;AAAA,QACA;AAAA,QACA,SAAS,eAAe;AAAA,QACxB,kBAAkB,MAAM,SAAS,OAAO;AAAA;AAAA,MAC1C,CAAC;AAED;AACA,UAAI,oBAAoB,cAAc,GAAG;AACvC,cAAM,WAAW;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,sBAAO,iBAAiB;AAAA,QAC1B;AACA,qBAAa,QAAQ;AAAA,MACvB;AAAA,IACF,CAAC;AAED,IAAAA,UAAS,GAAG,OAAOD,QAAO;AAC1B,IAAAC,UAAS,GAAG,SAAS,MAAM;AAAA,EAC7B,CAAC;AAGD,QAAM,aAAa,oBAAI,IAAoB;AAC3C,MAAI;AAEF,UAAM,QAAW,cAAS,QAAQ;AAClC,UAAM,WAAW,KAAK,IAAI,MAAM,MAAM,GAAO;AAC7C,UAAM,KAAQ,cAAS,UAAU,GAAG;AACpC,UAAM,SAAS,OAAO,MAAM,QAAQ;AACpC,IAAG,cAAS,IAAI,QAAQ,GAAG,UAAU,MAAM,OAAO,QAAQ;AAC1D,IAAG,eAAU,EAAE;AAEf,UAAM,cAAc,OAAO,SAAS,OAAO;AAC3C,UAAM,aAAaL,mBAAkB,aAAa,SAAS;AAC3D,QAAI,YAAY;AACd,YAAM,aAAa,KAAK,MAAM,UAAU;AACxC,iBAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQ,UAAU,GAAG;AACtD,YAAI,UAAU,OAAO,WAAW,YAAY,OAAO,WAAW,aAAa,GAAG;AAC5E,qBAAW,IAAI,KAAK,MAAM;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,aAAW,UAAU,UAAU,OAAO,GAAG;AACvC,UAAM,SAAS,WAAW,IAAI,OAAO,UAAU;AAC/C,QAAI,OAAQ,QAAO,SAAS;AAAA,EAC9B;AAGA,QAAM,UAA0B,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,IACzE,YAAY,EAAE;AAAA,IACd,aAAa,EAAE;AAAA,IACf,eAAe,EAAE;AAAA,IACjB,QAAQ,EAAE;AAAA,EACZ,EAAE;AACF,QAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AAGvC,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,WAAW;AACvD,UAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,SAAS;AACjD,UAAM,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,EACxC;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAGzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,UAAU,IAAI,qBAAM;AACvE,MAAI,kBAAkB,GAAG;AACvB,YAAQ,QAAQ,gBAAM,eAAe,iHAAuB;AAAA,EAC9D;AAEA,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK;AAAA,EACvE;AACF;AAIO,IAAMM,WAAkB;AAAA,EAC7B,SAAAP;AAAA,EACA,OAAO;AACT;AAKA,IAAMQ,WAAwB;AAAA,EAC5B,SAAAC;AAAA,EACA,QAAQC;AAAA,EACR,cAAc;AAChB;AAEA,IAAO,+BAAQF;;;AElaf,YAAYG,UAAQ;AACpB,YAAYC,YAAU;AACtB,YAAYC,eAAc;AAgBnB,IAAMC,WAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA;AAAA,IAEV,MAAM,CAAC,gCAAgC;AAAA,IACvC,gBAAgB,CAAC,YAAY,UAAU;AAAA,EACzC;AACF;AAiGA,SAASC,oBAAmB,SAAiB,SAAoC,YAAmC;AAClH,MAAI,WAAY;AAGhB,MAAI,YAAY,SAAU;AAG1B,MAAI,QAAQ,WAAW,QAAQ;AAC7B,UAAM,eAAe,QAAQ,UAAU,CAAC,EAAE;AAC1C,YAAQ,cAAc;AAAA,MACpB,KAAK;AACH;AAAA,MACF,KAAK;AACH;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH;AAAA,MACF,KAAK;AACH;AAAA,MACF,KAAK;AACH;AAAA,IACJ;AAAA,EACF;AAGA,MAAI,QAAQ,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,SAAS,iBAAiB,EAAE,SAAS,YAAY,GAAG;AAC3G;AAAA,EACF;AAGA,QAAM,OAAO,QAAQ,MAAM,KAAK,KAAK;AACrC,MAAI,KAAK,SAAS,gBAAM,KAAK,KAAK,SAAS,gCAAO,KAAK,SAAS,iBAAQ;AACxE,MAAI,KAAK,SAAS,cAAI,KAAK,SAAS,iBAAQ;AAC5C,MAAI,KAAK,SAAS,oBAAK,KAAK,KAAK,SAAS,oBAAK,KAAK,SAAS,uBAAS;AACtE,MAAI,KAAK,SAAS,0BAAM,KAAK,KAAK,SAAS,0BAAM,KAAK,KAAK,SAAS,0BAAM,EAAG;AAC7E,MAAI,SAAS,oBAAU,SAAS,oBAAU,SAAS,uBAAS;AAC5D,MAAI,SAAS,oBAAU,SAAS,6BAAU;AAC1C,MAAI,SAAS,oBAAU,SAAS,6BAAU;AAC1C,MAAI,SAAS,oBAAU,SAAS,6BAAU;AAE1C;AACF;AAOA,SAAS,aAAa,cAAgC;AACpD,QAAM,UAAa,kBAAa,cAAc,OAAO;AACrD,SAAO,KAAK,MAAM,OAAO;AAC3B;AAKA,SAAS,qBAAqB,OAA0B;AAEtD,MAAI,MAAM,aAAc,QAAO,MAAM;AAErC,MAAI,MAAM,KAAM,QAAO,MAAM;AAE7B,MAAI,MAAM,SAAU,QAAO,UAAU,MAAM,QAAQ;AACnD,QAAM,IAAI,MAAM,yDAAiB;AACnC;AAKA,SAAS,qBAAqB,OAA0B;AAEtD,MAAI,MAAM,UAAU,OAAW,QAAO,MAAM;AAE5C,MAAI,MAAM,aAAa,OAAW,QAAO,MAAM;AAC/C,SAAO;AACT;AAKA,SAAS,oBAAoB,UAAoB,SAAyB;AACxE,MAAI,QAAQ;AACZ,aAAW,SAAS,SAAS,QAAQ,QAAQ;AAC3C,UAAM,eAAe,qBAAqB,KAAK;AAC/C,UAAM,YAAiB,YAAK,SAAS,YAAY;AACjD,QAAO,gBAAW,SAAS,GAAG;AAC5B,eAAY,cAAS,SAAS,EAAE;AAAA,IAClC;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,YAAY,SAAiB,aAAoE;AACxG,QAAM,aAAa,oBAAI,IAAoB;AAC3C,MAAI,CAAC,aAAa,KAAM,QAAO;AAE/B,QAAM,cAAmB,YAAK,SAAS,YAAY,IAAI;AACvD,MAAI,CAAI,gBAAW,WAAW,EAAG,QAAO;AAExC,MAAI;AACF,UAAM,UAAa,kBAAa,aAAa,OAAO;AACpD,UAAM,UAAU,KAAK,MAAM,OAAO;AAClC,eAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,UAAI,UAAU,OAAO,WAAW,YAAY,OAAO,WAAW,aAAa,GAAG;AAC5E,mBAAW,IAAI,KAAK,MAAM;AAAA,MAC5B;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAKA,gBAAgB,cACd,UACA,cACgC;AAChC,MAAI,aAAmC;AACvC,MAAI,KAAgC;AACpC,MAAI,aAAa;AACjB,MAAI,kBAAkB;AACtB,QAAM,0BAA0B;AAEhC,MAAI;AACF,iBAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAChE,SAAc,0BAAgB;AAAA,MAC5B,OAAO;AAAA,MACP,WAAW;AAAA,IACb,CAAC;AAED,qBAAiB,QAAQ,IAAI;AAC3B;AACA,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,QAAS;AAEd,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AAAA,MAC1B,SAAS,OAAO;AACd;AAEA,YAAI,mBAAmB,2BAA2B,cAAc;AAC9D,gBAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,uBAAa,YAAY,QAAQ;AAAA,QACnC;AAAA,MAEF;AAAA,IACF;AAAA,EACF,UAAE;AAEA,QAAI,IAAI;AACN,SAAG,MAAM;AAAA,IACX;AACA,QAAI,YAAY;AACd,iBAAW,QAAQ;AAAA,IACrB;AAAA,EACF;AACF;AAIA,gBAAgB,kBAAkBC,UAAkE;AAClG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAG1D,QAAM,eAAe;AACrB,QAAM,UAAe,eAAQ,YAAY;AAEzC,UAAQ,QAAQ,sCAAkB,YAAY,EAAE;AAChD,UAAQ,QAAQ,6BAAS,OAAO,EAAE;AAGlC,MAAI;AACJ,MAAI;AACF,eAAW,aAAa,YAAY;AACpC,YAAQ,QAAQ,mCAAe;AAAA,EACjC,SAAS,OAAO;AACd,UAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,YAAQ,SAAS,2CAAuB,QAAQ,EAAE;AAClD,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,2CAAuB,QAAQ,EAAE,EAAE;AAC1E;AAAA,EACF;AAGA,MAAI,SAAS,SAAS,WAAW,iBAAiB;AAChD,YAAQ,SAAS,yCAAW,SAAS,SAAS,MAAM,EAAE;AACtD,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,yCAAW,SAAS,SAAS,MAAM,EAAE,EAAE;AAC9E;AAAA,EACF;AAEA,QAAM,aAAa,oBAAoB,UAAU,OAAO;AACxD,MAAI,YAAY;AAChB,MAAI,oBAAoB;AACxB,MAAI,kBAAkB;AACtB,MAAI,cAAc;AAGlB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAE5B;AAAA,IACE;AAAA,IACA,yDAA2B,SAAS,SAAS,OAAO,iBAAO,SAAS,QAAQ,OAAO,MAAM,yCAAW,SAAS,WAAW,aAAa,qDAAa,KAAK,MAAM,aAAa,OAAO,IAAI,CAAC;AAAA,EACxL;AAGA,MAAI;AACJ,MAAI;AACF,iBAAa,YAAY,SAAS,SAAS,OAAO;AAClD,QAAI,WAAW,OAAO,GAAG;AACvB,cAAQ,QAAQ,sBAAO,WAAW,IAAI,iCAAQ;AAAA,IAChD;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,SAAS,qDAAa,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AACtF,iBAAa,oBAAI,IAAI;AAAA,EACvB;AAGA,QAAM,WAAW,SAAS,SAAS,SAAS;AAC5C,QAAM,OAAmB;AAAA,IACvB,MAAM,SAAS,SAAS,QAAQ;AAAA,IAChC,UAAU,gBAAgB;AAAA,IAC1B,MAAM;AAAA,IACN,SAAS,SAAS,SAAS,WAAW,SAAS,SAAS;AAAA,EAC1D;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AACjC,UAAQ,QAAQ,2BAAiB,KAAK,IAAI,UAAU,KAAK,IAAI,aAAa,KAAK,OAAO,EAAE;AAGxF,QAAM,YAAY,oBAAI,IAAwB;AAG9C,MAAI,eAAgC,CAAC;AAGrC,WAAS,aAAa,GAAG,aAAa,SAAS,QAAQ,OAAO,QAAQ,cAAc;AAClF,UAAM,YAAY,SAAS,QAAQ,OAAO,UAAU;AACpD,UAAM,eAAe,qBAAqB,SAAS;AACnD,UAAM,YAAiB,YAAK,SAAS,YAAY;AACjD,UAAM,oBAAoB,qBAAqB,SAAS;AAExD,QAAI,CAAI,gBAAW,SAAS,GAAG;AAC7B,cAAQ,SAAS,+CAAY,SAAS,EAAE;AACxC;AAAA,IACF;AAEA,UAAM,YAAe,cAAS,SAAS,EAAE;AACzC,QAAI,oBAAoB;AACxB,QAAI,mBAAmB;AAEvB;AAAA,MACE;AAAA,MACA,yCAAW,aAAa,CAAC,IAAI,SAAS,QAAQ,OAAO,MAAM,MAAM,YAAY,kBAAQ,iBAAiB,wBAAS,KAAK,MAAM,YAAY,IAAI,CAAC;AAAA,IAC7I;AAGA,QAAI;AACF,YAAM,qBAAqB,CAAC,YAAoB,aAAqB;AACnE,gBAAQ,SAAS,gBAAM,YAAY,WAAM,UAAU,0CAAiB,QAAQ,EAAE;AAAA,MAChF;AAEA,uBAAiB,OAAO,cAAc,WAAW,kBAAkB,GAAG;AACpE;AAGA,cAAM,aAAa,IAAI,QAAQ,OAAO,IAAI,QAAQ;AAClD,YAAI,CAAC,cAAc,eAAe,OAAO,eAAe,gBAAM;AAC5D;AACA;AAAA,QACF;AAMA,cAAM,cAAc,IAAI,QAAQ,YAAY,IAAI,QAAQ,QAAQ;AAChE,cAAM,gBAAgB,IAAI,QAAQ,aAAa;AAG/C,cAAM,iBAAiB,UAAU,IAAI,UAAU;AAC/C,YAAI,CAAC,gBAAgB;AACnB,oBAAU,IAAI,YAAY;AAAA,YACxB;AAAA,YACA;AAAA,YACA;AAAA,YACA,QAAQ,WAAW,IAAI,UAAU;AAAA,UACnC,CAAC;AAAA,QACH,OAAO;AACL,yBAAe,cAAc;AAC7B,cAAI,cAAe,gBAAe,gBAAgB;AAClD,cAAI,CAAC,eAAe,OAAQ,gBAAe,SAAS,WAAW,IAAI,UAAU;AAAA,QAC/E;AAGA,cAAM,YACJ,OAAO,IAAI,cAAc,WAAW,KAAK,MAAM,IAAI,YAAY,GAAI,IAAI,eAAe,IAAI,SAAS;AAErG,YAAI,cAAc,QAAQ,CAAC,YAAY,SAAS,GAAG;AACjD;AACA;AAAA,QACF;AAGA,cAAM,OAAO,IAAI,2BAA8BD,oBAAmB,IAAI,MAAM,IAAI,SAAS,IAAI,QAAQ;AAGrG,YAAI,cAAc,IAAI,SAAS,QAAQ;AACvC,YAAI,IAAI,SAAU,eAAc,0BAAW;AAG3C,qBAAa,KAAK;AAAA,UAChB,mBAAmB,IAAI;AAAA,UACvB,kBAAkB;AAAA,UAClB,mBAAmB;AAAA,UACnB,qBAAqB;AAAA,UACrB;AAAA,UACA;AAAA,UACA,SAAS,eAAe;AAAA,QAC1B,CAAC;AAED;AAGA,YAAI,aAAa,UAAU,WAAW;AACpC,gBAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,yBAAe,CAAC;AAGhB,gBAAM,gBAAgB,oBAAoB,IAAI,oBAAoB,oBAAoB;AACtF,gBAAM,iBAAiB,KAAK,MAAM,gBAAgB,SAAS;AAC3D,gBAAM,mBAAmB,YAAY;AACrC,gBAAM,WAAW;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,sBAAO,iBAAiB;AAAA,UAC1B;AACA,gBAAM,EAAE,MAAM,YAAY,MAAM,SAAS;AACzC,uBAAa,QAAQ;AAAA,QACvB;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,YAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,cAAQ,SAAS,4BAAQ,YAAY,oCAAW,QAAQ,EAAE;AAC1D;AACA;AAAA,IAEF;AAGA,iBAAa;AAGb;AAAA,MACE;AAAA,MACA,gBAAM,YAAY,2CAAa,iBAAiB,yBAAU,iBAAiB,yBAAU,eAAe,UAAK,mBAAmB,IAAI,kBAAQ,gBAAgB,YAAO,EAAE;AAAA,IACnK;AAAA,EACF;AAGA,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,YAAQ,QAAQ,qDAAa,aAAa,MAAM,SAAI;AAAA,EACtD;AAGA,QAAM,UAA0B,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,IACzE,YAAY,EAAE;AAAA,IACd,aAAa,EAAE;AAAA,IACf,eAAe,EAAE;AAAA,IACjB,QAAQ,EAAE;AAAA,EACZ,EAAE;AACF,QAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AACvC,UAAQ,QAAQ,yCAAW,QAAQ,MAAM,qBAAM;AAG/C,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAEzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,UAAU,IAAI,qBAAM;AACvE,MAAI,kBAAkB,GAAG;AACvB,YAAQ,QAAQ,gBAAM,eAAe,iHAAuB;AAAA,EAC9D;AACA,MAAI,cAAc,GAAG;AACnB,YAAQ,SAAS,8CAAW,WAAW,qBAAM;AAAA,EAC/C;AAEA,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK;AAAA,EACvE;AACF;AAIO,IAAME,WAAkB;AAAA,EAC7B,SAAAH;AAAA,EACA,OAAO;AACT;AAEA,IAAMI,WAAwB;AAAA,EAC5B,SAAAJ;AAAA,EACA,QAAQG;AACV;AAEA,IAAO,uCAAQC;;;ACliBf,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAC/B,IAAAC,uBAAwB;AALxB,YAAYC,UAAQ;AACpB,YAAYC,YAAU;;;ACCf,IAAM,qBAAmC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM9C,gBAAgB,WAAmB,WAA4B;AAI7D,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WAAW,UAAkB,aAAkE;AAMnG,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,WAAyB;AAAA,EAKjC;AACF;;;ADlBA,IAAM,EAAE,QAAAC,QAAO,IAAI,oBAAAC;AACnB,IAAM,EAAE,MAAAC,MAAK,IAAI,aAAAC;AACjB,IAAM,EAAE,cAAAC,cAAa,IAAI,qBAAAC;AACzB,IAAM,EAAE,OAAAC,OAAM,IAAI,qBAAAC;AAOlB,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,gBAAS,QAAQ;AACvC,QAAM,OAAOA,WAAS,QAAQ,YAAY,EAAE;AAC5C,SAAO,QAAQ;AACjB;AAIO,IAAMC,WAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA;AAAA;AAAA;AAAA,IAIV,MAAM,CAAC,mBAAmB;AAAA,IAC1B,gBAAgB,CAAC,UAAU,SAAS;AAAA,EACtC;AACF;AAkCA,SAASC,oBAAmB,SAA8B;AACxD,UAAQ,SAAS;AAAA,IACf,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF;AAEE;AAAA,EACJ;AACF;AAgBA,gBAAgB,YAAYC,UAAkE;AAC5F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AAGxB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAG5B,UAAQ,QAAQ,gFAAyB,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGlF,QAAM,cAAc,kBAAkB,UAAU,GAAI;AAGpD,MAAI,UAAgC;AACpC,MAAI;AACF,UAAM,IAAI,QAAc,CAACC,aAAY;AACnC,YAAM,gBAAmB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAEzE,UAAI,iBAAiB;AACrB,UAAI,YAAY;AAChB,UAAI,aAAa;AACjB,UAAI,WAAW;AACf,UAAI,SAAS;AAEb,oBAAc,GAAG,QAAQ,CAAC,UAA2B;AACnD,cAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,MAAM,SAAS;AAE/D,iBAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,gBAAM,OAAO,IAAI,CAAC;AAElB,cAAI,CAAC,WAAW;AAEd,kBAAM,YAAY;AAClB,gBAAI,IAAI,MAAM,GAAG,IAAI,UAAU,MAAM,MAAM,WAAW;AACpD,0BAAY;AACZ,mBAAK,UAAU,SAAS;AACxB;AAAA,YACF;AAAA,UACF,OAAO;AACL,8BAAkB;AAElB,gBAAI,QAAQ;AACV,uBAAS;AACT;AAAA,YACF;AAEA,gBAAI,SAAS,QAAQ,UAAU;AAC7B,uBAAS;AACT;AAAA,YACF;AAEA,gBAAI,SAAS,KAAK;AAChB,yBAAW,CAAC;AACZ;AAAA,YACF;AAEA,gBAAI,CAAC,UAAU;AACb,kBAAI,SAAS,IAAK;AAClB,kBAAI,SAAS,KAAK;AAChB;AACA,oBAAI,eAAe,GAAG;AACpB,gCAAc,QAAQ;AACtB;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAED,oBAAc,GAAG,SAAS,MAAM;AAC9B,YAAI,gBAAgB;AAClB,cAAI;AACF,sBAAU,KAAK,MAAM,cAAc;AAAA,UACrC,QAAQ;AAAA,UAER;AAAA,QACF;AACA,QAAAA,SAAQ;AAAA,MACV,CAAC;AAED,oBAAc,GAAG,SAAS,MAAMA,SAAQ,CAAC;AAAA,IAC3C,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AAKA,MAAI;AAEJ,QAAM,cAAc;AACpB,MAAI,aAAa;AACf,QAAI,YAAY,SAAS,gBAAM;AAC7B;AAAA,IACF,WAAW,YAAY,SAAS,gBAAM;AACpC;AAAA,IACF,WAAW,YAAY,QAAQ,CAAC,YAAY,KAAK,SAAS,WAAW,GAAG;AACtE;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,aAAa,eAAe,aAAa,YAAYL,yBAAwB,QAAQ;AAItG,QAAM,UAAU,oCAA+B,aAAa,OAAO,YAAY,OAAO;AAKtF,QAAM,aAAa,oBAAI,IAAoB;AAM3C,WAAS,qBAAqB,SAAgC;AAC5D,UAAM,YAAY;AAClB,UAAM,WAAW,QAAQ,QAAQ,SAAS;AAC1C,QAAI,aAAa,GAAI,QAAO;AAE5B,QAAI,IAAI,WAAW,UAAU;AAE7B,WAAO,IAAI,QAAQ,UAAU,KAAK,KAAK,QAAQ,CAAC,CAAC,EAAG;AAEpD,QAAI,QAAQ,CAAC,MAAM,IAAK,QAAO;AAG/B,QAAI,aAAa;AACjB,QAAI,WAAW;AACf,QAAI,SAAS;AACb,UAAM,WAAW;AAEjB,WAAO,IAAI,QAAQ,QAAQ,KAAK;AAC9B,YAAM,OAAO,QAAQ,CAAC;AAEtB,UAAI,QAAQ;AACV,iBAAS;AACT;AAAA,MACF;AAEA,UAAI,SAAS,QAAQ,UAAU;AAC7B,iBAAS;AACT;AAAA,MACF;AAEA,UAAI,SAAS,KAAK;AAChB,mBAAW,CAAC;AACZ;AAAA,MACF;AAEA,UAAI,CAAC,UAAU;AACb,YAAI,SAAS,IAAK;AAClB,YAAI,SAAS,KAAK;AAChB;AACA,cAAI,eAAe,GAAG;AACpB,mBAAO,QAAQ,MAAM,UAAU,IAAI,CAAC;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,MAAI;AAEF,UAAM,iBAAiB,qBAAqB,WAAW;AACvD,QAAI,gBAAgB;AAElB,YAAM,aAAa,KAAK,MAAM,cAAc;AAC5C,iBAAW,CAAC,MAAM,aAAa,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC9D,YAAI,iBAAiB,OAAO,kBAAkB,UAAU;AACtD,qBAAW,IAAI,MAAM,aAAa;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI,WAAW,SAAS,GAAG;AACzB,QAAI;AACF,YAAM,IAAI,QAAc,CAACK,aAAY;AACnC,cAAM,eAAkB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAExE,YAAI,iBAAiB;AACrB,YAAI,YAAY;AAChB,YAAI,aAAa;AACjB,YAAI,WAAW;AACf,YAAI,SAAS;AAEb,qBAAa,GAAG,QAAQ,CAAC,UAA2B;AAClD,gBAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,MAAM,SAAS;AAE/D,mBAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,kBAAM,OAAO,IAAI,CAAC;AAElB,gBAAI,CAAC,WAAW;AAEd,oBAAM,YAAY;AAClB,kBAAI,IAAI,MAAM,GAAG,IAAI,UAAU,MAAM,MAAM,WAAW;AACpD,4BAAY;AAEZ,qBAAK,UAAU,SAAS;AACxB;AAAA,cACF;AAAA,YACF,OAAO;AAEL,gCAAkB;AAElB,kBAAI,QAAQ;AACV,yBAAS;AACT;AAAA,cACF;AAEA,kBAAI,SAAS,QAAQ,UAAU;AAC7B,yBAAS;AACT;AAAA,cACF;AAEA,kBAAI,SAAS,KAAK;AAChB,2BAAW,CAAC;AACZ;AAAA,cACF;AAEA,kBAAI,CAAC,UAAU;AACb,oBAAI,SAAS,IAAK;AAClB,oBAAI,SAAS,KAAK;AAChB;AACA,sBAAI,eAAe,GAAG;AAEpB,iCAAa,QAAQ;AACrB;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AAED,qBAAa,GAAG,SAAS,MAAM;AAC7B,cAAI,gBAAgB;AAClB,gBAAI;AAEF,oBAAM,aAAa,KAAK,MAAM,cAAc;AAC5C,yBAAW,CAAC,MAAM,aAAa,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC9D,oBAAI,iBAAiB,OAAO,kBAAkB,UAAU;AACtD,6BAAW,IAAI,MAAM,aAAa;AAAA,gBACpC;AAAA,cACF;AAAA,YACF,QAAQ;AAAA,YAER;AAAA,UACF;AACA,UAAAA,SAAQ;AAAA,QACV,CAAC;AAED,qBAAa,GAAG,SAAS,MAAMA,SAAQ,CAAC;AAAA,MAC1C,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,cAAc,aAAa,WAAW,UAAU,WAAW,IAAI,OAAO,IAAI;AAGhF,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,QAAc,CAACA,aAAY;AACnC,YAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AACtE,YAAM,eAAeP,OAAM,CAAC,YAAYN,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAEtG,mBAAa,GAAG,QAAQ,CAAC,EAAE,MAAM,MAAgC;AAC/D,YAAI,MAAM,WAAW,KAAK,MAAM,kBAAkB,CAAC,MAAM,eAAe,SAAS,WAAW,GAAG;AAC7F,oBAAU,MAAM;AAChB,qBAAW,QAAQ;AAAA,QACrB;AAAA,MACF,CAAC;AAED,iBAAW,GAAG,SAAS,MAAMS,SAAQ,CAAC;AACtC,mBAAa,GAAG,OAAO,MAAMA,SAAQ,CAAC;AACtC,mBAAa,GAAG,SAAS,MAAMA,SAAQ,CAAC;AAAA,IAC1C,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AAGA,QAAM,OAAmB;AAAA,IACvB,MAAM;AAAA,IACN,UAAU,gBAAgB;AAAA,IAC1B,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,YAAY,oBAAI,IAAwB;AAC9C,QAAM,eAAgC,CAAC;AAGvC,QAAM,IAAI,QAAc,CAACA,UAAS,WAAW;AAC3C,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAEtE,eAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,mBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,IAC5E,CAAC;AAED,UAAMC,YAAWR,OAAM,CAAC,YAAYN,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAElG,UAAM,iBAAiB,CAAC,QAA6C;AAEnE,UAAI,CAAC,IAAI,kBAAkB,IAAI,eAAe,QAAW;AACvD,eAAO;AAAA,MACT;AAEA,YAAM,aAAa,IAAI;AAIvB,UAAI,WAAW,SAAS,WAAW,GAAG;AACpC,eAAO;AAAA,MACT;AAEA,YAAM,cAAc,IAAI,qBAAqB;AAG7C,YAAM,YAAY,IAAI,mBAAmB,IAAI;AAC7C,YAAM,SAAS,WAAW,IAAI,SAAS;AAGvC,UAAI,CAAC,UAAU,IAAI,UAAU,GAAG;AAC9B,kBAAU,IAAI,YAAY;AAAA,UACxB;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AAEL,cAAM,WAAW,UAAU,IAAI,UAAU;AACzC,iBAAS,cAAc;AAEvB,YAAI,QAAQ;AACV,mBAAS,SAAS;AAAA,QACpB;AAAA,MACF;AAGA,YAAM,OAAOO,oBAAmB,IAAI,IAAI;AAIxC,UAAI,UAAyB;AAC7B,UAAI,IAAI,WAAW,MAAM;AACvB,cAAM,aAAa,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,KAAK,UAAU,IAAI,OAAO;AAC7F,kBAAU,WAAW,KAAK,KAAK;AAAA,MACjC;AAEA,aAAO;AAAA,QACL,mBAAmB,OAAO,IAAI,OAAO;AAAA;AAAA,QACrC,kBAAkB;AAAA,QAClB,mBAAmB;AAAA;AAAA,QAEnB,qBAAqB;AAAA,QACrB,WAAW,IAAI;AAAA,QACf;AAAA,QACA;AAAA;AAAA,MAEF;AAAA,IACF;AAGA,UAAM,iBAAkC,CAAC;AAEzC,IAAAG,UAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAAgC;AAC3D,YAAM,SAAS,eAAe,KAAK;AACnC,UAAI,QAAQ;AACV,uBAAe,KAAK,MAAM;AAC1B;AAGA,YAAI,eAAe,UAAU,WAAW;AACtC,uBAAa,KAAK,GAAG,cAAc;AACnC,yBAAe,SAAS;AAExB,gBAAM,WAAW;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,sBAAO,iBAAiB;AAAA,UAC1B;AACA,uBAAa,QAAQ;AAAA,QACvB;AAAA,MACF;AAAA,IACF,CAAC;AAED,IAAAA,UAAS,GAAG,OAAO,MAAM;AAEvB,UAAI,eAAe,SAAS,GAAG;AAC7B,qBAAa,KAAK,GAAG,cAAc;AAAA,MACrC;AACA,MAAAD,SAAQ;AAAA,IACV,CAAC;AAED,IAAAC,UAAS,GAAG,SAAS,MAAM;AAAA,EAC7B,CAAC;AAGD,QAAM,UAA0B,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,IACzE,YAAY,EAAE;AAAA,IACd,aAAa,EAAE;AAAA,IACf,QAAQ,EAAE;AAAA,EACZ,EAAE;AACF,QAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AAGvC,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,WAAW;AACvD,UAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,SAAS;AACjD,UAAM,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,EACxC;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAGzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,UAAU,IAAI,qBAAM;AAEvE,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK;AAAA,EACvE;AACF;AAOO,IAAMC,WAAkB;AAAA,EAC7B,SAAAC;AAAA,EACA,OAAO;AACT;AASA,IAAMC,WAAwB;AAAA,EAC5B,SAAAC;AAAA,EACA,QAAQC;AAAA,EACR,cAAc;AAChB;AAEA,IAAO,iBAAQF;;;AEhmBR,IAAMG,WAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA;AAAA,IAEV,MAAM,CAAC,iBAAiB,wBAAwB,yBAAyB;AAAA,IACzE,gBAAgB,CAAC,WAAW,UAAU;AAAA,EACxC;AACF;AAKO,IAAMC,WAAkB;AAAA,EAC7B,SAAAD;AAAA,EACA,OAAO;AACT;AAQA,IAAME,WAAwB;AAAA,EAC5B,SAAAC;AAAA,EACA,QAAQC;AAAA,EACR,cAAc;AAChB;AAEA,IAAO,8BAAQF;;;AC3Cf,IAAAG,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAC/B,IAAAC,uBAAwB;AAJxB,YAAYC,UAAQ;AAkBpB,IAAM,EAAE,QAAAC,QAAO,IAAI,oBAAAC;AACnB,IAAM,EAAE,MAAAC,MAAK,IAAI,aAAAC;AACjB,IAAM,EAAE,cAAAC,cAAa,IAAI,qBAAAC;AACzB,IAAM,EAAE,OAAAC,OAAM,IAAI,qBAAAC;AAkFlB,SAAS,sBAAsB,MAA2B;AACxD,UAAQ,MAAM;AAAA;AAAA,IAEZ,KAAK;AAAA,IACL,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA;AAAA,IAEF,KAAK;AACH;AAAA;AAAA,IAEF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH;AAAA,IACF;AACE;AAAA,EACJ;AACF;AAKA,SAAS,6BAA6B,YAA4C;AAChF,QAAM,WAAW,WAAW,SAAS,YAAY;AAGjD,MAAI,sCAAsC,KAAK,QAAQ,GAAG;AACxD;AAAA,EACF;AAEA,MAAI,6BAA6B,KAAK,QAAQ,GAAG;AAC/C;AAAA,EACF;AAEA,MAAI,6BAA6B,KAAK,QAAQ,GAAG;AAC/C;AAAA,EACF;AAEA;AACF;AASA,IAAM,oBAAoB;AAAA,EACxB,OAAO,CAAC,aAAqB,WAAW,QAAQ;AAAA,EAChD,OAAO,CAAC,aAAqB,WAAW,QAAQ;AAAA,EAChD,OAAO,CAAC,aAAqB,WAAW,QAAQ;AAAA,EAChD,MAAM,CAAC,aAAqB,UAAU,QAAQ;AAAA,EAC9C,SAAS,CAAC,SAAiB,aAAa,IAAI;AAAA,EAC5C,OAAO,CAAC,UAAkB,UAAU,KAAK;AAC3C;AAKA,SAAS,iBAAiB,YAAuC;AAC/D,QAAM,WAAW,WAAW,SAAS,YAAY;AAEjD,MAAI,sCAAsC,KAAK,QAAQ,GAAG;AACxD,WAAO,kBAAkB,MAAM,WAAW,QAAQ;AAAA,EACpD;AACA,MAAI,6BAA6B,KAAK,QAAQ,GAAG;AAC/C,WAAO,kBAAkB,MAAM,WAAW,QAAQ;AAAA,EACpD;AACA,MAAI,6BAA6B,KAAK,QAAQ,GAAG;AAC/C,WAAO,kBAAkB,MAAM,WAAW,QAAQ;AAAA,EACpD;AACA,SAAO,kBAAkB,KAAK,WAAW,QAAQ;AACnD;AAKA,SAAS,YAAY,OAA6B;AAChD,QAAM,QAAQ,MAAM,SAAS,MAAM,OAAO,MAAM,aAAa,MAAM,GAAG,EAAE,KAAK;AAC7E,SAAO,kBAAkB,MAAM,KAAK;AACtC;AAKA,SAAS,cAAc,SAAiC;AACtD,SAAO,kBAAkB,QAAQ,QAAQ,IAAI;AAC/C;AAIO,IAAMC,WAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA,IACV,MAAM,CAAC,oBAAoB,oBAAoB;AAAA,IAC/C,gBAAgB,CAAC,SAAS,WAAW,UAAU;AAAA,EACjD;AACF;AAOA,SAASC,gBAAe,WAA2B;AACjD,QAAM,OAAO,IAAI,KAAK,SAAS;AAC/B,SAAO,KAAK,MAAM,KAAK,QAAQ,IAAI,GAAI;AACzC;AAKA,SAAS,aAAa,cAA2C;AAC/D,SAAO,aAAa,IAAI,CAAC,UAAU;AAAA,IACjC,IAAI,KAAK;AAAA,IACT,MAAM,KAAK;AAAA,EACb,EAAE;AACJ;AAIA,gBAAgB,qBAAqBC,UAAkE;AACrG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AAGxB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAE5B,UAAQ,QAAQ,iFAA0B,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGnF,QAAM,cAAc,kBAAkB,UAAU,GAAK;AAGrD,MAAI,QAA6B;AACjC,MAAI,UAAiC;AAErC,MAAI;AACF,UAAM,aAAa,YAAY,MAAM,2BAA2B;AAChE,QAAI,YAAY;AACd,cAAQ,KAAK,MAAM,WAAW,CAAC,CAAC;AAAA,IAClC;AAEA,UAAM,eAAe,YAAY,MAAM,6BAA6B;AACpE,QAAI,cAAc;AAChB,gBAAU,KAAK,MAAM,aAAa,CAAC,CAAC;AAAA,IACtC;AAAA,EACF,SAAS,GAAG;AACV,YAAQ,SAAS,qDAAa,CAAC,EAAE;AAAA,EACnC;AAGA,QAAM,WAAW,SAAS,UAAU,GAAG,MAAM,IAAI,MAAM,QAAQ,IAAI,KAAK,SAAS,QAAQ;AAEzF,QAAM,OAAmB;AAAA,IACvB,MAAM;AAAA,IACN,UAAU,gBAAgB;AAAA,IAC1B;AAAA;AAAA,IACA,SAAS,SAAS;AAAA,IAClB,aAAa,OAAO;AAAA,EACtB;AAEA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AACjC,UAAQ,QAAQ,6BAAS,QAAQ,EAAE;AAGnC,QAAM,YAAY,oBAAI,IAA0B;AAChD,QAAM,eAAgC,CAAC;AAGvC,QAAM,IAAI,QAAc,CAACC,UAAS,WAAW;AAC3C,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAEtE,eAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,mBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,IAC5E,CAAC;AAED,UAAMC,YAAWN,OAAM,CAAC,YAAYN,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAGlG,UAAM,iBAAkC,CAAC;AAEzC,IAAAQ,UAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAAiC;AAC5D,YAAM,MAAM;AACZ,YAAM,SAAS,IAAI;AAGnB,UAAI,CAAC,UAAU,IAAI,OAAO,EAAE,GAAG;AAC7B,kBAAU,IAAI,OAAO,IAAI;AAAA,UACvB,YAAY,OAAO;AAAA,UACnB,aAAa,OAAO;AAAA,UACpB,eAAe,OAAO,YAAY;AAAA,UAClC,QAAQ,OAAO;AAAA,UACf,OAAO,aAAa,OAAO,KAAK;AAAA,QAClC,CAAC;AAAA,MACH,OAAO;AAEL,cAAM,iBAAiB,UAAU,IAAI,OAAO,EAAE;AAC9C,YAAI,OAAO,MAAM,UAAU,eAAe,OAAO,UAAU,IAAI;AAC7D,yBAAe,QAAQ,aAAa,OAAO,KAAK;AAAA,QAClD;AAAA,MACF;AAGA,UAAI,cAAc,sBAAsB,IAAI,IAAI;AAChD,UAAI,UAAU,IAAI,WAAW;AAG7B,UAAI,IAAI,eAAe,IAAI,YAAY,SAAS,GAAG;AAEjD,YAAI,CAAC,WAAW,IAAI,YAAY,WAAW,GAAG;AAC5C,wBAAc,6BAA6B,IAAI,YAAY,CAAC,CAAC;AAAA,QAC/D;AAEA,cAAM,kBAAkB,IAAI,YAAY,IAAI,gBAAgB;AAC5D,kBAAU,UAAU,GAAG,OAAO;AAAA,EAAK,gBAAgB,KAAK,IAAI,CAAC,KAAK,gBAAgB,KAAK,IAAI;AAAA,MAC7F;AAGA,UAAI,IAAI,UAAU,IAAI,OAAO,SAAS,GAAG;AACvC,cAAM,aAAa,IAAI,OAAO,IAAI,WAAW;AAC7C,kBAAU,UAAU,GAAG,OAAO;AAAA,EAAK,WAAW,KAAK,IAAI,CAAC,KAAK,WAAW,KAAK,IAAI;AAEjF,YAAI,CAAC,IAAI,WAAW,CAAC,IAAI,aAAa,UAAU,IAAI,OAAO,SAAS,GAAG;AACrE;AAAA,QACF;AAAA,MACF;AAGA,UAAI,IAAI,YAAY,IAAI,SAAS,SAAS,GAAG;AAC3C,cAAM,eAAe,IAAI,SAAS,IAAI,aAAa;AACnD,kBAAU,UAAU,GAAG,OAAO;AAAA,EAAK,aAAa,KAAK,IAAI,CAAC,KAAK,aAAa,KAAK,IAAI;AAErF,YAAI,CAAC,IAAI,WAAW,CAAC,IAAI,aAAa,UAAU,CAAC,IAAI,QAAQ,QAAQ;AACnE;AAAA,QACF;AAAA,MACF;AAEA,qBAAe,KAAK;AAAA,QAClB,mBAAmB,IAAI;AAAA;AAAA,QACvB,kBAAkB,OAAO;AAAA,QACzB,mBAAmB,OAAO;AAAA,QAC1B,qBAAqB,OAAO,YAAY;AAAA,QACxC,WAAWH,gBAAe,IAAI,SAAS;AAAA,QACvC,MAAM;AAAA,QACN,SAAS,WAAW;AAAA,QACpB,kBAAkB,IAAI,WAAW,aAAa;AAAA,MAChD,CAAC;AAED;AAGA,UAAI,eAAe,UAAU,WAAW;AACtC,qBAAa,KAAK,GAAG,cAAc;AACnC,uBAAe,SAAS;AAExB,cAAM,WAAW;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,sBAAO,iBAAiB;AAAA,QAC1B;AACA,qBAAa,QAAQ;AAAA,MACvB;AAAA,IACF,CAAC;AAED,IAAAG,UAAS,GAAG,OAAO,MAAM;AAEvB,UAAI,eAAe,SAAS,GAAG;AAC7B,qBAAa,KAAK,GAAG,cAAc;AAAA,MACrC;AACA,MAAAD,SAAQ;AAAA,IACV,CAAC;AAED,IAAAC,UAAS,GAAG,SAAS,MAAM;AAAA,EAC7B,CAAC;AAGD,QAAM,UAAU,MAAM,KAAK,UAAU,OAAO,CAAC;AAC7C,QAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AACvC,UAAQ,QAAQ,sBAAO,QAAQ,MAAM,qBAAM;AAG3C,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,WAAW;AACvD,UAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,SAAS;AACjD,UAAM,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,EACxC;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAEzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,QAAQ,MAAM,qBAAM;AAEvE,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,cAAc;AAAA,MACd,aAAa,QAAQ;AAAA,IACvB;AAAA,EACF;AACF;AAIO,IAAMC,WAAkB;AAAA,EAC7B,SAAAL;AAAA,EACA,OAAO;AACT;AAIA,IAAMM,WAAwB;AAAA,EAC5B,SAAAN;AAAA,EACA,QAAQK;AACV;AAEA,IAAO,kCAAQC;;;AClcf,IAAAC,uBAAwB;AACxB,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAJ/B,YAAYC,UAAQ;;;AC8Bb,SAAS,YAAY,cAAgC;AAC1D,UAAQ,cAAc;AAAA,IACpB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH;AAAA,IACF;AACE;AAAA,EACJ;AACF;AAMO,SAAS,YAAY,MAAuE;AACjG,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO;AAEjC,SAAO,KACJ,IAAI,CAAC,SAAS;AACb,QAAI,OAAO,SAAS,SAAU,QAAO;AACrC,QAAI,QAAQ,OAAO,SAAS,YAAY,UAAU,KAAM,QAAO,KAAK;AACpE,WAAO;AAAA,EACT,CAAC,EACA,KAAK,EAAE;AACZ;AAMO,SAAS,kBAAkB,QAAoC;AACpE,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,OAAO,QAAQ,mBAAmB,EAAE;AAC7C;AAKO,SAAS,kBAAkB,KAAmC;AAEnE,MAAI,IAAI,SAAS,UAAW;AAG5B,MAAI,IAAI,eAAe,UAAW;AAElC,MAAI,IAAI,MAAO;AAEf,MAAI,IAAI,eAAe,YAAa;AAEpC,MAAI,IAAI,eAAe,aAAc;AAErC,MAAI,IAAI,eAAe,gBAAiB;AAExC,MAAI,IAAI,eAAe,gBAAiB;AAExC,MAAI,IAAI,QAAQ,CAAC,IAAI,WAAY;AAGjC;AACF;AAKO,SAAS,aAAa,KAAqC;AAChE,QAAM,OAAO,YAAY,IAAI,IAAI;AAGjC,MAAI,IAAI,SAAS,WAAW;AAC1B,UAAM,SAAS,IAAI,UAAU;AAC7B,UAAM,UAAU,IAAI,SAAS,KAAK,IAAI,KAAK;AAC3C,QAAI,QAAS,QAAO,IAAI,MAAM,KAAK,OAAO;AAE1C,QAAI,OAAQ,QAAO,IAAI,MAAM;AAC7B,WAAO,QAAQ;AAAA,EACjB;AAGA,MAAI,IAAI,eAAe,aAAa,IAAI,eAAe;AACrD,WAAO,OAAO,YAAY,IAAI,aAAa,KAAK,IAAI,KAAK,YAAY,IAAI,aAAa;AAAA,EACxF;AAGA,MAAI,IAAI,SAAS,IAAI,QAAQ,IAAI,YAAY;AAC3C,UAAM,aAAa,IAAI,eAAe,IAAI,QAAQ,UAAU;AAC5D,QAAI,KAAM,QAAO,IAAI,UAAU,KAAK,IAAI;AACxC,WAAO,IAAI,UAAU;AAAA,EACvB;AAEA,SAAO,QAAQ;AACjB;;;AD7GA,IAAM,EAAE,OAAAC,OAAM,IAAI,qBAAAC;AAClB,IAAM,EAAE,QAAAC,QAAO,IAAI,oBAAAC;AACnB,IAAM,EAAE,MAAAC,MAAK,IAAI,aAAAC;AACjB,IAAM,EAAE,cAAAC,cAAa,IAAI,qBAAAC;AAoBlB,IAAMC,WAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA;AAAA,IAEV,MAAM,CAAC,WAAW;AAAA;AAAA,IAElB,gBAAgB,CAAC,OAAO;AAAA,EAC1B;AAAA,EACA,WAAW;AACb;AAWA,eAAsB,UAAU,UAA+C;AAC7E,QAAM,QAA4B,CAAC;AAEnC,SAAO,IAAI,QAA4B,CAACC,UAAS,WAAW;AAC1D,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAItE,UAAMC,YAAWV,OAAM,CAAC,YAAYE,QAAO,GAAGE,MAAK,EAAE,QAAQ,qBAAqB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAErG,IAAAI,UAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAA+B;AAC1D,YAAM,OAAO;AACb,YAAM,KAAK;AAAA,QACT,OAAO,MAAM;AAAA,QACb,MAAM,KAAK,QAAQ,QAAQ,KAAK,EAAE;AAAA,QAClC,MAAM,KAAK;AAAA,QACX,IAAI,KAAK;AAAA,QACT,cAAc,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,SAAS,SAAS;AAAA,MACtE,CAAC;AAAA,IACH,CAAC;AAED,IAAAA,UAAS,GAAG,OAAO,MAAM;AACvB,MAAAD,SAAQ,KAAK;AAAA,IACf,CAAC;AAED,IAAAC,UAAS,GAAG,SAAS,CAAC,QAAe;AACnC,aAAO,IAAI,MAAM,mDAAqB,IAAI,OAAO,EAAE,CAAC;AAAA,IACtD,CAAC;AAAA,EACH,CAAC;AACH;AAIA,gBAAgB,cAAcC,UAAkE;AAC9F,QAAM,EAAE,UAAU,YAAY,KAAM,eAAe,YAAY,MAAM,IAAIA;AAGzE,QAAM,YAAa,eAAe,aAAwB;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AAGxB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAE5B,UAAQ,QAAQ,2EAA8B,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AACvF,UAAQ,QAAQ,yCAAW,SAAS,EAAE;AAItC,QAAM,iBAAiB,cAAc,SAAS;AAE9C,QAAM,WAAW,MAAM,IAAI,QAA6B,CAACF,UAAS,WAAW;AAC3E,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAEtE,eAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,mBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,IAC5E,CAAC;AAED,UAAMC,YAAWV,OAAM;AAAA,MACrB;AAAA,MACAE,QAAO;AAAA,MACPE,MAAK,EAAE,QAAQ,IAAI,OAAO,IAAI,eAAe,QAAQ,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC;AAAA,MACtEE,cAAa;AAAA,IACf,CAAC;AAED,QAAI,QAAQ;AAEZ,IAAAI,UAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAA+B;AAC1D,cAAQ;AACR,MAAAD,SAAQ,KAAK;AAAA,IACf,CAAC;AAED,IAAAC,UAAS,GAAG,OAAO,MAAM;AACvB,UAAI,CAAC,MAAO,CAAAD,SAAQ,IAAI;AAAA,IAC1B,CAAC;AAED,IAAAC,UAAS,GAAG,SAAS,MAAM;AAAA,EAC7B,CAAC;AAED,MAAI,CAAC,UAAU;AACb,YAAQ,SAAS,kCAAS,SAAS,iCAAQ;AAC3C,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,kCAAS,SAAS,iCAAQ,EAAE;AACnE;AAAA,EACF;AAEA,UAAQ,QAAQ,8BAAU,SAAS,IAAI,oBAAU,SAAS,IAAI,yBAAU,SAAS,UAAU,UAAU,CAAC,EAAE;AAGxG,QAAM,WAAW,YAAY,SAAS,IAAI;AAG1C,QAAM,OAAmB;AAAA,IACvB,MAAM,SAAS,QAAQ,iBAAiB,SAAS,EAAE;AAAA,IACnD,UAAU,gBAAgB;AAAA,IAC1B,MAAM;AAAA,IACN,SAAS,mCAA8B,OAAO,SAAS,EAAE,IAAI;AAAA,EAC/D;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,YAAY,oBAAI,IAA0B;AAChD,QAAM,eAAgC,CAAC;AACvC,QAAM,WAAW,SAAS,YAAY,CAAC;AAEvC,aAAW,OAAO,UAAU;AAE1B,QAAI;AACJ,QAAI;AAEJ,QAAI,IAAI,SAAS,WAAW;AAE1B,yBAAmB,kBAAkB,IAAI,QAAQ;AACjD,mBAAa,IAAI,SAAS;AAAA,IAC5B,OAAO;AACL,yBAAmB,kBAAkB,IAAI,OAAO;AAChD,mBAAa,IAAI,QAAQ;AAAA,IAC3B;AAGA,QAAI,CAAC,UAAU,IAAI,gBAAgB,KAAK,qBAAqB,WAAW;AACtE,gBAAU,IAAI,kBAAkB;AAAA,QAC9B,YAAY;AAAA,QACZ,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AAGA,UAAM,YAAY,SAAS,IAAI,eAAe,EAAE;AAChD,QAAI,MAAM,SAAS,EAAG;AAGtB,UAAM,YAA2B;AAAA,MAC/B,mBAAmB,OAAO,IAAI,EAAE;AAAA,MAChC;AAAA,MACA,mBAAmB;AAAA,MACnB;AAAA,MACA,MAAM,kBAAkB,GAAG;AAAA,MAC3B,SAAS,aAAa,GAAG;AAAA,MACzB,kBAAkB,IAAI,sBAAsB,OAAO,IAAI,mBAAmB,IAAI;AAAA,IAChF;AAEA,iBAAa,KAAK,SAAS;AAC3B;AAGA,QAAI,aAAa,UAAU,WAAW;AACpC,YAAM,EAAE,MAAM,YAAY,MAAM,CAAC,GAAG,YAAY,EAAE;AAClD,mBAAa,SAAS;AAEtB,YAAM,WAAW;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,sBAAO,iBAAiB;AAAA,MAC1B;AACA,YAAM,EAAE,MAAM,YAAY,MAAM,SAAS;AACzC,mBAAa,QAAQ;AAAA,IACvB;AAAA,EACF;AAGA,QAAM,EAAE,MAAM,WAAW,MAAM,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE;AAG9D,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAAA,EAC/C;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAEzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,UAAU,IAAI,sCAAa,QAAQ,EAAE;AAExF,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK;AAAA,EACvE;AACF;AAIO,IAAME,WAAkB;AAAA,EAC7B,SAAAJ;AAAA,EACA,OAAO;AACT;AAEA,IAAMK,WAAwB;AAAA,EAC5B,SAAAL;AAAA,EACA,QAAQI;AAAA,EACR;AACF;AAEA,IAAO,0BAAQC;;;AE3Qf,IAAAC,uBAAwB;AACxB,IAAAC,sBAAuB;AACvB,IAAAC,uBAA+B;AAH/B,YAAYC,UAAQ;AAoBpB,IAAM,EAAE,OAAAC,OAAM,IAAI,qBAAAC;AAClB,IAAM,EAAE,QAAAC,QAAO,IAAI,oBAAAC;AACnB,IAAM,EAAE,cAAAC,cAAa,IAAI,qBAAAC;AAIlB,IAAMC,WAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA;AAAA;AAAA,IAGV,MAAM,CAAC,6BAA6B;AAAA,IACpC,gBAAgB,CAAC,UAAU;AAAA,IAC3B,eAAe;AAAA;AAAA,MAEb,kBACE;AAAA,IACJ;AAAA,EACF;AACF;AAIA,gBAAgB,oBAAoBC,UAAkE;AACpG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,oBAAoB;AAGxB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAE5B,UAAQ,QAAQ,8FAAkC,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAG3F,QAAM,WAAW,MAAM,IAAI,QAA6B,CAACC,UAAS,WAAW;AAC3E,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAEtE,UAAMC,YAAWT,OAAM,CAAC,YAAYE,QAAO,GAAGE,cAAa,CAAC,CAAC;AAE7D,QAAI,QAAQ;AAEZ,IAAAK,UAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAA+B;AAC1D,UAAI,CAAC,OAAO;AACV,gBAAQ;AACR,QAAAD,SAAQ,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AAED,IAAAC,UAAS,GAAG,OAAO,MAAM;AACvB,UAAI,CAAC,MAAO,CAAAD,SAAQ,IAAI;AAAA,IAC1B,CAAC;AAED,IAAAC,UAAS,GAAG,SAAS,MAAM;AAAA,EAC7B,CAAC;AAED,MAAI,CAAC,UAAU;AACb,YAAQ,SAAS,kEAAqB;AACtC,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,kEAAqB,EAAE;AAC9D;AAAA,EACF;AAEA,UAAQ,QAAQ,kBAAQ,SAAS,IAAI,oBAAU,SAAS,IAAI,yBAAU,SAAS,UAAU,UAAU,CAAC,EAAE;AAGtG,QAAM,WAAW,YAAY,SAAS,IAAI;AAG1C,QAAM,OAAmB;AAAA,IACvB,MAAM,SAAS,QAAQ,iBAAiB,SAAS,EAAE;AAAA,IACnD,UAAU,gBAAgB;AAAA,IAC1B,MAAM;AAAA,IACN,SAAS,mCAA8B,OAAO,SAAS,EAAE,IAAI;AAAA,EAC/D;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,YAAY,oBAAI,IAA0B;AAChD,QAAM,eAAgC,CAAC;AACvC,QAAM,WAAW,SAAS,YAAY,CAAC;AAEvC,aAAW,OAAO,UAAU;AAE1B,QAAI;AACJ,QAAI;AAEJ,QAAI,IAAI,SAAS,WAAW;AAE1B,yBAAmB,kBAAkB,IAAI,QAAQ;AACjD,mBAAa,IAAI,SAAS;AAAA,IAC5B,OAAO;AACL,yBAAmB,kBAAkB,IAAI,OAAO;AAChD,mBAAa,IAAI,QAAQ;AAAA,IAC3B;AAGA,QAAI,CAAC,UAAU,IAAI,gBAAgB,KAAK,qBAAqB,WAAW;AACtE,gBAAU,IAAI,kBAAkB;AAAA,QAC9B,YAAY;AAAA,QACZ,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AAGA,UAAM,YAAY,SAAS,IAAI,eAAe,EAAE;AAChD,QAAI,MAAM,SAAS,EAAG;AAGtB,UAAM,YAA2B;AAAA,MAC/B,mBAAmB,OAAO,IAAI,EAAE;AAAA,MAChC;AAAA,MACA,mBAAmB;AAAA,MACnB;AAAA,MACA,MAAM,kBAAkB,GAAG;AAAA,MAC3B,SAAS,aAAa,GAAG;AAAA,MACzB,kBAAkB,IAAI,sBAAsB,OAAO,IAAI,mBAAmB,IAAI;AAAA,IAChF;AAEA,iBAAa,KAAK,SAAS;AAC3B;AAGA,QAAI,aAAa,UAAU,WAAW;AACpC,YAAM,EAAE,MAAM,YAAY,MAAM,CAAC,GAAG,YAAY,EAAE;AAClD,mBAAa,SAAS;AAEtB,YAAM,WAAW;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,sBAAO,iBAAiB;AAAA,MAC1B;AACA,YAAM,EAAE,MAAM,YAAY,MAAM,SAAS;AACzC,mBAAa,QAAQ;AAAA,IACvB;AAAA,EACF;AAGA,QAAM,EAAE,MAAM,WAAW,MAAM,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE;AAG9D,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAAA,EAC/C;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAEzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,UAAU,IAAI,sCAAa,QAAQ,EAAE;AAExF,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK;AAAA,EACvE;AACF;AAIO,IAAMC,WAAkB;AAAA,EAC7B,SAAAJ;AAAA,EACA,OAAO;AACT;AAEA,IAAMK,WAAwB;AAAA,EAC5B,SAAAL;AAAA,EACA,QAAQI;AACV;AAEA,IAAO,iCAAQC;;;ACrMf,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,YAAYC,eAAc;AAqB1B,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,gBAAS,QAAQ;AAEvC,QAAM,YAAYA,WAAS,MAAM,iCAAiC;AAClE,MAAI,UAAW,QAAO,UAAU,CAAC,EAAE,KAAK;AAExC,QAAM,YAAYA,WAAS,MAAM,iCAAiC;AAClE,MAAI,UAAW,QAAO,UAAU,CAAC,EAAE,KAAK;AAExC,SAAOA,WAAS,QAAQ,WAAW,EAAE,KAAK;AAC5C;AAIO,IAAMC,YAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,MAAM;AAAA,EACnB,YAAY;AAAA;AAAA;AAAA,IAGV,MAAM;AAAA,MACJ;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AAAA;AAAA,IAEA,UAAU,CAAC,gCAAgC,gCAAgC,WAAW;AAAA,EACxF;AACF;AAQA,SAAS,UAAU,MAAsB;AAEvC,SAAO,KAAK,QAAQ,0DAA0D,EAAE,EAAE,KAAK;AACzF;AAMA,IAAM,wBAAwB;AAK9B,IAAM,wBAAwB;AAI9B,IAAM,uBAAuB;AAI7B,IAAM,0BAA0B;AAAA;AAAA,EAE9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,gBAAgB,SAA0B;AACjD,SAAO,wBAAwB,KAAK,CAAC,YAAY,QAAQ,KAAK,OAAO,CAAC;AACxE;AAIA,SAASC,mBAAkB,SAA8B;AACvD,QAAM,UAAU,QAAQ,KAAK;AAG7B,MACE,YAAY,4CACZ,YAAY,wDACZ,YAAY,oCACZ,YAAY,oCACZ,YAAY,oCACZ,YAAY,mBACZ,YAAY,mBACZ,YAAY;AAEZ;AACF,MAAI,QAAQ,SAAS,sBAAO,KAAK,QAAQ,SAAS,gBAAM,KAAK,QAAQ,SAAS,2BAAO;AACnF;AAGF,MAAI,YAAY,oCAAW,YAAY,iCAAS;AAGhD,MAAI,YAAY,gDAAa,QAAQ,WAAW,sCAAQ,KAAK,QAAQ,WAAW,4CAAS;AACvF;AAGF,MAAI,gBAAgB,OAAO,EAAG;AAE9B;AACF;AAKA,IAAM,eAAoC;AAAA,EACxC,CAAC,WAAW,IAAI;AAAA,EAChB,CAAC,eAAe,IAAI;AAAA,EACpB,CAAC,MAAM,IAAI;AAAA,EACX,CAAC,MAAM,IAAI;AAAA,EACX,CAAC,MAAM,IAAI;AAAA,EACX,CAAC,WAAW,KAAK;AAAA,EACjB,CAAC,eAAe,KAAK;AAAA,EACrB,CAAC,MAAM,KAAK;AAAA,EACZ,CAAC,MAAM,KAAK;AAAA,EACZ,CAAC,MAAM,KAAK;AACd;AAOA,SAAS,yBAAyB,KAA4B;AAE5D,MAAI,MAAM,IAAI,QAAQ,uEAAuE,GAAG,EAAE,KAAK;AAGvG,MAAI,OAAuB;AAC3B,aAAW,CAAC,SAAS,EAAE,KAAK,cAAc;AACxC,QAAI,QAAQ,KAAK,GAAG,GAAG;AACrB,aAAO;AACP,YAAM,IAAI,QAAQ,SAAS,EAAE,EAAE,KAAK;AACpC;AAAA,IACF;AAAA,EACF;AAGA,QAAM,IAAI,QAAQ,MAAM,GAAG,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAGvD,QAAM,QAAQ,IAAI,MAAM,4DAA4D;AACpF,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,CAAC,EAAE,UAAU,QAAQ,IAAI;AAC/B,QAAM,YAAY,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAChE,QAAM,YAAY,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAGhE,MAAI,MAAc,OAAe;AACjC,MAAI,UAAU,CAAC,IAAI,IAAI;AAErB,WAAO,UAAU,CAAC;AAClB,YAAQ,UAAU,CAAC;AACnB,UAAM,UAAU,CAAC;AAAA,EACnB,WAAW,UAAU,CAAC,IAAI,IAAI;AAE5B,UAAM,UAAU,CAAC;AACjB,YAAQ,UAAU,CAAC;AACnB,WAAO,UAAU,CAAC;AAAA,EACpB,OAAO;AAEL,YAAQ,UAAU,CAAC;AACnB,UAAM,UAAU,CAAC;AACjB,WAAO,MAAO,UAAU,CAAC;AAAA,EAC3B;AAGA,MAAI,OAAO,UAAU,CAAC;AACtB,QAAM,SAAS,UAAU,CAAC;AAC1B,QAAM,SAAS,UAAU,CAAC,KAAK;AAE/B,MAAI,SAAS,QAAQ,SAAS,GAAI,SAAQ;AAC1C,MAAI,SAAS,SAAS,SAAS,GAAI,QAAO;AAG1C,QAAM,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG,KAAK,MAAM,QAAQ,MAAM;AAChE,QAAM,KAAK,KAAK,MAAM,KAAK,QAAQ,IAAI,GAAI;AAC3C,SAAO,MAAM,EAAE,IAAI,OAAO;AAC5B;AAMA,SAAS,iBAAiB,SAAyB;AAEjD,QAAM,MAAM,QAAQ,QAAQ,MAAM,EAAE,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAChE,QAAM,QAAQ,IAAI,MAAM,uDAAuD;AAC/E,MAAI,CAAC,OAAO;AACV,UAAM,aAAa,QAAQ,QAAQ,MAAM,EAAE,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,IAAI;AACvF,WAAO,KAAK,MAAM,IAAI,KAAK,UAAU,EAAE,QAAQ,IAAI,GAAI;AAAA,EACzD;AAEA,QAAM,QAAQ,CAAC,SAAS,MAAM,CAAC,GAAG,EAAE,GAAG,SAAS,MAAM,CAAC,GAAG,EAAE,GAAG,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC;AACrF,QAAM,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAClC,QAAM,SAAS,SAAS,MAAM,CAAC,GAAG,EAAE;AAEpC,MAAI,MAAc,OAAe;AACjC,MAAI,MAAM,CAAC,IAAI,IAAI;AACjB,WAAO,MAAM,CAAC;AACd,YAAQ,MAAM,CAAC;AACf,UAAM,MAAM,CAAC;AAAA,EACf,WAAW,MAAM,CAAC,IAAI,IAAI;AACxB,UAAM,MAAM,CAAC;AACb,YAAQ,MAAM,CAAC;AACf,WAAO,MAAM,CAAC;AAAA,EAChB,OAAO;AACL,YAAQ,MAAM,CAAC;AACf,UAAM,MAAM,CAAC;AACb,WAAO,MAAO,MAAM,CAAC;AAAA,EACvB;AAEA,QAAM,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG,KAAK,MAAM,QAAQ,CAAC;AAC3D,QAAM,KAAK,KAAK,MAAM,KAAK,QAAQ,IAAI,GAAI;AAC3C,SAAO,MAAM,EAAE,IAAI,IAAI;AACzB;AAWA,gBAAgB,cAAcC,UAAkE;AAC9F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AACxB,MAAI,eAAe;AAGnB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAG5B,UAAQ,QAAQ,0EAA6B,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGtF,QAAM,WAAWJ,yBAAwB,QAAQ;AACjD,QAAM,YAAY,oBAAI,IAAwB;AAC9C,QAAM,WAA4B,CAAC;AAGnC,MAAI,iBAIO;AAGX,QAAM,qBAAqB,MAAM;AAC/B,QAAI,gBAAgB;AAClB,YAAM,UAAU,eAAe,aAAa,KAAK,IAAI,EAAE,KAAK;AAC5D,YAAM,OAAOG,mBAAkB,OAAO;AAGtC,YAAM,mBAAmB,eAAe,UAAU;AAClD,YAAM,aAAa,eAAe,UAAU;AAE5C,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,mBAAmB;AAAA,QACnB,WAAW,eAAe;AAAA,QAC1B;AAAA,QACA,SAAS,WAAW;AAAA,MACtB,CAAC;AAGD,UAAI,eAAe,QAAQ;AACzB,kBAAU,IAAI,kBAAkB;AAAA,UAC9B,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ,CAAC;AAAA,MACH;AAEA;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AACtE,QAAM,KAAc,0BAAgB;AAAA,IAClC,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AAED,aAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,iBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,EAC5E,CAAC;AAED,mBAAiB,QAAQ,IAAI;AAE3B,UAAM,cAAc,UAAU,IAAI;AAGlC,UAAM,UAAU,YAAY,MAAM,qBAAqB;AACvD,QAAI,SAAS;AACX,yBAAmB;AACnB,YAAM,cAAc,QAAQ,CAAC;AAC7B,YAAM,cAAc,YAAY,MAAM,oBAAoB;AAC1D,UAAI,eAAe,CAAC,gBAAgB,WAAW,GAAG;AAChD,yBAAiB;AAAA,UACf,WAAW,iBAAiB,QAAQ,CAAC,CAAC;AAAA,UACtC,QAAQ,YAAY,CAAC,EAAE,KAAK;AAAA,UAC5B,cAAc,CAAC,YAAY,CAAC,CAAC;AAAA,QAC/B;AAAA,MACF,OAAO;AACL,yBAAiB;AAAA,UACf,WAAW,iBAAiB,QAAQ,CAAC,CAAC;AAAA,UACtC,QAAQ;AAAA,UACR,cAAc,CAAC,WAAW;AAAA,QAC5B;AAAA,MACF;AACA,UAAI,oBAAoB,QAAQ,GAAG;AACjC;AAAA,UACE,eAAe,WAAW,WAAW,YAAY,mBAAmB,sBAAO,iBAAiB,wBAAS;AAAA,QACvG;AAAA,MACF;AACA;AAAA,IACF;AAEA,UAAM,UAAU,YAAY,MAAM,qBAAqB;AACvD,QAAI,SAAS;AACX,YAAM,YAAY,yBAAyB,QAAQ,CAAC,CAAC;AACrD,UAAI,cAAc,MAAM;AAEtB,2BAAmB;AACnB,cAAM,cAAc,QAAQ,CAAC;AAC7B,cAAM,cAAc,YAAY,MAAM,oBAAoB;AAC1D,YAAI,eAAe,CAAC,gBAAgB,WAAW,GAAG;AAChD,2BAAiB;AAAA,YACf;AAAA,YACA,QAAQ,YAAY,CAAC,EAAE,KAAK;AAAA,YAC5B,cAAc,CAAC,YAAY,CAAC,CAAC;AAAA,UAC/B;AAAA,QACF,OAAO;AACL,2BAAiB;AAAA,YACf;AAAA,YACA,QAAQ;AAAA,YACR,cAAc,CAAC,WAAW;AAAA,UAC5B;AAAA,QACF;AACA,YAAI,oBAAoB,QAAQ,GAAG;AACjC;AAAA,YACE,eAAe,WAAW,WAAW,YAAY,mBAAmB,sBAAO,iBAAiB,wBAAS;AAAA,UACvG;AAAA,QACF;AACA;AAAA,MACF;AAAA,IAEF;AAGA,QAAI,kBAAkB,aAAa;AACjC,qBAAe,aAAa,KAAK,WAAW;AAAA,IAC9C,WAAW,aAAa;AAEtB;AAAA,IACF;AAAA,EACF;AAGA,qBAAmB;AAKnB,QAAM,kBAAkB,UAAU,IAAI,QAAQ;AAC9C,QAAM,kBAAkB,kBAAkB,UAAU,OAAO,IAAI,UAAU;AAEzE,QAAM,WAAW,kBAAkB;AAGnC,QAAM,OAAmB;AAAA,IACvB,MAAM;AAAA,IACN,UAAU,gBAAgB;AAAA,IAC1B,MAAM;AAAA,EACR;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,UAA0B,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,IACzE,YAAY,EAAE;AAAA,IACd,aAAa,EAAE;AAAA,EACjB,EAAE;AACF,QAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AAGvC,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,WAAW;AACnD,UAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,SAAS;AAC7C,UAAM,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,EACxC;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAGzB,QAAM,aAAa,oBAAI,IAAyB;AAChD,aAAW,OAAO,UAAU;AAC1B,eAAW,IAAI,IAAI,OAAO,WAAW,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC;AAAA,EAC9D;AAGA,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,UAAU,IAAI,sCAAa,QAAQ,EAAE;AACxF;AAAA,IACE;AAAA,IACA,yCAAW,MAAM,KAAK,WAAW,QAAQ,CAAC,EACvC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,GAAG,IAAI,IAAI,KAAK,EAAE,EACzC,KAAK,IAAI,CAAC;AAAA,EACf;AACA,MAAI,eAAe,GAAG;AACpB,YAAQ,QAAQ,gBAAM,YAAY,mDAAW;AAAA,EAC/C;AAEA,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK;AAAA,EACvE;AACF;AAIO,IAAME,WAAkB;AAAA,EAC7B,SAAAH;AAAA,EACA,OAAO;AACT;AAIA,IAAMI,YAAwB;AAAA,EAC5B,SAAAJ;AAAA,EACA,QAAQG;AAAA;AAEV;AAEA,IAAO,8BAAQC;;;ACpff,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,YAAYC,eAAc;AAmB1B,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,gBAAS,QAAQ;AACvC,QAAM,OAAOA,WAAS,QAAQ,WAAW,EAAE;AAC3C,SAAO,QAAQ;AACjB;AAIO,IAAMC,YAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,MAAM;AAAA,EACnB,YAAY;AAAA;AAAA,IAEV,MAAM,CAAC,mBAAmB,SAAS,MAAM;AAAA,EAC3C;AACF;AAOA,IAAM,uBAAuB;AAG7B,IAAM,mBAAmB;AAIzB,SAASC,mBAAkB,SAA8B;AACvD,QAAM,UAAU,QAAQ,KAAK;AAG7B,MAAI,YAAY,iBAAQ;AACxB,MAAI,YAAY,iBAAQ;AACxB,MAAI,YAAY,iBAAQ;AACxB,MAAI,YAAY,iBAAQ;AACxB,MAAI,YAAY,iBAAQ;AACxB,MAAI,YAAY,oBAAU,YAAY,6BAAU;AAChD,MAAI,YAAY,oBAAU,YAAY,6BAAU;AAGhD,MAAI,YAAY,oBAAU,QAAQ,SAAS,gCAAO,EAAG;AACrD,MAAI,YAAY,oBAAU,QAAQ,SAAS,0BAAM,EAAG;AACpD,MAAI,QAAQ,SAAS,oBAAK,KAAK,YAAY,uBAAS;AACpD,MAAI,YAAY,gCAAY,YAAY,gCAAY,QAAQ,SAAS,0BAAM,EAAG;AAC9E,MAAI,YAAY,oBAAU,YAAY,oBAAU,YAAY,uBAAS;AACrE,MAAI,QAAQ,WAAW,gBAAM,EAAG;AAChC,MAAI,YAAY,oBAAU,YAAY,6BAAU;AAGhD,MAAI,QAAQ,SAAS,4CAAS,KAAK,YAAY,iBAAQ;AACvD,MACE,QAAQ,SAAS,gCAAO,KACxB,QAAQ,SAAS,gCAAO,KACxB,QAAQ,SAAS,gCAAO,KACxB,QAAQ,SAAS,sCAAQ,KACzB,QAAQ,SAAS,gCAAO,KACxB,QAAQ,SAAS,oBAAK,GACtB;AACA;AAAA,EACF;AAGA,MAAI,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,EAAG;AAEtD;AACF;AAQA,SAAS,eAAe,SAAyB;AAE/C,QAAM,OAAO,IAAI,KAAK,QAAQ,QAAQ,KAAK,GAAG,CAAC;AAC/C,SAAO,KAAK,MAAM,KAAK,QAAQ,IAAI,GAAI;AACzC;AAQA,SAAS,cAAc,UAA0B;AAE/C,SAAO,SAAS,QAAQ,kBAAkB,EAAE,EAAE,KAAK;AACrD;AAUA,IAAM,oBAAoB,oBAAI,IAAoB;AAIlD,gBAAgB,SAASC,UAAkE;AACzF,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,YAAY;AAChB,MAAI,oBAAoB;AACxB,MAAI,eAAe;AAGnB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAG5B,UAAQ,QAAQ,oEAAuB,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGhF,MAAI,YAAY;AAChB,QAAM,YAAY,oBAAI,IAAwB;AAC9C,QAAM,WAA4B,CAAC;AAGnC,MAAI,iBAKO;AAGX,QAAM,qBAAqB,MAAM;AAC/B,QAAI,gBAAgB;AAClB,YAAM,UAAU,eAAe,aAAa,KAAK,IAAI,EAAE,KAAK;AAC5D,YAAM,OAAOD,mBAAkB,OAAO;AAEtC,eAAS,KAAK;AAAA,QACZ,kBAAkB,eAAe;AAAA,QACjC,mBAAmB,eAAe;AAAA;AAAA;AAAA,QAElC,WAAW,eAAe;AAAA,QAC1B;AAAA,QACA,SAAS,WAAW;AAAA,MACtB,CAAC;AAGD,gBAAU,IAAI,eAAe,YAAY;AAAA,QACvC,YAAY,eAAe;AAAA,QAC3B,UAAU,eAAe;AAAA,MAC3B,CAAC;AAED;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AACtE,QAAM,KAAc,0BAAgB;AAAA,IAClC,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AAED,aAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,iBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,EAC5E,CAAC;AAED,mBAAiB,QAAQ,IAAI;AAE3B,UAAM,aAAa,KAAK,MAAM,gBAAgB;AAC9C,QAAI,YAAY;AACd,kBAAY,WAAW,CAAC,EAAE,KAAK;AAC/B;AAAA,IACF;AAGA,UAAM,cAAc,KAAK,MAAM,oBAAoB;AACnD,QAAI,aAAa;AAEf,yBAAmB;AAEnB,YAAM,UAAU,YAAY,CAAC;AAC7B,YAAM,cAAc,YAAY,CAAC,EAAE,KAAK;AACxC,UAAI,WAAW,cAAc,WAAW;AAExC,YAAM,aAAa,YAAY,CAAC,KAAK,YAAY,CAAC,KAAK;AAGvD,UAAI,aAAa,cAAc,YAAY,CAAC,GAAG;AAE7C,cAAM,mBAAmB,kBAAkB,IAAI,UAAU;AACzD,YAAI,kBAAkB;AACpB,qBAAW;AAAA,QACb;AAAA,MAEF,WAAW,YAAY,CAAC,KAAK,YAAY,CAAC,GAAG;AAE3C,0BAAkB,IAAI,YAAY,QAAQ;AAAA,MAC5C;AAEA,uBAAiB;AAAA,QACf,WAAW,eAAe,OAAO;AAAA,QACjC;AAAA,QACA;AAAA,QACA,cAAc,CAAC;AAAA,MACjB;AAGA,UAAI,oBAAoB,QAAS,GAAG;AAClC,cAAM,WAAW;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,sBAAO,iBAAiB;AAAA,QAC1B;AACA,qBAAa,QAAQ;AAAA,MACvB;AAEA;AAAA,IACF;AAGA,QAAI,gBAAgB;AAElB,UAAI,KAAK,WAAW,OAAO,EAAG;AAE9B,UAAI,KAAK,WAAW,0BAAM,KAAK,KAAK,WAAW,0BAAM,EAAG;AAExD,qBAAe,aAAa,KAAK,IAAI;AAAA,IACvC,OAAO;AAEL,YAAM,UAAU,KAAK,KAAK;AAC1B,UACE,WACA,CAAC,QAAQ,WAAW,OAAO,KAC3B,CAAC,QAAQ,WAAW,0BAAM,KAC1B,CAAC,QAAQ,WAAW,0BAAM,GAC1B;AAEA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,qBAAmB;AAGnB,QAAM,OAAmB;AAAA,IACvB,MAAM,cAAc,6BAASH,yBAAwB,QAAQ,IAAI;AAAA,IACjE,UAAU,gBAAgB;AAAA,IAC1B;AAAA,EACF;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,UAA0B,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,IACzE,YAAY,EAAE;AAAA,IACd,aAAa,EAAE;AAAA;AAAA;AAAA,EAEjB,EAAE;AACF,QAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AAGvC,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,WAAW;AACnD,UAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,SAAS;AAC7C,UAAM,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,EACxC;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAGzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,UAAU,IAAI,qBAAM;AACvE,MAAI,eAAe,GAAG;AACpB,YAAQ,QAAQ,gBAAM,YAAY,mDAAW;AAAA,EAC/C;AAEA,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK;AAAA,EACvE;AACF;AAIO,IAAMK,YAAkB;AAAA,EAC7B,SAAAH;AAAA,EACA,OAAO;AACT;AAIA,IAAMI,YAAwB;AAAA,EAC5B,SAAAJ;AAAA,EACA,QAAQG;AAAA;AAEV;AAEA,IAAO,wBAAQC;;;ACvUf,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAgBf,IAAMC,YAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA;AAAA;AAAA,IAGV,gBAAgB,CAAC,gBAAgB,UAAU;AAAA,IAC3C,MAAM,CAAC,gCAAgC;AAAA,EACzC;AACF;AAkEA,SAAS,oBAAoB,MAAsB;AACjD,MAAI;AAEF,UAAM,QAAQ,IAAI,WAAW,KAAK,MAAM;AACxC,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,CAAC,IAAI,KAAK,WAAW,CAAC;AAAA,IAC9B;AAEA,WAAO,IAAI,YAAY,OAAO,EAAE,OAAO,KAAK;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,gBAAS,QAAQ;AACvC,SAAOA,WAAS,QAAQ,YAAY,EAAE,KAAK;AAC7C;AAKA,SAASC,iBAAgB,SAA0B;AACjD,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO,eAAe,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC;AACvD;AAKA,SAASC,mBAAkB,KAAoC;AAC7D,QAAM,UAAU,IAAI,WAAW;AAG/B,MAAI,WAAWD,iBAAgB,OAAO,GAAG;AACvC;AAAA,EACF;AAGA,MAAI,IAAI,QAAQ,OAAQ;AACxB,MAAI,IAAI,QAAQ,OAAQ;AACxB,MAAI,IAAI,aAAa,OAAQ;AAG7B,MAAI,IAAI,OAAO;AACb,UAAM,OAAO,IAAI,MAAM,QAAQ;AAC/B,QAAI,KAAK,SAAS,WAAW,EAAG;AAChC;AAAA,EACF;AAGA,MAAI,QAAS;AAGb;AACF;AAKA,SAAS,kBAAkB,KAAsC;AAE/D,MAAI,IAAI,SAAS;AACf,WAAO,oBAAoB,IAAI,OAAO;AAAA,EACxC;AAGA,MAAI,IAAI,QAAQ,QAAQ;AACtB,WAAO,kBAAQ,IAAI,OAAO,CAAC,EAAE,GAAG;AAAA,EAClC;AAGA,MAAI,IAAI,QAAQ,QAAQ;AACtB,WAAO,kBAAQ,IAAI,OAAO,CAAC,EAAE,GAAG;AAAA,EAClC;AAGA,MAAI,IAAI,aAAa,QAAQ;AAC3B,WAAO,kBAAQ,IAAI,YAAY,CAAC,EAAE,GAAG;AAAA,EACvC;AAGA,MAAI,IAAI,OAAO;AACb,UAAM,OAAO,IAAI,MAAM,QAAQ;AAC/B,QAAI,KAAK,SAAS,WAAW,GAAG;AAC9B,aAAO,SAAS,IAAI;AAAA,IACtB;AACA,WAAO,kBAAQ,IAAI;AAAA,EACrB;AAGA,SAAO;AACT;AAIA,gBAAgB,eAAeE,UAAkE;AAC/F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,oBAAoB;AAGxB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,gEAAwB;AAC5F,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAE5B,UAAQ,QAAQ,mFAA4B,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGrF,MAAI;AACJ,MAAI;AACF,UAAM,UAAa,kBAAa,UAAU,OAAO;AACjD,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,SAAS,OAAO;AACd,UAAM,MAAM,IAAI,MAAM,yDAA2B,KAAK,EAAE;AACxD,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI;AACjC;AAAA,EACF;AAGA,QAAM,UAAU,KAAK,aAAa,SAAS,KAAK,CAAC,CAAC,KAAK;AACvD,QAAM,WAAW;AAGjB,MAAI;AACJ,MAAI,sCAA+B;AAEjC,UAAM,QAAQ,KAAK,aAAa,KAAK,CAAC,MAAM,oBAAoB,EAAE,IAAI,MAAM,oBAAoB,KAAK,KAAK,CAAC;AAC3G,cAAU,QAAQ,oBAAoB,MAAM,IAAI,IAAI;AAAA,EACtD,OAAO;AAEL,UAAM,YAAY,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,YAAY,wBAAwB;AAClF,cAAU,YAAY,oBAAoB,UAAU,WAAW,IAAI;AAAA,EACrE;AAGA,QAAM,OAAmB;AAAA,IACvB,MAAM,oBAAoB,KAAK,KAAK,KAAKJ,yBAAwB,QAAQ;AAAA,IACzE,UAAU,gBAAgB;AAAA,IAC1B,MAAM;AAAA,IACN;AAAA,EACF;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,YAAY,oBAAI,IAA0B;AAChD,aAAW,eAAe,KAAK,cAAc;AAC3C,UAAM,OAAO,oBAAoB,YAAY,IAAI;AACjD,cAAU,IAAI,MAAM;AAAA,MAClB,YAAY;AAAA,MACZ,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAGA,QAAM,UAAU,MAAM,KAAK,UAAU,OAAO,CAAC;AAC7C,QAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AAGvC,QAAM,mBAAmB,CAAC,GAAG,KAAK,QAAQ,EAAE,QAAQ;AACpD,QAAM,eAAgC,CAAC;AAEvC,aAAW,OAAO,kBAAkB;AAClC,UAAM,aAAa,oBAAoB,IAAI,WAAW;AACtD,UAAM,YAAY,KAAK,MAAM,IAAI,eAAe,GAAI;AACpD,UAAM,OAAOG,mBAAkB,GAAG;AAClC,UAAM,UAAU,kBAAkB,GAAG;AAGrC,QAAI,CAAC,UAAU,IAAI,UAAU,GAAG;AAC9B,gBAAU,IAAI,YAAY;AAAA,QACxB,YAAY;AAAA,QACZ,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AAEA,iBAAa,KAAK;AAAA,MAChB,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED;AAGA,QAAI,aAAa,UAAU,WAAW;AACpC,YAAM,EAAE,MAAM,YAAY,MAAM,CAAC,GAAG,YAAY,EAAE;AAClD,mBAAa,SAAS;AAEtB,YAAM,WAAW;AAAA,QACf;AAAA,QACA,KAAK,MAAO,oBAAoB,iBAAiB,SAAU,UAAU;AAAA,QACrE;AAAA,QACA;AAAA,QACA,sBAAO,iBAAiB;AAAA,MAC1B;AACA,mBAAa,QAAQ;AAAA,IACvB;AAAA,EACF;AAGA,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAAA,EAC/C;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,0BAAM;AAC7F,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAEzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,UAAU,IAAI,qBAAM;AAEvE,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK;AAAA,EACvE;AACF;AAIO,IAAME,YAAkB;AAAA,EAC7B,SAAAN;AAAA,EACA,OAAO;AACT;AAEA,IAAMO,YAAwB;AAAA,EAC5B,SAAAP;AAAA,EACA,QAAQM;AACV;AAEA,IAAO,2BAAQC;;;ACjWf,IAAAC,uBAAwB;AACxB,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAL/B,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAiBtB,IAAM,EAAE,OAAAC,OAAM,IAAI,qBAAAC;AAClB,IAAM,EAAE,QAAAC,QAAO,IAAI,oBAAAC;AACnB,IAAM,EAAE,MAAAC,MAAK,IAAI,aAAAC;AACjB,IAAM,EAAE,cAAAC,cAAa,IAAI,qBAAAC;AAgDzB,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B,CAAC,WAAW,CAAC;AAAA,EACb,CAAC,YAAY,CAAC;AAAA,EACd,CAAC,SAAS,CAAC;AAAA,EACX,CAAC,SAAS,CAAC;AAAA,EACX,CAAC,OAAO,CAAC;AAAA,EACT,CAAC,QAAQ,CAAC;AAAA,EACV,CAAC,QAAQ,CAAC;AAAA,EACV,CAAC,UAAU,CAAC;AAAA,EACZ,CAAC,aAAa,CAAC;AAAA,EACf,CAAC,WAAW,CAAC;AAAA,EACb,CAAC,YAAY,EAAE;AAAA,EACf,CAAC,YAAY,EAAE;AACjB,CAAC;AAEM,IAAMC,YAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,OAAO;AAAA,EACpB,YAAY;AAAA,IACV,MAAM,CAAC,8CAA8C;AAAA,IACrD,gBAAgB,CAAC,UAAU,WAAW,cAAc;AAAA,EACtD;AACF;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,MACJ,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEA,SAAS,mBACP,MACA,YACA,KACA,MACA,QACA,QACe;AACf,QAAM,cAAc,KAAK,IAAI,MAAM,YAAY,KAAK,MAAM,QAAQ,MAAM;AACxE,QAAM,OAAO,IAAI,KAAK,WAAW;AACjC,MACE,KAAK,eAAe,MAAM,QAC1B,KAAK,YAAY,MAAM,cACvB,KAAK,WAAW,MAAM,OACtB,KAAK,YAAY,MAAM,QACvB,KAAK,cAAc,MAAM,UACzB,KAAK,cAAc,MAAM,QACzB;AACA,WAAO;AAAA,EACT;AACA,SAAO,KAAK,MAAM,cAAc,GAAI;AACtC;AAEA,SAAS,oBAAoB,OAA8B;AACzD,QAAM,QAAQ,gBAAgB,KAAK,EAAE;AAAA,IACnC;AAAA,EACF;AACA,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO;AAAA,IACL,OAAO,MAAM,CAAC,CAAC;AAAA,IACf,OAAO,MAAM,CAAC,CAAC,IAAI;AAAA,IACnB,OAAO,MAAM,CAAC,CAAC;AAAA,IACf,OAAO,MAAM,CAAC,CAAC;AAAA,IACf,OAAO,MAAM,CAAC,CAAC;AAAA,IACf,OAAO,MAAM,CAAC,CAAC;AAAA,EACjB;AACF;AAEA,SAAS,oBAAoB,OAA8B;AACzD,QAAM,QAAQ,gBAAgB,KAAK,EAAE;AAAA,IACnC;AAAA,EACF;AACA,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,aAAa,eAAe,IAAI,MAAM,CAAC,EAAE,YAAY,CAAC;AAC5D,MAAI,eAAe,OAAW,QAAO;AAErC,MAAI,OAAO,OAAO,MAAM,CAAC,CAAC;AAC1B,MAAI,OAAO,KAAK,OAAO,GAAI,QAAO;AAClC,MAAI,MAAM,CAAC,EAAE,YAAY,MAAM,MAAM;AACnC,QAAI,SAAS,GAAI,QAAO;AAAA,EAC1B,WAAW,SAAS,IAAI;AACtB,YAAQ;AAAA,EACV;AAEA,SAAO,mBAAmB,OAAO,MAAM,CAAC,CAAC,GAAG,YAAY,OAAO,MAAM,CAAC,CAAC,GAAG,MAAM,OAAO,MAAM,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC,CAAC;AACpH;AAMO,SAAS,oBAAoB,OAA8B;AAChE,SAAO,oBAAoB,KAAK,KAAK,oBAAoB,KAAK;AAChE;AAEA,SAAS,kBAAkB,MAAwE;AACjG,QAAM,QAAQ,MAAM,OAAO,KAAK,EAAE,YAAY;AAC9C,QAAM,OAAO,MAAM,MAAM,KAAK,KAAK,SAAS;AAC5C,SAAO;AAAA,IACL,YAAY,SAAS,KAAK,YAAY;AAAA,IACtC;AAAA,EACF;AACF;AAEA,SAAS,SAAY,UAAqB;AACxC,SAAO,KAAK,MAAS,kBAAa,UAAU,MAAM,CAAC;AACrD;AAEA,SAAS,oBAAoB,aAAqB,cAA8B;AAC9E,QAAM,WAAgB,eAAQ,aAAa,YAAY;AACvD,QAAMC,YAAgB,gBAAS,aAAa,QAAQ;AACpD,MAAIA,UAAS,WAAW,IAAI,KAAU,kBAAWA,SAAQ,GAAG;AAC1D,UAAM,IAAI,MAAM,oDAAoD,YAAY,EAAE;AAAA,EACpF;AACA,SAAO;AACT;AAEA,SAASC,cAAa,UAKpB;AACA,QAAM,WAAW,SAA6B,QAAQ;AACtD,MAAI,SAAS,WAAW,iCAAiC,SAAS,YAAY,GAAG;AAC/E,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,MAAI,SAAS,aAAa,aAAa,SAAS,aAAa,SAAS;AACpE,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AAEA,QAAM,cAAmB,eAAQ,QAAQ;AACzC,SAAO;AAAA,IACL;AAAA,IACA,cAAc,oBAAoB,aAAa,SAAS,YAAY;AAAA,IACpE,eAAe,oBAAoB,aAAa,SAAS,aAAa;AAAA,IACtE,cAAc,oBAAoB,aAAa,SAAS,YAAY;AAAA,EACtE;AACF;AAEA,SAAS,kBAAkB,YAAiD;AAC1E,SAAO,WAAW,eAAe,KAAK,KAAK,WAAW,aAAa,KAAK,KAAK;AAC/E;AAEA,SAAS,iBAAiB,SAA2C;AACnE,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,OAAO,MAAM,KAAK;AACpC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,UAAU,OAAO,SAAS,MAAM,KAAK,KAAK,OAAO,SAAS,OAAO,KAAK;AAC5E,SAAO,UAAU,KAAK,OAAO,KAAK,SAAS,KAAK,KAAK,SAAS;AAChE;AAEA,SAAS,oBAAoB,SAAoC;AAC/D,QAAM,QAAkB,CAAC;AACzB,QAAM,QAAQ,iBAAiB,OAAO;AACtC,MAAI,MAAO,OAAM,KAAK,KAAK;AAE3B,QAAM,OAAO,QAAQ,MAAM,KAAK;AAChC,MAAI,KAAM,OAAM,KAAK,IAAI;AAEzB,aAAW,cAAc,QAAQ,kBAAkB,CAAC,GAAG;AACrD,UAAM,OAAO,kBAAkB,UAAU;AACzC,QAAI,KAAM,OAAM,KAAK,kBAAQ,IAAI,EAAE;AAAA,EACrC;AAEA,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAC/C;AAEA,SAASC,mBAAkB,SAAyC;AAClE,MAAI,QAAQ,MAAM,KAAK,EAAG;AAC1B,OAAK,QAAQ,kBAAkB,CAAC,GAAG,KAAK,CAAC,eAAe,kBAAkB,UAAU,CAAC,GAAG;AACtF;AAAA,EACF;AACA;AACF;AAEA,SAAS,eACP,UACA,WACA,SACQ;AACR,MAAI,SAAS,UAAU,KAAK,EAAG,QAAO,SAAS,SAAS,KAAK;AAC7D,MAAI,SAAS,aAAa,WAAW,UAAU,MAAM,KAAK,EAAG,QAAO,UAAU,KAAK,KAAK;AAExF,QAAM,eAAe,UAAU,WAAW,CAAC,GACxC,IAAI,iBAAiB,EACrB,KAAK,CAAC,WAAW,CAAC,WAAW,OAAO,eAAe,OAAO;AAC7D,SACE,aAAa,QACb,UAAU,SAAS,IAAI,CAAC,WAAW,kBAAkB,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,KAC5E,SAAS;AAEb;AAEA,gBAAgB,gBAAgBC,UAAkE;AAChG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAC1D,QAAM,EAAE,UAAU,cAAc,eAAe,aAAa,IAAIF,cAAa,QAAQ;AACrF,QAAM,WAAW,SAA6B,YAAY;AAC1D,QAAM,YAAY,SAA8B,aAAa;AAC7D,QAAM,QAAQ,SAAS,OAAO,kBAAkB,SAAS,IAAI,IAAI;AACjE,QAAM,aAAa,YAAY,YAAY;AAE3C,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAE5B,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,MAAM,eAAe,UAAU,WAAW,OAAO,UAAU;AAAA,MAC3D,UAAU,gBAAgB;AAAA,MAC1B,MAAM,SAAS,aAAa;AAAA,MAC5B,GAAI,SAAS,aAAa,UAAU,EAAE,SAAS,SAAS,OAAO,IAAI,CAAC;AAAA,MACpE,SAAS,OAAO;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,YAAY,oBAAI,IAA0B;AAChD,aAAW,UAAU,UAAU,WAAW,CAAC,GAAG;AAC5C,UAAM,WAAW,kBAAkB,MAAM;AACzC,cAAU,IAAI,SAAS,YAAY;AAAA,MACjC,YAAY,SAAS;AAAA,MACrB,aAAa,SAAS;AAAA,IACxB,CAAC;AAAA,EACH;AACA,MAAI,SAAS,CAAC,UAAU,IAAI,MAAM,UAAU,GAAG;AAC7C,cAAU,IAAI,MAAM,YAAY;AAAA,MAC9B,YAAY,MAAM;AAAA,MAClB,aAAa,MAAM;AAAA,IACrB,CAAC;AAAA,EACH;AACA,QAAM,EAAE,MAAM,WAAW,MAAM,MAAM,KAAK,UAAU,OAAO,CAAC,EAAE;AAE9D,QAAM,aAAgB,sBAAiB,cAAc,EAAE,UAAU,OAAO,CAAC;AACzE,MAAI,YAAY;AAChB,MAAI,oBAAoB;AACxB,aAAW,GAAG,QAAQ,CAAC,UAA2B;AAChD,iBAAa,OAAO,UAAU,WAAW,OAAO,WAAW,KAAK,IAAI,MAAM;AAAA,EAC5E,CAAC;AAED,QAAMG,YAAWb,OAAM,CAAC,YAAYE,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAClG,QAAM,eAAgC,CAAC;AAEvC,MAAI;AACF,qBAAiB,QAAQO,WAAyD;AAChF,YAAM,UAAU,KAAK;AACrB,YAAM,SAAS,kBAAkB,QAAQ,OAAO;AAChD,mBAAa,KAAK;AAAA,QAChB,mBAAmB,QAAQ,aAAa,OAAO,QAAQ,UAAU,IAAI;AAAA,QACrE,kBAAkB,OAAO;AAAA,QACzB,mBAAmB,OAAO;AAAA,QAC1B,WAAW,oBAAoB,QAAQ,gBAAgB,QAAQ,gBAAgB,EAAE,KAAK,OAAO;AAAA,QAC7F,MAAMF,mBAAkB,OAAO;AAAA,QAC/B,SAAS,oBAAoB,OAAO;AAAA,MACtC,CAAC;AACD;AAEA,UAAI,aAAa,UAAU,WAAW;AACpC,cAAM,EAAE,MAAM,YAAY,MAAM,aAAa,OAAO,CAAC,EAAE;AACvD,cAAM,WAAW,eAAe,WAAW,WAAW,YAAY,mBAAmB,EAAE;AACvF,cAAM,EAAE,MAAM,YAAY,MAAM,SAAS;AACzC,qBAAa,QAAQ;AAAA,MACvB;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,SAAS,yCAAyC,OAAO,EAAE;AACnE,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,yCAAyC,OAAO,EAAE,EAAE;AAC3F;AAAA,EACF;AAEA,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAAA,EAC/C;AAEA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AACzB,QAAM,EAAE,MAAM,QAAQ,MAAM,EAAE,cAAc,mBAAmB,aAAa,UAAU,KAAK,EAAE;AAC/F;AAEO,IAAMG,YAAkB;AAAA,EAC7B,SAAAN;AAAA,EACA,OAAO;AACT;AAEA,IAAMO,YAAwB;AAAA,EAC5B,SAAAP;AAAA,EACA,QAAQM;AACV;AAEA,IAAO,8BAAQC;;;AC9Vf,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAgBf,IAAMC,YAAyB;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU,gBAAgB;AAAA,EAC1B,UAAU;AAAA,EACV,YAAY,CAAC,MAAM;AAAA,EACnB,YAAY;AAAA,IACV,MAAM;AAAA;AAAA,MAEJ;AAAA,MACA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA,IACF;AAAA;AAAA,IAEA,UAAU,CAAC,WAAW;AAAA,EACxB;AACF;AAOA,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,gBAAS,UAAU,MAAM;AAE/C,QAAM,OAAOA,WAAS,QAAQ,iBAAiB,EAAE,EAAE,KAAK;AACxD,SAAO,QAAQ;AACjB;AAMA,SAAS,sBAAsB,QAA2D;AAGxF,QAAM,iBAAiB,OAAO,MAAM,yCAAyC;AAC7E,MAAI,eAAgB,QAAO,EAAE,MAAM,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,MAAM;AAE5E,QAAM,eAAe,OAAO,MAAM,uCAAuC;AACzE,MAAI,aAAc,QAAO,EAAE,MAAM,aAAa,CAAC,EAAE,KAAK,GAAG,SAAS,KAAK;AAIvE,QAAM,iBAAiB,OAAO,MAAM,uBAAuB;AAC3D,MAAI,eAAgB,QAAO,EAAE,MAAM,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,MAAM;AAE5E,QAAM,eAAe,OAAO,MAAM,sBAAsB;AACxD,MAAI,aAAc,QAAO,EAAE,MAAM,aAAa,CAAC,EAAE,KAAK,GAAG,SAAS,KAAK;AAIvE,QAAM,mBAAmB,OAAO,MAAM,sBAAsB;AAC5D,MAAI,iBAAkB,QAAO,EAAE,MAAM,iBAAiB,CAAC,EAAE,KAAK,GAAG,SAAS,MAAM;AAEhF,QAAM,iBAAiB,OAAO,MAAM,qBAAqB;AACzD,MAAI,eAAgB,QAAO,EAAE,MAAM,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,KAAK;AAI3E,QAAM,mBAAmB,OAAO,MAAM,sBAAsB;AAC5D,MAAI,iBAAkB,QAAO,EAAE,MAAM,iBAAiB,CAAC,EAAE,KAAK,GAAG,SAAS,MAAM;AAEhF,QAAM,iBAAiB,OAAO,MAAM,qBAAqB;AACzD,MAAI,eAAgB,QAAO,EAAE,MAAM,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,KAAK;AAE3E,SAAO;AACT;AAKA,IAAM,gBAAgB;AAAA;AAAA,EAEpB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AACF;AAKA,SAAS,cAAc,MAA2B;AAChD,aAAW,WAAW,eAAe;AACnC,UAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,QAAI,OAAO;AAET,UAAI,YAAY,cAAc,CAAC,KAAK,YAAY,cAAc,CAAC,GAAG;AAEhE,cAAM,OAAO,SAAS,MAAM,CAAC,CAAC;AAC9B,cAAM,QAAQ,SAAS,MAAM,CAAC,CAAC,IAAI;AACnC,cAAM,MAAM,SAAS,MAAM,CAAC,CAAC;AAC7B,eAAO,IAAI,KAAK,MAAM,OAAO,GAAG;AAAA,MAClC,WAAW,YAAY,cAAc,CAAC,GAAG;AAEvC,cAAM,QAAQ,SAAS,MAAM,CAAC,CAAC,IAAI;AACnC,cAAM,MAAM,SAAS,MAAM,CAAC,CAAC;AAC7B,cAAM,OAAO,SAAS,MAAM,CAAC,CAAC;AAC9B,eAAO,IAAI,KAAK,MAAM,OAAO,GAAG;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOA,IAAM,sBAAsB;AAE5B,IAAM,oBAAoB;AAE1B,IAAM,qBAAqB;AAK3B,IAAM,wBAAqD;AAAA;AAAA,EAEzD;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAKA,SAASC,mBAAkB,SAA8B;AAEvD,aAAW,CAAC,SAAS,IAAI,KAAK,OAAO,QAAQ,qBAAqB,GAAG;AACnE,QAAI,YAAY,WAAW,QAAQ,WAAW,OAAO,GAAG;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,QAAQ,WAAW,QAAQ,KAAK,QAAQ,SAAS,iBAAiB,GAAG;AACvE;AAAA,EACF;AAGA;AAAA;AAAA,IAEE,QAAQ,SAAS,mBAAmB;AAAA,IACpC,QAAQ,SAAS,gCAAO;AAAA,IACxB,QAAQ,SAAS,gCAAO;AAAA,IACxB,QAAQ,SAAS,0EAAc;AAAA;AAAA,IAE/B,QAAQ,SAAS,SAAS;AAAA,IAC1B,QAAQ,SAAS,eAAe;AAAA,IAChC,QAAQ,SAAS,cAAI;AAAA,IACrB,QAAQ,SAAS,0BAAM;AAAA,IACvB,QAAQ,SAAS,0BAAM;AAAA,IACvB,QAAQ,SAAS,oBAAK;AAAA,IACtB,QAAQ,SAAS,oBAAK;AAAA,IACtB,QAAQ,SAAS,0EAAc;AAAA;AAAA,IAE/B,QAAQ,SAAS,iBAAiB;AAAA,IAClC,QAAQ,SAAS,oBAAK;AAAA,IACtB,QAAQ,SAAS,gCAAO;AAAA,IACxB,QAAQ,SAAS,0EAAc;AAAA;AAAA,IAE/B,QAAQ,SAAS,sBAAsB;AAAA,IACvC,QAAQ,SAAS,gCAAO;AAAA,IACxB,QAAQ,SAAS,gCAAO;AAAA,IACxB,QAAQ,SAAS,8DAAY;AAAA;AAAA,IAE7B,QAAQ,SAAS,kBAAkB;AAAA,IACnC,YAAY;AAAA,IACZ,QAAQ,SAAS,4CAAS;AAAA,IAC1B,QAAQ,SAAS,gCAAO;AAAA,IACxB,QAAQ,SAAS,8DAAY;AAAA;AAAA,IAE7B,QAAQ,WAAW,YAAY;AAAA,IAC/B;AACA;AAAA,EACF;AAGA,MAAI,QAAQ,MAAM,cAAc,GAAG;AACjC;AAAA,EACF;AAEA;AACF;AAIA,gBAAgB,UAAUC,UAAkE;AAC1F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAIA;AAE1D,QAAM,aAAa,YAAY,QAAQ;AACvC,MAAI,oBAAoB;AAGxB,QAAM,kBAAkB,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE;AACtE,QAAM,EAAE,MAAM,YAAY,MAAM,gBAAgB;AAChD,eAAa,eAAe;AAE5B,UAAQ,QAAQ,8EAAuB,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGhF,QAAM,UAAa,kBAAa,UAAU,OAAO;AAEjD,QAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE,IAAI,CAAC,SAAS,KAAK,QAAQ,OAAO,EAAE,CAAC;AAGvE,MAAI,cAA2B;AAC/B,MAAI,WAAWH,yBAAwB,QAAQ;AAC/C,MAAI,gBAAgB;AACpB,MAAI,kBAAkB;AACtB,QAAM,YAAY,oBAAI,IAA0B;AAChD,QAAM,WAA4B,CAAC;AACnC,MAAI,cAAoC;AACxC,MAAI,YAAY;AAGhB,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,YAAY,MAAM,CAAC,EAAE,KAAK;AAChC,YAAQ,SAAS,6BAAc,SAAS,GAAG;AAC3C,UAAM,eAAe,sBAAsB,SAAS;AACpD,QAAI,cAAc;AAChB,iBAAW,aAAa;AACxB,sBAAgB,CAAC,aAAa;AAC9B,wBAAkB;AAClB,kBAAY;AACZ,cAAQ,SAAS,0DAAkB,aAAa,IAAI,mBAAS,aAAa,OAAO,EAAE;AAAA,IACrF;AAAA,EACF;AAGA,MAAI,CAAC,iBAAiB,MAAM,SAAS,GAAG;AACtC,eAAW,QAAQ,OAAO;AACxB,UAAI,oBAAoB,KAAK,IAAI,GAAG;AAClC,0BAAkB;AAClB,gBAAQ,SAAS,sDAAmB;AACpC;AAAA,MACF;AACA,UAAI,kBAAkB,KAAK,IAAI,GAAG;AAChC,0BAAkB;AAClB,gBAAQ,SAAS,6DAAgB;AACjC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,SAAS,kDAA8B,eAAe,eAAe,SAAS,EAAE;AAGxF,MAAI,gBAAgB;AACpB,WAAS,IAAI,WAAW,IAAI,MAAM,QAAQ,KAAK;AAC7C,UAAM,OAAO,MAAM,CAAC;AAGpB,UAAM,aAAa,cAAc,IAAI;AACrC,QAAI,YAAY;AACd,oBAAc;AACd,UAAI,gBAAgB,GAAG;AACrB,gBAAQ,SAAS,2BAAY,CAAC,MAAM,IAAI,OAAO,WAAW,YAAY,CAAC,EAAE;AAAA,MAC3E;AACA;AAAA,IACF;AAGA,UAAM,aAAa,kBAAkB,sBAAsB;AAC3D,UAAM,WAAW,KAAK,MAAM,UAAU;AAGtC,QAAI,gBAAgB,KAAK,KAAK,KAAK,GAAG;AACpC,cAAQ,SAAS,eAAU,CAAC,OAAO,KAAK,UAAU,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE;AAClF;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,YAAM,CAAC,EAAE,SAAS,QAAQ,UAAU,IAAI;AACxC,UAAII,WAAU,WAAW,KAAK;AAG9B,UAAI,oBAAoB;AACxB,UAAIA,SAAQ,WAAW,GAAG,GAAG;AAC3B,QAAAA,WAAUA,SAAQ,UAAU,CAAC;AAC7B,4BAAoB,CAACA,SAAQ,SAAS,GAAG;AACzC,YAAIA,SAAQ,SAAS,GAAG,GAAG;AACzB,UAAAA,WAAUA,SAAQ,UAAU,GAAGA,SAAQ,SAAS,CAAC;AAAA,QACnD;AAAA,MACF;AAGA,UAAI,QAAQ;AACZ,UAAI,UAAU;AAEd,YAAM,SAAS,QAAQ,MAAM,gBAAgB,IAAI,CAAC;AAClD,YAAM,YAAY,QAAQ,QAAQ,kBAAkB,EAAE;AACtD,YAAM,aAAa,UAAU,MAAM,mCAAmC;AAEtE,UAAI,YAAY;AACd,gBAAQ,SAAS,WAAW,CAAC,CAAC;AAC9B,kBAAU,SAAS,WAAW,CAAC,CAAC;AAChC,cAAM,SAAS,WAAW,CAAC,GAAG,YAAY;AAG1C,YAAI,WAAW,QAAQ,QAAQ,GAAI,UAAS;AAC5C,YAAI,WAAW,QAAQ,UAAU,GAAI,SAAQ;AAG7C,aAAK,WAAW,kBAAQ,WAAW,mBAAS,QAAQ,GAAI,UAAS;AACjE,aAAK,WAAW,kBAAQ,WAAW,mBAAS,UAAU,GAAI,SAAQ;AAAA,MACpE,OAAO;AAEL,cAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAC3C,gBAAQ,MAAM,CAAC;AACf,kBAAU,MAAM,CAAC;AAAA,MACnB;AAEA,UAAI;AAEJ,UAAI,aAAa;AACf,cAAM,UAAU,IAAI,KAAK,WAAW;AACpC,gBAAQ,SAAS,OAAO,SAAS,GAAG,CAAC;AACrC,oBAAY,KAAK,MAAM,QAAQ,QAAQ,IAAI,GAAI;AAAA,MACjD,OAAO;AAEL,cAAM,MAAM,oBAAI,KAAK;AACrB,YAAI,SAAS,OAAO,SAAS,GAAG,CAAC;AACjC,oBAAY,KAAK,MAAM,IAAI,QAAQ,IAAI,GAAI;AAAA,MAC7C;AAGA,YAAM,UAAUF,mBAAkBE,QAAO;AAGzC,UAAI,CAAC,UAAU,IAAI,MAAM,GAAG;AAC1B,kBAAU,IAAI,QAAQ;AAAA,UACpB,YAAY;AAAA,UACZ,aAAa;AAAA,QACf,CAAC;AAAA,MACH;AAGA,oBAAc;AAAA,QACZ,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,QACnB;AAAA,QACA,MAAM;AAAA,QACN,SAASA,YAAW;AAAA,QACpB,oBAAoB;AAAA;AAAA,MACtB;AACA,eAAS,KAAK,WAAW;AACzB;AAGA,UAAI,oBAAoB,QAAS,GAAG;AAClC,cAAM,WAAW;AAAA,UACf;AAAA,UACA;AAAA,UACA,MAAM;AAAA,UACN;AAAA,UACA,sBAAO,iBAAiB;AAAA,QAC1B;AACA,qBAAa,QAAQ;AAAA,MACvB;AAAA,IACF,OAAO;AAEL,YAAM,cAAc,KAAK,MAAM,kBAAkB;AACjD,UAAI,aAAa;AACf,cAAM,CAAC,EAAE,SAAS,UAAU,IAAI;AAChC,cAAMA,WAAU,WAAW,KAAK;AAGhC,YAAI,QAAQ;AACZ,YAAI,UAAU;AAEd,cAAM,SAAS,QAAQ,MAAM,gBAAgB,IAAI,CAAC;AAClD,cAAM,YAAY,QAAQ,QAAQ,kBAAkB,EAAE;AACtD,cAAM,aAAa,UAAU,MAAM,mCAAmC;AAEtE,YAAI,YAAY;AACd,kBAAQ,SAAS,WAAW,CAAC,CAAC;AAC9B,oBAAU,SAAS,WAAW,CAAC,CAAC;AAChC,gBAAM,SAAS,WAAW,CAAC,GAAG,YAAY;AAE1C,cAAI,WAAW,QAAQ,QAAQ,GAAI,UAAS;AAC5C,cAAI,WAAW,QAAQ,UAAU,GAAI,SAAQ;AAE7C,eAAK,WAAW,kBAAQ,WAAW,mBAAS,QAAQ,GAAI,UAAS;AACjE,eAAK,WAAW,kBAAQ,WAAW,mBAAS,UAAU,GAAI,SAAQ;AAAA,QACpE,OAAO;AACL,gBAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAC3C,kBAAQ,MAAM,CAAC;AACf,oBAAU,MAAM,CAAC;AAAA,QACnB;AAEA,YAAI;AACJ,YAAI,aAAa;AACf,gBAAM,UAAU,IAAI,KAAK,WAAW;AACpC,kBAAQ,SAAS,OAAO,SAAS,GAAG,CAAC;AACrC,sBAAY,KAAK,MAAM,QAAQ,QAAQ,IAAI,GAAI;AAAA,QACjD,OAAO;AACL,gBAAM,MAAM,oBAAI,KAAK;AACrB,cAAI,SAAS,OAAO,SAAS,GAAG,CAAC;AACjC,sBAAY,KAAK,MAAM,IAAI,QAAQ,IAAI,GAAI;AAAA,QAC7C;AAGA,sBAAc;AAAA,UACZ,kBAAkB;AAAA,UAClB,mBAAmB;AAAA,UACnB;AAAA,UACA;AAAA,UACA,SAASA,YAAW;AAAA,QACtB;AACA,iBAAS,KAAK,WAAW;AACzB;AAAA,MACF,WAAW,KAAK,KAAK,KAAK,aAAa;AAErC,YAAI,aAAa;AACjB,cAAM,YAAY;AAGlB,YAAI,UAAU,sBAAsB,WAAW,SAAS,GAAG,GAAG;AAC5D,uBAAa,WAAW,UAAU,GAAG,WAAW,SAAS,CAAC;AAC1D,iBAAO,UAAU;AAAA,QACnB;AAEA,YAAI,YAAY,SAAS;AACvB,sBAAY,WAAW,OAAO;AAAA,QAChC,OAAO;AACL,sBAAY,UAAU;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,cAAc,UAAU;AAC9B,QAAM,WAAW,eAAe;AAGhC,QAAM,OAAmB;AAAA,IACvB,MAAM;AAAA,IACN,UAAU,gBAAgB;AAAA,IAC1B,MAAM;AAAA,EACR;AACA,QAAM,EAAE,MAAM,QAAQ,MAAM,KAAK;AAGjC,QAAM,UAAU,MAAM,KAAK,UAAU,OAAO,CAAC;AAC7C,QAAM,EAAE,MAAM,WAAW,MAAM,QAAQ;AAGvC,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,WAAW;AACnD,UAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,SAAS;AAC7C,UAAM,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,EACxC;AAGA,QAAM,eAAe,eAAe,QAAQ,YAAY,YAAY,mBAAmB,EAAE;AACzF,QAAM,EAAE,MAAM,YAAY,MAAM,aAAa;AAC7C,eAAa,YAAY;AAEzB,UAAQ,QAAQ,6BAAS,iBAAiB,wBAAS,WAAW,qBAAM;AAEpE,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,EAAE,cAAc,mBAAmB,YAAY;AAAA,EACvD;AACF;AAIO,IAAMC,YAAkB;AAAA,EAC7B,SAAAN;AAAA,EACA,OAAO;AACT;AAEA,IAAMO,YAAwB;AAAA,EAC5B,SAAAP;AAAA,EACA,QAAQM;AACV;AAEA,IAAO,0BAAQC;;;ACjiBR,IAAM,UAA0B;AAAA,EACrC;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;;;ArBhBA,IAAM,UAAU,cAAc;AAC9B,QAAQ,YAAY,OAAO;AASpB,SAAS,aAAa,UAAwC;AACnE,SAAO,QAAQ,MAAM,QAAQ;AAC/B;AAQO,SAAS,iBAAiB,UAAmC;AAClE,SAAO,QAAQ,SAAS,QAAQ;AAClC;AAcO,SAAS,sBAAuC;AACrD,SAAO,QAAQ,oBAAoB;AACrC;AAMO,SAAS,qBAAqB,UAAwC;AAC3E,QAAM,MAAM,QAAQ,oBAAoB;AACxC,SAAO,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ,KAAK;AAC/C;AAKO,SAAS,gBAAgB,UAAkB;AAChD,QAAMC,YAAU,QAAQ,MAAM,QAAQ;AACtC,MAAI,CAACA,UAAS,QAAO;AAErB,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,QAAQ,OAAOA,UAAQ,EAAE;AAC9D,SAAO,QAAQ,gBAAgB;AACjC;AAQA,eAAsB,kBAAkB,UAA4C;AAClF,QAAMA,YAAU,QAAQ,MAAM,QAAQ;AACtC,MAAI,CAACA,WAAS;AACZ,UAAM,IAAI,MAAM,qDAAa,QAAQ,EAAE;AAAA,EACzC;AAEA,MAAI,CAACA,UAAQ,WAAW;AACtB,UAAM,IAAI,MAAM,iBAAOA,UAAQ,IAAI,8CAAW;AAAA,EAChD;AAEA,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,QAAQ,OAAOA,UAAQ,EAAE;AAC9D,MAAI,CAAC,QAAQ,WAAW;AACtB,UAAM,IAAI,MAAM,iBAAOA,UAAQ,IAAI,mEAAgC;AAAA,EACrE;AAEA,SAAO,OAAO,UAAU,QAAQ;AAClC;AAKO,SAAS,gBAAgB,UAA2B;AACzD,QAAM,eAAe,gBAAgB,QAAQ;AAC7C,MAAI,CAAC,aAAc,QAAO;AAE1B,QAAM,WAAW,YAAY,QAAQ;AACrC,SAAO,aAAa,gBAAgB,UAAU,QAAQ;AACxD;AAOA,gBAAuB,UAAUC,UAAkE;AACjG,QAAMC,WAAS,QAAQ,UAAUD,SAAQ,QAAQ;AACjD,MAAI,CAACC,UAAQ;AACX,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,qDAAaD,SAAQ,QAAQ,EAAE,EAAE;AACxE;AAAA,EACF;AAEA,UAAQ,IAAI,6BAA6BC,SAAO,QAAQ,IAAI,EAAE;AAC9D,SAAOA,SAAO,MAAMD,QAAO;AAC7B;AAQA,gBAAuB,oBACrB,UACAA,UAC2C;AAC3C,QAAMC,WAAS,QAAQ,cAAc,QAAQ;AAC7C,MAAI,CAACA,UAAQ;AACX,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,sCAAa,QAAQ,EAAE,EAAE;AAChE;AAAA,EACF;AAEA,UAAQ,IAAI,wCAAwCA,SAAO,QAAQ,IAAI,EAAE;AACzE,SAAOA,SAAO,MAAMD,QAAO;AAC7B;AA0GO,SAAS,yBAAyB,SAAiB,WAAW,GAAkB;AACrF,WAAS,KAAK,SAAiB,OAA8B;AAC3D,QAAI,QAAQ,SAAU,QAAO;AAC7B,QAAI;AACJ,QAAI;AACF,gBAAa,iBAAY,SAAS,EAAE,eAAe,KAAK,CAAC;AAAA,IAC3D,QAAQ;AACN,aAAO;AAAA,IACT;AACA,eAAW,SAAS,SAAS;AAC3B,YAAM,OAAY,YAAK,SAAS,MAAM,IAAI;AAC1C,UAAI,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,OAAO,GAAG;AAClD,cAAM,SAAS,aAAa,IAAI;AAChC,YAAI,OAAQ,QAAO;AAAA,MACrB;AAAA,IACF;AACA,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,QAAQ,KAAU,YAAK,SAAS,MAAM,IAAI,GAAG,QAAQ,CAAC;AAC5D,YAAI,MAAO,QAAO;AAAA,MACpB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,SAAS,CAAC;AACxB;AAoDA,eAAsB,gBACpB,UACA,WACA,UACe;AACf,QAAM,EAAE,YAAY,QAAQ,WAAW,gBAAgB,OAAO,YAAY,KAAM,cAAc,IAAI;AAGlG,QAAM,YAAY,WACd,oBAAoB,UAAU,EAAE,UAAU,WAAW,eAAe,YAAY,MAAM,CAAC,IACvF,UAAU,EAAE,UAAU,WAAW,eAAe,YAAY,MAAM,CAAC;AAEvE,mBAAiB,SAAS,WAAW;AACnC,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,eAAO,MAAM,IAAI;AACjB;AAAA,MACF,KAAK;AACH,kBAAU,MAAM,IAAI;AACpB;AAAA,MACF,KAAK;AACH,uBAAe,MAAM,IAAI;AACzB;AAAA,MACF,KAAK;AACH,mBAAW,MAAM,IAAI;AACrB;AAAA,MACF,KAAK;AACH,cAAM,MAAM;AAAA,IAChB;AAAA,EACF;AACF;;;AsB1WA,YAAYE,UAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,qBAAqB;AAgB9B,IAAI,UAAyB;AAC7B,IAAM,iBAAiB,oBAAI,IAA6B;AACxD,IAAMC,WAAU,cAAc,YAAY,GAAG;AAKtC,SAAS,WAAW,QAAsB;AAC/C,YAAU;AACZ;AAEO,SAAS,YAA2B;AACzC,SAAO;AACT;AAEA,SAAS,iBAAiB,QAAyB;AACjD,MAAI,CAAC,QAAS,QAAO;AACrB,SAAU,gBAAgB,YAAK,SAAS,GAAG,MAAM,OAAO,CAAC;AAC3D;AAEA,SAAS,oBAAoB,QAA+B;AAC1D,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,WAAgB,YAAK,SAAS,GAAG,MAAM,OAAO;AACpD,MAAI,CAAI,gBAAW,QAAQ,EAAG,QAAO;AACrC,MAAI;AACF,WAAU,kBAAa,QAAQ;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,SAAS,WAAqB,WAA0B;AACtE,QAAM,gBAAgB,aAAa,YAAY,UAAU;AACzD,QAAM,SAAS,eAAe,IAAI,aAAa;AAC/C,MAAI,QAAQ;AACV,QAAI,iBAAiB,aAAa,EAAG,QAAO;AAC5C,mBAAe,OAAO,aAAa;AACnC,YAAQ,IAAI,iDAAiD,aAAa,EAAE;AAAA,EAC9E;AAEA,MAAI;AACF,UAAM,EAAE,MAAM,IAAIA,SAAQ,gBAAgB;AAC1C,QAAIC;AACJ,UAAM,WAAW,oBAAoB,aAAa;AAClD,QAAI,UAAU;AACZ,MAAAA,YAAW,MAAM,SAAS,QAAQ;AAClC,cAAQ,IAAI,4BAA4B,aAAa,KAAK,SAAS,MAAM,SAAS;AAAA,IACpF,OAAO;AACL,MAAAA,YAAW,IAAI,MAAM;AACrB,cAAQ,KAAK,6BAA6B,aAAa,kCAAkC;AAAA,IAC3F;AACA,mBAAe,IAAI,eAAeA,SAAQ;AAC1C,WAAOA;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,2CAA2C,aAAa,MAAM,KAAK;AACjF,UAAM,IAAI,MAAM,+CAAiB,aAAa,GAAG;AAAA,EACnD;AACF;AAKO,SAAS,mBAAmB,UAA0B;AAC3D,iBAAe,OAAO,QAAQ;AAC9B,UAAQ,IAAI,iCAAiC,QAAQ,EAAE;AACzD;AAEA,SAAS,eACP,MACAC,WAA4F,CAAC,GACnF;AACV,QAAM,EAAE,gBAAgB,cAAc,eAAe,WAAW,UAAU,IAAIA;AAC9E,QAAM,UAAU,UAAU,IAAI;AAC9B,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,MAAI;AACF,UAAM,QAAQ,SAAS,QAAQ;AAC/B,QAAI,kBAAkB,MAAO,QAAO,MAAM,IAAI,SAAS,KAAK;AAC5D,UAAM,SAAS,MAAM,IAAI,OAAO;AAChC,UAAM,cAAc,kBAAkB,YAAY,gBAAgB,IAAI,IAAI,aAAa,IAAI;AAC3F,WAAO,OAAO,OAAO,CAAC,SAAS,YAAY,IAAI,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI;AAAA,EACnF,SAAS,OAAO;AACd,YAAQ,MAAM,sCAAsC,KAAK;AACzD,QAAI;AACF,aAAO,SAAS,SAAS,EAAE,IAAI,UAAU,IAAI,GAAG,KAAK;AAAA,IACvD,QAAQ;AACN,aAAO,QAAQ,MAAM,EAAE;AAAA,IACzB;AAAA,EACF;AACF;AAEA,SAAS,eAAe,MAAwB;AAC9C,QAAM,UAAU,UAAU,IAAI;AAC9B,MAAI,CAAC,QAAS,QAAO,CAAC;AACtB,MAAI;AACF,UAAM,YAAY,IAAI,KAAK,UAAU,MAAM,EAAE,aAAa,OAAO,CAAC;AAClE,WAAO,CAAC,GAAG,UAAU,QAAQ,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,YAAY,CAAC;AAAA,EACvG,QAAQ;AACN,WAAO,QACJ,YAAY,EACZ,MAAM,KAAK,EACX,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA,EAC/B;AACF;AAEA,SAAS,gBAAgB,MAAwB;AAC/C,QAAM,UAAU,UAAU,IAAI;AAC9B,MAAI,CAAC,QAAS,QAAO,CAAC;AACtB,MAAI;AACF,UAAM,YAAY,IAAI,KAAK,UAAU,MAAM,EAAE,aAAa,OAAO,CAAC;AAClE,WAAO,CAAC,GAAG,UAAU,QAAQ,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO;AAAA,EACzF,QAAQ;AACN,WAAO,QAAQ,MAAM,EAAE,EAAE,OAAO,CAAC,OAAO,GAAG,KAAK,EAAE,SAAS,CAAC;AAAA,EAC9D;AACF;AAKO,SAAS,QAAQ,MAAc,QAAyBA,WAA0B,CAAC,GAAa;AACrG,QAAM;AAAA,IACJ;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA,kBAAkB;AAAA,IAClB,WAAW;AAAA,EACb,IAAIA;AACJ,QAAMC,aAAY,OAAO,WAAW,IAAI;AACxC,QAAM,aAAa,WAAW;AAC9B,QAAM,mBAAmBA,cAAa,aAAa,IAAI;AACvD,QAAM,qBAAqB,aAAa;AAExC,MAAI;AACJ,MAAIA,YAAW;AACb,YAAQ,eAAe,MAAM,EAAE,eAAe,eAAe,SAAS,CAAC;AAAA,EACzE,WAAW,YAAY;AACrB,YAAQ,gBAAgB,IAAI;AAAA,EAC9B,OAAO;AACL,YAAQ,eAAe,IAAI;AAAA,EAC7B;AAEA,SAAO,MAAM,OAAO,CAAC,SAAS,YAAY,MAAM,QAAQ,oBAAoB,iBAAiB,UAAU,CAAC;AAC1G;AAKO,SAAS,0BACd,OACA,QACAD,WAA+B,CAAC,GACZ;AACpB,QAAM;AAAA,IACJ;AAAA,IACA,WAAW;AAAA,IACX,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAIA;AACJ,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAM,aAAa,cAAc,SAAS,IAAI,IAAI,aAAa,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,IAAI;AAE9F,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,QAAQ,MAAM,QAAQ,EAAE,WAAW,eAAe,eAAe,iBAAiB,SAAS,CAAC;AAC1G,eAAW,QAAQ,OAAO;AACxB,UAAI,cAAc,WAAW,IAAI,KAAK,YAAY,CAAC,EAAG;AACtD,oBAAc,IAAI,OAAO,cAAc,IAAI,IAAI,KAAK,KAAK,CAAC;AAAA,IAC5D;AAAA,EACF;AAEA,QAAM,WAAW,oBAAI,IAAoB;AACzC,MAAI,aAAa;AACjB,aAAW,CAAC,MAAM,KAAK,KAAK,eAAe;AACzC,QAAI,SAAS,UAAU;AACrB,eAAS,IAAI,MAAM,KAAK;AACxB,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,GAAG,SAAS,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;AAChF,SAAO,EAAE,OAAO,IAAI,IAAI,MAAM,GAAG,aAAa,SAAS,MAAM,WAAW;AAC1E;AASO,SAAS,6BACd,OACA,QACAA,WAA+B,CAAC,GAC2B;AAC3D,QAAM;AAAA,IACJ;AAAA,IACA,WAAW;AAAA,IACX,OAAO;AAAA,IACP,gBAAgB;AAAA,IAChB;AAAA,IACA,kBAAkB;AAAA,IAClB,WAAW;AAAA,IACX;AAAA,EACF,IAAIA;AAEJ,QAAM,qBAAqB,aAAa;AACxC,QAAM,cAAc,kBAAkB,YAAY,gBAAgB,IAAI,IAAI,aAAa,IAAI;AAC3F,QAAM,aAAa,cAAc,SAAS,IAAI,IAAI,aAAa,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,IAAI;AAE9F,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAM,cAAc,oBAAI,IAAoB;AAE5C,MAAI;AACF,UAAM,QAAQ,SAAS,QAAQ;AAC/B,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,UAAU,IAAI;AAC9B,UAAI,CAAC,QAAS;AACd,iBAAW,EAAE,KAAK,KAAK,KAAK,MAAM,IAAI,OAAO,GAAG;AAC9C,YAAI,CAAC,YAAY,MAAM,QAAQ,oBAAoB,iBAAiB,UAAU,EAAG;AAEjF,oBAAY,IAAI,MAAM,YAAY,IAAI,GAAG,KAAK,KAAK,CAAC;AAEpD,YAAI,CAAC,YAAY,IAAI,GAAG,EAAG;AAC3B,YAAI,cAAc,WAAW,IAAI,KAAK,YAAY,CAAC,EAAG;AACtD,sBAAc,IAAI,OAAO,cAAc,IAAI,IAAI,KAAK,KAAK,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,kDAAkD,KAAK;AAAA,EACvE;AAEA,QAAM,WAAW,oBAAI,IAAoB;AACzC,MAAI,aAAa;AACjB,aAAW,CAAC,MAAM,KAAK,KAAK,eAAe;AACzC,QAAI,SAAS,UAAU;AACrB,eAAS,IAAI,MAAM,KAAK;AACxB,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,GAAG,SAAS,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;AAChF,SAAO,EAAE,OAAO,IAAI,IAAI,MAAM,GAAG,aAAa,SAAS,MAAM,YAAY,YAAY;AACvF;AAKO,SAAS,mBACd,OACA,gBAAwB,GACxB,kBAA2B,MAC3B,WAAqB,WACA;AACrB,QAAM,WAAW,oBAAI,IAAoB;AACzC,MAAI;AACF,UAAM,QAAQ,SAAS,QAAQ;AAC/B,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,UAAU,IAAI;AAC9B,UAAI,CAAC,QAAS;AACd,YAAM,SAAS,MAAM,IAAI,OAAO;AAChC,iBAAW,QAAQ,QAAQ;AACzB,YAAI,CAAC,YAAY,KAAK,MAAM,SAAS,eAAe,iBAAiB,UAAU,EAAG;AAClF,iBAAS,IAAI,KAAK,MAAM,SAAS,IAAI,KAAK,GAAG,KAAK,KAAK,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,sCAAsC,KAAK;AAAA,EAC3D;AACA,SAAO;AACT;AAQO,SAAS,uBAAqC;AACnD,SAAO;AACT;;;ACrSA,SAAS,kBACP,YACA,UACuC;AACvC,QAAM,aAAuB,CAAC;AAC9B,QAAM,SAAoB,CAAC;AAE3B,MAAI,YAAY,YAAY,QAAW;AACrC,eAAW,KAAK,aAAa;AAC7B,WAAO,KAAK,WAAW,OAAO;AAAA,EAChC;AACA,MAAI,YAAY,UAAU,QAAW;AACnC,eAAW,KAAK,aAAa;AAC7B,WAAO,KAAK,WAAW,KAAK;AAAA,EAC9B;AACA,MAAI,aAAa,UAAa,aAAa,MAAM;AAC/C,eAAW,KAAK,mBAAmB;AACnC,WAAO,KAAK,QAAQ;AAAA,EACtB;AAEA,aAAW,KAAK,4DAAwC;AACxD,aAAW,KAAK,cAAc;AAC9B,aAAW,KAAK,yBAAyB;AACzC,aAAW,KAAK,yBAAyB;AAEzC,SAAO;AAAA,IACL,QAAQ,UAAU,WAAW,KAAK,OAAO,CAAC;AAAA,IAC1C;AAAA,EACF;AACF;AAKO,SAAS,qBAAqB,IAAqB,QAAkD;AAC1G,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB;AAAA,IACA,kBAAkB;AAAA,IAClB,WAAW;AAAA,IACX;AAAA,EACF,IAAI;AAEJ,QAAM,EAAE,QAAQ,QAAQ,aAAa,IAAI,kBAAkB,YAAY,QAAQ;AAE/E,QAAM,WAAW,GACd,QAAQ,4EAA4E,MAAM,EAAE,EAC5F,IAAI,GAAG,YAAY;AAEtB,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,EAAE,OAAO,CAAC,GAAG,YAAY,GAAG,eAAe,GAAG,aAAa,EAAE;AAAA,EACtE;AAEA,QAAM,QAAQ,SAAS,IAAI,CAAC,MAAM,EAAE,OAAO;AAE3C,QAAM,iBAAiB;AAAA,IACrB,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AAGJ,MAAI,OAAO,WAAW,IAAI,KAAK,kBAAkB,OAAO;AACtD,UAAM,WAAW,6BAA6B,OAAO,QAA2B,cAAc;AAC9F,aAAS,EAAE,OAAO,SAAS,OAAO,aAAa,SAAS,aAAa,YAAY,SAAS,WAAW;AACrG,kBAAc,CAAC,GAAG,SAAS,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE;AAAA,EAC1F,OAAO;AACL,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,YAAM,cAAc,mBAAmB,OAAO,iBAAiB,GAAG,iBAAiB,QAAoB;AACvG,oBAAc,CAAC,GAAG,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE;AAAA,IACjF;AACA,aAAS,0BAA0B,OAAO,QAA2B,cAAc;AAAA,EACrF;AAEA,MAAI,iBAAiB;AACrB,aAAW,SAAS,OAAO,MAAM,OAAO,EAAG,mBAAkB;AAE7D,QAAM,QAAQ,CAAC,GAAG,OAAO,MAAM,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO;AAAA,IAChE;AAAA,IACA;AAAA,IACA,YAAY,iBAAiB,IAAI,KAAK,MAAO,QAAQ,iBAAkB,GAAK,IAAI,MAAM;AAAA,EACxF,EAAE;AAEF,SAAO;AAAA,IACL;AAAA,IACA,YAAY,OAAO;AAAA,IACnB,eAAe,SAAS;AAAA,IACxB,aAAa,OAAO;AAAA,IACpB;AAAA,EACF;AACF;AAKO,SAAS,YAAY,MAAc,QAAyB,WAA8B;AAC/F,SAAO,QAAQ,MAAM,QAAQ,EAAE,UAAU,CAAC;AAC5C;;;ACrHO,SAAS,eAAe,MAAyC;AACtE,MAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO;AAE9C,MAAI;AACF,UAAM,QAAQ,SAAS;AACvB,UAAM,SAAS,MAAM,IAAI,MAAM,KAAK;AACpC,WAAO,OACJ,IAAI,CAACE,OAAMA,GAAE,KAAK,EAAE,YAAY,CAAC,EACjC,OAAO,CAACA,OAAMA,GAAE,SAAS,CAAC,EAC1B,KAAK,GAAG;AAAA,EACb,QAAQ;AACN,WAAO,iBAAiB,IAAI;AAAA,EAC9B;AACF;AAEA,SAAS,iBAAiB,MAAsB;AAC9C,SAAO,KACJ,YAAY,EACZ,MAAM,KAAK,EACX,OAAO,CAACA,OAAMA,GAAE,SAAS,CAAC,EAC1B,KAAK,GAAG;AACb;AAEA,SAAS,YAAY,OAAuB;AAC1C,SAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;AACtC;AAQO,SAAS,oBAAoB,UAA4B;AAC9D,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,QAAM,SAAS,SACZ,IAAI,CAAC,OAAO;AACX,UAAM,UAAU,GAAG,KAAK;AACxB,QAAI,CAAC,QAAS,QAAO;AAErB,QAAI;AACF,YAAM,QAAQ,SAAS;AACvB,YAAM,SAAS,MACZ,IAAI,SAAS,KAAK,EAClB,IAAI,CAACA,OAAMA,GAAE,KAAK,EAAE,YAAY,CAAC,EACjC,OAAO,CAACA,OAAMA,GAAE,SAAS,CAAC;AAE7B,UAAI,OAAO,WAAW,EAAG,QAAO;AAChC,UAAI,OAAO,WAAW,EAAG,QAAO,YAAY,OAAO,CAAC,CAAC;AACrD,aAAO,OAAO,IAAI,WAAW,EAAE,KAAK,GAAG;AAAA,IACzC,QAAQ;AACN,YAAM,SAAS,QAAQ,YAAY,EAAE,KAAK;AAC1C,aAAO,SAAS,YAAY,MAAM,IAAI;AAAA,IACxC;AAAA,EACF,CAAC,EACA,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAE7B,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,MAAI,OAAO,WAAW,EAAG,QAAO,OAAO,CAAC;AAExC,SAAO,OAAO,IAAI,CAAC,MAAO,EAAE,SAAS,GAAG,IAAI,IAAI,CAAC,MAAM,CAAE,EAAE,KAAK,MAAM;AACxE;;;ACnEA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,cAAAC,mBAAkB;AAI3B,IAAM,yBAAyB;AAC/B,IAAM,cAAsC;AAAA,EAC1C,SAAS;AAAA,EACT,SAAS;AACX;AAEA,IAAM,kBAAwE;AAAA,EAC5E,EAAE,IAAI,SAAS,OAAO,4BAAQ,QAAQ,QAAQ;AAAA,EAC9C,EAAE,IAAI,SAAS,OAAO,4BAAQ,QAAQ,QAAQ;AAChD;AAEA,SAAS,gBAAgB,QAAgB,QAAwB;AAC/D,SAAY,YAAK,QAAQ,GAAG,MAAM,OAAO;AAC3C;AAEO,SAAS,iBAAiB,QAAgB,QAAyB;AACxE,SAAU,gBAAW,gBAAgB,QAAQ,MAAM,CAAC;AACtD;AAEO,SAAS,YAAY,QAA4B;AACtD,SAAO,gBAAgB,IAAI,CAAC,MAAM;AAChC,UAAM,WAAW,gBAAgB,QAAQ,EAAE,EAAE;AAC7C,UAAM,aAAgB,gBAAW,QAAQ;AACzC,QAAI;AACJ,QAAI,YAAY;AACd,UAAI;AACF,mBAAc,cAAS,QAAQ,EAAE;AAAA,MACnC,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO,EAAE,GAAG,GAAG,YAAY,SAAS;AAAA,EACtC,CAAC;AACH;AAEO,SAAS,eAAe,QAAgB,QAA+B;AAC5E,QAAM,WAAW,gBAAgB,QAAQ,MAAM;AAC/C,MAAI,CAAI,gBAAW,QAAQ,EAAG,QAAO;AACrC,MAAI;AACF,WAAU,kBAAa,QAAQ;AAAA,EACjC,SAAS,OAAO;AACd,YAAQ,MAAM,+CAA+C,QAAQ,IAAI,KAAK;AAC9E,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,aACpB,QACA,QACA,YAC+C;AAC/C,MAAI,CAAC,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,GAAG;AACjD,WAAO,EAAE,SAAS,OAAO,OAAO,iBAAiB,MAAM,GAAG;AAAA,EAC5D;AAEA,EAAG,eAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AACxC,QAAM,MAAM,GAAG,sBAAsB,IAAI,MAAM;AAC/C,QAAM,WAAW,gBAAgB,QAAQ,MAAM;AAC/C,QAAM,UAAU,WAAW;AAE3B,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,IAAO,EAAE,CAAC;AAC1E,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,yBAAyB,SAAS,MAAM,EAAE;AAE5E,UAAM,QAAQ,OAAO,SAAS,QAAQ,IAAI,gBAAgB,KAAK,CAAC;AAChE,QAAI;AAEJ,QAAI,CAAC,SAAS,MAAM;AAClB,eAAS,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC;AAAA,IACnD,OAAO;AACL,YAAM,SAAS,SAAS,KAAK,UAAU;AACvC,YAAM,SAAmB,CAAC;AAC1B,UAAI,SAAS;AACb,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AACV,cAAM,QAAQ,OAAO,KAAK,KAAK;AAC/B,eAAO,KAAK,KAAK;AACjB,kBAAU,MAAM;AAChB,YAAI,QAAQ,KAAK,WAAY,YAAW,KAAK,MAAO,SAAS,QAAS,GAAG,CAAC;AAAA,MAC5E;AACA,eAAS,OAAO,OAAO,MAAM;AAAA,IAC/B;AAEA,UAAM,gBAAgB;AACtB,QAAI,OAAO,SAAS,eAAe;AACjC,aAAO,EAAE,SAAS,OAAO,OAAO,+BAA+B,OAAO,MAAM,UAAU;AAAA,IACxF;AACA,UAAM,OAAO,OAAO,SAAS,GAAG,EAAE,EAAE,SAAS,OAAO,EAAE,KAAK;AAC3D,QAAI,KAAK,WAAW,IAAI,KAAK,KAAK,WAAW,OAAO,GAAG;AACrD,aAAO,EAAE,SAAS,OAAO,OAAO,iDAAiD;AAAA,IACnF;AAEA,UAAM,iBAAiB,YAAY,MAAM;AACzC,QAAI,CAAC,gBAAgB;AACnB,aAAO,EAAE,SAAS,OAAO,OAAO,qCAAqC,MAAM,GAAG;AAAA,IAChF;AACA,UAAM,eAAeC,YAAW,QAAQ,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK;AACrE,QAAI,iBAAiB,gBAAgB;AACnC,aAAO,EAAE,SAAS,OAAO,OAAO,sDAAsD;AAAA,IACxF;AAEA,IAAG,mBAAc,SAAS,MAAM;AAChC,QAAO,gBAAW,QAAQ,EAAG,CAAG,gBAAW,QAAQ;AACnD,IAAG,gBAAW,SAAS,QAAQ;AAC/B,uBAAmB,MAAa;AAEhC,YAAQ,IAAI,sCAAsC,MAAM,KAAQ,cAAS,QAAQ,EAAE,IAAI,SAAS;AAChG,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB,SAAS,OAAO;AACd,QAAO,gBAAW,OAAO,GAAG;AAC1B,UAAI;AACF,QAAG,gBAAW,OAAO;AAAA,MACvB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,YAAQ,MAAM,yCAAyC,MAAM,KAAK,GAAG;AACrE,WAAO,EAAE,SAAS,OAAO,OAAO,IAAI;AAAA,EACtC;AACF;AAEO,SAAS,WAAW,QAAgB,QAAsD;AAC/F,QAAM,WAAW,gBAAgB,QAAQ,MAAM;AAC/C,MAAI,CAAI,gBAAW,QAAQ,EAAG,QAAO,EAAE,SAAS,KAAK;AACrD,MAAI;AACF,IAAG,gBAAW,QAAQ;AACtB,uBAAmB,MAAa;AAChC,YAAQ,IAAI,mCAAmC,MAAM,EAAE;AACvD,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,YAAQ,MAAM,uCAAuC,MAAM,KAAK,GAAG;AACnE,WAAO,EAAE,SAAS,OAAO,OAAO,IAAI;AAAA,EACtC;AACF;AAKA,eAAsB,kBAAkB,QAA+B;AACrE,MAAI,iBAAiB,QAAQ,OAAO,EAAG;AACvC,UAAQ,IAAI,yEAAyE;AACrF,QAAM,SAAS,MAAM,aAAa,QAAQ,OAAO;AACjD,MAAI,OAAO,SAAS;AAClB,YAAQ,IAAI,2DAA2D;AAAA,EACzE,OAAO;AACL,YAAQ,KAAK,sDAAsD,OAAO,KAAK;AAAA,EACjF;AACF;;;ACxJA,IAAM,aAAa;AAKZ,SAAS,YAAY,IAA8B;AACxD,MAAI;AACF,UAAM,MAAM,GAAG,QAAQ,0EAA0E,EAAE,IAAI;AACvG,WAAO,CAAC,CAAC;AAAA,EACX,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,eAAe,IAA2B;AACxD,KAAG,KAAK,gBAAgB;AAC1B;AAMO,SAAS,cAAc,IAA0C;AACtE,iBAAe,EAAE;AAEjB,QAAM,YAAY,GAAG,QAAQ,uDAAuD;AAEpF,QAAM,WAAW,GACd,QAAQ,gGAAgG,EACxG,IAAI;AACP,QAAM,QAAQ,UAAU,SAAS;AAEjC,MAAI,UAAU;AACd,MAAI,SAAS;AAEb,SAAO,SAAS,OAAO;AACrB,UAAM,OAAO,GACV;AAAA,MACC;AAAA;AAAA;AAAA,IAGF,EACC,IAAI,YAAY,MAAM;AAEzB,QAAI,KAAK,WAAW,EAAG;AAEvB,OAAG,YAAY,MAAM;AACnB,iBAAW,OAAO,MAAM;AACtB,cAAM,SAAS,eAAe,IAAI,OAAO;AACzC,YAAI,QAAQ;AACV,oBAAU,IAAI,IAAI,IAAI,MAAM;AAAA,QAC9B;AAAA,MACF;AAAA,IACF,CAAC;AAED,eAAW,KAAK;AAChB,cAAU;AAAA,EACZ;AAEA,SAAO,EAAE,QAAQ;AACnB;AAKO,SAAS,gBAAgB,IAA0C;AACxE,MAAI,YAAY,EAAE,GAAG;AACnB,OAAG,KAAK,wBAAwB;AAAA,EAClC;AACA,SAAO,cAAc,EAAE;AACzB;AAMO,SAAS,iBAAiB,IAAqB,SAA8D;AAClH,MAAI,CAAC,YAAY,EAAE,EAAG;AAEtB,QAAM,YAAY,GAAG,QAAQ,uDAAuD;AAEpF,KAAG,YAAY,MAAM;AACnB,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,SAAS;AACjB,cAAM,SAAS,eAAe,MAAM,OAAO;AAC3C,YAAI,QAAQ;AACV,oBAAU,IAAI,MAAM,IAAI,MAAM;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAKO,SAAS,YACd,IACA,UACA,QAAQ,KACR,SAAS,GAC4B;AACrC,MAAI,SAAS,WAAW,EAAG,QAAO,EAAE,QAAQ,CAAC,GAAG,OAAO,EAAE;AAEzD,QAAM,aAAa,oBAAoB,QAAQ;AAC/C,MAAI,CAAC,WAAY,QAAO,EAAE,QAAQ,CAAC,GAAG,OAAO,EAAE;AAE/C,MAAI;AACF,UAAM,WAAW,GAAG,QAAQ,iEAAiE,EAAE,IAAI,UAAU;AAG7G,UAAM,QAAQ,UAAU,SAAS;AAEjC,UAAM,OAAO,GACV,QAAQ,oFAAoF,EAC5F,IAAI,YAAY,OAAO,MAAM;AAEhC,WAAO;AAAA,MACL,QAAQ,KAAK,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,MAC/B;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,+BAA+B,YAAY,KAAK;AAC9D,WAAO,EAAE,QAAQ,CAAC,GAAG,OAAO,EAAE;AAAA,EAChC;AACF;;;AClHA,YAAYC,UAAQ;AA0DpB,IAAM,oBAAoB;AAC1B,IAAM,sBAAsB;AAE5B,SAAS,2BAAmC;AAC1C,QAAM,KAAK,KAAK,IAAI;AACpB,QAAM,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,CAAC;AACtD,SAAO,QAAQ,EAAE,IAAI,IAAI;AAC3B;AAMA,eAAsB,gBACpB,UACA,MACA,eACA,mBAC6B;AAC7B,MAAI,eAAe,UAAU;AAC3B,UAAM,WAAW,cAAc;AAC/B,UAAMC,YAAU,qBAAqB,QAAQ;AAC7C,QAAI,CAACA,WAAS;AACZ,aAAO,EAAE,SAAS,OAAO,OAAO,0BAA0B;AAAA,IAC5D;AACA,WAAO,mBAAmB,UAAU,MAAMA,WAAS,eAAe,iBAAiB;AAAA,EACrF;AAEA,QAAM,aAAa,iBAAiB,QAAQ;AAC5C,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,SAAS,OAAO,OAAO,4BAA4B;AAAA,EAC9D;AAEA,MAAI,WAAW,SAAS,GAAG;AACzB,WAAO,yBAAyB,UAAU,MAAM,YAAY,eAAe,iBAAiB;AAAA,EAC9F;AAEA,SAAO,mBAAmB,UAAU,MAAM,WAAW,CAAC,GAAG,eAAe,iBAAiB;AAC3F;AAEA,eAAe,yBACb,UACA,MACA,YACA,eACA,mBAC6B;AAC7B,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,UAAM,YAAY,WAAW,CAAC;AAC9B,SAAK,QAAQ,KAAK,gCAAgC,IAAI,CAAC,IAAI,WAAW,MAAM,KAAK,UAAU,IAAI,KAAK,UAAU,EAAE,GAAG;AAEnH,UAAM,SAAS,MAAM,mBAAmB,UAAU,MAAM,WAAW,eAAe,iBAAiB;AAEnG,QAAI,OAAO,SAAS;AAClB,UAAI,IAAI,GAAG;AACT,aAAK,QAAQ;AAAA,UACX,sCAAsC,UAAU,IAAI,WAAW,CAAC,kBAAkB,IAAI,IAAI,MAAM,EAAE;AAAA,QACpG;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,WAAW,SAAS,EAAG,QAAO;AAExC,SAAK,QAAQ,KAAK,yBAAyB,UAAU,IAAI,yDAAyD;AAAA,EACpH;AAEA,SAAO,EAAE,SAAS,OAAO,OAAO,oBAAoB;AACtD;AAEA,eAAe,mBACb,UACA,MACA,eACA,eACA,mBAC6B;AAC7B,QAAM,EAAE,YAAY,OAAO,IAAI;AAC/B,QAAM,QAAQ,KAAK,qBAAqB;AAExC,UAAQ,MAAM;AACd,QAAM,YAAY,qBAAqB,MAAM;AAC7C,UAAQ,KAAK,SAAS;AAEtB,UAAQ,KAAK,cAAc,QAAQ,EAAE;AACrC,UAAQ,KAAK,oBAAoB,cAAc,IAAI,KAAK,cAAc,EAAE,GAAG;AAC3E,UAAQ,KAAK,aAAa,cAAc,QAAQ,EAAE;AAClD,UAAQ,KAAK,kBAAkB,CAAC;AAGhC,MAAI,iBAAiB;AACrB,MAAI,eAA8B;AAClC,QAAM,eAAe,gBAAgB,QAAQ;AAE7C,MAAI,gBAAgB,gBAAgB,QAAQ,GAAG;AAC7C,YAAQ,KAAK,qDAAqD;AAClE,eAAW;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,YAAY;AAAA,MACZ,SAAS;AAAA,IACX,CAAC;AAED,QAAI;AACF,qBAAe,MAAM,aAAa,WAAW,UAAU,CAAC,aAA4B;AAClF,mBAAW,EAAE,GAAG,UAAU,SAAS,GAAG,CAAC;AAAA,MACzC,CAAC;AACD,uBAAiB;AACjB,cAAQ,KAAK,kCAAkC,YAAY,EAAE;AAAA,IAC/D,SAAS,KAAK;AACZ,YAAM,WAAW,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC1F,cAAQ,MAAM,UAAU,eAAe,QAAQ,MAAM,MAAS;AAC9D,aAAO,EAAE,SAAS,OAAO,OAAO,SAAS;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,KAAK,KAAK,aAAa,SAAS;AAEtC,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA,EACF;AACA,QAAM,eAAe,GAAG;AAAA,IACtB;AAAA,EACF;AACA,QAAM,cAAc,GAAG,QAAQ,6CAA6C;AAC5E,QAAM,gBAAgB,GAAG;AAAA,IACvB;AAAA;AAAA,EAEF;AACA,QAAM,oBAAoB,GAAG;AAAA,IAC3B;AAAA,EACF;AACA,QAAM,0BAA0B,GAAG,QAAQ,0DAA0D;AACrG,QAAM,4BAA4B,GAAG,QAAQ,4DAA4D;AAEzG,QAAM,cAAc,oBAAI,IAAoB;AAC5C,QAAM,qBAAqB,oBAAI,IAG7B;AACF,QAAM,uBAAuB,oBAAI,IAG/B;AAEF,MAAI,eAAe;AACnB,MAAI,sBAAsB;AAC1B,MAAI,oBAAoB;AACxB,MAAI,sBAAsB;AAC1B,MAAI,gBAAgB;AAEpB,QAAM,mBAAmB,MAAM;AAC7B,QAAI,CAAC,eAAe;AAClB,SAAG,KAAK,mBAAmB;AAC3B,sBAAgB;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,eAAe,MAAM;AACzB,QAAI;AACF,SAAG,OAAO,0BAA0B;AAAA,IACtC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAM;AAC9B,QAAI,eAAe;AACjB,SAAG,KAAK,QAAQ;AAChB,sBAAgB;AAChB,cAAQ,KAAK,sBAAsB,mBAAmB,iBAAiB;AAEvE,UAAI,oBAAoB,uBAAuB,qBAAqB;AAClE,qBAAa;AACb,gBAAQ,KAAK,kBAAkB,iBAAiB;AAChD,8BAAsB;AAAA,MACxB;AAEA,iBAAW;AAAA,QACT,OAAO;AAAA,QACP,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,mBAAmB;AAAA,QACnB,YAAY;AAAA,QACZ,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,qBAAiB;AAAA,EACnB;AAEA,mBAAiB;AAEjB,MAAI,iBAAiB;AACrB,MAAI,cAA6B;AAEjC,QAAM,gBAAgB;AAAA,IACpB,iBAAiB;AAAA,IACjB,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,sBAAsB;AAAA,IACtB,uBAAuB;AAAA,IACvB,mBAAmB;AAAA,IACnB,sBAAsB;AAAA,IACtB,yBAAyB;AAAA,IACzB,eAAe;AAAA,EACjB;AAEA,UAAQ,KAAK,6BAA6B;AAE1C,MAAI;AACF,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,WAAW;AAAA,QACX;AAAA,QAEA,YAAY,CAAC,aAA4B;AACvC,wBAAc;AACd,qBAAW,QAAQ;AAAA,QACrB;AAAA,QAEA,OAAO,CAAC,OAAe,YAAoB;AACzC,wBAAc;AACd,cAAI,UAAU,SAAS;AACrB,oBAAQ,MAAM,OAAO;AAAA,UACvB,OAAO;AACL,oBAAQ,KAAK,OAAO;AAAA,UACtB;AAAA,QACF;AAAA,QAEA,QAAQ,CAAC,SAAqB;AAC5B,wBAAc;AACd,cAAI,CAAC,cAAc;AACjB,oBAAQ,KAAK,sBAAsB,KAAK,IAAI,UAAU,KAAK,IAAI,cAAc,KAAK,QAAQ,EAAE;AAC5F,uBAAW;AAAA,cACT,KAAK;AAAA,cACL,KAAK;AAAA,cACL,KAAK;AAAA,cACL,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,cAC5B,KAAK,WAAW;AAAA,cAChB,KAAK,eAAe;AAAA,cACpB,KAAK,WAAW;AAAA,YAClB;AACA,2BAAe;AAAA,UACjB;AAAA,QACF;AAAA,QAEA,WAAW,CAAC,YAA4B;AACtC,wBAAc;AACd,wBAAc,wBAAwB,QAAQ;AAC9C,kBAAQ,KAAK,0BAA0B,QAAQ,MAAM,UAAU;AAC/D,qBAAW,UAAU,SAAS;AAC5B,yBAAa;AAAA,cACX,OAAO;AAAA,cACP,OAAO,eAAe;AAAA,cACtB,OAAO,iBAAiB;AAAA,cACxB,OAAO,UAAU;AAAA,cACjB,OAAO,QAAQ,KAAK,UAAU,OAAO,KAAK,IAAI;AAAA,YAChD;AACA,kBAAM,MAAM,YAAY,IAAI,OAAO,UAAU;AAC7C,gBAAI,IAAK,aAAY,IAAI,OAAO,YAAY,IAAI,EAAE;AAAA,UACpD;AAAA,QACF;AAAA,QAEA,gBAAgB,CAAC,aAA8B;AAC7C,wBAAc;AACd,wBAAc,yBAAyB,SAAS;AAChD,cAAI,cAAc,uBAAuB,KAAK,cAAc,sBAAsB,OAAO,GAAG;AAC1F,oBAAQ,KAAK,2BAA2B,cAAc,mBAAmB,KAAK,SAAS,MAAM,WAAW;AAAA,UAC1G;AAEA,cAAI,mBAAmB;AACvB,cAAI,mBAAmB;AACvB,cAAI,oBAAoB;AACxB,cAAI,oBAAoB;AACxB,cAAI,oBAAoB;AACxB,cAAI,oBAAoB;AACxB,cAAI,sBAAsB;AAE1B,qBAAW,OAAO,UAAU;AAC1B,gBAAI,CAAC,IAAI,kBAAkB;AACzB,4BAAc;AACd;AAAA,YACF;AACA,gBAAI,CAAC,IAAI,mBAAmB;AAC1B,4BAAc;AACd;AAAA,YACF;AACA,gBAAI,IAAI,cAAc,UAAa,IAAI,cAAc,QAAQ,MAAM,IAAI,SAAS,GAAG;AACjF,4BAAc;AACd;AAAA,YACF;AACA,gBAAI,IAAI,SAAS,UAAa,IAAI,SAAS,MAAM;AAC/C,4BAAc;AACd;AAAA,YACF;AAEA,gBAAI,KAAK,KAAK,IAAI;AAClB,gBAAI,CAAC,YAAY,IAAI,IAAI,gBAAgB,GAAG;AAC1C,2BAAa;AAAA,gBACX,IAAI;AAAA,gBACJ,IAAI,qBAAqB;AAAA,gBACzB,IAAI,uBAAuB;AAAA,gBAC3B;AAAA,gBACA;AAAA,cACF;AACA,oBAAM,MAAM,YAAY,IAAI,IAAI,gBAAgB;AAChD,kBAAI,IAAK,aAAY,IAAI,IAAI,kBAAkB,IAAI,EAAE;AACrD;AACA,kCAAoB,KAAK,IAAI,IAAI;AAAA,YACnC,OAAO;AACL;AACA,kCAAoB,KAAK,IAAI,IAAI;AAAA,YACnC;AAEA,kBAAM,WAAW,YAAY,IAAI,IAAI,gBAAgB;AACrD,gBAAI,aAAa,OAAW;AAE5B,gBAAI,cAA6B;AACjC,gBAAI,IAAI,WAAW,MAAM;AACvB,4BAAc,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,KAAK,UAAU,IAAI,OAAO;AAAA,YAC1F;AAEA,iBAAK,KAAK,IAAI;AACd,0BAAc;AAAA,cACZ;AAAA,cACA,IAAI,qBAAqB;AAAA,cACzB,IAAI,uBAAuB;AAAA,cAC3B,IAAI;AAAA,cACJ,IAAI;AAAA,cACJ;AAAA,cACA,IAAI,oBAAoB;AAAA,cACxB,IAAI,qBAAqB;AAAA,YAC3B;AACA,iCAAqB,KAAK,IAAI,IAAI;AAClC;AACA;AAEA,iBAAK,KAAK,IAAI;AACd,0BAAc,oBAAoB,IAAI,kBAAkB,IAAI,mBAAmB,IAAI,SAAS;AAC5F,0BAAc,sBAAsB,IAAI,kBAAkB,IAAI,qBAAqB,IAAI,SAAS;AAChG,iCAAqB,KAAK,IAAI,IAAI;AAElC,mCAAuB,mBAAmB,IAAI,IAAI,gBAAgB,GAAG,QAAQ,WAAW,IAAI,IAAI;AAEhG,gBAAI,uBAAuB,mBAAmB;AAC5C,oBAAM,SACJ,2BAA2B,gBAAgB,OAAO,iBAAiB,4BACjD,gBAAgB,OAAO,iBAAiB,6BACvC,iBAAiB,2BACd,iBAAiB,OAAO,mBAAmB;AACnE,sBAAQ,WAAW,MAAM;AACzB,gCAAkB;AAClB,oCAAsB;AACtB,iCAAmB;AACnB,iCAAmB;AACnB,kCAAoB;AACpB,kCAAoB;AACpB,kCAAoB;AACpB,kCAAoB;AACpB,oCAAsB;AAAA,YACxB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,cAAc;AAAA,IAChB;AAEA,QAAI,eAAe;AACjB,SAAG,KAAK,QAAQ;AAChB,sBAAgB;AAAA,IAClB;AAGA,eAAW;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,YAAY;AAAA,MACZ,SAAS;AAAA,IACX,CAAC;AACD,YAAQ,KAAK,4BAA4B,iBAAiB;AAE1D,OAAG,KAAK,mBAAmB;AAC3B,QAAI,eAAe;AAEnB,yBAAqB,oBAAoB,gBAAgB,aAAa,mBAAmB,uBAAuB;AAChH;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,mBAAe,aAAa,kBAAkB,IAAI,aAAa,oBAAoB;AAEnF,OAAG,KAAK,QAAQ;AAChB,YAAQ,KAAK,6BAA6B,YAAY,aAAa,iBAAiB;AAGpF,eAAW;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,YAAY;AAAA,MACZ,SAAS;AAAA,IACX,CAAC;AACD,YAAQ,KAAK,oBAAoB,iBAAiB;AAClD,OAAG,KAAK,eAAe;AACvB,YAAQ,KAAK,mBAAmB,iBAAiB;AAGjD,QAAI;AACF,oBAAc,EAAE;AAChB,cAAQ,KAAK,mBAAmB,iBAAiB;AAAA,IACnD,SAAS,UAAU;AACjB,cAAQ,MAAM,sCAAsC,oBAAoB,QAAQ,WAAW,MAAS;AAAA,IACtG;AAGA,eAAW;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,YAAY;AAAA,MACZ,SAAS;AAAA,IACX,CAAC;AACD,iBAAa;AACb,YAAQ,KAAK,uBAAuB,iBAAiB;AAGrD,QAAI;AACF,2BAAqB,EAAE;AACvB,cAAQ,KAAK,uBAAuB,iBAAiB;AAAA,IACvD,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,YAAM,KAAK,iBAAiB,IAAI,SAAS;AACzC,UAAI,KAAK,eAAgB,SAAQ,KAAK,yBAAyB,iBAAiB;AAAA,IAClF,QAAQ;AAAA,IAER;AAEA,YAAQ,KAAK,oBAAoB,iBAAiB;AAGlD,YAAQ,KAAK,+BAA+B;AAC5C,YAAQ,KAAK,qBAAqB,cAAc,eAAe,EAAE;AACjE,YAAQ,KAAK,gBAAgB,cAAc,UAAU,EAAE;AACvD,YAAQ,KAAK,iBAAiB,cAAc,WAAW,EAAE;AACzD,YAAQ;AAAA,MACN,oBAAoB,cAAc,cAAc,oBAAoB,cAAc,oBAAoB;AAAA,IACxG;AACA,YAAQ;AAAA,MACN,yBAAyB,cAAc,mBAAmB,qBAAqB,cAAc,qBAAqB;AAAA,IACpH;AACA,QACE,cAAc,oBAAoB,KAClC,cAAc,uBAAuB,KACrC,cAAc,0BAA0B,KACxC,cAAc,gBAAgB,GAC9B;AACA,cAAQ,KAAK,gCAAgC;AAC7C,UAAI,cAAc,oBAAoB;AACpC,gBAAQ,KAAK,+BAA+B,cAAc,iBAAiB,EAAE;AAC/E,UAAI,cAAc,uBAAuB;AACvC,gBAAQ,KAAK,gCAAgC,cAAc,oBAAoB,EAAE;AACnF,UAAI,cAAc,0BAA0B;AAC1C,gBAAQ,KAAK,wBAAwB,cAAc,uBAAuB,EAAE;AAC9E,UAAI,cAAc,gBAAgB,EAAG,SAAQ,KAAK,mBAAmB,cAAc,aAAa,EAAE;AAAA,IACpG;AAEA,YAAQ,QAAQ,mBAAmB,YAAY,IAAI;AAEnD,QAAI,sBAAsB,GAAG;AAC3B,cAAQ;AAAA,QACN,+CAA+C,cAAc,qBAAqB;AAAA,MACpF;AACA,uBAAiB;AACjB,oBAAc;AAAA,IAChB;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,iBAAiB,iBAAiB,QAAQ,QAAQ,MAAS;AACzE,QAAI,eAAe;AACjB,UAAI;AACF,WAAG,KAAK,UAAU;AAAA,MACpB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,qBAAiB;AACjB,kBAAc,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,EACrE,UAAE;AACA,OAAG,MAAM;AAET,QAAI,gBAAgB,cAAc;AAChC,mBAAa,QAAQ,YAAY;AAAA,IACnC;AAEA,QAAI,gBAAgB;AAClB,WAAK,eAAe,SAAS;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,cAAiC;AAAA,IACrC,SAAS,QAAQ,kBAAkB,KAAK;AAAA,IACxC,gBAAgB,gBAAgB,GAAG,cAAc,IAAI,KAAK,cAAc,EAAE,MAAM;AAAA,IAChF,kBAAkB,cAAc;AAAA,IAChC,iBAAiB;AAAA,IACjB,iBACE,cAAc,oBACd,cAAc,uBACd,cAAc,0BACd,cAAc;AAAA,IAChB,aAAa;AAAA,MACX,YAAY,cAAc;AAAA,MAC1B,eAAe,cAAc;AAAA,MAC7B,kBAAkB,cAAc;AAAA,MAChC,QAAQ,cAAc;AAAA,IACxB;AAAA,EACF;AAEA,MAAI,aAAa;AACf,WAAO,EAAE,SAAS,OAAO,OAAO,aAAa,YAAY;AAAA,EAC3D;AACA,SAAO,EAAE,SAAS,MAAM,WAAW,YAAY;AACjD;AAWA,eAAsB,iBACpB,UACA,YACiC;AACjC,QAAM,gBAAgB,aAAa,QAAQ;AAC3C,MAAI,CAAC,eAAe;AAClB,WAAO,EAAE,eAAe,GAAG,cAAc,GAAG,MAAM,MAAM,OAAO,4BAA4B;AAAA,EAC7F;AAEA,MAAI,OAAgE;AACpE,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,gBAAgB;AAEpB,QAAM,gBAAgB,UAAU;AAAA,IAC9B,QAAQ,CAAC,eAA2B;AAClC,aAAO,EAAE,MAAM,WAAW,MAAM,UAAU,WAAW,UAAU,MAAM,WAAW,KAAK;AAAA,IACvF;AAAA,IACA,WAAW,CAAC,YAA4B;AACtC,iBAAW,KAAK,QAAS,WAAU,IAAI,EAAE,UAAU;AAAA,IACrD;AAAA,IACA,YAAY,CAAC,aAA4B;AACvC,iBAAW,QAAQ;AAAA,IACrB;AAAA,IACA,gBAAgB,CAAC,UAA2B;AAC1C,iBAAW,OAAO,OAAO;AACvB;AACA,YAAI,CAAC,UAAU,IAAI,IAAI,gBAAgB,EAAG,WAAU,IAAI,IAAI,gBAAgB;AAAA,MAC9E;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,eAAe,cAAc,UAAU,MAAM,KAAK;AAC7D;AAmBA,eAAsB,oBACpB,UACA,MACoC;AACpC,QAAM,gBAAgB,aAAa,QAAQ;AAC3C,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAEA,QAAM,WAAc,cAAS,QAAQ,EAAE;AAEvC,OAAK,WAAW;AAAA,IACd,OAAO;AAAA,IACP,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,YAAY;AAAA,IACZ,SAAS;AAAA,EACX,CAAC;AAED,QAAM,EAAE,IAAI,WAAW,IAAI,KAAK,mBAAmB,QAAQ;AAE3D,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA,EACF;AACA,QAAM,eAAe,GAAG;AAAA,IACtB;AAAA,EACF;AACA,QAAM,gBAAgB,GAAG;AAAA,IACvB;AAAA;AAAA,EAEF;AAEA,MAAI,OAAmB,EAAE,MAAM,WAAW,UAAU,cAAc,UAAU,MAAM,EAAS;AAC3F,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,eAAe;AACnB,MAAI,eAAe;AAEnB,KAAG,KAAK,mBAAmB;AAE3B,MAAI;AACF,UAAM,gBAAgB,UAAU;AAAA,MAC9B,WAAW,WAAW,MAAM,OAAO,OAAO,MAAO;AAAA,MAEjD,YAAY,CAAC,aAA4B;AACvC,aAAK,WAAW,QAAQ;AAAA,MAC1B;AAAA,MAEA,QAAQ,CAAC,eAA2B;AAClC,eAAO;AACP,YAAI,CAAC,cAAc;AACjB,qBAAW;AAAA,YACT,WAAW;AAAA,YACX,WAAW;AAAA,YACX,WAAW;AAAA,YACX,WAAW,WAAW;AAAA,YACtB,WAAW,eAAe;AAAA,YAC1B,WAAW,WAAW;AAAA,UACxB;AACA,yBAAe;AAAA,QACjB;AAAA,MACF;AAAA,MAEA,WAAW,CAAC,kBAAkC;AAC5C,mBAAW,KAAK,eAAe;AAC7B,cAAI,CAAC,UAAU,IAAI,EAAE,UAAU,GAAG;AAChC,sBAAU,IAAI,EAAE,UAAU;AAC1B,yBAAa,IAAI,EAAE,YAAY,EAAE,eAAe,MAAM,EAAE,iBAAiB,MAAM,EAAE,UAAU,IAAI;AAAA,UACjG;AAAA,QACF;AAAA,MACF;AAAA,MAEA,gBAAgB,CAAC,UAA2B;AAC1C,mBAAW,OAAO,OAAO;AACvB,cAAI,CAAC,UAAU,IAAI,IAAI,gBAAgB,GAAG;AACxC,sBAAU,IAAI,IAAI,gBAAgB;AAClC,yBAAa,IAAI,IAAI,kBAAkB,IAAI,qBAAqB,MAAM,IAAI,uBAAuB,MAAM,IAAI;AAAA,UAC7G;AAEA,wBAAc;AAAA,YACZ,IAAI;AAAA,YACJ,IAAI,qBAAqB;AAAA,YACzB,IAAI,uBAAuB;AAAA,YAC3B,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI,WAAW;AAAA,UACjB;AACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,OAAG,KAAK,QAAQ;AAChB,OAAG,MAAM;AAET,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,QAAQ,cAAc;AAAA,MACtB,UAAU,KAAK;AAAA,MACf;AAAA,MACA,aAAa,UAAU;AAAA,MACvB;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,QAAI;AACF,SAAG,KAAK,UAAU;AAAA,IACpB,QAAQ;AAAA,IAER;AACA,OAAG,MAAM;AAET,QAAI;AACF,UAAO,gBAAW,UAAU,EAAG,CAAG,gBAAW,UAAU;AAAA,IACzD,QAAQ;AAAA,IAER;AAEA,UAAM;AAAA,EACR;AACF;AASA,SAAS,cACP,SACA,YACA,MACA,WACM;AACN,MAAI,CAAC,KAAM;AAEX,QAAM,WAAW,QAAQ,IAAI,UAAU;AACvC,MAAI,CAAC,UAAU;AACb,YAAQ,IAAI,YAAY;AAAA,MACtB,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,SAAS,CAAC,EAAE,MAAM,SAAS,UAAU,CAAC;AAAA,IACxC,CAAC;AAAA,EACH,WAAW,SAAS,gBAAgB,MAAM;AACxC,aAAS,QAAQ,KAAK,EAAE,MAAM,SAAS,UAAU,CAAC;AAClD,aAAS,cAAc;AACvB,aAAS,aAAa;AAAA,EACxB,OAAO;AACL,aAAS,aAAa;AAAA,EACxB;AACF;AAMA,SAAS,qBACP,SACA,UACA,aACA,mBACA,kBACM;AACN,aAAW,CAAC,YAAY,IAAI,KAAK,QAAQ,QAAQ,GAAG;AAClD,QAAI,CAAC,cAAc,eAAe,OAAO,eAAe,YAAa;AAErE,UAAM,WAAW,YAAY,IAAI,UAAU;AAC3C,QAAI,CAAC,SAAU;AAEf,UAAM,cAAc,oBAAI,IAAiD;AACzE,eAAW,KAAK,KAAK,SAAS;AAC5B,YAAM,WAAW,YAAY,IAAI,EAAE,IAAI;AACvC,UAAI,CAAC,UAAU;AACb,oBAAY,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,QAAQ,EAAE,QAAQ,CAAC;AAAA,MACnE,OAAO;AACL,iBAAS,SAAS,EAAE;AAAA,MACtB;AAAA,IACF;AAGA,QAAI,aAAa,gBAAgB;AAC/B,kBAAY,OAAO,UAAU;AAAA,IAC/B;AAEA,QAAI,YAAY,QAAQ,GAAG;AACzB,uBAAiB,IAAI,KAAK,aAAa,UAAU;AACjD;AAAA,IACF;AAEA,UAAM,gBAAgB,MAAM,KAAK,YAAY,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO;AAClG,aAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,YAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,cAAc,CAAC;AAC3C,YAAM,QAAQ,IAAI,cAAc,SAAS,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC,EAAE,UAAU;AAC/E,wBAAkB,IAAI,UAAU,UAAU,MAAM,SAAS,KAAK;AAAA,IAChE;AAEA,qBAAiB,IAAI,KAAK,aAAa,UAAU;AAAA,EACnD;AACF;AAEA,SAAS,aAAa,SAAkC;AACtD,MAAI,QAAQ;AACZ,aAAW,CAAC,EAAE,IAAI,KAAK,QAAQ,QAAQ,GAAG;AACxC,QAAI,KAAK,QAAQ,SAAS,EAAG,UAAS,KAAK,QAAQ;AAAA,EACrD;AACA,SAAO;AACT;;;ACjzBA,SAAS,kBAAkB,IAGzB;AACA,QAAM,yBAAyB,oBAAI,IAAY;AAC/C,QAAM,eAAe,oBAAI,IAAY;AAErC,QAAM,WAAW,GACd,QAAQ,+EAA+E,EACvF,IAAI;AACP,aAAW,OAAO,UAAU;AAC1B,2BAAuB,IAAI,IAAI,mBAAmB;AAAA,EACpD;AAEA,QAAM,WAAW,GACd;AAAA,IACC;AAAA;AAAA;AAAA,EAGF,EACC,IAAI;AACP,aAAW,OAAO,UAAU;AAC1B,iBAAa,IAAI,mBAAmB,IAAI,IAAI,IAAI,oBAAoB,IAAI,OAAO,CAAC;AAAA,EAClF;AAEA,SAAO,EAAE,wBAAwB,aAAa;AAChD;AAEA,SAAS,YACP,KACA,wBACA,cACS;AACT,MAAI,IAAI,mBAAmB;AACzB,QAAI,uBAAuB,IAAI,IAAI,iBAAiB,EAAG,QAAO;AAC9D,2BAAuB,IAAI,IAAI,iBAAiB;AAChD,WAAO;AAAA,EACT;AACA,QAAM,MAAM,mBAAmB,IAAI,WAAW,IAAI,kBAAkB,IAAI,OAAO;AAC/E,MAAI,aAAa,IAAI,GAAG,EAAG,QAAO;AAClC,eAAa,IAAI,GAAG;AACpB,SAAO;AACT;AAEA,SAAS,mBAAmB,WAAmC;AAC7D,QAAM,QAAQ,OAAO,cAAc,YAAY,UAAU,KAAK,MAAM,KAAK,OAAO,SAAS,IAAI;AAC7F,SAAO,OAAO,UAAU,YAAY,QAAQ,KAAK,OAAO,SAAS,KAAK,IAAI,QAAQ;AACpF;AAIA,eAAsB,yBACpB,WACA,UACA,MACmC;AACnC,QAAM,gBAAgB,aAAa,QAAQ;AAC3C,MAAI,CAAC,eAAe;AAClB,WAAO,EAAE,OAAO,6BAA6B,iBAAiB,GAAG,gBAAgB,GAAG,aAAa,EAAE;AAAA,EACrG;AAEA,MAAI;AACJ,MAAI;AACF,SAAK,KAAK,aAAa,WAAW,IAAI;AAAA,EACxC,QAAQ;AACN,WAAO,EAAE,OAAO,2BAA2B,iBAAiB,GAAG,gBAAgB,GAAG,aAAa,EAAE;AAAA,EACnG;AAEA,QAAM,EAAE,wBAAwB,aAAa,IAAI,kBAAkB,EAAE;AACrE,KAAG,MAAM;AAET,MAAI,cAAc;AAClB,MAAI,kBAAkB;AACtB,MAAI,iBAAiB;AAErB,QAAM,gBAAgB,UAAU;AAAA,IAC9B,QAAQ,MAAM;AAAA,IAAC;AAAA,IACf,WAAW,MAAM;AAAA,IAAC;AAAA,IAClB,YAAY,CAAC,aAA4B;AACvC,WAAK,WAAW,QAAQ;AAAA,IAC1B;AAAA,IACA,gBAAgB,CAAC,UAAU;AACzB,iBAAW,OAAO,OAAO;AACvB;AACA,cAAM,YAAY,mBAAmB,IAAI,SAAS;AAClD,YAAI,cAAc,KAAM;AAExB,YAAI,YAAY,EAAE,GAAG,KAAK,UAAU,GAAG,wBAAwB,YAAY,GAAG;AAC5E;AAAA,QACF,OAAO;AACL;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,iBAAiB,gBAAgB,YAAY;AACxD;AAIA,eAAsB,kBACpB,WACA,UACA,MACAC,UACkC;AAClC,QAAM,gBAAgB,aAAa,QAAQ;AAC3C,MAAI,CAAC,eAAe;AAClB,WAAO,EAAE,SAAS,OAAO,iBAAiB,GAAG,OAAO,4BAA4B;AAAA,EAClF;AAEA,MAAI;AACJ,MAAI;AACF,SAAK,KAAK,aAAa,WAAW,KAAK;AAAA,EACzC,QAAQ;AACN,WAAO,EAAE,SAAS,OAAO,iBAAiB,GAAG,OAAO,0BAA0B;AAAA,EAChF;AAEA,QAAM,iBAAiBA,UAAS,kBAAkB;AAClD,QAAM,mBAAmBA,UAAS,oBAAoB;AAEtD,MAAI;AA6DF,QAASC,cAAT,SAAoB,OAAe,QAAgB,QAAgB;AACjE,MAAAC;AACA,wBAAkB,MAAM,KAAK,kBAAkB,MAAM,KAAK,KAAK;AAC/D,UAAI,aAAa,SAAS,mBAAmB;AAC3C,qBAAa,KAAK,EAAE,OAAO,QAAQ,OAAO,CAAC;AAAA,MAC7C;AAAA,IACF;AANS,qBAAAD;AA5DT,UAAM,EAAE,wBAAwB,aAAa,IAAI,kBAAkB,EAAE;AAErE,UAAM,cAAc,oBAAI,IAAoB;AAC5C,UAAM,kBAAkB,GAAG,QAAQ,oCAAoC,EAAE,IAAI;AAI7E,eAAW,KAAK,iBAAiB;AAC/B,kBAAY,IAAI,EAAE,aAAa,EAAE,EAAE;AAAA,IACrC;AAEA,UAAM,eAAe,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAQ/B;AAED,UAAM,sBAAsB,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGtC;AAED,UAAM,cAAc,GAAG,QAAQ,6CAA6C;AAE5E,UAAM,gBAAgB,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGhC;AAED,UAAM,aAAa,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAO7B;AAED,UAAM,gBACH,GAAG,QAAQ,uCAAuC,EAAE,IAAI,GAAiC,UAAU;AAEtG,OAAG,KAAK,mBAAmB;AAE3B,QAAI,kBAAkB;AACtB,QAAI,iBAAiB;AACrB,QAAI,iBAAiB;AACrB,QAAI,eAAe;AACnB,QAAI,cAAc;AAClB,QAAI,eAAe;AACnB,QAAI,iBAAiB;AACrB,QAAIC,cAAa;AACjB,UAAM,oBAA4C,CAAC;AACnD,UAAM,eAA8B,CAAC;AACrC,UAAM,oBAAoB;AAC1B,UAAMC,cAAa;AAUnB,UAAM,gBAA+D,CAAC;AAEtE,UAAM,gBAAgB,UAAU;AAAA,MAC9B,QAAQ,CAAC,SAAS;AAChB,YAAI,mBAAmB,OAAQ;AAC/B,mBAAW;AAAA,UACT,KAAK,QAAQ;AAAA,UACb,KAAK,WAAW;AAAA,UAChB,KAAK,eAAe;AAAA,UACpB,KAAK,WAAW;AAAA,UAChB,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,QAC9B;AACA,sBAAc;AAAA,MAChB;AAAA,MACA,WAAW,CAAC,YAAY;AACtB,YAAI,qBAAqB,OAAQ;AACjC,mBAAW,KAAK,SAAS;AACvB,gBAAM,UAAU,YAAY,IAAI,EAAE,UAAU;AAC5C,uBAAa;AAAA,YACX,EAAE;AAAA,YACF,EAAE,eAAe;AAAA,YACjB,EAAE,iBAAiB;AAAA,YACnB,EAAE,UAAU;AAAA,YACZ,EAAE,QAAQ,KAAK,UAAU,EAAE,KAAK,IAAI;AAAA,UACtC;AACA,cAAI,CAAC,SAAS;AACZ,kBAAM,MAAM,YAAY,IAAI,EAAE,UAAU;AACxC,gBAAI,IAAK,aAAY,IAAI,EAAE,YAAY,IAAI,EAAE;AAC7C;AAAA,UACF,OAAO;AACL;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,YAAY,CAAC,aAA4B;AACvC,aAAK,WAAW,QAAQ;AAAA,MAC1B;AAAA,MACA,gBAAgB,CAAC,UAAU;AACzB,mBAAW,OAAO,OAAO;AACvB;AAEA,cAAI,CAAC,IAAI,kBAAkB;AACzB,YAAAF,YAAW,gBAAgB,kBAAkB,uBAAuB;AACpE;AAAA,UACF;AACA,cAAI,IAAI,cAAc,UAAa,IAAI,cAAc,MAAM;AACzD,YAAAA,YAAW,gBAAgB,qBAAqB,4BAA4B;AAC5E;AAAA,UACF;AACA,gBAAM,YAAY,mBAAmB,IAAI,SAAS;AAClD,cAAI,cAAc,MAAM;AACtB,YAAAA,YAAW,gBAAgB,qBAAqB,oBAAoB,IAAI,SAAS,EAAE;AACnF;AAAA,UACF;AAEA,cAAI,YAAY,EAAE,GAAG,KAAK,UAAU,GAAG,wBAAwB,YAAY,GAAG;AAC5E;AACA;AAAA,UACF;AAEA,cAAI,WAAW,YAAY,IAAI,IAAI,gBAAgB;AACnD,cAAI,CAAC,UAAU;AACb,gCAAoB;AAAA,cAClB,IAAI;AAAA,cACJ,IAAI,qBAAqB;AAAA,cACzB,IAAI,uBAAuB;AAAA,cAC3B;AAAA,YACF;AACA,kBAAM,MAAM,YAAY,IAAI,IAAI,gBAAgB;AAChD,gBAAI,KAAK;AACP,yBAAW,IAAI;AACf,0BAAY,IAAI,IAAI,kBAAkB,QAAQ;AAC9C;AAAA,YACF;AAAA,UACF;AACA,cAAI,CAAC,SAAU;AAEf,gBAAM,YAAY,cAAc;AAAA,YAC9B;AAAA,YACA,IAAI,qBAAqB;AAAA,YACzB,IAAI,uBAAuB;AAAA,YAC3B;AAAA,YACA,IAAI;AAAA,YACJ,IAAI,WAAW;AAAA,YACf,IAAI,oBAAoB;AAAA,YACxB,IAAI,qBAAqB;AAAA,UAC3B;AAEA,wBAAc,KAAK;AAAA,YACjB,IAAI,OAAQ,UAAkB,mBAAmB,CAAC;AAAA,YAClD,SAAS,IAAI,WAAW;AAAA,UAC1B,CAAC;AACD,cAAI,YAAY,aAAc,gBAAe;AAC7C;AAAA,QACF;AAEA,YAAI,iBAAiBE,gBAAe,GAAG;AACrC,eAAK,WAAW;AAAA,YACd,OAAO;AAAA,YACP,WAAW;AAAA,YACX,YAAY;AAAA,YACZ,mBAAmB;AAAA,YACnB,YAAY;AAAA,YACZ,SAAS,aAAa,cAAc,WAAW,eAAe;AAAA,UAChE,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAED,OAAG,KAAK,QAAQ;AAEhB,QAAI,CAAC,aAAa;AAChB,SAAG,QAAQ,iCAAiC,EAAE,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;AAAA,IACjF;AAGA,QAAI,cAAc,SAAS,KAAK,YAAY,EAAE,GAAG;AAC/C,UAAI;AACF,yBAAiB,IAAI,aAAa;AAAA,MACpC,QAAQ;AAAA,MAER;AAAA,IACF;AAMA,QAAI,kBAAkB,GAAG;AACvB,UAAI;AACF,YAAI,eAAe,eAAe;AAChC,+BAAqB,EAAE;AAAA,QACzB,OAAO;AACL,0CAAgC,EAAE;AAAA,QACpC;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,eAAe,GAClB;AAAA,MACC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,EACC,IAAI;AACP,UAAM,iBAAiB,GAAG,QAAQ,sCAAsC,EAAE,IAAI;AAG9E,QAAI;AACF,YAAM,KAAK,iBAAiB,IAAI,SAAS;AAAA,IAC3C,QAAQ;AAAA,IAER;AAEA,OAAG,MAAM;AAET,SAAK,WAAW;AAAA,MACd,OAAO;AAAA,MACP,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,YAAY;AAAA,MACZ,SAAS,0BAA0B,eAAe;AAAA,IACpD,CAAC;AAED,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,OAAO;AAAA,QACL,eAAe;AAAA,QACf,cAAc;AAAA,QACd;AAAA,QACA,YAAAD;AAAA,QACA;AAAA,QACA,aAAa;AAAA,MACf;AAAA,MACA,SAAS;AAAA,QACP,YAAY,aAAa;AAAA,QACzB,aAAa,eAAe;AAAA,QAC5B,gBAAgB,aAAa;AAAA,QAC7B,eAAe,aAAa;AAAA,MAC9B;AAAA,MACA,SAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,QAAI;AACF,SAAG,KAAK,UAAU;AAAA,IACpB,QAAQ;AAAA,IAER;AACA,OAAG,MAAM;AAET,YAAQ,MAAM,8BAA8B,KAAK;AACjD,WAAO,EAAE,SAAS,OAAO,iBAAiB,GAAG,OAAO,OAAO,KAAK,EAAE;AAAA,EACpE;AACF;;;ACtcO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YACW,MACT,SACAE,UACA;AACA,UAAM,SAASA,QAAO;AAJb;AAKT,SAAK,OAAO;AAAA,EACd;AAAA,EANW;AAOb;;;AChBA,mBAA4B;AAJ5B,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,gBAAgB;AAWzB,IAAM,sBAAsB;AAC5B,IAAM,0BAA0B,KAAK,OAAO,OAAO;AACnD,IAAM,gCAAgC;AAE/B,SAAS,yBAAyB,UAAwB;AAC/D,MACE,SAAS,WAAW,GAAG,KACvB,eAAe,KAAK,QAAQ,KAC5B,SAAS,SAAS,IAAI,KACtB,SAAS,MAAM,GAAG,EAAE,SAAS,IAAI,GACjC;AACA,UAAM,IAAI,mBAAmB,6BAA6B,8BAA8B,QAAQ,EAAE;AAAA,EACpG;AACF;AAEA,SAAS,gBAAgB,OAAoC;AAC3D,MAAI,iBAAiB,mBAAoB,QAAO;AAChD,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,SAAO,IAAI,mBAAmB,yBAAyB,wBAAwB,OAAO,IAAI;AAAA,IACxF,OAAO;AAAA,EACT,CAAC;AACH;AAEA,SAAS,UAAU,OAAmC;AACpD,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,gBAAgB,MAAM;AAAA,IACtB,kBAAkB,MAAM;AAAA,IACxB,mBAAmB,MAAM;AAAA,IACzB,aAAa,MAAM,SAAS,SAAS,GAAG;AAAA,EAC1C;AACF;AAMA,SAAS,cAAc,OAAcC,UAAkD;AACrF,2BAAyB,MAAM,QAAQ;AAEvC,MAAI,MAAM,YAAY,GAAG;AACvB,UAAM,IAAI,mBAAmB,2BAA2B,yCAAyC,MAAM,QAAQ,EAAE;AAAA,EACnH;AACA,MAAI,CAAC,MAAM,kBAAkB,KAAM,MAAM,sBAAsB,KAAK,MAAM,sBAAsB,GAAI;AAClG,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iDAAiD,MAAM,QAAQ;AAAA,IACjE;AAAA,EACF;AACA,MAAI,MAAM,mBAAmBA,SAAQ,eAAe;AAClD,UAAM,IAAI,mBAAmB,gCAAgC,qCAAqC,MAAM,QAAQ,EAAE;AAAA,EACpH;AAEA,MAAI,MAAM,mBAAmB,GAAG;AAC9B,UAAM,QAAQ,MAAM,mBAAmB,IAAI,OAAO,oBAAoB,MAAM,mBAAmB,MAAM;AACrG,QAAI,QAAQA,SAAQ,qBAAqB;AACvC,YAAM,IAAI;AAAA,QACR;AAAA,QACA,kDAAkD,MAAM,QAAQ;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,mBAAN,MAAuB;AAAA,EAG5B,YACmB,aACjBA,WAAmC,CAAC,GACpC;AAFiB;AAGjB,SAAK,UAAU;AAAA,MACb,YAAYA,SAAQ,cAAc;AAAA,MAClC,eAAeA,SAAQ,iBAAiB;AAAA,MACxC,qBAAqBA,SAAQ,uBAAuB;AAAA,IACtD;AAAA,EACF;AAAA,EARmB;AAAA,EAHF;AAAA,EAajB,MAAc,OAAyB;AACrC,QAAI;AACF,aAAO,UAAM,0BAAY,KAAK,aAAa;AAAA,QACzC,WAAW;AAAA,QACX,eAAe;AAAA,QACf,oBAAoB;AAAA,QACpB,iBAAiB;AAAA,MACnB,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,gBAAgB,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,cAA8C;AAClD,UAAM,UAAiC,CAAC;AACxC,UAAM,KAAK,aAAa,CAAC,UAAU;AACjC,cAAQ,KAAK,KAAK;AAAA,IACpB,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAa,SAA6C;AAC9D,UAAM,UAAU,MAAM,KAAK,KAAK;AAChC,QAAI,QAAQ;AACZ,QAAI;AACF,uBAAiB,SAAS,QAAQ,UAAU,GAAG;AAC7C;AACA,YAAI,QAAQ,KAAK,QAAQ,YAAY;AACnC,gBAAM,IAAI,mBAAmB,gCAAgC,uCAAuC;AAAA,QACtG;AACA,sBAAc,OAAO,KAAK,OAAO;AAEjC,YAAI,SAAS;AACb,cAAM,aAAuC,YAAY;AACvD,cAAI,QAAQ;AACV,kBAAM,IAAI,mBAAmB,yBAAyB,kCAAkC,MAAM,QAAQ,EAAE;AAAA,UAC1G;AACA,mBAAS;AACT,cAAI;AACF,mBAAO,MAAM,QAAQ,sBAAsB,KAAK;AAAA,UAClD,SAAS,OAAO;AACd,kBAAM,gBAAgB,KAAK;AAAA,UAC7B;AAAA,QACF;AACA,cAAM,QAAQ,UAAU,KAAK,GAAG,UAAU;AAAA,MAC5C;AAAA,IACF,SAAS,OAAO;AACd,YAAM,gBAAgB,KAAK;AAAA,IAC7B,UAAE;AACA,UAAI,QAAQ,OAAQ,SAAQ,MAAM;AAAA,IACpC;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,SAAqD;AACrE,UAAM,YAAY,IAAI,IAAI,QAAQ,KAAK,CAAC;AACxC,UAAM,KAAK,aAAa,OAAO,OAAO,eAAe;AACnD,YAAM,cAAc,QAAQ,IAAI,MAAM,IAAI;AAC1C,UAAI,CAAC,eAAe,MAAM,YAAa;AACvC,MAAG,eAAe,eAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,YAAM,SAAS,MAAM,WAAW,GAAM,uBAAkB,aAAa,EAAE,OAAO,KAAK,CAAC,CAAC;AACrF,gBAAU,OAAO,MAAM,IAAI;AAAA,IAC7B,CAAC;AAED,QAAI,UAAU,OAAO,GAAG;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,QACA,4CAA4C,MAAM,KAAK,SAAS,EAAE,KAAK,IAAI,CAAC;AAAA,MAC9E;AAAA,IACF;AAAA,EACF;AACF;;;AClKA,IAAAC,uBAAwB;AACxB,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAN/B,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAUtB,IAAM,EAAE,OAAAC,OAAM,IAAI,qBAAAC;AAClB,IAAM,EAAE,QAAAC,SAAO,IAAI,oBAAAC;AACnB,IAAM,EAAE,MAAAC,MAAK,IAAI,aAAAC;AACjB,IAAM,EAAE,cAAAC,cAAa,IAAI,qBAAAC;AAEzB,IAAM,aAAa;AACnB,IAAM,iBAAiB;AACvB,IAAM,eAAe;AACrB,IAAM,qBAAqB,IAAI,OAAO;AAyBtC,eAAe,cAAiB,QAAkB,WAAW,oBAAgC;AAC3F,QAAM,SAAmB,CAAC;AAC1B,MAAI,QAAQ;AACZ,mBAAiB,SAAS,QAA0C;AAClE,UAAM,SAAS,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK;AACjE,aAAS,OAAO;AAChB,QAAI,QAAQ,UAAU;AACpB,YAAM,IAAI,mBAAmB,gCAAgC,6CAA6C;AAAA,IAC5G;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACA,MAAI;AACF,WAAO,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;AAAA,EAC1D,SAAS,OAAO;AACd,UAAM,IAAI,mBAAmB,yBAAyB,qCAAqC,EAAE,OAAO,MAAM,CAAC;AAAA,EAC7G;AACF;AAEA,eAAe,cAAc,QAAmC;AAC9D,QAAMC,YAAWR,OAAM,CAAC,QAAQE,SAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAC9F,MAAI,QAAQ;AACZ,MAAI;AACF,qBAAiB,SAASE,UAAoC;AAC9D,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,IAAI,mBAAmB,yBAAyB,qCAAqC,EAAE,OAAO,MAAM,CAAC;AAAA,EAC7G;AACF;AAEA,SAAS,eAAe,MAAiD;AACvE,SAAO,MAAM,OAAO,KAAK,EAAE,YAAY,KAAK;AAC9C;AAEA,SAAS,uBAAuB,OAA0B,YAAmC;AAC3F,QAAM,YAAY,MAAM;AACxB,MAAI,MAAM,SAAS,WAAW,WAAW,MAAM,KAAK,EAAG,QAAO,UAAU,KAAK,KAAK;AAElF,QAAM,UAAU,WAAW,WAAW,CAAC;AACvC,QAAM,QAAQ,QAAQ,KAAK,CAAC,WAAW;AACrC,UAAM,QAAQ,eAAe,MAAM;AACnC,WAAO,CAAC,cAAc,UAAU;AAAA,EAClC,CAAC;AACD,SAAO,OAAO,MAAM,KAAK,KAAK,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,GAAG,MAAM,KAAK,KAAK,MAAM;AAC1F;AAEO,IAAM,4BAAN,MAA2D;AAAA,EACvD,KAAK;AAAA,EACL,WAAW;AAAA,EAEpB,OAAO,SAAyC;AAC9C,UAAM,WAAW,oBAAI,IAAY;AACjC,UAAM,aAAa,oBAAI,IAAyB;AAEhD,eAAW,SAAS,SAAS;AAC3B,YAAM,UAAU,MAAM,KAAK,MAAM,YAAY,IAAI,CAAC;AAClD,UAAI,QAAS,UAAS,IAAI,OAAO;AAEjC,YAAM,QAAQ,MAAM,KAAK,MAAM,UAAU;AACzC,UAAI,CAAC,MAAO;AACZ,YAAM,QAAQ,WAAW,IAAI,MAAM,CAAC,CAAC,KAAK,oBAAI,IAAY;AAC1D,YAAM,IAAI,MAAM,CAAC,CAAC;AAClB,iBAAW,IAAI,MAAM,CAAC,GAAG,KAAK;AAAA,IAChC;AAEA,WACE,SAAS,SAAS,KAClB,SAAS,IAAI,aAAa,KAC1B,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,KAAK,CAAC,UAAU,MAAM,IAAI,YAAY,KAAK,MAAM,IAAI,UAAU,CAAC;AAAA,EAEpG;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,KAAK,QAAyD;AAClE,QAAI,aAA4B;AAChC,UAAM,SAAS,oBAAI,IAA+B;AAElD,UAAM,OAAO,aAAa,OAAO,OAAO,eAAe;AACrD,UAAI,eAAe,KAAK,MAAM,IAAI,GAAG;AACnC,cAAM,YAAY,MAAM,cAAkC,MAAM,WAAW,CAAC;AAC5E,qBAAa,eAAe,UAAU,IAAI;AAC1C;AAAA,MACF;AAEA,YAAM,QAAQ,MAAM,KAAK,MAAM,UAAU;AACzC,UAAI,CAAC,MAAO;AACZ,YAAM,SAAS,UAAU,MAAM,CAAC,CAAC;AACjC,YAAM,QACJ,OAAO,IAAI,MAAM,KAChB;AAAA,QACC;AAAA,QACA,MAAM,MAAM,CAAC;AAAA,MACf;AAEF,UAAI,MAAM,CAAC,MAAM,cAAc;AAC7B,cAAM,iBAAiB,MAAM;AAC7B,cAAM,YAAY,MAAM,cAAmC,MAAM,WAAW,CAAC;AAAA,MAC/E,OAAO;AACL,cAAM,gBAAgB,MAAM;AAC5B,cAAM,eAAe,MAAM,cAAc,MAAM,WAAW,CAAC;AAAA,MAC7D;AACA,aAAO,IAAI,QAAQ,KAAK;AAAA,IAC1B,CAAC;AAED,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO,MAAM,KAAK,OAAO,OAAO,CAAC,EAC9B,IAAI,CAAC,UAAU;AACd,UAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,iBAAiB,CAAC,MAAM,aAAa,MAAM,iBAAiB,QAAW;AACzG,cAAM,IAAI;AAAA,UACR;AAAA,UACA,uDAAuD,MAAM,MAAM;AAAA,QACrE;AAAA,MACF;AACA,aAAO;AAAA,QACL,QAAQ,MAAM;AAAA,QACd,MAAM,uBAAuB,OAAO,UAAU;AAAA,QAC9C,MAAM,MAAM,SAAS,UAAU,UAAU;AAAA,QACzC,cAAc,MAAM;AAAA,QACpB,aAAa,MAAM,UAAU,SAAS,UAAU;AAAA,MAClD;AAAA,IACF,CAAC,EACA,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,cAAc,EAAE,MAAM,CAAC;AAAA,EACpD;AAAA,EAEA,MAAM,YACJ,QACA,MACA,WAC6B;AAC7B,UAAM,UAAU,MAAM,OAAO,YAAY;AACzC,UAAM,cAAc,uBAAuB,KAAK,MAAM;AACtD,UAAM,gBAAgB,QAAQ,KAAK,CAAC,UAAU,eAAe,KAAK,MAAM,IAAI,CAAC,GAAG;AAChF,UAAM,iBAAiB,GAAG,WAAW;AACrC,UAAM,gBAAgB,GAAG,WAAW;AACpC,QACE,CAAC,iBACD,CAAC,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,cAAc,KACtD,CAAC,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,aAAa,GACrD;AACA,YAAM,IAAI;AAAA,QACR;AAAA,QACA,uDAAuD,KAAK,MAAM;AAAA,MACpE;AAAA,IACF;AAEA,IAAG,eAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,UAAM,OAAO;AAAA,MACX,oBAAI,IAAI;AAAA,QACN,CAAC,eAAoB,YAAK,WAAW,gBAAgB,CAAC;AAAA,QACtD,CAAC,gBAAqB,YAAK,WAAW,iBAAiB,CAAC;AAAA,QACxD,CAAC,eAAoB,YAAK,WAAW,eAAe,CAAC;AAAA,MACvD,CAAC;AAAA,IACH;AAEA,UAAM,eAAoB,YAAK,WAAW,yBAAyB;AACnE,IAAG;AAAA,MACD;AAAA,MACA,KAAK;AAAA,QACH;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,QAAQ,KAAK;AAAA,UACb,UAAU,KAAK;AAAA,UACf,UAAU,KAAK;AAAA,UACf,cAAc;AAAA,UACd,eAAe;AAAA,UACf,cAAc;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,EAAE,UAAU,QAAQ,MAAM,KAAK;AAAA,IACjC;AACA,WAAO,EAAE,aAAa;AAAA,EACxB;AACF;;;ACnOA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,cAAAC,mBAAkB;AAM3B,IAAM,iBAAiB,KAAK,KAAK;AAiB1B,IAAM,6BAAN,MAAiC;AAAA,EACrB,UAAU,oBAAI,IAA0B;AAAA,EACxC,mBAAmB,oBAAI,IAAY;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAYC,WAA6C,CAAC,GAAG;AAC3D,SAAK,WAAWA,SAAQ,YAAiB,YAAK,QAAQ,IAAI,GAAG,yBAAyB;AACtF,SAAK,QAAQA,SAAQ,SAAS;AAC9B,SAAK,MAAMA,SAAQ,OAAO,KAAK;AAC/B,SAAK,YAAYA,SAAQ,aAAa,CAAC,IAAI,0BAA0B,CAAC;AACtE,IAAG,eAAU,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,EACjD;AAAA,EAEA,oBAAoB,aAAoD;AACtE,WAAO,KAAK,eAAe,aAAa,KAAK;AAAA,EAC/C;AAAA,EAEA,oBAAoB,aAAoD;AACtE,WAAO,KAAK,eAAe,aAAa,IAAI;AAAA,EAC9C;AAAA,EAEA,MAAc,eAAe,aAAqB,aAAqD;AACrG,UAAM,KAAK,eAAe;AAC1B,UAAM,SAAS,IAAI,iBAAiB,WAAW;AAC/C,QAAI;AACF,YAAM,UAAU,MAAM,OAAO,YAAY;AACzC,YAAM,UAAU,KAAK,UAAU,OAAO,CAACC,cAAaA,UAAS,OAAO,OAAO,CAAC;AAC5E,UAAI,QAAQ,WAAW,GAAG;AACxB,cAAM,IAAI;AAAA,UACR;AAAA,UACA,QAAQ,WAAW,IAAI,+BAA+B;AAAA,QACxD;AAAA,MACF;AAEA,YAAM,WAAW,QAAQ,CAAC;AAC1B,YAAM,QAAQ,MAAM,SAAS,KAAK,MAAM;AACxC,YAAM,YAAY,KAAK,IAAI;AAC3B,YAAM,WAAWC,YAAW;AAC5B,YAAM,aAAmC;AAAA,QACvC;AAAA,QACA,UAAU,SAAS;AAAA,QACnB,UAAU,SAAS;AAAA,QACnB;AAAA,QACA,WAAW,YAAY,KAAK;AAAA,MAC9B;AACA,WAAK,QAAQ,IAAI,UAAU;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc;AAAA,MAChB,CAAC;AACD,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,YAAa,MAAK,WAAW,WAAW;AAC5C,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,qBACJ,UACA,QACA,SACY;AACZ,UAAM,SAAS,MAAM,KAAK,gBAAgB,QAAQ;AAClD,UAAM,OAAO,OAAO,WAAW,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM;AAC1E,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR;AAAA,QACA,uCAAuC,MAAM;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,kBAAqB,iBAAiB,YAAK,KAAK,UAAU,gBAAgB,QAAQ,GAAG,CAAC;AAC5F,QAAI;AACF,YAAM,SAAS,IAAI,iBAAiB,OAAO,WAAW;AACtD,YAAM,eAAe,MAAM,OAAO,SAAS,YAAY,QAAQ,MAAM,eAAe;AACpF,aAAO,MAAM,QAAQ,aAAa,YAAY;AAAA,IAChD,UAAE;AACA,WAAK,WAAW,eAAe;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,UAAiC;AAC7C,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,CAAC,OAAQ;AACb,SAAK,QAAQ,OAAO,QAAQ;AAC5B,QAAI,OAAO,YAAa,MAAK,WAAW,OAAO,WAAW;AAAA,EAC5D;AAAA,EAEA,MAAM,eAAe,MAAM,KAAK,IAAI,GAAoB;AACtD,QAAI,UAAU;AACd,eAAW,CAAC,UAAU,MAAM,KAAK,KAAK,SAAS;AAC7C,UAAI,MAAM,OAAO,eAAe,KAAK,MAAO;AAC5C,WAAK,QAAQ,OAAO,QAAQ;AAC5B,WAAK,iBAAiB,IAAI,QAAQ;AAClC,UAAI,OAAO,YAAa,MAAK,WAAW,OAAO,WAAW;AAC1D;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAuB;AAC3B,eAAW,YAAY,MAAM,KAAK,KAAK,QAAQ,KAAK,CAAC,GAAG;AACtD,YAAM,KAAK,QAAQ,QAAQ;AAAA,IAC7B;AACA,SAAK,WAAW,KAAK,QAAQ;AAAA,EAC/B;AAAA,EAEA,MAAc,gBAAgB,UAAyC;AACrE,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,CAAC,QAAQ;AACX,YAAM,OAAO,KAAK,iBAAiB,IAAI,QAAQ,IAAI,gCAAgC;AACnF,YAAM,IAAI,mBAAmB,MAAM,yCAAyC,QAAQ,EAAE;AAAA,IACxF;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,MAAM,OAAO,gBAAgB,KAAK,OAAO;AAC3C,WAAK,QAAQ,OAAO,QAAQ;AAC5B,WAAK,iBAAiB,IAAI,QAAQ;AAClC,UAAI,OAAO,YAAa,MAAK,WAAW,OAAO,WAAW;AAC1D,YAAM,IAAI,mBAAmB,+BAA+B,kCAAkC,QAAQ,EAAE;AAAA,IAC1G;AAEA,WAAO,eAAe;AACtB,WAAO,WAAW,YAAY,MAAM,KAAK;AACzC,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,YAA0B;AAC3C,QAAI;AACF,MAAG,YAAO,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACxD,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;ACjKA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAIf,IAAM,WAAN,MAAe;AAAA,EACZ,YAAY;AAAA,EACZ;AAAA,EACA,UAAyB;AAAA,EACzB,YAAmC;AAAA,EAE3C,YAAY,SAAiB;AAC3B,SAAK,SAAc,YAAK,SAAS,IAAI;AAAA,EACvC;AAAA,EAEA,aAAa,SAAwB;AACnC,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAkB,SAAiB,MAAsB;AAC7D,SAAK,SAAS,SAAS,UAAU,SAAS,IAAI;AAAA,EAChD;AAAA,EAEA,KAAK,UAAkB,SAAiB,MAAsB;AAC5D,SAAK,SAAS,QAAQ,UAAU,SAAS,IAAI;AAAA,EAC/C;AAAA,EAEA,KAAK,UAAkB,SAAiB,MAAsB;AAC5D,SAAK,SAAS,QAAQ,UAAU,SAAS,IAAI;AAAA,EAC/C;AAAA,EAEA,MAAM,UAAkB,SAAiB,MAAsB;AAC7D,SAAK,SAAS,SAAS,UAAU,SAAS,IAAI;AAAA,EAChD;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,WAAW;AAClB,WAAK,UAAU,IAAI;AACnB,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,aAAqB;AACnB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,qBAAoC;AAClC,QAAI,KAAK,WAAc,gBAAW,KAAK,OAAO,GAAG;AAC/C,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAqB;AAC3B,QAAI,CAAI,gBAAW,KAAK,MAAM,GAAG;AAC/B,MAAG,eAAU,KAAK,QAAQ,EAAE,WAAW,KAAK,CAAC;AAAA,IAC/C;AAAA,EACF;AAAA,EAEQ,iBAAyB;AAC/B,QAAI,KAAK,QAAS,QAAO,KAAK;AAE9B,SAAK,aAAa;AAClB,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,OAAO,IAAI,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAC3C,UAAM,QAAQ,OAAO,IAAI,SAAS,CAAC,EAAE,SAAS,GAAG,GAAG;AACpD,UAAM,UAAU,OAAO,IAAI,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACxD,SAAK,UAAe,YAAK,KAAK,QAAQ,MAAM,IAAI,IAAI,KAAK,IAAI,OAAO,MAAM;AAE1E,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,eAA+B;AACrC,QAAI,KAAK,UAAW,QAAO,KAAK;AAEhC,UAAM,WAAW,KAAK,eAAe;AACrC,UAAM,mBAAmB,CAAI,gBAAW,QAAQ,KAAQ,cAAS,QAAQ,EAAE,SAAS;AACpF,SAAK,YAAe,uBAAkB,UAAU,EAAE,OAAO,KAAK,UAAU,QAAQ,CAAC;AACjF,QAAI,kBAAkB;AACpB,WAAK,UAAU,MAAM,gBAAgB,SAAS,QAAQ,MAAM,KAAK,CAAC;AAAA;AAAA,CAAM;AAAA,IAC1E;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,SAAS,OAAiB,UAAkB,SAAiB,MAAsB;AACzF,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,QAAI,UAAU,IAAI,SAAS,MAAM,KAAK,MAAM,QAAQ,KAAK,OAAO;AAEhE,QAAI,SAAS,QAAW;AACtB,UAAI;AACF,cAAM,UAAU,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AAC9E,YAAI,CAAC,KAAK,aAAa,QAAQ,SAAS,KAAM;AAC5C,qBAAW;AAAA,EAAK,QAAQ,MAAM,GAAG,GAAI,CAAC,kBAAkB,QAAQ,MAAM;AAAA,QACxE,OAAO;AACL,qBAAW;AAAA,EAAK,OAAO;AAAA,QACzB;AAAA,MACF,QAAQ;AACN,mBAAW;AAAA;AAAA,MACb;AAAA,IACF;AAEA,eAAW;AAEX,QAAI;AACF,YAAM,SAAS,KAAK,aAAa;AACjC,aAAO,MAAM,OAAO;AAAA,IACtB,SAAS,OAAO;AACd,cAAQ,MAAM,mCAAmC,KAAK;AAAA,IACxD;AAEA,QAAI,UAAU,UAAU,UAAU,SAAS;AACzC,cAAQ,IAAI,QAAQ,OAAO,EAAE;AAAA,IAC/B;AAAA,EACF;AACF;AAEO,SAAS,iBAAiB,OAAyC;AACxE,MAAI,iBAAiB,OAAO;AAC1B,UAAM,OAAgC;AAAA,MACpC,MAAM,MAAM;AAAA,MACZ,SAAS,MAAM;AAAA,IACjB;AACA,QAAI,WAAW,SAAS,MAAM,OAAO;AACnC,WAAK,QAAQ,iBAAiB,MAAM,KAAK;AAAA,IAC3C;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,EAAE,KAAK,KAAK,UAAU,KAAK,EAAE;AAAA,EACtC;AACA,SAAO,EAAE,SAAS,OAAO,KAAK,EAAE;AAClC;AAEO,SAAS,kBAAkB,OAAgB,aAAqB,GAAkB;AACvF,MAAI,iBAAiB,SAAS,MAAM,OAAO;AACzC,UAAM,QAAQ,MAAM,MAAM,MAAM,IAAI;AACpC,WAAO,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,KAAK,IAAI;AAAA,EACjD;AACA,SAAO;AACT;;;AC9IA,SAAS,qBAAqB,OAI5B;AACA,QAAM,aAAwB,CAAC;AAC/B,MAAI,MAAO,YAAW,KAAK,KAAK;AAEhC,QAAM,WAAW;AACjB,MAAI,UAAU,UAAW,YAAW,KAAK,SAAS,SAAS;AAC3D,MAAI,MAAM,QAAQ,UAAU,MAAM,EAAG,YAAW,KAAK,GAAG,SAAS,MAAM;AAEvE,MAAI,aAAa;AACjB,MAAI;AACJ,MAAI;AAEJ,aAAW,aAAa,YAAY;AAClC,QAAI,CAAC,aAAa,OAAO,cAAc,UAAU;AAC/C,UAAI,CAAC,cAAc,OAAO,cAAc,SAAU,cAAa;AAC/D;AAAA,IACF;AAEA,UAAM,SAAS;AACf,QAAI,OAAO,OAAO,eAAe,SAAU,cAAa,OAAO;AAC/D,QAAI,CAAC,cAAc,OAAO,OAAO,YAAY,SAAU,cAAa,OAAO;AAE3E,QAAI,CAAC,cAAc,OAAO,QAAQ,OAAO,OAAO,SAAS,UAAU;AACjE,YAAM,OAAO,OAAO;AACpB,UAAI,KAAK,OAAO,QAAS,cAAa,KAAK,MAAM;AAAA,IACnD;AAEA,QAAI,OAAO,gBAAgB,OAAO,OAAO,iBAAiB,UAAU;AAClE,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,OAAO,YAAY;AAC7C,YAAI,CAAC,cAAc,OAAO,OAAO,QAAS,cAAa,OAAO,MAAM;AAAA,MACtE,QAAQ;AACN,YAAI,CAAC,WAAY,cAAa,OAAO;AAAA,MACvC;AAAA,IACF;AAEA,QAAI,YAAY;AACd,YAAM,aAAa,WAAW,MAAM,sBAAsB;AAC1D,UAAI,WAAY,gBAAe,KAAK,KAAK,OAAO,WAAW,CAAC,CAAC,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,cAAc,OAAO,KAAK,GAAG,YAAY,aAAa;AAC7E;AAMO,SAAS,cAAc,OAAgBC,UAAwC;AACpF,QAAM,EAAE,YAAY,iBAAiB,YAAY,aAAa,IAAI,qBAAqB,KAAK;AAC5F,QAAM,eAAe,gBAAgB,YAAY;AACjD,QAAM,eAAeA,UAAS,gBAAgB;AAE9C,MAAI,kBAAkB;AAEtB,MAAI,eAAe,OAAO,aAAa,SAAS,OAAO,KAAK,aAAa,SAAS,oBAAoB,GAAG;AACvG,sBAAkB,eACd,GAAG,YAAY,wCAAwC,YAAY,6BACnE,GAAG,YAAY;AAAA,EACrB,WACE,eAAe,QACd,aAAa,SAAS,OAAO,KAAK,aAAa,SAAS,YAAY,KAAK,aAAa,SAAS,cAAc,IAC9G;AACA,sBAAkB,GAAG,YAAY;AAAA,EACnC,WAAW,eAAe,OAAO,aAAa,SAAS,YAAY,KAAK,aAAa,SAAS,aAAa,GAAG;AAC5G,sBAAkB,GAAG,YAAY;AAAA,EACnC,WAAW,gBAAgB,SAAS,KAAK;AACvC,sBAAkB,GAAG,gBAAgB,MAAM,GAAG,GAAG,CAAC;AAAA,EACpD,OAAO;AACL,sBAAkB;AAAA,EACpB;AAEA,QAAM,gBAAgBA,UAAS,iBAAiB;AAChD,QAAM,UAAU,CAAC,aAAa,UAAU,UAAU,KAAK,MAAM,eAAe,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAEvG,MAAI,oBAAoB,iBAAiB;AACvC,WAAO,GAAG,eAAe;AAAA;AAAA,EAAO,aAAa,KAAK,OAAO;AAAA,EAC3D;AAEA,SAAO;AACT;;;ACzFA,YAAYC,YAAU;AACtB,OAAO,YAAY;AAIZ,SAAS,mBAAmB,SAAiB,UAA0C;AAC5F,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,SAAS,KAAK,IAAI,OAAO,OAAO;AAElD,UAAM,KAAK,GAAG,MAAW,gBAAS,UAAU,KAAK;AACjD,UAAM,OAAO,GAAG;AAChB,QAAI,CAAC,KAAM,QAAO;AAElB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,cAAc,KAAK,KAAK;AAAA,MACxB,iBAAiB,iBAAiB,GAAG,eAAe;AAAA,MACpD,qBAAqB,0BAA0B,iBAAiB,GAAG,mBAAmB,CAAC;AAAA,MACvF,WAAW,OAAO,GAAG,cAAc,WAAW,GAAG,YAAY;AAAA,MAC7D,qBAAqB,eAAe,GAAG,mBAAmB;AAAA,MAC1D,kBAAkB,iBAAiB,GAAG,gBAAgB;AAAA,IACxD;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB,QAAiC;AAClE,QAAM,KAA8B;AAAA,IAClC,IAAI,OAAO;AAAA,IACX,MAAM,OAAO;AAAA,EACf;AAEA,MAAI,OAAO,UAAW,IAAG,YAAY,OAAO;AAC5C,MAAI,OAAO,qBAAqB,OAAQ,IAAG,sBAAsB,OAAO;AACxE,MAAI,OAAO,kBAAkB,OAAQ,IAAG,mBAAmB,OAAO;AAClE,MAAI,OAAO,qBAAqB,OAAQ,IAAG,sBAAsB,0BAA0B,OAAO,mBAAmB;AACrH,MAAI,OAAO,iBAAiB,OAAQ,IAAG,kBAAkB,OAAO;AAEhE,SAAO,OAAO,UAAU;AAAA,EAAK,OAAO,YAAY;AAAA,GAAM,EAAE;AAC1D;AAEA,SAAS,iBAAiB,KAAwB;AAChD,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO,IAAI,IAAI,MAAM,EAAE,OAAO,OAAO;AAC7D,SAAO,CAAC;AACV;AAEA,SAAS,eAAe,KAAmD;AACzE,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO;AAChC,QAAM,QAAQ,IAAI,OAAO,CAAC,MAAgC,MAAM,WAAW,MAAM,SAAS;AAC1F,SAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;;;ACCA,SAASC,WAAU,QAA2C;AAC5D,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,MAAM,OAAO;AAAA,IACb,cAAc,OAAO;AAAA,IACrB,iBAAiB,OAAO;AAAA,IACxB,WAAW,OAAO;AAAA,IAClB,qBAAqB,OAAO;AAAA,IAC5B,kBAAkB,OAAO;AAAA,EAC3B;AACF;AAEO,IAAM,mBAAN,MAAuB;AAAA,EACpB;AAAA,EACA;AAAA,EACA,eAAe,oBAAI,IAA6B;AAAA,EAChD,QAAQ,oBAAI,IAA6B;AAAA,EACzC,cAAc;AAAA,EAEtB,YAAY,MAA4B;AACtC,SAAK,OAAO;AACZ,SAAK,aAAa,KAAK,cAAc,CAAC,cAAc,cAAc,YAAY;AAC9E,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,CAAC,KAAK,KAAK,kBAAmB;AAClC,eAAW,EAAE,IAAI,QAAQ,KAAK,KAAK,KAAK,mBAAmB;AACzD,YAAM,SAAS,mBAAmB,SAAS,GAAG,EAAE,KAAK;AACrD,UAAI,OAAQ,MAAK,aAAa,IAAI,OAAO,IAAI,MAAM;AAAA,IACrD;AAAA,EACF;AAAA,EAEQ,iBAAiB,IAAyC;AAChE,WAAO,KAAK,aAAa,IAAI,EAAE;AAAA,EACjC;AAAA,EAEQ,oBAA0B;AAChC,QAAI,CAAC,KAAK,YAAa,MAAK,KAAK;AAAA,EACnC;AAAA;AAAA,EAIA,OAA4B;AAC1B,UAAM,EAAE,IAAAC,MAAI,cAAc,IAAI,KAAK;AACnC,IAAAA,KAAG,UAAU,aAAa;AAE1B,UAAM,iBAAiB,KAAK,wBAAwB;AACpD,SAAK,QAAQ;AAEb,SAAK,cAAc;AACnB,SAAK,KAAK,QAAQ,KAAK,oBAAoB,eAAe;AAAA,MACxD,OAAO,KAAK,MAAM;AAAA,MAClB;AAAA,IACF,CAAC;AAED,WAAO,EAAE,OAAO,KAAK,MAAM,MAAM,eAAe;AAAA,EAClD;AAAA,EAEQ,0BAAmC;AACzC,UAAM,EAAE,IAAAA,MAAI,cAAc,IAAI,KAAK;AACnC,QAAI,aAAa;AACjB,eAAW,MAAM,KAAK,YAAY;AAChC,YAAM,SAAS,KAAK,iBAAiB,EAAE;AACvC,UAAI,CAAC,OAAQ;AAEb,YAAM,WAAWA,KAAG,SAAS,eAAe,GAAG,EAAE,KAAK;AACtD,UAAIA,KAAG,WAAW,QAAQ,EAAG;AAE7B,MAAAA,KAAG,UAAU,UAAU,mBAAmB,EAAE,GAAG,QAAQ,WAAW,OAAO,GAAG,CAAC,CAAC;AAC9E,mBAAa;AAAA,IACf;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,UAAgB;AACtB,UAAM,EAAE,IAAAA,MAAI,cAAc,IAAI,KAAK;AACnC,SAAK,MAAM,MAAM;AAEjB,UAAM,QAAQA,KAAG,UAAU,eAAe,KAAK;AAC/C,eAAW,QAAQ,OAAO;AACxB,UAAI;AACF,cAAM,WAAWA,KAAG,SAAS,eAAe,IAAI;AAChD,cAAM,UAAUA,KAAG,SAAS,QAAQ;AACpC,cAAM,SAAS,mBAAmB,SAAS,QAAQ;AACnD,YAAI,QAAQ;AACV,eAAK,MAAM,IAAI,OAAO,IAAI,MAAM;AAAA,QAClC,OAAO;AACL,eAAK,KAAK,QAAQ,KAAK,oBAAoB,oBAAoB,IAAI,EAAE;AAAA,QACvE;AAAA,MACF,SAAS,OAAO;AACd,aAAK,KAAK,QAAQ,KAAK,oBAAoB,mBAAmB,IAAI,IAAI,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,MAChG;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIA,mBAAuC;AACrC,SAAK,kBAAkB;AACvB,WAAO,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC,EAClC,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC,EAC3C,IAAID,UAAS;AAAA,EAClB;AAAA,EAEA,mBAAmB,IAAoC;AACrD,SAAK,kBAAkB;AACvB,WAAO,KAAK,MAAM,IAAI,EAAE,KAAK;AAAA,EAC/B;AAAA,EAEA,aAAa,IAAqB;AAChC,SAAK,kBAAkB;AACvB,WAAO,KAAK,MAAM,IAAI,EAAE;AAAA,EAC1B;AAAA,EAEA,oBAA4C;AAC1C,SAAK,kBAAkB;AACvB,WAAO,CAAC;AAAA,EACV;AAAA,EAEA,mBAAmB,IAAqB;AACtC,WAAO,KAAK,WAAW,SAAS,EAAE;AAAA,EACpC;AAAA;AAAA,EAIA,gBAAgB,WAAwC;AACtD,SAAK,kBAAkB;AAEvB,UAAM,gBAAgB,KAAK,iBAAiB,SAAS;AACrD,QAAI,CAAC,cAAe,QAAO,EAAE,SAAS,OAAO,OAAO,gCAAgC,SAAS,GAAG;AAEhG,UAAM,WAAW,KAAK,gBAAgB,SAAS;AAC/C,QAAI,SAAU,QAAO,EAAE,SAAS,OAAO,OAAO,+BAA+B,SAAS,GAAG;AAEzF,WAAO,KAAK,WAAW,EAAE,GAAG,eAAe,WAAW,cAAc,GAAG,CAAC;AAAA,EAC1E;AAAA,EAEA,kBAAkB,IAAiC;AACjD,SAAK,kBAAkB;AAEvB,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAClC,QAAI,CAAC,SAAU,QAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,EAAE,GAAG;AAC5E,QAAI,CAAC,SAAS,UAAW,QAAO,EAAE,SAAS,OAAO,OAAO,qDAAqD;AAE9G,UAAM,gBAAgB,KAAK,iBAAiB,SAAS,SAAS;AAC9D,QAAI,CAAC,cAAe,QAAO,EAAE,SAAS,OAAO,OAAO,+BAA+B,SAAS,SAAS,GAAG;AAExG,WAAO,KAAK,WAAW,EAAE,GAAG,eAAe,IAAI,SAAS,IAAI,WAAW,SAAS,UAAU,CAAC;AAAA,EAC7F;AAAA,EAEA,sBAAsB,OAAsD;AAC1E,SAAK,kBAAkB;AAEvB,UAAM,SAAS,mBAAmB,OAAO,iBAAiB;AAC1D,QAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,OAAO,OAAO,qCAAqC;AAElF,QAAI,KAAK,MAAM,IAAI,OAAO,EAAE,EAAG,QAAO,EAAE,SAAS,OAAO,OAAO,6BAA6B,OAAO,EAAE,GAAG;AAExG,UAAM,SAAS,KAAK,WAAW,MAAM;AACrC,WAAO,EAAE,GAAG,QAAQ,IAAI,OAAO,UAAU,OAAO,KAAK,OAAU;AAAA,EACjE;AAAA;AAAA,EAIA,gBAAgB,IAAY,SAAwD;AAClF,SAAK,kBAAkB;AAEvB,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAClC,QAAI,CAAC,SAAU,QAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,EAAE,GAAG;AAE5E,WAAO,KAAK,WAAW,EAAE,GAAG,UAAU,GAAG,SAAS,GAAG,CAAC;AAAA,EACxD;AAAA,EAEA,gBAAgB,QAA4E;AAC1F,SAAK,kBAAkB;AAEvB,UAAM,KAAK,KAAK,KAAK,aAAa,KAAK,UAAU,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AACxE,UAAM,YAA6B,EAAE,GAAG,QAAQ,IAAI,WAAW,OAAU;AAEzE,UAAM,SAAS,KAAK,WAAW,SAAS;AACxC,WAAO,EAAE,GAAG,QAAQ,IAAI,OAAO,UAAU,KAAK,OAAU;AAAA,EAC1D;AAAA,EAEA,gBAAgB,IAAiC;AAC/C,SAAK,kBAAkB;AAEvB,QAAI,KAAK,WAAW,SAAS,EAAE,EAAG,QAAO,EAAE,SAAS,OAAO,OAAO,gDAAgD;AAElH,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAClC,QAAI,CAAC,SAAU,QAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,EAAE,GAAG;AAE5E,QAAI;AACF,YAAM,WAAW,KAAK,KAAK,GAAG,SAAS,KAAK,KAAK,eAAe,GAAG,EAAE,KAAK;AAC1E,UAAI,KAAK,KAAK,GAAG,WAAW,QAAQ,EAAG,MAAK,KAAK,GAAG,WAAW,QAAQ;AACvE,WAAK,MAAM,OAAO,EAAE;AACpB,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,aAAO,EAAE,SAAS,OAAO,OAAO,OAAO,KAAK,EAAE;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,eAAe,IAAiC;AAC9C,SAAK,kBAAkB;AAEvB,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAClC,QAAI,CAAC,UAAU,UAAW,QAAO,EAAE,SAAS,OAAO,OAAO,uCAAuC;AAEjG,UAAM,gBAAgB,KAAK,iBAAiB,SAAS,SAAS;AAC9D,QAAI,CAAC,cAAe,QAAO,EAAE,SAAS,OAAO,OAAO,6BAA6B,SAAS,SAAS,GAAG;AAEtG,WAAO,KAAK,WAAW,EAAE,GAAG,eAAe,IAAI,SAAS,IAAI,WAAW,SAAS,UAAU,CAAC;AAAA,EAC7F;AAAA;AAAA,EAIQ,gBAAgB,WAAgD;AACtE,WAAO,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS;AAAA,EAC9E;AAAA,EAEQ,WAAW,QAA8C;AAC/D,QAAI;AACF,YAAM,WAAW,KAAK,KAAK,GAAG,SAAS,KAAK,KAAK,eAAe,GAAG,OAAO,EAAE,KAAK;AACjF,WAAK,KAAK,GAAG,UAAU,UAAU,mBAAmB,MAAM,CAAC;AAC3D,WAAK,MAAM,IAAI,OAAO,IAAI,MAAM;AAChC,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,WAAK,KAAK,QAAQ,MAAM,oBAAoB,mBAAmB,OAAO,EAAE,IAAI,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AACpG,aAAO,EAAE,SAAS,OAAO,OAAO,OAAO,KAAK,EAAE;AAAA,IAChD;AAAA,EACF;AACF;;;AC5RA,YAAYE,YAAU;AACtB,OAAOC,aAAY;AAGZ,SAAS,eAAe,SAAiB,UAAmC;AACjF,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,SAAS,OAAO,IAAIA,QAAO,OAAO;AAEpD,UAAM,KAAK,GAAG,MAAW,gBAAS,UAAU,KAAK;AACjD,UAAM,OAAO,GAAG;AAChB,QAAI,CAAC,KAAM,QAAO;AAElB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,aAAa,GAAG,eAAe;AAAA,MAC/B,MAAM,UAAU,GAAG,IAAI;AAAA,MACvB,WAAW,kBAAkB,GAAG,SAAS;AAAA,MACzC,OAAO,MAAM,QAAQ,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAAA,MAC7C,QAAQ,OAAO,KAAK;AAAA,IACtB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAe,SAAiB,UAAiC;AAC/E,MAAI;AACF,UAAM,EAAE,MAAM,GAAG,IAAIA,QAAO,OAAO;AACnC,WAAO,GAAG,MAAW,gBAAS,UAAU,KAAK;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,KAAwB;AACzC,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO,IACJ,MAAM,GAAG,EACT,IAAI,CAACC,OAAMA,GAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAAA,EACnB;AACA,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO,IAAI,IAAI,MAAM,EAAE,OAAO,OAAO;AAC7D,SAAO,CAAC;AACV;AAEA,SAAS,kBAAkB,KAA2C;AACpE,MAAI,QAAQ,WAAW,QAAQ,UAAW,QAAO;AACjD,SAAO;AACT;;;AC9CA,OAAOC,eAAc;AACrB,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAItB,IAAM,qBAAqB;AAuF3B,IAAM,gBAAqC;AAAA,EACzC,KAAK,WAAW,SAAS,OAAO;AAC9B,YAAQ,KAAK,cAAc,OAAO,IAAI,SAAS,EAAE;AAAA,EACnD;AACF;AAIO,IAAM,gBAAN,MAAoB;AAAA,EACjB,KAA+B;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA,yBAAyB,oBAAI,IAAoB;AAAA,EAElE,YAAY,WAAmBC,UAAoE;AACjG,SAAK,YAAY;AACjB,SAAK,SAASA,UAAS,UAAU;AACjC,SAAK,gBAAgBA,UAAS;AAAA,EAChC;AAAA,EAEQ,UAAU,KAAmB;AACnC,QAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,MAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AAAA,EACF;AAAA,EAEQ,QAA2B;AACjC,QAAI,KAAK,GAAI,QAAO,KAAK;AAEzB,SAAK,UAAU,KAAK,SAAS;AAC7B,UAAM,SAAc,YAAK,KAAK,WAAW,kBAAkB;AAC3D,SAAK,KAAK,KAAK,gBAAgB,IAAIH,UAAS,QAAQ,EAAE,eAAe,KAAK,cAAc,CAAC,IAAI,IAAIA,UAAS,MAAM;AAChH,SAAK,GAAG,OAAO,oBAAoB;AAEnC,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,qCAKoB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAuBlD;AAED,SAAK,gBAAgB,KAAK,EAAE;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,YAAY,IAAuB,WAA4B;AACrE,UAAM,MAAM,GAAG,QAAQ,+DAA+D,EAAE,IAAI,SAAS;AAGrG,WAAO,CAAC,CAAC;AAAA,EACX;AAAA,EAEQ,gBAAgB,IAAuB,WAA6B;AAC1E,UAAM,YAAY,GAAG,OAAO,cAAc,SAAS,GAAG;AACtD,WAAO,UAAU,IAAI,CAAC,QAAQ,IAAI,IAAI;AAAA,EACxC;AAAA,EAEQ,8BAA8B,IAAuB,WAAyB;AACpF,UAAM,iBAAiB,KAAK,gBAAgB,IAAI,SAAS;AACzD,QAAI,CAAC,eAAe,SAAS,gBAAgB,GAAG;AAC9C,SAAG,KAAK,eAAe,SAAS,iCAAiC;AAAA,IACnE;AACA,QAAI,CAAC,eAAe,SAAS,aAAa,GAAG;AAC3C,SAAG,KAAK,eAAe,SAAS,8BAA8B;AAAA,IAChE;AACA,QAAI,CAAC,eAAe,SAAS,eAAe,GAAG;AAC7C,SAAG,KAAK,eAAe,SAAS,gCAAgC;AAAA,IAClE;AACA,QAAI,CAAC,eAAe,SAAS,WAAW,GAAG;AACzC,SAAG,KAAK,eAAe,SAAS,4BAA4B;AAAA,IAC9D;AACA,QAAI,CAAC,eAAe,SAAS,kBAAkB,GAAG;AAChD,SAAG,KAAK,eAAe,SAAS,mCAAmC;AAAA,IACrE;AACA,QAAI,CAAC,eAAe,SAAS,cAAc,GAAG;AAC5C,SAAG,KAAK,eAAe,SAAS,4CAA4C;AAAA,IAC9E;AAAA,EACF;AAAA,EAEQ,gBAAgB,IAA6B;AACnD,QAAI;AACF,YAAM,6BAA6B,KAAK,YAAY,IAAI,iBAAiB;AAEzE,UAAI,4BAA4B;AAC9B,cAAM,cAAc,KAAK,gBAAgB,IAAI,iBAAiB;AAC9D,YAAI,CAAC,YAAY,SAAS,cAAc,GAAG;AACzC,aAAG,KAAK,qEAAqE,kBAAkB,GAAG;AAAA,QACpG;AACA,YAAI,CAAC,YAAY,SAAS,mBAAmB,GAAG;AAC9C,aAAG,KAAK,+DAA+D;AAAA,QACzE;AAEA,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA,kEAIkD,kBAAkB;AAAA;AAAA;AAAA,SAG3E;AAAA,MACH;AAEA,WAAK,8BAA8B,IAAI,YAAY;AAEnD,YAAM,iBAAiB,KAAK,gBAAgB,IAAI,YAAY;AAC5D,YAAM,0BAA0B,eAAe,SAAS,iBAAiB;AACzE,YAAM,2BACJ,CAAC,eAAe,SAAS,WAAW,KACpC,CAAC,eAAe,SAAS,kBAAkB,KAC3C,CAAC,eAAe,SAAS,cAAc,KACvC;AAEF,UAAI,2BAA2B,CAAC,eAAe,SAAS,YAAY,GAAG;AACrE,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SA8BP;AAAA,MACH;AAEA,UAAI,4BAA4B;AAC9B,WAAG,KAAK;AAAA;AAAA;AAAA,SAGP;AAAA,MACH;AAEA,UAAI,4BAA4B,KAAK,2BAA2B,EAAE,GAAG;AACnE,aAAK,oBAAoB,EAAE;AAAA,MAC7B;AAEA,SAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,OAKP;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,uCAAuC,KAAK;AAAA,IAC5D;AAAA,EACF;AAAA,EAEQ,2BAA2B,IAAgC;AACjE,UAAM,MAAM,GACT;AAAA,MACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAeF,EACC,IAAI;AACP,WAAO,CAAC,CAAC;AAAA,EACX;AAAA,EAEQ,oBAAoB,IAA6B;AACvD,UAAM,UAAU,GAAG,QAAQ,wBAAwB,EAAE,IAAI;AACzD,UAAM,gBAAgB,GAAG;AAAA,MACvB;AAAA,IACF;AACA,UAAM,eAAe,GAAG,QAAQ,uDAAuD;AAEvF,UAAM,KAAK,GAAG,YAAY,MAAM;AAC9B,iBAAW,UAAU,SAAS;AAC5B,cAAM,WAAW,GACd;AAAA,UACC;AAAA;AAAA,QAEF,EACC,IAAI,OAAO,EAAE;AAEhB,YAAI,aAA4B;AAChC,mBAAW,WAAW,UAAU;AAC9B,gBAAM,WAAW,QAAQ,aAAa,SAAY,aAAc,QAAQ,YAAY;AACpF,wBAAc,IAAI,UAAU,QAAQ,kBAAkB,QAAQ,IAAI,QAAQ,EAAE;AAC5E,uBAAa,QAAQ;AAAA,QACvB;AAEA,cAAM,YAAY,GACf,QAAQ,uEAAuE,EAC/E,IAAI,OAAO,EAAE;AAChB,YAAI,CAAC,WAAW,mBAAmB,YAAY;AAC7C,uBAAa,IAAI,YAAY,OAAO,EAAE;AAAA,QACxC;AAAA,MACF;AAAA,IACF,CAAC;AAED,OAAG;AAAA,EACL;AAAA,EAEQ,WAAW,QAAwB;AACzC,WAAO,GAAG,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,EAC1E;AAAA,EAEQ,gBAAgB,KAA8B;AACpD,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,UAAU,IAAI;AAAA,MACd,MAAM,IAAI;AAAA,MACV,SAAS,IAAI;AAAA,MACb,WAAW,IAAI;AAAA,MACf,UAAU,IAAI,YAAY;AAAA,MAC1B,cAAc,IAAI,eAAe,KAAK,MAAM,IAAI,YAAY,IAAI;AAAA,MAChE,kBAAkB,IAAI,oBAAoB;AAAA,MAC1C,eAAe,IAAI,gBAAgB,KAAK,MAAM,IAAI,aAAa,IAAI;AAAA,MACnE,YAAY,IAAI,aAAa,KAAK,MAAM,IAAI,UAAU,IAAI;AAAA,IAC5D;AAAA,EACF;AAAA,EAEQ,cAAc,WAAwC;AAC5D,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,GACT;AAAA,MACC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,EACC,IAAI,SAAS;AAChB,WAAO,OAAO;AAAA,EAChB;AAAA,EAEQ,mBAAmB,UAAiC;AAC1D,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,GAAG,QAAQ,uEAAuE,EAAE,IAAI,QAAQ;AAG5G,QAAI,KAAK,iBAAiB;AACxB,YAAM,eAAe,GAAG,QAAQ,uCAAuC,EAAE,IAAI,IAAI,eAAe;AAChG,UAAI,aAAc,QAAO,IAAI;AAAA,IAC/B;AAEA,UAAM,WAAW,GACd,QAAQ,yFAAyF,EACjG,IAAI,QAAQ;AACf,QAAI,UAAU,IAAI;AAChB,SAAG,QAAQ,uDAAuD,EAAE,IAAI,SAAS,IAAI,QAAQ;AAC7F,aAAO,SAAS;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,kBAAkB,UAAkC;AAC1D,WAAO,KAAK,MAAM,EACf;AAAA,MACC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,EACC,IAAI,QAAQ;AAAA,EACjB;AAAA,EAEQ,kBAAkB,UAAkB,eAA+C;AACzF,QAAI,kBAAkB,KAAM,QAAO,CAAC;AAEpC,UAAM,UAAU,KAAK,kBAAkB,QAAQ;AAC/C,QAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAElC,UAAM,SAAS,IAAI,IAAI,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;AAC1D,QAAI,YAAY,iBAAiB,KAAK,mBAAmB,QAAQ;AACjE,UAAME,SAAuB,CAAC;AAC9B,UAAM,OAAO,oBAAI,IAAY;AAE7B,WAAO,aAAa,CAAC,KAAK,IAAI,SAAS,GAAG;AACxC,WAAK,IAAI,SAAS;AAClB,YAAM,MAAM,OAAO,IAAI,SAAS;AAChC,UAAI,CAAC,IAAK;AACV,MAAAA,OAAK,KAAK,GAAG;AACb,kBAAY,IAAI;AAAA,IAClB;AAEA,WAAOA,OAAK,SAAS,IAAIA,OAAK,QAAQ,IAAI;AAAA,EAC5C;AAAA;AAAA,EAIA,QAAc;AACZ,QAAI,KAAK,IAAI;AACX,WAAK,GAAG,MAAM;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA;AAAA,EAIA,cAGG;AACD,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,GACZ,QAAQ,gFAAgF,EACxF,IAAI;AAEP,WAAO,OAAO,IAAI,CAACE,OAAM;AACvB,YAAM,UAAU,GAAG,OAAO,eAAeA,GAAE,IAAI,IAAI;AAMnD,aAAO;AAAA,QACL,MAAMA,GAAE;AAAA,QACR,SAAS,QAAQ,IAAI,CAAC,OAAO;AAAA,UAC3B,MAAM,EAAE;AAAA,UACR,MAAM,EAAE;AAAA,UACR,SAAS,CAAC,CAAC,EAAE;AAAA,UACb,IAAI,CAAC,CAAC,EAAE;AAAA,QACV,EAAE;AAAA,MACJ;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,aAAa,KAMX;AACA,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,UAAU,IAAI,KAAK;AACzB,UAAM,WAAW,WAAW,KAAK,OAAO;AAExC,QAAI,UAAU;AACZ,YAAM,OAAO,GAAG,QAAQ,OAAO;AAC/B,YAAM,OAAO,KAAK,IAAI;AACtB,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,YAAM,UAAU,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC;AAC1D,aAAO;AAAA,QACL;AAAA,QACA,MAAM,KAAK,IAAI,CAAC,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAAA,QAC9C,UAAU,KAAK;AAAA,QACf;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF,OAAO;AACL,YAAM,SAAS,GAAG,QAAQ,OAAO,EAAE,IAAI;AACvC,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,aAAO;AAAA,QACL,SAAS,CAAC,WAAW,iBAAiB;AAAA,QACtC,MAAM,CAAC,CAAC,OAAO,SAAS,OAAO,OAAO,eAAe,CAAC,CAAC;AAAA,QACvD,UAAU;AAAA,QACV;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIA,aAAa,WAAmB,OAA2B,aAA6B;AACtF,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,UAAM,KAAK,KAAK,WAAW,MAAM;AAEjC,OAAG;AAAA,MACD;AAAA;AAAA,IAEF,EAAE,IAAI,IAAI,WAAW,SAAS,MAAM,aAAa,KAAK,GAAG;AAEzD,WAAO,EAAE,IAAI,WAAW,OAAO,SAAS,MAAM,aAAa,iBAAiB,MAAM,WAAW,KAAK,WAAW,IAAI;AAAA,EACnH;AAAA,EAEA,2BAAgD;AAC9C,UAAM,SAAS,oBAAI,IAAoB;AACvC,QAAI;AACF,YAAM,KAAK,KAAK,MAAM;AACtB,YAAM,OAAO,GAAG,QAAQ,uEAAuE,EAAE,IAAI;AAIrG,iBAAW,OAAO,MAAM;AACtB,eAAO,IAAI,IAAI,YAAY,IAAI,KAAK;AAAA,MACtC;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,WAA6B;AACtC,UAAM,KAAK,KAAK,MAAM;AACtB,WAAO,GACJ;AAAA,MACC;AAAA;AAAA;AAAA;AAAA,IAIF,EACC,IAAI,SAAS;AAAA,EAClB;AAAA,EAEA,UAAU,UAAiC;AACzC,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,GACT;AAAA,MACC;AAAA;AAAA;AAAA;AAAA,IAIF,EACC,IAAI,QAAQ;AACf,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,kBAAkB,UAAkB,OAAwB;AAC1D,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,UAAM,SAAS,GAAG,QAAQ,2DAA2D,EAAE,IAAI,OAAO,KAAK,QAAQ;AAC/G,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,aAAa,UAA2B;AACtC,UAAM,KAAK,KAAK,MAAM;AACtB,OAAG,QAAQ,6CAA6C,EAAE,IAAI,QAAQ;AACtE,UAAM,SAAS,GAAG,QAAQ,kCAAkC,EAAE,IAAI,QAAQ;AAC1E,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA;AAAA,EAIA,WACE,UACA,MACA,SACA,cACA,kBACA,eACA,YACW;AACX,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,UAAM,KAAK,KAAK,WAAW,KAAK;AAChC,UAAM,WAAW,KAAK,mBAAmB,QAAQ;AACjD,UAAM,iBAAiB;AACvB,UAAM,cAAc;AAEpB,UAAM,eAAe,SAAS,cAAc,KAAK,uBAAuB,IAAI,QAAQ,IAAI;AACxF,QAAI,cAAc;AAChB,WAAK,uBAAuB,OAAO,QAAQ;AAAA,IAC7C;AAEA,OAAG;AAAA,MACD;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,EAAE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,KAAK,UAAU,YAAY,IAAI;AAAA,MAC9C,oBAAoB;AAAA,MACpB,gBAAgB,KAAK,UAAU,aAAa,IAAI;AAAA,MAChD,aAAa,KAAK,UAAU,UAAU,IAAI;AAAA,MAC1C,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,OAAG,QAAQ,uEAAuE,EAAE,IAAI,IAAI,KAAK,QAAQ;AAEzG,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY,UAA+B;AACzC,WAAO,KAAK,kBAAkB,QAAQ,EAAE,IAAI,CAAC,QAAQ,KAAK,gBAAgB,GAAG,CAAC;AAAA,EAChF;AAAA,EAEA,cAAc,WAA4B;AACxC,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,GAAG,QAAQ,qCAAqC,EAAE,IAAI,SAAS;AAC9E,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,mBAAmB,UAAkB,WAAyB;AAC5D,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,KAAK,cAAc,SAAS;AAC3C,QAAI,CAAC,UAAU,OAAO,aAAa,UAAU;AAC3C,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AAEA,UAAM,aAAa,KAAK,kBAAkB,QAAQ;AAClD,UAAM,cAAc,WAAW,UAAU,CAAC,QAAQ,IAAI,OAAO,SAAS;AACtE,QAAI,cAAc,GAAG;AACnB,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,UAAM,cAAc,WAAW,MAAM,WAAW,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE;AACrE,UAAM,eAAe,YAAY,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACzD,OAAG,QAAQ,uCAAuC,YAAY,GAAG,EAAE,IAAI,GAAG,WAAW;AAErF,UAAM,YAAY,cAAc,IAAI,WAAW,cAAc,CAAC,EAAG,KAAK;AACtE,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,OAAG,QAAQ,uEAAuE,EAAE,IAAI,WAAW,KAAK,QAAQ;AAAA,EAClH;AAAA,EAEA,WAAW,gBAAwB,eAAuB,OAAwB;AAChF,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,KAAK,UAAU,cAAc;AAC5C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,UAAM,aAAa,KAAK,kBAAkB,cAAc;AACxD,UAAM,WAAW,WAAW,UAAU,CAAC,QAAQ,IAAI,OAAO,aAAa;AACvE,QAAI,WAAW,GAAG;AAChB,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,UAAM,iBAAiB,WAAW,MAAM,GAAG,WAAW,CAAC;AACvD,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,UAAM,YAAY,KAAK,WAAW,MAAM;AACxC,UAAM,YAAY,SAAS,GAAG,OAAO,SAAS,UAAU;AAExD,OAAG;AAAA,MACD;AAAA;AAAA,IAEF,EAAE,IAAI,WAAW,OAAO,WAAW,WAAW,OAAO,aAAa,KAAK,GAAG;AAE1E,UAAM,QAAQ,oBAAI,IAAoB;AACtC,QAAI,YAA2B;AAE/B,eAAW,OAAO,gBAAgB;AAChC,YAAM,WAAW,KAAK,WAAW,KAAK;AACtC,YAAM,IAAI,IAAI,IAAI,QAAQ;AAC1B,YAAM,cAAc,IAAI,WAAY,MAAM,IAAI,IAAI,QAAQ,KAAK,OAAQ;AAEvE,SAAG;AAAA,QACD;AAAA;AAAA;AAAA;AAAA;AAAA,MAKF,EAAE;AAAA,QACA;AAAA,QACA;AAAA,QACA,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,MACF;AACA,kBAAY;AAAA,IACd;AAEA,QAAI,WAAW;AACb,SAAG,QAAQ,uDAAuD,EAAE,IAAI,WAAW,SAAS;AAAA,IAC9F;AAEA,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,WAAW,OAAO;AAAA,MAClB,OAAO;AAAA,MACP,aAAa,OAAO;AAAA,MACpB,iBAAiB;AAAA,MACjB,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EAEA,qBAAqB,WAAmB,YAA0B;AAChE,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,GAAG,QAAQ,gDAAgD,EAAE,IAAI,YAAY,SAAS;AACrG,QAAI,OAAO,YAAY,EAAG,OAAM,IAAI,MAAM,mBAAmB;AAAA,EAC/D;AAAA,EAEA,uBAAuB,UAAkB,WAAyB;AAChE,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,KAAK,cAAc,SAAS;AAC3C,QAAI,CAAC,UAAU,OAAO,aAAa,UAAU;AAC3C,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AAEA,OAAG,QAAQ,4EAA4E,EAAE;AAAA,MACvF,OAAO;AAAA,MACP;AAAA,MACA;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AACpC,QAAI,MAAM,oBAAoB,WAAW;AACvC,YAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,SAAG,QAAQ,uEAAuE,EAAE;AAAA,QAClF,OAAO;AAAA,QACP;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,OAAG,QAAQ,qCAAqC,EAAE,IAAI,SAAS;AAAA,EACjE;AAAA,EAEA,mBACE,UACA,gBACA,MACA,SACA,eACA,YACW;AACX,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,UAAM,KAAK,KAAK,WAAW,KAAK;AAEhC,UAAM,eAAe,SAAS,cAAc,KAAK,uBAAuB,IAAI,QAAQ,IAAI;AACxF,QAAI,cAAc;AAChB,WAAK,uBAAuB,OAAO,QAAQ;AAAA,IAC7C;AAEA,UAAM,WAAW,GACd,QAAQ,0EAA0E,EAClF,IAAI,gBAAgB,QAAQ;AAE/B,OAAG;AAAA,MACD;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,EAAE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB,KAAK,UAAU,aAAa,IAAI;AAAA,MAChD,aAAa,KAAK,UAAU,UAAU,IAAI;AAAA,MAC1C,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,SAAG,QAAQ,kDAAkD,EAAE,IAAI,IAAI,SAAS,EAAE;AAClF,SAAG,QAAQ,gDAAgD,EAAE,IAAI,KAAK,QAAQ;AAAA,IAChF,OAAO;AACL,SAAG,QAAQ,uEAAuE,EAAE,IAAI,IAAI,KAAK,QAAQ;AAAA,IAC3G;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,oBAAoB,UAAkC;AACpD,UAAM,OAAO,KAAK,kBAAkB,QAAQ;AAC5C,UAAM,SAAyB;AAAA,MAC7B,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IACpB;AACA,eAAW,OAAO,MAAM;AACtB,UAAI,IAAI,YAAY;AAClB,cAAM,QAAQ,KAAK,MAAM,IAAI,UAAU;AACvC,eAAO,gBAAgB,MAAM;AAC7B,eAAO,oBAAoB,MAAM;AACjC,eAAO,eAAe,MAAM;AAC5B,eAAO,mBAAoB,MAAM,mBAAmB;AACpD,eAAO,oBAAqB,MAAM,oBAAoB;AAAA,MACxD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,uBAAuB,UAAkB,cAA4B;AACnE,SAAK,uBAAuB,IAAI,UAAU,YAAY;AAAA,EACxD;AAAA,EAEA,gBAAgB,WAAmB,cAA4B;AAC7D,UAAM,KAAK,KAAK,MAAM;AACtB,OAAG,QAAQ,sDAAsD,EAAE,IAAI,cAAc,SAAS;AAAA,EAChG;AAAA,EAEA,uBAA+B;AAC7B,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,GAAG,QAAQ,4EAA4E,EAAE,IAAI;AAC5G,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA,EAIA,mBACE,UACA,aACA,eACoG;AACpG,UAAM,WAAW,KAAK,kBAAkB,UAAU,aAAa,EAAE,IAAI,CAAC,QAAQ,KAAK,gBAAgB,GAAG,CAAC;AACvG,UAAM,gBAAgB,SAAS;AAAA,MAC7B,CAAC,OACE,EAAE,SAAS,UAAU,EAAE,SAAS,eAAe,EAAE,SAAS,eAC1D,EAAE,SAAS,KAAK,KAAM,EAAE,SAAS,eAAe,EAAE,eAAe,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAAA,IACnG;AAEA,QAAI;AACJ,aAAS,IAAI,cAAc,SAAS,GAAG,KAAK,GAAG,KAAK;AAClD,UAAI,cAAc,CAAC,EAAE,SAAS,WAAW;AACvC,qBAAa,cAAc,CAAC;AAC5B;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AAEJ,QAAI,YAAY;AACd,YAAM,YAAY,WAAW,eAAe;AAAA,QAC1C,CAAC,MAA4D,EAAE,SAAS;AAAA,MAC1E;AACA,YAAM,iBAAiB,WAAW;AAElC,UAAI,CAAC,WAAW;AACd,aAAK,OAAO,KAAK,WAAW,4EAA4E;AAAA,UACtG;AAAA,UACA,WAAW,WAAW;AAAA,QACxB,CAAC;AAAA,MACH;AAEA,YAAM,kBAAkB,iBACpB,cAAc,OAAO,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,aAAa,cAAc,IACjF,CAAC;AAEL,eAAS;AAAA,QACP,EAAE,MAAM,WAAoB,SAAS,WAAW,QAAQ;AAAA,QACxD,GAAG,gBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,eAAe,EAAE,cAAc,EAAE;AAAA,MACtG;AAAA,IACF,OAAO;AACL,eAAS,cAAc,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,eAAe,EAAE,cAAc,EAAE;AAAA,IAC1G;AAEA,QAAI,eAAe,OAAO,SAAS,aAAa;AAC9C,UAAI,OAAO,SAAS,KAAK,OAAO,CAAC,EAAE,SAAS,WAAW;AACrD,cAAM,OAAO,OAAO,MAAM,CAAC;AAC3B,cAAM,YAAY,KAAK,MAAM,EAAE,cAAc,EAAE;AAC/C,eAAO,CAAC,OAAO,CAAC,GAAG,GAAG,SAAS;AAAA,MACjC;AACA,aAAO,OAAO,MAAM,CAAC,WAAW;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,kBACE,UACA,SACA,MACW;AACX,UAAM,gBAAgC;AAAA,MACpC;AAAA,QACE,MAAM;AAAA,QACN,yBAAyB,KAAK;AAAA,QAC9B,wBAAwB,KAAK;AAAA,MAC/B;AAAA,IACF;AAEA,WAAO,KAAK,WAAW,UAAU,WAAW,SAAS,QAAW,QAAW,aAAa;AAAA,EAC1F;AAAA,EAEA,iBAAiB,UAAoC;AACnD,UAAM,MAAM,CAAC,GAAG,KAAK,kBAAkB,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,YAAY,QAAQ,SAAS,SAAS;AACxG,WAAO,MAAM,KAAK,gBAAgB,GAAG,IAAI;AAAA,EAC3C;AAAA,EAEA,wBACE,UACA,kBACoG;AACpG,WAAO,KAAK,kBAAkB,QAAQ,EACnC,OAAO,CAAC,QAAQ,IAAI,YAAY,qBAAqB,IAAI,SAAS,UAAU,IAAI,SAAS,YAAY,EACrG,IAAI,CAAC,QAAQ,KAAK,qBAAqB,GAAG,CAAC;AAAA,EAChD;AAAA,EAEA,4BACE,UACoG;AACpG,WAAO,KAAK,kBAAkB,QAAQ,EACnC,OAAO,CAAC,QAAQ,IAAI,SAAS,UAAU,IAAI,SAAS,WAAW,EAC/D,IAAI,CAAC,QAAQ,KAAK,qBAAqB,GAAG,CAAC;AAAA,EAChD;AAAA,EAEQ,qBAAqB,KAK3B;AACA,WAAO;AAAA,MACL,MAAM,IAAI;AAAA,MACV,SAAS,IAAI;AAAA,MACb,WAAW,IAAI;AAAA,MACf,eAAe,IAAI,gBAAgB,KAAK,MAAM,IAAI,aAAa,IAAI;AAAA,IACrE;AAAA,EACF;AAAA,EAEA,4BAA4B,UAA0B;AACpD,UAAM,UAAU,KAAK,iBAAiB,QAAQ;AAC9C,QAAI,CAAC,SAAS;AACZ,aAAO,KAAK,kBAAkB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS,UAAU,IAAI,SAAS,WAAW,EAAE;AAAA,IAC3G;AAEA,UAAM,YAAY,QAAQ,eAAe;AAAA,MACvC,CAAC,MAA4D,EAAE,SAAS;AAAA,IAC1E;AACA,UAAM,WAAW,WAAW,2BAA2B,QAAQ;AAE/D,WAAO,KAAK,kBAAkB,QAAQ,EAAE;AAAA,MACtC,CAAC,QAAQ,IAAI,aAAa,aAAa,IAAI,SAAS,UAAU,IAAI,SAAS;AAAA,IAC7E,EAAE;AAAA,EACJ;AACF;;;AC79BA,SAAS,gBAAgB;AAEzB,IAAI,UAA2B;AAC/B,IAAI,cAAoC;AAOxC,eAAsB,gBAA+B;AACnD,MAAI,QAAS;AACb,MAAI,YAAa,QAAO;AAExB,iBAAe,YAAY;AACzB,UAAM,SAAS,MAAM,OAAO,+BAA+B,GAAG;AAC9D,cAAU,IAAI,SAAS,KAAK;AAAA,EAC9B,GAAG;AAEH,SAAO;AACT;AAMA,SAAS,eAAe,MAAsB;AAC5C,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,aAAW,MAAM,MAAM;AACrB,UAAM,KAAK,GAAG,YAAY,CAAC,KAAK;AAEhC,QACG,MAAM,SAAU,MAAM,SACtB,MAAM,SAAU,MAAM,SACtB,MAAM,SAAU,MAAM,SACtB,MAAM,SAAU,MAAM,OACvB;AACA;AAAA,IACF,OAAO;AACL;AAAA,IACF;AAAA,EACF;AACA,SAAO,KAAK,KAAK,MAAM,MAAM,QAAQ,CAAC;AACxC;AAMO,SAAS,YAAY,MAAsB;AAChD,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,SAAS;AACX,WAAO,QAAQ,OAAO,IAAI,EAAE;AAAA,EAC9B;AACA,SAAO,eAAe,IAAI;AAC5B;AAMO,SAAS,oBACd,UACA,cACQ;AACR,MAAI,QAAQ;AAEZ,MAAI,cAAc;AAChB,aAAS,YAAY,YAAY,IAAI;AAAA,EACvC;AAEA,aAAW,OAAO,UAAU;AAC1B,aAAS,YAAY,IAAI,OAAO,IAAI;AAAA,EACtC;AAGA,WAAS;AAET,SAAO;AACT;;;AChGO,IAAM,uBAAuB;AAEpC,IAAM,oBAAoB;AAAA;AAAA;AAI1B,IAAM,qBAAqB;AAC3B,IAAM,2BAA2B;AAAA;AAAA,EAAO,kBAAkB;AASnD,SAAS,oBAAoB,MAA6B;AAC/D,QAAM,iBAAiB,KAAK,WAAW,KAAK,KAAK,QAAQ,KAAK;AAC9D,SAAO,KAAK,KAAK,EAAE,KAAK,KAAK,IAAI,WAAM,KAAK,WAAW,GAAG,cAAc;AAC1E;AAEO,SAAS,mBAAmB,OAAyC;AAC1E,QAAM,eAAe,wBAAwB,KAAK;AAClD,MAAI,aAAa,WAAW,EAAG,QAAO;AAEtC,SAAO,GAAG,iBAAiB;AAAA;AAAA,EAE3B,aAAa,KAAK,IAAI,CAAC;AAAA;AAAA,EAEvB,kBAAkB;AACpB;AAEO,SAAS,qBAAqB,UAAqC,OAAyC;AACjH,QAAM,eAAe,wBAAwB,KAAK;AAClD,MAAI,aAAa,WAAW,EAAG,QAAO,YAAY;AAClD,MAAI,CAAC,SAAU,QAAO,mBAAmB,YAAY;AAErD,QAAM,YAAY;AAAA,EAAK,aAAa,KAAK,IAAI,CAAC;AAC9C,MAAI,CAAC,SAAS,SAAS,wBAAwB,GAAG;AAChD,WAAO,GAAG,QAAQ,GAAG,SAAS;AAAA,EAChC;AAEA,SAAO,SAAS,QAAQ,0BAA0B,GAAG,SAAS,GAAG,wBAAwB,EAAE;AAC7F;AAEA,SAAS,wBAAwB,OAAoC;AACnE,SAAO,MAAM,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EAAE,OAAO,OAAO;AACxD;;;ACaA,SAASC,WAAU,KAA6B;AAC9C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,aAAa,IAAI;AAAA,IACjB,MAAM,IAAI;AAAA,IACV,WAAW,IAAI;AAAA,IACf,OAAO,IAAI;AAAA,IACX,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,SAAS,gBAAgB,OAAe,WAA2B;AACjE,MAAI,MAAM,SAAS,YAAY,EAAG,QAAO;AACzC,QAAM,mBAAmB,MAAM,QAAQ,SAAS,CAAC;AACjD,MAAI,qBAAqB,GAAI,QAAO;AACpC,SAAO,MAAM,MAAM,GAAG,gBAAgB,IAAI;AAAA,aAAgB,SAAS,KAAK,MAAM,MAAM,gBAAgB;AACtG;AAEA,SAAS,eAAe,SAAyB;AAC/C,SAAO,QAAQ,QAAQ,qBAAqB,IAAI;AAClD;AAEA,SAAS,WAAW,SAAyB;AAC3C,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAS,QAAQ,KAAK,OAAO,QAAQ,WAAW,CAAC,IAAK;AAAA,EACxD;AACA,SAAO,KAAK,SAAS,EAAE;AACzB;AAIO,IAAM,mBAAN,MAAuB;AAAA,EACpB;AAAA,EACA,cAAc,oBAAI,IAAsB;AAAA,EACxC,gBAAgB,oBAAI,IAAoB;AAAA,EACxC,QAAQ,oBAAI,IAAsB;AAAA,EAClC,cAAc;AAAA,EACd;AAAA,EAER,YAAY,MAA4B;AACtC,SAAK,OAAO;AACZ,SAAK,SAAS,KAAK,eAAe;AAClC,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,CAAC,KAAK,KAAK,iBAAkB;AACjC,eAAW,EAAE,IAAI,QAAQ,KAAK,KAAK,KAAK,kBAAkB;AACxD,WAAK,cAAc,IAAI,IAAI,OAAO;AAClC,YAAM,MAAM,eAAe,SAAS,GAAG,EAAE,KAAK;AAC9C,UAAI,IAAK,MAAK,YAAY,IAAI,IAAI,GAAG;AAAA,IACvC;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,QAAI,CAAC,KAAK,YAAa,MAAK,KAAK;AAAA,EACnC;AAAA;AAAA,EAIA,OAAwB;AACtB,UAAM,EAAE,IAAAC,MAAI,UAAU,IAAI,KAAK;AAC/B,IAAAA,KAAG,UAAU,SAAS;AACtB,SAAK,QAAQ;AAEb,SAAK,cAAc;AACnB,SAAK,KAAK,QAAQ,KAAK,gBAAgB,eAAe,EAAE,OAAO,KAAK,MAAM,KAAK,CAAC;AAEhF,WAAO,EAAE,OAAO,KAAK,MAAM,KAAK;AAAA,EAClC;AAAA,EAEQ,UAAgB;AACtB,UAAM,EAAE,IAAAA,MAAI,UAAU,IAAI,KAAK;AAC/B,SAAK,MAAM,MAAM;AAEjB,UAAM,QAAQA,KAAG,UAAU,WAAW,KAAK;AAC3C,eAAW,QAAQ,OAAO;AACxB,UAAI;AACF,cAAM,WAAWA,KAAG,SAAS,WAAW,IAAI;AAC5C,cAAM,UAAUA,KAAG,SAAS,QAAQ;AACpC,cAAM,MAAM,eAAe,SAAS,QAAQ;AAC5C,YAAI,KAAK;AACP,eAAK,MAAM,IAAI,IAAI,IAAI,GAAG;AAAA,QAC5B,OAAO;AACL,eAAK,KAAK,QAAQ,KAAK,gBAAgB,oBAAoB,IAAI,EAAE;AAAA,QACnE;AAAA,MACF,SAAS,OAAO;AACd,aAAK,KAAK,QAAQ,KAAK,gBAAgB,mBAAmB,IAAI,IAAI,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIA,eAA+B;AAC7B,SAAK,kBAAkB;AACvB,WAAO,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC,EAAE,IAAID,UAAS;AAAA,EACtD;AAAA,EAEA,eAAe,IAA6B;AAC1C,SAAK,kBAAkB;AACvB,WAAO,KAAK,MAAM,IAAI,EAAE,KAAK;AAAA,EAC/B;AAAA;AAAA,EAIA,oBAAwC;AACtC,SAAK,kBAAkB;AAEvB,WAAO,MAAM,KAAK,KAAK,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,WAAWE,QAAO,MAAM;AAC1E,YAAM,YAAY,KAAK,gBAAgB,SAAS;AAChD,YAAM,WAAW,CAAC,CAAC;AACnB,YAAM,YAAY,WAAW,KAAK,iBAAiB,WAAW,SAAU,IAAI;AAE5E,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,MAAMA,SAAQ;AAAA,QACd,aAAaA,SAAQ;AAAA,QACrB,MAAMA,SAAQ;AAAA,QACd,WAAWA,SAAQ;AAAA,QACnB,OAAOA,SAAQ;AAAA,QACf;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,YAAY,WAAsD;AAChE,SAAK,kBAAkB;AAEvB,UAAM,aAAa,KAAK,cAAc,IAAI,SAAS;AACnD,QAAI,CAAC,WAAY,QAAO,EAAE,SAAS,OAAO,OAAO,4BAA4B,SAAS,GAAG;AAEzF,UAAM,WAAW,KAAK,gBAAgB,SAAS;AAC/C,QAAI,SAAU,QAAO,EAAE,SAAS,OAAO,OAAO,2BAA2B,SAAS,GAAG;AAErF,UAAM,gBAAgB,gBAAgB,YAAY,SAAS;AAC3D,UAAM,MAAM,eAAe,eAAe,GAAG,SAAS,KAAK;AAC3D,QAAI,CAAC,IAAK,QAAO,EAAE,SAAS,OAAO,OAAO,kCAAkC,SAAS,GAAG;AACxF,QAAI,YAAY;AAEhB,UAAM,SAAS,KAAK,WAAW,IAAI,IAAI,eAAe,GAAG;AACzD,WAAO,EAAE,GAAG,QAAQ,IAAI,OAAO,UAAU,IAAI,KAAK,OAAU;AAAA,EAC9D;AAAA,EAEA,cAAc,IAA6B;AACzC,SAAK,kBAAkB;AAEvB,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAClC,QAAI,CAAC,SAAU,QAAO,EAAE,SAAS,OAAO,OAAO,oBAAoB,EAAE,GAAG;AACxE,QAAI,CAAC,SAAS,UAAW,QAAO,EAAE,SAAS,OAAO,OAAO,iDAAiD;AAE1G,UAAM,aAAa,KAAK,cAAc,IAAI,SAAS,SAAS;AAC5D,QAAI,CAAC,WAAY,QAAO,EAAE,SAAS,OAAO,OAAO,+BAA+B,SAAS,SAAS,GAAG;AAErG,UAAM,gBAAgB,gBAAgB,YAAY,SAAS,SAAS;AACpE,UAAM,MAAM,eAAe,eAAe,GAAG,EAAE,KAAK;AACpD,QAAI,CAAC,IAAK,QAAO,EAAE,SAAS,OAAO,OAAO,kCAAkC,SAAS,SAAS,GAAG;AACjG,QAAI,YAAY,SAAS;AAEzB,WAAO,KAAK,WAAW,IAAI,eAAe,GAAG;AAAA,EAC/C;AAAA,EAEA,kBAAkB,OAAkD;AAClE,SAAK,kBAAkB;AAEvB,UAAM,MAAM,eAAe,OAAO,iBAAiB;AACnD,QAAI,CAAC,IAAK,QAAO,EAAE,SAAS,OAAO,OAAO,iCAAiC;AAE3E,QAAI,KAAK,MAAM,IAAI,IAAI,EAAE,EAAG,QAAO,EAAE,SAAS,OAAO,OAAO,yBAAyB,IAAI,EAAE,GAAG;AAE9F,UAAM,SAAS,KAAK,WAAW,IAAI,IAAI,OAAO,GAAG;AACjD,WAAO,EAAE,GAAG,QAAQ,IAAI,OAAO,UAAU,IAAI,KAAK,OAAU;AAAA,EAC9D;AAAA;AAAA,EAIA,YAAY,IAAY,OAAgC;AACtD,SAAK,kBAAkB;AAEvB,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAClC,QAAI,CAAC,SAAU,QAAO,EAAE,SAAS,OAAO,OAAO,oBAAoB,EAAE,GAAG;AAExE,UAAM,MAAM,eAAe,OAAO,GAAG,EAAE,KAAK;AAC5C,QAAI,CAAC,IAAK,QAAO,EAAE,SAAS,OAAO,OAAO,gCAAgC;AAE1E,QAAI,KAAK;AACT,QAAI,SAAS,UAAW,KAAI,YAAY,SAAS;AAEjD,WAAO,KAAK,WAAW,IAAI,OAAO,GAAG;AAAA,EACvC;AAAA,EAEA,YAAY,OAAkD;AAC5D,SAAK,kBAAkB;AAEvB,UAAM,MAAM,eAAe,OAAO,cAAc;AAChD,QAAI,CAAC,IAAK,QAAO,EAAE,SAAS,OAAO,OAAO,gCAAgC;AAE1E,QAAI,KAAK,MAAM,IAAI,IAAI,EAAE,GAAG;AAC1B,UAAI,KAAK,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AAAA,IAC/C;AAEA,UAAM,SAAS,KAAK,WAAW,IAAI,IAAI,OAAO,GAAG;AACjD,WAAO,EAAE,GAAG,QAAQ,IAAI,OAAO,UAAU,IAAI,KAAK,OAAU;AAAA,EAC9D;AAAA,EAEA,YAAY,IAA6B;AACvC,SAAK,kBAAkB;AAEvB,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAClC,QAAI,CAAC,SAAU,QAAO,EAAE,SAAS,OAAO,OAAO,oBAAoB,EAAE,GAAG;AAExE,QAAI;AACF,YAAM,WAAW,KAAK,KAAK,GAAG,SAAS,KAAK,KAAK,WAAW,GAAG,EAAE,KAAK;AACtE,UAAI,KAAK,KAAK,GAAG,WAAW,QAAQ,EAAG,MAAK,KAAK,GAAG,WAAW,QAAQ;AACvE,WAAK,MAAM,OAAO,EAAE;AACpB,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,aAAO,EAAE,SAAS,OAAO,OAAO,OAAO,KAAK,EAAE;AAAA,IAChD;AAAA,EACF;AAAA;AAAA,EAIA,aAAa,UAA+B,cAAwC;AAClF,SAAK,kBAAkB;AAEvB,UAAM,aAAa,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC,EAAE,OAAO,CAAC,UAAU;AACnE,UAAI,MAAM,cAAc,SAAS,MAAM,cAAc,SAAU,QAAO;AACtE,UAAI,MAAM,MAAM,SAAS,KAAK,iBAAiB,QAAW;AACxD,YAAI,CAAC,MAAM,MAAM,MAAM,CAACC,OAAM,aAAa,SAASA,EAAC,CAAC,EAAG,QAAO;AAAA,MAClE;AACA,aAAO;AAAA,IACT,CAAC;AAED,QAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,UAAM,QAAQ,WAAW,MAAM,GAAG,oBAAoB;AACtD,WAAO,mBAAmB,MAAM,IAAI,mBAAmB,CAAC;AAAA,EAC1D;AAAA;AAAA,EAIQ,gBAAgB,WAAyC;AAC/D,WAAO,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS;AAAA,EAC9E;AAAA,EAEQ,WAAW,IAAY,OAAe,KAAgC;AAC5E,QAAI;AACF,YAAM,WAAW,KAAK,KAAK,GAAG,SAAS,KAAK,KAAK,WAAW,GAAG,EAAE,KAAK;AACtE,WAAK,KAAK,GAAG,UAAU,UAAU,KAAK;AACtC,WAAK,MAAM,IAAI,IAAI,GAAG;AACtB,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,WAAK,KAAK,QAAQ,MAAM,gBAAgB,mBAAmB,EAAE,IAAI,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AACzF,aAAO,EAAE,SAAS,OAAO,OAAO,OAAO,KAAK,EAAE;AAAA,IAChD;AAAA,EACF;AAAA,EAEQ,iBAAiB,WAAmB,WAA8B;AACxE,UAAM,aAAa,KAAK,cAAc,IAAI,SAAS;AACnD,QAAI,CAAC,WAAY,QAAO;AAExB,QAAI;AACF,YAAM,eAAe,KAAK,KAAK,GAAG,SAAS,KAAK,KAAK,WAAW,GAAG,UAAU,EAAE,KAAK;AACpF,YAAM,cAAc,KAAK,KAAK,GAAG,SAAS,YAAY;AACtD,aAAO,KAAK,OAAO,UAAU,MAAM,KAAK,OAAO,eAAe,WAAW,CAAC;AAAA,IAC5E,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACxUA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAItB,IAAM,kBAAkB;AAOxB,IAAMC,iBAAoC;AAAA,EACxC,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,MAAM,MAAM;AAAA,EAAC;AACf;AAEA,IAAM,SAAyB;AAAA,EAC7B,UAAU,KAAK;AACb,IAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACvC;AAAA,EACA,UAAU,KAAK,KAAK;AAClB,QAAI,CAAI,gBAAW,GAAG,EAAG,QAAO,CAAC;AACjC,WAAU,iBAAY,GAAG,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,GAAG,CAAC;AAAA,EAChE;AAAA,EACA,SAAS,UAAU;AACjB,WAAU,kBAAa,UAAU,OAAO;AAAA,EAC1C;AAAA,EACA,UAAU,UAAU,SAAS;AAC3B,IAAG,mBAAc,UAAU,SAAS,OAAO;AAAA,EAC7C;AAAA,EACA,WAAW,UAAU;AACnB,IAAG,gBAAW,QAAQ;AAAA,EACxB;AAAA,EACA,WAAW,UAAU;AACnB,WAAU,gBAAW,QAAQ;AAAA,EAC/B;AAAA,EACA,YAAY,OAAO;AACjB,WAAY,YAAK,GAAG,KAAK;AAAA,EAC3B;AACF;AAEO,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA;AAAA,EAER,YAAY,WAAmB,QAA6B;AAC1D,SAAK,SAAS,UAAUA;AACxB,SAAK,OAAO,IAAI,iBAAiB;AAAA,MAC/B,IAAI;AAAA,MACJ,WAAgB,YAAK,WAAW,eAAe;AAAA,MAC/C,kBAAkB,CAAC;AAAA,MACnB,QAAQ;AAAA,QACN,MAAM,MAAM;AAAA,QAAC;AAAA,QACb,MAAM,CAAC,WAAW,SAAS,SAAS,KAAK,OAAO,KAAK,SAAS,cAAc,IAAI,CAAC;AAAA,QACjF,OAAO,CAAC,WAAW,SAAS,SAAS,KAAK,OAAO,KAAK,SAAS,cAAc,IAAI,CAAC;AAAA,MACpF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,OAAwB;AACtB,UAAM,SAAS,KAAK,KAAK,KAAK;AAC9B,SAAK,OAAO,KAAK,6BAA6B,OAAO,KAAK,SAAS;AACnE,WAAO;AAAA,EACT;AAAA,EAEA,eAAe,IAA6B;AAC1C,WAAO,KAAK,KAAK,eAAe,EAAE;AAAA,EACpC;AAAA,EAEA,eAA+B;AAC7B,WAAO,KAAK,KAAK,aAAa;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,UAA+B,cAAwC;AAClF,WAAO,KAAK,KAAK,aAAa,UAAU,YAAY;AAAA,EACtD;AACF;AAEA,SAAS,cAAc,MAAoD;AACzE,MAAI,SAAS,OAAW,QAAO;AAC/B,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO;AACrE,SAAO,EAAE,KAAK;AAChB;;;AC3DO,SAAS,wBAAwBC,UAAsD;AAC5F,QAAM,EAAE,UAAU,cAAc,eAAe,gBAAgB,SAAS,QAAQ,IAAIA;AACpF,QAAM,OAAO,OAAO,WAAW,IAAI;AAEnC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa,OACT,mIACA;AAAA,IACJ,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,QACV,UAAU;AAAA,UACR,MAAM;AAAA,UACN,aAAa,OAAO,oBAAU;AAAA,QAChC;AAAA,MACF;AAAA,MACA,UAAU,CAAC,UAAU;AAAA,IACvB;AAAA,IACA,SAAS,OAAO,aAAqB,WAAoB;AACvD,YAAM,aAAc,UAAU,OAAO,WAAW,WAAW,SAAS,CAAC;AACrE,YAAM,UAAU,WAAW,YAAY;AACvC,YAAM,QAAyB,eAAe,OAAO;AACrD,UAAI,CAAC,OAAO;AACV,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,OAAO,mCAAU,kBAAkB,CAAC;AAAA,UAC7E,SAAS,EAAE,SAAS,OAAO,MAAM;AAAA,QACnC;AAAA,MACF;AAEA,UAAI,MAAM,cAAc,SAAS,MAAM,cAAc,UAAU;AAC7D,cAAM,WAAW,OACb,6CAAU,MAAM,cAAc,UAAU,iBAAO,cAAI,iBACnD,oCAAoC,MAAM,cAAc,UAAU,eAAe,cAAc;AACnG,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,SAAS,CAAC;AAAA,UACnD,SAAS,EAAE,SAAS,OAAO,MAAM,YAAY,MAAM;AAAA,QACrD;AAAA,MACF;AAEA,UAAI,MAAM,MAAM,SAAS,KAAK,iBAAiB,QAAW;AACxD,cAAM,UAAU,MAAM,MAAM,OAAO,CAACC,OAAM,EAAE,eAAe,IAAIA,EAAC,KAAK,UAAU,CAAC,aAAa,SAASA,EAAC,CAAC;AACxG,YAAI,QAAQ,SAAS,GAAG;AACtB,gBAAM,MAAM,OACR,6FAAkB,QAAQ,KAAK,IAAI,CAAC,KACpC,yDAAyD,QAAQ,KAAK,IAAI,CAAC;AAC/E,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,IAAI,CAAC;AAAA,YAC9C,SAAS,EAAE,SAAS,OAAO,MAAM,YAAY,OAAO,cAAc,QAAQ;AAAA,UAC5E;AAAA,QACF;AAAA,MACF;AAEA,YAAM,eAAe,OACjB,wVACA;AAEJ,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,GAAG,MAAM,MAAM,GAAG,YAAY,GAAG,CAAC;AAAA,QAC3E,SAAS,EAAE,SAAS,OAAO,MAAM,YAAY,KAAK;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AACF;;;AC3FO,IAAM,8BAA8B,CAAC,YAAY;AACjD,IAAM,kCAAkC,CAAC,cAAc;AAE9D,IAAM,qBAAqB,oBAAI,IAAI,CAAC,aAAa,CAAC;AAkB3C,SAAS,sCAAsC,cAAmD;AACvG,QAAM,aAAa,gBAAgB,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,mBAAmB,IAAI,QAAQ,CAAC;AAC7F,SAAO,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,WAAW,GAAG,+BAA+B,CAAC,CAAC;AAC/E;AAEO,SAAS,wCAAwC,cAA+D;AACrH,MAAI,CAAC,aAAc,QAAO;AAC1B,MAAI,aAAa,WAAW,EAAG,QAAO,CAAC;AACvC,SAAO,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,cAAc,GAAG,+BAA+B,CAAC,CAAC;AAClF;AAEO,SAAS,mCAAmC,SAA0B;AAC3E,QAAM,aAAa,QAAQ,KAAK,EAAE,YAAY;AAC9C,MAAI,CAAC,WAAY,QAAO;AAExB,SAAO,qKAAqK;AAAA,IAC1K;AAAA,EACF;AACF;AAEO,SAAS,+CAA+C,SAA0B;AACvF,QAAM,aAAa,QAAQ,KAAK,EAAE,YAAY;AAC9C,MAAI,CAAC,WAAY,QAAO;AACxB,MAAI,mCAAmC,UAAU,EAAG,QAAO;AAE3D,SAAO,sKAAsK;AAAA,IAC3K;AAAA,EACF;AACF;AAEO,SAAS,oCAAoCC,UAKhB;AAClC,QAAM,mBAAmB,IAAI,IAAIA,SAAQ,cAAc;AACvD,MAAI,CAAC,iBAAiB,IAAI,YAAY,KAAK,CAAC,iBAAiB,IAAI,cAAc,GAAG;AAChF,WAAO;AAAA,EACT;AACA,QAAM,gBAAgBA,SAAQ,iBAAiB;AAC/C,QAAM,cACJ,kBAAkB,aACd,mCAAmCA,SAAQ,WAAW,IACtD,+CAA+CA,SAAQ,WAAW;AACxE,MAAI,CAAC,aAAa;AAChB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQA,SAAQ,UAAU,SAAS,WAAW,IAAI;AACxD,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO,OAAO,6BAAS;AAAA,IACvB,OAAO,CAAC,cAAc,cAAc;AAAA,IACpC,UAAU,OACN,gdACA;AAAA,EACN;AACF;AAEO,SAAS,8BAA8BA,UAA2D;AACvG,QAAM,SAASA,SAAQ,UAAU;AACjC,QAAM,uBAAuBA,SAAQ,YAAY;AACjD,QAAM,+BACJA,SAAQ,kBAAkB,SACtBA,SAAQ,kBAAkB,eAC1BA,SAAQ,kCAAkC;AAChD,QAAM,wBACJ,mCAAmCA,SAAQ,eAAe,EAAE,KAC3D,gCAAgC,+CAA+CA,SAAQ,eAAe,EAAE;AAC3G,QAAM,mBAAmBA,SAAQ,wBAAwB,QAAQ,CAACA,SAAQ,WAAW;AAErF,MAAI,CAAC,wBAAwB,CAAC,kBAAkB;AAC9C,WAAO,EAAE,mBAAmB,MAAM;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,mBAAmB;AAAA,IACnB,UAAU,EAAE,GAAG,wBAAwB,MAAM,GAAG,WAAW,MAAM;AAAA,IACjE,qBAAqB,sCAAsCA,SAAQ,qBAAqB;AAAA,EAC1F;AACF;AAEO,SAAS,wBAAwB,SAAiB,SAAmB;AAC1E,QAAM,OAAO,OAAO,WAAW,IAAI;AACnC,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM,OAAO,6BAAS;AAAA,IACtB,aAAa,OAAO,qGAAqB;AAAA,IACzC,MAAM,CAAC,OAAO,iBAAO,OAAO;AAAA,IAC5B,WAAW;AAAA,IACX,OAAO,CAAC,gBAAgB,YAAY;AAAA,IACpC,QAAQ,OAAO,YAAY;AAAA,IAC3B,WAAW;AAAA,EACb;AACF;AAEA,SAAS,iBAAiB,QAAwB;AAChD,QAAM,QAAQ,wBAAwB,MAAM;AAC5C,QAAM,OAAO,OAAO,WAAW,IAAI;AACnC,QAAM,WAAW,OACb,sTACA;AACJ,SAAO,oBAAoB;AAAA,IACzB,IAAI,MAAM;AAAA,IACV,MAAM,MAAM;AAAA,IACZ,aAAa,MAAM;AAAA,IACnB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,+BACd,UACA,SAAiB,SACjB,cACe;AACf,MAAI,gBAAgB,CAAC,aAAa,SAAS,gCAAgC,CAAC,CAAC,EAAG,QAAO,YAAY;AACnG,MAAI,UAAU,SAAS,yBAAyB,EAAG,QAAO;AAE1D,QAAM,YAAY,iBAAiB,MAAM;AACzC,SAAO,qBAAqB,UAAU,CAAC,SAAS,CAAC,KAAK;AACxD;AAEO,SAAS,+BACd,IACA,SAAiB,SACjB,gBACiB;AACjB,MAAI,OAAO,0BAA2B,QAAO,wBAAwB,MAAM;AAC3E,SAAO,eAAe,EAAE;AAC1B;AAEA,IAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4ClB,IAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC3MX,IAAM,gCACX;AAMK,SAAS,6BAAmD;AACjE,SAAO,EAAE,YAAY,MAAM;AAC7B;AAEO,SAAS,wBAAwB,MAAsB,OAA6C;AACzG,QAAM,kBAAkB,KAAK;AAE7B,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,OACP,YACA,QACA,QACA,aACkC;AAClC,UAAI,KAAK,SAAS,kBAAkB,CAAC,MAAM,YAAY;AACrD,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,8BAA8B,CAAC;AAAA,UAC/D,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,gBAAgB,YAAY,QAAQ,QAAQ,QAAiB;AAElF,UAAI,KAAK,SAAS,cAAc;AAC9B,cAAM,aAAa;AAAA,MACrB;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACMA,SAAS,qBAA8B;AACrC,SAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,EAAE;AAAA,EACrE;AACF;AAEO,SAAS,sBAAsB,OAAmD;AACvF,MAAI,MAAM,SAAS,OAAQ,QAAO;AAClC,QAAM,EAAE,YAAY,QAAQ,OAAO,IAAI,MAAM;AAC7C,SACE,OAAO,eAAe,YACtB,WAAW,SAAS,KACpB,OAAO,WAAW,aACjB,WAAW,UAAU,WAAW;AAErC;AAGA,SAAS,oBAAoB,QAA2D;AACtF,MAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAM,UAAmC,CAAC;AAC1C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,CAAC,IAAI,WAAW,GAAG,EAAG,SAAQ,GAAG,IAAI;AAAA,EAC3C;AACA,SAAO;AACT;AAEA,SAAS,qBACP,SACA,YACA,WACkB;AAClB,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,KAAM,WAAW,OAAO;AAAA,IACxB,UAAU,WAAW,YAAY;AAAA,IACjC,OAAO,WAAW,MAAM;AAAA,IACxB,OAAO,mBAAmB;AAAA,IAC1B;AAAA,IACA,WAAW,KAAK,IAAI;AAAA,EACtB;AACF;AAEA,SAAS,sBAAsB,QAAwB,KAAkBC,UAA+B;AACtG,QAAM,EAAE,WAAW,kBAAkB,IAAIA,YAAW,CAAC;AACrD,QAAM,iBAAiB,CAAC,CAAC,qBAAqB,OAAO,KAAK,qBAAqB;AAE/E,MAAI,UAAuC,CAAC;AAE5C,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,QAAQ;AACzB,UAAI,MAAM,KAAK,KAAK,EAAG,SAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC;AAAA,IACxE,WAAW,MAAM,SAAS,WAAW,kBAAkB,MAAM,KAAK,KAAK,GAAG;AACxE,cAAQ,KAAK,EAAE,MAAM,YAAY,UAAU,MAAM,MAAM,kBAAkB,CAAC;AAAA,IAC5E,WAAW,sBAAsB,KAAK,GAAG;AACvC,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,IAAI,MAAM,KAAK;AAAA,QACf,MAAM,MAAM,KAAK;AAAA,QACjB,WAAW,oBAAoB,MAAM,KAAK,MAAM;AAAA,MAClD,CAAC;AACD,UAAI,KAAK,qBAAqB,SAAS,WAAW,SAAS,CAAC;AAC5D,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,YAAY,MAAM,KAAK;AAAA,QACvB,UAAU,MAAM,KAAK;AAAA,QACrB,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,uBAAuB,MAAM,KAAK,MAAM,KAAK,iBAAiB,CAAC;AAAA,QAC/F,SAAS,MAAM,KAAK,WAAW,MAAM,KAAK,WAAW;AAAA,QACrD,WAAW,KAAK,IAAI;AAAA,MACtB,CAAC;AACD,gBAAU,CAAC;AAAA,IACb;AAAA,EAEF;AAEA,MAAI,QAAQ,SAAS,GAAG;AACtB,QAAI,KAAK,qBAAqB,SAAS,QAAQ,SAAS,CAAC;AAAA,EAC3D;AACF;AAEO,SAAS,oBAAoB,UAAkCA,UAAsC;AAC1G,QAAM,MAAmB,CAAC;AAE1B,aAAW,OAAO,UAAU;AAC1B,QAAI,IAAI,SAAS,QAAQ;AACvB,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,IAAI,WAAW,GAAG,CAAC;AAAA,QACnD,WAAW,KAAK,IAAI;AAAA,MACtB,CAAC;AACD;AAAA,IACF;AAGA,UAAM,aAAa,IAAI,SAAS,eAAe,IAAI,eAAe,KAAK,qBAAqB;AAC5F,QAAI,YAAY;AACd,4BAAsB,IAAI,eAAgB,KAAKA,QAAO;AAAA,IACxD,OAAO;AACL,UAAI,KAAK,qBAAqB,CAAC,EAAE,MAAM,QAAQ,MAAM,IAAI,WAAW,GAAG,CAAC,GAAG,QAAQA,UAAS,SAAS,CAAC;AAAA,IACxG;AAAA,EACF;AAEA,SAAO;AACT;;;ACtIA,IAAM,yBAAyB;AAE/B,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAenC,IAAM,iCAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBvC,IAAMC,iBAAmC;AAAA,EACvC,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,OAAO,MAAM;AAAA,EAAC;AAChB;AAEA,eAAsB,iBACpB,UACA,QACA,cACA,YACA,aACA,SAA4BA,gBACA;AAC5B,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,EAAE,YAAY,OAAO,QAAQ,mBAAmB;AAAA,EACzD;AAEA,MAAI;AACF,UAAM,gBAAgB,WAAW,iBAAiB;AAClD,UAAM,kBAAkB,KAAK,MAAM,iBAAiB,OAAO,wBAAwB,OAAO,IAAI;AAE9F,UAAM,UAAU,YAAY,iBAAiB,QAAQ;AAErD,QAAI;AACJ,QAAI,SAAS;AACX,YAAM,YAAY,QAAQ,eAAe;AAAA,QACvC,CAAC,MAA4D,EAAE,SAAS;AAAA,MAC1E;AACA,YAAM,WAAW,WAAW,2BAA2B,QAAQ;AAC/D,iBAAW,YAAY,wBAAwB,UAAU,WAAW,CAAC;AAAA,IACvE,OAAO;AACL,iBAAW,YAAY,4BAA4B,QAAQ;AAAA,IAC7D;AAEA,UAAM,uBAAiE,CAAC;AACxE,QAAI,SAAS;AACX,2BAAqB,KAAK,EAAE,MAAM,aAAa,SAAS,QAAQ,QAAQ,CAAC;AAAA,IAC3E;AACA,eAAW,OAAO,UAAU;AAC1B,2BAAqB,KAAK,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI,QAAQ,CAAC;AAGlE,iBAAW,YAAY,wBAAwB,IAAI,aAAa,GAAG;AACjE,6BAAqB,KAAK,EAAE,MAAM,QAAQ,SAAS,SAAS,CAAC;AAAA,MAC/D;AAAA,IACF;AAEA,UAAM,gBAAgB,oBAAoB,sBAAsB,YAAY;AAE5E,WAAO,KAAK,eAAe,gBAAgB,aAAa,MAAM,eAAe,KAAK,aAAa,YAAY;AAAA,MACzG;AAAA,MACA,cAAc,SAAS;AAAA,MACvB,YAAY,CAAC,CAAC;AAAA,IAChB,CAAC;AAED,QAAI,gBAAgB,iBAAiB;AACnC,aAAO,EAAE,YAAY,OAAO,QAAQ,2BAA2B,cAAc,cAAc;AAAA,IAC7F;AAEA,UAAM,oBAAoB,KAAK,MAAM,iBAAiB,OAAO,oBAAoB,IAAI;AACrF,UAAM,EAAE,gBAAgB,mBAAmB,IAAI,4BAA4B,UAAU,iBAAiB;AAEtG,UAAM,2BAA2B;AACjC,QAAI,mBAAmB,SAAS,0BAA0B;AACxD,aAAO,EAAE,YAAY,OAAO,QAAQ,2BAA2B,cAAc,cAAc;AAAA,IAC7F;AAEA,UAAM,gBAAgB,CAAC,CAAC;AACxB,UAAM,gBAAgB,sBAAsB,oBAAoB,OAAO;AACvE,UAAM,eAAe,KAAK,IAAI,KAAK,MAAM,gBAAgB,GAAG,GAAG,KAAK;AAEpE,UAAM,WAAW,gBAAgB,iCAAiC;AAClE,UAAM,SAAS,SAAS,QAAQ,eAAe,OAAO,YAAY,CAAC,EAAE,QAAQ,cAAc,aAAa;AAExG,QAAI,cAAc,MAAM,WAAW,SAAS,QAAQ,YAAY;AAEhE,QAAI,CAAC,aAAa;AAChB,aAAO,KAAK,eAAe,oDAAoD;AAC/E,oBAAc,cAAc,eAAe,YAAY;AAAA,IACzD;AAEA,UAAM,iBACJ,eAAe,SAAS,IACpB,eAAe,CAAC,EAAE,YAClB,mBAAmB,mBAAmB,SAAS,CAAC,EAAG,YAAY;AAErE,gBAAY,kBAAkB,UAAU,aAAa;AAAA,MACnD,yBAAyB;AAAA,MACzB,wBAAwB,mBAAmB;AAAA,IAC7C,CAAC;AAED,UAAM,kBAA4D,CAAC,EAAE,MAAM,aAAa,SAAS,YAAY,CAAC;AAC9G,eAAW,KAAK,gBAAgB;AAC9B,sBAAgB,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,CAAC;AACzD,iBAAW,YAAY,wBAAwB,EAAE,aAAa,GAAG;AAC/D,wBAAgB,KAAK,EAAE,MAAM,QAAQ,SAAS,SAAS,CAAC;AAAA,MAC1D;AAAA,IACF;AACA,UAAM,cAAc,oBAAoB,iBAAiB,YAAY;AAErE,QAAI,eAAe,iBAAiB;AAClC,aAAO;AAAA,QACL;AAAA,QACA,uBAAuB,WAAW,sCAAsC,eAAe;AAAA,MACzF;AACA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,cAAc;AAAA,QACd;AAAA,QACA,gBAAgB;AAAA,MAClB;AAAA,IACF;AAEA,WAAO,KAAK,eAAe,eAAe,aAAa,WAAM,WAAW,SAAS;AACjF,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,cAAc;AAAA,MACd;AAAA,MACA,gBAAgB;AAAA,IAClB;AAAA,EACF,SAAS,OAAO;AACd,WAAO,MAAM,eAAe,sBAAsB,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAC1E,WAAO,EAAE,YAAY,OAAO,QAAQ,SAAS,OAAO,OAAO,KAAK,EAAE;AAAA,EACpE;AACF;AAEA,eAAsB,eACpB,UACA,QACA,cACA,YACA,aACA,QAC4B;AAC5B,QAAM,eAAe,YAAY,4BAA4B,QAAQ;AACrE,MAAI,eAAe,GAAG;AACpB,WAAO,EAAE,YAAY,OAAO,QAAQ,qBAAqB;AAAA,EAC3D;AAEA,QAAM,iBAAiB,EAAE,GAAG,QAAQ,SAAS,MAAM,uBAAuB,EAAE;AAC5E,SAAO,iBAAiB,UAAU,gBAAgB,cAAc,YAAY,aAAa,MAAM;AACjG;AAKA,SAAS,wBAAwB,QAAmC;AAClE,MAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,SAAO,OAAO,OAAO,qBAAqB,EAAE,IAAI,CAAC,UAAU,uBAAuB,MAAM,KAAK,MAAM,CAAC;AACtG;AAEA,SAAS,4BAA4B,KAAkC;AACrE,MAAI,SAAS,YAAY,IAAI,OAAO,IAAI;AACxC,aAAW,YAAY,wBAAwB,IAAI,aAAa,GAAG;AACjE,cAAU,YAAY,QAAQ,IAAI;AAAA,EACpC;AACA,SAAO;AACT;AAEA,SAAS,4BACP,UACA,mBAIA;AACA,MAAI,eAAe;AACnB,MAAI,aAAa,SAAS;AAE1B,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,YAAY,4BAA4B,SAAS,CAAC,CAAC;AACzD,QAAI,eAAe,YAAY,mBAAmB;AAChD,mBAAa,IAAI;AACjB;AAAA,IACF;AACA,oBAAgB;AAChB,QAAI,MAAM,GAAG;AACX,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AAAA,IACL,gBAAgB,SAAS,MAAM,UAAU;AAAA,IACzC,oBAAoB,SAAS,MAAM,GAAG,UAAU;AAAA,EAClD;AACF;AAEA,SAAS,sBACP,oBACA,iBACQ;AACR,QAAM,QAAkB,CAAC;AAEzB,MAAI,iBAAiB;AACnB,UAAM,KAAK;AAAA,EAAuC,gBAAgB,OAAO;AAAA,CAAI;AAC7E,UAAM,KAAK,2CAAsC;AAAA,EACnD;AAEA,aAAW,OAAO,oBAAoB;AACpC,UAAM,YAAY,IAAI,SAAS,SAAS,SAAS;AACjD,UAAM,KAAK,GAAG,SAAS,KAAK,IAAI,OAAO,EAAE;AACzC,eAAW,UAAU,IAAI,iBAAiB,CAAC,GAAG,OAAO,qBAAqB,GAAG;AAC3E,YAAM,KAAK,iBAAiB,MAAM,KAAK,IAAI;AAAA,EAAM,uBAAuB,MAAM,KAAK,MAAM,CAAC,EAAE;AAAA,IAC9F;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,MAAM;AAC1B;AAEA,SAAS,cAAc,OAAe,cAA8B;AAClE,QAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS;AACb,aAAW,QAAQ,OAAO;AACxB,UAAM,aAAa,YAAY,IAAI;AACnC,QAAI,SAAS,aAAa,aAAc;AACxC,WAAO,KAAK,IAAI;AAChB,cAAU;AAAA,EACZ;AACA,SAAO,OAAO,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG,eAAe,CAAC;AAC7D;;;ACzRA;AAAA,EACE;AAAA,OAIK;AAWP,IAAMC,0BAAyB;AAMxB,SAAS,4BAA4BC,UAAoE;AAC9G,QAAM,EAAE,SAAS,QAAQ,eAAe,QAAQ,IAAIA;AACpD,QAAM,gBAAgBA,SAAQ,iBAAiB,QAAQ,iBAAiBD;AAExE,SAAO;AAAA,IACL;AAAA,IACA,UAAU,OAAO,QAAgB,cAAsB;AACrD,sBAAgB;AAChB,UAAI;AACF,cAAM,SAAS,MAAM;AAAA,UACnB;AAAA,UACA;AAAA,YACE,cAAc;AAAA,YACd,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC,GAAG,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,UAC/F;AAAA,UACA,EAAE,QAAQ,UAAU;AAAA,QACtB;AACA,cAAM,OAAO,OAAO,QACjB,OAAO,CAAC,SAAgC,KAAK,SAAS,MAAM,EAC5D,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE;AACV,eAAO,QAAQ;AAAA,MACjB,SAAS,OAAO;AACd,kBAAU,KAAK;AACf,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;;;AC3CA,IAAM,uBAAuB;AAO7B,IAAME,iBAAkC;AAAA,EACtC,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,MAAM,MAAM;AAAA,EAAC;AACf;AAEO,SAAS,mBACd,UACA,QACA,SAA2BA,gBACtB;AACL,MAAI,CAAC,UAAU,CAAC,cAAc,MAAM,EAAG,QAAO;AAC9C,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,QAAM,aAAa,SAAS;AAC5B,MAAI,SAAc,CAAC,GAAG,QAAQ;AAC9B,QAAM,UAAoB,CAAC;AAE3B,MAAI,OAAO,iBAAiB,OAAO;AACjC,UAAM,UAAU,kBAAkB,MAAM;AACxC,QAAI,QAAQ,UAAU,GAAG;AACvB,eAAS,QAAQ;AACjB,cAAQ,KAAK,iBAAiB,QAAQ,OAAO,mBAAmB;AAAA,IAClE;AAAA,EACF;AAEA,MAAI,OAAO,kBAAkB,SAAS,GAAG;AACvC,UAAM,SAAS,OAAO;AACtB,aAAS,qBAAqB,QAAQ,OAAO,iBAAiB;AAC9D,YAAQ,KAAK,cAAc,MAAM,WAAM,OAAO,MAAM,MAAM,SAAS,OAAO,MAAM,GAAG;AAAA,EACrF;AAEA,MAAI,OAAO,SAAS;AAClB,UAAM,SAAS,OAAO;AACtB,aAAS,aAAa,MAAM;AAC5B,YAAQ,KAAK,YAAY,MAAM,WAAM,OAAO,MAAM,MAAM,SAAS,OAAO,MAAM,GAAG;AAAA,EACnF;AAEA,MAAI,OAAO,kBAAkB;AAC3B,UAAM,SAAS,OAAO;AACtB,aAAS,sBAAsB,QAAQ,OAAO,sBAAsB,oBAAoB;AACxF,YAAQ,KAAK,UAAU,MAAM,WAAM,OAAO,MAAM,MAAM,SAAS,OAAO,MAAM,GAAG;AAAA,EACjF;AAEA,MAAI,OAAO,aAAa;AACtB,UAAM,gBAAgB,OAAO,oBAAoB,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO;AAC5E,QAAI,aAAa,SAAS,GAAG;AAC3B,eAAS,iBAAiB,QAAQ,cAAc,MAAM;AACtD,cAAQ,KAAK,gBAAgB,aAAa,MAAM,gBAAgB;AAAA,IAClE;AAAA,EACF;AAEA,SAAO,KAAK,cAAc,aAAa,UAAU,WAAM,OAAO,MAAM,aAAa;AAAA,IAC/E,YAAY;AAAA,EACd,CAAC;AAED,SAAO;AACT;AAEA,SAAS,cAAc,QAAmC;AACxD,SACE,OAAO,iBAAiB,SACxB,OAAO,oBACP,OAAO,kBAAkB,SAAS,KAClC,OAAO,WACP,OAAO;AAEX;AAIA,SAAS,kBAAmD,UAAmD;AAC7G,MAAI,UAAU;AACd,QAAM,SAAS,SAAS,IAAI,CAAC,QAAQ;AACnC,QAAI,CAAC,IAAI,QAAS,QAAO;AACzB,UAAM,UAAU,gBAAgB,IAAI,OAAO;AAC3C,QAAI,YAAY,IAAI,QAAS,QAAO;AACpC;AACA,WAAO,EAAE,GAAG,KAAK,SAAS,QAAQ;AAAA,EACpC,CAAC;AACD,SAAO,EAAE,UAAU,QAAQ,QAAQ;AACrC;AAEA,IAAM,YAAY;AAElB,SAAS,gBAAgB,SAAyB;AAChD,QAAM,UAAU,QAAQ,KAAK;AAC7B,MAAI,CAAC,UAAU,KAAK,OAAO,EAAG,QAAO;AAErC,QAAM,QAAQ,cAAc,SAAS,OAAO;AAC5C,QAAM,MAAM,cAAc,SAAS,KAAK;AAExC,MAAI,OAAO;AACT,WAAO,MAAM,kBAAQ,KAAK,MAAM,GAAG,KAAK,kBAAQ,KAAK;AAAA,EACvD;AACA,SAAO;AACT;AAEA,SAAS,cAAc,KAAa,KAA4B;AAC9D,QAAM,UAAU,IAAI,GAAG;AACvB,QAAM,WAAW,KAAK,GAAG;AACzB,QAAM,QAAQ,IAAI,QAAQ,OAAO;AACjC,MAAI,UAAU,GAAI,QAAO;AACzB,QAAM,eAAe,QAAQ,QAAQ;AACrC,QAAM,MAAM,IAAI,QAAQ,UAAU,YAAY;AAC9C,MAAI,QAAQ,GAAI,QAAO;AACvB,QAAM,QAAQ,IAAI,MAAM,cAAc,GAAG,EAAE,KAAK;AAChD,SAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;AAEA,SAAS,qBAAsD,UAAe,UAAyB;AACrG,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,gBAAgB,SAAS,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AACzD,SAAO,SAAS,OAAO,CAAC,QAAQ;AAC9B,QAAI,CAAC,IAAI,QAAS,QAAO;AACzB,UAAM,QAAQ,IAAI,QAAQ,YAAY;AACtC,WAAO,CAAC,cAAc,KAAK,CAAC,OAAO,MAAM,SAAS,EAAE,CAAC;AAAA,EACvD,CAAC;AACH;AAEA,SAAS,aAA8C,UAAoB;AACzE,SAAO,SAAS,OAAO,CAAC,QAAQ;AAC9B,QAAI,CAAC,IAAI,QAAS,QAAO;AACzB,QAAI,IAAI,iBAAkB,QAAO;AACjC,UAAM,UAAU,IAAI,QAAQ,KAAK;AACjC,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,QAAI,oBAAoB,IAAI,OAAO,EAAG,QAAO;AAC7C,QAAI,YAAY,OAAO,EAAG,QAAO;AACjC,WAAO;AAAA,EACT,CAAC;AACH;AAEA,IAAM,sBAAsB,oBAAI,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,YAAY,KAAsB;AACzC,QAAM,WAAW,IACd,QAAQ,WAAC,2BAAuB,IAAE,GAAE,EAAE,EACtC,QAAQ,WAAC,8BAA0B,IAAE,GAAE,EAAE,EACzC,QAAQ,WAAW,EAAE,EACrB,QAAQ,WAAW,EAAE,EACrB,QAAQ,WAAW,EAAE,EACrB,QAAQ,OAAO,EAAE;AACpB,SAAO,SAAS,WAAW;AAC7B;AAEA,SAAS,sBAAuD,UAAe,eAA4B;AACzG,MAAI,SAAS,UAAU,EAAG,QAAO;AAEjC,QAAM,SAAc,CAAC;AACrB,MAAI,UAAoB;AAExB,aAAW,OAAO,UAAU;AAC1B,QAAI,CAAC,SAAS;AACZ,gBAAU,EAAE,GAAG,IAAI;AACnB;AAAA,IACF;AAEA,UAAM,aAAa,aAAa,SAAS,GAAG;AAC5C,UAAM,eAAe,KAAK,IAAI,IAAI,YAAY,QAAQ,SAAS,KAAK;AAEpE,QAAI,cAAc,cAAc;AAC9B,gBAAU;AAAA,QACR,GAAG;AAAA,QACH,SAAS,CAAC,QAAQ,SAAS,IAAI,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAAA,MACnE;AAAA,IACF,OAAO;AACL,aAAO,KAAK,OAAO;AACnB,gBAAU,EAAE,GAAG,IAAI;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,QAAS,QAAO,KAAK,OAAO;AAChC,SAAO;AACT;AAEA,SAAS,aAAa,GAA0B,GAAmC;AACjF,MAAI,EAAE,oBAAoB,EAAE,kBAAkB;AAC5C,WAAO,EAAE,qBAAqB,EAAE;AAAA,EAClC;AACA,SAAO,EAAE,eAAe,EAAE;AAC5B;AAEA,SAAS,iBACP,UACA,OACA,QACK;AACL,QAAM,gBAAgB,aAAa,OAAO,MAAM;AAChD,MAAI,cAAc,WAAW,EAAG,QAAO;AAEvC,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,CAAC,IAAI,QAAS,QAAO;AACzB,QAAI,UAAU,IAAI;AAClB,eAAW,EAAE,OAAO,YAAY,KAAK,eAAe;AAClD,YAAM,YAAY;AAClB,gBAAU,QAAQ,QAAQ,OAAO,WAAW;AAAA,IAC9C;AACA,QAAI,YAAY,IAAI,QAAS,QAAO;AACpC,WAAO,EAAE,GAAG,KAAK,QAAQ;AAAA,EAC3B,CAAC;AACH;AAEA,IAAM,aAAa,oBAAI,IAA2B;AAElD,SAAS,aACP,OACA,QAC+C;AAC/C,QAAM,SAAwD,CAAC;AAC/D,aAAW,QAAQ,OAAO;AACxB,QAAI,QAAQ,WAAW,IAAI,KAAK,OAAO;AACvC,QAAI,UAAU,QAAW;AACvB,UAAI;AACF,gBAAQ,IAAI,OAAO,KAAK,SAAS,GAAG;AACpC,mBAAW,IAAI,KAAK,SAAS,KAAK;AAAA,MACpC,QAAQ;AACN,eAAO,KAAK,cAAc,sCAAsC,KAAK,EAAE,MAAM,KAAK,OAAO,EAAE;AAC3F,mBAAW,IAAI,KAAK,SAAS,IAAI;AACjC;AAAA,MACF;AAAA,IACF;AACA,QAAI,OAAO;AACT,aAAO,KAAK,EAAE,OAAO,aAAa,KAAK,YAAY,CAAC;AAAA,IACtD;AAAA,EACF;AACA,SAAO;AACT;;;AC7PO,IAAM,mCAAmC;AAWhD,IAAM,aAAyD;AAAA,EAC7D;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AACF;AAEO,IAAM,4BAA+C;AAAA;AAAA,EAE1D;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,OAAO;AAAA,IACjB,OAAO;AAAA,EACT;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SACE;AAAA,IACF,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SACE;AAAA,IACF,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC;AAAA,IACV,OAAO;AAAA,EACT;AACF;AAKO,SAAS,yBAAyB,QAAmC;AAC1E,SAAO,0BAA0B,OAAO,CAAC,SAAS,KAAK,QAAQ,WAAW,KAAK,KAAK,QAAQ,SAAS,MAAM,CAAC,EAAE;AAAA,IAC5G,CAAC,UAAU,EAAE,GAAG,KAAK;AAAA,EACvB;AACF;AAEO,SAAS,8BACd,OACA,YAAqC,CAAC,GACnB;AACnB,SAAO,MAAM,IAAI,CAAC,UAAU;AAAA,IAC1B,GAAG;AAAA,IACH,SAAS,UAAU,KAAK,EAAE,KAAK,KAAK;AAAA,EACtC,EAAE;AACJ;AAEO,SAAS,uBACd,QACA,YAAqC,CAAC,GACd;AACxB,QAAM,QAAQ,8BAA8B,yBAAyB,MAAM,GAAG,SAAS;AACvF,QAAM,eAAe,oBAAI,IAA+B;AACxD,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,MAAO;AACjB,UAAM,aAAa,aAAa,IAAI,KAAK,KAAK,KAAK,CAAC;AACpD,eAAW,KAAK,IAAI;AACpB,iBAAa,IAAI,KAAK,OAAO,UAAU;AAAA,EACzC;AAEA,SAAO,WAAW,OAAO,CAAC,UAAU;AAClC,QAAI,MAAM,QAAQ,SAAS,KAAK,CAAC,MAAM,QAAQ,SAAS,MAAM,EAAG,QAAO;AACxE,YAAQ,aAAa,IAAI,MAAM,EAAE,GAAG,UAAU,KAAK;AAAA,EACrD,CAAC,EACE,IAAI,CAAC,WAAW;AAAA,IACf,GAAG;AAAA,IACH,OAAO,aAAa,IAAI,MAAM,EAAE,KAAK,CAAC;AAAA,EACxC,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACrC;AAKO,SAAS,oBACd,UACA,QACA,YAAqC,CAAC,GACnB;AACnB,QAAM,cAAc,SAAS,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO;AAC3D,QAAM,eAAe,8BAA8B,yBAAyB,MAAM,GAAG,SAAS;AAE9F,SAAO,CAAC,GAAG,cAAc,GAAG,WAAW;AACzC;;;ACzTO,SAAS,gBAAgB,QAA0B;AACxD,SAAO,QAAQ,WAAW,IAAI,KAAK;AACrC;AAEO,IAAM,YAAY;AAAA,EACvB,SAAS,EAAE,IAAI,4BAAQ,IAAI,WAAW;AAAA,EACtC,WAAW,EAAE,IAAI,wBAAS,IAAI,eAAe;AAAA,EAC7C,gBAAgB,EAAE,IAAI,wCAAU,IAAI,mBAAmB;AAAA,EACvD,UAAU,EAAE,IAAI,gBAAM,IAAI,UAAU;AAAA,EACpC,gBAAgB,EAAE,IAAI,kCAAS,IAAI,mBAAmB;AAAA,EACtD,gBAAgB,EAAE,IAAI,sEAAe,IAAI,kDAAkD;AAAA,EAC3F,kBAAkB,EAAE,IAAI,4EAAgB,IAAI,+BAA+B;AAAA,EAC3E,UAAU,EAAE,IAAI,UAAK,IAAI,GAAG;AAAA,EAC5B,OAAO,EAAE,IAAI,gBAAM,IAAI,QAAQ;AAAA,EAC/B,UAAU;AAAA,IACR,IAAI,CAAC,IAAI,gBAAM,gBAAM,gBAAM,gBAAM,gBAAM,gBAAM,cAAI;AAAA,IACjD,IAAI,CAAC,IAAI,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AAAA,EAC1D;AAAA,EACA,cAAc;AAAA,IACZ,IAAI,CAAC,MAAc,OAAe,QAAgB,eAAK,IAAI,eAAK,KAAK,2BAAO,GAAG;AAAA,IAC/E,IAAI,CAAC,MAAc,OAAe,QAAgB,QAAQ,IAAI,UAAU,KAAK,kBAAkB,GAAG;AAAA,EACpG;AACF;AAIO,SAAS,EAAE,KAAmB,QAAoC;AACvE,QAAM,OAAO,UAAU,GAAG;AAC1B,MAAI,OAAO,SAAS,YAAY,QAAQ,QAAQ,QAAQ,MAAM;AAC5D,WAAO,gBAAgB,MAAM,IAAI,KAAK,KAAK,KAAK;AAAA,EAClD;AACA,SAAO;AACT;AAEA,IAAM,6BAA6B;AAM5B,SAAS,qBACd,KAMA,QACQ;AACR,QAAM,YAAY,gBAAgB,MAAM,IAAI,UAAU;AACtD,QAAM,OAAO,IAAI,KAAK,IAAI,YAAY,GAAI,EAAE,eAAe,SAAS;AACpE,MAAI,UAAU,IAAI,WAAY,EAAE,aAAa,MAAM;AAEnD,MAAI,QAAQ,SAAS,4BAA4B;AAC/C,cAAU,QAAQ,MAAM,GAAG,0BAA0B,IAAI;AAAA,EAC3D;AAEA,SAAO,GAAG,IAAI,IAAI,IAAI,UAAU,KAAK,OAAO;AAC9C;AAKO,SAAS,gBACd,YACA,QACyC;AACzC,MAAI,CAAC,WAAY,QAAO,EAAE,WAAW,MAAM;AAC3C,QAAM,YAAY,gBAAgB,MAAM,IAAI,UAAU;AACtD,SAAO;AAAA,IACL,OAAO,IAAI,KAAK,WAAW,UAAU,GAAI,EAAE,eAAe,SAAS;AAAA,IACnE,KAAK,IAAI,KAAK,WAAW,QAAQ,GAAI,EAAE,eAAe,SAAS;AAAA,EACjE;AACF;AAKO,SAAS,uBAAuB,SAA0C;AAC/E,QAAM,QAAkB,CAAC;AACzB,QAAM,WAAW,QAAQ;AAEzB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,QAAQ,WAAY;AAGxB,QAAI,QAAQ,cAAe;AAC3B,QAAI,UAAU,UAAa,UAAU,KAAM;AAE3C,QAAI,OAAO,UAAU,UAAU;AAC7B,UAAI,WAAY,SAAqC,SAAU,OAAmC;AAChG,cAAM,QAAQ;AACd,cAAM,KAAK,GAAG,GAAG,KAAK,MAAM,KAAK,MAAM,MAAM,GAAG,EAAE;AAAA,MACpD,WAAW,MAAM,QAAQ,KAAK,GAAG;AAC/B,cAAM,KAAK,GAAG,GAAG,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE;AAAA,MAC1C,OAAO;AACL,cAAM,KAAK,GAAG,GAAG,KAAK,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,MAC/C;AAAA,IACF,OAAO;AACL,YAAM,KAAK,GAAG,GAAG,KAAK,KAAK,EAAE;AAAA,IAC/B;AAAA,EACF;AAEA,MAAI,YAAY,SAAS,SAAS,GAAG;AACnC,UAAM,KAAK,EAAE;AACb,QAAI,WAAW;AACf,eAAW,OAAO,UAAU;AAC1B,YAAM,WAAW,IAAI,QAAQ,GAAG;AAChC,YAAM,iBAAiB,IAAI,QAAQ,KAAK,WAAW,CAAC;AACpD,UAAI,WAAW,KAAK,iBAAiB,GAAG;AACtC,cAAM,OAAO,IAAI,MAAM,GAAG,QAAQ;AAClC,cAAM,OAAO,IAAI,MAAM,WAAW,CAAC;AACnC,YAAI,SAAS,UAAU;AACrB,gBAAM,KAAK,OAAO,IAAI,MAAM;AAC5B,qBAAW;AAAA,QACb;AACA,cAAM,KAAK,IAAI;AAAA,MACjB,OAAO;AACL,cAAM,KAAK,GAAG;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAMO,SAAS,sBACd,UACA,iBACQ;AACR,QAAM,UAAU,oBAAI,IAAmD;AACvE,aAAW,OAAO,UAAU;AAC1B,QAAI,IAAI,YAAY,QAAQ,CAAC,QAAQ,IAAI,IAAI,QAAQ,GAAG;AACtD,cAAQ,IAAI,IAAI,UAAU,EAAE,MAAM,IAAI,YAAY,YAAY,IAAI,iBAAiB,CAAC;AAAA,IACtF;AAAA,EACF;AAEA,MAAI,QAAQ,SAAS,EAAG,QAAO;AAE/B,aAAW,OAAO,UAAU;AAC1B,QAAI,IAAI,YAAY,MAAM;AACxB,UAAI,aAAa,IAAI,IAAI,QAAQ;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,UAAoB,CAAC;AAC3B,aAAW,CAAC,IAAI,EAAE,MAAM,WAAW,CAAC,KAAK,SAAS;AAChD,UAAM,UAAU,mBAAmB,eAAe;AAClD,YAAQ,KAAK,IAAI,EAAE,IAAI,IAAI,GAAG,UAAU,YAAY,EAAE,EAAE;AAAA,EAC1D;AAEA,SAAO,cAAc,QAAQ,KAAK,KAAK,CAAC;AAC1C;AAKO,SAAS,0BACd,WACA,WACA,UACA,eAC+C;AAC/C,QAAM,SAAS,YAAY;AAE3B,MAAI,cAAc;AAClB,aAAW,QAAQ,WAAW;AAC5B,mBAAe,cAAc,IAAI,IAAI;AAAA,EACvC;AACA,MAAI,eAAe,QAAQ;AACzB,WAAO,EAAE,UAAU,WAAW,cAAc,MAAM;AAAA,EACpD;AAEA,MAAI,aAAa,cAAc;AAC7B,QAAI,SAAS;AACb,QAAI,WAAW,UAAU;AACzB,aAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,gBAAU,cAAc,UAAU,CAAC,CAAC,IAAI;AACxC,UAAI,SAAS,QAAQ;AACnB,mBAAW;AACX;AAAA,MACF;AAAA,IACF;AACA,WAAO,EAAE,UAAU,UAAU,MAAM,GAAG,QAAQ,GAAG,cAAc,WAAW,UAAU,OAAO;AAAA,EAC7F,OAAO;AACL,QAAI,SAAS;AACb,QAAI,WAAW;AACf,aAAS,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,gBAAU,cAAc,UAAU,CAAC,CAAC,IAAI;AACxC,UAAI,SAAS,QAAQ;AACnB,mBAAW,IAAI;AACf;AAAA,MACF;AAAA,IACF;AACA,WAAO,EAAE,UAAU,UAAU,MAAM,QAAQ,GAAG,cAAc,WAAW,EAAE;AAAA,EAC3E;AACF;;;ACxKO,SAAS,2BAA2BC,UAAoE;AAC7G,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,qBAAqB;AAAA,IACrB,eAAe,CAAC;AAAA,IAChB;AAAA,EACF,IAAIA;AAEJ,QAAM,YAAY,mBAAmB,aAAa,kBAAkB,MAAM;AAE1E,MAAI,cAAc;AAClB,MAAI,gBAAgB;AAClB,kBAAc,sBAAsB,WAAW,eAAe;AAAA,EAChE;AAEA,MAAI,YAAY,UAAU,IAAI,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAEpE,MAAI,eAAe;AACnB,QAAM,gBAAgB,UAAU;AAEhC,MAAI,uBAAuB,sBAAsB,GAAG;AAClD,UAAM,cAAc,0BAA0B,WAAW,qBAAqB,oBAAoB,WAAW;AAC7G,QAAI,YAAY,cAAc;AAC5B,kBAAY,YAAY;AACxB,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,eAAwC,EAAE,GAAG,cAAc,UAAU,WAAW,UAAU,UAAU,OAAO;AAEjH,MAAI,cAAc,uBAAuB,YAAY;AAErD,MAAI,cAAc;AAChB,UAAM,eAAe,uBAAuB,eAAe,kBAAkB;AAC7E,UAAM,SAAS,mCAAyB,aAAa,4BAA4B,UAAU,MAAM,IAAI,YAAY;AACjH,kBAAc,SAAS,OAAO;AAAA,EAChC;AAEA,MAAI,aAAa;AACf,kBAAc,cAAc,OAAO;AAAA,EACrC;AAEA,SAAO,EAAE,MAAM,aAAa,SAAS,aAAa;AACpD;;;ACpFO,IAAM,0BAA0B;;;ACOvC,SAAS,SAAS,mBAAmB;AAErC;AAAA,EAGE,gBAAgB;AAAA,EAChB;AAAA,OACK;AAQP,SAAS,YAAY,SAA+C;AAClE,SAAO,QAAQ,SAAS,UAAU,QAAQ,SAAS,eAAe,QAAQ,SAAS;AACrF;AAEA,eAAsB,aAAaC,UAAqD;AACtF,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAIA;AAGJ,QAAM,cAAc;AAEpB,QAAM,mBAAoBA,SAAQ,YAAY;AAE9C,QAAM,aAA8B;AAAA,IAClC,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,EACpB;AACA,QAAM,YAAsB,CAAC;AAC7B,MAAI,aAAa;AAEjB,QAAM,aAAa,CAAC,UAAoB;AACtC,QAAI,CAAC,MAAO;AACZ,eAAW,gBAAgB,MAAM,SAAS;AAC1C,eAAW,oBAAoB,MAAM,UAAU;AAC/C,eAAW,eAAe,MAAM,eAAe,MAAM,QAAQ,MAAM,UAAU;AAC7E,eAAW,mBAAmB,MAAM,aAAa;AACjD,eAAW,oBAAoB,MAAM,cAAc;AAAA,EACrD;AAEA,MAAI,aAAa,SAAS;AACxB,WAAO,EAAE,OAAO,YAAY,eAAe,CAAC,GAAG,WAAW,CAAC,GAAG,YAAY,EAAE;AAAA,EAC9E;AAYA,QAAM,YAAY,CAACA,SAAQ,iBAAiBA,SAAQ,kBAAkB;AACtE,QAAM,iBAAiB,YACnB,EAAE,GAAG,SAAS,WAAW,OAAO,QAAQ,QAAW,kBAAkB,OAAU,IAC/E;AAEJ,QAAM,yBAAyB,MAAM;AACnC,QAAI,UAAW,QAAO;AACtB,UAAM,QAAQA,SAAQ;AACtB,QAAI,UAAU,QAAQ;AACpB,UAAI,CAAC,QAAQ,UAAW,QAAO;AAC/B,YAAM,SAAS,QAAQ;AACvB,UAAI,QAAQ,eAAgB,QAAO;AAGnC,UAAI,QAAQ,iBAAkB,QAAO;AACrC,aAAO;AAAA,IACT;AACA,WAAO,mBAAmB,SAAS,KAAkD;AAAA,EACvF,GAAG;AAEH,QAAM,qBAAqB,0BAA0B,QAAQ,YAAY,SAAY;AAKrF,QAAM,iBAAkB,QAAQ,QAAgD;AAChF,QAAM,gBACJ,QAAQ,aAAa,mBAAmB,aACpC;AAAA,IACE,WAAW,EAAE,KAAK,QAAQ,KAAK,UAAU,QAAQ,UAAU,IAAI,QAAQ,GAAG;AAAA,IAC1E,mBAAmB;AAAA,EACrB,IACA;AAEN,QAAM,YAAY,IAAI,YAAY;AAAA,IAChC,cAAc;AAAA,MACZ;AAAA,MACA,OAAO;AAAA,MACP,eAAe;AAAA,MACf,OAAO,gBAAgB,IAAI,QAAQ,CAAC;AAAA,MACpC,UAAU,oBAAoB,SAAS,aAAa;AAAA,IACtD;AAAA,IACA,WAAW,MAAM;AAAA,IACjB,UAAU;AAAA,IACV,cAAc,CAAC,aAAa;AAC1B,YAAM,WAAW,SAAS;AAAA,QACxB,CAAC,QAA0B,IAAI,SAAS,UAAU,IAAI,SAAS,eAAe,IAAI,SAAS;AAAA,MAC7F;AACA,uBAAiB,QAAQ;AACzB,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AAED,MAAI,2BAA2B;AAC/B,QAAM,oBAAoB,oBAAI,IAAoB;AAIlD,QAAM,iBAAiB,2BAA2B,QAAQ,UAAU,QAAQ,EAAE;AAC9E,MAAI;AACJ,QAAM,cAAc,iBAChB,IAAI,wBAAwB,CAAC,OAAO;AAClC,YAAQ,GAAG,MAAM;AAAA,MACf,KAAK;AACH,gBAAQ,EAAE,MAAM,WAAW,SAAS,GAAG,QAAQ,CAAC;AAChD;AAAA,MACF,KAAK;AACH,+BAAuB,KAAK,IAAI;AAChC,gBAAQ,EAAE,MAAM,iBAAiB,CAAC;AAClC;AAAA,MACF,KAAK;AACH,gBAAQ,EAAE,MAAM,kBAAkB,SAAS,GAAG,QAAQ,CAAC;AACvD;AAAA,MACF,KAAK,gBAAgB;AACnB,cAAM,aAAa,uBAAuB,KAAK,IAAI,IAAI,uBAAuB;AAC9E,+BAAuB;AACvB,gBAAQ,EAAE,MAAM,gBAAgB,WAAW,CAAC;AAC5C;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC,IACD;AAEJ,QAAM,cAAc,UAAU,UAAU,CAAC,UAAwB;AAC/D,QAAI,MAAM,SAAS,kBAAkB;AACnC,YAAM,SAAS,MAAM;AACrB,UAAI,OAAO,SAAS,cAAc;AAChC,YAAI,aAAa;AACf,sBAAY,KAAK,OAAO,KAAK;AAAA,QAC/B,OAAO;AACL,kBAAQ,EAAE,MAAM,WAAW,SAAS,OAAO,MAAM,CAAC;AAAA,QACpD;AAAA,MACF,WAAW,OAAO,SAAS,kBAAkB;AAC3C,0BAAkB,IAAI,OAAO,cAAc,KAAK,IAAI,CAAC;AACrD,gBAAQ,EAAE,MAAM,iBAAiB,CAAC;AAAA,MACpC,WAAW,OAAO,SAAS,kBAAkB;AAC3C,gBAAQ,EAAE,MAAM,kBAAkB,SAAS,OAAO,MAAM,CAAC;AAAA,MAC3D,WAAW,OAAO,SAAS,gBAAgB;AACzC,cAAM,YAAY,kBAAkB,IAAI,OAAO,YAAY;AAC3D,cAAM,aAAa,YAAY,KAAK,IAAI,IAAI,YAAY;AACxD,0BAAkB,OAAO,OAAO,YAAY;AAC5C,gBAAQ,EAAE,MAAM,gBAAgB,WAAW,CAAC;AAAA,MAC9C;AAAA,IACF,WAAW,MAAM,SAAS,wBAAwB;AAChD,gBAAU,KAAK,MAAM,QAAQ;AAC7B,cAAQ;AAAA,QACN,MAAM;AAAA,QACN,YAAY,MAAM;AAAA,QAClB,UAAU,MAAM;AAAA,QAChB,YAAa,MAAM,QAAQ,CAAC;AAAA,MAC9B,CAAC;AAAA,IACH,WAAW,MAAM,SAAS,sBAAsB;AAC9C,cAAQ;AAAA,QACN,MAAM;AAAA,QACN,YAAY,MAAM;AAAA,QAClB,UAAU,MAAM;AAAA,QAChB,YAAY,MAAM;AAAA,QAClB,SAAS,MAAM;AAAA,MACjB,CAAC;AAAA,IACH,WAAW,MAAM,SAAS,YAAY;AACpC,YAAM,eAAe,MAAM,YAAY,SAAS;AAChD,UAAI,cAAc;AAChB,sBAAc;AACd,YAAI,CAAC,4BAA4B,gBAAgB,KAAK,cAAc,eAAe;AACjF,qCAA2B;AAC3B,oBAAU,MAAM,QAAQ,CAAC;AACzB,oBAAU,MAAM;AAAA,YACd,MAAM;AAAA,YACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,aAAa,CAAC;AAAA,YAC9C,WAAW,KAAK,IAAI;AAAA,UACtB,CAAc;AAAA,QAChB;AAAA,MACF;AACA,cAAQ,EAAE,MAAM,YAAY,OAAO,YAAY,aAAa,CAAC;AAAA,IAC/D,WAAW,MAAM,SAAS,eAAe;AACvC,UAAI,MAAM,QAAQ,SAAS,aAAa;AACtC,qBAAa,MAAM;AACnB,mBAAW,MAAM,QAAQ,KAAK;AAC9B,gBAAQ,EAAE,MAAM,gBAAgB,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,eAAe,MAAM,UAAU,MAAM;AAC3C,MAAI,aAAa;AACf,gBAAY,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAAA,EACpE;AAEA,MAAI;AACF,QAAI,gBAAgB;AAClB,UAAI;AACF,cAAM,gBAAgB;AAAA,UACpB,EAAE,MAAM,UAAU,SAAS,aAAa;AAAA,UACxC,GAAG,QAAQ,IAAI,CAAC,OAAO;AAAA,YACrB,MAAM,EAAE,SAAS,YAAY,cAAc,EAAE;AAAA,YAC7C,SAAS,EAAE;AAAA,UACb,EAAE;AAAA,UACF,EAAE,MAAM,QAAQ,SAAS,YAAY;AAAA,QACvC;AACA,uBAAe,aAAa;AAAA,MAC9B,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,UAAU,OAAO,WAAW;AAElC,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO,UAAU,MAAM,gBAAgB;AAAA,MACvC,eAAe,UAAU,MAAM,SAAS,OAAO,WAAW;AAAA,MAC1D,WAAW,CAAC,GAAG,SAAS;AAAA,MACxB;AAAA,IACF;AAAA,EACF,UAAE;AACA,gBAAY;AACZ,QAAI,aAAa;AACf,kBAAY,oBAAoB,SAAS,YAAY;AAAA,IACvD;AAAA,EACF;AACF;;;ACtPA,SAAS,WAAW,OAAuB;AACzC,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,SAAO,KAAK,MAAM,QAAQ,EAAE,IAAI;AAClC;AAEA,SAAS,wBAAwB,WAA8C;AAC7E,SAAO,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,IAAI,OAAO,SAAS,IAAI;AAC3F;AAEA,SAAS,mBAAmB,OAAuB;AACjD,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,QAAM,UAAU,KAAK,MAAM,QAAQ,EAAE,IAAI;AACzC,SAAO,OAAO,UAAU,OAAO,IAAI,GAAG,OAAO,MAAM,GAAG,QAAQ,QAAQ,CAAC,CAAC;AAC1E;AAEO,SAAS,+BACd,UAC0B;AAC1B,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,aAAa,SAAS,cAAc,CAAC;AAE3C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAM,SAAS;AAAA,IACf,UAAU,SAAS;AAAA,IACnB,MAAM,SAAS;AAAA,IACf,eAAe,SAAS;AAAA,IACxB,cAAc,SAAS;AAAA,IACvB,gBAAgB,SAAS;AAAA,IACzB,eAAe,SAAS;AAAA,IACxB,mBAAmB,WAAW,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,YAAY;AAAA,MAC1D,UAAU,OAAO;AAAA,MACjB,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,MACrB,OAAO,SAAS,gBAAgB,IAAI,WAAY,OAAO,QAAQ,SAAS,gBAAiB,GAAG,IAAI;AAAA,IAClG,EAAE;AAAA,IACF,kBAAkB;AAAA,MAChB,gBAAgB,SAAS,gBAAgB;AAAA,IAC3C;AAAA,EACF;AACF;AAEO,SAAS,4BAA4B,cAAgD;AAC1F,MAAI,CAAC,aAAc,QAAO;AAE1B,SAAO;AAAA,IACL,SAAS,aAAa,IAAI;AAAA,IAC1B,aAAa,aAAa,QAAQ;AAAA,IAClC,SAAS,aAAa,IAAI;AAAA,IAC1B,mBAAmB,aAAa,aAAa;AAAA,IAC7C,kBAAkB,aAAa,YAAY;AAAA,IAC3C,qBAAqB,wBAAwB,aAAa,cAAc,CAAC;AAAA,IACzE,oBAAoB,wBAAwB,aAAa,aAAa,CAAC;AAAA,IACvE,gCAAgC,aAAa,kBAAkB,kBAAkB,CAAC;AAAA,IAClF,6BAA6B,aAAa,mBAAmB,UAAU,CAAC;AAAA,EAC1E,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,6BAA6B,cAAgD;AAC3F,MAAI,CAAC,aAAc,QAAO;AAE1B,QAAM,cAAc,aAAa,qBAAqB,CAAC;AACvD,QAAM,kBACJ,YAAY,SAAS,IACjB,YACG;AAAA,IACC,CAAC,QAAQ,UACP,GAAG,QAAQ,CAAC,eAAe,OAAO,QAAQ,mBAAmB,OAAO,WAAW,eAAe,OAAO,YAAY,YAAY,mBAAmB,OAAO,KAAK,CAAC;AAAA,EACjK,EACC,KAAK,IAAI,IACZ;AAEN,SAAO,GAAG,4BAA4B,YAAY,CAAC;AAAA;AAAA,EAEnD,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQjB;;;AC/FA;AAAA,EACE,kBAAAC;AAAA,OAIK;AAuBP,IAAM,4CAA4C;AAElD,SAAS,gBAAgB,OAAuB;AAC9C,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACvC;AAEA,SAAS,cAAc,MAAsB;AAC3C,SAAO,KAAK,KAAK,EAAE,QAAQ,QAAQ,GAAG;AACxC;AAEA,SAAS,SAAS,MAAc,UAA6B;AAC3D,SAAO,SAAS,KAAK,CAAC,YAAY,QAAQ,KAAK,IAAI,CAAC;AACtD;AAEA,SAAS,QAAQ,OAAiD;AAChE,SAAO,UAAU,qBAAqB,UAAU,mBAAmB,UAAU;AAC/E;AAEA,SAAS,aAAa,QAAqF;AACzG,SAAO,OAAO,WAAW,WAAW,EAAE,MAAM,OAAO,IAAI;AACzD;AAEA,SAASC,mBAAkB,MAAsB;AAC/C,QAAM,SAAS,KAAK,MAAM,+BAA+B;AACzD,MAAI,SAAS,CAAC,EAAG,QAAO,OAAO,CAAC,EAAE,KAAK;AACvC,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,SAAS,KAAK,MAAM,MAAO,QAAO,KAAK,MAAM,OAAO,MAAM,CAAC;AAC/D,SAAO,KAAK,KAAK;AACnB;AAEA,SAAS,iBAAiB,SAAiB,OAAsD;AAC/F,MAAI;AACF,UAAM,SAAS,KAAK,MAAMA,mBAAkB,OAAO,CAAC;AACpD,QAAI,CAAC,QAAQ,OAAO,KAAK,EAAG,QAAO;AACnC,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,YAAY,gBAAgB,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa,GAAG;AAAA,MAC3F,QAAQ,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,KAAK,IAAI,OAAO,OAAO,KAAK,IAAI;AAAA,MAC3F,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,qBAAqB,OAAoBC,eAAqC;AACrF,QAAM,YAAY,MAAM,gBAAgB,SAAS,MAAM,eAAe,KAAK,IAAI,IAAI;AACnF,QAAM,eAAe,4BAA4B,MAAM,YAAY;AAEnE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAWG,MAAM,MAAM;AAAA,cACV,MAAM,QAAQ;AAAA,oBACR,SAAS;AAAA,kBACX,YAAY;AAAA,sBACR,MAAM,oBAAoB,QAAQ;AAAA,kBACtC,MAAM,gBAAgB,QAAQ;AAAA,yBACvB,MAAM,uBAAuB,QAAQ;AAAA,kBAC5CA,cAAa,KAAK,gBAAgBA,cAAa,UAAU,YAAYA,cAAa,MAAM;AAAA;AAAA;AAAA,EAGxG,MAAM,WAAW;AACnB;AAEA,SAAS,mBAAmB,MAAoC;AAC9D,MACE,SAAS,MAAM,CAAC,+BAA+B,oCAAoC,CAAC,GACpF;AACA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,SAAS,MAAM,CAAC,sEAAsE,CAAC,GAAG;AAC5F,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,SAAS,MAAM,CAAC,oCAAoC,CAAC,GAAG;AAC1D,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,SAAS,MAAM,CAAC,2BAA2B,CAAC,GAAG;AACjD,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAoC;AAC/D,MAAI,SAAS,MAAM,CAAC,oDAAoD,CAAC,GAAG;AAC1E,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,aAAa;AAAA,IACjB,SAAS,MAAM,CAAC,2CAA2C,CAAC;AAAA,IAC5D,SAAS,MAAM;AAAA,MACb;AAAA,IACF,CAAC;AAAA,IACD,SAAS,MAAM,CAAC,oDAAoD,CAAC;AAAA,IACrE,SAAS,MAAM,CAAC,8BAA8B,CAAC;AAAA,IAC/C,SAAS,MAAM,CAAC,2DAA2D,CAAC;AAAA,IAC5E,SAAS,MAAM,CAAC,qCAAqC,CAAC;AAAA,EACxD;AACA,QAAM,QAAQ,WAAW,OAAO,OAAO,EAAE;AACzC,MAAI,SAAS,GAAG;AACd,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY,KAAK,IAAI,MAAM,OAAO,QAAQ,IAAI;AAAA,MAC9C,QAAQ,gBAAgB,KAAK;AAAA,MAC7B,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAAoC;AAC5D,MACE,SAAS,MAAM;AAAA,IACb;AAAA,IACA;AAAA,EACF,CAAC,GACD;AACA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MACE,SAAS,MAAM;AAAA,IACb;AAAA,IACA;AAAA,EACF,CAAC,GACD;AACA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,aAAa,OAAmC;AACvD,QAAM,OAAO,cAAc,MAAM,WAAW;AAI5C,SACE,oBAAoB,IAAI,KACxB,iBAAiB,IAAI,KACrB,mBAAmB,IAAI,KAAK;AAAA,IAC1B,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAEJ;AAEA,eAAsB,mBACpB,OACAC,WAAqC,CAAC,GACd;AACxB,QAAM,WAAW,aAAa,KAAK;AACnC,QAAM,YAAYA,SAAQ,kCAAkC;AAC5D,MAAI,SAAS,cAAc,aAAa,CAACA,SAAQ,WAAW;AAC1D,WAAO,EAAE,GAAG,UAAU,YAAY,gBAAgB,SAAS,UAAU,EAAE;AAAA,EACzE;AAEA,QAAM,cAAc,MAAMA,SAAQ,UAAU,OAAO,QAAQ;AAC3D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,YAAY,gBAAgB,YAAY,UAAU;AAAA,EACpD;AACF;AAEO,SAAS,sBAAsBA,UAAwD;AAC5F,SAAO,OAAO,OAAOD,kBAAiB;AACpC,UAAM,SAAS,qBAAqB,OAAOA,aAAY;AAEvD,QAAI;AACF,YAAM,YAAYC,SAAQ,WACtB,MAAMA,SAAQ,SAAS,QAAQA,SAAQ,WAAW,IAClD,MAAM,iBAAiB,QAAQA,QAAO;AAC1C,YAAM,EAAE,MAAM,MAAM,IAAI,aAAa,SAAS;AAC9C,YAAM,WAAW,iBAAiB,MAAM,KAAK;AAC7C,UAAI,SAAU,QAAO;AACrB,aAAO;AAAA,QACL,GAAGD;AAAA,QACH,QAAQ,GAAGA,cAAa,MAAM;AAAA,MAChC;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,aAAO;AAAA,QACL,GAAGA;AAAA,QACH,QAAQ,GAAGA,cAAa,MAAM,yBAAyB,OAAO;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,iBACb,QACAC,UACoC;AACpC,MAAI,CAACA,SAAQ,WAAW,CAACA,SAAQ,QAAQ;AACvC,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAEA,QAAM,SAAS,MAAMH;AAAA,IACnBG,SAAQ;AAAA,IACR;AAAA,MACE,cAAc;AAAA,MACd,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC,GAAG,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,IAC/F;AAAA,IACA;AAAA,MACE,QAAQA,SAAQ;AAAA,MAChB,WAAWA,SAAQ,aAAa;AAAA,MAChC,aAAaA,SAAQ,eAAe;AAAA,MACpC,QAAQA,SAAQ;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,OAAO,OAAO,QACjB,OAAO,CAAC,SAAgC,KAAK,SAAS,MAAM,EAC5D,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE;AAEV,SAAO;AAAA,IACL;AAAA,IACA,OAAO;AAAA,MACL,cAAc,OAAO,OAAO;AAAA,MAC5B,kBAAkB,OAAO,OAAO;AAAA,MAChC,aAAa,OAAO,OAAO;AAAA,IAC7B;AAAA,EACF;AACF;;;ACnTA;AAAA,EACE,kBAAAC;AAAA,EACA;AAAA,OAIK;AAkCP,IAAM,eAA8C;AAAA,EAClD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,IAAM,+BAA+B,KAAK,IAAI,WAAW,QAAQ,UAAU,QAAQ,SAAS,MAAM,IAAI;AAEtG,SAASC,UAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,SAAS,OAA6C;AAC7D,SAAO,OAAO,UAAU,YAAY,aAAa,SAAS,KAA2B;AACvF;AAEA,SAAS,gBAAgB,OAA+B;AACtD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,OAAO,MAAM,KAAK;AACxB,SAAO,KAAK,SAAS,IAAI,OAAO;AAClC;AAEA,SAASC,mBAAkB,MAAsB;AAC/C,QAAM,SAAS,KAAK,MAAM,+BAA+B;AACzD,MAAI,SAAS,CAAC,EAAG,QAAO,OAAO,CAAC,EAAE,KAAK;AACvC,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,SAAS,KAAK,MAAM,MAAO,QAAO,KAAK,MAAM,OAAO,MAAM,CAAC;AAC/D,SAAO,KAAK,KAAK;AACnB;AAEA,SAAS,cAAc,SAAiB,gBAAyD;AAC/F,MAAI;AACF,UAAM,SAAS,KAAK,MAAMA,mBAAkB,OAAO,CAAC;AACpD,QAAI,CAACD,UAAS,MAAM,EAAG,QAAO;AAC9B,UAAM,QAAQ,gBAAgB,OAAO,KAAK;AAC1C,QAAI,CAAC,SAAS,CAAC,SAAS,OAAO,MAAM,KAAK,CAAC,MAAM,QAAQ,OAAO,KAAK,EAAG,QAAO;AAI/E,UAAM,QAAQ,OAAO,MAClB,OAAOA,SAAQ,EACf,IAAI,CAAC,SAAS;AACb,YAAM,OAAO,gBAAgB,KAAK,IAAI;AACtC,YAAM,iBAAiB,gBAAgB,KAAK,cAAc;AAC1D,UAAI,CAAC,QAAQ,CAAC,eAAgB,QAAO;AACrC,YAAM,iBAAiB,MAAM,QAAQ,KAAK,cAAc,IACpD,KAAK,eAAe,OAAO,CAAC,SAAyB,OAAO,SAAS,YAAY,eAAe,IAAI,IAAI,CAAC,IACzG,CAAC;AACL,aAAO,EAAE,MAAM,gBAAgB,MAAM,KAAK,IAAI,IAAI,cAAc,CAAC,GAAG,eAAe;AAAA,IACrF,CAAC,EACA,OAAO,CAAC,SAA2C,SAAS,IAAI,EAChE,MAAM,GAAG,CAAC;AAEb,QAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,UAAM,kBAAkB,MAAM,QAAQ,OAAO,eAAe,IACxD,OAAO,gBACJ,IAAI,eAAe,EACnB,OAAO,CAAC,SAAyB,SAAS,IAAI,EAC9C,MAAM,GAAG,CAAC,IACb,CAAC;AACL,QAAI,gBAAgB,WAAW,EAAG,QAAO;AAEzC,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,OAAO;AAAA,MACP,QAAQ,OAAO;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAASE,cAAa,QAAyC;AAC7D,SAAO,OAAO,WAAW,WAAW,SAAS,OAAO;AACtD;AAEA,SAAS,mBAAmB,OAA6B;AACvD,QAAM,eAAe,6BAA6B,MAAM,YAAY;AACpE,QAAM,wBACJ,MAAM,yBAAyB,MAAM,sBAAsB,SAAS,IAChE,MAAM,sBACH,IAAI,CAAC,eAAe;AACnB,UAAM,QAAQ,WAAW,MAAM,KAAK,IAAI,KAAK;AAC7C,WAAO,OAAO,WAAW,EAAE,KAAK,WAAW,KAAK,YAAY,KAAK,MAAM,WAAW,QAAQ;AAAA,EAC5F,CAAC,EACA,KAAK,IAAI,IACZ;AAEN,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAmCG,MAAM,MAAM;AAAA,cACV,MAAM,QAAQ;AAAA,oBACR,MAAM,eAAe,KAAK,IAAI,KAAK,QAAQ;AAAA;AAAA,EAE7D,qBAAqB;AAAA,kBACL,YAAY;AAAA,sBACR,MAAM,oBAAoB,QAAQ;AAAA,kBACtC,MAAM,gBAAgB,QAAQ;AAAA,yBACvB,MAAM,uBAAuB,QAAQ;AAAA;AAAA;AAAA,EAG5D,MAAM,WAAW;AACnB;AAEO,SAAS,sBAAsBC,UAAwD;AAC5F,SAAO,OAAO,OAAO,WAAW;AAC9B,QAAI;AACF,YAAM,SAAS,mBAAmB,KAAK;AACvC,YAAM,YAAoC;AAAA,QACxC,aAAaA,SAAQ,gBAAgB,MAAM;AAAA,QAAC;AAAA,QAC5C,iBAAiBA,SAAQ;AAAA,QACzB,eAAeA,SAAQ;AAAA,QACvB,mBAAmBA,SAAQ;AAAA,QAC3B,iBAAiBA,SAAQ;AAAA,MAC3B;AACA,YAAM,YAAYA,SAAQ,SACtB,MAAMA,SAAQ,OAAO,QAAQ,WAAW,MAAM,IAC9CA,SAAQ,WACN,MAAMA,SAAQ,SAAS,QAAQ,MAAM,IACrCA,SAAQ,cACN,MAAM,eAAe,QAAQA,UAAS,MAAM,IAC5C,MAAMC,kBAAiB,QAAQD,UAAS,MAAM;AACtD,aAAO,cAAcD,cAAa,SAAS,GAAG,IAAI,IAAI,MAAM,cAAc,CAAC;AAAA,IAC7E,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,SAAS,uBACd,MACA,SAAqC,WACnB;AAClB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,kBAAkB,MAAmC;AACnE,QAAM,QAAQ,KAAK,MAChB,IAAI,CAAC,MAAM,UAAU;AACpB,UAAM,QAAQ,KAAK,eAAe,SAAS,IAAI,KAAK,eAAe,KAAK,IAAI,IAAI;AAChF,WAAO,GAAG,QAAQ,CAAC,KAAK,KAAK,IAAI;AAAA,sBAAyB,KAAK,cAAc;AAAA,sBAAyB,KAAK;AAAA,EAC7G,CAAC,EACA,KAAK,IAAI;AACZ,QAAM,kBAAkB,KAAK,gBAAgB,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,EAAE,KAAK,IAAI;AAEjF,SAAO;AAAA;AAAA,cAEK,KAAK,KAAK;AAAA,UACd,KAAK,MAAM;AAAA;AAAA;AAAA,EAGnB,KAAK;AAAA;AAAA;AAAA,EAGL,eAAe;AACjB;AAEA,eAAeE,kBACb,QACAD,UACA,QACkC;AAClC,MAAI,CAACA,SAAQ,WAAW,CAACA,SAAQ,QAAQ;AACvC,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAEA,QAAM,SAAS,MAAME;AAAA,IACnBF,SAAQ;AAAA,IACR;AAAA,MACE,cAAc;AAAA,MACd,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC,GAAG,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,IAC/F;AAAA,IACA;AAAA,MACE,QAAQA,SAAQ;AAAA,MAChB,WAAWA,SAAQ,aAAa;AAAA,MAChC,aAAaA,SAAQ,eAAe;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,OAAO,QACV,OAAO,CAAC,SAAgC,KAAK,SAAS,MAAM,EAC5D,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE;AAAA,EACZ;AACF;AAEA,SAAS,uBAAuB,SAAiB,KAAgC,UAA4B;AAC3G,QAAM,UAAU,IAAI,GAAG;AACvB,QAAM,WAAW,KAAK,GAAG;AACzB,QAAM,aAAa,QAAQ,QAAQ,OAAO;AAC1C,MAAI,aAAa,EAAG,QAAO;AAE3B,QAAM,YAAY,aAAa,QAAQ;AACvC,QAAM,aAAa,QAAQ,QAAQ,UAAU,SAAS;AACtD,QAAM,aAAa,SAAS,IAAI,CAAC,YAAY,QAAQ,QAAQ,SAAS,SAAS,CAAC,EAAE,OAAO,CAAC,UAAU,SAAS,CAAC;AAC9G,QAAM,gBAAgB,CAAC,YAAY,GAAG,UAAU,EAAE,OAAO,CAAC,UAAU,SAAS,CAAC;AAC9E,QAAM,UACJ,cAAc,SAAS,IACnB,KAAK,IAAI,GAAG,aAAa,IACzB,KAAK,IAAI,WAAW,QAAQ,SAAS,4BAA4B;AACvE,QAAM,OAAO,QAAQ,MAAM,WAAW,OAAO;AAC7C,SAAO,KAAK,QAAQ,QAAQ,EAAE;AAChC;AAEA,eAAe,eACb,QACAA,UACA,QACkC;AAClC,MAAI,CAACA,SAAQ,WAAW,CAACA,SAAQ,QAAQ;AACvC,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAEA,QAAM,SAAS;AAAA,IACbA,SAAQ;AAAA,IACR;AAAA,MACE,cAAc;AAAA,MACd,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC,GAAG,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,IAC/F;AAAA,IACA;AAAA,MACE,QAAQA,SAAQ;AAAA,MAChB,WAAWA,SAAQ,aAAa;AAAA,MAChC,aAAaA,SAAQ,eAAe;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO;AACX,MAAI,qBAAqB;AACzB,MAAI,oBAAoB;AACxB,MAAI,oBAAoB;AACxB,MAAI;AACJ,MAAI;AACJ,MAAI,qBAAqB;AACzB,MAAI;AACJ,MAAI,kBAAkB;AAEtB,mBAAiB,SAAS,QAAQ;AAChC,QAAI,QAAQ,QAAS;AACrB,QAAI,MAAM,SAAS,kBAAkB;AACnC,0BAAoB,KAAK,IAAI;AAC7B;AAAA,IACF;AACA,QAAI,MAAM,SAAS,kBAAkB;AACnC,MAAAA,SAAQ,kBAAkB,MAAM,KAAK;AACrC;AAAA,IACF;AACA,QAAI,MAAM,SAAS,gBAAgB;AACjC,YAAM,aAAa,oBAAoB,KAAK,IAAI,IAAI,oBAAoB;AACxE,0BAAoB;AACpB,MAAAA,SAAQ,gBAAgB,UAAU;AAClC;AAAA,IACF;AACA,QAAI,MAAM,SAAS,aAAc;AAEjC,YAAQ,MAAM;AACd,UAAM,YAAY,uBAAuB,MAAM,SAAS,CAAC,UAAU,QAAQ,CAAC;AAC5E,QAAI,UAAU,SAAS,oBAAoB;AACzC,UAAI,CAAC,uBAAwB,0BAAyB,KAAK,IAAI;AAC/D,YAAM,QAAQ,UAAU,MAAM,kBAAkB;AAChD,2BAAqB,UAAU;AAC/B,UAAI,MAAM,KAAK,EAAE,SAAS,KAAK,KAAK,KAAK,KAAK,GAAG;AAC/C,QAAAA,SAAQ,kBAAkB,KAAK;AAAA,MACjC;AAAA,IACF;AACA,QAAI,CAAC,sBAAsB,qBAAqB,MAAM,KAAK,SAAS,UAAU,KAAK,KAAK,SAAS,QAAQ,IAAI;AAC3G,2BAAqB;AACrB,YAAM,aAAa,yBAAyB,KAAK,IAAI,IAAI,yBAAyB;AAClF,MAAAA,SAAQ,gBAAgB,UAAU;AAAA,IACpC;AAEA,UAAM,WAAW,uBAAuB,MAAM,QAAQ,CAAC,QAAQ,CAAC;AAChE,QAAI,SAAS,SAAS,mBAAmB;AACvC,YAAM,QAAQ,SAAS,MAAM,iBAAiB;AAC9C,0BAAoB,SAAS;AAC7B,UAAI,MAAM,KAAK,EAAE,SAAS,KAAK,KAAK,KAAK,KAAK,GAAG;AAC/C,QAAAA,SAAQ,cAAc,KAAK;AAAA,MAC7B;AAAA,IACF;AAEA,UAAM,WAAW,uBAAuB,MAAM,QAAQ,CAAC,CAAC;AACxD,QAAI,SAAS,SAAS,mBAAmB;AACvC,UAAI,CAAC,oBAAqB,uBAAsB,KAAK,IAAI;AACzD,YAAM,QAAQ,SAAS,MAAM,iBAAiB;AAC9C,0BAAoB,SAAS;AAC7B,UAAI,MAAM,KAAK,EAAE,SAAS,KAAK,KAAK,KAAK,KAAK,GAAG;AAC/C,QAAAA,SAAQ,oBAAoB,KAAK;AAAA,MACnC;AAAA,IACF;AACA,QAAI,CAAC,mBAAmB,oBAAoB,KAAK,KAAK,SAAS,SAAS,GAAG;AACzE,wBAAkB;AAClB,YAAM,aAAa,sBAAsB,KAAK,IAAI,IAAI,sBAAsB;AAC5E,MAAAA,SAAQ,kBAAkB,UAAU;AAAA,IACtC;AAAA,EACF;AAEA,SAAO,EAAE,KAAK;AAChB;;;AC1XA,SAAS,UAAU,QAA0B;AAC3C,UAAQ,UAAU,IAAI,YAAY,EAAE,WAAW,IAAI;AACrD;AAEO,SAAS,4BAA4B,QAAyB;AACnE,MAAI,UAAU,MAAM,GAAG;AACrB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,EAAE;AAAA,EACX;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,EAAE;AACX;;;ACsDA,SAAS,uBAAuB,MAAsB;AACpD,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,aAAa,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAClD,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,YAAY,WAAW,MAAM,+BAA+B,KAAK,CAAC,GAAG;AAC3E,QAAM,aAAa,WAAW,SAAS;AACvC,SAAO,KAAK,IAAI,GAAG,KAAK,KAAK,WAAW,OAAO,aAAa,CAAC,CAAC;AAChE;AAEA,SAAS,mBAAmB,SAA4B;AACtD,MAAI,QAAQ,SAAS,QAAQ;AAC3B,QAAI,OAAO,QAAQ,YAAY,SAAU,QAAO,QAAQ;AACxD,WAAO,QAAQ,QACZ,IAAI,CAAC,SAAS;AACb,UAAI,KAAK,SAAS,OAAQ,QAAO,KAAK;AACtC,UAAI,KAAK,SAAS,QAAS,QAAO;AAClC,aAAO;AAAA,IACT,CAAC,EACA,KAAK,IAAI;AAAA,EACd;AAEA,MAAI,QAAQ,SAAS,aAAa;AAChC,WAAO,QAAQ,QACZ,IAAI,CAAC,SAAS;AACb,UAAI,KAAK,SAAS,OAAQ,QAAO,KAAK;AACtC,UAAI,KAAK,SAAS,WAAY,QAAO,KAAK;AAC1C,UAAI,KAAK,SAAS,WAAY,QAAO,GAAG,KAAK,IAAI,IAAI,KAAK,UAAU,KAAK,aAAa,CAAC,CAAC,CAAC;AACzF,aAAO;AAAA,IACT,CAAC,EACA,KAAK,IAAI;AAAA,EACd;AAEA,MAAI,QAAQ,SAAS,cAAc;AACjC,WAAO,QAAQ,QACZ,IAAI,CAAC,SAAS;AACb,UAAI,KAAK,SAAS,OAAQ,QAAO,KAAK;AACtC,aAAO;AAAA,IACT,CAAC,EACA,KAAK,IAAI;AAAA,EACd;AAEA,SAAO;AACT;AAEA,SAAS,sBAAsB,cAAsB,UAAuB,oBAAqC;AAC/G,MAAI,SAAS,uBAAuB,YAAY;AAChD,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,SAAS,aAAc;AACnC,cAAU,uBAAuB,mBAAmB,OAAO,CAAC;AAAA,EAC9D;AACA,MAAI,oBAAoB;AACtB,cAAU,uBAAuB,kBAAkB;AAAA,EACrD;AACA,SAAO;AACT;AAOO,IAAM,oBAAN,MAAwB;AAAA,EACpB,YAAsB,CAAC;AAAA,EAChC,aAAqB;AAAA,EAEb,aAAyB;AAAA,IAC/B,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,EACpB;AAAA,EACQ,eAAe;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAA4B;AACtC,SAAK,UAAU,OAAO;AACtB,SAAK,UAAU,OAAO;AACtB,SAAK,eAAe,OAAO;AAAA,EAC7B;AAAA,EAEA,gBAAgB,OAAuB,UAA6B;AAClE,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,aAAK,QAAQ,EAAE,MAAM,WAAW,SAAS,MAAM,QAAQ,CAAC;AACxD,aAAK,WAAW,cAAc,QAAQ;AACtC;AAAA,MACF,KAAK;AACH,aAAK,WAAW,YAAY,UAAU,EAAE,OAAO,KAAK,CAAC;AACrD;AAAA,MACF,KAAK;AACH,aAAK,QAAQ,EAAE,MAAM,SAAS,SAAS,MAAM,SAAS,UAAU,WAAW,CAAC;AAC5E,aAAK,WAAW,YAAY,QAAQ;AACpC;AAAA,MACF,KAAK;AACH,aAAK,QAAQ,EAAE,MAAM,SAAS,SAAS,IAAI,UAAU,YAAY,iBAAiB,MAAM,WAAW,CAAC;AACpG,aAAK,WAAW,cAAc,UAAU,EAAE,OAAO,KAAK,CAAC;AACvD;AAAA,MACF,KAAK,cAAc;AACjB,cAAM,SAAS,KAAK,oBAAoB,MAAM,UAAU,MAAM,UAAU;AACxE,aAAK,UAAU,KAAK,MAAM,QAAQ;AAClC,aAAK,QAAQ,EAAE,MAAM,cAAc,YAAY,MAAM,YAAY,UAAU,MAAM,UAAU,YAAY,OAAO,CAAC;AAC/G,aAAK,WAAW,gBAAgB,UAAU,EAAE,aAAa,MAAM,UAAU,OAAO,KAAK,CAAC;AACtF;AAAA,MACF;AAAA,MACA,KAAK;AACH,aAAK,QAAQ;AAAA,UACX,MAAM;AAAA,UACN,YAAY,MAAM;AAAA,UAClB,UAAU,MAAM;AAAA,UAChB,YAAY,MAAM;AAAA,UAClB,aAAa,MAAM;AAAA,QACrB,CAAC;AACD,aAAK,WAAW,YAAY,UAAU,EAAE,OAAO,KAAK,CAAC;AACrD;AAAA,MACF,KAAK;AACH,aAAK,aAAa,MAAM;AACxB,aAAK,WAAW,YAAY,UAAU,EAAE,OAAO,KAAK,CAAC;AACrD;AAAA,MACF,KAAK;AACH,aAAK,aAAa,EAAE,GAAG,MAAM,MAAM;AACnC,aAAK,WAAW,cAAc,UAAU,EAAE,OAAO,KAAK,CAAC;AACvD;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,aAAyB;AACvB,WAAO;AAAA,MACL,cAAc,KAAK,WAAW;AAAA,MAC9B,kBAAkB,KAAK,WAAW;AAAA,MAClC,aAAa,KAAK,WAAW;AAAA,MAC7B,iBAAiB,KAAK,WAAW;AAAA,MACjC,kBAAkB,KAAK,WAAW;AAAA,IACpC;AAAA,EACF;AAAA,EAEA,WACE,OACA,UACAG,UACM;AACN,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,CAACA,UAAS,SAAS,MAAM,KAAK,eAAe,IAAK;AACtD,SAAK,eAAe;AAEpB,UAAM,gBAAgB,sBAAsB,KAAK,cAAc,UAAUA,UAAS,kBAAkB;AACpG,UAAM,SAA6B;AAAA,MACjC;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK,UAAU;AAAA,MAC1B,aAAaA,UAAS;AAAA,MACtB;AAAA,MACA,YAAY,KAAK,WAAW;AAAA,MAC5B,WAAW;AAAA,IACb;AACA,SAAK,QAAQ,EAAE,MAAM,UAAU,OAAO,CAAC;AAAA,EACzC;AAAA,EAEA,oBAAoB,UAAkB,QAA0D;AAC9F,UAAM,aAAa,EAAE,GAAG,OAAO;AAC/B,UAAM,iBAAiB,CAAC,mBAAmB,uBAAuB,0BAA0B;AAC5F,QAAI,KAAK,QAAQ,oBAAoB,eAAe,SAAS,QAAQ,GAAG;AACtE,iBAAW,QAAQ,KAAK,QAAQ;AAAA,IAClC;AACA,QAAI,KAAK,QAAQ,eAAe,aAAa,qBAAqB,aAAa,wBAAwB;AACrG,iBAAW,cAAc,KAAK,QAAQ;AAAA,IACxC;AACA,WAAO;AAAA,EACT;AACF;;;AChMA,SAAS,OAAOC,IAAgB,KAAa,QAAgBC,UAA2C;AACtG,SAAOD,GAAE,KAAK,EAAE,KAAK,QAAQ,GAAGC,SAAQ,CAAC;AAC3C;AAEA,SAASC,yBAAwB,WAA8C;AAC7E,SAAO,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,IAAI,OAAO,SAAS,IAAI;AAC3F;AAEA,SAASC,oBAAmB,OAAuB;AACjD,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,QAAM,UAAU,KAAK,MAAM,QAAQ,EAAE,IAAI;AACzC,SAAO,OAAO,UAAU,OAAO,IAAI,GAAG,OAAO,MAAM,GAAG,QAAQ,QAAQ,CAAC,CAAC;AAC1E;AAEA,SAAS,oBAAoB,MAAsB;AACjD,SAAO,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACxC;AAEO,SAAS,0BAA0BH,IAAgB,cAA4B,QAAwB;AAC5G,QAAM,cAAc,aAAa,qBAAqB,CAAC;AACvD,QAAM,kBACJ,YAAY,WAAW,IACnB,OAAOA,IAAG,+CAA+C,MAAM,IAC/D,aAAa,gBAAgB,KAC3B,OAAOA,IAAG,uCAAuC,MAAM,IACvD,OAAOA,IAAG,uCAAuC,MAAM;AAG/D,QAAM,kBACJ,YAAY,SAAS,IACjB,YACG,IAAI,CAAC,QAAQ,UAAU;AACtB,WAAO,GAAG,QAAQ,CAAC,eAAe,OAAO,QAAQ,mBAAmB,oBAAoB,OAAO,WAAW,CAAC,eAAe,OAAO,YAAY,YAAYG,oBAAmB,OAAO,KAAK,CAAC;AAAA,EAC3L,CAAC,EACA,KAAK,IAAI,IACZ,OAAOH,IAAG,yCAAyC,MAAM;AAE/D,SAAO,OAAOA,IAAG,gCAAgC,QAAQ;AAAA,IACvD,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,MAAM,aAAa;AAAA,IACnB,eAAe,aAAa;AAAA,IAC5B,cAAc,aAAa;AAAA,IAC3B,gBAAgBE,yBAAwB,aAAa,cAAc;AAAA,IACnE,kBAAkB,gBAAgB,aAAa,gBAAgB,MAAM;AAAA,IACrE,eAAeA,yBAAwB,aAAa,aAAa;AAAA,IACjE,iBAAiB,gBAAgB,aAAa,eAAe,MAAM;AAAA,IACnE,qBAAqB,aAAa,kBAAkB,kBAAkB;AAAA,IACtE;AAAA,IACA;AAAA,IACA,YAAY,OAAOF,IAAG,mCAAmC,MAAM;AAAA,EACjE,CAAC;AACH;AAEA,SAAS,uBACPA,IACA,UACA,WACA,QACA,kBACA,cACQ;AACR,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,aAAa,OAAO,WAAW,IAAI,IAAI,UAAU;AACvD,QAAM,cAAc,IAAI,mBAAmB,YAAY;AAAA,IACrD,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,IACL,SAAS;AAAA,EACX,CAAC;AAED,QAAM,YAAY,aAAa;AAC/B,QAAM,cAAc,OAAOA,IAAG,wBAAwB,QAAQ,IAAI,MAAM;AAExE,QAAM,YAAY,YACd,OAAOA,IAAG,sBAAsB,QAAQ;AAAA,IACtC,aAAa,UAAU;AAAA,IACvB,YAAY,UAAU;AAAA,IACtB;AAAA,EACF,CAAC,IACD;AAEJ,QAAM,aAAa,YACf,OAAOA,IAAG,8BAA8B,MAAM,IAC9C,OAAOA,IAAG,4BAA4B,MAAM;AAEhD,QAAM,uBACJ,oBAAoB,iBAAiB,SAAS,IAC1C,GAAG,OAAOA,IAAG,iCAAiC,MAAM,CAAC;AAAA,EAAK,iBACvD,IAAI,CAAC,WAAW;AACf,UAAM,YAAY,OAAO,QAAQ,SAAS,IAAI,cAAc,OAAO,QAAQ,KAAK,GAAG,CAAC,KAAK;AACzF,WAAO,eAAe,OAAO,QAAQ,cAAc,OAAO,WAAW,mBAAmB,OAAO,WAAW,kBAAkB,OAAO,UAAU,GAAG,SAAS;AAAA,EAC3J,CAAC,EACA,KAAK,IAAI,CAAC;AAAA,IACb;AAEN,QAAM,OAAO,IAAI,YAAY;AAC7B,QAAM,mBAAmB,eAAe,GAAG,0BAA0BA,IAAG,cAAc,MAAM,CAAC;AAAA,IAAO;AAEpG,SAAO,GAAG,OAAOA,IAAG,0BAA0B,MAAM,CAAC,IAAI,WAAW;AAAA,EACpE,SAAS;AAAA,EACT,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,OAAOA,IAAG,4BAA4B,MAAM,CAAC;AAAA,EAC7C,OAAOA,IAAG,4BAA4B,QAAQ,EAAE,KAAK,CAAC,CAAC;AAAA,EACvD,OAAOA,IAAG,2BAA2B,MAAM,CAAC;AAAA;AAAA,EAE5C,OAAOA,IAAG,gCAAgC,MAAM,CAAC;AACnD;AAEA,SAAS,0BAA0BA,IAAgB,UAA+B,QAAwB;AACxG,SAAO,OAAOA,IAAG,mCAAmC,QAAQ,IAAI,MAAM;AACxE;AAEA,SAAS,gBAAgB,WAAsC,QAAwB;AACrF,MAAI,CAAC,UAAW,QAAO,OAAO,WAAW,IAAI,IAAI,iBAAO;AAExD,QAAM,OAAO,IAAI,KAAK,YAAY,GAAI;AACtC,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO,OAAO,WAAW,IAAI,IAAI,iBAAO;AAE1E,QAAM,aAAa,OAAO,WAAW,IAAI,IAAI,UAAU;AACvD,SAAO,KAAK,eAAe,YAAY;AAAA,IACrC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACH;AAEO,SAAS,kBAAkBC,UAA2C;AAC3E,QAAM;AAAA,IACJ,GAAAD;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAIC;AAEJ,QAAM,eAAe,yBAAyB,0BAA0BD,IAAG,UAAU,MAAM;AAC3F,QAAM,gBAAgB,uBAAuBA,IAAG,UAAU,WAAW,QAAQ,kBAAkB,YAAY;AAE3G,MAAI,eAAe;AACnB,MAAI,UAAU,UAAU;AACtB,mBACE;AAAA,KAAQ,OAAOA,IAAG,wBAAwB,MAAM,CAAC,SAAI,SAAS,SAAS,IAAI;AAAA,EACxE,OAAOA,IAAG,8BAA8B,MAAM,CAAC;AAAA,IAClD,SAAS,SAAS;AAAA,EACtB,WAAW,UAAU,WAAW;AAC9B,mBAAe;AAAA,EAAK,SAAS,SAAS;AAAA,EACxC;AAEA,SAAO,GAAG,YAAY,GAAG,YAAY;AAAA;AAAA,EAErC,aAAa;AACf;;;ACzNA,IAAO,gBAAQ;AAAA,EACb,IAAI;AAAA,IACF,OAAO;AAAA,MACL,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,WAAW;AAAA,UACX,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,WAAW;AAAA,UACX,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA,aAAa;AAAA,QACX,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,yBAAyB;AAAA,QACvB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,0BAA0B;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,aAAa;AAAA,UACb,aAAa;AAAA,UACb,OAAO;AAAA,UACP,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,aACE;AAAA,UACF,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,uBAAuB;AAAA,QACrB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,wBAAwB;AAAA,QACtB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,+DAAa;AAAA,QAC7B,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,2BAA2B;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,UACX,MAAM;AAAA,QACR;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,uBAAuB;AAAA,QACrB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,UACX,MAAM;AAAA,QACR;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,yDAAY;AAAA,QAC5B,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,2BAA2B;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,0BAA0B;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,6BAA6B;AAAA,QAC3B,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,+DAAa;AAAA,QAC7B,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,8BAA8B;AAAA,QAC5B,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,+DAAa;AAAA,QAC7B,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,wBAAwB;AAAA,QACtB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,YAAY;AAAA,QACV,MAAM;AAAA,QACN,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,KAAK;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,IAEA,OAAO;AAAA,MACL,oBAAoB;AAAA,MACpB,WAAW;AAAA,MACX,eAAe;AAAA,MACf,aAAa;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,MACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,mBAAmB;AAAA;AAAA;AAAA;AAAA,MAInB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMjB,sBAAsB;AAAA,MACtB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,kBACE;AAAA,MACF,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiBrB,4BAA4B;AAAA,MAC5B,4BAA4B;AAAA,MAC5B,oCAAoC;AAAA,MACpC,8BAA8B;AAAA,MAC9B,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASxB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,MAKhB,aAAa;AAAA,MACb,mBAAmB;AAAA,MACnB,qBAAqB;AAAA,MACrB,wBAAwB;AAAA,QACtB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASP,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,qBAAqB;AAAA,IACrB,eAAe;AAAA,EACjB;AAAA,EAEA,SAAS;AAAA,IACP,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,EACrB;AACF;;;ACnWA,IAAO,gBAAQ;AAAA,EACb,IAAI;AAAA,IACF,OAAO;AAAA,MACL,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UACE;AAAA,UACF,WACE;AAAA,UACF,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YACE;AAAA,UACF,UACE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,WAAW;AAAA,UACX,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YACE;AAAA,UACF,UACE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA,aAAa;AAAA,QACX,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,yBAAyB;AAAA,QACvB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,0BAA0B;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,aAAa;AAAA,UACb,aAAa;AAAA,UACb,OAAO;AAAA,UACP,YACE;AAAA,UACF,UACE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,aACE;AAAA,UACF,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,uBAAuB;AAAA,QACrB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA;AAAA,MAEA,wBAAwB;AAAA,QACtB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,mCAAmC;AAAA,QACnD,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,2BAA2B;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,UACX,MAAM;AAAA,QACR;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,uBAAuB;AAAA,QACrB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,UACX,MAAM;AAAA,QACR;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,6BAA6B;AAAA,QAC7C,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,2BAA2B;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,0BAA0B;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,6BAA6B;AAAA,QAC3B,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,mCAAmC;AAAA,QACnD,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,8BAA8B;AAAA,QAC5B,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,mCAAmC;AAAA,QACnD,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,wBAAwB;AAAA,QACtB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,YAAY;AAAA,QACV,MAAM;AAAA,QACN,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,KAAK;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA;AAAA,IAGA,OAAO;AAAA,MACL,oBAAoB;AAAA,MACpB,WAAW;AAAA,MACX,eAAe;AAAA,MACf,aAAa;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,MACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,mBAAmB;AAAA;AAAA;AAAA;AAAA,MAInB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMjB,sBACE;AAAA,MACF,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,kBACE;AAAA,MACF,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiBrB,4BAA4B;AAAA,MAC5B,4BAA4B;AAAA,MAC5B,oCAAoC;AAAA,MACpC,8BAA8B;AAAA,MAC9B,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASxB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,MAKhB,aAAa;AAAA,MACb,mBACE;AAAA,MACF,qBACE;AAAA,MACF,wBAAwB;AAAA,QACtB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOP,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,qBAAqB;AAAA,IACrB,eAAe;AAAA,EACjB;AAAA,EAEA,SAAS;AAAA,IACP,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,mBACE;AAAA,EACJ;AACF;;;AC7WA,IAAO,gBAAQ;AAAA,EACb,IAAI;AAAA,IACF,OAAO;AAAA,MACL,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UACE;AAAA,UACF,WACE;AAAA,UACF,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YACE;AAAA,UACF,UACE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UACE;AAAA,UACF,WAAW;AAAA,UACX,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YACE;AAAA,UACF,UACE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA,aAAa;AAAA,QACX,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,yBAAyB;AAAA,QACvB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,0BAA0B;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,aAAa;AAAA,UACb,aAAa;AAAA,UACb,OAAO;AAAA,UACP,YACE;AAAA,UACF,UACE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,aACE;AAAA,UACF,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,uBAAuB;AAAA,QACrB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,wBAAwB;AAAA,QACtB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,6FAAkB;AAAA,QAClC,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,2BAA2B;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,UACX,MAAM;AAAA,QACR;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,uBAAuB;AAAA,QACrB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,UACX,MAAM;AAAA,QACR;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,yGAAoB;AAAA,QACpC,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,2BAA2B;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,0BAA0B;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,6BAA6B;AAAA,QAC3B,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,6FAAkB;AAAA,QAClC,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,8BAA8B;AAAA,QAC5B,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,6FAAkB;AAAA,QAClC,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,wBAAwB;AAAA,QACtB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,YAAY;AAAA,QACV,MAAM;AAAA,QACN,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,KAAK;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,IAEA,OAAO;AAAA,MACL,oBAAoB;AAAA,MACpB,WAAW;AAAA,MACX,eAAe;AAAA,MACf,aAAa;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,MACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,mBAAmB;AAAA;AAAA;AAAA;AAAA,MAInB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMjB,sBACE;AAAA,MACF,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,kBACE;AAAA,MACF,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiBrB,4BAA4B;AAAA,MAC5B,4BAA4B;AAAA,MAC5B,oCAAoC;AAAA,MACpC,8BAA8B;AAAA,MAC9B,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASxB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,MAKhB,qBAAqB;AAAA,MACrB,wBAAwB;AAAA,QACtB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASP,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,qBAAqB;AAAA,IACrB,eAAe;AAAA,EACjB;AAAA,EAEA,SAAS;AAAA,IACP,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,EACrB;AACF;;;AC3WA,IAAO,gBAAQ;AAAA,EACb,IAAI;AAAA,IACF,OAAO;AAAA,MACL,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,WAAW;AAAA,UACX,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,WAAW;AAAA,UACX,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA,aAAa;AAAA,QACX,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,yBAAyB;AAAA,QACvB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,0BAA0B;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,aAAa;AAAA,UACb,aAAa;AAAA,UACb,OAAO;AAAA,UACP,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,aACE;AAAA,UACF,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,YAAY;AAAA,UACZ,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,uBAAuB;AAAA,QACrB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQN,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,YAAY;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,wBAAwB;AAAA,QACtB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,+DAAa;AAAA,QAC7B,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,2BAA2B;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,UACX,MAAM;AAAA,QACR;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,uBAAuB;AAAA,QACrB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,WAAW;AAAA,UACX,MAAM;AAAA,QACR;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,yDAAY;AAAA,QAC5B,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,2BAA2B;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,0BAA0B;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,6BAA6B;AAAA,QAC3B,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,qBAAqB;AAAA,QACnB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,sBAAsB;AAAA,QACpB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,+DAAa;AAAA,QAC7B,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,8BAA8B;AAAA,QAC5B,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,EAAE,MAAM,+DAAa;AAAA,QAC7B,aAAa;AAAA,QACb,iBAAiB;AAAA,QACjB,UAAU;AAAA,MACZ;AAAA,MACA,wBAAwB;AAAA,QACtB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,YAAY;AAAA,QACV,MAAM;AAAA,QACN,QAAQ,CAAC;AAAA,MACX;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,KAAK;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,IAEA,OAAO;AAAA,MACL,oBAAoB;AAAA,MACpB,WAAW;AAAA,MACX,eAAe;AAAA,MACf,aAAa;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,MACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,mBAAmB;AAAA;AAAA;AAAA;AAAA,MAInB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMjB,sBAAsB;AAAA,MACtB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,kBACE;AAAA,MACF,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiBrB,4BAA4B;AAAA,MAC5B,4BAA4B;AAAA,MAC5B,oCAAoC;AAAA,MACpC,8BAA8B;AAAA,MAC9B,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASxB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,MAKhB,qBAAqB;AAAA,MACrB,wBAAwB;AAAA,QACtB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASP,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,KAAK;AAAA,IACH,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,qBAAqB;AAAA,IACrB,eAAe;AAAA,EACjB;AAAA,EAEA,SAAS;AAAA,IACP,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,EACrB;AACF;;;ACpVA,IAAM,YAAqD;AAAA,EACzD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AACX;AAEA,SAAS,eAAe,KAA8BI,QAAuB;AAC3E,MAAI,UAAmB;AACvB,aAAWC,YAAWD,OAAK,MAAM,GAAG,GAAG;AACrC,QAAI,WAAW,QAAQ,OAAO,YAAY,SAAU,QAAO;AAC3D,cAAW,QAAoCC,QAAO;AAAA,EACxD;AACA,SAAO;AACT;AAEA,SAAS,YAAY,UAAkB,MAAuC;AAC5E,SAAO,SAAS,QAAQ,kBAAkB,CAAC,GAAG,QAAQ;AACpD,UAAM,MAAM,KAAK,GAAG;AACpB,WAAO,OAAO,OAAO,OAAO,GAAG,IAAI;AAAA,EACrC,CAAC;AACH;AAMO,SAAS,kBAAkB,gBAAwB,SAAsB;AAC9E,SAAO,CAAC,KAAaC,aAA8C;AACjE,UAAM,MAAOA,UAAS,OAAkB;AACxC,UAAM,SAAS,UAAU,GAAG,IAAI,MAAM;AACtC,UAAM,QAAQ,eAAe,UAAU,MAAM,GAAG,GAAG,KAAK,eAAe,UAAU,OAAO,GAAG,GAAG;AAE9F,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAI,CAACA,SAAS,QAAO;AACrB,WAAO,YAAY,OAAOA,QAAO;AAAA,EACnC;AACF;AAGO,IAAM,YAAY,EAAE,SAAS,eAAM,SAAS,eAAM,SAAS,eAAM,SAAS,cAAK;;;ACTtF,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAC7B,IAAM,oBAAoB;AAI1B,SAAS,sBAAsB,cAAsB,WAA4B,YAAoB;AACnG,MAAI,aAAa,SAAS;AACxB,QAAI,gBAAgB,GAAI,QAAO;AAC/B,QAAI,gBAAgB,GAAI,QAAO;AAC/B,QAAI,gBAAgB,IAAK,QAAO;AAChC,WAAO;AAAA,EACT;AAEA,MAAI,gBAAgB,GAAI,QAAO;AAC/B,MAAI,gBAAgB,GAAI,QAAO;AAC/B,MAAI,gBAAgB,IAAK,QAAO;AAChC,SAAO;AACT;AAEA,IAAM,sBAAsB,CAAC,gBAAM,gBAAM,gBAAM,gBAAM,gBAAM,gBAAM,gBAAM,gBAAM,cAAI;AACjF,IAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,IAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,IAAM,qBAAqB,CAAC,iBAAiB,aAAa,eAAe,YAAY;AACrF,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,eAAe,SAA0B;AACvD,QAAM,UAAU,QAAQ,KAAK;AAC7B,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,QAAQ,UAAU,KAAK,CAAC,oBAAoB,SAAS,OAAO,EAAG,QAAO;AAE1E,QAAM,QAAQ,QAAQ,YAAY;AAClC,MAAI,qBAAqB,SAAS,KAAK,EAAG,QAAO;AAEjD,MAAI,2BAA2B,KAAK,OAAO,EAAG,QAAO;AACrD,MAAI,aAAa,KAAK,CAAC,MAAM,UAAU,EAAE,YAAY,CAAC,EAAG,QAAO;AAChE,MAAI,mBAAmB,KAAK,CAAC,MAAM,EAAE,KAAK,OAAO,CAAC,EAAG,QAAO;AAC5D,MAAI,mBAAmB,KAAK,CAAC,MAAM,EAAE,KAAK,OAAO,CAAC,EAAG,QAAO;AAE5D,SAAO;AACT;AAEO,SAAS,oBAAoB,UAA4E;AAC9G,SAAO,SACJ,OAAO,CAAC,MAAM,EAAE,WAAW,eAAe,EAAE,OAAO,CAAC,EACpD,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,SAAS,EAAE,QAAS,KAAK,EAAE,EAAE;AAC1E;AAEA,SAAS,eAAe,UAAkE;AACxF,SAAO,SAAS,IAAI,CAAC,MAAM,GAAG,EAAE,UAAU,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AACvE;AAEO,SAAS,kBACd,UACA,oBACuD;AACvD,QAAM,WAAkE,CAAC;AACzE,MAAI,iBAAiE,CAAC;AACtE,MAAI,gBAAgB;AAEpB,aAAW,OAAO,UAAU;AAC1B,UAAM,YAAY,IAAI,WAAW,SAAS,IAAI,QAAQ,SAAS;AAC/D,QAAI,gBAAgB,YAAY,sBAAsB,eAAe,SAAS,GAAG;AAC/E,eAAS,KAAK,cAAc;AAC5B,uBAAiB,CAAC;AAClB,sBAAgB;AAAA,IAClB;AACA,mBAAe,KAAK,GAAG;AACvB,qBAAiB;AAAA,EACnB;AACA,MAAI,eAAe,SAAS,EAAG,UAAS,KAAK,cAAc;AAC3D,SAAO;AACT;AAIA,SAAS,mBAAmB,SAAiB,aAAqB,QAAgB,UAAmC;AACnH,MAAI,aAAa,SAAS;AACxB,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,aAAO,mDAAW,WAAW;AAAA;AAAA,EAAiD,OAAO;AAAA,IACvF;AACA,WAAO,uDAAuD,WAAW;AAAA;AAAA,EAAgF,OAAO;AAAA,EAClK;AACA,MAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,WAAO,+DAAa,WAAW;AAAA;AAAA,EAAgF,OAAO;AAAA,EACxH;AACA,SAAO,yCAAyC,WAAW;AAAA;AAAA,EAA+M,OAAO;AACnR;AAEA,SAAS,sBAAsB,SAAiB,QAAgB,UAAmC;AACjG,QAAM,QAAQ,aAAa,UAAU,KAAK;AAC1C,MAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,WAAO,qEAAc,KAAK;AAAA;AAAA,EAAsD,OAAO;AAAA,EACzF;AACA,SAAO,6DAA6D,KAAK;AAAA;AAAA,EAA2F,OAAO;AAC7K;AAEA,SAAS,iBACP,cACA,aACA,QACA,UACQ;AACR,QAAM,OAAO,aAAa,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AACnE,MAAI,aAAa,SAAS;AACxB,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,aAAO,uLAAiC,WAAW;AAAA;AAAA,EAAmC,IAAI;AAAA,IAC5F;AACA,WAAO,uGAAuG,WAAW;AAAA;AAAA,EAAkE,IAAI;AAAA,EACjM;AACA,MAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,WAAO,uLAAiC,WAAW;AAAA;AAAA,EAAiD,IAAI;AAAA,EAC1G;AACA,SAAO,uGAAuG,WAAW;AAAA;AAAA,EAAkH,IAAI;AACjP;AAIA,SAAS,mBAAmB,SAAiB,aAA6B;AACxE,MAAI,SAAS;AACb,MAAK,OAAO,WAAW,GAAG,KAAK,OAAO,SAAS,GAAG,KAAO,OAAO,WAAW,QAAG,KAAK,OAAO,SAAS,QAAG,GAAI;AACxG,aAAS,OAAO,MAAM,GAAG,EAAE;AAAA,EAC7B;AACA,QAAM,YAAY,KAAK,MAAM,cAAc,GAAG;AAC9C,MAAI,OAAO,SAAS,WAAW;AAC7B,aAAS,OAAO,MAAM,GAAG,YAAY,CAAC,IAAI;AAAA,EAC5C;AACA,SAAO;AACT;AAIA,eAAsB,uBACpB,MACA,WACAC,WAA0B,CAAC,GACH;AACxB,QAAM,EAAE,SAAS,SAAS,kBAAkB,OAAO,WAAW,WAAW,IAAIA;AAC7E,QAAM,MAAM,KAAK;AAEjB,MAAI;AACF,QAAI,CAAC,iBAAiB;AACpB,YAAM,WAAW,KAAK,WAAW,SAAS;AAC1C,UAAI,SAAU,QAAO,EAAE,SAAS,MAAM,SAAS,SAAS;AAAA,IAC1D;AAEA,UAAM,cAAc,KAAK,aAAa,SAAS;AAC/C,QAAI,CAAC,aAAa;AAChB,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,EAAE,yBAAyB,EAAE;AAAA,IACpE;AAEA,QAAI,YAAY,SAAS,mBAAmB;AAC1C,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,EAAE,0BAA0B,EAAE,OAAO,kBAAkB,CAAC,EAAE;AAAA,IACjG;AAEA,UAAM,gBAAgB,oBAAoB,WAAW;AACrD,QAAI,cAAc,SAAS,mBAAmB;AAC5C,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,EAAE,+BAA+B,EAAE,OAAO,kBAAkB,CAAC,EAAE;AAAA,IACtG;AAEA,UAAM,cAAc,sBAAsB,cAAc,QAAQ,QAAQ;AACxE,UAAM,YAAY,aAAa,UAAU,MAAM;AAC/C,UAAM,eAAe,aAAa,UAAU,MAAM;AAClD,UAAM,UAAU,eAAe,aAAa;AAE5C,SAAK;AAAA,MACH;AAAA,MACA,iCAAiC,SAAS,cAAc,QAAQ,SAAS,YAAY,MAAM,WAAW,cAAc,MAAM,WAAW,QAAQ,MAAM;AAAA,IACrJ;AAEA,QAAI;AACJ,QAAI,QAAQ,UAAU,mBAAmB;AACvC,gBAAU,MAAM,KAAK;AAAA,QACnB,KAAK,EAAE,4BAA4B;AAAA,QACnC,mBAAmB,SAAS,aAAa,QAAQ,QAAQ;AAAA,QACzD,EAAE,aAAa,KAAK,UAAU;AAAA,MAChC;AACA,gBAAU,QAAQ,KAAK;AAAA,IACzB,OAAO;AACL,YAAM,WAAW,kBAAkB,eAAe,oBAAoB;AACtE,WAAK,KAAK,WAAW,2BAA2B,SAAS,MAAM,WAAW;AAE1E,YAAM,eAAyB,CAAC;AAChC,iBAAWC,YAAW,UAAU;AAC9B,cAAM,aAAa,eAAeA,QAAO;AACzC,cAAM,MAAM,MAAM,KAAK;AAAA,UACrB,KAAK,EAAE,4BAA4B;AAAA,UACnC,sBAAsB,YAAY,QAAQ,QAAQ;AAAA,UAClD,EAAE,aAAa,KAAK,WAAW,aAAa;AAAA,QAC9C;AACA,qBAAa,KAAK,IAAI,KAAK,CAAC;AAAA,MAC9B;AAEA,UAAI,aAAa,WAAW,GAAG;AAC7B,kBAAU,aAAa,CAAC;AAAA,MAC1B,OAAO;AACL,kBAAU,MAAM,KAAK;AAAA,UACnB,KAAK,EAAE,2BAA2B;AAAA,UAClC,iBAAiB,cAAc,aAAa,QAAQ,QAAQ;AAAA,UAC5D,EAAE,aAAa,KAAK,UAAU;AAAA,QAChC;AACA,kBAAU,QAAQ,KAAK;AAAA,MACzB;AAAA,IACF;AAEA,cAAU,mBAAmB,SAAS,WAAW;AACjD,SAAK,YAAY,WAAW,OAAO;AAEnC,SAAK,KAAK,WAAW,uBAAuB,QAAQ,MAAM,GAAG,EAAE,CAAC,MAAM;AACtE,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC,SAAS,OAAO;AACd,SAAK,MAAM,WAAW,6BAA6B,KAAK;AACxD,WAAO,EAAE,SAAS,OAAO,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,EACzF;AACF;AAEA,eAAsB,yBACpB,MACA,YACAD,WAA0B,CAAC,GAC3B,YAC+D;AAC/D,MAAI,UAAU;AACd,MAAI,SAAS;AACb,MAAI,UAAU;AAEd,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,UAAM,SAAS,MAAM,uBAAuB,MAAM,WAAW,CAAC,GAAGA,QAAO;AACxE,QAAI,OAAO,SAAS;AAClB;AAAA,IACF,WAAW,OAAO,OAAO,SAAS,cAAI,KAAK,OAAO,OAAO,SAAS,WAAW,KAAK,OAAO,OAAO,SAAS,KAAK,GAAG;AAC/G;AAAA,IACF,OAAO;AACL;AAAA,IACF;AACA,iBAAa,IAAI,GAAG,WAAW,MAAM;AAAA,EACvC;AAEA,SAAO,EAAE,SAAS,QAAQ,QAAQ;AACpC;AAEO,SAAS,gCACd,MACA,YACwD;AACxD,QAAM,UAAU,oBAAI,IAAuD;AAE3E,aAAW,MAAM,YAAY;AAC3B,UAAM,WAAW,KAAK,aAAa,EAAE;AACrC,QAAI,CAAC,UAAU;AACb,cAAQ,IAAI,IAAI,EAAE,aAAa,OAAO,QAAQ,KAAK,EAAE,yBAAyB,EAAE,CAAC;AACjF;AAAA,IACF;AACA,QAAI,SAAS,SAAS,mBAAmB;AACvC,cAAQ,IAAI,IAAI,EAAE,aAAa,OAAO,QAAQ,KAAK,EAAE,wBAAwB,EAAE,CAAC;AAChF;AAAA,IACF;AACA,UAAM,QAAQ,oBAAoB,QAAQ;AAC1C,QAAI,MAAM,SAAS,mBAAmB;AACpC,cAAQ,IAAI,IAAI,EAAE,aAAa,OAAO,QAAQ,KAAK,EAAE,6BAA6B,EAAE,CAAC;AACrF;AAAA,IACF;AACA,YAAQ,IAAI,IAAI,EAAE,aAAa,KAAK,CAAC;AAAA,EACvC;AAEA,SAAO;AACT;;;AClVO,IAAM,mBAAmB;AAwBhC,SAAS,SAAS,KAAaE,UAA2C;AACxE,MAAI,QAAQ,oBAAoBA,UAAS,MAAO,QAAO,WAAWA,SAAQ,KAAK;AAC/E,MAAI,QAAQ,qBAAsB,QAAO;AACzC,SAAO;AACT;AAEA,SAAS,oBAA4B;AACnC,SAAO,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACjE;AAEA,SAAS,YAAY,MAAoC,SAA8C;AACrG,MAAI,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,QAAQ,EAAG,QAAO;AAChE,QAAM,WAAW,QAAQ,CAAC;AAC1B,SAAO,WAAW,EAAE,UAAU,SAAS,IAAI,SAAS,SAAS,SAAS,GAAG,IAAI;AAC/E;AAEA,SAAS,eAAe,QAA6C;AACnE,SAAQ,OAA8C;AACxD;AAEA,SAAS,kBAAkB,SAA4B,aAAqB,iBAAmC;AAC7G,SAAO,QAAQ,KAAK,CAAC,WAAW;AAC9B,UAAM,oBAAoB,eAAe,MAAM;AAC/C,QAAI,sBAAsB,YAAa,QAAO;AAC9C,WAAO,CAAC,qBAAqB,oBAAoB,UAAa,OAAO,aAAa;AAAA,EACpF,CAAC;AACH;AAEO,IAAM,iBAAN,MAAqB;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,SAAwB,OAA2B,CAAC,GAAG;AACjE,SAAK,UAAU;AACf,SAAK,IAAI,KAAK,KAAK;AACnB,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,kBAAkB,KAAK;AAC5B,SAAK,kBAAkB,KAAK;AAC5B,SAAK,gBAAgB,KAAK;AAC1B,SAAK,gBAAgB,KAAK;AAAA,EAC5B;AAAA,EAEA,YAA2B;AACzB,UAAM,QAAQ,KAAK,QAAQ,SAAwB,YAAY;AAC/D,QAAI,CAAC,OAAO;AACV,aAAO,EAAE,SAAS,CAAC,GAAG,kBAAkB,MAAM,WAAW,KAAK;AAAA,IAChE;AAEA,SAAK,gBAAgB,MAAM,OAAO;AAElC,UAAM,kBAAkB,KAAK,gBACzB,MAAM,QAAQ,IAAI,CAAC,WAAW;AAC5B,YAAM,aAAa,KAAK;AAAA,QACtB,OAAO;AAAA,QACN,OAA8C;AAAA,MACjD;AACA,aAAO,EAAE,GAAG,QAAQ,QAAQ,cAAc,OAAO,UAAU,GAAG;AAAA,IAChE,CAAC,IACD,MAAM;AAEV,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS;AAAA,MACT,kBAAkB,YAAY,MAAM,kBAAkB,eAAe;AAAA,MACrE,WAAW,YAAY,MAAM,WAAW,eAAe;AAAA,IACzD;AAAA,EACF;AAAA,EAEA,UAAU,OAA4B;AACpC,SAAK,QAAQ,UAAyB,cAAc;AAAA,MAClD,GAAG;AAAA,MACH,SAAS,MAAM,QAAQ,IAAI,CAAC,YAAY;AAAA,QACtC,GAAG;AAAA,QACH,QAAQ;AAAA,MACV,EAAE;AAAA,IACJ,CAAC;AAAA,EACH;AAAA,EAEA,gBAAmC;AACjC,WAAO,KAAK,UAAU,EAAE;AAAA,EAC1B;AAAA,EAEA,0BAA4C;AAC1C,UAAM,QAAQ,KAAK,UAAU;AAC7B,WAAO,YAAY,MAAM,kBAAkB,MAAM,OAAO;AAAA,EAC1D;AAAA,EAEA,4BAAoD;AAClD,UAAM,QAAQ,KAAK,UAAU;AAC7B,UAAM,OAAO,YAAY,MAAM,kBAAkB,MAAM,OAAO;AAC9D,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,QAAQ;AAC/D,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,EAAE,GAAG,QAAQ,OAAO,KAAK,WAAW,OAAO,MAAM;AAAA,EAC1D;AAAA,EAEA,mBAAqC;AACnC,UAAM,QAAQ,KAAK,UAAU;AAC7B,QAAI,MAAM,cAAc,KAAM,QAAO;AACrC,WAAO,YAAY,MAAM,WAAW,MAAM,OAAO;AAAA,EACnD;AAAA,EAEA,qBAA6C;AAC3C,UAAM,QAAQ,KAAK,UAAU;AAC7B,QAAI,MAAM,cAAc,KAAM,QAAO,KAAK,0BAA0B;AAEpE,UAAM,OAAO,YAAY,MAAM,WAAW,MAAM,OAAO;AACvD,QAAI,MAAM;AACR,YAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,QAAQ;AAC/D,UAAI,OAAQ,QAAO,EAAE,GAAG,QAAQ,OAAO,KAAK,WAAW,OAAO,MAAM;AAAA,IACtE;AACA,WAAO,KAAK,0BAA0B;AAAA,EACxC;AAAA,EAEA,cAAc,IAAoC;AAChD,UAAM,QAAQ,KAAK,UAAU;AAC7B,WAAO,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;AAAA,EACnD;AAAA,EAEA,UAAU,QAIR;AACA,UAAM,QAAQ,KAAK,UAAU;AAE7B,QAAI,MAAM,QAAQ,UAAU,kBAAkB;AAC5C,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,EAAE,kBAAkB,EAAE,OAAO,iBAAiB,CAAC,EAAE;AAAA,IACxF;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,YAA6B;AAAA,MACjC,GAAG;AAAA,MACH,IAAI,KAAK,WAAW;AAAA,MACpB,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAEA,UAAM,QAAQ,KAAK,SAAS;AAE5B,QAAI,MAAM,QAAQ,WAAW,GAAG;AAC9B,YAAM,mBAAmB,EAAE,UAAU,UAAU,IAAI,SAAS,UAAU,SAAS,GAAG;AAAA,IACpF;AAEA,QAAI,UAAU,UAAU,KAAK,iBAAiB;AAC5C,YAAM,cAAc,KAAK,gBAAgB,WAAW,UAAU,MAAM;AACpE,UAAI,aAAa;AACf;AAAC,QAAC,UAAiD,cAAc;AAAA,MACnE;AAAA,IACF;AAEA,SAAK,UAAU,KAAK;AACpB,WAAO,EAAE,SAAS,MAAM,QAAQ,EAAE,GAAG,WAAW,QAAQ,GAAG,EAAE;AAAA,EAC/D;AAAA,EAEA,aACE,IACA,SACsC;AACtC,UAAM,QAAQ,KAAK,UAAU;AAC7B,UAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AAExD,QAAI,UAAU,IAAI;AAChB,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,EAAE,oBAAoB,EAAE;AAAA,IAC/D;AAEA,UAAM,YAAY,MAAM,QAAQ,KAAK;AACrC,UAAM,iBAAkB,UAAiD;AAEzE,UAAM,UAAU;AAAA,MACd,GAAG;AAAA,MACH,GAAG;AAAA,MACH,WAAW,KAAK,IAAI;AAAA,IACtB;AACA,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI,qBAA6C;AAEjD,QAAI,QAAQ,UAAU,KAAK,iBAAiB;AAC1C,YAAM,cAAc,KAAK,gBAAgB,SAAS,QAAQ,MAAM;AAChE,UAAI,aAAa;AACf;AAAC,QAAC,MAAM,QAAQ,KAAK,EAAyC,cAAc;AAC5E,YACE,kBACA,mBAAmB,eACnB,CAAC,kBAAkB,MAAM,SAAS,gBAAgB,UAAU,QAAQ,GACpE;AACA,+BAAqB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,SAAK,UAAU,KAAK;AACpB,QAAI,mBAAoB,MAAK,kBAAkB,kBAAkB;AACjE,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EAEA,aAAa,IAAkD;AAC7D,UAAM,QAAQ,KAAK,UAAU;AAC7B,UAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AAExD,QAAI,UAAU,IAAI;AAChB,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,EAAE,oBAAoB,EAAE;AAAA,IAC/D;AAEA,UAAM,UAAU,MAAM,QAAQ,KAAK;AACnC,UAAM,QAAQ,OAAO,OAAO,CAAC;AAE7B,UAAM,WAAW,MAAM,QAAQ,CAAC;AAChC,QAAI,MAAM,kBAAkB,aAAa,IAAI;AAC3C,YAAM,mBAAmB,WAAW,EAAE,UAAU,SAAS,IAAI,SAAS,SAAS,SAAS,GAAG,IAAI;AAAA,IACjG;AACA,QAAI,MAAM,WAAW,aAAa,IAAI;AACpC,YAAM,YAAY,WAAW,EAAE,UAAU,SAAS,IAAI,SAAS,SAAS,SAAS,GAAG,IAAI;AAAA,IAC1F;AAEA,SAAK,UAAU,KAAK;AACpB,UAAM,qBAAqB,eAAe,OAAO;AACjD,QAAI,CAAC,sBAAsB,CAAC,kBAAkB,MAAM,SAAS,oBAAoB,QAAQ,QAAQ,GAAG;AAClG,WAAK,kBAAkB,OAAO;AAAA,IAChC;AACA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EAEA,yBAAyB,UAAkB,SAAuD;AAChG,UAAM,QAAQ,KAAK,UAAU;AAC7B,UAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ;AAE1D,QAAI,CAAC,QAAQ;AACX,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,EAAE,oBAAoB,EAAE;AAAA,IAC/D;AAEA,UAAM,mBAAmB,EAAE,UAAU,QAAQ;AAC7C,SAAK,UAAU,KAAK;AACpB,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EAEA,aAAa,MAA8D;AACzE,UAAM,QAAQ,KAAK,UAAU;AAE7B,QAAI,SAAS,MAAM;AACjB,YAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,QAAQ;AAC/D,UAAI,CAAC,QAAQ;AACX,eAAO,EAAE,SAAS,OAAO,OAAO,KAAK,EAAE,oBAAoB,EAAE;AAAA,MAC/D;AAAA,IACF;AAEA,UAAM,YAAY;AAClB,SAAK,UAAU,KAAK;AACpB,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EAEA,kBAA2B;AACzB,WAAO,KAAK,0BAA0B,MAAM;AAAA,EAC9C;AACF;;;ACjTA,SAAS,mBAA2B;AAClC,SAAO,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACxE;AAEO,IAAM,sBAAN,MAA0B;AAAA,EAC/B,YAAoB,SAAwB;AAAxB;AAAA,EAAyB;AAAA,EAAzB;AAAA,EAEpB,SAA+B;AAC7B,WAAO,KAAK,QAAQ,SAA+B,kBAAkB,KAAK,CAAC;AAAA,EAC7E;AAAA,EAEA,IAAI,OAQmB;AACrB,UAAM,YAAY,KAAK,OAAO;AAC9B,UAAM,cAAkC;AAAA,MACtC,IAAI,iBAAiB;AAAA,MACrB,MAAM,MAAM;AAAA,MACZ,MAAO,MAAM,QAAQ;AAAA,MACrB,gBAAgB,MAAM;AAAA,MACtB,UAAU;AAAA,MACV,sBAAsB,MAAM,wBAAwB;AAAA,MACpD,UAAU,MAAM,YAAY,CAAC;AAAA,MAC7B,SAAS;AAAA,MACT,kBAAkB;AAAA,MAClB,SAAS,MAAM;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AACA,cAAU,KAAK,WAAW;AAC1B,SAAK,QAAQ,UAAU,oBAAoB,SAAS;AACpD,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,IAAY,SAA4E;AAC7F,UAAM,YAAY,KAAK,OAAO;AAC9B,UAAM,QAAQ,UAAU,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AACpD,QAAI,UAAU,GAAI,QAAO,EAAE,SAAS,OAAO,OAAO,4BAA4B;AAC9E,cAAU,KAAK,IAAI,EAAE,GAAG,UAAU,KAAK,GAAG,GAAG,QAAQ;AACrD,SAAK,QAAQ,UAAU,oBAAoB,SAAS;AACpD,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EAEA,OAAO,IAAkD;AACvD,UAAM,YAAY,KAAK,OAAO;AAC9B,UAAM,QAAQ,UAAU,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AACpD,QAAI,UAAU,GAAI,QAAO,EAAE,SAAS,OAAO,OAAO,4BAA4B;AAC9E,cAAU,OAAO,OAAO,CAAC;AACzB,SAAK,QAAQ,UAAU,oBAAoB,SAAS;AACpD,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AACF;AAEO,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAAoB,SAAwB;AAAxB;AAAA,EAAyB;AAAA,EAAzB;AAAA,EAEpB,SAA4B;AAC1B,WAAO,KAAK,QAAQ,SAA4B,eAAe,KAAK,CAAC;AAAA,EACvE;AAAA,EAEA,IAAI,OAS8D;AAChE,UAAM,SAAS,KAAK,OAAO;AAC3B,QAAI,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,MAAM,EAAE,eAAe,MAAM,UAAU,GAAG;AAC9E,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,UAAU,MAAM,EAAE,oCAAoC,MAAM,UAAU;AAAA,MAC/E;AAAA,IACF;AACA,UAAM,WAA4B;AAAA,MAChC,IAAI,MAAM;AAAA,MACV,YAAY,MAAM;AAAA,MAClB,MAAM,MAAM;AAAA,MACZ,aAAa,MAAM;AAAA,MACnB,eAAe,MAAM;AAAA,MACrB,cAAe,MAAM,gBAAgB,CAAC,MAAM;AAAA,MAC5C,gBAAiB,MAAM,kBAAkB,CAAC,MAAM;AAAA,MAChD,QAAS,MAAM,UAAU;AAAA,MACzB,SAAS;AAAA,MACT,UAAU;AAAA,IACZ;AACA,WAAO,KAAK,QAAQ;AACpB,SAAK,QAAQ,UAAU,iBAAiB,MAAM;AAC9C,WAAO,EAAE,SAAS,MAAM,OAAO,SAAS;AAAA,EAC1C;AAAA,EAEA,OAAO,YAAoB,SAAiB,SAAyE;AACnH,UAAM,SAAS,KAAK,OAAO;AAC3B,UAAM,QAAQ,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,WAAW,EAAE,eAAe,UAAU;AACrF,QAAI,UAAU,GAAI,QAAO,EAAE,SAAS,OAAO,OAAO,yBAAyB;AAC3E,WAAO,KAAK,IAAI,EAAE,GAAG,OAAO,KAAK,GAAG,GAAG,QAAQ;AAC/C,SAAK,QAAQ,UAAU,iBAAiB,MAAM;AAC9C,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EAEA,OAAO,YAAoB,SAAuD;AAChF,UAAM,SAAS,KAAK,OAAO;AAC3B,UAAM,QAAQ,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,WAAW,EAAE,eAAe,UAAU;AACrF,QAAI,UAAU,GAAI,QAAO,EAAE,SAAS,OAAO,OAAO,yBAAyB;AAC3E,WAAO,OAAO,OAAO,CAAC;AACtB,SAAK,QAAQ,UAAU,iBAAiB,MAAM;AAC9C,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AACF;;;ACvFO,SAAS,0BAA0B,KAAqB;AAC7D,SAAO,IAAI,QAAQ,YAAY,EAAE;AACnC;AAMO,SAAS,iCAAiC,KAAqB;AACpE,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,UAAU,IAAI,QAAQ,QAAQ,EAAE;AACtC,MAAI,QAAQ,SAAS,KAAK,EAAG,QAAO;AACpC,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,OAAO;AAC9B,QAAI,OAAO,aAAa,OAAO,OAAO,aAAa,IAAI;AACrD,aAAO,UAAU;AAAA,IACnB;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEA,IAAMC,0BAAyB;AAE/B,SAAS,iBAAiB,YAAoB,SAAyC;AACrF,QAAM,cAAc,2BAA2B,UAAU;AACzD,SAAO,YAAY,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,KAAK,eAAe,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,KAAK;AACtG;AAEA,IAAM,uBAA8C;AAAA,EAClD,QAAQ;AAAA,EACR,WAAW;AACb;AAiBA,SAAS,eAAe,UAAkB,SAAiB,UAAwD;AAIjH,QAAM,YACJ,UAAU,aAAa,SAAS,WAAW,KAAM,CAAC,UAAU,WAAW,iBAAiB,UAAU,OAAO;AAE3G,MAAI,CAAC,UAAW,QAAO,EAAE,WAAW,OAAO,QAAQ,OAAU;AAE7D,QAAM,iBAAiB,kBAAkB,UAAU,OAAO;AAC1D,MAAI,OAAO,KAAK,cAAc,EAAE,WAAW,GAAG;AAC5C,WAAO,EAAE,WAAW,MAAM,QAAQ,OAAU;AAAA,EAC9C;AAIA,QAAM,EAAE,kBAAkB,GAAG,aAAa,IAAI;AAC9C,SAAO;AAAA,IACL,WAAW;AAAA,IACX,QAAQ,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,IAC9D;AAAA,EACF;AACF;AAEO,SAAS,aAAa,QAAuBC,UAA+C;AACjG,QAAM,cAAc,kBAAkB,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,QAAQ;AAC1E,QAAM,UAAU,OAAO,WAAW,aAAa,kBAAkB;AACjE,QAAM,UAAU,OAAO,SAAS;AAEhC,QAAM,YAAYA,UAAS,eAAe;AAC1C,QAAM,WAAW,UAAU,OAAO,UAAU,OAAO;AACnD,QAAM,gBAAgB,UAAU,iBAAiBD;AAEjD,QAAM,YAAoB,OAAO,aAAuB,qBAAqB,OAAO,QAAQ,KAAK;AAEjG,MAAI,cAAc,wBAAwB;AACxC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,KAAK;AAAA,MACL,UAAU;AAAA,MACV;AAAA,MACA,WAAW,iBAAiB,OAAO,UAAU,OAAO;AAAA,MACpD,OAAO,CAAC,MAAM;AAAA,MACd,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AAAA,MACzD;AAAA,MACA,WAAW,OAAO,aAAa;AAAA,IACjC;AAAA,EACF;AAEA,MAAI,cAAc,sBAAsB;AACtC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,KAAK;AAAA,MACL,UAAU;AAAA,MACV,SAAS,0BAA0B,OAAO;AAAA,MAC1C,WAAW;AAAA,MACX,OAAO,CAAC,MAAM;AAAA,MACd,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AAAA,MACzD;AAAA,MACA,WAAW,OAAO,aAAa;AAAA,IACjC;AAAA,EACF;AAEA,QAAM,kBACJ,OAAO,aAAa,wBAAwB,cAAc,wBAAwB,cAAc,sBAC5F,iCAAiC,OAAO,IACxC;AAEN,QAAM,EAAE,WAAW,QAAQ,iBAAiB,IAAI,eAAe,OAAO,UAAU,SAAS,QAAQ;AAEjG,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,UAAU,OAAO;AAAA,IACjB,SAAS;AAAA,IACT,SAASC,UAAS;AAAA,IAClB;AAAA,IACA;AAAA,IACA,OAAO,CAAC,MAAM;AAAA,IACd,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AAAA,IACzD;AAAA,IACA,WAAW,OAAO,aAAa;AAAA,IAC/B;AAAA,EACF;AACF;;;ACtKA,SAAS,kBAAAC,uBAAsB;AAwB/B,eAAsB,kBACpB,UACA,QACA,SACA,WACAC,UACkC;AAClC,QAAM,qBAAqB,aAAa;AAExC,MAAI,uBAAuB,sBAAsB;AAC/C,WAAO,EAAE,SAAS,OAAO,OAAO,mDAAmD;AAAA,EACrF;AAEA,QAAM,cAAc,kBAAkB,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ;AACnE,QAAM,aAAa,WAAW,aAAa,kBAAkB;AAC7D,MAAI,CAAC,YAAY;AACf,WAAO,EAAE,SAAS,OAAO,OAAO,uBAAuB;AAAA,EACzD;AAEA,QAAM,kBAAkB,IAAI,gBAAgB;AAC5C,QAAM,UAAU,WAAW,MAAM,gBAAgB,MAAM,GAAG,IAAK;AAE/D,MAAI;AACF,QAAI;AACJ,UAAM,UAAkC,EAAE,GAAGA,UAAS,QAAQ;AAE9D,QAAI,uBAAuB,wBAAwB;AACjD,YAAM,UAAU,WAAW,QAAQ,QAAQ,EAAE,EAAE,QAAQ,gBAAgB,EAAE;AACzE,YAAM,GAAG,OAAO,sBAAsB,MAAM;AAAA,IAC9C,OAAO;AACL,YAAM,WAAW,iCAAiC,UAAU;AAC5D,YAAM,GAAG,QAAQ;AACjB,cAAQ,eAAe,IAAI,UAAU,MAAM;AAAA,IAC7C;AAEA,IAAAA,UAAS,QAAQ,QAAQ,OAAO,0BAA0B;AAAA,MACxD,KAAK,IAAI,QAAQ,aAAa,SAAS;AAAA,MACvC;AAAA,IACF,CAAC;AAED,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ,gBAAgB;AAAA,IAC1B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,aAAO,EAAE,SAAS,OAAO,OAAO,QAAQ,SAAS,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,GAAG;AAAA,IACnF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,QAAI;AAEJ,QAAI,uBAAuB,wBAAwB;AACjD,YAAM,eAAgB,KAAK,UAAU,CAAC;AAKtC,eAAS,aAAa,IAAI,CAAC,MAAM;AAC/B,cAAM,MAAM,EAAE,QAAQ,IAAI,QAAQ,aAAa,EAAE;AACjD,eAAO;AAAA,UACL;AAAA,UACA,MAAM,EAAE,eAAe;AAAA,UACvB,SAAS;AAAA,UACT,eAAe,EAAE,mBAAmB;AAAA,QACtC;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AACL,YAAM,OAAQ,KAAK,QAAQ,CAAC;AAK5B,eAAS,KACN,OAAO,CAAC,MAAM,EAAE,EAAE,EAClB,IAAI,CAAC,OAAO;AAAA,QACX,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,SAAS,EAAE;AAAA,QACX,eAAe,EAAE,kBAAkB;AAAA,MACrC,EAAE;AAAA,IACN;AAEA,IAAAA,UAAS,QAAQ,QAAQ,OAAO,WAAW,OAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC;AACtF,WAAO,EAAE,SAAS,MAAM,OAAO;AAAA,EACjC,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,QAAI,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,YAAY,GAAG;AACjE,aAAO,EAAE,SAAS,OAAO,OAAO,0BAA0B;AAAA,IAC5D;AACA,WAAO,EAAE,SAAS,OAAO,OAAO,QAAQ;AAAA,EAC1C,UAAE;AACA,iBAAa,OAAO;AAAA,EACtB;AACF;AAEA,eAAsB,eACpB,UACA,QACA,SACA,OACA,WACAA,UAC+C;AAC/C,QAAM,cAAc,kBAAkB,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ;AACnE,QAAM,eAAe,aAAa,WAAW,CAAC;AAE9C,QAAM,SAAwB;AAAA,IAC5B;AAAA,IACA,OAAO,SAAS;AAAA,IAChB;AAAA,IACA;AAAA,EACF;AACA,QAAM,UAA0B,aAAa,QAAQ,EAAE,SAASA,UAAS,QAAQ,CAAC;AAElF,QAAM,kBAAkB,IAAI,gBAAgB;AAC5C,QAAM,UAAU,WAAW,MAAM,gBAAgB,MAAM,GAAG,IAAK;AAE/D,MAAI;AACF,UAAM,SAAS,MAAMC;AAAA,MACnB;AAAA,MACA,EAAE,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,MAAM,WAAW,KAAK,IAAI,EAAE,CAAC,EAAS;AAAA,MAC5E,EAAE,QAAQ,WAAW,GAAG,QAAQ,gBAAgB,OAAO;AAAA,IACzD;AACA,QAAI,OAAO,eAAe,WAAW,OAAO,eAAe,WAAW;AACpE,aAAO,EAAE,SAAS,OAAO,OAAO,OAAO,gBAAgB,oBAAoB;AAAA,IAC7E;AACA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,QAAI,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,YAAY,GAAG;AACjE,aAAO,EAAE,SAAS,OAAO,OAAO,0BAA0B;AAAA,IAC5D;AACA,WAAO,EAAE,SAAS,OAAO,OAAO,QAAQ;AAAA,EAC1C,UAAE;AACA,iBAAa,OAAO;AAAA,EACtB;AACF;;;ACrKA,SAAS,gBAAAC,qBAAoB;AAwB7B,SAAS,aAAa,UAAoD,WAAgC;AACxG,SAAO,SAAS,IAAI,CAAC,OAAO;AAAA,IAC1B,MAAM,EAAE;AAAA,IACR,SAAS,EAAE;AAAA,IACX;AAAA,EACF,EAAE;AACJ;AAMA,eAAsB,mBAAmBC,UAAmD;AAC1F,QAAM,EAAE,UAAU,QAAQ,SAAS,aAAa,WAAW,SAAS,YAAY,IAAIA;AAEpF,QAAM,YAAY,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AAC1D,QAAM,gBAAgB,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ;AAChE,QAAM,MAAM,KAAK,IAAI;AAErB,QAAM,cAAcD;AAAA,IAClB;AAAA,IACA;AAAA,MACE,cAAc,WAAW;AAAA,MACzB,UAAU,aAAa,eAAe,GAAG;AAAA,IAC3C;AAAA,IACA,EAAE,QAAQ,aAAa,WAAW,QAAQ,YAAY;AAAA,EACxD;AAEA,MAAI,mBAAmB;AAEvB,MAAI;AACF,qBAAiB,SAAS,aAAa;AACrC,UAAI,aAAa,SAAS;AACxB,YAAI,CAAC,kBAAkB;AACrB,6BAAmB;AACnB,kBAAQ,EAAE,SAAS,IAAI,YAAY,MAAM,cAAc,OAAO,CAAC;AAAA,QACjE;AACA;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,kBAAkB;AACnC,gBAAQ,EAAE,SAAS,IAAI,YAAY,OAAO,UAAU,GAAG,CAAC;AACxD;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,kBAAkB;AACnC,gBAAQ,EAAE,SAAS,IAAI,YAAY,OAAO,UAAU,MAAM,MAAM,CAAC;AACjE;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,gBAAgB;AACjC,gBAAQ,EAAE,SAAS,IAAI,YAAY,OAAO,UAAU,IAAI,cAAc,KAAK,CAAC;AAC5E;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,cAAc;AAC/B,gBAAQ,EAAE,SAAS,MAAM,OAAO,YAAY,MAAM,CAAC;AACnD;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,QAAQ;AACzB,2BAAmB;AACnB,gBAAQ,EAAE,SAAS,IAAI,YAAY,MAAM,cAAc,MAAM,WAAW,WAAW,WAAW,OAAO,CAAC;AACtG;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,SAAS;AAC1B,2BAAmB;AACnB,cAAM,WACJ,MAAM,OAAO,SACT,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAChC,IAAI,CAAC,MAAO,UAAU,IAAI,EAAE,OAAO,EAAG,EACtC,KAAK,EAAE,KAAK;AACjB,gBAAQ,EAAE,SAAS,IAAI,YAAY,MAAM,cAAc,SAAS,OAAO,SAAS,CAAC;AACjF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,kBAAkB;AACrB,cAAQ,EAAE,SAAS,IAAI,YAAY,MAAM,cAAc,OAAO,CAAC;AAAA,IACjE;AAAA,EACF,SAAS,OAAO;AACd,QAAI,CAAC,kBAAkB;AACrB,YAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,cAAQ,EAAE,SAAS,IAAI,YAAY,MAAM,cAAc,SAAS,OAAO,IAAI,CAAC;AAAA,IAC9E;AAAA,EACF;AACF;;;AC0DA,SAAS,MAAM,kBAAAE,iBAAgB,gBAAAC,qBAAoB;;;ACtKnD,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAKtB,IAAM,cAAwC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE;AAGrF,IAAM,iBAAiB,KAAK,OAAO;AAEnC,IAAM,YAAN,MAAgB;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,SAAiB;AAC3B,SAAK,UAAe,YAAK,SAAS,SAAS;AAC3C,SAAK,aAAkB,YAAK,SAAS,aAAa;AAClD,SAAK,YAAY,iBAAiB;AAAA,EACpC;AAAA,EAEA,MAAM,OAAe,SAAiB,MAAsB;AAC1D,SAAK,MAAM,SAAS,OAAO,SAAS,IAAI;AAAA,EAC1C;AAAA,EAEA,KAAK,OAAe,SAAiB,MAAsB;AACzD,SAAK,MAAM,QAAQ,OAAO,SAAS,IAAI;AAAA,EACzC;AAAA,EAEA,KAAK,OAAe,SAAiB,MAAsB;AACzD,SAAK,MAAM,QAAQ,OAAO,SAAS,IAAI;AAAA,EACzC;AAAA;AAAA,EAGA,MAAM,OAAe,SAAiB,MAAsB;AAC1D,SAAK,MAAM,SAAS,OAAO,SAAS,IAAI;AAAA,EAC1C;AAAA,EAEA,aAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,MAAM,OAAiB,OAAe,SAAiB,MAAsB;AACnF,QAAI,YAAY,KAAK,IAAI,YAAY,KAAK,SAAS,EAAG;AAEtD,QAAI,OAAO,KAAI,oBAAI,KAAK,GAAE,YAAY,CAAC,MAAM,KAAK,MAAM,KAAK,KAAK,OAAO;AAEzE,QAAI;AACF,YAAM,OAAO,WAAW,IAAI;AAC5B,UAAI,KAAM,SAAQ;AAAA,EAAK,IAAI;AAC3B,cAAQ;AACR,MAAAC,WAAe,eAAQ,KAAK,OAAO,CAAC;AACpC,WAAK,eAAe;AACpB,MAAG,oBAAe,KAAK,SAAS,MAAM,OAAO;AAAA,IAC/C,SAAS,KAAK;AAEZ,cAAQ,MAAM,oCAAoC,GAAG;AAAA,IACvD;AAAA,EACF;AAAA;AAAA,EAGQ,iBAAuB;AAC7B,QAAI,OAAO;AACX,QAAI;AACF,aAAU,cAAS,KAAK,OAAO,EAAE;AAAA,IACnC,QAAQ;AACN;AAAA,IACF;AACA,QAAI,OAAO,eAAgB;AAC3B,IAAG,gBAAW,KAAK,SAAS,KAAK,UAAU;AAAA,EAC7C;AACF;AAEA,SAAS,mBAA6B;AACpC,QAAM,OAAO,QAAQ,IAAI,qBAAqB,IAAI,YAAY;AAC9D,MAAI,QAAQ,WAAW,QAAQ,UAAU,QAAQ,UAAU,QAAQ,QAAS,QAAO;AACnF,SAAO;AACT;AAEA,SAAS,WAAW,MAAuB;AACzC,MAAI,SAAS,OAAW,QAAO;AAC/B,MAAI,gBAAgB,OAAO;AACzB,UAAM,OAAO,iBAAiB,IAAI;AAClC,UAAM,QAAQ,kBAAkB,IAAI;AACpC,WAAO,KAAK,UAAU,IAAI,KAAK,QAAQ;AAAA,EAAK,KAAK,KAAK;AAAA,EACxD;AACA,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI;AACF,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAASA,WAAU,KAAmB;AACpC,MAAI,CAAI,gBAAW,GAAG,EAAG,CAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAChE;AAEA,IAAI,WAA6B;AAG1B,SAAS,cAAc,SAAuB;AACnD,aAAW,IAAI,UAAU,OAAO;AAClC;AAMO,IAAM,YAAY;AAAA,EACvB,OAAO,CAAC,OAAe,SAAiB,SAAmB,UAAU,MAAM,OAAO,SAAS,IAAI;AAAA,EAC/F,MAAM,CAAC,OAAe,SAAiB,SAAmB,UAAU,KAAK,OAAO,SAAS,IAAI;AAAA,EAC7F,MAAM,CAAC,OAAe,SAAiB,SAAmB,UAAU,KAAK,OAAO,SAAS,IAAI;AAAA,EAC7F,OAAO,CAAC,OAAe,SAAiB,SAAmB,UAAU,MAAM,OAAO,SAAS,IAAI;AAAA,EAC/F,YAAY,MAAM,UAAU,WAAW,KAAK;AAC9C;;;ACjHA,YAAYC,UAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;AAGtB,IAAM,iBAAiB,CAAC,MAAM,YAAY,SAAS,QAAQ,QAAQ,KAAK;AACxE,IAAM,sBAAsB;AAa5B,SAAS,yBAAiC;AACxC,QAAM,OAAU,YAAQ;AACxB,UAAQ,QAAQ,UAAU;AAAA,IACxB,KAAK;AACH,aAAY,YAAK,MAAM,WAAW,uBAAuB,SAAS;AAAA,IACpE,KAAK,SAAS;AACZ,YAAM,UAAU,QAAQ,IAAI,WAAgB,YAAK,MAAM,WAAW,SAAS;AAC3E,aAAY,YAAK,SAAS,SAAS;AAAA,IACrC;AAAA,IACA;AACE,aAAY,YAAK,MAAM,WAAW,SAAS;AAAA,EAC/C;AACF;AAEA,SAAS,0BAA0B,kBAAyC;AAC1E,QAAM,aAAkB,YAAK,kBAAkB,mBAAmB;AAClE,MAAI,CAAI,gBAAW,UAAU,EAAG,QAAO,CAAC;AACxC,MAAI;AACF,WAAO,KAAK,MAAS,kBAAa,YAAY,OAAO,CAAC;AAAA,EACxD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAKA,SAAS,yBAAiC;AACxC,QAAM,mBAAmB,uBAAuB;AAChD,QAAM,gBAAgB,0BAA0B,gBAAgB;AAChE,MAAI,cAAc,WAAgB,kBAAW,cAAc,OAAO,GAAG;AACnE,WAAO,cAAc;AAAA,EACvB;AACA,SAAY,YAAK,kBAAkB,MAAM;AAC3C;AAEA,SAAS,aAAa,KAAsB;AAC1C,QAAM,QAAa,YAAK,KAAK,WAAW;AACxC,MAAI,CAAI,gBAAW,KAAK,EAAG,QAAO;AAClC,MAAI;AACF,WAAU,iBAAY,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AAAA,EAC5D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAASC,WAAU,SAAuB;AACxC,MAAI,CAAI,gBAAW,OAAO,GAAG;AAC3B,IAAG,eAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACF;AAEA,SAAS,aAAa,KAAa,MAAmD;AACpF,QAAM,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAAE;AACtC,MAAI,CAAI,gBAAW,GAAG,EAAG,QAAO;AAEhC,EAAAA,WAAU,IAAI;AACd,MAAI;AACJ,MAAI;AACF,cAAa,iBAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,EACvD,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,aAAW,SAAS,SAAS;AAC3B,UAAM,UAAe,YAAK,KAAK,MAAM,IAAI;AACzC,UAAM,WAAgB,YAAK,MAAM,MAAM,IAAI;AAC3C,QAAI;AACF,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,MAAM,aAAa,SAAS,QAAQ;AAC1C,cAAM,UAAU,IAAI;AACpB,cAAM,WAAW,IAAI;AAAA,MACvB,WAAW,CAAI,gBAAW,QAAQ,GAAG;AACnC,QAAG,kBAAa,SAAS,QAAQ;AACjC,cAAM;AAAA,MACR,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,QAAgB,SAAuB;AAChE,MAAI;AACF,IAAAA,WAAU,MAAM;AAChB,UAAM,UAAe,YAAK,QAAQ,SAAS;AAC3C,UAAM,MAAK,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE;AACjE,IAAG,oBAAe,SAAS,IAAI,EAAE,iBAAiB,OAAO;AAAA,GAAM,OAAO;AAAA,EACxE,QAAQ;AAAA,EAER;AACF;AAeO,SAAS,kBAA2B;AACzC,QAAM,mBAAmB,uBAAuB;AAChD,MAAI,CAAI,gBAAW,gBAAgB,EAAG,QAAO;AAG7C,SACK,gBAAgB,YAAK,kBAAkB,aAAa,CAAC,KACrD,gBAAgB,YAAK,kBAAkB,eAAe,CAAC,KACvD,gBAAgB,YAAK,kBAAkB,iBAAiB,CAAC,KACzD,gBAAgB,YAAK,kBAAkB,MAAM,CAAC;AAErD;AAYO,SAAS,kBAAkB,aAA8B;AAC9D,MAAO,gBAAW,WAAW,GAAG;AAC9B,QAAI;AACF,UAAO,iBAAY,WAAW,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,EAAG,QAAO;AAAA,IACzE,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,SAAO,CAAC,gBAAgB;AAC1B;AAaO,SAAS,4BAA4B,WAA4C;AACtF,QAAM,kBAAkB,uBAAuB;AAE/C,MAAI,CAAC,aAAa,eAAe,GAAG;AAClC,WAAO,EAAE,UAAU,OAAO,kBAAkB,gBAAgB,EAAE;AAAA,EAChE;AAEA,UAAQ,IAAI,0CAA0C,eAAe,EAAE;AAEvE,MAAI;AAEF,qBAAiB,QAAQ,iBAAiB,eAAe;AAGzD,QAAI,cAAc;AAClB,QAAI,eAAe;AACnB,UAAM,YAAsB,CAAC;AAE7B,eAAW,UAAU,gBAAgB;AACnC,YAAM,SAAc,YAAK,iBAAiB,MAAM;AAChD,YAAM,UAAe,YAAK,WAAW,MAAM;AAC3C,UAAI,CAAI,gBAAW,MAAM,EAAG;AAE5B,YAAM,EAAE,QAAQ,QAAQ,IAAI,aAAa,QAAQ,OAAO;AACxD,UAAI,SAAS,KAAK,UAAU,GAAG;AAC7B,kBAAU,KAAK,MAAM;AACrB,uBAAe;AACf,wBAAgB;AAChB,gBAAQ,IAAI,eAAe,MAAM,YAAY,MAAM,aAAa,OAAO,EAAE;AAAA,MAC3E;AAAA,IACF;AAGA,eAAW,UAAU,WAAW;AAC9B,YAAM,SAAc,YAAK,iBAAiB,MAAM;AAChD,UAAI;AACF,QAAG,YAAO,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MACpD,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,UAAe,YAAK,WAAW,MAAM;AAC3C,UAAM,UACJ,4CAA4C,eAAe,yCACpB,WAAW,kBAAkB,UAAU,KAAK,IAAI,KAAK,MAAM;AACpG,sBAAkB,SAAS,OAAO;AAClC,YAAQ,IAAI,eAAe,OAAO,EAAE;AAEpC,WAAO;AAAA,MACL,UAAU;AAAA,MACV,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,kBAAkB;AAAA,IACpB;AAAA,EACF,SAAS,OAAO;AACd,UAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACtE,YAAQ,MAAM,+CAA+C,QAAQ;AACrE,QAAI;AACF,wBAAuB,YAAK,WAAW,MAAM,GAAG,8BAA8B,QAAQ,EAAE;AAAA,IAC1F,QAAQ;AAAA,IAER;AACA,WAAO,EAAE,UAAU,OAAO,kBAAkB,MAAM,OAAO,SAAS;AAAA,EACpE;AACF;;;ACnPA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAYf,SAAS,aAAa,WAAmB,UAA0B;AACxE,SAAY,YAAK,UAAU,GAAG,SAAS,aAAa;AACtD;AAEA,SAAS,cAAc,WAAqC;AAC1D,MAAI;AACF,QAAI,CAAI,gBAAW,SAAS,EAAG,QAAO;AACtC,WAAO,KAAK,MAAS,kBAAa,WAAW,OAAO,CAAC;AAAA,EACvD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,eAAe,WAAmB,SAA0B;AACnE,MAAI;AACF,UAAM,MAAW,eAAQ,SAAS;AAClC,QAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,MAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AACA,IAAG,mBAAc,WAAW,KAAK,UAAU,OAAO,GAAG,OAAO;AAAA,EAC9D,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,SAAY,WAAmB,KAAa,UAA4B;AACtF,QAAM,YAAY,aAAa,WAAW,QAAQ;AAClD,QAAM,OAAO,cAAc,SAAS;AACpC,MAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,EAAG,QAAO;AAChC,SAAO,KAAK,GAAG,EAAE;AACnB;AAEO,SAAS,SAAY,WAAmB,KAAa,MAAS,UAAwB;AAC3F,QAAM,YAAY,aAAa,WAAW,QAAQ;AAClD,QAAM,OAAO,cAAc,SAAS,KAAK,CAAC;AAC1C,OAAK,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,EAAE;AACtD,iBAAe,WAAW,IAAI;AAChC;AAEO,SAAS,gBAAgB,WAAmB,UAAkB,KAAoB;AACvF,QAAM,YAAY,aAAa,WAAW,QAAQ;AAClD,MAAI;AACF,QAAI,CAAC,KAAK;AACR,UAAO,gBAAW,SAAS,EAAG,CAAG,gBAAW,SAAS;AAAA,IACvD,OAAO;AACL,YAAM,OAAO,cAAc,SAAS;AACpC,UAAI,QAAQ,KAAK,GAAG,GAAG;AACrB,eAAO,KAAK,GAAG;AACf,uBAAe,WAAW,IAAI;AAAA,MAChC;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,mBAAmB,WAAmB,UAAwB;AAC5E,QAAM,YAAY,aAAa,WAAW,QAAQ;AAClD,MAAI;AACF,QAAO,gBAAW,SAAS,EAAG,CAAG,gBAAW,SAAS;AAAA,EACvD,QAAQ;AAAA,EAER;AACF;AAIO,IAAM,qBAAqB;AAqBlC,SAAS,gBAAgB,IAA6B;AACpD,QAAM,MAAM,GAAG,QAAQ,kCAAkC,EAAE,IAAI;AAC/D,SAAO,IAAI,KAAK;AAClB;AAEO,SAAS,2BAA2B,IAAqB,WAAmB,UAAiC;AAClH,QAAM,WAAW,GAAG,QAAQ,6DAA6D,EAAE,IAAI;AAK/F,QAAM,gBACJ,GACG;AAAA,IACC;AAAA;AAAA;AAAA,EAGF,EACC,IAAI,EACP;AAEF,QAAM,eACJ,GAAG,QAAQ,qGAAiF,EAAE,IAAI,EAGlG;AAEF,QAAM,OAAsB;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,gBAAgB,SAAS;AAAA,IACzB,eAAe,SAAS;AAAA,IACxB,cAAc,gBAAgB,EAAE;AAAA,EAClC;AAEA,WAAS,WAAW,oBAAoB,MAAM,QAAQ;AACtD,4BAA0B,IAAI,WAAW,QAAQ;AAEjD,SAAO;AACT;AAaO,SAAS,0BAA0B,IAAqB,WAAmB,UAAiC;AACjH,QAAM,SAAS,SAAwB,WAAW,oBAAoB,QAAQ;AAC9E,MAAI,UAAU,OAAO,iBAAiB,UAAa,OAAO,iBAAiB,gBAAgB,EAAE,GAAG;AAC9F,WAAO;AAAA,EACT;AACA,SAAO,2BAA2B,IAAI,WAAW,QAAQ;AAC3D;AAIO,IAAM,oBAAoB;AAW1B,SAAS,0BAA0B,IAAqB,WAAmB,UAAgC;AAChH,QAAM,OAAO,GACV;AAAA,IACC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKF,EACC,IAAI;AAEP,QAAM,UAAsC,CAAC;AAC7C,aAAW,OAAO,MAAM;AACtB,YAAQ,IAAI,SAAS,IAAI,EAAE,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM;AAAA,EAC9D;AAEA,QAAM,OAAqB,EAAE,QAAQ;AACrC,WAAS,WAAW,mBAAmB,MAAM,QAAQ;AACrD,SAAO;AACT;;;AC3MA,OAAOC,UAAQ;AACf,OAAOC,YAAU;AAuCV,IAAM,4BAAN,cAAwC,MAAM;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EAEtB,YACE,MACA,SACAC,UAOA;AACA,UAAM,SAAS,EAAE,OAAOA,SAAQ,MAAM,CAAC;AACvC,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,cAAcA,SAAQ;AAC3B,SAAK,WAAWA,SAAQ;AACxB,SAAK,iBAAiBA,SAAQ;AAC9B,SAAK,oBAAoBA,SAAQ;AAAA,EACnC;AACF;AAEO,SAAS,6BAA6B,aAAsD;AACjG,QAAM,WAAW,YAAY,WAAW;AACxC,MAAI,CAACF,KAAG,WAAW,QAAQ,EAAG,QAAO;AAErC,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAMA,KAAG,aAAa,UAAU,OAAO,CAAC;AAAA,EACxD,SAAS,OAAO;AACd,UAAM,uBAAuB,aAAa,UAAU,mDAAmD,KAAK;AAAA,EAC9G;AAEA,SAAO,iCAAiC,QAAQ,aAAa,QAAQ;AACvE;AAEO,SAAS,wBACd,cACA,SACAE,WAA6C,CAAC,GACxC;AACN,QAAM,cAAc,aAAa,eAAe;AAChD,QAAM,OAAO,6BAA6B,WAAW;AACrD,MAAI,CAAC,KAAM;AAEX,QAAM,uBAAuB,8BAA8B,QAAQ,OAAO;AAC1E,MAAI,CAAC,sBAAsB;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,kGAAkG,QAAQ,OAAO;AAAA,MACjH;AAAA,QACE;AAAA,QACA,UAAU,YAAY,WAAW;AAAA,QACjC,gBAAgB,QAAQ;AAAA,QACxB,mBAAmB,KAAK;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAEA,MAAI,oBAAoB,sBAAsB,KAAK,iBAAiB,IAAI,GAAG;AACzE,UAAM,MAAMA,SAAQ,OAAO,QAAQ;AACnC,QAAI,IAAI,wCAAwC,KAAK;AACnD,YAAM,OAAOA,SAAQ,QAAQ,QAAQ;AACrC;AAAA,QACE;AAAA,UACE;AAAA,UACA,mBAAmB,QAAQ,IAAI,IAAI,QAAQ,OAAO,8BAA8B,KAAK,iBAAiB;AAAA,UACtG,mBAAmB,WAAW;AAAA,QAChC,EAAE,KAAK,GAAG;AAAA,MACZ;AACA;AAAA,IACF;AAEA,UAAM,IAAI;AAAA,MACR;AAAA,MACA,mDAAmD,KAAK,iBAAiB,iCAAiC,QAAQ,OAAO;AAAA,MACzH;AAAA,QACE;AAAA,QACA,UAAU,YAAY,WAAW;AAAA,QACjC,gBAAgB,QAAQ;AAAA,QACxB,mBAAmB,KAAK;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,8BACd,cACA,OAC0B;AAC1B,QAAM,cAAc,aAAa,eAAe;AAChD,QAAM,WAAW,YAAY,WAAW;AAExC,sBAAoB,MAAM,mBAAmB,aAAa,UAAU,4BAA4B;AAChG,QAAM,uBAAuB,+BAA+B,MAAM,QAAQ,SAAS,aAAa,QAAQ;AACxG,MAAI,CAAC,cAAc,MAAM,QAAQ,IAAI,GAAG;AACtC,UAAM,uBAAuB,aAAa,UAAU,uBAAuB;AAAA,EAC7E;AACA,MAAI,CAAC,iBAAiB,MAAM,MAAM,KAAK,CAAC,iBAAiB,MAAM,MAAM,GAAG;AACtE,UAAM,uBAAuB,aAAa,UAAU,uCAAuC;AAAA,EAC7F;AACA,MAAI,CAAC,OAAO,UAAU,MAAM,wBAAwB,KAAK,MAAM,2BAA2B,GAAG;AAC3F,UAAM,uBAAuB,aAAa,UAAU,mCAAmC;AAAA,EACzF;AAEA,QAAM,WAAW,6BAA6B,WAAW;AACzD,QAAM,oBACJ,YAAY,oBAAoB,SAAS,mBAAmB,MAAM,iBAAiB,IAAI,IACnF,SAAS,oBACT,MAAM;AACZ,QAAM,2BAA2B,KAAK,IAAI,UAAU,4BAA4B,GAAG,MAAM,wBAAwB;AACjH,QAAM,UAAU,aAAa,UAAU,WAAW,CAAC,GAAG,MAAM,MAAM;AAClE,QAAM,MAAM,MAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAE5D,QAAM,WAAqC;AAAA,IACzC,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,MACT,SAAS,MAAM,QAAQ;AAAA,MACvB,QAAQ,MAAM;AAAA,MACd,SAAS;AAAA,IACX;AAAA,IACA,WAAW,IAAI;AAAA,EACjB;AAEA,kBAAgB,aAAa,UAAU,QAAQ;AAC/C,SAAO;AACT;AAEA,SAAS,YAAY,aAA6B;AAChD,SAAOD,OAAK,KAAK,aAAa,oBAAoB;AACpD;AAEA,SAAS,iCACP,OACA,aACA,UAC0B;AAC1B,MAAI,CAACE,UAAS,KAAK,GAAG;AACpB,UAAM,uBAAuB,aAAa,UAAU,sDAAsD;AAAA,EAC5G;AAEA,MAAI,MAAM,kBAAkB,GAAG;AAC7B,UAAM,uBAAuB,aAAa,UAAU,uDAAuD;AAAA,EAC7G;AAEA,QAAM,oBAAoB,MAAM;AAChC,QAAM,2BAA2B,MAAM;AACvC,QAAM,UAAU,MAAM;AACtB,QAAM,YAAY,MAAM;AACxB,QAAM,YAAY,MAAM;AAExB,MACE,CAAC,eAAe,iBAAiB,KACjC,OAAO,6BAA6B,YACpC,CAAC,OAAO,UAAU,wBAAwB,KAC1C,2BAA2B,KAC3B,CAAC,MAAM,QAAQ,OAAO,KACtB,CAAC,QAAQ,MAAM,gBAAgB,KAC/B,CAACA,UAAS,SAAS,KACnB,CAAC,cAAc,UAAU,OAAO,KAChC,CAAC,iBAAiB,UAAU,MAAM,KAClC,CAAC,eAAe,UAAU,OAAO,KACjC,OAAO,cAAc,YACrB,CAAC,OAAO,UAAU,SAAS,KAC3B,YAAY,GACZ;AACA,UAAM,uBAAuB,aAAa,UAAU,mDAAmD;AAAA,EACzG;AAEA,SAAO;AAAA,IACL,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA,SAAS,CAAC,GAAG,OAAO;AAAA,IACpB,WAAW;AAAA,MACT,SAAS,UAAU;AAAA,MACnB,QAAQ,UAAU;AAAA,MAClB,SAAS,UAAU;AAAA,IACrB;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,uBACP,aACA,UACA,SACA,OAC2B;AAC3B,SAAO,IAAI,0BAA0B,uCAAuC,SAAS;AAAA,IACnF;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,SAAS,oBAAoB,SAAiB,aAAqB,UAAkB,SAAuB;AAC1G,MAAI,CAAC,eAAe,OAAO,GAAG;AAC5B,UAAM,uBAAuB,aAAa,UAAU,OAAO;AAAA,EAC7D;AACF;AAEA,SAAS,eAAe,SAAqC;AAC3D,SAAO,OAAO,YAAY,YAAY,kBAAkB,KAAK,OAAO;AACtE;AAEA,SAAS,+BAA+B,SAAiB,aAAqB,UAA0B;AACtG,QAAM,aAAa,8BAA8B,OAAO;AACxD,MAAI,CAAC,YAAY;AACf,UAAM,uBAAuB,aAAa,UAAU,0BAA0B;AAAA,EAChF;AACA,SAAO;AACT;AAGA,SAAS,8BAA8B,SAAgC;AACrE,QAAM,QAAQ,2DAA2D,KAAK,QAAQ,KAAK,CAAC;AAC5F,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,MAAM,CAAC,MAAM,WAAW,MAAM,CAAC,EAAG,QAAO;AAC7C,SAAO,MAAM,CAAC;AAChB;AAEA,SAAS,oBAAoB,MAAc,OAAuB;AAChE,QAAM,YAAY,KAAK,MAAM,GAAG,EAAE,IAAI,MAAM;AAC5C,QAAM,aAAa,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM;AAE9C,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG;AAC7B,QAAI,UAAU,CAAC,IAAI,WAAW,CAAC,EAAG,QAAO;AACzC,QAAI,UAAU,CAAC,IAAI,WAAW,CAAC,EAAG,QAAO;AAAA,EAC3C;AAEA,SAAO;AACT;AAEA,SAAS,aAAa,UAAoB,MAAwB;AAChE,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,UAAU,IAAI,CAAC,CAAC;AACzC;AAEA,SAAS,gBAAgB,aAAqB,UAAkB,MAAsC;AACpG,EAAAH,KAAG,UAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC7C,QAAM,WAAWC,OAAK,KAAK,aAAa,sBAAsB,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC,MAAM;AAC7F,MAAI;AACF,IAAAD,KAAG,cAAc,UAAU,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,GAAM,OAAO;AACxE,IAAAA,KAAG,WAAW,UAAU,QAAQ;AAAA,EAClC,SAAS,OAAO;AACd,QAAI;AACF,UAAIA,KAAG,WAAW,QAAQ,EAAG,CAAAA,KAAG,WAAW,QAAQ;AAAA,IACrD,QAAQ;AAAA,IAER;AACA,UAAM;AAAA,EACR;AACF;AAEA,SAASG,UAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,iBAAiB,OAAiC;AACzD,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC5D;AAEA,SAAS,cAAc,OAAsC;AAC3D,SAAO,UAAU,SAAS,UAAU,aAAa,UAAU,SAAS,UAAU;AAChF;;;AChSO,IAAM,+BAA2D;AAAA,EACtE;AAAA,IACE,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,0BAA0B;AAAA,IAC1B,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAEO,SAAS,6BAA6B,cAA4B,SAAgC;AACvG,aAAW,sBAAsB,8BAA8B;AAC7D,kCAA8B,cAAc;AAAA,MAC1C,mBAAmB,mBAAmB;AAAA,MACtC,0BAA0B,mBAAmB;AAAA,MAC7C,QAAQ,mBAAmB;AAAA,MAC3B;AAAA,MACA,QAAQ,mBAAmB;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;AAQO,SAAS,oBAAoB,MAAuC;AACzE,QAAMC,aAAY,CAAC,IAAqB,WAAmB,eAAgC;AACzF,UAAM,YAAY,GAAG,OAAO,cAAc,SAAS,GAAG;AACtD,WAAO,UAAU,KAAK,CAAC,QAAQ,IAAI,SAAS,UAAU;AAAA,EACxD;AAEA,QAAM,qBAAqB,CAAC,IAAqB,WAAmB,YAAoB,eAA6B;AACnH,QAAI,CAACA,WAAU,IAAI,WAAW,UAAU,GAAG;AACzC,SAAG,KAAK,eAAe,SAAS,eAAe,UAAU,IAAI,UAAU,EAAE;AAAA,IAC3E;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,MACb,IAAI,CAAC,OAAwB;AAC3B,cAAM,YAAY,GAAG,OAAO,kBAAkB;AAC9C,YAAI,CAAC,UAAU,KAAK,CAAC,QAAQ,IAAI,SAAS,UAAU,GAAG;AACrD,aAAG,KAAK,2CAA2C;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,MACb,IAAI,CAAC,OAAwB;AAC3B,cAAM,kBAAkB,GAAG,OAAO,oBAAoB;AACtD,YAAI,CAAC,gBAAgB,KAAK,CAAC,QAAQ,IAAI,SAAS,OAAO,GAAG;AACxD,aAAG,KAAK,uDAAuD;AAAA,QACjE;AAEA,cAAM,mBAAmB,GAAG,OAAO,qBAAqB;AAExD,YAAI,CAAC,iBAAiB,KAAK,CAAC,QAAQ,IAAI,SAAS,qBAAqB,GAAG;AACvE,aAAG,KAAK,sEAAsE;AAAA,QAChF;AAEA,YAAI,CAAC,iBAAiB,KAAK,CAAC,QAAQ,IAAI,SAAS,qBAAqB,GAAG;AACvE,aAAG,KAAK,sEAAsE;AAAA,QAChF;AAEA,YAAI;AACF,aAAG,KAAK,oFAAoF;AAAA,QAC9F,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,MACb,IAAI,CAAC,OAAwB;AAC3B,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SASP;AAED,YAAI;AACF,aAAG,KAAK,+EAA+E;AAAA,QACzF,QAAQ;AAAA,QAER;AAEA,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAMP;AAED,YAAI;AACF,aAAG,KAAK,+EAA+E;AAAA,QACzF,QAAQ;AAAA,QAER;AAEA,cAAM,YAAY,GAAG,OAAO,kBAAkB;AAC9C,YAAI,CAAC,UAAU,KAAK,CAAC,QAAQ,IAAI,SAAS,uBAAuB,GAAG;AAClE,aAAG,KAAK,2DAA2D;AAAA,QACrE;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,MACb,IAAI,CAAC,OAAwB;AAC3B,cAAMC,YAAW,GAAG,QAAQ,0EAA0E,EAAE,IAAI;AAC5G,YAAIA,UAAU;AAEd,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAMP;AAED,cAAM,WAAW,MAAM;AACvB,YAAI,CAAC,SAAU;AAEf,cAAMC,cAAa;AACnB,cAAM,YAAY,GAAG,QAAQ,uDAAuD;AAEpF,cAAM,WAAW,GACd,QAAQ,gGAAgG,EACxG,IAAI;AAEP,cAAM,QAAQ,UAAU,SAAS;AACjC,YAAI,SAAS;AACb,eAAO,SAAS,OAAO;AACrB,gBAAM,OAAO,GACV;AAAA,YACC;AAAA;AAAA;AAAA,UAGF,EACC,IAAIA,aAAY,MAAM;AAEzB,cAAI,KAAK,WAAW,EAAG;AAEvB,qBAAW,OAAO,MAAM;AACtB,kBAAM,SAAS,SAAS,IAAI,OAAO;AACnC,gBAAI,QAAQ;AACV,wBAAU,IAAI,IAAI,IAAI,MAAM;AAAA,YAC9B;AAAA,UACF;AAEA,oBAAUA;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,MACb,IAAI,CAAC,OAAwB;AAC3B,2BAAmB,IAAI,QAAQ,YAAY,MAAM;AACjD,2BAAmB,IAAI,QAAQ,gBAAgB,MAAM;AACrD,2BAAmB,IAAI,QAAQ,YAAY,MAAM;AACjD,2BAAmB,IAAI,QAAQ,yBAAyB,SAAS;AAEjE,cAAM,gBAAgBF,WAAU,IAAI,UAAU,MAAM;AACpD,cAAM,oBAAoBA,WAAU,IAAI,UAAU,UAAU;AAC5D,2BAAmB,IAAI,UAAU,gBAAgB,MAAM;AACvD,2BAAmB,IAAI,UAAU,kBAAkB,MAAM;AACzD,2BAAmB,IAAI,UAAU,WAAW,mBAAmB;AAC/D,2BAAmB,IAAI,UAAU,UAAU,MAAM;AACjD,2BAAmB,IAAI,UAAU,SAAS,mBAAmB;AAE7D,YAAI,eAAe;AACjB,aAAG,KAAK,2EAA2E;AAAA,QACrF;AACA,YAAI,mBAAmB;AACrB,aAAG,KAAK,mFAAmF;AAAA,QAC7F;AACA,WAAG,KAAK,oFAAoF;AAE5F,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAUP;AACD,2BAAmB,IAAI,uBAAuB,aAAa,6BAA6B;AACxF,2BAAmB,IAAI,WAAW,uBAAuB,MAAM;AAC/D,2BAAmB,IAAI,WAAW,yBAAyB,MAAM;AACjE,2BAAmB,IAAI,WAAW,uBAAuB,mBAAmB;AAC5E,2BAAmB,IAAI,WAAW,uBAAuB,mBAAmB;AAE5E,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SASP;AACD,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAMP;AAED,WAAG,KAAK,oFAAoF;AAC5F,WAAG,KAAK,gGAAgG;AACxG,WAAG,KAAK,+EAA+E;AACvF,WAAG,KAAK,+EAA+E;AAAA,MACzF;AAAA,IACF;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,MACb,IAAI,CAAC,OAAwB;AAC3B,cAAMC,YAAW,CAAC,cAA+B;AAC/C,gBAAM,MAAM,GAAG,QAAQ,6DAA6D,EAAE,IAAI,SAAS;AAGnG,iBAAO,CAAC,CAAC;AAAA,QACX;AAEA,WAAG,KAAK;AAAA;AAAA;AAAA,SAGP;AAED,cAAM,uBAAuBA,UAAS,cAAc;AACpD,cAAM,aAAaA,UAAS,SAAS;AAErC,YAAI,wBAAwB,CAAC,YAAY;AACvC,aAAG,KAAK,4CAA4C;AAAA,QACtD,WAAW,wBAAwB,YAAY;AAC7C,aAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,WAKP;AAAA,QACH;AAEA,YAAIA,UAAS,iBAAiB,KAAKD,WAAU,IAAI,mBAAmB,YAAY,GAAG;AACjF,aAAG,KAAK,oEAAoE;AAAA,QAC9E;AAEA,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAkBP;AAAA,MACH;AAAA,IACF;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,MACb,IAAI,CAAC,OAAwB;AAC3B,cAAM,SAAS,GACZ;AAAA,UACC;AAAA;AAAA;AAAA;AAAA;AAAA,QAKF,EACC,IAAI;AASP,YAAI,CAAC,UAAU,OAAO,iBAAiB,KAAK,OAAO,iBAAiB,KAAK,OAAO,eAAe,GAAG;AAChG;AAAA,QACF;AAEA,YAAI,OAAO,yBAAyB,OAAO,cAAc;AACvD,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAEA,cAAM,eAAe,GAClB;AAAA,UACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAWF,EACC,IAAI;AACP,aAAK,cAAc,SAAS,KAAK,GAAG;AAClC,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAEA,cAAM,qBAAqB,GACxB;AAAA,UACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQF,EACC,IAAI;AACP,aAAK,oBAAoB,SAAS,KAAK,GAAG;AACxC,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAEA,cAAM,SAAS,GACZ;AAAA,UACC;AAAA;AAAA;AAAA;AAAA,QAIF,EACC,IAAI;AACP,YAAI,OAAO,YAAY,OAAO,cAAc;AAC1C,gBAAM,IAAI,MAAM,sFAAsF;AAAA,QACxG;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,MACb,IAAI,CAAC,OAAwB;AAC3B,2BAAmB,IAAI,WAAW,uBAAuB,mBAAmB;AAE5E,WAAG,KAAK;AAAA;AAAA;AAAA;AAAA,SAIP;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;;;ACnZA,YAAYG,UAAQ;AACpB,YAAYC,YAAU;AAqBtB,SAAS,oBAAoB,WAA0C;AACrE,SAAO;AAAA,IACL;AAAA,IACA,GAAG;AAAA,EACL;AACF;AAeO,IAAM,kBAAN,MAAsB;AAAA,EAM3B,YACU,cACRC,WAAkC,CAAC,GACnC;AAFQ;AAGR,QAAI,CAACA,SAAQ,WAAW,CAACA,SAAQ,6BAA6B;AAC5D,YAAM,IAAI,MAAM,uFAAuF;AAAA,IACzG;AAEA,SAAK,gBAAgBA,UAAS;AAC9B,SAAK,gBAAgB,oBAAoBA,UAAS,aAAa;AAC/D,SAAK,UAAUA,UAAS,WAAW;AAAA,EACrC;AAAA,EAVU;AAAA,EANF,QAAQ,oBAAI,IAA6B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAkBR,KAAK,WAAmBA,UAA0D;AAChF,SAAK,iBAAiB;AAEtB,QAAI,KAAK,MAAM,IAAI,SAAS,GAAG;AAC7B,aAAO,KAAK,MAAM,IAAI,SAAS;AAAA,IACjC;AAEA,UAAM,SAAS,KAAK,UAAU,SAAS;AACvC,QAAI,CAAI,gBAAW,MAAM,EAAG,QAAO;AAEnC,SAAK,gBAAgB,MAAM;AAC3B,SAAK,iBAAiB;AAEtB,UAAM,UAAU,yBAAyB,QAAQ;AAAA,MAC/C,UAAUA,UAAS,YAAY;AAAA,MAC/B,eAAe,KAAK;AAAA,IACtB,CAAC;AACD,SAAK,MAAM,IAAI,WAAW,OAAO;AACjC,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,QAAsB;AAC5C,UAAM,kBAAkB,yBAAyB,QAAQ;AAAA,MACvD,UAAU;AAAA,MACV,eAAe,KAAK;AAAA,IACtB,CAAC;AAED,QAAI;AACF,UAAI,CAAC,gBAAgB,eAAe,EAAG;AACvC,WAAK,+BAA+B,iBAAiB,eAAe,CAAC;AACrE,UAAI,CAAC,eAAmB,iBAAiB,sBAAsB,EAAG;AAAA,IACpE,UAAE;AACA,sBAAgB,MAAM;AAAA,IACxB;AAEA,UAAM,UAAU,yBAAyB,QAAQ;AAAA,MAC/C,UAAU;AAAA,MACV,eAAe,KAAK;AAAA,IACtB,CAAC;AAED,QAAI;AACF,YAAM,aAAa,oBAAoB,KAAK,aAAa;AACzD,WAAK,cAAc,SAAS,UAAU;AAAA,IACxC,UAAE;AACA,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,WAA2C;AACtD,SAAK,iBAAiB;AAEtB,UAAM,WAAW,KAAK,MAAM,IAAI,SAAS;AACzC,QAAI,YAAY,CAAC,SAAS,SAAU,QAAO;AAE3C,QAAI,UAAU;AACZ,eAAS,MAAM;AACf,WAAK,MAAM,OAAO,SAAS;AAAA,IAC7B;AAEA,UAAM,SAAS,KAAK,UAAU,SAAS;AACvC,QAAI,CAAI,gBAAW,MAAM,EAAG,QAAO;AAEnC,UAAM,UAAU,yBAAyB,QAAQ;AAAA,MAC/C,UAAU;AAAA,MACV,eAAe,KAAK;AAAA,IACtB,CAAC;AAED,QAAI,eAAmB,SAAS,sBAAsB,GAAG;AACvD,YAAM,aAAa,oBAAoB,KAAK,aAAa;AACzD,WAAK,cAAc,SAAS,UAAU;AACtC,WAAK,iBAAiB;AAAA,IACxB,OAAO;AACL,WAAK,+BAA+B,iBAAiB,OAAO,CAAC;AAAA,IAC/D;AAEA,SAAK,MAAM,IAAI,WAAW,OAAO;AACjC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAyB;AAC7B,UAAM,UAAU,KAAK,MAAM,IAAI,SAAS;AACxC,QAAI,SAAS;AACX,cAAQ,MAAM;AACd,WAAK,MAAM,OAAO,SAAS;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,eAAW,CAAC,IAAI,OAAO,KAAK,KAAK,OAAO;AACtC,cAAQ,MAAM;AACd,WAAK,MAAM,OAAO,EAAE;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,iBAA2B;AACzB,SAAK,iBAAiB;AAEtB,UAAM,QAAQ,KAAK,aAAa,eAAe;AAC/C,QAAI,CAAI,gBAAW,KAAK,EAAG,QAAO,CAAC;AAEnC,WACG,iBAAY,KAAK,EACjB,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,EAC/B,IAAI,CAAC,MAAM,EAAE,QAAQ,OAAO,EAAE,CAAC,EAC/B,OAAO,CAAC,OAAO;AACd,YAAM,KAAK,KAAK,KAAK,EAAE;AACvB,UAAI,CAAC,GAAI,QAAO;AAChB,aAAO,gBAAgB,EAAE;AAAA,IAC3B,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,WAA2B;AACnC,WAAY,YAAK,KAAK,aAAa,eAAe,GAAG,GAAG,SAAS,KAAK;AAAA,EACxE;AAAA,EAEA,uBAAuB,WAAmBA,WAAyC,CAAC,GAAoB;AACtG,SAAK,iBAAiB;AAEtB,UAAM,SAAS,KAAK,UAAU,SAAS;AACvC,QAAI,CAACA,SAAQ,UAAU,CAAI,gBAAW,MAAM,GAAG;AAC7C,YAAM,IAAI,MAAM,+BAA+B,SAAS,EAAE;AAAA,IAC5D;AAEA,QAAI,CAACA,SAAQ,UAAU;AACrB,WAAK,MAAM,SAAS;AACpB,MAAG,eAAe,eAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,IACxD;AAEA,UAAM,UAAU,yBAAyB,QAAQ;AAAA,MAC/C,UAAUA,SAAQ,YAAY;AAAA,MAC9B,eAAe,KAAK;AAAA,IACtB,CAAC;AAED,QAAIA,SAAQ,sBAAsB;AAChC,cAAQ,KAAK,cAAc;AAAA,IAC7B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,2BAA2B,WAA4B;AACrD,SAAK,MAAM,SAAS;AAEpB,UAAM,SAAS,KAAK,UAAU,SAAS;AACvC,UAAM,UAAU,CAAC,IAAI,QAAQ,MAAM,EAAE,KAAK,CAAC,WAAc,gBAAW,SAAS,MAAM,CAAC;AACpF,eAAW,UAAU,CAAC,IAAI,QAAQ,MAAM,GAAG;AACzC,UAAI;AACF,cAAM,WAAW,SAAS;AAC1B,YAAO,gBAAW,QAAQ,EAAG,CAAG,gBAAW,QAAQ;AAAA,MACrD,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM,WAAW,KAAK,aAAa,YAAY;AAC/C,uBAAmB,WAAW,QAAQ;AACtC,uBAAmB,WAAgB,YAAK,UAAU,OAAO,CAAC;AAC1D,WAAO;AAAA,EACT;AAAA,EAEA,sCAA4C;AAC1C,QAAI,CAAC,KAAK,QAAS;AACnB,iCAA6B,KAAK,cAAc,KAAK,OAAO;AAAA,EAC9D;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,CAAC,KAAK,QAAS;AACnB,4BAAwB,KAAK,cAAc,KAAK,OAAO;AAAA,EACzD;AAAA,EAEQ,cAAc,SAA0B,YAA0D;AACxG,UAAM,gBAAgB,iBAAiB,OAAO;AAC9C,UAAM,WAAW,cAAkB,SAAS,UAAU;AACtD,QAAI,CAAC,YAAY,CAAC,KAAK,QAAS;AAEhC,UAAM,eAAe,iBAAiB,OAAO;AAC7C,QAAI,6BAA6B,eAAe,YAAY,GAAG;AAC7D,WAAK,oCAAoC;AAAA,IAC3C;AAAA,EACF;AAAA,EAEQ,+BAA+B,eAA6B;AAClE,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI,8BAA8B,aAAa,GAAG;AAChD,WAAK,oCAAoC;AAAA,IAC3C;AAAA,EACF;AACF;AAEA,SAAS,6BAA6B,eAAuB,cAA+B;AAC1F,SAAO,6BAA6B;AAAA,IAClC,CAAC,uBACC,gBAAgB,mBAAmB,oBAAoB,gBAAgB,mBAAmB;AAAA,EAC9F;AACF;AAEA,SAAS,8BAA8B,eAAgC;AACrE,SAAO,6BAA6B,KAAK,CAAC,uBAAuB,iBAAiB,mBAAmB,gBAAgB;AACvH;;;ACzOO,SAAS,6BACd,QACA,MACA,QACc;AACd,QAAM,KAAK,KAAK,aAAa,OAAO,SAAS;AAC7C,MAAI,CAAC,IAAI;AACP,WAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,aAAa,GAAG,eAAe,EAAE;AAAA,EAC3F;AAEA,QAAM,WAAW,KAAK;AAEtB,MAAI;AACF,WAAO,MAAM,KAAK,OAAO,WAAW;AAAA;AAAA,CAA4B;AAChE,WAAO,MAAM,mBAAkB,oBAAI,KAAK,GAAE,eAAe,CAAC;AAAA;AAAA,CAAM;AAEhE,WAAO,MAAM;AAAA;AAAA,CAA0B;AACvC,QAAI,OAAO,eAAe,aAAa;AACrC,UAAI,OAAO,YAAY,OAAO,SAAS,SAAS,GAAG;AACjD,eAAO,MAAM,eAAe,OAAO,SAAS,KAAK,IAAI,CAAC;AAAA,CAAI;AAAA,MAC5D;AACA,UAAI,OAAO,YAAY;AACrB,cAAM,QAAQ,IAAI,KAAK,OAAO,WAAW,UAAU,GAAI,EAAE,eAAe;AACxE,cAAM,MAAM,IAAI,KAAK,OAAO,WAAW,QAAQ,GAAI,EAAE,eAAe;AACpE,eAAO,MAAM,iBAAiB,KAAK,MAAM,GAAG;AAAA,CAAI;AAAA,MAClD;AACA,aAAO,MAAM,kBAAe,OAAO,eAAe,EAAE;AAAA,CAAa;AAAA,IACnE,OAAO;AACL,aAAO,MAAM;AAAA,CAA0B;AACvC,aAAO,MAAM,wBAAwB,OAAO,YAAY,UAAU,CAAC;AAAA,CAAI;AAAA,IACzE;AACA,WAAO,MAAM,IAAI;AAEjB,QAAI,gBAAgB;AACpB,QAAI,aAAa;AAEjB,QAAI,OAAO,eAAe,aAAa;AACrC,YAAM,SAAS,oBAAoB,IAAI,QAAQ,QAAQ,QAAQ;AAC/D,sBAAgB,OAAO;AACvB,mBAAa,OAAO;AAAA,IACtB,OAAO;AACL,YAAM,SAAS,kBAAkB,IAAI,QAAQ,QAAQ,QAAQ;AAC7D,sBAAgB,OAAO;AACvB,mBAAa,OAAO;AAAA,IACtB;AAEA,WAAO,IAAI;AAEX,eAAW;AAAA,MACT,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,SAAS,oBAAoB,UAAU;AAAA,IACzC,CAAC;AAED,WAAO,EAAE,SAAS,MAAM,aAAa,YAAY,cAAc;AAAA,EACjE,SAAS,OAAO;AACd,eAAW;AAAA,MACT,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,SAAS,kBAAkB,OAAO,KAAK,CAAC;AAAA,IAC1C,CAAC;AACD,WAAO,EAAE,SAAS,OAAO,OAAO,OAAO,KAAK,GAAG,aAAa,GAAG,eAAe,EAAE;AAAA,EAClF;AACF;AAIA,SAAS,oBACP,IACA,QACA,QACA,UAC+C;AAC/C,QAAM,cAAc,OAAO,eAAe;AAE1C,QAAM,iBAAiB;AAAA;AAAA;AAAA,MAGnB,OAAO,aAAa,8BAA8B,EAAE;AAAA;AAAA;AAGxD,QAAM,YAAuB,CAAC;AAC9B,MAAI,OAAO,YAAY;AACrB,cAAU,KAAK,OAAO,WAAW,SAAS,OAAO,WAAW,KAAK;AAAA,EACnE;AAEA,QAAM,aAAuB,CAAC;AAC9B,MAAI,WAAW;AACf,QAAM,OAAO,GAAG,QAAQ,cAAc;AACtC,QAAM,OAAO,KAAK,IAAI,GAAG,SAAS;AAOlC,aAAW,OAAO,MAAM;AACtB,QAAI,QAAQ;AACZ,QAAI,OAAO,YAAY,OAAO,SAAS,SAAS,GAAG;AACjD,YAAM,WAAW,IAAI,WAAW,IAAI,YAAY;AAChD,cAAQ,OAAO,SAAS,KAAK,CAAC,OAAO,QAAQ,SAAS,GAAG,YAAY,CAAC,CAAC;AAAA,IACzE;AACA,QAAI,SAAS,OAAO,aAAa,OAAO,UAAU,SAAS,GAAG;AAC5D,cAAQ,OAAO,UAAU,SAAS,IAAI,QAAQ;AAAA,IAChD;AACA,QAAI,MAAO,YAAW,KAAK,QAAQ;AACnC;AAAA,EACF;AAEA,QAAM,YAAY,WAAW;AAE7B,aAAW;AAAA,IACT,OAAO;AAAA,IACP,cAAc;AAAA,IACd,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,SAAS,oBAAoB,SAAS;AAAA,EACxC,CAAC;AAED,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,MAAM;AAAA;AAAA;AAAA,CAAiC;AAC9C,WAAO,IAAI;AACX,WAAO,EAAE,eAAe,GAAG,YAAY,EAAE;AAAA,EAC3C;AAEA,QAAM,gBAAgB;AACtB,QAAM,SAAsE,CAAC;AAC7E,aAAW,UAAU,YAAY;AAC/B,UAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,WAAW;AAC9C,UAAM,MAAM,KAAK,IAAI,gBAAgB,GAAG,SAAS,WAAW;AAC5D,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAI,SAAS,KAAK,MAAM,GAAG;AACzB,aAAK,MAAM,KAAK,IAAI,KAAK,KAAK,GAAG;AACjC,aAAK,WAAW,KAAK,MAAM;AAC3B;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,EAAE,OAAO,KAAK,YAAY,CAAC,MAAM,EAAE,CAAC;AAAA,EAClD;AAEA,QAAM,cAAc,OAAO;AAC3B,aAAW;AAAA,IACT,OAAO;AAAA,IACP,cAAc;AAAA,IACd;AAAA,IACA,YAAY;AAAA,IACZ,SAAS,aAAa,WAAW;AAAA,EACnC,CAAC;AAED,SAAO,MAAM;AAAA;AAAA,YAA8B,WAAW;AAAA,UAAa,SAAS;AAAA;AAAA,CAAM;AAClF,SAAO,MAAM;AAAA;AAAA,CAA6B;AAE1C,MAAI,gBAAgB;AACpB,MAAI,aAAa;AAEjB,aAAW,SAAS,QAAQ;AAC1B;AACA,eAAW;AAAA,MACT,OAAO;AAAA,MACP,cAAc;AAAA,MACd;AAAA,MACA,YAAY,KAAK,MAAM,MAAO,aAAa,KAAK,cAAe,EAAE;AAAA,MACjE,SAAS,mBAAmB,UAAU,IAAI,WAAW;AAAA,IACvD,CAAC;AAED,UAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMb,OAAO,aAAa,sCAAsC,EAAE;AAAA;AAAA;AAAA;AAIhE,UAAM,cAAyB,CAAC;AAChC,QAAI,OAAO,YAAY;AACrB,kBAAY,KAAK,OAAO,WAAW,SAAS,OAAO,WAAW,KAAK;AAAA,IACrE;AACA,gBAAY,KAAK,MAAM,MAAM,MAAM,QAAQ,GAAG,MAAM,KAAK;AAEzD,UAAM,WAAW,GAAG,QAAQ,QAAQ,EAAE,IAAI,GAAG,WAAW;AAOxD,QAAI,SAAS,WAAW,EAAG;AAE3B,UAAM,cAAc,IAAI,IAAI,MAAM,WAAW,IAAI,CAAC,QAAQ,MAAM,MAAM,KAAK,CAAC;AAC5E,UAAM,YAAY,IAAI,KAAK,SAAS,CAAC,EAAE,KAAK,GAAI,EAAE,eAAe;AACjE,UAAM,UAAU,IAAI,KAAK,SAAS,SAAS,SAAS,CAAC,EAAE,KAAK,GAAI,EAAE,eAAe;AACjF,WAAO,MAAM,aAAa,UAAU,KAAK,SAAS,MAAM,OAAO;AAAA;AAAA,CAAO;AAEtE,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,MAAM,SAAS,CAAC;AACtB,YAAM,OAAO,IAAI,KAAK,IAAI,KAAK,GAAI,EAAE,mBAAmB;AACxD,YAAM,UAAU,YAAY,IAAI,CAAC,IAAI,YAAO;AAC5C,aAAO,MAAM,GAAG,IAAI,IAAI,IAAI,UAAU,GAAG,OAAO,KAAK,IAAI,WAAW,oBAAoB;AAAA,CAAI;AAC5F;AAAA,IACF;AACA,WAAO,MAAM,IAAI;AAAA,EACnB;AAEA,SAAO,EAAE,eAAe,WAAW;AACrC;AAIA,SAAS,kBACP,IACA,QACA,QACA,UAC+C;AAC/C,MAAI,CAAC,OAAO,cAAc,OAAO,WAAW,WAAW,GAAG;AACxD,WAAO,MAAM;AAAA;AAAA;AAAA,CAA2C;AACxD,WAAO,IAAI;AACX,WAAO,EAAE,eAAe,GAAG,YAAY,EAAE;AAAA,EAC3C;AAEA,aAAW;AAAA,IACT,OAAO;AAAA,IACP,cAAc;AAAA,IACd,aAAa,OAAO,WAAW;AAAA,IAC/B,YAAY;AAAA,IACZ,SAAS,uBAAuB,OAAO,WAAW,MAAM;AAAA,EAC1D,CAAC;AAED,QAAM,cAAc;AAAA;AAAA;AAAA,mBAGH,OAAO,WAAW,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA;AAAA;AAG3D,QAAM,WAAW,GAAG,QAAQ,WAAW,EAAE,IAAI,GAAG,OAAO,UAAU;AAMjE,QAAM,cAAc,SAAS;AAC7B,SAAO,MAAM;AAAA;AAAA,YAA8B,WAAW;AAAA;AAAA,CAAM;AAC5D,SAAO,MAAM;AAAA;AAAA,CAA6B;AAE1C,QAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYpB,MAAI,gBAAgB;AACpB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B;AACA,eAAW;AAAA,MACT,OAAO;AAAA,MACP,cAAc;AAAA,MACd;AAAA,MACA,YAAY,KAAK,MAAM,MAAO,aAAa,KAAK,cAAe,EAAE;AAAA,MACjE,SAAS,qBAAqB,UAAU,IAAI,WAAW;AAAA,IACzD,CAAC;AAED,UAAM,WAAW,GAAG,QAAQ,WAAW,EAAE,IAAI,QAAQ,EAAE;AAOvD,QAAI,SAAS,WAAW,EAAG;AAE3B,UAAM,YAAY,IAAI,KAAK,QAAQ,UAAU,GAAI,EAAE,eAAe;AAClE,UAAM,UAAU,IAAI,KAAK,QAAQ,QAAQ,GAAI,EAAE,eAAe;AAC9D,WAAO,MAAM,aAAa,UAAU,KAAK,SAAS,MAAM,OAAO;AAAA;AAAA,CAAO;AAEtE,eAAW,OAAO,UAAU;AAC1B,YAAM,OAAO,IAAI,KAAK,IAAI,YAAY,GAAI,EAAE,mBAAmB;AAC/D,aAAO,MAAM,GAAG,IAAI,IAAI,IAAI,UAAU,KAAK,IAAI,WAAW,oBAAoB;AAAA,CAAI;AAClF;AAAA,IACF;AACA,WAAO,MAAM,IAAI;AAAA,EACnB;AAEA,SAAO,EAAE,eAAe,WAAW;AACrC;;;ACnUA,SAASC,eAAc,IAAqB,YAA+D;AACzG,QAAM,YAAY,CAAC,CAAC;AACpB,QAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMR,YAAY,sCAAsC,EAAE;AAAA;AAAA;AAGxD,QAAM,SAAoB,CAAC;AAC3B,MAAI,WAAW;AACb,WAAO,KAAK,WAAY,SAAS,WAAY,KAAK;AAAA,EACpD;AACA,SAAO,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AACtC;AAEA,SAAS,WAAW,IAAoB;AACtC,SAAO,IAAI,KAAK,KAAK,GAAI,EAAE,eAAe;AAC5C;AAEA,SAAS,gBAAgB,IAAoB;AAC3C,SAAO,IAAI,KAAK,KAAK,GAAI,EAAE,mBAAmB,SAAS,EAAE,MAAM,WAAW,QAAQ,WAAW,QAAQ,UAAU,CAAC;AAClH;AAEA,SAAS,YAAY,UAAwB,aAA6B;AACxE,QAAM,QAAkB,CAAC,GAAG,WAAW;AAAA,CAAI;AAC3C,MAAI,WAAW;AACf,aAAW,OAAO,UAAU;AAC1B,UAAM,OAAO,IAAI,KAAK,IAAI,KAAK,GAAI,EAAE,mBAAmB;AACxD,QAAI,SAAS,UAAU;AACrB,YAAM,KAAK;AAAA,MAAS,IAAI;AAAA,CAAQ;AAChC,iBAAW;AAAA,IACb;AACA,UAAM,KAAK,GAAG,gBAAgB,IAAI,EAAE,CAAC,IAAI,IAAI,UAAU,KAAK,IAAI,WAAW,YAAY,EAAE;AAAA,EAC3F;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,aAAa,UAAwB,aAA6B;AACzE,QAAM,OAAO;AAAA,IACX;AAAA,IACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,eAAe,SAAS;AAAA,IACxB,UAAU,SAAS,IAAI,CAAC,SAAS;AAAA,MAC/B,WAAW,IAAI;AAAA,MACf,MAAM,WAAW,IAAI,EAAE;AAAA,MACvB,QAAQ,IAAI;AAAA,MACZ,SAAS,IAAI,WAAW;AAAA,IAC1B,EAAE;AAAA,EACJ;AACA,SAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AACrC;AAEA,SAAS,iBAAiB,UAAwB,aAA6B;AAC7E,QAAM,QAAkB,CAAC,KAAK,WAAW;AAAA,GAAM,mBAAkB,oBAAI,KAAK,GAAE,eAAe,CAAC;AAAA,CAAI;AAChG,MAAI,WAAW;AACf,aAAW,OAAO,UAAU;AAC1B,UAAM,OAAO,IAAI,KAAK,IAAI,KAAK,GAAI,EAAE,mBAAmB;AACxD,QAAI,SAAS,UAAU;AACrB,YAAM,KAAK;AAAA,KAAQ,IAAI;AAAA,CAAI;AAC3B,iBAAW;AAAA,IACb;AACA,UAAM,KAAK,KAAK,gBAAgB,IAAI,EAAE,CAAC,IAAI,IAAI,UAAU,OAAO,IAAI,WAAW,cAAc,EAAE;AAAA,EACjG;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,IAAM,gBAAqE;AAAA,EACzE,KAAK,EAAE,KAAK,OAAO,MAAM,4BAA4B;AAAA,EACrD,MAAM,EAAE,KAAK,QAAQ,MAAM,kCAAkC;AAAA,EAC7D,UAAU,EAAE,KAAK,MAAM,MAAM,+BAA+B;AAC9D;AAEO,SAAS,iBACd,QACA,cACoB;AACpB,QAAM,KAAK,aAAa,OAAO,SAAS;AACxC,MAAI,CAAC,IAAI;AACP,WAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,eAAe,GAAG,SAAS,IAAI,UAAU,IAAI,UAAU,GAAG;AAAA,EACpH;AAEA,MAAI;AACF,UAAM,WAAWA,eAAc,IAAI,OAAO,UAAU;AACpD,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,eAAe;AAAA,QACf,SAAS;AAAA,QACT,UAAU;AAAA,QACV,UAAU;AAAA,MACZ;AAAA,IACF;AAEA,UAAM,EAAE,KAAK,KAAK,IAAI,cAAc,OAAO,MAAM;AACjD,QAAI;AACJ,YAAQ,OAAO,QAAQ;AAAA,MACrB,KAAK;AACH,kBAAU,YAAY,UAAU,OAAO,WAAW;AAClD;AAAA,MACF,KAAK;AACH,kBAAU,aAAa,UAAU,OAAO,WAAW;AACnD;AAAA,MACF,KAAK;AACH,kBAAU,iBAAiB,UAAU,OAAO,WAAW;AACvD;AAAA,IACJ;AAEA,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,WAAW,GAAG,OAAO,WAAW,WAAW,SAAS,IAAI,GAAG;AACjE,WAAO,EAAE,SAAS,MAAM,eAAe,SAAS,QAAQ,SAAS,UAAU,UAAU,KAAK;AAAA,EAC5F,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,OAAO,KAAK;AAAA,MACnB,eAAe;AAAA,MACf,SAAS;AAAA,MACT,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AACF;;;ACjJA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAQf,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCvB,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA,YAAY,oBAAI,IAAY;AAAA,EAC5B,eAAe;AAAA,EAEvB,YAAY,IAAqB;AAC/B,SAAK,KAAK;AACV,OAAG,KAAK,cAAc;AACtB,OAAG,KAAK,mBAAmB;AAAA,EAC7B;AAAA,EAEA,UAAU,MAOD;AACP,SAAK,GACF,QAAQ,qGAAqG,EAC7G,IAAI,KAAK,MAAM,KAAK,UAAU,KAAK,MAAM,KAAK,WAAW,MAAM,KAAK,eAAe,MAAM,KAAK,WAAW,IAAI;AAAA,EAClH;AAAA,EAEA,aAAa,SAA+B;AAC1C,UAAM,SAAS,KAAK,GAAG;AAAA,MACrB;AAAA,IACF;AACA,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,KAAK,UAAU,IAAI,EAAE,UAAU,GAAG;AACrC,aAAK,UAAU,IAAI,EAAE,UAAU;AAC/B,eAAO,IAAI,EAAE,YAAY,EAAE,eAAe,MAAM,EAAE,iBAAiB,MAAM,EAAE,UAAU,IAAI;AAAA,MAC3F;AAAA,IACF;AAAA,EACF;AAAA,EAEA,cAAc,UAAiC;AAC7C,UAAM,SAAS,KAAK,GAAG;AAAA,MACrB;AAAA;AAAA,IAEF;AACA,UAAM,eAAe,KAAK,GAAG;AAAA,MAC3B;AAAA,IACF;AACA,eAAW,OAAO,UAAU;AAC1B,UAAI,CAAC,KAAK,UAAU,IAAI,IAAI,gBAAgB,GAAG;AAC7C,aAAK,UAAU,IAAI,IAAI,gBAAgB;AACvC,qBAAa,IAAI,IAAI,kBAAkB,IAAI,qBAAqB,MAAM,IAAI,uBAAuB,MAAM,IAAI;AAAA,MAC7G;AACA,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,IAAI,qBAAqB;AAAA,QACzB,IAAI,uBAAuB;AAAA,QAC3B,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI,WAAW;AAAA,MACjB;AACA,WAAK;AAAA,IACP;AAAA,EACF;AAAA,EAEA,SAAwD;AACtD,SAAK,GAAG,KAAK,QAAQ;AACrB,UAAM,SAAS,EAAE,cAAc,KAAK,cAAc,aAAa,KAAK,UAAU,KAAK;AACnF,SAAK,GAAG,MAAM;AACd,WAAO;AAAA,EACT;AAAA,EAEA,QAAc;AACZ,QAAI;AACF,WAAK,GAAG,KAAK,UAAU;AAAA,IACzB,QAAQ;AAAA,IAER;AACA,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;AAYO,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EAER,YAAY,IAAqB;AAC/B,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,UAA6B;AAC3B,UAAM,MAAM,KAAK,GAAG,QAAQ,4BAA4B,EAAE,IAAI;AAG9D,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO;AAAA,MACL,MAAM,IAAI;AAAA,MACV,UAAU,IAAI;AAAA,MACd,MAAM,IAAI;AAAA,MACV,SAAS,IAAI,YAAY;AAAA,MACzB,aAAa,IAAI,gBAAgB;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,aAA6B;AAC3B,UAAM,OAAO,KAAK,GAAG,QAAQ,sBAAsB,EAAE,IAAI;AAMzD,WAAO,KAAK,IAAI,CAAC,OAAO;AAAA,MACtB,YAAY,EAAE;AAAA,MACd,aAAa,EAAE,gBAAgB,EAAE;AAAA,MACjC,eAAe,EAAE,kBAAkB;AAAA,MACnC,QAAQ,EAAE,UAAU;AAAA,IACtB,EAAE;AAAA,EACJ;AAAA,EAEA,kBAA0B;AACxB,UAAM,MAAM,KAAK,GAAG,QAAQ,uCAAuC,EAAE,IAAI;AACzE,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,eAAe,WAAmB,UAAqD;AACrF,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG5B;AAED,QAAI,SAAS;AACb,WAAO,MAAM;AACX,YAAM,OAAO,KAAK,IAAI,WAAW,MAAM;AASvC,UAAI,KAAK,WAAW,EAAG;AAEvB,YAAM,WAA4B,KAAK,IAAI,CAAC,OAAO;AAAA,QACjD,kBAAkB,EAAE;AAAA,QACpB,mBAAmB,EAAE,uBAAuB,EAAE;AAAA,QAC9C,qBAAqB,EAAE,yBAAyB;AAAA,QAChD,WAAW,EAAE;AAAA,QACb,MAAM,EAAE;AAAA,QACR,SAAS,EAAE;AAAA,MACb,EAAE;AAEF,eAAS,QAAQ;AACjB,gBAAU;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,iBAAkC;AAChC,UAAM,OAAO,KAAK,GACf;AAAA,MACC;AAAA;AAAA,IAEF,EACC,IAAI;AASP,WAAO,KAAK,IAAI,CAAC,OAAO;AAAA,MACtB,kBAAkB,EAAE;AAAA,MACpB,mBAAmB,EAAE,uBAAuB,EAAE;AAAA,MAC9C,qBAAqB,EAAE,yBAAyB;AAAA,MAChD,WAAW,EAAE;AAAA,MACb,MAAM,EAAE;AAAA,MACR,SAAS,EAAE;AAAA,IACb,EAAE;AAAA,EACJ;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AAAA;AAAA,EAGA,eAAiC;AAC/B,WAAO,2BAA2B,IAAI;AAAA,EACxC;AACF;AAEA,SAAS,2BAA2B,QAAwC;AAC1E,SAAO;AAAA,IACL,UAAmC;AACjC,YAAM,OAAO,OAAO,QAAQ;AAC5B,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX,UAAU,KAAK;AAAA,QACf,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,aAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,IACA,aAA6B;AAC3B,aAAO,OAAO,WAAW,EAAE,IAAI,CAAC,OAAO;AAAA,QACrC,YAAY,EAAE;AAAA,QACd,aAAa,EAAE;AAAA,QACf,eAAe,EAAE;AAAA,QACjB,QAAQ,EAAE;AAAA,MACZ,EAAE;AAAA,IACJ;AAAA,IACA,kBAA0B;AACxB,aAAO,OAAO,gBAAgB;AAAA,IAChC;AAAA,IACA,eAAe,WAAmB,UAAqD;AACrF,aAAO,eAAe,WAAW,CAAC,aAAa;AAC7C;AAAA,UACE,SAAS,IAAI,CAAC,SAAS;AAAA,YACrB,kBAAkB,IAAI;AAAA,YACtB,mBAAmB,IAAI;AAAA,YACvB,qBAAqB,IAAI;AAAA,YACzB,WAAW,IAAI;AAAA,YACf,MAAM,IAAI;AAAA,YACV,SAAS,IAAI;AAAA,UACf,EAAE;AAAA,QACJ;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAsBO,SAAS,oBAAoB,IAAsC;AACxE,QAAM,OAAO,GAAG,QAAQ,oBAAoB,EAAE,IAAI;AAIlD,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,0BAA0B;AAErD,QAAM,UAAU,GAAG,QAAQ,sEAAsE,EAAE,IAAI;AAOvG,QAAM,WAAW,GACd;AAAA,IACC;AAAA;AAAA;AAAA,EAGF,EACC,IAAI;AASP,SAAO;AAAA,IACL,SAAS;AAAA,MACP,SAAS;AAAA,MACT,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,MACxC,WAAW;AAAA,MACX,aAAa,0BAA0B,KAAK,IAAI;AAAA,IAClD;AAAA,IACA,MAAM;AAAA,MACJ,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,aAAa,KAAK;AAAA,IACpB;AAAA,IACA,SAAS,QAAQ,IAAI,CAAC,OAAO;AAAA,MAC3B,YAAY,EAAE;AAAA,MACd,aAAa,EAAE,gBAAgB,EAAE;AAAA,MACjC,eAAe,EAAE,kBAAkB;AAAA,MACnC,QAAQ,EAAE;AAAA,IACZ,EAAE;AAAA,IACF,UAAU,SAAS,IAAI,CAAC,SAAS;AAAA,MAC/B,QAAQ,IAAI;AAAA,MACZ,aAAa,IAAI,eAAe,IAAI;AAAA,MACpC,eAAe,IAAI,iBAAiB;AAAA,MACpC,WAAW,IAAI;AAAA,MACf,MAAM,IAAI;AAAA,MACV,SAAS,IAAI,WAAW;AAAA,IAC1B,EAAE;AAAA,EACJ;AACF;AAIO,SAAS,mBAAmB,QAAsB;AACvD,MAAI;AACF,QAAO,gBAAW,MAAM,EAAG,CAAG,gBAAW,MAAM;AAC/C,UAAM,UAAU,SAAS;AACzB,UAAM,UAAU,SAAS;AACzB,QAAO,gBAAW,OAAO,EAAG,CAAG,gBAAW,OAAO;AACjD,QAAO,gBAAW,OAAO,EAAG,CAAG,gBAAW,OAAO;AAAA,EACnD,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,qBAAqB,SAAuB;AAC1D,MAAI;AACF,QAAI,CAAI,gBAAW,OAAO,EAAG;AAC7B,UAAM,QAAW,iBAAY,OAAO;AACpC,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,WAAW,QAAQ,KAAK,KAAK,SAAS,KAAK,GAAG;AACrD,2BAAwB,YAAK,SAAS,IAAI,CAAC;AAAA,MAC7C;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;;;AC5YA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAAC;AAAA,EAAA,4BAAAC;AAAA;AAgDA,SAAS,aACP,IACA,IACA,QACAC,UAC2B;AAC3B,QAAM,OAAO,eAAe,EAAE;AAC9B,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,WAAWA,UAAS,kBAAkBA,SAAQ,gBAAgB,IAAI,EAAE,IAAI;AAC9E,QAAM,OAAO,WAAW,iBAAiB,MAAM,UAAU,gBAAgB,EAAE,CAAC,IAAI,eAAe,EAAE;AACjG,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,eAA8B;AAClC,MAAI,KAAK,SAAS,WAAW;AAC3B,mBAAe,2BAA2B,IAAI,KAAK,MAAM,KAAK,OAAO;AAAA,EACvE;AAEA,MAAI,MAA0B,EAAE,GAAG,MAAM,IAAI,QAAQ,cAAc,qBAAqB,EAAE;AAC1F,MAAIA,UAAS,eAAe;AAC1B,UAAMA,SAAQ,cAAc,GAAG;AAAA,EACjC;AACA,SAAO;AACT;AAQO,SAAS,qBACd,SACAA,UACsB;AACtB,QAAM,aAAa,QAAQ,eAAe;AAC1C,QAAM,WAAiC,CAAC;AAExC,aAAW,MAAM,YAAY;AAC3B,UAAM,KAAK,QAAQ,aAAa,EAAE;AAClC,QAAI,CAAC,GAAI;AACT,QAAI,CAAC,gBAAgB,EAAE,EAAG;AAE1B,UAAM,MAAM,aAAa,IAAI,IAAI,QAAQ,UAAU,EAAE,GAAGA,QAAO;AAC/D,QAAI,IAAK,UAAS,KAAK,GAAG;AAAA,EAC5B;AAEA,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU;AAC5D;AAKO,SAAS,mBACd,SACA,WACAA,UAC2B;AAC3B,QAAM,KAAK,QAAQ,aAAa,SAAS;AACzC,MAAI,CAAC,GAAI,QAAO;AAChB,SAAO,aAAa,IAAI,WAAW,QAAQ,UAAU,SAAS,GAAGA,QAAO;AAC1E;AAKO,SAASC,eAAc,SAAgC,WAAmB,MAAoB;AACnG,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,gBAAkB,IAAI,IAAI;AAC5B;AAKO,SAASC,sBAAqB,SAAgC,WAAmB,SAA8B;AACpH,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,uBAAyB,IAAI,OAAO;AACtC;AAMO,SAAS,cAAc,SAAgC,WAA4B;AACxF,SAAO,QAAQ,kBAAkB,SAAS;AAC5C;;;ACrIA;AAAA;AAAA,sBAAAC;AAAA,EAAA,4BAAAC;AAAA,EAAA,kBAAAC;AAAA,EAAA,2BAAAC;AAAA,EAAA,oBAAAC;AAAA,EAAA,2BAAAC;AAAA;AAgCO,SAASC,YAAW,SAAgC,WAAwC;AACjG,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,sBAA0B,EAAE;AACrC;AAKO,SAASC,qBACd,SACA,WACA,QACqB;AACrB,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,QAAM,SAAS,oBAAwB,IAAI,MAAM;AACjD,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,OAAO,OAAO;AAAA,IACd,MAAM,OAAO;AAAA,IACb,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO;AAAA,EACrB;AACF;AAMO,SAASC,qBACd,SACA,WACA,UACA,SACS;AACT,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,oBAAwB,IAAI,UAAU,OAAO;AACtD;AAUO,SAASC,cACd,SACA,WACA,WACA,WACS;AACT,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,aAAiB,IAAI,WAAW,SAAS;AAClD;AAMO,SAASC,cAAa,SAAgC,WAAmB,UAA2B;AACzG,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,aAAiB,IAAI,QAAQ;AACtC;AAMO,SAASC,sBAAqB,SAAgC,WAAmB,UAAkB;AACxG,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,qBAAyB,IAAI,QAAQ;AAC9C;;;ACxGA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeO,SAAS,cAAc,SAAgC,WAAmB,eAAuB,MAAc;AACpH,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,qBAAyB,IAAI,YAAY;AAClD;AAEO,SAAS,yBACd,SACA,WACA,eAAuB,MACf;AACR,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,gCAAoC,IAAI,YAAY;AAC7D;AAEO,SAAS,WAAW,SAAgC,WAAyB;AAClF,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,oBAAsB,EAAE;AAC1B;AAEO,SAAS,aAAa,SAAgC,WAA4B;AACvF,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,YAAY,EAAE;AACvB;AAEO,SAAS,UACd,SACA,WACA,UACA,OACA,QACA;AACA,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,YAAY,IAAI,UAAU,OAAO,MAAM;AAChD;AAEO,SAAS,WAAW,SAAgC,WAAmB;AAC5E,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,SAAO,gBAAgB,EAAE;AAC3B;AAQO,SAAS,iBAAiB,SAA0D;AACzF,QAAM,MAAM,QAAQ,eAAe;AACnC,SAAO,IAAI,IAAI,CAAC,cAAc;AAC5B,QAAI;AACF,YAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,YAAM,QAAQ,qBAAqB,EAAE;AACrC,aAAO,EAAE,WAAW,UAAU,MAAM,UAAU,cAAc,MAAM,aAAa;AAAA,IACjF,QAAQ;AACN,aAAO,EAAE,WAAW,UAAU,OAAO,cAAc,EAAE;AAAA,IACvD;AAAA,EACF,CAAC;AACH;;;ACxEA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BA,SAAS,iBAAiB,IAAqB,WAAsB,MAAuC;AAC1G,QAAM,UAAU,KAAK,aAAa,SAAS;AAC3C,SAAO;AAAA,IACL,aAAa,WAAW,QAAQ,KAAK;AACnC,YAAM,OAAO,mBAAmB,IAAI,WAAW,KAAK;AACpD,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO,KAAK,SAAS,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,SAAS,EAAE,QAAQ,EAAE;AAAA,IACpF;AAAA,IACA,YAAY,WAAW,SAAS;AAC9B,yBAAmB,IAAI,WAAW,OAAO;AAAA,IAC3C;AAAA,IACA,WAAW,WAAW;AACpB,aAAO,kBAAkB,IAAI,SAAS;AAAA,IACxC;AAAA,IACA,MAAM,YAAY,cAAc,YAAYC,UAAS;AACnD,YAAM,SAAS,MAAMC;AAAA,QACnB;AAAA,QACA;AAAA,UACE;AAAA,UACA,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,WAAW,CAAC,GAAG,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,QACnG;AAAA,QACA,EAAE,QAAQ,UAAU,QAAQ,WAAWD,UAAS,WAAW,aAAaA,UAAS,YAAY;AAAA,MAC/F;AACA,aAAO,OAAO,QACX,OAAO,CAAC,SAAgC,KAAK,SAAS,MAAM,EAC5D,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE;AAAA,IACZ;AAAA,IACA,GAAG,CAAC,QAAgB;AAAA,EACtB;AACF;AAEA,eAAsB,gBACpB,SACA,WACA,WACA,aACAA,UACA;AACA,QAAM,YAAY,YAAY,aAAa;AAC3C,MAAI,CAAC,WAAW;AACd,WAAO,EAAE,SAAS,OAAgB,OAAO,iCAAiC;AAAA,EAC5E;AAEA,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,QAAM,OAAO,iBAAiB,IAAI,WAAW,WAAW;AACxD,SAAO,uBAAuB,MAAM,WAAWA,QAAO;AACxD;AAEA,eAAsB,qBACpB,SACA,WACA,aACAA,UACA;AACA,QAAM,YAAY,YAAY,aAAa;AAC3C,MAAI,CAAC,WAAW;AACd,WAAO,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,iCAAiC;AAAA,EACpF;AAEA,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,QAAM,eAAe,mBAAmB,EAAE;AAC1C,QAAM,OAAO,iBAAiB,IAAI,WAAW,WAAW;AAExD,MAAI,UAAU;AACd,MAAI,SAAS;AAEb,aAAW,MAAM,cAAc;AAC7B,UAAM,SAAS,MAAM,uBAAuB,MAAM,GAAG,IAAIA,QAAO;AAChE,QAAI,OAAO,QAAS;AAAA,QACf;AAAA,EACP;AAEA,SAAO,EAAE,SAAS,QAAQ,OAAO,aAAa,OAAO;AACvD;AAEO,SAAS,iBACd,SACA,WACA,YAC2D;AAC3D,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,QAAM,OAAgD;AAAA,IACpD,aAAa,WAAW,QAAQ,KAAK;AACnC,YAAM,OAAO,mBAAmB,IAAI,WAAW,KAAK;AACpD,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO,KAAK,SAAS,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,SAAS,EAAE,QAAQ,EAAE;AAAA,IACpF;AAAA,IACA,GAAG,CAAC,QAAgB;AAAA,EACtB;AACA,QAAM,YAAY,gCAAgC,MAAM,UAAU;AAClE,QAAM,SAAoE,CAAC;AAC3E,aAAW,CAAC,IAAI,IAAI,KAAK,WAAW;AAClC,WAAO,EAAE,IAAI;AAAA,EACf;AACA,SAAO;AACT;;;AC5HA;AAAA;AAAA;AAAA;AAAA;AAUO,SAAS,eACd,SACA,QAC2C;AAC3C,QAAM,SAAmB,CAAC;AAC1B,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,MACE,aAAa,WAAmB;AAC9B,eAAO,QAAQ,aAAa,SAAS;AAAA,MACvC;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM,OAAe;AACnB,eAAO,KAAK,KAAK;AAAA,MACnB;AAAA,MACA,MAAM;AAAA,MAEN;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,SAAS,OAAO,KAAK,EAAE,EAAE;AAC5C;AAEO,SAAS,gBACd,SACA,QAMoB;AACpB,SAAO,iBAAiB,QAAQ,CAAC,cAAc,QAAQ,aAAa,SAAS,CAAC;AAChF;;;AC5CA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BA,SAAS,kBAAkB,QAAmC;AAC5D,SAAO,OAAO,iBAAiB,OAAO,eAAe,OAAO;AAC9D;AAEA,SAAS,YAAY,aAAiC,WAA4B;AAChF,SAAO,YAAY,KAAK,EAAE,+BAA+B,SAAS,SAAS;AAC7E;AAOO,SAAS,qBACd,SACA,aACA,WACyB;AACzB,QAAM,YAAY,YAAY,aAAa,SAAS;AAEpD,QAAM,KAAK,QAAQ,aAAa,SAAS;AACzC,MAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,GAAG;AAC/B,WAAO,EAAE,SAAS,OAAO,QAAQ,mBAAmB,UAAU;AAAA,EAChE;AACA,QAAM,OAAO,eAAe,EAAE;AAC9B,MAAI,CAAC,MAAM;AACT,WAAO,EAAE,SAAS,OAAO,QAAQ,mBAAmB,UAAU;AAAA,EAChE;AACA,MAAI,KAAK,SAAS;AAChB,WAAO,EAAE,SAAS,OAAO,SAAS,KAAK,SAAS,QAAQ,eAAe,UAAU;AAAA,EACnF;AAEA,QAAM,UAAU,YAAY,KAAK,EAAE,wBAAwB,KAAK,QAAQ;AACxE,MAAI,CAAC,SAAS;AACZ,WAAO,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU;AAAA,EAC3D;AAEA,QAAM,SAAS,kBAAkB,KAAK,UAAU,SAAS,sBAAsB,EAAE,CAAC;AAClF,MAAI,OAAO,SAAS,WAAW,OAAO,SAAS,QAAQ;AACrD,UAAM,WAAW,QAAQ,eAAe,SAAS;AACjD,yBAAyB,UAAU,OAAO,UAAU;AACpD,WAAO,EAAE,SAAS,MAAM,SAAS,OAAO,YAAY,UAAU;AAAA,EAChE;AACA,SAAO,EAAE,SAAS,OAAO,QAAQ,OAAO,SAAS,cAAc,cAAc,YAAY,UAAU;AACrG;AAUO,SAAS,wBACd,SACA,aACA,WACA,iBAC+B;AAC/B,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,QAAM,OAAO,eAAe,EAAE;AAC9B,MAAI,CAAC,MAAM;AACT,UAAM,OAAO,OAAO,IAAI,MAAM,wBAAwB,SAAS,EAAE,GAAG,EAAE,YAAY,IAAI,CAAC;AAAA,EACzF;AAEA,QAAM,UAAU,sBAAsB,EAAE;AACxC,QAAM,WAAW,QAAQ,KAAK,CAAC,MAAM,EAAE,eAAe,eAAe;AACrE,MAAI,CAAC,UAAU;AACb,UAAM,OAAO,OAAO,IAAI,MAAM,gCAAgC,eAAe,EAAE,GAAG,EAAE,YAAY,IAAI,CAAC;AAAA,EACvG;AAEA,uBAAyB,IAAI,eAAe;AAE5C,QAAM,QAAQ,YAAY,KAAK;AAC/B,QAAM,WAAW,MAAM,wBAAwB,KAAK,QAAQ;AAC5D,QAAM,YAAY,YAAY,SAAS,eAAe,kBAAkB,SAAS,iBAAiB,CAAC;AACnG,QAAM,UAAwB;AAAA,IAC5B,YAAY;AAAA,IACZ,aAAa,kBAAkB,QAAQ;AAAA,IACvC,gBAAgB,oBAAoB,WAAW,EAAE,GAAG,UAAU,aAAa,kBAAkB,QAAQ,EAAE,CAAC;AAAA,IACxG,WAAW,oBAAoB,KAAK,QAAQ,IAAI,SAAS;AAAA,IACzD,WAAW,KAAK,IAAI;AAAA,EACtB;AAEA,QAAM,kBAAkB,4BAA4B,SAAS,KAAK,UAAU,SAAS,SAAS;AAC9F,QAAM,oBAAoB,gBAAgB,IAAI,CAAC,MAAM,EAAE,EAAE;AACzD,QAAM,yBAAiD,CAAC;AACxD,aAAW,KAAK,iBAAiB;AAC/B,2BAAuB,EAAE,EAAE,IAAI,EAAE;AAAA,EACnC;AAEA,QAAM,oBAAoB,oBAAI,IAAI,CAAC,WAAW,GAAG,iBAAiB,CAAC;AACnE,cAAY,KAAK;AAAA,IACf,yBAAyB,EAAE,GAAG,MAAM,yBAAyB,CAAC,KAAK,QAAQ,GAAG,QAAQ;AAAA,IACtF,gCAAgC,MAAM,+BAA+B,OAAO,CAAC,OAAO,CAAC,kBAAkB,IAAI,EAAE,CAAC;AAAA,EAChH,CAAC;AAED,SAAO,EAAE,WAAW,UAAU,KAAK,UAAU,SAAS,iBAAiB,mBAAmB,uBAAuB;AACnH;AAQA,SAAS,4BACP,SACAE,WACA,SACA,kBACwC;AACxC,QAAM,UAAkD,CAAC;AACzD,aAAW,MAAM,QAAQ,eAAe,GAAG;AACzC,QAAI,OAAO,iBAAkB;AAC7B,QAAI;AACF,YAAM,KAAK,QAAQ,aAAa,EAAE;AAClC,UAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,EAAG;AACjC,YAAM,OAAO,eAAe,EAAE;AAC9B,UAAI,CAAC,QAAQ,KAAK,aAAaA,aAAY,KAAK,QAAS;AAEzD,YAAM,SAAS,kBAAkBA,WAAU,SAAS,sBAAsB,EAAE,CAAC;AAC7E,UAAI,OAAO,SAAS,WAAW,OAAO,SAAS,QAAQ;AACrD,6BAAyB,QAAQ,eAAe,EAAE,GAAG,OAAO,UAAU;AACtE,gBAAQ,KAAK,EAAE,IAAI,SAAS,OAAO,WAAW,CAAC;AAAA,MACjD;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,qDAAqD,EAAE,KAAK,GAAG;AAAA,IAC9E;AAAA,EACF;AACA,SAAO;AACT;AAMO,SAAS,mBAAmB,aAAiC,WAAyB;AAC3F,QAAM,QAAQ,YAAY,KAAK;AAC/B,MAAI,MAAM,+BAA+B,SAAS,SAAS,EAAG;AAC9D,cAAY,KAAK;AAAA,IACf,gCAAgC,CAAC,GAAG,MAAM,gCAAgC,SAAS;AAAA,EACrF,CAAC;AACH;AAMO,SAAS,kBAAkB,SAAgC,WAAyB;AACzF,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,uBAAyB,IAAI,IAAI;AACnC;;;ACzKA,OAAOC,UAAQ;AACf,OAAOC,YAAU;AAmCV,IAAM,gCAAgC;AAGtC,IAAM,6BAA6B;AACnC,IAAM,yBAAyB;AAC/B,IAAM,yBAAyB;AAE/B,IAAM,8BAA8B;AAEpC,SAAS,sBAAsB,OAAmC;AACvE,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACjE,SAAO,KAAK,IAAI,wBAAwB,KAAK,IAAI,wBAAwB,KAAK,MAAM,KAAK,CAAC,CAAC;AAC7F;AAMO,SAAS,6BAAkD;AAChE,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO,EAAE,SAAS,GAAG;AAAA,IACrB,KAAK;AAAA,IACL,kBAAkB;AAAA,EACpB;AACF;AAKO,SAAS,eAAe,QAAqC;AAClE,MAAI,OAAO,SAAS,SAAS,OAAO,KAAK;AACvC,WAAO,OAAO,OAAO,IAAI,OAAO,IAAI,OAAO,IAAI,KAAK;AAAA,EACtD;AACA,SAAO,OAAO,MAAM;AACtB;AAGO,SAAS,0BAA0B,QAAsC;AAC9E,MAAI,OAAO,SAAS,QAAS,QAAO,OAAO,MAAM,QAAQ,KAAK,EAAE,SAAS;AACzE,SAAO,CAAC,CAAC,OAAO,OAAO,OAAO,IAAI,QAAQ,KAAK,EAAE,SAAS,KAAK,OAAO,IAAI,MAAM,KAAK,EAAE,SAAS;AAClG;AAGO,SAAS,oBAAoB,QAAsC;AACxE,SAAO,OAAO,WAAW,0BAA0B,MAAM;AAC3D;AAGO,SAAS,iCAAiC,SAAsC;AACrF,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,UAAM,OAAO,IAAI,SAAS,YAAY;AACtC,UAAM,aAAa,SAAS,eAAe,SAAS,eAAe,SAAS;AAC5E,UAAM,OAAO,IAAI,SAAS,IAAI,aAAa,WAAW,QAAQ;AAC9D,WAAO,cAAc,SAAS;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gBAAgB,KAA2E;AAClG,QAAM,OAAO,2BAA2B;AACxC,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,OAA0B,IAAI,SAAS,QAAQ,QAAQ;AAC7D,QAAM,MAAM,IAAI,MACZ;AAAA,IACE,SAAS,IAAI,IAAI,WAAW;AAAA,IAC5B,OAAO,IAAI,IAAI,SAAS;AAAA,IACxB,aAAa,IAAI,IAAI;AAAA,IACrB,KAAK,IAAI,IAAI;AAAA,EACf,IACA;AACJ,SAAO;AAAA,IACL,SAAS;AAAA;AAAA,IAET,SAAS,IAAI,WAAW;AAAA,IACxB;AAAA,IACA,OAAO,EAAE,SAAS,IAAI,OAAO,WAAW,KAAK,MAAM,QAAQ;AAAA,IAC3D;AAAA,IACA,kBAAkB;AAAA,EACpB;AACF;AAEO,IAAM,2BAAN,MAA+B;AAAA,EAC5B;AAAA,EAER,YAAY,UAAkB;AAC5B,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,eAAwB;AACtB,WAAO,0BAA0B,KAAK,IAAI,CAAC;AAAA,EAC7C;AAAA,EAEA,YAAqB;AACnB,WAAO,KAAK,IAAI,EAAE;AAAA,EACpB;AAAA,EAEA,SAAkB;AAChB,WAAO,oBAAoB,KAAK,IAAI,CAAC;AAAA,EACvC;AAAA;AAAA,EAGA,WAAW,SAAuC;AAChD,WAAO,KAAK,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAAA,EAC5C;AAAA,EAEA,MAA2B;AACzB,QAAI,CAACD,KAAG,WAAW,KAAK,QAAQ,EAAG,QAAO,2BAA2B;AACrE,QAAI;AACF,YAAM,MAAM,KAAK,MAAMA,KAAG,aAAa,KAAK,UAAU,OAAO,CAAC;AAC9D,aAAO,gBAAgB,GAAG;AAAA,IAC5B,QAAQ;AACN,aAAO,2BAA2B;AAAA,IACpC;AAAA,EACF;AAAA,EAEA,IAAI,QAAuD;AACzD,UAAM,aAAa,gBAAgB,MAAM;AACzC,UAAM,MAAMC,OAAK,QAAQ,KAAK,QAAQ;AACtC,QAAI,CAACD,KAAG,WAAW,GAAG,EAAG,CAAAA,KAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAC9D,IAAAA,KAAG,cAAc,KAAK,UAAU,KAAK,UAAU,YAAY,MAAM,CAAC,GAAG,OAAO;AAC5E,WAAO;AAAA,EACT;AAAA,EAEA,iBAAyB;AACvB,WAAO,eAAe,KAAK,IAAI,CAAC;AAAA,EAClC;AACF;;;ACzKA,OAAOE,YAAU;;;ACJjB,SAAS,cAAAC,mBAAkB;AAUpB,IAAM,kBAAkB;AAGxB,IAAM,cAAc;AA6BpB,IAAM,yBAAwC;AAAA,EACnD,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB,2BAA2B;AAC7B;AAEA,SAAS,UAAU,OAAuB;AACxC,SAAOA,YAAW,QAAQ,EAAE,OAAO,OAAO,OAAO,EAAE,OAAO,KAAK;AACjE;AAKO,SAAS,yBAAyB,QAA+B;AACtE,QAAM,YAAY,KAAK,UAAU;AAAA,IAC/B,kBAAkB,OAAO;AAAA,IACzB,iBAAiB,OAAO;AAAA,IACxB,qBAAqB,OAAO;AAAA,IAC5B,qBAAqB,OAAO;AAAA,IAC5B,sBAAsB,OAAO;AAAA,IAC7B,sBAAsB,OAAO;AAAA,IAC7B,oBAAoB,OAAO;AAAA,IAC3B,iBAAiB,OAAO;AAAA,IACxB,2BAA2B,OAAO;AAAA,EACpC,CAAC;AACD,SAAO,UAAU,SAAS;AAC5B;AAGO,SAAS,kBAAkB,WAA2B;AAC3D,SAAO,UAAU,SAAS,EAAE,MAAM,GAAG,EAAE;AACzC;AAMO,SAAS,eAAe,YAAoB,SAAiB,cAA8B;AAChG,SAAO,GAAG,UAAU,IAAI,OAAO,IAAI,YAAY;AACjD;AAMO,SAAS,eAAe,QAMpB;AACT,SAAO,UAAU,OAAO,cAAc,IAAI,OAAO,YAAY,IAAI,OAAO,UAAU,IAAI,OAAO,cAAc,IAAI,OAAO,iBAAiB;AACzI;AAMO,SAAS,kBAAkB,UAA2E;AAC3G,QAAM,WAAW,SAAS,MAAM,GAAG;AACnC,MAAI,SAAS,SAAS,KAAK,SAAS,CAAC,MAAM,SAAU,QAAO;AAC5D,QAAM,iBAAiB,OAAO,SAAS,CAAC,CAAC;AACzC,QAAM,eAAe,OAAO,SAAS,CAAC,CAAC;AACvC,MAAI,CAAC,OAAO,SAAS,cAAc,KAAK,CAAC,OAAO,SAAS,YAAY,EAAG,QAAO;AAC/E,SAAO,EAAE,gBAAgB,aAAa;AACxC;;;AC/GO,IAAM,0BAA0B;AAmBvC,IAAM,iBAA0B,CAAC,KAAK,SAAS,MAAM,KAAK,IAAI;AAE9D,SAAS,mBAAmB,SAAyB;AACnD,QAAM,UAAU,QAAQ,QAAQ,QAAQ,EAAE;AAC1C,SAAO,QAAQ,SAAS,aAAa,IAAI,UAAU,GAAG,OAAO;AAC/D;AAEO,IAAM,oCAAN,MAAqE;AAAA,EACjE;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EAErB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EAEnB,YAAY,QAAgCC,WAAiC,CAAC,GAAG;AAC/E,SAAK,SAAS;AACd,SAAK,UAAUA,SAAQ,WAAW;AAClC,SAAK,UAAU,OAAO;AACtB,SAAK,YAAY,OAAO,aAAa;AACrC,SAAK,MAAM,mBAAmB,OAAO,OAAO;AAAA,EAC9C;AAAA;AAAA,EAGA,IAAI,MAAc;AAChB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,eAAe,OAA0C;AAC7D,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AACA,QAAI,KAAK,OAAO,OAAO,KAAK,EAAG,SAAQ,gBAAgB,UAAU,KAAK,OAAO,MAAM;AAEnF,UAAM,WAAW,MAAM,KAAK,QAAQ,KAAK,KAAK;AAAA,MAC5C,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,OAAO,KAAK,OAAO,OAAO,OAAO,MAAM,CAAC;AAAA,IACjE,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACnD,YAAM,IAAI,MAAM,iCAAiC,SAAS,MAAM,IAAI,MAAM,GAAG,KAAK,CAAC;AAAA,IACrF;AAEA,UAAM,UAAW,MAAM,SAAS,KAAK;AACrC,UAAM,SAAS,CAAC,GAAG,QAAQ,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACjE,UAAM,UAAU,OAAO,IAAI,CAAC,SAAS,aAAa,KAAK,KAAK,SAAS,CAAC;AACtE,QAAI,QAAQ,SAAS,EAAG,MAAK,WAAW,QAAQ,CAAC,EAAE;AACnD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,MAAqC;AACpD,UAAM,CAAC,MAAM,IAAI,MAAM,KAAK,eAAe,CAAC,IAAI,CAAC;AACjD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAyB;AAAA,EAE/B;AACF;;;AC1FO,SAAS,sBAAsB,aAAqB,OAAuB;AAChF,QAAM,UAAU,YAAY,KAAK;AACjC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,QAAQ,SAAS,GAAG,KAAK,QAAQ,SAAS,QAAG,EAAG,QAAO,GAAG,OAAO,GAAG,KAAK;AAC7E,SAAO,aAAa,OAAO;AAAA,SAAY,KAAK;AAC9C;AAGO,SAAS,eAAe,MAAc,UAA2B;AACtE,MAAI,aAAa,UAAa,KAAK,UAAU,SAAU,QAAO;AAC9D,SAAO,KAAK,MAAM,GAAG,QAAQ;AAC/B;;;ACAA,IAAM,wBAAwB,oBAAI,IAAI,CAAC,UAAU,WAAW,SAAS,CAAC;AAgBtE,eAAsB,iBAAiB,UAAyC;AAC9E,QAAM,SAAS,IAAI,IAAI,QAAQ;AAC/B,MAAI,sBAAsB,IAAI,OAAO,QAAQ,GAAG;AAC9C,UAAM,IAAI;AAAA,MACR,qEAAqE,OAAO,QAAQ,KAAK,OAAO,IAAI;AAAA,IACtG;AAAA,EACF;AACA,QAAM,EAAE,OAAO,aAAa,WAAW,IAAI,MAAM,OAAO,uBAAQ;AAChE,QAAM,aAAa,IAAI,WAAW,QAAQ;AAC1C,UAAQ,CAAC,OAAO,SAAS;AACvB,UAAM,WAA8B,EAAE,GAAI,MAAwC,WAAW;AAC7F,WAAO,YAAY,OAAqC,QAAQ;AAAA,EAClE;AACF;AAEA,IAAM,yBAA+C,OAAO,EAAE,SAAS,OAAO,UAAU,sBAAsB,MAAM;AAClH,QAAM,eAAe,MAAM,OAAO,kCAA2B;AAC7D,MAAI,UAAU;AACZ,iBAAa,IAAI,WAAW;AAC5B,iBAAa,IAAI,oBAAoB;AAAA,EACvC;AACA,MAAI,uBAAuB;AACzB,iBAAa,IAAI,QAAQ,MAAM,iBAAiB,qBAAqB;AAAA,EACvE;AACA,QAAM,YAAY,MAAM,aAAa,SAAS,sBAAsB,SAAS,QAAQ,EAAE,MAAM,IAAI,MAAS;AAC1G,SAAO,OAAO,OAAOC,aAAY;AAC/B,UAAM,MAAM,MAAM,UAAU,OAAO,EAAE,SAASA,SAAQ,SAAS,WAAWA,SAAQ,UAAU,CAAC;AAC7F,WAAO,IAAI,OAAO;AAAA,EACpB;AACF;AAQO,IAAM,yBAAN,MAA0D;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAED;AAAA,EACA;AAAA,EACA,mBAAqD;AAAA,EAE7D,YAAY,SAAgCA,WAAyC,CAAC,GAAG;AACvF,SAAK,UAAU;AACf,SAAK,UAAUA;AACf,SAAK,UAAU,QAAQ;AACvB,SAAK,MAAM,QAAQ;AACnB,SAAK,YAAY,QAAQ;AACzB,SAAK,oBAAoB,QAAQ,gBAAgB;AAAA,EACnD;AAAA,EAEQ,eAA0C;AAChD,QAAI,CAAC,KAAK,kBAAkB;AAC1B,YAAM,UAAU,KAAK,QAAQ,mBAAmB;AAChD,WAAK,mBAAmB,QAAQ;AAAA,QAC9B,SAAS,KAAK,QAAQ;AAAA,QACtB,OAAO,KAAK,QAAQ;AAAA,QACpB,UAAU,KAAK,QAAQ;AAAA,QACvB,uBAAuB,KAAK,QAAQ;AAAA,MACtC,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,UAAU,QAAgC;AAChD,QAAI,OAAO,WAAW,KAAK,QAAQ,KAAK;AACtC,YAAM,IAAI,MAAM,SAAS,KAAK,QAAQ,OAAO,iBAAiB,OAAO,MAAM,cAAc,KAAK,QAAQ,GAAG,EAAE;AAAA,IAC7G;AACA,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC;AAAA,EAEA,MAAM,eAAe,OAA0C;AAC7D,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,UAAM,YAAY,MAAM,KAAK,aAAa;AAC1C,UAAM,YAAY,KAAK,QAAQ,gBAAgB,MAAM;AACrD,UAAM,UAAU,MAAM,IAAI,CAACC,OAAM,eAAeA,IAAG,KAAK,QAAQ,YAAY,CAAC;AAE7E,UAAM,SAAyB,CAAC;AAChC,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;AAClD,YAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,SAAS;AAC5C,YAAM,UAAU,MAAM,UAAU,OAAO,EAAE,SAAS,KAAK,QAAQ,SAAS,WAAW,KAAK,QAAQ,UAAU,CAAC;AAC3G,iBAAW,KAAK,QAAS,QAAO,KAAK,KAAK,UAAU,CAAC,CAAC;AAAA,IACxD;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,MAAqC;AACpD,UAAM,kBAAkB,sBAAsB,KAAK,QAAQ,kBAAkB,IAAI;AACjF,UAAM,CAAC,MAAM,IAAI,MAAM,KAAK,eAAe,CAAC,eAAe,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAyB;AAC7B,UAAM,KAAK,aAAa;AAAA,EAC1B;AACF;;;ACnGO,IAAM,gBAAuC;AAAA,EAClD,aAAa;AAAA,EACb,SAAS;AAAA,EACT,cAAc;AAAA,EACd,KAAK;AAAA,EACL,WAAW;AAAA,EACX,cAAc;AAAA,EACd,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA,EACP,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,kBAAkB;AACpB;AAGO,IAAM,mBAA0C;AAAA,EACrD,aAAa;AAAA,EACb,SAAS;AAAA,EACT,cAAc;AAAA,EACd,KAAK;AAAA,EACL,WAAW;AAAA,EACX,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA,EACP,kBAAkB;AAAA,EAClB,kBAAkB;AACpB;AAEA,IAAM,qBAA8C,CAAC,eAAe,gBAAgB;AAM7E,SAAS,0BAA0B,QAAyC;AACjF,MAAI,OAAO,WAAW,IAAI,EAAG,QAAO,CAAC,eAAe,gBAAgB;AACpE,SAAO,CAAC,aAAa;AACvB;AAEO,SAAS,yBAAyB,SAA+C;AACtF,SAAO,mBAAmB,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,KAAK;AAClE;;;AC7CO,SAAS,eAAe,QAA6B,OAA4B,CAAC,GAAsB;AAC7G,MAAI,OAAO,SAAS,OAAO;AACzB,QAAI,CAAC,OAAO,OAAO,CAAC,OAAO,IAAI,WAAW,CAAC,OAAO,IAAI,OAAO;AAC3D,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AACA,UAAMC,WAAU,KAAK,iBAAiB;AACtC,UAAM,SAASA,SAAQ,IAAI,OAAO,IAAI,WAAW;AACjD,WAAO,IAAI;AAAA,MACT,EAAE,SAAS,OAAO,IAAI,SAAS,QAAQ,OAAO,OAAO,IAAI,MAAM;AAAA,MAC/D,EAAE,SAAS,KAAK,QAAQ;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,UAAU,yBAAyB,OAAO,MAAM,OAAO;AAC7D,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,kCAAkC,OAAO,MAAM,OAAO,EAAE;AAAA,EAC1E;AACA,SAAO,IAAI,uBAAuB,SAAS;AAAA,IACzC,UAAU,KAAK;AAAA,IACf,uBAAuB,KAAK;AAAA,IAC5B,iBAAiB,KAAK;AAAA,EACxB,CAAC;AACH;;;ACxCA,OAAOC,eAAc;;;ACVrB,SAAS,qBAAqB;AAC9B,SAAS,MAAM,gBAAgB;AAG/B,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAC7B,IAAM,qBAAqB,CAAC,CAAC,UAAS,KAAK,GAAE,CAAC,SAAQ,KAAK,GAAE,CAAC,UAAS,OAAO,GAAE,CAAC,SAAQ,KAAK,GAAE,CAAC,SAAQ,OAAO,CAAC;AAEjH,IAAM,8BAA8B,4BAA4B,iBAAiB,UAAU,QAAQ,IAAI,IAAI,sCAAsC,mBAC9I,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAC3B,KAAK,GAAG,CAAC,kBAAkB,iBAAiB;AAI/C,SAAS,cAAcC,WAAUC,OAAM;AACrC,SACE,mBAAmB,KAAK,CAAC,CAAC,GAAG,CAAC,MAAMD,cAAa,KAAKC,UAAS,CAAC,MAAM;AAE1E;AACA,SAAS,gBAAgBD,WAAU;AACjC,MAAIA,cAAa,QAAS,QAAO;AACjC,MAAIA,cAAa,SAAU,QAAO;AAClC,SAAO;AACT;AACA,SAAS,oBAAoBA,WAAUC,OAAM;AAC3C,QAAMC,MAAKF,cAAa,UAAU,YAAYA;AAC9C,SAAO,GAAG,iBAAiB,IAAIE,GAAE,IAAID,KAAI;AAC3C;AAEA,SAAS,kBAAkB;AACzB,MAAI,CAAC,cAAc,UAAU,IAAI,GAAG;AAClC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,cAAc,oBAAoB,UAAU,IAAI;AACtD,QAAM,eAAe,cAAc,YAAY,QAAQ,cAAc,MAAM,uBAAuB,MAAM,gBAAgB,QAAQ,CAAC,CAAC;AAClI,SAAO;AACT;AAEA,SAAS,KAAK,IAAI;AAChB,KAAG,cAAc,gBAAgB,CAAC;AACpC;;;ACjCO,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BjC,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAOnC,IAAM,yBAAyB,2BAA2B;AAS1D,IAAM,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBrC,SAAS,aAAa,KAAqB;AAChD,SAAO,aAAa,GAAG;AACzB;AAGO,SAAS,eAAe,KAAqB;AAClD,SAAO;AAAA,yCACgC,aAAa,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA,wBAIlC,GAAG;AAAA;AAAA;AAG3B;;;AF/DA,IAAM,uBAAsC,CAAC,OAAiB,KAAK,EAAE;AAsBrE,SAAS,YAAY,KAA4B;AAC/C,SAAO;AAAA,IACL,SAAS,IAAI;AAAA,IACb,YAAY,IAAI;AAAA,IAChB,YAAY,IAAI;AAAA,IAChB,SAAS,IAAI;AAAA,IACb,KAAK,IAAI;AAAA,IACT,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,IACpB,cAAc,IAAI;AAAA,IAClB,SAAS,IAAI;AAAA,IACb,OAAO,IAAI;AAAA,IACX,cAAc,IAAI;AAAA,IAClB,gBAAgB,IAAI;AAAA,IACpB,oBAAoB,IAAI;AAAA,IACxB,gBAAgB,IAAI;AAAA,IACpB,mBAAmB,IAAI;AAAA,IACvB,WAAW,IAAI;AAAA,IACf,QAAQ,IAAI;AAAA,EACd;AACF;AAEA,SAAS,gBAAgB,WAA4C;AACnE,QAAM,MAAM,qBAAqB,eAAe,YAAY,IAAI,aAAa,SAAS;AACtF,SAAO,OAAO,KAAK,IAAI,QAAQ,IAAI,YAAY,IAAI,UAAU;AAC/D;AAEA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,gBAAgB,kBAAkB,KAAK,IAAI;AACjD,IAAM,yBAAyB,kBAAkB,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAExE,IAAM,sBAAN,MAA0B;AAAA,EACvB;AAAA,EACA,iBAAiB,oBAAI,IAAY;AAAA,EAEzC,YAAY,QAAgBE,UAAyF;AACnH,SAAK,KAAK,IAAIC,UAAS,QAAQ,EAAE,UAAUD,UAAS,YAAY,OAAO,eAAeA,UAAS,cAAc,CAAC;AAC7G,KAACA,UAAS,iBAAiB,sBAAsB,KAAK,EAAE;AACzD,QAAI,CAACA,UAAS,UAAU;AACtB,WAAK,GAAG,OAAO,oBAAoB;AACnC,WAAK,GAAG,KAAK,sBAAsB;AAAA,IACrC;AAAA,EACF;AAAA;AAAA,EAGQ,eAAe,KAAmB;AACxC,QAAI,KAAK,eAAe,IAAI,GAAG,EAAG;AAClC,SAAK,GAAG,KAAK,eAAe,GAAG,CAAC;AAChC,SAAK,eAAe,IAAI,GAAG;AAAA,EAC7B;AAAA,EAEQ,UAAU,MAAyB;AACzC,UAAM,EAAE,QAAQ,UAAU,IAAI;AAC9B,QAAI,UAAU,WAAW,OAAO,KAAK;AACnC,YAAM,IAAI,MAAM,oBAAoB,UAAU,MAAM,mBAAmB,OAAO,GAAG,cAAc,OAAO,OAAO,EAAE;AAAA,IACjH;AACA,SAAK,eAAe,OAAO,GAAG;AAE9B,UAAM,OAAO,KAAK,GACf;AAAA;AAAA,MAEC,6CAA6C,aAAa;AAAA;AAAA;AAAA;AAAA,IAI5D,EACC,IAAI;AAAA,MACH,SAAS,OAAO;AAAA,MAChB,YAAY,OAAO;AAAA,MACnB,YAAY,OAAO;AAAA,MACnB,SAAS,OAAO;AAAA,MAChB,KAAK,OAAO;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,cAAc,OAAO;AAAA,MACrB,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO;AAAA,MACd,cAAc,OAAO;AAAA,MACrB,gBAAgB,OAAO;AAAA,MACvB,oBAAoB,OAAO;AAAA,MAC3B,gBAAgB,OAAO;AAAA,MACvB,mBAAmB,OAAO;AAAA,MAC1B,WAAW,OAAO;AAAA,MAClB,QAAQ,OAAO;AAAA,IACjB,CAAC;AAEH,QAAI,KAAK,YAAY,EAAG;AACxB,SAAK,GACF;AAAA,MACC,eAAe,aAAa,OAAO,GAAG,CAAC;AAAA;AAAA,IAEzC,EACC,IAAI,OAAO,KAAK,eAAe,GAAG,OAAO,YAAY,OAAO,SAAS,gBAAgB,SAAS,CAAC;AAAA,EACpG;AAAA;AAAA,EAGA,YAAY,QAAqB,WAA0C;AACzE,SAAK,GAAG,YAAY,MAAM,KAAK,UAAU,EAAE,QAAQ,UAAU,CAAC,CAAC,EAAE;AAAA,EACnE;AAAA;AAAA,EAGA,aAAa,OAA4B;AACvC,SAAK,GAAG,YAAY,MAAM;AACxB,iBAAW,QAAQ,MAAO,MAAK,UAAU,IAAI;AAAA,IAC/C,CAAC,EAAE;AAAA,EACL;AAAA;AAAA,EAGA,WAAW,QAA8C;AACvD,UAAM,QAAQ,aAAa,OAAO,GAAG;AACrC,QAAI,CAAC,KAAK,YAAY,KAAK,EAAG,QAAO,CAAC;AAEtC,UAAM,OAAO,KAAK,GACf;AAAA,MACC,kCAAkC,sBAAsB;AAAA,gBAChD,KAAK;AAAA;AAAA;AAAA;AAAA,IAIf,EACC,IAAI,gBAAgB,OAAO,SAAS,GAAG,OAAO,YAAY,OAAO,SAAS,OAAO,CAAC;AAIrF,WAAO,KAAK,IAAI,CAAC,SAAS,EAAE,SAAS,IAAI,UAAU,UAAU,IAAI,UAAU,QAAQ,YAAY,GAAG,EAAE,EAAE;AAAA,EACxG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAkB,QAAkD;AAClE,UAAM,MAAM,KAAK,GACd;AAAA,MACC,UAAU,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,IAKzB,EACC;AAAA,MACC,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAEF,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI,IAAI,SAAS,OAAO,UAAW,QAAO;AAC1C,QAAI,IAAI,WAAW,OAAO,aAAa,IAAI,iBAAiB,OAAO,UAAW,QAAO;AACrF,WAAO,YAAY,GAAG;AAAA,EACxB;AAAA;AAAA,EAGA,mBAAmB,YAA4B;AAC7C,WAAO,KAAK,GAAG,YAAY,MAAM;AAC/B,YAAM,OAAO,KAAK,GACf,QAAQ,2EAA2E,EACnF,IAAI,UAAU;AAEjB,YAAM,WAAW,oBAAI,IAAgC;AACrD,iBAAW,EAAE,OAAO,IAAI,KAAK,MAAM;AACjC,cAAM,QAAQ,aAAa,GAAG;AAC9B,YAAI,CAAC,KAAK,YAAY,KAAK,EAAG;AAC9B,YAAI,OAAO,SAAS,IAAI,GAAG;AAC3B,YAAI,CAAC,MAAM;AACT,iBAAO,KAAK,GAAG,QAAQ,eAAe,KAAK,uCAAuC;AAClF,mBAAS,IAAI,KAAK,IAAI;AAAA,QACxB;AACA,aAAK,IAAI,KAAK;AAAA,MAChB;AAEA,YAAM,MAAM,KAAK,GAAG,QAAQ,uDAAuD,EAAE,IAAI,UAAU;AACnG,aAAO,IAAI;AAAA,IACb,CAAC,EAAE;AAAA,EACL;AAAA;AAAA,EAGA,0BAA0B,QAKf;AACT,WAAO,KAAK,GAAG,YAAY,MAAM;AAC/B,YAAM,OAAO,KAAK,GACf;AAAA,QACC;AAAA;AAAA;AAAA;AAAA,MAIF,EACC,IAAI,OAAO,YAAY,OAAO,SAAS,OAAO,SAAS,OAAO,SAAS,OAAO,cAAc;AAK/F,YAAM,WAAW,oBAAI,IAAgC;AACrD,iBAAW,EAAE,OAAO,IAAI,KAAK,MAAM;AACjC,cAAM,QAAQ,aAAa,GAAG;AAC9B,YAAI,CAAC,KAAK,YAAY,KAAK,EAAG;AAC9B,YAAI,OAAO,SAAS,IAAI,GAAG;AAC3B,YAAI,CAAC,MAAM;AACT,iBAAO,KAAK,GAAG,QAAQ,eAAe,KAAK,uCAAuC;AAClF,mBAAS,IAAI,KAAK,IAAI;AAAA,QACxB;AACA,aAAK,IAAI,KAAK;AAAA,MAChB;AAEA,YAAM,MAAM,KAAK,GACd;AAAA,QACC;AAAA;AAAA;AAAA;AAAA,MAIF,EACC,IAAI,OAAO,YAAY,OAAO,SAAS,OAAO,SAAS,OAAO,SAAS,OAAO,cAAc;AAC/F,aAAO,IAAI;AAAA,IACb,CAAC,EAAE;AAAA,EACL;AAAA;AAAA,EAGA,mBAA6B;AAC3B,UAAM,OAAO,KAAK,GAAG,QAAQ,sDAAsD,EAAE,IAAI;AAGzF,WAAO,KAAK,IAAI,CAAC,MAAM,EAAE,YAAY;AAAA,EACvC;AAAA;AAAA,EAGA,YAAY,YAAoB,SAA0B;AACxD,UAAM,MAAM,UACP,KAAK,GACH,QAAQ,sFAAsF,EAC9F,IAAI,YAAY,OAAO,IACzB,KAAK,GAAG,QAAQ,qEAAqE,EAAE,IAAI,UAAU;AAG1G,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAGA,OAAO,YAAoB,SAAgC;AACzD,UAAM,MAAM,KAAK,GACd,QAAQ,oFAAoF,EAC5F,IAAI,YAAY,OAAO;AAC1B,WAAO,MAAM,IAAI,MAAM;AAAA,EACzB;AAAA;AAAA,EAGA,aAAa,SAAqC;AAChD,UAAM,MAAM,KAAK,GAAG,QAAQ,UAAU,aAAa,6CAA6C,EAAE,IAAI,OAAO;AAG7G,WAAO,MAAM,YAAY,GAAG,IAAI;AAAA,EAClC;AAAA,EAEQ,YAAY,MAAuB;AACzC,WAAO,CAAC,CAAC,KAAK,GAAG,QAAQ,4CAA4C,EAAE,IAAI,IAAI;AAAA,EACjF;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;;;AGxUA,OAAOE,eAAc;AAkErB,SAAS,WAAW,KAA4C;AAC9D,SAAO;AAAA,IACL,YAAY,IAAI;AAAA,IAChB,QAAQ,IAAI;AAAA,IACZ,SAAS,IAAI,YAAY;AAAA,IACzB,SAAS,IAAI;AAAA,IACb,aAAa,IAAI;AAAA,IACjB,eAAe,IAAI;AAAA,IACnB,eAAe,IAAI;AAAA,IACnB,iBAAiB,IAAI;AAAA,IACrB,sBAAsB,IAAI;AAAA,IAC1B,YAAY,IAAI;AAAA,IAChB,gBAAgB,IAAI,mBAAmB;AAAA,IACvC,mBAAmB,IAAI,uBAAuB;AAAA,IAC9C,OAAO,IAAI;AAAA,IACX,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEO,IAAM,0BAAN,MAA8B;AAAA,EAC3B;AAAA,EAER,YAAY,QAAgBC,UAAsC;AAChE,SAAK,KAAK,IAAIC,UAAS,QAAQ,EAAE,eAAeD,UAAS,cAAc,CAAC;AACxE,SAAK,GAAG,OAAO,oBAAoB;AACnC,SAAK,GAAG,KAAK,4BAA4B;AACzC,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA,EAGQ,uBAA6B;AACnC,UAAM,UAAU,KAAK,GAAG,QAAQ,2CAA2C,EAAE,IAAI;AACjF,UAAM,QAAQ,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAChD,QAAI,CAAC,MAAM,IAAI,iBAAiB,GAAG;AACjC,WAAK,GAAG,KAAK,oEAAoE;AAAA,IACnF;AACA,QAAI,CAAC,MAAM,IAAI,qBAAqB,GAAG;AACrC,WAAK,GAAG,KAAK,wEAAwE;AAAA,IACvF;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,QAA4B;AACjC,UAAM,MAAM,KAAK,IAAI;AACrB,SAAK,GACF;AAAA,MACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcF,EACC,IAAI;AAAA,MACH,YAAY,OAAO;AAAA,MACnB,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO;AAAA,MAChB,gBAAgB,OAAO;AAAA,MACvB,mBAAmB,OAAO;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,YAAoB,QAAsB;AACnD,SAAK,GACF;AAAA,MACC;AAAA,IACF,EACC,IAAI,QAAQ,KAAK,IAAI,GAAG,UAAU;AAAA,EACvC;AAAA;AAAA,EAGA,QAAQ,YAA0B;AAChC,SAAK,GACF;AAAA,MACC;AAAA;AAAA;AAAA,IAGF,EACC,IAAI,KAAK,IAAI,GAAG,UAAU;AAAA,EAC/B;AAAA,EAEA,eAAe,YAAoB,OAA4B;AAC7D,UAAM,OAAiB,CAAC;AACxB,UAAM,SAAkC,EAAE,YAAY,KAAK,KAAK,IAAI,EAAE;AACtE,UAAM,MAA2C;AAAA,MAC/C,aAAa;AAAA,MACb,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,sBAAsB;AAAA,MACtB,YAAY;AAAA,MACZ,OAAO;AAAA,IACT;AACA,eAAW,OAAO,OAAO,KAAK,GAAG,GAA8B;AAC7D,UAAI,MAAM,GAAG,MAAM,QAAW;AAC5B,aAAK,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,GAAG,EAAE;AACjC,eAAO,GAAG,IAAI,MAAM,GAAG;AAAA,MACzB;AAAA,IACF;AACA,QAAI,KAAK,WAAW,EAAG;AACvB,SAAK,GACF;AAAA,MACC,qCAAqC,KAAK,KAAK,IAAI,CAAC;AAAA,IACtD,EACC,IAAI,MAAM;AAAA,EACf;AAAA,EAEA,eAAe,YAAoB,QAA6B,OAA6B;AAC3F,SAAK,GACF,QAAQ,sGAAsG,EAC9G,IAAI,QAAQ,SAAS,MAAM,KAAK,IAAI,GAAG,UAAU;AAAA,EACtD;AAAA,EAEA,iBAAiB,YAAoB,QAA0C;AAC7E,SAAK,GACF,QAAQ,6FAA6F,EACrG,IAAI,QAAQ,KAAK,IAAI,GAAG,UAAU;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBACE,YACA,UACM;AACN,SAAK,GACF;AAAA,MACC;AAAA;AAAA;AAAA,IAGF,EACC,IAAI,SAAS,SAAS,SAAS,gBAAgB,SAAS,mBAAmB,KAAK,IAAI,GAAG,UAAU;AAAA,EACtG;AAAA;AAAA,EAGA,cAAc,YAA0B;AACtC,SAAK,GACF;AAAA,MACC;AAAA;AAAA;AAAA;AAAA,IAIF,EACC,IAAI,KAAK,IAAI,GAAG,UAAU;AAAA,EAC/B;AAAA,EAEA,SAAS,YAAsD;AAC7D,UAAM,MAAM,KAAK,GAAG,QAAQ,6DAA6D,EAAE,IAAI,UAAU;AAGzG,WAAO,MAAM,WAAW,GAAG,IAAI;AAAA,EACjC;AAAA,EAEA,cAA2C;AACzC,UAAM,OAAO,KAAK,GACf,QAAQ,4EAA4E,EACpF,IAAI;AACP,WAAO,KAAK,IAAI,UAAU;AAAA,EAC5B;AAAA,EAEA,UAAuC;AACrC,UAAM,OAAO,KAAK,GAAG,QAAQ,0DAA0D,EAAE,IAAI;AAC7F,WAAO,KAAK,IAAI,UAAU;AAAA,EAC5B;AAAA,EAEA,qBAAkD;AAChD,UAAM,OAAO,KAAK,GACf,QAAQ,2FAA2F,EACnG,IAAI;AACP,WAAO,KAAK,IAAI,UAAU;AAAA,EAC5B;AAAA,EAEA,OAAO,YAA4B;AACjC,WAAO,KAAK,GAAG,QAAQ,2DAA2D,EAAE,IAAI,UAAU,EAAE;AAAA,EACtG;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;;;AC/OO,IAAM,wBAAN,MAA4B;AAAA,EACzB;AAAA,EACA,UAA8B,CAAC;AAAA,EAC/B,aAAa;AAAA,EACb,cAA6B;AAAA,EAC7B,YAAuB;AAAA,EACvB,gBAAgC,CAAC;AAAA,EAEzC,YAAY,UAAuB;AACjC,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,QAAQ,KAA6B;AACnC,UAAM,YAAY,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,eAAe,IAAI,cAAc,EAAE,SAAS,IAAI,IAAI;AACjG,QAAI,UAAW;AACf,SAAK,QAAQ,KAAK,GAAG;AACrB,SAAK,KAAK,QAAQ;AAAA,EACpB;AAAA;AAAA,EAGA,MAAM,YAA0B;AAC9B,SAAK,cAAc,UAAU;AAC7B,QAAI,KAAK,gBAAgB,WAAY,MAAK,YAAY;AAAA,EACxD;AAAA;AAAA,EAGA,OAAO,YAA0B;AAC/B,SAAK,cAAc,UAAU;AAC7B,QAAI,KAAK,gBAAgB,WAAY,MAAK,YAAY;AAAA,EACxD;AAAA,EAEA,UAAU,YAA6B;AACrC,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA,EAEA,SAAS,YAA6B;AACpC,WAAO,KAAK,gBAAgB,cAAc,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,eAAe,UAAU;AAAA,EAChG;AAAA;AAAA,EAGA,WAA0B;AACxB,QAAI,CAAC,KAAK,cAAc,KAAK,QAAQ,WAAW,EAAG,QAAO,QAAQ,QAAQ;AAC1E,WAAO,IAAI,QAAQ,CAACE,aAAY,KAAK,cAAc,KAAKA,QAAO,CAAC;AAAA,EAClE;AAAA,EAEQ,cAAc,YAA0B;AAC9C,SAAK,UAAU,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,eAAe,UAAU;AAAA,EACvE;AAAA,EAEA,MAAc,UAAyB;AACrC,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAElB,WAAO,KAAK,QAAQ,SAAS,GAAG;AAC9B,YAAM,MAAM,KAAK,QAAQ,MAAM;AAC/B,WAAK,cAAc,IAAI;AACvB,WAAK,YAAY;AACjB,UAAI;AACF,cAAM,KAAK,SAAS,EAAE,KAAK,WAAW,MAAM,KAAK,UAAU,CAAC;AAAA,MAC9D,QAAQ;AAAA,MAER;AACA,WAAK,cAAc;AACnB,WAAK,YAAY;AAAA,IACnB;AAEA,SAAK,aAAa;AAClB,UAAM,YAAY,KAAK;AACvB,SAAK,gBAAgB,CAAC;AACtB,eAAWA,YAAW,UAAW,CAAAA,SAAQ;AAAA,EAC3C;AACF;;;ACvFA,SAAS,cAAAC,mBAAkB;;;ACRpB,SAASC,gBAAe,MAAsB;AACnD,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,aAAW,MAAM,MAAM;AACrB,QAAI,gCAAgC,KAAK,EAAE,EAAG;AAAA,QACzC;AAAA,EACP;AACA,SAAO,MAAM,KAAK,KAAK,QAAQ,CAAC;AAClC;;;ADiEA,IAAM,qBAAqB,oBAAI,IAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAc3C,CAAC;AAGD,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,uBAAuB,CAAC,4CAAS;AAGvC,IAAM,eAAe,oBAAI,IAAI;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,SAAS,gBAAgB,SAAyB;AAChD,SAAO,QACJ,KAAK,EACL,YAAY,EACZ,QAAQ,sBAAsB,EAAE;AACrC;AAEO,SAAS,eAAe,SAAqC;AAClE,MAAI,QAAQ,SAAS,UAAa,mBAAmB,IAAI,QAAQ,IAAI,EAAG,QAAO;AAE/E,QAAM,WAAW,QAAQ,WAAW,IAAI,KAAK;AAC7C,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,MAAI,mBAAmB,SAAS,OAAO,EAAG,QAAO;AACjD,MAAI,qBAAqB,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,EAAG,QAAO;AAElE,SAAO,aAAa,IAAI,gBAAgB,OAAO,CAAC;AAClD;AAIA,SAAS,mBAAmB,SAAoC;AAC9D,SAAO,eAAe,OAAO,IAAI,MAAM,QAAQ,WAAW,IAAI,KAAK;AACrE;AAEA,SAAS,kBAAkB,UAAuC;AAChE,MAAI,QAAQ;AACZ,aAAW,KAAK,SAAU,UAAS,mBAAmB,CAAC,EAAE;AACzD,SAAO;AACT;AAEA,SAASC,WAAU,OAAuB;AACxC,SAAOC,YAAW,QAAQ,EAAE,OAAO,OAAO,OAAO,EAAE,OAAO,KAAK;AACjE;AAIA,SAAS,KAAK,GAAmB;AAC/B,SAAO,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAClC;AAEA,SAAS,eAAe,IAAoB;AAC1C,QAAM,IAAI,IAAI,KAAK,EAAE;AACrB,SAAO,GAAG,EAAE,YAAY,CAAC,IAAI,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE,QAAQ,CAAC,CAAC,IAAI,KAAK,EAAE,SAAS,CAAC,CAAC,IAAI,KAAK,EAAE,WAAW,CAAC,CAAC;AACxH;AAEA,SAASC,YAAW,IAAoB;AACtC,QAAM,IAAI,IAAI,KAAK,EAAE;AACrB,SAAO,GAAG,KAAK,EAAE,SAAS,CAAC,CAAC,IAAI,KAAK,EAAE,WAAW,CAAC,CAAC;AACtD;AAEA,SAASC,iBAAgB,SAAiB,OAAuB;AAC/D,QAAM,QAAQ,IAAI,KAAK,OAAO;AAC9B,QAAM,MAAM,IAAI,KAAK,KAAK;AAC1B,QAAM,UACJ,MAAM,YAAY,MAAM,IAAI,YAAY,KACxC,MAAM,SAAS,MAAM,IAAI,SAAS,KAClC,MAAM,QAAQ,MAAM,IAAI,QAAQ;AAClC,SAAO,GAAG,eAAe,OAAO,CAAC,MAAM,UAAUD,YAAW,KAAK,IAAI,eAAe,KAAK,CAAC;AAC5F;AAEA,SAAS,oBAAoB,QAAqB,UAAuC;AACvF,QAAM,YAAY,SAAS,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;AAC3D,QAAM,UAAU,SAAS,CAAC,EAAE;AAC5B,QAAM,QAAQ,SAAS,SAAS,SAAS,CAAC,EAAE;AAE5C,QAAM,QAAkB,CAAC;AACzB,QAAM,YAAY,OAAO,SAAS,UAAU,iBAAO;AACnD,QAAM,KAAK,kBAAQ,OAAO,KAAK,SAAI,SAAS,QAAG;AAC/C,QAAM,KAAK,8BAAUC,iBAAgB,SAAS,KAAK,CAAC,EAAE;AACtD,MAAI,OAAO,SAAS,SAAS;AAC3B,UAAM,eAAe,CAAC,GAAG,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACpE,UAAM,KAAK,wBAAS,aAAa,KAAK,QAAG,CAAC,EAAE;AAAA,EAC9C;AACA,QAAM,KAAK,EAAE;AACb,aAAW,KAAK,WAAW;AACzB,UAAM,KAAK,GAAG,EAAE,UAAU,MAAM,EAAE,WAAW,IAAI,KAAK,CAAC,EAAE;AAAA,EAC3D;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,iBAAiB,UAAuC;AAC/D,SAAOH,WAAU,SAAS,IAAI,CAAC,MAAM,GAAG,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC;AAC/E;AAIA,SAAS,eAAe,UAA+B,QAA8C;AACnG,QAAM,UAAiC,CAAC;AACxC,MAAI,UAA+B,CAAC;AACpC,QAAM,QAAQ,OAAO,mBAAmB;AAExC,aAAW,KAAK,UAAU;AACxB,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,KAAK,CAAC;AACd;AAAA,IACF;AACA,UAAM,OAAO,QAAQ,QAAQ,SAAS,CAAC;AACvC,UAAM,UAAU,EAAE,KAAK,KAAK,KAAK;AACjC,UAAM,aAAaI,gBAAe,QAAQ,IAAI,CAAC,OAAO,EAAE,WAAW,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,KAAK,OAAO;AACrG,QAAI,WAAW,YAAY;AACzB,cAAQ,KAAK,OAAO;AACpB,gBAAU,CAAC,CAAC;AAAA,IACd,OAAO;AACL,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACA,MAAI,QAAQ,SAAS,EAAG,SAAQ,KAAK,OAAO;AAC5C,SAAO;AACT;AAEA,SAAS,iBAAiB,QAA6B,QAA8C;AACnG,QAAM,SAAgC,CAAC;AACvC,MAAI,UAA+B,CAAC;AACpC,MAAI,WAAW;AAEf,QAAM,eAAe,MAAM;AACzB,WAAO,KAAK,OAAO;AACnB,UAAM,OAAO,OAAO,kBAAkB,IAAI,QAAQ,MAAM,CAAC,OAAO,eAAe,IAAI,CAAC;AACpF,cAAU,CAAC,GAAG,IAAI;AAClB,eAAW;AAAA,EACb;AAEA,aAAW,KAAK,QAAQ;AACtB,YAAQ,KAAK,CAAC;AACd;AACA,UAAM,iBAAiB,kBAAkB,OAAO;AAChD,UAAM,eAAe,QAAQ;AAC7B,UAAM,YAAY,kBAAkB,OAAO;AAC3C,UAAM,aAAaA,gBAAe,QAAQ,IAAI,CAAC,MAAM,mBAAmB,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,KAAK,OAAO;AAElG,UAAM,mBAAmB,gBAAgB,OAAO,wBAAwB,kBAAkB,OAAO;AAEjG,UAAM,mBAAmB,gBAAgB,OAAO;AAChD,QAAI,aAAa,cAAc,oBAAoB,iBAAkB,cAAa;AAAA,EACpF;AAGA,MAAI,QAAQ,SAAS,MAAM,WAAW,KAAK,OAAO,WAAW,IAAI;AAC/D,WAAO,KAAK,OAAO;AAAA,EACrB;AACA,SAAO;AACT;AAEO,SAAS,cAAc,OAAwC;AACpE,QAAM,SAAS,MAAM,UAAU;AAC/B,QAAM,oBAAoB,yBAAyB,MAAM;AAEzD,QAAM,UAAU,eAAe,MAAM,UAAU,MAAM;AACrD,QAAM,SAAuB,CAAC;AAC9B,MAAI,eAAe;AAEnB,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,eAAe;AAAA,MAC9B,gBAAgB,OAAO,CAAC,EAAE;AAAA,MAC1B,cAAc,OAAO,OAAO,SAAS,CAAC,EAAE;AAAA,MACxC,YAAY,OAAO;AAAA,MACnB,gBAAgB;AAAA,MAChB;AAAA,IACF,CAAC;AAED,eAAW,SAAS,iBAAiB,QAAQ,MAAM,GAAG;AACpD,YAAM,iBAAiB,kBAAkB,KAAK;AAC9C,UAAI,iBAAiB,OAAO,2BAA2B;AACrD;AACA;AAAA,MACF;AACA,YAAM,iBAAiB,MAAM,CAAC,EAAE;AAChC,YAAM,eAAe,MAAM,MAAM,SAAS,CAAC,EAAE;AAC7C,YAAM,iBAAiB,oBAAoB,MAAM,QAAQ,KAAK;AAC9D,aAAO,KAAK;AAAA,QACV,cAAc,GAAG,QAAQ,IAAI,cAAc,IAAI,YAAY;AAAA,QAC3D;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,MAAM,CAAC,EAAE;AAAA,QAClB,OAAO,MAAM,MAAM,SAAS,CAAC,EAAE;AAAA,QAC/B,cAAc,MAAM;AAAA,QACpB;AAAA,QACA,gBAAgB,iBAAiB,KAAK;AAAA,QACtC,oBAAoBJ,WAAU,cAAc;AAAA,QAC5C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,EAAE,gBAAgB,iBAAiB,mBAAmB,QAAQ,cAAc,aAAa,QAAQ,OAAO;AACjH;;;AEzRA,SAAS,yBAAyB,UAAqC;AACrE,QAAM,OAAO,SAAS;AACtB,SAAO,OAAO,SAAS,IAAI,KAAK,QAAQ,OAAO,IAAI,KAAK,MAAM,IAAI,IAAI;AACxE;AAEA,eAAsB,UAAUK,UAAqD;AACnF,QAAM,EAAE,YAAY,SAAS,UAAU,OAAO,YAAY,QAAQ,QAAQ,UAAU,IAAIA;AAExF,MAAI,gBAAgB;AACpB,MAAI;AACF,UAAM,WAAW,OAAO,gBAAgB;AACxC,UAAM,QAAQ,OAAO,cAAc;AACnC,UAAM,aAAa,WAAW,SAAS,UAAU;AACjD,eAAW,eAAe,YAAY,EAAE,aAAa,WAAW,eAAe,OAAO,OAAO,KAAK,CAAC;AAGnG,UAAM,kBAAkB,oBAAI,IAAoB;AAChD,aAAS,QAAQ,CAAC,GAAG,MAAM,gBAAgB,IAAI,EAAE,IAAI,CAAC,CAAC;AAEvD,UAAM,kBAAkB,YAAY,wBAAwB;AAC5D,UAAM,iBAAiB,oBAAoB,OAAQ,gBAAgB,IAAI,eAAe,KAAK,KAAM;AAKjG,UAAM,kBAAkB,kBAAkB,KAAK,iBAAiB,OAAO,YAAY,mBAAmB;AACtG,QAAI,iBAAiB;AACnB,YAAM,mBAAmB,UAAU;AAAA,IACrC;AACA,UAAM,EAAE,QAAQ,kBAAkB,IAAI,cAAc,EAAE,UAAU,QAAQ,OAAO,UAAU,GAAG,OAAO,CAAC;AAEpG,UAAM,cAAc,OAAO,IAAI,CAAC,WAAW;AAAA,MACzC;AAAA,MACA,YAAY,gBAAgB,IAAI,MAAM,cAAc,KAAK;AAAA,MACzD,UAAU,gBAAgB,IAAI,MAAM,YAAY,KAAK;AAAA,IACvD,EAAE;AAEF,QAAI,cAAc,kBAAkB,KAAK;AACzC,UAAM,sBAAsB,CAAC,mBAAmB,kBAAkB,KAAK,SAAS,YAAY,iBAAiB;AAC7G,QAAI,qBAAqB;AACvB,YAAM,cACJ,YAAY,KAAK,CAAC,UAAU,MAAM,cAAc,kBAAkB,kBAAkB,MAAM,QAAQ,KAClG,YAAY,OAAO,CAAC,UAAU,MAAM,YAAY,cAAc,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AAE3G,UAAI,aAAa;AACf,cAAM,eAAe,YAAY,OAAO,CAAC,UAAU,MAAM,MAAM,aAAa,YAAY,MAAM,QAAQ;AACtG,cAAM,cAAc,KAAK,IAAI,GAAG,aAAa,IAAI,CAAC,UAAU,MAAM,UAAU,EAAE,OAAO,CAAC,UAAU,SAAS,CAAC,CAAC;AAC3G,cAAM,gBAAgB,SAAS,WAAW;AAC1C,YAAI,eAAe;AACjB,gBAAM,0BAA0B;AAAA,YAC9B;AAAA,YACA;AAAA,YACA,SAAS,cAAc;AAAA,YACvB,gBAAgB,cAAc;AAAA,UAChC,CAAC;AACD,wBAAc,cAAc;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAGA,QAAI,mBAAmB,MAAM,YAAY,YAAY,OAAO;AAC5D,QAAI,cAAc,EAAG,oBAAmB;AAExC,UAAM,gBAAgB,YAAY,OAAO,CAAC,EAAE,SAAS,MAAM,WAAW,WAAW;AACjF,UAAM,YAAY,yBAAyB,QAAQ;AACnD,aAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK,WAAW;AACxD,YAAM,cAAc,cAAc,MAAM,GAAG,IAAI,SAAS;AAExD,YAAM,OAAO,YAAY;AACzB,UAAI,MAAM;AACR,mBAAW,eAAe,YAAY,SAAS,WAAW,WAAW,WAAW;AAChF,eAAO,EAAE,QAAQ,MAAM,cAAc;AAAA,MACvC;AAEA,YAAM,UAAU,MAAM,SAAS,eAAe,YAAY,IAAI,CAAC,EAAE,MAAM,MAAM,MAAM,cAAc,CAAC;AAClG,UAAI,QAAQ,WAAW,YAAY,QAAQ;AACzC,cAAM,IAAI,MAAM,+BAA+B,QAAQ,MAAM,gBAAgB,YAAY,MAAM,SAAS;AAAA,MAC1G;AACA,YAAM,qBAAqB,YAAY;AACvC,UAAI,uBAAuB,aAAa;AACtC,mBAAW,eAAe,YAAY,WAAW;AACjD,eAAO,EAAE,QAAQ,aAAa,cAAc;AAAA,MAC9C;AAGA,YAAM,YAAY,KAAK,IAAI;AAC3B,YAAM,UAAyB,YAAY,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU;AACnE,cAAM,SAAS,QAAQ,KAAK;AAC5B,cAAM,SAAsB;AAAA,UAC1B,SAAS,eAAe,YAAY,SAAS,MAAM,YAAY;AAAA,UAC/D;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,UACA,KAAK,OAAO;AAAA,UACZ,UAAU,MAAM;AAAA,UAChB,gBAAgB,MAAM;AAAA,UACtB,cAAc,MAAM;AAAA,UACpB,SAAS,MAAM;AAAA,UACf,OAAO,MAAM;AAAA,UACb,cAAc,MAAM;AAAA,UACpB,gBAAgB,MAAM;AAAA,UACtB,oBAAoB,MAAM;AAAA,UAC1B,gBAAgB;AAAA,UAChB;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,QACV;AACA,eAAO,EAAE,QAAQ,WAAW,OAAO;AAAA,MACrC,CAAC;AACD,YAAM,aAAa,OAAO;AAC1B,uBAAiB,QAAQ;AACzB,0BAAoB,QAAQ;AAE5B,YAAM,YAAY,YAAY,YAAY,SAAS,CAAC;AACpD,iBAAW,eAAe,YAAY;AAAA,QACpC,aAAa;AAAA,QACb,iBAAiB,UAAU,WAAW;AAAA,QACtC,sBAAsB,UAAU,MAAM;AAAA,QACtC,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAEA,eAAW,eAAe,YAAY,EAAE,iBAAiB,OAAO,YAAY,iBAAiB,CAAC;AAC9F,eAAW,eAAe,YAAY,aAAa,IAAI;AACvD,WAAO,EAAE,QAAQ,aAAa,cAAc;AAAA,EAC9C,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,eAAW,eAAe,YAAY,UAAU,OAAO;AACvD,WAAO,EAAE,QAAQ,UAAU,eAAe,OAAO,QAAQ;AAAA,EAC3D;AACF;;;ACtKO,SAAS,qBAAqB,aAAyB,IAAI,IAAiB;AACjF,QAAM,SAAS,oBAAI,IAAoB;AACvC,QAAM,YAAY,oBAAI,IAAoB;AAC1C,MAAI,MAAM;AAEV,aAAW,QAAQ,aAAa;AAC9B,SAAK,QAAQ,CAAC,IAAI,SAAS;AACzB,aAAO,IAAI,KAAK,OAAO,IAAI,EAAE,KAAK,KAAK,KAAK,IAAI,KAAK;AACrD,UAAI,CAAC,UAAU,IAAI,EAAE,EAAG,WAAU,IAAI,IAAI,KAAK;AAAA,IACjD,CAAC;AAAA,EACH;AAEA,SAAO,CAAC,GAAG,OAAO,QAAQ,CAAC,EACxB,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,IAAI,MAAM,EAAE,EACpC,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,UAAU,IAAI,EAAE,EAAE,IAAK,UAAU,IAAI,EAAE,EAAE,CAAE;AACpF;;;ACqBA,SAAS,oBAAoB,QAAqB,QAAuC;AACvF,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,WAAW,QAAQ,OAAO,QAAQ,OAAO,QAAS,QAAO;AACpE,MAAI,OAAO,SAAS,QAAQ,OAAO,UAAU,OAAO,MAAO,QAAO;AAClE,SAAO;AACT;AAGA,IAAM,8BAA8B;AAYpC,eAAsB,aAAa,MAAwB,QAA2D;AACpH,QAAM,EAAE,UAAU,OAAO,IAAI,IAAI;AACjC,QAAM,EAAE,OAAO,YAAY,SAAS,YAAY,KAAK,OAAO,IAAI,YAAY,GAAG,YAAY,IAAI;AAE/F,MAAI,CAAC,MAAM,KAAK,EAAG,QAAO,CAAC;AAG3B,QAAM,aAAa,cAAc,8BAA8B;AAC/D,QAAM,YAAY,KAAK,KAAK,OAAO,aAAa,MAAM,YAAY,EAAE;AACpE,QAAM,UAAU,KAAK,KAAK,OAAO,WAAW,MAAM,YAAY,EAAE;AAEhE,QAAM,UAAU,oBAAI,IAAyB;AAG7C,QAAM,cAAc,MAAM,SAAS,WAAW,KAAK;AACnD,QAAM,QAAQ,MAAM,WAAW,EAAE,YAAY,SAAS,KAAK,WAAW,aAAa,GAAG,UAAU,CAAC;AACjG,QAAM,WAAqB,CAAC;AAC5B,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,aAAW,OAAO,OAAO;AACvB,QAAI,CAAC,oBAAoB,IAAI,QAAQ,WAAW,EAAG;AACnD,kBAAc,IAAI,IAAI,SAAS,SAAS,MAAM;AAC9C,aAAS,KAAK,IAAI,OAAO;AACzB,YAAQ,IAAI,IAAI,SAAS,IAAI,MAAM;AAAA,EACrC;AAGA,QAAM,SAAmB,CAAC;AAC1B,QAAM,cAAc,oBAAI,IAAoB;AAC5C,aAAW,EAAE,IAAI,WAAW,IAAI,UAAU,KAAK,IAAI,OAAO,OAAO,OAAO,GAAG;AACzE,UAAM,SAAS,MAAM,kBAAkB,EAAE,YAAY,SAAS,YAAY,WAAW,UAAU,CAAC;AAChG,QAAI,CAAC,UAAU,YAAY,IAAI,OAAO,OAAO,EAAG;AAChD,QAAI,CAAC,oBAAoB,QAAQ,WAAW,EAAG;AAC/C,gBAAY,IAAI,OAAO,SAAS,OAAO,MAAM;AAC7C,WAAO,KAAK,OAAO,OAAO;AAC1B,QAAI,CAAC,QAAQ,IAAI,OAAO,OAAO,EAAG,SAAQ,IAAI,OAAO,SAAS,MAAM;AAAA,EACtE;AAEA,QAAM,QAAQ,qBAAqB,CAAC,UAAU,MAAM,GAAG,IAAI;AAE3D,QAAM,UAAgC,CAAC;AACvC,aAAW,EAAE,IAAI,MAAM,KAAK,OAAO;AACjC,UAAM,SAAS,QAAQ,IAAI,EAAE,KAAK,MAAM,aAAa,EAAE;AACvD,QAAI,CAAC,OAAQ;AAEb,QAAI,CAAC,oBAAoB,QAAQ,WAAW,EAAG;AAC/C,YAAQ,KAAK;AAAA,MACX,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,WAAW,cAAc,IAAI,EAAE,KAAK;AAAA,MACpC,SAAS,YAAY,IAAI,EAAE,KAAK;AAAA,IAClC,CAAC;AACD,QAAI,QAAQ,UAAU,UAAW;AAAA,EACnC;AACA,SAAO;AACT;;;AC1DO,SAAS,uBAAuB,UAAqC;AAC1E,SAAO,SAAS,IAAI,CAAC,MAAM,GAAG,EAAE,UAAU,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AACvE;AAEA,SAAS,eAAe,UAAqC;AAC3D,SAAOC,gBAAe,uBAAuB,QAAQ,CAAC;AACxD;AAcO,SAAS,iBACd,QACA,MACA,SAAyB,CAAC,GACV;AAChB,QAAM,cAAc,OAAO,eAAe;AAC1C,QAAM,UAAU,OAAO,sBAAsB;AAC7C,QAAM,SAAS,OAAO,kBAAkB;AACxC,QAAM,YAAY,OAAO,aAAa;AAEtC,QAAM,WAAW,KAAK,MAAM,GAAG,SAAS;AACxC,QAAM,cAAc,oBAAI,IAA+B;AAGvD,QAAM,MAAsB,CAAC;AAC7B,aAAW,OAAO,UAAU;AAC1B,UAAM,SAAS,kBAAkB,IAAI,QAAQ;AAC7C,QAAI,CAAC,OAAQ;AACb,QAAI,aAAa,YAAY,IAAI,IAAI,QAAQ;AAC7C,QAAI,CAAC,YAAY;AACf,mBAAa,OAAO,UAAU,OAAO,gBAAgB,OAAO,YAAY;AACxE,kBAAY,IAAI,IAAI,UAAU,UAAU;AAAA,IAC1C;AACA,QAAI,WAAW,WAAW,EAAG;AAG7B,UAAM,eAAe,WAAW,UAAU,CAAC,MAAM,EAAE,OAAO,IAAI,cAAc;AAC5E,QAAI,aAAa;AACjB,aAAS,IAAI,WAAW,SAAS,GAAG,KAAK,GAAG,KAAK;AAC/C,UAAI,WAAW,CAAC,EAAE,OAAO,IAAI,cAAc;AACzC,qBAAa;AACb;AAAA,MACF;AAAA,IACF;AACA,QAAI,eAAe,KAAK,aAAa,aAAc;AAEnD,QAAI,KAAK;AAAA,MACP,UAAU,IAAI;AAAA,MACd;AAAA,MACA,UAAU,KAAK,IAAI,GAAG,eAAe,MAAM;AAAA,MAC3C,QAAQ,KAAK,IAAI,WAAW,SAAS,GAAG,aAAa,MAAM;AAAA,MAC3D;AAAA,MACA;AAAA,MACA,UAAU,CAAC,IAAI,OAAO;AAAA,MACtB,OAAO,IAAI;AAAA,IACb,CAAC;AAAA,EACH;AAGA,QAAM,WAAW,oBAAI,IAA4B;AACjD,aAAW,SAAS,KAAK;AACvB,UAAM,OAAO,SAAS,IAAI,MAAM,QAAQ,KAAK,CAAC;AAC9C,SAAK,KAAK,KAAK;AACf,aAAS,IAAI,MAAM,UAAU,IAAI;AAAA,EACnC;AAEA,QAAM,SAAyB,CAAC;AAChC,aAAW,QAAQ,SAAS,OAAO,GAAG;AACpC,SAAK,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAC3C,eAAW,SAAS,MAAM;AACxB,YAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,YAAM,YAAY,QAAQ,KAAK,aAAa,MAAM,YAAY,MAAM,YAAY,KAAK,SAAS;AAC9F,UAAI,WAAW;AACb,cAAM,YAAY,KAAK,IAAI,KAAK,UAAU,MAAM,QAAQ;AACxD,cAAM,UAAU,KAAK,IAAI,KAAK,QAAQ,MAAM,MAAM;AAClD,cAAM,aAAa,eAAe,MAAM,WAAW,MAAM,WAAW,UAAU,CAAC,CAAC;AAChF,YAAI,cAAc,SAAS;AACzB,eAAK,WAAW;AAChB,eAAK,SAAS;AACd,eAAK,eAAe,KAAK,IAAI,KAAK,cAAc,MAAM,YAAY;AAClE,eAAK,aAAa,KAAK,IAAI,KAAK,YAAY,MAAM,UAAU;AAC5D,eAAK,SAAS,KAAK,GAAG,MAAM,QAAQ;AACpC,eAAK,QAAQ,KAAK,IAAI,KAAK,OAAO,MAAM,KAAK;AAC7C;AAAA,QACF;AAAA,MACF;AACA,aAAO,KAAK,EAAE,GAAG,MAAM,CAAC;AAAA,IAC1B;AAAA,EACF;AAGA,aAAW,SAAS,OAAQ,aAAY,OAAO,OAAO;AAGtD,SAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACvC,QAAM,SAA0B,CAAC;AACjC,MAAI,OAAO;AACX,aAAW,SAAS,QAAQ;AAC1B,QAAI,SAAS,eAAe,MAAM,WAAW,MAAM,MAAM,UAAU,MAAM,SAAS,CAAC,CAAC;AACpF,QAAI,OAAO,SAAS,aAAa;AAC/B,kBAAY,OAAO,KAAK,IAAI,SAAS,cAAc,IAAI,CAAC;AACxD,eAAS,eAAe,MAAM,WAAW,MAAM,MAAM,UAAU,MAAM,SAAS,CAAC,CAAC;AAChF,UAAI,OAAO,SAAS,YAAa;AAAA,IACnC;AACA,UAAM,WAAW,MAAM,WAAW,MAAM,MAAM,UAAU,MAAM,SAAS,CAAC;AACxE,WAAO,KAAK;AAAA,MACV,UAAU,MAAM;AAAA,MAChB,gBAAgB,SAAS,CAAC,EAAE;AAAA,MAC5B,cAAc,SAAS,SAAS,SAAS,CAAC,EAAE;AAAA,MAC5C;AAAA,MACA;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM;AAAA,IACf,CAAC;AACD,YAAQ;AAAA,EACV;AAEA,SAAO,EAAE,QAAQ,aAAa,KAAK;AACrC;AAGA,SAAS,YAAY,OAAqB,KAAmB;AAC3D,SAAO,eAAe,MAAM,WAAW,MAAM,MAAM,UAAU,MAAM,SAAS,CAAC,CAAC,IAAI,KAAK;AACrF,UAAM,cAAc,MAAM,eAAe,MAAM;AAC/C,UAAM,YAAY,MAAM,SAAS,MAAM;AACvC,QAAI,eAAe,KAAK,aAAa,EAAG;AACxC,QAAI,aAAa,YAAa,OAAM;AAAA,QAC/B,OAAM;AAAA,EACb;AACF;;;ACtMA,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBrB,SAAS,0BAA0B,IAAqB,QAA4C;AACzG,SAAO;AAAA,IACL,WAAW,MAAM;AAAA,IACjB,eAAe,MAAM;AACnB,YAAM,MAAM,GAAG,QAAQ,mCAAmC,EAAE,IAAI;AAChE,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,IACA,iBAAiB,MAAM;AACrB,YAAM,OAAO,GAAG,QAAQ,mBAAmB,EAAE,IAAI;AACjD,aAAO,KAAK,IAAI,CAAC,OAAO;AAAA,QACtB,IAAI,EAAE;AAAA,QACN,YAAY,EAAE,cAAc;AAAA,QAC5B,SAAS,EAAE;AAAA,QACX,IAAI,EAAE,KAAK;AAAA,QACX,MAAM,EAAE;AAAA,MACV,EAAE;AAAA,IACJ;AAAA,EACF;AACF;;;ACrCA,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2Bd,SAAS,+BAA+B,IAAyC;AACtF,SAAO;AAAA,IACL,UAAU,SAAiB,OAAkC;AAC3D,YAAM,OAAO,GAAG,QAAQ,YAAY,EAAE,IAAI,SAAS,OAAO,SAAS,KAAK;AACxE,aAAO,KAAK,IAAI,CAAC,OAAO;AAAA,QACtB,IAAI,EAAE;AAAA,QACN,YAAY,EAAE,cAAc;AAAA,QAC5B,SAAS,EAAE,WAAW;AAAA,QACtB,IAAI,EAAE,KAAK;AAAA,QACX,UAAU,EAAE,YAAY;AAAA,QACxB,kBAAkB,EAAE,oBAAoB;AAAA,MAC1C,EAAE;AAAA,IACJ;AAAA,EACF;AACF;;;ACxCO,SAAS,mBAAmB,OAAyB;AAC1D,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO,CAAC;AACtB,MAAI;AACF,UAAM,SAAS,SAAS,EACrB,IAAI,SAAS,KAAK,EAClB,IAAI,CAACC,OAAMA,GAAE,KAAK,CAAC,EACnB,OAAO,CAACA,OAAMA,GAAE,SAAS,CAAC;AAC7B,WAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;AAAA,EAC5B,QAAQ;AACN,WAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,MAAM,KAAK,EAAE,OAAO,CAACA,OAAMA,GAAE,SAAS,CAAC,CAAC,CAAC;AAAA,EACtE;AACF;AAEO,SAAS,wBAAwB,IAAkC;AACxE,SAAO;AAAA,IACL,OAAO,OAAe,MAAiD;AACrE,YAAM,WAAW,mBAAmB,KAAK;AACzC,UAAI,SAAS,WAAW,EAAG,QAAO,CAAC;AACnC,YAAM,EAAE,OAAO,IAAI,YAAY,IAAI,UAAU,MAAM,CAAC;AACpD,UAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,YAAM,eAAe,OAAO,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACpD,YAAM,SAAS,GAAG,QAAQ,2CAA2C,YAAY,GAAG,EAAE,IAAI,GAAG,MAAM;AAInG,YAAM,SAAS,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,GAAI,CAAC,CAAC;AAC7D,aAAO,OAAO,QAAQ,CAAC,OAAO;AAC5B,cAAM,KAAK,OAAO,IAAI,EAAE;AACxB,eAAO,OAAO,SAAY,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;ApB+BO,IAAM,8BAA8B;AAOpC,SAAS,2BACd,aACA,QACAC,UACqB;AACrB,MAAI,OAAO;AACX,MAAIA,UAAS,UAAU,OAAO,SAAS,SAAS,OAAO,KAAK;AAC1D,UAAM,SAASA,SAAQ,oBAAoB;AAC3C,WAAO,6BAA6B,EAAE,MAAM,WAAW,UAAU,kBAAkB,KAAKA,SAAQ,OAAO,CAAC;AACxG,WAAO,EAAE,GAAG,QAAQ,KAAK,EAAE,GAAG,OAAO,KAAK,aAAa,4BAA4B,EAAE;AAAA,EACvF;AACA,SAAO,YAAY,IAAI,IAAI;AAC7B;AAGO,SAAS,8BACd,QACAC,iBAAoE,eAC3D;AACT,MAAI,OAAO,SAAS,SAAS,CAAC,OAAO,OAAO,CAAC,OAAO,IAAI,YAAa,QAAO;AAC5E,SAAOA,eAAc,kBAAkB,OAAO,IAAI,WAAW,MAAM;AACrE;AA2DA,IAAM,0BAA0B;AAOhC,IAAM,8BAA8B;AAE7B,SAAS,gCAAgC,iBAAkC;AAChF,SAAO,mBAAmB,kBAAkB,IACxC,KAAK,IAAI,6BAA6B,eAAe,IACrD;AACN;AAaA,SAAS,oBAAoB,QAAwB;AACnD,SAAOC,OAAK,SAAS,QAAQ,KAAK;AACpC;AAGO,IAAM,yBAAyB;AAC/B,IAAM,6BAA6B;AAQnC,SAAS,2BAA2B,QAOlB;AACvB,QAAM,EAAE,aAAa,IAAI;AACzB,SAAO,IAAI,qBAAqB;AAAA,IAC9B,cAAcA,OAAK,KAAK,aAAa,aAAa,GAAG,sBAAsB;AAAA,IAC3E,YAAYA,OAAK,KAAK,aAAa,aAAa,GAAG,0BAA0B;AAAA,IAC7E,gBAAgBA,OAAK,KAAK,aAAa,aAAa,GAAG,UAAU,gBAAgB;AAAA,IACjF,uBAAuB,OAAO;AAAA,IAC9B,gBAAgB,OAAO;AAAA,IACvB,eAAe,OAAO;AAAA,IACtB,eAAe,OAAO;AAAA,IACtB,qBAAqB,OAAO;AAAA,EAC9B,CAAC;AACH;AAEO,IAAM,uBAAN,MAA2B;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,WAAqC;AAAA,EACrC,kBAAiC;AAAA,EACjC,qBAAiE;AAAA;AAAA,EAGxD,iBAAiB;AAAA,EACjB,oBAAoB,yBAAyB,sBAAsB;AAAA,EAEpF,YAAYF,UAAsC;AAChD,SAAK,UAAUA;AACf,SAAK,iBAAiBA,SAAQ;AAC9B,SAAK,QAAQ,IAAI,oBAAoBA,SAAQ,cAAc;AAAA,MACzD,eAAeA,SAAQ;AAAA,MACvB,eAAeA,SAAQ;AAAA,IACzB,CAAC;AACD,SAAK,aAAa,IAAI,wBAAwBA,SAAQ,cAAc,EAAE,eAAeA,SAAQ,cAAc,CAAC;AAC5G,SAAK,cAAc,IAAI,yBAAyBA,SAAQ,UAAU;AAClE,SAAK,QAAQ,IAAI,sBAAsB,CAAC,QAAQ,KAAK,OAAO,GAAG,CAAC;AAAA,EAClE;AAAA;AAAA,EAIA,YAAiC;AAC/B,WAAO,KAAK,YAAY,IAAI;AAAA,EAC9B;AAAA;AAAA,EAGA,eAAwB;AACtB,WAAO,KAAK,YAAY,aAAa;AAAA,EACvC;AAAA;AAAA,EAGA,YAAqB;AACnB,WAAO,KAAK,YAAY,UAAU;AAAA,EACpC;AAAA;AAAA,EAGA,SAAkB;AAChB,QAAI,CAAC,KAAK,YAAY,OAAO,EAAG,QAAO;AACvC,UAAM,MAAM,KAAK,YAAY,IAAI;AACjC,WAAO,IAAI,SAAS,SAAS,iCAAiC,IAAI,KAAK,OAAO,KAAK,KAAK,UAAU;AAAA,EACpG;AAAA;AAAA,EAGA,YAAqB;AACnB,WAAO,8BAA8B,KAAK,YAAY,IAAI,GAAG,KAAK,QAAQ,qBAAqB,aAAa;AAAA,EAC9G;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAA6D;AAC3D,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU,QAAkCA,UAAoD;AAC9F,UAAM,QAAQ,2BAA2B,KAAK,aAAa,QAAQ;AAAA,MACjE,QAAQA,UAAS;AAAA,MACjB,kBAAkB,KAAK,QAAQ;AAAA,IACjC,CAAC;AACD,SAAK,WAAW;AAChB,SAAK,kBAAkB;AACvB,SAAK,qBAAqB;AAE1B,QAAI,CAAC,MAAM,SAAS;AAClB,iBAAW,SAAS,KAAK,WAAW,YAAY,EAAG,MAAK,MAAM,MAAM,MAAM,UAAU;AAAA,IACtF;AAEA,QAAI,MAAM,WAAW,0BAA0B,KAAK,KAAK,MAAM,SAAS,SAAS;AAC/E,YAAM,UAAU,MAAM,MAAM;AAC5B,YAAM,WAAW,KAAK,QAAQ;AAC9B,WAAK,qBAAqB;AAC1B,gBAAU,KAAK,kBAAkB,yCAAyC,EAAE,SAAS,SAAS,CAAC;AAC/F,WAAK,KAAK,YAAY,EACnB,UAAU,EACV,KAAK,MAAM;AACV,aAAK,qBAAqB;AAC1B,kBAAU,KAAK,kBAAkB,2CAA2C,EAAE,SAAS,SAAS,CAAC;AAAA,MACnG,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,aAAK,qBAAqB;AAC1B,kBAAU,MAAM,kBAAkB,yCAAyC,OAAO,IAAI,KAAK;AAAA,MAC7F,CAAC;AAAA,IACL;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAyB;AAC/B,WAAO,KAAK,YAAY,eAAe;AAAA,EACzC;AAAA,EAEQ,cAAiC;AACvC,UAAM,UAAU,KAAK,eAAe;AACpC,QAAI,CAAC,KAAK,YAAY,KAAK,oBAAoB,SAAS;AACtD,WAAK,WAAW,eAAe,KAAK,YAAY,IAAI,GAAG;AAAA,QACrD,gBAAgB,KAAK,QAAQ;AAAA,QAC7B,uBAAuB,KAAK,QAAQ;AAAA,QACpC,GAAG,KAAK,QAAQ;AAAA,MAClB,CAAC;AACD,WAAK,kBAAkB;AAAA,IACzB;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAIQ,QAAQ,WAA2B;AACzC,WAAO,kBAAkB,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,YAAY,WAA2B;AAC7C,UAAM,OAAO,KAAK,QAAQ,SAAS;AACnC,QAAI,KAAK,WAAW,SAAS,IAAI,EAAG,QAAO;AAC3C,UAAM,SAAS,KAAK,WAAW,QAAQ,EAAE,KAAK,CAAC,MAAM,oBAAoB,EAAE,MAAM,MAAM,SAAS;AAChG,WAAO,QAAQ,cAAc;AAAA,EAC/B;AAAA;AAAA,EAGQ,QAAQ,OAAkC,gBAAiC;AACjF,WACE,MAAM,YAAY,kBAClB,MAAM,mBAAmB,KAAK,kBAC9B,MAAM,sBAAsB,KAAK;AAAA,EAErC;AAAA,EAEQ,aACN,QACA,YAOA;AACA,WAAO;AAAA,MACL,YAAY,cAAc,kBAAkB,oBAAoB,MAAM,CAAC;AAAA,MACvE;AAAA,MACA,SAAS,KAAK,eAAe;AAAA,MAC7B,gBAAgB,KAAK;AAAA,MACrB,mBAAmB,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,OAAO,WAAyB;AAC9B,QAAI,CAAC,KAAK,OAAO,EAAG;AACpB,UAAM,SAAS,KAAK,eAAe,UAAU,SAAS;AACtD,UAAM,OAAO,kBAAkB,SAAS;AACxC,UAAM,WAAW,KAAK,WAAW,SAAS,IAAI;AAC9C,UAAM,QAAQ,CAAC,CAAC,YAAY,KAAK,QAAQ,UAAU,KAAK,eAAe,CAAC;AACxE,QAAI,OAAO;AAIT,WAAK,WAAW,WAAW,MAAM,MAAM;AACvC,WAAK,WAAW,eAAe,MAAM,MAAM;AAAA,IAC7C,OAAO;AACL,WAAK,WAAW,OAAO,KAAK,aAAa,MAAM,CAAC;AAAA,IAClD;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,WAAyB;AAC9B,UAAM,OAAO,KAAK,YAAY,SAAS;AACvC,SAAK,MAAM,OAAO,IAAI;AACtB,SAAK,MAAM,mBAAmB,IAAI;AAClC,SAAK,WAAW,OAAO,IAAI;AAAA,EAC7B;AAAA;AAAA,EAGA,MAAM,WAAyB;AAC7B,QAAI,CAAC,KAAK,OAAO,EAAG;AACpB,SAAK,MAAM,QAAQ,EAAE,MAAM,SAAS,YAAY,KAAK,YAAY,SAAS,EAAE,CAAC;AAAA,EAC/E;AAAA,EAEA,MAAM,WAAyB;AAC7B,SAAK,MAAM,MAAM,KAAK,YAAY,SAAS,CAAC;AAAA,EAC9C;AAAA,EAEA,OAAO,WAAyB;AAC9B,SAAK,MAAM,OAAO,KAAK,YAAY,SAAS,CAAC;AAAA,EAC/C;AAAA;AAAA,EAGA,QAAQ,WAAyB;AAC/B,QAAI,CAAC,KAAK,OAAO,EAAG;AACpB,UAAM,SAAS,KAAK,eAAe,UAAU,SAAS;AACtD,UAAM,OAAO,KAAK,YAAY,SAAS;AACvC,SAAK,MAAM,OAAO,IAAI;AACtB,SAAK,WAAW,OAAO,KAAK,aAAa,QAAQ,IAAI,CAAC;AACtD,SAAK,MAAM,QAAQ,EAAE,MAAM,WAAW,YAAY,KAAK,CAAC;AAAA,EAC1D;AAAA;AAAA,EAGA,kBAAwB;AACtB,QAAI,CAAC,KAAK,OAAO,EAAG;AACpB,UAAM,UAAU,KAAK,eAAe;AACpC,eAAW,SAAS,KAAK,WAAW,YAAY,GAAG;AACjD,UAAI,MAAM,gBAAgB,UAAW;AACrC,UAAI,KAAK,QAAQ,OAAO,OAAO,GAAG;AAChC,aAAK,MAAM,QAAQ,EAAE,MAAM,WAAW,YAAY,MAAM,WAAW,CAAC;AAAA,MACtE,WAAW,MAAM,gBAAgB,aAAa;AAC5C,aAAK,MAAM,QAAQ,EAAE,MAAM,SAAS,YAAY,MAAM,WAAW,CAAC;AAAA,MACpE,OAAO;AAEL,cAAM,YAAY,oBAAoB,MAAM,MAAM;AAClD,cAAM,KAAK,KAAK,eAAe,aAAa,SAAS;AACrD,YAAI,IAAI;AACN,gBAAM,YAAa,GAAG,QAAQ,mCAAmC,EAAE,IAAI,GAAiC,KAAK;AAC7G,cAAI,YAAY,MAAM,eAAe;AACnC,iBAAK,MAAM,QAAQ,EAAE,MAAM,SAAS,YAAY,MAAM,WAAW,CAAC;AAAA,UACpE,WAAW,YAAY,MAAM,eAAe;AAE1C,iBAAK,MAAM,QAAQ,EAAE,MAAM,WAAW,YAAY,MAAM,WAAW,CAAC;AAAA,UACtE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIQ,SAAS,OAAkC,gBAAoD;AACrG,UAAM,eAAe,MAAM,WAAW,KAAK,QAAQ,OAAO,cAAc;AACxE,UAAM,WAAW,MAAM,gBAAgB,IAAI,KAAK,IAAI,GAAG,MAAM,kBAAkB,MAAM,aAAa,IAAI;AACtG,WAAO;AAAA,MACL,WAAW,oBAAoB,MAAM,MAAM;AAAA,MAC3C,SAAS,MAAM;AAAA,MACf,aAAa,MAAM;AAAA,MACnB;AAAA,MACA,eAAe,MAAM;AAAA,MACrB,iBAAiB,MAAM;AAAA,MACvB,YAAY,MAAM;AAAA,MAClB;AAAA,MACA,QAAQ,KAAK,MAAM,SAAS,MAAM,UAAU;AAAA,MAC5C,SAAS,KAAK,MAAM,UAAU,MAAM,UAAU;AAAA,MAC9C,SAAS,MAAM,gBAAgB;AAAA,MAC/B,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,OAAO,WAAsD;AAC3D,UAAM,QAAQ,KAAK,WAAW,SAAS,KAAK,YAAY,SAAS,CAAC;AAClE,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,KAAK,SAAS,OAAO,KAAK,eAAe,CAAC;AAAA,EACnD;AAAA;AAAA,EAGA,kBAAkB,YAAoD;AACpE,UAAM,UAAU,KAAK,eAAe;AACpC,UAAM,SAAuC,CAAC;AAC9C,eAAW,aAAa,YAAY;AAClC,YAAM,QAAQ,KAAK,WAAW,SAAS,KAAK,YAAY,SAAS,CAAC;AAClE,UAAI,MAAO,QAAO,KAAK,KAAK,SAAS,OAAO,OAAO,CAAC;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AAAA,EAEA,sBAAoD;AAClD,UAAM,UAAU,KAAK,eAAe;AACpC,WAAO,KAAK,WAAW,YAAY,EAAE,IAAI,CAAC,MAAM;AAC9C,YAAM,SAAS,KAAK,SAAS,GAAG,OAAO;AACvC,UAAI,EAAE,gBAAgB,eAAe,CAAC,OAAO,gBAAgB,CAAC,OAAO,UAAU,CAAC,OAAO,SAAS;AAC9F,cAAM,YAAY,oBAAoB,EAAE,MAAM;AAC9C,cAAM,KAAK,KAAK,eAAe,aAAa,SAAS;AACrD,YAAI,IAAI;AACN,gBAAM,YAAa,GAAG,QAAQ,mCAAmC,EAAE,IAAI,GAAiC,KAAK;AAC7G,cAAI,cAAc,EAAE,cAAe,QAAO,iBAAiB;AAAA,QAC7D;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,OACJ,WACA,OACAA,UAC+B;AAC/B,QAAI,CAAC,KAAK,OAAO;AACf,aAAO,EAAE,WAAW,OAAO,QAAQ,YAAY,QAAQ,CAAC,GAAG,UAAU,GAAG,SAAS,OAAO,UAAU,EAAE;AAEtG,UAAM,OAAO,KAAK,YAAY,SAAS;AACvC,UAAM,QAAQ,KAAK,WAAW,SAAS,IAAI;AAC3C,UAAM,UAAU,KAAK,eAAe;AAEpC,QAAI,CAAC,SAAS,CAAC,MAAM;AACnB,aAAO,EAAE,WAAW,OAAO,QAAQ,YAAY,QAAQ,CAAC,GAAG,UAAU,GAAG,SAAS,OAAO,UAAU,EAAE;AACtG,QAAI,KAAK,QAAQ,OAAO,OAAO;AAC7B,aAAO,EAAE,WAAW,OAAO,QAAQ,iBAAiB,QAAQ,CAAC,GAAG,UAAU,GAAG,SAAS,MAAM,UAAU,EAAE;AAE1G,UAAM,MAAM,KAAK,MAAM,OAAO,MAAM,OAAO;AAC3C,QAAI,CAAC,OAAO,MAAM,eAAe;AAC/B,aAAO;AAAA,QACL,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,QAAQ,CAAC;AAAA,QACT,UAAU;AAAA,QACV,SAAS,MAAM,gBAAgB;AAAA,QAC/B,UAAU;AAAA,MACZ;AAEF,UAAM,KAAK,KAAK,eAAe,aAAa,SAAS;AACrD,QAAI,CAAC,GAAI,QAAO,EAAE,WAAW,OAAO,QAAQ,aAAa,QAAQ,CAAC,GAAG,UAAU,GAAG,SAAS,OAAO,UAAU,EAAE;AAE9G,UAAM,WAAW,KAAK,YAAY;AAClC,UAAM,MAAM,wBAAwB,EAAE;AACtC,UAAM,YAAYA,UAAS,aAAa;AAExC,UAAM,gBAAgB,KAAK,IAAI,IAAI,YAAY,CAAC;AAChD,UAAM,OAAO,MAAM;AAAA,MACjB,EAAE,UAAU,OAAO,KAAK,OAAO,IAAI;AAAA,MACnC;AAAA,QACE;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,aAAaA,UAAS;AAAA,MACxB;AAAA,IACF;AAEA,UAAM,eAA8B,KAAK,IAAI,CAAC,OAAO;AAAA,MACnD,SAAS,EAAE;AAAA,MACX,OAAO,EAAE;AAAA,MACT,UAAU,EAAE,OAAO;AAAA,MACnB,gBAAgB,EAAE,OAAO;AAAA,MACzB,cAAc,EAAE,OAAO;AAAA,MACvB,SAAS,EAAE,OAAO;AAAA,MAClB,OAAO,EAAE,OAAO;AAAA,IAClB,EAAE;AACF,UAAM,SAAS,+BAA+B,EAAE;AAChD,UAAM,EAAE,OAAO,IAAI,iBAAiB,QAAQ,cAAcA,UAAS,MAAM;AAEzE,UAAM,WAAW,MAAM,gBAAgB,IAAI,KAAK,IAAI,GAAG,MAAM,kBAAkB,MAAM,aAAa,IAAI;AACtG,WAAO,EAAE,WAAW,MAAM,QAAQ,UAAU,SAAS,MAAM,gBAAgB,aAAa,UAAU,KAAK,OAAO;AAAA,EAChH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,WAA4B;AACpC,QAAI,CAAC,KAAK,OAAO,EAAG,QAAO;AAC3B,UAAM,OAAO,KAAK,YAAY,SAAS;AACvC,UAAM,QAAQ,KAAK,WAAW,SAAS,IAAI;AAC3C,QAAI,CAAC,SAAS,CAAC,MAAM,QAAS,QAAO;AACrC,UAAM,UAAU,KAAK,eAAe;AACpC,QAAI,KAAK,QAAQ,OAAO,OAAO,EAAG,QAAO;AACzC,QAAI,MAAM,cAAc,EAAG,QAAO;AAClC,WAAO,CAAC,CAAC,KAAK,MAAM,OAAO,MAAM,OAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cACJ,WACA,OACAA,UACmC;AACnC,UAAM,cAAc,CAAC,QAAgB,UAAU,WAAqC;AAAA,MAClF,WAAW;AAAA,MACX;AAAA,MACA,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU;AAAA,MACV;AAAA,MACA,UAAU;AAAA,MACV,WAAW;AAAA,MACX,SAAS,CAAC;AAAA,IACZ;AAEA,UAAM,IAAI,OAAO,KAAK;AACtB,QAAI,CAAC,EAAG,QAAO,YAAY,aAAa;AAExC,UAAM,oBAAoB,sBAAsB,KAAK,YAAY,IAAI,EAAE,gBAAgB;AACvF,UAAM,YAAYA,UAAS,cAAc;AACzC,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,6BAA6B,KAAK,MAAM,SAAS,CAAC,CAAC;AAI1F,UAAM,iBAAiB,gCAAgCA,UAAS,eAAe;AAE/E,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,KAAK,OAAO,WAAW,GAAG;AAAA,QACvC;AAAA,QACA,QAAQ,EAAE,WAAW,WAAW,aAAa,eAAe;AAAA,QAC5D,aAAaA,UAAS;AAAA,MACxB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,aAAO,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACrE;AACA,QAAI,CAAC,OAAO,UAAW,QAAO,YAAY,OAAO,UAAU,eAAe,OAAO,OAAO;AAGxF,UAAM,mBAAmBA,UAAS,mBAC7B,EAAE,mBAAmB,CAAC,GAAG,kBAAkB,CAAC,GAAG,GAAGA,SAAQ,iBAAiB,IAC5E;AACJ,UAAM,iBAAiB,CAAC,CAAC,kBAAkB;AAC3C,UAAM,SAASA,UAAS;AACxB,UAAM,kBAAkBA,UAAS;AAEjC,UAAM,UAAsC,CAAC;AAC7C,UAAM,aAAuB,CAAC;AAE9B,UAAM,UAAU,oBAAI,IAAmD;AACvE,QAAI,WAAW,OAAO;AACtB,QAAI,SAAS,OAAO;AAEpB,eAAW,SAAS,OAAO,QAAQ;AACjC,YAAM,cAAuC,MAAM,SAAS,IAAI,CAAC,OAAO;AAAA,QACtE,IAAI,EAAE;AAAA,QACN,UAAU,EAAE;AAAA,QACZ,YAAY,EAAE;AAAA,QACd,kBAAkB,EAAE;AAAA,QACpB,SAAS,EAAE;AAAA;AAAA,QAEX,WAAW,KAAK,MAAM,EAAE,KAAK,GAAI;AAAA,MACnC,EAAE;AACF,iBAAW,KAAK,MAAM,UAAU;AAC9B,YAAI,EAAE,YAAY,QAAQ,CAAC,QAAQ,IAAI,EAAE,QAAQ,GAAG;AAClD,kBAAQ,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,YAAY,EAAE,iBAAiB,CAAC;AAAA,QAChF;AAAA,MACF;AAEA,YAAM,EAAE,QAAQ,IAAI,2BAA2B;AAAA,QAC7C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,YAAM,QAAQ,MAAM,QAAQ,QAAQ,QAAQ,IAAK,QAAQ,WAAwB,CAAC;AAClF,YAAM,WAAW,MAAM,KAAK,IAAI,EAAE,KAAK;AACvC,YAAM,QAAQ,MAAM,SAAS,CAAC;AAC9B,YAAM,OAAO,MAAM,SAAS,MAAM,SAAS,SAAS,CAAC;AACrD,YAAM,YAAY,QAAQ,IAAI,KAAK,MAAM,EAAE,EAAE,YAAY,IAAI;AAC7D,YAAM,UAAU,OAAO,IAAI,KAAK,KAAK,EAAE,EAAE,YAAY,IAAI;AACzD,iBAAW,KAAK,MAAM,UAAU;AAC9B,YAAI,EAAE,KAAK,SAAU,YAAW,EAAE;AAClC,YAAI,EAAE,KAAK,OAAQ,UAAS,EAAE;AAAA,MAChC;AACA,YAAM,UACJ,SAAS,SAAS,0BAA0B,GAAG,SAAS,MAAM,GAAG,uBAAuB,CAAC,WAAM;AACjG,cAAQ,KAAK;AAAA,QACX,gBAAgB,MAAM;AAAA,QACtB,cAAc,MAAM;AAAA,QACpB,OAAO,MAAM;AAAA,QACb,UAAU,MAAM;AAAA,QAChB;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA;AAAA,MACF,CAAC;AACD,YAAM,aAAa,aAAa,UAAU,GAAG,SAAS,MAAM,OAAO,KAAK;AACxE,iBAAW,KAAK,OAAO,UAAU;AAAA,EAAK,QAAQ,EAAE;AAAA,IAClD;AAEA,QAAI,OAAO,WAAW,KAAK,MAAM;AACjC,QAAI,kBAAkB,QAAQ,OAAO,GAAG;AACtC,YAAM,UAAU,CAAC,GAAG,QAAQ,QAAQ,CAAC,EAAE;AAAA,QACrC,CAAC,CAAC,IAAI,EAAE,MAAM,WAAW,CAAC,MACxB,IAAI,EAAE,IAAI,IAAI,GAAG,mBAAmB,eAAe,kBAAkB,YAAY,EAAE;AAAA,MACvF;AACA,aAAO,cAAc,QAAQ,KAAK,KAAK,CAAC;AAAA;AAAA,EAAO,IAAI;AAAA,IACrD;AAEA,UAAM,YACJ,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,MAAM,IAC/C,EAAE,UAAU,IAAI,KAAK,QAAQ,EAAE,YAAY,GAAG,QAAQ,IAAI,KAAK,MAAM,EAAE,YAAY,EAAE,IACrF;AAGN,UAAM,YAAY,OAAO,YAAY;AAErC,WAAO;AAAA,MACL,WAAW;AAAA,MACX;AAAA,MACA,UAAU,OAAO,OAAO;AAAA,MACxB,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,gBAAqC;AACnC,QAAI,UAAU;AAGd,UAAM,mBAAmB,IAAI,IAAI,KAAK,eAAe,eAAe,CAAC;AACrE,eAAW,SAAS,KAAK,WAAW,QAAQ,GAAG;AAC7C,UAAI,MAAM,kBAAkB,aAAa,CAAC,iBAAiB,IAAI,oBAAoB,MAAM,MAAM,CAAC,GAAG;AACjG,aAAK,MAAM,OAAO,MAAM,UAAU;AAClC,aAAK,WAAW,QAAQ,MAAM,UAAU;AAAA,MAC1C;AAAA,IACF;AAEA,eAAW,SAAS,KAAK,WAAW,mBAAmB,GAAG;AACxD,WAAK,MAAM,OAAO,MAAM,UAAU;AAClC,WAAK,MAAM,mBAAmB,MAAM,UAAU;AAC9C,WAAK,WAAW,cAAc,MAAM,UAAU;AAC9C,WAAK,WAAW,iBAAiB,MAAM,YAAY,MAAM;AACzD;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,IAAI,KAAK,WAAW,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC;AACxE,eAAW,QAAQ,KAAK,MAAM,iBAAiB,GAAG;AAChD,UAAI,CAAC,MAAM,IAAI,IAAI,GAAG;AACpB,aAAK,MAAM,mBAAmB,IAAI;AAClC;AAAA,MACF;AAAA,IACF;AACA,WAAO,EAAE,QAAQ;AAAA,EACnB;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,eAAW,SAAS,KAAK,WAAW,QAAQ,GAAG;AAC7C,UAAI,MAAM,gBAAgB,WAAW;AACnC,aAAK,WAAW,eAAe,MAAM,YAAY,QAAQ;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIQ,cAAc,IAAkC;AACtD,QAAI;AACF,YAAM,MAAM,GAAG,QAAQ,qCAAqC,EAAE,IAAI;AAGlE,YAAM,OAA4B,KAAK,SAAS,UAAU,UAAU;AACpE,aAAO,EAAE,OAAO,KAAK,QAAQ,IAAI,KAAK;AAAA,IACxC,QAAQ;AACN,aAAO,EAAE,OAAO,IAAI,MAAM,UAAU;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAc,OAAO,KAAgC;AACnD,UAAM,EAAE,KAAK,UAAU,IAAI;AAC3B,QAAI,IAAI,SAAS,WAAW;AAC1B,WAAK,cAAc;AACnB;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,WAAW,SAAS,IAAI,UAAU;AACrD,QAAI,CAAC,SAAS,CAAC,MAAM,QAAS;AAC9B,UAAM,YAAY,oBAAoB,MAAM,MAAM;AAElD,UAAM,KAAK,KAAK,eAAe,aAAa,SAAS;AACrD,QAAI,CAAC,IAAI;AACP,WAAK,WAAW,eAAe,IAAI,YAAY,UAAU,4BAA4B;AACrF;AAAA,IACF;AAEA,QAAI,IAAI,SAAS,WAAW;AAC1B,WAAK,MAAM,mBAAmB,IAAI,UAAU;AAC5C,WAAK,WAAW,cAAc,IAAI,UAAU;AAE5C,WAAK,WAAW,iBAAiB,IAAI,YAAY;AAAA,QAC/C,SAAS,KAAK,eAAe;AAAA,QAC7B,gBAAgB,KAAK;AAAA,QACrB,mBAAmB,KAAK;AAAA,MAC1B,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,0BAA0B,IAAI,KAAK,cAAc,EAAE,CAAC;AACnE,UAAM,UAAU;AAAA,MACd,YAAY,IAAI;AAAA,MAChB,SAAS,KAAK,eAAe;AAAA,MAC7B,UAAU,KAAK,YAAY;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,WAA0B;AACxB,WAAO,KAAK,MAAM,SAAS;AAAA,EAC7B;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AACjB,SAAK,WAAW,MAAM;AAAA,EACxB;AACF;;;AqB9zBO,SAAS,qBAAqB,cAAwD;AAC3F,SAAO;AAAA,IACL,WAAW,aAAa,aAAa;AAAA,IACrC,aAAa,aAAa,eAAe;AAAA,IACzC,aAAa,aAAa,eAAe;AAAA,IACzC,WAAW,aAAa,aAAa;AAAA,IACrC,WAAW,aAAa,aAAa;AAAA,IACrC,aAAa,aAAa,eAAe;AAAA,IACzC,UAAU,aAAa,YAAY;AAAA,IACnC,SAAS,aAAa,WAAW;AAAA,IACjC,SAAS,aAAa,WAAW;AAAA,IACjC,cAAc,aAAa,gBAAgB;AAAA,EAC7C;AACF;AAEO,IAAM,qBAAN,MAAiD;AAAA,EACtD,YAA6B,OAAmC;AAAnC;AAAA,EAAoC;AAAA,EAApC;AAAA,EAE7B,eAAuB;AACrB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,iBAAyB;AACvB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,iBAAyB;AACvB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,iBAAyB;AACvB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,cAAsB;AACpB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,aAAqB;AACnB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,aAAqB;AACnB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,kBAA0B;AACxB,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;;;AC7DO,SAAS,6BAA6B,WAAmD;AAC9F,SAAO;AAAA,IACL,gBAAgB,MAAM,UAAU,eAAe;AAAA,IAC/C,cAAc,CAAC,cAAc,UAAU,KAAK,SAAS;AAAA,IACrD,cAAc,CAAC,cAAc,UAAU,aAAa,SAAS;AAAA,IAC7D,cAAc,CAAC,cAAc,UAAU,MAAM,SAAS;AAAA,IACtD,WAAW,CAAC,cAAc,UAAU,UAAU,SAAS;AAAA,IAEvD,kBAAkB,WAA4B;AAC5C,aAAO,UAAU,2BAA2B,SAAS;AAAA,IACvD;AAAA,IAEA,eAAe,WAAoC;AACjD,YAAM,KAAK,UAAU,KAAK,SAAS;AACnC,UAAI,CAAC,IAAI;AACP,cAAM,OAAO,OAAO,IAAI,MAAM,sBAAsB,SAAS,EAAE,GAAG,EAAE,YAAY,IAAI,CAAC;AAAA,MACvF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,eAAe,WAAoC;AACjD,YAAM,KAAK,UAAU,aAAa,SAAS;AAC3C,UAAI,CAAC,IAAI;AACP,cAAM,OAAO,OAAO,IAAI,MAAM,sBAAsB,SAAS,EAAE,GAAG,EAAE,YAAY,IAAI,CAAC;AAAA,MACvF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AC/BA,YAAYG,UAAQ;AACpB,YAAYC,YAAU;AACtB,YAAY,YAAY;AAWjB,IAAM,oBAAN,MAAwB;AAAA,EACrB,QAAQ,oBAAI,IAAwB;AAAA,EACpC;AAAA,EACA;AAAA,EAER,YAAY,cAA4BC,UAAsC;AAC5E,SAAK,UAAU,aAAa,WAAW;AACvC,SAAK,gBAAgBA,UAAS;AAC9B,QAAI,CAAI,gBAAW,KAAK,OAAO,GAAG;AAChC,MAAG,eAAU,KAAK,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,mBAAmB,UAA0B;AAC3C,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC;AAClD,UAAM,WAAW,SAAS,QAAQ,kBAAkB,GAAG,EAAE,MAAM,GAAG,EAAE;AACpE,WAAY,YAAK,KAAK,SAAS,SAAS,QAAQ,IAAI,EAAE,IAAI,IAAI,KAAK;AAAA,EACrE;AAAA;AAAA,EAGA,MAAM,UAAkB,YAA4B;AAClD,UAAM,SAAgB,kBAAW;AACjC,SAAK,MAAM,IAAI,QAAQ,EAAE,YAAY,UAAU,WAAW,KAAK,IAAI,EAAE,CAAC;AACtE,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,WAAW,QAAmE;AAC5E,UAAM,QAAQ,KAAK,MAAM,IAAI,MAAM;AACnC,QAAI,CAAC,SAAS,CAAI,gBAAW,MAAM,UAAU,EAAG,QAAO;AACvD,UAAM,UAAU,yBAAyB,MAAM,YAAY;AAAA,MACzD,UAAU;AAAA,MACV,eAAe,KAAK;AAAA,IACtB,CAAC;AACD,WAAO,EAAE,QAAQ,IAAI,aAAa,OAAO,GAAG,UAAU,MAAM,SAAS;AAAA,EACvE;AAAA;AAAA,EAGA,mBAAmB,UAA+D;AAChF,UAAM,aAAa,KAAK,mBAAmB,QAAQ;AACnD,UAAM,UAAU,yBAAyB,YAAY,EAAE,eAAe,KAAK,cAAc,CAAC;AAC1F,WAAO,EAAE,IAAI,SAAS,WAAW;AAAA,EACnC;AAAA;AAAA,EAGA,OAAO,QAAsB;AAC3B,UAAM,QAAQ,KAAK,MAAM,IAAI,MAAM;AACnC,QAAI,OAAO;AACT,yBAAmB,MAAM,UAAU;AACnC,WAAK,MAAM,OAAO,MAAM;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,eAAW,SAAS,KAAK,MAAM,OAAO,GAAG;AACvC,yBAAmB,MAAM,UAAU;AAAA,IACrC;AACA,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA;AAAA,EAGA,iBAAuB;AACrB,yBAAqB,KAAK,OAAO;AAAA,EACnC;AAAA;AAAA,EAGA,eAAe,WAAW,MAAgB;AACxC,UAAM,MAAM,KAAK,IAAI;AACrB,eAAW,CAAC,QAAQ,KAAK,KAAK,KAAK,OAAO;AACxC,UAAI,MAAM,MAAM,YAAY,UAAU;AACpC,2BAAmB,MAAM,UAAU;AACnC,aAAK,MAAM,OAAO,MAAM;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACF;;;AC3FA,YAAYC,UAAQ;AAcpB,IAAM,mBAAmB,oBAAI,IAAY;AACzC,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAqD3B,SAAS,gBAAgB,SAA4B,OAA+B;AAClF,QAAM,WAAW,QAAQ;AACzB,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,EAAG,QAAO;AAE9D,MAAI,OAAO;AACT,QAAI,CAAC,QAAQ,QAAS,QAAO;AAC7B,QAAI,CAAC,QAAQ,KAAM,QAAO;AAC1B,UAAM,IAAI,QAAQ;AAClB,QAAI,CAAC,EAAE,KAAM,QAAO;AACpB,QAAI,CAAC,EAAE,SAAU,QAAO;AACxB,QAAI,CAAC,EAAE,KAAM,QAAO;AAAA,EACtB;AAEA,QAAM,EAAE,gBAAgB,iBAAiB,IAAI,QAAQ,WAAW,CAAC;AACjE,MAAI,mBAAmB,UAAa,mBAAmB,WAAW,mBAAmB,QAAQ;AAC3F,WAAO;AAAA,EACT;AACA,MAAI,qBAAqB,UAAa,qBAAqB,YAAY,qBAAqB,QAAQ;AAClG,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,YAAY,UAAa,CAAC,MAAM,QAAQ,QAAQ,OAAO,EAAG,QAAO;AAC7E,MAAI,QAAQ,SAAS;AACnB,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,QAAQ,KAAK;AAC/C,UAAI,OAAO,QAAQ,QAAQ,CAAC,EAAE,eAAe,YAAY,QAAQ,QAAQ,CAAC,EAAE,WAAW,WAAW;AAChG,eAAO,WAAW,CAAC;AAAA,IACvB;AAAA,EACF;AAEA,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,OAAO,IAAI,WAAW,YAAY,IAAI,OAAO,WAAW,EAAG,QAAO,YAAY,CAAC;AACnF,QAAI,OAAO,IAAI,cAAc,YAAY,IAAI,aAAa;AACxD,aAAO,YAAY,CAAC;AACtB,QAAI,OAAO,IAAI,SAAS,SAAU,QAAO,YAAY,CAAC;AACtD,QAAI,IAAI,YAAY,UAAa,IAAI,YAAY,QAAQ,OAAO,IAAI,YAAY;AAC9E,aAAO,YAAY,CAAC;AACtB,QAAI,IAAI,sBAAsB,UAAa,OAAO,IAAI,sBAAsB;AAC1E,aAAO,YAAY,CAAC;AACtB,QAAI,IAAI,qBAAqB,UAAa,OAAO,IAAI,qBAAqB;AACxE,aAAO,YAAY,CAAC;AAAA,EACxB;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,IAAkD;AACpE,QAAM,MAAM,GAAG,QAAQ,0EAA0E,EAAE,IAAI;AAKvG,QAAM,YAAY,GACf,QAAQ,0EAA0E,EAClF,IAAI,kBAAkB;AACzB,SAAO,EAAE,YAAY,IAAI,OAAO,aAAa,UAAU,KAAK,gBAAgB,IAAI,OAAO,eAAe,IAAI,KAAK;AACjH;AAEA,SAAS,2BAA2B,QAAgB,aAAqD;AACvG,SAAO,WAAW,mBAAmB,qBAAqB;AAC5D;AAEA,SAAS,6BAA6B,QAAgB,eAAuD;AAC3G,SAAO,WAAW,mBAAmB,qBAAqB;AAC5D;AAEA,SAAS,cACP,IACA,UACA,eACA,cAMA;AACA,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA;AAAA,EAEF;AACA,QAAM,cAAc,GAAG,QAAQ,6CAA6C;AAC5E,QAAM,sBAAsB,GAAG,QAAQ,wEAAwE;AAE/G,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,MAAI,eAAe;AACnB,MAAI,iBAAiB;AACrB,MAAI,eAAe;AACnB,QAAM,aAA4D,CAAC;AAEnE,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAErE,KAAG,YAAY,MAAM;AACnB,eAAW,OAAO,QAAQ;AACxB,YAAM,MAAM,mBAAmB,IAAI,WAAW,IAAI,QAAQ,IAAI,WAAW,IAAI;AAC7E,UAAI,IAAI,mBAAmB;AACzB,YAAI,cAAc,IAAI,IAAI,iBAAiB,GAAG;AAC5C;AACA;AAAA,QACF;AACA,YAAI,aAAa,IAAI,GAAG,GAAG;AACzB;AACA;AAAA,QACF;AACA,sBAAc,IAAI,IAAI,iBAAiB;AAGvC,qBAAa,IAAI,GAAG;AAAA,MACtB,OAAO;AACL,YAAI,aAAa,IAAI,GAAG,GAAG;AACzB;AACA;AAAA,QACF;AACA,qBAAa,IAAI,GAAG;AAAA,MACtB;AAEA,UAAI,WAAW,cAAc,IAAI,IAAI,MAAM;AAC3C,UAAI,CAAC,UAAU;AACb,4BAAoB,IAAI,IAAI,QAAQ,2BAA2B,IAAI,QAAQ,IAAI,WAAW,KAAK,IAAI;AACnG,cAAM,MAAM,YAAY,IAAI,IAAI,MAAM;AACtC,YAAI,KAAK;AACP,qBAAW,IAAI;AACf,wBAAc,IAAI,IAAI,QAAQ,QAAQ;AAAA,QACxC;AAAA,MACF;AACA,UAAI,CAAC,SAAU;AAEf,YAAM,SAAS,UAAU;AAAA,QACvB;AAAA,QACA,2BAA2B,IAAI,QAAQ,IAAI,WAAW,KAAK;AAAA,QAC3D,6BAA6B,IAAI,QAAQ,IAAI,aAAa,KAAK;AAAA,QAC/D,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI,WAAW;AAAA,QACf,IAAI,oBAAoB;AAAA,QACxB,IAAI,qBAAqB;AAAA,MAC3B;AACA,iBAAW,KAAK,EAAE,IAAI,OAAO,OAAO,eAAe,GAAG,SAAS,IAAI,WAAW,KAAK,CAAC;AACpF,UAAI,IAAI,YAAY,aAAc,gBAAe,IAAI;AACrD;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,cAAc,gBAAgB,cAAc,WAAW;AAClE;AAEA,SAAS,WACP,IACA,MACA,SACA,UACwE;AACxE,KAAG,KAAK,cAAc;AAItB,QAAM,WAAW,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC;AACzD,QAAM,eAAmC,CAAC;AAC1C,aAAW,OAAO,UAAU;AAC1B,QAAI,CAAC,SAAS,IAAI,IAAI,MAAM,GAAG;AAC7B,mBAAa,KAAK;AAAA,QAChB,YAAY,IAAI;AAAA,QAChB,aAAa,2BAA2B,IAAI,QAAQ,IAAI,WAAW;AAAA,QACnE,eAAe,6BAA6B,IAAI,QAAQ,IAAI,aAAa;AAAA,MAC3E,CAAC;AACD,eAAS,IAAI,IAAI,MAAM;AAAA,IACzB;AAAA,EACF;AACA,QAAM,aAAa,aAAa,SAAS,IAAI,CAAC,GAAG,SAAS,GAAG,YAAY,IAAI;AAG7E,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,WAAW,oBAAI,IAAY;AACjC,MAAI,iBAAiB;AACrB,QAAM,kBAAuC,CAAC;AAC9C,aAAW,OAAO,UAAU;AAC1B,UAAM,MAAM,mBAAmB,IAAI,WAAW,IAAI,QAAQ,IAAI,WAAW,IAAI;AAC7E,QAAI,IAAI,mBAAmB;AACzB,UAAI,MAAM,IAAI,IAAI,iBAAiB,GAAG;AACpC;AACA;AAAA,MACF;AACA,UAAI,SAAS,IAAI,GAAG,GAAG;AACrB;AACA;AAAA,MACF;AACA,YAAM,IAAI,IAAI,iBAAiB;AAC/B,eAAS,IAAI,GAAG;AAAA,IAClB,OAAO;AACL,UAAI,SAAS,IAAI,GAAG,GAAG;AACrB;AACA;AAAA,MACF;AACA,eAAS,IAAI,GAAG;AAAA,IAClB;AACA,oBAAgB,KAAK,GAAG;AAAA,EAC1B;AAIA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,WAAW,IAAI,CAAC,OAAO;AAAA,MACrB,GAAG;AAAA,MACH,aAAa,2BAA2B,EAAE,YAAY,EAAE,WAAW,KAAK,EAAE;AAAA,MAC1E,eAAe,6BAA6B,EAAE,YAAY,EAAE,aAAa;AAAA,IAC3E,EAAE;AAAA,IACF,gBAAgB,IAAI,CAAC,OAAO;AAAA,MAC1B,kBAAkB,EAAE;AAAA,MACpB,mBAAmB,2BAA2B,EAAE,QAAQ,EAAE,WAAW,KAAK,EAAE;AAAA,MAC5E,qBAAqB,6BAA6B,EAAE,QAAQ,EAAE,aAAa;AAAA,MAC3E,WAAW,EAAE;AAAA,MACb,MAAM,EAAE;AAAA,MACR,SAAS,EAAE,WAAW;AAAA,MACtB,mBAAmB,EAAE;AAAA,MACrB,kBAAkB,EAAE;AAAA,IACtB,EAAE;AAAA,EACJ;AAEA,gBAAc,EAAE;AAEhB,MAAI;AACF,yBAAqB,EAAE;AAAA,EACzB,QAAQ;AAAA,EAER;AAEA,SAAO,EAAE,cAAc,MAAM,cAAc,gBAAgB,cAAc,QAAQ,OAAO;AAC1F;AAEA,SAASC,mBACP,IACA,SAOA;AACA,QAAM,iBAAiB,QAAQ,SAAS,kBAAkB;AAC1D,QAAM,mBAAmB,QAAQ,SAAS,oBAAoB;AAG9D,QAAM,gBAAgB,oBAAI,IAAY;AACtC,QAAM,eAAe,oBAAI,IAAY;AACpC,EACC,GAAG,QAAQ,+EAA+E,EAAE,IAAI,EAGhG,QAAQ,CAAC,MAAM,cAAc,IAAI,EAAE,mBAAmB,CAAC;AAIxD,EACC,GACG,QAAQ,kGAAkG,EAC1G,IAAI,EACP,QAAQ,CAAC,MAAM,aAAa,IAAI,mBAAmB,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;AAErF,MAAI,cAAc;AAClB,MAAI,eAAe;AACnB,MAAI,iBAAiB;AAErB,MAAI,QAAQ,QAAQ,mBAAmB,SAAS;AAC9C,UAAM,IAAI,QAAQ;AAClB,OAAG;AAAA,MACD;AAAA,IACF,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,WAAW,IAAI,EAAE,eAAe,IAAI,EAAE,WAAW,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;AACxG,kBAAc;AAAA,EAChB;AAEA,MAAI,QAAQ,WAAW,qBAAqB,UAAU;AACpD,UAAM,eAAe,GAAG;AAAA,MACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMF;AACA,UAAM,cAAc,GAAG,QAAQ,6CAA6C;AAC5E,UAAM,oBAAoB,IAAI;AAAA,MAC3B,GAAG,QAAQ,gCAAgC,EAAE,IAAI,EAAqC,IAAI,CAAC,MAAM,EAAE,WAAW;AAAA,IACjH;AAEA,OAAG,YAAY,MAAM;AACnB,iBAAW,KAAK,QAAQ,SAAU;AAChC,cAAM,UAAU,kBAAkB,IAAI,EAAE,UAAU;AAClD,cAAM,cAAc,2BAA2B,EAAE,YAAY,EAAE,WAAW;AAC1E,cAAM,gBAAgB,6BAA6B,EAAE,YAAY,EAAE,aAAa;AAChF,qBAAa;AAAA,UACX,EAAE;AAAA,UACF,eAAe;AAAA,UACf,iBAAiB;AAAA,UACjB,EAAE,UAAU;AAAA,UACZ,EAAE,QAAQ,KAAK,UAAU,EAAE,KAAK,IAAI;AAAA,QACtC;AACA,YAAI,CAAC,SAAS;AACZ;AACA,gBAAM,MAAM,YAAY,IAAI,EAAE,UAAU;AACxC,cAAI,IAAK,mBAAkB,IAAI,EAAE,UAAU;AAAA,QAC7C,OAAO;AACL;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,gBACH,GAAG,QAAQ,uCAAuC,EAAE,IAAI,GAAiC,UAAU;AAEtG,QAAM,EAAE,cAAc,gBAAgB,cAAc,WAAW,IAAI;AAAA,IACjE;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACF;AAEA,MAAI,WAAW,SAAS,GAAG;AACzB,QAAI;AACF,uBAAiB,IAAI,UAAU;AAAA,IACjC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,eAAe,GAAG;AACpB,QAAI;AAGF,UAAI,eAAe,eAAe;AAChC,6BAAqB,EAAE;AAAA,MACzB,OAAO;AACL,wCAAgC,EAAE;AAAA,MACpC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,aAAa;AAChB,OAAG,QAAQ,iCAAiC,EAAE,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;AAAA,EACjF;AAEA,SAAO,EAAE,cAAc,gBAAgB,aAAa,cAAc,eAAe;AACnF;AAIA,IAAM,qBAAqB;AAE3B,eAAsB,WACpB,WACA,WACA,SAC4B;AAC5B,MAAI,CAAC,mBAAmB,KAAK,SAAS,KAAK,UAAU,SAAS,IAAI,GAAG;AACnE,WAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB,SAAS,wCAAwC;AAAA,EAClG;AAEA,MAAI,iBAAiB,IAAI,SAAS,GAAG;AACnC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,EACF;AAEA,QAAM,SAAS,UAAU,UAAU,SAAS;AAC5C,QAAM,QAAQ,CAAI,gBAAW,MAAM;AAEnC,QAAM,kBAAkB,gBAAgB,SAAS,KAAK;AACtD,MAAI,iBAAiB;AACnB,WAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB,SAAS,gBAAgB;AAAA,EAC1E;AAEA,mBAAiB,IAAI,SAAS;AAC9B,MAAI;AACF,QAAI,OAAO;AACT,YAAMC,MAAK,UAAU,uBAAuB,WAAW,EAAE,QAAQ,KAAK,CAAC;AACvE,UAAI;AACF,cAAM,EAAE,cAAc,gBAAgB,aAAa,IAAI;AAAA,UACrDA;AAAA,UACA,QAAQ;AAAA,UACR,QAAQ,WAAW,CAAC;AAAA,UACpB,QAAQ;AAAA,QACV;AACA,cAAM,UAAU,WAAWA,GAAE;AAC7B,kBAAU,oCAAoC;AAC9C,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,QAAQ;AAAA,YACN;AAAA,YACA,SAAS;AAAA,YACT,OAAO,EAAE,eAAe,QAAQ,SAAU,QAAQ,cAAc,eAAe;AAAA,YAC/E;AAAA,YACA,SAAS,EAAE,aAAa,MAAM,cAAc,gBAAgB,EAAE;AAAA,UAChE;AAAA,QACF;AAAA,MACF,UAAE;AACA,QAAAA,IAAG,MAAM;AAAA,MACX;AAAA,IACF;AAEA,UAAM,KAAK,UAAU,uBAAuB,WAAW,EAAE,UAAU,MAAM,CAAC;AAC1E,QAAI;AACF,YAAM,EAAE,cAAc,gBAAgB,aAAa,cAAc,eAAe,IAAID,mBAAkB,IAAI,OAAO;AACjH,YAAM,UAAU,WAAW,EAAE;AAC7B,gBAAU,oCAAoC;AAC9C,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,QAAQ;AAAA,UACN;AAAA,UACA,SAAS;AAAA,UACT,OAAO,EAAE,eAAe,QAAQ,SAAU,QAAQ,cAAc,eAAe;AAAA,UAC/E;AAAA,UACA,SAAS,EAAE,aAAa,cAAc,eAAe;AAAA,QACvD;AAAA,MACF;AAAA,IACF,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,SAAS,KAAc;AAGrB,QAAI,eAAe,0BAA2B,OAAM;AAEpD,QAAI,OAAO;AACT,UAAI;AACF,kBAAU,2BAA2B,SAAS;AAAA,MAChD,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,EACzG,UAAE;AACA,qBAAiB,OAAO,SAAS;AAAA,EACnC;AACF;","names":["options","options","t","options","path","options","result","options","fns","chain","options","options","options","path","options","options","fs","buffer","options","fs","openPromise","path","options","resolve","err","fs","path","fs","path","fs","path","fs","path","fs","path","fs","path","fs","os","path","options","fs","path","LogLevel","options","options","options","options","options","members","BATCH_SIZE","isChinese","chain","options","t","platform","platform","mergeMembers","fs","path","fs","path","feature","parser","fs","fs","path","streamJson","pickModule","streamValuesModule","streamChain","basename","options","resolve","pipeline","fs","path","extractNameFromFilePath","basename","feature","options","parser","module_","import_stream_json","import_Pick","import_StreamValues","import_stream_chain","fs","path","import_stream_json","import_Pick","import_StreamValues","import_stream_chain","fs","path","os","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","chain","streamChain","resolve","pipeline","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","chain","streamChain","feature","extractJsonObject","extractNameFromFilePath","basename","options","resolve","pipeline","parser_","module_","feature","parser_","fs","path","readline","feature","convertMessageType","options","parser_","module_","import_stream_json","import_Pick","import_StreamValues","import_stream_chain","fs","path","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","chain","streamChain","extractNameFromFilePath","basename","feature","convertMessageType","options","resolve","pipeline","parser_","feature","module_","feature","parser_","feature","parser_","module_","feature","parser_","import_stream_json","import_Pick","import_StreamValues","import_stream_chain","fs","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","chain","streamChain","feature","parseTimestamp","options","resolve","pipeline","parser_","module_","import_stream_chain","import_stream_json","import_Pick","import_StreamValues","fs","chain","streamChain","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","feature","resolve","pipeline","options","parser_","module_","import_stream_chain","import_stream_json","import_StreamValues","fs","chain","streamChain","parser","streamJson","streamValues","streamValuesModule","feature","options","resolve","pipeline","parser_","module_","fs","path","readline","extractNameFromFilePath","basename","feature","detectMessageType","options","parser_","module_","fs","path","readline","extractNameFromFilePath","basename","feature","detectMessageType","options","parser_","module_","fs","path","feature","extractNameFromFilePath","basename","isSystemMessage","detectMessageType","options","parser_","module_","import_stream_chain","import_stream_json","import_Pick","import_StreamValues","fs","path","chain","streamChain","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","feature","relative","readManifest","detectMessageType","options","pipeline","parser_","module_","fs","path","feature","extractNameFromFilePath","basename","detectMessageType","options","content","parser_","module_","feature","options","parser","fs","path","require","instance","options","isChinese","t","fs","path","createHash","createHash","fs","feature","options","trackError","errorCount","BATCH_SIZE","options","fs","path","options","import_stream_chain","import_stream_json","import_Pick","import_StreamValues","fs","path","chain","streamChain","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","pipeline","fs","path","randomUUID","options","resolver","randomUUID","fs","path","options","path","toSummary","fs","path","matter","t","Database","fs","path","options","t","toSummary","fs","builtin","t","fs","path","defaultLogger","options","t","options","options","defaultLogger","DEFAULT_CONTEXT_WINDOW","options","defaultLogger","options","options","completeSimple","extractJsonObject","ruleDecision","options","completeSimple","isRecord","extractJsonObject","resultToText","options","completeWithPiAi","completeSimple","options","t","options","formatNullableTimestamp","formatSharePercent","path","segment","options","options","segment","options","DEFAULT_CONTEXT_WINDOW","options","completeSimple","options","completeSimple","streamSimple","options","completeSimple","streamSimple","fs","path","ensureDir","fs","os","path","ensureDir","fs","path","fs","path","options","isRecord","hasColumn","hasTable","BATCH_SIZE","fs","path","options","queryMessages","fs","path","renameSession","updateSessionOwnerId","options","renameSession","updateSessionOwnerId","deleteMember","getMemberNameHistory","getMembers","getMembersPaginated","mergeMembers","updateMemberAliases","getMembers","getMembersPaginated","updateMemberAliases","mergeMembers","deleteMember","getMemberNameHistory","options","completeSimple","platform","fs","path","path","createHash","options","options","t","resolve","Database","platform","arch","os","options","Database","Database","options","Database","resolve","createHash","estimateTokens","sha256Hex","createHash","formatTime","formatTimeRange","estimateTokens","options","estimateTokens","t","options","resolveApiKey","path","fs","path","options","fs","incrementalImport","db"]}