feat(小宝): 优化助手入口与预警已读

关键改动:

- 更新 README 项目说明和小宝相关入口命名

- 优化问翻小宝页面与侧边栏测试

- 小宝预警已读状态抑制同签名重复 AI 请求

Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
Script Generator
2026-07-01 13:35:52 +08:00
parent 756621d366
commit d2a07b0da2
10 changed files with 320 additions and 171 deletions

View File

@@ -3,15 +3,17 @@ import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
test('sidebar links wenfan xiaobao as an independent entry before xiaobao warning', () => {
test('sidebar labels AI assistant before smart warning', () => {
const sidebar = readFileSync(join(process.cwd(), 'components/layout/Sidebar.tsx'), 'utf8');
const askIndex = sidebar.indexOf("label: '问翻小宝'");
const warningIndex = sidebar.indexOf("label: '小宝预警'");
const askIndex = sidebar.indexOf("label: 'AI 助手'");
const warningIndex = sidebar.indexOf("label: '智能预警'");
assert.notEqual(askIndex, -1);
assert.notEqual(warningIndex, -1);
assert.ok(askIndex < warningIndex);
assert.doesNotMatch(sidebar, /label: '问翻小宝'/);
assert.doesNotMatch(sidebar, /label: '小宝预警'/);
assert.match(sidebar, /path: '\/wenfan-xiaobao'/);
});
@@ -36,6 +38,7 @@ test('wenfan xiaobao page provides records, chat, and voice input surfaces', ()
assert.match(page, /deleteWenfanConversationRecord/);
assert.match(page, /删除历史记录/);
assert.doesNotMatch(page, /setHistory\(\(current\) => \[question/);
assert.doesNotMatch(page, /<p className="mb-1 text-\[13px\] font-semibold text-\[#171717\]">问翻小宝<\/p>/);
assert.match(page, /入口路径/);
assert.match(page, /必填字段/);
assert.match(page, /状态流转/);

View File

@@ -12,6 +12,7 @@ import {
getXiaobaoWarningUpdateSignature,
isXiaobaoWarningUpdated,
markXiaobaoWarningRead,
shouldSkipXiaobaoRiskInsightRequestForReadState,
sanitizeRiskInsight,
} from './xiaobao-warning-view';
@@ -192,6 +193,31 @@ test('xiaobao warning update state is unread until the current user reads the sa
assert.equal(isXiaobaoWarningUpdated(baseRisk, readStates, 'user-2'), true);
});
test('read current warning state suppresses refresh-triggered AI requests only for the same signature', () => {
const baseRisk = risk();
const readStates = markXiaobaoWarningRead([], 'user-1', baseRisk, '2026-06-30T11:00:00.000Z');
const changedRisk = risk({
signals: {
...baseRisk.signals,
failedTestCount: 1,
},
riskScore: 72,
});
assert.equal(
shouldSkipXiaobaoRiskInsightRequestForReadState(baseRisk, readStates, 'user-1', true),
true,
);
assert.equal(
shouldSkipXiaobaoRiskInsightRequestForReadState(changedRisk, readStates, 'user-1', true),
false,
);
assert.equal(
shouldSkipXiaobaoRiskInsightRequestForReadState(baseRisk, readStates, 'user-1', false),
true,
);
});
test('xiaobao warning update state becomes unread again when risk facts change', () => {
const baseRisk = risk();
const readStates = markXiaobaoWarningRead([], 'user-1', baseRisk, '2026-06-30T11:00:00.000Z');

View File

@@ -87,6 +87,17 @@ export function isXiaobaoWarningUpdated(
return readSignature !== currentSignature;
}
export function shouldSkipXiaobaoRiskInsightRequestForReadState(
risk: XiaobaoVersionRisk,
readStates: XiaobaoWarningReadState[],
userId: string | undefined,
readStateLoaded: boolean,
): boolean {
if (!userId) return false;
if (!readStateLoaded) return true;
return !isXiaobaoWarningUpdated(risk, readStates, userId);
}
export function getXiaobaoWarningUnreadUpdateCount(
risks: XiaobaoVersionRisk[],
readStates: XiaobaoWarningReadState[],