代码提交
This commit is contained in:
1 parent
250db4de04
commit
1244fb1f2f
14 files changed
+515
-38
No files matched your search
@@ -31,6 +31,7 @@ import {
|
||||
syncPositionOptionsFromRoles,
|
||||
} from "@/position-roundtable/lib/positions";
|
||||
import type { DraftStep3SummarySnapshot } from "@/roundtable-planning/api/drafts";
|
||||
import { EIGHTBF_INTENT_AGENT_ID } from "@/roundtable-planning/api/intent";
|
||||
import type { ThreadIdMap } from "@/roundtable-planning/api/multi-agent";
|
||||
import "@/position-roundtable/styles/intent-understanding.css";
|
||||
import type { Step1PanelProps } from "@/roundtable-planning/components/Step1Panel";
|
||||
@@ -96,6 +97,8 @@ export function EightBfStep1Panel({
|
||||
}: EightBfStep1PanelProps) {
|
||||
const [rightPanelOpen, setRightPanelOpen] = useState(true);
|
||||
const [rightTab, setRightTab] = useState<"agents" | "results">("agents");
|
||||
// 第一步内置任务理解智能体:其「常用问题」显示在输入框左上角,点击直接发送。
|
||||
const { agent: intentAgent } = useAgent(EIGHTBF_INTENT_AGENT_ID);
|
||||
|
||||
const openPanel = (tab: "agents" | "results") => {
|
||||
if (rightPanelOpen && rightTab === tab) {
|
||||
@@ -167,6 +170,8 @@ export function EightBfStep1Panel({
|
||||
modelOptions={modelOptions}
|
||||
selectedModel={selectedModel}
|
||||
onSelectedModelChange={onSelectedModelChange}
|
||||
commonQuestions={intentAgent?.common_questions ?? null}
|
||||
sendCommonQuestionOnClick
|
||||
/>
|
||||
)}
|
||||
intentReady={intentReady}
|
||||
@@ -331,8 +336,26 @@ function EightBfAgentWorkspace({
|
||||
summaryReport: step3Summary,
|
||||
});
|
||||
|
||||
// 第一步绑定的内置任务理解智能体:不在业务链席位里,固定置顶展示,
|
||||
// 供管理员在本面板查看 / 配置其提示词与常用问题。
|
||||
const intentAgent = useMemo(
|
||||
() => agents.find((agent) => agent.id === EIGHTBF_INTENT_AGENT_ID) ?? null,
|
||||
[agents],
|
||||
);
|
||||
const intentAgentMatchesKeyword = useMemo(() => {
|
||||
const keyword = agentKeyword.trim().toLowerCase();
|
||||
if (!keyword) return true;
|
||||
const name = (intentAgent?.name || "8BF 任务理解").toLowerCase();
|
||||
const description = (intentAgent?.description || "").toLowerCase();
|
||||
return name.includes(keyword) || description.includes(keyword);
|
||||
}, [agentKeyword, intentAgent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAgentId && !seats.some((seat) => seat.agent_id === selectedAgentId)) {
|
||||
if (
|
||||
selectedAgentId &&
|
||||
selectedAgentId !== EIGHTBF_INTENT_AGENT_ID &&
|
||||
!seats.some((seat) => seat.agent_id === selectedAgentId)
|
||||
) {
|
||||
setSelectedAgentId(null);
|
||||
}
|
||||
}, [seats, selectedAgentId]);
|
||||
@@ -412,7 +435,12 @@ function EightBfAgentWorkspace({
|
||||
) : (
|
||||
<AgentDetailPanel
|
||||
agent={selectedAgent}
|
||||
agentName={selectedAgent?.name ?? selectedSeat?.name ?? ""}
|
||||
agentName={
|
||||
selectedAgent?.name
|
||||
?? (selectedAgentId === EIGHTBF_INTENT_AGENT_ID
|
||||
? "8BF 任务理解"
|
||||
: selectedSeat?.name ?? "")
|
||||
}
|
||||
onAskQuestion={() => toast.info("请在中间对话区向任务理解智能体发起问题")}
|
||||
showRecentChats={false}
|
||||
avatarSrc={agentConfigLogo}
|
||||
@@ -468,7 +496,7 @@ function EightBfAgentWorkspace({
|
||||
</header>
|
||||
{isLoading && seats.length > 0 ? (
|
||||
<div className="eightbf-roundtable__agent-selector-empty">正在加载智能体…</div>
|
||||
) : filteredSeatAgents.length === 0 ? (
|
||||
) : filteredSeatAgents.length === 0 && !intentAgentMatchesKeyword ? (
|
||||
<div className="eightbf-roundtable__agent-selector-empty">
|
||||
<p>{seatAgents.length ? "没有符合条件的智能体" : "当前岗位尚未配置智能体"}</p>
|
||||
{!seatAgents.length && canConfigureChain && (
|
||||
@@ -477,6 +505,13 @@ function EightBfAgentWorkspace({
|
||||
</div>
|
||||
) : (
|
||||
<div className="eightbf-roundtable__agent-selector-list">
|
||||
{intentAgentMatchesKeyword && (
|
||||
<EightBfAgentCard
|
||||
name={intentAgent?.name || "8BF 任务理解"}
|
||||
description={intentAgent?.description || "第一步「意图识别」的内置任务理解智能体"}
|
||||
onSelect={() => setSelectedAgentId(EIGHTBF_INTENT_AGENT_ID)}
|
||||
/>
|
||||
)}
|
||||
{filteredSeatAgents.map(({ seat, agent }) => (
|
||||
<EightBfAgentRow
|
||||
key={seat.agent_id}
|
||||
@@ -503,6 +538,19 @@ function EightBfAgentRow({
|
||||
}) {
|
||||
const name = agent?.name || seat.name || "未命名智能体";
|
||||
const description = agent?.description || seat.description || "智能体助手";
|
||||
return <EightBfAgentCard name={name} description={description} onSelect={onSelect} />;
|
||||
}
|
||||
|
||||
/** 智能体列表卡片:席位智能体与置顶的内置任务理解智能体共用同一展示结构。 */
|
||||
function EightBfAgentCard({
|
||||
name,
|
||||
description,
|
||||
onSelect,
|
||||
}: {
|
||||
name: string;
|
||||
description: string;
|
||||
onSelect: () => void;
|
||||
}) {
|
||||
const avatarText = firstAgentAvatarChar(name);
|
||||
return (
|
||||
<button type="button" className="eightbf-roundtable__agent-selector-card" onClick={onSelect}>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useSearchParams } from "react-router-dom";
|
||||
import { Loader2, Save, X } from "lucide-react";
|
||||
|
||||
import { getAccountDisplayName, getStoredAuth } from "@/core/auth";
|
||||
import { EIGHTBF_INTENT_AGENT_ID } from "@/roundtable-planning/api/intent";
|
||||
import { EightBfTaskCard } from "@/eightbf-roundtable/components/EightBfTaskCard";
|
||||
import {
|
||||
EightBfStep1Panel,
|
||||
@@ -76,6 +77,9 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
const [selectedChainId, setSelectedChainId] = useState("");
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
// 第二步「人工干预」:开启后每走一步暂停等待用户输入;关闭自动推进。会话内状态,
|
||||
// 新建会话 / 刷新后回到默认关闭(与现状一致)。
|
||||
const [step2Intervention, setStep2Intervention] = useState(false);
|
||||
const configuredChain = useMemo(
|
||||
() => chains.find((chain) => chain.id === mapping?.chainId) ?? null,
|
||||
[chains, mapping?.chainId],
|
||||
@@ -260,6 +264,12 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
// 无 taskId 进入时每次都开全新会话(不回显 anonymous 键下的最新草稿);带 taskId 保持回显现状。
|
||||
skipInitialDraftRestore={!taskId}
|
||||
taskContext={taskContext as Record<string, unknown> | null}
|
||||
// 第一步绑定 8BF 专属内置意图智能体(roundtable-intent 的同源复制,
|
||||
// 提示词独立可调优),不再与通用多智能体会商共用一个 SOUL。
|
||||
intentAgentId={EIGHTBF_INTENT_AGENT_ID}
|
||||
// 页头「人工干预」开关:控制第二步是否每步暂停等待用户输入。
|
||||
step2InterventionEnabled={step2Intervention}
|
||||
onStep2InterventionChange={setStep2Intervention}
|
||||
entryRailTaskId={taskId || null}
|
||||
entryRailTaskCardSlot={taskId ? (
|
||||
<EightBfTaskCard
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
* 全新部署自动播种「通用分析结构 / 递进式论证结构」),支持 启用/停用 筛选
|
||||
* 与 标题 筛选;卡片上可 switch 启停、编辑、删除(二次确认),并显示
|
||||
* 最后修改时间与创建用户名;新增时不填状态(服务端强制默认启用)。
|
||||
* - 详情视图:点击卡片即「选择」该结构(内容回填到高级配置的报告结构
|
||||
* custom_outline),弹窗切换为展示 标题 + Markdown 大纲预览,右上角「返回」回列表。
|
||||
* **点击卡片即「选择」该结构(内容回填到高级配置的报告结构 custom_outline,
|
||||
* 卡片留在列表中高亮为当前使用,不进入详情)**。
|
||||
* - 详情视图:通过卡片右上角「查看大纲结构」眼睛图标进入,展示 标题 + Markdown
|
||||
* 大纲预览,右上角「返回」回列表。
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
@@ -238,8 +240,9 @@ export function WorkflowConfigDialog({
|
||||
toast.error(`「${structure.title}」已停用,请先启用后再选用`);
|
||||
return;
|
||||
}
|
||||
// 点击卡片只做「选择」:内容回填后留在列表视图(卡片高亮为当前使用),
|
||||
// 详情视图通过卡片上的「查看大纲结构」眼睛图标进入。
|
||||
onApply?.(structure.content, structure.structureMode, structure.retrievalDirections, structure.retrievalSkills);
|
||||
setDetailId(structure.id);
|
||||
toast.success(`已选择报告结构「${structure.title}」,并填充到报告结构配置`);
|
||||
};
|
||||
|
||||
@@ -325,7 +328,7 @@ export function WorkflowConfigDialog({
|
||||
</h2>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-slate-500 dark:text-slate-400 sm:text-[13px]">
|
||||
点击卡片选择报告结构(自动填充到报告结构配置);可启停、编辑、删除。
|
||||
点击卡片选择报告结构(自动填充到报告结构配置);点击卡片上的眼睛图标查看大纲详情;可启停、编辑、删除。
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -501,7 +504,7 @@ export function WorkflowConfigDialog({
|
||||
key={structure.id}
|
||||
structure={structure}
|
||||
applied={structure.id === appliedStructureId}
|
||||
onOpen={() => handleSelect(structure)}
|
||||
onSelect={() => handleSelect(structure)}
|
||||
onPreview={(event) => {
|
||||
event.stopPropagation();
|
||||
setDetailId(structure.id);
|
||||
@@ -750,7 +753,7 @@ export function WorkflowConfigDialog({
|
||||
function StructureCard({
|
||||
structure,
|
||||
applied,
|
||||
onOpen,
|
||||
onSelect,
|
||||
onPreview,
|
||||
onDuplicate,
|
||||
onEdit,
|
||||
@@ -760,7 +763,7 @@ function StructureCard({
|
||||
}: {
|
||||
structure: ReportStructure;
|
||||
applied: boolean;
|
||||
onOpen: () => void;
|
||||
onSelect: () => void;
|
||||
onPreview: (event: React.MouseEvent) => void;
|
||||
onDuplicate: (event: React.MouseEvent) => void;
|
||||
onEdit: (event: React.MouseEvent) => void;
|
||||
@@ -775,11 +778,11 @@ function StructureCard({
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onOpen}
|
||||
onClick={onSelect}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
onOpen();
|
||||
onSelect();
|
||||
}
|
||||
}}
|
||||
className={`group relative flex cursor-pointer select-none flex-col justify-between rounded-2xl border p-5 text-left transition-all duration-200 outline-none focus-visible:ring-2 focus-visible:ring-[#F5222D]/30 ${
|
||||
|
||||
@@ -26,6 +26,11 @@ interface PositionIntentInputProps extends Step1InputAreaProps {
|
||||
selectedModel?: string;
|
||||
onSelectedModelChange?: (model: string) => void;
|
||||
commonQuestions?: (string | AgentQuestion)[] | null;
|
||||
/**
|
||||
* 点击常用问题 chip 时直接把问题发送出去(8BF 第一步)。
|
||||
* 缺省 false:仅填充输入框(岗位会商等既有页面的行为保持不变)。
|
||||
*/
|
||||
sendCommonQuestionOnClick?: boolean;
|
||||
/** 仅保留上传、模型选择和发送的紧凑输入区(用于会商第 3 步)。 */
|
||||
hideUtilityActions?: boolean;
|
||||
}
|
||||
@@ -68,6 +73,7 @@ export function PositionIntentInput({
|
||||
selectedModel,
|
||||
onSelectedModelChange,
|
||||
commonQuestions,
|
||||
sendCommonQuestionOnClick = false,
|
||||
hideUtilityActions = false,
|
||||
}: PositionIntentInputProps) {
|
||||
// Files are owned by Step1Panel. This ref only opens the native picker and
|
||||
@@ -86,7 +92,13 @@ export function PositionIntentInput({
|
||||
: (modelOptions[0]?.value ?? "");
|
||||
|
||||
const pickCommonQuestion = (question: AgentQuestion) => {
|
||||
onValueChange(question.prompt);
|
||||
// 直接发送模式:以问题全文为覆盖文本调 onSend,绕过输入框 state 的
|
||||
// 异步更新(同 tick 里 onSend 闭包读不到刚 setValue 的新值)。
|
||||
if (sendCommonQuestionOnClick) {
|
||||
onSend(question.prompt);
|
||||
} else {
|
||||
onValueChange(question.prompt);
|
||||
}
|
||||
setShowMoreQuestions(false);
|
||||
};
|
||||
|
||||
@@ -160,6 +172,7 @@ export function PositionIntentInput({
|
||||
type="button"
|
||||
className="position-roundtable-intent-common-question"
|
||||
title={question.prompt}
|
||||
disabled={sendCommonQuestionOnClick ? isSending : undefined}
|
||||
onClick={() => pickCommonQuestion(question)}
|
||||
>
|
||||
<span aria-hidden="true" />
|
||||
@@ -185,6 +198,7 @@ export function PositionIntentInput({
|
||||
key={`${question.prompt}-${index}`}
|
||||
type="button"
|
||||
title={question.prompt}
|
||||
disabled={sendCommonQuestionOnClick ? isSending : undefined}
|
||||
onClick={() => pickCommonQuestion(question)}
|
||||
>
|
||||
{question.title}
|
||||
@@ -268,7 +282,7 @@ export function PositionIntentInput({
|
||||
<button
|
||||
id="send-message-btn"
|
||||
type="button"
|
||||
onClick={isSending ? onStop : onSend}
|
||||
onClick={() => (isSending ? onStop() : onSend())}
|
||||
disabled={isSending ? false : !canSend}
|
||||
aria-label={isSending ? "暂停生成" : "发送"}
|
||||
title={isSending ? "暂停生成" : "发送"}
|
||||
|
||||
@@ -12,6 +12,9 @@ export type { ClarificationPayload };
|
||||
export const INTENT_AGENT_ID = "roundtable-intent";
|
||||
/** Dedicated Step 1 agent for the position-roundtable workspace. */
|
||||
export const POSITION_INTENT_AGENT_ID = "position-roundtable-intent";
|
||||
/** Dedicated Step 1 agent for the 8BF roundtable workspace (same prompt family
|
||||
* as `roundtable-intent`, independently tunable via the agent config UI). */
|
||||
export const EIGHTBF_INTENT_AGENT_ID = "eightbf-roundtable-intent";
|
||||
|
||||
/**
|
||||
* Task-intent agent (Step 1) API. Mirror of multi-agent.ts but with a single
|
||||
|
||||
@@ -81,7 +81,8 @@ export interface Step1InputAreaProps {
|
||||
isSending: boolean;
|
||||
canSend: boolean;
|
||||
pendingClarification: boolean;
|
||||
onSend: () => void;
|
||||
/** 发送当前输入框内容;传 `overrideText` 时直接发送该文本(常用问题一键发送)。 */
|
||||
onSend: (overrideText?: string) => void;
|
||||
onStop: () => void;
|
||||
onFollowUp: () => void;
|
||||
onSkip: () => void;
|
||||
@@ -236,9 +237,12 @@ export function Step1Panel(props: Step1PanelProps) {
|
||||
setAttachedFiles((prev) => prev.filter((_, i) => i !== idx));
|
||||
};
|
||||
|
||||
const handleSend = () => {
|
||||
if (!inputText.trim() && attachedFiles.length === 0) return;
|
||||
onSendMessage(inputText, attachedFiles.length > 0 ? attachedFiles : undefined);
|
||||
const handleSend = (overrideText?: string) => {
|
||||
// overrideText:常用问题 chip 一键发送——绕过异步的输入框 state,
|
||||
// 直接以问题全文作为本回合消息(仍共享附件与发送后的清空逻辑)。
|
||||
const textToSend = overrideText ?? inputText;
|
||||
if (!textToSend.trim() && attachedFiles.length === 0) return;
|
||||
onSendMessage(textToSend, attachedFiles.length > 0 ? attachedFiles : undefined);
|
||||
handleInputTextChange("");
|
||||
setAttachedFiles([]);
|
||||
};
|
||||
@@ -411,7 +415,7 @@ export function Step1Panel(props: Step1PanelProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
id="send-message-btn"
|
||||
onClick={handleSend}
|
||||
onClick={() => handleSend()}
|
||||
disabled={isIntentSending || !intentThreadId}
|
||||
className="bg-primary text-white hover:bg-[var(--brand-900)] disabled:bg-slate-300 disabled:cursor-not-allowed text-xs font-bold px-4 py-2.5 rounded-xl flex items-center gap-1.5 transition-all shadow-[0_2px_8px_rgba(245,34,45,0.15)] active:scale-95"
|
||||
>
|
||||
|
||||
@@ -422,6 +422,12 @@ export interface UseStep2OrchestrationOptions {
|
||||
seatSkillDirectives?: Record<string, boolean>;
|
||||
/** Per-seat human-validation checkpoints inherited from the selected chain. */
|
||||
humanValidations?: Record<string, boolean>;
|
||||
/**
|
||||
* 全局「人工干预」开关(8BF 页头):开启后第二步每走一步(链条每席位 / DAG 每 stage
|
||||
* 完成)就暂停等待用户输入或恢复;关闭则全自动推进,与既有行为一致。与 per-seat
|
||||
* `humanValidations` 取 OR——任一生效即在最近一步边界暂停。
|
||||
*/
|
||||
manualIntervention?: boolean;
|
||||
/**
|
||||
* 每席位「推理深度覆盖」映射 {agentId: SeatMode | null}(沿用所选业务链条里每个席位的配置)。
|
||||
* 派活该 special 席位时按 agentId 查表,**席位覆盖优先、否则跟随链条级 `selectedMode`**:
|
||||
@@ -593,6 +599,7 @@ export function useStep2Orchestration(
|
||||
selectedMode,
|
||||
seatSkillDirectives = {},
|
||||
humanValidations = {},
|
||||
manualIntervention = false,
|
||||
seatModes = {},
|
||||
gatherFirst = false,
|
||||
orchestrationMode,
|
||||
@@ -746,6 +753,9 @@ export function useStep2Orchestration(
|
||||
// 每席位技能识别强化映射(所选业务链条各席位开关)同样打到 ref,席位调用按 agentId 读最新值。
|
||||
const seatSkillDirectivesRef = useRef<Record<string, boolean>>(seatSkillDirectives);
|
||||
const humanValidationsRef = useRef<Record<string, boolean>>(humanValidations);
|
||||
// 全局「人工干预」开关(8BF 页头):编排循环在每个一步边界读最新值,
|
||||
// 中途切换只影响下一次边界,不打断在途 run。
|
||||
const manualInterventionRef = useRef<boolean>(manualIntervention);
|
||||
// 每席位推理深度覆盖映射(所选业务链条各席位 seatMode)打到 ref,席位调用按 agentId 读最新值;
|
||||
// 覆盖优先、否则跟随 selectedModeRef。
|
||||
const seatModesRef = useRef<Record<string, SeatMode | null>>(seatModes);
|
||||
@@ -800,6 +810,9 @@ export function useStep2Orchestration(
|
||||
useEffect(() => {
|
||||
humanValidationsRef.current = humanValidations;
|
||||
}, [humanValidations]);
|
||||
useEffect(() => {
|
||||
manualInterventionRef.current = manualIntervention;
|
||||
}, [manualIntervention]);
|
||||
useEffect(() => {
|
||||
seatModesRef.current = seatModes;
|
||||
}, [seatModes]);
|
||||
@@ -1681,12 +1694,16 @@ export function useStep2Orchestration(
|
||||
);
|
||||
|
||||
const pauseForHumanValidation = useCallback(
|
||||
(continuation: HumanValidationContinuation, seatNames: string[]) => {
|
||||
(
|
||||
continuation: HumanValidationContinuation,
|
||||
seatNames: string[],
|
||||
source = "业务链条配置",
|
||||
) => {
|
||||
humanValidationContinuationRef.current = continuation;
|
||||
appendDialogue({
|
||||
sender: "系统",
|
||||
avatarType: "blue",
|
||||
content: `已完成 ${seatNames.join("、")} 的输出,按业务链条配置暂停,等待人工校验。可直接点击“恢复会商”继续,或在下方输入修改意见后提交。`,
|
||||
content: `已完成 ${seatNames.join("、")} 的输出,按${source}暂停,等待人工校验。可直接点击“恢复会商”继续,或在下方输入修改意见后提交。`,
|
||||
confidence: "等待人工校验",
|
||||
});
|
||||
setIsRoundTableRunning(false);
|
||||
@@ -2793,18 +2810,27 @@ export function useStep2Orchestration(
|
||||
}
|
||||
if (!chainInvalidNames.includes(seatName)) chainInvalidNames.push(seatName);
|
||||
finishedAllSeats = false;
|
||||
if (humanValidationsRef.current[seat.agent_id]) {
|
||||
pauseForHumanValidation({ mode: "chain", nextSeatIndex: i }, [seatName]);
|
||||
if (manualInterventionRef.current || humanValidationsRef.current[seat.agent_id]) {
|
||||
pauseForHumanValidation(
|
||||
{ mode: "chain", nextSeatIndex: i },
|
||||
[seatName],
|
||||
manualInterventionRef.current && !humanValidationsRef.current[seat.agent_id]
|
||||
? "人工干预开关"
|
||||
: undefined,
|
||||
);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
setConsensusPercentage(Math.min(95, Math.round(((i + 1) / total) * 90)));
|
||||
if (humanValidationsRef.current[seat.agent_id]) {
|
||||
if (manualInterventionRef.current || humanValidationsRef.current[seat.agent_id]) {
|
||||
pauseForHumanValidation(
|
||||
{ mode: "chain", nextSeatIndex: i + 1 },
|
||||
[seatName],
|
||||
manualInterventionRef.current && !humanValidationsRef.current[seat.agent_id]
|
||||
? "人工干预开关"
|
||||
: undefined,
|
||||
);
|
||||
finishedAllSeats = false;
|
||||
break;
|
||||
@@ -3237,8 +3263,11 @@ export function useStep2Orchestration(
|
||||
setConsensusPercentage(
|
||||
Math.min(95, Math.round(((s + 1) / plan.stages.length) * 90)),
|
||||
);
|
||||
const manualInterventionOn = manualInterventionRef.current;
|
||||
const validationSeatNames = stageSeats
|
||||
.filter(({ agentId }) => humanValidationsRef.current[agentId])
|
||||
.filter(
|
||||
({ agentId }) => manualInterventionOn || humanValidationsRef.current[agentId],
|
||||
)
|
||||
.map(({ name }) => name);
|
||||
if (validationSeatNames.length > 0) {
|
||||
pauseForHumanValidation(
|
||||
@@ -3248,6 +3277,7 @@ export function useStep2Orchestration(
|
||||
priorDeliveries: [...priorDeliveries],
|
||||
},
|
||||
validationSeatNames,
|
||||
manualInterventionOn ? "人工干预开关" : undefined,
|
||||
);
|
||||
finishedAllStages = false;
|
||||
break;
|
||||
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { desensitize } from "@/lib/desensitize";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useDesensitizeSubtree } from "@/roundtable-planning/lib/use-desensitize-subtree";
|
||||
@@ -324,6 +325,15 @@ export default function RoundtablePlanningPage({
|
||||
entryRailTaskCardSlot,
|
||||
/** 独立业务页传给意图智能体的隐藏任务上下文。 */
|
||||
taskContext,
|
||||
/** 第一步绑定的内置意图智能体 id(如 8BF 的 eightbf-roundtable-intent)。
|
||||
* 缺省沿用通用会商的 roundtable-intent,原多智能体会商入口行为不变。 */
|
||||
intentAgentId,
|
||||
/**
|
||||
* 8BF 页头「人工干预」开关(受控):开启后第二步每走一步暂停等待用户输入,
|
||||
* 关闭自动推进。提供 onStep2InterventionChange 时才渲染开关,通用会商页不受影响。
|
||||
*/
|
||||
step2InterventionEnabled,
|
||||
onStep2InterventionChange,
|
||||
/** 独立业务页的任务作用域,仅用于左栏记录筛选,不改变原深链逻辑。 */
|
||||
entryRailTaskId,
|
||||
/** 独立业务页的根节点样式类,避免修改原会商样式。 */
|
||||
@@ -367,6 +377,11 @@ export default function RoundtablePlanningPage({
|
||||
entryRailBusinessChainSlot?: ReactNode;
|
||||
entryRailTaskCardSlot?: ReactNode;
|
||||
taskContext?: Record<string, unknown> | null;
|
||||
/** 第一步绑定的内置意图智能体 id;缺省走通用 roundtable-intent。 */
|
||||
intentAgentId?: string;
|
||||
/** 第二步「人工干预」开关(8BF 页头,新建任务按钮左侧)。 */
|
||||
step2InterventionEnabled?: boolean;
|
||||
onStep2InterventionChange?: (enabled: boolean) => void;
|
||||
entryRailTaskId?: string | null;
|
||||
rootClassName?: string;
|
||||
renderStep1?: (props: Step1PanelProps & {
|
||||
@@ -755,6 +770,7 @@ export default function RoundtablePlanningPage({
|
||||
selectedMode: seatMode,
|
||||
seatSkillDirectives,
|
||||
humanValidations,
|
||||
manualIntervention: step2InterventionEnabled ?? false,
|
||||
seatModes,
|
||||
gatherFirst,
|
||||
orchestrationMode,
|
||||
@@ -771,6 +787,7 @@ export default function RoundtablePlanningPage({
|
||||
selectedModel,
|
||||
triggerToast,
|
||||
taskContext,
|
||||
intentAgentId,
|
||||
});
|
||||
|
||||
const taskArtifactSubmission = useTaskArtifactSubmission(
|
||||
@@ -2381,6 +2398,23 @@ export default function RoundtablePlanningPage({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 8BF:第二步「人工干预」开关。开启后第二步每走一步暂停,等待用户输入 /
|
||||
恢复后继续;关闭则自动推进(与原行为一致)。仅独立业务页传入受控回调时渲染。 */}
|
||||
{onStep2InterventionChange && (
|
||||
<label
|
||||
id="step2-intervention-toggle"
|
||||
title="开启后:第二步每完成一步暂停,可在下方输入修改意见或点击“恢复会商”继续;关闭后:自动完成整个研讨"
|
||||
className="bg-white hover:bg-slate-50 border border-[#e2e8f0] text-[#334155] text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-2 transition cursor-pointer select-none dark:border-border dark:bg-secondary dark:text-foreground dark:hover:bg-accent"
|
||||
>
|
||||
<Switch
|
||||
checked={step2InterventionEnabled ?? false}
|
||||
onCheckedChange={onStep2InterventionChange}
|
||||
className="scale-90"
|
||||
/>
|
||||
人工干预
|
||||
</label>
|
||||
)}
|
||||
|
||||
{/* 深链接(taskId)模式:任务固定,把「新建任务」换成「重新分析」——清空会话、对同一任务重做意图分析。 */}
|
||||
{prefillRef.current?.taskId ? (
|
||||
<button
|
||||
|
||||
Reference in new issue
Block a user