点击确认

This commit is contained in:
董宏凯 committed 2026-05-27 17:25:10 +08:00
1 parent 1ff8860b98
commit deb9993a64
3 files changed
+77 -47

No files matched your search

@@ -171,18 +171,18 @@ export function Step2Panel(props: Step2PanelProps) {
</option> </option>
))} ))}
</select> </select>
{isRoundTableRunning && isOrchestrating ? ( {isRoundTableRunning ? (
<button <button
onClick={() => { onClick={() => {
setIsRoundTableRunning(false); setIsRoundTableRunning(false);
onTriggerToast("⏸️ 会商模拟暂挂!智能体挂起..."); 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" />
挂起协商 暂停会商
</button> </button>
) : !isRoundTableRunning ? ( ) : (
<button <button
onClick={() => { onClick={() => {
setIsRoundTableRunning(true); setIsRoundTableRunning(true);
@@ -193,7 +193,7 @@ export function Step2Panel(props: Step2PanelProps) {
<Play className="w-3.5 h-3.5 fill-emerald-800" /> <Play className="w-3.5 h-3.5 fill-emerald-800" />
恢复会商 恢复会商
</button> </button>
) : null} )}
</div> </div>
</div> </div>
@@ -280,6 +280,9 @@ 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。 */
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
@@ -1043,7 +1046,11 @@ export function useStep2Orchestration(
useEffect(() => { useEffect(() => {
if (currentStep !== 2) return; if (currentStep !== 2) return;
if (!isRoundTableRunning) { if (!isRoundTableRunning) {
stopOrchestration(); // 干预研审会先写入 pending 再置 false;此时 stop 已在 handler 里触发,
// 避免 effect 再次 stop 误杀随后 flush 启动的 inject 编排。
if (!pendingInjectPromptRef.current) {
stopOrchestration();
}
return; return;
} }
if ( if (
@@ -1061,6 +1068,21 @@ export function useStep2Orchestration(
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [isRoundTableRunning]); }, [isRoundTableRunning]);
// 干预研审:当前轮次停止后 flush 待执行的 inject 编排(编排期间 UI 仍为「恢复会商」)。
useEffect(() => {
if (isOrchestrating) return;
const pending = pendingInjectPromptRef.current;
if (!pending) return;
if (!threadIdsRef.current || !coordinatorNameRef.current) return;
pendingInjectPromptRef.current = null;
triggerToast("您的人工纠偏指令已同步至圆桌节点!");
void runOrchestration(
pending,
threadIdsRef.current,
coordinatorNameRef.current,
);
}, [isOrchestrating, injectFlushToken, runOrchestration, triggerToast]);
// ─── Public handlers ───────────────────────────────────────────── // ─── Public handlers ─────────────────────────────────────────────
const handleInjectToRoundtable = useCallback(() => { const handleInjectToRoundtable = useCallback(() => {
if (!customAgentInput.trim()) return; if (!customAgentInput.trim()) return;
@@ -1068,28 +1090,31 @@ export function useStep2Orchestration(
triggerToast("⚠️ 圆桌尚未就绪,请稍候"); triggerToast("⚠️ 圆桌尚未就绪,请稍候");
return; return;
} }
if (isOrchestratingRef.current) {
triggerToast("⏳ 当前轮次仍在进行中,请等待或暂停后再发送");
return;
}
const userText = customAgentInput.trim(); const userText = customAgentInput.trim();
setCustomAgentInput(""); setCustomAgentInput("");
const prompt = `用户人工干预新指令:${userText}\n\n请结合当前讨论状态,决定是否继续派活或直接给出更新后的结论。`;
pendingInjectPromptRef.current = prompt;
// 与 Step2Panel 右上角「暂停会商 / 恢复会商」联动:立即进入挂起态。
setIsRoundTableRunning(false);
stopOrchestration();
appendDialogue({ appendDialogue({
sender: "用户 (插话干预)", sender: "用户 (插话干预)",
avatarType: "human", avatarType: "human",
content: `💡 (人工指令介入) ${userText}`, content: `💡 (人工指令介入) ${userText}`,
confidence: "最高指令", confidence: "最高指令",
}); });
triggerToast("您的人工纠偏指令已同步至圆桌节点!");
void runOrchestration( if (isOrchestratingRef.current) {
`用户人工干预新指令:${userText}\n\n请结合当前讨论状态,决定是否继续派活或直接给出更新后的结论。`, triggerToast("⏸️ 会商已暂停,当前轮次结束后将处理您的干预指令");
threadIdsRef.current, } else {
coordinatorNameRef.current, triggerToast("⏸️ 会商已暂停,正在处理您的干预指令…");
); }
}, [appendDialogue, customAgentInput, runOrchestration, triggerToast]); setInjectFlushToken((n) => n + 1);
}, [appendDialogue, customAgentInput, stopOrchestration, triggerToast]);
const submitClarification = useCallback( const submitClarification = useCallback(
(rawAnswer: string) => { (rawAnswer: string) => {
@@ -1237,6 +1262,7 @@ export function useStep2Orchestration(
setArtifacts([]); setArtifacts([]);
seatStubMessagesRef.current = {}; seatStubMessagesRef.current = {};
setSeatStubMessages({}); setSeatStubMessages({});
pendingInjectPromptRef.current = null;
deselectArtifact(); deselectArtifact();
step2InitRef.current = false; step2InitRef.current = false;
}, [abortAllInFlight, deselectArtifact, setArtifacts]); }, [abortAllInFlight, deselectArtifact, setArtifacts]);
@@ -351,37 +351,41 @@ export default function RoundtablePlanningPage() {
新建任务 新建任务
</button> </button>
<button {currentStep !== 2 && (
id="start-clear-btn" <>
onClick={() => { <button
setCurrentStep(1); id="start-clear-btn"
triggerToast("已切换至第一阶段:人机问答意图澄清"); onClick={() => {
}} setCurrentStep(1);
className="bg-primary text-white hover:bg-[var(--brand-900)] text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition-all shadow-sm shadow-primary/10 active:scale-95" triggerToast("已切换至第一阶段:人机问答意图澄清");
> }}
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.5"> className="bg-primary text-white hover:bg-[var(--brand-900)] text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition-all shadow-sm shadow-primary/10 active:scale-95"
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> >
</svg> <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.5">
开始澄清 <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</button> </svg>
开始澄清
</button>
<button <button
id="human-intervention-btn" id="human-intervention-btn"
onClick={() => { onClick={() => {
setRoles((prev) => setRoles((prev) =>
prev.map((r) => prev.map((r) =>
r.id === 1 r.id === 1
? { ...r, status: r.status === "在线" ? "等待激活" : "在线" } ? { ...r, status: r.status === "在线" ? "等待激活" : "在线" }
: r, : r,
), ),
); );
triggerToast("指挥专家状态变更!人工介入切换"); triggerToast("指挥专家状态变更!人工介入切换");
}} }}
className="bg-white hover:bg-slate-50 border border-slate-200 text-slate-700 text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition-all dark:border-border dark:bg-secondary dark:text-foreground dark:hover:bg-accent" className="bg-white hover:bg-slate-50 border border-slate-200 text-slate-700 text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition-all dark:border-border dark:bg-secondary dark:text-foreground dark:hover:bg-accent"
> >
<Users className="w-3.5 h-3.5 text-slate-500 dark:text-muted-foreground" /> <Users className="w-3.5 h-3.5 text-slate-500 dark:text-muted-foreground" />
人工介入 人工介入
</button> </button>
</>
)}
<button <button
id="save-draft-header-btn" id="save-draft-header-btn"