From 32fd5139d449512319b577c5e1e20467eaed6116 Mon Sep 17 00:00:00 2001 From: digua Date: Fri, 9 Jan 2026 00:04:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E6=B6=88=E6=81=AFid=E5=92=8C=E5=9B=9E=E5=A4=8Did=EF=BC=8C?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E8=BF=9B=E8=A1=8C=E8=A1=A8=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/database/core.ts | 11 +++- electron/main/database/migrations.ts | 30 ++++++++-- electron/main/parser/formats/chatlab-jsonl.ts | 2 + electron/main/parser/formats/chatlab.ts | 2 + .../parser/formats/tyrrrz-discord-exporter.ts | 1 + electron/main/worker/import/streamImport.ts | 11 +++- electron/main/worker/query/messages.ts | 58 ++++++++++++++++--- .../common/ChatRecord/MessageItem.vue | 19 +++++- src/types/base.ts | 3 + src/types/format.ts | 3 + 10 files changed, 121 insertions(+), 19 deletions(-) diff --git a/electron/main/database/core.ts b/electron/main/database/core.ts index 190fe8a..fb61197 100644 --- a/electron/main/database/core.ts +++ b/electron/main/database/core.ts @@ -106,11 +106,14 @@ function createDatabase(sessionId: string): Database.Database { ts INTEGER NOT NULL, type INTEGER NOT NULL, content TEXT, + reply_to_message_id TEXT DEFAULT NULL, + platform_message_id TEXT DEFAULT NULL, FOREIGN KEY(sender_id) REFERENCES member(id) ); CREATE INDEX IF NOT EXISTS idx_message_ts ON message(ts); CREATE INDEX IF NOT EXISTS idx_message_sender ON message(sender_id); + CREATE INDEX IF NOT EXISTS idx_message_platform_id ON message(platform_message_id); CREATE INDEX IF NOT EXISTS idx_member_name_history_member_id ON member_name_history(member_id); `) @@ -201,8 +204,8 @@ export function importData(parseResult: ParseResult): string { const groupNicknameTracker = new Map() const insertMessage = db.prepare(` - INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content) - VALUES (?, ?, ?, ?, ?, ?) + INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) `) const insertNameHistory = db.prepare(` INSERT INTO member_name_history (member_id, name_type, name, start_ts, end_ts) @@ -230,7 +233,9 @@ export function importData(parseResult: ParseResult): string { msg.senderGroupNickname || null, msg.timestamp, msg.type, - msg.content + msg.content, + msg.replyToMessageId || null, + msg.platformMessageId || null ) // 追踪 account_name 变化 diff --git a/electron/main/database/migrations.ts b/electron/main/database/migrations.ts index 3126f10..29c0c5b 100644 --- a/electron/main/database/migrations.ts +++ b/electron/main/database/migrations.ts @@ -56,15 +56,37 @@ const migrations: Migration[] = [ }, { version: 2, - description: '添加 roles 字段到 member 表', - userMessage: '支持成员角色(群主、管理员等)', + description: '添加 roles、reply_to_message_id、platform_message_id 字段', + userMessage: '支持成员角色、消息回复关系和回复内容预览', up: (db) => { // 检查 roles 列是否已存在(防止重复执行) - const tableInfo = db.prepare('PRAGMA table_info(member)').all() as Array<{ name: string }> - const hasRolesColumn = tableInfo.some((col) => col.name === 'roles') + const memberTableInfo = db.prepare('PRAGMA table_info(member)').all() as Array<{ name: string }> + const hasRolesColumn = memberTableInfo.some((col) => col.name === 'roles') if (!hasRolesColumn) { db.exec("ALTER TABLE member ADD COLUMN roles TEXT DEFAULT '[]'") } + + // 检查 message 表的列 + const messageTableInfo = db.prepare('PRAGMA table_info(message)').all() as Array<{ name: string }> + + // 检查 reply_to_message_id 列是否已存在 + const hasReplyColumn = messageTableInfo.some((col) => col.name === 'reply_to_message_id') + if (!hasReplyColumn) { + db.exec('ALTER TABLE message ADD COLUMN reply_to_message_id TEXT DEFAULT NULL') + } + + // 添加 platform_message_id 列(存储平台原始消息 ID,用于回复关联查询) + const hasPlatformMsgIdColumn = messageTableInfo.some((col) => col.name === 'platform_message_id') + if (!hasPlatformMsgIdColumn) { + db.exec('ALTER TABLE message ADD COLUMN platform_message_id TEXT DEFAULT NULL') + } + + // 创建索引以加速回复查询 + try { + db.exec('CREATE INDEX IF NOT EXISTS idx_message_platform_id ON message(platform_message_id)') + } catch { + // 索引可能已存在 + } }, }, ] diff --git a/electron/main/parser/formats/chatlab-jsonl.ts b/electron/main/parser/formats/chatlab-jsonl.ts index 927eb41..4e7a912 100644 --- a/electron/main/parser/formats/chatlab-jsonl.ts +++ b/electron/main/parser/formats/chatlab-jsonl.ts @@ -73,6 +73,7 @@ interface JsonlMessage { timestamp: number type: number content: string | null + replyToMessageId?: string } /** 任意 JSONL 行 */ @@ -215,6 +216,7 @@ async function* parseChatLabJsonl(options: ParseOptions): AsyncGenerator ? ${timeCondition} ${keywordCondition} @@ -585,9 +624,14 @@ export function getConversationBetween( m.avatar, msg.content, msg.ts as timestamp, - msg.type + msg.type, + msg.reply_to_message_id, + reply_msg.content as replyToContent, + COALESCE(reply_m.group_nickname, reply_m.account_name, reply_m.platform_id) as replyToSenderName FROM message msg JOIN member m ON msg.sender_id = m.id + LEFT JOIN message reply_msg ON msg.reply_to_message_id = reply_msg.platform_message_id + LEFT JOIN member reply_m ON reply_msg.sender_id = reply_m.id WHERE msg.sender_id IN (?, ?) ${timeCondition} AND msg.content IS NOT NULL AND msg.content != '' diff --git a/src/components/common/ChatRecord/MessageItem.vue b/src/components/common/ChatRecord/MessageItem.vue index 78b36c4..4976206 100644 --- a/src/components/common/ChatRecord/MessageItem.vue +++ b/src/components/common/ChatRecord/MessageItem.vue @@ -250,6 +250,19 @@ function highlightContent(content: string): string { class="relative inline-block rounded-lg px-3 py-2 transition-shadow" :class="[bubbleColor, isTarget ? 'ring-2 ring-yellow-400 dark:ring-yellow-500' : '']" > + +
+ {{ t('replyTo') }} + + {{ message.replyToSenderName }} + +

+ {{ message.replyToContent }} +

+

diff --git a/src/types/base.ts b/src/types/base.ts index 50f40b6..3cf4e59 100644 --- a/src/types/base.ts +++ b/src/types/base.ts @@ -168,6 +168,7 @@ export interface DbMessage { ts: number // 时间戳(秒) type: MessageType // 消息类型 content: string | null // 纯文本内容 + reply_to_message_id: string | null // 回复的目标消息 ID(平台原始 ID) } // ==================== Parser 解析结果 ==================== @@ -187,12 +188,14 @@ export interface ParsedMember { * 解析后的消息 */ export interface ParsedMessage { + platformMessageId?: string // 消息的平台原始 ID(用于回复关联查询) senderPlatformId: string // 发送者平台ID senderAccountName: string // 发送时的账号名称 senderGroupNickname?: string // 发送时的群昵称(可为空) timestamp: number // 时间戳(秒) type: MessageType // 消息类型 content: string | null // 内容 + replyToMessageId?: string // 回复的目标消息 ID(平台原始 ID,可为空) } /** diff --git a/src/types/format.ts b/src/types/format.ts index 41f1b44..1c18226 100644 --- a/src/types/format.ts +++ b/src/types/format.ts @@ -182,4 +182,7 @@ export interface ChatRecordMessage { content: string timestamp: number type: number + replyToMessageId: string | null // 回复的目标消息 ID(平台原始 ID) + replyToContent: string | null // 被回复消息的内容预览 + replyToSenderName: string | null // 被回复消息的发送者名称 }