feat: AI 会话支持按轮保存完整笔记
This commit is contained in:
+48
-2
@@ -871,9 +871,10 @@ test('AI 会话调用 api.ai.sessions 契约并带上 sessionId 发送', () => {
|
||||
assert.match(shell, /res\.data\.assistantMessageId|ids\.assistantMessageId/);
|
||||
});
|
||||
|
||||
test('AI 每条回答各自提供保存为笔记与复制', () => {
|
||||
test('AI 每条回答各自提供保存此回答与复制', () => {
|
||||
const shell = fs.readFileSync(path.join(__dirname, '..', 'ui', 'reader', 'shell.mjs'), 'utf8');
|
||||
assert.match(shell, /save\.className = 'tb-btn sm ai-msg-save'/);
|
||||
assert.match(shell, /save\.textContent = '保存此回答'/);
|
||||
assert.match(shell, /copy\.className = 'tb-btn ghost sm ai-msg-copy'/);
|
||||
assert.match(shell, /saveAiNote\(aiResultOf\(box\.dataset\.messageId\)\)/);
|
||||
assert.match(shell, /copyAiMessage\(box\.dataset\.messageId\)/);
|
||||
@@ -884,6 +885,51 @@ test('AI 每条回答各自提供保存为笔记与复制', () => {
|
||||
assert.match(shell, /async function saveAiNote\(target\)/);
|
||||
});
|
||||
|
||||
test('AI 会话可按最近轮数或全部保留消息保存为一条笔记', () => {
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'reader.html'), 'utf8');
|
||||
const shell = fs.readFileSync(path.join(__dirname, '..', 'ui', 'reader', 'shell.mjs'), 'utf8');
|
||||
|
||||
assert.match(html, /id="aiSaveBtn"[^>]+title="把当前会话保存为一条笔记"[^>]*>保存会话…</);
|
||||
assert.match(html, /id="aiSessionSaveModal"[\s\S]*id="aiSessionSaveRecent"/);
|
||||
assert.match(html, /id="aiSessionSaveRounds"[^>]+type="number"[^>]+min="1"[^>]+max="100"/);
|
||||
assert.match(html, /id="aiSessionSaveAll"[\s\S]*当前保留会话全部/);
|
||||
assert.match(html, /id="aiSessionSaveRoundCount"[\s\S]*id="aiSessionSaveCharCount"/);
|
||||
|
||||
// 保存时必须另取后端当前保留的全部 200 条,不能复用界面当前 60 条
|
||||
assert.match(shell, /const AI_THREAD_LIMIT = 60/);
|
||||
assert.match(shell, /const AI_SESSION_SAVE_MESSAGE_LIMIT = 200/);
|
||||
assert.match(shell, /sessions\.messages\(wanted, \{ limit: AI_SESSION_SAVE_MESSAGE_LIMIT \}\)/);
|
||||
assert.match(shell, /function aiConversationRounds\(messages\)/);
|
||||
assert.match(shell, /message\.role === 'user'[\s\S]{0,220}message\.role === 'assistant' && user/);
|
||||
assert.match(shell, /return all\.slice\(-count\)/);
|
||||
|
||||
// 范围和 N 记住上次选择,确认前展示真实轮数和最终正文字符数
|
||||
assert.match(shell, /const AI_SESSION_SAVE_SETTING = 'reader\.aiSessionSave'/);
|
||||
assert.match(shell, /api\.settings\.get\(AI_SESSION_SAVE_SETTING, aiSessionSavePreference\)/);
|
||||
assert.match(shell, /api\.settings\.set\(AI_SESSION_SAVE_SETTING, preference\)/);
|
||||
assert.match(shell, /aiSessionSaveRoundCount\.textContent = `\$\{selected\.length\.toLocaleString\(\)\} 轮`/);
|
||||
assert.match(shell, /aiSessionSaveCharCount\.textContent = `\$\{text\.length\.toLocaleString\(\)\} 字`/);
|
||||
|
||||
const saveStart = shell.indexOf('async function saveAiSessionNote()');
|
||||
const saveEnd = shell.indexOf('\nasync function renameAiSession()', saveStart);
|
||||
assert.ok(saveStart >= 0 && saveEnd > saveStart, '缺少保存会话实现');
|
||||
const saveBlock = shell.slice(saveStart, saveEnd);
|
||||
assert.strictEqual((saveBlock.match(/api\.reader\.addNote\(/g) || []).length, 1, '一次会话保存只能调用一次 addNote');
|
||||
assert.match(saveBlock, /title: state\.title/);
|
||||
assert.match(saveBlock, /\n\s+text,/);
|
||||
assert.match(saveBlock, /quote: ''/);
|
||||
assert.match(saveBlock, /locator: null/);
|
||||
assert.match(saveBlock, /已将 \$\{selected\.length\.toLocaleString\(\)\} 轮会话(\$\{text\.length\.toLocaleString\(\)\} 字)保存为一条笔记/);
|
||||
});
|
||||
|
||||
test('AI 会话笔记正文标明问答结构与未保留的较早消息', () => {
|
||||
const shell = fs.readFileSync(path.join(__dirname, '..', 'ui', 'reader', 'shell.mjs'), 'utf8');
|
||||
assert.match(shell, /`## 第 \$\{index \+ 1\} 轮\\n\\n### 问题\\n\\n\$\{question\}\\n\\n### 回答\\n\\n\$\{answer\}`/);
|
||||
assert.match(shell, /更早的 \$\{dropped\.toLocaleString\(\)\} 条消息已不在当前保留会话中,因此未保存/);
|
||||
assert.match(shell, /更早的 \$\{dropped\.toLocaleString\(\)\} 条消息已被会话存储上限淘汰,不会出现在笔记中/);
|
||||
assert.match(shell, /会话正文将完整写入一条笔记,不会拆分,也不会在此处截断/);
|
||||
});
|
||||
|
||||
test('AI 气泡标注上下文范围、停止、裁剪与省略轮次', () => {
|
||||
const shell = fs.readFileSync(path.join(__dirname, '..', 'ui', 'reader', 'shell.mjs'), 'utf8');
|
||||
assert.match(shell, /const parts = \[scopeName\(ref\.scope\)\]/);
|
||||
@@ -892,7 +938,7 @@ test('AI 气泡标注上下文范围、停止、裁剪与省略轮次', () => {
|
||||
assert.match(shell, /if \(message\.truncated\) flags\.push\('内容已裁剪'\)/);
|
||||
assert.match(shell, /if \(message\.cancelled\) flags\.push\('已停止生成'\)/);
|
||||
assert.match(shell, /box\.classList\.add\('ai-msg-error'\)/);
|
||||
assert.match(shell, /较早的 \$\{dropped\.toLocaleString\(\)\} 轮对话已省略/);
|
||||
assert.match(shell, /较早的 \$\{dropped\.toLocaleString\(\)\} 条消息已不再保留/);
|
||||
});
|
||||
|
||||
test('AI 线程里的模型输出全部经 AiMarkdown 渲染,不直接写 innerHTML', () => {
|
||||
|
||||
Reference in New Issue
Block a user