From 0c348530669d3ebe098fe61efba8e8b982283f8c Mon Sep 17 00:00:00 2001 From: babybitter <2829245700@qq.com> Date: Sun, 30 Aug 2026 18:30:22 +0800 Subject: [PATCH 01/68] feat(analysis): support voice transcriptions in ChatLab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 适配导入文件中的语音转写内容,让 ChatLab 在语言分析、词频统计和口头禅分析中使用实际转写文字,而不是把语音时长标签当作文本。 识别并移除语音消息前的时长标签,保留转写内容;纯语音占位符继续从文本分析中排除。 统一桌面端、CLI Web 和 Web WASM 使用的文本清洗逻辑,并补充 ChatLab JSONL 导入、词频、语言偏好和口头禅分析测试。 测试使用脱敏后的合成消息,不包含个人聊天记录。 --- packages/core/src/nlp/index.ts | 8 +++- packages/core/src/nlp/text-utils.test.ts | 12 ++++- packages/core/src/nlp/text-utils.ts | 32 ++++++++++++- .../__tests__/browser-word-frequency.test.ts | 19 ++++++++ .../src/query/advanced/languagePreference.ts | 8 ++-- .../core/src/query/advanced/repeat.test.ts | 48 +++++++++++++++++++ packages/core/src/query/advanced/repeat.ts | 34 +++++++++++-- .../core/src/query/advanced/text-filters.ts | 5 +- .../parser/src/formats/chatlab-jsonl.test.ts | 39 +++++++++++++++ 9 files changed, 192 insertions(+), 13 deletions(-) diff --git a/packages/core/src/nlp/index.ts b/packages/core/src/nlp/index.ts index 6fd1c64e3..d09168683 100644 --- a/packages/core/src/nlp/index.ts +++ b/packages/core/src/nlp/index.ts @@ -24,4 +24,10 @@ export { POS_TAG_DEFINITIONS, MEANINGFUL_POS_TAGS } from './pos-tags' export { CHINESE_STOPWORDS, ENGLISH_STOPWORDS, JAPANESE_STOPWORDS, getStopwords, isStopword } from './stopwords' -export { cleanText, isValidWord } from './text-utils' +export { + cleanText, + isMediaPlaceholderContent, + isValidWord, + stripMediaPlaceholders, + stripVoiceTranscriptionPrefix, +} from './text-utils' diff --git a/packages/core/src/nlp/text-utils.test.ts b/packages/core/src/nlp/text-utils.test.ts index 87aedd223..0192d1686 100644 --- a/packages/core/src/nlp/text-utils.test.ts +++ b/packages/core/src/nlp/text-utils.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'node:test' import assert from 'node:assert/strict' -import { cleanText } from './text-utils' +import { cleanText, isMediaPlaceholderContent, stripVoiceTranscriptionPrefix } from './text-utils' describe('cleanText', () => { const cases = [ @@ -22,4 +22,14 @@ describe('cleanText', () => { assert.equal(cleanText(input), expected) }) } + + it('removes duration-bearing voice labels while retaining the transcription', () => { + assert.equal(cleanText('[语音 3秒] 这是测试语音内容。'), '这是测试语音内容') + assert.equal(stripVoiceTranscriptionPrefix('[语音 3秒] 这是测试语音内容。'), '这是测试语音内容。') + }) + + it('recognizes duration-bearing media-only content as a placeholder', () => { + assert.equal(isMediaPlaceholderContent('[语音 3秒]'), true) + assert.equal(isMediaPlaceholderContent('[语音 3秒] 有转写内容'), false) + }) }) diff --git a/packages/core/src/nlp/text-utils.ts b/packages/core/src/nlp/text-utils.ts index c7d4ae1a6..30b4b0942 100644 --- a/packages/core/src/nlp/text-utils.ts +++ b/packages/core/src/nlp/text-utils.ts @@ -8,8 +8,21 @@ const PUNCTUATION_REGEX = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~,。!?、 const URL_REGEX = /https?:\/\/[^\s]+/g const MENTION_REGEX = /@[^\s@]+/g const PURE_NUMBER_REGEX = /^\d+$/ -const SYSTEM_PLACEHOLDER_REGEX = - /\[(?:图片|视频|语音|文件|动画表情|表情|链接|位置|名片|红包|转账|音乐|Image|Video|Voice|File|Sticker|Link)\]/gi +const MEDIA_PLACEHOLDER_NAME_PATTERN = + '(?:图片|视频|语音|文件|动画表情|表情|链接|位置|地理位置|名片|红包|转账|音乐|Image|Photo|Video|Voice|Audio|File|Sticker|Link|Location)' +const MEDIA_PLACEHOLDER_DURATION_PATTERN = '(?:\\s+\\d+(?:\\.\\d+)?\\s*(?:秒|s))?' +const SYSTEM_PLACEHOLDER_REGEX = new RegExp( + `\\[${MEDIA_PLACEHOLDER_NAME_PATTERN}${MEDIA_PLACEHOLDER_DURATION_PATTERN}\\]`, + 'gi' +) +const MEDIA_PLACEHOLDER_ONLY_REGEX = new RegExp( + `^\\[${MEDIA_PLACEHOLDER_NAME_PATTERN}${MEDIA_PLACEHOLDER_DURATION_PATTERN}\\]$`, + 'i' +) +const VOICE_TRANSCRIPTION_PREFIX_REGEX = new RegExp( + `^\\s*\\[(?:语音|Voice|Audio)${MEDIA_PLACEHOLDER_DURATION_PATTERN}\\]\\s*`, + 'i' +) const BRACKET_EMOJI_PLACEHOLDER_REGEX = /(?:\[|【)([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Letter}\p{Number}_-]{1,16})(?:\]|】)/gu const CJK_TEXT_REGEX = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u @@ -118,6 +131,21 @@ export function cleanText(text: string): string { .trim() } +/** Remove media placeholders, including duration-bearing labels such as `[语音 3秒]`. */ +export function stripMediaPlaceholders(text: string): string { + return text.replace(SYSTEM_PLACEHOLDER_REGEX, ' ') +} + +/** Return the transcribed text after a leading voice-message label. */ +export function stripVoiceTranscriptionPrefix(text: string): string { + return text.replace(VOICE_TRANSCRIPTION_PREFIX_REGEX, '').trim() +} + +/** Whether the complete value is a media placeholder rather than message text. */ +export function isMediaPlaceholderContent(text: string): boolean { + return MEDIA_PLACEHOLDER_ONLY_REGEX.test(text.trim()) +} + /** * 判断是否为有效词语 */ diff --git a/packages/core/src/query/__tests__/browser-word-frequency.test.ts b/packages/core/src/query/__tests__/browser-word-frequency.test.ts index f5da2dd9c..67caa4474 100644 --- a/packages/core/src/query/__tests__/browser-word-frequency.test.ts +++ b/packages/core/src/query/__tests__/browser-word-frequency.test.ts @@ -119,6 +119,25 @@ describe('getBrowserWordFrequency', () => { assert.equal(result.uniqueWords, 2) }) + it('keeps voice transcription words but removes the duration label', () => { + const insert = raw.prepare('INSERT INTO message (id, sender_id, ts, type, content) VALUES (?, ?, ?, ?, ?)') + insert.run(6, 1, 600, 0, '[Voice 3s] sample transcript') + insert.run(7, 1, 700, 0, '[Voice 3s] sample transcript') + + const result = getBrowserWordFrequency(database, { + sessionId: 'session-one', + locale: 'en-US', + topN: 20, + minCount: 2, + posFilterMode: 'all', + enableStopwords: false, + }) + + const words = result.words.map((word) => word.word) + assert.ok(words.includes('sample')) + assert.ok(!words.includes('voice')) + }) + it('applies member, time, excluded-word, and excluded-message filters', () => { const result = getBrowserWordFrequency(database, { sessionId: 'session-one', diff --git a/packages/core/src/query/advanced/languagePreference.ts b/packages/core/src/query/advanced/languagePreference.ts index 735a49a76..c25d74f87 100644 --- a/packages/core/src/query/advanced/languagePreference.ts +++ b/packages/core/src/query/advanced/languagePreference.ts @@ -7,6 +7,7 @@ import type { TimeFilter } from '@openchatlab/shared-types' import type { DatabaseAdapter } from '../../interfaces' +import { stripMediaPlaceholders, stripVoiceTranscriptionPrefix } from '../../nlp' import { buildTimeFilter } from '../filters' import { isSystemPlaceholderContent } from './text-filters' @@ -46,7 +47,7 @@ const RE_PUNCTUATION = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~,。!?、; const RE_PURE_NUMBER = /^\d+$/ function cleanTextForNlp(text: string): string { - return text + return stripMediaPlaceholders(text) .replace(RE_URL, ' ') .replace(RE_MENTION, ' ') .replace(RE_EMOJI, ' ') @@ -106,14 +107,15 @@ export function getLanguagePreferenceAnalysis(db: DatabaseAdapter, params: Langu const memberMessages = new Map() for (const row of rows) { - if (isSystemPlaceholderContent(row.content)) continue + const content = stripVoiceTranscriptionPrefix(row.content) + if (!content || isSystemPlaceholderContent(content)) continue let entry = memberMessages.get(row.memberId) if (!entry) { entry = { name: row.name, messages: [] } memberMessages.set(row.memberId, entry) } - entry.messages.push(row.content) + entry.messages.push(content) } const isChinese = locale.startsWith('zh') diff --git a/packages/core/src/query/advanced/repeat.test.ts b/packages/core/src/query/advanced/repeat.test.ts index 708cd6f5d..6bd8f4e32 100644 --- a/packages/core/src/query/advanced/repeat.test.ts +++ b/packages/core/src/query/advanced/repeat.test.ts @@ -170,6 +170,30 @@ describe('getCatchphraseAnalysis', () => { ) db.close() }) + + it('shows voice transcriptions without the duration label', () => { + const db = createCatchphraseDb([ + { memberId: 1, content: '[语音 3秒] 测试转写内容', count: 3 }, + { memberId: 1, content: '[语音 2秒]', count: 5 }, + ]) + + const result = getCatchphraseAnalysis(db) + + assert.deepEqual(result.members[0]?.catchphrases, [{ content: '测试转写内容', count: 3 }]) + db.close() + }) + + it('aggregates the same transcription when voice durations differ', () => { + const db = createCatchphraseDb([ + { memberId: 1, content: '[语音 2秒] 同一句测试转写', count: 1 }, + { memberId: 1, content: '[语音 3秒] 同一句测试转写', count: 1 }, + ]) + + const result = getCatchphraseAnalysis(db) + + assert.deepEqual(result.members[0]?.catchphrases, [{ content: '同一句测试转写', count: 2 }]) + db.close() + }) }) describe('getLanguagePreferenceAnalysis', () => { @@ -188,4 +212,28 @@ describe('getLanguagePreferenceAnalysis', () => { assert.equal(result.members[0]?.totalMessages, 2) assert.deepEqual(result.members[0]?.catchphrases, [{ content: 'noted', count: 2 }]) }) + + it('uses voice transcription text for language analysis instead of the duration label', () => { + const db = createRowsDb([ + { memberId: 1, name: 'Alice', content: '[Voice 3s] sample transcript' }, + { memberId: 1, name: 'Alice', content: '[Voice 3s] sample transcript' }, + { memberId: 1, name: 'Alice', content: '[语音 2秒]' }, + { memberId: 2, name: 'Bob', content: '普通文本' }, + { memberId: 2, name: 'Bob', content: '普通文本' }, + ]) + + const result = getLanguagePreferenceAnalysis(db, { locale: 'en-US' }) + const alice = result.members.find((member: { name: string }) => member.name === 'Alice') + + assert.equal(alice?.totalMessages, 2) + assert.deepEqual(alice?.catchphrases, [{ content: 'sample transcript', count: 2 }]) + assert.equal( + alice?.topWords.some((word: { word: string }) => word.word === 'voice'), + false + ) + assert.equal( + alice?.topWords.some((word: { word: string }) => word.word === 'sample'), + true + ) + }) }) diff --git a/packages/core/src/query/advanced/repeat.ts b/packages/core/src/query/advanced/repeat.ts index a12ed6d2d..698cd897f 100644 --- a/packages/core/src/query/advanced/repeat.ts +++ b/packages/core/src/query/advanced/repeat.ts @@ -4,9 +4,21 @@ import type { TimeFilter } from '@openchatlab/shared-types' import type { DatabaseAdapter } from '../../interfaces' +import { stripVoiceTranscriptionPrefix } from '../../nlp' import { buildTimeFilter } from '../filters' import { isSystemPlaceholderContent } from './text-filters' +// Keep one-off voice transcription rows in the grouped query so JavaScript can +// normalize away duration labels before applying the repeated-phrase threshold. +const VOICE_TRANSCRIPTION_SQL = ` + TRIM(msg.content) LIKE '[语音] %' OR + TRIM(msg.content) LIKE '[语音 %] %' OR + TRIM(msg.content) LIKE '[Voice] %' OR + TRIM(msg.content) LIKE '[Voice %] %' OR + TRIM(msg.content) LIKE '[Audio] %' OR + TRIM(msg.content) LIKE '[Audio %] %' + ` + export interface CatchphraseItem { content: string count: number @@ -48,7 +60,7 @@ export function getCatchphraseAnalysis(db: DatabaseAdapter, filter?: TimeFilter) JOIN member m ON msg.sender_id = m.id ${whereClause} GROUP BY m.id, TRIM(msg.content) - HAVING COUNT(*) >= 2 + HAVING COUNT(*) >= 2 OR (${VOICE_TRANSCRIPTION_SQL}) ORDER BY m.id, count DESC ` ) @@ -61,9 +73,11 @@ export function getCatchphraseAnalysis(db: DatabaseAdapter, filter?: TimeFilter) }> const memberMap = new Map() + const phraseMaps = new Map>() for (const row of rows) { - if (isSystemPlaceholderContent(row.content)) continue + const content = stripVoiceTranscriptionPrefix(row.content) + if (!content || isSystemPlaceholderContent(content)) continue if (!memberMap.has(row.memberId)) { memberMap.set(row.memberId, { @@ -74,13 +88,23 @@ export function getCatchphraseAnalysis(db: DatabaseAdapter, filter?: TimeFilter) }) } - const member = memberMap.get(row.memberId)! - if (member.catchphrases.length < 100) { - member.catchphrases.push({ content: row.content, count: row.count }) + let phrases = phraseMaps.get(row.memberId) + if (!phrases) { + phrases = new Map() + phraseMaps.set(row.memberId, phrases) } + const existing = phrases.get(content) + if (existing) existing.count += Number(row.count) + else phrases.set(content, { content, count: Number(row.count) }) } const members = Array.from(memberMap.values()) + for (const member of members) { + member.catchphrases = [...(phraseMaps.get(member.memberId)?.values() ?? [])] + .filter((item) => item.count >= 2) + .sort((a, b) => b.count - a.count || a.content.localeCompare(b.content)) + .slice(0, 100) + } members.sort((a, b) => { const countDifference = (b.catchphrases[0]?.count ?? 0) - (a.catchphrases[0]?.count ?? 0) return countDifference || a.memberId - b.memberId diff --git a/packages/core/src/query/advanced/text-filters.ts b/packages/core/src/query/advanced/text-filters.ts index 71c2984e1..b8c492ccd 100644 --- a/packages/core/src/query/advanced/text-filters.ts +++ b/packages/core/src/query/advanced/text-filters.ts @@ -1,3 +1,5 @@ +import { isMediaPlaceholderContent } from '../../nlp' + const SYSTEM_PLACEHOLDER_CONTENTS = new Set([ '[图片]', '[视频]', @@ -24,5 +26,6 @@ const SYSTEM_PLACEHOLDER_CONTENTS = new Set([ ]) export function isSystemPlaceholderContent(content: string): boolean { - return SYSTEM_PLACEHOLDER_CONTENTS.has(content.trim()) + const trimmed = content.trim() + return SYSTEM_PLACEHOLDER_CONTENTS.has(trimmed) || isMediaPlaceholderContent(trimmed) } diff --git a/packages/parser/src/formats/chatlab-jsonl.test.ts b/packages/parser/src/formats/chatlab-jsonl.test.ts index 2dfb4d9ca..366b7dc14 100644 --- a/packages/parser/src/formats/chatlab-jsonl.test.ts +++ b/packages/parser/src/formats/chatlab-jsonl.test.ts @@ -84,6 +84,45 @@ test('ChatLab JSONL emits message batches and progress while reading', async () } }) +test('preserves a duration-bearing voice transcription in the parsed message content', async () => { + const root = makeTempDir() + const filePath = path.join(root, 'voice-transcription.jsonl') + const transcription = '[语音 3秒] 这是测试语音内容。' + fs.writeFileSync( + filePath, + [ + JSON.stringify({ + _type: 'header', + chatlab: { version: '0.0.2', exportedAt: 1711468800 }, + meta: { name: 'Voice transcription', platform: 'douyin', type: 'private' }, + }), + JSON.stringify({ _type: 'member', platformId: 'member-1', accountName: 'Alice' }), + JSON.stringify({ + _type: 'message', + sender: 'member-1', + accountName: 'Alice', + timestamp: 1711468800, + type: MessageType.TEXT, + content: transcription, + platformMessageId: 'voice-1', + }), + ].join('\n') + '\n', + 'utf-8' + ) + + try { + const messages: Array<{ type: number; content: string | null }> = [] + for await (const event of parseFileWithFormat('chatlab-jsonl', { filePath })) { + if (event.type === 'messages') { + messages.push(...event.data.map(({ type, content }) => ({ type, content }))) + } + } + assert.deepEqual(messages, [{ type: MessageType.TEXT, content: transcription }]) + } finally { + fs.rmSync(root, { recursive: true, force: true }) + } +}) + test('directory entry detection accepts a top-level ChatLab JSONL file', () => { const root = makeTempDir() const filePath = path.join(root, 'chat.jsonl') From 8b7d62e2484aabaf012b1cd3617c0c1e8fc6fee8 Mon Sep 17 00:00:00 2001 From: babybitter <2829245700@qq.com> Date: Sun, 30 Aug 2026 19:16:33 +0800 Subject: [PATCH 02/68] fix(analysis): separate system messages from emoji phrases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 区分抖音导出中的显式系统通知、分享占位和用户表情,避免系统提示进入口头禅及语言、词频统计。 新增统一的系统/分享前缀识别,覆盖 [系统]、[系统消息]、[分享内容] 及对应全角和英文形式;不误判 [强壮]、[躺平] 等表情标签。口头禅、语言偏好以及 Node/Browser 词频分析支持文本与表情消息,并在统计阶段排除系统通知和分享占位;原始聊天记录保持不变。 补充脱敏合成数据回归测试,覆盖系统通知、分享内容占位排除以及表情保留。 --- packages/core/src/index.ts | 1 + packages/core/src/nlp/index.ts | 1 + packages/core/src/nlp/text-utils.test.ts | 25 ++++++++- packages/core/src/nlp/text-utils.ts | 7 +++ .../__tests__/browser-word-frequency.test.ts | 23 ++++++++ .../src/query/advanced/languagePreference.ts | 10 ++-- .../core/src/query/advanced/repeat.test.ts | 52 ++++++++++++++++++- packages/core/src/query/advanced/repeat.ts | 13 +++-- .../core/src/query/browser-word-frequency.ts | 17 ++++-- .../src/nlp/word-frequency.test.ts | 22 ++++++++ .../node-runtime/src/nlp/word-frequency.ts | 14 +++-- 11 files changed, 162 insertions(+), 23 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index aaa63073d..185d913bf 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -298,6 +298,7 @@ export { getStopwords, isStopword, cleanText, + isSystemMessageContent, isValidWord, } from './nlp' export type { diff --git a/packages/core/src/nlp/index.ts b/packages/core/src/nlp/index.ts index d09168683..ff55c424f 100644 --- a/packages/core/src/nlp/index.ts +++ b/packages/core/src/nlp/index.ts @@ -27,6 +27,7 @@ export { CHINESE_STOPWORDS, ENGLISH_STOPWORDS, JAPANESE_STOPWORDS, getStopwords, export { cleanText, isMediaPlaceholderContent, + isSystemMessageContent, isValidWord, stripMediaPlaceholders, stripVoiceTranscriptionPrefix, diff --git a/packages/core/src/nlp/text-utils.test.ts b/packages/core/src/nlp/text-utils.test.ts index 0192d1686..fcde4649a 100644 --- a/packages/core/src/nlp/text-utils.test.ts +++ b/packages/core/src/nlp/text-utils.test.ts @@ -1,6 +1,11 @@ import { describe, it } from 'node:test' import assert from 'node:assert/strict' -import { cleanText, isMediaPlaceholderContent, stripVoiceTranscriptionPrefix } from './text-utils' +import { + cleanText, + isMediaPlaceholderContent, + isSystemMessageContent, + stripVoiceTranscriptionPrefix, +} from './text-utils' describe('cleanText', () => { const cases = [ @@ -32,4 +37,22 @@ describe('cleanText', () => { assert.equal(isMediaPlaceholderContent('[语音 3秒]'), true) assert.equal(isMediaPlaceholderContent('[语音 3秒] 有转写内容'), false) }) + + it('recognizes explicit system markers without treating bracketed emoji labels as system text', () => { + const cases = [ + ['[系统] 对方赞了你分享的 图文', true], + ['【系统消息】你赞了对方分享的视频', true], + ['[System Message] shared a post', true], + ['[分享内容]', true], + ['【分享内容】', true], + ['[强壮]', false], + ['[躺平]', false], + ['[分享]', false], + ['我说“[系统]”这个词', false], + ] as const + + for (const [input, expected] of cases) { + assert.equal(isSystemMessageContent(input), expected, input) + } + }) }) diff --git a/packages/core/src/nlp/text-utils.ts b/packages/core/src/nlp/text-utils.ts index 30b4b0942..12375e435 100644 --- a/packages/core/src/nlp/text-utils.ts +++ b/packages/core/src/nlp/text-utils.ts @@ -23,6 +23,8 @@ const VOICE_TRANSCRIPTION_PREFIX_REGEX = new RegExp( `^\\s*\\[(?:语音|Voice|Audio)${MEDIA_PLACEHOLDER_DURATION_PATTERN}\\]\\s*`, 'i' ) +const SYSTEM_MESSAGE_PREFIX_REGEX = + /^\s*(?:\[(?:系统(?:消息)?|分享内容|system(?:\s+message)?)\]|【(?:系统(?:消息)?|分享内容|system(?:\s+message)?)】)\s*/iu const BRACKET_EMOJI_PLACEHOLDER_REGEX = /(?:\[|【)([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Letter}\p{Number}_-]{1,16})(?:\]|】)/gu const CJK_TEXT_REGEX = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u @@ -141,6 +143,11 @@ export function stripVoiceTranscriptionPrefix(text: string): string { return text.replace(VOICE_TRANSCRIPTION_PREFIX_REGEX, '').trim() } +/** Identify an explicit system/share marker without classifying bracketed emoji labels as system text. */ +export function isSystemMessageContent(text: string): boolean { + return SYSTEM_MESSAGE_PREFIX_REGEX.test(text) +} + /** Whether the complete value is a media placeholder rather than message text. */ export function isMediaPlaceholderContent(text: string): boolean { return MEDIA_PLACEHOLDER_ONLY_REGEX.test(text.trim()) diff --git a/packages/core/src/query/__tests__/browser-word-frequency.test.ts b/packages/core/src/query/__tests__/browser-word-frequency.test.ts index 67caa4474..6874b58dc 100644 --- a/packages/core/src/query/__tests__/browser-word-frequency.test.ts +++ b/packages/core/src/query/__tests__/browser-word-frequency.test.ts @@ -138,6 +138,29 @@ describe('getBrowserWordFrequency', () => { assert.ok(!words.includes('voice')) }) + it('excludes explicit system notifications from word frequency', () => { + const insert = raw.prepare('INSERT INTO message (id, sender_id, ts, type, content) VALUES (?, ?, ?, ?, ?)') + insert.run(8, 1, 800, 0, '[系统] 对方赞了你分享的 systemnoise') + insert.run(9, 1, 900, 0, '[系统] 对方赞了你分享的 systemnoise') + insert.run(10, 1, 1000, 5, '[强壮]') + insert.run(11, 1, 1100, 0, '[分享内容] share_noise') + insert.run(12, 1, 1200, 0, '[分享内容] share_noise') + + const result = getBrowserWordFrequency(database, { + sessionId: 'session-one', + locale: 'en-US', + topN: 20, + minCount: 1, + posFilterMode: 'all', + enableStopwords: false, + }) + + const words = result.words.map((word) => word.word) + assert.ok(!words.includes('systemnoise')) + assert.ok(!words.includes('share_noise')) + assert.equal(result.totalMessages, 4) + }) + it('applies member, time, excluded-word, and excluded-message filters', () => { const result = getBrowserWordFrequency(database, { sessionId: 'session-one', diff --git a/packages/core/src/query/advanced/languagePreference.ts b/packages/core/src/query/advanced/languagePreference.ts index c25d74f87..a70d20287 100644 --- a/packages/core/src/query/advanced/languagePreference.ts +++ b/packages/core/src/query/advanced/languagePreference.ts @@ -5,9 +5,9 @@ * 调用方负责提供对应平台的实现(Electron / Server 用 jieba,浏览器可降级)。 */ -import type { TimeFilter } from '@openchatlab/shared-types' +import { MessageType, type TimeFilter } from '@openchatlab/shared-types' import type { DatabaseAdapter } from '../../interfaces' -import { stripMediaPlaceholders, stripVoiceTranscriptionPrefix } from '../../nlp' +import { isSystemMessageContent, stripMediaPlaceholders, stripVoiceTranscriptionPrefix } from '../../nlp' import { buildTimeFilter } from '../filters' import { isSystemPlaceholderContent } from './text-filters' @@ -45,6 +45,7 @@ const RE_EMOJI = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu const RE_PUNCTUATION = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~,。!?、;:""''()【】《》…—~·\s]/g const RE_PURE_NUMBER = /^\d+$/ +const LANGUAGE_ANALYSIS_MESSAGE_TYPES = [MessageType.TEXT, MessageType.EMOJI].join(', ') function cleanTextForNlp(text: string): string { return stripMediaPlaceholders(text) @@ -88,8 +89,7 @@ export function getLanguagePreferenceAnalysis(db: DatabaseAdapter, params: Langu const { clause, params: filterParams } = buildTimeFilter(timeFilter) let whereClause = clause - const textFilter = - " COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2" + const textFilter = ` COALESCE(m.account_name, '') != '系统消息' AND msg.type IN (${LANGUAGE_ANALYSIS_MESSAGE_TYPES}) AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2` if (whereClause.includes('WHERE')) { whereClause += ' AND ' + textFilter } else { @@ -108,7 +108,7 @@ export function getLanguagePreferenceAnalysis(db: DatabaseAdapter, params: Langu const memberMessages = new Map() for (const row of rows) { const content = stripVoiceTranscriptionPrefix(row.content) - if (!content || isSystemPlaceholderContent(content)) continue + if (!content || isSystemMessageContent(content) || isSystemPlaceholderContent(content)) continue let entry = memberMessages.get(row.memberId) if (!entry) { diff --git a/packages/core/src/query/advanced/repeat.test.ts b/packages/core/src/query/advanced/repeat.test.ts index 6bd8f4e32..4c1266af5 100644 --- a/packages/core/src/query/advanced/repeat.test.ts +++ b/packages/core/src/query/advanced/repeat.test.ts @@ -1,6 +1,7 @@ import { describe, it } from 'node:test' import assert from 'node:assert/strict' import Database from 'better-sqlite3' +import { MessageType } from '@openchatlab/shared-types' import type { DatabaseAdapter, PreparedStatement, RunResult } from '../../interfaces' import { getLanguagePreferenceAnalysis } from './languagePreference' import { getCatchphraseAnalysis } from './repeat' @@ -91,6 +92,7 @@ interface CatchphraseFixture { memberId: number content: string count: number + type?: number } function createCatchphraseDb(fixtures: CatchphraseFixture[]): DatabaseAdapter { @@ -114,11 +116,17 @@ function createCatchphraseDb(fixtures: CatchphraseFixture[]): DatabaseAdapter { (2, 'bob', 'Bob', NULL); `) - const insert = database.prepare('INSERT INTO message (id, sender_id, ts, type, content) VALUES (?, ?, ?, 0, ?)') + const insert = database.prepare('INSERT INTO message (id, sender_id, ts, type, content) VALUES (?, ?, ?, ?, ?)') let messageId = 1 for (const fixture of fixtures) { for (let index = 0; index < fixture.count; index += 1) { - insert.run(messageId, fixture.memberId, 1_700_000_000 + messageId, fixture.content) + insert.run( + messageId, + fixture.memberId, + 1_700_000_000 + messageId, + fixture.type ?? MessageType.TEXT, + fixture.content + ) messageId += 1 } } @@ -194,6 +202,24 @@ describe('getCatchphraseAnalysis', () => { assert.deepEqual(result.members[0]?.catchphrases, [{ content: '同一句测试转写', count: 2 }]) db.close() }) + + it('filters explicit system notifications but keeps emoji labels', () => { + const db = createCatchphraseDb([ + { memberId: 1, content: '[系统] 对方赞了你分享的 图文', count: 4 }, + { memberId: 1, content: '[系统] 你赞了对方分享的 视频', count: 3 }, + { memberId: 1, content: '[分享内容]', count: 5 }, + { memberId: 1, content: '[强壮]', count: 4, type: MessageType.EMOJI }, + { memberId: 1, content: '[躺平]', count: 3, type: MessageType.TEXT }, + ]) + + const result = getCatchphraseAnalysis(db) + + assert.deepEqual(result.members[0]?.catchphrases, [ + { content: '[强壮]', count: 4 }, + { content: '[躺平]', count: 3 }, + ]) + db.close() + }) }) describe('getLanguagePreferenceAnalysis', () => { @@ -236,4 +262,26 @@ describe('getLanguagePreferenceAnalysis', () => { true ) }) + + it('filters system notifications while retaining bracketed emoji phrases', () => { + const db = createRowsDb([ + { memberId: 1, name: 'Alice', content: '[系统] 对方赞了你分享的 图文' }, + { memberId: 1, name: 'Alice', content: '[系统] 对方赞了你分享的 图文' }, + { memberId: 1, name: 'Alice', content: '[分享内容] share_noise' }, + { memberId: 1, name: 'Alice', content: '[分享内容] share_noise' }, + { memberId: 1, name: 'Alice', content: '[强壮]' }, + { memberId: 1, name: 'Alice', content: '[强壮]' }, + { memberId: 1, name: 'Alice', content: '[躺平]' }, + { memberId: 1, name: 'Alice', content: '[躺平]' }, + ]) + + const result = getLanguagePreferenceAnalysis(db, { locale: 'zh-CN' }) + const alice = result.members.find((member: { name: string }) => member.name === 'Alice') + + assert.equal(alice?.totalMessages, 4) + assert.deepEqual(alice?.catchphrases, [ + { content: '[强壮]', count: 2 }, + { content: '[躺平]', count: 2 }, + ]) + }) }) diff --git a/packages/core/src/query/advanced/repeat.ts b/packages/core/src/query/advanced/repeat.ts index 698cd897f..c437a2491 100644 --- a/packages/core/src/query/advanced/repeat.ts +++ b/packages/core/src/query/advanced/repeat.ts @@ -2,9 +2,9 @@ * 口头禅分析模块(平台无关) */ -import type { TimeFilter } from '@openchatlab/shared-types' +import { MessageType, type TimeFilter } from '@openchatlab/shared-types' import type { DatabaseAdapter } from '../../interfaces' -import { stripVoiceTranscriptionPrefix } from '../../nlp' +import { isSystemMessageContent, stripVoiceTranscriptionPrefix } from '../../nlp' import { buildTimeFilter } from '../filters' import { isSystemPlaceholderContent } from './text-filters' @@ -18,6 +18,7 @@ const VOICE_TRANSCRIPTION_SQL = ` TRIM(msg.content) LIKE '[Audio] %' OR TRIM(msg.content) LIKE '[Audio %] %' ` +const CATCHPHRASE_MESSAGE_TYPES = [MessageType.TEXT, MessageType.EMOJI].join(', ') export interface CatchphraseItem { content: string @@ -40,11 +41,9 @@ export function getCatchphraseAnalysis(db: DatabaseAdapter, filter?: TimeFilter) let whereClause = clause if (whereClause.includes('WHERE')) { - whereClause += - " AND COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2" + whereClause += ` AND COALESCE(m.account_name, '') != '系统消息' AND msg.type IN (${CATCHPHRASE_MESSAGE_TYPES}) AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2` } else { - whereClause = - " WHERE COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2" + whereClause = ` WHERE COALESCE(m.account_name, '') != '系统消息' AND msg.type IN (${CATCHPHRASE_MESSAGE_TYPES}) AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2` } const rows = db @@ -77,7 +76,7 @@ export function getCatchphraseAnalysis(db: DatabaseAdapter, filter?: TimeFilter) for (const row of rows) { const content = stripVoiceTranscriptionPrefix(row.content) - if (!content || isSystemPlaceholderContent(content)) continue + if (!content || isSystemMessageContent(content) || isSystemPlaceholderContent(content)) continue if (!memberMap.has(row.memberId)) { memberMap.set(row.memberId, { diff --git a/packages/core/src/query/browser-word-frequency.ts b/packages/core/src/query/browser-word-frequency.ts index ab1a796f8..a1d678e56 100644 --- a/packages/core/src/query/browser-word-frequency.ts +++ b/packages/core/src/query/browser-word-frequency.ts @@ -1,5 +1,13 @@ import type { DatabaseAdapter } from '../interfaces' -import { cleanText, isStopword, isValidWord, type WordFrequencyParams, type WordFrequencyResult } from '../nlp' +import { MessageType } from '@openchatlab/shared-types' +import { + cleanText, + isStopword, + isSystemMessageContent, + isValidWord, + type WordFrequencyParams, + type WordFrequencyResult, +} from '../nlp' import { buildExcludeKeywordsConditions } from './message-sql' function tokenize(text: string, locale: string): string[] { @@ -34,7 +42,7 @@ export function getBrowserWordFrequency(db: DatabaseAdapter, params: WordFrequen } = params const conditions = [ "COALESCE(m.account_name, '') != '系统消息'", - 'msg.type = 0', + `msg.type IN (${MessageType.TEXT}, ${MessageType.EMOJI})`, 'msg.content IS NOT NULL', "TRIM(msg.content) != ''", ] @@ -66,10 +74,11 @@ export function getBrowserWordFrequency(db: DatabaseAdapter, params: WordFrequen WHERE ${conditions.join(' AND ')}` ) .all(...queryParams) as Array<{ content: string }> + const analyzableMessages = messages.filter((message) => !isSystemMessageContent(message.content)) const excludedWords = new Set(excludeWords.map((word) => word.trim().toLocaleLowerCase(locale)).filter(Boolean)) const frequencies = new Map() - for (const message of messages) { + for (const message of analyzableMessages) { for (const rawWord of tokenize(message.content, locale)) { const word = rawWord.toLocaleLowerCase(locale) if (excludedWords.has(word)) continue @@ -92,7 +101,7 @@ export function getBrowserWordFrequency(db: DatabaseAdapter, params: WordFrequen percentage: rankedTotal > 0 ? Math.round((count / rankedTotal) * 10_000) / 100 : 0, })), totalWords, - totalMessages: messages.length, + totalMessages: analyzableMessages.length, uniqueWords: filteredFrequencies.length, } } diff --git a/packages/node-runtime/src/nlp/word-frequency.test.ts b/packages/node-runtime/src/nlp/word-frequency.test.ts index 081416d72..cfe9e6ab0 100644 --- a/packages/node-runtime/src/nlp/word-frequency.test.ts +++ b/packages/node-runtime/src/nlp/word-frequency.test.ts @@ -112,4 +112,26 @@ describe('computeWordFrequency excludeKeywords pushdown', () => { }) assert.equal(wildcard.totalMessages, 3) }) + + it('excludes explicit system notifications while keeping emoji messages eligible', () => { + const insert = raw.prepare('INSERT INTO message (id, sender_id, ts, type, content) VALUES (?, ?, ?, ?, ?)') + insert.run(4, 1, 4000, 0, '[系统] 你赞了对方分享的 systemnoise') + insert.run(5, 1, 5000, 0, '[系统] 你赞了对方分享的 systemnoise') + insert.run(6, 1, 6000, 5, '[强壮]') + insert.run(7, 1, 7000, 0, '[分享内容] share_noise') + insert.run(8, 1, 8000, 0, '[分享内容] share_noise') + + const result = computeWordFrequency(db, { + sessionId: 's1', + locale: 'en-US', + minCount: 1, + posFilterMode: 'all', + enableStopwords: false, + }) + + const words = result.words.map((word) => word.word) + assert.ok(!words.includes('systemnoise')) + assert.ok(!words.includes('share_noise')) + assert.equal(result.totalMessages, 4) + }) }) diff --git a/packages/node-runtime/src/nlp/word-frequency.ts b/packages/node-runtime/src/nlp/word-frequency.ts index 054487739..5c2421a4a 100644 --- a/packages/node-runtime/src/nlp/word-frequency.ts +++ b/packages/node-runtime/src/nlp/word-frequency.ts @@ -5,7 +5,8 @@ * 供 Electron worker 和 Server HTTP 路由共用。 */ -import { buildExcludeKeywordsConditions, type DatabaseAdapter } from '@openchatlab/core' +import { buildExcludeKeywordsConditions, isSystemMessageContent, type DatabaseAdapter } from '@openchatlab/core' +import { MessageType } from '@openchatlab/shared-types' import type { SupportedLocale, DictType, @@ -44,7 +45,7 @@ function buildMessageQuery( } conditions.push("COALESCE(m.account_name, '') != '系统消息'") - conditions.push('msg.type = 0') + conditions.push(`msg.type IN (${MessageType.TEXT}, ${MessageType.EMOJI})`) conditions.push('msg.content IS NOT NULL') conditions.push("TRIM(msg.content) != ''") @@ -83,7 +84,12 @@ export function computeWordFrequency(db: DatabaseAdapter, params: WordFrequencyP return { words: [], totalWords: 0, totalMessages: 0, uniqueWords: 0 } } - const texts = messages.map((m) => m.content) + const analyzableMessages = messages.filter((message) => !isSystemMessageContent(message.content)) + if (analyzableMessages.length === 0) { + return { words: [], totalWords: 0, totalMessages: 0, uniqueWords: 0 } + } + + const texts = analyzableMessages.map((m) => m.content) const segmentOptions = { minLength: minWordLength, @@ -124,7 +130,7 @@ export function computeWordFrequency(db: DatabaseAdapter, params: WordFrequencyP return { words, totalWords: result.totalWords, - totalMessages: messages.length, + totalMessages: analyzableMessages.length, uniqueWords: result.uniqueWords, posTagStats, } From 4392519d303cc58eddc9145fec2dcba6e0376283 Mon Sep 17 00:00:00 2001 From: digua Date: Fri, 4 Sep 2026 00:21:40 +0800 Subject: [PATCH 03/68] fix(analysis): omit members without repeated catchphrases Voice transcription normalization can remove every phrase for a member while leaving an empty member entry. Filter those entries from the result so overview and selected-member views retain their empty states. Add a regression test for single-use transcriptions alongside another member with valid catchphrases. --- packages/core/src/query/advanced/repeat.test.ts | 14 ++++++++++++++ packages/core/src/query/advanced/repeat.ts | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/core/src/query/advanced/repeat.test.ts b/packages/core/src/query/advanced/repeat.test.ts index 4c1266af5..0c83b11bb 100644 --- a/packages/core/src/query/advanced/repeat.test.ts +++ b/packages/core/src/query/advanced/repeat.test.ts @@ -203,6 +203,20 @@ describe('getCatchphraseAnalysis', () => { db.close() }) + it('omits members whose normalized voice phrases never repeat', (t) => { + const db = createCatchphraseDb([ + { memberId: 1, content: '[语音 3秒] 只转写一次', count: 1 }, + { memberId: 2, content: '收到', count: 3 }, + ]) + t.after(() => db.close()) + + assert.deepEqual( + getCatchphraseAnalysis(db).members.map((member) => member.memberId), + [2] + ) + assert.deepEqual(getCatchphraseAnalysis(db, { memberId: 1 }), { members: [] }) + }) + it('filters explicit system notifications but keeps emoji labels', () => { const db = createCatchphraseDb([ { memberId: 1, content: '[系统] 对方赞了你分享的 图文', count: 4 }, diff --git a/packages/core/src/query/advanced/repeat.ts b/packages/core/src/query/advanced/repeat.ts index c437a2491..0daf79b9e 100644 --- a/packages/core/src/query/advanced/repeat.ts +++ b/packages/core/src/query/advanced/repeat.ts @@ -109,5 +109,5 @@ export function getCatchphraseAnalysis(db: DatabaseAdapter, filter?: TimeFilter) return countDifference || a.memberId - b.memberId }) - return { members } + return { members: members.filter((member) => member.catchphrases.length > 0) } } From a9ec32ad312f76ba94daa7e269d6f74da1328dd8 Mon Sep 17 00:00:00 2001 From: digua Date: Fri, 4 Sep 2026 16:26:12 +0800 Subject: [PATCH 04/68] feat(changelog): add contributor attribution --- changelogs/cn.json | 230 ++++++++++++++++++++----- changelogs/cn.md | 86 ++++----- changelogs/en.json | 228 +++++++++++++++++++----- changelogs/en.md | 86 ++++----- changelogs/ja.json | 228 +++++++++++++++++++----- changelogs/ja.md | 86 ++++----- changelogs/tw.json | 230 ++++++++++++++++++++----- changelogs/tw.md | 86 ++++----- scripts/changelog-to-markdown.mjs | 30 ++-- src/components/home/ChangelogModal.vue | 24 ++- src/components/home/changelogData.ts | 21 ++- 11 files changed, 979 insertions(+), 356 deletions(-) diff --git a/changelogs/cn.json b/changelogs/cn.json index 2d7066428..f25790760 100644 --- a/changelogs/cn.json +++ b/changelogs/cn.json @@ -11,19 +11,37 @@ "新增长期记忆,支持管理偏好、关于我、联系人和群聊记忆,并按问题自动调用相关内容", "长期记忆支持关联来源消息,AI 形成的结论可返回原对话核验", "统一全局与单会话 AI 的对话列表、输入框和 @ 选择交互", - "聊天记录查看器支持拖拽调整宽度并记住布局", + { + "text": "聊天记录查看器支持拖拽调整宽度并记住布局", + "contributors": ["KokerZhou"] + }, "分析页将 SQL 实验室和调试工具统一收纳到“更多”", - "【CLI】支持通过 Unix Domain Socket 启动 Web 服务" + { + "text": "【CLI】支持通过 Unix Domain Socket 启动 Web 服务", + "contributors": ["KokerZhou"] + } ] }, { "type": "fix", "items": [ "修复 WeFlow 与缺少稳定消息 ID 的聊天记录在增量导入、合并时可能过度去重的问题,避免真实消息丢失", - "修复 AI 回复失败后错误状态、会话列表和后续提问可能异常的问题", - "修复 AI 对话保存和编辑时可能留下不完整轮次的问题,并仅允许编辑最新一轮提问", - "修复长对话压缩可能丢失消息边界和记忆来源关联的问题", - "修复同名 AI 服务配置可能读取错误凭据的问题", + { + "text": "修复 AI 回复失败后错误状态、会话列表和后续提问可能异常的问题", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "修复 AI 对话保存和编辑时可能留下不完整轮次的问题,并仅允许编辑最新一轮提问", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "修复长对话压缩可能丢失消息边界和记忆来源关联的问题", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "修复同名 AI 服务配置可能读取错误凭据的问题", + "contributors": ["Sanssssssssssssssss"] + }, "修复切换或返回分析页面时可能重复加载或继续显示导入前旧数据的问题", "完善跨对话分析的时间范围、联系人解析、扫描预算和截断提示" ] @@ -50,7 +68,12 @@ }, { "type": "fix", - "items": ["导入文件夹改为独立按钮,避免与选择文件互相抢入口"] + "items": [ + { + "text": "导入文件夹改为独立按钮,避免与选择文件互相抢入口", + "contributors": ["kongjiyu"] + } + ] }, { "type": "perf", @@ -140,7 +163,10 @@ "items": [ "修复 QQ 自动导入在 UID/UIN 变化、缺失 owner 或合并会话中的匹配与成员映射问题,避免重复会话和成员身份错乱", "修复合并导入因平台消息 ID 冲突或私聊身份不足导致的消息误删,并保留有效 owner 与会话身份信息", - "修复动态关键词排行请求、快速切换与多色模式问题,避免结果过期或未知类型导致页面崩溃", + { + "text": "修复动态关键词排行请求、快速切换与多色模式问题,避免结果过期或未知类型导致页面崩溃", + "contributors": ["kelaocai"] + }, "统一洞察加载过渡,并恢复年度活动日历配色与关系视图切换控件" ] }, @@ -161,7 +187,10 @@ "QQ Shuakami 与 QQ Chat Exporter 导入接入 Rust 原生解析,提升大型导出文件的处理稳定性", "多文件导入新增安全并发调度,避免同一会话并发写入,并提供逐文件进度和失败详情", "新增隐私安全的匿名产品统计,仅上报白名单内的运行环境与功能使用事件", - "【Web CLI】本地语义索引运行组件改为首次使用时按需安装,Docker 镜像继续预装所需组件" + { + "text": "【Web CLI】本地语义索引运行组件改为首次使用时按需安装,Docker 镜像继续预装所需组件", + "contributors": ["kelaocai"] + } ] }, { @@ -240,19 +269,35 @@ "type": "fix", "items": [ "修复纯 Web 端并发访问 OPFS、概览筛选竞争、词频统计和关系分析过滤问题", - "前端图标改为本地打包,避免运行时依赖外部图标服务", + { + "text": "前端图标改为本地打包,避免运行时依赖外部图标服务", + "contributors": ["KokerZhou"] + }, "统一 ChatLab JSONL 自动识别与格式校验规则,并拒绝无法解析的 owner 信息", "【桌面端】阻止迁移过程删除当前数据目录,并拒绝新旧数据目录相互嵌套或指向同一路径", - "【CLI】Docker 部署持久化用户数据与系统状态,并默认仅向 localhost 暴露端口" + { + "text": "【CLI】Docker 部署持久化用户数据与系统状态,并默认仅向 localhost 暴露端口", + "contributors": ["KokerZhou"] + } ] }, { "type": "docs", - "items": ["新增 Docker 部署指南,并完善 AI 分析价值与本地隐私说明"] + "items": [ + { + "text": "新增 Docker 部署指南,并完善 AI 分析价值与本地隐私说明", + "contributors": ["KokerZhou"] + } + ] }, { "type": "ci", - "items": ["发布 amd64 与 arm64 架构的 ChatLab CLI Docker 镜像"] + "items": [ + { + "text": "发布 amd64 与 arm64 架构的 ChatLab CLI Docker 镜像", + "contributors": ["KokerZhou"] + } + ] } ] }, @@ -263,7 +308,12 @@ "changes": [ { "type": "feat", - "items": ["【桌面端】新增应用锁,支持密码、立即锁定、启动时解锁和闲置超时自动锁定"] + "items": [ + { + "text": "【桌面端】新增应用锁,支持密码、立即锁定、启动时解锁和闲置超时自动锁定", + "contributors": ["xyz-225648"] + } + ] }, { "type": "fix", @@ -271,7 +321,10 @@ "数据目录迁移后保留旧数据并提示人工确认清理,避免迁移或重试过程中误删源文件", "大体积 ChatLab JSONL 改为分批流式处理,避免导入卡死并保留合并导入中的成员头像", "统一桌面端与 CLI Web 的多文件批量导入流程,支持串行进度、取消和结果汇总", - "【桌面端】应用锁增加失败次数限制、冷却时间和启动状态保护,避免刷新或重启绕过锁定" + { + "text": "【桌面端】应用锁增加失败次数限制、冷却时间和启动状态保护,避免刷新或重启绕过锁定", + "contributors": ["xyz-225648"] + } ] } ] @@ -334,7 +387,10 @@ "items": [ "统一各端 Push 导入的校验、去重、成员推断和 dry-run 结果,避免预览与实际写入不一致", "补导历史消息后,聊天记录按时间与消息 ID 稳定排序,避免旧消息出现在末尾", - "修复 QCE / QQ Chat Exporter 私聊被误判为群聊并产生“0”成员的问题" + { + "text": "修复 QCE / QQ Chat Exporter 私聊被误判为群聊并产生“0”成员的问题", + "contributors": ["shuakami"] + } ] } ] @@ -359,7 +415,10 @@ "items": [ "修复 ChatLab JSON 导出不可直接重新导入的问题,并保留成员、owner 和别名元数据", "增强自动增量导入的去重、混合消息身份识别和 ChatLab 重复导入区分能力", - "WhatsApp TXT 解析支持带括号时间戳和点号 AM/PM 标记" + { + "text": "WhatsApp TXT 解析支持带括号时间戳和点号 AM/PM 标记", + "contributors": ["hh7418695"] + } ] }, { @@ -415,7 +474,10 @@ "修复 SQL 实验室导出 CSV 失败的问题", "导入 JSONL 等流式成员数据时保留头像,支持增量导入新头像", "打开 AI 日志改为共享路由处理,本机 CLI Web 和桌面端行为保持一致", - "概览身份卡跟随当前选择的时间范围刷新" + { + "text": "概览身份卡跟随当前选择的时间范围刷新", + "contributors": ["qikairo7"] + } ] }, { @@ -584,8 +646,14 @@ "type": "fix", "items": [ "修复多关键词消息搜索功能", - "修复词云词典加载 UX 回归问题", - "修复排行榜标签与 emoji 清洗", + { + "text": "修复词云词典加载 UX 回归问题", + "contributors": ["qikairo7"] + }, + { + "text": "修复排行榜标签与 emoji 清洗", + "contributors": ["qikairo7"] + }, "修复 v8 数据库迁移缺少国际化文本", "【CLI】修复工具适配器中会话上下文传递与预处理缺失" ] @@ -612,8 +680,14 @@ { "type": "fix", "items": [ - "修复高频词统计中含有平台回复消息占位符的问题", - "修复词云文本中包含媒体占位符的问题", + { + "text": "修复高频词统计中含有平台回复消息占位符的问题", + "contributors": ["qikairo7"] + }, + { + "text": "修复词云文本中包含媒体占位符的问题", + "contributors": ["qikairo7"] + }, "修复 Jieba 自定义词典变更后语言偏好缓存与词频缓存未失效的问题", "修复时效性分析缓存未按日失效的问题", "修复时间轴面板默认滚动位置及会话跳转功能失效的问题", @@ -719,7 +793,10 @@ "修复 AI 助手消息未填满聊天区域宽度", "优化分析计划生成区块的展示样式", "修复思考深度选择跨重启不持久的问题", - "修复 JSONL 时间戳规范化导致增量导入异常", + { + "text": "修复 JSONL 时间戳规范化导致增量导入异常", + "contributors": ["cnYui"] + }, "【桌面端】修复 execute_sql 工具未注册到桌面端工具注册表" ] }, @@ -776,8 +853,14 @@ { "type": "feat", "items": [ - "新增斜杠触发的 AI 图表运行时,支持在对话中生成并渲染 ECharts 图表", - "优化 AI 图表结果展示体验,补充图表渲染状态和多类型图表适配" + { + "text": "新增斜杠触发的 AI 图表运行时,支持在对话中生成并渲染 ECharts 图表", + "contributors": ["CEQ151"] + }, + { + "text": "优化 AI 图表结果展示体验,补充图表渲染状态和多类型图表适配", + "contributors": ["CEQ151"] + } ] }, { @@ -787,7 +870,10 @@ "修复预设问题标签和侧边栏元素层叠顺序异常", "【CLI Web】修复开发后端生命周期清理不完整的问题", "【桌面端】修复自动技能未注册图表工具,导致图表技能无法完整执行的问题", - "【桌面端】修复 Windows 标题栏覆盖层切换主题后缓存未刷新和显示不平滑的问题" + { + "text": "【桌面端】修复 Windows 标题栏覆盖层切换主题后缓存未刷新和显示不平滑的问题", + "contributors": ["CEQ151"] + } ] }, { @@ -860,7 +946,10 @@ "items": [ "修复数据目录迁移与数据库迁移流程中的多处边界问题,避免旧库字段缺失或路径切换导致读取失败", "修复数据目录迁移提示在旧数据不存在或目录变更后仍错误显示的问题", - "修复增量导入后侧边栏消息数量未刷新的问题", + { + "text": "修复增量导入后侧边栏消息数量未刷新的问题", + "contributors": ["CEQ151"] + }, "修复侧边栏折叠状态仅存于 sessionStorage 导致刷新后丢失的问题", "修复 AI 配置编辑时已保存密钥未正确启用拉取模型和验证按钮的问题", "修复 AI 共享 SSE 流式响应的稳定性问题", @@ -925,7 +1014,10 @@ { "type": "feat", "items": [ - "新增消息派生(Fork)功能,可从任意 AI 回复处创建独立分支会话", + { + "text": "新增消息派生(Fork)功能,可从任意 AI 回复处创建独立分支会话", + "contributors": ["CEQ151"] + }, "状态栏新增推理强度选择器,支持为每个推理模型单独记忆并切换思维等级", "推理等级控制新增 default/auto 选项,扩展对 Kimi、Doubao、Gemini 等更多模型家族的覆盖", "消息分析视图拆分为「类型分析」和「时间分析」两个标签页,增加丰富统计洞察卡片", @@ -1144,7 +1236,10 @@ "items": [ "修复 Web 模式下会话索引、跨域代理、Demo 保护和运行时异常等问题", "修复 Web 模式下应用版本显示为空的问题", - "修复 AI Agent 证据检索、Web 端事件流和错误格式化等问题", + { + "text": "修复 AI Agent 证据检索、Web 端事件流和错误格式化等问题", + "contributors": ["CEQ151"] + }, "修复同步展示与导入逻辑不一致的问题", "修复 CLI 开发模式下 ESM 模块解析问题", "修复 CLI 安装后可能找不到既有桌面端数据的问题", @@ -1255,7 +1350,10 @@ { "type": "feat", "items": [ - "订阅会话时支持按类型筛选与选择", + { + "text": "订阅会话时支持按类型筛选与选择", + "contributors": ["xunchahaha"] + }, "支持远程会话分页发现与按需加载更多", "支持为数据源配置每次拉取的消息条数" ] @@ -1457,7 +1555,10 @@ "type": "fix", "items": [ "修复 WhatsApp 时间解析正则与行匹配正则宽严不一致问题", - "修复 WhatsApp 12 小时制时间与 NNBSP 字符解析兼容性问题" + { + "text": "修复 WhatsApp 12 小时制时间与 NNBSP 字符解析兼容性问题", + "contributors": ["kongjiyu"] + } ] }, { @@ -1532,7 +1633,10 @@ "优化 AI 对话样式与整体交互体验", "支持 FTS5 全文搜索并新增快速搜索工具", "精简部分工具搜索参数,降低 token 开销", - "新增 Electron 应用 E2E 测试框架,支持端口管理与实例隔离" + { + "text": "新增 Electron 应用 E2E 测试框架,支持端口管理与实例隔离", + "contributors": ["l17728"] + } ] }, { @@ -1545,7 +1649,12 @@ }, { "type": "test", - "items": ["补充可复用的 E2E 启动器冒烟测试能力"] + "items": [ + { + "text": "补充可复用的 E2E 启动器冒烟测试能力", + "contributors": ["l17728"] + } + ] }, { "type": "docs", @@ -1607,7 +1716,10 @@ { "type": "fix", "items": [ - "修复消息去重误判,并统一空字符串去重语义", + { + "text": "修复消息去重误判,并统一空字符串去重语义", + "contributors": ["lnexin"] + }, "修复 AI 会话链路与前端 type-check 错误", "增加默认 assistant 兜底逻辑,修复异常场景", "修复每日消息趋势不展示的问题" @@ -1674,7 +1786,10 @@ "items": [ "新增聊天记录预处理管道", "新增预处理设置界面与配置管理能力", - "Agent 支持基于会话的上下文时间线与运行状态展示", + { + "text": "Agent 支持基于会话的上下文时间线与运行状态展示", + "contributors": ["n-WN"] + }, "新增 AI 调试模式并增强日志可观测性" ] }, @@ -1771,7 +1886,10 @@ "type": "fix", "items": [ "修复增量更新后会话索引生成范围不准确的问题(fix #79)", - "修复迁移与会话扫描时误处理非聊天 SQLite 文件的问题" + { + "text": "修复迁移与会话扫描时误处理非聊天 SQLite 文件的问题", + "contributors": ["n-WN"] + } ] }, { @@ -1801,7 +1919,12 @@ }, { "type": "fix", - "items": ["修复 LINE 聊天记录解析问题"] + "items": [ + { + "text": "修复 LINE 聊天记录解析问题", + "contributors": ["haotien91"] + } + ] }, { "type": "docs", @@ -1817,9 +1940,15 @@ { "type": "feat", "items": [ - "设置内新增数据目录位置配置", + { + "text": "设置内新增数据目录位置配置", + "contributors": ["xunchahaha"] + }, "数据存储目录迁移逻辑优化", - "目录切换新增确认弹窗", + { + "text": "目录切换新增确认弹窗", + "contributors": ["xunchahaha"] + }, "解析逻辑优化(WeFlow / Echotrace)" ] }, @@ -1829,12 +1958,21 @@ "修复 Windows 自定义筛选时消息量过大导致崩溃的问题", "修复第三方中转 API 调用 tool_call 导致对话异常结束的问题", "修复部分 WhatsApp 聊天记录无法正确识别的问题", - "修复管理页面表头层级显示问题" + { + "text": "修复管理页面表头层级显示问题", + "contributors": ["xunchahaha"] + } ] }, { "type": "refactor", - "items": ["重构 session 查询模块", "完善迁移日志输出"] + "items": [ + "重构 session 查询模块", + { + "text": "完善迁移日志输出", + "contributors": ["xunchahaha"] + } + ] } ] }, @@ -2010,7 +2148,10 @@ "支持增量导入", "支持批量导入", "样式优化", - "Windows 端支持原生窗口控制并实现主题同步 (#31)" + { + "text": "Windows 端支持原生窗口控制并实现主题同步 (#31)", + "contributors": ["JiQingzhe2004"] + } ] }, { @@ -2128,7 +2269,10 @@ "支持配置代理(resolve #7)", "优化AI模型界面交互", "添加用户配置API教程", - "1、新增GLM两个免费调用模型 2、新增豆包服务商和对应的最新模型", + { + "text": "1、新增GLM两个免费调用模型 2、新增豆包服务商和对应的最新模型", + "contributors": ["JiQingzhe2004"] + }, "AI回复不输出think内容" ] } diff --git a/changelogs/cn.md b/changelogs/cn.md index 99ba886e0..ccb14eecd 100644 --- a/changelogs/cn.md +++ b/changelogs/cn.md @@ -10,17 +10,17 @@ - 新增长期记忆,支持管理偏好、关于我、联系人和群聊记忆,并按问题自动调用相关内容 - 长期记忆支持关联来源消息,AI 形成的结论可返回原对话核验 - 统一全局与单会话 AI 的对话列表、输入框和 @ 选择交互 -- 聊天记录查看器支持拖拽调整宽度并记住布局 +- 聊天记录查看器支持拖拽调整宽度并记住布局 (by [@KokerZhou](https://github.com/KokerZhou)) - 分析页将 SQL 实验室和调试工具统一收纳到“更多” -- 【CLI】支持通过 Unix Domain Socket 启动 Web 服务 +- 【CLI】支持通过 Unix Domain Socket 启动 Web 服务 (by [@KokerZhou](https://github.com/KokerZhou)) ### 🐛 修复 - 修复 WeFlow 与缺少稳定消息 ID 的聊天记录在增量导入、合并时可能过度去重的问题,避免真实消息丢失 -- 修复 AI 回复失败后错误状态、会话列表和后续提问可能异常的问题 -- 修复 AI 对话保存和编辑时可能留下不完整轮次的问题,并仅允许编辑最新一轮提问 -- 修复长对话压缩可能丢失消息边界和记忆来源关联的问题 -- 修复同名 AI 服务配置可能读取错误凭据的问题 +- 修复 AI 回复失败后错误状态、会话列表和后续提问可能异常的问题 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 修复 AI 对话保存和编辑时可能留下不完整轮次的问题,并仅允许编辑最新一轮提问 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 修复长对话压缩可能丢失消息边界和记忆来源关联的问题 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 修复同名 AI 服务配置可能读取错误凭据的问题 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) - 修复切换或返回分析页面时可能重复加载或继续显示导入前旧数据的问题 - 完善跨对话分析的时间范围、联系人解析、扫描预算和截断提示 @@ -41,7 +41,7 @@ ### 🐛 修复 -- 导入文件夹改为独立按钮,避免与选择文件互相抢入口 +- 导入文件夹改为独立按钮,避免与选择文件互相抢入口 (by [@kongjiyu](https://github.com/kongjiyu)) ### ⚡ 性能 @@ -109,7 +109,7 @@ - 修复 QQ 自动导入在 UID/UIN 变化、缺失 owner 或合并会话中的匹配与成员映射问题,避免重复会话和成员身份错乱 - 修复合并导入因平台消息 ID 冲突或私聊身份不足导致的消息误删,并保留有效 owner 与会话身份信息 -- 修复动态关键词排行请求、快速切换与多色模式问题,避免结果过期或未知类型导致页面崩溃 +- 修复动态关键词排行请求、快速切换与多色模式问题,避免结果过期或未知类型导致页面崩溃 (by [@kelaocai](https://github.com/kelaocai)) - 统一洞察加载过渡,并恢复年度活动日历配色与关系视图切换控件 ### ⚡ 性能 @@ -125,7 +125,7 @@ - QQ Shuakami 与 QQ Chat Exporter 导入接入 Rust 原生解析,提升大型导出文件的处理稳定性 - 多文件导入新增安全并发调度,避免同一会话并发写入,并提供逐文件进度和失败详情 - 新增隐私安全的匿名产品统计,仅上报白名单内的运行环境与功能使用事件 -- 【Web CLI】本地语义索引运行组件改为首次使用时按需安装,Docker 镜像继续预装所需组件 +- 【Web CLI】本地语义索引运行组件改为首次使用时按需安装,Docker 镜像继续预装所需组件 (by [@kelaocai](https://github.com/kelaocai)) ### 🐛 修复 @@ -183,18 +183,18 @@ ### 🐛 修复 - 修复纯 Web 端并发访问 OPFS、概览筛选竞争、词频统计和关系分析过滤问题 -- 前端图标改为本地打包,避免运行时依赖外部图标服务 +- 前端图标改为本地打包,避免运行时依赖外部图标服务 (by [@KokerZhou](https://github.com/KokerZhou)) - 统一 ChatLab JSONL 自动识别与格式校验规则,并拒绝无法解析的 owner 信息 - 【桌面端】阻止迁移过程删除当前数据目录,并拒绝新旧数据目录相互嵌套或指向同一路径 -- 【CLI】Docker 部署持久化用户数据与系统状态,并默认仅向 localhost 暴露端口 +- 【CLI】Docker 部署持久化用户数据与系统状态,并默认仅向 localhost 暴露端口 (by [@KokerZhou](https://github.com/KokerZhou)) ### 📝 文档 -- 新增 Docker 部署指南,并完善 AI 分析价值与本地隐私说明 +- 新增 Docker 部署指南,并完善 AI 分析价值与本地隐私说明 (by [@KokerZhou](https://github.com/KokerZhou)) ### 👷 CI -- 发布 amd64 与 arm64 架构的 ChatLab CLI Docker 镜像 +- 发布 amd64 与 arm64 架构的 ChatLab CLI Docker 镜像 (by [@KokerZhou](https://github.com/KokerZhou)) ## v0.32.0 (2026-07-18) @@ -202,14 +202,14 @@ ### ✨ 新功能 -- 【桌面端】新增应用锁,支持密码、立即锁定、启动时解锁和闲置超时自动锁定 +- 【桌面端】新增应用锁,支持密码、立即锁定、启动时解锁和闲置超时自动锁定 (by [@xyz-225648](https://github.com/xyz-225648)) ### 🐛 修复 - 数据目录迁移后保留旧数据并提示人工确认清理,避免迁移或重试过程中误删源文件 - 大体积 ChatLab JSONL 改为分批流式处理,避免导入卡死并保留合并导入中的成员头像 - 统一桌面端与 CLI Web 的多文件批量导入流程,支持串行进度、取消和结果汇总 -- 【桌面端】应用锁增加失败次数限制、冷却时间和启动状态保护,避免刷新或重启绕过锁定 +- 【桌面端】应用锁增加失败次数限制、冷却时间和启动状态保护,避免刷新或重启绕过锁定 (by [@xyz-225648](https://github.com/xyz-225648)) ## v0.31.3 (2026-07-17) @@ -253,7 +253,7 @@ - 统一各端 Push 导入的校验、去重、成员推断和 dry-run 结果,避免预览与实际写入不一致 - 补导历史消息后,聊天记录按时间与消息 ID 稳定排序,避免旧消息出现在末尾 -- 修复 QCE / QQ Chat Exporter 私聊被误判为群聊并产生“0”成员的问题 +- 修复 QCE / QQ Chat Exporter 私聊被误判为群聊并产生“0”成员的问题 (by [@shuakami](https://github.com/shuakami)) ## v0.31.0 (2026-07-13) @@ -271,7 +271,7 @@ - 修复 ChatLab JSON 导出不可直接重新导入的问题,并保留成员、owner 和别名元数据 - 增强自动增量导入的去重、混合消息身份识别和 ChatLab 重复导入区分能力 -- WhatsApp TXT 解析支持带括号时间戳和点号 AM/PM 标记 +- WhatsApp TXT 解析支持带括号时间戳和点号 AM/PM 标记 (by [@hh7418695](https://github.com/hh7418695)) ### ♻️ 重构 @@ -314,7 +314,7 @@ - 修复 SQL 实验室导出 CSV 失败的问题 - 导入 JSONL 等流式成员数据时保留头像,支持增量导入新头像 - 打开 AI 日志改为共享路由处理,本机 CLI Web 和桌面端行为保持一致 -- 概览身份卡跟随当前选择的时间范围刷新 +- 概览身份卡跟随当前选择的时间范围刷新 (by [@qikairo7](https://github.com/qikairo7)) ### 📝 文档 @@ -445,8 +445,8 @@ ### 🐛 修复 - 修复多关键词消息搜索功能 -- 修复词云词典加载 UX 回归问题 -- 修复排行榜标签与 emoji 清洗 +- 修复词云词典加载 UX 回归问题 (by [@qikairo7](https://github.com/qikairo7)) +- 修复排行榜标签与 emoji 清洗 (by [@qikairo7](https://github.com/qikairo7)) - 修复 v8 数据库迁移缺少国际化文本 - 【CLI】修复工具适配器中会话上下文传递与预处理缺失 @@ -466,8 +466,8 @@ ### 🐛 修复 -- 修复高频词统计中含有平台回复消息占位符的问题 -- 修复词云文本中包含媒体占位符的问题 +- 修复高频词统计中含有平台回复消息占位符的问题 (by [@qikairo7](https://github.com/qikairo7)) +- 修复词云文本中包含媒体占位符的问题 (by [@qikairo7](https://github.com/qikairo7)) - 修复 Jieba 自定义词典变更后语言偏好缓存与词频缓存未失效的问题 - 修复时效性分析缓存未按日失效的问题 - 修复时间轴面板默认滚动位置及会话跳转功能失效的问题 @@ -555,7 +555,7 @@ - 修复 AI 助手消息未填满聊天区域宽度 - 优化分析计划生成区块的展示样式 - 修复思考深度选择跨重启不持久的问题 -- 修复 JSONL 时间戳规范化导致增量导入异常 +- 修复 JSONL 时间戳规范化导致增量导入异常 (by [@cnYui](https://github.com/cnYui)) - 【桌面端】修复 execute_sql 工具未注册到桌面端工具注册表 ### test @@ -600,8 +600,8 @@ ### ✨ 新功能 -- 新增斜杠触发的 AI 图表运行时,支持在对话中生成并渲染 ECharts 图表 -- 优化 AI 图表结果展示体验,补充图表渲染状态和多类型图表适配 +- 新增斜杠触发的 AI 图表运行时,支持在对话中生成并渲染 ECharts 图表 (by [@CEQ151](https://github.com/CEQ151)) +- 优化 AI 图表结果展示体验,补充图表渲染状态和多类型图表适配 (by [@CEQ151](https://github.com/CEQ151)) ### 🐛 修复 @@ -609,7 +609,7 @@ - 修复预设问题标签和侧边栏元素层叠顺序异常 - 【CLI Web】修复开发后端生命周期清理不完整的问题 - 【桌面端】修复自动技能未注册图表工具,导致图表技能无法完整执行的问题 -- 【桌面端】修复 Windows 标题栏覆盖层切换主题后缓存未刷新和显示不平滑的问题 +- 【桌面端】修复 Windows 标题栏覆盖层切换主题后缓存未刷新和显示不平滑的问题 (by [@CEQ151](https://github.com/CEQ151)) ### ♻️ 重构 @@ -666,7 +666,7 @@ - 修复数据目录迁移与数据库迁移流程中的多处边界问题,避免旧库字段缺失或路径切换导致读取失败 - 修复数据目录迁移提示在旧数据不存在或目录变更后仍错误显示的问题 -- 修复增量导入后侧边栏消息数量未刷新的问题 +- 修复增量导入后侧边栏消息数量未刷新的问题 (by [@CEQ151](https://github.com/CEQ151)) - 修复侧边栏折叠状态仅存于 sessionStorage 导致刷新后丢失的问题 - 修复 AI 配置编辑时已保存密钥未正确启用拉取模型和验证按钮的问题 - 修复 AI 共享 SSE 流式响应的稳定性问题 @@ -717,7 +717,7 @@ ### ✨ 新功能 -- 新增消息派生(Fork)功能,可从任意 AI 回复处创建独立分支会话 +- 新增消息派生(Fork)功能,可从任意 AI 回复处创建独立分支会话 (by [@CEQ151](https://github.com/CEQ151)) - 状态栏新增推理强度选择器,支持为每个推理模型单独记忆并切换思维等级 - 推理等级控制新增 default/auto 选项,扩展对 Kimi、Doubao、Gemini 等更多模型家族的覆盖 - 消息分析视图拆分为「类型分析」和「时间分析」两个标签页,增加丰富统计洞察卡片 @@ -900,7 +900,7 @@ - 修复 Web 模式下会话索引、跨域代理、Demo 保护和运行时异常等问题 - 修复 Web 模式下应用版本显示为空的问题 -- 修复 AI Agent 证据检索、Web 端事件流和错误格式化等问题 +- 修复 AI Agent 证据检索、Web 端事件流和错误格式化等问题 (by [@CEQ151](https://github.com/CEQ151)) - 修复同步展示与导入逻辑不一致的问题 - 修复 CLI 开发模式下 ESM 模块解析问题 - 修复 CLI 安装后可能找不到既有桌面端数据的问题 @@ -990,7 +990,7 @@ ### ✨ 新功能 -- 订阅会话时支持按类型筛选与选择 +- 订阅会话时支持按类型筛选与选择 (by [@xunchahaha](https://github.com/xunchahaha)) - 支持远程会话分页发现与按需加载更多 - 支持为数据源配置每次拉取的消息条数 @@ -1147,7 +1147,7 @@ ### 🐛 修复 - 修复 WhatsApp 时间解析正则与行匹配正则宽严不一致问题 -- 修复 WhatsApp 12 小时制时间与 NNBSP 字符解析兼容性问题 +- 修复 WhatsApp 12 小时制时间与 NNBSP 字符解析兼容性问题 (by [@kongjiyu](https://github.com/kongjiyu)) ### 🔧 杂项 @@ -1210,7 +1210,7 @@ - 优化 AI 对话样式与整体交互体验 - 支持 FTS5 全文搜索并新增快速搜索工具 - 精简部分工具搜索参数,降低 token 开销 -- 新增 Electron 应用 E2E 测试框架,支持端口管理与实例隔离 +- 新增 Electron 应用 E2E 测试框架,支持端口管理与实例隔离 (by [@l17728](https://github.com/l17728)) ### 🐛 修复 @@ -1223,7 +1223,7 @@ ### test -- 补充可复用的 E2E 启动器冒烟测试能力 +- 补充可复用的 E2E 启动器冒烟测试能力 (by [@l17728](https://github.com/l17728)) ### 📝 文档 @@ -1274,7 +1274,7 @@ ### 🐛 修复 -- 修复消息去重误判,并统一空字符串去重语义 +- 修复消息去重误判,并统一空字符串去重语义 (by [@lnexin](https://github.com/lnexin)) - 修复 AI 会话链路与前端 type-check 错误 - 增加默认 assistant 兜底逻辑,修复异常场景 - 修复每日消息趋势不展示的问题 @@ -1333,7 +1333,7 @@ - 新增聊天记录预处理管道 - 新增预处理设置界面与配置管理能力 -- Agent 支持基于会话的上下文时间线与运行状态展示 +- Agent 支持基于会话的上下文时间线与运行状态展示 (by [@n-WN](https://github.com/n-WN)) - 新增 AI 调试模式并增强日志可观测性 ### 🐛 修复 @@ -1413,7 +1413,7 @@ ### 🐛 修复 - 修复增量更新后会话索引生成范围不准确的问题(fix #79) -- 修复迁移与会话扫描时误处理非聊天 SQLite 文件的问题 +- 修复迁移与会话扫描时误处理非聊天 SQLite 文件的问题 (by [@n-WN](https://github.com/n-WN)) ### ♻️ 重构 @@ -1437,7 +1437,7 @@ ### 🐛 修复 -- 修复 LINE 聊天记录解析问题 +- 修复 LINE 聊天记录解析问题 (by [@haotien91](https://github.com/haotien91)) ### 📝 文档 @@ -1449,9 +1449,9 @@ ### ✨ 新功能 -- 设置内新增数据目录位置配置 +- 设置内新增数据目录位置配置 (by [@xunchahaha](https://github.com/xunchahaha)) - 数据存储目录迁移逻辑优化 -- 目录切换新增确认弹窗 +- 目录切换新增确认弹窗 (by [@xunchahaha](https://github.com/xunchahaha)) - 解析逻辑优化(WeFlow / Echotrace) ### 🐛 修复 @@ -1459,12 +1459,12 @@ - 修复 Windows 自定义筛选时消息量过大导致崩溃的问题 - 修复第三方中转 API 调用 tool_call 导致对话异常结束的问题 - 修复部分 WhatsApp 聊天记录无法正确识别的问题 -- 修复管理页面表头层级显示问题 +- 修复管理页面表头层级显示问题 (by [@xunchahaha](https://github.com/xunchahaha)) ### ♻️ 重构 - 重构 session 查询模块 -- 完善迁移日志输出 +- 完善迁移日志输出 (by [@xunchahaha](https://github.com/xunchahaha)) ## v0.9.2 (2026-02-02) @@ -1603,7 +1603,7 @@ - 支持增量导入 - 支持批量导入 - 样式优化 -- Windows 端支持原生窗口控制并实现主题同步 (#31) +- Windows 端支持原生窗口控制并实现主题同步 (#31) (by [@JiQingzhe2004](https://github.com/JiQingzhe2004)) ### 🔧 杂项 @@ -1697,7 +1697,7 @@ - 支持配置代理(resolve #7) - 优化AI模型界面交互 - 添加用户配置API教程 -- 1、新增GLM两个免费调用模型 2、新增豆包服务商和对应的最新模型 +- 1、新增GLM两个免费调用模型 2、新增豆包服务商和对应的最新模型 (by [@JiQingzhe2004](https://github.com/JiQingzhe2004)) - AI回复不输出think内容 ## v0.1.3 (2025-12-25) diff --git a/changelogs/en.json b/changelogs/en.json index f9fef0c95..ea720530c 100644 --- a/changelogs/en.json +++ b/changelogs/en.json @@ -11,19 +11,37 @@ "Add long-term memory for managing preferences, details about yourself, contacts, and groups, with relevant entries loaded automatically for each question", "Link long-term memories to source messages so AI-derived conclusions can be verified in the original conversation", "Unify conversation lists, composers, and @ mention selection across global and per-chat AI conversations", - "Let users resize the chat record viewer by dragging and remember the chosen width", + { + "text": "Let users resize the chat record viewer by dragging and remember the chosen width", + "contributors": ["KokerZhou"] + }, "Move SQL Lab and debug tools into a unified More section on analysis pages", - "【CLI】Support starting the Web service over a Unix Domain Socket" + { + "text": "【CLI】Support starting the Web service over a Unix Domain Socket", + "contributors": ["KokerZhou"] + } ] }, { "type": "fix", "items": [ "Fix excessive deduplication when incrementally importing or merging WeFlow records and messages without stable IDs, preventing valid messages from being lost", - "Fix error state, conversation list, and follow-up issues after an AI reply fails", - "Prevent incomplete turns when saving or editing AI conversations, and allow editing only the latest user turn", - "Fix long-conversation compression losing message boundaries or links to memory sources", - "Fix AI services with the same configuration name loading the wrong credentials", + { + "text": "Fix error state, conversation list, and follow-up issues after an AI reply fails", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "Prevent incomplete turns when saving or editing AI conversations, and allow editing only the latest user turn", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "Fix long-conversation compression losing message boundaries or links to memory sources", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "Fix AI services with the same configuration name loading the wrong credentials", + "contributors": ["Sanssssssssssssssss"] + }, "Fix analysis pages reloading duplicate data or showing stale results after switching away and back", "Improve time ranges, contact resolution, scan budgets, and truncation notices for cross-chat analysis" ] @@ -52,7 +70,12 @@ }, { "type": "fix", - "items": ["Turn folder import into a dedicated button so it no longer competes with file selection"] + "items": [ + { + "text": "Turn folder import into a dedicated button so it no longer competes with file selection", + "contributors": ["kongjiyu"] + } + ] }, { "type": "perf", @@ -147,7 +170,10 @@ "items": [ "Fix QQ automatic import matching and member mapping when UID/UIN values change, owner metadata is missing, or sessions have been merged, preventing duplicate sessions and incorrect member identities", "Prevent merge imports from incorrectly removing messages when platform message IDs conflict or private-chat identity is incomplete, while preserving valid owner and session identity metadata", - "Fix request handling, rapid switching, and multi-color mode in dynamic keyword rankings, preventing stale results and crashes caused by unknown types", + { + "text": "Fix request handling, rapid switching, and multi-color mode in dynamic keyword rankings, preventing stale results and crashes caused by unknown types", + "contributors": ["kelaocai"] + }, "Unify loading transitions across Insights, and restore annual activity calendar colors and relationship view controls" ] }, @@ -170,7 +196,10 @@ "Use the Rust native parser for QQ Shuakami and QQ Chat Exporter imports to improve reliability with large exports", "Add safe concurrent scheduling for multi-file imports, preventing concurrent writes to the same session while reporting per-file progress and failures", "Add privacy-preserving anonymous product analytics that only report allowlisted environment and feature usage events", - "【CLI Web】Install the local semantic-index runtime on first use, while keeping it preinstalled in the Docker image" + { + "text": "【CLI Web】Install the local semantic-index runtime on first use, while keeping it preinstalled in the Docker image", + "contributors": ["kelaocai"] + } ] }, { @@ -253,19 +282,35 @@ "type": "fix", "items": [ "Fix concurrent OPFS access, overview filter race conditions, word frequency totals, and relationship analysis filters in the browser app", - "Bundle frontend icons locally to remove the runtime dependency on external icon services", + { + "text": "Bundle frontend icons locally to remove the runtime dependency on external icon services", + "contributors": ["KokerZhou"] + }, "Align ChatLab JSONL auto-detection with format validation and reject unresolved owner metadata", "【Desktop】Prevent migrations from deleting the active data directory and reject nested or equivalent legacy and active data paths", - "【CLI】Persist user data and system state in Docker deployments, and bind published ports to localhost by default" + { + "text": "【CLI】Persist user data and system state in Docker deployments, and bind published ports to localhost by default", + "contributors": ["KokerZhou"] + } ] }, { "type": "docs", - "items": ["Add a Docker deployment guide and clarify the benefits of AI analysis and local data privacy"] + "items": [ + { + "text": "Add a Docker deployment guide and clarify the benefits of AI analysis and local data privacy", + "contributors": ["KokerZhou"] + } + ] }, { "type": "ci", - "items": ["Publish ChatLab CLI Docker images for amd64 and arm64"] + "items": [ + { + "text": "Publish ChatLab CLI Docker images for amd64 and arm64", + "contributors": ["KokerZhou"] + } + ] } ] }, @@ -277,7 +322,10 @@ { "type": "feat", "items": [ - "【Desktop】Add an app lock with password protection, immediate locking, a startup unlock prompt, and automatic locking after inactivity" + { + "text": "【Desktop】Add an app lock with password protection, immediate locking, a startup unlock prompt, and automatic locking after inactivity", + "contributors": ["xyz-225648"] + } ] }, { @@ -286,7 +334,10 @@ "Keep old data after migrating the data directory and prompt for manual cleanup, preventing source files from being deleted during migrations or retries", "Process large ChatLab JSONL files in streaming batches to prevent import freezes and preserve member avatars in merged imports", "Unify multi-file batch imports across Desktop and CLI Web with sequential progress, cancellation, and result summaries", - "【Desktop】Add failed-attempt limits, cooldowns, and startup state protection to the app lock so refreshes or restarts cannot bypass it" + { + "text": "【Desktop】Add failed-attempt limits, cooldowns, and startup state protection to the app lock so refreshes or restarts cannot bypass it", + "contributors": ["xyz-225648"] + } ] } ] @@ -349,7 +400,10 @@ "items": [ "Align validation, deduplication, member inference, and dry-run results for Push imports across runtimes", "Keep backfilled chat history in chronological order using timestamps and message IDs", - "Fix QCE / QQ Chat Exporter private chats being detected as groups and creating a phantom “0” member" + { + "text": "Fix QCE / QQ Chat Exporter private chats being detected as groups and creating a phantom “0” member", + "contributors": ["shuakami"] + } ] } ] @@ -374,7 +428,10 @@ "items": [ "Fix ChatLab JSON exports so they can be imported again while preserving member, owner, and alias metadata", "Improve automatic incremental import deduplication, mixed message identity handling, and ChatLab reimport disambiguation", - "Support bracketed timestamps and dotted AM/PM markers in WhatsApp TXT parsing" + { + "text": "Support bracketed timestamps and dotted AM/PM markers in WhatsApp TXT parsing", + "contributors": ["hh7418695"] + } ] }, { @@ -435,7 +492,10 @@ "Fix CSV export failures in SQL Lab", "Preserve avatars when importing JSONL and other streamed member data, including incremental avatar imports", "Open AI logs through the shared route so local CLI Web and Desktop behave consistently", - "Refresh the overview identity card when the selected time range changes" + { + "text": "Refresh the overview identity card when the selected time range changes", + "contributors": ["qikairo7"] + } ] }, { @@ -606,8 +666,14 @@ "type": "fix", "items": [ "Fix multi-keyword message search", - "Fix UX regression in word cloud dictionary loading", - "Fix ranking label formatting and emoji cleanup", + { + "text": "Fix UX regression in word cloud dictionary loading", + "contributors": ["qikairo7"] + }, + { + "text": "Fix ranking label formatting and emoji cleanup", + "contributors": ["qikairo7"] + }, "Add missing i18n text for the v8 database migration", "[CLI] Fix missing session context forwarding and preprocessing in the tool adapter" ] @@ -634,8 +700,14 @@ { "type": "fix", "items": [ - "Fix catchphrase stats including platform reply message placeholders", - "Fix word cloud text including media placeholders", + { + "text": "Fix catchphrase stats including platform reply message placeholders", + "contributors": ["qikairo7"] + }, + { + "text": "Fix word cloud text including media placeholders", + "contributors": ["qikairo7"] + }, "Fix language-preference and word-frequency caches not being invalidated when the Jieba custom dictionary changes", "Fix time-sensitive analytics cache entries not expiring daily", "Fix timeline panel default scroll position and session jump not working", @@ -744,7 +816,10 @@ "Fix assistant messages not filling the full chat area width", "Improve the visual layout of the analysis plan generation block", "Fix thinking-level selection not persisting across restarts", - "Fix JSONL timestamp normalization causing incremental import failures", + { + "text": "Fix JSONL timestamp normalization causing incremental import failures", + "contributors": ["cnYui"] + }, "Desktop: Fix `execute_sql` tool not being registered in the desktop tool registry" ] }, @@ -803,8 +878,14 @@ { "type": "feat", "items": [ - "Add a slash-activated AI chart runtime that can generate and render ECharts charts in conversations", - "Improve the AI chart result experience with clearer rendering states and support for more chart types" + { + "text": "Add a slash-activated AI chart runtime that can generate and render ECharts charts in conversations", + "contributors": ["CEQ151"] + }, + { + "text": "Improve the AI chart result experience with clearer rendering states and support for more chart types", + "contributors": ["CEQ151"] + } ] }, { @@ -814,7 +895,10 @@ "Fix preset question chips and sidebar elements stacking incorrectly", "CLI Web: Fix incomplete cleanup of the development backend lifecycle", "Desktop: Register the chart tool for automatic skills so chart skills can run correctly", - "Desktop: Refresh the Windows title bar overlay cache after theme changes and smooth its display" + { + "text": "Desktop: Refresh the Windows title bar overlay cache after theme changes and smooth its display", + "contributors": ["CEQ151"] + } ] }, { @@ -889,7 +973,10 @@ "items": [ "Fix multiple edge cases in data directory and database migrations to prevent missing legacy columns or failed reads after path changes", "Fix legacy data migration prompts appearing incorrectly when no legacy data exists or after directory changes", - "Fix sidebar message counts not refreshing after incremental imports", + { + "text": "Fix sidebar message counts not refreshing after incremental imports", + "contributors": ["CEQ151"] + }, "Fix sidebar collapsed state being lost after refresh because it was stored only in sessionStorage", "Fix fetch-models and validate buttons not being enabled when editing AI settings with a saved key", "Fix stability issues in shared AI SSE streaming", @@ -956,7 +1043,10 @@ { "type": "feat", "items": [ - "Add a Fork button to AI replies to branch the conversation from any point into an independent session", + { + "text": "Add a Fork button to AI replies to branch the conversation from any point into an independent session", + "contributors": ["CEQ151"] + }, "Add a per-model thinking level selector in the status bar, with the choice remembered per model slot", "Extend thinking level controls with default and auto options, covering Kimi, Doubao, Gemini, and more model families", "Split the message analysis view into Type Analysis and Time Analysis tabs, each with enriched statistical insight cards", @@ -1184,7 +1274,10 @@ "items": [ "Fix Web-mode issues around session indexes, CORS proxying, demo safeguards, and runtime errors", "Fix the app version showing as empty in Web mode", - "Fix AI Agent evidence retrieval, Web event streaming, and error formatting issues", + { + "text": "Fix AI Agent evidence retrieval, Web event streaming, and error formatting issues", + "contributors": ["CEQ151"] + }, "Fix inconsistencies between sync display and import logic", "Fix ESM module resolution in CLI development mode", "Fix cases where the CLI could not find existing desktop data after installation", @@ -1299,7 +1392,10 @@ { "type": "feat", "items": [ - "Add session type filtering and selection when subscribing to sessions.", + { + "text": "Add session type filtering and selection when subscribing to sessions.", + "contributors": ["xunchahaha"] + }, "Add paginated remote session discovery with on-demand loading.", "Allow each data source to configure how many messages are fetched per request." ] @@ -1503,7 +1599,10 @@ "type": "fix", "items": [ "Fix inconsistency between WhatsApp time-parsing regex rules and line-matching regex rules.", - "Fix compatibility issues when parsing WhatsApp 12-hour time formats and NNBSP characters." + { + "text": "Fix compatibility issues when parsing WhatsApp 12-hour time formats and NNBSP characters.", + "contributors": ["kongjiyu"] + } ] }, { @@ -1584,7 +1683,10 @@ "Improve AI chat styling and overall interaction flow.", "Add FTS5 full-text search support with a quick search tool.", "Trim search parameters in selected tools to reduce token usage.", - "Add an E2E testing framework for Electron apps with port management and instance isolation." + { + "text": "Add an E2E testing framework for Electron apps with port management and instance isolation.", + "contributors": ["l17728"] + } ] }, { @@ -1600,7 +1702,12 @@ }, { "type": "test", - "items": ["Add reusable smoke-test coverage for the E2E app launcher."] + "items": [ + { + "text": "Add reusable smoke-test coverage for the E2E app launcher.", + "contributors": ["l17728"] + } + ] }, { "type": "docs", @@ -1662,7 +1769,10 @@ { "type": "fix", "items": [ - "Fix false positives in message deduplication and unify empty-string deduplication behavior", + { + "text": "Fix false positives in message deduplication and unify empty-string deduplication behavior", + "contributors": ["lnexin"] + }, "Fix AI conversation flow issues and frontend type-check errors", "Add a fallback default assistant for edge cases", "Fix daily message trends not rendering" @@ -1729,7 +1839,10 @@ "items": [ "Add a chat-history preprocessing pipeline.", "Add preprocessing settings UI and configuration management.", - "Add session-based context timelines and runtime status for the Agent.", + { + "text": "Add session-based context timelines and runtime status for the Agent.", + "contributors": ["n-WN"] + }, "Add an AI debug mode with improved log observability." ] }, @@ -1836,7 +1949,10 @@ "type": "fix", "items": [ "Fix inaccurate session index generation scope after incremental updates (fix #79).", - "Fix non-chat SQLite files being incorrectly processed during migration and session scanning." + { + "text": "Fix non-chat SQLite files being incorrectly processed during migration and session scanning.", + "contributors": ["n-WN"] + } ] }, { @@ -1866,7 +1982,12 @@ }, { "type": "fix", - "items": ["Fix parsing issues for LINE chat logs."] + "items": [ + { + "text": "Fix parsing issues for LINE chat logs.", + "contributors": ["haotien91"] + } + ] }, { "type": "docs", @@ -1882,9 +2003,15 @@ { "type": "feat", "items": [ - "Add a data directory location setting", + { + "text": "Add a data directory location setting", + "contributors": ["xunchahaha"] + }, "Optimize data directory migration logic", - "Add a confirmation dialog for directory switching", + { + "text": "Add a confirmation dialog for directory switching", + "contributors": ["xunchahaha"] + }, "Improve parser logic (WeFlow / Echotrace)" ] }, @@ -1894,12 +2021,21 @@ "Fix crashes on Windows when custom filtering processes large message volumes", "Fix conversations ending early when third-party relay APIs call tool_call", "Fix some WhatsApp chat logs not being detected correctly", - "Fix manage page header stacking above settings" + { + "text": "Fix manage page header stacking above settings", + "contributors": ["xunchahaha"] + } ] }, { "type": "refactor", - "items": ["Refactor session query module", "Improve migration logging"] + "items": [ + "Refactor session query module", + { + "text": "Improve migration logging", + "contributors": ["xunchahaha"] + } + ] } ] }, @@ -2079,7 +2215,10 @@ "Support incremental import", "Support batch import", "Style improvements", - "Support native window controls and theme sync on Windows (#31)" + { + "text": "Support native window controls and theme sync on Windows (#31)", + "contributors": ["JiQingzhe2004"] + } ] }, { @@ -2197,7 +2336,10 @@ "Support proxy configuration (resolve #7)", "Improve AI model UI interactions", "Add API tutorial for user configuration", - "Add two free GLM models; add Doubao provider and latest models", + { + "text": "Add two free GLM models; add Doubao provider and latest models", + "contributors": ["JiQingzhe2004"] + }, "AI replies no longer output think content" ] } diff --git a/changelogs/en.md b/changelogs/en.md index 6917e2ce0..f6e9281fd 100644 --- a/changelogs/en.md +++ b/changelogs/en.md @@ -10,17 +10,17 @@ - Add long-term memory for managing preferences, details about yourself, contacts, and groups, with relevant entries loaded automatically for each question - Link long-term memories to source messages so AI-derived conclusions can be verified in the original conversation - Unify conversation lists, composers, and @ mention selection across global and per-chat AI conversations -- Let users resize the chat record viewer by dragging and remember the chosen width +- Let users resize the chat record viewer by dragging and remember the chosen width (by [@KokerZhou](https://github.com/KokerZhou)) - Move SQL Lab and debug tools into a unified More section on analysis pages -- 【CLI】Support starting the Web service over a Unix Domain Socket +- 【CLI】Support starting the Web service over a Unix Domain Socket (by [@KokerZhou](https://github.com/KokerZhou)) ### 🐛 Bug Fixes - Fix excessive deduplication when incrementally importing or merging WeFlow records and messages without stable IDs, preventing valid messages from being lost -- Fix error state, conversation list, and follow-up issues after an AI reply fails -- Prevent incomplete turns when saving or editing AI conversations, and allow editing only the latest user turn -- Fix long-conversation compression losing message boundaries or links to memory sources -- Fix AI services with the same configuration name loading the wrong credentials +- Fix error state, conversation list, and follow-up issues after an AI reply fails (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- Prevent incomplete turns when saving or editing AI conversations, and allow editing only the latest user turn (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- Fix long-conversation compression losing message boundaries or links to memory sources (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- Fix AI services with the same configuration name loading the wrong credentials (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) - Fix analysis pages reloading duplicate data or showing stale results after switching away and back - Improve time ranges, contact resolution, scan budgets, and truncation notices for cross-chat analysis @@ -41,7 +41,7 @@ ### 🐛 Bug Fixes -- Turn folder import into a dedicated button so it no longer competes with file selection +- Turn folder import into a dedicated button so it no longer competes with file selection (by [@kongjiyu](https://github.com/kongjiyu)) ### ⚡ Performance @@ -109,7 +109,7 @@ - Fix QQ automatic import matching and member mapping when UID/UIN values change, owner metadata is missing, or sessions have been merged, preventing duplicate sessions and incorrect member identities - Prevent merge imports from incorrectly removing messages when platform message IDs conflict or private-chat identity is incomplete, while preserving valid owner and session identity metadata -- Fix request handling, rapid switching, and multi-color mode in dynamic keyword rankings, preventing stale results and crashes caused by unknown types +- Fix request handling, rapid switching, and multi-color mode in dynamic keyword rankings, preventing stale results and crashes caused by unknown types (by [@kelaocai](https://github.com/kelaocai)) - Unify loading transitions across Insights, and restore annual activity calendar colors and relationship view controls ### ⚡ Performance @@ -125,7 +125,7 @@ - Use the Rust native parser for QQ Shuakami and QQ Chat Exporter imports to improve reliability with large exports - Add safe concurrent scheduling for multi-file imports, preventing concurrent writes to the same session while reporting per-file progress and failures - Add privacy-preserving anonymous product analytics that only report allowlisted environment and feature usage events -- 【CLI Web】Install the local semantic-index runtime on first use, while keeping it preinstalled in the Docker image +- 【CLI Web】Install the local semantic-index runtime on first use, while keeping it preinstalled in the Docker image (by [@kelaocai](https://github.com/kelaocai)) ### 🐛 Bug Fixes @@ -183,18 +183,18 @@ ### 🐛 Bug Fixes - Fix concurrent OPFS access, overview filter race conditions, word frequency totals, and relationship analysis filters in the browser app -- Bundle frontend icons locally to remove the runtime dependency on external icon services +- Bundle frontend icons locally to remove the runtime dependency on external icon services (by [@KokerZhou](https://github.com/KokerZhou)) - Align ChatLab JSONL auto-detection with format validation and reject unresolved owner metadata - 【Desktop】Prevent migrations from deleting the active data directory and reject nested or equivalent legacy and active data paths -- 【CLI】Persist user data and system state in Docker deployments, and bind published ports to localhost by default +- 【CLI】Persist user data and system state in Docker deployments, and bind published ports to localhost by default (by [@KokerZhou](https://github.com/KokerZhou)) ### 📝 Documentation -- Add a Docker deployment guide and clarify the benefits of AI analysis and local data privacy +- Add a Docker deployment guide and clarify the benefits of AI analysis and local data privacy (by [@KokerZhou](https://github.com/KokerZhou)) ### 👷 CI -- Publish ChatLab CLI Docker images for amd64 and arm64 +- Publish ChatLab CLI Docker images for amd64 and arm64 (by [@KokerZhou](https://github.com/KokerZhou)) ## v0.32.0 (2026-07-18) @@ -202,14 +202,14 @@ ### ✨ Features -- 【Desktop】Add an app lock with password protection, immediate locking, a startup unlock prompt, and automatic locking after inactivity +- 【Desktop】Add an app lock with password protection, immediate locking, a startup unlock prompt, and automatic locking after inactivity (by [@xyz-225648](https://github.com/xyz-225648)) ### 🐛 Bug Fixes - Keep old data after migrating the data directory and prompt for manual cleanup, preventing source files from being deleted during migrations or retries - Process large ChatLab JSONL files in streaming batches to prevent import freezes and preserve member avatars in merged imports - Unify multi-file batch imports across Desktop and CLI Web with sequential progress, cancellation, and result summaries -- 【Desktop】Add failed-attempt limits, cooldowns, and startup state protection to the app lock so refreshes or restarts cannot bypass it +- 【Desktop】Add failed-attempt limits, cooldowns, and startup state protection to the app lock so refreshes or restarts cannot bypass it (by [@xyz-225648](https://github.com/xyz-225648)) ## v0.31.3 (2026-07-17) @@ -253,7 +253,7 @@ - Align validation, deduplication, member inference, and dry-run results for Push imports across runtimes - Keep backfilled chat history in chronological order using timestamps and message IDs -- Fix QCE / QQ Chat Exporter private chats being detected as groups and creating a phantom “0” member +- Fix QCE / QQ Chat Exporter private chats being detected as groups and creating a phantom “0” member (by [@shuakami](https://github.com/shuakami)) ## v0.31.0 (2026-07-13) @@ -271,7 +271,7 @@ - Fix ChatLab JSON exports so they can be imported again while preserving member, owner, and alias metadata - Improve automatic incremental import deduplication, mixed message identity handling, and ChatLab reimport disambiguation -- Support bracketed timestamps and dotted AM/PM markers in WhatsApp TXT parsing +- Support bracketed timestamps and dotted AM/PM markers in WhatsApp TXT parsing (by [@hh7418695](https://github.com/hh7418695)) ### ♻️ Refactoring @@ -314,7 +314,7 @@ - Fix CSV export failures in SQL Lab - Preserve avatars when importing JSONL and other streamed member data, including incremental avatar imports - Open AI logs through the shared route so local CLI Web and Desktop behave consistently -- Refresh the overview identity card when the selected time range changes +- Refresh the overview identity card when the selected time range changes (by [@qikairo7](https://github.com/qikairo7)) ### 📝 Documentation @@ -445,8 +445,8 @@ ### 🐛 Bug Fixes - Fix multi-keyword message search -- Fix UX regression in word cloud dictionary loading -- Fix ranking label formatting and emoji cleanup +- Fix UX regression in word cloud dictionary loading (by [@qikairo7](https://github.com/qikairo7)) +- Fix ranking label formatting and emoji cleanup (by [@qikairo7](https://github.com/qikairo7)) - Add missing i18n text for the v8 database migration - [CLI] Fix missing session context forwarding and preprocessing in the tool adapter @@ -466,8 +466,8 @@ ### 🐛 Bug Fixes -- Fix catchphrase stats including platform reply message placeholders -- Fix word cloud text including media placeholders +- Fix catchphrase stats including platform reply message placeholders (by [@qikairo7](https://github.com/qikairo7)) +- Fix word cloud text including media placeholders (by [@qikairo7](https://github.com/qikairo7)) - Fix language-preference and word-frequency caches not being invalidated when the Jieba custom dictionary changes - Fix time-sensitive analytics cache entries not expiring daily - Fix timeline panel default scroll position and session jump not working @@ -555,7 +555,7 @@ - Fix assistant messages not filling the full chat area width - Improve the visual layout of the analysis plan generation block - Fix thinking-level selection not persisting across restarts -- Fix JSONL timestamp normalization causing incremental import failures +- Fix JSONL timestamp normalization causing incremental import failures (by [@cnYui](https://github.com/cnYui)) - Desktop: Fix `execute_sql` tool not being registered in the desktop tool registry ### test @@ -600,8 +600,8 @@ ### ✨ Features -- Add a slash-activated AI chart runtime that can generate and render ECharts charts in conversations -- Improve the AI chart result experience with clearer rendering states and support for more chart types +- Add a slash-activated AI chart runtime that can generate and render ECharts charts in conversations (by [@CEQ151](https://github.com/CEQ151)) +- Improve the AI chart result experience with clearer rendering states and support for more chart types (by [@CEQ151](https://github.com/CEQ151)) ### 🐛 Bug Fixes @@ -609,7 +609,7 @@ - Fix preset question chips and sidebar elements stacking incorrectly - CLI Web: Fix incomplete cleanup of the development backend lifecycle - Desktop: Register the chart tool for automatic skills so chart skills can run correctly -- Desktop: Refresh the Windows title bar overlay cache after theme changes and smooth its display +- Desktop: Refresh the Windows title bar overlay cache after theme changes and smooth its display (by [@CEQ151](https://github.com/CEQ151)) ### ♻️ Refactoring @@ -666,7 +666,7 @@ - Fix multiple edge cases in data directory and database migrations to prevent missing legacy columns or failed reads after path changes - Fix legacy data migration prompts appearing incorrectly when no legacy data exists or after directory changes -- Fix sidebar message counts not refreshing after incremental imports +- Fix sidebar message counts not refreshing after incremental imports (by [@CEQ151](https://github.com/CEQ151)) - Fix sidebar collapsed state being lost after refresh because it was stored only in sessionStorage - Fix fetch-models and validate buttons not being enabled when editing AI settings with a saved key - Fix stability issues in shared AI SSE streaming @@ -717,7 +717,7 @@ ### ✨ Features -- Add a Fork button to AI replies to branch the conversation from any point into an independent session +- Add a Fork button to AI replies to branch the conversation from any point into an independent session (by [@CEQ151](https://github.com/CEQ151)) - Add a per-model thinking level selector in the status bar, with the choice remembered per model slot - Extend thinking level controls with default and auto options, covering Kimi, Doubao, Gemini, and more model families - Split the message analysis view into Type Analysis and Time Analysis tabs, each with enriched statistical insight cards @@ -900,7 +900,7 @@ - Fix Web-mode issues around session indexes, CORS proxying, demo safeguards, and runtime errors - Fix the app version showing as empty in Web mode -- Fix AI Agent evidence retrieval, Web event streaming, and error formatting issues +- Fix AI Agent evidence retrieval, Web event streaming, and error formatting issues (by [@CEQ151](https://github.com/CEQ151)) - Fix inconsistencies between sync display and import logic - Fix ESM module resolution in CLI development mode - Fix cases where the CLI could not find existing desktop data after installation @@ -990,7 +990,7 @@ ### ✨ Features -- Add session type filtering and selection when subscribing to sessions. +- Add session type filtering and selection when subscribing to sessions. (by [@xunchahaha](https://github.com/xunchahaha)) - Add paginated remote session discovery with on-demand loading. - Allow each data source to configure how many messages are fetched per request. @@ -1147,7 +1147,7 @@ ### 🐛 Bug Fixes - Fix inconsistency between WhatsApp time-parsing regex rules and line-matching regex rules. -- Fix compatibility issues when parsing WhatsApp 12-hour time formats and NNBSP characters. +- Fix compatibility issues when parsing WhatsApp 12-hour time formats and NNBSP characters. (by [@kongjiyu](https://github.com/kongjiyu)) ### 🔧 Chores @@ -1210,7 +1210,7 @@ - Improve AI chat styling and overall interaction flow. - Add FTS5 full-text search support with a quick search tool. - Trim search parameters in selected tools to reduce token usage. -- Add an E2E testing framework for Electron apps with port management and instance isolation. +- Add an E2E testing framework for Electron apps with port management and instance isolation. (by [@l17728](https://github.com/l17728)) ### 🐛 Bug Fixes @@ -1223,7 +1223,7 @@ ### test -- Add reusable smoke-test coverage for the E2E app launcher. +- Add reusable smoke-test coverage for the E2E app launcher. (by [@l17728](https://github.com/l17728)) ### 📝 Documentation @@ -1274,7 +1274,7 @@ ### 🐛 Bug Fixes -- Fix false positives in message deduplication and unify empty-string deduplication behavior +- Fix false positives in message deduplication and unify empty-string deduplication behavior (by [@lnexin](https://github.com/lnexin)) - Fix AI conversation flow issues and frontend type-check errors - Add a fallback default assistant for edge cases - Fix daily message trends not rendering @@ -1333,7 +1333,7 @@ - Add a chat-history preprocessing pipeline. - Add preprocessing settings UI and configuration management. -- Add session-based context timelines and runtime status for the Agent. +- Add session-based context timelines and runtime status for the Agent. (by [@n-WN](https://github.com/n-WN)) - Add an AI debug mode with improved log observability. ### 🐛 Bug Fixes @@ -1413,7 +1413,7 @@ ### 🐛 Bug Fixes - Fix inaccurate session index generation scope after incremental updates (fix #79). -- Fix non-chat SQLite files being incorrectly processed during migration and session scanning. +- Fix non-chat SQLite files being incorrectly processed during migration and session scanning. (by [@n-WN](https://github.com/n-WN)) ### ♻️ Refactoring @@ -1437,7 +1437,7 @@ ### 🐛 Bug Fixes -- Fix parsing issues for LINE chat logs. +- Fix parsing issues for LINE chat logs. (by [@haotien91](https://github.com/haotien91)) ### 📝 Documentation @@ -1449,9 +1449,9 @@ ### ✨ Features -- Add a data directory location setting +- Add a data directory location setting (by [@xunchahaha](https://github.com/xunchahaha)) - Optimize data directory migration logic -- Add a confirmation dialog for directory switching +- Add a confirmation dialog for directory switching (by [@xunchahaha](https://github.com/xunchahaha)) - Improve parser logic (WeFlow / Echotrace) ### 🐛 Bug Fixes @@ -1459,12 +1459,12 @@ - Fix crashes on Windows when custom filtering processes large message volumes - Fix conversations ending early when third-party relay APIs call tool_call - Fix some WhatsApp chat logs not being detected correctly -- Fix manage page header stacking above settings +- Fix manage page header stacking above settings (by [@xunchahaha](https://github.com/xunchahaha)) ### ♻️ Refactoring - Refactor session query module -- Improve migration logging +- Improve migration logging (by [@xunchahaha](https://github.com/xunchahaha)) ## v0.9.2 (2026-02-02) @@ -1603,7 +1603,7 @@ - Support incremental import - Support batch import - Style improvements -- Support native window controls and theme sync on Windows (#31) +- Support native window controls and theme sync on Windows (#31) (by [@JiQingzhe2004](https://github.com/JiQingzhe2004)) ### 🔧 Chores @@ -1697,7 +1697,7 @@ - Support proxy configuration (resolve #7) - Improve AI model UI interactions - Add API tutorial for user configuration -- Add two free GLM models; add Doubao provider and latest models +- Add two free GLM models; add Doubao provider and latest models (by [@JiQingzhe2004](https://github.com/JiQingzhe2004)) - AI replies no longer output think content ## v0.1.3 (2025-12-25) diff --git a/changelogs/ja.json b/changelogs/ja.json index 0597fd75f..48ad2d60c 100644 --- a/changelogs/ja.json +++ b/changelogs/ja.json @@ -11,19 +11,37 @@ "好み、自分について、連絡先、グループの情報を管理できる長期メモリを追加し、質問に応じて関連する内容を自動的に参照", "長期メモリを参照元メッセージに関連付け、AI が生成した結論を元の会話で確認できるように改善", "グローバル AI チャットと各チャット内 AI の会話一覧、入力欄、@ メンション選択を統一", - "チャット履歴ビューアーをドラッグでリサイズし、選択した幅を保持できるように改善", + { + "text": "チャット履歴ビューアーをドラッグでリサイズし、選択した幅を保持できるように改善", + "contributors": ["KokerZhou"] + }, "分析ページの SQL ラボとデバッグツールを「その他」にまとめて配置", - "【CLI】Unix Domain Socket 経由で Web サービスを起動できる機能を追加" + { + "text": "【CLI】Unix Domain Socket 経由で Web サービスを起動できる機能を追加", + "contributors": ["KokerZhou"] + } ] }, { "type": "fix", "items": [ "WeFlow および安定したメッセージ ID がないチャット履歴の差分インポートや統合で重複排除が過剰に働き、有効なメッセージが失われる問題を修正", - "AI の応答に失敗した後、エラー状態、会話一覧、次の質問が正しく処理されないことがある問題を修正", - "AI 会話の保存や編集で不完全なターンが残る問題を修正し、編集対象を直近の質問のみに制限", - "長い会話の圧縮時に、メッセージ境界やメモリの参照元リンクが失われることがある問題を修正", - "同じ名前の AI サービス設定で誤った認証情報が読み込まれる問題を修正", + { + "text": "AI の応答に失敗した後、エラー状態、会話一覧、次の質問が正しく処理されないことがある問題を修正", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "AI 会話の保存や編集で不完全なターンが残る問題を修正し、編集対象を直近の質問のみに制限", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "長い会話の圧縮時に、メッセージ境界やメモリの参照元リンクが失われることがある問題を修正", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "同じ名前の AI サービス設定で誤った認証情報が読み込まれる問題を修正", + "contributors": ["Sanssssssssssssssss"] + }, "分析ページを切り替えて戻った際に重複して読み込んだり、インポート前の古いデータが表示されたりする問題を修正", "クロスチャット分析の期間指定、連絡先の解決、スキャン上限、結果省略時の案内を改善" ] @@ -50,7 +68,12 @@ }, { "type": "fix", - "items": ["フォルダーのインポートを独立したボタンに変更し、ファイル選択と操作が競合しないように改善"] + "items": [ + { + "text": "フォルダーのインポートを独立したボタンに変更し、ファイル選択と操作が競合しないように改善", + "contributors": ["kongjiyu"] + } + ] }, { "type": "perf", @@ -145,7 +168,10 @@ "items": [ "QQ の自動インポートで UID/UIN の変更、owner 情報の欠落、セッションのマージがある場合の照合とメンバー対応を修正し、セッションの重複やメンバー識別の不整合を防止", "プラットフォームメッセージ ID の競合や個人チャットの識別情報不足により、マージ時にメッセージが誤って削除される問題を修正。有効な owner とセッション識別情報も保持", - "動的キーワードランキングのリクエスト処理、高速な表示切り替え、マルチカラーモードを修正し、古い結果の表示や未知の種類によるクラッシュを防止", + { + "text": "動的キーワードランキングのリクエスト処理、高速な表示切り替え、マルチカラーモードを修正し、古い結果の表示や未知の種類によるクラッシュを防止", + "contributors": ["kelaocai"] + }, "インサイトの読み込み切り替えを統一し、年間アクティビティカレンダーの配色と関係ビューの切り替え操作を復元" ] }, @@ -168,7 +194,10 @@ "QQ Shuakami / QQ Chat Exporter のインポートに Rust ネイティブパーサーを導入し、大容量エクスポートの処理を安定化", "複数ファイルのインポートに安全な並行スケジューリングを追加し、同一セッションへの同時書き込みを防止。ファイルごとの進捗と失敗内容も表示", "許可リストに含まれる実行環境と機能利用イベントだけを送信する、プライバシーに配慮した匿名利用統計を追加", - "【CLI Web】ローカルセマンティックインデックスの実行コンポーネントを初回利用時に自動インストール。Docker イメージには引き続き必要なコンポーネントを同梱" + { + "text": "【CLI Web】ローカルセマンティックインデックスの実行コンポーネントを初回利用時に自動インストール。Docker イメージには引き続き必要なコンポーネントを同梱", + "contributors": ["kelaocai"] + } ] }, { @@ -247,19 +276,35 @@ "type": "fix", "items": [ "ブラウザ版における OPFS の同時アクセス、概要フィルターの競合、単語頻度集計、関係分析フィルターの問題を修正", - "フロントエンドのアイコンをローカルに同梱し、実行時の外部アイコンサービス依存を解消", + { + "text": "フロントエンドのアイコンをローカルに同梱し、実行時の外部アイコンサービス依存を解消", + "contributors": ["KokerZhou"] + }, "ChatLab JSONL の自動判定と形式検証ルールを統一し、解決できない owner 情報を拒否", "【デスクトップ版】移行処理による使用中のデータディレクトリ削除を防止し、新旧ディレクトリの入れ子や同一パス指定を拒否", - "【CLI】Docker デプロイでユーザーデータとシステム状態を永続化し、公開ポートをデフォルトで localhost のみにバインド" + { + "text": "【CLI】Docker デプロイでユーザーデータとシステム状態を永続化し、公開ポートをデフォルトで localhost のみにバインド", + "contributors": ["KokerZhou"] + } ] }, { "type": "docs", - "items": ["Docker デプロイガイドを追加し、AI 分析の利点とローカルプライバシーの説明を改善"] + "items": [ + { + "text": "Docker デプロイガイドを追加し、AI 分析の利点とローカルプライバシーの説明を改善", + "contributors": ["KokerZhou"] + } + ] }, { "type": "ci", - "items": ["amd64 / arm64 向けの ChatLab CLI Docker イメージを公開"] + "items": [ + { + "text": "amd64 / arm64 向けの ChatLab CLI Docker イメージを公開", + "contributors": ["KokerZhou"] + } + ] } ] }, @@ -271,7 +316,10 @@ { "type": "feat", "items": [ - "【デスクトップ版】パスワード保護、即時ロック、起動時のロック解除要求、無操作時の自動ロックに対応したアプリロックを追加" + { + "text": "【デスクトップ版】パスワード保護、即時ロック、起動時のロック解除要求、無操作時の自動ロックに対応したアプリロックを追加", + "contributors": ["xyz-225648"] + } ] }, { @@ -280,7 +328,10 @@ "データディレクトリの移行後も旧データを残し、手動での確認・削除を案内することで、移行や再試行時の移行元ファイルの誤削除を防止", "大容量の ChatLab JSONL をストリーミングで分割処理し、インポートの停止を防ぐとともに、マージインポート時のメンバー画像を保持", "デスクトップ版と CLI Web の複数ファイル一括インポート処理を統一し、順次処理の進捗表示、キャンセル、結果サマリーに対応", - "【デスクトップ版】アプリロックに失敗回数制限、クールダウン、起動時の状態保護を追加し、再読み込みや再起動による回避を防止" + { + "text": "【デスクトップ版】アプリロックに失敗回数制限、クールダウン、起動時の状態保護を追加し、再読み込みや再起動による回避を防止", + "contributors": ["xyz-225648"] + } ] } ] @@ -343,7 +394,10 @@ "items": [ "各環境の Push インポートで検証、重複排除、メンバー推定、dry-run の結果を統一し、事前確認と実際の書き込みの差異を解消", "過去メッセージの追加インポート後も、タイムスタンプとメッセージ ID に基づく安定した時系列順で表示", - "QCE / QQ Chat Exporter の個人チャットがグループと誤判定され、「0」メンバーが生成される問題を修正" + { + "text": "QCE / QQ Chat Exporter の個人チャットがグループと誤判定され、「0」メンバーが生成される問題を修正", + "contributors": ["shuakami"] + } ] } ] @@ -368,7 +422,10 @@ "items": [ "ChatLab JSON の書き出しをそのまま再インポートできるよう修正し、メンバー、owner、別名のメタデータを保持", "自動増分インポートの重複排除、混在するメッセージ ID の判定、ChatLab 再インポートの識別を強化", - "WhatsApp TXT 解析で括弧付きタイムスタンプとドット付き AM/PM 表記に対応" + { + "text": "WhatsApp TXT 解析で括弧付きタイムスタンプとドット付き AM/PM 表記に対応", + "contributors": ["hh7418695"] + } ] }, { @@ -427,7 +484,10 @@ "SQL Lab で CSV エクスポートに失敗する問題を修正", "JSONL などのストリーミングされたメンバー情報をインポートする際にアバターを保持し、増分インポートでも新しいアバターを取り込めるよう修正", "AI ログを共有ルートから開くようにし、ローカルの CLI Web とデスクトップで挙動を統一", - "概要の本人カードが選択中の期間に合わせて更新されるよう修正" + { + "text": "概要の本人カードが選択中の期間に合わせて更新されるよう修正", + "contributors": ["qikairo7"] + } ] }, { @@ -596,8 +656,14 @@ "type": "fix", "items": [ "複数キーワードによるメッセージ検索を修正", - "ワードクラウドの辞書読み込みにおける UX 回帰を修正", - "ランキングラベルと絵文字クリーニングの問題を修正", + { + "text": "ワードクラウドの辞書読み込みにおける UX 回帰を修正", + "contributors": ["qikairo7"] + }, + { + "text": "ランキングラベルと絵文字クリーニングの問題を修正", + "contributors": ["qikairo7"] + }, "v8 データベース移行時の i18n テキスト欠落を修正", "【CLI】ツールアダプターでのセッションコンテキスト転送と前処理の欠落を修正" ] @@ -624,8 +690,14 @@ { "type": "fix", "items": [ - "頻出ワード統計にプラットフォームの返信プレースホルダーが含まれる問題を修正", - "ワードクラウドのテキストにメディアプレースホルダーが含まれる問題を修正", + { + "text": "頻出ワード統計にプラットフォームの返信プレースホルダーが含まれる問題を修正", + "contributors": ["qikairo7"] + }, + { + "text": "ワードクラウドのテキストにメディアプレースホルダーが含まれる問題を修正", + "contributors": ["qikairo7"] + }, "Jieba カスタム辞書変更後に言語設定キャッシュと単語頻度キャッシュが無効化されない問題を修正", "時間依存の分析キャッシュが1日ごとに期限切れにならない問題を修正", "タイムラインパネルのデフォルトスクロール位置とセッションジャンプが機能しない問題を修正", @@ -734,7 +806,10 @@ "AI アシスタントのメッセージがチャット領域の幅全体を埋めない問題を修正しました", "分析プラン生成ブロックの表示スタイルを改善しました", "再起動後に思考レベルの選択が保持されない問題を修正しました", - "JSONL のタイムスタンプ正規化により差分インポートが失敗する問題を修正しました", + { + "text": "JSONL のタイムスタンプ正規化により差分インポートが失敗する問題を修正しました", + "contributors": ["cnYui"] + }, "【デスクトップ】execute_sql ツールがデスクトップのツールレジストリに登録されない問題を修正しました" ] }, @@ -793,8 +868,14 @@ { "type": "feat", "items": [ - "スラッシュ操作で起動できる AI グラフ実行環境を追加し、会話内で ECharts グラフを生成・表示できるようにしました", - "AI グラフ結果の表示体験を改善し、レンダリング状態と複数のグラフ種別への対応を追加しました" + { + "text": "スラッシュ操作で起動できる AI グラフ実行環境を追加し、会話内で ECharts グラフを生成・表示できるようにしました", + "contributors": ["CEQ151"] + }, + { + "text": "AI グラフ結果の表示体験を改善し、レンダリング状態と複数のグラフ種別への対応を追加しました", + "contributors": ["CEQ151"] + } ] }, { @@ -804,7 +885,10 @@ "プリセット質問チップとサイドバー要素の重なり順が崩れる問題を修正しました", "【CLI Web】開発バックエンドのライフサイクル cleanup が不完全になる問題を修正しました", "【デスクトップ】自動スキルでグラフツールが登録されず、グラフスキルを正しく実行できない問題を修正しました", - "【デスクトップ】Windows のタイトルバー overlay がテーマ切り替え後にキャッシュを更新せず、表示が不安定になる問題を修正しました" + { + "text": "【デスクトップ】Windows のタイトルバー overlay がテーマ切り替え後にキャッシュを更新せず、表示が不安定になる問題を修正しました", + "contributors": ["CEQ151"] + } ] }, { @@ -879,7 +963,10 @@ "items": [ "データディレクトリ移行とデータベース移行の複数の境界ケースを修正し、旧データベースの列不足やパス切り替え後の読み取り失敗を防止", "旧データが存在しない場合やディレクトリ変更後にも移行案内が誤って表示される問題を修正", - "増分インポート後にサイドバーのメッセージ数が更新されない問題を修正", + { + "text": "増分インポート後にサイドバーのメッセージ数が更新されない問題を修正", + "contributors": ["CEQ151"] + }, "サイドバーの折りたたみ状態が sessionStorage のみに保存され、再読み込み後に失われる問題を修正", "保存済みキーで AI 設定を編集する際、モデル取得と検証ボタンが正しく有効化されない問題を修正", "共有 AI SSE ストリーミングの安定性に関する問題を修正", @@ -946,7 +1033,10 @@ { "type": "feat", "items": [ - "AI返答の任意の箇所から会話を分岐できるFork機能を追加", + { + "text": "AI返答の任意の箇所から会話を分岐できるFork機能を追加", + "contributors": ["CEQ151"] + }, "ステータスバーに推論強度セレクターを追加し、モデルごとに思考レベルを記憶・切替可能に", "推論レベル制御にdefault/autoオプションを追加し、Kimi・Doubao・Geminiなど多数のモデルファミリーに対応", "メッセージ分析ビューを「タイプ分析」と「時間分析」の2タブに分割し、統計インサイトカードを強化", @@ -1174,7 +1264,10 @@ "items": [ "Web モードのセッションインデックス、CORS プロキシ、Demo 保護、実行時エラーに関する問題を修正", "Web モードでアプリのバージョンが空表示になる問題を修正", - "AI Agent の根拠取得、Web 側イベントストリーム、エラー整形に関する問題を修正", + { + "text": "AI Agent の根拠取得、Web 側イベントストリーム、エラー整形に関する問題を修正", + "contributors": ["CEQ151"] + }, "同期表示とインポート処理の不整合を修正", "CLI 開発モードでの ESM モジュール解決問題を修正", "CLI インストール後に既存のデスクトップ版データを見つけられない場合がある問題を修正", @@ -1292,7 +1385,10 @@ { "type": "feat", "items": [ - "会話を購読する際に、種類で絞り込んで選択できるようにしました。", + { + "text": "会話を購読する際に、種類で絞り込んで選択できるようにしました。", + "contributors": ["xunchahaha"] + }, "リモート会話のページ分割取得と、必要に応じた追加読み込みに対応しました。", "データソースごとに、1 回の取得で読み込むメッセージ件数を設定できるようにしました。" ] @@ -1494,7 +1590,10 @@ "type": "fix", "items": [ "WhatsApp の時刻解析正規表現と行マッチ正規表現の厳密さが一致しない問題を修正", - "WhatsApp の12時間表記時刻と NNBSP 文字の解析互換性問題を修正" + { + "text": "WhatsApp の12時間表記時刻と NNBSP 文字の解析互換性問題を修正", + "contributors": ["kongjiyu"] + } ] }, { @@ -1573,7 +1672,10 @@ "AI 会話のスタイルと全体の操作体験を改善", "FTS5 全文検索に対応し、クイック検索ツールを追加", "一部ツールの検索パラメータを整理し、token 消費を削減", - "Electron アプリ向けに、ポート管理とインスタンス分離に対応した E2E テスト基盤を追加" + { + "text": "Electron アプリ向けに、ポート管理とインスタンス分離に対応した E2E テスト基盤を追加", + "contributors": ["l17728"] + } ] }, { @@ -1589,7 +1691,12 @@ }, { "type": "test", - "items": ["再利用可能な E2E ランチャーのスモークテストを追加"] + "items": [ + { + "text": "再利用可能な E2E ランチャーのスモークテストを追加", + "contributors": ["l17728"] + } + ] }, { "type": "docs", @@ -1651,7 +1758,10 @@ { "type": "fix", "items": [ - "メッセージ重複判定の誤検知を修正し、空文字列の重複排除ルールを統一", + { + "text": "メッセージ重複判定の誤検知を修正し、空文字列の重複排除ルールを統一", + "contributors": ["lnexin"] + }, "AI会話フローの不具合とフロントエンドのtype-checkエラーを修正", "例外時のための既定assistantフォールバックを追加", "日次メッセージ推移が表示されない問題を修正" @@ -1718,7 +1828,10 @@ "items": [ "チャット履歴の前処理パイプラインを追加", "前処理設定画面と設定管理機能を追加", - "Agent でセッション単位のコンテキストタイムラインと実行状態を表示", + { + "text": "Agent でセッション単位のコンテキストタイムラインと実行状態を表示", + "contributors": ["n-WN"] + }, "AI デバッグモードを追加し、ログの可観測性を向上" ] }, @@ -1821,7 +1934,10 @@ "type": "fix", "items": [ "増分更新後にセッション索引の生成範囲が不正確になる問題を修正(fix #79)", - "移行やセッション走査時に非チャット用 SQLite ファイルを誤処理する問題を修正" + { + "text": "移行やセッション走査時に非チャット用 SQLite ファイルを誤処理する問題を修正", + "contributors": ["n-WN"] + } ] }, { @@ -1851,7 +1967,12 @@ }, { "type": "fix", - "items": ["LINE チャット履歴の解析問題を修正"] + "items": [ + { + "text": "LINE チャット履歴の解析問題を修正", + "contributors": ["haotien91"] + } + ] }, { "type": "docs", @@ -1867,9 +1988,15 @@ { "type": "feat", "items": [ - "設定画面にデータディレクトリ位置の設定項目を追加", + { + "text": "設定画面にデータディレクトリ位置の設定項目を追加", + "contributors": ["xunchahaha"] + }, "データ保存先の移行ロジックを改善", - "ディレクトリ切り替え時の確認ダイアログを追加", + { + "text": "ディレクトリ切り替え時の確認ダイアログを追加", + "contributors": ["xunchahaha"] + }, "パーサーロジックを改善(WeFlow / Echotrace)" ] }, @@ -1879,12 +2006,21 @@ "Windows で大量メッセージを絞り込むとクラッシュする問題を修正", "外部中継 API が tool_call を返した際に会話が異常終了する問題を修正", "一部の WhatsApp 履歴を正しく認識できない問題を修正", - "管理画面のヘッダー階層表示の問題を修正" + { + "text": "管理画面のヘッダー階層表示の問題を修正", + "contributors": ["xunchahaha"] + } ] }, { "type": "refactor", - "items": ["session 検索モジュールを再構成", "移行ログ出力を改善"] + "items": [ + "session 検索モジュールを再構成", + { + "text": "移行ログ出力を改善", + "contributors": ["xunchahaha"] + } + ] } ] }, @@ -2067,7 +2203,10 @@ "増分インポートに対応", "一括インポートに対応", "スタイルを改善", - "Windows でネイティブウィンドウ操作とテーマ同期に対応(#31)" + { + "text": "Windows でネイティブウィンドウ操作とテーマ同期に対応(#31)", + "contributors": ["JiQingzhe2004"] + } ] }, { @@ -2185,7 +2324,10 @@ "プロキシ設定に対応(resolve #7)", "AI モデル画面の操作性を改善", "ユーザー設定 API のチュートリアルを追加", - "無料の GLM モデル 2 種を追加し、Doubao プロバイダーと最新モデルを追加", + { + "text": "無料の GLM モデル 2 種を追加し、Doubao プロバイダーと最新モデルを追加", + "contributors": ["JiQingzhe2004"] + }, "AI 応答で think 内容を出力しないよう調整" ] } diff --git a/changelogs/ja.md b/changelogs/ja.md index 3d97d262b..182b11617 100644 --- a/changelogs/ja.md +++ b/changelogs/ja.md @@ -10,17 +10,17 @@ - 好み、自分について、連絡先、グループの情報を管理できる長期メモリを追加し、質問に応じて関連する内容を自動的に参照 - 長期メモリを参照元メッセージに関連付け、AI が生成した結論を元の会話で確認できるように改善 - グローバル AI チャットと各チャット内 AI の会話一覧、入力欄、@ メンション選択を統一 -- チャット履歴ビューアーをドラッグでリサイズし、選択した幅を保持できるように改善 +- チャット履歴ビューアーをドラッグでリサイズし、選択した幅を保持できるように改善 (by [@KokerZhou](https://github.com/KokerZhou)) - 分析ページの SQL ラボとデバッグツールを「その他」にまとめて配置 -- 【CLI】Unix Domain Socket 経由で Web サービスを起動できる機能を追加 +- 【CLI】Unix Domain Socket 経由で Web サービスを起動できる機能を追加 (by [@KokerZhou](https://github.com/KokerZhou)) ### 🐛 バグ修正 - WeFlow および安定したメッセージ ID がないチャット履歴の差分インポートや統合で重複排除が過剰に働き、有効なメッセージが失われる問題を修正 -- AI の応答に失敗した後、エラー状態、会話一覧、次の質問が正しく処理されないことがある問題を修正 -- AI 会話の保存や編集で不完全なターンが残る問題を修正し、編集対象を直近の質問のみに制限 -- 長い会話の圧縮時に、メッセージ境界やメモリの参照元リンクが失われることがある問題を修正 -- 同じ名前の AI サービス設定で誤った認証情報が読み込まれる問題を修正 +- AI の応答に失敗した後、エラー状態、会話一覧、次の質問が正しく処理されないことがある問題を修正 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- AI 会話の保存や編集で不完全なターンが残る問題を修正し、編集対象を直近の質問のみに制限 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 長い会話の圧縮時に、メッセージ境界やメモリの参照元リンクが失われることがある問題を修正 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 同じ名前の AI サービス設定で誤った認証情報が読み込まれる問題を修正 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) - 分析ページを切り替えて戻った際に重複して読み込んだり、インポート前の古いデータが表示されたりする問題を修正 - クロスチャット分析の期間指定、連絡先の解決、スキャン上限、結果省略時の案内を改善 @@ -41,7 +41,7 @@ ### 🐛 バグ修正 -- フォルダーのインポートを独立したボタンに変更し、ファイル選択と操作が競合しないように改善 +- フォルダーのインポートを独立したボタンに変更し、ファイル選択と操作が競合しないように改善 (by [@kongjiyu](https://github.com/kongjiyu)) ### ⚡ パフォーマンス @@ -109,7 +109,7 @@ - QQ の自動インポートで UID/UIN の変更、owner 情報の欠落、セッションのマージがある場合の照合とメンバー対応を修正し、セッションの重複やメンバー識別の不整合を防止 - プラットフォームメッセージ ID の競合や個人チャットの識別情報不足により、マージ時にメッセージが誤って削除される問題を修正。有効な owner とセッション識別情報も保持 -- 動的キーワードランキングのリクエスト処理、高速な表示切り替え、マルチカラーモードを修正し、古い結果の表示や未知の種類によるクラッシュを防止 +- 動的キーワードランキングのリクエスト処理、高速な表示切り替え、マルチカラーモードを修正し、古い結果の表示や未知の種類によるクラッシュを防止 (by [@kelaocai](https://github.com/kelaocai)) - インサイトの読み込み切り替えを統一し、年間アクティビティカレンダーの配色と関係ビューの切り替え操作を復元 ### ⚡ パフォーマンス @@ -125,7 +125,7 @@ - QQ Shuakami / QQ Chat Exporter のインポートに Rust ネイティブパーサーを導入し、大容量エクスポートの処理を安定化 - 複数ファイルのインポートに安全な並行スケジューリングを追加し、同一セッションへの同時書き込みを防止。ファイルごとの進捗と失敗内容も表示 - 許可リストに含まれる実行環境と機能利用イベントだけを送信する、プライバシーに配慮した匿名利用統計を追加 -- 【CLI Web】ローカルセマンティックインデックスの実行コンポーネントを初回利用時に自動インストール。Docker イメージには引き続き必要なコンポーネントを同梱 +- 【CLI Web】ローカルセマンティックインデックスの実行コンポーネントを初回利用時に自動インストール。Docker イメージには引き続き必要なコンポーネントを同梱 (by [@kelaocai](https://github.com/kelaocai)) ### 🐛 バグ修正 @@ -183,18 +183,18 @@ ### 🐛 バグ修正 - ブラウザ版における OPFS の同時アクセス、概要フィルターの競合、単語頻度集計、関係分析フィルターの問題を修正 -- フロントエンドのアイコンをローカルに同梱し、実行時の外部アイコンサービス依存を解消 +- フロントエンドのアイコンをローカルに同梱し、実行時の外部アイコンサービス依存を解消 (by [@KokerZhou](https://github.com/KokerZhou)) - ChatLab JSONL の自動判定と形式検証ルールを統一し、解決できない owner 情報を拒否 - 【デスクトップ版】移行処理による使用中のデータディレクトリ削除を防止し、新旧ディレクトリの入れ子や同一パス指定を拒否 -- 【CLI】Docker デプロイでユーザーデータとシステム状態を永続化し、公開ポートをデフォルトで localhost のみにバインド +- 【CLI】Docker デプロイでユーザーデータとシステム状態を永続化し、公開ポートをデフォルトで localhost のみにバインド (by [@KokerZhou](https://github.com/KokerZhou)) ### 📝 ドキュメント -- Docker デプロイガイドを追加し、AI 分析の利点とローカルプライバシーの説明を改善 +- Docker デプロイガイドを追加し、AI 分析の利点とローカルプライバシーの説明を改善 (by [@KokerZhou](https://github.com/KokerZhou)) ### 👷 CI -- amd64 / arm64 向けの ChatLab CLI Docker イメージを公開 +- amd64 / arm64 向けの ChatLab CLI Docker イメージを公開 (by [@KokerZhou](https://github.com/KokerZhou)) ## v0.32.0 (2026-07-18) @@ -202,14 +202,14 @@ ### ✨ 新機能 -- 【デスクトップ版】パスワード保護、即時ロック、起動時のロック解除要求、無操作時の自動ロックに対応したアプリロックを追加 +- 【デスクトップ版】パスワード保護、即時ロック、起動時のロック解除要求、無操作時の自動ロックに対応したアプリロックを追加 (by [@xyz-225648](https://github.com/xyz-225648)) ### 🐛 バグ修正 - データディレクトリの移行後も旧データを残し、手動での確認・削除を案内することで、移行や再試行時の移行元ファイルの誤削除を防止 - 大容量の ChatLab JSONL をストリーミングで分割処理し、インポートの停止を防ぐとともに、マージインポート時のメンバー画像を保持 - デスクトップ版と CLI Web の複数ファイル一括インポート処理を統一し、順次処理の進捗表示、キャンセル、結果サマリーに対応 -- 【デスクトップ版】アプリロックに失敗回数制限、クールダウン、起動時の状態保護を追加し、再読み込みや再起動による回避を防止 +- 【デスクトップ版】アプリロックに失敗回数制限、クールダウン、起動時の状態保護を追加し、再読み込みや再起動による回避を防止 (by [@xyz-225648](https://github.com/xyz-225648)) ## v0.31.3 (2026-07-17) @@ -253,7 +253,7 @@ - 各環境の Push インポートで検証、重複排除、メンバー推定、dry-run の結果を統一し、事前確認と実際の書き込みの差異を解消 - 過去メッセージの追加インポート後も、タイムスタンプとメッセージ ID に基づく安定した時系列順で表示 -- QCE / QQ Chat Exporter の個人チャットがグループと誤判定され、「0」メンバーが生成される問題を修正 +- QCE / QQ Chat Exporter の個人チャットがグループと誤判定され、「0」メンバーが生成される問題を修正 (by [@shuakami](https://github.com/shuakami)) ## v0.31.0 (2026-07-13) @@ -271,7 +271,7 @@ - ChatLab JSON の書き出しをそのまま再インポートできるよう修正し、メンバー、owner、別名のメタデータを保持 - 自動増分インポートの重複排除、混在するメッセージ ID の判定、ChatLab 再インポートの識別を強化 -- WhatsApp TXT 解析で括弧付きタイムスタンプとドット付き AM/PM 表記に対応 +- WhatsApp TXT 解析で括弧付きタイムスタンプとドット付き AM/PM 表記に対応 (by [@hh7418695](https://github.com/hh7418695)) ### ♻️ リファクタリング @@ -314,7 +314,7 @@ - SQL Lab で CSV エクスポートに失敗する問題を修正 - JSONL などのストリーミングされたメンバー情報をインポートする際にアバターを保持し、増分インポートでも新しいアバターを取り込めるよう修正 - AI ログを共有ルートから開くようにし、ローカルの CLI Web とデスクトップで挙動を統一 -- 概要の本人カードが選択中の期間に合わせて更新されるよう修正 +- 概要の本人カードが選択中の期間に合わせて更新されるよう修正 (by [@qikairo7](https://github.com/qikairo7)) ### 📝 ドキュメント @@ -445,8 +445,8 @@ ### 🐛 バグ修正 - 複数キーワードによるメッセージ検索を修正 -- ワードクラウドの辞書読み込みにおける UX 回帰を修正 -- ランキングラベルと絵文字クリーニングの問題を修正 +- ワードクラウドの辞書読み込みにおける UX 回帰を修正 (by [@qikairo7](https://github.com/qikairo7)) +- ランキングラベルと絵文字クリーニングの問題を修正 (by [@qikairo7](https://github.com/qikairo7)) - v8 データベース移行時の i18n テキスト欠落を修正 - 【CLI】ツールアダプターでのセッションコンテキスト転送と前処理の欠落を修正 @@ -466,8 +466,8 @@ ### 🐛 バグ修正 -- 頻出ワード統計にプラットフォームの返信プレースホルダーが含まれる問題を修正 -- ワードクラウドのテキストにメディアプレースホルダーが含まれる問題を修正 +- 頻出ワード統計にプラットフォームの返信プレースホルダーが含まれる問題を修正 (by [@qikairo7](https://github.com/qikairo7)) +- ワードクラウドのテキストにメディアプレースホルダーが含まれる問題を修正 (by [@qikairo7](https://github.com/qikairo7)) - Jieba カスタム辞書変更後に言語設定キャッシュと単語頻度キャッシュが無効化されない問題を修正 - 時間依存の分析キャッシュが1日ごとに期限切れにならない問題を修正 - タイムラインパネルのデフォルトスクロール位置とセッションジャンプが機能しない問題を修正 @@ -555,7 +555,7 @@ - AI アシスタントのメッセージがチャット領域の幅全体を埋めない問題を修正しました - 分析プラン生成ブロックの表示スタイルを改善しました - 再起動後に思考レベルの選択が保持されない問題を修正しました -- JSONL のタイムスタンプ正規化により差分インポートが失敗する問題を修正しました +- JSONL のタイムスタンプ正規化により差分インポートが失敗する問題を修正しました (by [@cnYui](https://github.com/cnYui)) - 【デスクトップ】execute_sql ツールがデスクトップのツールレジストリに登録されない問題を修正しました ### test @@ -600,8 +600,8 @@ ### ✨ 新機能 -- スラッシュ操作で起動できる AI グラフ実行環境を追加し、会話内で ECharts グラフを生成・表示できるようにしました -- AI グラフ結果の表示体験を改善し、レンダリング状態と複数のグラフ種別への対応を追加しました +- スラッシュ操作で起動できる AI グラフ実行環境を追加し、会話内で ECharts グラフを生成・表示できるようにしました (by [@CEQ151](https://github.com/CEQ151)) +- AI グラフ結果の表示体験を改善し、レンダリング状態と複数のグラフ種別への対応を追加しました (by [@CEQ151](https://github.com/CEQ151)) ### 🐛 バグ修正 @@ -609,7 +609,7 @@ - プリセット質問チップとサイドバー要素の重なり順が崩れる問題を修正しました - 【CLI Web】開発バックエンドのライフサイクル cleanup が不完全になる問題を修正しました - 【デスクトップ】自動スキルでグラフツールが登録されず、グラフスキルを正しく実行できない問題を修正しました -- 【デスクトップ】Windows のタイトルバー overlay がテーマ切り替え後にキャッシュを更新せず、表示が不安定になる問題を修正しました +- 【デスクトップ】Windows のタイトルバー overlay がテーマ切り替え後にキャッシュを更新せず、表示が不安定になる問題を修正しました (by [@CEQ151](https://github.com/CEQ151)) ### ♻️ リファクタリング @@ -666,7 +666,7 @@ - データディレクトリ移行とデータベース移行の複数の境界ケースを修正し、旧データベースの列不足やパス切り替え後の読み取り失敗を防止 - 旧データが存在しない場合やディレクトリ変更後にも移行案内が誤って表示される問題を修正 -- 増分インポート後にサイドバーのメッセージ数が更新されない問題を修正 +- 増分インポート後にサイドバーのメッセージ数が更新されない問題を修正 (by [@CEQ151](https://github.com/CEQ151)) - サイドバーの折りたたみ状態が sessionStorage のみに保存され、再読み込み後に失われる問題を修正 - 保存済みキーで AI 設定を編集する際、モデル取得と検証ボタンが正しく有効化されない問題を修正 - 共有 AI SSE ストリーミングの安定性に関する問題を修正 @@ -717,7 +717,7 @@ ### ✨ 新機能 -- AI返答の任意の箇所から会話を分岐できるFork機能を追加 +- AI返答の任意の箇所から会話を分岐できるFork機能を追加 (by [@CEQ151](https://github.com/CEQ151)) - ステータスバーに推論強度セレクターを追加し、モデルごとに思考レベルを記憶・切替可能に - 推論レベル制御にdefault/autoオプションを追加し、Kimi・Doubao・Geminiなど多数のモデルファミリーに対応 - メッセージ分析ビューを「タイプ分析」と「時間分析」の2タブに分割し、統計インサイトカードを強化 @@ -900,7 +900,7 @@ - Web モードのセッションインデックス、CORS プロキシ、Demo 保護、実行時エラーに関する問題を修正 - Web モードでアプリのバージョンが空表示になる問題を修正 -- AI Agent の根拠取得、Web 側イベントストリーム、エラー整形に関する問題を修正 +- AI Agent の根拠取得、Web 側イベントストリーム、エラー整形に関する問題を修正 (by [@CEQ151](https://github.com/CEQ151)) - 同期表示とインポート処理の不整合を修正 - CLI 開発モードでの ESM モジュール解決問題を修正 - CLI インストール後に既存のデスクトップ版データを見つけられない場合がある問題を修正 @@ -990,7 +990,7 @@ ### ✨ 新機能 -- 会話を購読する際に、種類で絞り込んで選択できるようにしました。 +- 会話を購読する際に、種類で絞り込んで選択できるようにしました。 (by [@xunchahaha](https://github.com/xunchahaha)) - リモート会話のページ分割取得と、必要に応じた追加読み込みに対応しました。 - データソースごとに、1 回の取得で読み込むメッセージ件数を設定できるようにしました。 @@ -1147,7 +1147,7 @@ ### 🐛 バグ修正 - WhatsApp の時刻解析正規表現と行マッチ正規表現の厳密さが一致しない問題を修正 -- WhatsApp の12時間表記時刻と NNBSP 文字の解析互換性問題を修正 +- WhatsApp の12時間表記時刻と NNBSP 文字の解析互換性問題を修正 (by [@kongjiyu](https://github.com/kongjiyu)) ### 🔧 雑務 @@ -1210,7 +1210,7 @@ - AI 会話のスタイルと全体の操作体験を改善 - FTS5 全文検索に対応し、クイック検索ツールを追加 - 一部ツールの検索パラメータを整理し、token 消費を削減 -- Electron アプリ向けに、ポート管理とインスタンス分離に対応した E2E テスト基盤を追加 +- Electron アプリ向けに、ポート管理とインスタンス分離に対応した E2E テスト基盤を追加 (by [@l17728](https://github.com/l17728)) ### 🐛 バグ修正 @@ -1223,7 +1223,7 @@ ### test -- 再利用可能な E2E ランチャーのスモークテストを追加 +- 再利用可能な E2E ランチャーのスモークテストを追加 (by [@l17728](https://github.com/l17728)) ### 📝 ドキュメント @@ -1274,7 +1274,7 @@ ### 🐛 バグ修正 -- メッセージ重複判定の誤検知を修正し、空文字列の重複排除ルールを統一 +- メッセージ重複判定の誤検知を修正し、空文字列の重複排除ルールを統一 (by [@lnexin](https://github.com/lnexin)) - AI会話フローの不具合とフロントエンドのtype-checkエラーを修正 - 例外時のための既定assistantフォールバックを追加 - 日次メッセージ推移が表示されない問題を修正 @@ -1333,7 +1333,7 @@ - チャット履歴の前処理パイプラインを追加 - 前処理設定画面と設定管理機能を追加 -- Agent でセッション単位のコンテキストタイムラインと実行状態を表示 +- Agent でセッション単位のコンテキストタイムラインと実行状態を表示 (by [@n-WN](https://github.com/n-WN)) - AI デバッグモードを追加し、ログの可観測性を向上 ### 🐛 バグ修正 @@ -1413,7 +1413,7 @@ ### 🐛 バグ修正 - 増分更新後にセッション索引の生成範囲が不正確になる問題を修正(fix #79) -- 移行やセッション走査時に非チャット用 SQLite ファイルを誤処理する問題を修正 +- 移行やセッション走査時に非チャット用 SQLite ファイルを誤処理する問題を修正 (by [@n-WN](https://github.com/n-WN)) ### ♻️ リファクタリング @@ -1437,7 +1437,7 @@ ### 🐛 バグ修正 -- LINE チャット履歴の解析問題を修正 +- LINE チャット履歴の解析問題を修正 (by [@haotien91](https://github.com/haotien91)) ### 📝 ドキュメント @@ -1449,9 +1449,9 @@ ### ✨ 新機能 -- 設定画面にデータディレクトリ位置の設定項目を追加 +- 設定画面にデータディレクトリ位置の設定項目を追加 (by [@xunchahaha](https://github.com/xunchahaha)) - データ保存先の移行ロジックを改善 -- ディレクトリ切り替え時の確認ダイアログを追加 +- ディレクトリ切り替え時の確認ダイアログを追加 (by [@xunchahaha](https://github.com/xunchahaha)) - パーサーロジックを改善(WeFlow / Echotrace) ### 🐛 バグ修正 @@ -1459,12 +1459,12 @@ - Windows で大量メッセージを絞り込むとクラッシュする問題を修正 - 外部中継 API が tool_call を返した際に会話が異常終了する問題を修正 - 一部の WhatsApp 履歴を正しく認識できない問題を修正 -- 管理画面のヘッダー階層表示の問題を修正 +- 管理画面のヘッダー階層表示の問題を修正 (by [@xunchahaha](https://github.com/xunchahaha)) ### ♻️ リファクタリング - session 検索モジュールを再構成 -- 移行ログ出力を改善 +- 移行ログ出力を改善 (by [@xunchahaha](https://github.com/xunchahaha)) ## v0.9.2 (2026-02-02) @@ -1603,7 +1603,7 @@ - 増分インポートに対応 - 一括インポートに対応 - スタイルを改善 -- Windows でネイティブウィンドウ操作とテーマ同期に対応(#31) +- Windows でネイティブウィンドウ操作とテーマ同期に対応(#31) (by [@JiQingzhe2004](https://github.com/JiQingzhe2004)) ### 🔧 雑務 @@ -1697,7 +1697,7 @@ - プロキシ設定に対応(resolve #7) - AI モデル画面の操作性を改善 - ユーザー設定 API のチュートリアルを追加 -- 無料の GLM モデル 2 種を追加し、Doubao プロバイダーと最新モデルを追加 +- 無料の GLM モデル 2 種を追加し、Doubao プロバイダーと最新モデルを追加 (by [@JiQingzhe2004](https://github.com/JiQingzhe2004)) - AI 応答で think 内容を出力しないよう調整 ## v0.1.3 (2025-12-25) diff --git a/changelogs/tw.json b/changelogs/tw.json index 6bb37c7db..7389559af 100644 --- a/changelogs/tw.json +++ b/changelogs/tw.json @@ -11,19 +11,37 @@ "新增加長期記憶,可管理偏好、關於我、聯絡人與群組記憶,並依問題自動載入相關內容", "長期記憶可連結來源訊息,方便回到原始對話核對 AI 產生的結論", "統一全域與單一對話 AI 的對話清單、輸入框和 @ 選取操作", - "聊天記錄檢視器可拖曳調整寬度,並記住版面設定", + { + "text": "聊天記錄檢視器可拖曳調整寬度,並記住版面設定", + "contributors": ["KokerZhou"] + }, "分析頁將 SQL 實驗室與除錯工具統一收納至「更多」", - "【CLI】支援透過 Unix Domain Socket 啟動 Web 服務" + { + "text": "【CLI】支援透過 Unix Domain Socket 啟動 Web 服務", + "contributors": ["KokerZhou"] + } ] }, { "type": "fix", "items": [ "修正 WeFlow 與缺少穩定訊息 ID 的聊天記錄在增量匯入、合併時可能過度去重的問題,避免真實訊息遺失", - "修正 AI 回覆失敗後,錯誤狀態、對話清單與後續提問可能異常的問題", - "修正 AI 對話在儲存或編輯時可能留下不完整輪次的問題,並僅允許編輯最新一輪提問", - "修正長對話壓縮可能遺失訊息邊界或記憶來源連結的問題", - "修正同名 AI 服務設定可能讀取錯誤憑證的問題", + { + "text": "修正 AI 回覆失敗後,錯誤狀態、對話清單與後續提問可能異常的問題", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "修正 AI 對話在儲存或編輯時可能留下不完整輪次的問題,並僅允許編輯最新一輪提問", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "修正長對話壓縮可能遺失訊息邊界或記憶來源連結的問題", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "修正同名 AI 服務設定可能讀取錯誤憑證的問題", + "contributors": ["Sanssssssssssssssss"] + }, "修正切換或返回分析頁面時可能重複載入,或繼續顯示匯入前舊資料的問題", "完善跨對話分析的時間範圍、聯絡人解析、掃描預算與截斷提示" ] @@ -50,7 +68,12 @@ }, { "type": "fix", - "items": ["匯入資料夾改為獨立按鈕,避免與選取檔案互相搶入口"] + "items": [ + { + "text": "匯入資料夾改為獨立按鈕,避免與選取檔案互相搶入口", + "contributors": ["kongjiyu"] + } + ] }, { "type": "perf", @@ -140,7 +163,10 @@ "items": [ "修正 QQ 自動匯入在 UID/UIN 變更、缺少 owner 資訊或工作階段已合併時的比對與成員對應問題,避免產生重複工作階段或錯誤的成員身分", "修正合併匯入因平台訊息 ID 衝突或私人聊天身分資訊不足而誤刪訊息的問題,並保留有效的 owner 與工作階段身分資訊", - "修正動態關鍵字排行的請求、快速切換與多色模式,避免顯示過期結果或因未知類型導致頁面當機", + { + "text": "修正動態關鍵字排行的請求、快速切換與多色模式,避免顯示過期結果或因未知類型導致頁面當機", + "contributors": ["kelaocai"] + }, "統一洞察頁面的載入轉場,並恢復年度活動日曆配色與關係檢視切換控制項" ] }, @@ -161,7 +187,10 @@ "QQ Shuakami 與 QQ Chat Exporter 匯入改用 Rust 原生解析,提升大型匯出檔案的處理穩定性", "多檔案匯入加入安全的並行排程,避免同一工作階段同時寫入,並顯示各檔案的進度與失敗資訊", "新增兼顧隱私的匿名產品統計,只回報白名單內的執行環境與功能使用事件", - "【CLI Web】本機語意索引執行元件改為首次使用時按需安裝,Docker 映像仍預先內建所需元件" + { + "text": "【CLI Web】本機語意索引執行元件改為首次使用時按需安裝,Docker 映像仍預先內建所需元件", + "contributors": ["kelaocai"] + } ] }, { @@ -240,19 +269,35 @@ "type": "fix", "items": [ "修正純 Web 版同時存取 OPFS、概覽篩選競態、詞頻統計與關係分析篩選問題", - "前端圖示改為隨應用程式一併封裝,避免執行時依賴外部圖示服務", + { + "text": "前端圖示改為隨應用程式一併封裝,避免執行時依賴外部圖示服務", + "contributors": ["KokerZhou"] + }, "統一 ChatLab JSONL 自動辨識與格式驗證規則,並拒絕無法解析的 owner 資訊", "【桌面版】防止移轉程序刪除目前使用中的資料目錄,並拒絕新舊資料目錄互相巢狀或指向同一路徑", - "【CLI】Docker 部署會保存使用者資料與系統狀態,並預設只將連接埠綁定至 localhost" + { + "text": "【CLI】Docker 部署會保存使用者資料與系統狀態,並預設只將連接埠綁定至 localhost", + "contributors": ["KokerZhou"] + } ] }, { "type": "docs", - "items": ["新增 Docker 部署指南,並補充 AI 分析效益與本機隱私說明"] + "items": [ + { + "text": "新增 Docker 部署指南,並補充 AI 分析效益與本機隱私說明", + "contributors": ["KokerZhou"] + } + ] }, { "type": "ci", - "items": ["發布適用於 amd64 與 arm64 架構的 ChatLab CLI Docker 映像檔"] + "items": [ + { + "text": "發布適用於 amd64 與 arm64 架構的 ChatLab CLI Docker 映像檔", + "contributors": ["KokerZhou"] + } + ] } ] }, @@ -263,7 +308,12 @@ "changes": [ { "type": "feat", - "items": ["【桌面版】新增應用程式鎖定,支援密碼、立即鎖定、啟動時解鎖,以及閒置逾時自動鎖定"] + "items": [ + { + "text": "【桌面版】新增應用程式鎖定,支援密碼、立即鎖定、啟動時解鎖,以及閒置逾時自動鎖定", + "contributors": ["xyz-225648"] + } + ] }, { "type": "fix", @@ -271,7 +321,10 @@ "資料目錄移轉後保留舊資料並提示使用者手動確認清理,避免移轉或重試時誤刪來源檔案", "大型 ChatLab JSONL 改為串流分批處理,避免匯入卡住,並保留合併匯入中的成員頭像", "統一桌面版與 CLI Web 的多檔批次匯入流程,提供循序處理進度、取消與結果摘要", - "【桌面版】應用程式鎖定新增失敗次數限制、冷卻時間與啟動狀態保護,避免重新整理或重新啟動後繞過鎖定" + { + "text": "【桌面版】應用程式鎖定新增失敗次數限制、冷卻時間與啟動狀態保護,避免重新整理或重新啟動後繞過鎖定", + "contributors": ["xyz-225648"] + } ] } ] @@ -334,7 +387,10 @@ "items": [ "統一各端 Push 匯入的驗證、去重、成員推斷與 dry-run 結果,避免預覽與實際寫入不一致", "補匯入歷史訊息後,聊天記錄會依時間與訊息 ID 穩定排序,避免舊訊息出現在最後", - "修正 QCE / QQ Chat Exporter 私人聊天被誤判為群組並產生「0」成員的問題" + { + "text": "修正 QCE / QQ Chat Exporter 私人聊天被誤判為群組並產生「0」成員的問題", + "contributors": ["shuakami"] + } ] } ] @@ -359,7 +415,10 @@ "items": [ "修正 ChatLab JSON 匯出無法直接重新匯入的問題,並保留成員、owner 與別名元資料", "強化自動增量匯入的去重、混合訊息身份識別與 ChatLab 重複匯入判斷", - "WhatsApp TXT 解析支援括號時間戳與點號 AM/PM 標記" + { + "text": "WhatsApp TXT 解析支援括號時間戳與點號 AM/PM 標記", + "contributors": ["hh7418695"] + } ] }, { @@ -415,7 +474,10 @@ "修正 SQL 實驗室匯出 CSV 失敗的問題", "匯入 JSONL 等串流成員資料時保留頭像,並支援增量匯入新頭像", "開啟 AI 日誌改由共享路由處理,讓本機 CLI Web 與桌面端行為一致", - "概覽身份卡會跟隨目前選取的時間範圍更新" + { + "text": "概覽身份卡會跟隨目前選取的時間範圍更新", + "contributors": ["qikairo7"] + } ] }, { @@ -584,8 +646,14 @@ "type": "fix", "items": [ "修正多關鍵字訊息搜尋功能", - "修正詞雲詞典載入 UX 回歸問題", - "修正排行榜標籤與 emoji 清理", + { + "text": "修正詞雲詞典載入 UX 回歸問題", + "contributors": ["qikairo7"] + }, + { + "text": "修正排行榜標籤與 emoji 清理", + "contributors": ["qikairo7"] + }, "修正 v8 資料庫遷移缺少國際化文字的問題", "【CLI】修正工具轉接器中會話上下文傳遞與前處理缺失的問題" ] @@ -612,8 +680,14 @@ { "type": "fix", "items": [ - "修正高頻詞統計中含有平台回覆訊息占位符的問題", - "修正詞雲文字中包含媒體占位符的問題", + { + "text": "修正高頻詞統計中含有平台回覆訊息占位符的問題", + "contributors": ["qikairo7"] + }, + { + "text": "修正詞雲文字中包含媒體占位符的問題", + "contributors": ["qikairo7"] + }, "修正 Jieba 自訂詞典變更後語言偏好快取與詞頻快取未失效的問題", "修正時效性分析快取未按日失效的問題", "修正時間軸面板預設捲動位置及會話跳轉功能失效的問題", @@ -719,7 +793,10 @@ "修正 AI 助理訊息未填滿聊天區域寬度", "改善分析計畫產生區塊的顯示樣式", "修正思考深度選擇跨重新啟動後不持久的問題", - "修正 JSONL 時間戳記規範化導致增量匯入異常", + { + "text": "修正 JSONL 時間戳記規範化導致增量匯入異常", + "contributors": ["cnYui"] + }, "【桌面版】修正 execute_sql 工具未註冊至桌面版工具清單" ] }, @@ -776,8 +853,14 @@ { "type": "feat", "items": [ - "新增以斜線指令觸發的 AI 圖表執行環境,可在對話中產生並渲染 ECharts 圖表", - "改善 AI 圖表結果呈現,補上更清楚的渲染狀態與多種圖表類型支援" + { + "text": "新增以斜線指令觸發的 AI 圖表執行環境,可在對話中產生並渲染 ECharts 圖表", + "contributors": ["CEQ151"] + }, + { + "text": "改善 AI 圖表結果呈現,補上更清楚的渲染狀態與多種圖表類型支援", + "contributors": ["CEQ151"] + } ] }, { @@ -787,7 +870,10 @@ "修正預設問題標籤與側邊欄元素的層疊順序異常", "【CLI Web】修正開發後端生命週期清理不完整的問題", "【桌面版】修正自動技能未註冊圖表工具,導致圖表技能無法完整執行的問題", - "【桌面版】修正 Windows 標題列覆蓋層在切換主題後快取未更新與顯示不順的問題" + { + "text": "【桌面版】修正 Windows 標題列覆蓋層在切換主題後快取未更新與顯示不順的問題", + "contributors": ["CEQ151"] + } ] }, { @@ -860,7 +946,10 @@ "items": [ "修正資料目錄遷移與資料庫遷移流程中的多個邊界情境,避免舊資料庫欄位缺失或路徑切換後讀取失敗", "修正舊資料不存在或目錄變更後,資料遷移提示仍錯誤顯示的問題", - "修正增量匯入後側邊欄訊息數量未更新的問題", + { + "text": "修正增量匯入後側邊欄訊息數量未更新的問題", + "contributors": ["CEQ151"] + }, "修正側邊欄收合狀態只存於 sessionStorage,導致重新整理後遺失的問題", "修正編輯 AI 設定且已儲存金鑰時,拉取模型與驗證按鈕未正確啟用的問題", "修正 AI 共享 SSE 串流回應的穩定性問題", @@ -925,7 +1014,10 @@ { "type": "feat", "items": [ - "新增訊息分叉(Fork)功能,可從任意 AI 回覆處建立獨立分支對話", + { + "text": "新增訊息分叉(Fork)功能,可從任意 AI 回覆處建立獨立分支對話", + "contributors": ["CEQ151"] + }, "狀態列新增推理強度選擇器,支援為每個推理模型個別記憶並切換思維等級", "推理等級控制新增 default/auto 選項,擴展對 Kimi、Doubao、Gemini 等更多模型系列的支援", "訊息分析檢視拆分為「類型分析」與「時間分析」兩個分頁,提供豐富統計洞察卡片", @@ -1144,7 +1236,10 @@ "items": [ "修復 Web 模式下會話索引、跨來源代理、Demo 保護與執行階段錯誤等問題", "修復 Web 模式下應用程式版本顯示為空的問題", - "修復 AI Agent 證據檢索、Web 端事件串流與錯誤格式化等問題", + { + "text": "修復 AI Agent 證據檢索、Web 端事件串流與錯誤格式化等問題", + "contributors": ["CEQ151"] + }, "修復同步顯示與匯入邏輯不一致的問題", "修復 CLI 開發模式下的 ESM 模組解析問題", "修復 CLI 安裝後可能找不到既有桌面版資料的問題", @@ -1255,7 +1350,10 @@ { "type": "feat", "items": [ - "訂閱會話時支援依類型篩選與選取", + { + "text": "訂閱會話時支援依類型篩選與選取", + "contributors": ["xunchahaha"] + }, "支援遠端會話分頁發現與按需載入更多", "支援為資料來源設定每次拉取的訊息數量" ] @@ -1457,7 +1555,10 @@ "type": "fix", "items": [ "修正 WhatsApp 時間解析正則與行匹配正則寬嚴不一致的問題", - "修正 WhatsApp 12 小時制時間與 NNBSP 字元的解析相容性問題" + { + "text": "修正 WhatsApp 12 小時制時間與 NNBSP 字元的解析相容性問題", + "contributors": ["kongjiyu"] + } ] }, { @@ -1532,7 +1633,10 @@ "優化 AI 對話樣式與整體互動體驗", "支援 FTS5 全文搜尋,並新增快速搜尋工具", "精簡部分工具的搜尋參數,降低 token 消耗", - "新增 Electron 應用 E2E 測試框架,支援連接埠管理與實例隔離" + { + "text": "新增 Electron 應用 E2E 測試框架,支援連接埠管理與實例隔離", + "contributors": ["l17728"] + } ] }, { @@ -1545,7 +1649,12 @@ }, { "type": "test", - "items": ["補充可重複使用的 E2E 啟動器冒煙測試能力"] + "items": [ + { + "text": "補充可重複使用的 E2E 啟動器冒煙測試能力", + "contributors": ["l17728"] + } + ] }, { "type": "docs", @@ -1607,7 +1716,10 @@ { "type": "fix", "items": [ - "修復訊息去重誤判,並統一空字串的去重行為", + { + "text": "修復訊息去重誤判,並統一空字串的去重行為", + "contributors": ["lnexin"] + }, "修復 AI 對話流程與前端 type-check 錯誤", "補上預設 assistant 的兜底邏輯,修復例外情境", "修復每日訊息趨勢未顯示的問題" @@ -1674,7 +1786,10 @@ "items": [ "新增聊天紀錄前處理流程", "新增前處理設定介面與設定管理能力", - "Agent 支援依會話顯示上下文時間軸與執行狀態", + { + "text": "Agent 支援依會話顯示上下文時間軸與執行狀態", + "contributors": ["n-WN"] + }, "新增 AI 除錯模式並提升日誌可觀測性" ] }, @@ -1771,7 +1886,10 @@ "type": "fix", "items": [ "修復增量更新後會話索引產生範圍不準確的問題(fix #79)", - "修復遷移與會話掃描時誤處理非聊天 SQLite 檔案的問題" + { + "text": "修復遷移與會話掃描時誤處理非聊天 SQLite 檔案的問題", + "contributors": ["n-WN"] + } ] }, { @@ -1801,7 +1919,12 @@ }, { "type": "fix", - "items": ["修復 LINE 聊天紀錄解析問題"] + "items": [ + { + "text": "修復 LINE 聊天紀錄解析問題", + "contributors": ["haotien91"] + } + ] }, { "type": "docs", @@ -1817,9 +1940,15 @@ { "type": "feat", "items": [ - "設定中新增資料目錄位置選項", + { + "text": "設定中新增資料目錄位置選項", + "contributors": ["xunchahaha"] + }, "優化資料儲存目錄遷移邏輯", - "切換目錄時新增確認彈窗", + { + "text": "切換目錄時新增確認彈窗", + "contributors": ["xunchahaha"] + }, "優化解析邏輯(WeFlow / Echotrace)" ] }, @@ -1829,12 +1958,21 @@ "修復 Windows 自訂篩選時訊息量過大導致崩潰的問題", "修復第三方中轉 API 呼叫 tool_call 導致對話異常結束的問題", "修復部分 WhatsApp 聊天紀錄無法正確辨識的問題", - "修復管理頁表頭層級顯示問題" + { + "text": "修復管理頁表頭層級顯示問題", + "contributors": ["xunchahaha"] + } ] }, { "type": "refactor", - "items": ["重構 session 查詢模組", "補強遷移日誌輸出"] + "items": [ + "重構 session 查詢模組", + { + "text": "補強遷移日誌輸出", + "contributors": ["xunchahaha"] + } + ] } ] }, @@ -2010,7 +2148,10 @@ "支援增量匯入", "支援批次匯入", "優化樣式", - "Windows 端支援原生視窗控制並實作主題同步(#31)" + { + "text": "Windows 端支援原生視窗控制並實作主題同步(#31)", + "contributors": ["JiQingzhe2004"] + } ] }, { @@ -2128,7 +2269,10 @@ "支援設定代理(resolve #7)", "優化 AI 模型介面互動", "新增使用者設定 API 教學", - "新增 2 個免費 GLM 模型,並加入豆包服務商與最新模型", + { + "text": "新增 2 個免費 GLM 模型,並加入豆包服務商與最新模型", + "contributors": ["JiQingzhe2004"] + }, "AI 回覆不再輸出 think 內容" ] } diff --git a/changelogs/tw.md b/changelogs/tw.md index 311836f6a..9af1a3532 100644 --- a/changelogs/tw.md +++ b/changelogs/tw.md @@ -10,17 +10,17 @@ - 新增加長期記憶,可管理偏好、關於我、聯絡人與群組記憶,並依問題自動載入相關內容 - 長期記憶可連結來源訊息,方便回到原始對話核對 AI 產生的結論 - 統一全域與單一對話 AI 的對話清單、輸入框和 @ 選取操作 -- 聊天記錄檢視器可拖曳調整寬度,並記住版面設定 +- 聊天記錄檢視器可拖曳調整寬度,並記住版面設定 (by [@KokerZhou](https://github.com/KokerZhou)) - 分析頁將 SQL 實驗室與除錯工具統一收納至「更多」 -- 【CLI】支援透過 Unix Domain Socket 啟動 Web 服務 +- 【CLI】支援透過 Unix Domain Socket 啟動 Web 服務 (by [@KokerZhou](https://github.com/KokerZhou)) ### 🐛 修復 - 修正 WeFlow 與缺少穩定訊息 ID 的聊天記錄在增量匯入、合併時可能過度去重的問題,避免真實訊息遺失 -- 修正 AI 回覆失敗後,錯誤狀態、對話清單與後續提問可能異常的問題 -- 修正 AI 對話在儲存或編輯時可能留下不完整輪次的問題,並僅允許編輯最新一輪提問 -- 修正長對話壓縮可能遺失訊息邊界或記憶來源連結的問題 -- 修正同名 AI 服務設定可能讀取錯誤憑證的問題 +- 修正 AI 回覆失敗後,錯誤狀態、對話清單與後續提問可能異常的問題 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 修正 AI 對話在儲存或編輯時可能留下不完整輪次的問題,並僅允許編輯最新一輪提問 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 修正長對話壓縮可能遺失訊息邊界或記憶來源連結的問題 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 修正同名 AI 服務設定可能讀取錯誤憑證的問題 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) - 修正切換或返回分析頁面時可能重複載入,或繼續顯示匯入前舊資料的問題 - 完善跨對話分析的時間範圍、聯絡人解析、掃描預算與截斷提示 @@ -41,7 +41,7 @@ ### 🐛 修復 -- 匯入資料夾改為獨立按鈕,避免與選取檔案互相搶入口 +- 匯入資料夾改為獨立按鈕,避免與選取檔案互相搶入口 (by [@kongjiyu](https://github.com/kongjiyu)) ### ⚡ 效能 @@ -109,7 +109,7 @@ - 修正 QQ 自動匯入在 UID/UIN 變更、缺少 owner 資訊或工作階段已合併時的比對與成員對應問題,避免產生重複工作階段或錯誤的成員身分 - 修正合併匯入因平台訊息 ID 衝突或私人聊天身分資訊不足而誤刪訊息的問題,並保留有效的 owner 與工作階段身分資訊 -- 修正動態關鍵字排行的請求、快速切換與多色模式,避免顯示過期結果或因未知類型導致頁面當機 +- 修正動態關鍵字排行的請求、快速切換與多色模式,避免顯示過期結果或因未知類型導致頁面當機 (by [@kelaocai](https://github.com/kelaocai)) - 統一洞察頁面的載入轉場,並恢復年度活動日曆配色與關係檢視切換控制項 ### ⚡ 效能 @@ -125,7 +125,7 @@ - QQ Shuakami 與 QQ Chat Exporter 匯入改用 Rust 原生解析,提升大型匯出檔案的處理穩定性 - 多檔案匯入加入安全的並行排程,避免同一工作階段同時寫入,並顯示各檔案的進度與失敗資訊 - 新增兼顧隱私的匿名產品統計,只回報白名單內的執行環境與功能使用事件 -- 【CLI Web】本機語意索引執行元件改為首次使用時按需安裝,Docker 映像仍預先內建所需元件 +- 【CLI Web】本機語意索引執行元件改為首次使用時按需安裝,Docker 映像仍預先內建所需元件 (by [@kelaocai](https://github.com/kelaocai)) ### 🐛 修復 @@ -183,18 +183,18 @@ ### 🐛 修復 - 修正純 Web 版同時存取 OPFS、概覽篩選競態、詞頻統計與關係分析篩選問題 -- 前端圖示改為隨應用程式一併封裝,避免執行時依賴外部圖示服務 +- 前端圖示改為隨應用程式一併封裝,避免執行時依賴外部圖示服務 (by [@KokerZhou](https://github.com/KokerZhou)) - 統一 ChatLab JSONL 自動辨識與格式驗證規則,並拒絕無法解析的 owner 資訊 - 【桌面版】防止移轉程序刪除目前使用中的資料目錄,並拒絕新舊資料目錄互相巢狀或指向同一路徑 -- 【CLI】Docker 部署會保存使用者資料與系統狀態,並預設只將連接埠綁定至 localhost +- 【CLI】Docker 部署會保存使用者資料與系統狀態,並預設只將連接埠綁定至 localhost (by [@KokerZhou](https://github.com/KokerZhou)) ### 📝 文件 -- 新增 Docker 部署指南,並補充 AI 分析效益與本機隱私說明 +- 新增 Docker 部署指南,並補充 AI 分析效益與本機隱私說明 (by [@KokerZhou](https://github.com/KokerZhou)) ### 👷 CI -- 發布適用於 amd64 與 arm64 架構的 ChatLab CLI Docker 映像檔 +- 發布適用於 amd64 與 arm64 架構的 ChatLab CLI Docker 映像檔 (by [@KokerZhou](https://github.com/KokerZhou)) ## v0.32.0 (2026-07-18) @@ -202,14 +202,14 @@ ### ✨ 新功能 -- 【桌面版】新增應用程式鎖定,支援密碼、立即鎖定、啟動時解鎖,以及閒置逾時自動鎖定 +- 【桌面版】新增應用程式鎖定,支援密碼、立即鎖定、啟動時解鎖,以及閒置逾時自動鎖定 (by [@xyz-225648](https://github.com/xyz-225648)) ### 🐛 修復 - 資料目錄移轉後保留舊資料並提示使用者手動確認清理,避免移轉或重試時誤刪來源檔案 - 大型 ChatLab JSONL 改為串流分批處理,避免匯入卡住,並保留合併匯入中的成員頭像 - 統一桌面版與 CLI Web 的多檔批次匯入流程,提供循序處理進度、取消與結果摘要 -- 【桌面版】應用程式鎖定新增失敗次數限制、冷卻時間與啟動狀態保護,避免重新整理或重新啟動後繞過鎖定 +- 【桌面版】應用程式鎖定新增失敗次數限制、冷卻時間與啟動狀態保護,避免重新整理或重新啟動後繞過鎖定 (by [@xyz-225648](https://github.com/xyz-225648)) ## v0.31.3 (2026-07-17) @@ -253,7 +253,7 @@ - 統一各端 Push 匯入的驗證、去重、成員推斷與 dry-run 結果,避免預覽與實際寫入不一致 - 補匯入歷史訊息後,聊天記錄會依時間與訊息 ID 穩定排序,避免舊訊息出現在最後 -- 修正 QCE / QQ Chat Exporter 私人聊天被誤判為群組並產生「0」成員的問題 +- 修正 QCE / QQ Chat Exporter 私人聊天被誤判為群組並產生「0」成員的問題 (by [@shuakami](https://github.com/shuakami)) ## v0.31.0 (2026-07-13) @@ -271,7 +271,7 @@ - 修正 ChatLab JSON 匯出無法直接重新匯入的問題,並保留成員、owner 與別名元資料 - 強化自動增量匯入的去重、混合訊息身份識別與 ChatLab 重複匯入判斷 -- WhatsApp TXT 解析支援括號時間戳與點號 AM/PM 標記 +- WhatsApp TXT 解析支援括號時間戳與點號 AM/PM 標記 (by [@hh7418695](https://github.com/hh7418695)) ### ♻️ 重構 @@ -314,7 +314,7 @@ - 修正 SQL 實驗室匯出 CSV 失敗的問題 - 匯入 JSONL 等串流成員資料時保留頭像,並支援增量匯入新頭像 - 開啟 AI 日誌改由共享路由處理,讓本機 CLI Web 與桌面端行為一致 -- 概覽身份卡會跟隨目前選取的時間範圍更新 +- 概覽身份卡會跟隨目前選取的時間範圍更新 (by [@qikairo7](https://github.com/qikairo7)) ### 📝 文件 @@ -445,8 +445,8 @@ ### 🐛 修復 - 修正多關鍵字訊息搜尋功能 -- 修正詞雲詞典載入 UX 回歸問題 -- 修正排行榜標籤與 emoji 清理 +- 修正詞雲詞典載入 UX 回歸問題 (by [@qikairo7](https://github.com/qikairo7)) +- 修正排行榜標籤與 emoji 清理 (by [@qikairo7](https://github.com/qikairo7)) - 修正 v8 資料庫遷移缺少國際化文字的問題 - 【CLI】修正工具轉接器中會話上下文傳遞與前處理缺失的問題 @@ -466,8 +466,8 @@ ### 🐛 修復 -- 修正高頻詞統計中含有平台回覆訊息占位符的問題 -- 修正詞雲文字中包含媒體占位符的問題 +- 修正高頻詞統計中含有平台回覆訊息占位符的問題 (by [@qikairo7](https://github.com/qikairo7)) +- 修正詞雲文字中包含媒體占位符的問題 (by [@qikairo7](https://github.com/qikairo7)) - 修正 Jieba 自訂詞典變更後語言偏好快取與詞頻快取未失效的問題 - 修正時效性分析快取未按日失效的問題 - 修正時間軸面板預設捲動位置及會話跳轉功能失效的問題 @@ -555,7 +555,7 @@ - 修正 AI 助理訊息未填滿聊天區域寬度 - 改善分析計畫產生區塊的顯示樣式 - 修正思考深度選擇跨重新啟動後不持久的問題 -- 修正 JSONL 時間戳記規範化導致增量匯入異常 +- 修正 JSONL 時間戳記規範化導致增量匯入異常 (by [@cnYui](https://github.com/cnYui)) - 【桌面版】修正 execute_sql 工具未註冊至桌面版工具清單 ### test @@ -600,8 +600,8 @@ ### ✨ 新功能 -- 新增以斜線指令觸發的 AI 圖表執行環境,可在對話中產生並渲染 ECharts 圖表 -- 改善 AI 圖表結果呈現,補上更清楚的渲染狀態與多種圖表類型支援 +- 新增以斜線指令觸發的 AI 圖表執行環境,可在對話中產生並渲染 ECharts 圖表 (by [@CEQ151](https://github.com/CEQ151)) +- 改善 AI 圖表結果呈現,補上更清楚的渲染狀態與多種圖表類型支援 (by [@CEQ151](https://github.com/CEQ151)) ### 🐛 修復 @@ -609,7 +609,7 @@ - 修正預設問題標籤與側邊欄元素的層疊順序異常 - 【CLI Web】修正開發後端生命週期清理不完整的問題 - 【桌面版】修正自動技能未註冊圖表工具,導致圖表技能無法完整執行的問題 -- 【桌面版】修正 Windows 標題列覆蓋層在切換主題後快取未更新與顯示不順的問題 +- 【桌面版】修正 Windows 標題列覆蓋層在切換主題後快取未更新與顯示不順的問題 (by [@CEQ151](https://github.com/CEQ151)) ### ♻️ 重構 @@ -666,7 +666,7 @@ - 修正資料目錄遷移與資料庫遷移流程中的多個邊界情境,避免舊資料庫欄位缺失或路徑切換後讀取失敗 - 修正舊資料不存在或目錄變更後,資料遷移提示仍錯誤顯示的問題 -- 修正增量匯入後側邊欄訊息數量未更新的問題 +- 修正增量匯入後側邊欄訊息數量未更新的問題 (by [@CEQ151](https://github.com/CEQ151)) - 修正側邊欄收合狀態只存於 sessionStorage,導致重新整理後遺失的問題 - 修正編輯 AI 設定且已儲存金鑰時,拉取模型與驗證按鈕未正確啟用的問題 - 修正 AI 共享 SSE 串流回應的穩定性問題 @@ -717,7 +717,7 @@ ### ✨ 新功能 -- 新增訊息分叉(Fork)功能,可從任意 AI 回覆處建立獨立分支對話 +- 新增訊息分叉(Fork)功能,可從任意 AI 回覆處建立獨立分支對話 (by [@CEQ151](https://github.com/CEQ151)) - 狀態列新增推理強度選擇器,支援為每個推理模型個別記憶並切換思維等級 - 推理等級控制新增 default/auto 選項,擴展對 Kimi、Doubao、Gemini 等更多模型系列的支援 - 訊息分析檢視拆分為「類型分析」與「時間分析」兩個分頁,提供豐富統計洞察卡片 @@ -900,7 +900,7 @@ - 修復 Web 模式下會話索引、跨來源代理、Demo 保護與執行階段錯誤等問題 - 修復 Web 模式下應用程式版本顯示為空的問題 -- 修復 AI Agent 證據檢索、Web 端事件串流與錯誤格式化等問題 +- 修復 AI Agent 證據檢索、Web 端事件串流與錯誤格式化等問題 (by [@CEQ151](https://github.com/CEQ151)) - 修復同步顯示與匯入邏輯不一致的問題 - 修復 CLI 開發模式下的 ESM 模組解析問題 - 修復 CLI 安裝後可能找不到既有桌面版資料的問題 @@ -990,7 +990,7 @@ ### ✨ 新功能 -- 訂閱會話時支援依類型篩選與選取 +- 訂閱會話時支援依類型篩選與選取 (by [@xunchahaha](https://github.com/xunchahaha)) - 支援遠端會話分頁發現與按需載入更多 - 支援為資料來源設定每次拉取的訊息數量 @@ -1147,7 +1147,7 @@ ### 🐛 修復 - 修正 WhatsApp 時間解析正則與行匹配正則寬嚴不一致的問題 -- 修正 WhatsApp 12 小時制時間與 NNBSP 字元的解析相容性問題 +- 修正 WhatsApp 12 小時制時間與 NNBSP 字元的解析相容性問題 (by [@kongjiyu](https://github.com/kongjiyu)) ### 🔧 雜項 @@ -1210,7 +1210,7 @@ - 優化 AI 對話樣式與整體互動體驗 - 支援 FTS5 全文搜尋,並新增快速搜尋工具 - 精簡部分工具的搜尋參數,降低 token 消耗 -- 新增 Electron 應用 E2E 測試框架,支援連接埠管理與實例隔離 +- 新增 Electron 應用 E2E 測試框架,支援連接埠管理與實例隔離 (by [@l17728](https://github.com/l17728)) ### 🐛 修復 @@ -1223,7 +1223,7 @@ ### test -- 補充可重複使用的 E2E 啟動器冒煙測試能力 +- 補充可重複使用的 E2E 啟動器冒煙測試能力 (by [@l17728](https://github.com/l17728)) ### 📝 文件 @@ -1274,7 +1274,7 @@ ### 🐛 修復 -- 修復訊息去重誤判,並統一空字串的去重行為 +- 修復訊息去重誤判,並統一空字串的去重行為 (by [@lnexin](https://github.com/lnexin)) - 修復 AI 對話流程與前端 type-check 錯誤 - 補上預設 assistant 的兜底邏輯,修復例外情境 - 修復每日訊息趨勢未顯示的問題 @@ -1333,7 +1333,7 @@ - 新增聊天紀錄前處理流程 - 新增前處理設定介面與設定管理能力 -- Agent 支援依會話顯示上下文時間軸與執行狀態 +- Agent 支援依會話顯示上下文時間軸與執行狀態 (by [@n-WN](https://github.com/n-WN)) - 新增 AI 除錯模式並提升日誌可觀測性 ### 🐛 修復 @@ -1413,7 +1413,7 @@ ### 🐛 修復 - 修復增量更新後會話索引產生範圍不準確的問題(fix #79) -- 修復遷移與會話掃描時誤處理非聊天 SQLite 檔案的問題 +- 修復遷移與會話掃描時誤處理非聊天 SQLite 檔案的問題 (by [@n-WN](https://github.com/n-WN)) ### ♻️ 重構 @@ -1437,7 +1437,7 @@ ### 🐛 修復 -- 修復 LINE 聊天紀錄解析問題 +- 修復 LINE 聊天紀錄解析問題 (by [@haotien91](https://github.com/haotien91)) ### 📝 文件 @@ -1449,9 +1449,9 @@ ### ✨ 新功能 -- 設定中新增資料目錄位置選項 +- 設定中新增資料目錄位置選項 (by [@xunchahaha](https://github.com/xunchahaha)) - 優化資料儲存目錄遷移邏輯 -- 切換目錄時新增確認彈窗 +- 切換目錄時新增確認彈窗 (by [@xunchahaha](https://github.com/xunchahaha)) - 優化解析邏輯(WeFlow / Echotrace) ### 🐛 修復 @@ -1459,12 +1459,12 @@ - 修復 Windows 自訂篩選時訊息量過大導致崩潰的問題 - 修復第三方中轉 API 呼叫 tool_call 導致對話異常結束的問題 - 修復部分 WhatsApp 聊天紀錄無法正確辨識的問題 -- 修復管理頁表頭層級顯示問題 +- 修復管理頁表頭層級顯示問題 (by [@xunchahaha](https://github.com/xunchahaha)) ### ♻️ 重構 - 重構 session 查詢模組 -- 補強遷移日誌輸出 +- 補強遷移日誌輸出 (by [@xunchahaha](https://github.com/xunchahaha)) ## v0.9.2 (2026-02-02) @@ -1603,7 +1603,7 @@ - 支援增量匯入 - 支援批次匯入 - 優化樣式 -- Windows 端支援原生視窗控制並實作主題同步(#31) +- Windows 端支援原生視窗控制並實作主題同步(#31) (by [@JiQingzhe2004](https://github.com/JiQingzhe2004)) ### 🔧 雜項 @@ -1697,7 +1697,7 @@ - 支援設定代理(resolve #7) - 優化 AI 模型介面互動 - 新增使用者設定 API 教學 -- 新增 2 個免費 GLM 模型,並加入豆包服務商與最新模型 +- 新增 2 個免費 GLM 模型,並加入豆包服務商與最新模型 (by [@JiQingzhe2004](https://github.com/JiQingzhe2004)) - AI 回覆不再輸出 think 內容 ## v0.1.3 (2025-12-25) diff --git a/scripts/changelog-to-markdown.mjs b/scripts/changelog-to-markdown.mjs index 3f49d62db..8966344e5 100644 --- a/scripts/changelog-to-markdown.mjs +++ b/scripts/changelog-to-markdown.mjs @@ -26,14 +26,14 @@ const LOCALES = [ /** type → 各语言的标题(含 emoji)。缺失 type 回退为原始字符串,兼容未来扩展。 */ const TYPE_LABELS = { - feat: { cn: '✨ 新功能', en: '✨ Features', tw: '✨ 新功能', ja: '✨ 新機能' }, - fix: { cn: '🐛 修复', en: '🐛 Bug Fixes', tw: '🐛 修復', ja: '🐛 バグ修正' }, - refactor: { cn: '♻️ 重构', en: '♻️ Refactoring', tw: '♻️ 重構', ja: '♻️ リファクタリング' }, - perf: { cn: '⚡ 性能', en: '⚡ Performance', tw: '⚡ 效能', ja: '⚡ パフォーマンス' }, - docs: { cn: '📝 文档', en: '📝 Documentation', tw: '📝 文件', ja: '📝 ドキュメント' }, - style: { cn: '💄 样式', en: '💄 Styles', tw: '💄 樣式', ja: '💄 スタイル' }, - ci: { cn: '👷 CI', en: '👷 CI', tw: '👷 CI', ja: '👷 CI' }, - chore: { cn: '🔧 杂项', en: '🔧 Chores', tw: '🔧 雜項', ja: '🔧 雑務' }, + feat: { cn: '✨ 新功能', en: '✨ Features', tw: '✨ 新功能', ja: '✨ 新機能' }, + fix: { cn: '🐛 修复', en: '🐛 Bug Fixes', tw: '🐛 修復', ja: '🐛 バグ修正' }, + refactor: { cn: '♻️ 重构', en: '♻️ Refactoring', tw: '♻️ 重構', ja: '♻️ リファクタリング' }, + perf: { cn: '⚡ 性能', en: '⚡ Performance', tw: '⚡ 效能', ja: '⚡ パフォーマンス' }, + docs: { cn: '📝 文档', en: '📝 Documentation', tw: '📝 文件', ja: '📝 ドキュメント' }, + style: { cn: '💄 样式', en: '💄 Styles', tw: '💄 樣式', ja: '💄 スタイル' }, + ci: { cn: '👷 CI', en: '👷 CI', tw: '👷 CI', ja: '👷 CI' }, + chore: { cn: '🔧 杂项', en: '🔧 Chores', tw: '🔧 雜項', ja: '🔧 雑務' }, } // ── 渲染函数 ──────────────────────────────────────────────────────────────── @@ -44,6 +44,16 @@ const TYPE_LABELS = { * @param {string} langKey - 'cn' | 'en' | 'tw' | 'ja'(用于 type 标题查表) * @param {Array} entries - changelog 数组 */ +function renderChangelogItem(item) { + if (typeof item === 'string') return item + + const contributors = Array.isArray(item.contributors) ? item.contributors : [] + if (contributors.length === 0) return item.text + + const links = contributors.map((login) => `[@${login}](https://github.com/${encodeURIComponent(login)})`).join(', ') + return `${item.text} (by ${links})` +} + function renderMarkdown(h1, langKey, entries) { const lines = [`# ${h1}`, ''] @@ -66,7 +76,7 @@ function renderMarkdown(h1, langKey, entries) { lines.push(`### ${label}`) lines.push('') for (const item of items) { - lines.push(`- ${item}`) + lines.push(`- ${renderChangelogItem(item)}`) } lines.push('') } @@ -83,7 +93,7 @@ let hasError = false for (const { key, h1 } of LOCALES) { const jsonPath = resolve(ROOT, 'changelogs', `${key}.json`) - const mdPath = resolve(ROOT, 'changelogs', `${key}.md`) + const mdPath = resolve(ROOT, 'changelogs', `${key}.md`) let entries try { diff --git a/src/components/home/ChangelogModal.vue b/src/components/home/ChangelogModal.vue index da33ccd19..491056d3e 100644 --- a/src/components/home/ChangelogModal.vue +++ b/src/components/home/ChangelogModal.vue @@ -8,8 +8,11 @@ import { getChangeTypeConfig } from './changelogTypeConfig' import { usePlatformService } from '@/services' import CaptureButton from '@/components/common/CaptureButton.vue' import { + getChangelogItemContributors, + getChangelogItemText, getBundledChangelogs, getBundledLatestVersion, + getGithubContributorUrl, hasBundledChangelogVersion, isBundledLatestVersion, normalizeChangelogVersion, @@ -370,7 +373,26 @@ defineExpose({ open, openWithData, close, fetchChangelogs, getLatestVersion, che class="relative text-sm text-gray-600 dark:text-gray-400" > - {{ item }} + {{ getChangelogItemText(item) }} + + (by  + + ) + diff --git a/src/components/home/changelogData.ts b/src/components/home/changelogData.ts index 1491173ee..a51fc6682 100644 --- a/src/components/home/changelogData.ts +++ b/src/components/home/changelogData.ts @@ -5,13 +5,20 @@ import twChangelogs from '../../../changelogs/tw.json' export type ChangelogChangeType = 'feat' | 'fix' | 'refactor' | 'docs' | 'chore' | 'style' | 'ci' +export interface StructuredChangelogChangeItem { + text: string + contributors?: string[] +} + +export type ChangelogChangeItem = string | StructuredChangelogChangeItem + export interface ChangelogItem { version: string date: string summary: string changes: { type: ChangelogChangeType - items: string[] + items: ChangelogChangeItem[] }[] } @@ -26,6 +33,18 @@ export function normalizeChangelogVersion(version?: string | null) { return version ? version.trim().replace(/^v/i, '') : null } +export function getChangelogItemText(item: ChangelogChangeItem) { + return typeof item === 'string' ? item : item.text +} + +export function getChangelogItemContributors(item: ChangelogChangeItem) { + return typeof item === 'string' ? [] : (item.contributors ?? []) +} + +export function getGithubContributorUrl(login: string) { + return `https://github.com/${encodeURIComponent(login)}` +} + export function getBundledChangelogs(locale: string): ChangelogItem[] { return BUNDLED_CHANGELOGS[locale] ?? BUNDLED_CHANGELOGS['en-US'] } From d844578bab17d68f8f960dea4675781645c170ff Mon Sep 17 00:00:00 2001 From: digua Date: Fri, 4 Sep 2026 16:49:46 +0800 Subject: [PATCH 05/68] release: v0.37.1 --- apps/cli/package.json | 2 +- changelogs/cn.json | 33 +++++++++++++++++++++++++++++++++ changelogs/cn.md | 14 ++++++++++++++ changelogs/en.json | 33 +++++++++++++++++++++++++++++++++ changelogs/en.md | 14 ++++++++++++++ changelogs/ja.json | 33 +++++++++++++++++++++++++++++++++ changelogs/ja.md | 14 ++++++++++++++ changelogs/tw.json | 33 +++++++++++++++++++++++++++++++++ changelogs/tw.md | 14 ++++++++++++++ package.json | 2 +- 10 files changed, 190 insertions(+), 2 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index deedf1a3f..5b8390c07 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "chatlab-cli", - "version": "0.37.0", + "version": "0.37.1", "description": "ChatLab CLI & Service - Chat history analysis tool", "type": "module", "bin": { diff --git a/changelogs/cn.json b/changelogs/cn.json index f25790760..49742b5ab 100644 --- a/changelogs/cn.json +++ b/changelogs/cn.json @@ -1,4 +1,37 @@ [ + { + "version": "0.37.1", + "date": "2026-09-04", + "summary": "新增贡献者署名,优化 Docker 启动,并修复 AI 工具循环、文本统计和关系图资源占用。", + "changes": [ + { + "type": "feat", + "items": [ + { + "text": "语言偏好、词频和口头禅分析支持读取语音转写内容", + "contributors": ["babybitter"] + }, + { + "text": "【CLI】Docker 镜像内置默认中文分词词典,首次启动无需下载", + "contributors": ["KokerZhou"] + } + ] + }, + { + "type": "fix", + "items": [ + { + "text": "修复 AI 工具调用达到轮次上限后可能重复生成最终回答、工具错误被记为成功及上下文重复写入的问题", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "修复系统通知和分享占位污染词频、语言偏好与口头禅统计,并保留用户表情内容", + "contributors": ["babybitter"] + } + ] + } + ] + }, { "version": "0.37.0", "date": "2026-08-28", diff --git a/changelogs/cn.md b/changelogs/cn.md index ccb14eecd..58107d4e2 100644 --- a/changelogs/cn.md +++ b/changelogs/cn.md @@ -1,5 +1,19 @@ # 更新日志 +## v0.37.1 (2026-09-04) + +> 新增贡献者署名,优化 Docker 启动,并修复 AI 工具循环、文本统计和关系图资源占用。 + +### ✨ 新功能 + +- 语言偏好、词频和口头禅分析支持读取语音转写内容 (by [@babybitter](https://github.com/babybitter)) +- 【CLI】Docker 镜像内置默认中文分词词典,首次启动无需下载 (by [@KokerZhou](https://github.com/KokerZhou)) + +### 🐛 修复 + +- 修复 AI 工具调用达到轮次上限后可能重复生成最终回答、工具错误被记为成功及上下文重复写入的问题 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 修复系统通知和分享占位污染词频、语言偏好与口头禅统计,并保留用户表情内容 (by [@babybitter](https://github.com/babybitter)) + ## v0.37.0 (2026-08-28) > 新增跨对话 AI 分析与长期记忆,修复导入去重、AI回复失败等大量问题,并优化洞察性能和聊天记录查看体验。
目前的跨对话分析测试不足,仍然为实验性功能,后续版本会继续优化和完善。 diff --git a/changelogs/en.json b/changelogs/en.json index ea720530c..dda8c10bd 100644 --- a/changelogs/en.json +++ b/changelogs/en.json @@ -1,4 +1,37 @@ [ + { + "version": "0.37.1", + "date": "2026-09-04", + "summary": "Add contributor attribution, improve Docker startup, and fix AI tool loops, text statistics, and relationship graph resource usage.", + "changes": [ + { + "type": "feat", + "items": [ + { + "text": "Include voice transcriptions in language preference, word frequency, and catchphrase analysis", + "contributors": ["babybitter"] + }, + { + "text": "【CLI】Bundle the default Chinese NLP dictionary in Docker images to avoid downloading it on first startup", + "contributors": ["KokerZhou"] + } + ] + }, + { + "type": "fix", + "items": [ + { + "text": "Fix duplicate final responses after AI tool calls reach the turn limit, tool errors recorded as successful, and duplicated context writes", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "Exclude system notifications and share placeholders from word frequency, language preference, and catchphrase statistics while preserving user emoji content", + "contributors": ["babybitter"] + } + ] + } + ] + }, { "version": "0.37.0", "date": "2026-08-28", diff --git a/changelogs/en.md b/changelogs/en.md index f6e9281fd..e0cd96753 100644 --- a/changelogs/en.md +++ b/changelogs/en.md @@ -1,5 +1,19 @@ # Changelog +## v0.37.1 (2026-09-04) + +> Add contributor attribution, improve Docker startup, and fix AI tool loops, text statistics, and relationship graph resource usage. + +### ✨ Features + +- Include voice transcriptions in language preference, word frequency, and catchphrase analysis (by [@babybitter](https://github.com/babybitter)) +- 【CLI】Bundle the default Chinese NLP dictionary in Docker images to avoid downloading it on first startup (by [@KokerZhou](https://github.com/KokerZhou)) + +### 🐛 Bug Fixes + +- Fix duplicate final responses after AI tool calls reach the turn limit, tool errors recorded as successful, and duplicated context writes (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- Exclude system notifications and share placeholders from word frequency, language preference, and catchphrase statistics while preserving user emoji content (by [@babybitter](https://github.com/babybitter)) + ## v0.37.0 (2026-08-28) > Add cross-chat AI analysis and long-term memory, fix issues including import deduplication and failed AI replies, and improve Insights performance and the chat record viewer.
Cross-chat analysis remains experimental due to limited testing and will continue to improve in future releases. diff --git a/changelogs/ja.json b/changelogs/ja.json index 48ad2d60c..410195755 100644 --- a/changelogs/ja.json +++ b/changelogs/ja.json @@ -1,4 +1,37 @@ [ + { + "version": "0.37.1", + "date": "2026-09-04", + "summary": "コントリビューター表記を追加し、Docker の起動を改善。AI ツールのループ、テキスト統計、関係グラフのリソース使用に関する問題を修正しました。", + "changes": [ + { + "type": "feat", + "items": [ + { + "text": "音声の文字起こし内容を言語傾向、単語頻度、口癖の分析対象に追加", + "contributors": ["babybitter"] + }, + { + "text": "【CLI】Docker イメージに既定の中国語形態素解析辞書を同梱し、初回起動時のダウンロードを不要に変更", + "contributors": ["KokerZhou"] + } + ] + }, + { + "type": "fix", + "items": [ + { + "text": "AI ツールの呼び出しが上限に達した際の最終回答の重複、ツールエラーの成功扱い、コンテキストの重複書き込みを修正", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "ユーザーの絵文字コンテンツを保持しつつ、システム通知や共有プレースホルダーが単語頻度、言語傾向、口癖の統計に含まれる問題を修正", + "contributors": ["babybitter"] + } + ] + } + ] + }, { "version": "0.37.0", "date": "2026-08-28", diff --git a/changelogs/ja.md b/changelogs/ja.md index 182b11617..7e3517338 100644 --- a/changelogs/ja.md +++ b/changelogs/ja.md @@ -1,5 +1,19 @@ # 変更履歴 +## v0.37.1 (2026-09-04) + +> コントリビューター表記を追加し、Docker の起動を改善。AI ツールのループ、テキスト統計、関係グラフのリソース使用に関する問題を修正しました。 + +### ✨ 新機能 + +- 音声の文字起こし内容を言語傾向、単語頻度、口癖の分析対象に追加 (by [@babybitter](https://github.com/babybitter)) +- 【CLI】Docker イメージに既定の中国語形態素解析辞書を同梱し、初回起動時のダウンロードを不要に変更 (by [@KokerZhou](https://github.com/KokerZhou)) + +### 🐛 バグ修正 + +- AI ツールの呼び出しが上限に達した際の最終回答の重複、ツールエラーの成功扱い、コンテキストの重複書き込みを修正 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- ユーザーの絵文字コンテンツを保持しつつ、システム通知や共有プレースホルダーが単語頻度、言語傾向、口癖の統計に含まれる問題を修正 (by [@babybitter](https://github.com/babybitter)) + ## v0.37.0 (2026-08-28) > クロスチャット AI 分析と長期メモリを追加し、インポートの重複排除や AI 応答失敗を含む多数の問題を修正。インサイトのパフォーマンスとチャット履歴ビューアーも改善しました。
クロスチャット分析はまだ十分にテストされていない実験的な機能です。今後のバージョンでも継続して改善します。 diff --git a/changelogs/tw.json b/changelogs/tw.json index 7389559af..473b89958 100644 --- a/changelogs/tw.json +++ b/changelogs/tw.json @@ -1,4 +1,37 @@ [ + { + "version": "0.37.1", + "date": "2026-09-04", + "summary": "新增貢獻者署名,改善 Docker 啟動,並修正 AI 工具迴圈、文字統計與關係圖資源占用問題。", + "changes": [ + { + "type": "feat", + "items": [ + { + "text": "語言偏好、詞頻與口頭禪分析支援讀取語音轉寫內容", + "contributors": ["babybitter"] + }, + { + "text": "【CLI】Docker 映像檔內建預設中文斷詞詞典,首次啟動不再需要下載", + "contributors": ["KokerZhou"] + } + ] + }, + { + "type": "fix", + "items": [ + { + "text": "修正 AI 工具呼叫達到回合上限後可能重複產生最終回覆、工具錯誤被記為成功,以及上下文內容重複寫入的問題", + "contributors": ["Sanssssssssssssssss"] + }, + { + "text": "修正系統通知和分享預留文字被納入詞頻、語言偏好與口頭禪統計的問題,並保留使用者的表情符號內容", + "contributors": ["babybitter"] + } + ] + } + ] + }, { "version": "0.37.0", "date": "2026-08-28", diff --git a/changelogs/tw.md b/changelogs/tw.md index 9af1a3532..39444bd12 100644 --- a/changelogs/tw.md +++ b/changelogs/tw.md @@ -1,5 +1,19 @@ # 更新日誌 +## v0.37.1 (2026-09-04) + +> 新增貢獻者署名,改善 Docker 啟動,並修正 AI 工具迴圈、文字統計與關係圖資源占用問題。 + +### ✨ 新功能 + +- 語言偏好、詞頻與口頭禪分析支援讀取語音轉寫內容 (by [@babybitter](https://github.com/babybitter)) +- 【CLI】Docker 映像檔內建預設中文斷詞詞典,首次啟動不再需要下載 (by [@KokerZhou](https://github.com/KokerZhou)) + +### 🐛 修復 + +- 修正 AI 工具呼叫達到回合上限後可能重複產生最終回覆、工具錯誤被記為成功,以及上下文內容重複寫入的問題 (by [@Sanssssssssssssssss](https://github.com/Sanssssssssssssssss)) +- 修正系統通知和分享預留文字被納入詞頻、語言偏好與口頭禪統計的問題,並保留使用者的表情符號內容 (by [@babybitter](https://github.com/babybitter)) + ## v0.37.0 (2026-08-28) > 新增跨對話 AI 分析與長期記憶,修正匯入去重、AI 回覆失敗等多項問題,並改善洞察效能與聊天記錄檢視體驗。
目前跨對話分析的測試仍不完整,屬於實驗性功能,後續版本將持續改善。 diff --git a/package.json b/package.json index 221b11d20..fa740c964 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ChatLab", - "version": "0.37.0", + "version": "0.37.1", "private": true, "description": "本地化的聊天记录分析工具,通过 SQL 和 AI Agent 回顾你的社交记忆", "repository": { From 42d8171d5de82ffbdaeab908ba0c96b3bb4997b0 Mon Sep 17 00:00:00 2001 From: digua Date: Sat, 5 Sep 2026 22:35:45 +0800 Subject: [PATCH 06/68] docs(agents): refine verification and authorization rules --- AGENTS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e8a8c01fa..a1690745a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ - 文档:开始开发前,请先查看公开开发指南 `docs/cn/contributing/development.md`。如果工作区存在 `.docs/`,再查看 `./.docs/README.md` 并阅读与当前需求相关的文档;`.docs/` 是可选的个人或团队私有开发上下文,可用于维护任务、决策、AI 协作记忆和临时规划,公开文档与公开 PR 不应依赖 `.docs/` 才能理解。 - 目标:用最小改动快速交付正确、可维护、可回归的业务结果 -- 每次完成任务后,对产生修改的文件进行类型检查、lint检查和format格式化,指定修改的文件路径去执行,确保代码质量 +- 每次完成任务后,按修改文件的类型和改动范围执行适用的类型检查、lint 和 format;lint 和 format 优先指定修改文件,类型检查按相关项目执行。具体命令见“命令与验证”。 - 在执行检查时,如果有其他与本次修改无关的报错,也需要一并修复 ## 实现原则 @@ -17,7 +17,7 @@ - 处理外部 review、bug 报告或架构建议时,必须先核对代码事实,再决定接受或反驳;不要因为 reviewer 表达确定就直接改代码 - 判断问题是否成立时,至少阅读相关函数、调用方、被调用方、相邻测试和现有文档;不能只看 diff 或单行评论 - 如果涉及依赖、框架、OpenAI/LLM SDK、数据库、打包发布等外部行为,应优先查官方文档、源码或类型定义,不凭记忆判断 -- 结论必须包含证据:涉及的文件/函数、当前行为、风险、建议修复方式,以及已运行或应运行的验证 +- 审查结论必须说明涉及的文件/函数、当前行为和判断依据;问题成立时,补充用户影响、建议修复方式,以及已运行或应运行的验证。 - 评估异步、并发、缓存、性能、外部依赖或资源异常问题时,必须证明完整因果链和现实可达性;不能只凭 Promise 理论上可永久 pending、竞态在抽象状态中存在或极端环境可能失败,就判定为严重 Bug - 严重度应同时考虑触发概率、用户影响和可恢复性,高影响后果不能单独替代可达性证据;如果前置异常与后续有害操作共享同一故障域,必须解释为什么前者失败或阻塞时后者仍能成功 - 多轮 AI review 应按根因和关键假设聚合;若新评论只是把同一未证实假设传播到另一个下游,且没有新的独立代码或运行时证据,默认降低优先级并停止无限追深 @@ -44,7 +44,7 @@ - 必须新增:修复真实的行为 Bug,或修改用户数据、数据库迁移、导入解析、去重、权限认证、配置/API Key 迁移、公开 API、跨端共享 service 等高风险逻辑时,应优先补充能在修改前失败的回归测试。已有测试完整覆盖时不重复新增。 - 通常不新增:文案、样式、类型、日志、注释、文档、常量、简单 getter、文件列表和无行为变化的重构。只运行相关类型检查、lint、format、构建或现有测试。 - 避免机械测试:不要通过扫描源码字符串、匹配完整 SQL、断言私有函数调用顺序、锁定日志文案或枚举每个常量来证明功能正确。Mock 调用次数只用于 adapter 的参数传递或权限边界,不用于锁定内部实现。 -- 提高信息密度:同一规则的多个输入优先使用表驱动测试或通用断言;下层已覆盖算法时,上层只验证调用链和返回契约。修改相关模块时,应搜索并合并或删除重复、过期和低价值测试。 +- 提高信息密度:同一规则的多个输入优先使用表驱动测试或通用断言;下层已覆盖算法时,上层只验证调用链和返回契约。修改相关模块时,检查受影响的测试;发现与本次变更直接相关的重复、过期或低价值用例时,再合并或删除。 - 测试分层:纯函数和参数规范化使用单元测试;SQL、迁移、导入写库、Fastify route 和跨包 service 优先使用轻量真实依赖或临时数据库;真实 Electron、浏览器、LLM 和网络只用于少量显式启用的 Smoke/E2E。 - 测试位置:模块单元测试放在被测文件附近;跨模块、集成、E2E 和测试工具放在根目录 `tests/` 或对应 app/package 的既有测试目录。 - Review 判断:缺少测试本身通常不是严重 Bug。Reviewer 必须指出具体未覆盖的用户风险;只有数据安全、迁移、权限、公开契约等高风险行为缺少必要验证时,才应阻塞当前 PR。 @@ -53,8 +53,8 @@ - 类型检查:Node/CLI/Electron 主进程相关改动运行 `pnpm run type-check:node`;前端/Vue 相关改动运行 `pnpm run type-check:web`;跨端或发布前改动运行 `pnpm run type-check:all` - Lint:优先对修改文件运行 `pnpm exec eslint `;需要全量修复时再运行 `pnpm lint` -- Format:优先对修改文件运行 `pnpm exec prettier --write `;大范围格式化才运行 `pnpm format` -- 单元/集成测试:日常默认运行 `pnpm test` 或 `pnpm run test:unit`;优先运行相关测试文件时用 `pnpm test -- path/to/file.test.ts` +- Format:优先对修改文件运行 `pnpm exec prettier --write `;大范围格式化才运行 `pnpm format`。`docs/**/*.md` 被 Prettier 默认忽略,格式化这些文件时使用 `pnpm exec prettier --write --ignore-path .gitignore `。 +- 单元/集成测试:按改动范围优先运行相关测试,使用 `pnpm test -- path/to/file.test.ts`;需要全量回归时运行 `pnpm test` 或 `pnpm run test:unit`。相关测试通过后,仅在新增改动、失败或具体未解决风险需要时,扩大或重复测试。 - 文档:修改公开文档或 VitePress 配置后运行 `pnpm docs:build`;只改 `.docs/` 私有任务文档时不需要构建公开文档站 - E2E/Smoke:`pnpm run test:e2e:launcher`、`pnpm run test:e2e:smoke` 和真实 LLM/真实 Electron/真实网络测试只在相关功能需要时运行,不加入默认 `pnpm test` - 最后检查:提交前运行 `git diff --check`,确认没有空白错误 @@ -87,7 +87,7 @@ ## 安全与发布 - 不得提交真实 API Key、token、用户聊天数据库、个人数据目录、日志、截图或包含隐私的导出文件 -- 修改依赖、lockfile、构建产物、发布脚本、版本号、changelog、npm publish、release 流程时,必须先获得明确确认 +- 修改依赖、lockfile、构建产物、发布脚本、版本号、changelog、npm publish、release 流程时,必须先获得明确确认。用户已在当前对话明确授权相应操作时,无需重复确认。 - 涉及 API Key、auth profile、配置迁移、数据目录迁移和数据库迁移时,必须优先保护已有用户数据,并提供回滚或失败中断策略 ## 提交规范 From 7310b0670fde904c835df5809db2b9987a92e64a Mon Sep 17 00:00:00 2001 From: digua Date: Sat, 5 Sep 2026 22:58:27 +0800 Subject: [PATCH 07/68] docs(skills): streamline agent guidance and workflows --- docs/cn/contributing/ai-workflow.md | 73 ++++++++++++++++++++++++++ docs/cn/contributing/development.md | 80 +++++++++++++++-------------- skills/chatlab-analyze-cn/SKILL.md | 25 +++------ skills/chatlab-analyze/SKILL.md | 25 +++------ skills/chatlab-convert-cn/SKILL.md | 43 ++++------------ skills/chatlab-convert/SKILL.md | 43 ++++------------ skills/chatlab-import-cn/SKILL.md | 22 ++------ skills/chatlab-import/SKILL.md | 22 ++------ 8 files changed, 154 insertions(+), 179 deletions(-) create mode 100644 docs/cn/contributing/ai-workflow.md diff --git a/docs/cn/contributing/ai-workflow.md b/docs/cn/contributing/ai-workflow.md new file mode 100644 index 000000000..3d4b9a792 --- /dev/null +++ b/docs/cn/contributing/ai-workflow.md @@ -0,0 +1,73 @@ +--- +layout: doc +title: AI 协作工作流 +--- + +# AI 协作工作流 + +本文说明 ChatLab 如何维护开发代理指令与对外技能。日常入口是根目录 `AGENTS.md`;只有维护技能、调整工作流或处理复杂审查时才需要阅读本文。 + +## 指令放在哪里 + +| 位置 | 用途 | 维护原则 | +| --- | --- | --- | +| `AGENTS.md` | 跨任务稳定约定、项目入口、验证命令 | 保持精简,只保留会影响决策的信息 | +| `docs/cn/contributing/` | 公开开发细则 | 按任务引用,公开 PR 可独立理解 | +| `skills/*/SKILL.md` | 供外部 Agent 使用的分析、导入、转换流程 | 每个技能一个职责,中英文独立分发并保持语义一致 | +| `skills/*/references/`、`scripts/` | 格式契约与严格验证器 | 按需读取;确定性校验交给脚本 | +| `.github/workflows/` | CI、构建、发布及产物清理 | 保留真实质量门禁,不承载模型提示词 | +| 可选的本地规则和私有技能 | 个人协作偏好与维护任务 | 不覆盖用户当前意图,不作为公开贡献前提 | + +`skills/` 中的文件是产品分发资源,不会因存在于仓库就自动成为 Codex 开发技能。需要本地自动发现时,按宿主支持的技能安装方式配置;不要为了开发本项目而自动加载聊天分析技能。Codex 的仓库技能发现目录是 `.agents/skills`,正文按需加载。[官方技能文档](https://learn.chatgpt.com/docs/build-skills) + +## 执行与授权 + +从用户目标、输入和完成条件出发,直接完成已授权的工作。复杂任务可以先列简短计划;计划不构成新的审批门槛。只有会影响正确性、数据归属或授权范围的缺失信息才需要澄清。 + +已明确要求“转换并导入”时,验证成功后继续导入;仅要求预览时只返回计划。安装依赖、提交、推送和发布依各自授权执行。工具缺失时完成可做的工作,并准确说明未完成的验证或操作。 + +一次性审批要求不能散落在技能描述、默认提示词和正文中互相冲突。需要停下时说明具体规则及缺少的授权;不要求每次回复都进入交互弹窗,也不以固定口令作为任务完成条件。 + +## 技能维护 + +- `description` 只写任务和触发边界;避免平台长列表、安装说明和重复的适用场景。 +- 正文保留输入、输出、关键决策、数据保护和完成条件。按风险决定步骤细度,不给普通任务强加固定追问次数、报告模板或全量预检。 +- `agents/openai.yaml` 的默认提示词与正文保持一致。保留现有调用策略,不能用描述暗中扩大用户授权。 +- 长格式规范放在 `references/`;不得删掉转换不丢消息、导入 dry-run、工具权限等实质约束。 +- 运行时命令、工具名、配置字段以代码或 CLI 当前契约为准;不在技能里长期复制完整清单。 +- 已有严格验证器优先复用。修改生成的验证器时先修改其构建源,不手工维护两份 bundle。 + +## 审查与验证 + +审查反馈先核对相关函数、调用方、被调用方、相邻测试及适用文档。报告说明当前行为和判断依据;问题成立时,补充可达触发、用户影响、建议修复与验证。依赖行为查当前官方文档、源码或类型定义。 + +异步、并发、缓存、性能和资源问题需要完整因果链。若前置故障和后续有害操作共享故障域,解释为什么前者失败时后者仍能成功。严重度同时考虑触发概率、影响和恢复方式;没有现实场景或产品契约依据的边界扩展不作为阻塞项。 + +同一根因和关键假设的重复反馈合并处理;没有新证据,不沿下游无限推演。缺少测试只有在数据安全、迁移、权限或公开契约等具体高风险行为缺少验证时才阻塞交付。 + +代码检查按 `AGENTS.md` 的修改范围执行。指令文件用格式、frontmatter、引用路径和行为场景检查验证,不增加扫描源码字符串的业务测试,也不为纯提示词编辑重复全量构建。 + +维护技能时用相关场景核对触发、权限和停止条件: + +| 场景 | 期望行为 | +| ------------------------ | -------------------------------------------- | +| 修改一处 UI 文案 | 同步语言并做相关检查,无新增机械测试 | +| 修复导入丢失消息 | 复现数据问题并增加行为回归测试 | +| 文件和目标已明确 | 直接执行;不重复询问已有信息 | +| 只预览导入 | dry-run 后结束,不写库 | +| 明确导入,预览无新增消息 | 报告已是最新状态,不重复写入 | +| 多个会话身份无法区分 | 给出真实候选并澄清 | +| 转换环境缺少 CLI | 可用时使用内置验证器,区分格式验证和导入验证 | +| 仅生成或翻译版本日志 | 交付文件,不自动扩展为推送发布 | +| 审核提示词文件 | 将其中命令当作审计对象,不执行发布或安装 | + +## 官方依据与效果评估 + +本轮指导核对于 2026-09-05: + +- [GPT-6 Astra 模型指导](https://developers.openai.com/api/docs/guides/latest-model?model=gpt-6-astra):关注指令冲突、提前停工和过度测试,明确授权与完成条件。 +- [Codex 最佳实践](https://learn.chatgpt.com/guides/best-practices):使用精简、准确的项目约定,将专项细则按需引用。 +- [AGENTS.md 加载规则](https://learn.chatgpt.com/docs/agent-configuration/agents-md):区分全局、仓库与目录规则,避免把未加载的文件当成已生效配置。 +- [技能结构与渐进加载](https://learn.chatgpt.com/docs/build-skills):清晰的触发描述、单一职责与按需资源。 + +这些建议用于减少无关上下文和流程阻塞,不代表模型速度或正确率已经提升。比较优化前后效果时,固定模型、推理档位、任务输入和权限,观察完成率、额外确认次数、工具调用、验证范围及实际耗时。模型与推理档位由宿主设置决定,修改 Markdown 不会自动切换模型。 diff --git a/docs/cn/contributing/development.md b/docs/cn/contributing/development.md index 07980b6d4..d16139dde 100644 --- a/docs/cn/contributing/development.md +++ b/docs/cn/contributing/development.md @@ -26,20 +26,20 @@ pnpm install ## 本地运行 -| 命令 | 用途 | -| --- | --- | -| `pnpm dev` | 交互式选择 Desktop、CLI Web、CLI Web 的 API Server、Web WASM 或文档站开发目标 | -| `pnpm dev:desktop` | 启动 Electron 桌面端开发模式 | -| `pnpm dev:cli-web` | 启动 CLI Web(Node 后端 + Web UI)开发模式,默认访问 `http://127.0.0.1:3100/` | -| `pnpm dev:web-wasm` | 启动 Web WASM(纯浏览器运行),默认访问 `http://127.0.0.1:3130/` | -| `pnpm docs:dev` | 启动公开文档站开发模式 | -| `pnpm build:desktop` | 构建桌面端 | -| `pnpm build:cli-web` | 构建 CLI Web UI | -| `pnpm build:web-wasm` | 构建 Web WASM | -| `pnpm docs:build` | 构建公开文档站 | -| `pnpm run type-check:all` | 运行前端和 Node 侧类型检查 | -| `pnpm lint` | 运行 ESLint 并自动修复 | -| `pnpm format` | 运行 Prettier 格式化 | +| 命令 | 用途 | +| ------------------------- | ----------------------------------------------------------------------------- | +| `pnpm dev` | 交互式选择 Desktop、CLI Web、CLI Web 的 API Server、Web WASM 或文档站开发目标 | +| `pnpm dev:desktop` | 启动 Electron 桌面端开发模式 | +| `pnpm dev:cli-web` | 启动 CLI Web(Node 后端 + Web UI)开发模式,默认访问 `http://127.0.0.1:3100/` | +| `pnpm dev:web-wasm` | 启动 Web WASM(纯浏览器运行),默认访问 `http://127.0.0.1:3130/` | +| `pnpm docs:dev` | 启动公开文档站开发模式 | +| `pnpm build:desktop` | 构建桌面端 | +| `pnpm build:cli-web` | 构建 CLI Web UI | +| `pnpm build:web-wasm` | 构建 Web WASM | +| `pnpm docs:build` | 构建公开文档站 | +| `pnpm run type-check:all` | 运行前端和 Node 侧类型检查 | +| `pnpm lint` | 运行 ESLint 并自动修复 | +| `pnpm format` | 运行 Prettier 格式化 | 小范围改动优先对修改文件或相关子项目做定向检查;跨模块、发布或架构类改动再跑全量检查。 @@ -53,18 +53,18 @@ pnpm install ## 目录职责 -| 路径 | 职责 | -| --- | --- | -| `src/` | 共享前端应用代码,包含页面、组件、服务封装、状态和 i18n | -| `src/services/` | 前端访问 Electron、CLI Web API 和平台能力的服务层 | -| `apps/desktop/` | Electron 主进程、preload 和桌面端构建配置 | -| `apps/cli/` | CLI、HTTP API、CLI Web 运行时和导入命令 | -| `packages/core/` | 平台无关的核心数据模型、查询、导入和成员操作 | -| `packages/node-runtime/` | Node.js 运行时服务、数据库、AI、导出、缓存和迁移 | -| `packages/tools/` | 统一 AI 工具定义和数据访问适配 | -| `docs/` | 公开文档站源码 | -| `changelogs/` | 应用内和发布使用的多语言更新日志 | -| `.docs/` | 可选的个人或团队私有开发上下文,不是公开贡献的必需依赖 | +| 路径 | 职责 | +| ------------------------ | ------------------------------------------------------- | +| `src/` | 共享前端应用代码,包含页面、组件、服务封装、状态和 i18n | +| `src/services/` | 前端访问 Electron、CLI Web API 和平台能力的服务层 | +| `apps/desktop/` | Electron 主进程、preload 和桌面端构建配置 | +| `apps/cli/` | CLI、HTTP API、CLI Web 运行时和导入命令 | +| `packages/core/` | 平台无关的核心数据模型、查询、导入和成员操作 | +| `packages/node-runtime/` | Node.js 运行时服务、数据库、AI、导出、缓存和迁移 | +| `packages/tools/` | 统一 AI 工具定义和数据访问适配 | +| `docs/` | 公开文档站源码 | +| `changelogs/` | 应用内和发布使用的多语言更新日志 | +| `.docs/` | 可选的个人或团队私有开发上下文,不是公开贡献的必需依赖 | ## 架构边界 @@ -129,23 +129,23 @@ CHATLAB_ALLOW_INCOMPATIBLE_DATA_DIR=1 ## 常见改动入口 -| 想改什么 | 先看哪里 | -| --- | --- | -| 前端页面和组件 | `src/pages/`、`src/components/` | -| 图表分析 | `src/components/analysis/`、`src/components/charts/` | -| 数据、消息、会话 API 调用 | `src/services/` | -| Electron 主进程 | `apps/desktop/main/`、`apps/desktop/preload/` | -| CLI 和 Web API | `apps/cli/` | -| 共享业务逻辑 | `packages/node-runtime/src/services/`、`packages/core/` | -| AI 工具和 Agent | `packages/tools/`、`packages/node-runtime/src/ai/`、`src/services/ai*` | -| 导入解析 | `packages/core/`、`apps/cli/src/import/`、`src/services/import/` | -| 文档站 | `docs/`、`docs/.vitepress/config.mts` | -| 更新日志 | `changelogs/` | +| 想改什么 | 先看哪里 | +| ------------------------- | ---------------------------------------------------------------------- | +| 前端页面和组件 | `src/pages/`、`src/components/` | +| 图表分析 | `src/components/analysis/`、`src/components/charts/` | +| 数据、消息、会话 API 调用 | `src/services/` | +| Electron 主进程 | `apps/desktop/main/`、`apps/desktop/preload/` | +| CLI 和 Web API | `apps/cli/` | +| 共享业务逻辑 | `packages/node-runtime/src/services/`、`packages/core/` | +| AI 工具和 Agent | `packages/tools/`、`packages/node-runtime/src/ai/`、`src/services/ai*` | +| 导入解析 | `packages/core/`、`apps/cli/src/import/`、`src/services/import/` | +| 文档站 | `docs/`、`docs/.vitepress/config.mts` | +| 更新日志 | `changelogs/` | ## 测试与检查 - 修改 TypeScript 或 Vue 代码后,至少运行相关类型检查。 -- 修改公开文档后,运行 `pnpm docs:build` 或对修改的 Markdown/配置文件运行格式化检查。 +- 修改公开文档或 VitePress 配置后,运行 `pnpm docs:build`,并格式化修改文件;`docs/**/*.md` 被 Prettier 默认忽略,定向格式化时使用 `pnpm exec prettier --write --ignore-path .gitignore `。 - 修改跨平台共享逻辑后,确认 Electron 和 CLI Web 两端入口没有产生行为分歧。 - 修复会影响用户数据、业务逻辑、异步任务、缓存状态、跨端共享 service、公开 API 契约、导入解析、去重逻辑、权限认证、AI 工具 allowlist、配置/API key 迁移或数据库 schema/迁移的行为 bug 时,必须优先补能失败的回归测试。 - 只改 UI 文案、i18n key/翻译、样式、类型声明、日志、注释、无行为变化的小重构,或修复低风险展示细节时,可以不新增测试,但仍需运行相关类型检查、lint 和 format;不要为了低价值页面文案或源码字符串扫描新增脆弱测试。 @@ -163,6 +163,8 @@ CHATLAB_ALLOW_INCOMPATIBLE_DATA_DIR=1 AI 可以帮助阅读代码、生成补丁和补测试,但公开 PR 必须让 reviewer 能在公开上下文中理解。建议让 AI 先读 `AGENTS.md` 和本页;如果你维护了自己的 `.docs/`,可以把它作为额外上下文,但不要让变更说明、测试理由或设计依据只存在于私有 `.docs/` 中。 +维护代理指令、技能或审查流程时,参见 [AI 协作工作流](./ai-workflow.md)。日常任务按修改范围完成验证,不需要加载所有技能或反复执行全量检查。 + ## PR 与提交规范 - 明显的 Bug 修复可以直接提交 PR。 diff --git a/skills/chatlab-analyze-cn/SKILL.md b/skills/chatlab-analyze-cn/SKILL.md index 5f33fe734..01726994f 100644 --- a/skills/chatlab-analyze-cn/SKILL.md +++ b/skills/chatlab-analyze-cn/SKILL.md @@ -7,33 +7,20 @@ description: 通过 clb CLI 分析本地 ChatLab 聊天记录。当用户要求 通过只读 `clb` CLI 查询和分析已经导入 ChatLab 的聊天记录。 -## 适用场景 - -- 查找某段对话,或确认谁最先提到某件事。 -- 总结近期话题,或分析与某个人的聊天关系。 -- 比较成员活跃度、关键词或回复规律。 - -如果要导入新的聊天导出文件,请使用 `chatlab-import-cn`。 - -安装命令: - -```bash -npx skills add ChatLab/ChatLab --skill chatlab-analyze-cn -g -``` +导入新文件时,可用则使用 `chatlab-import-cn`;否则参考 `clb import --help`,在写入前预览准确的导入计划。 ## 工作流程 ### 1. 准备查询 -检查 CLI、读取当前命令契约并列出会话: +每个任务读取一次当前命令契约;只有目标尚未确定时才列出会话: ```bash -clb --help clb manifest clb sessions list --format json ``` -只有一个相关会话时直接使用;多个会话或成员都可能匹配时,让用户从返回的候选项中选择。 +复用对话中已确定的会话、成员和时间范围。仅在候选项和上下文仍无法消除关键歧义时询问。CLI 不可用时说明缺少的能力;安装需要授权。 ### 2. 从专用命令开始 @@ -56,7 +43,7 @@ clb messages context --id 1021 --session --window 10 --format agent clb stats keywords --session --member --last 90d --top 20 --format json ``` -当 `meta.hasMore` 为 true 时,保持相同查询条件并使用 `--cursor ` 继续。通过数量和 Token 参数只获取回答问题所需的上下文。 +仍需证据且 `meta.hasMore` 为 true 时,保持相同查询条件并使用 `--cursor ` 继续。问题已回答即可停止;查询不完整时说明覆盖范围,不把单页当成全集。 ### 4. 最后才使用 SQL @@ -69,9 +56,9 @@ clb sql "SELECT COUNT(*) AS n FROM message" --session --format json ## 隐私与回答 -- 绝不使用 `--raw`、修改 ChatLab 数据、导入文件、更改配置或启动长期运行的服务。 +- 查询保持隐私预处理启用,不使用 `--raw` 或修改数据、配置。另有明确导入请求时按导入流程处理。 - 绝不泄露完整聊天记录。ChatLab 的安全输出会应用用户配置的隐私预处理。 -- 使用 `[#1021]`、`[#1021*]` 或 `[#1021-1024]` 等可用标记引用消息证据。 +- 用 `[#1021]`、`[#1021*]` 或 `[#1021-1024]` 引用证据;`messages context --id` 只接受单个 ID,合并范围只用于展示。 - 先直接回答,说明实际查询的会话和时间范围,再区分观察到的事实与解释。 - 分析关系时不要过度推断情绪意图。仅在修正方式明确时遵循 `error.hint`。 - 优先使用用户当前使用的中文变体回答,命令、参数、JSON 字段和证据标记保持原样。 diff --git a/skills/chatlab-analyze/SKILL.md b/skills/chatlab-analyze/SKILL.md index bed6bb562..394eaace7 100644 --- a/skills/chatlab-analyze/SKILL.md +++ b/skills/chatlab-analyze/SKILL.md @@ -7,33 +7,20 @@ description: Analyze local ChatLab chat records through the clb CLI. Use when th Query and analyze records already imported into ChatLab through the read-only `clb` CLI. -## Good Fit - -- Find conversations or determine who mentioned something first. -- Summarize recent topics or inspect a named relationship. -- Compare member activity, keywords, or response patterns. - -For importing a new chat export, use `chatlab-import` instead. - -Install this skill with: - -```bash -npx skills add ChatLab/ChatLab --skill chatlab-analyze -g -``` +For importing a new chat export, use `chatlab-import` when available; otherwise follow `clb import --help` and preview the exact import before writing. ## Workflow ### 1. Prepare the Query -Check the CLI, load its current command contract, and list sessions: +Load the current command contract once per task. List sessions only when the target is not already known: ```bash -clb --help clb manifest clb sessions list --format json ``` -Use the only relevant session. If multiple sessions or members match the request, ask the user to choose from the returned candidates. +Reuse the session, member, and time range already identified in the conversation. Ask only when the returned candidates and context cannot resolve a material ambiguity. If the CLI is unavailable, report the missing capability; installation requires authorization. ### 2. Start with a Dedicated Command @@ -56,7 +43,7 @@ clb messages context --id 1021 --session --window 10 --format agent clb stats keywords --session --member --last 90d --top 20 --format json ``` -When `meta.hasMore` is true, continue with `--cursor ` and the same query conditions. Use limits and token controls to retrieve only the context needed. +When more evidence is needed and `meta.hasMore` is true, continue with `--cursor ` and the same query conditions. Stop when the question is answered; disclose any partial coverage instead of treating a page as the complete dataset. ### 4. Use SQL Only as a Fallback @@ -69,8 +56,8 @@ clb sql "SELECT COUNT(*) AS n FROM message" --session --format json ## Privacy and Answers -- Never use `--raw`, modify ChatLab data, import files, change config, or start long-running services. +- Queries keep privacy preprocessing enabled; do not use `--raw` or mutate data/config. A separate request to import follows the import workflow. - Never reveal full chat dumps. ChatLab's safe output applies the user's privacy preprocessing. -- Cite available message evidence with markers such as `[#1021]`, `[#1021*]`, or `[#1021-1024]`. +- Cite available evidence with `[#1021]`, `[#1021*]`, or `[#1021-1024]`. Only individual IDs can be passed to `messages context --id`; merged ranges are display-only. - Start with the answer, name the queried session and time range, then separate observed facts from interpretation. - Avoid overclaiming emotional intent in relationship analysis. Follow `error.hint` only when the correction is clear. diff --git a/skills/chatlab-convert-cn/SKILL.md b/skills/chatlab-convert-cn/SKILL.md index 5aa2f98cd..c0cf7d4e5 100644 --- a/skills/chatlab-convert-cn/SKILL.md +++ b/skills/chatlab-convert-cn/SKILL.md @@ -1,21 +1,15 @@ --- name: chatlab-convert-cn -description: 将 ChatLab 暂不支持的本地聊天导出转换为经过验证的 ChatLab JSONL 或 JSON:分析源文件结构,编写并运行本地 Node.js 或 Python 转换脚本,并在导入前核对记录数量。当 ChatLab 无法识别 CSV、HTML、TXT、XML、SQLite/数据库、厂商自定义 JSON/JSONL、压缩包等聊天导出,或用户要求 Agent 适配未知聊天格式时使用。 +description: 编写可复用的本地脚本,将不受支持的聊天导出转换并验证为 ChatLab JSONL 或 JSON。用于未知格式适配,不用于聊天分析或直接导入已支持的文件。 --- # ChatLab 聊天转换 在不修改源文件的前提下转换暂不支持的聊天导出。优先交付可复用的转换脚本和经过验证的 JSONL,而不是一次性改写文件。 -安装命令: - -```bash -npx skills add ChatLab/ChatLab --skill chatlab-convert-cn -g -``` - ## 不可省略的规则 -- 始终以只读方式处理源文件。脚本、样本、解压文件和输出都写入独立的同级目录。 +- 始终以只读方式处理源文件。脚本、样本、解压文件和输出写入独立工作目录;用户指定输出位置时遵循该位置。 - 聊天数据只留在本机。不要上传到网络服务,也不要在 AI 可见的终端输出中打印消息正文。 - 先检查结构:字段名、列名、值类型、数量、时间戳形态和脱敏样本。诊断输出中的消息正文替换为类型和长度。 - 绝不静默跳过损坏或不支持的源记录。应携带源行号/索引失败,或保留为消息类型 `99` 并明确统计映射数量。 @@ -26,7 +20,7 @@ npx skills add ChatLab/ChatLab --skill chatlab-convert-cn -g ### 1. 确认源文件和 CLI -确认唯一、准确的源路径。存在多个候选文件时询问用户,不要猜测。每条命令都要给路径加引号。 +从请求和对话中确定源路径,仍有歧义时才询问。命令中的路径加引号;环境能力每个任务检查一次,任务内复用结果。 ```bash clb --help @@ -35,18 +29,11 @@ clb formats clb import "/absolute/path/to/source" --dry-run --json ``` -如果 dry-run 已经识别源文件,不要重复转换,改用 `chatlab-import-cn` 完成导入。 - -如果 `clb` 不存在,或已有版本没有 `validate` 命令: +如果 dry-run 已识别源文件,说明无需转换。只有用户要求导入时才继续,可用则使用 `chatlab-import-cn`,否则按下文预览和写入步骤执行。 -1. 告诉用户未检测到可用的 ChatLab CLI,但仍可使用本 Skill 内置的严格验证器继续转换; -2. 建议安装或更新 CLI,并在执行前征得用户同意:`npm install -g chatlab-cli@latest`; -3. 用户跳过安装、安装失败或当前无法联网时,不要阻断转换,改用 `scripts/validate-chatlab.mjs`; -4. 跳过 `clb formats` 和源文件 dry-run,并明确说明无法确认 ChatLab 是否已原生支持该源格式。 +`clb validate` 不可用时,直接使用 `scripts/validate-chatlab.mjs` 继续转换,不因推荐安装而暂停。内置验证器需要 Node.js 22.19 或更高版本;旧 CLI 可能仍支持 `formats` 和导入 dry-run,按实际可用能力执行。完全没有 CLI 时,说明无法检查原生格式支持和导入就绪状态。 -内置验证器需要 Node.js 22.19 或更高版本。如果 `node --version` 也不可用,可以继续编写转换器,但必须把结果标记为“尚未验证”,并引导用户安装 Node.js 和 `chatlab-cli`;未经同意不要自行安装。 - -不能用肉眼检查替代 CLI 或内置严格验证器。 +两个严格验证器均无法运行时,尽可能完成转换器并将产物标记为尚未验证;不能用肉眼检查替代严格验证。只有缺失的运行时或 CLI 阻塞用户要求的结果时,才请求安装授权。 ### 2. 在不暴露正文的前提下检查结构 @@ -64,7 +51,7 @@ clb import "/absolute/path/to/source" --dry-run --json ### 3. 确定字段映射 -编写转换器前,完整阅读 [references/chatlab-format.md](references/chatlab-format.md)。记录源字段到 ChatLab 字段的映射,包括: +编写转换器前,阅读 [references/chatlab-format.md](references/chatlab-format.md) 的字段契约与所选输出格式。记录源字段到 ChatLab 字段的映射,包括: - 会话边界以及群聊/私聊类型; - 稳定的成员身份和本人身份; @@ -80,11 +67,7 @@ clb import "/absolute/path/to/source" --dry-run --json 默认输出 JSONL。只有源文件较小、天然结构化,并且可以明确安全地放入内存时才使用 JSON。 -选择当前已安装且最简单的运行时: - -- JSON、JSONL、HTML 和 JavaScript 风格数据优先使用 Node.js; -- CSV、SQLite、XML、编码复杂的文本和表格数据优先使用 Python; -- Shell 只负责发现文件和组合命令。 +使用已安装且最适合当前任务的 Node.js 或 Python;不按源格式强制指定语言。 转换器必须: @@ -101,7 +84,7 @@ clb import "/absolute/path/to/source" --dry-run --json ### 5. 先证明小样本 -先对受限的本地样本或脚本的 sample 模式运行转换器,然后验证。有 CLI 时运行: +大文件或映射尚不确定时,先转换并验证受限样本;小文件可以直接全量转换验证。有 CLI 时运行: ```bash clb validate "/absolute/path/to/sample.jsonl" --json @@ -137,14 +120,6 @@ node "/absolute/path/to/chatlab-convert-cn/scripts/validate-chatlab.mjs" "/absol 没有 CLI 时只能报告“格式验证通过”,并提醒用户之后可以安装 CLI 完成 dry-run,或将结果文件拖入 ChatLab;不能声称已经通过导入验证。 -只有同时满足以下条件,才能认为转换结果已经通过完整导入验证: - -- 严格验证返回 `ok: true`; -- 导入 dry-run 返回 `ok: true`; -- 转换器输出消息数等于验证器消息数; -- 源消息数等于输出消息数加上用户明确接受的跳过数; -- 没有隐藏任何源解析错误。 - ### 7. 仅在用户要求时导入 如果用户只要求转换,在当前环境可完成的验证后停止。报告输出路径、脚本路径、会话数、成员数、消息数、跳过数、警告、映射限制和实际达到的验证级别,不要引用消息正文。 diff --git a/skills/chatlab-convert/SKILL.md b/skills/chatlab-convert/SKILL.md index 7eb2cb96a..673873f27 100644 --- a/skills/chatlab-convert/SKILL.md +++ b/skills/chatlab-convert/SKILL.md @@ -1,21 +1,15 @@ --- name: chatlab-convert -description: Convert unsupported local chat exports into validated ChatLab JSONL or JSON by inspecting their structure, writing and running a local Node.js or Python converter, and verifying record counts before import. Use when ChatLab cannot recognize a CSV, HTML, TXT, XML, SQLite/database, vendor-specific JSON/JSONL, archive, or other chat export format, or when a user asks an agent to adapt an unknown chat format for ChatLab. +description: Convert unsupported local chat exports into validated ChatLab JSONL or JSON with a reproducible local script. Use for unknown-format adaptation, not analysis or direct import of already supported files. --- # ChatLab Convert Convert an unsupported local export without modifying the source. Prefer a reproducible script and a validated JSONL result over a one-off rewritten file. -Install this skill with: - -```bash -npx skills add ChatLab/ChatLab --skill chatlab-convert -g -``` - ## Non-negotiable rules -- Keep every source file read-only. Write scripts, samples, extracted archives, and output to a separate sibling directory. +- Keep every source file read-only. Write scripts, samples, extracted archives, and output to a separate working directory, using the user's output location when specified. - Keep chat data local. Do not upload it, send it to a network service, or dump message bodies into model-visible terminal output. - Inspect structure first: keys, column names, value types, counts, timestamp shapes, and masked samples. Replace message text in diagnostic output with its type and length. - Never silently skip a malformed or unsupported source record. Fail with its source row/index, or preserve it as message type `99` and count the mapping explicitly. @@ -26,7 +20,7 @@ npx skills add ChatLab/ChatLab --skill chatlab-convert -g ### 1. Confirm the source and CLI -Resolve the exact source path. If several files could be the export, ask the user instead of guessing. Quote paths in every command. +Resolve the source from the request and conversation; ask only if it remains ambiguous. Quote paths in every command. Check available capabilities once, reusing results within the task. ```bash clb --help @@ -35,18 +29,11 @@ clb formats clb import "/absolute/path/to/source" --dry-run --json ``` -If the dry run recognizes the source, do not convert it. Use `chatlab-import` for the supported file. - -If `clb` is missing, or the installed version has no `validate` command: +If the dry run recognizes the source, report that conversion is unnecessary. Import only if requested, using `chatlab-import` when available or the preview/write steps below. -1. Tell the user that ChatLab CLI was not detected but conversion can continue with this Skill's bundled strict validator. -2. Recommend installing or updating the CLI, and obtain permission before running `npm install -g chatlab-cli@latest`. -3. If the user skips installation, installation fails, or the network is unavailable, continue with `scripts/validate-chatlab.mjs`. -4. Skip `clb formats` and the source dry run, and state that native ChatLab support for the source format could not be checked. +If `clb validate` is unavailable, continue with `scripts/validate-chatlab.mjs` without pausing conversion to propose installation. The bundled validator needs Node.js 22.19 or later. An older CLI may still support `formats` and import dry-run; use the capabilities it actually provides. If no CLI is available, state that native format support and import readiness could not be checked. -The bundled validator requires Node.js 22.19 or later. If `node --version` is also unavailable, the converter may still be written, but mark the result as “not yet validated” and guide the user to install Node.js and `chatlab-cli`. Do not install either without permission. - -Do not replace either the CLI validator or bundled strict validator with visual inspection. +If neither strict validator can run, finish the converter where possible and report the output as unvalidated. Do not replace strict validation with visual inspection. Request installation authorization only when a missing runtime or CLI blocks the requested outcome. ### 2. Inspect without exposing content @@ -64,7 +51,7 @@ For an archive, extract into the separate working directory and reject entries t ### 3. Define the mapping -Read [references/chatlab-format.md](references/chatlab-format.md) completely before writing the converter. Record the mapping from source fields to ChatLab fields, including: +Read the field contract and chosen output format in [references/chatlab-format.md](references/chatlab-format.md) before writing the converter. Record the mapping from source fields to ChatLab fields, including: - conversation boundaries and group/private type; - stable member identity and owner identity; @@ -80,11 +67,7 @@ One ChatLab file represents one conversation. If the source contains multiple co Prefer JSONL. Use JSON only for a small, naturally structured export where holding the full result in memory is clearly safe. -Choose the simplest installed runtime: - -- Node.js for JSON, JSONL, HTML, and JavaScript-shaped exports; -- Python for CSV, SQLite, XML, encoding-heavy text, and tabular data; -- shell for file discovery and command composition only. +Use the simplest suitable installed Node.js or Python runtime; the source format does not mandate a particular language. The converter must: @@ -101,7 +84,7 @@ Default `skipped` to zero. Mapping an unknown message to type `99` is preservati ### 5. Prove a small sample -Run the converter on a bounded local sample or sample mode first. With the CLI available, validate that output with: +For a large file or uncertain mapping, run a bounded local sample first. A small input can be converted and validated in full directly. With the CLI available, validate that output with: ```bash clb validate "/absolute/path/to/sample.jsonl" --json @@ -137,14 +120,6 @@ Keep the result levels distinct: Without the CLI, report only “format validated.” Tell the user they can install the CLI later to run the dry-run or drag the result into ChatLab. Do not claim import validation passed. -Require all of the following before calling the result fully import validated: - -- strict validation returns `ok: true`; -- the import dry run returns `ok: true`; -- converter output message count equals the validator message count; -- source message count equals output message count plus explicitly accepted skipped records; -- no source parse error was hidden. - ### 7. Import only when requested If the user asked only to convert, stop after all validation available in the current environment. Report output and script paths, conversations, members, messages, skipped records, warnings, mapping limitations, and the validation level actually reached without quoting messages. diff --git a/skills/chatlab-import-cn/SKILL.md b/skills/chatlab-import-cn/SKILL.md index a14b3cd6d..00630ab52 100644 --- a/skills/chatlab-import-cn/SKILL.md +++ b/skills/chatlab-import-cn/SKILL.md @@ -1,25 +1,13 @@ --- name: chatlab-import-cn -description: 通过 clb CLI 安全预览并将本地聊天导出文件导入 ChatLab。当用户要求外部 Agent 从本地 QQ、微信、Telegram、WhatsApp、LINE、Discord、Instagram、Google Chat、ChatLab JSON/JSONL 或其他受支持的聊天导出文件执行首次导入、重新导入或增量更新时使用。 +description: 通过 clb 预览并导入本地聊天导出文件,支持重新导入和增量更新。用于导入请求,不用于只读分析或未知格式转换。 --- # ChatLab 聊天导入 通过 `clb` CLI 导入本地聊天导出文件。始终先预览准确的写入计划。将用户明确提出的导入请求视为授权,预览成功后继续执行,不再请求二次确认。 -## 适用场景 - -- 首次导入本地聊天导出文件。 -- 重新导入更新后的文件,只补充新增消息。 -- 让外部 Agent 安全处理格式识别和会话匹配。 - -如果只需分析 ChatLab 中已有的聊天记录,请使用 `chatlab-analyze-cn`。 - -安装命令: - -```bash -npx skills add ChatLab/ChatLab --skill chatlab-import-cn -g -``` +分析已有记录时,可用则使用 `chatlab-analyze-cn`;源格式不受支持时,可用则使用 `chatlab-convert-cn`。受支持格式的导入不依赖这两个技能。 ## 工作流程 @@ -29,7 +17,7 @@ npx skills add ChatLab/ChatLab --skill chatlab-import-cn -g clb --help ``` -如果 CLI 未安装,告诉用户运行 `npm install -g chatlab-cli`。未经批准不要自行安装软件。存在多个候选文件时不要猜测,每条命令中的路径都要加引号。 +如果 CLI 未安装,告诉用户运行 `npm install -g chatlab-cli`。未经批准不要自行安装软件。复用用户指定的文件和目标,路径仍有歧义时才询问。每条命令中的路径都要加引号。 2. 只读预览导入计划: @@ -41,7 +29,7 @@ clb import "/absolute/path/to/chat-export.json" --dry-run --json 3. 只总结新建或更新模式、目标会话 ID、扫描消息数、新增消息数、重复消息数,以及匹配方式或新建原因。不要引用消息正文。 -4. 根据预览结果决定下一步: +4. 确认 `ok: true`,从 `data` 读取计划后决定: - 用户只要求预览或检查时,到此停止,不写入数据。 - `importMode` 为 `incremental` 且 `newMessageCount` 为 `0` 时,告诉用户会话已经是最新状态,然后停止。 @@ -63,5 +51,5 @@ clb import "/absolute/path/to/chat-export.json" --json - 绝不泄露完整聊天导出文件或消息正文。 - 绝不编造文件路径、解析格式或会话 ID。 - 仅在修正方式明确时遵循 `error.hint`。 -- `FILE_NOT_FOUND` 要求正确路径;`UNRECOGNIZED_FORMAT` 查看 `clb formats`;`IMPORT_IN_PROGRESS` 稍后重试;`INVALID_SESSION_ID` 要求有效 ID。 +- `FILE_NOT_FOUND`、`INVALID_SESSION_ID` 优先从上下文修正,无法确定时询问;`UNRECOGNIZED_FORMAT` 查看 `clb formats`;`IMPORT_IN_PROGRESS` 在现有导入完成后重试一次,仍阻塞则报告,不无限轮询。 - 优先使用用户当前使用的中文变体回答,命令、参数、JSON 字段和错误码保持原样。 diff --git a/skills/chatlab-import/SKILL.md b/skills/chatlab-import/SKILL.md index 1edfdf494..1bc4e5683 100644 --- a/skills/chatlab-import/SKILL.md +++ b/skills/chatlab-import/SKILL.md @@ -1,25 +1,13 @@ --- name: chatlab-import -description: Safely preview and import local chat export files into ChatLab through the clb CLI. Use when a user asks an external agent to import, re-import, or incrementally update ChatLab from a local QQ, WeChat, Telegram, WhatsApp, LINE, Discord, Instagram, Google Chat, ChatLab JSON/JSONL, or other supported chat export. +description: Preview and import local chat exports into ChatLab with clb, including re-imports and incremental updates. Use for import requests, not read-only analysis or unsupported-format conversion. --- # ChatLab Import Import local chat exports through the `clb` CLI. Always preview the exact write. Treat an explicit request to import as authorization to continue after a successful preview; do not ask for a second confirmation. -## Good Fit - -- Import a local chat export for the first time. -- Re-import a newer export and add only new messages. -- Let an external agent handle file detection and session matching safely. - -For read-only analysis of records already in ChatLab, use `chatlab-analyze` instead. - -Install this skill with: - -```bash -npx skills add ChatLab/ChatLab --skill chatlab-import -g -``` +For analysis of imported records, use `chatlab-analyze` when available. For an unsupported source, use `chatlab-convert` when available; neither skill is required for a supported import. ## Workflow @@ -29,7 +17,7 @@ npx skills add ChatLab/ChatLab --skill chatlab-import -g clb --help ``` -If the CLI is missing, tell the user to install it with `npm install -g chatlab-cli`. Do not install software without approval. Do not guess between multiple files, and quote the path in every command. +If the CLI is missing, tell the user to install it with `npm install -g chatlab-cli`. Do not install software without approval. Reuse the user's specified file and target. Ask only if the path remains ambiguous, and quote it in every command. 2. Preview the import without writing: @@ -41,7 +29,7 @@ If the user selected an existing session, include `--session-id ` in 3. Summarize only the plan: create or update mode, target session ID, scanned messages, new messages, duplicates, and match method or create reason. Never quote message bodies. -4. Decide from the preview: +4. Require `ok: true`, then read the plan from `data` and decide: - If the user asked only to preview or inspect, stop without writing. - If `importMode` is `incremental` and `newMessageCount` is `0`, report that the session is already up to date and stop. @@ -63,4 +51,4 @@ Report the resulting session ID, import mode, new-message count, and duplicate c - Never reveal a full chat export or message bodies. - Never invent a file path, parser format, or session ID. - Follow `error.hint` only when the correction is unambiguous. -- For `FILE_NOT_FOUND`, request the correct path. For `UNRECOGNIZED_FORMAT`, inspect `clb formats`. For `IMPORT_IN_PROGRESS`, retry later. For `INVALID_SESSION_ID`, request a valid ID. +- For `FILE_NOT_FOUND` or `INVALID_SESSION_ID`, resolve from available context or request the correct value. For `UNRECOGNIZED_FORMAT`, inspect `clb formats`. For `IMPORT_IN_PROGRESS`, retry once after the active import finishes; if still blocked, report it instead of polling indefinitely. From 59520a2e61dbf3070f7558fc5799d9afe82c1d8e Mon Sep 17 00:00:00 2001 From: digua Date: Sat, 5 Sep 2026 23:06:10 +0800 Subject: [PATCH 08/68] refactor(skills): unify multilingual skill entrypoints --- docs/cn/ai/external-agent.md | 18 ++- docs/cn/contributing/ai-workflow.md | 2 +- docs/cn/standard/ai-converter.md | 14 +- docs/cn/usage/how-to-import.md | 8 +- docs/en/ai/external-agent.md | 12 +- docs/en/standard/ai-converter.md | 6 +- docs/en/usage/how-to-import.md | 4 +- docs/tw/standard/ai-converter.md | 14 +- docs/tw/usage/how-to-import.md | 8 +- scripts/build-chatlab-skill-validator.mjs | 8 +- skills/chatlab-analyze-cn/SKILL.md | 64 -------- skills/chatlab-analyze-cn/agents/openai.yaml | 4 - skills/chatlab-analyze/SKILL.md | 5 +- skills/chatlab-analyze/agents/openai.yaml | 4 +- skills/chatlab-convert-cn/SKILL.md | 135 ---------------- skills/chatlab-convert-cn/agents/openai.yaml | 4 - .../references/chatlab-format.md | 144 ------------------ .../scripts/validate-chatlab.mjs | 5 - skills/chatlab-convert/SKILL.md | 5 +- skills/chatlab-convert/agents/openai.yaml | 4 +- .../scripts/validate-chatlab.mjs | 4 +- skills/chatlab-import-cn/SKILL.md | 55 ------- skills/chatlab-import-cn/agents/openai.yaml | 4 - skills/chatlab-import/SKILL.md | 5 +- skills/chatlab-import/agents/openai.yaml | 4 +- tests/chatlab-skill-validator.test.ts | 46 +++--- 26 files changed, 96 insertions(+), 490 deletions(-) delete mode 100644 skills/chatlab-analyze-cn/SKILL.md delete mode 100644 skills/chatlab-analyze-cn/agents/openai.yaml delete mode 100644 skills/chatlab-convert-cn/SKILL.md delete mode 100644 skills/chatlab-convert-cn/agents/openai.yaml delete mode 100644 skills/chatlab-convert-cn/references/chatlab-format.md delete mode 100644 skills/chatlab-convert-cn/scripts/validate-chatlab.mjs delete mode 100644 skills/chatlab-import-cn/SKILL.md delete mode 100644 skills/chatlab-import-cn/agents/openai.yaml diff --git a/docs/cn/ai/external-agent.md b/docs/cn/ai/external-agent.md index 628351632..10fc20b93 100644 --- a/docs/cn/ai/external-agent.md +++ b/docs/cn/ai/external-agent.md @@ -8,22 +8,24 @@ ChatLab 可以让 Codex、Claude Code、HermesAgent 等 AI Agent 在本地查询 ## 开始使用 -需要 Node.js 22.19 或更高版本,并已在 ChatLab 中导入聊天记录。安装 ChatLab CLI 和中文 Skill: +需要 Node.js 22.19 或更高版本,并已在 ChatLab 中导入聊天记录。安装 ChatLab CLI 和分析 Skill: ```bash npm install -g chatlab-cli -npx skills add ChatLab/ChatLab --skill chatlab-analyze-cn -g +npx skills add ChatLab/ChatLab --skill chatlab-analyze -g ``` +各语言共用同一个技能,可以直接用中文提出需求。曾安装 `chatlab-analyze-cn` 时,请先保留自定义改动,再安装上面的统一版本,并通过原安装工具移除旧入口,避免重复显示。 + 安装后,可以在 Codex、Claude Code、Cursor 等外部 Agent 中直接说: ```text -chatlab-analyze-cn 帮我分析我和小红的聊天记录 +chatlab-analyze 帮我分析我和小红的聊天记录 ``` 这个技能会引导 Agent 先执行 `clb manifest` 获取机读命令清单,再用安全的 `--format agent/json` 查询聊天记录。 -`chatlab-analyze-cn` 始终只读。如果你希望 Agent 导入新的聊天导出文件,请改用独立的 `chatlab-import-cn` Skill;它会先预览导入计划,再自动新建或增量导入,详见[导入聊天记录指南](/cn/usage/how-to-import)。 +`chatlab-analyze` 始终只读。如果你希望 Agent 导入新的聊天导出文件,请改用独立的 `chatlab-import` Skill;它会先预览导入计划,再自动新建或增量导入,详见[导入聊天记录指南](/cn/usage/how-to-import)。 ## 命令总览 @@ -65,7 +67,13 @@ agent/json 模式下 stdout 只包含一个 JSON 响应信封,日志一律走 "ok": true, "command": "messages.search", "data": { "text": "returned: 2\n\n--- 2026/6/1 ---\n[#1*] 09:00 老王: 今年五一去旅游吧..." }, - "meta": { "totalHits": 2, "returnedHits": 2, "hasMore": false, "preprocess": { "desensitized": true }, "apiVersion": 1 } + "meta": { + "totalHits": 2, + "returnedHits": 2, + "hasMore": false, + "preprocess": { "desensitized": true }, + "apiVersion": 1 + } } ``` diff --git a/docs/cn/contributing/ai-workflow.md b/docs/cn/contributing/ai-workflow.md index 3d4b9a792..8434c0ce8 100644 --- a/docs/cn/contributing/ai-workflow.md +++ b/docs/cn/contributing/ai-workflow.md @@ -13,7 +13,7 @@ title: AI 协作工作流 | --- | --- | --- | | `AGENTS.md` | 跨任务稳定约定、项目入口、验证命令 | 保持精简,只保留会影响决策的信息 | | `docs/cn/contributing/` | 公开开发细则 | 按任务引用,公开 PR 可独立理解 | -| `skills/*/SKILL.md` | 供外部 Agent 使用的分析、导入、转换流程 | 每个技能一个职责,中英文独立分发并保持语义一致 | +| `skills/*/SKILL.md` | 供外部 Agent 使用的分析、导入、转换流程 | 每个技能一个统一名称,使用简短双语描述与英文正文;回答遵循用户语言 | | `skills/*/references/`、`scripts/` | 格式契约与严格验证器 | 按需读取;确定性校验交给脚本 | | `.github/workflows/` | CI、构建、发布及产物清理 | 保留真实质量门禁,不承载模型提示词 | | 可选的本地规则和私有技能 | 个人协作偏好与维护任务 | 不覆盖用户当前意图,不作为公开贡献前提 | diff --git a/docs/cn/standard/ai-converter.md b/docs/cn/standard/ai-converter.md index faf956931..7e7087b98 100644 --- a/docs/cn/standard/ai-converter.md +++ b/docs/cn/standard/ai-converter.md @@ -16,24 +16,26 @@ outline: deep npm install -g chatlab-cli ``` -再在支持 Skills 的 Agent 环境中安装中文版转换 Skill: +再在支持 Skills 的 Agent 环境中安装转换 Skill: ```bash -npx skills add ChatLab/ChatLab --skill chatlab-convert-cn -g +npx skills add ChatLab/ChatLab --skill chatlab-convert -g ``` +各语言共用同一个技能,可以直接用中文提出需求。曾安装 `chatlab-convert-cn` 时,请先保留自定义改动,再安装上面的统一版本,并通过原安装工具移除旧入口,避免重复显示。 + ## 开始转换 把聊天导出的准确路径告诉 Agent: ```text -使用 $chatlab-convert-cn,把 "/你的/聊天记录路径" 转换并导入 ChatLab。 +使用 $chatlab-convert,把 "/你的/聊天记录路径" 转换并导入 ChatLab。 ``` 如果只希望生成转换结果,不立即导入: ```text -使用 $chatlab-convert-cn,把 "/你的/聊天记录路径" 转换为 ChatLab 格式,只转换,不导入。 +使用 $chatlab-convert,把 "/你的/聊天记录路径" 转换为 ChatLab 格式,只转换,不导入。 ``` Skill 会自动选择电脑上已有的 Node.js、Python 或 Shell 工具,完成以下流程: @@ -59,6 +61,4 @@ clb import "/转换后的文件.jsonl" --dry-run --json 只有严格验证和导入预览都成功,并且源消息数与输出消息数核对一致,才会报告转换成功。转换脚本和输出文件都会保留,方便以后重新转换更新后的聊天记录。 -::: warning 隐私提醒 -请使用能够访问本机文件和终端的 Agent 执行转换,不要把完整聊天文件上传到在线对话框。即使需要排查特殊文本格式,也应只检查最小范围并隐藏消息正文。 -::: +::: warning 隐私提醒请使用能够访问本机文件和终端的 Agent 执行转换,不要把完整聊天文件上传到在线对话框。即使需要排查特殊文本格式,也应只检查最小范围并隐藏消息正文。::: diff --git a/docs/cn/usage/how-to-import.md b/docs/cn/usage/how-to-import.md index 90d7699f5..8c759a7fb 100644 --- a/docs/cn/usage/how-to-import.md +++ b/docs/cn/usage/how-to-import.md @@ -19,17 +19,19 @@ ChatLab 支持多种方式导入聊天记录。 适合已经使用 Codex、Claude Code 等各类 AI Agent,希望让 Agent 代为执行导入的用户。 -需要 Node.js 22.19 或更高版本。安装 ChatLab CLI 和中文 Skill: +需要 Node.js 22.19 或更高版本。安装 ChatLab CLI 和导入 Skill: ```bash npm install -g chatlab-cli -npx skills add ChatLab/ChatLab --skill chatlab-import-cn -g +npx skills add ChatLab/ChatLab --skill chatlab-import -g ``` +各语言共用同一个技能,可以直接用中文提出需求。曾安装 `chatlab-import-cn` 时,请先保留自定义改动,再安装上面的统一版本,并通过原安装工具移除旧入口,避免重复显示。 + 然后直接告诉 Agent: ```text -chatlab-import-cn 帮我把 /absolute/path/to/chat-export.json 导入 ChatLab +chatlab-import 帮我把 /absolute/path/to/chat-export.json 导入 ChatLab ``` Agent 会先在后台预览,预览成功后自动新建或增量导入,无需再次确认。 diff --git a/docs/en/ai/external-agent.md b/docs/en/ai/external-agent.md index 84323cb79..5e94a6095 100644 --- a/docs/en/ai/external-agent.md +++ b/docs/en/ai/external-agent.md @@ -8,13 +8,15 @@ ChatLab lets AI agents such as Codex, Claude Code, and HermesAgent query and ana ## Get started -Node.js 22.19 or newer is required, and chat records must already be imported into ChatLab. Install ChatLab CLI and the English Skill: +Node.js 22.19 or newer is required, and chat records must already be imported into ChatLab. Install ChatLab CLI and the analysis skill: ```bash npm install -g chatlab-cli npx skills add ChatLab/ChatLab --skill chatlab-analyze -g ``` +The same skill works across languages; ask in your preferred language. + After installation, ask Codex, Claude Code, Cursor, or another external agent: ```text @@ -65,7 +67,13 @@ In agent/json mode stdout contains exactly one JSON envelope; logs go to stderr: "ok": true, "command": "messages.search", "data": { "text": "returned: 2\n\n--- 2026/6/1 ---\n[#1*] 09:00 Wang: how about a trip on May Day..." }, - "meta": { "totalHits": 2, "returnedHits": 2, "hasMore": false, "preprocess": { "desensitized": true }, "apiVersion": 1 } + "meta": { + "totalHits": 2, + "returnedHits": 2, + "hasMore": false, + "preprocess": { "desensitized": true }, + "apiVersion": 1 + } } ``` diff --git a/docs/en/standard/ai-converter.md b/docs/en/standard/ai-converter.md index ff294dd9c..f09a158f6 100644 --- a/docs/en/standard/ai-converter.md +++ b/docs/en/standard/ai-converter.md @@ -22,6 +22,8 @@ Install the conversion skill in a skill-compatible agent environment: npx skills add ChatLab/ChatLab --skill chatlab-convert -g ``` +The same skill works across languages; ask in your preferred language. + ## Convert an export Give the agent the exact export path: @@ -59,6 +61,4 @@ clb import "/absolute/path/to/converted.jsonl" --dry-run --json Success requires strict validation, a successful import preview, and matching source/output message counts. The converter and outputs remain available so a newer export can be converted again. -::: warning Privacy -Use an agent that can work with local files and a local terminal. Do not upload a complete chat export to an online chat box. When an unusual text format requires sampling, inspect the smallest possible range and mask message bodies. -::: +::: warning Privacy Use an agent that can work with local files and a local terminal. Do not upload a complete chat export to an online chat box. When an unusual text format requires sampling, inspect the smallest possible range and mask message bodies. ::: diff --git a/docs/en/usage/how-to-import.md b/docs/en/usage/how-to-import.md index 3bd707c8e..14c45152a 100644 --- a/docs/en/usage/how-to-import.md +++ b/docs/en/usage/how-to-import.md @@ -19,13 +19,15 @@ The homepage also supports incremental imports. When a new file matches an impor Use this when you already work with Codex, Claude Code, or another AI agent and want the agent to handle the import for you. -Node.js 22.19 or newer is required. Install ChatLab CLI and the English skill: +Node.js 22.19 or newer is required. Install ChatLab CLI and the import skill: ```bash npm install -g chatlab-cli npx skills add ChatLab/ChatLab --skill chatlab-import -g ``` +The same skill works across languages; ask in your preferred language. + Then ask the agent: ```text diff --git a/docs/tw/standard/ai-converter.md b/docs/tw/standard/ai-converter.md index 45a37f502..10b38f554 100644 --- a/docs/tw/standard/ai-converter.md +++ b/docs/tw/standard/ai-converter.md @@ -16,24 +16,26 @@ outline: deep npm install -g chatlab-cli ``` -再於支援 Skills 的 Agent 環境安裝中文版轉換 Skill: +再於支援 Skills 的 Agent 環境安裝轉換 Skill: ```bash -npx skills add ChatLab/ChatLab --skill chatlab-convert-cn -g +npx skills add ChatLab/ChatLab --skill chatlab-convert -g ``` +各語言共用同一個技能,可以直接用繁體中文提出需求。曾安裝 `chatlab-convert-cn` 時,請先保留自訂修改,再安裝上面的統一版本,並透過原安裝工具移除舊入口,避免重複顯示。 + ## 開始轉換 把聊天匯出的準確路徑告訴 Agent: ```text -使用 $chatlab-convert-cn,把「/你的/聊天記錄路徑」轉換並匯入 ChatLab。 +使用 $chatlab-convert,把「/你的/聊天記錄路徑」轉換並匯入 ChatLab。 ``` 如果只希望產生轉換結果,不立即匯入: ```text -使用 $chatlab-convert-cn,把「/你的/聊天記錄路徑」轉換為 ChatLab 格式,只轉換,不匯入。 +使用 $chatlab-convert,把「/你的/聊天記錄路徑」轉換為 ChatLab 格式,只轉換,不匯入。 ``` Skill 會自動選擇電腦上已有的 Node.js、Python 或 Shell 工具,完成以下流程: @@ -59,6 +61,4 @@ clb import "/轉換後的檔案.jsonl" --dry-run --json 只有嚴格驗證和匯入預覽都成功,而且來源訊息數與輸出訊息數核對一致,才會回報轉換成功。轉換腳本和輸出檔案都會保留,方便之後重新轉換更新後的聊天記錄。 -::: warning 隱私提醒 -請使用能存取本機檔案和終端的 Agent 執行轉換,不要把完整聊天檔案上傳到線上對話框。即使需要排查特殊文字格式,也應只檢查最小範圍並隱藏訊息正文。 -::: +::: warning 隱私提醒請使用能存取本機檔案和終端的 Agent 執行轉換,不要把完整聊天檔案上傳到線上對話框。即使需要排查特殊文字格式,也應只檢查最小範圍並隱藏訊息正文。::: diff --git a/docs/tw/usage/how-to-import.md b/docs/tw/usage/how-to-import.md index 3c5f27200..972c03155 100644 --- a/docs/tw/usage/how-to-import.md +++ b/docs/tw/usage/how-to-import.md @@ -19,17 +19,19 @@ ChatLab 支援多種方式匯入聊天記錄。 適合已經使用 Codex、Claude Code 等各類 AI Agent,希望讓 Agent 代為執行匯入的使用者。 -需要 Node.js 22.19 或更高版本。安裝 ChatLab CLI 和中文 Skill: +需要 Node.js 22.19 或更高版本。安裝 ChatLab CLI 和匯入 Skill: ```bash npm install -g chatlab-cli -npx skills add ChatLab/ChatLab --skill chatlab-import-cn -g +npx skills add ChatLab/ChatLab --skill chatlab-import -g ``` +各語言共用同一個技能,可以直接用繁體中文提出需求。曾安裝 `chatlab-import-cn` 時,請先保留自訂修改,再安裝上面的統一版本,並透過原安裝工具移除舊入口,避免重複顯示。 + 然後直接告訴 Agent: ```text -chatlab-import-cn 幫我把 /absolute/path/to/chat-export.json 匯入 ChatLab +chatlab-import 幫我把 /absolute/path/to/chat-export.json 匯入 ChatLab ``` Agent 會先在背景預覽,預覽成功後自動建立或增量匯入,無需再次確認。 diff --git a/scripts/build-chatlab-skill-validator.mjs b/scripts/build-chatlab-skill-validator.mjs index 1aa00b05b..50b766e2f 100644 --- a/scripts/build-chatlab-skill-validator.mjs +++ b/scripts/build-chatlab-skill-validator.mjs @@ -105,11 +105,9 @@ try { { cwd: rootDir, stdio: 'inherit' } ) - for (const skillName of ['chatlab-convert', 'chatlab-convert-cn']) { - const scriptsDir = path.join(rootDir, 'skills', skillName, 'scripts') - mkdirSync(scriptsDir, { recursive: true }) - copyFileSync(outputPath, path.join(scriptsDir, 'validate-chatlab.mjs')) - } + const scriptsDir = path.join(rootDir, 'skills', 'chatlab-convert', 'scripts') + mkdirSync(scriptsDir, { recursive: true }) + copyFileSync(outputPath, path.join(scriptsDir, 'validate-chatlab.mjs')) } finally { rmSync(tempDir, { recursive: true, force: true }) } diff --git a/skills/chatlab-analyze-cn/SKILL.md b/skills/chatlab-analyze-cn/SKILL.md deleted file mode 100644 index 01726994f..000000000 --- a/skills/chatlab-analyze-cn/SKILL.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -name: chatlab-analyze-cn -description: 通过 clb CLI 分析本地 ChatLab 聊天记录。当用户要求外部 Agent 检查对话、查找证据、总结话题、比较成员,或基于已导入的 ChatLab 数据分析指定人物或群组关系时使用。 ---- - -# ChatLab 聊天分析 - -通过只读 `clb` CLI 查询和分析已经导入 ChatLab 的聊天记录。 - -导入新文件时,可用则使用 `chatlab-import-cn`;否则参考 `clb import --help`,在写入前预览准确的导入计划。 - -## 工作流程 - -### 1. 准备查询 - -每个任务读取一次当前命令契约;只有目标尚未确定时才列出会话: - -```bash -clb manifest -clb sessions list --format json -``` - -复用对话中已确定的会话、成员和时间范围。仅在候选项和上下文仍无法消除关键歧义时询问。CLI 不可用时说明缺少的能力;安装需要授权。 - -### 2. 从专用命令开始 - -先使用能直接回答问题的最简单命令: - -```bash -clb messages search "" --session --format agent -clb messages between --member me --member --session --last 90d --format agent -clb topics list --session --last 30d --format agent -``` - -读取消息文本时使用 `--format agent`;侦察会话、成员、数量和 `--no-content` 搜索等结构时使用 `--format json`。 - -### 3. 补充上下文或统计 - -只有第一步结果不足时再深入: - -```bash -clb messages context --id 1021 --session --window 10 --format agent -clb stats keywords --session --member --last 90d --top 20 --format json -``` - -仍需证据且 `meta.hasMore` 为 true 时,保持相同查询条件并使用 `--cursor ` 继续。问题已回答即可停止;查询不完整时说明覆盖范围,不把单页当成全集。 - -### 4. 最后才使用 SQL - -只有专用命令无法回答时,才使用只读 SQL: - -```bash -clb schema --session --format json -clb sql "SELECT COUNT(*) AS n FROM message" --session --format json -``` - -## 隐私与回答 - -- 查询保持隐私预处理启用,不使用 `--raw` 或修改数据、配置。另有明确导入请求时按导入流程处理。 -- 绝不泄露完整聊天记录。ChatLab 的安全输出会应用用户配置的隐私预处理。 -- 用 `[#1021]`、`[#1021*]` 或 `[#1021-1024]` 引用证据;`messages context --id` 只接受单个 ID,合并范围只用于展示。 -- 先直接回答,说明实际查询的会话和时间范围,再区分观察到的事实与解释。 -- 分析关系时不要过度推断情绪意图。仅在修正方式明确时遵循 `error.hint`。 -- 优先使用用户当前使用的中文变体回答,命令、参数、JSON 字段和证据标记保持原样。 diff --git a/skills/chatlab-analyze-cn/agents/openai.yaml b/skills/chatlab-analyze-cn/agents/openai.yaml deleted file mode 100644 index cce7b004e..000000000 --- a/skills/chatlab-analyze-cn/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: 'ChatLab 聊天分析' - short_description: '通过 ChatLab CLI 安全查询并分析本地聊天记录' - default_prompt: '使用 $chatlab-analyze-cn 分析我在 ChatLab 中的本地聊天记录。' diff --git a/skills/chatlab-analyze/SKILL.md b/skills/chatlab-analyze/SKILL.md index 394eaace7..6d64eb0d6 100644 --- a/skills/chatlab-analyze/SKILL.md +++ b/skills/chatlab-analyze/SKILL.md @@ -1,12 +1,15 @@ --- name: chatlab-analyze -description: Analyze local ChatLab chat records through the clb CLI. Use when the user asks an external agent to inspect conversations, find evidence, summarize topics, compare members, or analyze a named relationship from imported ChatLab data. +description: >- + Query and analyze imported ChatLab chats with clb: find evidence, summarize topics, and compare activity or relationships. 查询和分析 ChatLab 已导入聊天记录:查找证据、总结话题、比较活跃度或关系。 --- # ChatLab Analyze Query and analyze records already imported into ChatLab through the read-only `clb` CLI. +Follow the user's requested response language; otherwise use the conversation language, preserving the user's Chinese variant when applicable. Keep commands, field names, IDs, and evidence markers unchanged. + For importing a new chat export, use `chatlab-import` when available; otherwise follow `clb import --help` and preview the exact import before writing. ## Workflow diff --git a/skills/chatlab-analyze/agents/openai.yaml b/skills/chatlab-analyze/agents/openai.yaml index 1bd7f9f31..f74d8be8a 100644 --- a/skills/chatlab-analyze/agents/openai.yaml +++ b/skills/chatlab-analyze/agents/openai.yaml @@ -1,4 +1,4 @@ interface: - display_name: 'ChatLab Analyze' - short_description: 'Safely query and analyze local chats with ChatLab CLI' + display_name: 'ChatLab Analyze · 聊天分析' + short_description: 'Query and analyze local chats · 查询和分析本地聊天记录' default_prompt: 'Use $chatlab-analyze to analyze my local ChatLab chat records.' diff --git a/skills/chatlab-convert-cn/SKILL.md b/skills/chatlab-convert-cn/SKILL.md deleted file mode 100644 index c0cf7d4e5..000000000 --- a/skills/chatlab-convert-cn/SKILL.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -name: chatlab-convert-cn -description: 编写可复用的本地脚本,将不受支持的聊天导出转换并验证为 ChatLab JSONL 或 JSON。用于未知格式适配,不用于聊天分析或直接导入已支持的文件。 ---- - -# ChatLab 聊天转换 - -在不修改源文件的前提下转换暂不支持的聊天导出。优先交付可复用的转换脚本和经过验证的 JSONL,而不是一次性改写文件。 - -## 不可省略的规则 - -- 始终以只读方式处理源文件。脚本、样本、解压文件和输出写入独立工作目录;用户指定输出位置时遵循该位置。 -- 聊天数据只留在本机。不要上传到网络服务,也不要在 AI 可见的终端输出中打印消息正文。 -- 先检查结构:字段名、列名、值类型、数量、时间戳形态和脱敏样本。诊断输出中的消息正文替换为类型和长度。 -- 绝不静默跳过损坏或不支持的源记录。应携带源行号/索引失败,或保留为消息类型 `99` 并明确统计映射数量。 -- 未经允许不要安装依赖或系统工具。优先使用已有 Node.js/Python 标准库;Shell 只用于检查和编排。 -- 用户没有明确要求导入时,不要写入 ChatLab。用户明确说“转换并导入”,则所有门禁通过后无需二次确认。 - -## 工作流程 - -### 1. 确认源文件和 CLI - -从请求和对话中确定源路径,仍有歧义时才询问。命令中的路径加引号;环境能力每个任务检查一次,任务内复用结果。 - -```bash -clb --help -clb validate --help -clb formats -clb import "/absolute/path/to/source" --dry-run --json -``` - -如果 dry-run 已识别源文件,说明无需转换。只有用户要求导入时才继续,可用则使用 `chatlab-import-cn`,否则按下文预览和写入步骤执行。 - -`clb validate` 不可用时,直接使用 `scripts/validate-chatlab.mjs` 继续转换,不因推荐安装而暂停。内置验证器需要 Node.js 22.19 或更高版本;旧 CLI 可能仍支持 `formats` 和导入 dry-run,按实际可用能力执行。完全没有 CLI 时,说明无法检查原生格式支持和导入就绪状态。 - -两个严格验证器均无法运行时,尽可能完成转换器并将产物标记为尚未验证;不能用肉眼检查替代严格验证。只有缺失的运行时或 CLI 阻塞用户要求的结果时,才请求安装授权。 - -### 2. 在不暴露正文的前提下检查结构 - -先检查文件元信息、编码、压缩包目录、JSON 字段、CSV 表头、XML/HTML 元素名或数据库表结构。不要运行会打印原始消息列的 `cat`、无限制 `head` 或 SQL 查询。 - -确实需要样本值时,编写一个简短的本地检查脚本,只输出: - -- 字段名和数据类型; -- 记录数和空值数; -- 时间戳与标识符形态; -- `` 这样的正文掩码; -- 少量结构不同的记录。 - -压缩包要解压到独立工作目录,并拒绝逃逸该目录的条目。数据库必须只读打开。 - -### 3. 确定字段映射 - -编写转换器前,阅读 [references/chatlab-format.md](references/chatlab-format.md) 的字段契约与所选输出格式。记录源字段到 ChatLab 字段的映射,包括: - -- 会话边界以及群聊/私聊类型; -- 稳定的成员身份和本人身份; -- 时间戳单位与时区; -- 消息类型、正文、原始消息 ID 和回复关系; -- 源记录总数,以及无法完全表达的记录。 - -只有当歧义会改变身份、会话边界、时间含义或本人归属时才询问用户;这些字段不能猜测。 - -一个 ChatLab 文件只能表示一个会话。如果源文件包含多个会话,要使用防冲突且确定性的文件名分别输出,绝不能误合并。 - -### 4. 编写确定性的转换器 - -默认输出 JSONL。只有源文件较小、天然结构化,并且可以明确安全地放入内存时才使用 JSON。 - -使用已安装且最适合当前任务的 Node.js 或 Python;不按源格式强制指定语言。 - -转换器必须: - -- 通过参数接收输入、输出路径,不写死本机路径; -- 保持源文件不变,并拒绝覆盖源文件; -- 先写临时输出,成功后再重命名; -- 保留源顺序,或按时间戳加稳定的源序号排序; -- 优先保留源 ID;没有消息 ID 时省略可选字段,只有回复关联需要时才生成确定性 ID; -- 缺少成员 ID 时根据稳定身份字段确定性生成,绝不能使用随机值; -- 大文件流式处理,进度输出不得包含消息正文; -- 解析失败时以非零状态退出,并输出源记录数、输出数和跳过数汇总。 - -`skipped` 默认为零。把未知消息映射为类型 `99` 属于保留,不计为跳过。 - -### 5. 先证明小样本 - -大文件或映射尚不确定时,先转换并验证受限样本;小文件可以直接全量转换验证。有 CLI 时运行: - -```bash -clb validate "/absolute/path/to/sample.jsonl" --json -``` - -没有 CLI 时,定位本 `SKILL.md` 所在目录,并使用随 Skill 分发的验证器: - -```bash -node "/absolute/path/to/chatlab-convert-cn/scripts/validate-chatlab.mjs" "/absolute/path/to/sample.jsonl" -``` - -修复全部 error 后才能全量转换。逐项检查 warning 并说明为什么可以接受,不能自动忽略。 - -### 6. 全量转换并双重验证 - -运行完整转换,然后使用 CLI 或内置验证器验证每一个输出文件。有 CLI 时继续执行导入 dry-run: - -```bash -clb validate "/absolute/path/to/converted.jsonl" --json -clb import "/absolute/path/to/converted.jsonl" --dry-run --json -``` - -没有 CLI 时运行: - -```bash -node "/absolute/path/to/chatlab-convert-cn/scripts/validate-chatlab.mjs" "/absolute/path/to/converted.jsonl" -``` - -按验证能力区分结果,不能混用结论: - -- **格式验证通过**:CLI 或内置严格验证器返回 `ok: true`,转换器输出消息数等于验证器消息数,源消息数等于输出消息数加上用户明确接受的跳过数,且没有隐藏源解析错误; -- **导入验证通过**:在格式验证通过的基础上,`clb import --dry-run --json` 也返回 `ok: true`。 - -没有 CLI 时只能报告“格式验证通过”,并提醒用户之后可以安装 CLI 完成 dry-run,或将结果文件拖入 ChatLab;不能声称已经通过导入验证。 - -### 7. 仅在用户要求时导入 - -如果用户只要求转换,在当前环境可完成的验证后停止。报告输出路径、脚本路径、会话数、成员数、消息数、跳过数、警告、映射限制和实际达到的验证级别,不要引用消息正文。 - -如果用户明确要求导入,使用同一个已验证文件移除 `--dry-run`: - -```bash -clb import "/absolute/path/to/converted.jsonl" --json -``` - -如果此时仍没有 CLI,说明自动导入需要 `chatlab-cli`,建议安装并征得用户同意;不能把“格式验证通过”当成已经导入。 - -存在多个输出时,逐个预览并独立导入。报告每个结果的会话 ID 和数量;不要删除转换脚本或转换结果。 diff --git a/skills/chatlab-convert-cn/agents/openai.yaml b/skills/chatlab-convert-cn/agents/openai.yaml deleted file mode 100644 index 52bbfa834..000000000 --- a/skills/chatlab-convert-cn/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: 'ChatLab 聊天转换' - short_description: '将暂不支持的聊天记录转换并验证为 ChatLab 标准格式' - default_prompt: '使用 $chatlab-convert-cn 将这个不受支持的聊天导出转换为经过验证的 ChatLab JSONL 文件。' diff --git a/skills/chatlab-convert-cn/references/chatlab-format.md b/skills/chatlab-convert-cn/references/chatlab-format.md deleted file mode 100644 index 6f4290f9d..000000000 --- a/skills/chatlab-convert-cn/references/chatlab-format.md +++ /dev/null @@ -1,144 +0,0 @@ -# ChatLab 转换协议 - -生成 ChatLab Format `0.0.2` 时遵循本协议。这里只保留转换器需要的内容;生成文件以 ChatLab CLI 或本 Skill 内置严格验证器的结果为准: - -```bash -clb validate "/absolute/path/to/output.jsonl" --json -node "/absolute/path/to/chatlab-convert-cn/scripts/validate-chatlab.mjs" "/absolute/path/to/output.jsonl" -``` - -两条命令按环境选择其一,不需要重复运行;内置验证器只验证格式,不代替 `clb import --dry-run --json` 的导入验证。 - -## 核心约束 - -- 一个输出文件只表示一个会话。 -- 使用 UTF-8。时间统一使用整数型 Unix **秒级**时间戳,绝不能使用毫秒。 -- `meta.type` 只能是 `group` 或 `private`。平台使用稳定的小写标识,例如 `qq`、`weixin` 或源产品名称。 -- `sender` 和 `ownerId` 引用成员的 `platformId`。 -- 保留每一条源记录。无法识别的消息映射为类型 `99`,不能丢弃。 -- 有原始成员 ID、消息 ID 时原样保留;即使源数据是数字,输出 ID 也使用字符串。 -- `platformMessageId` 可省略,但出现时必须唯一;`replyToMessageId` 引用其中一个消息 ID。 -- 消息按 `timestamp` 升序排列;时间相同时保留稳定的源序号。 -- `accountName` 表示账号名称,`groupNickname` 表示该会话中的昵称。 - -## 推荐的 JSONL 结构 - -每行写一个紧凑 JSON 对象。第一个非注释数据行是唯一的 `header`,随后写全部 `member`,最后写 `message`。 - -```jsonl -{"_type":"header","chatlab":{"version":"0.0.2","exportedAt":1711468800,"generator":"local-converter"},"meta":{"name":"示例群聊","platform":"example","type":"group","groupId":"group-1","ownerId":"member-1"}} -{"_type":"member","platformId":"member-1","accountName":"小红","groupNickname":"小红","aliases":["红红"],"roles":[{"id":"owner"}]} -{"_type":"member","platformId":"member-2","accountName":"小明"} -{"_type":"message","platformMessageId":"message-1","sender":"member-1","accountName":"小红","groupNickname":"小红","timestamp":1711468800,"type":0,"content":"你好"} -{"_type":"message","platformMessageId":"message-2","replyToMessageId":"message-1","sender":"member-2","accountName":"小明","timestamp":1711468810,"type":25,"content":"收到"} -``` - -允许空行和以 `#` 开头的注释行,但转换器通常不应生成它们。只有源数据完全没有成员信息时才省略成员行;只要写了成员行,就必须在第一条消息前写完所有发送者。 - -## JSON 结构 - -只有完整结果可以安全地放入内存时才使用 JSON。`members` 和 `messages` 都是必需数组。 - -```json -{ - "chatlab": { - "version": "0.0.2", - "exportedAt": 1711468800, - "generator": "local-converter" - }, - "meta": { - "name": "示例私聊", - "platform": "example", - "type": "private", - "ownerId": "member-1" - }, - "members": [ - { "platformId": "member-1", "accountName": "小红" }, - { "platformId": "member-2", "accountName": "小明" } - ], - "messages": [ - { - "sender": "member-1", - "accountName": "小红", - "timestamp": 1711468800, - "type": 0, - "content": "你好" - } - ] -} -``` - -## 字段 - -### 文件头 - -| 路径 | 必需 | 约束 | -| --------------------- | ---- | ---------------------- | -| `chatlab.version` | 是 | 固定为 `0.0.2` | -| `chatlab.exportedAt` | 是 | 整数型 Unix 秒级时间戳 | -| `chatlab.generator` | 否 | 转换器名称 | -| `chatlab.description` | 否 | 不含隐私的简短说明 | -| `meta.name` | 是 | 会话显示名称 | -| `meta.platform` | 是 | 稳定的平台标识 | -| `meta.type` | 是 | `group` 或 `private` | -| `meta.groupId` | 否 | 原始群组/会话 ID | -| `meta.groupAvatar` | 否 | Data URL 或网络 URL | -| `meta.ownerId` | 否 | 导出者的 `platformId` | - -### 成员 - -| 字段 | 必需 | 约束 | -| --------------- | ---- | ------------------------------------------------ | -| `platformId` | 是 | 稳定且唯一的字符串身份 | -| `accountName` | 是 | 账号显示名称 | -| `groupNickname` | 否 | 当前会话中的显示名称 | -| `aliases` | 否 | 字符串数组 | -| `avatar` | 否 | Data URL 或网络 URL | -| `roles` | 否 | 如 `[{"id":"owner"}]`、`[{"id":"admin"}]` 的数组 | - -### 消息 - -| 字段 | 必需 | 约束 | -| ------------------- | ---- | ------------------------------------- | -| `sender` | 是 | 发送者的 `platformId` | -| `accountName` | 是 | 发送时的账号名称 | -| `groupNickname` | 否 | 发送时的群昵称 | -| `timestamp` | 是 | 整数型 Unix 秒级时间戳 | -| `type` | 是 | 使用下表中的数字 | -| `content` | 是 | 字符串或 `null`;尽量保留有用的源表示 | -| `platformMessageId` | 否 | 唯一的原始消息 ID | -| `replyToMessageId` | 否 | 被回复消息的原始 ID | - -## 消息类型 - -| 值 | 含义 | 值 | 含义 | -| ---- | ------------- | ---- | --------- | -| `0` | 文本 | `1` | 图片 | -| `2` | 语音 | `3` | 视频 | -| `4` | 文件 | `5` | 表情/贴纸 | -| `7` | 链接/卡片 | `8` | 位置 | -| `20` | 红包 | `21` | 转账 | -| `22` | 拍一拍/戳一戳 | `23` | 通话 | -| `24` | 分享 | `25` | 回复 | -| `26` | 转发消息 | `27` | 名片 | -| `80` | 系统事件 | `81` | 撤回消息 | -| `99` | 其他/未知 | | | - -媒体消息没有内嵌正文时,在 `content` 中保留有用的占位符或源文件相对路径;只有源数据确实没有任何表示时才使用 `null`。 - -## 身份兜底规则 - -优先使用源平台不可变的用户 ID。确实缺少时,根据同一平台、同一会话内的稳定身份字段确定性生成,例如对规范化账号身份计算 SHA-256,并添加不敏感的前缀。成员顺序可能变化时不能只使用数组下标,绝不能使用随机值。 - -有原始消息 ID 时保留;没有时通常省略 `platformMessageId`。只有回复关系必须依赖消息 ID 时才生成确定性 ID,并纳入会话身份、发送者、秒级时间戳、稳定源序号和正文摘要,确保重复执行结果一致。 - -## 必须核对的数量 - -转换器必须在不输出正文的前提下报告: - -- 源会话数和源消息记录数; -- 输出会话数、成员数和消息数; -- 映射为类型 `99` 的记录数; -- 跳过数和失败数。 - -跳过数和失败数默认为零。只有源消息数等于输出消息数加用户明确接受的跳过数、严格验证通过,并且 `clb import --dry-run --json` 成功,转换才算完成。 diff --git a/skills/chatlab-convert-cn/scripts/validate-chatlab.mjs b/skills/chatlab-convert-cn/scripts/validate-chatlab.mjs deleted file mode 100644 index a21f8ff08..000000000 --- a/skills/chatlab-convert-cn/scripts/validate-chatlab.mjs +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node -// Generated from the canonical ChatLab validator. Do not edit directly. -/* eslint-disable */ -import{existsSync as H}from"node:fs";import j from"node:path";import{createReadStream as P}from"node:fs";import{readFile as v,stat as k}from"node:fs/promises";import{extname as D}from"node:path";import{createInterface as V}from"node:readline";var R="0.0.2",S=["0.0.1",R];function C(e){return S.includes(e)}var h=(s=>(s[s.TEXT=0]="TEXT",s[s.IMAGE=1]="IMAGE",s[s.VOICE=2]="VOICE",s[s.VIDEO=3]="VIDEO",s[s.FILE=4]="FILE",s[s.EMOJI=5]="EMOJI",s[s.LINK=7]="LINK",s[s.LOCATION=8]="LOCATION",s[s.RED_PACKET=20]="RED_PACKET",s[s.TRANSFER=21]="TRANSFER",s[s.POKE=22]="POKE",s[s.CALL=23]="CALL",s[s.SHARE=24]="SHARE",s[s.REPLY=25]="REPLY",s[s.FORWARD=26]="FORWARD",s[s.CONTACT=27]="CONTACT",s[s.SYSTEM=80]="SYSTEM",s[s.RECALL=81]="RECALL",s[s.OTHER=99]="OTHER",s))(h||{});var G=100,M=1e11,F=new Set(Object.values(h).filter(e=>typeof e=="number")),E="SYSTEM",$=new Set([80,81]);function y(e,t){return{format:e,fileSize:t,errorCount:0,warningCount:0,issues:[],stats:{headerCount:0,memberCount:0,messageCount:0,uniqueSenderCount:0,commentLineCount:0,blankLineCount:0},memberIds:new Set,ownerIds:new Set,senderIds:new Set,messageIds:new Set,replyTargetIds:new Set}}function a(e,t,r,n,o={}){t==="error"?e.errorCount++:e.warningCount++,e.issues.length0?!0:(a(e,"error","INVALID_REQUIRED_STRING","Expected a non-empty string.",p(n,r)),!1)}function u(e,t,r,n,o){c(t,r)&&typeof t[r]!="string"&&a(e,"error","INVALID_OPTIONAL_STRING","Expected a string.",p(o,n))}function x(e,t,r,n){return typeof t=="number"&&Number.isInteger(t)&&t>=0&&ttypeof o!="string"))&&a(e,"error","INVALID_ALIASES","Expected aliases to be an array of strings.",{...n,path:`${r}.aliases`}),c(t,"roles")&&(Array.isArray(t.roles)?t.roles.forEach((o,i)=>{let g=`${r}.roles[${i}]`;if(!d(o)){a(e,"error","INVALID_ROLE","Expected a role object.",{...n,path:g});return}m(e,o.id,`${g}.id`,n),u(e,o,"name",`${g}.name`,n)}):a(e,"error","INVALID_ROLES","Expected roles to be an array.",{...n,path:`${r}.roles`}))}function N(e,t,r,n){if(e.stats.messageCount++,!d(t)){a(e,"error","INVALID_MESSAGE","Expected a message object.",p(n,r));return}let o=m(e,t.sender,`${r}.sender`,n)?t.sender:void 0;o!==void 0&&e.senderIds.add(o),m(e,t.accountName,`${r}.accountName`,n),x(e,t.timestamp,`${r}.timestamp`,n)&&(e.lastTimestamp!==void 0&&t.timestamp0?e.memberIds:t?void 0:e.senderIds;if(n)for(let i of e.ownerIds)n.has(i)||a(e,"error","UNKNOWN_OWNER","meta.ownerId must reference a member platformId.");if(e.memberIds.size>0)for(let i of e.senderIds)i!==E&&!e.memberIds.has(i)&&a(e,"error","UNKNOWN_MESSAGE_SENDER","Every message sender must reference an explicit member platformId.");for(let i of e.replyTargetIds)e.messageIds.has(i)||a(e,"warning","UNKNOWN_REPLY_TARGET","A replyToMessageId does not match any platformMessageId in this file.");e.stats.uniqueSenderCount=e.senderIds.size;let o=e.errorCount+e.warningCount;return{valid:e.errorCount===0,format:e.format,version:e.version,fileSize:e.fileSize,errorCount:e.errorCount,warningCount:e.warningCount,issues:e.issues,truncatedIssueCount:Math.max(0,o-e.issues.length),stats:e.stats}}async function U(e,t){let r=y("json",t),n;try{n=JSON.parse(await v(e,"utf8"))}catch{return a(r,"error","INVALID_JSON","The file is not valid JSON."),b(r,!0,!1)}return d(n)?(T(r,n.chatlab,n.meta,{}),Array.isArray(n.members)?n.members.forEach((o,i)=>_(r,o,`members[${i}]`,{})):a(r,"error","INVALID_MEMBERS","Expected members to be an array.",{path:"members"}),Array.isArray(n.messages)?n.messages.forEach((o,i)=>N(r,o,`messages[${i}]`,{})):a(r,"error","INVALID_MESSAGES","Expected messages to be an array.",{path:"messages"}),b(r,!0)):(a(r,"error","INVALID_ROOT","Expected a top-level JSON object."),b(r,!0,!1))}async function z(e,t){let r=y("jsonl",t),n=P(e,{encoding:"utf8"}),o=V({input:n,crlfDelay:1/0}),i=0,g=0,A=!1;for await(let w of o){i++;let I=w.trim();if(!I){r.stats.blankLineCount++;continue}if(I.startsWith("#")){r.stats.commentLineCount++;continue}g++;let l;try{l=JSON.parse(I)}catch{a(r,"error","INVALID_JSONL_LINE","The line is not valid JSON.",{line:i});continue}if(!d(l)){a(r,"error","INVALID_JSONL_VALUE","Expected a JSON object on every data line.",{line:i});continue}switch(g===1&&l._type!=="header"&&a(r,"error","HEADER_NOT_FIRST","The first data line must be a header.",{line:i,path:"_type"}),l._type){case"header":r.stats.headerCount>0&&a(r,"error","DUPLICATE_HEADER","Only one header line is allowed.",{line:i,path:"_type"}),T(r,l.chatlab,l.meta,{line:i});break;case"member":A&&a(r,"error","MEMBER_AFTER_MESSAGE","Member lines must appear before message lines.",{line:i}),_(r,l,`line[${i}]`,{line:i});break;case"message":A=!0,N(r,l,`line[${i}]`,{line:i});break;default:a(r,"error","UNKNOWN_LINE_TYPE","Expected _type to be header, member, or message.",{line:i,path:"_type"})}}return b(r,!1)}async function L(e){let t=await k(e),r=D(e).toLowerCase();if(r===".json")return U(e,t.size);if(r===".jsonl")return z(e,t.size);let n=y("unknown",t.size);return a(n,"error","UNSUPPORTED_EXTENSION","Expected a .json or .jsonl file."),b(n,!1,!1)}var O=process.argv[2];function f(e){process.stdout.write(`${JSON.stringify(e)} -`)}if(!O)f({ok:!1,command:"validate",error:{code:"MISSING_FILE",message:"Usage: node validate-chatlab.mjs "},meta:{apiVersion:1,standalone:!0}}),process.exitCode=2;else{let e=j.resolve(O);if(!H(e))f({ok:!1,command:"validate",error:{code:"FILE_NOT_FOUND",message:`File not found: ${e}`,hint:"Pass an absolute path or a path relative to the current working directory."},meta:{apiVersion:1,standalone:!0}}),process.exitCode=3;else try{let t=await L(e);f(t.valid?{ok:!0,command:"validate",data:t,meta:{apiVersion:1,standalone:!0}}:{ok:!1,command:"validate",data:t,error:{code:"INVALID_CHATLAB_FORMAT",message:`Validation found ${t.errorCount} error(s).`,hint:"Fix every error before importing the file into ChatLab."},meta:{apiVersion:1,standalone:!0}}),t.valid||(process.exitCode=1)}catch(t){f({ok:!1,command:"validate",error:{code:"VALIDATION_FAILED",message:t instanceof Error?t.message:String(t)},meta:{apiVersion:1,standalone:!0}}),process.exitCode=1}} diff --git a/skills/chatlab-convert/SKILL.md b/skills/chatlab-convert/SKILL.md index 673873f27..7d35b93b0 100644 --- a/skills/chatlab-convert/SKILL.md +++ b/skills/chatlab-convert/SKILL.md @@ -1,12 +1,15 @@ --- name: chatlab-convert -description: Convert unsupported local chat exports into validated ChatLab JSONL or JSON with a reproducible local script. Use for unknown-format adaptation, not analysis or direct import of already supported files. +description: >- + Convert unsupported local chat exports into validated ChatLab JSONL or JSON with a reusable local script. 用可复用的本地脚本,将不受支持的聊天导出转换并验证为 ChatLab JSONL 或 JSON。 --- # ChatLab Convert Convert an unsupported local export without modifying the source. Prefer a reproducible script and a validated JSONL result over a one-off rewritten file. +Follow the user's requested response language; otherwise use the conversation language, preserving the user's Chinese variant when applicable. Keep commands, field names, IDs, and evidence markers unchanged. + ## Non-negotiable rules - Keep every source file read-only. Write scripts, samples, extracted archives, and output to a separate working directory, using the user's output location when specified. diff --git a/skills/chatlab-convert/agents/openai.yaml b/skills/chatlab-convert/agents/openai.yaml index fb5d72c74..4c4215d1a 100644 --- a/skills/chatlab-convert/agents/openai.yaml +++ b/skills/chatlab-convert/agents/openai.yaml @@ -1,4 +1,4 @@ interface: - display_name: 'ChatLab Convert' - short_description: 'Convert unsupported chat exports into ChatLab format' + display_name: 'ChatLab Convert · 聊天转换' + short_description: 'Convert unsupported chat exports · 转换不受支持的聊天格式' default_prompt: 'Use $chatlab-convert to convert this unsupported chat export into a validated ChatLab JSONL file.' diff --git a/skills/chatlab-convert/scripts/validate-chatlab.mjs b/skills/chatlab-convert/scripts/validate-chatlab.mjs index a21f8ff08..8c2bd1b1c 100644 --- a/skills/chatlab-convert/scripts/validate-chatlab.mjs +++ b/skills/chatlab-convert/scripts/validate-chatlab.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node // Generated from the canonical ChatLab validator. Do not edit directly. /* eslint-disable */ -import{existsSync as H}from"node:fs";import j from"node:path";import{createReadStream as P}from"node:fs";import{readFile as v,stat as k}from"node:fs/promises";import{extname as D}from"node:path";import{createInterface as V}from"node:readline";var R="0.0.2",S=["0.0.1",R];function C(e){return S.includes(e)}var h=(s=>(s[s.TEXT=0]="TEXT",s[s.IMAGE=1]="IMAGE",s[s.VOICE=2]="VOICE",s[s.VIDEO=3]="VIDEO",s[s.FILE=4]="FILE",s[s.EMOJI=5]="EMOJI",s[s.LINK=7]="LINK",s[s.LOCATION=8]="LOCATION",s[s.RED_PACKET=20]="RED_PACKET",s[s.TRANSFER=21]="TRANSFER",s[s.POKE=22]="POKE",s[s.CALL=23]="CALL",s[s.SHARE=24]="SHARE",s[s.REPLY=25]="REPLY",s[s.FORWARD=26]="FORWARD",s[s.CONTACT=27]="CONTACT",s[s.SYSTEM=80]="SYSTEM",s[s.RECALL=81]="RECALL",s[s.OTHER=99]="OTHER",s))(h||{});var G=100,M=1e11,F=new Set(Object.values(h).filter(e=>typeof e=="number")),E="SYSTEM",$=new Set([80,81]);function y(e,t){return{format:e,fileSize:t,errorCount:0,warningCount:0,issues:[],stats:{headerCount:0,memberCount:0,messageCount:0,uniqueSenderCount:0,commentLineCount:0,blankLineCount:0},memberIds:new Set,ownerIds:new Set,senderIds:new Set,messageIds:new Set,replyTargetIds:new Set}}function a(e,t,r,n,o={}){t==="error"?e.errorCount++:e.warningCount++,e.issues.length0?!0:(a(e,"error","INVALID_REQUIRED_STRING","Expected a non-empty string.",p(n,r)),!1)}function u(e,t,r,n,o){c(t,r)&&typeof t[r]!="string"&&a(e,"error","INVALID_OPTIONAL_STRING","Expected a string.",p(o,n))}function x(e,t,r,n){return typeof t=="number"&&Number.isInteger(t)&&t>=0&&ttypeof o!="string"))&&a(e,"error","INVALID_ALIASES","Expected aliases to be an array of strings.",{...n,path:`${r}.aliases`}),c(t,"roles")&&(Array.isArray(t.roles)?t.roles.forEach((o,i)=>{let g=`${r}.roles[${i}]`;if(!d(o)){a(e,"error","INVALID_ROLE","Expected a role object.",{...n,path:g});return}m(e,o.id,`${g}.id`,n),u(e,o,"name",`${g}.name`,n)}):a(e,"error","INVALID_ROLES","Expected roles to be an array.",{...n,path:`${r}.roles`}))}function N(e,t,r,n){if(e.stats.messageCount++,!d(t)){a(e,"error","INVALID_MESSAGE","Expected a message object.",p(n,r));return}let o=m(e,t.sender,`${r}.sender`,n)?t.sender:void 0;o!==void 0&&e.senderIds.add(o),m(e,t.accountName,`${r}.accountName`,n),x(e,t.timestamp,`${r}.timestamp`,n)&&(e.lastTimestamp!==void 0&&t.timestamp0?e.memberIds:t?void 0:e.senderIds;if(n)for(let i of e.ownerIds)n.has(i)||a(e,"error","UNKNOWN_OWNER","meta.ownerId must reference a member platformId.");if(e.memberIds.size>0)for(let i of e.senderIds)i!==E&&!e.memberIds.has(i)&&a(e,"error","UNKNOWN_MESSAGE_SENDER","Every message sender must reference an explicit member platformId.");for(let i of e.replyTargetIds)e.messageIds.has(i)||a(e,"warning","UNKNOWN_REPLY_TARGET","A replyToMessageId does not match any platformMessageId in this file.");e.stats.uniqueSenderCount=e.senderIds.size;let o=e.errorCount+e.warningCount;return{valid:e.errorCount===0,format:e.format,version:e.version,fileSize:e.fileSize,errorCount:e.errorCount,warningCount:e.warningCount,issues:e.issues,truncatedIssueCount:Math.max(0,o-e.issues.length),stats:e.stats}}async function U(e,t){let r=y("json",t),n;try{n=JSON.parse(await v(e,"utf8"))}catch{return a(r,"error","INVALID_JSON","The file is not valid JSON."),b(r,!0,!1)}return d(n)?(T(r,n.chatlab,n.meta,{}),Array.isArray(n.members)?n.members.forEach((o,i)=>_(r,o,`members[${i}]`,{})):a(r,"error","INVALID_MEMBERS","Expected members to be an array.",{path:"members"}),Array.isArray(n.messages)?n.messages.forEach((o,i)=>N(r,o,`messages[${i}]`,{})):a(r,"error","INVALID_MESSAGES","Expected messages to be an array.",{path:"messages"}),b(r,!0)):(a(r,"error","INVALID_ROOT","Expected a top-level JSON object."),b(r,!0,!1))}async function z(e,t){let r=y("jsonl",t),n=P(e,{encoding:"utf8"}),o=V({input:n,crlfDelay:1/0}),i=0,g=0,A=!1;for await(let w of o){i++;let I=w.trim();if(!I){r.stats.blankLineCount++;continue}if(I.startsWith("#")){r.stats.commentLineCount++;continue}g++;let l;try{l=JSON.parse(I)}catch{a(r,"error","INVALID_JSONL_LINE","The line is not valid JSON.",{line:i});continue}if(!d(l)){a(r,"error","INVALID_JSONL_VALUE","Expected a JSON object on every data line.",{line:i});continue}switch(g===1&&l._type!=="header"&&a(r,"error","HEADER_NOT_FIRST","The first data line must be a header.",{line:i,path:"_type"}),l._type){case"header":r.stats.headerCount>0&&a(r,"error","DUPLICATE_HEADER","Only one header line is allowed.",{line:i,path:"_type"}),T(r,l.chatlab,l.meta,{line:i});break;case"member":A&&a(r,"error","MEMBER_AFTER_MESSAGE","Member lines must appear before message lines.",{line:i}),_(r,l,`line[${i}]`,{line:i});break;case"message":A=!0,N(r,l,`line[${i}]`,{line:i});break;default:a(r,"error","UNKNOWN_LINE_TYPE","Expected _type to be header, member, or message.",{line:i,path:"_type"})}}return b(r,!1)}async function L(e){let t=await k(e),r=D(e).toLowerCase();if(r===".json")return U(e,t.size);if(r===".jsonl")return z(e,t.size);let n=y("unknown",t.size);return a(n,"error","UNSUPPORTED_EXTENSION","Expected a .json or .jsonl file."),b(n,!1,!1)}var O=process.argv[2];function f(e){process.stdout.write(`${JSON.stringify(e)} -`)}if(!O)f({ok:!1,command:"validate",error:{code:"MISSING_FILE",message:"Usage: node validate-chatlab.mjs "},meta:{apiVersion:1,standalone:!0}}),process.exitCode=2;else{let e=j.resolve(O);if(!H(e))f({ok:!1,command:"validate",error:{code:"FILE_NOT_FOUND",message:`File not found: ${e}`,hint:"Pass an absolute path or a path relative to the current working directory."},meta:{apiVersion:1,standalone:!0}}),process.exitCode=3;else try{let t=await L(e);f(t.valid?{ok:!0,command:"validate",data:t,meta:{apiVersion:1,standalone:!0}}:{ok:!1,command:"validate",data:t,error:{code:"INVALID_CHATLAB_FORMAT",message:`Validation found ${t.errorCount} error(s).`,hint:"Fix every error before importing the file into ChatLab."},meta:{apiVersion:1,standalone:!0}}),t.valid||(process.exitCode=1)}catch(t){f({ok:!1,command:"validate",error:{code:"VALIDATION_FAILED",message:t instanceof Error?t.message:String(t)},meta:{apiVersion:1,standalone:!0}}),process.exitCode=1}} +import{existsSync as K}from"node:fs";import q from"node:path";import{createReadStream as O}from"node:fs";import{readFile as P,stat as L}from"node:fs/promises";import{extname as k}from"node:path";import{createInterface as D}from"node:readline";var x="0.0.2",C=["0.0.1",x];function f(e){return C.includes(e)}var y=(s=>(s[s.TEXT=0]="TEXT",s[s.IMAGE=1]="IMAGE",s[s.VOICE=2]="VOICE",s[s.VIDEO=3]="VIDEO",s[s.FILE=4]="FILE",s[s.EMOJI=5]="EMOJI",s[s.LINK=7]="LINK",s[s.LOCATION=8]="LOCATION",s[s.RED_PACKET=20]="RED_PACKET",s[s.TRANSFER=21]="TRANSFER",s[s.POKE=22]="POKE",s[s.CALL=23]="CALL",s[s.SHARE=24]="SHARE",s[s.REPLY=25]="REPLY",s[s.FORWARD=26]="FORWARD",s[s.CONTACT=27]="CONTACT",s[s.SYSTEM=80]="SYSTEM",s[s.RECALL=81]="RECALL",s[s.OTHER=99]="OTHER",s))(y||{});var G=100,V=1e11,F=new Set(Object.values(y).filter(e=>typeof e=="number")),A="SYSTEM",$=new Set([80,81]);function I(e,t){return{format:e,fileSize:t,errorCount:0,warningCount:0,issues:[],stats:{headerCount:0,memberCount:0,messageCount:0,uniqueSenderCount:0,commentLineCount:0,blankLineCount:0},memberIds:new Set,ownerIds:new Set,senderIds:new Set,messageIds:new Set,replyTargetIds:new Set}}function a(e,t,r,n,o={}){t==="error"?e.errorCount++:e.warningCount++,e.issues.length0?!0:(a(e,"error","INVALID_REQUIRED_STRING","Expected a non-empty string.",p(n,r)),!1)}function u(e,t,r,n,o){l(t,r)&&typeof t[r]!="string"&&a(e,"error","INVALID_OPTIONAL_STRING","Expected a string.",p(o,n))}function T(e,t,r,n){return typeof t=="number"&&Number.isInteger(t)&&t>=0&&ttypeof o!="string"))&&a(e,"error","INVALID_ALIASES","Expected aliases to be an array of strings.",{...n,path:`${r}.aliases`}),l(t,"roles")&&(Array.isArray(t.roles)?t.roles.forEach((o,i)=>{let g=`${r}.roles[${i}]`;if(!d(o)){a(e,"error","INVALID_ROLE","Expected a role object.",{...n,path:g});return}m(e,o.id,`${g}.id`,n),u(e,o,"name",`${g}.name`,n)}):a(e,"error","INVALID_ROLES","Expected roles to be an array.",{...n,path:`${r}.roles`}))}function _(e,t,r,n){if(e.stats.messageCount++,!d(t)){a(e,"error","INVALID_MESSAGE","Expected a message object.",p(n,r));return}let o=m(e,t.sender,`${r}.sender`,n)?t.sender:void 0;o!==void 0&&e.senderIds.add(o),m(e,t.accountName,`${r}.accountName`,n),T(e,t.timestamp,`${r}.timestamp`,n)&&(e.lastTimestamp!==void 0&&t.timestamp0?e.memberIds:t?void 0:e.senderIds;if(n)for(let i of e.ownerIds)n.has(i)||a(e,"error","UNKNOWN_OWNER","meta.ownerId must reference a member platformId.");if(e.memberIds.size>0)for(let i of e.senderIds)i!==A&&!e.memberIds.has(i)&&a(e,"error","UNKNOWN_MESSAGE_SENDER","Every message sender must reference an explicit member platformId.");for(let i of e.replyTargetIds)e.messageIds.has(i)||a(e,"warning","UNKNOWN_REPLY_TARGET","A replyToMessageId does not match any platformMessageId in this file.");e.stats.uniqueSenderCount=e.senderIds.size;let o=e.errorCount+e.warningCount;return{valid:e.errorCount===0,format:e.format,version:e.version,fileSize:e.fileSize,errorCount:e.errorCount,warningCount:e.warningCount,issues:e.issues,truncatedIssueCount:Math.max(0,o-e.issues.length),stats:e.stats}}async function U(e,t){let r=I("json",t),n;try{n=JSON.parse(await P(e,"utf8"))}catch{return a(r,"error","INVALID_JSON","The file is not valid JSON."),b(r,!0,!1)}return d(n)?(v(r,n.chatlab,n.meta,{}),Array.isArray(n.members)?n.members.forEach((o,i)=>E(r,o,`members[${i}]`,{})):a(r,"error","INVALID_MEMBERS","Expected members to be an array.",{path:"members"}),Array.isArray(n.messages)?n.messages.forEach((o,i)=>_(r,o,`messages[${i}]`,{})):a(r,"error","INVALID_MESSAGES","Expected messages to be an array.",{path:"messages"}),b(r,!0)):(a(r,"error","INVALID_ROOT","Expected a top-level JSON object."),b(r,!0,!1))}async function z(e,t){let r=I("jsonl",t),n=O(e,{encoding:"utf8"}),o=D({input:n,crlfDelay:1/0}),i=0,g=0,R=!1;for await(let M of o){i++;let h=M.trim();if(!h){r.stats.blankLineCount++;continue}if(h.startsWith("#")){r.stats.commentLineCount++;continue}g++;let c;try{c=JSON.parse(h)}catch{a(r,"error","INVALID_JSONL_LINE","The line is not valid JSON.",{line:i});continue}if(!d(c)){a(r,"error","INVALID_JSONL_VALUE","Expected a JSON object on every data line.",{line:i});continue}switch(g===1&&c._type!=="header"&&a(r,"error","HEADER_NOT_FIRST","The first data line must be a header.",{line:i,path:"_type"}),c._type){case"header":r.stats.headerCount>0&&a(r,"error","DUPLICATE_HEADER","Only one header line is allowed.",{line:i,path:"_type"}),v(r,c.chatlab,c.meta,{line:i});break;case"member":R&&a(r,"error","MEMBER_AFTER_MESSAGE","Member lines must appear before message lines.",{line:i}),E(r,c,`line[${i}]`,{line:i});break;case"message":R=!0,_(r,c,`line[${i}]`,{line:i});break;default:a(r,"error","UNKNOWN_LINE_TYPE","Expected _type to be header, member, or message.",{line:i,path:"_type"})}}return b(r,!1)}async function N(e){let t=await L(e),r=k(e).toLowerCase();if(r===".json")return U(e,t.size);if(r===".jsonl")return z(e,t.size);let n=I("unknown",t.size);return a(n,"error","UNSUPPORTED_EXTENSION","Expected a .json or .jsonl file."),b(n,!1,!1)}var w=process.argv[2];function S(e){process.stdout.write(`${JSON.stringify(e)} +`)}if(!w)S({ok:!1,command:"validate",error:{code:"MISSING_FILE",message:"Usage: node validate-chatlab.mjs "},meta:{apiVersion:1,standalone:!0}}),process.exitCode=2;else{let e=q.resolve(w);if(!K(e))S({ok:!1,command:"validate",error:{code:"FILE_NOT_FOUND",message:`File not found: ${e}`,hint:"Pass an absolute path or a path relative to the current working directory."},meta:{apiVersion:1,standalone:!0}}),process.exitCode=3;else try{let t=await N(e);S(t.valid?{ok:!0,command:"validate",data:t,meta:{apiVersion:1,standalone:!0}}:{ok:!1,command:"validate",data:t,error:{code:"INVALID_CHATLAB_FORMAT",message:`Validation found ${t.errorCount} error(s).`,hint:"Fix every error before importing the file into ChatLab."},meta:{apiVersion:1,standalone:!0}}),t.valid||(process.exitCode=1)}catch(t){S({ok:!1,command:"validate",error:{code:"VALIDATION_FAILED",message:t instanceof Error?t.message:String(t)},meta:{apiVersion:1,standalone:!0}}),process.exitCode=1}} diff --git a/skills/chatlab-import-cn/SKILL.md b/skills/chatlab-import-cn/SKILL.md deleted file mode 100644 index 00630ab52..000000000 --- a/skills/chatlab-import-cn/SKILL.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: chatlab-import-cn -description: 通过 clb 预览并导入本地聊天导出文件,支持重新导入和增量更新。用于导入请求,不用于只读分析或未知格式转换。 ---- - -# ChatLab 聊天导入 - -通过 `clb` CLI 导入本地聊天导出文件。始终先预览准确的写入计划。将用户明确提出的导入请求视为授权,预览成功后继续执行,不再请求二次确认。 - -分析已有记录时,可用则使用 `chatlab-analyze-cn`;源格式不受支持时,可用则使用 `chatlab-convert-cn`。受支持格式的导入不依赖这两个技能。 - -## 工作流程 - -1. 检查 CLI,并确认唯一、准确的文件路径: - -```bash -clb --help -``` - -如果 CLI 未安装,告诉用户运行 `npm install -g chatlab-cli`。未经批准不要自行安装软件。复用用户指定的文件和目标,路径仍有歧义时才询问。每条命令中的路径都要加引号。 - -2. 只读预览导入计划: - -```bash -clb import "/absolute/path/to/chat-export.json" --dry-run --json -``` - -如果用户选择了已有会话,在预览和正式导入中都加入 `--session-id `。 - -3. 只总结新建或更新模式、目标会话 ID、扫描消息数、新增消息数、重复消息数,以及匹配方式或新建原因。不要引用消息正文。 - -4. 确认 `ok: true`,从 `data` 读取计划后决定: - -- 用户只要求预览或检查时,到此停止,不写入数据。 -- `importMode` 为 `incremental` 且 `newMessageCount` 为 `0` 时,告诉用户会话已经是最新状态,然后停止。 -- `importMode` 为 `incremental` 且存在新增消息时,无需再次确认,自动继续。 -- `importMode` 为 `created` 时,自动继续。如果 `createReason` 为 `ambiguous`,通过新建会话保护已有会话,并在最终结果中说明原因。 -- 结果不足以确定安全操作时,停止并说明缺少的信息。 - -5. 根据上一步可以写入时,使用相同文件、目标和格式移除 `--dry-run`: - -```bash -clb import "/absolute/path/to/chat-export.json" --json -``` - -根据最终 JSON 信封报告结果会话 ID、导入模式、新增消息数和重复消息数。 - -## 安全边界 - -- 绝不跳过预览、直接编辑 ChatLab 数据库、删除或合并会话。 -- 绝不泄露完整聊天导出文件或消息正文。 -- 绝不编造文件路径、解析格式或会话 ID。 -- 仅在修正方式明确时遵循 `error.hint`。 -- `FILE_NOT_FOUND`、`INVALID_SESSION_ID` 优先从上下文修正,无法确定时询问;`UNRECOGNIZED_FORMAT` 查看 `clb formats`;`IMPORT_IN_PROGRESS` 在现有导入完成后重试一次,仍阻塞则报告,不无限轮询。 -- 优先使用用户当前使用的中文变体回答,命令、参数、JSON 字段和错误码保持原样。 diff --git a/skills/chatlab-import-cn/agents/openai.yaml b/skills/chatlab-import-cn/agents/openai.yaml deleted file mode 100644 index 60b92473e..000000000 --- a/skills/chatlab-import-cn/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: 'ChatLab 聊天导入' - short_description: '通过 ChatLab CLI 安全预览并导入本地聊天记录' - default_prompt: '使用 $chatlab-import-cn 安全预览并将这个聊天导出文件导入 ChatLab。' diff --git a/skills/chatlab-import/SKILL.md b/skills/chatlab-import/SKILL.md index 1bc4e5683..7584f284f 100644 --- a/skills/chatlab-import/SKILL.md +++ b/skills/chatlab-import/SKILL.md @@ -1,12 +1,15 @@ --- name: chatlab-import -description: Preview and import local chat exports into ChatLab with clb, including re-imports and incremental updates. Use for import requests, not read-only analysis or unsupported-format conversion. +description: >- + Preview and import supported local chat exports into ChatLab, including incremental updates. 预览并导入 ChatLab 支持的本地聊天导出,支持增量更新。 --- # ChatLab Import Import local chat exports through the `clb` CLI. Always preview the exact write. Treat an explicit request to import as authorization to continue after a successful preview; do not ask for a second confirmation. +Follow the user's requested response language; otherwise use the conversation language, preserving the user's Chinese variant when applicable. Keep commands, field names, IDs, and evidence markers unchanged. + For analysis of imported records, use `chatlab-analyze` when available. For an unsupported source, use `chatlab-convert` when available; neither skill is required for a supported import. ## Workflow diff --git a/skills/chatlab-import/agents/openai.yaml b/skills/chatlab-import/agents/openai.yaml index 0b5309f73..1c730735a 100644 --- a/skills/chatlab-import/agents/openai.yaml +++ b/skills/chatlab-import/agents/openai.yaml @@ -1,4 +1,4 @@ interface: - display_name: 'ChatLab Import' - short_description: 'Preview and import chat exports with ChatLab CLI' + display_name: 'ChatLab Import · 聊天导入' + short_description: 'Preview and import chat exports · 预览并导入聊天记录' default_prompt: 'Use $chatlab-import to safely preview and import this chat export into ChatLab.' diff --git a/tests/chatlab-skill-validator.test.ts b/tests/chatlab-skill-validator.test.ts index e765baa8f..081c31a26 100644 --- a/tests/chatlab-skill-validator.test.ts +++ b/tests/chatlab-skill-validator.test.ts @@ -7,10 +7,7 @@ import { after, before, describe, it } from 'node:test' import { fileURLToPath } from 'node:url' const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') -const VALIDATOR_PATHS = [ - 'skills/chatlab-convert/scripts/validate-chatlab.mjs', - 'skills/chatlab-convert-cn/scripts/validate-chatlab.mjs', -] +const VALIDATOR_PATH = path.join(ROOT_DIR, 'skills/chatlab-convert/scripts/validate-chatlab.mjs') let tempDir = '' @@ -31,8 +28,8 @@ function createDocument(sender: string, type: number): string { }) } -function runValidator(validatorPath: string, documentPath: string) { - const result = spawnSync(process.execPath, [path.join(ROOT_DIR, validatorPath), documentPath], { +function runValidator(documentPath: string) { + const result = spawnSync(process.execPath, [VALIDATOR_PATH, documentPath], { cwd: ROOT_DIR, encoding: 'utf8', }) @@ -54,30 +51,25 @@ after(() => { fs.rmSync(tempDir, { recursive: true, force: true }) }) -describe('standalone ChatLab skill validators', () => { - for (const validatorPath of VALIDATOR_PATHS) { - it(`${validatorPath} accepts reserved SYSTEM messages`, () => { - const documentPath = path.join(tempDir, `${path.basename(path.dirname(path.dirname(validatorPath)))}-system.json`) - fs.writeFileSync(documentPath, createDocument('SYSTEM', 80), 'utf8') +describe('standalone ChatLab skill validator', () => { + it('accepts reserved SYSTEM messages', () => { + const documentPath = path.join(tempDir, 'system.json') + fs.writeFileSync(documentPath, createDocument('SYSTEM', 80), 'utf8') - const result = runValidator(validatorPath, documentPath) + const result = runValidator(documentPath) - assert.equal(result.status, 0) - assert.equal(result.output.ok, true) - }) + assert.equal(result.status, 0) + assert.equal(result.output.ok, true) + }) - it(`${validatorPath} rejects SYSTEM as a regular message sender`, () => { - const documentPath = path.join( - tempDir, - `${path.basename(path.dirname(path.dirname(validatorPath)))}-invalid-system.json` - ) - fs.writeFileSync(documentPath, createDocument('SYSTEM', 0), 'utf8') + it('rejects SYSTEM as a regular message sender', () => { + const documentPath = path.join(tempDir, 'invalid-system.json') + fs.writeFileSync(documentPath, createDocument('SYSTEM', 0), 'utf8') - const result = runValidator(validatorPath, documentPath) + const result = runValidator(documentPath) - assert.equal(result.status, 1) - assert.equal(result.output.ok, false) - assert.ok(result.output.data?.issues.some((issue) => issue.code === 'INVALID_SYSTEM_SENDER_TYPE')) - }) - } + assert.equal(result.status, 1) + assert.equal(result.output.ok, false) + assert.ok(result.output.data?.issues.some((issue) => issue.code === 'INVALID_SYSTEM_SENDER_TYPE')) + }) }) From 7c6531f8468711fe461ba1d0c03a6ae8d373b80b Mon Sep 17 00:00:00 2001 From: digua Date: Sat, 5 Sep 2026 23:25:12 +0800 Subject: [PATCH 09/68] fix(skills): use unified import skill in home card --- src/pages/home/components/import/CliImportCard.vue | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/home/components/import/CliImportCard.vue b/src/pages/home/components/import/CliImportCard.vue index 076bec2a8..24389b865 100644 --- a/src/pages/home/components/import/CliImportCard.vue +++ b/src/pages/home/components/import/CliImportCard.vue @@ -4,10 +4,7 @@ import { useClipboard } from '@vueuse/core' import { useI18n } from 'vue-i18n' const { t, locale } = useI18n() -const skillInstallCommand = computed(() => { - const skillName = locale.value === 'zh-CN' || locale.value === 'zh-TW' ? 'chatlab-import-cn' : 'chatlab-import' - return `npx skills add ChatLab/ChatLab --skill ${skillName} -g` -}) +const skillInstallCommand = 'npx skills add ChatLab/ChatLab --skill chatlab-import -g' const cliImportCommand = 'clb import ~/Downloads/chat-export.json' const { copy: copySkillCommand, copied: skillCommandCopied } = useClipboard({ copiedDuring: 2000 }) const { copy: copyCliCommand, copied: cliCommandCopied } = useClipboard({ copiedDuring: 2000 }) From 8c36f5b99913feb39a62d2c82e5e924ceb0a1ed7 Mon Sep 17 00:00:00 2001 From: digua Date: Sat, 5 Sep 2026 23:41:38 +0800 Subject: [PATCH 10/68] fix(docs): restore converter privacy warning containers Separate warning titles, bodies, and closing fences with blank lines so VitePress renders the privacy notices correctly and Prettier preserves their structure. --- docs/cn/standard/ai-converter.md | 6 +++++- docs/en/standard/ai-converter.md | 6 +++++- docs/tw/standard/ai-converter.md | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/cn/standard/ai-converter.md b/docs/cn/standard/ai-converter.md index 7e7087b98..fd876e150 100644 --- a/docs/cn/standard/ai-converter.md +++ b/docs/cn/standard/ai-converter.md @@ -61,4 +61,8 @@ clb import "/转换后的文件.jsonl" --dry-run --json 只有严格验证和导入预览都成功,并且源消息数与输出消息数核对一致,才会报告转换成功。转换脚本和输出文件都会保留,方便以后重新转换更新后的聊天记录。 -::: warning 隐私提醒请使用能够访问本机文件和终端的 Agent 执行转换,不要把完整聊天文件上传到在线对话框。即使需要排查特殊文本格式,也应只检查最小范围并隐藏消息正文。::: +::: warning 隐私提醒 + +请使用能够访问本机文件和终端的 Agent 执行转换,不要把完整聊天文件上传到在线对话框。即使需要排查特殊文本格式,也应只检查最小范围并隐藏消息正文。 + +::: diff --git a/docs/en/standard/ai-converter.md b/docs/en/standard/ai-converter.md index f09a158f6..3511e3d8e 100644 --- a/docs/en/standard/ai-converter.md +++ b/docs/en/standard/ai-converter.md @@ -61,4 +61,8 @@ clb import "/absolute/path/to/converted.jsonl" --dry-run --json Success requires strict validation, a successful import preview, and matching source/output message counts. The converter and outputs remain available so a newer export can be converted again. -::: warning Privacy Use an agent that can work with local files and a local terminal. Do not upload a complete chat export to an online chat box. When an unusual text format requires sampling, inspect the smallest possible range and mask message bodies. ::: +::: warning Privacy + +Use an agent that can work with local files and a local terminal. Do not upload a complete chat export to an online chat box. When an unusual text format requires sampling, inspect the smallest possible range and mask message bodies. + +::: diff --git a/docs/tw/standard/ai-converter.md b/docs/tw/standard/ai-converter.md index 10b38f554..5d4f3df03 100644 --- a/docs/tw/standard/ai-converter.md +++ b/docs/tw/standard/ai-converter.md @@ -61,4 +61,8 @@ clb import "/轉換後的檔案.jsonl" --dry-run --json 只有嚴格驗證和匯入預覽都成功,而且來源訊息數與輸出訊息數核對一致,才會回報轉換成功。轉換腳本和輸出檔案都會保留,方便之後重新轉換更新後的聊天記錄。 -::: warning 隱私提醒請使用能存取本機檔案和終端的 Agent 執行轉換,不要把完整聊天檔案上傳到線上對話框。即使需要排查特殊文字格式,也應只檢查最小範圍並隱藏訊息正文。::: +::: warning 隱私提醒 + +請使用能存取本機檔案和終端的 Agent 執行轉換,不要把完整聊天檔案上傳到線上對話框。即使需要排查特殊文字格式,也應只檢查最小範圍並隱藏訊息正文。 + +::: From 29bea9a35de32cf46a022c100c399fd4cf168a83 Mon Sep 17 00:00:00 2001 From: digua Date: Sat, 5 Sep 2026 23:52:57 +0800 Subject: [PATCH 11/68] fix(skills): preserve released import skill selector Released Chinese and Traditional Chinese clients install chatlab-import-cn from the default branch. Keep a deprecated, self-contained English entry so those commands continue to work, and document migration to the unified skill. --- docs/cn/usage/how-to-import.md | 2 +- docs/en/usage/how-to-import.md | 2 +- docs/tw/usage/how-to-import.md | 2 +- skills/chatlab-import-cn/SKILL.md | 59 +++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 skills/chatlab-import-cn/SKILL.md diff --git a/docs/cn/usage/how-to-import.md b/docs/cn/usage/how-to-import.md index 8c759a7fb..f69daad50 100644 --- a/docs/cn/usage/how-to-import.md +++ b/docs/cn/usage/how-to-import.md @@ -26,7 +26,7 @@ npm install -g chatlab-cli npx skills add ChatLab/ChatLab --skill chatlab-import -g ``` -各语言共用同一个技能,可以直接用中文提出需求。曾安装 `chatlab-import-cn` 时,请先保留自定义改动,再安装上面的统一版本,并通过原安装工具移除旧入口,避免重复显示。 +各语言共用同一个技能,可以直接用中文提出需求。`chatlab-import-cn` 作为已弃用的兼容入口保留,确保 ChatLab v0.31.1 至 v0.37.1 首页中的旧安装命令继续有效;其正文也使用英文,回答遵循用户语言。曾安装旧入口时,请先保留自定义改动,再安装上面的统一版本,并通过原安装工具移除旧入口,避免重复显示。 然后直接告诉 Agent: diff --git a/docs/en/usage/how-to-import.md b/docs/en/usage/how-to-import.md index 14c45152a..a41369f7e 100644 --- a/docs/en/usage/how-to-import.md +++ b/docs/en/usage/how-to-import.md @@ -26,7 +26,7 @@ npm install -g chatlab-cli npx skills add ChatLab/ChatLab --skill chatlab-import -g ``` -The same skill works across languages; ask in your preferred language. +The same skill works across languages; ask in your preferred language. The deprecated `chatlab-import-cn` entry remains available for installation commands shown in ChatLab v0.31.1 through v0.37.1. Its instructions are also in English, and responses follow your language. If you already installed it, preserve any custom changes, install the unified skill above, then remove the old entry with your original installation tool to avoid duplicates. Then ask the agent: diff --git a/docs/tw/usage/how-to-import.md b/docs/tw/usage/how-to-import.md index 972c03155..c247320cd 100644 --- a/docs/tw/usage/how-to-import.md +++ b/docs/tw/usage/how-to-import.md @@ -26,7 +26,7 @@ npm install -g chatlab-cli npx skills add ChatLab/ChatLab --skill chatlab-import -g ``` -各語言共用同一個技能,可以直接用繁體中文提出需求。曾安裝 `chatlab-import-cn` 時,請先保留自訂修改,再安裝上面的統一版本,並透過原安裝工具移除舊入口,避免重複顯示。 +各語言共用同一個技能,可以直接用繁體中文提出需求。`chatlab-import-cn` 作為已棄用的相容入口保留,確保 ChatLab v0.31.1 至 v0.37.1 首頁中的舊安裝命令繼續有效;其正文也使用英文,回覆遵循使用者語言。曾安裝舊入口時,請先保留自訂修改,再安裝上面的統一版本,並透過原安裝工具移除舊入口,避免重複顯示。 然後直接告訴 Agent: diff --git a/skills/chatlab-import-cn/SKILL.md b/skills/chatlab-import-cn/SKILL.md new file mode 100644 index 000000000..1c9a759f0 --- /dev/null +++ b/skills/chatlab-import-cn/SKILL.md @@ -0,0 +1,59 @@ +--- +name: chatlab-import-cn +description: >- + Deprecated compatibility entry for local imports from released ChatLab clients; prefer chatlab-import for new installs. 旧版 ChatLab 导入命令的兼容入口,新安装请使用 chatlab-import。 +--- + +# ChatLab Import (Legacy Compatibility) + +This deprecated entry preserves installation commands shipped in ChatLab v0.31.1 through v0.37.1. It includes the complete import workflow below and does not require `chatlab-import` to be installed. For new installations, use `npx skills add ChatLab/ChatLab --skill chatlab-import -g`; migrating is not required to complete an import. + +Import local chat exports through the `clb` CLI. Always preview the exact write. Treat an explicit request to import as authorization to continue after a successful preview; do not ask for a second confirmation. + +Follow the user's requested response language; otherwise use the conversation language, preserving the user's Chinese variant when applicable. Keep commands, field names, IDs, and evidence markers unchanged. + +For analysis of imported records, use `chatlab-analyze` when available. For an unsupported source, use `chatlab-convert` when available; neither skill is required for a supported import. + +## Workflow + +1. Check the CLI and resolve one exact file path: + +```bash +clb --help +``` + +If the CLI is missing, tell the user to install it with `npm install -g chatlab-cli`. Do not install software without approval. Reuse the user's specified file and target. Ask only if the path remains ambiguous, and quote it in every command. + +2. Preview the import without writing: + +```bash +clb import "/absolute/path/to/chat-export.json" --dry-run --json +``` + +If the user selected an existing session, include `--session-id ` in both preview and import. + +3. Summarize only the plan: create or update mode, target session ID, scanned messages, new messages, duplicates, and match method or create reason. Never quote message bodies. + +4. Require `ok: true`, then read the plan from `data` and decide: + +- If the user asked only to preview or inspect, stop without writing. +- If `importMode` is `incremental` and `newMessageCount` is `0`, report that the session is already up to date and stop. +- If `importMode` is `incremental` and there are new messages, continue automatically without another confirmation. +- If `importMode` is `created`, continue automatically. When `createReason` is `ambiguous`, preserve existing sessions by creating a new one and explain this choice in the final result. +- If the result does not identify a safe action, stop and explain what is missing. + +5. When the decision permits writing, run the same command without `--dry-run`, keeping the file, target, and format unchanged: + +```bash +clb import "/absolute/path/to/chat-export.json" --json +``` + +Report the resulting session ID, import mode, new-message count, and duplicate count from the final JSON envelope. + +## Guardrails + +- Never skip the preview, edit ChatLab databases directly, or delete or merge sessions. +- Never reveal a full chat export or message bodies. +- Never invent a file path, parser format, or session ID. +- Follow `error.hint` only when the correction is unambiguous. +- For `FILE_NOT_FOUND` or `INVALID_SESSION_ID`, resolve from available context or request the correct value. For `UNRECOGNIZED_FORMAT`, inspect `clb formats`. For `IMPORT_IN_PROGRESS`, retry once after the active import finishes; if still blocked, report it instead of polling indefinitely. From 61919563385f4e242319cb6e1e9cfba6365db5d2 Mon Sep 17 00:00:00 2001 From: babybitter <2829245700@qq.com> Date: Fri, 4 Sep 2026 04:45:42 +0800 Subject: [PATCH 12/68] feat(analysis): recognize Douyin export emoji codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充抖音导出表情代码识别表,让文本分析把 [微笑]、[星星眼]、[666]、[V5]、[kisskiss] 等带方括号的抖音表情代码当作表情,而不是普通文本词汇。 新增平台表情代码数据模块 bracket-emoji,集中管理微信经典表情映射与从抖音导出中收集到的表情代码名(含此前的 [躺平]、[强壮]);cleanText 按 [代码]/【代码】 形式识别已知平台表情代码并在清洗时剥离。此前非中文名的抖音表情代码(如 [V5]、[ok]、[kisskiss])会被当作普通词汇进入词频统计;语言偏好分析的分词清洗也改用与词频一致的 cleanText,表情代码不再混入高频词,而口头禅与短语统计仍保留 [表情] 原始形式。 补充脱敏合成数据回归测试,覆盖括号格式文本清洗、Node 与 Browser 词频统计以及语言偏好分析;新测试在识别逻辑缺失时会失败。 (cherry picked from commit c80885e0e3c48037dac443232cc327a09ecef9ba) --- packages/core/src/nlp/bracket-emoji.ts | 290 ++++++++++++++++++ packages/core/src/nlp/text-utils.test.ts | 4 + packages/core/src/nlp/text-utils.ts | 90 +----- .../__tests__/browser-word-frequency.test.ts | 20 ++ .../src/query/advanced/languagePreference.ts | 19 +- .../core/src/query/advanced/repeat.test.ts | 18 ++ .../src/nlp/word-frequency.test.ts | 20 ++ 7 files changed, 358 insertions(+), 103 deletions(-) create mode 100644 packages/core/src/nlp/bracket-emoji.ts diff --git a/packages/core/src/nlp/bracket-emoji.ts b/packages/core/src/nlp/bracket-emoji.ts new file mode 100644 index 000000000..b9f4917d9 --- /dev/null +++ b/packages/core/src/nlp/bracket-emoji.ts @@ -0,0 +1,290 @@ +/** + * Platform bracket emoji code tables (pure data, platform-agnostic). + * + * Chat exports render platform-specific emoji as bracketed code names such as + * `[微笑]`. Text analysis must tell these emoji codes apart from user-authored + * text, so each table below lists the code names observed in real exports of + * one platform. The WeChat table keeps its classic semantic mapping to Unicode + * emoji; other platforms only need name recognition for text cleaning. + */ + +/** + * WeChat classic bracket emoji codes mapped to their Unicode equivalents. + * Kept as a mapping because the converted emoji may carry meaning beyond + * plain recognition. + */ +export const WECHAT_BRACKET_EMOJI_MAP: Record = { + 破涕为笑: '😂', + 微笑: '🙂', + 呲牙: '😁', + 大笑: '😄', + 笑哭: '😂', + 流泪: '😢', + 捂脸: '🤦', + 发呆: '😳', + 害羞: '😊', + 调皮: '😜', + 色: '😍', + 惊讶: '😮', + 撇嘴: '😒', + 难过: '😞', + 酷: '😎', + 抓狂: '😫', + 吐: '🤮', + 偷笑: '🤭', + 可爱: '😊', + 白眼: '🙄', + 傲慢: '😤', + 饥饿: '😋', + 困: '😴', + 惊恐: '😱', + 流汗: '😅', + 憨笑: '😄', + 悠闲: '😌', + 奋斗: '💪', + 咒骂: '😡', + 疑问: '❓', + 嘘: '🤫', + 晕: '😵', + 衰: '😞', + 敲打: '👊', + 再见: '👋', + 擦汗: '😅', + 抠鼻: '👃', + 鼓掌: '👏', + 坏笑: '😏', + 左哼哼: '😤', + 右哼哼: '😤', + 哈欠: '🥱', + 鄙视: '😒', + 委屈: '🥺', + 快哭了: '😢', + 阴险: '😏', + 亲亲: '😘', + 吓: '😱', + 可怜: '🥺', + 菜刀: '🔪', + 西瓜: '🍉', + 啤酒: '🍺', + 篮球: '🏀', + 乒乓: '🏓', + 咖啡: '☕', + 饭: '🍚', + 猪头: '🐷', + 玫瑰: '🌹', + 凋谢: '🥀', + 爱心: '❤️', + 心碎: '💔', + 蛋糕: '🎂', + 闪电: '⚡', + 炸弹: '💣', + 刀: '🔪', + 足球: '⚽', + 便便: '💩', + 月亮: '🌙', + 太阳: '☀️', + 礼物: '🎁', + 拥抱: '🤗', + 强: '👍', + 弱: '👎', + 握手: '🤝', + 胜利: '✌️', + 抱拳: '🙏', + 勾引: '☝️', + 拳头: '✊', + 差劲: '👎', + 爱你: '🤟', + NO: '🙅', + OK: '👌', +} + +/** + * Bracket emoji code names collected from Douyin chat exports, including the + * `[躺平]` and `[强壮]` codes covered by earlier system-message separation + * work. + */ +const DOUYIN_BRACKET_EMOJI_NAMES: readonly string[] = [ + '微笑', + '色', + '发呆', + '撇嘴', + '流泪', + '捂脸', + '发怒', + '竖牙', + '哈哈', + '害羞', + '调皮', + '呲牙', + '亲亲', + '比心', + '鼓掌', + '惊讶', + '赞', + '憨笑', + '拥抱', + '抱抱', + '玫瑰', + '尴尬', + '流汗', + '握手', + '星星眼', + '杀马特', + '黄脸干杯', + '裸奔', + '举手', + '挥手', + '绿火花', + '笑脸', + '哭', + '吐舌', + '小狗', + '送花', + '爱心', + '手', + '贴贴', + '灵机一动', + '大笑', + '机智', + '送心', + '666', + '闭嘴', + '看我来', + '我一起加油', + '寒心', + '震惊', + '落', + '囧', + '疑问', + '问号', + '泣不成声', + '小鼓掌', + '大金牙', + '偷笑', + '右看', + '思考', + '吐血', + '可怜', + '得意', + '委屈', + '笑哭', + '抓狂', + '恐惧', + '快哭了', + '翻白眼', + '互粉', + '我想静静', + '鄙视', + '飞吻', + '再见', + '听歌', + '求抱抱', + '绝望的凝视', + '不失礼貌的微笑', + '不看', + '揉开', + '干饭人', + '庆祝', + '呆无辜', + '白眼', + '猪头', + '冷漠', + '暗中观察', + '二哈', + '黑脸', + '展开说说', + '蜜蜂狗', + '呆头', + '摸头', + '腼腆', + '做鬼脸', + '强', + '如花', + '吓', + '惊喜', + '打', + '奋斗', + '吐彩虹', + '大英', + '吓唬', + '惊恐', + '困', + '斜眼', + '阴险', + '咒骂', + '吃瓜', + '群众', + '绿帽子', + '求求了', + '眼含热泪', + '宕', + '气', + '好开心', + '不是吧', + '驾', + '躺平', + '不你不想', + '一头乱麻', + 'kisskiss', + '你不大行', + '嗅', + '苦笑', + '苦涩', + '坚强', + '落泪', + '求机位', + '黄脸', + '点赞', + '精选', + '硬拳', + 'OOK', + '左上', + '抱拳', + '勾引', + '拳头', + '附', + '胜利', + '右边', + '右', + '躲', + '垃圾', + '啤酒', + '蛋糕', + '礼物', + '撒花', + '加一', + '呸', + 'ok', + 'V5', + '发', + '18', + '禁', + '西瓜', + '加鸡腿', + '太阳', + '月亮', + '给跪了', + '打心', + '打call', + '栓Q', + '雪花', + '气球', + '福', + 'candy', + '糖葫芦', + '鞭炮', + '元宝', + '灯笼', + '巧克力', + '威哥', + '棒棒糖', + '纸飞机', + '粽子', + '强壮', +] + +/** + * Every known platform bracket emoji name. Text cleaning recognizes these + * names as emoji placeholders instead of user-authored words. + */ +export const KNOWN_BRACKET_EMOJI_NAMES: ReadonlySet = new Set([...DOUYIN_BRACKET_EMOJI_NAMES]) diff --git a/packages/core/src/nlp/text-utils.test.ts b/packages/core/src/nlp/text-utils.test.ts index fcde4649a..a8d10aaac 100644 --- a/packages/core/src/nlp/text-utils.test.ts +++ b/packages/core/src/nlp/text-utils.test.ts @@ -18,6 +18,10 @@ describe('cleanText', () => { ['removes bracketed chat emoji placeholders before tokenization', '今天[破涕为笑][微笑][呲牙]很好', '今天 很好'], ['removes unknown short bracketed emoji placeholders', '收到[旺柴]马上来', '收到 马上来'], ['removes mapped emoji placeholders with variation selectors', '送你[爱心][太阳]', '送你'], + ['removes exact bracketed Douyin emoji codes', '太累了[躺平],一起[强壮]', '太累了 一起'], + ['removes full-width bracketed emoji codes', '【微笑】收到', '收到'], + ['removes non-CJK Douyin emoji codes as emoji', '真的[V5]呀[666]', '真的 呀'], + ['removes collected Douyin emoji codes in mixed text', '发一个[kisskiss]给你', '发一个 给你'], ['keeps ordinary non-bracketed words', '破涕为笑 微笑 呲牙', '破涕为笑 微笑 呲牙'], ['keeps non-CJK bracketed words as regular text', 'please check [report]', 'please check report'], ] as const diff --git a/packages/core/src/nlp/text-utils.ts b/packages/core/src/nlp/text-utils.ts index 12375e435..ae3820a9d 100644 --- a/packages/core/src/nlp/text-utils.ts +++ b/packages/core/src/nlp/text-utils.ts @@ -2,6 +2,8 @@ * 文本处理工具(纯函数,平台无关) */ +import { KNOWN_BRACKET_EMOJI_NAMES, WECHAT_BRACKET_EMOJI_MAP } from './bracket-emoji' + const EMOJI_REGEX = /[\u{1F300}-\u{1FAFF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu const EMOJI_VARIATION_SELECTOR_REGEX = /\u{FE0F}/gu const PUNCTUATION_REGEX = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~,。!?、;:""''()【】《》…—~·\s]/g @@ -29,91 +31,6 @@ const BRACKET_EMOJI_PLACEHOLDER_REGEX = /(?:\[|【)([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Letter}\p{Number}_-]{1,16})(?:\]|】)/gu const CJK_TEXT_REGEX = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u -const BRACKET_EMOJI_MAP: Record = { - 破涕为笑: '😂', - 微笑: '🙂', - 呲牙: '😁', - 大笑: '😄', - 笑哭: '😂', - 流泪: '😢', - 捂脸: '🤦', - 发呆: '😳', - 害羞: '😊', - 调皮: '😜', - 色: '😍', - 惊讶: '😮', - 撇嘴: '😒', - 难过: '😞', - 酷: '😎', - 抓狂: '😫', - 吐: '🤮', - 偷笑: '🤭', - 可爱: '😊', - 白眼: '🙄', - 傲慢: '😤', - 饥饿: '😋', - 困: '😴', - 惊恐: '😱', - 流汗: '😅', - 憨笑: '😄', - 悠闲: '😌', - 奋斗: '💪', - 咒骂: '😡', - 疑问: '❓', - 嘘: '🤫', - 晕: '😵', - 衰: '😞', - 敲打: '👊', - 再见: '👋', - 擦汗: '😅', - 抠鼻: '👃', - 鼓掌: '👏', - 坏笑: '😏', - 左哼哼: '😤', - 右哼哼: '😤', - 哈欠: '🥱', - 鄙视: '😒', - 委屈: '🥺', - 快哭了: '😢', - 阴险: '😏', - 亲亲: '😘', - 吓: '😱', - 可怜: '🥺', - 菜刀: '🔪', - 西瓜: '🍉', - 啤酒: '🍺', - 篮球: '🏀', - 乒乓: '🏓', - 咖啡: '☕', - 饭: '🍚', - 猪头: '🐷', - 玫瑰: '🌹', - 凋谢: '🥀', - 爱心: '❤️', - 心碎: '💔', - 蛋糕: '🎂', - 闪电: '⚡', - 炸弹: '💣', - 刀: '🔪', - 足球: '⚽', - 便便: '💩', - 月亮: '🌙', - 太阳: '☀️', - 礼物: '🎁', - 拥抱: '🤗', - 强: '👍', - 弱: '👎', - 握手: '🤝', - 胜利: '✌️', - 抱拳: '🙏', - 勾引: '☝️', - 拳头: '✊', - 差劲: '👎', - 爱你: '🤟', - NO: '🙅', - OK: '👌', -} - /** * 清理文本:移除 URL、@提及、表情、标点等 */ @@ -123,7 +40,8 @@ export function cleanText(text: string): string { .replace(MENTION_REGEX, ' ') .replace(SYSTEM_PLACEHOLDER_REGEX, ' ') .replace(BRACKET_EMOJI_PLACEHOLDER_REGEX, (match, name: string) => { - if (BRACKET_EMOJI_MAP[name]) return BRACKET_EMOJI_MAP[name] + if (WECHAT_BRACKET_EMOJI_MAP[name]) return WECHAT_BRACKET_EMOJI_MAP[name] + if (KNOWN_BRACKET_EMOJI_NAMES.has(name)) return ' ' return CJK_TEXT_REGEX.test(name) ? ' ' : match }) .replace(EMOJI_REGEX, ' ') diff --git a/packages/core/src/query/__tests__/browser-word-frequency.test.ts b/packages/core/src/query/__tests__/browser-word-frequency.test.ts index 6874b58dc..8cb4ea0db 100644 --- a/packages/core/src/query/__tests__/browser-word-frequency.test.ts +++ b/packages/core/src/query/__tests__/browser-word-frequency.test.ts @@ -161,6 +161,26 @@ describe('getBrowserWordFrequency', () => { assert.equal(result.totalMessages, 4) }) + it('treats Douyin emoji codes as emoji instead of words', () => { + const insert = raw.prepare('INSERT INTO message (id, sender_id, ts, type, content) VALUES (?, ?, ?, ?, ?)') + insert.run(13, 1, 1300, 0, '[kisskiss]') + insert.run(14, 1, 1400, 5, '[V5]') + + const result = getBrowserWordFrequency(database, { + sessionId: 'session-one', + locale: 'en-US', + topN: 20, + minCount: 1, + posFilterMode: 'all', + enableStopwords: false, + }) + + const words = result.words.map((word) => word.word) + assert.ok(!words.includes('kisskiss')) + assert.ok(!words.includes('v5')) + assert.equal(result.totalMessages, 5) + }) + it('applies member, time, excluded-word, and excluded-message filters', () => { const result = getBrowserWordFrequency(database, { sessionId: 'session-one', diff --git a/packages/core/src/query/advanced/languagePreference.ts b/packages/core/src/query/advanced/languagePreference.ts index a70d20287..470f74807 100644 --- a/packages/core/src/query/advanced/languagePreference.ts +++ b/packages/core/src/query/advanced/languagePreference.ts @@ -7,7 +7,7 @@ import { MessageType, type TimeFilter } from '@openchatlab/shared-types' import type { DatabaseAdapter } from '../../interfaces' -import { isSystemMessageContent, stripMediaPlaceholders, stripVoiceTranscriptionPrefix } from '../../nlp' +import { cleanText, isSystemMessageContent, stripVoiceTranscriptionPrefix } from '../../nlp' import { buildTimeFilter } from '../filters' import { isSystemPlaceholderContent } from './text-filters' @@ -39,24 +39,9 @@ const ADJ_TAGS = new Set(['a', 'an', 'ad', 'ag']) const ADV_TAGS = new Set(['d']) const MODAL_TAGS = new Set(['y', 'e']) -const RE_URL = /https?:\/\/[^\s]+/g -const RE_MENTION = /@[^\s@]+/g -const RE_EMOJI = - /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu -const RE_PUNCTUATION = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~,。!?、;:""''()【】《》…—~·\s]/g const RE_PURE_NUMBER = /^\d+$/ const LANGUAGE_ANALYSIS_MESSAGE_TYPES = [MessageType.TEXT, MessageType.EMOJI].join(', ') -function cleanTextForNlp(text: string): string { - return stripMediaPlaceholders(text) - .replace(RE_URL, ' ') - .replace(RE_MENTION, ' ') - .replace(RE_EMOJI, ' ') - .replace(RE_PUNCTUATION, ' ') - .replace(/\s+/g, ' ') - .trim() -} - function countMatches(text: string, regex: RegExp): number { regex.lastIndex = 0 const m = text.match(regex) @@ -144,7 +129,7 @@ export function getLanguagePreferenceAnalysis(db: DatabaseAdapter, params: Langu if (trimmed.length >= 2) phraseFreq.set(trimmed, (phraseFreq.get(trimmed) || 0) + 1) - const cleaned = cleanTextForNlp(content) + const cleaned = cleanText(content) if (!cleaned) continue if (isChinese && nlpProvider) { diff --git a/packages/core/src/query/advanced/repeat.test.ts b/packages/core/src/query/advanced/repeat.test.ts index 0c83b11bb..9107cd406 100644 --- a/packages/core/src/query/advanced/repeat.test.ts +++ b/packages/core/src/query/advanced/repeat.test.ts @@ -298,4 +298,22 @@ describe('getLanguagePreferenceAnalysis', () => { { content: '[躺平]', count: 2 }, ]) }) + + it('keeps emoji codes as phrases but excludes them from vocabulary', () => { + const db = createRowsDb([ + { memberId: 1, name: 'Alice', content: '[微笑]' }, + { memberId: 1, name: 'Alice', content: '[微笑]' }, + { memberId: 1, name: 'Alice', content: '[V5]' }, + { memberId: 1, name: 'Alice', content: '[V5]' }, + ]) + + const result = getLanguagePreferenceAnalysis(db, { locale: 'zh-CN' }) + const alice = result.members[0] + + assert.deepEqual(alice?.catchphrases, [ + { content: '[微笑]', count: 2 }, + { content: '[V5]', count: 2 }, + ]) + assert.ok((alice?.topWords ?? []).every((word: { word: string }) => !['微笑', 'v5'].includes(word.word))) + }) }) diff --git a/packages/node-runtime/src/nlp/word-frequency.test.ts b/packages/node-runtime/src/nlp/word-frequency.test.ts index cfe9e6ab0..bec9821d4 100644 --- a/packages/node-runtime/src/nlp/word-frequency.test.ts +++ b/packages/node-runtime/src/nlp/word-frequency.test.ts @@ -134,4 +134,24 @@ describe('computeWordFrequency excludeKeywords pushdown', () => { assert.ok(!words.includes('share_noise')) assert.equal(result.totalMessages, 4) }) + + it('treats Douyin emoji codes as emoji instead of vocabulary', () => { + const insert = raw.prepare('INSERT INTO message (id, sender_id, ts, type, content) VALUES (?, ?, ?, ?, ?)') + insert.run(4, 1, 4000, 0, '[kisskiss]') + insert.run(5, 1, 5000, 5, '[V5]') + + const result = computeWordFrequency(db, { + sessionId: 's1', + locale: 'en-US', + minCount: 1, + posFilterMode: 'all', + enableStopwords: false, + }) + + const words = result.words.map((word) => word.word) + assert.ok(!words.includes('kisskiss')) + assert.ok(!words.includes('v5')) + // Emoji messages stay eligible for analysis; they only contribute no words. + assert.equal(result.totalMessages, 5) + }) }) From 16cbdb96b617263ad4232176f73cd07a4d248434 Mon Sep 17 00:00:00 2001 From: digua Date: Sun, 6 Sep 2026 01:19:44 +0800 Subject: [PATCH 13/68] fix(analysis): preserve bracketed annotations in language profiles Reusing emoji cleaning also removed ordinary bracketed labels from language vocabulary. Preserve unknown bracketed text for language profiles while retaining known emoji filtering and the existing word-frequency behavior. Add a regression for annotated chat messages. --- packages/core/src/nlp/text-utils.ts | 4 ++-- .../core/src/query/advanced/languagePreference.ts | 3 ++- packages/core/src/query/advanced/repeat.test.ts | 12 ++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/core/src/nlp/text-utils.ts b/packages/core/src/nlp/text-utils.ts index ae3820a9d..b5b5826b6 100644 --- a/packages/core/src/nlp/text-utils.ts +++ b/packages/core/src/nlp/text-utils.ts @@ -34,7 +34,7 @@ const CJK_TEXT_REGEX = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u /** * 清理文本:移除 URL、@提及、表情、标点等 */ -export function cleanText(text: string): string { +export function cleanText(text: string, options: { preserveUnknownBracketedText?: boolean } = {}): string { return text .replace(URL_REGEX, ' ') .replace(MENTION_REGEX, ' ') @@ -42,7 +42,7 @@ export function cleanText(text: string): string { .replace(BRACKET_EMOJI_PLACEHOLDER_REGEX, (match, name: string) => { if (WECHAT_BRACKET_EMOJI_MAP[name]) return WECHAT_BRACKET_EMOJI_MAP[name] if (KNOWN_BRACKET_EMOJI_NAMES.has(name)) return ' ' - return CJK_TEXT_REGEX.test(name) ? ' ' : match + return !options.preserveUnknownBracketedText && CJK_TEXT_REGEX.test(name) ? ' ' : match }) .replace(EMOJI_REGEX, ' ') .replace(EMOJI_VARIATION_SELECTOR_REGEX, ' ') diff --git a/packages/core/src/query/advanced/languagePreference.ts b/packages/core/src/query/advanced/languagePreference.ts index 470f74807..43e916aee 100644 --- a/packages/core/src/query/advanced/languagePreference.ts +++ b/packages/core/src/query/advanced/languagePreference.ts @@ -129,7 +129,8 @@ export function getLanguagePreferenceAnalysis(db: DatabaseAdapter, params: Langu if (trimmed.length >= 2) phraseFreq.set(trimmed, (phraseFreq.get(trimmed) || 0) + 1) - const cleaned = cleanText(content) + // Preserve ordinary annotations while excluding known emoji from vocabulary. + const cleaned = cleanText(content, { preserveUnknownBracketedText: true }) if (!cleaned) continue if (isChinese && nlpProvider) { diff --git a/packages/core/src/query/advanced/repeat.test.ts b/packages/core/src/query/advanced/repeat.test.ts index 9107cd406..e4a85faa2 100644 --- a/packages/core/src/query/advanced/repeat.test.ts +++ b/packages/core/src/query/advanced/repeat.test.ts @@ -299,6 +299,18 @@ describe('getLanguagePreferenceAnalysis', () => { ]) }) + it('retains ordinary bracketed annotations in vocabulary', () => { + const db = createRowsDb([ + { memberId: 1, name: 'Alice', content: '[需求]今天讨论[微笑]' }, + { memberId: 1, name: 'Alice', content: '[需求]今天讨论[微笑]' }, + ]) + + const result = getLanguagePreferenceAnalysis(db, { locale: 'zh-CN' }) + const words = result.members[0]?.topWords ?? [] + + assert.equal(words.find((item: { word: string; count: number }) => item.word === '需求')?.count, 2) + }) + it('keeps emoji codes as phrases but excludes them from vocabulary', () => { const db = createRowsDb([ { memberId: 1, name: 'Alice', content: '[微笑]' }, From b9903e77e6d3f434d7290190451b98578b6f046e Mon Sep 17 00:00:00 2001 From: digua Date: Tue, 8 Sep 2026 15:31:19 +0800 Subject: [PATCH 14/68] feat(analytics): track deliberate insight tab visits after dwell --- .../src/services/analytics.test.ts | 38 ++++++++ .../node-runtime/src/services/analytics.ts | 26 ++++++ packages/shared-types/index.ts | 2 + .../navigation/NavigationTabsBase.vue | 10 +- src/components/navigation/SectionTabs.vue | 2 + .../useInsightTabAnalytics.test.ts | 91 +++++++++++++++++++ src/composables/useInsightTabAnalytics.ts | 57 ++++++++++++ .../components/insights/GroupChatInsights.vue | 8 +- src/pages/group-chat/index.vue | 9 ++ .../insights/PrivateChatInsights.vue | 8 +- src/pages/private-chat/index.vue | 9 ++ 11 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 src/composables/useInsightTabAnalytics.test.ts create mode 100644 src/composables/useInsightTabAnalytics.ts diff --git a/packages/node-runtime/src/services/analytics.test.ts b/packages/node-runtime/src/services/analytics.test.ts index fb4450e2b..dfb0b694a 100644 --- a/packages/node-runtime/src/services/analytics.test.ts +++ b/packages/node-runtime/src/services/analytics.test.ts @@ -144,6 +144,44 @@ test('track normalizes the shared locale instead of accepting arbitrary event da } }) +test('insight telemetry accepts only public tab categories and never forwards chat identifiers', async (t) => { + const systemDir = createTempSystemDir() + t.after(() => rmSync(systemDir, { recursive: true, force: true })) + const bodies: Array<{ payload: { name: string; url: string; data: Record } }> = [] + t.mock.method(globalThis, 'fetch', async (_input: unknown, init?: RequestInit) => { + bodies.push(JSON.parse(String(init?.body))) + return new Response(null, { status: 204 }) + }) + const service = new AnalyticsService(systemDir, createOptions()) + assert.equal(await service.track('insight_viewed', { chat_type: 'group' }), true) + assert.equal( + await service.track('insight_tab_used', { + chat_type: 'private', + tab_id: 'journey', + session_id: 'secret-chat', + name: 'Alice', + text: 'private message', + }), + true + ) + assert.equal(bodies[0].payload.url, '/insights/group') + assert.equal(bodies[1].payload.url, '/insights/private/journey') + assert.equal(bodies[1].payload.data.chat_type, 'private') + assert.equal(bodies[1].payload.data.tab_id, 'journey') + assert.equal(JSON.stringify(bodies).includes('secret-chat'), false) + assert.equal('name' in bodies[1].payload.data, false) + assert.equal('text' in bodies[1].payload.data, false) + for (const props of [ + { chat_type: 'group', tab_id: 'journey' }, + { chat_type: 'group', tab_id: '/private/path' }, + { chat_type: 'secret-chat', tab_id: 'overview' }, + { chat_type: 'private' }, + ]) + assert.equal(await service.track('insight_tab_used', props), false) + assert.equal(await service.track('insight_viewed', { chat_type: 'secret-chat' }), false) + assert.equal(bodies.length, 2) +}) + test('anonymous id persists across AnalyticsService instances', async () => { const systemDir = createTempSystemDir() const originalFetch = globalThis.fetch diff --git a/packages/node-runtime/src/services/analytics.ts b/packages/node-runtime/src/services/analytics.ts index fe7314f1a..68baf6ff7 100644 --- a/packages/node-runtime/src/services/analytics.ts +++ b/packages/node-runtime/src/services/analytics.ts @@ -52,6 +52,8 @@ const EVENT_NAMES = new Set([ 'chat_import_failed', 'incremental_import_used', 'feature_used', + 'insight_viewed', + 'insight_tab_used', 'ai_setup_completed', 'ai_request_started', 'ai_request_completed', @@ -79,6 +81,19 @@ const FAILURE_REASONS = new Set([ 'unknown', ]) +const INSIGHT_TABS = { + group: new Set(['overview', 'type-analysis', 'time-analysis', 'topic', 'relationship']), + private: new Set([ + 'overview', + 'type-analysis', + 'time-analysis', + 'topic', + 'relationship', + 'journey', + 'language-preference', + ]), +} + const APP_LOCALES = new Set(['zh-CN', 'zh-TW', 'en-US', 'ja-JP']) function getUmamiConfigFromEnv(): AnalyticsServiceOptions['umami'] | undefined { @@ -135,6 +150,15 @@ function normalizeEvent(eventName: string, props: Record = {}): properties.feature_id = props.feature_id } + if (name === 'insight_viewed' || name === 'insight_tab_used') { + if (props.chat_type !== 'group' && props.chat_type !== 'private') return null + properties.chat_type = props.chat_type + if (name === 'insight_tab_used') { + if (typeof props.tab_id !== 'string' || !INSIGHT_TABS[props.chat_type].has(props.tab_id)) return null + properties.tab_id = props.tab_id + } + } + if (name === 'ai_request_completed') { if (typeof props.success !== 'boolean') return null properties.success = props.success @@ -149,6 +173,8 @@ function normalizeEvent(eventName: string, props: Record = {}): } function eventPath(event: NormalizedEvent): string { + if (event.name === 'insight_viewed') return `/insights/${event.properties.chat_type}` + if (event.name === 'insight_tab_used') return `/insights/${event.properties.chat_type}/${event.properties.tab_id}` if (event.name === 'incremental_import_used') return '/import/incremental' if (event.name.startsWith('chat_import_')) return '/import' if (event.name.startsWith('ai_')) return '/ai' diff --git a/packages/shared-types/index.ts b/packages/shared-types/index.ts index 278e47416..c6a6118c0 100644 --- a/packages/shared-types/index.ts +++ b/packages/shared-types/index.ts @@ -1440,6 +1440,8 @@ export type AnalyticsEventName = | 'chat_import_failed' | 'incremental_import_used' | 'feature_used' + | 'insight_viewed' + | 'insight_tab_used' | 'ai_setup_completed' | 'ai_request_started' | 'ai_request_completed' diff --git a/src/components/navigation/NavigationTabsBase.vue b/src/components/navigation/NavigationTabsBase.vue index d597a517d..4dbaef414 100644 --- a/src/components/navigation/NavigationTabsBase.vue +++ b/src/components/navigation/NavigationTabsBase.vue @@ -17,6 +17,7 @@ interface Props { interface Emits { (e: 'update:modelValue', value: string): void (e: 'change', value: string): void + (e: 'select', value: string): void } const props = withDefaults(defineProps(), { @@ -40,6 +41,13 @@ const activeTab = computed({ }, }) +function selectTab(value: string) { + const changed = value !== activeTab.value + activeTab.value = value + // Restore/model changes are not user selections; reselecting the active tab is not a visit. + if (changed) emit('select', value) +} + function updateIndicator() { const activeButton = tabRefs.value[activeTab.value] if (!activeButton || !containerRef.value) return @@ -128,7 +136,7 @@ watch( ? 'text-primary-600 dark:text-primary-400' : 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300', ]" - @click="activeTab = tab.id" + @click="selectTab(tab.id)" > {{ tab.label }} diff --git a/src/components/navigation/SectionTabs.vue b/src/components/navigation/SectionTabs.vue index b2195f041..d2183aed1 100644 --- a/src/components/navigation/SectionTabs.vue +++ b/src/components/navigation/SectionTabs.vue @@ -10,6 +10,7 @@ defineProps<{ const model = defineModel({ required: true }) const emit = defineEmits<{ change: [value: string] + select: [value: string] }>() @@ -22,6 +23,7 @@ const emit = defineEmits<{ size="sm" :bordered="false" @change="emit('change', $event)" + @select="emit('select', $event)" >