代码修改

This commit is contained in:
董宏凯 committed 2026-05-27 19:43:28 +08:00
1 parent b7da062a4c
commit 8bdbf6ab15
3 files changed
+67 -56

No files matched your search

@@ -15,7 +15,7 @@
* *
* 状态全部来自 `useStep2Orchestration` —— 本组件不调任何后端、不持久化。 * 状态全部来自 `useStep2Orchestration` —— 本组件不调任何后端、不持久化。
*/ */
import { useRef } from "react"; import { useCallback, useRef, useState } from "react";
import { import {
Cpu, Cpu,
FileIcon, FileIcon,
@@ -80,7 +80,6 @@ export interface Step2PanelProps {
/** 主聊天里 `present_files` 会在气泡下方渲染卡片;Step 2 沿用同一交互。 */ /** 主聊天里 `present_files` 会在气泡下方渲染卡片;Step 2 沿用同一交互。 */
openArtifactPreview: (threadId: string, path: string) => void; openArtifactPreview: (threadId: string, path: string) => void;
// ─── Setters ────────────────────────────────────────────────── // ─── Setters ──────────────────────────────────────────────────
setIsRoundTableRunning: React.Dispatch<React.SetStateAction<boolean>>;
setClarificationDraft: React.Dispatch<React.SetStateAction<string>>; setClarificationDraft: React.Dispatch<React.SetStateAction<string>>;
setCustomAgentInput: React.Dispatch<React.SetStateAction<string>>; setCustomAgentInput: React.Dispatch<React.SetStateAction<string>>;
setAgentConfigs: React.Dispatch< setAgentConfigs: React.Dispatch<
@@ -88,6 +87,8 @@ export interface Step2PanelProps {
>; >;
setSelectedAgentId: React.Dispatch<React.SetStateAction<number | null>>; setSelectedAgentId: React.Dispatch<React.SetStateAction<number | null>>;
// ─── Handlers ───────────────────────────────────────────────── // ─── Handlers ─────────────────────────────────────────────────
onPauseRoundtable: () => void;
onResumeRoundtable: () => void;
onSubmitClarification: (answer: string) => void; onSubmitClarification: (answer: string) => void;
onInjectToRoundtable: () => void; onInjectToRoundtable: () => void;
onSavePersona: (id: number) => void; onSavePersona: (id: number) => void;
@@ -116,11 +117,12 @@ export function Step2Panel(props: Step2PanelProps) {
seatStubMessages, seatStubMessages,
openArtifactFromStep, openArtifactFromStep,
openArtifactPreview, openArtifactPreview,
setIsRoundTableRunning,
setClarificationDraft, setClarificationDraft,
setCustomAgentInput, setCustomAgentInput,
setAgentConfigs, setAgentConfigs,
setSelectedAgentId, setSelectedAgentId,
onPauseRoundtable,
onResumeRoundtable,
onSubmitClarification, onSubmitClarification,
onInjectToRoundtable, onInjectToRoundtable,
onSavePersona, onSavePersona,
@@ -132,6 +134,15 @@ export function Step2Panel(props: Step2PanelProps) {
const { atBottom: step2AtBottom, scrollToBottom: scrollStep2ToBottom } = const { atBottom: step2AtBottom, scrollToBottom: scrollStep2ToBottom } =
useAutoScroll(step2ScrollRef, [step2RoundtableDialogues]); useAutoScroll(step2ScrollRef, [step2RoundtableDialogues]);
// 干预研审 → 暂停会商联动反馈:点击「干预研审」瞬间让右上角按钮高亮闪烁,
// 让用户清楚看到状态已切换到「恢复会商」。
const [pauseFlash, setPauseFlash] = useState(false);
const handleInject = useCallback(() => {
onInjectToRoundtable();
setPauseFlash(true);
window.setTimeout(() => setPauseFlash(false), 1600);
}, [onInjectToRoundtable]);
const isSandboxStreaming = const isSandboxStreaming =
isOrchestrating || isOrchestrating ||
step2RoundtableDialogues.some((dialogue) => dialogue.streaming === true); step2RoundtableDialogues.some((dialogue) => dialogue.streaming === true);
@@ -177,10 +188,8 @@ export function Step2Panel(props: Step2PanelProps) {
</select> </select>
{isRoundTableRunning ? ( {isRoundTableRunning ? (
<button <button
onClick={() => { type="button"
setIsRoundTableRunning(false); onClick={onPauseRoundtable}
onTriggerToast("⏸️ 会商已暂停");
}}
className="bg-amber-100 hover:bg-amber-200 text-amber-800 text-[10px] font-bold px-2.5 py-1 rounded-xl flex items-center gap-1 transition border border-amber-200/40 shadow-sm" className="bg-amber-100 hover:bg-amber-200 text-amber-800 text-[10px] font-bold px-2.5 py-1 rounded-xl flex items-center gap-1 transition border border-amber-200/40 shadow-sm"
> >
<Pause className="w-3.5 h-3.5" /> <Pause className="w-3.5 h-3.5" />
@@ -188,11 +197,13 @@ export function Step2Panel(props: Step2PanelProps) {
</button> </button>
) : ( ) : (
<button <button
onClick={() => { type="button"
setIsRoundTableRunning(true); onClick={onResumeRoundtable}
onTriggerToast("▶️ 恢复大模型并行博弈与推理决策"); className={`bg-emerald-100 hover:bg-emerald-200 text-emerald-800 text-[10px] font-bold px-2.5 py-1 rounded-xl flex items-center gap-1 transition border border-emerald-200/40 shadow-sm ${
}} pauseFlash
className="bg-emerald-100 hover:bg-emerald-200 text-emerald-800 text-[10px] font-bold px-2.5 py-1 rounded-xl flex items-center gap-1 transition border border-emerald-200/40 shadow-sm" ? "ring-2 ring-emerald-400 ring-offset-1 animate-pulse"
: ""
}`}
> >
<Play className="w-3.5 h-3.5 fill-emerald-800" /> <Play className="w-3.5 h-3.5 fill-emerald-800" />
恢复会商 恢复会商
@@ -324,14 +335,15 @@ export function Step2Panel(props: Step2PanelProps) {
value={customAgentInput} value={customAgentInput}
onChange={(e) => setCustomAgentInput(e.target.value)} onChange={(e) => setCustomAgentInput(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") onInjectToRoundtable(); if (e.key === "Enter") handleInject();
}} }}
placeholder="作为指挥官,您可以随时插话输入指令,对智能体博弈朝向实施人工干预..." placeholder="作为指挥官,您可以随时插话输入指令,对智能体博弈朝向实施人工干预..."
className="flex-1 bg-white border border-slate-200 rounded-xl px-3.5 py-2.5 text-xs focus:outline-none focus:border-primary focus:ring-1 focus:ring-[var(--brand-200)]/50 font-medium" className="flex-1 bg-white border border-slate-200 rounded-xl px-3.5 py-2.5 text-xs focus:outline-none focus:border-primary focus:ring-1 focus:ring-[var(--brand-200)]/50 font-medium"
/> />
<button <button
onClick={onInjectToRoundtable} onClick={handleInject}
title="发送干预指令并暂停会商;点击右上角「恢复会商」继续执行"
className="bg-primary hover:bg-[var(--brand-900)] text-white text-xs font-bold px-4 py-2.5 rounded-xl transition shrink-0 cursor-pointer active:scale-95 shadow-md shadow-primary/10" className="bg-primary hover:bg-[var(--brand-900)] text-white text-xs font-bold px-4 py-2.5 rounded-xl transition shrink-0 cursor-pointer active:scale-95 shadow-md shadow-primary/10"
> >
干预研审 干预研审
@@ -171,9 +171,13 @@ export interface UseStep2OrchestrationReturn {
// ─── Handlers ─────────────────────────────────────────────────────── // ─── Handlers ───────────────────────────────────────────────────────
/** 中断当前编排循环 + 所有 in-flight SSE。 */ /** 中断当前编排循环 + 所有 in-flight SSE。 */
stopOrchestration: () => void; stopOrchestration: () => void;
/** 与 Step2Panel「暂停会商」按钮相同 —— 挂起编排并中止 in-flight SSE。 */
pauseRoundtable: () => void;
/** 与 Step2Panel「恢复会商」按钮相同 —— 恢复自动研讨(含待处理的干预指令)。 */
resumeRoundtable: () => void;
/** 中断所有 in-flight SSE(给新建任务 / 加载草稿用)。 */ /** 中断所有 in-flight SSE(给新建任务 / 加载草稿用)。 */
abortAllInFlight: () => void; abortAllInFlight: () => void;
/** 用户「干预研审」按钮 —— 追加一条人工指令气泡并触发一轮编排。 */ /** 用户「干预研审」—— 先暂停会商,干预指令在恢复后执行。 */
handleInjectToRoundtable: () => void; handleInjectToRoundtable: () => void;
/** 总控澄清回复 —— 把回答拼到 prompt 里再起一轮编排。 */ /** 总控澄清回复 —— 把回答拼到 prompt 里再起一轮编排。 */
submitClarification: (rawAnswer: string) => void; submitClarification: (rawAnswer: string) => void;
@@ -296,9 +300,8 @@ export function useStep2Orchestration(
const orchestrationCancelRef = useRef<boolean>(false); const orchestrationCancelRef = useRef<boolean>(false);
const step2InitRef = useRef<boolean>(false); const step2InitRef = useRef<boolean>(false);
const activeAbortersRef = useRef<Set<AbortController>>(new Set()); const activeAbortersRef = useRef<Set<AbortController>>(new Set());
/** 干预研审:暂停当前轮次后待执行的 leader prompt。 */ /** 干预研审:恢复会商后待执行的 leader prompt。 */
const pendingInjectPromptRef = useRef<string | null>(null); const pendingInjectPromptRef = useRef<string | null>(null);
const [injectFlushToken, setInjectFlushToken] = useState(0);
// selectedModel / agentConfigs / agentNameToRole / threadIds / coordinatorName // selectedModel / agentConfigs / agentNameToRole / threadIds / coordinatorName
// 等等被 runOrchestration 在循环里反复读取。把它们打到 ref 上,可以让 // 等等被 runOrchestration 在循环里反复读取。把它们打到 ref 上,可以让
// runOrchestration 的 useCallback deps 大幅收窄,避免每次模型切换、agentConfigs // runOrchestration 的 useCallback deps 大幅收窄,避免每次模型切换、agentConfigs
@@ -1112,17 +1115,11 @@ export function useStep2Orchestration(
// 挂起 / 恢复:与上面的 init effect 独立。`isRoundTableRunning` toggle 时: // 挂起 / 恢复:与上面的 init effect 独立。`isRoundTableRunning` toggle 时:
// - false → stopOrchestration // - false → stopOrchestration
// - true 且 idle → 触发一次「请继续」的 leader cycle // - true 且 idle → 触发 leader cycle(若有干预指令则优先执行)
// 注意 deps 故意只有 isRoundTableRunning,不再额外加 currentStep —— 回到 Step 2
// 检视讨论时不应该自动 fire 新的 leader 调用。
useEffect(() => { useEffect(() => {
if (currentStep !== 2) return; if (currentStep !== 2) return;
if (!isRoundTableRunning) { if (!isRoundTableRunning) {
// 干预研审会先写入 pending 再置 false;此时 stop 已在 handler 里触发, stopOrchestration();
// 避免 effect 再次 stop 误杀随后 flush 启动的 inject 编排。
if (!pendingInjectPromptRef.current) {
stopOrchestration();
}
return; return;
} }
if ( if (
@@ -1132,6 +1129,18 @@ export function useStep2Orchestration(
!coordinatorNameRef.current !coordinatorNameRef.current
) )
return; return;
const pending = pendingInjectPromptRef.current;
if (pending) {
pendingInjectPromptRef.current = null;
void runOrchestration(
pending,
threadIdsRef.current,
coordinatorNameRef.current,
);
return;
}
void runOrchestration( void runOrchestration(
"用户已恢复研讨,请继续上一轮的协调工作。", "用户已恢复研讨,请继续上一轮的协调工作。",
threadIdsRef.current, threadIdsRef.current,
@@ -1140,20 +1149,16 @@ export function useStep2Orchestration(
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [isRoundTableRunning]); }, [isRoundTableRunning]);
// 干预研审:当前轮次停止后 flush 待执行的 inject 编排(编排期间 UI 仍为「恢复会商」)。 const pauseRoundtable = useCallback(() => {
useEffect(() => { setIsRoundTableRunning(false);
if (isOrchestrating) return; stopOrchestration();
const pending = pendingInjectPromptRef.current; triggerToast("⏸️ 会商已暂停");
if (!pending) return; }, [stopOrchestration, triggerToast]);
if (!threadIdsRef.current || !coordinatorNameRef.current) return;
pendingInjectPromptRef.current = null; const resumeRoundtable = useCallback(() => {
triggerToast("您的人工纠偏指令已同步至圆桌节点!"); setIsRoundTableRunning(true);
void runOrchestration( triggerToast("▶️ 恢复大模型并行博弈与推理决策");
pending, }, [triggerToast]);
threadIdsRef.current,
coordinatorNameRef.current,
);
}, [isOrchestrating, injectFlushToken, runOrchestration, triggerToast]);
// ─── Public handlers ───────────────────────────────────────────── // ─── Public handlers ─────────────────────────────────────────────
const handleInjectToRoundtable = useCallback(() => { const handleInjectToRoundtable = useCallback(() => {
@@ -1166,12 +1171,8 @@ export function useStep2Orchestration(
const userText = customAgentInput.trim(); const userText = customAgentInput.trim();
setCustomAgentInput(""); setCustomAgentInput("");
const prompt = `用户人工干预新指令:${userText}\n\n请结合当前讨论状态,决定是否继续派活或直接给出更新后的结论。`; // 与右上角「暂停会商」完全相同的挂起逻辑。
pendingInjectPromptRef.current = prompt; pauseRoundtable();
// 与 Step2Panel 右上角「暂停会商 / 恢复会商」联动:立即进入挂起态。
setIsRoundTableRunning(false);
stopOrchestration();
appendDialogue({ appendDialogue({
sender: "用户 (插话干预)", sender: "用户 (插话干预)",
@@ -1180,13 +1181,8 @@ export function useStep2Orchestration(
confidence: "最高指令", confidence: "最高指令",
}); });
if (isOrchestratingRef.current) { pendingInjectPromptRef.current = `用户人工干预新指令:${userText}\n\n请结合当前讨论状态,决定是否继续派活或直接给出更新后的结论。`;
triggerToast("⏸️ 会商已暂停,当前轮次结束后将处理您的干预指令"); }, [appendDialogue, customAgentInput, pauseRoundtable, triggerToast]);
} else {
triggerToast("⏸️ 会商已暂停,正在处理您的干预指令…");
}
setInjectFlushToken((n) => n + 1);
}, [appendDialogue, customAgentInput, stopOrchestration, triggerToast]);
const submitClarification = useCallback( const submitClarification = useCallback(
(rawAnswer: string) => { (rawAnswer: string) => {
@@ -1378,6 +1374,8 @@ export function useStep2Orchestration(
setAgentConfigs, setAgentConfigs,
resetInitGate, resetInitGate,
stopOrchestration, stopOrchestration,
pauseRoundtable,
resumeRoundtable,
abortAllInFlight, abortAllInFlight,
handleInjectToRoundtable, handleInjectToRoundtable,
submitClarification, submitClarification,
@@ -496,7 +496,8 @@ export default function RoundtablePlanningPage() {
seatStubMessages={step2.seatStubMessages} seatStubMessages={step2.seatStubMessages}
openArtifactFromStep={step2.openArtifactFromStep} openArtifactFromStep={step2.openArtifactFromStep}
openArtifactPreview={step2.openArtifactPreview} openArtifactPreview={step2.openArtifactPreview}
setIsRoundTableRunning={step2.setIsRoundTableRunning} onPauseRoundtable={step2.pauseRoundtable}
onResumeRoundtable={step2.resumeRoundtable}
setClarificationDraft={step2.setClarificationDraft} setClarificationDraft={step2.setClarificationDraft}
setCustomAgentInput={step2.setCustomAgentInput} setCustomAgentInput={step2.setCustomAgentInput}
setAgentConfigs={step2.setAgentConfigs} setAgentConfigs={step2.setAgentConfigs}
@@ -956,8 +957,8 @@ function LeftSidebar({
<IntentSectionIcon className="h-5 w-5" /> <IntentSectionIcon className="h-5 w-5" />
</CollapsedSidebarSection> </CollapsedSidebarSection>
<CollapsedSidebarSection title="参与角色" className="flex-1 min-h-[120px]"> <CollapsedSidebarSection title="参与角色">
<div className="flex flex-col items-center gap-2 overflow-y-auto max-h-[320px] custom-scrollbar"> <div className="flex flex-col items-center gap-2">
{roles.map((role) => ( {roles.map((role) => (
<RoleAvatarBadge <RoleAvatarBadge
key={role.id} key={role.id}
@@ -1245,7 +1246,7 @@ function CollapsedSidebarSection({
<div <div
title={title} title={title}
className={cn( className={cn(
"w-full bg-white border border-slate-200/50 rounded-2xl p-2 shadow-[0_2px_8px_rgba(0,0,0,0.01)] flex flex-col items-center justify-center", "w-full shrink-0 bg-white border border-slate-200/50 rounded-2xl p-2 shadow-[0_2px_8px_rgba(0,0,0,0.01)] flex flex-col items-center justify-center",
className, className,
)} )}
> >