chatlab-cli 0.27.2 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chat-command-QVCZVDSB.mjs → chat-command-ISJ2DC7R.mjs} +5 -5
- package/dist/{chunk-OD3YH23N.mjs → chunk-3QBVO4J7.mjs} +2 -2
- package/dist/chunk-5FGB42EQ.mjs +7 -0
- package/dist/{chunk-JOXF6ROV.mjs → chunk-AB6TAXTQ.mjs} +206 -50
- package/dist/chunk-AB6TAXTQ.mjs.map +1 -0
- package/dist/{chunk-PXWAYFII.mjs → chunk-ERAAWLEF.mjs} +3 -3
- package/dist/{chunk-K7UUN3HT.mjs → chunk-GHTGFM6O.mjs} +26 -11
- package/dist/chunk-GHTGFM6O.mjs.map +1 -0
- package/dist/{chunk-H22VDCPB.mjs → chunk-NJB2NNB2.mjs} +2 -2
- package/dist/{chunk-MDDWX2MG.mjs → chunk-T7IETWHN.mjs} +6 -22
- package/dist/chunk-T7IETWHN.mjs.map +1 -0
- package/dist/chunk-YBS3YQB6.mjs +1336 -0
- package/dist/chunk-YBS3YQB6.mjs.map +1 -0
- package/dist/{chunk-B2VLK3FB.mjs → chunk-ZVDOFWL7.mjs} +13377 -4714
- package/dist/chunk-ZVDOFWL7.mjs.map +1 -0
- package/dist/cli.mjs +4 -4
- package/dist/{http-LSCVGWCI.mjs → http-MBILV37U.mjs} +9 -9
- package/dist/{import-E6HBJMMT.mjs → import-EYV3GXHE.mjs} +5 -12
- package/dist/index.mjs +9 -9
- package/dist/{mcp-I7JUC5JV.mjs → mcp-H55AEXU5.mjs} +5 -5
- package/dist/semantic-index-worker.mjs +1 -1
- package/dist/{src-HZILDI7C.mjs → src-36ZIR65K.mjs} +39 -21
- package/dist/{stream-import-IMRULRS2.mjs → stream-import-YZMZ56B5.mjs} +4 -4
- package/dist/stream-import-YZMZ56B5.mjs.map +1 -0
- package/dist/undici-ZSXBT7EA.mjs +18389 -0
- package/dist/undici-ZSXBT7EA.mjs.map +1 -0
- package/dist/{worker-runtime-WWQ7W74Q.mjs → worker-runtime-YDYS2C6T.mjs} +9 -5
- package/dist/worker-runtime-YDYS2C6T.mjs.map +1 -0
- package/dist-web/assets/{DebugToolsPanel.vue_vue_type_script_setup_true_lang-Du1FDD8L.js → DebugToolsPanel.vue_vue_type_script_setup_true_lang-Do3x4aL3.js} +5 -5
- package/dist-web/assets/{MessageExportModal-4ehLHG4x.js → MessageExportModal-rijqUusq.js} +1 -1
- package/dist-web/assets/electron-D9vE5t3H.js +1 -0
- package/dist-web/assets/{electron-B3AteJC8.js → electron-DWDCqx_d.js} +1 -1
- package/dist-web/assets/{fetch-BUcl-Frr.js → fetch-29ZBtGV0.js} +1 -1
- package/dist-web/assets/{fetch-BQxv_UGQ.js → fetch-BAxKmbKF.js} +1 -1
- package/dist-web/assets/{fetch-CZDFhvxY.js → fetch-BYczbT4C.js} +1 -1
- package/dist-web/assets/{fetch-ChZEF_n3.js → fetch-BcxrU2mr.js} +1 -1
- package/dist-web/assets/{fetch-BwQh9WUD.js → fetch-Bh1fo3Df.js} +1 -1
- package/dist-web/assets/{fetch-DlJFgbum.js → fetch-C3yhWO2S.js} +1 -1
- package/dist-web/assets/{fetch-DZFx8FA4.js → fetch-Da_TBxrX.js} +1 -1
- package/dist-web/assets/{fetch-C2mSdG3h.js → fetch-DyD8wDhb.js} +1 -1
- package/dist-web/assets/fetch-TCeykOkO.js +3 -0
- package/dist-web/assets/{fetch-DCQN6wfR.js → fetch-tIIVdXSv.js} +1 -1
- package/dist-web/assets/index-BzjLqlW_.js +173 -0
- package/dist-web/assets/{index-qVTX1tLg.js → index-C74qEDFh.js} +3 -3
- package/dist-web/assets/index-CM6xNPFp.css +1 -0
- package/dist-web/assets/index-CgK2LLIF.js +12 -0
- package/dist-web/assets/{index-DTlfOd_u.js → index-CjkOvM3N.js} +2 -2
- package/dist-web/assets/{index-DlkH2E7N.js → index-Dno7CADA.js} +1 -1
- package/dist-web/assets/{index-KAFPL6dl.js → index-fbWrQpgH.js} +1 -1
- package/dist-web/assets/{web-CoKgy4Oo.js → web-H3XLx159.js} +1 -1
- package/dist-web/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-B2VLK3FB.mjs.map +0 -1
- package/dist/chunk-DHRSLYUM.mjs +0 -7433
- package/dist/chunk-DHRSLYUM.mjs.map +0 -1
- package/dist/chunk-JOXF6ROV.mjs.map +0 -1
- package/dist/chunk-K7UUN3HT.mjs.map +0 -1
- package/dist/chunk-MDDWX2MG.mjs.map +0 -1
- package/dist/chunk-ST5APJB2.mjs +0 -389
- package/dist/chunk-ST5APJB2.mjs.map +0 -1
- package/dist/worker-runtime-WWQ7W74Q.mjs.map +0 -1
- package/dist-web/assets/electron-2m4Vdk2z.js +0 -1
- package/dist-web/assets/fetch-O8M5ZWsc.js +0 -3
- package/dist-web/assets/index-B_uZZGqi.js +0 -173
- package/dist-web/assets/index-CoX0L-A8.css +0 -1
- package/dist-web/assets/index-DR78oafF.js +0 -11
- /package/dist/{chat-command-QVCZVDSB.mjs.map → chat-command-ISJ2DC7R.mjs.map} +0 -0
- /package/dist/{chunk-OD3YH23N.mjs.map → chunk-3QBVO4J7.mjs.map} +0 -0
- /package/dist/{http-LSCVGWCI.mjs.map → chunk-5FGB42EQ.mjs.map} +0 -0
- /package/dist/{chunk-PXWAYFII.mjs.map → chunk-ERAAWLEF.mjs.map} +0 -0
- /package/dist/{chunk-H22VDCPB.mjs.map → chunk-NJB2NNB2.mjs.map} +0 -0
- /package/dist/{import-E6HBJMMT.mjs.map → http-MBILV37U.mjs.map} +0 -0
- /package/dist/{src-HZILDI7C.mjs.map → import-EYV3GXHE.mjs.map} +0 -0
- /package/dist/{mcp-I7JUC5JV.mjs.map → mcp-H55AEXU5.mjs.map} +0 -0
- /package/dist/{stream-import-IMRULRS2.mjs.map → src-36ZIR65K.mjs.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/http/port.ts"],"sourcesContent":["/**\n * ChatLab CLI entry point\n *\n * Dev: pnpm --filter chatlab-cli run cli -- sessions\n */\n\nimport * as fs from 'fs'\nimport { execSync } from 'child_process'\nimport { Command } from 'commander'\nimport { DEFAULT_API_PORT, loadConfig, getConfigPath } from '@openchatlab/config'\nimport {\n NodePathProvider,\n DatabaseManager,\n AIChatManager,\n applyPendingNodeDataDirMigrationIfNeeded,\n hasPendingElectronDataWarning,\n verifyCliDataPath,\n} from '@openchatlab/node-runtime'\nimport {\n getSessionMeta,\n getSessionOverview,\n getMemberActivity,\n searchMessagesLike,\n getMembers,\n executeReadonlySql,\n} from '@openchatlab/core'\nimport { getVersion } from './version'\nimport { resolveCliPath } from './paths'\nimport { isPortAvailable, formatPortInUseError } from './http/port'\nimport { assertCliDataDirCompatible } from './runtime-compat'\n\nconst program = new Command()\n\nprogram.name('chatlab').description('ChatLab - Chat history analysis tool').version(getVersion(), '-v, --version')\n\nprogram.hook('preAction', async (_thisCommand, actionCommand) => {\n if (actionCommand.name() === 'update') return\n const { checkForUpdatesInteractive } = await import('./update-checker')\n await checkForUpdatesInteractive()\n})\n\nprogram\n .command('update')\n .description('Update ChatLab CLI to the latest version')\n .action(async () => {\n const { performCliSelfUpdate } = await import('./update-checker')\n const result = await performCliSelfUpdate({\n write: (text) => process.stderr.write(text),\n })\n\n if (result.success) {\n console.error(' Updated successfully. Please restart chatlab to use the new version.\\n')\n return\n }\n\n console.error(` Update failed: ${result.error || 'unknown error'}\\n`)\n process.exitCode = 1\n })\n\nprogram\n .command('sessions')\n .description('List all imported chat sessions')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .action((options) => {\n const { dbManager } = initRuntime()\n const sessionIds = dbManager.listSessionIds()\n\n if (sessionIds.length === 0) {\n console.log('No chat sessions found.')\n console.log(`Data directory: ${dbManager['pathProvider'].getUserDataDir()}`)\n dbManager.closeAll()\n return\n }\n\n const sessions = sessionIds\n .map((id) => {\n const db = dbManager.open(id)\n if (!db) return null\n const meta = getSessionMeta(db)\n if (!meta) return null\n const overview = getSessionOverview(db)\n return { id, ...meta, ...overview }\n })\n .filter(Boolean)\n\n if (options.format === 'json') {\n console.log(JSON.stringify(sessions, null, 2))\n } else {\n console.log(`${sessions.length} session(s) found:\\n`)\n for (const s of sessions) {\n if (!s) continue\n const timeRange = formatTimeRange(s.firstMessageTs, s.lastMessageTs)\n console.log(` ${s.name}`)\n console.log(` ID: ${s.id}`)\n console.log(\n ` Platform: ${s.platform} | Type: ${s.type} | Members: ${s.totalMembers} | Messages: ${s.totalMessages}`\n )\n if (timeRange) console.log(` Range: ${timeRange}`)\n console.log()\n }\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('stats <session-id>')\n .description('Show session statistics overview')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .option('--top <n>', 'Show top N active members', '10')\n .action((sessionId, options) => {\n const { dbManager } = initRuntime()\n const db = dbManager.open(sessionId)\n if (!db) {\n console.error(`Session ${sessionId} not found`)\n process.exit(1)\n }\n\n const meta = getSessionMeta(db)\n const overview = getSessionOverview(db)\n const topMembers = getMemberActivity(db).slice(0, parseInt(options.top))\n\n if (options.format === 'json') {\n console.log(JSON.stringify({ meta, overview, topMembers }, null, 2))\n } else {\n console.log(`\\nSession: ${meta?.name}`)\n console.log(`Platform: ${meta?.platform} | Type: ${meta?.type}`)\n console.log(`Total messages: ${overview.totalMessages}`)\n console.log(`Total members: ${overview.totalMembers}`)\n console.log(`Time range: ${formatTimeRange(overview.firstMessageTs, overview.lastMessageTs)}`)\n\n if (topMembers.length > 0) {\n console.log(`\\nActivity ranking (Top ${options.top}):`)\n for (const [i, m] of topMembers.entries()) {\n console.log(` ${i + 1}. ${m.name} - ${m.messageCount} messages (${m.percentage}%)`)\n }\n }\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('search <session-id> <keyword>')\n .description('Search messages by keyword')\n .option('--limit <n>', 'Max results to return', '20')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .action((sessionId, keyword, options) => {\n const { dbManager } = initRuntime()\n const db = dbManager.open(sessionId)\n if (!db) {\n console.error(`Session ${sessionId} not found`)\n process.exit(1)\n }\n\n const limit = parseInt(options.limit)\n const result = searchMessagesLike(db, keyword, { limit })\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2))\n } else {\n console.log(\n `Search \"${keyword}\" - ${result.total} result(s)${result.hasMore ? ' (showing first ' + limit + ')' : ''}:\\n`\n )\n for (const msg of result.messages) {\n const time = new Date(msg.timestamp * 1000).toLocaleString()\n console.log(` [${time}] ${msg.senderName}: ${msg.content}`)\n }\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('members <session-id>')\n .description('List session members')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .action((sessionId, options) => {\n const { dbManager } = initRuntime()\n const db = dbManager.open(sessionId)\n if (!db) {\n console.error(`Session ${sessionId} not found`)\n process.exit(1)\n }\n\n const members = getMembers(db)\n\n if (options.format === 'json') {\n console.log(JSON.stringify(members, null, 2))\n } else {\n console.log(`${members.length} member(s):\\n`)\n for (const [i, m] of members.entries()) {\n console.log(` ${i + 1}. ${m.name} (${m.platformId}) - ${m.messageCount} messages`)\n }\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('query <session-id>')\n .description('Execute a read-only SQL query on a session database')\n .requiredOption('--sql <sql>', 'SQL query statement')\n .option('--format <format>', 'Output format (table|json)', 'table')\n .action((sessionId, options) => {\n const { dbManager } = initRuntime()\n const db = dbManager.open(sessionId)\n if (!db) {\n console.error(`Session ${sessionId} not found`)\n process.exit(1)\n }\n\n try {\n const result = executeReadonlySql(db, options.sql)\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2))\n } else {\n if (result.rows.length === 0) {\n console.log('No results.')\n } else {\n printTable(result.columns, result.rows)\n console.log(`\\n${result.rowCount} row(s)${result.truncated ? ' (truncated)' : ''}`)\n }\n }\n } catch (err) {\n console.error(`SQL error: ${err instanceof Error ? err.message : err}`)\n process.exit(1)\n }\n\n dbManager.closeAll()\n })\n\nprogram\n .command('import <file>')\n .description('Import a chat history file (14+ formats: QQ/WeChat/Telegram/WhatsApp/LINE/Discord/Instagram, etc.)')\n .option('--session-id <id>', 'Specify session ID (auto-generated if omitted)')\n .option('--format <id>', 'Specify format ID (skip auto-detection)')\n .action(async (file, options) => {\n if (!fs.existsSync(file)) {\n console.error(`File not found: ${file}`)\n process.exit(1)\n }\n\n const { streamImport, detectFormat } = await import('./import')\n const { dbManager, pathProvider } = initRuntime()\n const nativeBinding = resolveNativeBinding()\n\n const format = detectFormat(file)\n if (!format && !options.format) {\n console.error(`Unrecognized file format: ${file}`)\n console.error('Use --format <id> to specify manually, or run \"chatlab formats\" to see supported formats')\n process.exit(1)\n }\n\n console.log(`Importing: ${file}`)\n if (format) console.log(` Format: ${format.name} (${format.platform})`)\n\n try {\n const result = await streamImport(dbManager, file, {\n formatId: options.format,\n nativeBinding,\n onProgress: (p) => {\n process.stdout.write(`\\r ${p.stage}: ${p.progress}%`)\n },\n })\n\n if (result.success) {\n console.log(`\\n\\nImport succeeded!`)\n console.log(` Session ID: ${result.sessionId}`)\n console.log(` Messages written: ${result.diagnostics?.messagesWritten ?? 0}`)\n console.log(` Messages skipped: ${result.diagnostics?.messagesSkipped ?? 0}`)\n\n if (result.sessionId) {\n try {\n const { PreferencesManager, createDatabaseManagerAdapter, ownerProfileService } =\n await import('@openchatlab/node-runtime')\n const applied = ownerProfileService.tryApplyOwnerProfile(\n createDatabaseManagerAdapter(dbManager),\n new PreferencesManager(pathProvider.getSystemDir()),\n result.sessionId\n )\n if (applied.applied) {\n console.log(` Owner auto-detected: ${applied.ownerId}`)\n }\n } catch (ownerErr) {\n console.warn(` Owner profile apply skipped: ${ownerErr instanceof Error ? ownerErr.message : ownerErr}`)\n }\n }\n } else {\n console.error(`\\n\\nImport failed: ${result.error}`)\n process.exit(1)\n }\n } catch (err) {\n console.error(`\\n\\nImport error: ${err instanceof Error ? err.message : err}`)\n process.exit(1)\n } finally {\n dbManager.closeAll()\n }\n })\n\nprogram\n .command('formats')\n .description('List all supported chat history formats')\n .action(async () => {\n const { getSupportedFormats } = await import('./import')\n const formats = getSupportedFormats()\n console.log(`${formats.length} supported format(s):\\n`)\n for (const f of formats) {\n console.log(` ${f.id.padEnd(30)} ${f.name} (${f.platform}) [${f.extensions.join(', ')}]`)\n }\n })\n\nprogram\n .command('mcp')\n .description('Start MCP Server (stdio transport, for ClaudeCode / Cursor / AI agents)')\n .action(async () => {\n const { startCliMcpServer } = await import('./mcp')\n await startCliMcpServer()\n })\n\nprogram\n .command('chat')\n .description('Ask ChatLab AI about an imported chat session')\n .option('--session-id <id>', 'Source chat session ID')\n .option('--ai-chat-id <id>', 'Existing AI chat ID to continue')\n .option('-q, --question <text>', 'Question to ask')\n .option('--json', 'Output structured JSON')\n .option('--include-events', 'Include all agent stream chunks in JSON output')\n .option('--no-stream', 'Disable streaming output')\n .option('--locale <locale>', 'AI response locale', 'zh-CN')\n .action(async (options) => {\n const { runChatCommand } = await import('./ai/chat-command')\n const { dbManager, pathProvider } = initRuntime()\n const aiChatManager = new AIChatManager(pathProvider.getAiDataDir(), { nativeBinding: resolveNativeBinding() })\n\n try {\n await runChatCommand(\n {\n sessionId: options.sessionId,\n aiChatId: options.aiChatId,\n question: options.question,\n json: !!options.json,\n stream: options.stream,\n locale: options.locale,\n includeEvents: !!options.includeEvents,\n },\n { dbManager, pathProvider, aiChatManager }\n )\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n process.exitCode = 1\n } finally {\n aiChatManager.close()\n dbManager.closeAll()\n }\n })\n\nprogram\n .command('start')\n .description('Start ChatLab (HTTP API + Web UI)')\n .option('--port <port>', 'Server port', String(DEFAULT_API_PORT))\n .option('--host <host>', 'Listen address', '127.0.0.1')\n .option('--token <token>', 'Custom Bearer Token (reads from config or auto-generates if omitted)')\n .option('--headless', 'API-only mode, do not serve the Web UI')\n .option('--require-auth', 'Require Bearer token for all routes including /_web/*')\n .option('--no-open', 'Do not auto-open the browser')\n .option('--daemon', 'Run as a resident system service (auto-start on login, macOS/Linux)')\n .action(async (options) => {\n // --daemon: install as system service and exit\n if (options.daemon) {\n const { serviceInstall } = await import('./daemon/service')\n serviceInstall({\n port: parseInt(options.port, 10),\n host: options.host,\n token: options.token || undefined,\n headless: options.headless,\n requireAuth: options.requireAuth || undefined,\n })\n return\n }\n\n const { startHttpServer } = await import('./http')\n const port = parseInt(options.port, 10)\n\n let webRoot: string | undefined\n if (!options.headless) {\n const webDir = resolveCliPath('dist-web')\n if (fs.existsSync(webDir)) {\n webRoot = webDir\n } else {\n console.warn('Warning: dist-web/ not found, starting in API-only mode')\n }\n }\n\n try {\n // 启动前预检端口,快速失败,避免无谓的初始化后再报 EADDRINUSE;\n // 置于 try 内确保非 EADDRINUSE 错误(EACCES/EADDRNOTAVAIL 等)\n // 也能走到统一的 Startup failed 错误处理路径。\n if (!(await isPortAvailable(port, options.host))) {\n console.error(formatPortInUseError(port))\n process.exit(1)\n }\n\n const info = await startHttpServer({\n port,\n host: options.host,\n token: options.token || undefined,\n webRoot,\n requireAuth: options.requireAuth || undefined,\n })\n\n const { startPeriodicUpdateCheck } = await import('./update-checker')\n startPeriodicUpdateCheck()\n\n const displayHost = info.host === '0.0.0.0' ? '127.0.0.1' : info.host\n const url = `http://${displayHost}:${info.port}`\n\n console.log(`\\nChatLab v${getVersion()}`)\n if (webRoot) console.log(` Web UI: ${url}/`)\n console.log(` API: ${url}`)\n console.log(` Token: ${info.token}`)\n console.log(`\\nExample:`)\n console.log(` curl -H \"Authorization: Bearer ${info.token}\" ${url}/api/v1/status`)\n\n if (webRoot && options.open) {\n openBrowser(url)\n console.log(`\\nBrowser opened. Press Ctrl+C to stop.\\n`)\n } else {\n console.log(`\\nPress Ctrl+C to stop.\\n`)\n }\n\n const shutdown = async () => {\n console.log('\\nShutting down...')\n const { stopHttpServer } = await import('./http')\n await stopHttpServer()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n if (message.includes('EADDRINUSE')) {\n // 极低概率的 TOCTOU 竞态(预检通过后端口被占),复用统一文案\n console.error(formatPortInUseError(port))\n } else {\n console.error(`Startup failed: ${message}`)\n }\n process.exit(1)\n }\n })\n\nconst configCmd = program.command('config').description('Configuration management')\n\nconfigCmd\n .command('path')\n .description('Show config file path')\n .action(() => {\n console.log(getConfigPath())\n })\n\nconfigCmd\n .command('show')\n .description('Show current configuration')\n .action(() => {\n const config = loadConfig()\n console.log(JSON.stringify(config, null, 2))\n })\n\nprogram\n .command('stop')\n .description('Stop the resident service and remove auto-start (reverse of start --daemon)')\n .action(async () => {\n const { serviceUninstall } = await import('./daemon/service')\n serviceUninstall()\n })\n\nprogram\n .command('status')\n .description('Show resident service status')\n .action(async () => {\n const { getServiceStatus } = await import('./daemon/service')\n const svc = getServiceStatus()\n\n console.log('\\nChatLab Status')\n console.log('─'.repeat(36))\n\n if (svc.installed) {\n const portStr = svc.port ? `http://${svc.host ?? '127.0.0.1'}:${svc.port}` : ''\n console.log(` Service: ${svc.running ? 'running' : 'installed (not running)'}`)\n if (portStr) console.log(` Address: ${portStr}`)\n console.log(` Auto-start: enabled`)\n console.log(`\\n Use \\`chatlab stop\\` to remove the service.\\n`)\n } else {\n console.log(` Service: not installed`)\n console.log(` Auto-start: disabled`)\n console.log(`\\n Use \\`chatlab start --daemon\\` to install as a system service.\\n`)\n }\n })\n\n// --- 工具函数 ---\n\nfunction openBrowser(url: string): void {\n try {\n const cmd =\n process.platform === 'darwin'\n ? `open \"${url}\"`\n : process.platform === 'win32'\n ? `start \"\" \"${url}\"`\n : `xdg-open \"${url}\"`\n execSync(cmd, { stdio: 'ignore' })\n } catch {\n console.log(` Open manually: ${url}`)\n }\n}\n\n/**\n * Resolve standalone better-sqlite3 native module path.\n * Used in non-Electron environments to avoid electron-rebuild conflicts.\n */\nfunction resolveNativeBinding(): string | undefined {\n if (process.versions.electron) return undefined\n const nativePath = resolveCliPath('native/better_sqlite3.node')\n if (fs.existsSync(nativePath)) return nativePath\n return undefined\n}\n\nfunction initRuntime() {\n let config = loadConfig()\n const pendingMigration = applyPendingNodeDataDirMigrationIfNeeded()\n if (!pendingMigration.skipped) {\n if (pendingMigration.success) {\n console.log('[Migration] Pending data directory migration completed')\n config = loadConfig()\n } else {\n console.error('[Migration] Pending data directory migration failed:', pendingMigration.error)\n }\n }\n const userDataDir = config.data.user_data_dir || undefined\n const pathProvider = new NodePathProvider(userDataDir)\n pathProvider.ensureAllDirs()\n const runtime = assertCliDataDirCompatible(pathProvider, 'cli')\n\n if (hasPendingElectronDataWarning() || !verifyCliDataPath(pathProvider.getDatabaseDir())) {\n printElectronDataError()\n process.exit(1)\n }\n\n const nativeBinding = resolveNativeBinding()\n const dbManager = new DatabaseManager(pathProvider, { nativeBinding, runtime })\n return { config, pathProvider, dbManager }\n}\n\nfunction printElectronDataError(): void {\n console.error('\\n' + '='.repeat(68))\n console.error(' ChatLab: Electron desktop data not found')\n console.error('='.repeat(68))\n console.error('')\n console.error(' Detected that ChatLab desktop app was installed on this machine,')\n console.error(' but could not locate your chat databases.')\n console.error('')\n console.error(' This usually means you changed the data directory in desktop settings.')\n console.error('')\n console.error(' To fix this, choose one of:')\n console.error('')\n console.error(' 1. Open ChatLab desktop app — it will auto-migrate your data')\n console.error(' 2. Set the data directory manually:')\n console.error(' export CHATLAB_DATA_DIR=\"/path/to/your/data\"')\n console.error(' 3. Edit ~/.chatlab/config.toml:')\n console.error(' [data]')\n console.error(' user_data_dir = \"/path/to/your/data\"')\n console.error('')\n console.error('='.repeat(68) + '\\n')\n}\n\nfunction formatTimeRange(first: number | null, last: number | null): string {\n if (!first || !last) return 'Unknown'\n const from = new Date(first * 1000).toLocaleDateString()\n const to = new Date(last * 1000).toLocaleDateString()\n return `${from} ~ ${to}`\n}\n\nfunction printTable(columns: string[], rows: Record<string, unknown>[]): void {\n const widths = columns.map((col) => {\n const maxData = rows.reduce((max, row) => {\n const val = String(row[col] ?? '')\n return Math.max(max, val.length)\n }, 0)\n return Math.max(col.length, Math.min(maxData, 40))\n })\n\n const header = columns.map((col, i) => col.padEnd(widths[i])).join(' | ')\n const separator = widths.map((w) => '-'.repeat(w)).join('-+-')\n console.log(header)\n console.log(separator)\n\n for (const row of rows) {\n const line = columns\n .map((col, i) => {\n const val = String(row[col] ?? '')\n return val.length > 40 ? val.slice(0, 37) + '...' : val.padEnd(widths[i])\n })\n .join(' | ')\n console.log(line)\n }\n}\n\n/** CLI entry function */\nexport function run(argv?: string[]): void {\n program.parse(argv)\n}\n\n// Auto-execute when run directly as a script\nconst isDirectRun = process.argv[1]?.endsWith('cli.ts') || process.argv[1]?.endsWith('cli.js')\nif (isDirectRun) {\n run()\n}\n","/**\n * 端口可用性检测与错误文案\n */\n\nimport * as net from 'net'\n\n/**\n * 检测指定端口在给定 host 上是否可用。\n * 使用 net.createServer() 试探性绑定,无副作用(成功后立即释放)。\n */\nexport function isPortAvailable(port: number, host: string): Promise<boolean> {\n return new Promise((resolve, reject) => {\n const server = net.createServer()\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n resolve(false)\n } else {\n reject(err)\n }\n })\n server.once('listening', () => server.close(() => resolve(true)))\n server.listen(port, host)\n })\n}\n\n/**\n * 返回端口被占用时统一的友好报错文案。\n */\nexport function formatPortInUseError(port: number): string {\n return [\n ``,\n ` ✖ Error: port ${port} is already in use.`,\n ``,\n ` Another process (possibly a ChatLab instance) is using this port. You can:`,\n ` • Use another port: chatlab start --port <port>`,\n ` • Find the process: lsof -iTCP:${port} -sTCP:LISTEN`,\n ``,\n ].join('\\n')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,YAAY,QAAQ;AACpB,SAAS,gBAAgB;AACzB,SAAS,eAAe;;;ACJxB,YAAY,SAAS;AAMd,SAAS,gBAAgB,MAAc,MAAgC;AAC5E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAa,iBAAa;AAChC,WAAO,KAAK,SAAS,CAAC,QAA+B;AACnD,UAAI,IAAI,SAAS,cAAc;AAC7B,gBAAQ,KAAK;AAAA,MACf,OAAO;AACL,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AACD,WAAO,KAAK,aAAa,MAAM,OAAO,MAAM,MAAM,QAAQ,IAAI,CAAC,CAAC;AAChE,WAAO,OAAO,MAAM,IAAI;AAAA,EAC1B,CAAC;AACH;AAKO,SAAS,qBAAqB,MAAsB;AACzD,SAAO;AAAA,IACL;AAAA,IACA,wBAAmB,IAAI;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,6CAAwC,IAAI;AAAA,IAC5C;AAAA,EACF,EAAE,KAAK,IAAI;AACb;;;ADPA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QAAQ,KAAK,SAAS,EAAE,YAAY,sCAAsC,EAAE,QAAQ,WAAW,GAAG,eAAe;AAEjH,QAAQ,KAAK,aAAa,OAAO,cAAc,kBAAkB;AAC/D,MAAI,cAAc,KAAK,MAAM,SAAU;AACvC,QAAM,EAAE,2BAA2B,IAAI,MAAM,OAAO,+BAAkB;AACtE,QAAM,2BAA2B;AACnC,CAAC;AAED,QACG,QAAQ,QAAQ,EAChB,YAAY,0CAA0C,EACtD,OAAO,YAAY;AAClB,QAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,+BAAkB;AAChE,QAAM,SAAS,MAAM,qBAAqB;AAAA,IACxC,OAAO,CAAC,SAAS,QAAQ,OAAO,MAAM,IAAI;AAAA,EAC5C,CAAC;AAED,MAAI,OAAO,SAAS;AAClB,YAAQ,MAAM,0EAA0E;AACxF;AAAA,EACF;AAEA,UAAQ,MAAM,oBAAoB,OAAO,SAAS,eAAe;AAAA,CAAI;AACrE,UAAQ,WAAW;AACrB,CAAC;AAEH,QACG,QAAQ,UAAU,EAClB,YAAY,iCAAiC,EAC7C,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,CAAC,YAAY;AACnB,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,aAAa,UAAU,eAAe;AAE5C,MAAI,WAAW,WAAW,GAAG;AAC3B,YAAQ,IAAI,yBAAyB;AACrC,YAAQ,IAAI,mBAAmB,UAAU,cAAc,EAAE,eAAe,CAAC,EAAE;AAC3E,cAAU,SAAS;AACnB;AAAA,EACF;AAEA,QAAM,WAAW,WACd,IAAI,CAAC,OAAO;AACX,UAAM,KAAK,UAAU,KAAK,EAAE;AAC5B,QAAI,CAAC,GAAI,QAAO;AAChB,UAAM,OAAO,eAAe,EAAE;AAC9B,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,WAAW,mBAAmB,EAAE;AACtC,WAAO,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS;AAAA,EACpC,CAAC,EACA,OAAO,OAAO;AAEjB,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,EAC/C,OAAO;AACL,YAAQ,IAAI,GAAG,SAAS,MAAM;AAAA,CAAsB;AACpD,eAAW,KAAK,UAAU;AACxB,UAAI,CAAC,EAAG;AACR,YAAM,YAAY,gBAAgB,EAAE,gBAAgB,EAAE,aAAa;AACnE,cAAQ,IAAI,KAAK,EAAE,IAAI,EAAE;AACzB,cAAQ,IAAI,WAAW,EAAE,EAAE,EAAE;AAC7B,cAAQ;AAAA,QACN,iBAAiB,EAAE,QAAQ,YAAY,EAAE,IAAI,eAAe,EAAE,YAAY,gBAAgB,EAAE,aAAa;AAAA,MAC3G;AACA,UAAI,UAAW,SAAQ,IAAI,cAAc,SAAS,EAAE;AACpD,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,oBAAoB,EAC5B,YAAY,kCAAkC,EAC9C,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,aAAa,6BAA6B,IAAI,EACrD,OAAO,CAAC,WAAW,YAAY;AAC9B,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,KAAK,UAAU,KAAK,SAAS;AACnC,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,WAAW,SAAS,YAAY;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,eAAe,EAAE;AAC9B,QAAM,WAAW,mBAAmB,EAAE;AACtC,QAAM,aAAa,kBAAkB,EAAE,EAAE,MAAM,GAAG,SAAS,QAAQ,GAAG,CAAC;AAEvE,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,EAAE,MAAM,UAAU,WAAW,GAAG,MAAM,CAAC,CAAC;AAAA,EACrE,OAAO;AACL,YAAQ,IAAI;AAAA,WAAc,MAAM,IAAI,EAAE;AACtC,YAAQ,IAAI,aAAa,MAAM,QAAQ,YAAY,MAAM,IAAI,EAAE;AAC/D,YAAQ,IAAI,mBAAmB,SAAS,aAAa,EAAE;AACvD,YAAQ,IAAI,kBAAkB,SAAS,YAAY,EAAE;AACrD,YAAQ,IAAI,eAAe,gBAAgB,SAAS,gBAAgB,SAAS,aAAa,CAAC,EAAE;AAE7F,QAAI,WAAW,SAAS,GAAG;AACzB,cAAQ,IAAI;AAAA,wBAA2B,QAAQ,GAAG,IAAI;AACtD,iBAAW,CAAC,GAAG,CAAC,KAAK,WAAW,QAAQ,GAAG;AACzC,gBAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,EAAE,IAAI,MAAM,EAAE,YAAY,cAAc,EAAE,UAAU,IAAI;AAAA,MACrF;AAAA,IACF;AAAA,EACF;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,+BAA+B,EACvC,YAAY,4BAA4B,EACxC,OAAO,eAAe,yBAAyB,IAAI,EACnD,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,CAAC,WAAW,SAAS,YAAY;AACvC,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,KAAK,UAAU,KAAK,SAAS;AACnC,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,WAAW,SAAS,YAAY;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,SAAS,QAAQ,KAAK;AACpC,QAAM,SAAS,mBAAmB,IAAI,SAAS,EAAE,MAAM,CAAC;AAExD,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC7C,OAAO;AACL,YAAQ;AAAA,MACN,WAAW,OAAO,OAAO,OAAO,KAAK,aAAa,OAAO,UAAU,qBAAqB,QAAQ,MAAM,EAAE;AAAA;AAAA,IAC1G;AACA,eAAW,OAAO,OAAO,UAAU;AACjC,YAAM,OAAO,IAAI,KAAK,IAAI,YAAY,GAAI,EAAE,eAAe;AAC3D,cAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,UAAU,KAAK,IAAI,OAAO,EAAE;AAAA,IAC7D;AAAA,EACF;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,sBAAsB,EAC9B,YAAY,sBAAsB,EAClC,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,CAAC,WAAW,YAAY;AAC9B,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,KAAK,UAAU,KAAK,SAAS;AACnC,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,WAAW,SAAS,YAAY;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAAU,WAAW,EAAE;AAE7B,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EAC9C,OAAO;AACL,YAAQ,IAAI,GAAG,QAAQ,MAAM;AAAA,CAAe;AAC5C,eAAW,CAAC,GAAG,CAAC,KAAK,QAAQ,QAAQ,GAAG;AACtC,cAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,EAAE,UAAU,OAAO,EAAE,YAAY,WAAW;AAAA,IACpF;AAAA,EACF;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,oBAAoB,EAC5B,YAAY,qDAAqD,EACjE,eAAe,eAAe,qBAAqB,EACnD,OAAO,qBAAqB,8BAA8B,OAAO,EACjE,OAAO,CAAC,WAAW,YAAY;AAC9B,QAAM,EAAE,UAAU,IAAI,YAAY;AAClC,QAAM,KAAK,UAAU,KAAK,SAAS;AACnC,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,WAAW,SAAS,YAAY;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI;AACF,UAAM,SAAS,mBAAmB,IAAI,QAAQ,GAAG;AACjD,QAAI,QAAQ,WAAW,QAAQ;AAC7B,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,OAAO;AACL,UAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,gBAAQ,IAAI,aAAa;AAAA,MAC3B,OAAO;AACL,mBAAW,OAAO,SAAS,OAAO,IAAI;AACtC,gBAAQ,IAAI;AAAA,EAAK,OAAO,QAAQ,UAAU,OAAO,YAAY,iBAAiB,EAAE,EAAE;AAAA,MACpF;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,cAAc,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACtE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,YAAU,SAAS;AACrB,CAAC;AAEH,QACG,QAAQ,eAAe,EACvB,YAAY,oGAAoG,EAChH,OAAO,qBAAqB,gDAAgD,EAC5E,OAAO,iBAAiB,yCAAyC,EACjE,OAAO,OAAO,MAAM,YAAY;AAC/B,MAAI,CAAI,cAAW,IAAI,GAAG;AACxB,YAAQ,MAAM,mBAAmB,IAAI,EAAE;AACvC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,cAAc,aAAa,IAAI,MAAM,OAAO,uBAAU;AAC9D,QAAM,EAAE,WAAW,aAAa,IAAI,YAAY;AAChD,QAAM,gBAAgB,qBAAqB;AAE3C,QAAM,SAAS,aAAa,IAAI;AAChC,MAAI,CAAC,UAAU,CAAC,QAAQ,QAAQ;AAC9B,YAAQ,MAAM,6BAA6B,IAAI,EAAE;AACjD,YAAQ,MAAM,0FAA0F;AACxG,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,cAAc,IAAI,EAAE;AAChC,MAAI,OAAQ,SAAQ,IAAI,aAAa,OAAO,IAAI,KAAK,OAAO,QAAQ,GAAG;AAEvE,MAAI;AACF,UAAM,SAAS,MAAM,aAAa,WAAW,MAAM;AAAA,MACjD,UAAU,QAAQ;AAAA,MAClB;AAAA,MACA,YAAY,CAAC,MAAM;AACjB,gBAAQ,OAAO,MAAM,OAAO,EAAE,KAAK,KAAK,EAAE,QAAQ,GAAG;AAAA,MACvD;AAAA,IACF,CAAC;AAED,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAI;AAAA;AAAA,kBAAuB;AACnC,cAAQ,IAAI,iBAAiB,OAAO,SAAS,EAAE;AAC/C,cAAQ,IAAI,uBAAuB,OAAO,aAAa,mBAAmB,CAAC,EAAE;AAC7E,cAAQ,IAAI,uBAAuB,OAAO,aAAa,mBAAmB,CAAC,EAAE;AAE7E,UAAI,OAAO,WAAW;AACpB,YAAI;AACF,gBAAM,EAAE,oBAAoB,8BAA8B,oBAAoB,IAC5E,MAAM,OAAO,oBAA2B;AAC1C,gBAAM,UAAU,oBAAoB;AAAA,YAClC,6BAA6B,SAAS;AAAA,YACtC,IAAI,mBAAmB,aAAa,aAAa,CAAC;AAAA,YAClD,OAAO;AAAA,UACT;AACA,cAAI,QAAQ,SAAS;AACnB,oBAAQ,IAAI,0BAA0B,QAAQ,OAAO,EAAE;AAAA,UACzD;AAAA,QACF,SAAS,UAAU;AACjB,kBAAQ,KAAK,kCAAkC,oBAAoB,QAAQ,SAAS,UAAU,QAAQ,EAAE;AAAA,QAC1G;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ,MAAM;AAAA;AAAA,iBAAsB,OAAO,KAAK,EAAE;AAClD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM;AAAA;AAAA,gBAAqB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAC7E,YAAQ,KAAK,CAAC;AAAA,EAChB,UAAE;AACA,cAAU,SAAS;AAAA,EACrB;AACF,CAAC;AAEH,QACG,QAAQ,SAAS,EACjB,YAAY,yCAAyC,EACrD,OAAO,YAAY;AAClB,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,uBAAU;AACvD,QAAM,UAAU,oBAAoB;AACpC,UAAQ,IAAI,GAAG,QAAQ,MAAM;AAAA,CAAyB;AACtD,aAAW,KAAK,SAAS;AACvB,YAAQ,IAAI,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,EAAE,QAAQ,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG;AAAA,EAC3F;AACF,CAAC;AAEH,QACG,QAAQ,KAAK,EACb,YAAY,yEAAyE,EACrF,OAAO,YAAY;AAClB,QAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,oBAAO;AAClD,QAAM,kBAAkB;AAC1B,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,+CAA+C,EAC3D,OAAO,qBAAqB,wBAAwB,EACpD,OAAO,qBAAqB,iCAAiC,EAC7D,OAAO,yBAAyB,iBAAiB,EACjD,OAAO,UAAU,wBAAwB,EACzC,OAAO,oBAAoB,gDAAgD,EAC3E,OAAO,eAAe,0BAA0B,EAChD,OAAO,qBAAqB,sBAAsB,OAAO,EACzD,OAAO,OAAO,YAAY;AACzB,QAAM,EAAE,eAAe,IAAI,MAAM,OAAO,6BAAmB;AAC3D,QAAM,EAAE,WAAW,aAAa,IAAI,YAAY;AAChD,QAAM,gBAAgB,IAAI,cAAc,aAAa,aAAa,GAAG,EAAE,eAAe,qBAAqB,EAAE,CAAC;AAE9G,MAAI;AACF,UAAM;AAAA,MACJ;AAAA,QACE,WAAW,QAAQ;AAAA,QACnB,UAAU,QAAQ;AAAA,QAClB,UAAU,QAAQ;AAAA,QAClB,MAAM,CAAC,CAAC,QAAQ;AAAA,QAChB,QAAQ,QAAQ;AAAA,QAChB,QAAQ,QAAQ;AAAA,QAChB,eAAe,CAAC,CAAC,QAAQ;AAAA,MAC3B;AAAA,MACA,EAAE,WAAW,cAAc,cAAc;AAAA,IAC3C;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AACpE,YAAQ,WAAW;AAAA,EACrB,UAAE;AACA,kBAAc,MAAM;AACpB,cAAU,SAAS;AAAA,EACrB;AACF,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,mCAAmC,EAC/C,OAAO,iBAAiB,eAAe,OAAO,gBAAgB,CAAC,EAC/D,OAAO,iBAAiB,kBAAkB,WAAW,EACrD,OAAO,mBAAmB,sEAAsE,EAChG,OAAO,cAAc,wCAAwC,EAC7D,OAAO,kBAAkB,uDAAuD,EAChF,OAAO,aAAa,8BAA8B,EAClD,OAAO,YAAY,qEAAqE,EACxF,OAAO,OAAO,YAAY;AAEzB,MAAI,QAAQ,QAAQ;AAClB,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,wBAAkB;AAC1D,mBAAe;AAAA,MACb,MAAM,SAAS,QAAQ,MAAM,EAAE;AAAA,MAC/B,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ,SAAS;AAAA,MACxB,UAAU,QAAQ;AAAA,MAClB,aAAa,QAAQ,eAAe;AAAA,IACtC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,qBAAQ;AACjD,QAAM,OAAO,SAAS,QAAQ,MAAM,EAAE;AAEtC,MAAI;AACJ,MAAI,CAAC,QAAQ,UAAU;AACrB,UAAM,SAAS,eAAe,UAAU;AACxC,QAAO,cAAW,MAAM,GAAG;AACzB,gBAAU;AAAA,IACZ,OAAO;AACL,cAAQ,KAAK,yDAAyD;AAAA,IACxE;AAAA,EACF;AAEA,MAAI;AAIF,QAAI,CAAE,MAAM,gBAAgB,MAAM,QAAQ,IAAI,GAAI;AAChD,cAAQ,MAAM,qBAAqB,IAAI,CAAC;AACxC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,OAAO,MAAM,gBAAgB;AAAA,MACjC;AAAA,MACA,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ,SAAS;AAAA,MACxB;AAAA,MACA,aAAa,QAAQ,eAAe;AAAA,IACtC,CAAC;AAED,UAAM,EAAE,yBAAyB,IAAI,MAAM,OAAO,+BAAkB;AACpE,6BAAyB;AAEzB,UAAM,cAAc,KAAK,SAAS,YAAY,cAAc,KAAK;AACjE,UAAM,MAAM,UAAU,WAAW,IAAI,KAAK,IAAI;AAE9C,YAAQ,IAAI;AAAA,WAAc,WAAW,CAAC,EAAE;AACxC,QAAI,QAAS,SAAQ,IAAI,aAAa,GAAG,GAAG;AAC5C,YAAQ,IAAI,aAAa,GAAG,EAAE;AAC9B,YAAQ,IAAI,aAAa,KAAK,KAAK,EAAE;AACrC,YAAQ,IAAI;AAAA,SAAY;AACxB,YAAQ,IAAI,oCAAoC,KAAK,KAAK,KAAK,GAAG,gBAAgB;AAElF,QAAI,WAAW,QAAQ,MAAM;AAC3B,kBAAY,GAAG;AACf,cAAQ,IAAI;AAAA;AAAA,CAA2C;AAAA,IACzD,OAAO;AACL,cAAQ,IAAI;AAAA;AAAA,CAA2B;AAAA,IACzC;AAEA,UAAM,WAAW,YAAY;AAC3B,cAAQ,IAAI,oBAAoB;AAChC,YAAM,EAAE,eAAe,IAAI,MAAM,OAAO,qBAAQ;AAChD,YAAM,eAAe;AACrB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,GAAG,UAAU,QAAQ;AAC7B,YAAQ,GAAG,WAAW,QAAQ;AAAA,EAChC,SAAS,KAAc;AACrB,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,QAAI,QAAQ,SAAS,YAAY,GAAG;AAElC,cAAQ,MAAM,qBAAqB,IAAI,CAAC;AAAA,IAC1C,OAAO;AACL,cAAQ,MAAM,mBAAmB,OAAO,EAAE;AAAA,IAC5C;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,IAAM,YAAY,QAAQ,QAAQ,QAAQ,EAAE,YAAY,0BAA0B;AAElF,UACG,QAAQ,MAAM,EACd,YAAY,uBAAuB,EACnC,OAAO,MAAM;AACZ,UAAQ,IAAI,cAAc,CAAC;AAC7B,CAAC;AAEH,UACG,QAAQ,MAAM,EACd,YAAY,4BAA4B,EACxC,OAAO,MAAM;AACZ,QAAM,SAAS,WAAW;AAC1B,UAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC7C,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,6EAA6E,EACzF,OAAO,YAAY;AAClB,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAAkB;AAC5D,mBAAiB;AACnB,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,8BAA8B,EAC1C,OAAO,YAAY;AAClB,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAAkB;AAC5D,QAAM,MAAM,iBAAiB;AAE7B,UAAQ,IAAI,kBAAkB;AAC9B,UAAQ,IAAI,SAAI,OAAO,EAAE,CAAC;AAE1B,MAAI,IAAI,WAAW;AACjB,UAAM,UAAU,IAAI,OAAO,UAAU,IAAI,QAAQ,WAAW,IAAI,IAAI,IAAI,KAAK;AAC7E,YAAQ,IAAI,iBAAiB,IAAI,UAAU,YAAY,yBAAyB,EAAE;AAClF,QAAI,QAAS,SAAQ,IAAI,iBAAiB,OAAO,EAAE;AACnD,YAAQ,IAAI,uBAAuB;AACnC,YAAQ,IAAI;AAAA;AAAA,CAAmD;AAAA,EACjE,OAAO;AACL,YAAQ,IAAI,6BAA6B;AACzC,YAAQ,IAAI,wBAAwB;AACpC,YAAQ,IAAI;AAAA;AAAA,CAAsE;AAAA,EACpF;AACF,CAAC;AAIH,SAAS,YAAY,KAAmB;AACtC,MAAI;AACF,UAAM,MACJ,QAAQ,aAAa,WACjB,SAAS,GAAG,MACZ,QAAQ,aAAa,UACnB,aAAa,GAAG,MAChB,aAAa,GAAG;AACxB,aAAS,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,EACnC,QAAQ;AACN,YAAQ,IAAI,oBAAoB,GAAG,EAAE;AAAA,EACvC;AACF;AAMA,SAAS,uBAA2C;AAClD,MAAI,QAAQ,SAAS,SAAU,QAAO;AACtC,QAAM,aAAa,eAAe,4BAA4B;AAC9D,MAAO,cAAW,UAAU,EAAG,QAAO;AACtC,SAAO;AACT;AAEA,SAAS,cAAc;AACrB,MAAI,SAAS,WAAW;AACxB,QAAM,mBAAmB,yCAAyC;AAClE,MAAI,CAAC,iBAAiB,SAAS;AAC7B,QAAI,iBAAiB,SAAS;AAC5B,cAAQ,IAAI,wDAAwD;AACpE,eAAS,WAAW;AAAA,IACtB,OAAO;AACL,cAAQ,MAAM,wDAAwD,iBAAiB,KAAK;AAAA,IAC9F;AAAA,EACF;AACA,QAAM,cAAc,OAAO,KAAK,iBAAiB;AACjD,QAAM,eAAe,IAAI,iBAAiB,WAAW;AACrD,eAAa,cAAc;AAC3B,QAAM,UAAU,2BAA2B,cAAc,KAAK;AAE9D,MAAI,8BAA8B,KAAK,CAAC,kBAAkB,aAAa,eAAe,CAAC,GAAG;AACxF,2BAAuB;AACvB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,gBAAgB,qBAAqB;AAC3C,QAAM,YAAY,IAAI,gBAAgB,cAAc,EAAE,eAAe,QAAQ,CAAC;AAC9E,SAAO,EAAE,QAAQ,cAAc,UAAU;AAC3C;AAEA,SAAS,yBAA+B;AACtC,UAAQ,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;AACnC,UAAQ,MAAM,4CAA4C;AAC1D,UAAQ,MAAM,IAAI,OAAO,EAAE,CAAC;AAC5B,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,oEAAoE;AAClF,UAAQ,MAAM,6CAA6C;AAC3D,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,0EAA0E;AACxF,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,+BAA+B;AAC7C,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,qEAAgE;AAC9E,UAAQ,MAAM,uCAAuC;AACrD,UAAQ,MAAM,mDAAmD;AACjE,UAAQ,MAAM,mCAAmC;AACjD,UAAQ,MAAM,aAAa;AAC3B,UAAQ,MAAM,2CAA2C;AACzD,UAAQ,MAAM,EAAE;AAChB,UAAQ,MAAM,IAAI,OAAO,EAAE,IAAI,IAAI;AACrC;AAEA,SAAS,gBAAgB,OAAsB,MAA6B;AAC1E,MAAI,CAAC,SAAS,CAAC,KAAM,QAAO;AAC5B,QAAM,OAAO,IAAI,KAAK,QAAQ,GAAI,EAAE,mBAAmB;AACvD,QAAM,KAAK,IAAI,KAAK,OAAO,GAAI,EAAE,mBAAmB;AACpD,SAAO,GAAG,IAAI,MAAM,EAAE;AACxB;AAEA,SAAS,WAAW,SAAmB,MAAuC;AAC5E,QAAM,SAAS,QAAQ,IAAI,CAAC,QAAQ;AAClC,UAAM,UAAU,KAAK,OAAO,CAAC,KAAK,QAAQ;AACxC,YAAM,MAAM,OAAO,IAAI,GAAG,KAAK,EAAE;AACjC,aAAO,KAAK,IAAI,KAAK,IAAI,MAAM;AAAA,IACjC,GAAG,CAAC;AACJ,WAAO,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI,SAAS,EAAE,CAAC;AAAA,EACnD,CAAC;AAED,QAAM,SAAS,QAAQ,IAAI,CAAC,KAAK,MAAM,IAAI,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK;AACxE,QAAM,YAAY,OAAO,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,KAAK;AAC7D,UAAQ,IAAI,MAAM;AAClB,UAAQ,IAAI,SAAS;AAErB,aAAW,OAAO,MAAM;AACtB,UAAM,OAAO,QACV,IAAI,CAAC,KAAK,MAAM;AACf,YAAM,MAAM,OAAO,IAAI,GAAG,KAAK,EAAE;AACjC,aAAO,IAAI,SAAS,KAAK,IAAI,MAAM,GAAG,EAAE,IAAI,QAAQ,IAAI,OAAO,OAAO,CAAC,CAAC;AAAA,IAC1E,CAAC,EACA,KAAK,KAAK;AACb,YAAQ,IAAI,IAAI;AAAA,EAClB;AACF;AAGO,SAAS,IAAI,MAAuB;AACzC,UAAQ,MAAM,IAAI;AACpB;AAGA,IAAM,cAAc,QAAQ,KAAK,CAAC,GAAG,SAAS,QAAQ,KAAK,QAAQ,KAAK,CAAC,GAAG,SAAS,QAAQ;AAC7F,IAAI,aAAa;AACf,MAAI;AACN;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/import/stream-import.ts"],"sourcesContent":["/**\n * Server/CLI streaming import — adapter for @openchatlab/node-runtime StreamingImporter.\n *\n * Replaces the old buffered-in-memory approach with the same high-performance\n * streaming pipeline used by Electron (batched transactions, deferred indexes,\n * nickname history, FTS, format fallback).\n */\n\nimport type { DatabaseManager } from '@openchatlab/node-runtime'\nimport {\n DataDirCompatibilityError,\n streamingImport,\n analyzeNewImport as sharedAnalyzeNewImport,\n analyzeIncrementalImport as sharedAnalyzeIncremental,\n incrementalImport as sharedIncrementalImport,\n} from '@openchatlab/node-runtime'\nimport { generateSessionIndex, generateIncrementalSessionIndex } from '@openchatlab/core'\nimport type {\n StreamImportResult,\n StreamImportDeps,\n ImportProgressCallback,\n IncrementalImportResult,\n IncrementalAnalyzeResult,\n IncrementalImportDeps,\n ImportOptions,\n AnalyzeNewImportResult,\n} from '@openchatlab/node-runtime'\nimport {\n detectFormat as parserDetectFormat,\n detectAllFormats,\n getFormatFeatureById,\n getSupportedFormats as parserGetSupportedFormats,\n scanMultiChatFile as parserScanMultiChatFile,\n findEntryFileInDirectory,\n type FormatFeature,\n type MultiChatInfo,\n type ParseProgress,\n} from '@openchatlab/parser'\nimport * as crypto from 'crypto'\n\n// ==================== Legacy progress interface (for SSE routes) ====================\n\nexport interface StreamImportProgress {\n stage: 'detecting' | 'parsing' | 'saving' | 'indexing' | 'done' | 'error'\n progress: number\n message: string\n bytesRead?: number\n totalBytes?: number\n messagesProcessed?: number\n}\n\nexport interface StreamImportOptions {\n formatId?: string\n chatIndex?: number\n nativeBinding?: string\n onProgress?: (progress: StreamImportProgress) => void\n}\n\nfunction generateSessionId(): string {\n const ts = Date.now()\n const rand = crypto.randomBytes(4).toString('hex')\n return `chat_${ts}_${rand}`\n}\n\nfunction buildStreamImportDeps(dbManager: DatabaseManager, onProgress?: ImportProgressCallback): StreamImportDeps {\n return {\n openDatabase(sessionId: string) {\n return dbManager.openRawSessionDatabase(sessionId, { create: true, initializeChatTables: true })\n },\n deleteDatabase(sessionId: string) {\n dbManager.deleteSessionDatabaseFiles(sessionId)\n },\n onProgress: onProgress ?? (() => {}),\n postImportHook(db) {\n try {\n generateSessionIndex(db)\n } catch {\n /* non-fatal: frontend can regenerate via generate-index route */\n }\n },\n generateSessionId,\n }\n}\n\nfunction deleteSessionDatabase(dbManager: DatabaseManager, sessionId: string): void {\n dbManager.deleteSessionDatabaseFiles(sessionId)\n}\n\n/**\n * High-performance streaming import: parse a file and write to DB\n * with batched transactions, deferred indexes, and FTS.\n */\nexport async function streamImport(\n dbManager: DatabaseManager,\n filePath: string,\n options?: StreamImportOptions\n): Promise<StreamImportResult> {\n const { formatId, chatIndex, onProgress } = options || {}\n\n const formatOptions: Record<string, unknown> = {}\n if (formatId) formatOptions.formatId = formatId\n if (chatIndex !== undefined) formatOptions.chatIndex = chatIndex\n\n const progressAdapter: ImportProgressCallback = onProgress\n ? (progress) => {\n let stage: StreamImportProgress['stage'] = 'parsing'\n let pct = 0\n switch (progress.stage) {\n case 'detecting':\n stage = 'detecting'\n pct = 5\n break\n case 'parsing':\n stage = 'parsing'\n pct = Math.min(Math.round(progress.percentage * 0.7), 70)\n break\n case 'importing':\n case 'saving':\n stage = 'saving'\n pct = 80\n break\n case 'done':\n stage = 'done'\n pct = 100\n break\n case 'error':\n stage = 'error'\n pct = 0\n break\n }\n onProgress({\n stage,\n progress: pct,\n message: progress.message || '',\n bytesRead: progress.bytesRead,\n totalBytes: progress.totalBytes,\n messagesProcessed: progress.messagesProcessed,\n })\n }\n : () => {}\n\n const deps = buildStreamImportDeps(dbManager, progressAdapter)\n const result = await streamingImport(filePath, deps, formatOptions)\n if (!result.success || !result.sessionId) return result\n\n try {\n dbManager.raiseCurrentChatDbCompatibilityGate()\n } catch (error) {\n deleteSessionDatabase(dbManager, result.sessionId)\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n diagnostics: result.diagnostics,\n }\n }\n\n return result\n}\n\n// ==================== Incremental import ====================\n\nfunction buildIncrementalDeps(\n dbManager: DatabaseManager,\n onProgress?: ImportProgressCallback,\n onCompatibilityError?: (error: DataDirCompatibilityError) => void\n): IncrementalImportDeps {\n return {\n openDatabase(sessionId: string, readonly?: boolean) {\n try {\n return dbManager.openRawSessionDatabase(sessionId, { readonly: readonly ?? false })\n } catch (error) {\n if (error instanceof DataDirCompatibilityError) onCompatibilityError?.(error)\n throw error\n }\n },\n onProgress: onProgress ?? (() => {}),\n postImportHook(db) {\n try {\n generateIncrementalSessionIndex(db)\n } catch {\n /* non-fatal */\n }\n },\n }\n}\n\nexport async function incrementalImport(\n dbManager: DatabaseManager,\n sessionId: string,\n filePath: string,\n options?: ImportOptions & { onProgress?: ImportProgressCallback }\n): Promise<IncrementalImportResult> {\n const { onProgress, ...importOpts } = options || {}\n let compatibilityError: DataDirCompatibilityError | null = null\n const result = await sharedIncrementalImport(\n sessionId,\n filePath,\n buildIncrementalDeps(dbManager, onProgress, (error) => {\n compatibilityError = error\n }),\n importOpts\n )\n if (compatibilityError) throw compatibilityError\n if (!result.success) return result\n\n try {\n dbManager.raiseCurrentChatDbCompatibilityGate()\n } catch (error) {\n return {\n ...result,\n success: false,\n newMessageCount: 0,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n\n return result\n}\n\nexport async function analyzeIncrementalImport(\n dbManager: DatabaseManager,\n sessionId: string,\n filePath: string,\n onProgress?: ImportProgressCallback\n): Promise<IncrementalAnalyzeResult> {\n let compatibilityError: DataDirCompatibilityError | null = null\n const result = await sharedAnalyzeIncremental(\n sessionId,\n filePath,\n buildIncrementalDeps(dbManager, onProgress, (error) => {\n compatibilityError = error\n })\n )\n if (compatibilityError) throw compatibilityError\n return result\n}\n\nexport async function analyzeNewImport(\n filePath: string,\n onProgress?: ImportProgressCallback\n): Promise<AnalyzeNewImportResult> {\n return sharedAnalyzeNewImport(filePath, onProgress ?? (() => {}))\n}\n\n// ==================== Re-exports from parser ====================\n\nexport {\n parserDetectFormat as detectFormat,\n detectAllFormats,\n getFormatFeatureById,\n parserGetSupportedFormats as getSupportedFormats,\n parserScanMultiChatFile as scanMultiChatFile,\n findEntryFileInDirectory,\n}\nexport type { FormatFeature, MultiChatInfo, ParseProgress }\nexport type {\n StreamImportResult,\n IncrementalImportResult,\n IncrementalAnalyzeResult,\n AnalyzeNewImportResult,\n ImportOptions,\n}\n"],"mappings":"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","names":["incrementalImport","analyzeIncrementalImport","analyzeNewImport"]}
|
package/dist/chunk-ST5APJB2.mjs
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
import { createRequire as __createRequire } from 'module';
|
|
2
|
-
import { dirname as __pathDirname } from 'path';
|
|
3
|
-
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
4
|
-
const require = __createRequire(import.meta.url);
|
|
5
|
-
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname = __pathDirname(__filename);
|
|
7
|
-
import {
|
|
8
|
-
writeParseResultToDb
|
|
9
|
-
} from "./chunk-DHRSLYUM.mjs";
|
|
10
|
-
import {
|
|
11
|
-
CHAT_DB_SCHEMA,
|
|
12
|
-
FTS_TABLE_SCHEMA,
|
|
13
|
-
buildMemberIdMap,
|
|
14
|
-
generateMessageKey
|
|
15
|
-
} from "./chunk-B2VLK3FB.mjs";
|
|
16
|
-
|
|
17
|
-
// src/import/chatlab-reader.ts
|
|
18
|
-
import * as fs from "fs";
|
|
19
|
-
import * as readline from "readline";
|
|
20
|
-
function detectFormat(filePath) {
|
|
21
|
-
const ext = filePath.toLowerCase();
|
|
22
|
-
if (ext.endsWith(".jsonl")) {
|
|
23
|
-
const head = fs.readFileSync(filePath, { encoding: "utf-8", flag: "r" }).slice(0, 1024);
|
|
24
|
-
if (head.includes('"_type"') && head.includes('"chatlab"')) {
|
|
25
|
-
return "chatlab-jsonl";
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
if (ext.endsWith(".json")) {
|
|
30
|
-
const head = fs.readFileSync(filePath, { encoding: "utf-8", flag: "r" }).slice(0, 4096);
|
|
31
|
-
if (/"chatlab"\s*:\s*\{/.test(head)) {
|
|
32
|
-
return "chatlab-json";
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
async function parseChatLabJson(filePath, onProgress) {
|
|
39
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
40
|
-
const data = JSON.parse(raw);
|
|
41
|
-
if (!data.chatlab || typeof data.chatlab !== "object") {
|
|
42
|
-
throw new Error('Invalid ChatLab JSON: missing "chatlab" field');
|
|
43
|
-
}
|
|
44
|
-
const meta = {
|
|
45
|
-
name: data.meta?.name || "\u672A\u77E5",
|
|
46
|
-
platform: data.meta?.platform || "unknown",
|
|
47
|
-
type: data.meta?.type || "group",
|
|
48
|
-
groupId: data.meta?.groupId,
|
|
49
|
-
groupAvatar: data.meta?.groupAvatar,
|
|
50
|
-
ownerId: data.meta?.ownerId
|
|
51
|
-
};
|
|
52
|
-
const members = (data.members || []).map((m) => ({
|
|
53
|
-
platformId: String(m.platformId || ""),
|
|
54
|
-
accountName: String(m.accountName || m.platformId || ""),
|
|
55
|
-
groupNickname: m.groupNickname ? String(m.groupNickname) : void 0,
|
|
56
|
-
avatar: m.avatar ? String(m.avatar) : void 0,
|
|
57
|
-
roles: m.roles
|
|
58
|
-
}));
|
|
59
|
-
const rawMessages = data.messages || [];
|
|
60
|
-
const totalMessages = rawMessages.length;
|
|
61
|
-
const messages = rawMessages.map((msg, i) => {
|
|
62
|
-
if (onProgress && i % 1e4 === 0) {
|
|
63
|
-
onProgress(i, totalMessages);
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
platformMessageId: msg.platformMessageId ? String(msg.platformMessageId) : void 0,
|
|
67
|
-
senderPlatformId: String(msg.sender || ""),
|
|
68
|
-
senderAccountName: String(msg.accountName || msg.sender || ""),
|
|
69
|
-
senderGroupNickname: msg.groupNickname ? String(msg.groupNickname) : void 0,
|
|
70
|
-
timestamp: Number(msg.timestamp),
|
|
71
|
-
type: Number(msg.type ?? 0),
|
|
72
|
-
content: msg.content != null ? String(msg.content) : null,
|
|
73
|
-
replyToMessageId: msg.replyToMessageId ? String(msg.replyToMessageId) : void 0
|
|
74
|
-
};
|
|
75
|
-
});
|
|
76
|
-
if (onProgress) onProgress(totalMessages, totalMessages);
|
|
77
|
-
if (members.length === 0) {
|
|
78
|
-
collectMembersFromMessages(messages, members);
|
|
79
|
-
}
|
|
80
|
-
return { meta, members, messages };
|
|
81
|
-
}
|
|
82
|
-
async function parseChatLabJsonl(filePath, onProgress) {
|
|
83
|
-
const meta = { name: "\u672A\u77E5", platform: "unknown", type: "group" };
|
|
84
|
-
const members = [];
|
|
85
|
-
const messages = [];
|
|
86
|
-
const stat = fs.statSync(filePath);
|
|
87
|
-
let bytesRead = 0;
|
|
88
|
-
const rl = readline.createInterface({
|
|
89
|
-
input: fs.createReadStream(filePath, "utf-8"),
|
|
90
|
-
crlfDelay: Infinity
|
|
91
|
-
});
|
|
92
|
-
for await (const line of rl) {
|
|
93
|
-
bytesRead += Buffer.byteLength(line, "utf-8") + 1;
|
|
94
|
-
const trimmed = line.trim();
|
|
95
|
-
if (!trimmed) continue;
|
|
96
|
-
let obj;
|
|
97
|
-
try {
|
|
98
|
-
obj = JSON.parse(trimmed);
|
|
99
|
-
} catch {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
const type = obj._type;
|
|
103
|
-
if (type === "header" || !type && obj.chatlab) {
|
|
104
|
-
const m = obj.meta;
|
|
105
|
-
if (m) {
|
|
106
|
-
meta.name = String(m.name || "\u672A\u77E5");
|
|
107
|
-
meta.platform = String(m.platform || "unknown");
|
|
108
|
-
meta.type = String(m.type || "group");
|
|
109
|
-
if (m.groupId) meta.groupId = String(m.groupId);
|
|
110
|
-
if (m.groupAvatar) meta.groupAvatar = String(m.groupAvatar);
|
|
111
|
-
if (m.ownerId) meta.ownerId = String(m.ownerId);
|
|
112
|
-
}
|
|
113
|
-
} else if (type === "member") {
|
|
114
|
-
members.push({
|
|
115
|
-
platformId: String(obj.platformId || ""),
|
|
116
|
-
accountName: String(obj.accountName || obj.platformId || ""),
|
|
117
|
-
groupNickname: obj.groupNickname ? String(obj.groupNickname) : void 0,
|
|
118
|
-
avatar: obj.avatar ? String(obj.avatar) : void 0,
|
|
119
|
-
roles: obj.roles
|
|
120
|
-
});
|
|
121
|
-
} else if (type === "message") {
|
|
122
|
-
messages.push({
|
|
123
|
-
platformMessageId: obj.platformMessageId ? String(obj.platformMessageId) : void 0,
|
|
124
|
-
senderPlatformId: String(obj.sender || obj.senderPlatformId || ""),
|
|
125
|
-
senderAccountName: String(obj.accountName || obj.senderAccountName || obj.sender || ""),
|
|
126
|
-
senderGroupNickname: obj.groupNickname ? String(obj.groupNickname) : obj.senderGroupNickname ? String(obj.senderGroupNickname) : void 0,
|
|
127
|
-
timestamp: Number(obj.timestamp),
|
|
128
|
-
type: Number(obj.type ?? 0),
|
|
129
|
-
content: obj.content != null ? String(obj.content) : null,
|
|
130
|
-
replyToMessageId: obj.replyToMessageId ? String(obj.replyToMessageId) : void 0
|
|
131
|
-
});
|
|
132
|
-
if (onProgress && messages.length % 1e4 === 0) {
|
|
133
|
-
onProgress(bytesRead, stat.size);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (onProgress) onProgress(stat.size, stat.size);
|
|
138
|
-
if (members.length === 0) {
|
|
139
|
-
collectMembersFromMessages(messages, members);
|
|
140
|
-
}
|
|
141
|
-
return { meta, members, messages };
|
|
142
|
-
}
|
|
143
|
-
function collectMembersFromMessages(messages, members) {
|
|
144
|
-
const seen = /* @__PURE__ */ new Set();
|
|
145
|
-
for (const msg of messages) {
|
|
146
|
-
if (!seen.has(msg.senderPlatformId)) {
|
|
147
|
-
seen.add(msg.senderPlatformId);
|
|
148
|
-
members.push({
|
|
149
|
-
platformId: msg.senderPlatformId,
|
|
150
|
-
accountName: msg.senderAccountName || msg.senderPlatformId,
|
|
151
|
-
groupNickname: msg.senderGroupNickname
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
async function parseFile(filePath, onProgress) {
|
|
157
|
-
const format = detectFormat(filePath);
|
|
158
|
-
if (!format) {
|
|
159
|
-
throw new Error(`Unsupported file format: ${filePath}. Only ChatLab JSON (.json) and JSONL (.jsonl) are supported.`);
|
|
160
|
-
}
|
|
161
|
-
if (format === "chatlab-json") {
|
|
162
|
-
return parseChatLabJson(filePath, onProgress);
|
|
163
|
-
} else {
|
|
164
|
-
return parseChatLabJsonl(filePath, onProgress);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// src/import/importer.ts
|
|
169
|
-
import * as fs2 from "fs";
|
|
170
|
-
import * as crypto from "crypto";
|
|
171
|
-
function generateSessionId() {
|
|
172
|
-
const ts = Date.now();
|
|
173
|
-
const rand = crypto.randomBytes(4).toString("hex");
|
|
174
|
-
return `chat_${ts}_${rand}`;
|
|
175
|
-
}
|
|
176
|
-
function fullImport(db, data, onProgress) {
|
|
177
|
-
db.exec(CHAT_DB_SCHEMA);
|
|
178
|
-
onProgress?.(`\u5199\u5165 ${data.members.length} \u4E2A\u6210\u5458, ${data.messages.length} \u6761\u6D88\u606F...`);
|
|
179
|
-
const stats = writeParseResultToDb(db, data.meta, data.members, data.messages);
|
|
180
|
-
buildFts(db, onProgress);
|
|
181
|
-
return { messageCount: stats.messageCount, memberCount: stats.memberCount, duplicateCount: stats.skippedCount };
|
|
182
|
-
}
|
|
183
|
-
function incrementalImport(db, data, onProgress) {
|
|
184
|
-
onProgress?.("\u52A0\u8F7D\u53BB\u91CD\u7D22\u5F15...");
|
|
185
|
-
const existingKeys = /* @__PURE__ */ new Set();
|
|
186
|
-
const existingPmids = db.prepare("SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL").all();
|
|
187
|
-
for (const row of existingPmids) {
|
|
188
|
-
existingKeys.add(`pmid:${row.platform_message_id}`);
|
|
189
|
-
}
|
|
190
|
-
const existingHashes = db.prepare(
|
|
191
|
-
`SELECT msg.ts, m.platform_id, msg.content
|
|
192
|
-
FROM message msg
|
|
193
|
-
JOIN member m ON msg.sender_id = m.id
|
|
194
|
-
WHERE msg.platform_message_id IS NULL`
|
|
195
|
-
).all();
|
|
196
|
-
for (const row of existingHashes) {
|
|
197
|
-
existingKeys.add(`hash:${generateMessageKey(row.ts, row.platform_id, row.content)}`);
|
|
198
|
-
}
|
|
199
|
-
onProgress?.(`\u53BB\u91CD\u7D22\u5F15: ${existingKeys.size} \u6761\u5DF2\u6709\u6D88\u606F`);
|
|
200
|
-
const upsertMember = db.prepare(
|
|
201
|
-
`INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles)
|
|
202
|
-
VALUES (?, ?, ?, ?, ?)
|
|
203
|
-
ON CONFLICT(platform_id) DO UPDATE SET
|
|
204
|
-
account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name),
|
|
205
|
-
group_nickname = COALESCE(excluded.group_nickname, group_nickname)`
|
|
206
|
-
);
|
|
207
|
-
let newMembers = 0;
|
|
208
|
-
for (const m of data.members) {
|
|
209
|
-
const result = upsertMember.run(
|
|
210
|
-
m.platformId,
|
|
211
|
-
m.accountName || m.platformId,
|
|
212
|
-
m.groupNickname || null,
|
|
213
|
-
m.avatar || null,
|
|
214
|
-
m.roles ? JSON.stringify(m.roles) : "[]"
|
|
215
|
-
);
|
|
216
|
-
if (result.changes > 0) newMembers++;
|
|
217
|
-
}
|
|
218
|
-
const memberIdMap = buildMemberIdMap(db);
|
|
219
|
-
const insertMsg = db.prepare(
|
|
220
|
-
`INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)
|
|
221
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
222
|
-
);
|
|
223
|
-
const insertMemberMinimal = db.prepare(`INSERT OR IGNORE INTO member (platform_id, account_name) VALUES (?, ?)`);
|
|
224
|
-
let written = 0;
|
|
225
|
-
let duplicates = 0;
|
|
226
|
-
const BATCH = 5e3;
|
|
227
|
-
for (let i = 0; i < data.messages.length; i += BATCH) {
|
|
228
|
-
const batch = data.messages.slice(i, i + BATCH);
|
|
229
|
-
db.transaction(() => {
|
|
230
|
-
for (const msg of batch) {
|
|
231
|
-
if (isDuplicate(msg, existingKeys)) {
|
|
232
|
-
duplicates++;
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
let senderId = memberIdMap.get(msg.senderPlatformId);
|
|
236
|
-
if (!senderId) {
|
|
237
|
-
insertMemberMinimal.run(msg.senderPlatformId, msg.senderAccountName || msg.senderPlatformId);
|
|
238
|
-
senderId = db.prepare("SELECT id FROM member WHERE platform_id = ?").get(msg.senderPlatformId)?.id;
|
|
239
|
-
if (senderId) memberIdMap.set(msg.senderPlatformId, senderId);
|
|
240
|
-
}
|
|
241
|
-
if (!senderId) continue;
|
|
242
|
-
insertMsg.run(
|
|
243
|
-
senderId,
|
|
244
|
-
msg.senderAccountName || null,
|
|
245
|
-
msg.senderGroupNickname || null,
|
|
246
|
-
msg.timestamp,
|
|
247
|
-
msg.type,
|
|
248
|
-
msg.content,
|
|
249
|
-
msg.replyToMessageId || null,
|
|
250
|
-
msg.platformMessageId || null
|
|
251
|
-
);
|
|
252
|
-
written++;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
if (onProgress && (i + BATCH) % 5e4 < BATCH) {
|
|
256
|
-
onProgress(
|
|
257
|
-
`\u5DF2\u5904\u7406 ${Math.min(i + BATCH, data.messages.length)} / ${data.messages.length} \u6761\u6D88\u606F (\u65B0\u589E ${written}, \u91CD\u590D ${duplicates})`
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
if (written > 0) {
|
|
262
|
-
insertFtsEntries(db, onProgress);
|
|
263
|
-
db.prepare("UPDATE meta SET imported_at = ?").run(Math.floor(Date.now() / 1e3));
|
|
264
|
-
}
|
|
265
|
-
return { messageCount: written, memberCount: newMembers, duplicateCount: duplicates };
|
|
266
|
-
}
|
|
267
|
-
function isDuplicate(msg, existingKeys) {
|
|
268
|
-
if (msg.platformMessageId) {
|
|
269
|
-
return existingKeys.has(`pmid:${msg.platformMessageId}`);
|
|
270
|
-
}
|
|
271
|
-
const key = generateMessageKey(msg.timestamp, msg.senderPlatformId, msg.content);
|
|
272
|
-
return existingKeys.has(`hash:${key}`);
|
|
273
|
-
}
|
|
274
|
-
function buildFts(db, onProgress) {
|
|
275
|
-
onProgress?.("\u521B\u5EFA\u5168\u6587\u641C\u7D22\u7D22\u5F15...");
|
|
276
|
-
db.exec(FTS_TABLE_SCHEMA);
|
|
277
|
-
const textMessages = db.prepare("SELECT id, content FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''").all();
|
|
278
|
-
const insertFts = db.prepare("INSERT INTO message_fts(rowid, content) VALUES (?, ?)");
|
|
279
|
-
const BATCH = 5e3;
|
|
280
|
-
for (let i = 0; i < textMessages.length; i += BATCH) {
|
|
281
|
-
const batch = textMessages.slice(i, i + BATCH);
|
|
282
|
-
db.transaction(() => {
|
|
283
|
-
for (const row of batch) {
|
|
284
|
-
insertFts.run(row.id, row.content);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
function insertFtsEntries(db, onProgress) {
|
|
290
|
-
try {
|
|
291
|
-
db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='message_fts'").get();
|
|
292
|
-
} catch {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const hasFts = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message_fts'").get();
|
|
296
|
-
if (!hasFts || hasFts.cnt === 0) return;
|
|
297
|
-
onProgress?.("\u66F4\u65B0\u5168\u6587\u641C\u7D22\u7D22\u5F15...");
|
|
298
|
-
const maxRowid = db.prepare("SELECT MAX(rowid) as m FROM message_fts").get()?.m ?? 0;
|
|
299
|
-
const newRows = db.prepare("SELECT id, content FROM message WHERE type = 0 AND content IS NOT NULL AND content != '' AND id > ?").all(maxRowid);
|
|
300
|
-
if (newRows.length === 0) return;
|
|
301
|
-
const insertFts = db.prepare("INSERT INTO message_fts(rowid, content) VALUES (?, ?)");
|
|
302
|
-
db.transaction(() => {
|
|
303
|
-
for (const row of newRows) {
|
|
304
|
-
insertFts.run(row.id, row.content);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async function importData(dbManager, data, options) {
|
|
309
|
-
const sessionId = options?.sessionId || generateSessionId();
|
|
310
|
-
const dbPath = dbManager.getDbPath(sessionId);
|
|
311
|
-
const exists = fs2.existsSync(dbPath);
|
|
312
|
-
if (options?.dryRun) {
|
|
313
|
-
if (exists) {
|
|
314
|
-
const db = dbManager.open(sessionId);
|
|
315
|
-
if (!db)
|
|
316
|
-
return {
|
|
317
|
-
success: false,
|
|
318
|
-
sessionId,
|
|
319
|
-
created: false,
|
|
320
|
-
messageCount: 0,
|
|
321
|
-
memberCount: 0,
|
|
322
|
-
duplicateCount: 0,
|
|
323
|
-
error: "Failed to open database"
|
|
324
|
-
};
|
|
325
|
-
const existingCount = db.prepare("SELECT COUNT(*) as cnt FROM message").get().cnt;
|
|
326
|
-
return {
|
|
327
|
-
success: true,
|
|
328
|
-
sessionId,
|
|
329
|
-
created: false,
|
|
330
|
-
messageCount: data.messages.length,
|
|
331
|
-
memberCount: data.members.length,
|
|
332
|
-
duplicateCount: 0,
|
|
333
|
-
error: `Dry run: ${data.messages.length} messages to import into existing session (${existingCount} existing messages)`
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
return {
|
|
337
|
-
success: true,
|
|
338
|
-
sessionId,
|
|
339
|
-
created: true,
|
|
340
|
-
messageCount: data.messages.length,
|
|
341
|
-
memberCount: data.members.length,
|
|
342
|
-
duplicateCount: 0,
|
|
343
|
-
error: `Dry run: would create new session with ${data.messages.length} messages`
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
try {
|
|
347
|
-
if (exists) {
|
|
348
|
-
const db2 = dbManager.openRawSessionDatabase(sessionId, { readonly: false });
|
|
349
|
-
let result2;
|
|
350
|
-
try {
|
|
351
|
-
result2 = incrementalImport(db2, data, options?.onProgress);
|
|
352
|
-
} finally {
|
|
353
|
-
db2.close();
|
|
354
|
-
}
|
|
355
|
-
dbManager.raiseCurrentChatDbCompatibilityGate();
|
|
356
|
-
return { success: true, sessionId, created: false, ...result2 };
|
|
357
|
-
}
|
|
358
|
-
const db = dbManager.openRawSessionDatabase(sessionId, { create: true });
|
|
359
|
-
let result;
|
|
360
|
-
try {
|
|
361
|
-
result = fullImport(db, data, options?.onProgress);
|
|
362
|
-
} finally {
|
|
363
|
-
db.close();
|
|
364
|
-
}
|
|
365
|
-
dbManager.raiseCurrentChatDbCompatibilityGate();
|
|
366
|
-
return { success: true, sessionId, created: true, ...result };
|
|
367
|
-
} catch (err) {
|
|
368
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
369
|
-
if (!exists) {
|
|
370
|
-
dbManager.deleteSessionDatabaseFiles(sessionId);
|
|
371
|
-
}
|
|
372
|
-
return {
|
|
373
|
-
success: false,
|
|
374
|
-
sessionId,
|
|
375
|
-
created: false,
|
|
376
|
-
messageCount: 0,
|
|
377
|
-
memberCount: 0,
|
|
378
|
-
duplicateCount: 0,
|
|
379
|
-
error: message
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export {
|
|
385
|
-
detectFormat,
|
|
386
|
-
parseFile,
|
|
387
|
-
importData
|
|
388
|
-
};
|
|
389
|
-
//# sourceMappingURL=chunk-ST5APJB2.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/import/chatlab-reader.ts","../src/import/importer.ts"],"sourcesContent":["/**\n * ChatLab 格式读取器\n *\n * 支持 ChatLab JSON (.json) 和 ChatLab JSONL (.jsonl) 两种格式。\n * 不依赖 stream-json,使用原生 readline 和 JSON.parse 实现。\n */\n\nimport * as fs from 'fs'\nimport * as readline from 'readline'\n\nexport interface ImportMeta {\n name: string\n platform: string\n type: string\n groupId?: string\n groupAvatar?: string\n ownerId?: string\n}\n\nexport interface ImportMember {\n platformId: string\n accountName: string\n groupNickname?: string\n avatar?: string\n roles?: Array<{ id: string; name?: string }>\n}\n\nexport interface ImportMessage {\n platformMessageId?: string\n senderPlatformId: string\n senderAccountName: string\n senderGroupNickname?: string\n timestamp: number\n type: number\n content: string | null\n replyToMessageId?: string\n}\n\nexport interface ParsedData {\n meta: ImportMeta\n members: ImportMember[]\n messages: ImportMessage[]\n}\n\nexport type ProgressCallback = (processed: number, total: number) => void\n\n/**\n * 检测文件是否为 ChatLab 格式\n */\nexport function detectFormat(filePath: string): 'chatlab-json' | 'chatlab-jsonl' | null {\n const ext = filePath.toLowerCase()\n if (ext.endsWith('.jsonl')) {\n const head = fs.readFileSync(filePath, { encoding: 'utf-8', flag: 'r' }).slice(0, 1024)\n if (head.includes('\"_type\"') && head.includes('\"chatlab\"')) {\n return 'chatlab-jsonl'\n }\n return null\n }\n if (ext.endsWith('.json')) {\n const head = fs.readFileSync(filePath, { encoding: 'utf-8', flag: 'r' }).slice(0, 4096)\n if (/\"chatlab\"\\s*:\\s*\\{/.test(head)) {\n return 'chatlab-json'\n }\n return null\n }\n return null\n}\n\n/**\n * 解析 ChatLab JSON 格式文件\n */\nexport async function parseChatLabJson(filePath: string, onProgress?: ProgressCallback): Promise<ParsedData> {\n const raw = fs.readFileSync(filePath, 'utf-8')\n const data = JSON.parse(raw)\n\n if (!data.chatlab || typeof data.chatlab !== 'object') {\n throw new Error('Invalid ChatLab JSON: missing \"chatlab\" field')\n }\n\n const meta: ImportMeta = {\n name: data.meta?.name || '未知',\n platform: data.meta?.platform || 'unknown',\n type: data.meta?.type || 'group',\n groupId: data.meta?.groupId,\n groupAvatar: data.meta?.groupAvatar,\n ownerId: data.meta?.ownerId,\n }\n\n const members: ImportMember[] = (data.members || []).map((m: Record<string, unknown>) => ({\n platformId: String(m.platformId || ''),\n accountName: String(m.accountName || m.platformId || ''),\n groupNickname: m.groupNickname ? String(m.groupNickname) : undefined,\n avatar: m.avatar ? String(m.avatar) : undefined,\n roles: m.roles as ImportMember['roles'],\n }))\n\n const rawMessages = data.messages || []\n const totalMessages = rawMessages.length\n\n const messages: ImportMessage[] = rawMessages.map((msg: Record<string, unknown>, i: number) => {\n if (onProgress && i % 10000 === 0) {\n onProgress(i, totalMessages)\n }\n return {\n platformMessageId: msg.platformMessageId ? String(msg.platformMessageId) : undefined,\n senderPlatformId: String(msg.sender || ''),\n senderAccountName: String(msg.accountName || msg.sender || ''),\n senderGroupNickname: msg.groupNickname ? String(msg.groupNickname) : undefined,\n timestamp: Number(msg.timestamp),\n type: Number(msg.type ?? 0),\n content: msg.content != null ? String(msg.content) : null,\n replyToMessageId: msg.replyToMessageId ? String(msg.replyToMessageId) : undefined,\n }\n })\n\n if (onProgress) onProgress(totalMessages, totalMessages)\n\n if (members.length === 0) {\n collectMembersFromMessages(messages, members)\n }\n\n return { meta, members, messages }\n}\n\n/**\n * 解析 ChatLab JSONL 格式文件\n */\nexport async function parseChatLabJsonl(filePath: string, onProgress?: ProgressCallback): Promise<ParsedData> {\n const meta: ImportMeta = { name: '未知', platform: 'unknown', type: 'group' }\n const members: ImportMember[] = []\n const messages: ImportMessage[] = []\n\n const stat = fs.statSync(filePath)\n let bytesRead = 0\n\n const rl = readline.createInterface({\n input: fs.createReadStream(filePath, 'utf-8'),\n crlfDelay: Infinity,\n })\n\n for await (const line of rl) {\n bytesRead += Buffer.byteLength(line, 'utf-8') + 1\n\n const trimmed = line.trim()\n if (!trimmed) continue\n\n let obj: Record<string, unknown>\n try {\n obj = JSON.parse(trimmed)\n } catch {\n continue\n }\n\n const type = obj._type as string\n\n if (type === 'header' || (!type && obj.chatlab)) {\n const m = obj.meta as Record<string, unknown> | undefined\n if (m) {\n meta.name = String(m.name || '未知')\n meta.platform = String(m.platform || 'unknown')\n meta.type = String(m.type || 'group')\n if (m.groupId) meta.groupId = String(m.groupId)\n if (m.groupAvatar) meta.groupAvatar = String(m.groupAvatar)\n if (m.ownerId) meta.ownerId = String(m.ownerId)\n }\n } else if (type === 'member') {\n members.push({\n platformId: String(obj.platformId || ''),\n accountName: String(obj.accountName || obj.platformId || ''),\n groupNickname: obj.groupNickname ? String(obj.groupNickname) : undefined,\n avatar: obj.avatar ? String(obj.avatar) : undefined,\n roles: obj.roles as ImportMember['roles'],\n })\n } else if (type === 'message') {\n messages.push({\n platformMessageId: obj.platformMessageId ? String(obj.platformMessageId) : undefined,\n senderPlatformId: String(obj.sender || obj.senderPlatformId || ''),\n senderAccountName: String(obj.accountName || obj.senderAccountName || obj.sender || ''),\n senderGroupNickname: obj.groupNickname\n ? String(obj.groupNickname)\n : obj.senderGroupNickname\n ? String(obj.senderGroupNickname)\n : undefined,\n timestamp: Number(obj.timestamp),\n type: Number(obj.type ?? 0),\n content: obj.content != null ? String(obj.content) : null,\n replyToMessageId: obj.replyToMessageId ? String(obj.replyToMessageId) : undefined,\n })\n\n if (onProgress && messages.length % 10000 === 0) {\n onProgress(bytesRead, stat.size)\n }\n }\n }\n\n if (onProgress) onProgress(stat.size, stat.size)\n\n if (members.length === 0) {\n collectMembersFromMessages(messages, members)\n }\n\n return { meta, members, messages }\n}\n\nfunction collectMembersFromMessages(messages: ImportMessage[], members: ImportMember[]): void {\n const seen = new Set<string>()\n for (const msg of messages) {\n if (!seen.has(msg.senderPlatformId)) {\n seen.add(msg.senderPlatformId)\n members.push({\n platformId: msg.senderPlatformId,\n accountName: msg.senderAccountName || msg.senderPlatformId,\n groupNickname: msg.senderGroupNickname,\n })\n }\n }\n}\n\n/**\n * 统一入口:自动检测格式并解析\n */\nexport async function parseFile(filePath: string, onProgress?: ProgressCallback): Promise<ParsedData> {\n const format = detectFormat(filePath)\n if (!format) {\n throw new Error(`Unsupported file format: ${filePath}. Only ChatLab JSON (.json) and JSONL (.jsonl) are supported.`)\n }\n\n if (format === 'chatlab-json') {\n return parseChatLabJson(filePath, onProgress)\n } else {\n return parseChatLabJsonl(filePath, onProgress)\n }\n}\n","/**\n * ChatLab 数据导入器\n *\n * 将解析后的数据写入 SQLite 数据库。\n * 支持新建导入和增量导入(基于 platform_message_id 或内容哈希去重)。\n */\n\nimport * as fs from 'fs'\nimport * as crypto from 'crypto'\nimport type { DatabaseAdapter } from '@openchatlab/core'\nimport { CHAT_DB_SCHEMA, FTS_TABLE_SCHEMA, generateMessageKey, buildMemberIdMap } from '@openchatlab/core'\nimport type { DatabaseManager } from '@openchatlab/node-runtime'\nimport { writeParseResultToDb } from '@openchatlab/node-runtime'\nimport type { ParsedData, ImportMessage } from './chatlab-reader'\n\nexport interface ImportResult {\n success: boolean\n sessionId: string\n created: boolean\n messageCount: number\n memberCount: number\n duplicateCount: number\n error?: string\n}\n\nexport interface ImportOptions {\n sessionId?: string\n dryRun?: boolean\n nativeBinding?: string\n}\n\nfunction generateSessionId(): string {\n const ts = Date.now()\n const rand = crypto.randomBytes(4).toString('hex')\n return `chat_${ts}_${rand}`\n}\n\n/**\n * 新建导入:创建新的会话数据库并导入全部数据\n * Delegates to shared writeParseResultToDb for data writing + name history.\n */\nfunction fullImport(\n db: DatabaseAdapter,\n data: ParsedData,\n onProgress?: (msg: string) => void\n): { messageCount: number; memberCount: number; duplicateCount: number } {\n db.exec(CHAT_DB_SCHEMA)\n\n onProgress?.(`写入 ${data.members.length} 个成员, ${data.messages.length} 条消息...`)\n const stats = writeParseResultToDb(db, data.meta, data.members, data.messages)\n\n buildFts(db, onProgress)\n\n return { messageCount: stats.messageCount, memberCount: stats.memberCount, duplicateCount: stats.skippedCount }\n}\n\n/**\n * 增量导入:向已有数据库追加新消息,自动去重\n */\nfunction incrementalImport(\n db: DatabaseAdapter,\n data: ParsedData,\n onProgress?: (msg: string) => void\n): { messageCount: number; memberCount: number; duplicateCount: number } {\n onProgress?.('加载去重索引...')\n const existingKeys = new Set<string>()\n\n const existingPmids = 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 existingPmids) {\n existingKeys.add(`pmid:${row.platform_message_id}`)\n }\n\n const existingHashes = db\n .prepare(\n `SELECT msg.ts, m.platform_id, msg.content\n FROM message msg\n JOIN member m ON msg.sender_id = m.id\n WHERE msg.platform_message_id IS NULL`\n )\n .all() as Array<{ ts: number; platform_id: string; content: string | null }>\n for (const row of existingHashes) {\n existingKeys.add(`hash:${generateMessageKey(row.ts, row.platform_id, row.content)}`)\n }\n\n onProgress?.(`去重索引: ${existingKeys.size} 条已有消息`)\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(excluded.group_nickname, group_nickname)`\n )\n\n let newMembers = 0\n for (const m of data.members) {\n const result = upsertMember.run(\n m.platformId,\n m.accountName || m.platformId,\n m.groupNickname || null,\n m.avatar || null,\n m.roles ? JSON.stringify(m.roles) : '[]'\n )\n if (result.changes > 0) newMembers++\n }\n\n const memberIdMap = buildMemberIdMap(db)\n\n const insertMsg = db.prepare(\n `INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n )\n const insertMemberMinimal = db.prepare(`INSERT OR IGNORE INTO member (platform_id, account_name) VALUES (?, ?)`)\n\n let written = 0\n let duplicates = 0\n const BATCH = 5000\n\n for (let i = 0; i < data.messages.length; i += BATCH) {\n const batch = data.messages.slice(i, i + BATCH)\n db.transaction(() => {\n for (const msg of batch) {\n if (isDuplicate(msg, existingKeys)) {\n duplicates++\n continue\n }\n\n let senderId = memberIdMap.get(msg.senderPlatformId)\n if (!senderId) {\n insertMemberMinimal.run(msg.senderPlatformId, msg.senderAccountName || msg.senderPlatformId)\n senderId = (\n db.prepare('SELECT id FROM member WHERE platform_id = ?').get(msg.senderPlatformId) as { id: number }\n )?.id\n if (senderId) memberIdMap.set(msg.senderPlatformId, senderId)\n }\n if (!senderId) continue\n\n insertMsg.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 written++\n }\n })\n\n if (onProgress && (i + BATCH) % 50000 < BATCH) {\n onProgress(\n `已处理 ${Math.min(i + BATCH, data.messages.length)} / ${data.messages.length} 条消息 (新增 ${written}, 重复 ${duplicates})`\n )\n }\n }\n\n if (written > 0) {\n insertFtsEntries(db, onProgress)\n db.prepare('UPDATE meta SET imported_at = ?').run(Math.floor(Date.now() / 1000))\n }\n\n return { messageCount: written, memberCount: newMembers, duplicateCount: duplicates }\n}\n\nfunction isDuplicate(msg: ImportMessage, existingKeys: Set<string>): boolean {\n if (msg.platformMessageId) {\n return existingKeys.has(`pmid:${msg.platformMessageId}`)\n }\n const key = generateMessageKey(msg.timestamp, msg.senderPlatformId, msg.content)\n return existingKeys.has(`hash:${key}`)\n}\n\nfunction buildFts(db: DatabaseAdapter, onProgress?: (msg: string) => void): void {\n onProgress?.('创建全文搜索索引...')\n db.exec(FTS_TABLE_SCHEMA)\n\n const textMessages = db\n .prepare(\"SELECT id, content FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''\")\n .all() as Array<{ id: number; content: string }>\n\n const insertFts = db.prepare('INSERT INTO message_fts(rowid, content) VALUES (?, ?)')\n const BATCH = 5000\n for (let i = 0; i < textMessages.length; i += BATCH) {\n const batch = textMessages.slice(i, i + BATCH)\n db.transaction(() => {\n for (const row of batch) {\n insertFts.run(row.id, row.content)\n }\n })\n }\n}\n\nfunction insertFtsEntries(db: DatabaseAdapter, onProgress?: (msg: string) => void): void {\n try {\n db.prepare(\"SELECT 1 FROM sqlite_master WHERE type='table' AND name='message_fts'\").get()\n } catch {\n return\n }\n\n const hasFts = db\n .prepare(\"SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message_fts'\")\n .get() as { cnt: number }\n if (!hasFts || hasFts.cnt === 0) return\n\n onProgress?.('更新全文搜索索引...')\n const maxRowid = (db.prepare('SELECT MAX(rowid) as m FROM message_fts').get() as { m: number | null })?.m ?? 0\n const newRows = db\n .prepare(\"SELECT id, content FROM message WHERE type = 0 AND content IS NOT NULL AND content != '' AND id > ?\")\n .all(maxRowid) as Array<{ id: number; content: string }>\n\n if (newRows.length === 0) return\n\n const insertFts = db.prepare('INSERT INTO message_fts(rowid, content) VALUES (?, ?)')\n db.transaction(() => {\n for (const row of newRows) {\n insertFts.run(row.id, row.content)\n }\n })\n}\n\n/**\n * 执行导入(新建或增量)\n */\nexport async function importData(\n dbManager: DatabaseManager,\n data: ParsedData,\n options?: ImportOptions & { onProgress?: (msg: string) => void }\n): Promise<ImportResult> {\n const sessionId = options?.sessionId || generateSessionId()\n const dbPath = dbManager.getDbPath(sessionId)\n const exists = fs.existsSync(dbPath)\n\n if (options?.dryRun) {\n if (exists) {\n const db = dbManager.open(sessionId)\n if (!db)\n return {\n success: false,\n sessionId,\n created: false,\n messageCount: 0,\n memberCount: 0,\n duplicateCount: 0,\n error: 'Failed to open database',\n }\n\n const existingCount = (db.prepare('SELECT COUNT(*) as cnt FROM message').get() as { cnt: number }).cnt\n return {\n success: true,\n sessionId,\n created: false,\n messageCount: data.messages.length,\n memberCount: data.members.length,\n duplicateCount: 0,\n error: `Dry run: ${data.messages.length} messages to import into existing session (${existingCount} existing messages)`,\n }\n }\n return {\n success: true,\n sessionId,\n created: true,\n messageCount: data.messages.length,\n memberCount: data.members.length,\n duplicateCount: 0,\n error: `Dry run: would create new session with ${data.messages.length} messages`,\n }\n }\n\n try {\n if (exists) {\n const db = dbManager.openRawSessionDatabase(sessionId, { readonly: false })\n let result: { messageCount: number; memberCount: number; duplicateCount: number }\n try {\n result = incrementalImport(db, data, options?.onProgress)\n } finally {\n db.close()\n }\n dbManager.raiseCurrentChatDbCompatibilityGate()\n return { success: true, sessionId, created: false, ...result }\n }\n\n const db = dbManager.openRawSessionDatabase(sessionId, { create: true })\n let result: { messageCount: number; memberCount: number; duplicateCount: number }\n try {\n result = fullImport(db, data, options?.onProgress)\n } finally {\n db.close()\n }\n dbManager.raiseCurrentChatDbCompatibilityGate()\n return { success: true, sessionId, created: true, ...result }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n if (!exists) {\n dbManager.deleteSessionDatabaseFiles(sessionId)\n }\n return {\n success: false,\n sessionId,\n created: false,\n messageCount: 0,\n memberCount: 0,\n duplicateCount: 0,\n error: message,\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAOA,YAAY,QAAQ;AACpB,YAAY,cAAc;AAyCnB,SAAS,aAAa,UAA2D;AACtF,QAAM,MAAM,SAAS,YAAY;AACjC,MAAI,IAAI,SAAS,QAAQ,GAAG;AAC1B,UAAM,OAAU,gBAAa,UAAU,EAAE,UAAU,SAAS,MAAM,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;AACtF,QAAI,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,WAAW,GAAG;AAC1D,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACA,MAAI,IAAI,SAAS,OAAO,GAAG;AACzB,UAAM,OAAU,gBAAa,UAAU,EAAE,UAAU,SAAS,MAAM,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;AACtF,QAAI,qBAAqB,KAAK,IAAI,GAAG;AACnC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAKA,eAAsB,iBAAiB,UAAkB,YAAoD;AAC3G,QAAM,MAAS,gBAAa,UAAU,OAAO;AAC7C,QAAM,OAAO,KAAK,MAAM,GAAG;AAE3B,MAAI,CAAC,KAAK,WAAW,OAAO,KAAK,YAAY,UAAU;AACrD,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,QAAM,OAAmB;AAAA,IACvB,MAAM,KAAK,MAAM,QAAQ;AAAA,IACzB,UAAU,KAAK,MAAM,YAAY;AAAA,IACjC,MAAM,KAAK,MAAM,QAAQ;AAAA,IACzB,SAAS,KAAK,MAAM;AAAA,IACpB,aAAa,KAAK,MAAM;AAAA,IACxB,SAAS,KAAK,MAAM;AAAA,EACtB;AAEA,QAAM,WAA2B,KAAK,WAAW,CAAC,GAAG,IAAI,CAAC,OAAgC;AAAA,IACxF,YAAY,OAAO,EAAE,cAAc,EAAE;AAAA,IACrC,aAAa,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE;AAAA,IACvD,eAAe,EAAE,gBAAgB,OAAO,EAAE,aAAa,IAAI;AAAA,IAC3D,QAAQ,EAAE,SAAS,OAAO,EAAE,MAAM,IAAI;AAAA,IACtC,OAAO,EAAE;AAAA,EACX,EAAE;AAEF,QAAM,cAAc,KAAK,YAAY,CAAC;AACtC,QAAM,gBAAgB,YAAY;AAElC,QAAM,WAA4B,YAAY,IAAI,CAAC,KAA8B,MAAc;AAC7F,QAAI,cAAc,IAAI,QAAU,GAAG;AACjC,iBAAW,GAAG,aAAa;AAAA,IAC7B;AACA,WAAO;AAAA,MACL,mBAAmB,IAAI,oBAAoB,OAAO,IAAI,iBAAiB,IAAI;AAAA,MAC3E,kBAAkB,OAAO,IAAI,UAAU,EAAE;AAAA,MACzC,mBAAmB,OAAO,IAAI,eAAe,IAAI,UAAU,EAAE;AAAA,MAC7D,qBAAqB,IAAI,gBAAgB,OAAO,IAAI,aAAa,IAAI;AAAA,MACrE,WAAW,OAAO,IAAI,SAAS;AAAA,MAC/B,MAAM,OAAO,IAAI,QAAQ,CAAC;AAAA,MAC1B,SAAS,IAAI,WAAW,OAAO,OAAO,IAAI,OAAO,IAAI;AAAA,MACrD,kBAAkB,IAAI,mBAAmB,OAAO,IAAI,gBAAgB,IAAI;AAAA,IAC1E;AAAA,EACF,CAAC;AAED,MAAI,WAAY,YAAW,eAAe,aAAa;AAEvD,MAAI,QAAQ,WAAW,GAAG;AACxB,+BAA2B,UAAU,OAAO;AAAA,EAC9C;AAEA,SAAO,EAAE,MAAM,SAAS,SAAS;AACnC;AAKA,eAAsB,kBAAkB,UAAkB,YAAoD;AAC5G,QAAM,OAAmB,EAAE,MAAM,gBAAM,UAAU,WAAW,MAAM,QAAQ;AAC1E,QAAM,UAA0B,CAAC;AACjC,QAAM,WAA4B,CAAC;AAEnC,QAAM,OAAU,YAAS,QAAQ;AACjC,MAAI,YAAY;AAEhB,QAAM,KAAc,yBAAgB;AAAA,IAClC,OAAU,oBAAiB,UAAU,OAAO;AAAA,IAC5C,WAAW;AAAA,EACb,CAAC;AAED,mBAAiB,QAAQ,IAAI;AAC3B,iBAAa,OAAO,WAAW,MAAM,OAAO,IAAI;AAEhD,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAEd,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,MAAM,OAAO;AAAA,IAC1B,QAAQ;AACN;AAAA,IACF;AAEA,UAAM,OAAO,IAAI;AAEjB,QAAI,SAAS,YAAa,CAAC,QAAQ,IAAI,SAAU;AAC/C,YAAM,IAAI,IAAI;AACd,UAAI,GAAG;AACL,aAAK,OAAO,OAAO,EAAE,QAAQ,cAAI;AACjC,aAAK,WAAW,OAAO,EAAE,YAAY,SAAS;AAC9C,aAAK,OAAO,OAAO,EAAE,QAAQ,OAAO;AACpC,YAAI,EAAE,QAAS,MAAK,UAAU,OAAO,EAAE,OAAO;AAC9C,YAAI,EAAE,YAAa,MAAK,cAAc,OAAO,EAAE,WAAW;AAC1D,YAAI,EAAE,QAAS,MAAK,UAAU,OAAO,EAAE,OAAO;AAAA,MAChD;AAAA,IACF,WAAW,SAAS,UAAU;AAC5B,cAAQ,KAAK;AAAA,QACX,YAAY,OAAO,IAAI,cAAc,EAAE;AAAA,QACvC,aAAa,OAAO,IAAI,eAAe,IAAI,cAAc,EAAE;AAAA,QAC3D,eAAe,IAAI,gBAAgB,OAAO,IAAI,aAAa,IAAI;AAAA,QAC/D,QAAQ,IAAI,SAAS,OAAO,IAAI,MAAM,IAAI;AAAA,QAC1C,OAAO,IAAI;AAAA,MACb,CAAC;AAAA,IACH,WAAW,SAAS,WAAW;AAC7B,eAAS,KAAK;AAAA,QACZ,mBAAmB,IAAI,oBAAoB,OAAO,IAAI,iBAAiB,IAAI;AAAA,QAC3E,kBAAkB,OAAO,IAAI,UAAU,IAAI,oBAAoB,EAAE;AAAA,QACjE,mBAAmB,OAAO,IAAI,eAAe,IAAI,qBAAqB,IAAI,UAAU,EAAE;AAAA,QACtF,qBAAqB,IAAI,gBACrB,OAAO,IAAI,aAAa,IACxB,IAAI,sBACF,OAAO,IAAI,mBAAmB,IAC9B;AAAA,QACN,WAAW,OAAO,IAAI,SAAS;AAAA,QAC/B,MAAM,OAAO,IAAI,QAAQ,CAAC;AAAA,QAC1B,SAAS,IAAI,WAAW,OAAO,OAAO,IAAI,OAAO,IAAI;AAAA,QACrD,kBAAkB,IAAI,mBAAmB,OAAO,IAAI,gBAAgB,IAAI;AAAA,MAC1E,CAAC;AAED,UAAI,cAAc,SAAS,SAAS,QAAU,GAAG;AAC/C,mBAAW,WAAW,KAAK,IAAI;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAY,YAAW,KAAK,MAAM,KAAK,IAAI;AAE/C,MAAI,QAAQ,WAAW,GAAG;AACxB,+BAA2B,UAAU,OAAO;AAAA,EAC9C;AAEA,SAAO,EAAE,MAAM,SAAS,SAAS;AACnC;AAEA,SAAS,2BAA2B,UAA2B,SAA+B;AAC5F,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,OAAO,UAAU;AAC1B,QAAI,CAAC,KAAK,IAAI,IAAI,gBAAgB,GAAG;AACnC,WAAK,IAAI,IAAI,gBAAgB;AAC7B,cAAQ,KAAK;AAAA,QACX,YAAY,IAAI;AAAA,QAChB,aAAa,IAAI,qBAAqB,IAAI;AAAA,QAC1C,eAAe,IAAI;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAKA,eAAsB,UAAU,UAAkB,YAAoD;AACpG,QAAM,SAAS,aAAa,QAAQ;AACpC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,4BAA4B,QAAQ,+DAA+D;AAAA,EACrH;AAEA,MAAI,WAAW,gBAAgB;AAC7B,WAAO,iBAAiB,UAAU,UAAU;AAAA,EAC9C,OAAO;AACL,WAAO,kBAAkB,UAAU,UAAU;AAAA,EAC/C;AACF;;;ACjOA,YAAYA,SAAQ;AACpB,YAAY,YAAY;AAuBxB,SAAS,oBAA4B;AACnC,QAAM,KAAK,KAAK,IAAI;AACpB,QAAM,OAAc,mBAAY,CAAC,EAAE,SAAS,KAAK;AACjD,SAAO,QAAQ,EAAE,IAAI,IAAI;AAC3B;AAMA,SAAS,WACP,IACA,MACA,YACuE;AACvE,KAAG,KAAK,cAAc;AAEtB,eAAa,gBAAM,KAAK,QAAQ,MAAM,wBAAS,KAAK,SAAS,MAAM,wBAAS;AAC5E,QAAM,QAAQ,qBAAqB,IAAI,KAAK,MAAM,KAAK,SAAS,KAAK,QAAQ;AAE7E,WAAS,IAAI,UAAU;AAEvB,SAAO,EAAE,cAAc,MAAM,cAAc,aAAa,MAAM,aAAa,gBAAgB,MAAM,aAAa;AAChH;AAKA,SAAS,kBACP,IACA,MACA,YACuE;AACvE,eAAa,yCAAW;AACxB,QAAM,eAAe,oBAAI,IAAY;AAErC,QAAM,gBAAgB,GACnB,QAAQ,+EAA+E,EACvF,IAAI;AACP,aAAW,OAAO,eAAe;AAC/B,iBAAa,IAAI,QAAQ,IAAI,mBAAmB,EAAE;AAAA,EACpD;AAEA,QAAM,iBAAiB,GACpB;AAAA,IACC;AAAA;AAAA;AAAA;AAAA,EAIF,EACC,IAAI;AACP,aAAW,OAAO,gBAAgB;AAChC,iBAAa,IAAI,QAAQ,mBAAmB,IAAI,IAAI,IAAI,aAAa,IAAI,OAAO,CAAC,EAAE;AAAA,EACrF;AAEA,eAAa,6BAAS,aAAa,IAAI,iCAAQ;AAE/C,QAAM,eAAe,GAAG;AAAA,IACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKF;AAEA,MAAI,aAAa;AACjB,aAAW,KAAK,KAAK,SAAS;AAC5B,UAAM,SAAS,aAAa;AAAA,MAC1B,EAAE;AAAA,MACF,EAAE,eAAe,EAAE;AAAA,MACnB,EAAE,iBAAiB;AAAA,MACnB,EAAE,UAAU;AAAA,MACZ,EAAE,QAAQ,KAAK,UAAU,EAAE,KAAK,IAAI;AAAA,IACtC;AACA,QAAI,OAAO,UAAU,EAAG;AAAA,EAC1B;AAEA,QAAM,cAAc,iBAAiB,EAAE;AAEvC,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA;AAAA,EAEF;AACA,QAAM,sBAAsB,GAAG,QAAQ,wEAAwE;AAE/G,MAAI,UAAU;AACd,MAAI,aAAa;AACjB,QAAM,QAAQ;AAEd,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK,OAAO;AACpD,UAAM,QAAQ,KAAK,SAAS,MAAM,GAAG,IAAI,KAAK;AAC9C,OAAG,YAAY,MAAM;AACnB,iBAAW,OAAO,OAAO;AACvB,YAAI,YAAY,KAAK,YAAY,GAAG;AAClC;AACA;AAAA,QACF;AAEA,YAAI,WAAW,YAAY,IAAI,IAAI,gBAAgB;AACnD,YAAI,CAAC,UAAU;AACb,8BAAoB,IAAI,IAAI,kBAAkB,IAAI,qBAAqB,IAAI,gBAAgB;AAC3F,qBACE,GAAG,QAAQ,6CAA6C,EAAE,IAAI,IAAI,gBAAgB,GACjF;AACH,cAAI,SAAU,aAAY,IAAI,IAAI,kBAAkB,QAAQ;AAAA,QAC9D;AACA,YAAI,CAAC,SAAU;AAEf,kBAAU;AAAA,UACR;AAAA,UACA,IAAI,qBAAqB;AAAA,UACzB,IAAI,uBAAuB;AAAA,UAC3B,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI,oBAAoB;AAAA,UACxB,IAAI,qBAAqB;AAAA,QAC3B;AACA;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI,eAAe,IAAI,SAAS,MAAQ,OAAO;AAC7C;AAAA,QACE,sBAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,MAAM,CAAC,MAAM,KAAK,SAAS,MAAM,qCAAY,OAAO,kBAAQ,UAAU;AAAA,MACjH;AAAA,IACF;AAAA,EACF;AAEA,MAAI,UAAU,GAAG;AACf,qBAAiB,IAAI,UAAU;AAC/B,OAAG,QAAQ,iCAAiC,EAAE,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;AAAA,EACjF;AAEA,SAAO,EAAE,cAAc,SAAS,aAAa,YAAY,gBAAgB,WAAW;AACtF;AAEA,SAAS,YAAY,KAAoB,cAAoC;AAC3E,MAAI,IAAI,mBAAmB;AACzB,WAAO,aAAa,IAAI,QAAQ,IAAI,iBAAiB,EAAE;AAAA,EACzD;AACA,QAAM,MAAM,mBAAmB,IAAI,WAAW,IAAI,kBAAkB,IAAI,OAAO;AAC/E,SAAO,aAAa,IAAI,QAAQ,GAAG,EAAE;AACvC;AAEA,SAAS,SAAS,IAAqB,YAA0C;AAC/E,eAAa,qDAAa;AAC1B,KAAG,KAAK,gBAAgB;AAExB,QAAM,eAAe,GAClB,QAAQ,0FAA0F,EAClG,IAAI;AAEP,QAAM,YAAY,GAAG,QAAQ,uDAAuD;AACpF,QAAM,QAAQ;AACd,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,OAAO;AACnD,UAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,KAAK;AAC7C,OAAG,YAAY,MAAM;AACnB,iBAAW,OAAO,OAAO;AACvB,kBAAU,IAAI,IAAI,IAAI,IAAI,OAAO;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,SAAS,iBAAiB,IAAqB,YAA0C;AACvF,MAAI;AACF,OAAG,QAAQ,uEAAuE,EAAE,IAAI;AAAA,EAC1F,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,SAAS,GACZ,QAAQ,qFAAqF,EAC7F,IAAI;AACP,MAAI,CAAC,UAAU,OAAO,QAAQ,EAAG;AAEjC,eAAa,qDAAa;AAC1B,QAAM,WAAY,GAAG,QAAQ,yCAAyC,EAAE,IAAI,GAA4B,KAAK;AAC7G,QAAM,UAAU,GACb,QAAQ,qGAAqG,EAC7G,IAAI,QAAQ;AAEf,MAAI,QAAQ,WAAW,EAAG;AAE1B,QAAM,YAAY,GAAG,QAAQ,uDAAuD;AACpF,KAAG,YAAY,MAAM;AACnB,eAAW,OAAO,SAAS;AACzB,gBAAU,IAAI,IAAI,IAAI,IAAI,OAAO;AAAA,IACnC;AAAA,EACF,CAAC;AACH;AAKA,eAAsB,WACpB,WACA,MACA,SACuB;AACvB,QAAM,YAAY,SAAS,aAAa,kBAAkB;AAC1D,QAAM,SAAS,UAAU,UAAU,SAAS;AAC5C,QAAM,SAAY,eAAW,MAAM;AAEnC,MAAI,SAAS,QAAQ;AACnB,QAAI,QAAQ;AACV,YAAM,KAAK,UAAU,KAAK,SAAS;AACnC,UAAI,CAAC;AACH,eAAO;AAAA,UACL,SAAS;AAAA,UACT;AAAA,UACA,SAAS;AAAA,UACT,cAAc;AAAA,UACd,aAAa;AAAA,UACb,gBAAgB;AAAA,UAChB,OAAO;AAAA,QACT;AAEF,YAAM,gBAAiB,GAAG,QAAQ,qCAAqC,EAAE,IAAI,EAAsB;AACnG,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,SAAS;AAAA,QACT,cAAc,KAAK,SAAS;AAAA,QAC5B,aAAa,KAAK,QAAQ;AAAA,QAC1B,gBAAgB;AAAA,QAChB,OAAO,YAAY,KAAK,SAAS,MAAM,8CAA8C,aAAa;AAAA,MACpG;AAAA,IACF;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,SAAS;AAAA,MACT,cAAc,KAAK,SAAS;AAAA,MAC5B,aAAa,KAAK,QAAQ;AAAA,MAC1B,gBAAgB;AAAA,MAChB,OAAO,0CAA0C,KAAK,SAAS,MAAM;AAAA,IACvE;AAAA,EACF;AAEA,MAAI;AACF,QAAI,QAAQ;AACV,YAAMC,MAAK,UAAU,uBAAuB,WAAW,EAAE,UAAU,MAAM,CAAC;AAC1E,UAAIC;AACJ,UAAI;AACF,QAAAA,UAAS,kBAAkBD,KAAI,MAAM,SAAS,UAAU;AAAA,MAC1D,UAAE;AACA,QAAAA,IAAG,MAAM;AAAA,MACX;AACA,gBAAU,oCAAoC;AAC9C,aAAO,EAAE,SAAS,MAAM,WAAW,SAAS,OAAO,GAAGC,QAAO;AAAA,IAC/D;AAEA,UAAM,KAAK,UAAU,uBAAuB,WAAW,EAAE,QAAQ,KAAK,CAAC;AACvE,QAAI;AACJ,QAAI;AACF,eAAS,WAAW,IAAI,MAAM,SAAS,UAAU;AAAA,IACnD,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AACA,cAAU,oCAAoC;AAC9C,WAAO,EAAE,SAAS,MAAM,WAAW,SAAS,MAAM,GAAG,OAAO;AAAA,EAC9D,SAAS,KAAc;AACrB,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,QAAI,CAAC,QAAQ;AACX,gBAAU,2BAA2B,SAAS;AAAA,IAChD;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,SAAS;AAAA,MACT,cAAc;AAAA,MACd,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,OAAO;AAAA,IACT;AAAA,EACF;AACF;","names":["fs","db","result"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../packages/node-runtime/src/semantic-index/worker-runtime.ts"],"sourcesContent":["import type { RuntimeIdentity } from '../data-dir-compat'\nimport { DatabaseManager } from '../database-manager'\nimport { createDatabaseManagerAdapter } from '../services'\nimport { createSemanticIndexService } from './service'\nimport type { LoadSqliteVec } from './store'\nimport { StaticPathProvider, type StaticPathProviderSnapshot } from './static-path-provider'\nimport type { SemanticIndexRuntime } from './runtime'\n\nexport interface SemanticIndexWorkerStartupOptions {\n paths: StaticPathProviderSnapshot\n runtime: RuntimeIdentity\n nativeBinding?: string\n sqliteVecLoadablePath?: string\n}\n\nexport type SemanticIndexWorkerServiceFactory = () => SemanticIndexRuntime\n\nexport interface SemanticIndexWorkerRuntimeOptions {\n serviceFactory: SemanticIndexWorkerServiceFactory\n}\n\nexport class SemanticIndexWorkerRuntime {\n private service: SemanticIndexRuntime | null = null\n private readonly options: SemanticIndexWorkerRuntimeOptions\n\n constructor(options: SemanticIndexWorkerRuntimeOptions) {\n this.options = options\n }\n\n async handleRequest(method: string, args: unknown[]): Promise<unknown> {\n if (method === '__close') {\n await this.close()\n return null\n }\n const service = this.ensureService()\n const target = service as unknown as Record<string, (...args: unknown[]) => unknown>\n const fn = target[method]\n if (typeof fn !== 'function') throw new Error(`Unsupported semantic index worker method: ${method}`)\n return await fn.apply(service, args)\n }\n\n async close(): Promise<void> {\n const service = this.service\n this.service = null\n await service?.close()\n }\n\n private ensureService(): SemanticIndexRuntime {\n if (!this.service) {\n this.service = this.options.serviceFactory()\n void this.service.recover()\n }\n return this.service\n }\n}\n\nexport function createSemanticIndexWorkerRuntime(\n options: SemanticIndexWorkerRuntimeOptions\n): SemanticIndexWorkerRuntime {\n return new SemanticIndexWorkerRuntime(options)\n}\n\nexport function createSemanticIndexWorkerServiceFactory(\n options: SemanticIndexWorkerStartupOptions\n): SemanticIndexWorkerServiceFactory {\n return () => {\n const pathProvider = new StaticPathProvider(options.paths)\n const dbManager = new DatabaseManager(pathProvider, {\n nativeBinding: options.nativeBinding,\n runtime: options.runtime,\n })\n const sessionAdapter = createDatabaseManagerAdapter(dbManager)\n const loadSqliteVec: LoadSqliteVec | undefined = options.sqliteVecLoadablePath\n ? (db) => db.loadExtension(options.sqliteVecLoadablePath!)\n : undefined\n\n return createSemanticIndexService({\n pathProvider,\n sessionAdapter,\n nativeBinding: options.nativeBinding,\n loadSqliteVec,\n })\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqBO,IAAM,6BAAN,MAAiC;AAAA,EAC9B,UAAuC;AAAA,EAC9B;AAAA,EAEjB,YAAY,SAA4C;AACtD,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAM,cAAc,QAAgB,MAAmC;AACrE,QAAI,WAAW,WAAW;AACxB,YAAM,KAAK,MAAM;AACjB,aAAO;AAAA,IACT;AACA,UAAM,UAAU,KAAK,cAAc;AACnC,UAAM,SAAS;AACf,UAAM,KAAK,OAAO,MAAM;AACxB,QAAI,OAAO,OAAO,WAAY,OAAM,IAAI,MAAM,6CAA6C,MAAM,EAAE;AACnG,WAAO,MAAM,GAAG,MAAM,SAAS,IAAI;AAAA,EACrC;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,UAAU,KAAK;AACrB,SAAK,UAAU;AACf,UAAM,SAAS,MAAM;AAAA,EACvB;AAAA,EAEQ,gBAAsC;AAC5C,QAAI,CAAC,KAAK,SAAS;AACjB,WAAK,UAAU,KAAK,QAAQ,eAAe;AAC3C,WAAK,KAAK,QAAQ,QAAQ;AAAA,IAC5B;AACA,WAAO,KAAK;AAAA,EACd;AACF;AAEO,SAAS,iCACd,SAC4B;AAC5B,SAAO,IAAI,2BAA2B,OAAO;AAC/C;AAEO,SAAS,wCACd,SACmC;AACnC,SAAO,MAAM;AACX,UAAM,eAAe,IAAI,mBAAmB,QAAQ,KAAK;AACzD,UAAM,YAAY,IAAI,gBAAgB,cAAc;AAAA,MAClD,eAAe,QAAQ;AAAA,MACvB,SAAS,QAAQ;AAAA,IACnB,CAAC;AACD,UAAM,iBAAiB,6BAA6B,SAAS;AAC7D,UAAM,gBAA2C,QAAQ,wBACrD,CAAC,OAAO,GAAG,cAAc,QAAQ,qBAAsB,IACvD;AAEJ,WAAO,2BAA2B;AAAA,MAChC;AAAA,MACA;AAAA,MACA,eAAe,QAAQ;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function o(c){return typeof c=="string"?c:window.electron?.webUtils?.getPathForFile?.(c)??null}class m{async importFile(n,s,t){const a=o(n);return a?new Promise(i=>{const e=window.chatApi.onImportProgress(r=>{t?.({stage:r.stage||"parsing",progress:r.percentage||r.progress||0,message:r.message||"",bytesRead:r.bytesRead,totalBytes:r.totalBytes,messagesProcessed:r.messagesProcessed})});(s&&(s.formatId||s.chatIndex!==void 0)?window.chatApi.importWithOptions(a,s):window.chatApi.import(a)).then(r=>{e(),i({success:r.success,sessionId:r.sessionId,error:r.error,diagnostics:r.diagnostics})}).catch(r=>{e(),i({success:!1,error:r.message})})}):{success:!1,error:"Cannot get file path in Electron"}}async detectFormat(n){const s=o(n);if(!s)return null;const t=await window.chatApi.detectFormat(s);return t?{...t,extensions:t.extensions??[]}:null}async scanMultiChatFile(n){const s=o(n);if(!s)return[];const t=await window.chatApi.scanMultiChatFile(s);return!t.success||!t.chats?[]:t.chats}getSupportedFormats(){return window.chatApi.getSupportedFormats()}async importDemo(n,s){const t=window.chatApi.onDemoProgress(a=>{(a.stage==="downloading"||a.stage==="importing")&&s?.({stage:a.stage})});try{return await window.chatApi.importDemo(n)}finally{t()}}async analyzeIncrementalImport(n,s){const t=o(s);return t?window.chatApi.analyzeIncrementalImport(n,t):{newMessageCount:0,duplicateCount:0,totalInFile:0,error:"Cannot resolve file path"}}async incrementalImport(n,s,t){const a=o(s);if(!a)return{success:!1,newMessageCount:0,error:"Cannot resolve file path"};const i=window.chatApi.onImportProgress(e=>{t?.(e)});try{return await window.chatApi.incrementalImport(n,a)}finally{i()}}async importDirectory(n,s,t){return typeof n!="string"?{success:!1,error:"Expected directory path in Electron mode"}:new Promise(a=>{const i=window.chatApi.onImportProgress(e=>{t?.({stage:e.stage||"parsing",progress:e.percentage||e.progress||0,message:e.message||"",bytesRead:e.bytesRead,totalBytes:e.totalBytes,messagesProcessed:e.messagesProcessed})});window.chatApi.importDirectory(n).then(e=>{i(),a({success:e.success,sessionId:e.sessionId,error:e.error,diagnostics:e.diagnostics})}).catch(e=>{i(),a({success:!1,error:e.message})})})}}export{m as ElectronImportAdapter};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{W as d,Z as f,L as h}from"./index-DR78oafF.js";import"./vendor-reka-ui-mMvma1EP.js";import"./vendor-nuxt-ui-Zy8txNdC.js";import"./vendor-snapdom-XnBoh1gd.js";async function w(y,s,o){const t=y.body?.getReader();if(!t)return s;const e=new TextDecoder;let r="",n=s;for(;;){const{done:u,value:m}=await t.read();if(u)break;r+=e.decode(m,{stream:!0});const p=r.split(`
|
|
2
|
-
`);r=p.pop()||"";let i="";for(const a of p)if(a.startsWith("event: "))i=a.slice(7).trim();else if(a.startsWith("data: ")){const c=JSON.parse(a.slice(6));i==="progress"?o?.(c):(i==="done"||i==="error")&&(n=c),i=""}}return n}class F{async importFile(s,o,t){if(typeof s=="string")return{success:!1,error:"File path import is not supported in Web mode"};const e=new FormData;e.append("file",s),o?.formatId&&e.append("formatId",o.formatId),o?.chatIndex!==void 0&&e.append("chatIndex",String(o.chatIndex));const r=await d(`${f()}/import`,{method:"POST",body:e});if(!r.ok){const n=await r.text();return{success:!1,error:`HTTP ${r.status}: ${n}`}}return w(r,{success:!1,error:"Unknown error"},t)}async detectFormat(s){if(typeof s=="string")return null;const o=new FormData;o.append("file",s);const t=await d(`${f()}/detect-format`,{method:"POST",body:o});return t.ok?(await t.json()).format:null}async scanMultiChatFile(s){if(typeof s=="string")return[];const o=new FormData;o.append("file",s);const t=await d(`${f()}/scan-multi-chat`,{method:"POST",body:o});return t.ok?(await t.json()).chats:[]}getSupportedFormats(){return h("/supported-formats")}async importDemo(s,o){return new Promise(t=>{d(`${f()}/demo/import`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locale:s})}).then(async e=>{if(!e.ok||!e.body){t({success:!1,error:`HTTP ${e.status}`});return}const r=e.body.getReader(),n=new TextDecoder;let u="";for(;;){const{done:m,value:p}=await r.read();if(m)break;u+=n.decode(p,{stream:!0});const i=u.split(`
|
|
3
|
-
`);u=i.pop()||"";let a="";for(const c of i)if(c.startsWith("event: "))a=c.slice(7).trim();else if(c.startsWith("data: ")){const l=JSON.parse(c.slice(6));if(a==="progress")(l.stage==="downloading"||l.stage==="importing")&&o?.({stage:l.stage});else if(a==="result"){t(l);return}a=""}}t({success:!1,error:"Stream ended without result"})}).catch(e=>t({success:!1,error:String(e)}))})}async analyzeIncrementalImport(s,o){if(typeof o=="string")return{newMessageCount:0,duplicateCount:0,totalInFile:0,error:"File path not supported in Web mode"};const t=new FormData;t.append("file",o);const e=await d(`${f()}/sessions/${s}/import/incremental/analyze`,{method:"POST",body:t});if(!e.ok){const r=await e.text();return{newMessageCount:0,duplicateCount:0,totalInFile:0,error:`HTTP ${e.status}: ${r}`}}return await e.json()}async incrementalImport(s,o,t){if(typeof o=="string")return{success:!1,newMessageCount:0,error:"File path not supported in Web mode"};const e=new FormData;e.append("file",o);const r=await d(`${f()}/sessions/${s}/import/incremental`,{method:"POST",body:e});if(!r.ok){const n=await r.text();return{success:!1,newMessageCount:0,error:`HTTP ${r.status}: ${n}`}}return w(r,{success:!1,newMessageCount:0,error:"Unknown error"},t)}async importDirectory(s,o,t){if(typeof s=="string")return{success:!1,error:"Directory path import is not supported in Web mode"};if(s.length===0)return{success:!1,error:"No files in directory"};const e=new FormData;for(const n of s)e.append("files",n),e.append("relativePaths",n.webkitRelativePath||n.name);const r=await d(`${f()}/import-directory`,{method:"POST",body:e});if(!r.ok){const n=await r.text();return{success:!1,error:`HTTP ${r.status}: ${n}`}}return w(r,{success:!1,error:"Unknown error"},t)}}export{F as FetchImportAdapter};
|