chatlab-cli 0.23.1 → 0.24.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 (80) hide show
  1. package/dist/{chunk-643S2N2U.mjs → chunk-36W2HWQN.mjs} +2 -2
  2. package/dist/{chunk-AHQQQ5HZ.mjs → chunk-A6JCL4PT.mjs} +26 -14
  3. package/dist/chunk-A6JCL4PT.mjs.map +1 -0
  4. package/dist/{chunk-WTQ7LAZR.mjs → chunk-KCBIBRHL.mjs} +2 -2
  5. package/dist/{chunk-L37WJ33K.mjs → chunk-NE4A6PND.mjs} +1905 -1914
  6. package/dist/chunk-NE4A6PND.mjs.map +1 -0
  7. package/dist/{chunk-3MX7VF2B.mjs → chunk-RLCFXTTS.mjs} +2 -2
  8. package/dist/{chunk-G7Q7EQ3V.mjs → chunk-XJJXSOLO.mjs} +6199 -6988
  9. package/dist/chunk-XJJXSOLO.mjs.map +1 -0
  10. package/dist/cli.mjs +2 -2
  11. package/dist/{http-JDORENHT.mjs → http-OPQ352FZ.mjs} +6 -14
  12. package/dist/{import-LCBSBLYF.mjs → import-GZMDLK2E.mjs} +4 -4
  13. package/dist/index.mjs +8 -8
  14. package/dist/{mcp-AR7HBIK4.mjs → mcp-SHZ2CVXY.mjs} +3 -3
  15. package/dist/{service-WCGGRZTA.mjs → service-KZTHBDQ5.mjs} +3 -1
  16. package/dist/service-KZTHBDQ5.mjs.map +1 -0
  17. package/dist/{stream-import-QM5R5FDG.mjs → stream-import-RDDB3N7B.mjs} +3 -3
  18. package/dist-web/assets/{DebugToolsPanel-Yzfz2ntn.css → DebugToolsPanel-DaSNc1o-.css} +1 -1
  19. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-_gbTn1ha.js +161 -0
  20. package/dist-web/assets/{EChartWordcloud-Cenbzcyj.js → EChartWordcloud-VpNJDurs.js} +1 -1
  21. package/dist-web/assets/{MessageExportModal-DMHA4X0b.js → MessageExportModal-n4JgNg_F.js} +1 -1
  22. package/dist-web/assets/electron-BIWBW8k_.js +1 -0
  23. package/dist-web/assets/electron-Bt9dK4Sr.js +1 -0
  24. package/dist-web/assets/fetch-BOevuRK_.js +1 -0
  25. package/dist-web/assets/fetch-BhsrPRzn.js +3 -0
  26. package/dist-web/assets/fetch-ChECfwh7.js +1 -0
  27. package/dist-web/assets/fetch-ClDGP9DK.js +1 -0
  28. package/dist-web/assets/{fetch-CvfdN4gM.js → fetch-CnyupWWc.js} +1 -1
  29. package/dist-web/assets/fetch-CvaXKzA-.js +1 -0
  30. package/dist-web/assets/fetch-D1_jL6PO.js +1 -0
  31. package/dist-web/assets/fetch-DFWZ48Xw.js +1 -0
  32. package/dist-web/assets/fetch-DKD1bKt2.js +1 -0
  33. package/dist-web/assets/fetch-DwPB8NOH.js +1 -0
  34. package/dist-web/assets/index-BN79SJSr.js +173 -0
  35. package/dist-web/assets/index-BVQsKKwg.js +1 -0
  36. package/dist-web/assets/{index-C2439065.js → index-Bzg8BftZ.js} +6 -6
  37. package/dist-web/assets/{index-DC0LjKV6.js → index-C-5bP7_E.js} +2 -2
  38. package/dist-web/assets/index-C3A_Rjh-.css +1 -0
  39. package/dist-web/assets/{index-BUfibEyG.css → index-CGNgUIT-.css} +1 -1
  40. package/dist-web/assets/index-D6ga3hbF.js +163 -0
  41. package/dist-web/assets/index-ESCx9pRR.js +8 -0
  42. package/dist-web/assets/index-JbR9Q-bb.css +1 -0
  43. package/dist-web/assets/vendor-nuxt-ui-BLMzqeB8.js +15 -0
  44. package/dist-web/assets/{vendor-reka-ui-BrTYuIfH.js → vendor-reka-ui-CnB0mMNX.js} +1 -1
  45. package/dist-web/assets/web-DtlveK3q.js +1 -0
  46. package/dist-web/index.html +4 -4
  47. package/package.json +2 -1
  48. package/dist/chunk-AHQQQ5HZ.mjs.map +0 -1
  49. package/dist/chunk-G7Q7EQ3V.mjs.map +0 -1
  50. package/dist/chunk-L37WJ33K.mjs.map +0 -1
  51. package/dist/service-WCGGRZTA.mjs.map +0 -1
  52. package/dist/src-EGXULYFQ.mjs +0 -340
  53. package/dist/stream-import-QM5R5FDG.mjs.map +0 -1
  54. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-DXxg55kL.js +0 -161
  55. package/dist-web/assets/electron-DPsrYwTt.js +0 -1
  56. package/dist-web/assets/electron-DfpXsXsS.js +0 -1
  57. package/dist-web/assets/electron-DpE69lO4.js +0 -1
  58. package/dist-web/assets/electron-HoVzpIVc.js +0 -1
  59. package/dist-web/assets/electron-SyPZX9OU.js +0 -1
  60. package/dist-web/assets/fetch-B7OBmANJ.js +0 -1
  61. package/dist-web/assets/fetch-BikSxuED.js +0 -3
  62. package/dist-web/assets/fetch-BwMZFwbF.js +0 -1
  63. package/dist-web/assets/fetch-BxE8RQYE.js +0 -1
  64. package/dist-web/assets/fetch-LBHcBBkv.js +0 -1
  65. package/dist-web/assets/http-C741fIwb.js +0 -1
  66. package/dist-web/assets/index-CD5Fz-JY.js +0 -7
  67. package/dist-web/assets/index-Dl-X-izi.css +0 -1
  68. package/dist-web/assets/index-DywUneNT.css +0 -1
  69. package/dist-web/assets/index-JvmxkAGk.js +0 -173
  70. package/dist-web/assets/index-vEVpfw-R.js +0 -163
  71. package/dist-web/assets/sse-CACcGGWv.js +0 -2
  72. package/dist-web/assets/vendor-nuxt-ui-3XQAyMd0.js +0 -15
  73. package/dist-web/assets/web-Bm94rL5B.js +0 -1
  74. /package/dist/{chunk-643S2N2U.mjs.map → chunk-36W2HWQN.mjs.map} +0 -0
  75. /package/dist/{chunk-WTQ7LAZR.mjs.map → chunk-KCBIBRHL.mjs.map} +0 -0
  76. /package/dist/{chunk-3MX7VF2B.mjs.map → chunk-RLCFXTTS.mjs.map} +0 -0
  77. /package/dist/{http-JDORENHT.mjs.map → http-OPQ352FZ.mjs.map} +0 -0
  78. /package/dist/{import-LCBSBLYF.mjs.map → import-GZMDLK2E.mjs.map} +0 -0
  79. /package/dist/{mcp-AR7HBIK4.mjs.map → mcp-SHZ2CVXY.mjs.map} +0 -0
  80. /package/dist/{src-EGXULYFQ.mjs.map → stream-import-RDDB3N7B.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","../../../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/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/nlp/pos-tags.ts","../../../packages/core/src/nlp/stopwords.ts","../../../packages/core/src/nlp/text-utils.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/import/dedup.ts","../../../packages/core/src/import/writers.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/line-native-txt.ts","../../../packages/parser/src/formats/index.ts","../../../packages/node-runtime/src/import/streaming-importer.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/incremental-importer.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/conversations.ts","../../../packages/node-runtime/src/ai/tokenizer.ts","../../../packages/node-runtime/src/ai/skill-manager.ts","../../../packages/node-runtime/src/ai/skill-manager-core.ts","../../../packages/node-runtime/src/ai/activate-skill-tool.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/core.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/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/ai/rag/config.ts","../../../packages/node-runtime/src/ai/rag/types.ts","../../../packages/node-runtime/src/ai/rag/chunking/session.ts","../../../packages/node-runtime/src/ai/rag/chunking/types.ts","../../../packages/node-runtime/src/ai/rag/embedding/openai-compatible.ts","../../../packages/node-runtime/src/ai/rag/embedding/index.ts","../../../packages/node-runtime/src/ai/rag/store/index.ts","../../../packages/node-runtime/src/ai/rag/store/sqlite.ts","../../../packages/node-runtime/src/ai/rag/utils.ts","../../../packages/node-runtime/src/ai/rag/store/memory.ts","../../../packages/node-runtime/src/ai/rag/pipeline/semantic.ts","../../../packages/node-runtime/src/ai/rag/index.ts","../../../packages/node-runtime/src/node-path-provider.ts","../../../packages/config/src/loader.ts","../../../packages/config/src/schema.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/migrations/electron-data-migration.ts","../../../packages/node-runtime/src/database-manager.ts","../../../packages/node-runtime/src/migrations/chat-db-migrations.ts","../../../packages/node-runtime/src/jieba-nlp-provider.ts","../../../packages/node-runtime/src/export/markdown-exporter.ts","../../../packages/node-runtime/src/cache/session-cache.ts","../../../packages/node-runtime/src/preferences.ts","../../../packages/node-runtime/src/merger/index.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/adapters/database-manager-adapter.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","/**\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 db.pragma('journal_mode = WAL')\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\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 *\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, 'chat_session')) return 0\n const row = db\n .prepare(\"SELECT COUNT(*) as count FROM chat_session WHERE summary IS NOT NULL AND summary != ''\")\n .get() as { count: number } | 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}\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 SessionSearchItem {\n id: number\n startTs: number\n endTs: number\n messageCount: number\n isComplete: boolean\n previewMessages: SessionPreviewMessage[]\n}\n\nexport interface SessionMessagesData {\n sessionId: number\n startTs: number\n endTs: number\n messageCount: number\n returnedCount: number\n participants: string[]\n messages: SessionPreviewMessage[]\n}\n\nexport interface SearchSessionsOptions {\n keywords?: string[]\n timeFilter?: TimeFilter\n limit?: number\n previewCount?: number\n /** Pre-tokenized FTS match expression. When provided, uses FTS index for keyword filtering. */\n ftsMatchExpression?: string\n}\n\nexport interface SessionSummaryData {\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\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 }\n}\n\n/**\n * Search chat sessions with optional keyword and time filters.\n * Requires chat_session and message_context tables (session indexing).\n * Supports LIKE-based search and optional FTS when ftsMatchExpression is provided.\n */\nexport function searchSessions(\n db: DatabaseAdapter,\n keywords?: string[],\n timeFilter?: TimeFilter,\n limit: number = 20,\n previewCount: number = 5,\n ftsMatchExpression?: string\n): SessionSearchItem[] {\n if (!hasTable(db, 'chat_session')) return []\n\n let sessionSql = `\n SELECT cs.id, cs.start_ts as startTs, cs.end_ts as endTs, cs.message_count as messageCount\n FROM chat_session cs WHERE 1=1\n `\n const params: unknown[] = []\n\n if (timeFilter?.startTs !== undefined) {\n sessionSql += ' AND cs.start_ts >= ?'\n params.push(timeFilter.startTs)\n }\n if (timeFilter?.endTs !== undefined) {\n sessionSql += ' AND cs.end_ts <= ?'\n params.push(timeFilter.endTs)\n }\n\n if (keywords && keywords.length > 0) {\n if (ftsMatchExpression) {\n sessionSql += `\n AND cs.id IN (\n SELECT DISTINCT mc.session_id FROM message_context mc\n WHERE mc.message_id IN (SELECT rowid FROM message_fts WHERE content MATCH ?)\n )\n `\n params.push(ftsMatchExpression)\n } else {\n const keywordConditions = keywords.map(() => 'm.content LIKE ?').join(' OR ')\n sessionSql += `\n AND cs.id IN (\n SELECT DISTINCT mc.session_id FROM message_context mc\n JOIN message m ON m.id = mc.message_id\n WHERE (${keywordConditions})\n )\n `\n for (const kw of keywords) {\n params.push(`%${kw}%`)\n }\n }\n }\n\n sessionSql += ' ORDER BY cs.start_ts DESC LIMIT ?'\n params.push(limit)\n\n const sessions = db.prepare(sessionSql).all(...params) as Array<{\n id: number\n startTs: number\n endTs: number\n messageCount: number\n }>\n\n const previewSql = `\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.session_id = ? ORDER BY m.ts ASC LIMIT ?\n `\n\n return sessions.map((session) => {\n const previewMessages = db.prepare(previewSql).all(session.id, previewCount) as unknown as SessionPreviewMessage[]\n\n return {\n id: session.id,\n startTs: session.startTs,\n endTs: session.endTs,\n messageCount: session.messageCount,\n isComplete: session.messageCount <= previewCount,\n previewMessages,\n }\n })\n}\n\n/**\n * Get messages for a specific chat session\n */\nexport function getSessionMessages(\n db: DatabaseAdapter,\n chatSessionId: number,\n limit: number = 500\n): SessionMessagesData | null {\n if (!hasTable(db, 'chat_session')) 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 chat_session WHERE id = ?`\n )\n .get(chatSessionId) 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.session_id = ? ORDER BY m.ts ASC LIMIT ?`\n )\n .all(chatSessionId, 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 sessionId: 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 getSessionSummaries(\n db: DatabaseAdapter,\n options?: { limit?: number; timeFilter?: TimeFilter }\n): SessionSummaryData[] {\n if (!hasTable(db, 'chat_session')) 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 chat_session 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.session_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 (chat_session) ====================\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 chat_session table exists and has at least one row.\n */\nexport function hasSessionIndex(db: DatabaseAdapter): boolean {\n if (!hasTable(db, 'chat_session')) return false\n try {\n const row = db.prepare('SELECT COUNT(*) as count FROM chat_session').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, 'chat_session')) {\n try {\n const row = db.prepare('SELECT COUNT(*) as count FROM chat_session').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, 'chat_session')) 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.session_id = cs.id ORDER BY mc.message_id LIMIT 1) as firstMessageId\n FROM chat_session 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, 'chat_session')) 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.session_id = cs.id ORDER BY mc.message_id LIMIT 1) as firstMessageId\n FROM chat_session 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, 'chat_session')) 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.session_id = cs.id ORDER BY mc.message_id LIMIT 1) as firstMessageId\n FROM chat_session 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 loadSessionMessages(\n db: DatabaseAdapter,\n chatSessionId: 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.session_id = ?\n ORDER BY m.ts ASC\n LIMIT ?`\n )\n .all(chatSessionId, 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 getChatSessionSummary(db: DatabaseAdapter, chatSessionId: number): string | null {\n try {\n const row = db.prepare('SELECT summary FROM chat_session WHERE id = ?').get(chatSessionId) 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 saveChatSessionSummary(db: DatabaseAdapter, chatSessionId: number, summary: string): void {\n db.prepare('UPDATE chat_session SET summary = ? WHERE id = ?').run(summary, chatSessionId)\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 (chat_session + message_context).\n */\nexport function clearSessionIndex(db: DatabaseAdapter): void {\n db.exec('DELETE FROM message_context')\n db.exec('DELETE FROM chat_session')\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 clearSessionIndex(db)\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 chat_session (start_ts, end_ts, message_count, is_manual, summary) VALUES (?, ?, ?, 0, NULL)'\n )\n const insertContext = db.prepare('INSERT INTO message_context (message_id, session_id, topic_id) VALUES (?, ?, NULL)')\n\n return db.transaction(() => {\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, 'chat_session')\n ? (db.prepare('SELECT id, end_ts FROM chat_session 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 chat_session (start_ts, end_ts, message_count, is_manual, summary) VALUES (?, ?, ?, 0, NULL)'\n )\n const insertContext = db.prepare('INSERT INTO message_context (message_id, session_id, topic_id) VALUES (?, ?, NULL)')\n const updateSession = db.prepare('UPDATE chat_session 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 { FULL_MSG_SELECT, mapMessageRow, type FullMessageRow, type MappedMessage } 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 * 获取最近 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 session_id FROM message_context WHERE message_id = ?').get(messageId) as\n | { session_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.session_id = ? AND mc.message_id < ?\n ORDER BY mc.message_id DESC LIMIT ?`\n )\n .all(sessionRow.session_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.session_id = ? AND mc.message_id > ?\n ORDER BY mc.message_id ASC LIMIT ?`\n )\n .all(sessionRow.session_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","/**\n * 口头禅分析模块(平台无关)\n */\n\nimport type { TimeFilter } from '@openchatlab/shared-types'\nimport type { DatabaseAdapter } from '../../interfaces'\nimport { buildTimeFilter } from '../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 (!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 chat_session').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 sessionRows = db\n .prepare(\n `SELECT cs.id AS session_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.session_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.session_id = cs.id ORDER BY m.ts DESC, m.id DESC LIMIT 1) AS closer_id\n FROM chat_session cs ${whereClause} ORDER BY cs.start_ts ASC`\n )\n .all(...params) as Array<{\n session_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 sessionRows) {\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 sessionIdList = sessionRows.map((r) => r.session_id)\n const msgStats = queryMessageLevelStats(db, sessionIdList, memberNames, perseveranceThreshold)\n\n return {\n months,\n members,\n totalSessions: sessionRows.length,\n hasSessionIndex: true,\n iceBreakers,\n totalIceBreaks,\n ...msgStats,\n perseveranceThreshold,\n }\n}\n\nfunction queryMessageLevelStats(\n db: DatabaseAdapter,\n sessionIds: 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 (sessionIds.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 < sessionIds.length; i += BATCH_SIZE) {\n const batch = sessionIds.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.session_id ORDER BY m.ts, m.id) AS prev_sender_id,\n LAG(m.ts) OVER (PARTITION BY mc.session_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.session_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.session_id ORDER BY m.ts, m.id) AS prev_sender_id,\n LAG(m.ts) OVER (PARTITION BY mc.session_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.session_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'\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 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 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\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 =\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 PUNCTUATION_REGEX = /[!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~,。!?、;:\"\"''()【】《》…—~·\\s]/g\nconst URL_REGEX = /https?:\\/\\/[^\\s]+/g\nconst MENTION_REGEX = /@[^\\s@]+/g\nconst PURE_NUMBER_REGEX = /^\\d+$/\n\n/**\n * 清理文本:移除 URL、@提及、表情、标点等\n */\nexport function cleanText(text: string): string {\n return text\n .replace(URL_REGEX, ' ')\n .replace(MENTION_REGEX, ' ')\n .replace(EMOJI_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 * 内置工具目录(静态数据)\n *\n * 仅包含工具名称和分类,不含任何运行时依赖。\n * 用于前端展示工具列表和助手配置面板。\n */\n\nexport type ToolCategory = 'core' | 'analysis'\n\nexport interface BuiltinToolCatalogEntry {\n name: string\n category: ToolCategory\n}\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: 'search_sessions', category: 'core' },\n { name: 'get_session_messages', category: 'core' },\n { name: 'get_members', 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_session_summaries', category: 'analysis' },\n { name: 'response_time_analysis', category: 'analysis' },\n { name: 'keyword_frequency', 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 * 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 * Shared import writer helpers.\n *\n * Functions here operate on DatabaseAdapter and can be used by any\n * environment that wraps its DB connection accordingly.\n */\n\nimport type { DatabaseAdapter } from '../interfaces'\n\n/**\n * Build a Map from member platform_id → internal row id.\n * Used after bulk member insert to resolve sender_id for messages.\n */\nexport function buildMemberIdMap(db: DatabaseAdapter): Map<string, number> {\n const rows = db.prepare('SELECT id, platform_id FROM member').all() as Array<{ id: number; platform_id: string }>\n const map = new Map<string, number>()\n for (const row of rows) {\n map.set(row.platform_id, row.id)\n }\n return map\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 版本:4\n */\n\n/** 当前 Schema 版本(最新迁移的版本号) */\nexport const CURRENT_SCHEMA_VERSION = 4\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 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 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/**\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_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_session_time ON chat_session(start_ts, end_ts);\n CREATE INDEX IF NOT EXISTS idx_context_session ON message_context(session_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 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 interface AIGlobalSettings {\n maxMessagesPerRequest: number\n exportFormat: 'markdown' | 'txt'\n sqlExportFormat: 'csv' | 'json'\n enableAutoSkill: boolean\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}\n\nexport interface AIPreprocessConfig {\n dataCleaning: boolean\n mergeConsecutive: boolean\n mergeWindowSeconds: number\n blacklistKeywords: string[]\n denoise: boolean\n desensitize: 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 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}\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 * 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 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 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 instagramNative,\n whatsappNativeTxt,\n qqNativeTxt,\n lineNativeTxt,\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 } 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\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 // Post-import hook (e.g. overview cache)\n try {\n 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 * 分词引擎(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 * 收集文本的词性统计\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 { SupportedLocale, DictType, WordFrequencyParams, WordFrequencyResult, PosTagStat } from '@openchatlab/core'\nimport { segment, batchSegmentWithFrequency, 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 let posTagStats: PosTagStat[] | undefined\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\n const result = batchSegmentWithFrequency(texts, locale as SupportedLocale, {\n minLength: minWordLength,\n minCount,\n topN,\n posFilterMode,\n customPosTags,\n enableStopwords,\n dictType: dictType as DictType,\n excludeWords,\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 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 } 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\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\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 if (isDuplicate(msg, 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 db.exec('BEGIN TRANSACTION')\n\n let newMessageCount = 0\n let duplicateCount = 0\n let processedCount = 0\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 if (typeof msg.timestamp !== 'number' || msg.timestamp <= 0 || !isFinite(msg.timestamp)) {\n trackError(processedCount, 'INVALID_TIMESTAMP', `timestamp value: ${msg.timestamp}`)\n continue\n }\n\n if (isDuplicate(msg, 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 msg.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 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 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 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","/**\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 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: 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 = 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'\n\nconst DEFAULT_GENERAL_ID = 'general_cn'\n\n// ==================== 类型定义 ====================\n\nexport interface AIConversation {\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 | {\n type: 'tool'\n tool: {\n name: string\n displayName: string\n status: 'running' | 'done' | 'error'\n params?: Record<string, unknown>\n }\n }\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}\n\nexport interface AIMessage {\n id: string\n conversationId: 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 conversationId: 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 ConversationManagerLogger {\n warn(category: string, message: string, extra?: Record<string, unknown>): void\n}\n\nconst defaultLogger: ConversationManagerLogger = {\n warn(_category, message, extra) {\n console.warn(`[AI Conversations] ${message}`, extra ?? '')\n },\n}\n\n// ==================== AIConversationManager ====================\n\nexport class AIConversationManager {\n private db: Database.Database | null = null\n private readonly aiDataDir: string\n private readonly logger: ConversationManagerLogger\n private readonly nativeBinding?: string\n private readonly pendingDebugContextMap = new Map<string, string>()\n\n constructor(aiDataDir: string, options?: { logger?: ConversationManagerLogger; 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_conversation (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n title TEXT,\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 conversation_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 FOREIGN KEY(conversation_id) REFERENCES ai_conversation(id) ON DELETE CASCADE\n );\n\n CREATE INDEX IF NOT EXISTS idx_ai_conversation_session ON ai_conversation(session_id);\n CREATE INDEX IF NOT EXISTS idx_ai_message_conversation ON ai_message(conversation_id);\n `)\n\n this.migrateDatabase(this.db)\n return this.db\n }\n\n private migrateDatabase(db: Database.Database): void {\n try {\n const messageTableInfo = db.pragma('table_info(ai_message)') as Array<{ name: string }>\n const messageColumns = messageTableInfo.map((col) => col.name)\n\n const needsMessageTreeBackfill =\n !messageColumns.includes('parent_id') ||\n !messageColumns.includes('sibling_group_id') ||\n !messageColumns.includes('branch_index')\n\n if (!messageColumns.includes('content_blocks')) {\n db.exec('ALTER TABLE ai_message ADD COLUMN content_blocks TEXT')\n }\n if (!messageColumns.includes('token_usage')) {\n db.exec('ALTER TABLE ai_message ADD COLUMN token_usage TEXT')\n }\n if (!messageColumns.includes('debug_context')) {\n db.exec('ALTER TABLE ai_message ADD COLUMN debug_context TEXT')\n }\n if (!messageColumns.includes('parent_id')) {\n db.exec('ALTER TABLE ai_message ADD COLUMN parent_id TEXT')\n }\n if (!messageColumns.includes('sibling_group_id')) {\n db.exec('ALTER TABLE ai_message ADD COLUMN sibling_group_id TEXT')\n }\n if (!messageColumns.includes('branch_index')) {\n db.exec('ALTER TABLE ai_message ADD COLUMN branch_index INTEGER DEFAULT 0')\n }\n\n const convTableInfo = db.pragma('table_info(ai_conversation)') as Array<{ name: string }>\n const convColumns = convTableInfo.map((col) => col.name)\n const needsConversationBackfill = !convColumns.includes('active_message_id')\n\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 if (needsMessageTreeBackfill || needsConversationBackfill || this.hasUnbackfilledMessageTree(db)) {\n this.backfillMessageTree(db)\n }\n\n db.exec(`\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_conversation c\n WHERE EXISTS (\n SELECT 1 FROM ai_message m\n WHERE m.conversation_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.conversation_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 conversations = db.prepare('SELECT id FROM ai_conversation').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 updateConversation = db.prepare('UPDATE ai_conversation SET active_message_id = ? WHERE id = ?')\n\n const tx = db.transaction(() => {\n for (const conversation of conversations) {\n const messages = db\n .prepare(\n `SELECT id, parent_id as parentId, sibling_group_id as siblingGroupId\n FROM ai_message WHERE conversation_id = ? ORDER BY timestamp ASC, id ASC`\n )\n .all(conversation.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 conversationRow = db\n .prepare('SELECT active_message_id as activeMessageId FROM ai_conversation WHERE id = ?')\n .get(conversation.id) as { activeMessageId: string | null } | undefined\n if (!conversationRow?.activeMessageId && previousId) {\n updateConversation.run(previousId, conversation.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 conversationId: row.conversationId,\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, conversation_id as conversationId, 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(conversationId: string): string | null {\n const db = this.getDb()\n const row = db\n .prepare('SELECT active_message_id as activeMessageId FROM ai_conversation WHERE id = ?')\n .get(conversationId) as { activeMessageId: string | null } | 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 conversation_id = ? ORDER BY timestamp DESC, id DESC LIMIT 1')\n .get(conversationId) as { id: string } | undefined\n if (fallback?.id) {\n db.prepare('UPDATE ai_conversation SET active_message_id = ? WHERE id = ?').run(fallback.id, conversationId)\n return fallback.id\n }\n return null\n }\n\n private getAllMessageRows(conversationId: string): AIMessageRow[] {\n return this.getDb()\n .prepare(\n `SELECT id, conversation_id as conversationId, 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 conversation_id = ? ORDER BY timestamp ASC, id ASC`\n )\n .all(conversationId) as AIMessageRow[]\n }\n\n private getActivePathRows(conversationId: string, leafMessageId?: string | null): AIMessageRow[] {\n if (leafMessageId === null) return []\n\n const allRows = this.getAllMessageRows(conversationId)\n if (allRows.length === 0) return []\n\n const rowMap = new Map(allRows.map((row) => [row.id, row]))\n let currentId = leafMessageId ?? this.getActiveMessageId(conversationId)\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 createConversation(sessionId: string, title: string | undefined, assistantId: string): AIConversation {\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_conversation (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 getConversationCountsBySession(): Map<string, number> {\n const result = new Map<string, number>()\n try {\n const db = this.getDb()\n const rows = db\n .prepare('SELECT session_id, COUNT(*) as count FROM ai_conversation GROUP BY session_id')\n .all() as Array<{ session_id: string; count: number }>\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 getConversations(sessionId: string): AIConversation[] {\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_conversation WHERE session_id = ? ORDER BY updated_at DESC`\n )\n .all(sessionId) as AIConversation[]\n }\n\n getConversation(conversationId: string): AIConversation | 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_conversation WHERE id = ?`\n )\n .get(conversationId) as AIConversation | undefined\n return row || null\n }\n\n updateConversationTitle(conversationId: string, title: string): boolean {\n const db = this.getDb()\n const now = Math.floor(Date.now() / 1000)\n const result = db\n .prepare('UPDATE ai_conversation SET title = ?, updated_at = ? WHERE id = ?')\n .run(title, now, conversationId)\n return result.changes > 0\n }\n\n deleteConversation(conversationId: string): boolean {\n const db = this.getDb()\n db.prepare('DELETE FROM ai_message WHERE conversation_id = ?').run(conversationId)\n const result = db.prepare('DELETE FROM ai_conversation WHERE id = ?').run(conversationId)\n return result.changes > 0\n }\n\n // ==================== 消息管理 ====================\n\n addMessage(\n conversationId: 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(conversationId)\n const siblingGroupId = id\n const branchIndex = 0\n\n const pendingDebug = role === 'assistant' ? this.pendingDebugContextMap.get(conversationId) : undefined\n if (pendingDebug) {\n this.pendingDebugContextMap.delete(conversationId)\n }\n\n db.prepare(\n `INSERT INTO ai_message (\n id, conversation_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 conversationId,\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_conversation SET active_message_id = ?, updated_at = ? WHERE id = ?').run(\n id,\n now,\n conversationId\n )\n\n return {\n id,\n conversationId,\n role,\n content,\n timestamp: now,\n parentId,\n dataKeywords,\n dataMessageCount,\n contentBlocks,\n tokenUsage,\n }\n }\n\n getMessages(conversationId: string): AIMessage[] {\n return this.getActivePathRows(conversationId).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(conversationId: string, messageId: string): void {\n const db = this.getDb()\n const target = this.getMessageRow(messageId)\n if (!target || target.conversationId !== conversationId) {\n throw new Error('Message not found in conversation')\n }\n\n const activePath = this.getActivePathRows(conversationId)\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_conversation SET active_message_id = ?, updated_at = ? WHERE id = ?').run(\n newLeafId,\n now,\n conversationId\n )\n }\n\n forkConversation(sourceConversationId: string, upToMessageId: string, title?: string): AIConversation {\n const db = this.getDb()\n const source = this.getConversation(sourceConversationId)\n if (!source) {\n throw new Error('Source conversation not found')\n }\n\n const activePath = this.getActivePathRows(sourceConversationId)\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_conversation (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, conversation_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_conversation 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(conversationId: string, messageId: string): void {\n const db = this.getDb()\n const target = this.getMessageRow(messageId)\n if (!target || target.conversationId !== conversationId) {\n throw new Error('Message not found in conversation')\n }\n\n db.prepare('UPDATE ai_message SET parent_id = ? WHERE parent_id = ? AND conversation_id = ?').run(\n target.parentId,\n messageId,\n conversationId\n )\n\n const conv = this.getConversation(conversationId)\n if (conv?.activeMessageId === messageId) {\n const now = Math.floor(Date.now() / 1000)\n db.prepare('UPDATE ai_conversation SET active_message_id = ?, updated_at = ? WHERE id = ?').run(\n target.parentId,\n now,\n conversationId\n )\n }\n\n db.prepare('DELETE FROM ai_message WHERE id = ?').run(messageId)\n }\n\n insertMessageAfter(\n conversationId: 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(conversationId) : undefined\n if (pendingDebug) {\n this.pendingDebugContextMap.delete(conversationId)\n }\n\n const childRow = db\n .prepare('SELECT id FROM ai_message WHERE parent_id = ? AND conversation_id = ? LIMIT 1')\n .get(afterMessageId, conversationId) as { id: string } | undefined\n\n db.prepare(\n `INSERT INTO ai_message (\n id, conversation_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 conversationId,\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_conversation SET updated_at = ? WHERE id = ?').run(now, conversationId)\n } else {\n db.prepare('UPDATE ai_conversation SET active_message_id = ?, updated_at = ? WHERE id = ?').run(\n id,\n now,\n conversationId\n )\n }\n\n return {\n id,\n conversationId,\n role,\n content,\n timestamp: now,\n parentId: afterMessageId,\n contentBlocks,\n tokenUsage,\n }\n }\n\n getConversationTokenUsage(conversationId: string): TokenUsageData {\n const rows = this.getActivePathRows(conversationId)\n const result: TokenUsageData = { promptTokens: 0, completionTokens: 0, totalTokens: 0 }\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 }\n }\n return result\n }\n\n // ==================== Debug context ====================\n\n setPendingDebugContext(conversationId: string, debugContext: string): void {\n this.pendingDebugContextMap.set(conversationId, 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 conversationId: string,\n maxMessages?: number,\n leafMessageId?: string | null\n ): Array<{ role: 'user' | 'assistant' | 'summary'; content: string }> {\n const messages = this.getActivePathRows(conversationId, leafMessageId).map((row) => this.parseMessageRow(row))\n const validMessages = messages.filter(\n (m) => (m.role === 'user' || m.role === 'assistant' || m.role === 'summary') && m.content?.trim()\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 }>\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('Conversations', 'summary message missing summary_meta; agent context will be summary-only', {\n conversationId,\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 })),\n ]\n } else {\n result = validMessages.map((m) => ({ role: m.role, content: m.content }))\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 conversationId: 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(conversationId, 'summary', content, undefined, undefined, contentBlocks)\n }\n\n getLatestSummary(conversationId: string): AIMessage | null {\n const row = [...this.getActivePathRows(conversationId)].reverse().find((message) => message.role === 'summary')\n return row ? this.parseMessageRow(row) : null\n }\n\n getMessagesAfterSummary(\n conversationId: string,\n summaryTimestamp: number\n ): Array<{ role: AIMessageRole; content: string; timestamp: number }> {\n return this.getActivePathRows(conversationId)\n .filter((row) => row.timestamp > summaryTimestamp && (row.role === 'user' || row.role === 'assistant'))\n .map((row) => ({ role: row.role as AIMessageRole, content: row.content, timestamp: row.timestamp }))\n }\n\n getAllUserAssistantMessages(\n conversationId: string\n ): Array<{ role: AIMessageRole; content: string; timestamp: number }> {\n return this.getActivePathRows(conversationId)\n .filter((row) => row.role === 'user' || row.role === 'assistant')\n .map((row) => ({ role: row.role as AIMessageRole, content: row.content, timestamp: row.timestamp }))\n }\n\n getMessageCountAfterSummary(conversationId: string): number {\n const summary = this.getLatestSummary(conversationId)\n if (!summary) {\n return this.getActivePathRows(conversationId).filter((row) => row.role === 'user' || row.role === 'assistant')\n .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(conversationId).filter(\n (row) => row.timestamp >= boundary && (row.role === 'user' || row.role === 'assistant')\n ).length\n }\n}\n","/**\n * Token 计数模块\n * 使用 js-tiktoken 的 cl100k_base 编码进行近似 token 计数。\n * 该编码是 GPT-4/Claude 系列的近似值,对国内模型有一定误差,\n * 因此阈值计算时预留了余量。\n */\n\nimport { encodingForModel } from 'js-tiktoken'\n\nlet encoder: ReturnType<typeof encodingForModel> | null = null\n\nfunction getEncoder() {\n if (!encoder) {\n encoder = encodingForModel('gpt-4o')\n }\n return encoder\n}\n\n/**\n * 计算单段文本的 token 数\n */\nexport function countTokens(text: string): number {\n if (!text) return 0\n return getEncoder().encode(text).length\n}\n\n/**\n * 计算消息列表的总 token 数(含 systemPrompt)\n * 每条消息额外计 4 tokens 的格式开销(role + 分隔符)\n */\nexport function countMessagesTokens(messages: Array<{ role: string; content: string }>, systemPrompt?: string): number {\n const enc = getEncoder()\n let total = 0\n\n if (systemPrompt) {\n total += enc.encode(systemPrompt).length + 4\n }\n\n for (const msg of messages) {\n total += enc.encode(msg.content).length + 4\n }\n\n // 回复引导 token\n total += 3\n\n return total\n}\n","/**\n * 技能管理器(平台无关)\n *\n * 从 aiDataDir/skills/*.md 加载技能定义,\n * 提供查询和 AI 自选菜单构建。\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { parseSkillFile } from './skill-parser'\nimport type { SkillDef, SkillSummary } from './types'\n\nconst SKILLS_DIR_NAME = 'skills'\nconst MAX_SKILL_MENU_ITEMS = 15\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\nexport class SkillManager {\n private skills = new Map<string, SkillDef>()\n private initialized = false\n private skillsDir: string\n private logger: SkillManagerLogger\n\n constructor(aiDataDir: string, logger?: SkillManagerLogger) {\n this.skillsDir = path.join(aiDataDir, SKILLS_DIR_NAME)\n this.logger = logger ?? defaultLogger\n }\n\n init(): { total: number } {\n this.loadAll()\n this.initialized = true\n this.logger.info(`SkillManager initialized: ${this.skills.size} skills`)\n return { total: this.skills.size }\n }\n\n getSkillConfig(id: string): SkillDef | null {\n this.ensureInitialized()\n return this.skills.get(id) ?? null\n }\n\n getAllSkills(): SkillSummary[] {\n this.ensureInitialized()\n return Array.from(this.skills.values()).map((def) => ({\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\n /**\n * 构建 AI 自选技能菜单文本\n * 只包含与当前 chatType + 助手工具权限兼容的技能\n */\n getSkillMenu(chatType: 'group' | 'private', allowedTools?: string[]): string | null {\n this.ensureInitialized()\n\n const compatible = Array.from(this.skills.values()).filter((skill) => {\n if (skill.chatScope !== 'all' && skill.chatScope !== chatType) return false\n if (skill.tools.length > 0 && allowedTools && allowedTools.length > 0) {\n const allCovered = skill.tools.every((t) => allowedTools.includes(t))\n if (!allCovered) 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 const lines = items.map((s) => `- ${s.id}: ${s.name} — ${s.description}`)\n\n return `## 可用技能\n以下是你可以使用的分析技能。当你判断用户的问题适合使用某个技能时,\n请调用 activate_skill 工具激活它,然后按照返回的指导完成任务。\n\n${lines.join('\\n')}\n\n如果用户的问题不需要使用技能,直接回答即可。`\n }\n\n private ensureInitialized(): void {\n if (!this.initialized) {\n this.init()\n }\n }\n\n private loadAll(): void {\n this.skills.clear()\n if (!fs.existsSync(this.skillsDir)) return\n\n const files = fs.readdirSync(this.skillsDir).filter((f) => f.endsWith('.md'))\n for (const file of files) {\n try {\n const filePath = path.join(this.skillsDir, file)\n const content = fs.readFileSync(filePath, 'utf-8')\n const def = parseSkillFile(content, filePath)\n if (def) {\n this.skills.set(def.id, def)\n }\n } catch (error) {\n this.logger.warn(`Failed to load skill: ${file}`, { error: String(error) })\n }\n }\n }\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 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\nconst MAX_SKILL_MENU_ITEMS = 15\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 && allowedTools.length > 0) {\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 const lines = items.map((s) => `- ${s.id}: ${s.name} — ${s.description}`)\n\n return `## 可用技能\n以下是你可以使用的分析技能。当你判断用户的问题适合使用某个技能时,\n请调用 activate_skill 工具激活它,然后按照返回的指导完成任务。\n\n${lines.join('\\n')}\n\n如果用户的问题不需要使用技能,直接回答即可。`\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 * 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 && allowedTools.length > 0) {\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","/**\n * 上下文压缩核心逻辑(平台无关)\n *\n * 通过 CompressionLlmAdapter 抽象 LLM 调用,\n * 通过 AIConversationManager 操作对话数据。\n */\n\nimport { countTokens, countMessagesTokens } from '../tokenizer'\nimport type { AIConversationManager, ContentBlock, AIMessageRole } from '../conversations'\nimport type { CompressionConfig, CompressionResult, CompressionLogger, CompressionLlmAdapter } from './types'\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 conversationId: string,\n config: CompressionConfig,\n systemPrompt: string,\n llmAdapter: CompressionLlmAdapter,\n convManager: AIConversationManager,\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(conversationId)\n\n let messages: Array<{ role: AIMessageRole; content: string; timestamp: number }>\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(conversationId, boundary - 1)\n } else {\n messages = convManager.getAllUserAssistantMessages(conversationId)\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 }\n\n const currentTokens = countMessagesTokens(historyForTokenCount, systemPrompt)\n\n logger.info('Compression', `Token check: ${currentTokens} / ${thresholdTokens} (${contextWindow} window)`, {\n conversationId,\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(conversationId, summaryText, {\n bufferBoundaryTimestamp: bufferBoundary,\n compressedMessageCount: messagesToCompress.length,\n })\n\n const afterTokenCount: Array<{ role: string; content: string }> = [\n { role: 'assistant', content: summaryText },\n ...bufferMessages.map((m) => ({ role: m.role, content: m.content })),\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 conversationId: string,\n config: CompressionConfig,\n systemPrompt: string,\n llmAdapter: CompressionLlmAdapter,\n convManager: AIConversationManager,\n logger?: CompressionLogger\n): Promise<CompressionResult> {\n const messageCount = convManager.getMessageCountAfterSummary(conversationId)\n if (messageCount < 5) {\n return { compressed: false, reason: 'skipped_idempotent' }\n }\n\n const overrideConfig = { ...config, enabled: true, tokenThresholdPercent: 0 }\n return checkAndCompress(conversationId, overrideConfig, systemPrompt, llmAdapter, convManager, logger)\n}\n\n// ==================== Internal Helpers ====================\n\nfunction splitMessagesForCompression(\n messages: Array<{ role: string; content: string; timestamp: number }>,\n bufferTokenBudget: number\n): {\n bufferMessages: Array<{ role: string; content: string; timestamp: number }>\n messagesToCompress: Array<{ role: string; content: string; timestamp: number }>\n} {\n let bufferTokens = 0\n let splitIndex = messages.length\n\n for (let i = messages.length - 1; i >= 0; i--) {\n const msgTokens = countTokens(messages[i].content) + 4\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 }>,\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 }\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 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 },\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 },\n {\n id: 'cn_bank_card',\n label: 'desensitize.rules.cn_bank_card',\n pattern: '(?<!\\\\d)\\\\d{16,19}(?!\\\\d)',\n replacement: '[银行卡]',\n enabled: false,\n builtin: true,\n locales: ['zh-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: false,\n builtin: true,\n locales: ['zh-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 },\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 },\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: false,\n builtin: true,\n locales: ['en-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 },\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: false,\n builtin: true,\n locales: ['ja-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 },\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 },\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 },\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 },\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 },\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: false,\n builtin: true,\n locales: [],\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: false,\n builtin: true,\n locales: [],\n },\n {\n id: 'url',\n label: 'desensitize.rules.url',\n pattern: 'https?://[^\\\\s<>\"]+',\n replacement: '[URL]',\n enabled: false,\n builtin: true,\n locales: [],\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\n/**\n * 合并新 locale 的规则到现有规则列表\n */\nexport function mergeRulesForLocale(existing: DesensitizeRule[], locale: string): DesensitizeRule[] {\n const existingIds = new Set(existing.map((r) => r.id))\n const newRules = BUILTIN_DESENSITIZE_RULES.filter(\n (rule) => !existingIds.has(rule.id) && (rule.locales.length === 0 || rule.locales.includes(locale))\n ).map((rule) => ({ ...rule }))\n\n return [...existing, ...newRules]\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 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","/**\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, SimpleHistoryMessage } from './types'\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\nfunction toPiHistoryMessages(messages: SimpleHistoryMessage[]): PiMessage[] {\n return messages.map((msg): PiMessage => {\n if (msg.role === 'user') {\n return {\n role: 'user',\n content: [{ type: 'text', text: msg.content || '' }],\n timestamp: Date.now(),\n }\n }\n return {\n role: 'assistant',\n content: [{ type: 'text', text: msg.content || '' }],\n api: 'openai-completions',\n provider: 'chatlab',\n model: 'unknown',\n usage: createEmptyPiUsage(),\n stopReason: 'stop',\n timestamp: Date.now(),\n }\n })\n}\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 = 5,\n abortSignal,\n steerMessage = 'Please provide your final answer based on the information gathered.',\n onEvent,\n onConvertToLlm,\n onDebugContext,\n } = options\n\n const resolvedStreamFn = (options.streamFn ?? defaultStreamSimple) as typeof defaultStreamSimple\n\n const totalUsage: AgentTokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 }\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 }\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 console.debug('[agent-core] thinkingLevel resolution:', {\n input: options.thinkingLevel,\n modelId: piModel.id,\n modelReasoning: effectiveModel.reasoning,\n isDefault,\n resolved: resolvedThinkingLevel,\n final: finalThinkingLevel,\n })\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),\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 toolName: event.toolName,\n toolParams: (event.args || {}) as Record<string, unknown>,\n })\n } else if (event.type === 'tool_execution_end') {\n onEvent({ type: 'tool_end', toolName: event.toolName, toolResult: event.result })\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","/**\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'\n\n// ==================== Shared types ====================\n\nexport interface TokenUsage {\n promptTokens: number\n completionTokens: number\n totalTokens: 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: 'content' | 'think' | 'tool_start' | 'tool_result' | 'status' | 'compression_done' | 'done' | 'error'\n content?: string\n thinkTag?: string\n thinkDurationMs?: number\n toolName?: string\n toolParams?: Record<string, unknown>\n toolResult?: unknown\n error?: unknown\n isFinished?: boolean\n usage?: TokenUsage\n status?: AgentRuntimeStatus\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 = { promptTokens: 0, completionTokens: 0, totalTokens: 0 }\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', 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({ type: 'tool_result', toolName: event.toolName, toolResult: event.toolResult })\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 }\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 DataSnapshot {\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}\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 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\n ? `${agentT(t, 'ai.agent.dataSnapshotNote', locale, {\n name: dataSnapshot.name,\n platform: dataSnapshot.platform,\n totalMessages: dataSnapshot.totalMessages,\n totalMembers: dataSnapshot.totalMembers,\n firstMessageDate: formatTimestamp(dataSnapshot.firstMessageTs, locale),\n lastMessageDate: formatTimestamp(dataSnapshot.lastMessageTs, locale),\n })}\\n`\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: '获取群聊的时间分布统计。适用于回答\"什么时候最活跃\"、\"大家一般几点聊天\"等问题。',\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 search_sessions: {\n desc: '搜索聊天会话(对话段落)。会话是根据消息时间间隔自动切分的对话单元。适用于查找特定话题的讨论、了解某个时间段内发生了几次对话等场景。返回匹配的会话列表及每个会话的前5条消息预览。',\n params: {\n keywords: '可选的搜索关键词列表,只返回包含这些关键词的会话(OR 逻辑匹配)',\n limit: '返回会话数量限制,默认 20',\n year: '指定年份筛选会话,如 2024',\n month: '指定月份筛选会话(1-12),需配合 year 使用',\n day: '指定日期筛选会话(1-31),需配合 year 和 month 使用',\n start_time: '开始时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"',\n end_time: '结束时间,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"',\n },\n },\n get_session_messages: {\n desc: '获取指定会话的完整消息列表。用于在 search_sessions 找到相关会话后,获取该会话的完整上下文。返回会话的所有消息及参与者信息。',\n params: {\n session_id: '会话 ID,可以从 search_sessions 的返回结果中获取',\n limit: '返回消息数量限制,默认 1000。对于超长会话可以限制返回数量以节省 token',\n },\n },\n get_session_summaries: {\n desc: `获取会话摘要列表,快速了解群聊历史讨论的主题。\n\n适用场景:\n1. 了解群里最近在聊什么话题\n2. 按关键词搜索讨论过的话题\n3. 概览性问题如\"群里有没有讨论过旅游\"\n\n返回的摘要是对每个会话的简短总结,可以帮助快速定位感兴趣的会话,然后用 get_session_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 },\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 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 sessionNotFound: '会话不存在或数据库打开失败',\n tooFewMessages: '消息数量少于{{count}}条,无需生成摘要',\n tooFewValidMessages: '有效消息数量少于{{count}}条,无需生成摘要',\n sessionNotExist: '会话不存在',\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\".',\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 search_sessions: {\n desc: 'Search chat sessions (conversation segments). Sessions are conversation units automatically split by message time intervals. Suitable for finding discussions on specific topics or understanding how many conversations occurred in a time period. Returns matching sessions with a 5-message preview each.',\n params: {\n keywords: 'Optional keyword list, only returns sessions containing these keywords (OR logic)',\n limit: 'Session count limit, default 20',\n year: 'Filter sessions by year, e.g. 2024',\n month: 'Filter sessions by month (1-12), use with year',\n day: 'Filter sessions by day (1-31), use with year and month',\n start_time: 'Start time, format \"YYYY-MM-DD HH:mm\", e.g. \"2024-03-15 14:00\"',\n end_time: 'End time, format \"YYYY-MM-DD HH:mm\", e.g. \"2024-03-15 18:30\"',\n },\n },\n get_session_messages: {\n desc: 'Get the complete message list for a specific session. Used to get full context after finding a relevant session via search_sessions. Returns all messages and participant information.',\n params: {\n session_id: 'Session ID, can be obtained from search_sessions results',\n limit: 'Message count limit, default 1000. Can be limited for very long sessions to save tokens',\n },\n },\n get_session_summaries: {\n desc: `Get session 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 session, helping quickly locate sessions of interest. Use get_session_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 sessions by year',\n month: 'Filter sessions by month (1-12)',\n day: 'Filter sessions 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 session). Requires session 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 session 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 },\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 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 sessionNotFound: '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 sessionNotExist: '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: 'グループチャットの時間分布統計を取得する。「いつが一番アクティブ?」「みんな何時にチャットしている?」などの質問に適している。',\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 search_sessions: {\n desc: 'チャットセッション(会話セグメント)を検索する。セッションはメッセージの時間間隔に基づいて自動分割された会話単位である。特定トピックの議論を見つけたり、ある期間内の会話回数を把握するのに適している。マッチしたセッションリストと各セッションの最初の5件のメッセージプレビューを返却する。',\n params: {\n keywords: '任意の検索キーワードリスト。これらのキーワードを含むセッションのみ返却する(OR ロジックマッチ)',\n limit: '返却セッション数の上限。デフォルト 20',\n year: '指定年のセッションをフィルタリング(例:2024)',\n month: '指定月のセッションをフィルタリング(1-12)。year と併用する必要がある',\n day: '指定日のセッションをフィルタリング(1-31)。year と month と併用する必要がある',\n start_time: '開始時刻。形式 \"YYYY-MM-DD HH:mm\"(例:\"2024-03-15 14:00\")',\n end_time: '終了時刻。形式 \"YYYY-MM-DD HH:mm\"(例:\"2024-03-15 18:30\")',\n },\n },\n get_session_messages: {\n desc: '指定セッションの完全なメッセージリストを取得する。search_sessions で関連セッションを見つけた後、そのセッションの完全なコンテキストを取得するために使用する。セッションの全メッセージと参加者情報を返却する。',\n params: {\n session_id: 'セッション ID。search_sessions の返却結果から取得できる',\n limit: '返却メッセージ数の上限。デフォルト 1000。非常に長いセッションでは Token 節約のため件数を制限できる',\n },\n },\n get_session_summaries: {\n desc: `セッション要約リストを取得し、グループチャットの過去の議論トピックをすばやく把握する。\n\n適用シーン:\n1. グループで最近何が話題になっていたか知りたい\n2. キーワードで過去に議論されたトピックを検索\n3. 「グループで旅行について話したことある?」などの概要的な質問\n\n返却される要約は各セッションの短い概要であり、興味のあるセッションをすばやく特定し、get_session_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 },\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 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 sessionNotFound: 'セッションが存在しないか、データベースを開けませんでした',\n tooFewMessages: 'メッセージ数が{{count}}件未満のため、要約の生成は不要です',\n tooFewValidMessages: '有効なメッセージ数が{{count}}件未満のため、要約の生成は不要です',\n sessionNotExist: 'セッションが存在しません',\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: '取得群聊的時間分佈統計。適用於回答「什麼時候最活躍」、「大家一般幾點聊天」等問題。',\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 search_sessions: {\n desc: '搜尋聊天會話(對話段落)。會話是根據訊息時間間隔自動切分的對話單元。適用於查找特定話題的討論、了解某個時間段內發生了幾次對話等場景。回傳匹配的會話清單及每個會話的前5條訊息預覽。',\n params: {\n keywords: '可選的搜尋關鍵詞清單,只回傳包含這些關鍵詞的會話(OR 邏輯匹配)',\n limit: '回傳會話數量限制,預設 20',\n year: '篩選指定年份的會話,如 2024',\n month: '篩選指定月份的會話(1-12),需要配合 year 使用',\n day: '篩選指定日期的會話(1-31),需要配合 year 和 month 使用',\n start_time: '開始時間,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 14:00\"',\n end_time: '結束時間,格式 \"YYYY-MM-DD HH:mm\",如 \"2024-03-15 18:30\"',\n },\n },\n get_session_messages: {\n desc: '取得指定會話的完整訊息清單。用於在 search_sessions 找到相關會話後,取得該會話的完整上下文。回傳會話的所有訊息及參與者資訊。',\n params: {\n session_id: '會話 ID,可以從 search_sessions 的回傳結果中取得',\n limit: '回傳訊息數量限制,預設 1000。對於超長會話可以限制回傳數量以節省 token',\n },\n },\n get_session_summaries: {\n desc: `取得會話摘要清單,快速了解群聊歷史討論的主題。\n\n適用場景:\n1. 了解群裡最近在聊什麼話題\n2. 按關鍵詞搜尋討論過的話題\n3. 概覽性問題如「群裡有沒有討論過旅遊」\n\n回傳的摘要是對每個會話的簡短總結,可以幫助快速定位感興趣的會話,然後用 get_session_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 },\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 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 sessionNotFound: '會話不存在或資料庫開啟失敗',\n tooFewMessages: '訊息數量少於 {{count}} 條,無需產生摘要',\n tooFewValidMessages: '有效訊息數量少於 {{count}} 條,無需產生摘要',\n sessionNotExist: '找不到會話',\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: (chatSessionId: number, limit?: number) => SummaryMessage[] | null\n saveSummary: (chatSessionId: number, summary: string) => void\n getSummary: (chatSessionId: 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 chatSessionId: 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(chatSessionId)\n if (existing) return { success: true, summary: existing }\n }\n\n const rawMessages = deps.loadMessages(chatSessionId)\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=${chatSessionId}, 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(chatSessionId, 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 chatSessionIds: 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 < chatSessionIds.length; i++) {\n const result = await generateSessionSummary(deps, chatSessionIds[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, chatSessionIds.length)\n }\n\n return { success, failed, skipped }\n}\n\nexport function checkSessionsCanGenerateSummary(\n deps: Pick<SummaryDeps, 'loadMessages' | 't'>,\n chatSessionIds: number[]\n): Map<number, { canGenerate: boolean; reason?: string }> {\n const results = new Map<number, { canGenerate: boolean; reason?: string }>()\n\n for (const id of chatSessionIds) {\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 onApiKeyCreated?: (config: AIServiceConfig, apiKey: string) => 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\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) => 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.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 this.onApiKeyCreated(newConfig, newConfig.apiKey)\n }\n\n this.saveStore(store)\n return { success: true, config: newConfig }\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 updated = {\n ...store.configs[index],\n ...updates,\n updatedAt: Date.now(),\n }\n store.configs[index] = updated\n\n if (updates.apiKey && this.onApiKeyCreated) {\n this.onApiKeyCreated(updated, updates.apiKey)\n }\n\n this.saveStore(store)\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 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 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 * 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 { AIConversationManager } from './conversations'\nexport type {\n AIConversation,\n AIMessage,\n AIMessageRole,\n ContentBlock,\n TokenUsageData,\n ConversationManagerLogger,\n} from './conversations'\n\n// Tokenizer\nexport { countTokens, countMessagesTokens } 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'\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 TruncationStrategy,\n PreprocessLogger,\n} from './preprocessor'\nexport {\n preprocessMessages,\n BUILTIN_DESENSITIZE_RULES,\n getDefaultRulesForLocale,\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 { runAgentCore } 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// 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// ==================== RAG ====================\nexport {\n initRag,\n initRagConfig,\n loadEmbeddingConfigStore,\n saveEmbeddingConfigStore,\n getAllEmbeddingConfigs,\n getActiveEmbeddingConfig,\n getEmbeddingConfigById,\n addEmbeddingConfig,\n updateEmbeddingConfig,\n deleteEmbeddingConfig,\n setActiveEmbeddingConfig,\n isEmbeddingEnabled,\n getActiveEmbeddingConfigId,\n loadRAGConfig,\n saveRAGConfig,\n updateRAGConfig,\n resetRAGConfig,\n getVectorStoreDir,\n getEmbeddingService,\n resetEmbeddingService,\n validateEmbeddingConfig,\n getSessionChunks,\n getSessionChunk,\n formatSessionChunk,\n getVectorStore,\n resetVectorStore,\n getVectorStoreStats,\n SQLiteVectorStore,\n MemoryVectorStore,\n executeSemanticPipeline,\n cosineSimilarity,\n DEFAULT_RAG_CONFIG,\n DEFAULT_EMBEDDING_CONFIG_STORE,\n MAX_EMBEDDING_CONFIG_COUNT,\n} from './rag'\nexport type {\n RagInitOptions,\n RagLogger,\n RAGConfig,\n EmbeddingConfig,\n EmbeddingServiceConfig,\n EmbeddingConfigStore,\n VectorStoreConfig,\n RerankConfig,\n IEmbeddingService,\n IVectorStore,\n IRerankService,\n Chunk,\n ChunkMetadata,\n VectorSearchResult,\n VectorStoreStats,\n SemanticPipelineOptions,\n SemanticPipelineResult,\n LLMConfigForEmbedding,\n SemanticPipelineLLMConfig,\n ChunkingOptions,\n SessionMessage,\n SessionInfo,\n} from './rag'\n","/**\n * RAG configuration management (platform-agnostic)\n *\n * Supports multi-embedding configuration mode.\n * All path/logger dependencies are injected via `initRagConfig()`.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { randomUUID } from 'crypto'\nimport {\n DEFAULT_RAG_CONFIG,\n DEFAULT_EMBEDDING_CONFIG_STORE,\n MAX_EMBEDDING_CONFIG_COUNT,\n type RAGConfig,\n type EmbeddingServiceConfig,\n type EmbeddingConfigStore,\n type RagLogger,\n getNoopLogger,\n} from './types'\n\n// ==================== Module-level deps (set via initRagConfig) ====================\n\nlet _aiDataDir: string | null = null\nlet _logger: RagLogger = getNoopLogger()\n\n/**\n * Initialize RAG config module with platform-specific deps.\n * Must be called before any other config function.\n */\nexport function initRagConfig(aiDataDir: string, logger?: RagLogger): void {\n _aiDataDir = aiDataDir\n if (logger) _logger = logger\n}\n\nfunction requireAiDataDir(): string {\n if (!_aiDataDir) throw new Error('RAG config not initialized — call initRagConfig() first')\n return _aiDataDir\n}\n\n// ==================== Path helpers ====================\n\nfunction getConfigPath(): string {\n return path.join(requireAiDataDir(), 'rag-config.json')\n}\n\nfunction getEmbeddingConfigPath(): string {\n return path.join(requireAiDataDir(), 'embedding-config.json')\n}\n\nexport function getVectorStoreDir(): string {\n const dir = path.join(requireAiDataDir(), 'vectors')\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n return dir\n}\n\n// ==================== Embedding multi-config management ====================\n\nexport function loadEmbeddingConfigStore(): EmbeddingConfigStore {\n const configPath = getEmbeddingConfigPath()\n\n if (!fs.existsSync(configPath)) {\n return { ...DEFAULT_EMBEDDING_CONFIG_STORE }\n }\n\n try {\n const content = fs.readFileSync(configPath, 'utf-8')\n const data = JSON.parse(content) as EmbeddingConfigStore\n\n return {\n configs: data.configs || [],\n activeConfigId: data.activeConfigId || null,\n enabled: data.enabled ?? false,\n }\n } catch (error) {\n _logger.error('RAG', 'Failed to load Embedding configs', error)\n return { ...DEFAULT_EMBEDDING_CONFIG_STORE }\n }\n}\n\nexport function saveEmbeddingConfigStore(store: EmbeddingConfigStore): void {\n const configPath = getEmbeddingConfigPath()\n const dir = path.dirname(configPath)\n\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n fs.writeFileSync(configPath, JSON.stringify(store, null, 2), 'utf-8')\n _logger.info('RAG', 'Embedding configs saved')\n}\n\nexport function getAllEmbeddingConfigs(): EmbeddingServiceConfig[] {\n return loadEmbeddingConfigStore().configs\n}\n\nexport function getActiveEmbeddingConfig(): EmbeddingServiceConfig | null {\n const store = loadEmbeddingConfigStore()\n if (!store.activeConfigId || !store.enabled) return null\n return store.configs.find((c) => c.id === store.activeConfigId) || null\n}\n\nexport function getEmbeddingConfigById(id: string): EmbeddingServiceConfig | null {\n const store = loadEmbeddingConfigStore()\n return store.configs.find((c) => c.id === id) || null\n}\n\nexport function addEmbeddingConfig(config: Omit<EmbeddingServiceConfig, 'id' | 'createdAt' | 'updatedAt'>): {\n success: boolean\n config?: EmbeddingServiceConfig\n error?: string\n} {\n const store = loadEmbeddingConfigStore()\n\n if (store.configs.length >= MAX_EMBEDDING_CONFIG_COUNT) {\n return { success: false, error: `Max ${MAX_EMBEDDING_CONFIG_COUNT} configs allowed` }\n }\n\n const now = Date.now()\n const newConfig: EmbeddingServiceConfig = {\n ...config,\n id: randomUUID(),\n createdAt: now,\n updatedAt: now,\n }\n\n store.configs.push(newConfig)\n\n if (store.configs.length === 1) {\n store.activeConfigId = newConfig.id\n }\n\n saveEmbeddingConfigStore(store)\n _logger.info('RAG', `Adding Embedding config: ${newConfig.name}`)\n return { success: true, config: newConfig }\n}\n\nexport function updateEmbeddingConfig(\n id: string,\n updates: Partial<Omit<EmbeddingServiceConfig, 'id' | 'createdAt' | 'updatedAt'>>\n): { success: boolean; error?: string } {\n const store = loadEmbeddingConfigStore()\n const index = store.configs.findIndex((c) => c.id === id)\n\n if (index === -1) {\n return { success: false, error: 'Config not found' }\n }\n\n store.configs[index] = {\n ...store.configs[index],\n ...updates,\n updatedAt: Date.now(),\n }\n\n saveEmbeddingConfigStore(store)\n _logger.info('RAG', `Updating Embedding config: ${store.configs[index].name}`)\n return { success: true }\n}\n\nexport function deleteEmbeddingConfig(id: string): { success: boolean; error?: string } {\n const store = loadEmbeddingConfigStore()\n const index = store.configs.findIndex((c) => c.id === id)\n\n if (index === -1) {\n return { success: false, error: 'Config not found' }\n }\n\n const deletedName = store.configs[index].name\n store.configs.splice(index, 1)\n\n if (store.activeConfigId === id) {\n store.activeConfigId = store.configs.length > 0 ? store.configs[0].id : null\n }\n\n saveEmbeddingConfigStore(store)\n _logger.info('RAG', `Deleting Embedding config: ${deletedName}`)\n return { success: true }\n}\n\nexport function setActiveEmbeddingConfig(id: string): { success: boolean; error?: string } {\n const store = loadEmbeddingConfigStore()\n const config = store.configs.find((c) => c.id === id)\n\n if (!config) {\n return { success: false, error: 'Config not found' }\n }\n\n store.activeConfigId = id\n saveEmbeddingConfigStore(store)\n _logger.info('RAG', `Activating Embedding config: ${config.name}`)\n return { success: true }\n}\n\nexport function isEmbeddingEnabled(): boolean {\n const store = loadEmbeddingConfigStore()\n return store.activeConfigId !== null && store.configs.some((c) => c.id === store.activeConfigId)\n}\n\nexport function getActiveEmbeddingConfigId(): string | null {\n return loadEmbeddingConfigStore().activeConfigId\n}\n\n// ==================== Legacy RAG config (compat) ====================\n\nexport function loadRAGConfig(): RAGConfig {\n try {\n const configPath = getConfigPath()\n\n if (!fs.existsSync(configPath)) {\n return { ...DEFAULT_RAG_CONFIG }\n }\n\n const content = fs.readFileSync(configPath, 'utf-8')\n const config = JSON.parse(content) as RAGConfig\n\n return mergeConfig(DEFAULT_RAG_CONFIG, config)\n } catch (error) {\n _logger.error('RAG', 'Failed to load configs', error)\n return { ...DEFAULT_RAG_CONFIG }\n }\n}\n\nexport function saveRAGConfig(config: RAGConfig): void {\n try {\n const configPath = getConfigPath()\n const dir = path.dirname(configPath)\n\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')\n _logger.info('RAG', 'Configs saved')\n } catch (error) {\n _logger.error('RAG', 'Failed to save configs', error)\n throw error\n }\n}\n\nexport function updateRAGConfig(updates: Partial<RAGConfig>): RAGConfig {\n const current = loadRAGConfig()\n const updated = mergeConfig(current, updates)\n saveRAGConfig(updated)\n return updated\n}\n\nfunction mergeConfig<T extends object>(base: T, override: Partial<T>): T {\n const result = { ...base }\n\n for (const key in override) {\n if (Object.prototype.hasOwnProperty.call(override, key)) {\n const overrideValue = override[key]\n const baseValue = base[key]\n\n if (overrideValue !== undefined) {\n if (\n typeof overrideValue === 'object' &&\n overrideValue !== null &&\n !Array.isArray(overrideValue) &&\n typeof baseValue === 'object' &&\n baseValue !== null &&\n !Array.isArray(baseValue)\n ) {\n ;(result as Record<string, unknown>)[key] = mergeConfig(baseValue as object, overrideValue as object)\n } else {\n ;(result as Record<string, unknown>)[key] = overrideValue\n }\n }\n }\n }\n\n return result\n}\n\nexport function resetRAGConfig(): RAGConfig {\n const config = { ...DEFAULT_RAG_CONFIG }\n saveRAGConfig(config)\n return config\n}\n","/**\n * RAG module type definitions\n */\n\n// ==================== Logger interface ====================\n\nexport interface RagLogger {\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\nconst noopLogger: RagLogger = {\n info() {\n /* noop */\n },\n warn() {\n /* noop */\n },\n error() {\n /* noop */\n },\n}\n\nexport function getNoopLogger(): RagLogger {\n return noopLogger\n}\n\n// ==================== Embedding service config (multi-config) ====================\n\nexport interface EmbeddingServiceConfig {\n id: string\n name: string\n apiSource: 'reuse_llm' | 'custom'\n model: string\n baseUrl?: string\n apiKey?: string\n createdAt: number\n updatedAt: number\n}\n\nexport interface EmbeddingConfigStore {\n configs: EmbeddingServiceConfig[]\n activeConfigId: string | null\n enabled: boolean\n}\n\nexport const MAX_EMBEDDING_CONFIG_COUNT = 10\n\nexport const DEFAULT_EMBEDDING_CONFIG_STORE: EmbeddingConfigStore = {\n configs: [],\n activeConfigId: null,\n enabled: false,\n}\n\n// ==================== Legacy EmbeddingConfig (compat) ====================\n\n/** @deprecated Use EmbeddingServiceConfig instead */\nexport interface EmbeddingConfig {\n enabled: boolean\n provider: 'api'\n apiSource?: 'reuse_llm' | 'custom'\n model?: string\n baseUrl?: string\n apiKey?: string\n}\n\n// ==================== Vector store types ====================\n\nexport interface VectorStoreConfig {\n enabled: boolean\n type: 'memory' | 'sqlite' | 'lancedb'\n memoryCacheSize?: number\n dbPath?: string\n}\n\n// ==================== Rerank types (reserved) ====================\n\nexport interface RerankConfig {\n enabled: boolean\n provider: 'jina' | 'cohere' | 'bge' | 'custom'\n model?: string\n baseUrl?: string\n apiKey?: string\n topK?: number\n}\n\n// ==================== RAG config ====================\n\nexport interface RAGConfig {\n embedding?: EmbeddingConfig\n vectorStore?: VectorStoreConfig\n rerank?: RerankConfig\n enableSemanticPipeline?: boolean\n candidateLimit?: number\n topK?: number\n}\n\nexport const DEFAULT_RAG_CONFIG: RAGConfig = {\n embedding: {\n enabled: false,\n provider: 'api',\n apiSource: 'reuse_llm',\n },\n vectorStore: {\n enabled: true,\n type: 'sqlite',\n },\n enableSemanticPipeline: true,\n candidateLimit: 50,\n topK: 10,\n}\n\n// ==================== Embedding service interface ====================\n\nexport interface IEmbeddingService {\n getProvider(): string\n getDimensions(): number\n embed(text: string): Promise<number[]>\n embedBatch(texts: string[]): Promise<number[][]>\n validate(): Promise<{ success: boolean; error?: string }>\n dispose(): Promise<void>\n}\n\nexport interface EmbeddingResult {\n text: string\n vector: number[]\n dimensions: number\n}\n\n// ==================== Vector store service interface ====================\n\nexport interface IVectorStore {\n add(id: string, vector: number[], metadata?: Record<string, unknown>): Promise<void>\n addBatch(items: Array<{ id: string; vector: number[]; metadata?: Record<string, unknown> }>): Promise<void>\n get(id: string): Promise<number[] | null>\n has(id: string): Promise<boolean>\n delete(id: string): Promise<void>\n search(query: number[], topK: number): Promise<VectorSearchResult[]>\n clear(): Promise<void>\n getStats(): Promise<VectorStoreStats>\n close(): Promise<void>\n}\n\nexport interface VectorSearchResult {\n id: string\n score: number\n metadata?: Record<string, unknown>\n}\n\nexport interface VectorStoreStats {\n count: number\n dimensions?: number\n sizeBytes?: number\n}\n\n// ==================== Chunk types ====================\n\nexport interface Chunk {\n id: string\n type: 'session' | 'window' | 'time'\n content: string\n metadata: ChunkMetadata\n}\n\nexport interface ChunkMetadata {\n sessionId?: number\n startTs: number\n endTs: number\n messageCount: number\n participants: string[]\n subChunkIndex?: number\n totalSubChunks?: number\n}\n\n// ==================== Rerank service interface (reserved) ====================\n\nexport interface IRerankService {\n rerank(query: string, documents: string[], topK?: number): Promise<RerankResult[]>\n validate(): Promise<{ success: boolean; error?: string }>\n}\n\nexport interface RerankResult {\n index: number\n score: number\n text: string\n}\n\n// ==================== Pipeline types ====================\n\nexport interface SemanticPipelineOptions {\n userMessage: string\n dbPath: string\n timeFilter?: { startTs: number; endTs: number }\n candidateLimit?: number\n topK?: number\n useRerank?: boolean\n abortSignal?: AbortSignal\n}\n\nexport interface SemanticPipelineResult {\n success: boolean\n rewrittenQuery?: string\n results: Array<{\n score: number\n chunkId: string\n content: string\n metadata?: ChunkMetadata\n }>\n evidenceBlock?: string\n error?: string\n}\n\n// ==================== LLM config for embedding resolution ====================\n\n/**\n * Minimal LLM config needed to resolve embedding API params\n * when apiSource is 'reuse_llm'.\n */\nexport interface LLMConfigForEmbedding {\n provider: string\n apiKey?: string\n baseUrl?: string\n}\n","/**\n * Session-level chunking implementation (platform-agnostic)\n *\n * Uses better-sqlite3 directly (node-runtime already depends on it).\n */\n\nimport Database from 'better-sqlite3'\nimport type { Chunk, ChunkMetadata } from './types'\nimport type { SessionMessage, SessionInfo, ChunkingOptions } from './types'\nimport { INVALID_MESSAGE_TYPES, INVALID_TEXT_PATTERNS } from './types'\nimport type { RagLogger } from '../types'\nimport { getNoopLogger } from '../types'\n\nconst MAX_CHUNK_CHARS = 2000\nconst CHUNK_OVERLAP_CHARS = 200\n\nlet _logger: RagLogger = getNoopLogger()\n\nexport function initChunkingLogger(logger: RagLogger): void {\n _logger = logger\n}\n\nfunction formatTimeRange(startTs: number, endTs: number): string {\n const startDate = new Date(startTs)\n const endDate = new Date(endTs)\n\n const formatDate = (d: Date) => {\n const year = d.getFullYear()\n const month = String(d.getMonth() + 1).padStart(2, '0')\n const day = String(d.getDate()).padStart(2, '0')\n const hour = String(d.getHours()).padStart(2, '0')\n const minute = String(d.getMinutes()).padStart(2, '0')\n return `${year}-${month}-${day} ${hour}:${minute}`\n }\n\n return `${formatDate(startDate)} ~ ${formatDate(endDate)}`\n}\n\nfunction filterValidMessages(messages: SessionMessage[]): SessionMessage[] {\n return messages.filter((m) => {\n if (m.type !== undefined && INVALID_MESSAGE_TYPES.includes(m.type as (typeof INVALID_MESSAGE_TYPES)[number])) {\n return false\n }\n\n if (!m.content || m.content.trim().length === 0) {\n return false\n }\n\n const content = m.content.trim()\n for (const pattern of INVALID_TEXT_PATTERNS) {\n if (content.includes(pattern)) {\n return false\n }\n }\n\n return true\n })\n}\n\nexport function formatSessionChunk(\n session: SessionInfo,\n messages: SessionMessage[],\n filterInvalid: boolean = true\n): string {\n const timeRange = formatTimeRange(session.startTs, session.endTs)\n\n const validMessages = filterInvalid ? filterValidMessages(messages) : messages\n\n if (validMessages.length === 0) {\n return ''\n }\n\n const participants = [...new Set(validMessages.map((m) => m.senderName))].join('、')\n\n const content = validMessages.map((m) => `${m.senderName}: ${m.content}`).join('\\n')\n\n return `[${timeRange}] 参与者:${participants}\\n${content}`\n}\n\nfunction splitIntoSubChunks(\n content: string,\n maxChars: number = MAX_CHUNK_CHARS,\n overlapChars: number = CHUNK_OVERLAP_CHARS\n): string[] {\n if (content.length <= maxChars) {\n return [content]\n }\n\n const chunks: string[] = []\n const lines = content.split('\\n')\n\n let currentChunk = ''\n let overlapBuffer = ''\n\n for (const line of lines) {\n if (line.length > maxChars) {\n if (currentChunk) {\n chunks.push(currentChunk)\n const chunkLines = currentChunk.split('\\n')\n overlapBuffer = chunkLines.slice(-3).join('\\n').slice(-overlapChars)\n currentChunk = ''\n }\n\n let remaining = line\n while (remaining.length > 0) {\n const part = remaining.slice(0, maxChars - overlapBuffer.length)\n chunks.push(overlapBuffer + part)\n overlapBuffer = part.slice(-overlapChars)\n remaining = remaining.slice(maxChars - overlapBuffer.length)\n }\n continue\n }\n\n const newLength = currentChunk.length + (currentChunk ? 1 : 0) + line.length\n\n if (newLength > maxChars) {\n if (currentChunk) {\n chunks.push(currentChunk)\n const chunkLines = currentChunk.split('\\n')\n overlapBuffer = chunkLines.slice(-3).join('\\n').slice(-overlapChars)\n }\n currentChunk = overlapBuffer ? overlapBuffer + '\\n' + line : line\n } else {\n currentChunk = currentChunk ? currentChunk + '\\n' + line : line\n }\n }\n\n if (currentChunk) {\n chunks.push(currentChunk)\n }\n\n return chunks\n}\n\nfunction getSessionsFromDb(db: Database.Database, options: ChunkingOptions): SessionInfo[] {\n const { limit = 50, timeFilter } = options\n\n let sql = `\n SELECT\n id,\n start_ts as startTs,\n end_ts as endTs,\n message_count as messageCount\n FROM chat_session\n `\n\n const params: (number | undefined)[] = []\n\n if (timeFilter) {\n sql += ' WHERE start_ts >= ? AND end_ts <= ?'\n params.push(timeFilter.startTs, timeFilter.endTs)\n }\n\n sql += ' ORDER BY start_ts DESC LIMIT ?'\n params.push(limit)\n\n const sessions = db.prepare(sql).all(...params) as SessionInfo[]\n\n return sessions.reverse()\n}\n\nfunction getSessionMessagesFromDb(db: Database.Database, sessionId: number, limit: number = 500): SessionMessage[] {\n const sql = `\n SELECT\n m.id,\n COALESCE(mb.group_nickname, mb.account_name, mb.platform_id) as senderName,\n m.content,\n m.ts as timestamp,\n m.type\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.session_id = ?\n ORDER BY m.ts ASC\n LIMIT ?\n `\n\n return db.prepare(sql).all(sessionId, limit) as SessionMessage[]\n}\n\nexport function getSessionChunks(dbPath: string, options: ChunkingOptions = {}): Chunk[] {\n const { filterInvalid = true, maxChunkChars = MAX_CHUNK_CHARS } = options\n\n let db: Database.Database | null = null\n\n try {\n db = new Database(dbPath, { readonly: true })\n\n const sessions = getSessionsFromDb(db, options)\n\n if (sessions.length === 0) {\n return []\n }\n\n const chunks: Chunk[] = []\n\n for (const session of sessions) {\n const messages = getSessionMessagesFromDb(db, session.id)\n const content = formatSessionChunk(session, messages, filterInvalid)\n\n if (!content) {\n continue\n }\n\n const validMessages = filterInvalid ? filterValidMessages(messages) : messages\n const participants = [...new Set(validMessages.map((m) => m.senderName))]\n\n const baseMetadata: ChunkMetadata = {\n sessionId: session.id,\n startTs: session.startTs,\n endTs: session.endTs,\n messageCount: validMessages.length,\n participants,\n }\n\n if (content.length <= maxChunkChars) {\n chunks.push({\n id: `session_${session.id}`,\n type: 'session',\n content,\n metadata: baseMetadata,\n })\n } else {\n const subChunks = splitIntoSubChunks(content, maxChunkChars)\n\n for (let i = 0; i < subChunks.length; i++) {\n chunks.push({\n id: `session_${session.id}_part${i + 1}`,\n type: 'session',\n content: subChunks[i],\n metadata: {\n ...baseMetadata,\n subChunkIndex: i,\n totalSubChunks: subChunks.length,\n },\n })\n }\n }\n }\n\n return chunks\n } catch (error) {\n _logger.error('Chunking', 'Failed to get session chunks', error)\n return []\n } finally {\n if (db) {\n db.close()\n }\n }\n}\n\nexport function getSessionChunk(dbPath: string, sessionId: number): Chunk | null {\n let db: Database.Database | null = null\n\n try {\n db = new Database(dbPath, { readonly: true })\n\n const session = db\n .prepare(\n `\n SELECT\n id,\n start_ts as startTs,\n end_ts as endTs,\n message_count as messageCount\n FROM chat_session\n WHERE id = ?\n `\n )\n .get(sessionId) as SessionInfo | undefined\n\n if (!session) {\n return null\n }\n\n const messages = getSessionMessagesFromDb(db, sessionId)\n\n const content = formatSessionChunk(session, messages, true)\n\n if (!content) {\n return null\n }\n\n const validMessages = filterValidMessages(messages)\n const participants = [...new Set(validMessages.map((m) => m.senderName))]\n\n return {\n id: `session_${session.id}`,\n type: 'session',\n content,\n metadata: {\n sessionId: session.id,\n startTs: session.startTs,\n endTs: session.endTs,\n messageCount: validMessages.length,\n participants,\n },\n }\n } catch (error) {\n _logger.error('Chunking', 'Failed to get single session chunk', error)\n return null\n } finally {\n if (db) {\n db.close()\n }\n }\n}\n","/**\n * Chunking type definitions\n */\n\nexport type { Chunk, ChunkMetadata } from '../types'\n\n/**\n * Message types with no semantic value — filtered before embedding.\n */\nexport const INVALID_MESSAGE_TYPES = [\n 0, // system\n 3, // image\n 4, // voice\n 5, // video\n 6, // file\n 7, // location\n 8, // contact card\n 10, // revoked\n 11, // red packet\n 12, // transfer\n] as const\n\n/**\n * Placeholder text patterns to filter out.\n */\nexport const INVALID_TEXT_PATTERNS = [\n '[图片]',\n '[语音]',\n '[视频]',\n '[文件]',\n '[表情]',\n '[动画表情]',\n '[位置]',\n '[名片]',\n '[红包]',\n '[转账]',\n '[撤回消息]',\n '撤回了一条消息',\n '你撤回了一条消息',\n]\n\nexport interface SessionMessage {\n id: number\n senderName: string\n content: string | null\n timestamp: number\n type?: number\n}\n\nexport interface SessionInfo {\n id: number\n startTs: number\n endTs: number\n messageCount: number\n}\n\nexport interface ChunkingOptions {\n limit?: number\n timeFilter?: {\n startTs: number\n endTs: number\n }\n filterInvalid?: boolean\n maxChunkChars?: number\n}\n","/**\n * OpenAI-compatible embedding service (platform-agnostic)\n */\n\nimport type { IEmbeddingService, RagLogger } from '../types'\nimport { getNoopLogger } from '../types'\n\nexport interface OpenAICompatibleEmbeddingConfig {\n baseUrl: string\n apiKey?: string\n model: string\n}\n\nlet _logger: RagLogger = getNoopLogger()\n\nexport function initOpenAICompatibleLogger(logger: RagLogger): void {\n _logger = logger\n}\n\nexport class OpenAICompatibleEmbeddingService implements IEmbeddingService {\n private baseUrl: string\n private apiKey?: string\n private model: string\n private dimensions: number = 0\n\n constructor(config: OpenAICompatibleEmbeddingConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '')\n this.apiKey = config.apiKey\n this.model = config.model\n }\n\n async embed(text: string): Promise<number[]> {\n const vectors = await this.callEmbeddingApi([text])\n return vectors[0]\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n return this.callEmbeddingApi(texts)\n }\n\n private async callEmbeddingApi(input: string[]): Promise<number[][]> {\n const url = `${this.baseUrl}/embeddings`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`\n }\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n model: this.model,\n input,\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`API request failed (${response.status}): ${errorText}`)\n }\n\n const data = (await response.json()) as {\n data?: Array<{ embedding: number[]; index: number }>\n }\n\n if (!data.data || data.data.length === 0) {\n throw new Error('API returned empty data')\n }\n\n const sorted = data.data.sort((a, b) => a.index - b.index)\n const vectors = sorted.map((item) => item.embedding)\n\n if (vectors.length > 0 && this.dimensions === 0) {\n this.dimensions = vectors[0].length\n }\n\n return vectors\n } catch (error) {\n _logger.error('RAG', `Embedding API call failed: ${url}`, error)\n throw error\n }\n }\n\n getProvider(): string {\n return `OpenAI Compatible (${this.model})`\n }\n\n getDimensions(): number {\n return this.dimensions\n }\n\n async validate(): Promise<{ success: boolean; error?: string }> {\n try {\n const testVector = await this.embed('test')\n\n if (testVector.length === 0) {\n return { success: false, error: 'Returned vector is empty' }\n }\n\n return { success: true }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n }\n\n async dispose(): Promise<void> {\n // API service has no resources to release\n }\n}\n","/**\n * Embedding service manager (platform-agnostic)\n *\n * LLM config resolution is injected via `initEmbeddingService()`.\n */\n\nimport type {\n IEmbeddingService,\n EmbeddingConfig,\n EmbeddingServiceConfig,\n LLMConfigForEmbedding,\n RagLogger,\n} from '../types'\nimport { getNoopLogger } from '../types'\nimport { OpenAICompatibleEmbeddingService } from './openai-compatible'\nimport { getActiveEmbeddingConfig, isEmbeddingEnabled } from '../config'\n\nlet activeService: IEmbeddingService | null = null\nlet activeConfigId: string | null = null\n\nlet _logger: RagLogger = getNoopLogger()\nlet _getLLMConfig: (() => LLMConfigForEmbedding | null) | null = null\n\n/**\n * Initialize the embedding service module.\n * @param logger - Logger instance\n * @param getLLMConfig - Callback to resolve LLM config when apiSource is 'reuse_llm'\n */\nexport function initEmbeddingService(logger: RagLogger, getLLMConfig: () => LLMConfigForEmbedding | null): void {\n _logger = logger\n _getLLMConfig = getLLMConfig\n}\n\nexport async function getEmbeddingService(): Promise<IEmbeddingService | null> {\n if (!isEmbeddingEnabled()) {\n return null\n }\n\n const config = getActiveEmbeddingConfig()\n if (!config) {\n return null\n }\n\n if (activeService && activeConfigId === config.id) {\n return activeService\n }\n\n if (activeService) {\n await activeService.dispose()\n activeService = null\n }\n\n try {\n activeService = await createEmbeddingService(config)\n activeConfigId = config.id\n return activeService\n } catch (error) {\n _logger.error('RAG', 'Failed to create Embedding service', error)\n return null\n }\n}\n\nasync function createEmbeddingService(config: EmbeddingServiceConfig): Promise<IEmbeddingService> {\n const apiConfig = resolveApiConfig(config)\n _logger.info('RAG', `Using Embedding: ${config.name} (${apiConfig.model})`)\n\n return new OpenAICompatibleEmbeddingService(apiConfig)\n}\n\nfunction resolveApiConfig(config: EmbeddingServiceConfig): {\n baseUrl: string\n apiKey?: string\n model: string\n} {\n if (config.apiSource === 'reuse_llm') {\n if (!_getLLMConfig) {\n throw new Error('Embedding service not initialized — call initEmbeddingService() first')\n }\n\n const llmConfig = _getLLMConfig()\n\n if (!llmConfig) {\n throw new Error('No active LLM config found — add an AI service in Model Settings first')\n }\n\n const baseUrl = llmConfig.baseUrl || getDefaultBaseUrl(llmConfig.provider)\n\n return {\n baseUrl,\n apiKey: llmConfig.apiKey || undefined,\n model: config.model,\n }\n } else {\n if (!config.baseUrl) {\n throw new Error('Custom API mode requires a base URL')\n }\n\n return {\n baseUrl: config.baseUrl,\n apiKey: config.apiKey,\n model: config.model,\n }\n }\n}\n\nfunction getDefaultBaseUrl(provider: string): string {\n const defaultUrls: Record<string, string> = {\n deepseek: 'https://api.deepseek.com/v1',\n qwen: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n openai: 'https://api.openai.com/v1',\n 'openai-compatible': 'http://localhost:11434/v1',\n }\n\n return defaultUrls[provider] || 'http://localhost:11434/v1'\n}\n\nexport async function resetEmbeddingService(): Promise<void> {\n if (activeService) {\n await activeService.dispose()\n activeService = null\n activeConfigId = null\n _logger.info('RAG', 'Embedding service reset')\n }\n}\n\nexport async function validateEmbeddingConfig(\n config: EmbeddingConfig | EmbeddingServiceConfig\n): Promise<{ success: boolean; error?: string }> {\n try {\n const serviceConfig: EmbeddingServiceConfig =\n 'id' in config\n ? config\n : {\n id: 'temp',\n name: 'temp',\n apiSource: config.apiSource || 'reuse_llm',\n model: config.model || 'nomic-embed-text',\n baseUrl: config.baseUrl,\n apiKey: config.apiKey,\n createdAt: Date.now(),\n updatedAt: Date.now(),\n }\n\n const service = await createEmbeddingService(serviceConfig)\n const result = await service.validate()\n await service.dispose()\n return result\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\nexport type { IEmbeddingService, EmbeddingConfig, EmbeddingServiceConfig }\n","/**\n * Vector store manager (platform-agnostic)\n */\n\nimport * as path from 'path'\nimport type { IVectorStore, VectorStoreConfig } from './types'\nimport { SQLiteVectorStore } from './sqlite'\nimport { MemoryVectorStore } from './memory'\nimport { getVectorStoreDir, loadRAGConfig } from '../config'\nimport type { RagLogger } from '../types'\nimport { getNoopLogger } from '../types'\n\nlet activeStore: IVectorStore | null = null\n\nlet _logger: RagLogger = getNoopLogger()\n\nexport function initStoreLogger(logger: RagLogger): void {\n _logger = logger\n}\n\nexport async function getVectorStore(): Promise<IVectorStore | null> {\n const config = loadRAGConfig()\n\n if (!config.vectorStore?.enabled) {\n return null\n }\n\n if (activeStore) {\n return activeStore\n }\n\n try {\n activeStore = await createVectorStore(config.vectorStore)\n return activeStore\n } catch (error) {\n _logger.error('Store Manager', 'Failed to create store', error)\n return null\n }\n}\n\nasync function createVectorStore(config: VectorStoreConfig): Promise<IVectorStore> {\n switch (config.type) {\n case 'memory': {\n const capacity = config.memoryCacheSize || 10000\n _logger.info('Store Manager', `Using memory store, capacity: ${capacity}`)\n return new MemoryVectorStore(capacity)\n }\n\n case 'sqlite': {\n const dbPath = config.dbPath || path.join(getVectorStoreDir(), 'embeddings.db')\n _logger.info('Store Manager', `Using SQLite store: ${dbPath}`)\n return new SQLiteVectorStore(dbPath)\n }\n\n case 'lancedb': {\n throw new Error('LanceDB storage not implemented yet')\n }\n\n default:\n throw new Error(`Unknown store type: ${config.type}`)\n }\n}\n\nexport async function resetVectorStore(): Promise<void> {\n if (activeStore) {\n await activeStore.close()\n activeStore = null\n _logger.info('Store Manager', 'Store reset')\n }\n}\n\nexport async function getVectorStoreStats(): Promise<{\n enabled: boolean\n type?: string\n count?: number\n dimensions?: number\n sizeBytes?: number\n}> {\n const config = loadRAGConfig()\n\n if (!config.vectorStore?.enabled) {\n return { enabled: false }\n }\n\n const store = await getVectorStore()\n if (!store) {\n return { enabled: true, type: config.vectorStore.type }\n }\n\n const stats = await store.getStats()\n return {\n enabled: true,\n type: config.vectorStore.type,\n ...stats,\n }\n}\n\nexport { SQLiteVectorStore } from './sqlite'\nexport { MemoryVectorStore } from './memory'\nexport type { IVectorStore, VectorSearchResult, VectorStoreStats, VectorStoreConfig } from './types'\n","/**\n * SQLite vector store (BLOB format, platform-agnostic)\n *\n * Uses better-sqlite3 directly (node-runtime already depends on it).\n */\n\nimport Database from 'better-sqlite3'\nimport type { IVectorStore, VectorSearchResult, VectorStoreStats } from './types'\nimport { cosineSimilarity } from '../utils'\nimport type { RagLogger } from '../types'\nimport { getNoopLogger } from '../types'\n\nlet _logger: RagLogger = getNoopLogger()\n\nexport function initSqliteStoreLogger(logger: RagLogger): void {\n _logger = logger\n}\n\nfunction vectorToBuffer(vector: number[]): Buffer {\n const float32 = new Float32Array(vector)\n return Buffer.from(float32.buffer)\n}\n\nfunction bufferToVector(buffer: Buffer): number[] {\n const float32 = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.length / 4)\n return Array.from(float32)\n}\n\nexport class SQLiteVectorStore implements IVectorStore {\n private db: Database.Database\n private dbPath: string\n\n constructor(dbPath: string) {\n this.dbPath = dbPath\n this.db = new Database(dbPath)\n this.initSchema()\n }\n\n private initSchema(): void {\n this.db.pragma('journal_mode = WAL')\n\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS vectors (\n id TEXT PRIMARY KEY,\n vector BLOB NOT NULL,\n dimensions INTEGER NOT NULL,\n metadata TEXT,\n created_at INTEGER DEFAULT (strftime('%s', 'now') * 1000)\n )\n `)\n\n try {\n this.db.exec('CREATE INDEX IF NOT EXISTS idx_vectors_created ON vectors(created_at)')\n } catch {\n // index may already exist\n }\n\n _logger.info('SQLite Store', `Initialized: ${this.dbPath}`)\n }\n\n async add(id: string, vector: number[], metadata?: Record<string, unknown>): Promise<void> {\n const buffer = vectorToBuffer(vector)\n\n this.db\n .prepare(\n `\n INSERT OR REPLACE INTO vectors (id, vector, dimensions, metadata)\n VALUES (?, ?, ?, ?)\n `\n )\n .run(id, buffer, vector.length, metadata ? JSON.stringify(metadata) : null)\n }\n\n async addBatch(items: Array<{ id: string; vector: number[]; metadata?: Record<string, unknown> }>): Promise<void> {\n const insert = this.db.prepare(`\n INSERT OR REPLACE INTO vectors (id, vector, dimensions, metadata)\n VALUES (?, ?, ?, ?)\n `)\n\n type VectorBatchItem = { id: string; vector: number[]; metadata?: Record<string, unknown> }\n const insertMany = this.db.transaction((batchItems: VectorBatchItem[]) => {\n for (const item of batchItems) {\n const buffer = vectorToBuffer(item.vector)\n insert.run(item.id, buffer, item.vector.length, item.metadata ? JSON.stringify(item.metadata) : null)\n }\n })\n\n insertMany(items)\n }\n\n async get(id: string): Promise<number[] | null> {\n const row = this.db.prepare('SELECT vector FROM vectors WHERE id = ?').get(id) as { vector: Buffer } | undefined\n\n if (!row) return null\n\n return bufferToVector(row.vector)\n }\n\n async has(id: string): Promise<boolean> {\n const row = this.db.prepare('SELECT 1 FROM vectors WHERE id = ?').get(id)\n return !!row\n }\n\n async delete(id: string): Promise<void> {\n this.db.prepare('DELETE FROM vectors WHERE id = ?').run(id)\n }\n\n async search(query: number[], topK: number): Promise<VectorSearchResult[]> {\n const rows = this.db.prepare('SELECT id, vector, metadata FROM vectors').all() as Array<{\n id: string\n vector: Buffer\n metadata: string | null\n }>\n\n if (rows.length === 0) {\n return []\n }\n\n const results: VectorSearchResult[] = rows.map((row) => {\n const vector = bufferToVector(row.vector)\n const score = cosineSimilarity(query, vector)\n return {\n id: row.id,\n score,\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n }\n })\n\n results.sort((a, b) => b.score - a.score)\n return results.slice(0, topK)\n }\n\n async clear(): Promise<void> {\n this.db.exec('DELETE FROM vectors')\n _logger.info('SQLite Store', 'All vectors cleared')\n }\n\n async getStats(): Promise<VectorStoreStats> {\n const countRow = this.db.prepare('SELECT COUNT(*) as count FROM vectors').get() as { count: number }\n\n const dimRow = this.db.prepare('SELECT dimensions FROM vectors LIMIT 1').get() as { dimensions: number } | undefined\n\n let sizeBytes: number | undefined\n try {\n const fs = await import('fs')\n const stats = fs.statSync(this.dbPath)\n sizeBytes = stats.size\n } catch {\n // ignore\n }\n\n return {\n count: countRow.count,\n dimensions: dimRow?.dimensions,\n sizeBytes,\n }\n }\n\n async close(): Promise<void> {\n this.db.close()\n _logger.info('SQLite Store', 'Closed')\n }\n}\n","/**\n * Shared RAG utilities\n */\n\n/**\n * Cosine similarity between two vectors\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0\n let normA = 0\n let normB = 0\n const len = Math.min(a.length, b.length)\n\n for (let i = 0; i < len; i++) {\n dot += a[i] * b[i]\n normA += a[i] * a[i]\n normB += b[i] * b[i]\n }\n\n return dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-12)\n}\n","/**\n * In-memory vector store (LRU cache, platform-agnostic)\n */\n\nimport type { IVectorStore, VectorSearchResult, VectorStoreStats } from './types'\nimport { cosineSimilarity } from '../utils'\nimport type { RagLogger } from '../types'\nimport { getNoopLogger } from '../types'\n\nlet _logger: RagLogger = getNoopLogger()\n\nexport function initMemoryStoreLogger(logger: RagLogger): void {\n _logger = logger\n}\n\ninterface CacheNode {\n id: string\n vector: number[]\n metadata?: Record<string, unknown>\n prev: CacheNode | null\n next: CacheNode | null\n}\n\nexport class MemoryVectorStore implements IVectorStore {\n private capacity: number\n private cache: Map<string, CacheNode> = new Map()\n private head: CacheNode | null = null\n private tail: CacheNode | null = null\n\n constructor(capacity: number = 10000) {\n this.capacity = capacity\n }\n\n private moveToHead(node: CacheNode): void {\n if (node === this.head) return\n\n if (node.prev) {\n node.prev.next = node.next\n }\n if (node.next) {\n node.next.prev = node.prev\n }\n if (node === this.tail) {\n this.tail = node.prev\n }\n\n node.prev = null\n node.next = this.head\n if (this.head) {\n this.head.prev = node\n }\n this.head = node\n\n if (!this.tail) {\n this.tail = node\n }\n }\n\n private addToHead(node: CacheNode): void {\n node.prev = null\n node.next = this.head\n if (this.head) {\n this.head.prev = node\n }\n this.head = node\n\n if (!this.tail) {\n this.tail = node\n }\n }\n\n private removeTail(): CacheNode | null {\n if (!this.tail) return null\n\n const removed = this.tail\n this.tail = removed.prev\n if (this.tail) {\n this.tail.next = null\n } else {\n this.head = null\n }\n\n return removed\n }\n\n async add(id: string, vector: number[], metadata?: Record<string, unknown>): Promise<void> {\n if (this.cache.has(id)) {\n const node = this.cache.get(id)!\n node.vector = vector\n node.metadata = metadata\n this.moveToHead(node)\n } else {\n const node: CacheNode = {\n id,\n vector,\n metadata,\n prev: null,\n next: null,\n }\n\n this.cache.set(id, node)\n this.addToHead(node)\n\n if (this.cache.size > this.capacity) {\n const removed = this.removeTail()\n if (removed) {\n this.cache.delete(removed.id)\n }\n }\n }\n }\n\n async addBatch(items: Array<{ id: string; vector: number[]; metadata?: Record<string, unknown> }>): Promise<void> {\n for (const item of items) {\n await this.add(item.id, item.vector, item.metadata)\n }\n }\n\n async get(id: string): Promise<number[] | null> {\n const node = this.cache.get(id)\n if (!node) return null\n\n this.moveToHead(node)\n return node.vector\n }\n\n async has(id: string): Promise<boolean> {\n return this.cache.has(id)\n }\n\n async delete(id: string): Promise<void> {\n const node = this.cache.get(id)\n if (!node) return\n\n if (node.prev) {\n node.prev.next = node.next\n }\n if (node.next) {\n node.next.prev = node.prev\n }\n if (node === this.head) {\n this.head = node.next\n }\n if (node === this.tail) {\n this.tail = node.prev\n }\n\n this.cache.delete(id)\n }\n\n async search(query: number[], topK: number): Promise<VectorSearchResult[]> {\n const results: VectorSearchResult[] = []\n\n for (const node of this.cache.values()) {\n const score = cosineSimilarity(query, node.vector)\n results.push({\n id: node.id,\n score,\n metadata: node.metadata,\n })\n }\n\n results.sort((a, b) => b.score - a.score)\n return results.slice(0, topK)\n }\n\n async clear(): Promise<void> {\n this.cache.clear()\n this.head = null\n this.tail = null\n _logger.info('Memory Store', 'All vectors cleared')\n }\n\n async getStats(): Promise<VectorStoreStats> {\n let dimensions: number | undefined\n for (const node of this.cache.values()) {\n dimensions = node.vector.length\n break\n }\n\n let sizeBytes: number | undefined\n if (dimensions) {\n sizeBytes = this.cache.size * dimensions * 8\n }\n\n return {\n count: this.cache.size,\n dimensions,\n sizeBytes,\n }\n }\n\n async close(): Promise<void> {\n _logger.info('Memory Store', 'Closed')\n }\n}\n","/**\n * Semantic Pipeline implementation (platform-agnostic)\n *\n * RAG flow: Query rewrite → Chunk recall → Vector similarity ranking → Evidence block\n *\n * LLM dependencies (buildPiModel, completeSimple) are available from\n * @openchatlab/node-runtime. The active AI service config is injected\n * via `initSemanticPipeline()`.\n */\n\nimport type { SemanticPipelineOptions, SemanticPipelineResult, ChunkMetadata, RagLogger } from '../types'\nimport type { Chunk } from '../types'\nimport { getNoopLogger } from '../types'\nimport { cosineSimilarity } from '../utils'\nimport { getEmbeddingService } from '../embedding'\nimport { getVectorStore } from '../store'\nimport { getSessionChunks } from '../chunking'\nimport { loadRAGConfig } from '../config'\nimport { completeSimple, type TextContent } from '@earendil-works/pi-ai'\nimport { buildPiModel, type PiModelConfig } from '../../llm-builder'\n\n/**\n * Minimal LLM config for query rewrite. Provider-specific fields\n * are resolved by buildPiModel from @openchatlab/node-runtime.\n */\nexport interface SemanticPipelineLLMConfig extends PiModelConfig {\n apiKey?: string\n}\n\nlet _logger: RagLogger = getNoopLogger()\nlet _getLLMConfig: (() => SemanticPipelineLLMConfig | null) | null = null\n\n/**\n * Initialize the semantic pipeline module.\n * @param logger - Logger instance\n * @param getLLMConfig - Callback returning the active LLM config for query rewrite\n */\nexport function initSemanticPipeline(logger: RagLogger, getLLMConfig: () => SemanticPipelineLLMConfig | null): void {\n _logger = logger\n _getLLMConfig = getLLMConfig\n}\n\nconst QUERY_REWRITE_PROMPT = `你是一个查询优化专家。请将用户的问题改写为更适合语义检索的查询。\n\n要求:\n1. 保留核心语义,去除口语化表达\n2. 提取关键实体和概念\n3. 扩展同义词或相关表达\n4. 输出一个简洁的检索查询,不要解释\n\n用户问题:{query}\n\n改写后的查询:`\n\nasync function rewriteQuery(query: string, abortSignal?: AbortSignal): Promise<string> {\n try {\n if (!_getLLMConfig) return query\n\n const activeConfig = _getLLMConfig()\n if (!activeConfig) return query\n\n const piModel = buildPiModel(activeConfig)\n const prompt = QUERY_REWRITE_PROMPT.replace('{query}', query)\n\n const result = await completeSimple(\n piModel,\n {\n systemPrompt: '你是一个查询优化专家,专门将用户问题改写为更适合语义检索的形式。',\n messages: [{ role: 'user', content: prompt, timestamp: Date.now() }],\n },\n {\n apiKey: activeConfig.apiKey,\n temperature: 0.3,\n maxTokens: 200,\n signal: abortSignal,\n }\n )\n\n const rewritten = result.content\n .filter((item): item is TextContent => item.type === 'text')\n .map((item) => item.text)\n .join('')\n .trim()\n\n return rewritten || query\n } catch (error) {\n _logger.warn('Semantic Pipeline', 'Query rewrite failed, using original query', error)\n return query\n }\n}\n\nfunction formatEvidenceBlock(\n rewrittenQuery: string,\n results: Array<{ score: number; chunkId: string; content: string; metadata?: ChunkMetadata }>\n): string {\n if (results.length === 0) {\n return ''\n }\n\n const lines = [`<evidence query=\"${rewrittenQuery}\">`, `以下是与用户问题语义相关的历史对话片段(按相关度排序):`, '']\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n const score = (result.score * 100).toFixed(1)\n lines.push(`--- 片段 ${i + 1} (相关度: ${score}%) ---`)\n lines.push(result.content)\n lines.push('')\n }\n\n lines.push('</evidence>')\n lines.push('')\n lines.push('请基于以上历史对话片段回答用户的问题。如果片段中没有相关信息,请说明。')\n\n return lines.join('\\n')\n}\n\nexport async function executeSemanticPipeline(options: SemanticPipelineOptions): Promise<SemanticPipelineResult> {\n const { userMessage, dbPath, timeFilter, abortSignal } = options\n\n const ragConfig = loadRAGConfig()\n const candidateLimit = options.candidateLimit ?? ragConfig.candidateLimit ?? 50\n const topK = options.topK ?? ragConfig.topK ?? 10\n\n _logger.info('RAG', `Starting semantic search: \"${userMessage.slice(0, 50)}...\"`)\n\n try {\n const embeddingService = await getEmbeddingService()\n if (!embeddingService) {\n _logger.warn('RAG', 'Semantic search skipped: Embedding service not enabled')\n return {\n success: false,\n results: [],\n error: 'Embedding service not enabled or not configured',\n }\n }\n\n const rewrittenQuery = await rewriteQuery(userMessage, abortSignal)\n\n if (abortSignal?.aborted) {\n return { success: false, results: [], error: 'Operation cancelled' }\n }\n\n const chunks = getSessionChunks(dbPath, {\n limit: candidateLimit,\n timeFilter,\n filterInvalid: true,\n })\n\n if (chunks.length === 0) {\n _logger.warn('RAG', 'Semantic search skipped: no session chunks available')\n return {\n success: true,\n rewrittenQuery,\n results: [],\n evidenceBlock: '',\n }\n }\n\n const vectorStore = await getVectorStore()\n const chunkVectors = new Map<string, number[]>()\n const uncachedChunks: Chunk[] = []\n\n for (const chunk of chunks) {\n if (vectorStore) {\n const cached = await vectorStore.get(chunk.id)\n if (cached) {\n chunkVectors.set(chunk.id, cached)\n continue\n }\n }\n uncachedChunks.push(chunk)\n }\n\n if (abortSignal?.aborted) {\n return { success: false, results: [], error: 'Operation cancelled' }\n }\n\n if (uncachedChunks.length > 0) {\n const contents = uncachedChunks.map((c) => c.content)\n const vectors = await embeddingService.embedBatch(contents)\n\n for (let i = 0; i < uncachedChunks.length; i++) {\n const chunk = uncachedChunks[i]\n const vector = vectors[i]\n chunkVectors.set(chunk.id, vector)\n\n if (vectorStore) {\n await vectorStore.add(chunk.id, vector, chunk.metadata as unknown as Record<string, unknown>)\n }\n }\n }\n\n if (abortSignal?.aborted) {\n return { success: false, results: [], error: 'Operation cancelled' }\n }\n\n const queryVector = await embeddingService.embed(rewrittenQuery)\n\n const scoredResults: Array<{\n score: number\n chunkId: string\n content: string\n metadata?: ChunkMetadata\n }> = []\n\n for (const chunk of chunks) {\n const vector = chunkVectors.get(chunk.id)\n if (!vector) continue\n\n const score = cosineSimilarity(queryVector, vector)\n scoredResults.push({\n score,\n chunkId: chunk.id,\n content: chunk.content,\n metadata: chunk.metadata,\n })\n }\n\n scoredResults.sort((a, b) => b.score - a.score)\n const topResults = scoredResults.slice(0, topK)\n\n const topScore = topResults[0]?.score ?? 0\n _logger.info(\n 'RAG',\n `Semantic search done: returned ${topResults.length} results, top relevance ${(topScore * 100).toFixed(1)}%`\n )\n\n const evidenceBlock = formatEvidenceBlock(rewrittenQuery, topResults)\n\n return {\n success: true,\n rewrittenQuery,\n results: topResults,\n evidenceBlock,\n }\n } catch (error) {\n _logger.error('RAG', 'Semantic search failed', error)\n return {\n success: false,\n results: [],\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n","/**\n * RAG module (platform-agnostic)\n *\n * Provides RAG (Retrieval-Augmented Generation) functionality:\n * - Embedding service (API-based, multi-config mode)\n * - Session-level chunking\n * - Vector storage (SQLite BLOB + in-memory LRU)\n * - Semantic Pipeline\n *\n * Platform-specific deps (paths, logger, LLM config) are injected\n * via `initRag()` before any RAG function is called.\n */\n\nimport type { RagLogger, LLMConfigForEmbedding } from './types'\nimport type { SemanticPipelineLLMConfig } from './pipeline'\nimport { initRagConfig } from './config'\nimport { initChunkingLogger } from './chunking'\nimport { initEmbeddingService } from './embedding'\nimport { initOpenAICompatibleLogger } from './embedding/openai-compatible'\nimport { initStoreLogger } from './store'\nimport { initMemoryStoreLogger } from './store/memory'\nimport { initSqliteStoreLogger } from './store/sqlite'\nimport { initSemanticPipeline } from './pipeline'\n\n// ==================== Unified init ====================\n\nexport interface RagInitOptions {\n aiDataDir: string\n logger: RagLogger\n getLLMConfig: () => LLMConfigForEmbedding | null\n /** Returns the active LLM config for query rewrite (superset of LLMConfigForEmbedding). */\n getAssistantConfig: () => SemanticPipelineLLMConfig | null\n}\n\n/**\n * Initialize the entire RAG subsystem.\n * Must be called once at startup before using any RAG function.\n */\nexport function initRag(options: RagInitOptions): void {\n const { aiDataDir, logger, getLLMConfig, getAssistantConfig } = options\n initRagConfig(aiDataDir, logger)\n initChunkingLogger(logger)\n initEmbeddingService(logger, getLLMConfig)\n initOpenAICompatibleLogger(logger)\n initStoreLogger(logger)\n initMemoryStoreLogger(logger)\n initSqliteStoreLogger(logger)\n initSemanticPipeline(logger, getAssistantConfig)\n}\n\n// ==================== Config management ====================\n\nexport {\n initRagConfig,\n loadEmbeddingConfigStore,\n saveEmbeddingConfigStore,\n getAllEmbeddingConfigs,\n getActiveEmbeddingConfig,\n getEmbeddingConfigById,\n addEmbeddingConfig,\n updateEmbeddingConfig,\n deleteEmbeddingConfig,\n setActiveEmbeddingConfig,\n isEmbeddingEnabled,\n getActiveEmbeddingConfigId,\n loadRAGConfig,\n saveRAGConfig,\n updateRAGConfig,\n resetRAGConfig,\n getVectorStoreDir,\n} from './config'\n\n// ==================== Types ====================\n\nexport type {\n RagLogger,\n RAGConfig,\n EmbeddingConfig,\n EmbeddingServiceConfig,\n EmbeddingConfigStore,\n VectorStoreConfig,\n RerankConfig,\n IEmbeddingService,\n IVectorStore,\n IRerankService,\n Chunk,\n ChunkMetadata,\n VectorSearchResult,\n VectorStoreStats,\n SemanticPipelineOptions,\n SemanticPipelineResult,\n LLMConfigForEmbedding,\n} from './types'\n\nexport { DEFAULT_RAG_CONFIG, DEFAULT_EMBEDDING_CONFIG_STORE, MAX_EMBEDDING_CONFIG_COUNT } from './types'\n\n// ==================== Embedding service ====================\n\nexport { getEmbeddingService, resetEmbeddingService, validateEmbeddingConfig, initEmbeddingService } from './embedding'\n\n// ==================== Chunking ====================\n\nexport { getSessionChunks, getSessionChunk, formatSessionChunk } from './chunking'\nexport type { ChunkingOptions, SessionMessage, SessionInfo } from './chunking'\n\n// ==================== Vector store ====================\n\nexport { getVectorStore, resetVectorStore, getVectorStoreStats, SQLiteVectorStore, MemoryVectorStore } from './store'\n\n// ==================== Pipeline ====================\n\nexport { executeSemanticPipeline, initSemanticPipeline } from './pipeline'\nexport type { SemanticPipelineLLMConfig } from './pipeline'\n\n// ==================== Utilities ====================\n\nexport { cosineSimilarity } from './utils'\n","/**\n * PathProvider 的 Node.js 独立实现\n *\n * 为 CLI / npm 服务版提供路径管理,不依赖 Electron。\n *\n * 目录分为两类:\n * - 系统数据:固定在 ~/.chatlab/,存放配置、日志、缓存、AI 数据等\n * - 用户数据:可配置位置,存放聊天记录数据库等核心资产\n *\n * 用户数据目录(userDataDir)解析优先级:\n * 1. 构造函数传入的 userDataDir 参数\n * 2. CHATLAB_DATA_DIR 环境变量\n * 3. ~/.chatlab/config.toml → [data] user_data_dir\n * 4. 平台默认路径\n */\n\nimport * as fs from 'fs'\nimport * as os from 'os'\nimport * as path from 'path'\nimport type { PathProvider } from '@openchatlab/core'\nimport { writeConfigField, loadConfig } from '@openchatlab/config'\nimport { migrateFromElectronIfNeeded } from './migrations/electron-data-migration'\n\nconst SYSTEM_DIR = path.join(os.homedir(), '.chatlab')\n\n/**\n * Set after resolveUserDataDir() detects Electron installation but cannot find databases.\n * CLI should check this and block execution with instructions.\n */\nlet _pendingElectronDataWarning = false\n\nexport function hasPendingElectronDataWarning(): boolean {\n return _pendingElectronDataWarning\n}\n\nexport class NodePathProvider implements PathProvider {\n private systemDir: string\n private userDataDir: string\n\n constructor(userDataDir?: string) {\n this.systemDir = SYSTEM_DIR\n this.userDataDir = userDataDir || resolveUserDataDir()\n }\n\n getSystemDir(): string {\n return this.systemDir\n }\n\n getUserDataDir(): string {\n return this.userDataDir\n }\n\n getDatabaseDir(): string {\n return path.join(this.userDataDir, 'databases')\n }\n\n getAiDataDir(): string {\n return path.join(this.systemDir, 'ai')\n }\n\n getSettingsDir(): string {\n return path.join(this.systemDir, 'settings')\n }\n\n getCacheDir(): string {\n return path.join(this.systemDir, 'cache')\n }\n\n getTempDir(): string {\n return path.join(this.systemDir, 'temp')\n }\n\n getLogsDir(): string {\n return path.join(this.systemDir, 'logs')\n }\n\n getDownloadsDir(): string {\n return path.join(os.homedir(), 'Downloads')\n }\n\n /**\n * 确保系统目录和用户数据目录都存在\n */\n ensureAllDirs(): void {\n const dirs = [\n this.systemDir,\n this.userDataDir,\n this.getDatabaseDir(),\n this.getAiDataDir(),\n this.getSettingsDir(),\n this.getCacheDir(),\n this.getTempDir(),\n this.getLogsDir(),\n ]\n for (const dir of dirs) {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n }\n }\n}\n\nfunction resolveUserDataDir(): string {\n const envDir = process.env.CHATLAB_DATA_DIR\n if (envDir) {\n return expandHome(envDir)\n }\n\n const config = loadConfig()\n if (config.data.user_data_dir) {\n return expandHome(config.data.user_data_dir)\n }\n\n // First run: check for existing Electron desktop data before using default\n const result = migrateFromElectronIfNeeded(SYSTEM_DIR)\n if (result.migrated && result.userDataDir) {\n return result.userDataDir\n }\n\n const defaultDir = getDefaultUserDataDir()\n\n if (result.electronDetected) {\n // Electron was used but we couldn't find databases — likely a custom data directory.\n // Don't persist default to config.toml so migration retries on next startup.\n _pendingElectronDataWarning = true\n return defaultDir\n }\n\n writeConfigField('data', 'user_data_dir', defaultDir)\n return defaultDir\n}\n\nfunction getDefaultUserDataDir(): string {\n return path.join(os.homedir(), '.chatlab', 'data')\n}\n\nfunction expandHome(filePath: string): string {\n if (filePath.startsWith('~/') || filePath === '~') {\n return path.join(os.homedir(), filePath.slice(1))\n }\n return filePath\n}\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\n/**\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 !== undefined && value !== null && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = deepMerge((result[key] as Record<string, unknown>) ?? {}, value as Record<string, unknown>)\n } else if (value !== undefined) {\n result[key] = value\n }\n }\n return result\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})\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 * 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 * 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 * 数据库连接管理器\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 isChatSessionDb,\n runMigrations,\n needsMigration as coreNeedsMigration,\n CURRENT_SCHEMA_VERSION,\n} from '@openchatlab/core'\nimport { openBetterSqliteDatabase } from './better-sqlite3-adapter'\nimport { getChatDbMigrations, type MigrationDeps } from './migrations'\n\nexport class DatabaseManager {\n private cache = new Map<string, DatabaseAdapter>()\n private nativeBinding?: string\n private migrationDeps?: MigrationDeps\n\n constructor(\n private pathProvider: PathProvider,\n options?: { nativeBinding?: string; migrationDeps?: MigrationDeps }\n ) {\n this.nativeBinding = options?.nativeBinding\n this.migrationDeps = options?.migrationDeps\n }\n\n /**\n * Open a session DB (read-only by default, cached).\n */\n open(sessionId: string, options?: { readonly?: boolean }): DatabaseAdapter | null {\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 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 /**\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 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 runMigrations(adapter, migrations)\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 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","/**\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 { Migration as CoreMigration } from '@openchatlab/core'\n\nexport interface MigrationDeps {\n /** FTS tokenizer — needed by v4 migration for backfilling the FTS index */\n tokenizeForFts?: (content: string) => string | null\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 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}\n","/**\n * JiebaNlpProvider — NlpProvider 实现(基于 @node-rs/jieba)\n *\n * 提供给 @openchatlab/core 的 getLanguagePreferenceAnalysis 使用。\n * 仅在 Node.js 环境中可用(Electron worker / CLI server)。\n *\n * 使用 nlp/segmenter 的共享 getJieba() 实例,自动加载磁盘词库。\n */\n\nimport type { NlpProvider, PosTagResult } from '@openchatlab/core'\nimport { MEANINGFUL_POS_TAGS, isStopword } from '@openchatlab/core'\nimport { getJieba } from './nlp/segmenter'\nimport type { DictType } from '@openchatlab/core'\n\nexport function createJiebaNlpProvider(dictType: DictType = 'default'): NlpProvider {\n return {\n tag(text: string): PosTagResult[] {\n return getJieba(dictType).tag(text)\n },\n isStopword,\n meaningfulPosTags: MEANINGFUL_POS_TAGS,\n }\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 chatSessionIds?: 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.chatSessionIds?.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.chatSessionIds || params.chatSessionIds.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.chatSessionIds.length,\n percentage: 10,\n message: `Preparing to export ${params.chatSessionIds.length} sessions...`,\n })\n\n const sessionsSql = `\n SELECT id, start_ts as startTs, end_ts as endTs\n FROM chat_session\n WHERE id IN (${params.chatSessionIds.map(() => '?').join(',')})\n ORDER BY start_ts ASC\n `\n const sessions = db.prepare(sessionsSql).all(...params.chatSessionIds) 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.session_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 * 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\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 }\n\n setCache(sessionId, CACHE_KEY_OVERVIEW, data, cacheDir)\n computeAndSetMembersCache(db, sessionId, cacheDir)\n\n return data\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","/**\n * PreferencesManager — manages ~/.chatlab/preferences.json\n *\n * Stores complex user preferences that cannot fit into config.toml\n * (arrays, nested objects) and need cross-client consistency.\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport type {\n Preferences,\n AIGlobalSettings,\n AIPreprocessConfig,\n WordFilterScheme,\n KeywordTemplate,\n ContextCompressionSettings,\n DesensitizeRule,\n FilterHistoryItem,\n} from '@openchatlab/shared-types'\n\nexport type {\n Preferences,\n AIGlobalSettings,\n AIPreprocessConfig,\n WordFilterScheme,\n KeywordTemplate,\n ContextCompressionSettings,\n DesensitizeRule,\n FilterHistoryItem,\n}\n\nconst DEFAULTS: Preferences = {\n pinnedSessionIds: [],\n aiPreprocessConfig: {\n dataCleaning: true,\n mergeConsecutive: false,\n mergeWindowSeconds: 180,\n blacklistKeywords: [],\n denoise: false,\n desensitize: false,\n desensitizeRules: [],\n anonymizeNames: false,\n },\n aiGlobalSettings: {\n maxMessagesPerRequest: 1000,\n exportFormat: 'markdown',\n sqlExportFormat: 'csv',\n enableAutoSkill: true,\n searchContextBefore: 2,\n searchContextAfter: 2,\n contextCompression: {\n enabled: true,\n tokenThresholdPercent: 75,\n bufferSizePercent: 20,\n maxToolResultPercent: 50,\n },\n },\n customKeywordTemplates: [],\n deletedPresetTemplateIds: [],\n wordFilter: {\n schemes: [],\n defaultSchemeId: null,\n sessionSchemeOverrides: {},\n },\n filterHistory: [],\n}\n\nexport class PreferencesManager {\n private filePath: string\n private cache: Preferences | null = null\n\n constructor(systemDir: string) {\n this.filePath = path.join(systemDir, 'preferences.json')\n }\n\n load(): Preferences {\n if (this.cache) return this.cache\n\n try {\n if (fs.existsSync(this.filePath)) {\n const raw = fs.readFileSync(this.filePath, 'utf-8')\n const parsed = JSON.parse(raw) as Partial<Preferences>\n this.cache = this.mergeDefaults(parsed)\n return this.cache\n }\n } catch (err) {\n console.warn('[Preferences] Failed to load preferences.json:', err)\n }\n\n this.cache = { ...DEFAULTS }\n return this.cache\n }\n\n save(partial: Partial<Preferences>): { success: boolean; error?: string } {\n try {\n const current = this.load()\n const merged = this.deepMerge(\n current as unknown as Record<string, unknown>,\n partial as unknown as Record<string, unknown>\n )\n this.cache = merged as unknown as Preferences\n\n const dir = path.dirname(this.filePath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n fs.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), 'utf-8')\n return { success: true }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error('[Preferences] Failed to save:', msg)\n return { success: false, error: msg }\n }\n }\n\n getFilePath(): string {\n return this.filePath\n }\n\n invalidateCache(): void {\n this.cache = null\n }\n\n private mergeDefaults(partial: Partial<Preferences>): Preferences {\n return {\n pinnedSessionIds: partial.pinnedSessionIds ?? DEFAULTS.pinnedSessionIds,\n aiPreprocessConfig: partial.aiPreprocessConfig\n ? { ...DEFAULTS.aiPreprocessConfig, ...partial.aiPreprocessConfig }\n : { ...DEFAULTS.aiPreprocessConfig },\n aiGlobalSettings: partial.aiGlobalSettings\n ? {\n ...DEFAULTS.aiGlobalSettings,\n ...partial.aiGlobalSettings,\n contextCompression: {\n ...DEFAULTS.aiGlobalSettings.contextCompression,\n ...(partial.aiGlobalSettings.contextCompression ?? {}),\n },\n }\n : { ...DEFAULTS.aiGlobalSettings },\n customKeywordTemplates: partial.customKeywordTemplates ?? DEFAULTS.customKeywordTemplates,\n deletedPresetTemplateIds: partial.deletedPresetTemplateIds ?? DEFAULTS.deletedPresetTemplateIds,\n wordFilter: partial.wordFilter ? { ...DEFAULTS.wordFilter, ...partial.wordFilter } : { ...DEFAULTS.wordFilter },\n filterHistory: partial.filterHistory ?? DEFAULTS.filterHistory,\n }\n }\n\n /**\n * Deep merge: arrays are replaced (not concatenated), objects are merged.\n */\n private 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 === undefined) continue\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n typeof base[key] === 'object' &&\n base[key] !== null &&\n !Array.isArray(base[key])\n ) {\n result[key] = this.deepMerge(base[key] as Record<string, unknown>, value as Record<string, unknown>)\n } else {\n result[key] = value\n }\n }\n return result\n }\n}\n","/**\n * Merger orchestration — abstract data-source reading, conflict checking,\n * merge assembly, and ChatLab output formatting.\n *\n * Decoupled from Electron-specific TempDbReader via MergerDataSource interface.\n */\n\nimport {\n getCollidingPlatformIds,\n normalizePlatformId,\n detectConflictsInMessages,\n mergeMergerMembers,\n generateMessageKey,\n type MergerMember,\n type MergerMessage,\n type ConflictCheckResult,\n type MergedMember,\n type MergedMessage,\n} from '@openchatlab/core'\n\n// ==================== Data source abstraction ====================\n\nexport interface MergerSourceMeta {\n name: string\n platform: string\n type: string\n groupId?: string\n groupAvatar?: string\n}\n\n/**\n * Abstract data source for merger input.\n * In Electron, this wraps TempDbReader. Other platforms can implement\n * their own (e.g. reading from IndexedDB, REST API, etc.).\n */\nexport interface MergerDataSource {\n getMeta(): MergerSourceMeta | null\n getMembers(): MergerMember[]\n getMessageCount(): number\n streamMessages(batchSize: number, callback: (messages: MergerMessage[]) => void): void\n}\n\n// ==================== Output types ====================\n\nexport interface MergeSourceInfo {\n filename: string\n platform: string\n messageCount: number\n}\n\nexport interface ChatLabHeader {\n version: string\n exportedAt: number\n generator: string\n description: string\n}\n\nexport interface ChatLabMeta {\n name: string\n platform: string\n type: string\n sources: MergeSourceInfo[]\n groupId?: string\n groupAvatar?: string\n}\n\nexport interface ChatLabOutput {\n chatlab: ChatLabHeader\n meta: ChatLabMeta\n members: MergedMember[]\n messages: MergedMessage[]\n}\n\n// ==================== Conflict checking ====================\n\nexport function checkConflictsFromSources(\n dataSources: Array<{ source: MergerDataSource; filename: string }>\n): ConflictCheckResult {\n const allMessages: Array<{ msg: MergerMessage; source: string; platform: string }> = []\n\n for (const { source, filename } of dataSources) {\n const meta = source.getMeta()\n const platform = meta?.platform || 'unknown'\n source.streamMessages(10000, (messages) => {\n for (const msg of messages) {\n allMessages.push({ msg, source: filename, platform })\n }\n })\n }\n\n return detectConflictsInMessages(allMessages)\n}\n\n// ==================== Merge orchestration ====================\n\nexport interface MergeOrchestrationResult {\n success: true\n chatLabData: ChatLabOutput\n}\n\n/**\n * Build a merged ChatLabOutput from multiple data sources.\n * Pure orchestration: reads data sources, calls core algorithms,\n * assembles the output. Does NOT write to disk or import to DB.\n */\nexport function buildMergedOutput(\n dataSources: Array<{ source: MergerDataSource; filename: string }>,\n outputName: string\n): MergeOrchestrationResult {\n const metas = dataSources.map(({ source, filename }) => ({\n meta: source.getMeta(),\n members: source.getMembers(),\n filename,\n source,\n }))\n\n const collidingIds = getCollidingPlatformIds(\n metas.map(({ meta, members }) => ({\n platform: meta?.platform || 'unknown',\n members: members.map((m) => ({ platformId: m.platformId })),\n }))\n )\n\n const memberMap = mergeMergerMembers(\n metas.map(({ meta, members }) => ({\n platform: meta?.platform || 'unknown',\n members,\n })),\n collidingIds\n )\n\n // Streaming dedup: process batches to avoid loading all into memory\n const seenKeys = new Set<string>()\n const mergedMessages: MergedMessage[] = []\n\n for (const { source, meta } of metas) {\n const platform = meta?.platform || 'unknown'\n source.streamMessages(10000, (messages) => {\n for (const msg of messages) {\n const nid = normalizePlatformId(msg.senderPlatformId, platform, collidingIds)\n const key = generateMessageKey(msg.timestamp, nid, msg.content ?? null)\n if (seenKeys.has(key)) continue\n seenKeys.add(key)\n mergedMessages.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\n mergedMessages.sort((a, b) => a.timestamp - b.timestamp)\n\n const sources: MergeSourceInfo[] = dataSources.map(({ source, filename }) => ({\n filename,\n platform: source.getMeta()?.platform || 'unknown',\n messageCount: source.getMessageCount(),\n }))\n\n const uniquePlatforms = [...new Set(metas.map(({ meta }) => meta?.platform || 'unknown'))]\n const platform = uniquePlatforms.length === 1 ? uniquePlatforms[0] : 'mixed'\n\n const groupIds = new Set(metas.map(({ meta }) => meta?.groupId).filter(Boolean))\n const groupId = groupIds.size === 1 ? metas.find(({ meta }) => meta?.groupId)?.meta?.groupId : undefined\n const groupAvatar = groupId\n ? metas.filter(({ meta }) => meta?.groupId === groupId).pop()?.meta?.groupAvatar\n : undefined\n\n const chatLabData: ChatLabOutput = {\n chatlab: {\n version: '0.0.1',\n exportedAt: Math.floor(Date.now() / 1000),\n generator: 'ChatLab Merge Tool',\n description: `Merged from ${dataSources.length} files`,\n },\n meta: {\n name: outputName,\n platform,\n type: metas[0]?.meta?.type || 'group',\n sources,\n groupId,\n groupAvatar,\n },\n members: Array.from(memberMap.values()),\n messages: mergedMessages,\n }\n\n return { success: true, chatLabData }\n}\n\n// ==================== JSONL serialization ====================\n\n/**\n * Serialize ChatLabOutput to JSONL lines (generator for streaming writes).\n */\nexport function* serializeChatLabToJsonl(data: ChatLabOutput): Generator<string> {\n yield JSON.stringify({\n _type: 'header',\n chatlab: data.chatlab,\n meta: data.meta,\n })\n\n for (const member of data.members) {\n yield JSON.stringify({ _type: 'member', ...member })\n }\n\n for (const msg of data.messages) {\n yield JSON.stringify({ _type: 'message', ...msg })\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 file).\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 {\n getChatSessionSummary,\n saveChatSessionSummary,\n getChatSessionList,\n getSessionMessages,\n} 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(chatSessionId, limit = 500) {\n const data = getSessionMessages(db, chatSessionId, limit)\n if (!data) return null\n return data.messages.map((m) => ({ senderName: m.senderName, content: m.content }))\n },\n saveSummary(chatSessionId, summary) {\n saveChatSessionSummary(db, chatSessionId, summary)\n },\n getSummary(chatSessionId) {\n return getChatSessionSummary(db, chatSessionId)\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 chatSessionId: 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, chatSessionId, 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 chatSessionIds: number[]\n): Record<number, { canGenerate: boolean; reason?: string }> {\n const db = adapter.ensureReadonly(sessionId)\n const deps: Pick<SummaryDeps, 'loadMessages' | 't'> = {\n loadMessages(chatSessionId, limit = 500) {\n const data = getSessionMessages(db, chatSessionId, 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, chatSessionIds)\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 Markdown exporter with adapter-based DB opening.\n */\n\nimport { exportFilterResultToMarkdown, type ExportFilterParams, type ExportResult } 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","/**\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 * as fs from 'fs'\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 dbManager.close(sessionId)\n const dbPath = dbManager.getDbPath(sessionId)\n if (!fs.existsSync(dbPath)) return false\n fs.unlinkSync(dbPath)\n return true\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"],"mappings":"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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,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,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,cACJ;AACF,IAAM,oBAAoB;AAC1B,IAAM,YAAY;AAClB,IAAM,gBAAgB;AACtB,IAAM,oBAAoB;AAKnB,SAAS,UAAU,MAAsB;AAC9C,SAAO,KACJ,QAAQ,WAAW,GAAG,EACtB,QAAQ,eAAe,GAAG,EAC1B,QAAQ,aAAa,GAAG,EACxB,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;;;ACzBO,IAAM,uBAAkD;AAAA;AAAA,EAE7D,EAAE,MAAM,qBAAqB,UAAU,OAAO;AAAA,EAC9C,EAAE,MAAM,mBAAmB,UAAU,OAAO;AAAA,EAC5C,EAAE,MAAM,wBAAwB,UAAU,OAAO;AAAA,EACjD,EAAE,MAAM,uBAAuB,UAAU,OAAO;AAAA,EAChD,EAAE,MAAM,uBAAuB,UAAU,OAAO;AAAA,EAChD,EAAE,MAAM,mBAAmB,UAAU,OAAO;AAAA,EAC5C,EAAE,MAAM,wBAAwB,UAAU,OAAO;AAAA,EACjD,EAAE,MAAM,eAAe,UAAU,OAAO;AAAA;AAAA,EAGxC,EAAE,MAAM,oBAAoB,UAAU,WAAW;AAAA,EACjD,EAAE,MAAM,kBAAkB,UAAU,WAAW;AAAA,EAC/C,EAAE,MAAM,2BAA2B,UAAU,WAAW;AAAA,EACxD,EAAE,MAAM,4BAA4B,UAAU,WAAW;AAAA,EACzD,EAAE,MAAM,yBAAyB,UAAU,WAAW;AAAA,EACtD,EAAE,MAAM,0BAA0B,UAAU,WAAW;AAAA,EACvD,EAAE,MAAM,qBAAqB,UAAU,WAAW;AAAA;AAAA,EAGlD,EAAE,MAAM,0BAA0B,UAAU,WAAW;AAAA,EACvD,EAAE,MAAM,6BAA6B,UAAU,WAAW;AAAA,EAC1D,EAAE,MAAM,yBAAyB,UAAU,WAAW;AAAA,EACtD,EAAE,MAAM,kBAAkB,UAAU,WAAW;AAAA,EAC/C,EAAE,MAAM,6BAA6B,UAAU,WAAW;AAAA,EAC1D,EAAE,MAAM,4BAA4B,UAAU,WAAW;AAAA,EACzD,EAAE,MAAM,+BAA+B,UAAU,WAAW;AAAA,EAC5D,EAAE,MAAM,wBAAwB,UAAU,WAAW;AAAA,EACrD,EAAE,MAAM,gCAAgC,UAAU,WAAW;AAAA,EAC7D,EAAE,MAAM,oBAAoB,UAAU,WAAW;AAAA,EACjD,EAAE,MAAM,uBAAuB,UAAU,WAAW;AACtD;;;ACrCA,IAAM,SAA6B;AAAA,EACjC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU;AAAA,IACR;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,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,YAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,SAA6B;AAAA,EACjC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,WAA+B;AAAA,EACnC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU,CAAC,mBAAmB,mBAAmB;AACnD;AAEA,IAAM,OAA2B;AAAA,EAC/B,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,MAA0B;AAAA,EAC9B,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,OAA2B;AAAA,EAC/B,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU,CAAC,aAAa,oBAAoB,0BAA0B,wBAAwB,kBAAkB;AAClH;AAEA,IAAM,SAA6B;AAAA,EACjC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,cAAkC;AAAA,EACtC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU,CAAC,6BAA6B,2BAA2B,eAAe;AACpF;AAEA,IAAM,OAA2B;AAAA,EAC/B,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU,CAAC,mBAAmB,kBAAkB,oBAAoB,aAAa;AACnF;AAEA,IAAM,aAAiC;AAAA,EACrC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU,CAAC,4BAA4B,mCAAmC,oCAAoC;AAChH;AAEA,IAAM,MAA0B;AAAA,EAC9B,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU,CAAC,UAAU,UAAU,eAAe,eAAe,kBAAkB;AACjF;AAEA,IAAM,UAA8B;AAAA,EAClC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU,CAAC,gBAAgB,0BAA0B,gBAAgB,wBAAwB;AAC/F;AAEA,IAAM,oBAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,UAAU,CAAC;AACb;AAIO,IAAM,oBAA0C;AAAA,EACrD;AAAA,EACA;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;;;AC5QA,SAAS,QAAQ,GAAgC;AAC/C,SAAO,EAAE,GAAG,GAAG,SAAS,MAAM,UAAU,MAAM;AAChD;AAIA,IAAM,gBAAmC;AAAA,EACvC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,WAAW;AAAA,IAC1B,gBAAgB,CAAC,WAAW;AAAA,IAC5B,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,WAAW;AAAA,IAC1B,gBAAgB,CAAC,WAAW;AAAA,IAC5B,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,mBAAsC;AAAA,EAC1C,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,gBAAmC;AAAA,EACvC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,WAAW;AAAA,IAC1B,gBAAgB,CAAC,WAAW;AAAA,IAC5B,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,kBAAqC;AAAA,EACzC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,cAAiC;AAAA,EACrC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,MAAM;AAAA,IACrB,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,WAAW;AAAA,IAC1B,gBAAgB,CAAC,WAAW;AAAA,IAC5B,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,aAAgC;AAAA,EACpC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,WAAW;AAAA,IAC1B,gBAAgB,CAAC,WAAW;AAAA,IAC5B,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,cAAiC;AAAA,EACrC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,gBAAmC;AAAA,EACvC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,qBAAwC;AAAA,EAC5C,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,WAAW;AAAA,IAClC,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,MAAM;AAAA,IACrB,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,cAAiC;AAAA,EACrC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,MAAM;AAAA,IACrB,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,MAAM;AAAA,IACrB,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,MAAM;AAAA,IACrB,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,oBAAuC;AAAA,EAC3C,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,MAAM;AAAA,IACrB,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,aAAgC;AAAA,EACpC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,UAAU,kBAAkB;AAAA,IAChE,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,UAAU,kBAAkB;AAAA,IACnD,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,aAAa,kBAAkB;AAAA,IACtD,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AACH;AAIA,IAAM,iBAAoC;AAAA,EACxC,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC,MAAM;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc,CAAC,QAAQ,kBAAkB;AAAA,IACzC,gBAAgB,CAAC;AAAA,IACjB,QAAQ;AAAA,EACV,CAAC;AACH;AAIO,IAAM,iBAAoC;AAAA,EAC/C,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAGO,SAAS,2BAA2B,YAAuC;AAChF,SAAO,eAAe,OAAO,CAAC,MAAM,EAAE,eAAe,UAAU;AACjE;;;ACt3BO,IAAM,aAAa,CAAC,SAAS,YAAY,aAAa,cAAc,WAAW,UAAU;;;ACazF,IAAM,0BAAN,MAA8B;AAAA,EAC3B,QAAqB;AAAA,EACrB,SAAS;AAAA,EACT,YAAY;AAAA,EACZ;AAAA,EAER,YAAY,MAA0C;AACpD,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,KAAK,MAAoB;AACvB,SAAK,UAAU;AACf,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,CAAC,KAAK,OAAQ;AAClB,QAAI,KAAK,UAAU,YAAY;AAC7B,WAAK,KAAK,EAAE,MAAM,kBAAkB,SAAS,KAAK,OAAO,CAAC;AAAA,IAC5D,OAAO;AACL,WAAK,KAAK,EAAE,MAAM,WAAW,SAAS,KAAK,OAAO,CAAC;AAAA,IACrD;AACA,SAAK,SAAS;AAAA,EAChB;AAAA,EAEQ,gBAAsB;AAC5B,WAAO,KAAK,OAAO,SAAS,GAAG;AAC7B,YAAM,QAAQ,KAAK,OAAO,QAAQ,GAAG;AAErC,UAAI,UAAU,IAAI;AAChB,aAAK,kBAAkB,KAAK,MAAM;AAClC,aAAK,SAAS;AACd;AAAA,MACF;AAEA,UAAI,QAAQ,GAAG;AACb,aAAK,kBAAkB,KAAK,OAAO,MAAM,GAAG,KAAK,CAAC;AAClD,aAAK,SAAS,KAAK,OAAO,MAAM,KAAK;AAAA,MACvC;AAGA,YAAM,YAAY,KAAK,YAAY;AACnC,UAAI,cAAc,cAAc;AAE9B;AAAA,MACF;AACA,UAAI,cAAc,WAAW;AAE3B;AAAA,MACF;AAEA,WAAK,kBAAkB,GAAG;AAC1B,WAAK,SAAS,KAAK,OAAO,MAAM,CAAC;AAAA,IACnC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAqD;AAC3D,QAAI,KAAK,UAAU,WAAW;AAC5B,aAAO,KAAK,gBAAgB;AAAA,IAC9B;AACA,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AAAA,EAEQ,kBAAyD;AAE/D,eAAW,OAAO,YAAY;AAC5B,YAAM,UAAU,IAAI,GAAG;AACvB,UAAI,KAAK,OAAO,WAAW,OAAO,GAAG;AACnC,aAAK,SAAS,KAAK,OAAO,MAAM,QAAQ,MAAM;AAC9C,aAAK,QAAQ;AACb,aAAK,YAAY;AACjB,aAAK,KAAK,EAAE,MAAM,kBAAkB,IAAI,CAAC;AACzC,eAAO;AAAA,MACT;AAEA,UAAI,QAAQ,WAAW,KAAK,MAAM,KAAK,KAAK,OAAO,SAAS,QAAQ,QAAQ;AAC1E,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,mBAA0D;AAChE,UAAM,WAAW,KAAK,KAAK,SAAS;AACpC,QAAI,KAAK,OAAO,WAAW,QAAQ,GAAG;AACpC,WAAK,SAAS,KAAK,OAAO,MAAM,SAAS,MAAM;AAC/C,WAAK,QAAQ;AACb,WAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AAClC,WAAK,YAAY;AACjB,aAAO;AAAA,IACT;AAEA,QAAI,SAAS,WAAW,KAAK,MAAM,KAAK,KAAK,OAAO,SAAS,SAAS,QAAQ;AAC5E,aAAO;AAAA,IACT;AAEA,SAAK,KAAK,EAAE,MAAM,kBAAkB,SAAS,IAAI,CAAC;AAClD,SAAK,SAAS,KAAK,OAAO,MAAM,CAAC;AACjC,WAAO;AAAA,EACT;AAAA,EAEQ,kBAAkB,MAAoB;AAC5C,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,UAAU,YAAY;AAC7B,WAAK,KAAK,EAAE,MAAM,kBAAkB,SAAS,KAAK,CAAC;AAAA,IACrD,OAAO;AACL,WAAK,KAAK,EAAE,MAAM,WAAW,SAAS,KAAK,CAAC;AAAA,IAC9C;AAAA,EACF;AACF;AAMO,SAAS,2BAA2B,UAAkB,UAA2B;AACtF,SAAO,aAAa;AACtB;;;AC3EA,IAAM,iBAA+F;AAAA,EACnG,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAAA,EACA,YAAY;AAAA,IACV,KAAK;AAAA,IACL,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,IACJ,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAAA,EACA,QAAQ;AAAA,IACN,KAAK;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAAA,EACA,iBAAiB;AAAA,IACf,KAAK;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACF;AASA,IAAM,2BACJ;AAKF,SAAS,qBAAqB,UAAkB,SAA+B;AAC7E,QAAM,KAAK,QAAQ,YAAY;AAC/B,QAAM,OAAO,SAAS,YAAY;AAGlC,MAAI,GAAG,SAAS,gBAAgB,EAAG,QAAO;AAG1C,MAAI,SAAS,YAAa,QAAO;AAGjC,MAAI,SAAS,UAAU;AACrB,QAAI,qEAAqE,KAAK,EAAE,EAAG,QAAO;AAC1F,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,UAAU,cAAc,KAAK,EAAE,EAAG,QAAO;AAGtD,MAAI,+BAA+B,KAAK,EAAE,KAAK,4BAA4B,KAAK,EAAE,EAAG,QAAO;AAG5F,MAAI,mCAAmC,KAAK,EAAE,EAAG,QAAO;AAGxD,MAAI,SAAS,YAAY,SAAS,qBAAqB;AACrD,QAAI,8BAA8B,KAAK,EAAE,EAAG,QAAO;AACnD,QAAI,WAAW,KAAK,EAAE,EAAG,QAAO;AAChC,QAAI,QAAQ,KAAK,EAAE,EAAG,QAAO;AAC7B,QAAI,OAAO,KAAK,EAAE,EAAG,QAAO;AAAA,EAC9B;AAEA,MAAI,OAAO,KAAK,EAAE,EAAG,QAAO;AAG5B,MAAI,sCAAsC,KAAK,EAAE,EAAG,QAAO;AAG3D,MAAI,yCAAyC,KAAK,EAAE,EAAG,QAAO;AAC9D,MAAI,SAAS,UAAU,YAAY,KAAK,EAAE,EAAG,QAAO;AAGpD,MAAI,mBAAmB,KAAK,EAAE,KAAM,SAAS,YAAY,QAAQ,KAAK,EAAE,EAAI,QAAO;AAGnF,MAAI,yBAAyB,KAAK,EAAE,EAAG,QAAO;AAG9C,MAAI,SAAS,SAAS,iCAAiC,KAAK,EAAE,EAAG,QAAO;AAGxE,MAAI,SAAS,iBAAiB,UAAU,KAAK,EAAE,EAAG,QAAO;AAGzD,MAAI,SAAS,cAAc;AACzB,QAAI,0BAA0B,KAAK,EAAE,EAAG,QAAO;AAC/C,QAAI,0BAA0B,KAAK,EAAE,EAAG,QAAO;AAC/C,QAAI,sCAAsC,KAAK,EAAE,EAAG,QAAO;AAC3D,QAAI,yCAAyC,KAAK,EAAE,EAAG,QAAO;AAC9D,QAAI,yBAAyB,KAAK,EAAE,EAAG,QAAO;AAC9C,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,KAAK,EAAE,EAAG,QAAO;AAClC,MAAI,oBAAoB,KAAK,EAAE,EAAG,QAAO;AACzC,MAAI,cAAc,KAAK,EAAE,EAAG,QAAO;AAGnC,MAAI,oBAAoB,KAAK,EAAE,EAAG,QAAO;AACzC,MAAI,eAAe,KAAK,EAAE,EAAG,QAAO;AACpC,MAAI,wBAAwB,KAAK,EAAE,EAAG,QAAO;AAC7C,MAAI,0BAA0B,KAAK,EAAE,EAAG,QAAO;AAC/C,MAAI,cAAc,KAAK,EAAE,EAAG,QAAO;AACnC,MAAI,mBAAmB,KAAK,EAAE,EAAG,QAAO;AACxC,MAAI,yBAAyB,KAAK,EAAE,EAAG,QAAO;AAC9C,MAAI,4BAA4B,KAAK,EAAE,EAAG,QAAO;AAGjD,MAAI,cAAc,KAAK,EAAE,EAAG,QAAO;AACnC,MAAI,wCAAwC,KAAK,EAAE,EAAG,QAAO;AAC7D,MAAI,QAAQ,KAAK,EAAE,EAAG,QAAO;AAG7B,MAAI,yBAAyB,KAAK,EAAE,EAAG,QAAO;AAE9C,SAAO;AACT;AAqBO,SAAS,iBAAiB,UAAkB,SAA0B;AAC3E,SAAO,qBAAqB,UAAU,OAAO,MAAM;AACrD;AAcO,SAAS,kBAAkB,UAAkB,SAAiC;AACnF,QAAM,OAAO,qBAAqB,UAAU,OAAO;AAEnD,MAAI,SAAS,OAAQ,QAAO,CAAC;AAG7B,MAAI,SAAS,SAAU,QAAO,CAAC;AAE/B,MAAI,SAAS,UAAU,SAAS,WAAW;AACzC,WAAO,EAAE,gBAAgB,OAAO;AAAA,EAClC;AAEA,MAAI,SAAS,eAAe;AAC1B,WAAO;AAAA,MACL,gBAAgB;AAAA,MAChB,kBAAkB,EAAE,MAAM,QAAQ,OAAO,OAAO,SAAS,MAAM,KAAK,MAAM,QAAQ,KAAK;AAAA,IACzF;AAAA,EACF;AAEA,MAAI,SAAS,mBAAmB;AAC9B,WAAO;AAAA,MACL,gBAAgB;AAAA,MAChB,kBAAkB,EAAE,MAAM,QAAQ,MAAM,QAAQ,SAAS,MAAM,KAAK,MAAM,QAAQ,MAAM,OAAO,KAAK;AAAA,IACtG;AAAA,EACF;AAGA,QAAM,SAAyB,EAAE,yBAAyB,KAAK;AAC/D,QAAM,WAAW,eAAe,IAAI;AACpC,MAAI,UAAU;AACZ,WAAO,mBAAmB;AAAA,EAC5B;AACA,SAAO;AACT;;;AC5QA,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;;;ACrCO,SAAS,iBAAiB,IAA0C;AACzE,QAAM,OAAO,GAAG,QAAQ,oCAAoC,EAAE,IAAI;AAClE,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,IAAI,aAAa,IAAI,EAAE;AAAA,EACjC;AACA,SAAO;AACT;;;ACwCO,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,YAAoB,UAAkB,cAAmC;AAC3G,MAAI,CAAC,aAAa,IAAI,UAAU,EAAG,QAAO;AAC1C,QAAM,KAAK,mBAAmB,YAAY,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,UAAU,QAAQ,KAAK,SAAS;AAC3C,UAAM,IAAI,YAAY;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;AAaxB,IAAM,iBAAiB,iBAAiB;AAQxC,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACnFzB,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,YAAYC,UAAQ;AACpB,YAAYC,YAAU;;;ACDtB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AAItB,IAAM,YAAY,KAAK;AAKvB,SAAS,aAAa,UAAkB,OAAe,WAAmB;AACxE,QAAM,KAAQ,aAAS,UAAU,GAAG;AACpC,QAAM,SAAS,OAAO,MAAM,IAAI;AAChC,QAAM,YAAe,aAAS,IAAI,QAAQ,GAAG,MAAM,CAAC;AACpD,EAAG,cAAU,EAAE;AACf,SAAO,OAAO,MAAM,GAAG,SAAS,EAAE,SAAS,OAAO;AACpD;AAKA,SAAS,aAAa,UAA0B;AAC9C,SAAY,cAAQ,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,eAAS,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,QAAO,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,QAAO,KAAK,KAAK,SAAS;AAC9C,UAAI,KAAK,aAAaD,WAAS,KAAK,aAAa,QAAQ,GAAG;AAC1D,gBAAQ,KAAKC,OAAM;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,SAAQ;;;ACkDb,IAAM,kBAAkB;AAAA,EAC7B,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,MAAM;AAAA,EACN,SAAS;AACX;;;ACjEA,YAAYC,SAAQ;AAMb,SAAS,YAAY,UAA0B;AACpD,SAAU,aAAS,QAAQ,EAAE;AAC/B;AAKO,SAAS,kBAAkB,UAAkB,MAAsB;AACxE,QAAM,KAAQ,aAAS,UAAU,GAAG;AACpC,QAAM,SAAS,OAAO,MAAM,IAAI;AAChC,QAAM,YAAe,aAAS,IAAI,QAAQ,GAAG,MAAM,CAAC;AACpD,EAAG,cAAU,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,WAAU;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,eAAS,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,aAAa,SAAkE;AAC7F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,CAAC,SAAS,WAAW;AAC3C,UAAM,aAAgB,qBAAiB,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,UAAM,WAAW,MAAM,CAAC,YAAY,OAAO,GAAG,KAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAG,aAAa,CAAC,CAAC;AAGlG,UAAM,iBAAkC,CAAC;AAEzC,aAAS,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,aAAS,GAAG,OAAO,MAAM;AAEvB,UAAI,eAAe,SAAS,GAAG;AAC7B,qBAAa,KAAK,GAAG,cAAc;AAAA,MACrC;AACA,cAAQ;AAAA,IACV,CAAC;AAED,aAAS,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,SAAQ;AACpB,YAAY,cAAc;AAC1B,YAAYC,WAAU;AAiEtB,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,eAAS,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,kBAAkB,SAAkE;AAClG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,qBAAiB,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,QAAQF,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,IAAMG,UAAiB;AAAA,EAC5B,SAAAD;AAAA,EACA,OAAO;AACT;AAIA,IAAME,WAAwB;AAAA,EAC5B,SAAAF;AAAA,EACA,QAAAC;AACF;AAEA,IAAO,wBAAQC;;;AChRf,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAC/B,IAAAC,uBAAwB;AALxB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;;;ACTtB,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAC/B,IAAAC,uBAAwB;AANxB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AACtB,YAAY,QAAQ;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,WAAQ,UAAO,GAAG,SAAS;AACzC;AAKA,SAAS,UAAU,KAAmB;AACpC,MAAI,CAAI,eAAW,GAAG,GAAG;AACvB,IAAG,cAAU,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,aAAS,QAAQ;AAClC,UAAM,WAAW,KAAK,IAAI,MAAM,MAAM,GAAO;AAC7C,UAAM,KAAQ,aAAS,UAAU,GAAG;AACpC,UAAM,SAAS,OAAO,MAAM,QAAQ;AACpC,IAAG,aAAS,IAAI,QAAQ,GAAG,UAAU,MAAM,OAAO,QAAQ;AAC1D,IAAG,cAAU,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,eAAS,SAAS,CAAC;AACrE,QAAM,aAAkB,WAAK,SAAS,cAAc;AAEpD,eAAa,eAAe,WAAW,GAAG,YAAY,GAAG,EAAE,CAAC;AAG5D,QAAM,aAAa,oBAAoB,SAAS;AAEhD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,aAAuB,CAAC;AAC9B,QAAI,WAAW;AACf,UAAM,cAAc;AAEpB,UAAM,aAAgB,qBAAiB,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,qBAAiB,WAAW,EAAE,UAAU,QAAQ,CAAC;AACvE,YAAM,cAAiB,sBAAkB,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,YAAM,WAAWD,OAAM,CAAC,YAAYN,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAElG,eAAS,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,eAAS,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,kBAAQ,UAAU;AAAA,QACpB,CAAC;AAAA,MACH,CAAC;AAED,eAAS,GAAG,SAAS,CAAC,QAAQ;AAC5B,oBAAY,QAAQ;AACpB,YAAO,eAAW,UAAU,GAAG;AAC7B,UAAG,eAAW,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,eAAW,QAAQ,KAAK,SAAS,SAAS,WAAW,CAAC,GAAG;AAC9D,MAAG,eAAW,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,QAAAI,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,eAAS,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,QAAQ,SAAkE;AACxF,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,cAAcF,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,CAAC,SAAS,WAAW;AAC3C,UAAM,aAAgB,qBAAiB,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,UAAM,WAAWJ,OAAM,CAAC,YAAYN,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAElG,aAAS,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,aAAS,GAAG,OAAO,OAAO;AAC1B,aAAS,GAAG,SAAS,MAAM;AAAA,EAC7B,CAAC;AAGD,QAAM,aAAa,oBAAI,IAAoB;AAC3C,MAAI;AAEF,UAAM,QAAW,aAAS,QAAQ;AAClC,UAAM,WAAW,KAAK,IAAI,MAAM,MAAM,GAAO;AAC7C,UAAM,KAAQ,aAAS,UAAU,GAAG;AACpC,UAAM,SAAS,OAAO,MAAM,QAAQ;AACpC,IAAG,aAAS,IAAI,QAAQ,GAAG,UAAU,MAAM,OAAO,QAAQ;AAC1D,IAAG,cAAU,EAAE;AAEf,UAAM,cAAc,OAAO,SAAS,OAAO;AAC3C,UAAM,aAAaK,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,IAAMG,WAAkB;AAAA,EAC7B,SAAAJ;AAAA,EACA,OAAO;AACT;AAKA,IAAMK,WAAwB;AAAA,EAC5B,SAAAC;AAAA,EACA,QAAQC;AAAA,EACR,cAAc;AAChB;AAEA,IAAO,+BAAQF;;;AElaf,YAAYG,SAAQ;AACpB,YAAYC,WAAU;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,iBAAa,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,WAAK,SAAS,YAAY;AACjD,QAAO,eAAW,SAAS,GAAG;AAC5B,eAAY,aAAS,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,WAAK,SAAS,YAAY,IAAI;AACvD,MAAI,CAAI,eAAW,WAAW,EAAG,QAAO;AAExC,MAAI;AACF,UAAM,UAAa,iBAAa,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,qBAAiB,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,kBAAkB,SAAkE;AAClG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;AAG1D,QAAM,eAAe;AACrB,QAAM,UAAe,cAAQ,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,WAAK,SAAS,YAAY;AACjD,UAAM,oBAAoB,qBAAqB,SAAS;AAExD,QAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,cAAQ,SAAS,+CAAY,SAAS,EAAE;AACxC;AAAA,IACF;AAEA,UAAM,YAAe,aAAS,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,2BAA8BA,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,IAAMC,WAAkB;AAAA,EAC7B,SAAAF;AAAA,EACA,OAAO;AACT;AAEA,IAAMG,WAAwB;AAAA,EAC5B,SAAAH;AAAA,EACA,QAAQE;AACV;AAEA,IAAO,uCAAQC;;;ACliBf,IAAAC,sBAAuB;AACvB,IAAAC,eAAuB;AACvB,IAAAC,uBAA+B;AAC/B,IAAAC,uBAAwB;AALxB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;;;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,eAAS,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,YAAY,SAAkE;AAC5F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,CAAC,YAAY;AACnC,YAAM,gBAAmB,qBAAiB,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,gBAAQ;AAAA,MACV,CAAC;AAED,oBAAc,GAAG,SAAS,MAAM,QAAQ,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,YAAYH,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,CAAC,YAAY;AACnC,cAAM,eAAkB,qBAAiB,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,kBAAQ;AAAA,QACV,CAAC;AAED,qBAAa,GAAG,SAAS,MAAM,QAAQ,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,CAAC,YAAY;AACnC,YAAM,aAAgB,qBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AACtE,YAAM,eAAeF,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,MAAM,QAAQ,CAAC;AACtC,mBAAa,GAAG,OAAO,MAAM,QAAQ,CAAC;AACtC,mBAAa,GAAG,SAAS,MAAM,QAAQ,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,CAAC,SAAS,WAAW;AAC3C,UAAM,aAAgB,qBAAiB,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,UAAM,WAAWE,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,aAAS,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,aAAS,GAAG,OAAO,MAAM;AAEvB,UAAI,eAAe,SAAS,GAAG;AAC7B,qBAAa,KAAK,GAAG,cAAc;AAAA,MACrC;AACA,cAAQ;AAAA,IACV,CAAC;AAED,aAAS,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,qBAAqB,SAAkE;AACrG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,CAAC,SAAS,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,UAAM,WAAWH,OAAM,CAAC,YAAYN,QAAO,GAAGE,MAAK,EAAE,QAAQ,kBAAkB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAGlG,UAAM,iBAAkC,CAAC;AAEzC,aAAS,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,WAAWK,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,aAAS,GAAG,OAAO,MAAM;AAEvB,UAAI,eAAe,SAAS,GAAG;AAC7B,qBAAa,KAAK,GAAG,cAAc;AAAA,MACrC;AACA,cAAQ;AAAA,IACV,CAAC;AAED,aAAS,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,SAAAF;AAAA,EACA,OAAO;AACT;AAIA,IAAMG,WAAwB;AAAA,EAC5B,SAAAH;AAAA,EACA,QAAQE;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,CAAC,SAAS,WAAW;AAC1D,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAItE,UAAM,WAAWR,OAAM,CAAC,YAAYE,QAAO,GAAGE,MAAK,EAAE,QAAQ,qBAAqB,CAAC,GAAGE,cAAa,CAAC,CAAC;AAErG,aAAS,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,aAAS,GAAG,OAAO,MAAM;AACvB,cAAQ,KAAK;AAAA,IACf,CAAC;AAED,aAAS,GAAG,SAAS,CAAC,QAAe;AACnC,aAAO,IAAI,MAAM,mDAAqB,IAAI,OAAO,EAAE,CAAC;AAAA,IACtD,CAAC;AAAA,EACH,CAAC;AACH;AAIA,gBAAgB,cAAc,SAAkE;AAC9F,QAAM,EAAE,UAAU,YAAY,KAAM,eAAe,YAAY,MAAM,IAAI;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,CAAC,SAAS,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,UAAM,WAAWN,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,aAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAA+B;AAC1D,cAAQ;AACR,cAAQ,KAAK;AAAA,IACf,CAAC;AAED,aAAS,GAAG,OAAO,MAAM;AACvB,UAAI,CAAC,MAAO,SAAQ,IAAI;AAAA,IAC1B,CAAC;AAED,aAAS,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,IAAMG,WAAkB;AAAA,EAC7B,SAAAD;AAAA,EACA,OAAO;AACT;AAEA,IAAME,WAAwB;AAAA,EAC5B,SAAAF;AAAA,EACA,QAAQC;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,oBAAoB,SAAkE;AACpG,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,CAAC,SAAS,WAAW;AAC3E,UAAM,aAAgB,sBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAEtE,UAAM,WAAWN,OAAM,CAAC,YAAYE,QAAO,GAAGE,cAAa,CAAC,CAAC;AAE7D,QAAI,QAAQ;AAEZ,aAAS,GAAG,QAAQ,CAAC,EAAE,MAAM,MAA+B;AAC1D,UAAI,CAAC,OAAO;AACV,gBAAQ;AACR,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AAED,aAAS,GAAG,OAAO,MAAM;AACvB,UAAI,CAAC,MAAO,SAAQ,IAAI;AAAA,IAC1B,CAAC;AAED,aAAS,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,IAAMG,WAAkB;AAAA,EAC7B,SAAAD;AAAA,EACA,OAAO;AACT;AAEA,IAAME,WAAwB;AAAA,EAC5B,SAAAF;AAAA,EACA,QAAQC;AACV;AAEA,IAAO,iCAAQC;;;ACrMf,YAAYC,UAAQ;AACpB,YAAYC,WAAU;AACtB,YAAYC,eAAc;AAqB1B,SAASC,yBAAwB,UAA0B;AACzD,QAAMC,aAAgB,eAAS,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,cAAc,SAAkE;AAC9F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,WAAWH,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,IAAMC,WAAkB;AAAA,EAC7B,SAAAF;AAAA,EACA,OAAO;AACT;AAIA,IAAMG,YAAwB;AAAA,EAC5B,SAAAH;AAAA,EACA,QAAQE;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,SAAS,SAAkE;AACzF,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,OAAOA,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,IAAMI,YAAkB;AAAA,EAC7B,SAAAF;AAAA,EACA,OAAO;AACT;AAIA,IAAMG,YAAwB;AAAA,EAC5B,SAAAH;AAAA,EACA,QAAQE;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,eAAe,SAAkE;AAC/F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,KAAKF,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,IAAMC,YAAkB;AAAA,EAC7B,SAAAL;AAAA,EACA,OAAO;AACT;AAEA,IAAMM,YAAwB;AAAA,EAC5B,SAAAN;AAAA,EACA,QAAQK;AACV;AAEA,IAAO,2BAAQC;;;ACpVf,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,UAAU,SAAkE;AAC1F,QAAM,EAAE,UAAU,YAAY,KAAM,YAAY,MAAM,IAAI;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,WAAWF,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,UAAIG,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,UAAUD,mBAAkBC,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,SAAAL;AAAA,EACA,OAAO;AACT;AAEA,IAAMM,YAAwB;AAAA,EAC5B,SAAAN;AAAA,EACA,QAAQK;AACV;AAEA,IAAO,0BAAQC;;;ACliBR,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;AACF;;;ApBdA,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,UAAU,SAAkE;AACjG,QAAMC,UAAS,QAAQ,UAAU,QAAQ,QAAQ;AACjD,MAAI,CAACA,SAAQ;AACX,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,qDAAa,QAAQ,QAAQ,EAAE,EAAE;AACxE;AAAA,EACF;AAEA,UAAQ,IAAI,6BAA6BA,QAAO,QAAQ,IAAI,EAAE;AAC9D,SAAOA,QAAO,MAAM,OAAO;AAC7B;AAQA,gBAAuB,oBACrB,UACA,SAC2C;AAC3C,QAAMA,UAAS,QAAQ,cAAc,QAAQ;AAC7C,MAAI,CAACA,SAAQ;AACX,UAAM,EAAE,MAAM,SAAS,MAAM,IAAI,MAAM,sCAAa,QAAQ,EAAE,EAAE;AAChE;AAAA,EACF;AAEA,UAAQ,IAAI,wCAAwCA,QAAO,QAAQ,IAAI,EAAE;AACzE,SAAOA,QAAO,MAAM,OAAO;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;;;AqBxVA,YAAYC,UAAQ;;;AClBpB,YAAYC,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,QAAI;AACJ,UAAM,WAAW,oBAAoB,aAAa;AAClD,QAAI,UAAU;AACZ,iBAAW,MAAM,SAAS,QAAQ;AAClC,cAAQ,IAAI,4BAA4B,aAAa,KAAK,SAAS,MAAM,SAAS;AAAA,IACpF,OAAO;AACL,iBAAW,IAAI,MAAM;AACrB,cAAQ,KAAK,6BAA6B,aAAa,kCAAkC;AAAA,IAC3F;AACA,mBAAe,IAAI,eAAe,QAAQ;AAC1C,WAAO;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,MACA,UAA4F,CAAC,GACnF;AACV,QAAM,EAAE,gBAAgB,cAAc,eAAe,WAAW,UAAU,IAAI;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,QAAyB,UAA0B,CAAC,GAAa;AACrG,QAAM;AAAA,IACJ;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA,kBAAkB;AAAA,IAClB,WAAW;AAAA,EACb,IAAI;AACJ,QAAM,YAAY,OAAO,WAAW,IAAI;AACxC,QAAM,aAAa,WAAW;AAC9B,QAAM,mBAAmB,aAAa,aAAa,IAAI;AACvD,QAAM,qBAAqB,aAAa;AAExC,MAAI;AACJ,MAAI,WAAW;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,QACA,UAA+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,IAAI;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;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;;;AC9OA,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,MAAI;AACJ,MAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,UAAM,cAAc,mBAAmB,OAAO,iBAAiB,GAAG,iBAAiB,QAAoB;AACvG,kBAAc,CAAC,GAAG,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE;AAAA,EACjF;AAEA,QAAM,SAAS,0BAA0B,OAAO,QAA2B;AAAA,IACzE,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,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;;;ACpGO,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,CAACC,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,kBAAkB;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,eAAe,WAAW,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;;;ALxDA,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,WAAK,iBAAiB,IAAI,SAAS;AACnC,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;;;AMzyBA,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;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,YAAI,YAAY,KAAK,wBAAwB,YAAY,GAAG;AAC1D;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,MACA,SACkC;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,iBAAiB,SAAS,kBAAkB;AAClD,QAAM,mBAAmB,SAAS,oBAAoB;AAEtD,MAAI;AAyDF,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;AAxDT,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,OAAG,KAAK,mBAAmB;AAE3B,QAAI,kBAAkB;AACtB,QAAI,iBAAiB;AACrB,QAAI,iBAAiB;AACrB,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,cAAI,OAAO,IAAI,cAAc,YAAY,IAAI,aAAa,KAAK,CAAC,SAAS,IAAI,SAAS,GAAG;AACvF,YAAAA,YAAW,gBAAgB,qBAAqB,oBAAoB,IAAI,SAAS,EAAE;AACnF;AAAA,UACF;AAEA,cAAI,YAAY,KAAK,wBAAwB,YAAY,GAAG;AAC1D;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,IAAI;AAAA,YACJ,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;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;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,WAAK,iBAAiB,IAAI,SAAS;AAAA,IACrC,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;;;AC7aA,YAAYE,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,OAAgB,SAAwC;AACpF,QAAM,EAAE,YAAY,iBAAiB,YAAY,aAAa,IAAI,qBAAqB,KAAK;AAC5F,QAAM,eAAe,gBAAgB,YAAY;AACjD,QAAM,eAAe,SAAS,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,gBAAgB,SAAS,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;AAGZ,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,iBAAiB,GAAG,mBAAmB;AAAA,MAC5D,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,OAAO;AACxE,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;;;ACEA,SAAS,UAAU,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,IAAI,SAAS;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,YAAYC,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;AAEtB,IAAM,qBAAqB;AAwE3B,IAAM,gBAA2C;AAAA,EAC/C,KAAK,WAAW,SAAS,OAAO;AAC9B,YAAQ,KAAK,sBAAsB,OAAO,IAAI,SAAS,EAAE;AAAA,EAC3D;AACF;AAIO,IAAM,wBAAN,MAA4B;AAAA,EACzB,KAA+B;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA,yBAAyB,oBAAI,IAAoB;AAAA,EAElE,YAAY,WAAmB,SAA0E;AACvG,SAAK,YAAY;AACjB,SAAK,SAAS,SAAS,UAAU;AACjC,SAAK,gBAAgB,SAAS;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,IAAIF,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA2BZ;AAED,SAAK,gBAAgB,KAAK,EAAE;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,gBAAgB,IAA6B;AACnD,QAAI;AACF,YAAM,mBAAmB,GAAG,OAAO,wBAAwB;AAC3D,YAAM,iBAAiB,iBAAiB,IAAI,CAAC,QAAQ,IAAI,IAAI;AAE7D,YAAM,2BACJ,CAAC,eAAe,SAAS,WAAW,KACpC,CAAC,eAAe,SAAS,kBAAkB,KAC3C,CAAC,eAAe,SAAS,cAAc;AAEzC,UAAI,CAAC,eAAe,SAAS,gBAAgB,GAAG;AAC9C,WAAG,KAAK,uDAAuD;AAAA,MACjE;AACA,UAAI,CAAC,eAAe,SAAS,aAAa,GAAG;AAC3C,WAAG,KAAK,oDAAoD;AAAA,MAC9D;AACA,UAAI,CAAC,eAAe,SAAS,eAAe,GAAG;AAC7C,WAAG,KAAK,sDAAsD;AAAA,MAChE;AACA,UAAI,CAAC,eAAe,SAAS,WAAW,GAAG;AACzC,WAAG,KAAK,kDAAkD;AAAA,MAC5D;AACA,UAAI,CAAC,eAAe,SAAS,kBAAkB,GAAG;AAChD,WAAG,KAAK,yDAAyD;AAAA,MACnE;AACA,UAAI,CAAC,eAAe,SAAS,cAAc,GAAG;AAC5C,WAAG,KAAK,kEAAkE;AAAA,MAC5E;AAEA,YAAM,gBAAgB,GAAG,OAAO,6BAA6B;AAC7D,YAAM,cAAc,cAAc,IAAI,CAAC,QAAQ,IAAI,IAAI;AACvD,YAAM,4BAA4B,CAAC,YAAY,SAAS,mBAAmB;AAE3E,UAAI,CAAC,YAAY,SAAS,cAAc,GAAG;AACzC,WAAG,KAAK,qEAAqE,kBAAkB,GAAG;AAAA,MACpG;AACA,UAAI,CAAC,YAAY,SAAS,mBAAmB,GAAG;AAC9C,WAAG,KAAK,+DAA+D;AAAA,MACzE;AAEA,UAAI,4BAA4B,6BAA6B,KAAK,2BAA2B,EAAE,GAAG;AAChG,aAAK,oBAAoB,EAAE;AAAA,MAC7B;AAEA,SAAG,KAAK;AAAA;AAAA;AAAA,OAGP;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,gBAAgB,GAAG,QAAQ,gCAAgC,EAAE,IAAI;AACvE,UAAM,gBAAgB,GAAG;AAAA,MACvB;AAAA,IACF;AACA,UAAM,qBAAqB,GAAG,QAAQ,+DAA+D;AAErG,UAAM,KAAK,GAAG,YAAY,MAAM;AAC9B,iBAAW,gBAAgB,eAAe;AACxC,cAAM,WAAW,GACd;AAAA,UACC;AAAA;AAAA,QAEF,EACC,IAAI,aAAa,EAAE;AAEtB,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,kBAAkB,GACrB,QAAQ,+EAA+E,EACvF,IAAI,aAAa,EAAE;AACtB,YAAI,CAAC,iBAAiB,mBAAmB,YAAY;AACnD,6BAAmB,IAAI,YAAY,aAAa,EAAE;AAAA,QACpD;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,gBAAgB,IAAI;AAAA,MACpB,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,gBAAuC;AAChE,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,GACT,QAAQ,+EAA+E,EACvF,IAAI,cAAc;AACrB,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,8FAA8F,EACtG,IAAI,cAAc;AACrB,QAAI,UAAU,IAAI;AAChB,SAAG,QAAQ,+DAA+D,EAAE,IAAI,SAAS,IAAI,cAAc;AAC3G,aAAO,SAAS;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,kBAAkB,gBAAwC;AAChE,WAAO,KAAK,MAAM,EACf;AAAA,MACC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,EACC,IAAI,cAAc;AAAA,EACvB;AAAA,EAEQ,kBAAkB,gBAAwB,eAA+C;AAC/F,QAAI,kBAAkB,KAAM,QAAO,CAAC;AAEpC,UAAM,UAAU,KAAK,kBAAkB,cAAc;AACrD,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,cAAc;AACvE,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,CAACC,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,mBAAmB,WAAmB,OAA2B,aAAqC;AACpG,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,iCAAsD;AACpD,UAAM,SAAS,oBAAI,IAAoB;AACvC,QAAI;AACF,YAAM,KAAK,KAAK,MAAM;AACtB,YAAM,OAAO,GACV,QAAQ,+EAA+E,EACvF,IAAI;AACP,iBAAW,OAAO,MAAM;AACtB,eAAO,IAAI,IAAI,YAAY,IAAI,KAAK;AAAA,MACtC;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AAAA,EAEA,iBAAiB,WAAqC;AACpD,UAAM,KAAK,KAAK,MAAM;AACtB,WAAO,GACJ;AAAA,MACC;AAAA;AAAA;AAAA;AAAA,IAIF,EACC,IAAI,SAAS;AAAA,EAClB;AAAA,EAEA,gBAAgB,gBAA+C;AAC7D,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,GACT;AAAA,MACC;AAAA;AAAA;AAAA;AAAA,IAIF,EACC,IAAI,cAAc;AACrB,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,wBAAwB,gBAAwB,OAAwB;AACtE,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,UAAM,SAAS,GACZ,QAAQ,mEAAmE,EAC3E,IAAI,OAAO,KAAK,cAAc;AACjC,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,mBAAmB,gBAAiC;AAClD,UAAM,KAAK,KAAK,MAAM;AACtB,OAAG,QAAQ,kDAAkD,EAAE,IAAI,cAAc;AACjF,UAAM,SAAS,GAAG,QAAQ,0CAA0C,EAAE,IAAI,cAAc;AACxF,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA;AAAA,EAIA,WACE,gBACA,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,cAAc;AACvD,UAAM,iBAAiB;AACvB,UAAM,cAAc;AAEpB,UAAM,eAAe,SAAS,cAAc,KAAK,uBAAuB,IAAI,cAAc,IAAI;AAC9F,QAAI,cAAc;AAChB,WAAK,uBAAuB,OAAO,cAAc;AAAA,IACnD;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,+EAA+E,EAAE;AAAA,MAC1F;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,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,gBAAqC;AAC/C,WAAO,KAAK,kBAAkB,cAAc,EAAE,IAAI,CAAC,QAAQ,KAAK,gBAAgB,GAAG,CAAC;AAAA,EACtF;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,gBAAwB,WAAyB;AAClE,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,KAAK,cAAc,SAAS;AAC3C,QAAI,CAAC,UAAU,OAAO,mBAAmB,gBAAgB;AACvD,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,aAAa,KAAK,kBAAkB,cAAc;AACxD,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,+EAA+E,EAAE;AAAA,MAC1F;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,iBAAiB,sBAA8B,eAAuB,OAAgC;AACpG,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,KAAK,gBAAgB,oBAAoB;AACxD,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,UAAM,aAAa,KAAK,kBAAkB,oBAAoB;AAC9D,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,+DAA+D,EAAE,IAAI,WAAW,SAAS;AAAA,IACtG;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,gBAAwB,WAAyB;AACtE,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,SAAS,KAAK,cAAc,SAAS;AAC3C,QAAI,CAAC,UAAU,OAAO,mBAAmB,gBAAgB;AACvD,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,OAAG,QAAQ,iFAAiF,EAAE;AAAA,MAC5F,OAAO;AAAA,MACP;AAAA,MACA;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,gBAAgB,cAAc;AAChD,QAAI,MAAM,oBAAoB,WAAW;AACvC,YAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,SAAG,QAAQ,+EAA+E,EAAE;AAAA,QAC1F,OAAO;AAAA,QACP;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,OAAG,QAAQ,qCAAqC,EAAE,IAAI,SAAS;AAAA,EACjE;AAAA,EAEA,mBACE,gBACA,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,cAAc,IAAI;AAC9F,QAAI,cAAc;AAChB,WAAK,uBAAuB,OAAO,cAAc;AAAA,IACnD;AAEA,UAAM,WAAW,GACd,QAAQ,+EAA+E,EACvF,IAAI,gBAAgB,cAAc;AAErC,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,wDAAwD,EAAE,IAAI,KAAK,cAAc;AAAA,IAC9F,OAAO;AACL,SAAG,QAAQ,+EAA+E,EAAE;AAAA,QAC1F;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;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,0BAA0B,gBAAwC;AAChE,UAAM,OAAO,KAAK,kBAAkB,cAAc;AAClD,UAAM,SAAyB,EAAE,cAAc,GAAG,kBAAkB,GAAG,aAAa,EAAE;AACtF,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;AAAA,MAC9B;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,uBAAuB,gBAAwB,cAA4B;AACzE,SAAK,uBAAuB,IAAI,gBAAgB,YAAY;AAAA,EAC9D;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,gBACA,aACA,eACoE;AACpE,UAAM,WAAW,KAAK,kBAAkB,gBAAgB,aAAa,EAAE,IAAI,CAAC,QAAQ,KAAK,gBAAgB,GAAG,CAAC;AAC7G,UAAM,gBAAgB,SAAS;AAAA,MAC7B,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,SAAS,eAAe,EAAE,SAAS,cAAc,EAAE,SAAS,KAAK;AAAA,IAClG;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,iBAAiB,4EAA4E;AAAA,UAC5G;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,QAAQ,EAAE;AAAA,MACtE;AAAA,IACF,OAAO;AACL,eAAS,cAAc,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE;AAAA,IAC1E;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,gBACA,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,gBAAgB,WAAW,SAAS,QAAW,QAAW,aAAa;AAAA,EAChG;AAAA,EAEA,iBAAiB,gBAA0C;AACzD,UAAM,MAAM,CAAC,GAAG,KAAK,kBAAkB,cAAc,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,YAAY,QAAQ,SAAS,SAAS;AAC9G,WAAO,MAAM,KAAK,gBAAgB,GAAG,IAAI;AAAA,EAC3C;AAAA,EAEA,wBACE,gBACA,kBACoE;AACpE,WAAO,KAAK,kBAAkB,cAAc,EACzC,OAAO,CAAC,QAAQ,IAAI,YAAY,qBAAqB,IAAI,SAAS,UAAU,IAAI,SAAS,YAAY,EACrG,IAAI,CAAC,SAAS,EAAE,MAAM,IAAI,MAAuB,SAAS,IAAI,SAAS,WAAW,IAAI,UAAU,EAAE;AAAA,EACvG;AAAA,EAEA,4BACE,gBACoE;AACpE,WAAO,KAAK,kBAAkB,cAAc,EACzC,OAAO,CAAC,QAAQ,IAAI,SAAS,UAAU,IAAI,SAAS,WAAW,EAC/D,IAAI,CAAC,SAAS,EAAE,MAAM,IAAI,MAAuB,SAAS,IAAI,SAAS,WAAW,IAAI,UAAU,EAAE;AAAA,EACvG;AAAA,EAEA,4BAA4B,gBAAgC;AAC1D,UAAM,UAAU,KAAK,iBAAiB,cAAc;AACpD,QAAI,CAAC,SAAS;AACZ,aAAO,KAAK,kBAAkB,cAAc,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS,UAAU,IAAI,SAAS,WAAW,EAC1G;AAAA,IACL;AAEA,UAAM,YAAY,QAAQ,eAAe;AAAA,MACvC,CAAC,MAA4D,EAAE,SAAS;AAAA,IAC1E;AACA,UAAM,WAAW,WAAW,2BAA2B,QAAQ;AAE/D,WAAO,KAAK,kBAAkB,cAAc,EAAE;AAAA,MAC5C,CAAC,QAAQ,IAAI,aAAa,aAAa,IAAI,SAAS,UAAU,IAAI,SAAS;AAAA,IAC7E,EAAE;AAAA,EACJ;AACF;;;ACn4BA,SAAS,wBAAwB;AAEjC,IAAI,UAAsD;AAE1D,SAAS,aAAa;AACpB,MAAI,CAAC,SAAS;AACZ,cAAU,iBAAiB,QAAQ;AAAA,EACrC;AACA,SAAO;AACT;AAKO,SAAS,YAAY,MAAsB;AAChD,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,WAAW,EAAE,OAAO,IAAI,EAAE;AACnC;AAMO,SAAS,oBAAoB,UAAoD,cAA+B;AACrH,QAAM,MAAM,WAAW;AACvB,MAAI,QAAQ;AAEZ,MAAI,cAAc;AAChB,aAAS,IAAI,OAAO,YAAY,EAAE,SAAS;AAAA,EAC7C;AAEA,aAAW,OAAO,UAAU;AAC1B,aAAS,IAAI,OAAO,IAAI,OAAO,EAAE,SAAS;AAAA,EAC5C;AAGA,WAAS;AAET,SAAO;AACT;;;ACvCA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAItB,IAAM,kBAAkB;AACxB,IAAM,uBAAuB;AAO7B,IAAMC,iBAAoC;AAAA,EACxC,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,MAAM,MAAM;AAAA,EAAC;AACf;AAEO,IAAM,eAAN,MAAmB;AAAA,EAChB,SAAS,oBAAI,IAAsB;AAAA,EACnC,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EAER,YAAY,WAAmB,QAA6B;AAC1D,SAAK,YAAiB,YAAK,WAAW,eAAe;AACrD,SAAK,SAAS,UAAUA;AAAA,EAC1B;AAAA,EAEA,OAA0B;AACxB,SAAK,QAAQ;AACb,SAAK,cAAc;AACnB,SAAK,OAAO,KAAK,6BAA6B,KAAK,OAAO,IAAI,SAAS;AACvE,WAAO,EAAE,OAAO,KAAK,OAAO,KAAK;AAAA,EACnC;AAAA,EAEA,eAAe,IAA6B;AAC1C,SAAK,kBAAkB;AACvB,WAAO,KAAK,OAAO,IAAI,EAAE,KAAK;AAAA,EAChC;AAAA,EAEA,eAA+B;AAC7B,SAAK,kBAAkB;AACvB,WAAO,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS;AAAA,MACpD,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,aAAa,IAAI;AAAA,MACjB,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,OAAO,IAAI;AAAA,MACX,WAAW,IAAI;AAAA,IACjB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,UAA+B,cAAwC;AAClF,SAAK,kBAAkB;AAEvB,UAAM,aAAa,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,UAAU;AACpE,UAAI,MAAM,cAAc,SAAS,MAAM,cAAc,SAAU,QAAO;AACtE,UAAI,MAAM,MAAM,SAAS,KAAK,gBAAgB,aAAa,SAAS,GAAG;AACrE,cAAM,aAAa,MAAM,MAAM,MAAM,CAACC,OAAM,aAAa,SAASA,EAAC,CAAC;AACpE,YAAI,CAAC,WAAY,QAAO;AAAA,MAC1B;AACA,aAAO;AAAA,IACT,CAAC;AAED,QAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,UAAM,QAAQ,WAAW,MAAM,GAAG,oBAAoB;AACtD,UAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,WAAM,EAAE,WAAW,EAAE;AAExE,WAAO;AAAA;AAAA;AAAA;AAAA,EAIT,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,EAGhB;AAAA,EAEQ,oBAA0B;AAChC,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA,EAEQ,UAAgB;AACtB,SAAK,OAAO,MAAM;AAClB,QAAI,CAAI,gBAAW,KAAK,SAAS,EAAG;AAEpC,UAAM,QAAW,iBAAY,KAAK,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AAC5E,eAAW,QAAQ,OAAO;AACxB,UAAI;AACF,cAAM,WAAgB,YAAK,KAAK,WAAW,IAAI;AAC/C,cAAM,UAAa,kBAAa,UAAU,OAAO;AACjD,cAAM,MAAM,eAAe,SAAS,QAAQ;AAC5C,YAAI,KAAK;AACP,eAAK,OAAO,IAAI,IAAI,IAAI,GAAG;AAAA,QAC7B;AAAA,MACF,SAAS,OAAO;AACd,aAAK,OAAO,KAAK,yBAAyB,IAAI,IAAI,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AACF;;;ACxDA,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;AAIA,IAAMC,wBAAuB;AAEtB,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,IAAIF,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,WAAWG,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,gBAAgB,aAAa,SAAS,GAAG;AACrE,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,GAAGH,qBAAoB;AACtD,UAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,WAAM,EAAE,WAAW,EAAE;AAExE,WAAO;AAAA;AAAA;AAAA;AAAA,EAIT,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,EAGhB;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;;;ACrTO,SAAS,wBAAwB,SAAsD;AAC5F,QAAM,EAAE,UAAU,cAAc,eAAe,gBAAgB,SAAS,QAAQ,IAAI;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,gBAAgB,aAAa,SAAS,GAAG;AACrE,cAAM,UAAU,MAAM,MAAM,OAAO,CAACI,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;;;ACxFA,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,gBACA,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,cAAc;AAE3D,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,gBAAgB,WAAW,CAAC;AAAA,IAC7E,OAAO;AACL,iBAAW,YAAY,4BAA4B,cAAc;AAAA,IACnE;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;AAAA,IACpE;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,gBAAgB,aAAa;AAAA,MACzD,yBAAyB;AAAA,MACzB,wBAAwB,mBAAmB;AAAA,IAC7C,CAAC;AAED,UAAM,kBAA4D;AAAA,MAChE,EAAE,MAAM,aAAa,SAAS,YAAY;AAAA,MAC1C,GAAG,eAAe,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE;AAAA,IACrE;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,gBACA,QACA,cACA,YACA,aACA,QAC4B;AAC5B,QAAM,eAAe,YAAY,4BAA4B,cAAc;AAC3E,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,gBAAgB,gBAAgB,cAAc,YAAY,aAAa,MAAM;AACvG;AAIA,SAAS,4BACP,UACA,mBAIA;AACA,MAAI,eAAe;AACnB,MAAI,aAAa,SAAS;AAE1B,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,YAAY,YAAY,SAAS,CAAC,EAAE,OAAO,IAAI;AACrD,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;AAAA,EAC3C;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;;;ACtPA;AAAA,EACE;AAAA,OAIK;AAWP,IAAMC,0BAAyB;AAMxB,SAAS,4BAA4B,SAAoE;AAC9G,QAAM,EAAE,SAAS,QAAQ,eAAe,QAAQ,IAAI;AACpD,QAAM,gBAAgB,QAAQ,iBAAiB,QAAQ,iBAAiBA;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,IAAMC,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,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,EACnB;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,EACnB;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,EACnB;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,EACnB;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,EACnB;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,EACnB;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,EACnB;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,EACnB;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,EACnB;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,EACnB;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,EACnB;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,EACZ;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,EACZ;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,EACZ;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,EACZ;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,EACZ;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,EACZ;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;AAKO,SAAS,oBAAoB,UAA6B,QAAmC;AAClG,QAAM,cAAc,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACrD,QAAM,WAAW,0BAA0B;AAAA,IACzC,CAAC,SAAS,CAAC,YAAY,IAAI,KAAK,EAAE,MAAM,KAAK,QAAQ,WAAW,KAAK,KAAK,QAAQ,SAAS,MAAM;AAAA,EACnG,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,KAAK,EAAE;AAE7B,SAAO,CAAC,GAAG,UAAU,GAAG,QAAQ;AAClC;;;AC9LO,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;AACxB,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;;;ACrKO,SAAS,2BAA2B,SAAoE;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,IAAI;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;;;AC7EA,SAAS,SAAS,mBAAmB;AAErC;AAAA,EAGE,gBAAgB;AAAA,EAChB;AAAA,OACK;AAKP,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;AAEA,SAAS,oBAAoB,UAA+C;AAC1E,SAAO,SAAS,IAAI,CAAC,QAAmB;AACtC,QAAI,IAAI,SAAS,QAAQ;AACvB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,IAAI,WAAW,GAAG,CAAC;AAAA,QACnD,WAAW,KAAK,IAAI;AAAA,MACtB;AAAA,IACF;AACA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,IAAI,WAAW,GAAG,CAAC;AAAA,MACnD,KAAK;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,MACP,OAAO,mBAAmB;AAAA,MAC1B,YAAY;AAAA,MACZ,WAAW,KAAK,IAAI;AAAA,IACtB;AAAA,EACF,CAAC;AACH;AAEA,SAAS,YAAY,SAA+C;AAClE,SAAO,QAAQ,SAAS,UAAU,QAAQ,SAAS,eAAe,QAAQ,SAAS;AACrF;AAEA,eAAsB,aAAa,SAAqD;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,IAAI;AAEJ,QAAM,mBAAoB,QAAQ,YAAY;AAE9C,QAAM,aAA8B,EAAE,cAAc,GAAG,kBAAkB,GAAG,aAAa,EAAE;AAC3F,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;AAAA,EAC/E;AAEA,MAAI,aAAa,SAAS;AACxB,WAAO,EAAE,OAAO,YAAY,eAAe,CAAC,GAAG,WAAW,CAAC,GAAG,YAAY,EAAE;AAAA,EAC9E;AAYA,QAAM,YAAY,CAAC,QAAQ,iBAAiB,QAAQ,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,QAAQ,QAAQ;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;AACrF,UAAQ,MAAM,0CAA0C;AAAA,IACtD,OAAO,QAAQ;AAAA,IACf,SAAS,QAAQ;AAAA,IACjB,gBAAgB,eAAe;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,EACT,CAAC;AAED,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,OAAO;AAAA,IACvC;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,UAAU,MAAM;AAAA,QAChB,YAAa,MAAM,QAAQ,CAAC;AAAA,MAC9B,CAAC;AAAA,IACH,WAAW,MAAM,SAAS,sBAAsB;AAC9C,cAAQ,EAAE,MAAM,YAAY,UAAU,MAAM,UAAU,YAAY,MAAM,OAAO,CAAC;AAAA,IAClF,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;;;AC/MA,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,EAAE,cAAc,GAAG,kBAAkB,GAAG,aAAa,EAAE;AAAA,EAChF,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,UAAU,MAAM,UAAU,YAAY,OAAO,CAAC;AACjF,aAAK,WAAW,gBAAgB,UAAU,EAAE,aAAa,MAAM,UAAU,OAAO,KAAK,CAAC;AACtF;AAAA,MACF;AAAA,MACA,KAAK;AACH,aAAK,QAAQ,EAAE,MAAM,eAAe,UAAU,MAAM,UAAU,YAAY,MAAM,WAAW,CAAC;AAC5F,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,IAC/B;AAAA,EACF;AAAA,EAEA,WACE,OACA,UACA,SACM;AACN,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,CAAC,SAAS,SAAS,MAAM,KAAK,eAAe,IAAK;AACtD,SAAK,eAAe;AAEpB,UAAM,gBAAgB,sBAAsB,KAAK,cAAc,UAAU,SAAS,kBAAkB;AACpG,UAAM,SAA6B;AAAA,MACjC;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK,UAAU;AAAA,MAC1B,aAAa,SAAS;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;;;ACzKA,SAAS,OAAOC,IAAgB,KAAa,QAAgB,SAA2C;AACtG,SAAOA,GAAE,KAAK,EAAE,KAAK,QAAQ,GAAG,QAAQ,CAAC;AAC3C;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,eACrB,GAAG,OAAOA,IAAG,6BAA6B,QAAQ;AAAA,IAChD,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,eAAe,aAAa;AAAA,IAC5B,cAAc,aAAa;AAAA,IAC3B,kBAAkB,gBAAgB,aAAa,gBAAgB,MAAM;AAAA,IACrE,iBAAiB,gBAAgB,aAAa,eAAe,MAAM;AAAA,EACrE,CAAC,CAAC;AAAA,IACF;AAEJ,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,kBAAkB,SAA2C;AAC3E,QAAM;AAAA,IACJ,GAAAA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,eAAe,yBAAyB,0BAA0BA,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;;;ACpKA,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,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,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,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,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,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;;;ACnUA,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,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,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,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,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,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;;;AC7UA,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,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,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,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,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,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;;;AC3UA,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,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,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,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,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,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;;;ACpTA,IAAM,YAAqD;AAAA,EACzD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AACX;AAEA,SAAS,eAAe,KAA8BC,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,KAAa,YAA8C;AACjE,UAAM,MAAO,SAAS,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,CAAC,QAAS,QAAO;AACrB,WAAO,YAAY,OAAO,OAAO;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,eACA,UAA0B,CAAC,GACH;AACxB,QAAM,EAAE,SAAS,SAAS,kBAAkB,OAAO,WAAW,WAAW,IAAI;AAC7E,QAAM,MAAM,KAAK;AAEjB,MAAI;AACF,QAAI,CAAC,iBAAiB;AACpB,YAAM,WAAW,KAAK,WAAW,aAAa;AAC9C,UAAI,SAAU,QAAO,EAAE,SAAS,MAAM,SAAS,SAAS;AAAA,IAC1D;AAEA,UAAM,cAAc,KAAK,aAAa,aAAa;AACnD,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,aAAa,cAAc,QAAQ,SAAS,YAAY,MAAM,WAAW,cAAc,MAAM,WAAW,QAAQ,MAAM;AAAA,IACzJ;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,eAAe,OAAO;AAEvC,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,gBACA,UAA0B,CAAC,GAC3B,YAC+D;AAC/D,MAAI,UAAU;AACd,MAAI,SAAS;AACb,MAAI,UAAU;AAEd,WAAS,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK;AAC9C,UAAM,SAAS,MAAM,uBAAuB,MAAM,eAAe,CAAC,GAAG,OAAO;AAC5E,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,eAAe,MAAM;AAAA,EAC3C;AAEA,SAAO,EAAE,SAAS,QAAQ,QAAQ;AACpC;AAEO,SAAS,gCACd,MACA,gBACwD;AACxD,QAAM,UAAU,oBAAI,IAAuD;AAE3E,aAAW,MAAM,gBAAgB;AAC/B,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;AAqBhC,SAAS,SAAS,KAAa,SAA2C;AACxE,MAAI,QAAQ,oBAAoB,SAAS,MAAO,QAAO,WAAW,QAAQ,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;AAEO,IAAM,iBAAN,MAAqB;AAAA,EAClB;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,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,WAAK,gBAAgB,WAAW,UAAU,MAAM;AAAA,IAClD;AAEA,SAAK,UAAU,KAAK;AACpB,WAAO,EAAE,SAAS,MAAM,QAAQ,UAAU;AAAA,EAC5C;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,UAAU;AAAA,MACd,GAAG,MAAM,QAAQ,KAAK;AAAA,MACtB,GAAG;AAAA,MACH,WAAW,KAAK,IAAI;AAAA,IACtB;AACA,UAAM,QAAQ,KAAK,IAAI;AAEvB,QAAI,QAAQ,UAAU,KAAK,iBAAiB;AAC1C,WAAK,gBAAgB,SAAS,QAAQ,MAAM;AAAA,IAC9C;AAEA,SAAK,UAAU,KAAK;AACpB,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,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,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;;;AC5OO,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,QAAuB,SAA+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,YAAY,SAAS,eAAe;AAC1C,QAAM,WAAW,UAAU,OAAO,UAAU,OAAO;AACnD,QAAM,gBAAgB,UAAU,iBAAiBA;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,SAAS,SAAS;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,WACA,SACkC;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,GAAG,SAAS,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,aAAS,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,aAAS,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,WACA,SAC+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,SAAS,SAAS,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,oBAAoB;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,mBAAmB,SAAmD;AAC1F,QAAM,EAAE,UAAU,QAAQ,SAAS,aAAa,WAAW,SAAS,YAAY,IAAI;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,cAAc;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;;;AC0BA,SAAS,MAAM,kBAAAC,iBAAgB,gBAAAC,qBAAoB;;;ACzInD,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,kBAAkB;;;ACG3B,IAAM,aAAwB;AAAA,EAC5B,OAAO;AAAA,EAEP;AAAA,EACA,OAAO;AAAA,EAEP;AAAA,EACA,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,gBAA2B;AACzC,SAAO;AACT;AAqBO,IAAM,6BAA6B;AAEnC,IAAM,iCAAuD;AAAA,EAClE,SAAS,CAAC;AAAA,EACV,gBAAgB;AAAA,EAChB,SAAS;AACX;AA6CO,IAAM,qBAAgC;AAAA,EAC3C,WAAW;AAAA,IACT,SAAS;AAAA,IACT,UAAU;AAAA,IACV,WAAW;AAAA,EACb;AAAA,EACA,aAAa;AAAA,IACX,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AAAA,EACA,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAChB,MAAM;AACR;;;ADxFA,IAAI,aAA4B;AAChC,IAAI,UAAqB,cAAc;AAMhC,SAAS,cAAc,WAAmB,QAA0B;AACzE,eAAa;AACb,MAAI,OAAQ,WAAU;AACxB;AAEA,SAAS,mBAA2B;AAClC,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,8DAAyD;AAC1F,SAAO;AACT;AAIA,SAAS,gBAAwB;AAC/B,SAAY,YAAK,iBAAiB,GAAG,iBAAiB;AACxD;AAEA,SAAS,yBAAiC;AACxC,SAAY,YAAK,iBAAiB,GAAG,uBAAuB;AAC9D;AAEO,SAAS,oBAA4B;AAC1C,QAAM,MAAW,YAAK,iBAAiB,GAAG,SAAS;AACnD,MAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,IAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACvC;AACA,SAAO;AACT;AAIO,SAAS,2BAAiD;AAC/D,QAAM,aAAa,uBAAuB;AAE1C,MAAI,CAAI,gBAAW,UAAU,GAAG;AAC9B,WAAO,EAAE,GAAG,+BAA+B;AAAA,EAC7C;AAEA,MAAI;AACF,UAAM,UAAa,kBAAa,YAAY,OAAO;AACnD,UAAM,OAAO,KAAK,MAAM,OAAO;AAE/B,WAAO;AAAA,MACL,SAAS,KAAK,WAAW,CAAC;AAAA,MAC1B,gBAAgB,KAAK,kBAAkB;AAAA,MACvC,SAAS,KAAK,WAAW;AAAA,IAC3B;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,OAAO,oCAAoC,KAAK;AAC9D,WAAO,EAAE,GAAG,+BAA+B;AAAA,EAC7C;AACF;AAEO,SAAS,yBAAyB,OAAmC;AAC1E,QAAM,aAAa,uBAAuB;AAC1C,QAAM,MAAW,eAAQ,UAAU;AAEnC,MAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,IAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACvC;AAEA,EAAG,mBAAc,YAAY,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;AACpE,UAAQ,KAAK,OAAO,yBAAyB;AAC/C;AAEO,SAAS,yBAAmD;AACjE,SAAO,yBAAyB,EAAE;AACpC;AAEO,SAAS,2BAA0D;AACxE,QAAM,QAAQ,yBAAyB;AACvC,MAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,QAAS,QAAO;AACpD,SAAO,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,cAAc,KAAK;AACrE;AAEO,SAAS,uBAAuB,IAA2C;AAChF,QAAM,QAAQ,yBAAyB;AACvC,SAAO,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;AACnD;AAEO,SAAS,mBAAmB,QAIjC;AACA,QAAM,QAAQ,yBAAyB;AAEvC,MAAI,MAAM,QAAQ,UAAU,4BAA4B;AACtD,WAAO,EAAE,SAAS,OAAO,OAAO,OAAO,0BAA0B,mBAAmB;AAAA,EACtF;AAEA,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,YAAoC;AAAA,IACxC,GAAG;AAAA,IACH,IAAI,WAAW;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AAEA,QAAM,QAAQ,KAAK,SAAS;AAE5B,MAAI,MAAM,QAAQ,WAAW,GAAG;AAC9B,UAAM,iBAAiB,UAAU;AAAA,EACnC;AAEA,2BAAyB,KAAK;AAC9B,UAAQ,KAAK,OAAO,4BAA4B,UAAU,IAAI,EAAE;AAChE,SAAO,EAAE,SAAS,MAAM,QAAQ,UAAU;AAC5C;AAEO,SAAS,sBACd,IACA,SACsC;AACtC,QAAM,QAAQ,yBAAyB;AACvC,QAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AAExD,MAAI,UAAU,IAAI;AAChB,WAAO,EAAE,SAAS,OAAO,OAAO,mBAAmB;AAAA,EACrD;AAEA,QAAM,QAAQ,KAAK,IAAI;AAAA,IACrB,GAAG,MAAM,QAAQ,KAAK;AAAA,IACtB,GAAG;AAAA,IACH,WAAW,KAAK,IAAI;AAAA,EACtB;AAEA,2BAAyB,KAAK;AAC9B,UAAQ,KAAK,OAAO,8BAA8B,MAAM,QAAQ,KAAK,EAAE,IAAI,EAAE;AAC7E,SAAO,EAAE,SAAS,KAAK;AACzB;AAEO,SAAS,sBAAsB,IAAkD;AACtF,QAAM,QAAQ,yBAAyB;AACvC,QAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AAExD,MAAI,UAAU,IAAI;AAChB,WAAO,EAAE,SAAS,OAAO,OAAO,mBAAmB;AAAA,EACrD;AAEA,QAAM,cAAc,MAAM,QAAQ,KAAK,EAAE;AACzC,QAAM,QAAQ,OAAO,OAAO,CAAC;AAE7B,MAAI,MAAM,mBAAmB,IAAI;AAC/B,UAAM,iBAAiB,MAAM,QAAQ,SAAS,IAAI,MAAM,QAAQ,CAAC,EAAE,KAAK;AAAA,EAC1E;AAEA,2BAAyB,KAAK;AAC9B,UAAQ,KAAK,OAAO,8BAA8B,WAAW,EAAE;AAC/D,SAAO,EAAE,SAAS,KAAK;AACzB;AAEO,SAAS,yBAAyB,IAAkD;AACzF,QAAM,QAAQ,yBAAyB;AACvC,QAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAEpD,MAAI,CAAC,QAAQ;AACX,WAAO,EAAE,SAAS,OAAO,OAAO,mBAAmB;AAAA,EACrD;AAEA,QAAM,iBAAiB;AACvB,2BAAyB,KAAK;AAC9B,UAAQ,KAAK,OAAO,gCAAgC,OAAO,IAAI,EAAE;AACjE,SAAO,EAAE,SAAS,KAAK;AACzB;AAEO,SAAS,qBAA8B;AAC5C,QAAM,QAAQ,yBAAyB;AACvC,SAAO,MAAM,mBAAmB,QAAQ,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,cAAc;AACjG;AAEO,SAAS,6BAA4C;AAC1D,SAAO,yBAAyB,EAAE;AACpC;AAIO,SAAS,gBAA2B;AACzC,MAAI;AACF,UAAM,aAAa,cAAc;AAEjC,QAAI,CAAI,gBAAW,UAAU,GAAG;AAC9B,aAAO,EAAE,GAAG,mBAAmB;AAAA,IACjC;AAEA,UAAM,UAAa,kBAAa,YAAY,OAAO;AACnD,UAAM,SAAS,KAAK,MAAM,OAAO;AAEjC,WAAO,YAAY,oBAAoB,MAAM;AAAA,EAC/C,SAAS,OAAO;AACd,YAAQ,MAAM,OAAO,0BAA0B,KAAK;AACpD,WAAO,EAAE,GAAG,mBAAmB;AAAA,EACjC;AACF;AAEO,SAAS,cAAc,QAAyB;AACrD,MAAI;AACF,UAAM,aAAa,cAAc;AACjC,UAAM,MAAW,eAAQ,UAAU;AAEnC,QAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,MAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AAEA,IAAG,mBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;AACrE,YAAQ,KAAK,OAAO,eAAe;AAAA,EACrC,SAAS,OAAO;AACd,YAAQ,MAAM,OAAO,0BAA0B,KAAK;AACpD,UAAM;AAAA,EACR;AACF;AAEO,SAAS,gBAAgB,SAAwC;AACtE,QAAM,UAAU,cAAc;AAC9B,QAAM,UAAU,YAAY,SAAS,OAAO;AAC5C,gBAAc,OAAO;AACrB,SAAO;AACT;AAEA,SAAS,YAA8B,MAAS,UAAyB;AACvE,QAAM,SAAS,EAAE,GAAG,KAAK;AAEzB,aAAW,OAAO,UAAU;AAC1B,QAAI,OAAO,UAAU,eAAe,KAAK,UAAU,GAAG,GAAG;AACvD,YAAM,gBAAgB,SAAS,GAAG;AAClC,YAAM,YAAY,KAAK,GAAG;AAE1B,UAAI,kBAAkB,QAAW;AAC/B,YACE,OAAO,kBAAkB,YACzB,kBAAkB,QAClB,CAAC,MAAM,QAAQ,aAAa,KAC5B,OAAO,cAAc,YACrB,cAAc,QACd,CAAC,MAAM,QAAQ,SAAS,GACxB;AACA;AAAC,UAAC,OAAmC,GAAG,IAAI,YAAY,WAAqB,aAAuB;AAAA,QACtG,OAAO;AACL;AAAC,UAAC,OAAmC,GAAG,IAAI;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,iBAA4B;AAC1C,QAAM,SAAS,EAAE,GAAG,mBAAmB;AACvC,gBAAc,MAAM;AACpB,SAAO;AACT;;;AElRA,OAAOC,eAAc;;;ACGd,IAAM,wBAAwB;AAAA,EACnC;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;AAKO,IAAM,wBAAwB;AAAA,EACnC;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;;;AD1BA,IAAM,kBAAkB;AACxB,IAAM,sBAAsB;AAE5B,IAAIC,WAAqB,cAAc;AAEhC,SAAS,mBAAmB,QAAyB;AAC1D,EAAAA,WAAU;AACZ;AAEA,SAASC,iBAAgB,SAAiB,OAAuB;AAC/D,QAAM,YAAY,IAAI,KAAK,OAAO;AAClC,QAAM,UAAU,IAAI,KAAK,KAAK;AAE9B,QAAM,aAAa,CAAC,MAAY;AAC9B,UAAM,OAAO,EAAE,YAAY;AAC3B,UAAM,QAAQ,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACtD,UAAM,MAAM,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;AAC/C,UAAM,OAAO,OAAO,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,GAAG;AACjD,UAAM,SAAS,OAAO,EAAE,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACrD,WAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,IAAI,MAAM;AAAA,EAClD;AAEA,SAAO,GAAG,WAAW,SAAS,CAAC,MAAM,WAAW,OAAO,CAAC;AAC1D;AAEA,SAASC,qBAAoB,UAA8C;AACzE,SAAO,SAAS,OAAO,CAAC,MAAM;AAC5B,QAAI,EAAE,SAAS,UAAa,sBAAsB,SAAS,EAAE,IAA8C,GAAG;AAC5G,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,KAAK,EAAE,WAAW,GAAG;AAC/C,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,EAAE,QAAQ,KAAK;AAC/B,eAAW,WAAW,uBAAuB;AAC3C,UAAI,QAAQ,SAAS,OAAO,GAAG;AAC7B,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,mBACd,SACA,UACA,gBAAyB,MACjB;AACR,QAAM,YAAYD,iBAAgB,QAAQ,SAAS,QAAQ,KAAK;AAEhE,QAAM,gBAAgB,gBAAgBC,qBAAoB,QAAQ,IAAI;AAEtE,MAAI,cAAc,WAAW,GAAG;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,CAAC,GAAG,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,QAAG;AAElF,QAAM,UAAU,cAAc,IAAI,CAAC,MAAM,GAAG,EAAE,UAAU,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AAEnF,SAAO,IAAI,SAAS,6BAAS,YAAY;AAAA,EAAK,OAAO;AACvD;AAEA,SAAS,mBACP,SACA,WAAmB,iBACnB,eAAuB,qBACb;AACV,MAAI,QAAQ,UAAU,UAAU;AAC9B,WAAO,CAAC,OAAO;AAAA,EACjB;AAEA,QAAM,SAAmB,CAAC;AAC1B,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAEhC,MAAI,eAAe;AACnB,MAAI,gBAAgB;AAEpB,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,UAAU;AAC1B,UAAI,cAAc;AAChB,eAAO,KAAK,YAAY;AACxB,cAAM,aAAa,aAAa,MAAM,IAAI;AAC1C,wBAAgB,WAAW,MAAM,EAAE,EAAE,KAAK,IAAI,EAAE,MAAM,CAAC,YAAY;AACnE,uBAAe;AAAA,MACjB;AAEA,UAAI,YAAY;AAChB,aAAO,UAAU,SAAS,GAAG;AAC3B,cAAM,OAAO,UAAU,MAAM,GAAG,WAAW,cAAc,MAAM;AAC/D,eAAO,KAAK,gBAAgB,IAAI;AAChC,wBAAgB,KAAK,MAAM,CAAC,YAAY;AACxC,oBAAY,UAAU,MAAM,WAAW,cAAc,MAAM;AAAA,MAC7D;AACA;AAAA,IACF;AAEA,UAAM,YAAY,aAAa,UAAU,eAAe,IAAI,KAAK,KAAK;AAEtE,QAAI,YAAY,UAAU;AACxB,UAAI,cAAc;AAChB,eAAO,KAAK,YAAY;AACxB,cAAM,aAAa,aAAa,MAAM,IAAI;AAC1C,wBAAgB,WAAW,MAAM,EAAE,EAAE,KAAK,IAAI,EAAE,MAAM,CAAC,YAAY;AAAA,MACrE;AACA,qBAAe,gBAAgB,gBAAgB,OAAO,OAAO;AAAA,IAC/D,OAAO;AACL,qBAAe,eAAe,eAAe,OAAO,OAAO;AAAA,IAC7D;AAAA,EACF;AAEA,MAAI,cAAc;AAChB,WAAO,KAAK,YAAY;AAAA,EAC1B;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,IAAuB,SAAyC;AACzF,QAAM,EAAE,QAAQ,IAAI,WAAW,IAAI;AAEnC,MAAI,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASV,QAAM,SAAiC,CAAC;AAExC,MAAI,YAAY;AACd,WAAO;AACP,WAAO,KAAK,WAAW,SAAS,WAAW,KAAK;AAAA,EAClD;AAEA,SAAO;AACP,SAAO,KAAK,KAAK;AAEjB,QAAM,WAAW,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAE9C,SAAO,SAAS,QAAQ;AAC1B;AAEA,SAAS,yBAAyB,IAAuB,WAAmB,QAAgB,KAAuB;AACjH,QAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeZ,SAAO,GAAG,QAAQ,GAAG,EAAE,IAAI,WAAW,KAAK;AAC7C;AAEO,SAAS,iBAAiB,QAAgB,UAA2B,CAAC,GAAY;AACvF,QAAM,EAAE,gBAAgB,MAAM,gBAAgB,gBAAgB,IAAI;AAElE,MAAI,KAA+B;AAEnC,MAAI;AACF,SAAK,IAAIC,UAAS,QAAQ,EAAE,UAAU,KAAK,CAAC;AAE5C,UAAM,WAAW,kBAAkB,IAAI,OAAO;AAE9C,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,SAAkB,CAAC;AAEzB,eAAW,WAAW,UAAU;AAC9B,YAAM,WAAW,yBAAyB,IAAI,QAAQ,EAAE;AACxD,YAAM,UAAU,mBAAmB,SAAS,UAAU,aAAa;AAEnE,UAAI,CAAC,SAAS;AACZ;AAAA,MACF;AAEA,YAAM,gBAAgB,gBAAgBD,qBAAoB,QAAQ,IAAI;AACtE,YAAM,eAAe,CAAC,GAAG,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAExE,YAAM,eAA8B;AAAA,QAClC,WAAW,QAAQ;AAAA,QACnB,SAAS,QAAQ;AAAA,QACjB,OAAO,QAAQ;AAAA,QACf,cAAc,cAAc;AAAA,QAC5B;AAAA,MACF;AAEA,UAAI,QAAQ,UAAU,eAAe;AACnC,eAAO,KAAK;AAAA,UACV,IAAI,WAAW,QAAQ,EAAE;AAAA,UACzB,MAAM;AAAA,UACN;AAAA,UACA,UAAU;AAAA,QACZ,CAAC;AAAA,MACH,OAAO;AACL,cAAM,YAAY,mBAAmB,SAAS,aAAa;AAE3D,iBAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,iBAAO,KAAK;AAAA,YACV,IAAI,WAAW,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,YACtC,MAAM;AAAA,YACN,SAAS,UAAU,CAAC;AAAA,YACpB,UAAU;AAAA,cACR,GAAG;AAAA,cACH,eAAe;AAAA,cACf,gBAAgB,UAAU;AAAA,YAC5B;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,IAAAF,SAAQ,MAAM,YAAY,gCAAgC,KAAK;AAC/D,WAAO,CAAC;AAAA,EACV,UAAE;AACA,QAAI,IAAI;AACN,SAAG,MAAM;AAAA,IACX;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,QAAgB,WAAiC;AAC/E,MAAI,KAA+B;AAEnC,MAAI;AACF,SAAK,IAAIG,UAAS,QAAQ,EAAE,UAAU,KAAK,CAAC;AAE5C,UAAM,UAAU,GACb;AAAA,MACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASF,EACC,IAAI,SAAS;AAEhB,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,yBAAyB,IAAI,SAAS;AAEvD,UAAM,UAAU,mBAAmB,SAAS,UAAU,IAAI;AAE1D,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,UAAM,gBAAgBD,qBAAoB,QAAQ;AAClD,UAAM,eAAe,CAAC,GAAG,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAExE,WAAO;AAAA,MACL,IAAI,WAAW,QAAQ,EAAE;AAAA,MACzB,MAAM;AAAA,MACN;AAAA,MACA,UAAU;AAAA,QACR,WAAW,QAAQ;AAAA,QACnB,SAAS,QAAQ;AAAA,QACjB,OAAO,QAAQ;AAAA,QACf,cAAc,cAAc;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,IAAAF,SAAQ,MAAM,YAAY,sCAAsC,KAAK;AACrE,WAAO;AAAA,EACT,UAAE;AACA,QAAI,IAAI;AACN,SAAG,MAAM;AAAA,IACX;AAAA,EACF;AACF;;;AErSA,IAAII,WAAqB,cAAc;AAEhC,SAAS,2BAA2B,QAAyB;AAClE,EAAAA,WAAU;AACZ;AAEO,IAAM,mCAAN,MAAoE;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAqB;AAAA,EAE7B,YAAY,QAAyC;AACnD,SAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAC/C,SAAK,SAAS,OAAO;AACrB,SAAK,QAAQ,OAAO;AAAA,EACtB;AAAA,EAEA,MAAM,MAAM,MAAiC;AAC3C,UAAM,UAAU,MAAM,KAAK,iBAAiB,CAAC,IAAI,CAAC;AAClD,WAAO,QAAQ,CAAC;AAAA,EAClB;AAAA,EAEA,MAAM,WAAW,OAAsC;AACrD,WAAO,KAAK,iBAAiB,KAAK;AAAA,EACpC;AAAA,EAEA,MAAc,iBAAiB,OAAsC;AACnE,UAAM,MAAM,GAAG,KAAK,OAAO;AAE3B,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AAEA,QAAI,KAAK,QAAQ;AACf,cAAQ,eAAe,IAAI,UAAU,KAAK,MAAM;AAAA,IAClD;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,OAAO,KAAK;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,IAAI,MAAM,uBAAuB,SAAS,MAAM,MAAM,SAAS,EAAE;AAAA,MACzE;AAEA,YAAM,OAAQ,MAAM,SAAS,KAAK;AAIlC,UAAI,CAAC,KAAK,QAAQ,KAAK,KAAK,WAAW,GAAG;AACxC,cAAM,IAAI,MAAM,yBAAyB;AAAA,MAC3C;AAEA,YAAM,SAAS,KAAK,KAAK,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACzD,YAAM,UAAU,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS;AAEnD,UAAI,QAAQ,SAAS,KAAK,KAAK,eAAe,GAAG;AAC/C,aAAK,aAAa,QAAQ,CAAC,EAAE;AAAA,MAC/B;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,MAAAA,SAAQ,MAAM,OAAO,8BAA8B,GAAG,IAAI,KAAK;AAC/D,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,cAAsB;AACpB,WAAO,sBAAsB,KAAK,KAAK;AAAA,EACzC;AAAA,EAEA,gBAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,WAA0D;AAC9D,QAAI;AACF,YAAM,aAAa,MAAM,KAAK,MAAM,MAAM;AAE1C,UAAI,WAAW,WAAW,GAAG;AAC3B,eAAO,EAAE,SAAS,OAAO,OAAO,2BAA2B;AAAA,MAC7D;AAEA,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,OAAO;AACd,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAAA,EAE/B;AACF;;;ACnGA,IAAI,gBAA0C;AAC9C,IAAI,iBAAgC;AAEpC,IAAIC,WAAqB,cAAc;AACvC,IAAI,gBAA6D;AAO1D,SAAS,qBAAqB,QAAmB,cAAwD;AAC9G,EAAAA,WAAU;AACV,kBAAgB;AAClB;AAEA,eAAsB,sBAAyD;AAC7E,MAAI,CAAC,mBAAmB,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,yBAAyB;AACxC,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,MAAI,iBAAiB,mBAAmB,OAAO,IAAI;AACjD,WAAO;AAAA,EACT;AAEA,MAAI,eAAe;AACjB,UAAM,cAAc,QAAQ;AAC5B,oBAAgB;AAAA,EAClB;AAEA,MAAI;AACF,oBAAgB,MAAM,uBAAuB,MAAM;AACnD,qBAAiB,OAAO;AACxB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,IAAAA,SAAQ,MAAM,OAAO,sCAAsC,KAAK;AAChE,WAAO;AAAA,EACT;AACF;AAEA,eAAe,uBAAuB,QAA4D;AAChG,QAAM,YAAY,iBAAiB,MAAM;AACzC,EAAAA,SAAQ,KAAK,OAAO,oBAAoB,OAAO,IAAI,KAAK,UAAU,KAAK,GAAG;AAE1E,SAAO,IAAI,iCAAiC,SAAS;AACvD;AAEA,SAAS,iBAAiB,QAIxB;AACA,MAAI,OAAO,cAAc,aAAa;AACpC,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,4EAAuE;AAAA,IACzF;AAEA,UAAM,YAAY,cAAc;AAEhC,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,6EAAwE;AAAA,IAC1F;AAEA,UAAM,UAAU,UAAU,WAAW,kBAAkB,UAAU,QAAQ;AAEzE,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,UAAU,UAAU;AAAA,MAC5B,OAAO,OAAO;AAAA,IAChB;AAAA,EACF,OAAO;AACL,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAEA,WAAO;AAAA,MACL,SAAS,OAAO;AAAA,MAChB,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,IAChB;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,UAA0B;AACnD,QAAM,cAAsC;AAAA,IAC1C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,qBAAqB;AAAA,EACvB;AAEA,SAAO,YAAY,QAAQ,KAAK;AAClC;AAEA,eAAsB,wBAAuC;AAC3D,MAAI,eAAe;AACjB,UAAM,cAAc,QAAQ;AAC5B,oBAAgB;AAChB,qBAAiB;AACjB,IAAAA,SAAQ,KAAK,OAAO,yBAAyB;AAAA,EAC/C;AACF;AAEA,eAAsB,wBACpB,QAC+C;AAC/C,MAAI;AACF,UAAM,gBACJ,QAAQ,SACJ,SACA;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,WAAW,OAAO,aAAa;AAAA,MAC/B,OAAO,OAAO,SAAS;AAAA,MACvB,SAAS,OAAO;AAAA,MAChB,QAAQ,OAAO;AAAA,MACf,WAAW,KAAK,IAAI;AAAA,MACpB,WAAW,KAAK,IAAI;AAAA,IACtB;AAEN,UAAM,UAAU,MAAM,uBAAuB,aAAa;AAC1D,UAAM,SAAS,MAAM,QAAQ,SAAS;AACtC,UAAM,QAAQ,QAAQ;AACtB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D;AAAA,EACF;AACF;;;ACrJA,YAAYC,YAAU;;;ACEtB,OAAOC,eAAc;;;ACCd,SAASC,kBAAiB,GAAa,GAAqB;AACjE,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AAEvC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,WAAO,EAAE,CAAC,IAAI,EAAE,CAAC;AACjB,aAAS,EAAE,CAAC,IAAI,EAAE,CAAC;AACnB,aAAS,EAAE,CAAC,IAAI,EAAE,CAAC;AAAA,EACrB;AAEA,SAAO,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI;AACtD;;;ADRA,IAAIC,WAAqB,cAAc;AAEhC,SAAS,sBAAsB,QAAyB;AAC7D,EAAAA,WAAU;AACZ;AAEA,SAAS,eAAe,QAA0B;AAChD,QAAM,UAAU,IAAI,aAAa,MAAM;AACvC,SAAO,OAAO,KAAK,QAAQ,MAAM;AACnC;AAEA,SAAS,eAAe,QAA0B;AAChD,QAAM,UAAU,IAAI,aAAa,OAAO,QAAQ,OAAO,YAAY,OAAO,SAAS,CAAC;AACpF,SAAO,MAAM,KAAK,OAAO;AAC3B;AAEO,IAAM,oBAAN,MAAgD;AAAA,EAC7C;AAAA,EACA;AAAA,EAER,YAAY,QAAgB;AAC1B,SAAK,SAAS;AACd,SAAK,KAAK,IAAIC,UAAS,MAAM;AAC7B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,aAAmB;AACzB,SAAK,GAAG,OAAO,oBAAoB;AAEnC,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAQZ;AAED,QAAI;AACF,WAAK,GAAG,KAAK,uEAAuE;AAAA,IACtF,QAAQ;AAAA,IAER;AAEA,IAAAD,SAAQ,KAAK,gBAAgB,gBAAgB,KAAK,MAAM,EAAE;AAAA,EAC5D;AAAA,EAEA,MAAM,IAAI,IAAY,QAAkB,UAAmD;AACzF,UAAM,SAAS,eAAe,MAAM;AAEpC,SAAK,GACF;AAAA,MACC;AAAA;AAAA;AAAA;AAAA,IAIF,EACC,IAAI,IAAI,QAAQ,OAAO,QAAQ,WAAW,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,EAC9E;AAAA,EAEA,MAAM,SAAS,OAAmG;AAChH,UAAM,SAAS,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG9B;AAGD,UAAM,aAAa,KAAK,GAAG,YAAY,CAAC,eAAkC;AACxE,iBAAW,QAAQ,YAAY;AAC7B,cAAM,SAAS,eAAe,KAAK,MAAM;AACzC,eAAO,IAAI,KAAK,IAAI,QAAQ,KAAK,OAAO,QAAQ,KAAK,WAAW,KAAK,UAAU,KAAK,QAAQ,IAAI,IAAI;AAAA,MACtG;AAAA,IACF,CAAC;AAED,eAAW,KAAK;AAAA,EAClB;AAAA,EAEA,MAAM,IAAI,IAAsC;AAC9C,UAAM,MAAM,KAAK,GAAG,QAAQ,yCAAyC,EAAE,IAAI,EAAE;AAE7E,QAAI,CAAC,IAAK,QAAO;AAEjB,WAAO,eAAe,IAAI,MAAM;AAAA,EAClC;AAAA,EAEA,MAAM,IAAI,IAA8B;AACtC,UAAM,MAAM,KAAK,GAAG,QAAQ,oCAAoC,EAAE,IAAI,EAAE;AACxE,WAAO,CAAC,CAAC;AAAA,EACX;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,SAAK,GAAG,QAAQ,kCAAkC,EAAE,IAAI,EAAE;AAAA,EAC5D;AAAA,EAEA,MAAM,OAAO,OAAiB,MAA6C;AACzE,UAAM,OAAO,KAAK,GAAG,QAAQ,0CAA0C,EAAE,IAAI;AAM7E,QAAI,KAAK,WAAW,GAAG;AACrB,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,UAAgC,KAAK,IAAI,CAAC,QAAQ;AACtD,YAAM,SAAS,eAAe,IAAI,MAAM;AACxC,YAAM,QAAQE,kBAAiB,OAAO,MAAM;AAC5C,aAAO;AAAA,QACL,IAAI,IAAI;AAAA,QACR;AAAA,QACA,UAAU,IAAI,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI;AAAA,MACtD;AAAA,IACF,CAAC;AAED,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACxC,WAAO,QAAQ,MAAM,GAAG,IAAI;AAAA,EAC9B;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,GAAG,KAAK,qBAAqB;AAClC,IAAAF,SAAQ,KAAK,gBAAgB,qBAAqB;AAAA,EACpD;AAAA,EAEA,MAAM,WAAsC;AAC1C,UAAM,WAAW,KAAK,GAAG,QAAQ,uCAAuC,EAAE,IAAI;AAE9E,UAAM,SAAS,KAAK,GAAG,QAAQ,wCAAwC,EAAE,IAAI;AAE7E,QAAI;AACJ,QAAI;AACF,YAAMG,OAAK,MAAM,OAAO,IAAI;AAC5B,YAAM,QAAQA,KAAG,SAAS,KAAK,MAAM;AACrC,kBAAY,MAAM;AAAA,IACpB,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,YAAY,QAAQ;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,GAAG,MAAM;AACd,IAAAH,SAAQ,KAAK,gBAAgB,QAAQ;AAAA,EACvC;AACF;;;AEzJA,IAAII,WAAqB,cAAc;AAEhC,SAAS,sBAAsB,QAAyB;AAC7D,EAAAA,WAAU;AACZ;AAUO,IAAM,oBAAN,MAAgD;AAAA,EAC7C;AAAA,EACA,QAAgC,oBAAI,IAAI;AAAA,EACxC,OAAyB;AAAA,EACzB,OAAyB;AAAA,EAEjC,YAAY,WAAmB,KAAO;AACpC,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,WAAW,MAAuB;AACxC,QAAI,SAAS,KAAK,KAAM;AAExB,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,OAAO,KAAK;AAAA,IACxB;AACA,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,OAAO,KAAK;AAAA,IACxB;AACA,QAAI,SAAS,KAAK,MAAM;AACtB,WAAK,OAAO,KAAK;AAAA,IACnB;AAEA,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK;AACjB,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,OAAO;AAAA,IACnB;AACA,SAAK,OAAO;AAEZ,QAAI,CAAC,KAAK,MAAM;AACd,WAAK,OAAO;AAAA,IACd;AAAA,EACF;AAAA,EAEQ,UAAU,MAAuB;AACvC,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK;AACjB,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,OAAO;AAAA,IACnB;AACA,SAAK,OAAO;AAEZ,QAAI,CAAC,KAAK,MAAM;AACd,WAAK,OAAO;AAAA,IACd;AAAA,EACF;AAAA,EAEQ,aAA+B;AACrC,QAAI,CAAC,KAAK,KAAM,QAAO;AAEvB,UAAM,UAAU,KAAK;AACrB,SAAK,OAAO,QAAQ;AACpB,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,OAAO;AAAA,IACnB,OAAO;AACL,WAAK,OAAO;AAAA,IACd;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,IAAY,QAAkB,UAAmD;AACzF,QAAI,KAAK,MAAM,IAAI,EAAE,GAAG;AACtB,YAAM,OAAO,KAAK,MAAM,IAAI,EAAE;AAC9B,WAAK,SAAS;AACd,WAAK,WAAW;AAChB,WAAK,WAAW,IAAI;AAAA,IACtB,OAAO;AACL,YAAM,OAAkB;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAEA,WAAK,MAAM,IAAI,IAAI,IAAI;AACvB,WAAK,UAAU,IAAI;AAEnB,UAAI,KAAK,MAAM,OAAO,KAAK,UAAU;AACnC,cAAM,UAAU,KAAK,WAAW;AAChC,YAAI,SAAS;AACX,eAAK,MAAM,OAAO,QAAQ,EAAE;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,OAAmG;AAChH,eAAW,QAAQ,OAAO;AACxB,YAAM,KAAK,IAAI,KAAK,IAAI,KAAK,QAAQ,KAAK,QAAQ;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,IAAsC;AAC9C,UAAM,OAAO,KAAK,MAAM,IAAI,EAAE;AAC9B,QAAI,CAAC,KAAM,QAAO;AAElB,SAAK,WAAW,IAAI;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,IAAI,IAA8B;AACtC,WAAO,KAAK,MAAM,IAAI,EAAE;AAAA,EAC1B;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,OAAO,KAAK,MAAM,IAAI,EAAE;AAC9B,QAAI,CAAC,KAAM;AAEX,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,OAAO,KAAK;AAAA,IACxB;AACA,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,OAAO,KAAK;AAAA,IACxB;AACA,QAAI,SAAS,KAAK,MAAM;AACtB,WAAK,OAAO,KAAK;AAAA,IACnB;AACA,QAAI,SAAS,KAAK,MAAM;AACtB,WAAK,OAAO,KAAK;AAAA,IACnB;AAEA,SAAK,MAAM,OAAO,EAAE;AAAA,EACtB;AAAA,EAEA,MAAM,OAAO,OAAiB,MAA6C;AACzE,UAAM,UAAgC,CAAC;AAEvC,eAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,YAAM,QAAQC,kBAAiB,OAAO,KAAK,MAAM;AACjD,cAAQ,KAAK;AAAA,QACX,IAAI,KAAK;AAAA,QACT;AAAA,QACA,UAAU,KAAK;AAAA,MACjB,CAAC;AAAA,IACH;AAEA,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACxC,WAAO,QAAQ,MAAM,GAAG,IAAI;AAAA,EAC9B;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,MAAM,MAAM;AACjB,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,IAAAD,SAAQ,KAAK,gBAAgB,qBAAqB;AAAA,EACpD;AAAA,EAEA,MAAM,WAAsC;AAC1C,QAAI;AACJ,eAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,mBAAa,KAAK,OAAO;AACzB;AAAA,IACF;AAEA,QAAI;AACJ,QAAI,YAAY;AACd,kBAAY,KAAK,MAAM,OAAO,aAAa;AAAA,IAC7C;AAEA,WAAO;AAAA,MACL,OAAO,KAAK,MAAM;AAAA,MAClB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,IAAAA,SAAQ,KAAK,gBAAgB,QAAQ;AAAA,EACvC;AACF;;;AHvLA,IAAI,cAAmC;AAEvC,IAAIE,WAAqB,cAAc;AAEhC,SAAS,gBAAgB,QAAyB;AACvD,EAAAA,WAAU;AACZ;AAEA,eAAsB,iBAA+C;AACnE,QAAM,SAAS,cAAc;AAE7B,MAAI,CAAC,OAAO,aAAa,SAAS;AAChC,WAAO;AAAA,EACT;AAEA,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AAEA,MAAI;AACF,kBAAc,MAAM,kBAAkB,OAAO,WAAW;AACxD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,IAAAA,SAAQ,MAAM,iBAAiB,0BAA0B,KAAK;AAC9D,WAAO;AAAA,EACT;AACF;AAEA,eAAe,kBAAkB,QAAkD;AACjF,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK,UAAU;AACb,YAAM,WAAW,OAAO,mBAAmB;AAC3C,MAAAA,SAAQ,KAAK,iBAAiB,iCAAiC,QAAQ,EAAE;AACzE,aAAO,IAAI,kBAAkB,QAAQ;AAAA,IACvC;AAAA,IAEA,KAAK,UAAU;AACb,YAAM,SAAS,OAAO,UAAe,YAAK,kBAAkB,GAAG,eAAe;AAC9E,MAAAA,SAAQ,KAAK,iBAAiB,uBAAuB,MAAM,EAAE;AAC7D,aAAO,IAAI,kBAAkB,MAAM;AAAA,IACrC;AAAA,IAEA,KAAK,WAAW;AACd,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAAA,IAEA;AACE,YAAM,IAAI,MAAM,uBAAuB,OAAO,IAAI,EAAE;AAAA,EACxD;AACF;AAEA,eAAsB,mBAAkC;AACtD,MAAI,aAAa;AACf,UAAM,YAAY,MAAM;AACxB,kBAAc;AACd,IAAAA,SAAQ,KAAK,iBAAiB,aAAa;AAAA,EAC7C;AACF;AAEA,eAAsB,sBAMnB;AACD,QAAM,SAAS,cAAc;AAE7B,MAAI,CAAC,OAAO,aAAa,SAAS;AAChC,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B;AAEA,QAAM,QAAQ,MAAM,eAAe;AACnC,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,SAAS,MAAM,MAAM,OAAO,YAAY,KAAK;AAAA,EACxD;AAEA,QAAM,QAAQ,MAAM,MAAM,SAAS;AACnC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAM,OAAO,YAAY;AAAA,IACzB,GAAG;AAAA,EACL;AACF;;;AI7EA,SAAS,kBAAAC,uBAAwC;AAWjD,IAAIC,WAAqB,cAAc;AACvC,IAAIC,iBAAiE;AAO9D,SAAS,qBAAqB,QAAmB,cAA4D;AAClH,EAAAD,WAAU;AACV,EAAAC,iBAAgB;AAClB;AAEA,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAY7B,eAAe,aAAa,OAAe,aAA4C;AACrF,MAAI;AACF,QAAI,CAACA,eAAe,QAAO;AAE3B,UAAM,eAAeA,eAAc;AACnC,QAAI,CAAC,aAAc,QAAO;AAE1B,UAAM,UAAU,aAAa,YAAY;AACzC,UAAM,SAAS,qBAAqB,QAAQ,WAAW,KAAK;AAE5D,UAAM,SAAS,MAAMC;AAAA,MACnB;AAAA,MACA;AAAA,QACE,cAAc;AAAA,QACd,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,QAAQ,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,MACrE;AAAA,MACA;AAAA,QACE,QAAQ,aAAa;AAAA,QACrB,aAAa;AAAA,QACb,WAAW;AAAA,QACX,QAAQ;AAAA,MACV;AAAA,IACF;AAEA,UAAM,YAAY,OAAO,QACtB,OAAO,CAAC,SAA8B,KAAK,SAAS,MAAM,EAC1D,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE,EACP,KAAK;AAER,WAAO,aAAa;AAAA,EACtB,SAAS,OAAO;AACd,IAAAF,SAAQ,KAAK,qBAAqB,8CAA8C,KAAK;AACrF,WAAO;AAAA,EACT;AACF;AAEA,SAAS,oBACP,gBACA,SACQ;AACR,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,CAAC,oBAAoB,cAAc,MAAM,4KAAgC,EAAE;AAEzF,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,SAAS,QAAQ,CAAC;AACxB,UAAM,SAAS,OAAO,QAAQ,KAAK,QAAQ,CAAC;AAC5C,UAAM,KAAK,oBAAU,IAAI,CAAC,yBAAU,KAAK,QAAQ;AACjD,UAAM,KAAK,OAAO,OAAO;AACzB,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,QAAM,KAAK,aAAa;AACxB,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,oNAAqC;AAEhD,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAsB,wBAAwB,SAAmE;AAC/G,QAAM,EAAE,aAAa,QAAQ,YAAY,YAAY,IAAI;AAEzD,QAAM,YAAY,cAAc;AAChC,QAAM,iBAAiB,QAAQ,kBAAkB,UAAU,kBAAkB;AAC7E,QAAM,OAAO,QAAQ,QAAQ,UAAU,QAAQ;AAE/C,EAAAA,SAAQ,KAAK,OAAO,8BAA8B,YAAY,MAAM,GAAG,EAAE,CAAC,MAAM;AAEhF,MAAI;AACF,UAAM,mBAAmB,MAAM,oBAAoB;AACnD,QAAI,CAAC,kBAAkB;AACrB,MAAAA,SAAQ,KAAK,OAAO,wDAAwD;AAC5E,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS,CAAC;AAAA,QACV,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,iBAAiB,MAAM,aAAa,aAAa,WAAW;AAElE,QAAI,aAAa,SAAS;AACxB,aAAO,EAAE,SAAS,OAAO,SAAS,CAAC,GAAG,OAAO,sBAAsB;AAAA,IACrE;AAEA,UAAM,SAAS,iBAAiB,QAAQ;AAAA,MACtC,OAAO;AAAA,MACP;AAAA,MACA,eAAe;AAAA,IACjB,CAAC;AAED,QAAI,OAAO,WAAW,GAAG;AACvB,MAAAA,SAAQ,KAAK,OAAO,sDAAsD;AAC1E,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,SAAS,CAAC;AAAA,QACV,eAAe;AAAA,MACjB;AAAA,IACF;AAEA,UAAM,cAAc,MAAM,eAAe;AACzC,UAAM,eAAe,oBAAI,IAAsB;AAC/C,UAAM,iBAA0B,CAAC;AAEjC,eAAW,SAAS,QAAQ;AAC1B,UAAI,aAAa;AACf,cAAM,SAAS,MAAM,YAAY,IAAI,MAAM,EAAE;AAC7C,YAAI,QAAQ;AACV,uBAAa,IAAI,MAAM,IAAI,MAAM;AACjC;AAAA,QACF;AAAA,MACF;AACA,qBAAe,KAAK,KAAK;AAAA,IAC3B;AAEA,QAAI,aAAa,SAAS;AACxB,aAAO,EAAE,SAAS,OAAO,SAAS,CAAC,GAAG,OAAO,sBAAsB;AAAA,IACrE;AAEA,QAAI,eAAe,SAAS,GAAG;AAC7B,YAAM,WAAW,eAAe,IAAI,CAAC,MAAM,EAAE,OAAO;AACpD,YAAM,UAAU,MAAM,iBAAiB,WAAW,QAAQ;AAE1D,eAAS,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK;AAC9C,cAAM,QAAQ,eAAe,CAAC;AAC9B,cAAM,SAAS,QAAQ,CAAC;AACxB,qBAAa,IAAI,MAAM,IAAI,MAAM;AAEjC,YAAI,aAAa;AACf,gBAAM,YAAY,IAAI,MAAM,IAAI,QAAQ,MAAM,QAA8C;AAAA,QAC9F;AAAA,MACF;AAAA,IACF;AAEA,QAAI,aAAa,SAAS;AACxB,aAAO,EAAE,SAAS,OAAO,SAAS,CAAC,GAAG,OAAO,sBAAsB;AAAA,IACrE;AAEA,UAAM,cAAc,MAAM,iBAAiB,MAAM,cAAc;AAE/D,UAAM,gBAKD,CAAC;AAEN,eAAW,SAAS,QAAQ;AAC1B,YAAM,SAAS,aAAa,IAAI,MAAM,EAAE;AACxC,UAAI,CAAC,OAAQ;AAEb,YAAM,QAAQG,kBAAiB,aAAa,MAAM;AAClD,oBAAc,KAAK;AAAA,QACjB;AAAA,QACA,SAAS,MAAM;AAAA,QACf,SAAS,MAAM;AAAA,QACf,UAAU,MAAM;AAAA,MAClB,CAAC;AAAA,IACH;AAEA,kBAAc,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC9C,UAAM,aAAa,cAAc,MAAM,GAAG,IAAI;AAE9C,UAAM,WAAW,WAAW,CAAC,GAAG,SAAS;AACzC,IAAAH,SAAQ;AAAA,MACN;AAAA,MACA,kCAAkC,WAAW,MAAM,4BAA4B,WAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,IAC3G;AAEA,UAAM,gBAAgB,oBAAoB,gBAAgB,UAAU;AAEpE,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,IAAAA,SAAQ,MAAM,OAAO,0BAA0B,KAAK;AACpD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS,CAAC;AAAA,MACV,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D;AAAA,EACF;AACF;;;AC7MO,SAAS,QAAQ,SAA+B;AACrD,QAAM,EAAE,WAAW,QAAQ,cAAc,mBAAmB,IAAI;AAChE,gBAAc,WAAW,MAAM;AAC/B,qBAAmB,MAAM;AACzB,uBAAqB,QAAQ,YAAY;AACzC,6BAA2B,MAAM;AACjC,kBAAgB,MAAM;AACtB,wBAAsB,MAAM;AAC5B,wBAAsB,MAAM;AAC5B,uBAAqB,QAAQ,kBAAkB;AACjD;;;AChCA,YAAYI,UAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;;;ACTtB,YAAYC,UAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,SAAS,iBAAiB;;;ACNnC,SAAS,SAAS;AAEX,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,EAC/B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,EAC5B,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE;AACjC,CAAC;AAEM,IAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE;AAAA,EACpC,yBAAyB,EAAE,QAAQ,EAAE,QAAQ,KAAK;AACpD,CAAC;AAEM,IAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,EAAE,QAAQ,gBAAgB;AAAA,EACjE,MAAM,EAAE,OAAO,EAAE,QAAQ,WAAW;AAAA,EACpC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE;AAC9B,CAAC;AAEM,IAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE;AAC7B,CAAC;AAEM,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,qBAAqB,EAAE,KAAK,CAAC,YAAY,SAAS,CAAC,EAAE,QAAQ,UAAU;AAAA,EACvE,uBAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,KAAK,EAAE,QAAQ,IAAI;AAAA,EACvE,kBAAkB,EAAE,KAAK,CAAC,SAAS,UAAU,CAAC,EAAE,QAAQ,UAAU;AACpE,CAAC;AAEM,IAAM,eAAe,EAAE,OAAO;AAAA,EACnC,KAAK,gBAAgB,QAAQ,CAAC,CAAC;AAAA,EAC/B,MAAM,iBAAiB,QAAQ,CAAC,CAAC;AAAA,EACjC,KAAK,gBAAgB,QAAQ,CAAC,CAAC;AAAA,EAC/B,QAAQ,mBAAmB,QAAQ,CAAC,CAAC;AAAA,EACrC,IAAI,eAAe,QAAQ,CAAC,CAAC;AAC/B,CAAC;;;AD5BD,IAAM,aAAkB,YAAQ,YAAQ,GAAG,UAAU;AACrD,IAAM,cAAmB,YAAK,YAAY,aAAa;AACvD,IAAM,cAAmB,YAAK,YAAY,aAAa;AAKhD,SAAS,WAAW,WAAmD;AAC5E,QAAM,aAAa,eAAe;AAClC,QAAM,YAAY,cAAc;AAEhC,QAAM,SAAS,UAAU,UAAU,YAAY,SAAS,GAAG,aAAa,CAAC,CAAC;AAC1E,SAAO,aAAa,MAAM,MAAM;AAClC;AAKO,SAASC,iBAAwB;AACtC,MAAO,gBAAW,WAAW,EAAG,QAAO;AACvC,MAAO,gBAAW,WAAW,EAAG,QAAO;AACvC,SAAO;AACT;AAKO,SAAS,eAAuB;AACrC,SAAO;AACT;AAKA,SAAS,iBAA0C;AACjD,MAAO,gBAAW,WAAW,GAAG;AAC9B,QAAI;AACF,YAAM,UAAa,kBAAa,aAAa,OAAO;AACpD,aAAO,UAAU,OAAO;AAAA,IAC1B,SAAS,KAAK;AACZ,cAAQ,KAAK,4BAA4B,WAAW,KAAK,GAAG;AAAA,IAC9D;AAAA,EACF;AAEA,MAAO,gBAAW,WAAW,GAAG;AAC9B,QAAI;AACF,YAAM,UAAa,kBAAa,aAAa,OAAO;AACpD,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,KAAK;AACZ,cAAQ,KAAK,4BAA4B,WAAW,KAAK,GAAG;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO,CAAC;AACV;AAcA,SAAS,gBAAyC;AAChD,QAAM,SAAkD,CAAC;AAEzD,QAAM,SAAmG;AAAA,IACvG,EAAE,KAAK,oBAAoB,SAAS,QAAQ,KAAK,gBAAgB;AAAA,IACjE,EAAE,KAAK,oBAAoB,SAAS,OAAO,KAAK,QAAQ,WAAW,CAAC,MAAM,SAAS,GAAG,EAAE,EAAE;AAAA,IAC1F,EAAE,KAAK,oBAAoB,SAAS,OAAO,KAAK,OAAO;AAAA,IACvD,EAAE,KAAK,wBAAwB,SAAS,OAAO,KAAK,WAAW;AAAA,IAC/D,EAAE,KAAK,qBAAqB,SAAS,OAAO,KAAK,QAAQ;AAAA,IACzD,EAAE,KAAK,wBAAwB,SAAS,OAAO,KAAK,WAAW;AAAA,IAC/D,EAAE,KAAK,uBAAuB,SAAS,UAAU,KAAK,OAAO;AAAA,EAC/D;AAEA,aAAW,EAAE,KAAK,SAAS,KAAK,UAAU,KAAK,QAAQ;AACrD,UAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,QAAI,UAAU,UAAa,UAAU,IAAI;AACvC,UAAI,CAAC,OAAO,OAAO,EAAG,QAAO,OAAO,IAAI,CAAC;AACzC,aAAO,OAAO,EAAE,GAAG,IAAI,YAAY,UAAU,KAAK,IAAI;AAAA,IACxD;AAAA,EACF;AAEA,SAAO;AACT;AAQO,SAAS,iBAAiB,SAAiB,KAAa,OAAwC;AACrG,MAAI,CAAI,gBAAW,UAAU,GAAG;AAC9B,IAAG,eAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC9C;AAEA,MAAI,WAAoD,CAAC;AACzD,MAAO,gBAAW,WAAW,GAAG;AAC9B,QAAI;AACF,YAAM,UAAa,kBAAa,aAAa,OAAO;AACpD,iBAAW,UAAU,OAAO;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,OAAO,KAAK,OAAO,SAAS,OAAO,MAAM,UAAU;AAC/D,aAAS,OAAO,IAAI,CAAC;AAAA,EACvB;AACA,WAAS,OAAO,EAAE,GAAG,IAAI;AAEzB,QAAM,QAAkB,CAAC;AACzB,aAAW,CAAC,KAAK,OAAO,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,QAAI,OAAO,YAAY,YAAY,YAAY,KAAM;AACrD,UAAM,KAAK,IAAI,GAAG,GAAG;AACrB,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAkC,GAAG;AACvE,UAAI,OAAO,MAAM,UAAU;AACzB,cAAM,KAAK,GAAG,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE;AAAA,MAC1C,WAAW,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW;AAC1D,cAAM,KAAK,GAAG,CAAC,MAAM,CAAC,EAAE;AAAA,MAC1B;AAAA,IACF;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,EAAG,mBAAc,aAAa,MAAM,KAAK,IAAI,GAAG,OAAO;AACzD;AAKA,SAAS,UAAU,MAA+B,UAA4D;AAC5G,QAAM,SAAS,EAAE,GAAG,KAAK;AACzB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,QAAI,UAAU,UAAa,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/F,aAAO,GAAG,IAAI,UAAW,OAAO,GAAG,KAAiC,CAAC,GAAG,KAAgC;AAAA,IAC1G,WAAW,UAAU,QAAW;AAC9B,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;;;AE5JA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AActB,IAAM,qBAAqB;AAE3B,SAAS,sBAA8B;AACrC,SAAY,YAAK,aAAa,GAAG,kBAAkB;AACrD;AAKO,SAAS,mBAAqC;AACnD,QAAM,WAAW,oBAAoB;AACrC,MAAI,CAAI,gBAAW,QAAQ,GAAG;AAC5B,WAAO,EAAE,SAAS,GAAG,UAAU,CAAC,EAAE;AAAA,EACpC;AAEA,MAAI;AACF,UAAM,UAAa,kBAAa,UAAU,OAAO;AACjD,UAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,QAAI,CAAC,KAAK,YAAY,OAAO,KAAK,aAAa,UAAU;AACvD,aAAO,EAAE,SAAS,GAAG,UAAU,CAAC,EAAE;AAAA,IACpC;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,EAAE,SAAS,GAAG,UAAU,CAAC,EAAE;AAAA,EACpC;AACF;AAKO,SAAS,mBAAmB,aAA6B;AAC9D,QAAM,OAAO,iBAAiB;AAC9B,QAAM,UAAU,KAAK,SAAS,WAAW;AACzC,SAAO,SAAS,OAAO;AACzB;AAKO,SAAS,oBAAoB,UAA0B;AAC5D,QAAM,OAAO,iBAAiB;AAC9B,aAAW,WAAW,OAAO,OAAO,KAAK,QAAQ,GAAG;AAClD,QAAI,QAAQ,aAAa,UAAU;AACjC,aAAO,QAAQ,OAAO;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,cAAc,UAAkB,aAA8B;AAC5E,MAAI,aAAa;AACf,UAAM,MAAM,mBAAmB,WAAW;AAC1C,QAAI,IAAK,QAAO;AAAA,EAClB;AACA,SAAO,oBAAoB,QAAQ;AACrC;AAKO,SAAS,iBAAiB,MAAc,SAA4B;AACzE,QAAM,YAAY,aAAa;AAC/B,MAAI,CAAI,gBAAW,SAAS,GAAG;AAC7B,IAAG,eAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,EAC7C;AAEA,QAAM,OAAO,iBAAiB;AAC9B,OAAK,SAAS,IAAI,IAAI;AAEtB,QAAM,WAAW,oBAAoB;AACrC,EAAG,mBAAc,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,EAAE,UAAU,SAAS,MAAM,IAAM,CAAC;AAC9F;;;ACzFA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAGtB,IAAM,eAAe;AAEd,IAAM,kBAAN,MAAsB;AAAA,EACnB;AAAA,EACA;AAAA,EAER,YAAY,YAAyB,SAA2B;AAC9D,SAAK,aAAa,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;AACtE,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,oBAA4B;AAC1B,UAAM,cAAmB,YAAK,KAAK,QAAQ,SAAS,YAAY;AAChE,QAAI;AACF,UAAO,gBAAW,WAAW,GAAG;AAC9B,cAAM,MAAS,kBAAa,aAAa,OAAO,EAAE,KAAK;AACvD,cAAM,MAAM,SAAS,KAAK,EAAE;AAC5B,eAAO,OAAO,SAAS,GAAG,IAAI,MAAM;AAAA,MACtC;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,SAAuB;AAC1C,UAAM,cAAmB,YAAK,KAAK,QAAQ,SAAS,YAAY;AAChE,QAAI;AACF,UAAI,CAAI,gBAAW,KAAK,QAAQ,OAAO,GAAG;AACxC,QAAG,eAAU,KAAK,QAAQ,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,MACxD;AACA,MAAG,mBAAc,aAAa,OAAO,OAAO,GAAG,OAAO;AAAA,IACxD,SAAS,KAAK;AACZ,WAAK,QAAQ,OAAO,MAAM,aAAa,iCAAiC,GAAG,EAAE;AAAA,IAC/E;AAAA,EACF;AAAA,EAEA,MAAM,MAA6D;AACjE,UAAM,iBAAiB,KAAK,kBAAkB;AAC9C,UAAM,UAAU,KAAK,WAAW,OAAO,CAAC,MAAM,EAAE,UAAU,cAAc;AAExE,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO,EAAE,UAAU,GAAG,eAAe;AAAA,IACvC;AAEA,SAAK,QAAQ,OAAO,KAAK,aAAa,GAAG,QAAQ,MAAM,2CAA2C,cAAc,EAAE;AAElH,QAAI,cAAc;AAClB,QAAI,WAAW;AAEf,eAAW,aAAa,SAAS;AAC/B,WAAK,QAAQ,OAAO,KAAK,aAAa,aAAa,WAAW,UAAK,UAAU,OAAO,IAAI,UAAU,IAAI,EAAE;AACxG,UAAI;AACF,cAAM,UAAU,GAAG,KAAK,OAAO;AAC/B,sBAAc,UAAU;AACxB,aAAK,aAAa,WAAW;AAC7B;AACA,aAAK,QAAQ,OAAO,KAAK,aAAa,cAAc,UAAU,IAAI,EAAE;AAAA,MACtE,SAAS,KAAK;AACZ,aAAK,QAAQ,OAAO,MAAM,aAAa,WAAW,UAAU,IAAI,IAAI,GAAG;AACvE;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,UAAU,gBAAgB,YAAY;AAAA,EACjD;AACF;;;ACtEA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,cAAAC,mBAAkB;AAW3B,SAAS,mBAAmB,MAAyC;AACnE,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,MAAM;AACZ,SAAO,cAAc,OAAO,YAAY,OAAO,EAAE,aAAa;AAChE;AAEO,IAAM,0BAAqC;AAAA,EAChD,SAAS;AAAA,EACT,MAAM;AAAA,EACN,aAAa;AAAA,EAEb,MAAM,GAAG,KAAuB;AAC9B,UAAM,aAAkB,YAAK,IAAI,WAAW,iBAAiB;AAC7D,QAAI,CAAI,gBAAW,UAAU,EAAG;AAEhC,UAAM,MAAS,kBAAa,YAAY,OAAO;AAC/C,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,QAAQ;AACN;AAAA,IACF;AAEA,QAAI,CAAC,mBAAmB,IAAI,EAAG;AAE/B,QAAI,OAAO,KAAK,aAAa,sDAAsD;AAEnF,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,YAAY;AAAA,MAChB,IAAIA,YAAW;AAAA,MACf,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,QAAQ,KAAK,UAAU;AAAA,MACvB,OAAO,KAAK,SAAS;AAAA,MACrB,WAAW,KAAK;AAAA,MAChB,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAEA,UAAM,WAAW;AAAA,MACf,eAAe;AAAA,MACf,SAAS,CAAC,SAAS;AAAA,MACnB,kBAAkB,EAAE,UAAU,UAAU,IAAI,SAAS,UAAU,MAAM;AAAA,MACrE,WAAW;AAAA,IACb;AAEA,IAAG,mBAAc,YAAY,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AACvE,QAAI,OAAO,KAAK,aAAa,+CAA+C;AAAA,EAC9E;AACF;;;AC1DA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAGf,IAAM,eAA0B;AAAA,EACrC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,aAAa;AAAA,EAEb,MAAM,GAAG,KAAuB;AAC9B,UAAM,aAAkB,YAAK,IAAI,WAAW,iBAAiB;AAC7D,QAAI,CAAI,gBAAW,UAAU,EAAG;AAEhC,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAS,kBAAa,YAAY,OAAO,CAAC;AAAA,IACxD,QAAQ;AACN;AAAA,IACF;AAEA,UAAM,gBAAiB,KAAK,iBAA4B;AACxD,QAAI,iBAAiB,EAAG;AAExB,QAAI,OAAO,KAAK,aAAa,mCAAmC;AAEhE,UAAM,UAAW,KAAK,WAAyC,CAAC;AAChE,UAAM,iBAAiB,QAAQ,IAAI,CAAC,MAAM;AACxC,YAAM,EAAE,iBAAiB,KAAK,kBAAkB,KAAK,GAAG,KAAK,IAAI;AACjE,aAAO;AAAA,IACT,CAAC;AAED,SAAK,UAAU;AACf,SAAK,gBAAgB;AAErB,IAAG,mBAAc,YAAY,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AAAA,EACrE;AACF;;;ACvCA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAGf,IAAM,eAA0B;AAAA,EACrC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,aAAa;AAAA,EAEb,MAAM,GAAG,KAAuB;AAC9B,UAAM,aAAkB,YAAK,IAAI,WAAW,iBAAiB;AAC7D,QAAI,CAAI,gBAAW,UAAU,EAAG;AAEhC,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAS,kBAAa,YAAY,OAAO,CAAC;AAAA,IACxD,QAAQ;AACN;AAAA,IACF;AAEA,UAAM,gBAAiB,KAAK,iBAA4B;AACxD,QAAI,iBAAiB,EAAG;AAExB,QAAI,OAAO,KAAK,aAAa,+CAA+C;AAE5E,UAAM,UAAW,KAAK,WAAqD,CAAC;AAC5E,UAAMC,kBAAiB,KAAK;AAE5B,UAAM,iBACJA,mBAAkB,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAOA,eAAc,IACzD,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAOA,eAAc,IAC1C,QAAQ,CAAC,KAAK;AAErB,SAAK,mBAAmB,iBAAiB,EAAE,UAAU,eAAe,IAAI,SAAS,eAAe,SAAS,GAAG,IAAI;AAChH,SAAK,YAAY,KAAK,aAAa;AACnC,SAAK,gBAAgB;AACrB,WAAO,KAAK;AAEZ,IAAG,mBAAc,YAAY,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AAAA,EACrE;AACF;;;ACvCA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;;;ACHtB,YAAYC,UAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,kBAAkB,cAAAC,mBAAkB;AAC7C,SAAS,gBAAgB;AAEzB,IAAM,mBAAmB;AACzB,IAAM,YAAY;AAClB,IAAM,OAAO;AACb,IAAM,kBAAkB;AAEjB,SAAS,YAAY,OAAwB;AAClD,SAAO,OAAO,WAAW,gBAAgB,KAAK;AAChD;AAEA,SAAS,gBAAgB,SAAgC;AACvD,MAAI;AACF,QAAO,gBAAW,OAAO,GAAG;AAC1B,YAAM,MAAS,kBAAa,SAAS,OAAO,EAAE,KAAK;AACnD,UAAI,IAAI,UAAU,GAAI,QAAO;AAAA,IAC/B;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEA,SAAS,uBAAiC;AACxC,QAAM,OAAU,YAAQ;AACxB,QAAM,QAAQ,CAAM,YAAK,MAAM,YAAY,eAAe,CAAC;AAE3D,MAAI,QAAQ,aAAa,UAAU;AACjC,UAAM,KAAU,YAAK,MAAM,WAAW,uBAAuB,WAAW,QAAQ,eAAe,CAAC;AAAA,EAClG,WAAW,QAAQ,aAAa,SAAS;AACvC,UAAM,UAAU,QAAQ,IAAI,WAAgB,YAAK,MAAM,WAAW,SAAS;AAC3E,UAAM,KAAU,YAAK,SAAS,WAAW,QAAQ,eAAe,CAAC;AAAA,EACnE,OAAO;AACL,UAAM,KAAU,YAAK,MAAM,WAAW,WAAW,QAAQ,eAAe,CAAC;AAAA,EAC3E;AAEA,SAAO;AACT;AAEA,SAAS,mBAA6B;AACpC,QAAM,OAAiB,CAAC;AACxB,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,KAAK,qBAAqB,GAAG;AACtC,UAAM,IAAI,gBAAgB,CAAC;AAC3B,QAAI,KAAK,CAAC,KAAK,IAAI,CAAC,GAAG;AACrB,WAAK,IAAI,CAAC;AACV,WAAK,KAAK,CAAC;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,mBAA6B;AACpC,QAAM,OAAiB,CAAC;AACxB,MAAI;AACF,QAAI,MAAqB;AACzB,QAAI,QAAQ,aAAa,SAAS;AAChC,YAAM;AAAA,IACR,WAAW,QAAQ,aAAa,UAAU;AACxC,YAAM;AAAA,IACR,WAAW,QAAQ,aAAa,SAAS;AACvC,YAAM;AAAA,IACR;AAEA,QAAI,KAAK;AACP,YAAM,MAAM,SAAS,GAAG,EAAE,SAAS;AACnC,UAAI;AACJ,UAAI,QAAQ,aAAa,UAAU;AACjC,oBACE,IACG,MAAM,gBAAgB,EAAE,CAAC,GACxB,MAAM,IAAI,EAAE,CAAC,GACb,QAAQ,WAAW,EAAE,GACrB,YAAY,KAAK;AAAA,MACzB,WAAW,QAAQ,aAAa,SAAS;AACvC,oBACE,IACG,MAAM,QAAQ,EAAE,CAAC,GAChB,QAAQ,aAAa,EAAE,GACvB,YAAY,KAAK;AAAA,MACzB,OAAO;AACL,oBAAY,IAAI,QAAQ,aAAa,EAAE,EAAE,YAAY;AAAA,MACvD;AAEA,UAAI,WAAW;AACb,cAAM,SAASA,YAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK;AAClE,aAAK;AAAA,UACHA,YAAW,QAAQ,EAChB,OAAO,SAAS,IAAI,EACpB,OAAO;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,OAAK;AAAA,IACHA,YAAW,QAAQ,EAChB,OAAO,yBAAyB,IAAI,EACpC,OAAO;AAAA,EACZ;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,WAAmB,KAA4B;AACxE,MAAI;AACF,UAAM,QAAQ,UAAU,MAAM,iBAAiB,MAAM,EAAE,MAAM,GAAG;AAChE,QAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,UAAM,CAAC,UAAU,eAAe,UAAU,IAAI;AAC9C,UAAM,KAAK,OAAO,KAAK,UAAU,QAAQ;AACzC,UAAM,UAAU,OAAO,KAAK,eAAe,QAAQ;AAEnD,UAAM,WAAW,iBAAiB,WAAW,KAAK,EAAE;AACpD,aAAS,WAAW,OAAO;AAE3B,QAAI,YAAY,SAAS,OAAO,YAAY,UAAU,MAAM;AAC5D,iBAAa,SAAS,MAAM,MAAM;AAClC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,cAAc,WAA2B;AACvD,MAAI,CAAC,aAAa,CAAC,YAAY,SAAS,EAAG,QAAO,aAAa;AAE/D,aAAW,MAAM,iBAAiB,GAAG;AACnC,UAAM,UAAUA,YAAW,QAAQ,EAChC,OAAO,KAAK,IAAI,EAChB,OAAO;AACV,UAAM,SAAS,kBAAkB,WAAW,OAAO;AACnD,QAAI,WAAW,KAAM,QAAO;AAAA,EAC9B;AAEA,aAAW,aAAa,iBAAiB,GAAG;AAC1C,UAAM,SAAS,kBAAkB,WAAW,SAAS;AACrD,QAAI,WAAW,KAAM,QAAO;AAAA,EAC9B;AAEA,SAAO;AACT;;;ADxIA,SAAS,kBAAkB,UAAkB,QAAyC;AACpF,MAAI,aAAa,qBAAqB;AACpC,WAAO,SAAS,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAAA,EACnD;AAEA,QAAM,UAAU,OAAO;AACvB,MAAI,SAAS;AACX,QAAI;AACF,aAAO,IAAI,IAAI,OAAO,EAAE,SAAS,YAAY;AAAA,IAC/C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,OAAO,OAAO;AACpB,MAAI,KAAM,QAAO,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAEvD,SAAO;AACT;AAEO,IAAM,kCAA6C;AAAA,EACxD,SAAS;AAAA,EACT,MAAM;AAAA,EACN,aAAa;AAAA,EAEb,MAAM,GAAG,KAAuB;AAC9B,UAAM,aAAkB,YAAK,IAAI,WAAW,iBAAiB;AAC7D,QAAI,CAAI,gBAAW,UAAU,EAAG;AAEhC,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAS,kBAAa,YAAY,OAAO,CAAC;AAAA,IACxD,QAAQ;AACN;AAAA,IACF;AAEA,UAAM,UAAW,KAAK,WAA8C,CAAC;AACrE,QAAI,WAAW;AACf,UAAM,mBAAmB,oBAAI,IAAY;AAEzC,eAAW,UAAU,SAAS;AAC5B,YAAM,SAAS,OAAO;AACtB,UAAI,CAAC,OAAQ;AAEb,YAAM,WAAY,OAAO,YAAuB;AAChD,UAAI,WAA0B;AAE9B,UAAI,YAAY,MAAM,GAAG;AACvB,mBAAW,cAAc,MAAM;AAC/B,YAAI,CAAC,UAAU;AACb,cAAI,OAAO,KAAK,aAAa,kCAAkC,OAAO,IAAI,aAAa;AACvF;AAAA,QACF;AAAA,MACF,WAAW,OAAO,SAAS,GAAG;AAC5B,mBAAW;AAAA,MACb;AAEA,UAAI,UAAU;AACZ,cAAM,WAAW,kBAAkB,UAAU,MAAM;AAEnD,YAAI,cAAc;AAClB,YAAI,iBAAiB,IAAI,WAAW,GAAG;AACrC,cAAI,IAAI;AACR,iBAAO,iBAAiB,IAAI,GAAG,WAAW,IAAI,CAAC,EAAE,EAAG;AACpD,wBAAc,GAAG,WAAW,IAAI,CAAC;AAAA,QACnC;AACA,yBAAiB,IAAI,WAAW;AAEhC,yBAAiB,aAAa,EAAE,MAAM,WAAW,UAAU,KAAK,SAAS,CAAC;AAC1E,eAAO,SAAS;AAChB,mBAAW;AACX,YAAI,OAAO,KAAK,aAAa,oCAA+B,WAAW,GAAG;AAAA,MAC5E;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,MAAG,mBAAc,YAAY,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AACnE,UAAI,OAAO,KAAK,aAAa,uCAAuC;AAAA,IACtE;AAAA,EACF;AACF;;;AErFO,IAAM,iBAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACPA,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;;;AXtOA,IAAM,aAAkB,YAAQ,YAAQ,GAAG,UAAU;AAMrD,IAAI,8BAA8B;AAE3B,SAAS,gCAAyC;AACvD,SAAO;AACT;AAEO,IAAM,mBAAN,MAA+C;AAAA,EAC5C;AAAA,EACA;AAAA,EAER,YAAY,aAAsB;AAChC,SAAK,YAAY;AACjB,SAAK,cAAc,eAAe,mBAAmB;AAAA,EACvD;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,iBAAyB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,iBAAyB;AACvB,WAAY,YAAK,KAAK,aAAa,WAAW;AAAA,EAChD;AAAA,EAEA,eAAuB;AACrB,WAAY,YAAK,KAAK,WAAW,IAAI;AAAA,EACvC;AAAA,EAEA,iBAAyB;AACvB,WAAY,YAAK,KAAK,WAAW,UAAU;AAAA,EAC7C;AAAA,EAEA,cAAsB;AACpB,WAAY,YAAK,KAAK,WAAW,OAAO;AAAA,EAC1C;AAAA,EAEA,aAAqB;AACnB,WAAY,YAAK,KAAK,WAAW,MAAM;AAAA,EACzC;AAAA,EAEA,aAAqB;AACnB,WAAY,YAAK,KAAK,WAAW,MAAM;AAAA,EACzC;AAAA,EAEA,kBAA0B;AACxB,WAAY,YAAQ,YAAQ,GAAG,WAAW;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAsB;AACpB,UAAM,OAAO;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,eAAe;AAAA,MACpB,KAAK,aAAa;AAAA,MAClB,KAAK,eAAe;AAAA,MACpB,KAAK,YAAY;AAAA,MACjB,KAAK,WAAW;AAAA,MAChB,KAAK,WAAW;AAAA,IAClB;AACA,eAAW,OAAO,MAAM;AACtB,UAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,QAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,qBAA6B;AACpC,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,QAAQ;AACV,WAAO,WAAW,MAAM;AAAA,EAC1B;AAEA,QAAM,SAAS,WAAW;AAC1B,MAAI,OAAO,KAAK,eAAe;AAC7B,WAAO,WAAW,OAAO,KAAK,aAAa;AAAA,EAC7C;AAGA,QAAM,SAAS,4BAA4B,UAAU;AACrD,MAAI,OAAO,YAAY,OAAO,aAAa;AACzC,WAAO,OAAO;AAAA,EAChB;AAEA,QAAM,aAAa,sBAAsB;AAEzC,MAAI,OAAO,kBAAkB;AAG3B,kCAA8B;AAC9B,WAAO;AAAA,EACT;AAEA,mBAAiB,QAAQ,iBAAiB,UAAU;AACpD,SAAO;AACT;AAEA,SAAS,wBAAgC;AACvC,SAAY,YAAQ,YAAQ,GAAG,YAAY,MAAM;AACnD;AAEA,SAAS,WAAW,UAA0B;AAC5C,MAAI,SAAS,WAAW,IAAI,KAAK,aAAa,KAAK;AACjD,WAAY,YAAQ,YAAQ,GAAG,SAAS,MAAM,CAAC,CAAC;AAAA,EAClD;AACA,SAAO;AACT;;;AYrIA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;;;ACaf,SAAS,oBAAoB,MAAuC;AACzE,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,EACF;AACF;;;ADnIO,IAAM,kBAAN,MAAsB;AAAA,EAK3B,YACU,cACR,SACA;AAFQ;AAGR,SAAK,gBAAgB,SAAS;AAC9B,SAAK,gBAAgB,SAAS;AAAA,EAChC;AAAA,EALU;AAAA,EALF,QAAQ,oBAAI,IAA6B;AAAA,EACzC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAaR,KAAK,WAAmB,SAA0D;AAChF,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,UAAM,UAAU,yBAAyB,QAAQ;AAAA,MAC/C,UAAU,SAAS,YAAY;AAAA,MAC/B,eAAe,KAAK;AAAA,IACtB,CAAC;AACD,SAAK,MAAM,IAAI,WAAW,OAAO;AACjC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,WAA2C;AACtD,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,oBAAc,SAAS,UAAU;AAAA,IACnC;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,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;AACF;;;AEjHO,SAAS,uBAAuB,WAAqB,WAAwB;AAClF,SAAO;AAAA,IACL,IAAI,MAA8B;AAChC,aAAO,SAAS,QAAQ,EAAE,IAAI,IAAI;AAAA,IACpC;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,EACrB;AACF;;;ACkCO,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,gBAAgB,UAAU,CAAC;AAAA,CAAI;AAAA,IAC7E;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,kBAAkB,OAAO,eAAe,WAAW,GAAG;AAChE,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,eAAe;AAAA,IACnC,YAAY;AAAA,IACZ,SAAS,uBAAuB,OAAO,eAAe,MAAM;AAAA,EAC9D,CAAC;AAED,QAAM,cAAc;AAAA;AAAA;AAAA,mBAGH,OAAO,eAAe,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA;AAAA;AAG/D,QAAM,WAAW,GAAG,QAAQ,WAAW,EAAE,IAAI,GAAG,OAAO,cAAc;AAMrE,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;;;ACxVA,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;AAS3B,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,EAC1B;AAEA,WAAS,WAAW,oBAAoB,MAAM,QAAQ;AACtD,4BAA0B,IAAI,WAAW,QAAQ;AAEjD,SAAO;AACT;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;;;AC/JA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAuBtB,IAAM,WAAwB;AAAA,EAC5B,kBAAkB,CAAC;AAAA,EACnB,oBAAoB;AAAA,IAClB,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,IACpB,mBAAmB,CAAC;AAAA,IACpB,SAAS;AAAA,IACT,aAAa;AAAA,IACb,kBAAkB,CAAC;AAAA,IACnB,gBAAgB;AAAA,EAClB;AAAA,EACA,kBAAkB;AAAA,IAChB,uBAAuB;AAAA,IACvB,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,oBAAoB;AAAA,MAClB,SAAS;AAAA,MACT,uBAAuB;AAAA,MACvB,mBAAmB;AAAA,MACnB,sBAAsB;AAAA,IACxB;AAAA,EACF;AAAA,EACA,wBAAwB,CAAC;AAAA,EACzB,0BAA0B,CAAC;AAAA,EAC3B,YAAY;AAAA,IACV,SAAS,CAAC;AAAA,IACV,iBAAiB;AAAA,IACjB,wBAAwB,CAAC;AAAA,EAC3B;AAAA,EACA,eAAe,CAAC;AAClB;AAEO,IAAM,qBAAN,MAAyB;AAAA,EACtB;AAAA,EACA,QAA4B;AAAA,EAEpC,YAAY,WAAmB;AAC7B,SAAK,WAAgB,YAAK,WAAW,kBAAkB;AAAA,EACzD;AAAA,EAEA,OAAoB;AAClB,QAAI,KAAK,MAAO,QAAO,KAAK;AAE5B,QAAI;AACF,UAAO,gBAAW,KAAK,QAAQ,GAAG;AAChC,cAAM,MAAS,kBAAa,KAAK,UAAU,OAAO;AAClD,cAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,aAAK,QAAQ,KAAK,cAAc,MAAM;AACtC,eAAO,KAAK;AAAA,MACd;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,kDAAkD,GAAG;AAAA,IACpE;AAEA,SAAK,QAAQ,EAAE,GAAG,SAAS;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,SAAqE;AACxE,QAAI;AACF,YAAM,UAAU,KAAK,KAAK;AAC1B,YAAM,SAAS,KAAK;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AACA,WAAK,QAAQ;AAEb,YAAM,MAAW,eAAQ,KAAK,QAAQ;AACtC,UAAI,CAAI,gBAAW,GAAG,GAAG;AACvB,QAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,MACvC;AACA,MAAG,mBAAc,KAAK,UAAU,KAAK,UAAU,KAAK,OAAO,MAAM,CAAC,GAAG,OAAO;AAC5E,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,cAAQ,MAAM,iCAAiC,GAAG;AAClD,aAAO,EAAE,SAAS,OAAO,OAAO,IAAI;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,cAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,kBAAwB;AACtB,SAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,cAAc,SAA4C;AAChE,WAAO;AAAA,MACL,kBAAkB,QAAQ,oBAAoB,SAAS;AAAA,MACvD,oBAAoB,QAAQ,qBACxB,EAAE,GAAG,SAAS,oBAAoB,GAAG,QAAQ,mBAAmB,IAChE,EAAE,GAAG,SAAS,mBAAmB;AAAA,MACrC,kBAAkB,QAAQ,mBACtB;AAAA,QACE,GAAG,SAAS;AAAA,QACZ,GAAG,QAAQ;AAAA,QACX,oBAAoB;AAAA,UAClB,GAAG,SAAS,iBAAiB;AAAA,UAC7B,GAAI,QAAQ,iBAAiB,sBAAsB,CAAC;AAAA,QACtD;AAAA,MACF,IACA,EAAE,GAAG,SAAS,iBAAiB;AAAA,MACnC,wBAAwB,QAAQ,0BAA0B,SAAS;AAAA,MACnE,0BAA0B,QAAQ,4BAA4B,SAAS;AAAA,MACvE,YAAY,QAAQ,aAAa,EAAE,GAAG,SAAS,YAAY,GAAG,QAAQ,WAAW,IAAI,EAAE,GAAG,SAAS,WAAW;AAAA,MAC9G,eAAe,QAAQ,iBAAiB,SAAS;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,MAA+B,UAA4D;AAC3G,UAAM,SAAS,EAAE,GAAG,KAAK;AACzB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,UAAI,UAAU,OAAW;AACzB,UACE,UAAU,QACV,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,KAAK,KACpB,OAAO,KAAK,GAAG,MAAM,YACrB,KAAK,GAAG,MAAM,QACd,CAAC,MAAM,QAAQ,KAAK,GAAG,CAAC,GACxB;AACA,eAAO,GAAG,IAAI,KAAK,UAAU,KAAK,GAAG,GAA8B,KAAgC;AAAA,MACrG,OAAO;AACL,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;;;AC7FO,SAAS,0BACd,aACqB;AACrB,QAAM,cAA+E,CAAC;AAEtF,aAAW,EAAE,QAAQ,SAAS,KAAK,aAAa;AAC9C,UAAM,OAAO,OAAO,QAAQ;AAC5B,UAAM,WAAW,MAAM,YAAY;AACnC,WAAO,eAAe,KAAO,CAAC,aAAa;AACzC,iBAAW,OAAO,UAAU;AAC1B,oBAAY,KAAK,EAAE,KAAK,QAAQ,UAAU,SAAS,CAAC;AAAA,MACtD;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,0BAA0B,WAAW;AAC9C;AAcO,SAAS,kBACd,aACA,YAC0B;AAC1B,QAAM,QAAQ,YAAY,IAAI,CAAC,EAAE,QAAQ,SAAS,OAAO;AAAA,IACvD,MAAM,OAAO,QAAQ;AAAA,IACrB,SAAS,OAAO,WAAW;AAAA,IAC3B;AAAA,IACA;AAAA,EACF,EAAE;AAEF,QAAM,eAAe;AAAA,IACnB,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,OAAO;AAAA,MAChC,UAAU,MAAM,YAAY;AAAA,MAC5B,SAAS,QAAQ,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE;AAAA,IAC5D,EAAE;AAAA,EACJ;AAEA,QAAM,YAAYC;AAAA,IAChB,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,OAAO;AAAA,MAChC,UAAU,MAAM,YAAY;AAAA,MAC5B;AAAA,IACF,EAAE;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,oBAAI,IAAY;AACjC,QAAM,iBAAkC,CAAC;AAEzC,aAAW,EAAE,QAAQ,KAAK,KAAK,OAAO;AACpC,UAAMC,YAAW,MAAM,YAAY;AACnC,WAAO,eAAe,KAAO,CAAC,aAAa;AACzC,iBAAW,OAAO,UAAU;AAC1B,cAAM,MAAM,oBAAoB,IAAI,kBAAkBA,WAAU,YAAY;AAC5E,cAAM,MAAM,mBAAmB,IAAI,WAAW,KAAK,IAAI,WAAW,IAAI;AACtE,YAAI,SAAS,IAAI,GAAG,EAAG;AACvB,iBAAS,IAAI,GAAG;AAChB,uBAAe,KAAK;AAAA,UAClB,QAAQ;AAAA,UACR,aAAa,IAAI;AAAA,UACjB,eAAe,IAAI;AAAA,UACnB,WAAW,IAAI;AAAA,UACf,MAAM,IAAI;AAAA,UACV,SAAS,IAAI;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAEA,iBAAe,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAEvD,QAAM,UAA6B,YAAY,IAAI,CAAC,EAAE,QAAQ,SAAS,OAAO;AAAA,IAC5E;AAAA,IACA,UAAU,OAAO,QAAQ,GAAG,YAAY;AAAA,IACxC,cAAc,OAAO,gBAAgB;AAAA,EACvC,EAAE;AAEF,QAAM,kBAAkB,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,EAAE,KAAK,MAAM,MAAM,YAAY,SAAS,CAAC,CAAC;AACzF,QAAM,WAAW,gBAAgB,WAAW,IAAI,gBAAgB,CAAC,IAAI;AAErE,QAAM,WAAW,IAAI,IAAI,MAAM,IAAI,CAAC,EAAE,KAAK,MAAM,MAAM,OAAO,EAAE,OAAO,OAAO,CAAC;AAC/E,QAAM,UAAU,SAAS,SAAS,IAAI,MAAM,KAAK,CAAC,EAAE,KAAK,MAAM,MAAM,OAAO,GAAG,MAAM,UAAU;AAC/F,QAAM,cAAc,UAChB,MAAM,OAAO,CAAC,EAAE,KAAK,MAAM,MAAM,YAAY,OAAO,EAAE,IAAI,GAAG,MAAM,cACnE;AAEJ,QAAM,cAA6B;AAAA,IACjC,SAAS;AAAA,MACP,SAAS;AAAA,MACT,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,MACxC,WAAW;AAAA,MACX,aAAa,eAAe,YAAY,MAAM;AAAA,IAChD;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA,MAAM,MAAM,CAAC,GAAG,MAAM,QAAQ;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,SAAS,MAAM,KAAK,UAAU,OAAO,CAAC;AAAA,IACtC,UAAU;AAAA,EACZ;AAEA,SAAO,EAAE,SAAS,MAAM,YAAY;AACtC;AAOO,UAAU,wBAAwB,MAAwC;AAC/E,QAAM,KAAK,UAAU;AAAA,IACnB,OAAO;AAAA,IACP,SAAS,KAAK;AAAA,IACd,MAAM,KAAK;AAAA,EACb,CAAC;AAED,aAAW,UAAU,KAAK,SAAS;AACjC,UAAM,KAAK,UAAU,EAAE,OAAO,UAAU,GAAG,OAAO,CAAC;AAAA,EACrD;AAEA,aAAW,OAAO,KAAK,UAAU;AAC/B,UAAM,KAAK,UAAU,EAAE,OAAO,WAAW,GAAG,IAAI,CAAC;AAAA,EACnD;AACF;;;AC3MA,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,QACA,SAC2B;AAC3B,QAAM,OAAO,eAAe,EAAE;AAC9B,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,WAAW,SAAS,kBAAkB,QAAQ,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,MAAI,SAAS,eAAe;AAC1B,UAAM,QAAQ,cAAc,GAAG;AAAA,EACjC;AACA,SAAO;AACT;AAQO,SAAS,qBACd,SACA,SACsB;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,GAAG,OAAO;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,WACA,SAC2B;AAC3B,QAAM,KAAK,QAAQ,aAAa,SAAS;AACzC,MAAI,CAAC,GAAI,QAAO;AAChB,SAAO,aAAa,IAAI,WAAW,QAAQ,UAAU,SAAS,GAAG,OAAO;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;AAiCA,SAAS,iBAAiB,IAAqB,WAAsB,MAAuC;AAC1G,QAAM,UAAU,KAAK,aAAa,SAAS;AAC3C,SAAO;AAAA,IACL,aAAa,eAAe,QAAQ,KAAK;AACvC,YAAM,OAAO,mBAAmB,IAAI,eAAe,KAAK;AACxD,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO,KAAK,SAAS,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,SAAS,EAAE,QAAQ,EAAE;AAAA,IACpF;AAAA,IACA,YAAY,eAAe,SAAS;AAClC,6BAAuB,IAAI,eAAe,OAAO;AAAA,IACnD;AAAA,IACA,WAAW,eAAe;AACxB,aAAO,sBAAsB,IAAI,aAAa;AAAA,IAChD;AAAA,IACA,MAAM,YAAY,cAAc,YAAY,SAAS;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,WAAW,SAAS,WAAW,aAAa,SAAS,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,eACA,aACA,SACA;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,eAAe,OAAO;AAC5D;AAEA,eAAsB,qBACpB,SACA,WACA,aACA,SACA;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,IAAI,OAAO;AAChE,QAAI,OAAO,QAAS;AAAA,QACf;AAAA,EACP;AAEA,SAAO,EAAE,SAAS,QAAQ,OAAO,aAAa,OAAO;AACvD;AAEO,SAAS,iBACd,SACA,WACA,gBAC2D;AAC3D,QAAM,KAAK,QAAQ,eAAe,SAAS;AAC3C,QAAM,OAAgD;AAAA,IACpD,aAAa,eAAe,QAAQ,KAAK;AACvC,YAAM,OAAO,mBAAmB,IAAI,eAAe,KAAK;AACxD,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,cAAc;AACtE,QAAM,SAAoE,CAAC;AAC3E,aAAW,CAAC,IAAI,IAAI,KAAK,WAAW;AAClC,WAAO,EAAE,IAAI;AAAA,EACf;AACA,SAAO;AACT;;;ACjIA;AAAA;AAAA;AAAA;AASO,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;;;ACxBA,YAAYC,UAAQ;AAKb,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,gBAAU,MAAM,SAAS;AACzB,YAAM,SAAS,UAAU,UAAU,SAAS;AAC5C,UAAI,CAAI,gBAAW,MAAM,EAAG,QAAO;AACnC,MAAG,gBAAW,MAAM;AACpB,aAAO;AAAA,IACT;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;","names":["t","path","result","fns","chain","path","LogLevel","members","BATCH_SIZE","t","mergeMembers","fs","path","fs","path","feature","parser","fs","fs","path","streamJson","pickModule","streamValuesModule","streamChain","basename","fs","path","extractNameFromFilePath","basename","feature","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","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","chain","streamChain","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","chain","streamChain","feature","extractJsonObject","extractNameFromFilePath","basename","parser_","module_","feature","parser_","fs","path","readline","feature","convertMessageType","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","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","parser_","module_","import_stream_chain","import_stream_json","import_Pick","import_StreamValues","fs","chain","streamChain","parser","streamJson","pick","pickModule","streamValues","streamValuesModule","feature","parser_","module_","import_stream_chain","import_stream_json","import_StreamValues","fs","chain","streamChain","parser","streamJson","streamValues","streamValuesModule","feature","parser_","module_","fs","path","readline","extractNameFromFilePath","basename","feature","detectMessageType","parser_","module_","fs","path","readline","extractNameFromFilePath","basename","feature","detectMessageType","parser_","module_","fs","path","feature","extractNameFromFilePath","basename","isSystemMessage","detectMessageType","parser_","module_","fs","path","feature","extractNameFromFilePath","basename","detectMessageType","content","parser_","module_","feature","parser","fs","fs","path","require","t","fs","path","feature","trackError","errorCount","BATCH_SIZE","fs","path","path","fs","path","matter","t","Database","fs","path","t","fs","path","defaultLogger","t","toSummary","MAX_SKILL_MENU_ITEMS","fs","builtin","t","t","defaultLogger","DEFAULT_CONTEXT_WINDOW","defaultLogger","t","path","segment","segment","DEFAULT_CONTEXT_WINDOW","completeSimple","completeSimple","completeSimple","streamSimple","fs","path","Database","_logger","formatTimeRange","filterValidMessages","Database","_logger","_logger","path","Database","cosineSimilarity","_logger","Database","cosineSimilarity","fs","_logger","cosineSimilarity","_logger","completeSimple","_logger","_getLLMConfig","completeSimple","cosineSimilarity","fs","os","path","fs","os","path","getConfigPath","fs","path","fs","path","fs","path","randomUUID","fs","path","fs","path","activeConfigId","fs","path","fs","os","path","createHash","fs","os","path","ensureDir","fs","path","hasTable","BATCH_SIZE","fs","path","fs","path","mergeMembers","platform","fs","path","renameSession","updateSessionOwnerId","renameSession","updateSessionOwnerId","deleteMember","getMemberNameHistory","getMembers","getMembersPaginated","mergeMembers","updateMemberAliases","getMembers","getMembersPaginated","updateMemberAliases","mergeMembers","deleteMember","getMemberNameHistory","completeSimple","fs"]}