代码修改

This commit is contained in:
董宏凯 authored and xuhd committed 2026-05-28 18:29:10 +08:00
1 parent 8c53594df7
commit 37a9cca82d
3 files changed
+67 -56

No files matched your search

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