diff --git a/frontend-web/src/assets/img/blue-header.png b/frontend-web/src/assets/img/blue-header.png
new file mode 100644
index 00000000..5d904a2c
Binary files /dev/null and b/frontend-web/src/assets/img/blue-header.png differ
diff --git a/frontend-web/src/assets/img/dark-blue-header.svg b/frontend-web/src/assets/img/dark-blue-header.svg
new file mode 100644
index 00000000..27a56ee9
--- /dev/null
+++ b/frontend-web/src/assets/img/dark-blue-header.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend-web/src/assets/img/red-header.png b/frontend-web/src/assets/img/red-header.png
new file mode 100644
index 00000000..39e82ed2
Binary files /dev/null and b/frontend-web/src/assets/img/red-header.png differ
diff --git a/frontend-web/src/roundtable-planning/components/Step3ReportMode.tsx b/frontend-web/src/roundtable-planning/components/Step3ReportMode.tsx
index a4c30959..65abc900 100644
--- a/frontend-web/src/roundtable-planning/components/Step3ReportMode.tsx
+++ b/frontend-web/src/roundtable-planning/components/Step3ReportMode.tsx
@@ -9,7 +9,7 @@
*
* 进入即自动开跑(`useStep3Report.start`),右上角「重新绘制」可重跑。
*/
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState, type FormEvent } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import {
@@ -23,6 +23,7 @@ import {
PanelLeftClose,
PanelLeftOpen,
RefreshCw,
+ SendHorizontal,
Sparkles,
X,
} from "lucide-react";
@@ -197,7 +198,7 @@ export function Step3ReportMode({
);
diff --git a/frontend-web/src/roundtable-planning/hooks/useStep3Report.ts b/frontend-web/src/roundtable-planning/hooks/useStep3Report.ts
index 72f05b81..b6102430 100644
--- a/frontend-web/src/roundtable-planning/hooks/useStep3Report.ts
+++ b/frontend-web/src/roundtable-planning/hooks/useStep3Report.ts
@@ -72,11 +72,11 @@ export interface UseStep3ReportReturn {
/** 当前沙箱预览绑定的 thread id(这里恒为报告线程)。 */
activeArtifactThreadId: string | null;
/** 启动绘制(幂等:进行中重复调用会被忽略)。 */
- start: () => void;
+ start: (revisionInstruction?: string) => void;
/** 从草稿快照恢复展示(建合成 stub + 气泡 + 打开沙箱,phase=done,不重新绘制)。 */
hydrateSaved: (snapshot: DraftStep3Snapshot) => void;
/** 重新绘制(先中断在途请求再重跑)。 */
- redraw: () => void;
+ redraw: (revisionInstruction?: string) => void;
/** 中断在途请求(卸载 / 切回普通视图时调用)。 */
abort: () => void;
/** 点击气泡内工具步骤时打开沙箱(与第 2 步 openArtifactFromStep 同义)。 */
@@ -92,8 +92,10 @@ function buildReportPrompt(opts: {
intentReady: ReportIntent | null;
seatDeliveries: ReportSeatDelivery[];
consensusText: string;
+ revisionInstruction?: string;
+ previousHtml?: string;
}): string {
- const { intentReady, seatDeliveries, consensusText } = opts;
+ const { intentReady, seatDeliveries, consensusText, revisionInstruction, previousHtml } = opts;
const parts: string[] = [];
parts.push(
"请基于以下圆桌多智能体研讨的成果,撰写一份图文并茂、以文字叙述为主线的 HTML「方案研讨汇报」," +
@@ -135,6 +137,23 @@ function buildReportPrompt(opts: {
parts.push("");
}
+ const cleanRevisionInstruction = revisionInstruction?.trim();
+ if (cleanRevisionInstruction) {
+ parts.push("# User revision request");
+ parts.push(cleanRevisionInstruction);
+ parts.push("");
+ if (previousHtml?.trim()) {
+ parts.push("# Previous rendered HTML to revise");
+ parts.push(
+ "Use this as the baseline. Preserve parts not contradicted by the revision request, and output a complete updated self-contained HTML file.",
+ );
+ parts.push("```html");
+ parts.push(previousHtml.trim());
+ parts.push("```");
+ parts.push("");
+ }
+ }
+
parts.push(
"请严格遵循你的职责设定:自包含离线(纯 inline SVG 图表、禁止任何外链 CDN)、研讨叙事型结构" +
"(结论+KPI → 背景目标 → 各席位观点 → 综合分析 → 最终共识方案 → 执行路线)、" +
@@ -173,7 +192,7 @@ function reportPhaseLabel(phase: StreamPhase, toolName?: string): string {
export function useStep3Report(
options: UseStep3ReportOptions,
): UseStep3ReportReturn {
- const { intentReady, seatDeliveries, consensusText, selectedModel, onReportSaved } =
+ const { intentReady, seatDeliveries, consensusText, selectedModel, savedReport, onReportSaved } =
options;
// onReportSaved 身份可能每次 render 变 —— 打到 ref,避免进 run() 的 deps 反复重建。
@@ -601,7 +620,7 @@ export function useStep3Report(
runningRef.current = false;
}, []);
- const run = useCallback(async () => {
+ const run = useCallback(async (revisionInstruction?: string) => {
if (runningRef.current) return;
runningRef.current = true;
setError(null);
@@ -632,7 +651,13 @@ export function useStep3Report(
);
setPhase("streaming");
- const prompt = buildReportPrompt({ intentReady, seatDeliveries, consensusText });
+ const prompt = buildReportPrompt({
+ intentReady,
+ seatDeliveries,
+ consensusText,
+ revisionInstruction,
+ previousHtml: savedReport?.html,
+ });
const res = await streamMultiAgent({
agentType: "special",
agentName,
@@ -687,6 +712,7 @@ export function useStep3Report(
intentReady,
seatDeliveries,
consensusText,
+ savedReport?.html,
selectedModel,
appendStreamingDialogue,
appendToDialogue,
@@ -699,8 +725,8 @@ export function useStep3Report(
reconcileStubFromRealFile,
]);
- const start = useCallback(() => {
- void run();
+ const start = useCallback((revisionInstruction?: string) => {
+ void run(revisionInstruction);
}, [run]);
// 从草稿快照恢复:不跑 SSE,用保存的 HTML 重建一条「完成」气泡 + 合成 stub,
@@ -766,9 +792,9 @@ export function useStep3Report(
[abort, buildWriteFileVirtualUrl, openArtifact],
);
- const redraw = useCallback(() => {
+ const redraw = useCallback((revisionInstruction?: string) => {
abort();
- void run();
+ void run(revisionInstruction);
}, [abort, run]);
return {