-
需求与影响
-
- 已索引 {view.index.length} 份文档 · 条目 {view.items.length}
- {view.hidden_count > 0 ? ` · 另有 ${view.hidden_count} 条不可见` : ''}
-
- {view.recheck_marks.length > 0 && (
-
- {view.recheck_marks.map((mark) => (
- -
- 阶段 {mark.stage_key} 待复核({mark.reason || mark.source_doc_id})
-
-
- ))}
-
- )}
- {view.items.length > 0 && (
-
- {view.items.slice(0, 8).map((item) => (
- -
- {item.business_key}
- {item.statement ? ` · ${item.statement}` : ''}
-
- ))}
-
- )}
-
- );
-};
-
-const boxStyle: React.CSSProperties = {
- margin: '8px 12px 0',
- padding: '10px 12px',
- borderRadius: 8,
- border: '1px solid var(--border, #e2e8f0)',
- background: 'var(--bg-subtle, #f8fafc)',
- fontSize: 12,
-};
diff --git a/web/src/components/workflow/tracePanelRetired.test.ts b/web/src/components/workflow/tracePanelRetired.test.ts
new file mode 100644
index 00000000..3a26c2d8
--- /dev/null
+++ b/web/src/components/workflow/tracePanelRetired.test.ts
@@ -0,0 +1,10 @@
+import assert from 'node:assert/strict';
+import { readFileSync } from 'node:fs';
+import test from 'node:test';
+
+test('trace panel is not mounted above the chat stream', () => {
+ const app = readFileSync(new URL('../../App.tsx', import.meta.url), 'utf8');
+ assert.equal(app.includes('ProjectTracePanel'), false);
+ const card = readFileSync(new URL('../chat/InteractiveCard.tsx', import.meta.url), 'utf8');
+ assert.equal(card.includes("case 'RECHECK_NOTICE'"), true);
+});
diff --git a/web/src/hooks/useCardActions.ts b/web/src/hooks/useCardActions.ts
index 2fb78fe5..ae61d44b 100644
--- a/web/src/hooks/useCardActions.ts
+++ b/web/src/hooks/useCardActions.ts
@@ -1,4 +1,5 @@
import type { Dispatch, SetStateAction } from 'react';
+import { clearRecheckMark } from '../api/projectTrace';
import {
fetchIntentPlan,
submitIntentPlanDecision,
@@ -69,6 +70,32 @@ export function useCardActions(input: CardActionsInput) {
}
}
+ if (!targetCard) return;
+
+ if (targetCard.cardType === 'RECHECK_NOTICE' && actionId === 'recheck_no_change') {
+ const projectId = Number(payload?.project_id || currentProjectId);
+ const markId = Number(payload?.mark_id);
+ const serverId = messages.find((m) => m.interactiveCard?.cardId === cardId)?.serverId;
+ void clearRecheckMark(projectId, markId, 'no_change', serverId)
+ .then(() => {
+ setMessages((prev) => resolveCardInMessages(prev, cardId, '已标记为无需变更', currentUser.name));
+ })
+ .catch(() => {
+ setMessages((prev) => [
+ ...prev,
+ {
+ id: 'recheck-fail-' + Date.now(),
+ sessionId: activeSessionId,
+ role: 'assistant',
+ authorName: '项目助手',
+ content: '没能关闭这条待复核,请稍后再试。',
+ timestamp: new Date().toTimeString().substring(0, 5),
+ },
+ ]);
+ });
+ return;
+ }
+
if (isPlanConfirmCard(targetCard, payload)) {
const planId = String(payload?.plan_id || targetCard?.planId || '');
const expectedRevision = Number(payload?.expected_revision || targetCard?.planRevision || 0);
diff --git a/web/src/types/workbench.ts b/web/src/types/workbench.ts
index b83b0b0a..ba171e69 100644
--- a/web/src/types/workbench.ts
+++ b/web/src/types/workbench.ts
@@ -73,7 +73,8 @@ export type InteractiveCardType =
| 'PLAN_CONFIRM'
| 'SLOT_FILLING'
| 'DIFF_PROPOSAL'
- | 'REMINDER_NOTICE';
+ | 'REMINDER_NOTICE'
+ | 'RECHECK_NOTICE';
export type CardActionStatus =
| 'active'