diff --git a/docs/en/chatlab-api.md b/docs/en/chatlab-api.md index a61916c..66a4643 100644 --- a/docs/en/chatlab-api.md +++ b/docs/en/chatlab-api.md @@ -37,13 +37,13 @@ Response example: ## General Information -| Item | Description | -|------|-------------| -| Base URL | `http://127.0.0.1:5200` | -| API Prefix | `/api/v1` | -| Authentication | Bearer Token | -| Data Format | JSON | -| Bind Address | `127.0.0.1` (localhost only) | +| Item | Description | +| -------------- | ---------------------------- | +| Base URL | `http://127.0.0.1:5200` | +| API Prefix | `/api/v1` | +| Authentication | Bearer Token | +| Data Format | JSON | +| Bind Address | `127.0.0.1` (localhost only) | ### Authentication @@ -88,29 +88,29 @@ The Token can be viewed and regenerated in Settings → ChatLab API. ### System -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/status` | Service status | -| GET | `/api/v1/schema` | ChatLab Format JSON Schema | +| Method | Path | Description | +| ------ | ---------------- | -------------------------- | +| GET | `/api/v1/status` | Service status | +| GET | `/api/v1/schema` | ChatLab Format JSON Schema | ### Data Query (Export) -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/sessions` | List all sessions | -| GET | `/api/v1/sessions/:id` | Get single session details | -| GET | `/api/v1/sessions/:id/messages` | Query messages (paginated) | -| GET | `/api/v1/sessions/:id/members` | Get member list | -| GET | `/api/v1/sessions/:id/stats/overview` | Get overview statistics | -| POST | `/api/v1/sessions/:id/sql` | Execute custom SQL (read-only) | -| GET | `/api/v1/sessions/:id/export` | Export ChatLab Format JSON | +| Method | Path | Description | +| ------ | ------------------------------------- | ------------------------------ | +| GET | `/api/v1/sessions` | List all sessions | +| GET | `/api/v1/sessions/:id` | Get single session details | +| GET | `/api/v1/sessions/:id/messages` | Query messages (paginated) | +| GET | `/api/v1/sessions/:id/members` | Get member list | +| GET | `/api/v1/sessions/:id/stats/overview` | Get overview statistics | +| POST | `/api/v1/sessions/:id/sql` | Execute custom SQL (read-only) | +| GET | `/api/v1/sessions/:id/export` | Export ChatLab Format JSON | ### Data Import -| Method | Path | Description | -|--------|------|-------------| -| POST | `/api/v1/import` | Import chat records (new session) | -| POST | `/api/v1/sessions/:id/import` | Incremental import to existing session | +| Method | Path | Description | +| ------ | ----------------------------- | -------------------------------------- | +| POST | `/api/v1/import` | Import chat records (new session) | +| POST | `/api/v1/sessions/:id/import` | Incremental import to existing session | --- @@ -122,12 +122,12 @@ Get the running status of the API service. **Response:** -| Field | Type | Description | -|-------|------|-------------| -| `name` | string | Service name (`ChatLab API`) | -| `version` | string | ChatLab application version | -| `uptime` | number | Service uptime in seconds | -| `sessionCount` | number | Total number of sessions | +| Field | Type | Description | +| -------------- | ------ | ---------------------------- | +| `name` | string | Service name (`ChatLab API`) | +| `version` | string | ChatLab application version | +| `uptime` | number | Service uptime in seconds | +| `sessionCount` | number | Total number of sessions | --- @@ -167,9 +167,9 @@ Get detailed information for a single session. **Path parameters:** -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | string | Session ID | +| Parameter | Type | Description | +| --------- | ------ | ----------- | +| `id` | string | Session ID | --- @@ -179,15 +179,15 @@ Query messages from a specific session with pagination and filtering support. **Query parameters:** -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `page` | number | 1 | Page number | -| `limit` | number | 100 | Items per page (max 1000) | -| `startTime` | number | - | Start timestamp (Unix seconds) | -| `endTime` | number | - | End timestamp (Unix seconds) | -| `keyword` | string | - | Keyword search | -| `senderId` | string | - | Filter by sender's platformId | -| `type` | number | - | Filter by message type | +| Parameter | Type | Default | Description | +| ----------- | ------ | ------- | ------------------------------ | +| `page` | number | 1 | Page number | +| `limit` | number | 100 | Items per page (max 1000) | +| `startTime` | number | - | Start timestamp (Unix seconds) | +| `endTime` | number | - | End timestamp (Unix seconds) | +| `keyword` | string | - | Keyword search | +| `senderId` | string | - | Filter by sender's platformId | +| `type` | number | - | Filter by message type | **Request example:** @@ -262,7 +262,7 @@ Get overview statistics for a specific session. ``` | Field | Description | -|-------|-------------| +| --- | --- | | `messageCount` | Total message count | | `memberCount` | Total member count | | `timeRange` | Earliest/latest message timestamps (Unix seconds) | @@ -339,7 +339,7 @@ Import chat records into ChatLab, **creating a new session**. #### Supported Content-Types | Content-Type | Format | Use Case | Body Limit | -|------|------|------|------| +| --- | --- | --- | --- | | `application/json` | ChatLab Format JSON | Small to medium data (quick testing, script integration) | **50MB** | | `application/x-ndjson` | ChatLab JSONL format | Large-scale data (production integration) | **Unlimited** | @@ -409,9 +409,9 @@ The unique key for each message is `timestamp + senderPlatformId + contentLength **Path parameters:** -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | string | Target session ID | +| Parameter | Type | Description | +| --------- | ------ | ----------------- | +| `id` | string | Target session ID | Content-Type and request body format are the same as `POST /api/v1/import`. @@ -432,30 +432,30 @@ Content-Type and request body format are the same as `POST /api/v1/import`. ## Concurrency & Limits -| Limit | Value | Description | -|-------|-------|-------------| -| JSON body size | 50MB | `application/json` mode | -| JSONL body size | Unlimited | `application/x-ndjson` streaming mode | -| Export message limit | 100,000 | `/export` endpoint | -| Max page size | 1,000 | `/messages` endpoint | -| Import concurrency | 1 | Only one import operation allowed at a time | +| Limit | Value | Description | +| -------------------- | --------- | ------------------------------------------- | +| JSON body size | 50MB | `application/json` mode | +| JSONL body size | Unlimited | `application/x-ndjson` streaming mode | +| Export message limit | 100,000 | `/export` endpoint | +| Max page size | 1,000 | `/messages` endpoint | +| Import concurrency | 1 | Only one import operation allowed at a time | --- ## Error Codes -| Error Code | HTTP Status | Description | -|------------|-------------|-------------| -| `UNAUTHORIZED` | 401 | Invalid or missing token | -| `SESSION_NOT_FOUND` | 404 | Session not found | -| `INVALID_FORMAT` | 400 | Request body does not conform to ChatLab Format | -| `SQL_READONLY_VIOLATION` | 400 | SQL is not a SELECT statement | -| `SQL_EXECUTION_ERROR` | 400 | SQL execution error | -| `EXPORT_TOO_LARGE` | 400 | Message count exceeds export limit (100K) | -| `BODY_TOO_LARGE` | 413 | Request body exceeds 50MB (JSON mode only) | -| `IMPORT_IN_PROGRESS` | 409 | Another import is already in progress | -| `IMPORT_FAILED` | 500 | Import failed | -| `SERVER_ERROR` | 500 | Internal server error | +| Error Code | HTTP Status | Description | +| ------------------------ | ----------- | ----------------------------------------------- | +| `UNAUTHORIZED` | 401 | Invalid or missing token | +| `SESSION_NOT_FOUND` | 404 | Session not found | +| `INVALID_FORMAT` | 400 | Request body does not conform to ChatLab Format | +| `SQL_READONLY_VIOLATION` | 400 | SQL is not a SELECT statement | +| `SQL_EXECUTION_ERROR` | 400 | SQL execution error | +| `EXPORT_TOO_LARGE` | 400 | Message count exceeds export limit (100K) | +| `BODY_TOO_LARGE` | 413 | Request body exceeds 50MB (JSON mode only) | +| `IMPORT_IN_PROGRESS` | 409 | Another import is already in progress | +| `IMPORT_FAILED` | 500 | Import failed | +| `SERVER_ERROR` | 500 | Internal server error | --- @@ -494,6 +494,6 @@ Configure external data source URLs in the Settings page. ChatLab will automatic ## Version History -| Version | Description | -|---------|-------------| -| v1 | Initial release — session query, message search, SQL, export, import (JSON + JSONL), Pull scheduler | +| Version | Description | +| ------- | --------------------------------------------------------------------------------------------------- | +| v1 | Initial release — session query, message search, SQL, export, import (JSON + JSONL), Pull scheduler | diff --git a/electron/main/ai/assistant/builtins/general_en.md b/electron/main/ai/assistant/builtins/general_en.md index 693929d..9ef1513 100644 --- a/electron/main/ai/assistant/builtins/general_en.md +++ b/electron/main/ai/assistant/builtins/general_en.md @@ -10,10 +10,10 @@ presetQuestions: - Analyze the active hours of the chat --- -You are a professional yet friendly chat record analysis assistant. -Your job is to help users understand and analyze their chat history data. You can occasionally use light humor to keep the conversation engaging, but never at the expense of accuracy. +You are a professional yet friendly chat record analysis assistant. Your job is to help users understand and analyze their chat history data. You can occasionally use light humor to keep the conversation engaging, but never at the expense of accuracy. ## Response Guidelines + 1. Base your answers on data returned by tools — never fabricate information 2. If the data is insufficient to answer a question, say so clearly 3. Keep responses concise and use Markdown formatting diff --git a/electron/main/ai/assistant/builtins/general_ja.md b/electron/main/ai/assistant/builtins/general_ja.md index 72e80c8..24576cc 100644 --- a/electron/main/ai/assistant/builtins/general_ja.md +++ b/electron/main/ai/assistant/builtins/general_ja.md @@ -10,10 +10,10 @@ presetQuestions: - チャットの活発な時間帯を分析して --- -あなたはプロフェッショナルでありながら親しみやすいチャット記録分析アシスタントです。 -ユーザーのチャット履歴データを理解し分析する手助けをすることが主な役割です。適度にユーモアを交えても構いませんが、分析の正確性を最優先にしてください。 +あなたはプロフェッショナルでありながら親しみやすいチャット記録分析アシスタントです。ユーザーのチャット履歴データを理解し分析する手助けをすることが主な役割です。適度にユーモアを交えても構いませんが、分析の正確性を最優先にしてください。 ## 回答ガイドライン + 1. ツールから返されたデータに基づいて回答し、情報を捏造しないこと 2. データが不十分な場合はその旨を明確に伝えること 3. 簡潔で分かりやすい回答を心がけ、Markdown形式を使用すること diff --git a/electron/main/ai/assistant/types.ts b/electron/main/ai/assistant/types.ts index c35b4a4..e94fe13 100644 --- a/electron/main/ai/assistant/types.ts +++ b/electron/main/ai/assistant/types.ts @@ -189,4 +189,3 @@ export interface AssistantSaveResult { success: boolean error?: string } - diff --git a/electron/main/ai/skills/index.ts b/electron/main/ai/skills/index.ts index c61b4f9..74396f9 100644 --- a/electron/main/ai/skills/index.ts +++ b/electron/main/ai/skills/index.ts @@ -2,13 +2,7 @@ * 技能系统模块入口 */ -export type { - SkillDef, - SkillSummary, - BuiltinSkillInfo, - SkillInitResult, - SkillSaveResult, -} from './types' +export type { SkillDef, SkillSummary, BuiltinSkillInfo, SkillInitResult, SkillSaveResult } from './types' export { initSkillManager, diff --git a/electron/main/ai/skills/manager.ts b/electron/main/ai/skills/manager.ts index 78ef4ec..45dc899 100644 --- a/electron/main/ai/skills/manager.ts +++ b/electron/main/ai/skills/manager.ts @@ -16,13 +16,7 @@ import { createHash } from 'crypto' import { getAiDataDir, ensureDir } from '../../paths' import { aiLogger } from '../logger' import { parseSkillFile } from './parser' -import type { - SkillDef, - SkillSummary, - SkillInitResult, - SkillSaveResult, - BuiltinSkillInfo, -} from './types' +import type { SkillDef, SkillSummary, SkillInitResult, SkillSaveResult, BuiltinSkillInfo } from './types' // ==================== 内置技能模板 ==================== // 云端市场上线后,本地不再内置技能模板,全部从云端获取 @@ -271,10 +265,7 @@ const MAX_SKILL_MENU_ITEMS = 15 * 构建 AI 自选技能菜单文本 * 只包含与当前 chatType + 助手工具权限兼容的技能 */ -export function getSkillMenu( - chatType: 'group' | 'private', - allowedTools?: string[] -): string | null { +export function getSkillMenu(chatType: 'group' | 'private', allowedTools?: string[]): string | null { ensureInitialized() const compatible = Array.from(cachedSkills.values()).filter((skill) => { diff --git a/electron/main/ai/tools/definitions/keyword-frequency.ts b/electron/main/ai/tools/definitions/keyword-frequency.ts index ae577ef..542c947 100644 --- a/electron/main/ai/tools/definitions/keyword-frequency.ts +++ b/electron/main/ai/tools/definitions/keyword-frequency.ts @@ -40,7 +40,11 @@ export function createTool(context: ToolContext): AgentTool { } const texts = rows.map((r) => r.content) - const segLocale: SupportedLocale = locale?.startsWith('ja') ? 'ja-JP' : locale?.startsWith('zh') ? 'zh-CN' : 'en-US' + const segLocale: SupportedLocale = locale?.startsWith('ja') + ? 'ja-JP' + : locale?.startsWith('zh') + ? 'zh-CN' + : 'en-US' const segOptions: BatchSegmentOptions = { minCount: 2, topN, diff --git a/electron/main/ai/tools/definitions/response-time-analysis.ts b/electron/main/ai/tools/definitions/response-time-analysis.ts index 55020c0..a6ccb2f 100644 --- a/electron/main/ai/tools/definitions/response-time-analysis.ts +++ b/electron/main/ai/tools/definitions/response-time-analysis.ts @@ -37,7 +37,9 @@ export function createTool(context: ToolContext): AgentTool { ` const rows = await workerManager.pluginQuery(sessionId, sql, { days }) if (!rows || rows.length < 2) { - const text = isZh ? '该时间范围内消息不足,无法分析响应时间' : 'Not enough messages in this time range to analyze response time' + const text = isZh + ? '该时间范围内消息不足,无法分析响应时间' + : 'Not enough messages in this time range to analyze response time' return { content: [{ type: 'text', text }], details: null } } diff --git a/electron/main/ai/tools/definitions/sql-analysis.ts b/electron/main/ai/tools/definitions/sql-analysis.ts index 6ee065f..cde7a74 100644 --- a/electron/main/ai/tools/definitions/sql-analysis.ts +++ b/electron/main/ai/tools/definitions/sql-analysis.ts @@ -15,8 +15,7 @@ const SQL_TOOL_DEFS: CustomSqlToolDef[] = [ // ==================== 通用分析 ==================== { name: 'message_type_breakdown', - description: - '按消息类型统计近 N 天的消息分布(文本、图片、语音、表情等各有多少条)。适用于了解沟通方式偏好。', + description: '按消息类型统计近 N 天的消息分布(文本、图片、语音、表情等各有多少条)。适用于了解沟通方式偏好。', parameters: { type: 'object', properties: { @@ -183,8 +182,7 @@ const SQL_TOOL_DEFS: CustomSqlToolDef[] = [ }, { name: 'conversation_initiator_stats', - description: - '统计每个成员发起会话(作为会话首条消息的发送者)的次数,找出谁最常开启话题。需要已生成会话索引。', + description: '统计每个成员发起会话(作为会话首条消息的发送者)的次数,找出谁最常开启话题。需要已生成会话索引。', parameters: { type: 'object', properties: { @@ -204,8 +202,7 @@ const SQL_TOOL_DEFS: CustomSqlToolDef[] = [ }, { name: 'activity_heatmap', - description: - '返回 星期×小时 的消息数矩阵,适合生成活跃度热力图。weekday: 0=周日, 1=周一, ..., 6=周六。', + description: '返回 星期×小时 的消息数矩阵,适合生成活跃度热力图。weekday: 0=周日, 1=周一, ..., 6=周六。', parameters: { type: 'object', properties: { diff --git a/electron/main/api/dataSource.ts b/electron/main/api/dataSource.ts index 3c41079..907ae58 100644 --- a/electron/main/api/dataSource.ts +++ b/electron/main/api/dataSource.ts @@ -64,7 +64,9 @@ export function generateId(): string { return `ds_${crypto.randomBytes(6).toString('hex')}` } -export function addDataSource(partial: Omit): DataSource { +export function addDataSource( + partial: Omit +): DataSource { const sources = loadDataSources() const ds: DataSource = { ...partial, diff --git a/electron/main/api/pullScheduler.ts b/electron/main/api/pullScheduler.ts index 99d5922..5f0dc94 100644 --- a/electron/main/api/pullScheduler.ts +++ b/electron/main/api/pullScheduler.ts @@ -23,7 +23,9 @@ function getTempFilePath(ext: string): string { function cleanupTempFile(filePath: string): void { try { if (fs.existsSync(filePath)) fs.unlinkSync(filePath) - } catch { /* ignore */ } + } catch { + /* ignore */ + } } function notifySessionListChanged(): void { @@ -33,7 +35,9 @@ function notifySessionListChanged(): void { for (const win of wins) { win.webContents.send('api:importCompleted') } - } catch { /* ignore */ } + } catch { + /* ignore */ + } } function notifyPullResult(dsId: string, status: 'success' | 'error', detail: string): void { @@ -43,7 +47,9 @@ function notifyPullResult(dsId: string, status: 'success' | 'error', detail: str for (const win of wins) { win.webContents.send('api:pullResult', { dsId, status, detail }) } - } catch { /* ignore */ } + } catch { + /* ignore */ + } } /** @@ -51,9 +57,7 @@ function notifyPullResult(dsId: string, status: 'success' | 'error', detail: str */ async function fetchToTempFile(ds: DataSource): Promise { return new Promise((resolve, reject) => { - const url = ds.url.includes('?') - ? `${ds.url}&since=${ds.lastPullAt}` - : `${ds.url}?since=${ds.lastPullAt}` + const url = ds.url.includes('?') ? `${ds.url}&since=${ds.lastPullAt}` : `${ds.url}?since=${ds.lastPullAt}` const request = net.request(url) @@ -140,7 +144,9 @@ async function executePull(ds: DataSource): Promise { if (result.success) { try { await worker.generateIncrementalSessions(ds.targetSessionId) - } catch { /* ignore */ } + } catch { + /* ignore */ + } } } else { result = await worker.streamImport(tempFile) diff --git a/electron/main/api/routes/import.ts b/electron/main/api/routes/import.ts index a99bbb4..11d58b6 100644 --- a/electron/main/api/routes/import.ts +++ b/electron/main/api/routes/import.ts @@ -62,11 +62,7 @@ export function getImportingStatus(): boolean { return isImporting } -async function handleImport( - request: FastifyRequest, - reply: FastifyReply, - sessionId?: string -): Promise { +async function handleImport(request: FastifyRequest, reply: FastifyReply, sessionId?: string): Promise { if (isImporting) { const err = importInProgress() reply.code(err.statusCode).send(errorResponse(err)) @@ -174,12 +170,9 @@ async function handleImport( export function registerImportRoutes(server: FastifyInstance): void { // JSONL mode: skip fastify's default body parsing, use request.raw stream directly - server.addContentTypeParser( - 'application/x-ndjson', - (_request, _payload, done) => { - done(null, undefined) - } - ) + server.addContentTypeParser('application/x-ndjson', (_request, _payload, done) => { + done(null, undefined) + }) // POST /api/v1/import — Import to new session server.post('/api/v1/import', async (request, reply) => { @@ -187,10 +180,7 @@ export function registerImportRoutes(server: FastifyInstance): void { }) // POST /api/v1/sessions/:id/import — Incremental import to existing session - server.post<{ Params: { id: string } }>( - '/api/v1/sessions/:id/import', - async (request, reply) => { - await handleImport(request, reply, request.params.id) - } - ) + server.post<{ Params: { id: string } }>('/api/v1/sessions/:id/import', async (request, reply) => { + await handleImport(request, reply, request.params.id) + }) } diff --git a/electron/main/api/routes/sessions.ts b/electron/main/api/routes/sessions.ts index 6b418b1..582749d 100644 --- a/electron/main/api/routes/sessions.ts +++ b/electron/main/api/routes/sessions.ts @@ -57,24 +57,15 @@ export function registerSessionRoutes(server: FastifyInstance): void { const keywords = keyword ? [keyword] : [] const senderIdNum = senderId ? parseInt(senderId, 10) : undefined - const result = await worker.searchMessages( - id, - keywords, - hasFilter ? filter : undefined, - limit, - offset, - senderIdNum - ) + const result = await worker.searchMessages(id, keywords, hasFilter ? filter : undefined, limit, offset, senderIdNum) - return successResponse( - { - messages: result.messages, - total: result.total, - page, - limit, - totalPages: Math.ceil(result.total / limit), - } - ) + return successResponse({ + messages: result.messages, + total: result.total, + page, + limit, + totalPages: Math.ceil(result.total / limit), + }) }) // GET /api/v1/sessions/:id/members — Member list @@ -117,33 +108,30 @@ export function registerSessionRoutes(server: FastifyInstance): void { }) // POST /api/v1/sessions/:id/sql — Execute SQL (read-only) - server.post<{ Params: { id: string }; Body: { sql: string } }>( - '/api/v1/sessions/:id/sql', - async (request, reply) => { - const { id } = request.params - await ensureSession(id) + server.post<{ Params: { id: string }; Body: { sql: string } }>('/api/v1/sessions/:id/sql', async (request, reply) => { + const { id } = request.params + await ensureSession(id) - const { sql } = request.body || {} - if (!sql || typeof sql !== 'string') { - const err = sqlExecutionError('Missing sql parameter') - return reply.code(err.statusCode).send(errorResponse(err)) - } - - try { - const result = await worker.executeRawSQL(id, sql) - return successResponse(result) - } catch (err: any) { - const message = err.message || 'SQL execution error' - if (message.includes('SELECT') || message.includes('只读') || message.includes('readonly')) { - const apiErr = new ApiError('SQL_READONLY_VIOLATION' as any, message) - apiErr.statusCode = 400 - return reply.code(400).send(errorResponse(apiErr)) - } - const apiErr = sqlExecutionError(message) - return reply.code(apiErr.statusCode).send(errorResponse(apiErr)) - } + const { sql } = request.body || {} + if (!sql || typeof sql !== 'string') { + const err = sqlExecutionError('Missing sql parameter') + return reply.code(err.statusCode).send(errorResponse(err)) } - ) + + try { + const result = await worker.executeRawSQL(id, sql) + return successResponse(result) + } catch (err: any) { + const message = err.message || 'SQL execution error' + if (message.includes('SELECT') || message.includes('只读') || message.includes('readonly')) { + const apiErr = new ApiError('SQL_READONLY_VIOLATION' as any, message) + apiErr.statusCode = 400 + return reply.code(400).send(errorResponse(apiErr)) + } + const apiErr = sqlExecutionError(message) + return reply.code(apiErr.statusCode).send(errorResponse(apiErr)) + } + }) // GET /api/v1/sessions/:id/export — Export ChatLab Format JSON server.get<{ Params: { id: string } }>('/api/v1/sessions/:id/export', async (request, reply) => { diff --git a/electron/main/api/routes/system.ts b/electron/main/api/routes/system.ts index 6c8e945..54b9d61 100644 --- a/electron/main/api/routes/system.ts +++ b/electron/main/api/routes/system.ts @@ -46,7 +46,10 @@ export function registerSystemRoutes(server: FastifyInstance): void { required: ['name', 'platform', 'type'], properties: { name: { type: 'string' }, - platform: { type: 'string', enum: ['qq', 'wechat', 'telegram', 'discord', 'line', 'whatsapp', 'instagram', 'unknown'] }, + platform: { + type: 'string', + enum: ['qq', 'wechat', 'telegram', 'discord', 'line', 'whatsapp', 'instagram', 'unknown'], + }, type: { type: 'string', enum: ['group', 'private'] }, groupId: { type: 'string' }, }, diff --git a/electron/main/database/migrations.ts b/electron/main/database/migrations.ts index 6ebd31e..55f8269 100644 --- a/electron/main/database/migrations.ts +++ b/electron/main/database/migrations.ts @@ -144,9 +144,7 @@ const migrations: Migration[] = [ descriptionKey: 'database.migrationV4Desc', userMessageKey: 'database.migrationV4Message', up: (db) => { - const hasTable = db - .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'") - .get() + const hasTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'").get() if (hasTable) return db.exec(` @@ -161,9 +159,7 @@ const migrations: Migration[] = [ const insertFts = db.prepare('INSERT INTO message_fts(rowid, content) VALUES (?, ?)') const countRow = db - .prepare( - "SELECT COUNT(*) as total FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''" - ) + .prepare("SELECT COUNT(*) as total FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''") .get() as { total: number } let offset = 0 diff --git a/electron/main/database/sessionCache.ts b/electron/main/database/sessionCache.ts index 2a38e14..f44a2c6 100644 --- a/electron/main/database/sessionCache.ts +++ b/electron/main/database/sessionCache.ts @@ -111,11 +111,7 @@ export interface OverviewCache { /** * 从数据库计算概览统计并写入缓存 */ -export function computeAndSetOverviewCache( - db: Database.Database, - sessionId: string, - cacheDir: string -): OverviewCache { +export function computeAndSetOverviewCache(db: Database.Database, sessionId: string, cacheDir: string): OverviewCache { const msgStats = db.prepare('SELECT MIN(ts) as first_ts, MAX(ts) as last_ts FROM message').get() as { first_ts: number | null last_ts: number | null @@ -169,11 +165,7 @@ export interface MembersCache { /** * 从数据库计算成员统计并写入缓存 */ -export function computeAndSetMembersCache( - db: Database.Database, - sessionId: string, - cacheDir: string -): MembersCache { +export function computeAndSetMembersCache(db: Database.Database, sessionId: string, cacheDir: string): MembersCache { const rows = db .prepare( `SELECT msg.sender_id, COUNT(*) as count, diff --git a/electron/main/ipc/api.ts b/electron/main/ipc/api.ts index b54f543..0ca8fc3 100644 --- a/electron/main/ipc/api.ts +++ b/electron/main/ipc/api.ts @@ -6,19 +6,8 @@ import { ipcMain } from 'electron' import type { IpcContext } from './types' import * as apiServer from '../api' import { loadConfig, regenerateToken, updateConfig } from '../api/config' -import { - loadDataSources, - addDataSource, - updateDataSource, - deleteDataSource, - type DataSource, -} from '../api/dataSource' -import { - initScheduler, - stopAllTimers, - reloadTimer, - triggerPull, -} from '../api/pullScheduler' +import { loadDataSources, addDataSource, updateDataSource, deleteDataSource, type DataSource } from '../api/dataSource' +import { initScheduler, stopAllTimers, reloadTimer, triggerPull } from '../api/pullScheduler' export function registerApiHandlers(_ctx: IpcContext): void { // ==================== API Server Management ==================== @@ -63,10 +52,7 @@ export function registerApiHandlers(_ctx: IpcContext): void { 'api:addDataSource', ( _event, - partial: Omit< - DataSource, - 'id' | 'createdAt' | 'lastPullAt' | 'lastStatus' | 'lastError' | 'lastNewMessages' - > + partial: Omit ) => { const ds = addDataSource(partial) if (ds.enabled) { diff --git a/electron/main/worker/query/basic.ts b/electron/main/worker/query/basic.ts index fc8a137..4700e01 100644 --- a/electron/main/worker/query/basic.ts +++ b/electron/main/worker/query/basic.ts @@ -394,9 +394,10 @@ export function getTimeRange(sessionId: string): { start: number; end: number } const db = openDatabase(sessionId) if (!db) return null - const row = db - .prepare('SELECT MIN(ts) as start, MAX(ts) as end FROM message') - .get() as { start: number | null; end: number | null } + const row = db.prepare('SELECT MIN(ts) as start, MAX(ts) as end FROM message').get() as { + start: number | null + end: number | null + } if (row.start === null || row.end === null) return null diff --git a/electron/main/worker/query/fts.ts b/electron/main/worker/query/fts.ts index b9fc886..c0a7f28 100644 --- a/electron/main/worker/query/fts.ts +++ b/electron/main/worker/query/fts.ts @@ -43,9 +43,9 @@ export function hasFtsIndex(sessionId: string): boolean { const db = openDatabase(sessionId) if (!db) return false try { - const row = db - .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'") - .get() as { name: string } | undefined + const row = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'").get() as + | { name: string } + | undefined return !!row } catch { return false @@ -72,9 +72,9 @@ export function buildFtsIndex(sessionId: string): { indexed: number } { const insertFts = db.prepare('INSERT INTO message_fts(rowid, content) VALUES (?, ?)') - const countRow = db.prepare( - "SELECT COUNT(*) as total FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''" - ).get() as { total: number } + const countRow = db + .prepare("SELECT COUNT(*) as total FROM message WHERE type = 0 AND content IS NOT NULL AND content != ''") + .get() as { total: number } const total = countRow.total let indexed = 0 @@ -121,9 +121,7 @@ export function rebuildFtsIndex(sessionId: string): { indexed: number } { if (!db) return { indexed: 0 } try { - const hasTable = db - .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'") - .get() + const hasTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'").get() if (hasTable) { db.exec('DROP TABLE message_fts') @@ -142,17 +140,12 @@ export function rebuildFtsIndex(sessionId: string): { indexed: number } { * 批量写入 FTS 条目 * 用于增量导入时同步写入 */ -export function insertFtsEntries( - sessionId: string, - entries: Array<{ id: number; content: string | null }> -): void { +export function insertFtsEntries(sessionId: string, entries: Array<{ id: number; content: string | null }>): void { const db = openWritableDb(sessionId) if (!db) return try { - const hasTable = db - .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'") - .get() + const hasTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message_fts'").get() if (!hasTable) { db.close() return @@ -194,14 +187,12 @@ export function searchByFts( if (!matchQuery) return { rowids: [], total: 0 } try { - const countRow = db - .prepare('SELECT COUNT(*) as total FROM message_fts WHERE content MATCH ?') - .get(matchQuery) as { total: number } + const countRow = db.prepare('SELECT COUNT(*) as total FROM message_fts WHERE content MATCH ?').get(matchQuery) as { + total: number + } const rows = db - .prepare( - `SELECT rowid FROM message_fts WHERE content MATCH ? ORDER BY rank LIMIT ? OFFSET ?` - ) + .prepare(`SELECT rowid FROM message_fts WHERE content MATCH ? ORDER BY rank LIMIT ? OFFSET ?`) .all(matchQuery, limit, offset) as Array<{ rowid: number }> return { diff --git a/electron/main/worker/query/messages.ts b/electron/main/worker/query/messages.ts index 49c9861..a604eea 100644 --- a/electron/main/worker/query/messages.ts +++ b/electron/main/worker/query/messages.ts @@ -345,9 +345,7 @@ function searchMessagesWithFts( LIMIT ? OFFSET ? ` - const rows = db - .prepare(sql) - .all(matchQuery, ...timeParams, ...senderParams, limit, offset) as DbMessageRow[] + const rows = db.prepare(sql).all(matchQuery, ...timeParams, ...senderParams, limit, offset) as DbMessageRow[] return { messages: rows.map(sanitizeMessageRow), diff --git a/electron/preload/apis/api-server.ts b/electron/preload/apis/api-server.ts index 8520af6..209e264 100644 --- a/electron/preload/apis/api-server.ts +++ b/electron/preload/apis/api-server.ts @@ -68,7 +68,9 @@ export const apiServerApi = { return ipcRenderer.invoke('api:getDataSources') }, - addDataSource: (partial: Omit): Promise => { + addDataSource: ( + partial: Omit + ): Promise => { return ipcRenderer.invoke('api:addDataSource', partial) }, diff --git a/electron/preload/index.d.ts b/electron/preload/index.d.ts index 00275d4..8f7f7d7 100644 --- a/electron/preload/index.d.ts +++ b/electron/preload/index.d.ts @@ -916,7 +916,9 @@ interface ApiServerApi { regenerateToken: () => Promise onStartupError: (callback: (data: { error: string }) => void) => () => void getDataSources: () => Promise - addDataSource: (partial: Omit) => Promise + addDataSource: ( + partial: Omit + ) => Promise updateDataSource: (id: string, updates: Partial) => Promise deleteDataSource: (id: string) => Promise triggerPull: (id: string) => Promise<{ success: boolean; error?: string }> diff --git a/skills/generate-assistant-config/SKILL.md b/skills/generate-assistant-config/SKILL.md index 30e1c8d..8b2bb44 100644 --- a/skills/generate-assistant-config/SKILL.md +++ b/skills/generate-assistant-config/SKILL.md @@ -121,10 +121,12 @@ description: Use when 用户希望根据一句自然语言需求创建新的 Cha ## 工具选择规则 工具分为两类: + - **核心工具(core)**:始终启用,无需在 `allowedBuiltinTools` 中列出。包括:get_chat_overview, search_messages, get_recent_messages, get_message_context, search_sessions, get_session_messages, get_members - **分析工具(analysis)**:需在 `allowedBuiltinTools` 中显式列出才会启用 `allowedBuiltinTools` 仅用于控制分析工具,核心工具始终可用: + - 若角色不需要分析工具,可省略 `allowedBuiltinTools`(默认仅核心工具可用) - 若角色需要特定分析能力,列出所需的分析工具名称 - 仅在角色明显聚焦时选择对应的分析工具,例如: diff --git a/src/assets/styles/main.css b/src/assets/styles/main.css index 3169904..7791935 100644 --- a/src/assets/styles/main.css +++ b/src/assets/styles/main.css @@ -19,7 +19,7 @@ /* 分析页面使用的更深背景 - 统一使用 zinc-900 */ --color-page-dark: #18181b; - + /* 模块暗色模式半透明背景色 */ --color-card-dark: rgba(255, 255, 255, 0.03); } diff --git a/src/components/AIChat/assistant/AssistantConfigModal.vue b/src/components/AIChat/assistant/AssistantConfigModal.vue index 782b5b0..2ab2963 100644 --- a/src/components/AIChat/assistant/AssistantConfigModal.vue +++ b/src/components/AIChat/assistant/AssistantConfigModal.vue @@ -467,17 +467,11 @@ function closeModal() { {{ t('ai.assistant.config.analysisTools') }}
- | -
diff --git a/src/components/AIChat/input/AIChatInput.vue b/src/components/AIChat/input/AIChatInput.vue index bd79101..da30680 100644 --- a/src/components/AIChat/input/AIChatInput.vue +++ b/src/components/AIChat/input/AIChatInput.vue @@ -394,7 +394,9 @@ function fillInput(content: string) { }) } -function handleSelectMention(member: Pick) { +function handleSelectMention( + member: Pick +) { if (props.disabled || !mentionRange.value) return const prefix = inputValue.value.slice(0, mentionRange.value.start) diff --git a/src/components/AIChat/skill/SkillMarketModal.vue b/src/components/AIChat/skill/SkillMarketModal.vue index 46de785..2b9f4e8 100644 --- a/src/components/AIChat/skill/SkillMarketModal.vue +++ b/src/components/AIChat/skill/SkillMarketModal.vue @@ -285,9 +285,7 @@ function handleRetry() {