代码修改
This commit is contained in:
1 parent
8f93606c81
commit
ff90cac755
2 files changed
+432
-143
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
* Step 2 主面板 —— 多智能体圆桌研讨。
|
* Step 2 主面板 —— 多智能体圆桌研讨。
|
||||||
*
|
*
|
||||||
* 包含 4 块:
|
* 包含 4 块:
|
||||||
* 1) 顶部 Headline + 挂起/恢复按钮
|
* 1) 顶部 Headline + 推理模型下拉 + 挂起/恢复按钮
|
||||||
* 2) Persona 抽屉(点击席位头像后展开,见 `PersonaDrawer.tsx`)
|
* 2) Persona 抽屉(点击席位头像后展开,见 `PersonaDrawer.tsx`)
|
||||||
* 3) 对话流(初始化占位 + 气泡列表 + ChainOfThought 折叠卡 + markdown)
|
* 3) 对话流(初始化占位 + 气泡列表 + ChainOfThought 折叠卡 + markdown)
|
||||||
* 4) 底部澄清条 + 用户干预输入 + 「达成共识」入口
|
* 4) 底部澄清条 + 用户干预输入 + 「达成共识」入口
|
||||||
@@ -68,6 +68,7 @@ export interface Step2PanelProps {
|
|||||||
selectedModel: string;
|
selectedModel: string;
|
||||||
availableModels: ModelOption[];
|
availableModels: ModelOption[];
|
||||||
modelsLoading: boolean;
|
modelsLoading: boolean;
|
||||||
|
setSelectedModel: React.Dispatch<React.SetStateAction<string>>;
|
||||||
// ─── Setters ──────────────────────────────────────────────────
|
// ─── Setters ──────────────────────────────────────────────────
|
||||||
setIsRoundTableRunning: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsRoundTableRunning: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setClarificationDraft: React.Dispatch<React.SetStateAction<string>>;
|
setClarificationDraft: React.Dispatch<React.SetStateAction<string>>;
|
||||||
@@ -100,6 +101,7 @@ export function Step2Panel(props: Step2PanelProps) {
|
|||||||
selectedModel,
|
selectedModel,
|
||||||
availableModels,
|
availableModels,
|
||||||
modelsLoading,
|
modelsLoading,
|
||||||
|
setSelectedModel,
|
||||||
setIsRoundTableRunning,
|
setIsRoundTableRunning,
|
||||||
setClarificationDraft,
|
setClarificationDraft,
|
||||||
setCustomAgentInput,
|
setCustomAgentInput,
|
||||||
@@ -129,7 +131,32 @@ export function Step2Panel(props: Step2PanelProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5 shrink-0">
|
||||||
|
<select
|
||||||
|
id="step2-global-model-select"
|
||||||
|
value={selectedModel}
|
||||||
|
onChange={(e) => setSelectedModel(e.target.value)}
|
||||||
|
disabled={modelsLoading || availableModels.length === 0}
|
||||||
|
title={
|
||||||
|
modelsLoading
|
||||||
|
? "正在加载模型列表…"
|
||||||
|
: availableModels.length === 0
|
||||||
|
? "未在 config.yaml 中配置任何模型"
|
||||||
|
: "切换全局推理模型"
|
||||||
|
}
|
||||||
|
className="max-w-[220px] bg-slate-50/50 border border-slate-200 rounded-xl px-3 py-1.5 text-xs font-bold text-slate-700 cursor-pointer focus:outline-none focus:border-primary focus:ring-1 focus:ring-[var(--brand-200)]/50 disabled:opacity-50 disabled:cursor-not-allowed dark:bg-secondary dark:text-foreground dark:border-border"
|
||||||
|
>
|
||||||
|
{availableModels.length === 0 && (
|
||||||
|
<option value="">{modelsLoading ? "加载中…" : "未配置模型"}</option>
|
||||||
|
)}
|
||||||
|
{availableModels.map((m) => (
|
||||||
|
<option key={m.name} value={m.name}>
|
||||||
|
{m.display_name && m.display_name !== m.name
|
||||||
|
? `${m.display_name} (${m.name})`
|
||||||
|
: m.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
{isRoundTableRunning && isOrchestrating ? (
|
{isRoundTableRunning && isOrchestrating ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
*
|
*
|
||||||
* 重构后的主文件只负责"组合 + 编排":
|
* 重构后的主文件只负责"组合 + 编排":
|
||||||
* - 顶栏(步骤切换 + 草稿菜单 + 新建任务 + 设置)
|
* - 顶栏(步骤切换 + 草稿菜单 + 新建任务 + 设置)
|
||||||
* - 左侧栏(任务概览 + 角色面板 + 人工参与)
|
* - 左侧栏:Step1/2 为任务概览 + 角色 + 人工参与;Step2 侧栏可整体收起为图标条,含可收起的意图理解
|
||||||
* - 中间视区:Step1Panel / Step2Panel / Step3Panel(`currentStep` 切换)
|
* - 中间视区:Step1Panel / Step2Panel / Step3Panel(`currentStep` 切换);Step2 顶栏含推理模型下拉
|
||||||
* - 右侧栏(推理模型 / 当前意图摘要 / 澄清记录)
|
* - 右侧栏:仅 Step1(推理模型 / 当前意图摘要 / 澄清记录);Step2/3 隐藏以让中间区自适应
|
||||||
* - 推荐弹窗 + Toast
|
* - 推荐弹窗 + Toast
|
||||||
*
|
*
|
||||||
* 业务状态全部下沉到 hook:
|
* 业务状态全部下沉到 hook:
|
||||||
@@ -27,6 +27,10 @@ import {
|
|||||||
Check,
|
Check,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
PanelLeftClose,
|
||||||
|
PanelLeftOpen,
|
||||||
Clipboard,
|
Clipboard,
|
||||||
Clock,
|
Clock,
|
||||||
Cpu,
|
Cpu,
|
||||||
@@ -423,6 +427,7 @@ export default function RoundtablePlanningPage() {
|
|||||||
agentNameToRole={step2.agentNameToRole}
|
agentNameToRole={step2.agentNameToRole}
|
||||||
selectedAgents={step2.selectedAgents}
|
selectedAgents={step2.selectedAgents}
|
||||||
budgetLimit={step2.budgetLimit}
|
budgetLimit={step2.budgetLimit}
|
||||||
|
intentReady={step1.intentReady}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* MIDDLE VIEWPORT */}
|
{/* MIDDLE VIEWPORT */}
|
||||||
@@ -472,6 +477,7 @@ export default function RoundtablePlanningPage() {
|
|||||||
selectedModel={selectedModel}
|
selectedModel={selectedModel}
|
||||||
availableModels={availableModels}
|
availableModels={availableModels}
|
||||||
modelsLoading={modelsLoading}
|
modelsLoading={modelsLoading}
|
||||||
|
setSelectedModel={setSelectedModel}
|
||||||
setIsRoundTableRunning={step2.setIsRoundTableRunning}
|
setIsRoundTableRunning={step2.setIsRoundTableRunning}
|
||||||
setClarificationDraft={step2.setClarificationDraft}
|
setClarificationDraft={step2.setClarificationDraft}
|
||||||
setCustomAgentInput={step2.setCustomAgentInput}
|
setCustomAgentInput={step2.setCustomAgentInput}
|
||||||
@@ -787,6 +793,7 @@ interface LeftSidebarProps {
|
|||||||
>;
|
>;
|
||||||
selectedAgents: SelectedAgent[];
|
selectedAgents: SelectedAgent[];
|
||||||
budgetLimit: number;
|
budgetLimit: number;
|
||||||
|
intentReady: ReturnType<typeof useStep1Intent>["intentReady"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function LeftSidebar({
|
function LeftSidebar({
|
||||||
@@ -795,12 +802,31 @@ function LeftSidebar({
|
|||||||
agentNameToRole,
|
agentNameToRole,
|
||||||
selectedAgents,
|
selectedAgents,
|
||||||
budgetLimit: _budgetLimit,
|
budgetLimit: _budgetLimit,
|
||||||
|
intentReady,
|
||||||
}: LeftSidebarProps) {
|
}: LeftSidebarProps) {
|
||||||
|
const [sidebarExpanded, setSidebarExpanded] = useState(true);
|
||||||
|
const isStep2 = currentStep === 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
id="left-sidebar"
|
id="left-sidebar"
|
||||||
className="w-[280px] shrink-0 flex flex-col gap-4 overflow-y-auto pr-1"
|
className={cn(
|
||||||
|
"shrink-0 flex flex-col gap-3 overflow-y-auto pr-1 transition-[width] duration-200 ease-out",
|
||||||
|
isStep2 && !sidebarExpanded ? "w-[56px]" : "w-[280px]",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
|
{isStep2 && !sidebarExpanded && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSidebarExpanded(true)}
|
||||||
|
title="展开侧栏"
|
||||||
|
aria-expanded={false}
|
||||||
|
className="w-full shrink-0 flex h-10 items-center justify-center rounded-2xl border border-slate-200/50 bg-white text-slate-500 hover:text-primary hover:border-primary/30 transition-colors shadow-[0_2px_8px_rgba(0,0,0,0.01)]"
|
||||||
|
>
|
||||||
|
<PanelLeftOpen className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentStep === 3 ? (
|
{currentStep === 3 ? (
|
||||||
<>
|
<>
|
||||||
{/* Step 3 时只保留「参与与结论贡献」面板;原文件里其它两个 hidden 块未启用,
|
{/* Step 3 时只保留「参与与结论贡献」面板;原文件里其它两个 hidden 块未启用,
|
||||||
@@ -849,41 +875,67 @@ function LeftSidebar({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : currentStep === 2 ? (
|
||||||
<>
|
sidebarExpanded ? (
|
||||||
{/* 任务概览 */}
|
<>
|
||||||
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
<IntentSummaryCard
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-2.5">
|
intentReady={intentReady}
|
||||||
<Clipboard className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
title="意图理解"
|
||||||
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">任务概览</h2>
|
collapsible
|
||||||
</div>
|
sidebarToggle={{
|
||||||
<div className="space-y-3">
|
expanded: sidebarExpanded,
|
||||||
<div>
|
onToggle: () => setSidebarExpanded((v) => !v),
|
||||||
<span className="text-[10px] uppercase tracking-wider text-slate-400 font-bold block mb-1">
|
}}
|
||||||
当前节点
|
/>
|
||||||
</span>
|
|
||||||
<div className="flex items-center justify-between gap-2.5">
|
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)] flex-1 flex flex-col min-h-[240px]">
|
||||||
<span className="text-sm font-bold text-slate-800">初始任务</span>
|
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-3">
|
||||||
<span
|
<Users className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
||||||
className={`text-[10px] font-bold px-2 py-0.5 rounded-md border shrink-0 ${
|
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">参与角色</h2>
|
||||||
currentStep === 1
|
</div>
|
||||||
? "text-[#ff9500] bg-orange-50 border-orange-200/50"
|
<div className="space-y-2.5 overflow-y-auto flex-1 pr-1 custom-scrollbar">
|
||||||
: currentStep === 2
|
{roles.map((role) => (
|
||||||
? "text-[var(--brand-900)] bg-[var(--brand-200)] border-red-200/50"
|
<RoleRow key={role.id} role={role} agentNameToRole={agentNameToRole} />
|
||||||
: "text-emerald-600 bg-emerald-50 border-emerald-200/50"
|
))}
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{currentStep === 1 ? "待澄清" : currentStep === 2 ? "研讨中" : "已规划交付"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-slate-500 mt-2.5 leading-relaxed bg-slate-50 border border-slate-100 p-2 rounded-lg">
|
|
||||||
围绕某复杂任务制定规划方案
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 参与角色 */}
|
<HumanParticipationCard />
|
||||||
|
<TaskOverviewCard currentStep={currentStep} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CollapsedSidebarSection title="意图理解">
|
||||||
|
<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">
|
||||||
|
{roles.map((role) => (
|
||||||
|
<RoleAvatarBadge
|
||||||
|
key={role.id}
|
||||||
|
role={role}
|
||||||
|
agentNameToRole={agentNameToRole}
|
||||||
|
size="h-8 w-8"
|
||||||
|
showSpeakingAnimation
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CollapsedSidebarSection>
|
||||||
|
|
||||||
|
<CollapsedSidebarSection title="人工参与">
|
||||||
|
<Man1Avatar size="h-8 w-8" />
|
||||||
|
</CollapsedSidebarSection>
|
||||||
|
|
||||||
|
<CollapsedSidebarSection title="任务概览">
|
||||||
|
<Clipboard className="h-5 w-5 text-primary" strokeWidth={2.5} />
|
||||||
|
</CollapsedSidebarSection>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TaskOverviewCard currentStep={currentStep} />
|
||||||
|
|
||||||
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)] flex-1 flex flex-col min-h-[300px]">
|
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)] flex-1 flex flex-col min-h-[300px]">
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-3">
|
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-3">
|
||||||
<Users className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
<Users className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
||||||
@@ -896,32 +948,317 @@ function LeftSidebar({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 人工参与 */}
|
<HumanParticipationCard />
|
||||||
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-2.5">
|
|
||||||
<Activity className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
|
||||||
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">人工参与</h2>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between p-2 rounded-xl bg-slate-50 border border-slate-100">
|
|
||||||
<div className="flex items-center gap-2.5">
|
|
||||||
<Man1Avatar size="h-7 w-7" />
|
|
||||||
<span className="text-xs font-bold text-slate-800">用户</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<span className="flex h-1.5 w-1.5 relative">
|
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
|
||||||
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-emerald-500"></span>
|
|
||||||
</span>
|
|
||||||
<span className="text-[10px] font-bold text-emerald-600">在线</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TaskOverviewCard({ currentStep }: { currentStep: number }) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
||||||
|
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-2.5">
|
||||||
|
<Clipboard className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
||||||
|
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">任务概览</h2>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<span className="text-[10px] uppercase tracking-wider text-slate-400 font-bold block mb-1">
|
||||||
|
当前节点
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center justify-between gap-2.5">
|
||||||
|
<span className="text-sm font-bold text-slate-800">初始任务</span>
|
||||||
|
<span
|
||||||
|
className={`text-[10px] font-bold px-2 py-0.5 rounded-md border shrink-0 ${
|
||||||
|
currentStep === 1
|
||||||
|
? "text-[#ff9500] bg-orange-50 border-orange-200/50"
|
||||||
|
: currentStep === 2
|
||||||
|
? "text-[var(--brand-900)] bg-[var(--brand-200)] border-red-200/50"
|
||||||
|
: "text-emerald-600 bg-emerald-50 border-emerald-200/50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{currentStep === 1 ? "待澄清" : currentStep === 2 ? "研讨中" : "已规划交付"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-500 mt-2.5 leading-relaxed bg-slate-50 border border-slate-100 p-2 rounded-lg">
|
||||||
|
围绕某复杂任务制定规划方案
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HumanParticipationCard() {
|
||||||
|
return (
|
||||||
|
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
||||||
|
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-2.5">
|
||||||
|
<Activity className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
||||||
|
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">人工参与</h2>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between p-2 rounded-xl bg-slate-50 border border-slate-100">
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<Man1Avatar size="h-7 w-7" />
|
||||||
|
<span className="text-xs font-bold text-slate-800">用户</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span className="flex h-1.5 w-1.5 relative">
|
||||||
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||||
|
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-emerald-500"></span>
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] font-bold text-emerald-600">在线</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelSelectorCardProps {
|
||||||
|
selectedModel: string;
|
||||||
|
setSelectedModel: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
availableModels: ReadonlyArray<{ name: string; display_name?: string }>;
|
||||||
|
modelsLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModelSelectorCard({
|
||||||
|
selectedModel,
|
||||||
|
setSelectedModel,
|
||||||
|
availableModels,
|
||||||
|
modelsLoading,
|
||||||
|
}: ModelSelectorCardProps) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
||||||
|
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-2.5">
|
||||||
|
<Cpu className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
||||||
|
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">推理模型</h2>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
id="global-model-select"
|
||||||
|
value={selectedModel}
|
||||||
|
onChange={(e) => setSelectedModel(e.target.value)}
|
||||||
|
disabled={modelsLoading || availableModels.length === 0}
|
||||||
|
title={
|
||||||
|
modelsLoading
|
||||||
|
? "正在加载模型列表…"
|
||||||
|
: availableModels.length === 0
|
||||||
|
? "未在 config.yaml 中配置任何模型"
|
||||||
|
: "切换全局推理模型;不影响 Persona 抽屉里已设置的 per-seat 覆盖"
|
||||||
|
}
|
||||||
|
className="w-full bg-slate-50/50 border border-slate-100 rounded-xl px-3 py-2 text-xs font-bold text-slate-700 cursor-pointer focus:outline-none focus:border-primary focus:ring-1 focus:ring-[var(--brand-200)]/50 disabled:opacity-50 disabled:cursor-not-allowed dark:bg-secondary dark:text-foreground dark:border-border"
|
||||||
|
>
|
||||||
|
{availableModels.length === 0 && (
|
||||||
|
<option value="">{modelsLoading ? "加载中…" : "未配置模型"}</option>
|
||||||
|
)}
|
||||||
|
{availableModels.map((m) => (
|
||||||
|
<option key={m.name} value={m.name}>
|
||||||
|
{m.display_name && m.display_name !== m.name
|
||||||
|
? `${m.display_name} (${m.name})`
|
||||||
|
: m.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IntentSummaryCardProps {
|
||||||
|
intentReady: ReturnType<typeof useStep1Intent>["intentReady"];
|
||||||
|
title?: string;
|
||||||
|
/** Step 2 侧栏: 收起时仅展示目标,展开后显示约束与假设。 */
|
||||||
|
collapsible?: boolean;
|
||||||
|
/** 侧栏整体收起按钮,放在标题栏右侧。 */
|
||||||
|
sidebarToggle?: {
|
||||||
|
expanded: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function IntentSummaryCard({
|
||||||
|
intentReady,
|
||||||
|
title = "当前意图摘要",
|
||||||
|
collapsible = false,
|
||||||
|
sidebarToggle,
|
||||||
|
}: IntentSummaryCardProps) {
|
||||||
|
const [expanded, setExpanded] = useState(!collapsible);
|
||||||
|
const showFullDetail = !collapsible || expanded;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white border border-slate-200/50 rounded-2xl p-4.5 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
||||||
|
<div className="flex items-center justify-between gap-2 border-b border-slate-100 pb-2.5 mb-2.5">
|
||||||
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
|
<IntentSectionIcon className="w-4 h-4" />
|
||||||
|
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest truncate">{title}</h2>
|
||||||
|
</div>
|
||||||
|
{sidebarToggle && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={sidebarToggle.onToggle}
|
||||||
|
title={sidebarToggle.expanded ? "收起侧栏" : "展开侧栏"}
|
||||||
|
aria-expanded={sidebarToggle.expanded}
|
||||||
|
className="shrink-0 flex h-7 w-7 items-center justify-center rounded-lg text-slate-500 hover:text-primary hover:bg-slate-50 transition-colors"
|
||||||
|
>
|
||||||
|
{sidebarToggle.expanded ? (
|
||||||
|
<PanelLeftClose className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<PanelLeftOpen className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 bg-slate-50/50 border border-slate-100 rounded-xl space-y-3 mr-0.5">
|
||||||
|
{intentReady ? (
|
||||||
|
<>
|
||||||
|
<ul className="text-xs text-slate-700 space-y-2.5 font-normal">
|
||||||
|
<li className="flex items-baseline gap-2 leading-relaxed">
|
||||||
|
<span className="text-primary text-base leading-none">•</span>
|
||||||
|
<span>
|
||||||
|
<b className="text-slate-900 font-bold">目标</b>: {intentReady.objective}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{collapsible && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setExpanded((v) => !v)}
|
||||||
|
className="flex items-center gap-0.5 text-[10px] font-bold text-slate-500 hover:text-primary transition-colors"
|
||||||
|
aria-expanded={expanded}
|
||||||
|
>
|
||||||
|
{expanded ? (
|
||||||
|
<>
|
||||||
|
收起
|
||||||
|
<ChevronUp className="w-3.5 h-3.5" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
展开
|
||||||
|
<ChevronDown className="w-3.5 h-3.5" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{showFullDetail && (
|
||||||
|
<ul className="text-xs text-slate-700 space-y-2.5 font-normal">
|
||||||
|
{intentReady.constraints.map((c, i) => (
|
||||||
|
<li key={`c-${i}`} className="flex items-baseline gap-2 leading-relaxed">
|
||||||
|
<span className="text-primary text-base leading-none">•</span>
|
||||||
|
<span>
|
||||||
|
<b className="text-slate-900 font-bold">约束</b>: {c}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{(intentReady.assumptions ?? []).map((a, i) => (
|
||||||
|
<li key={`a-${i}`} className="flex items-baseline gap-2 leading-relaxed">
|
||||||
|
<span className="text-slate-400 text-base leading-none">•</span>
|
||||||
|
<span className="text-slate-500">
|
||||||
|
<b className="text-slate-700 font-bold">假设</b>: {a}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-slate-400 italic leading-relaxed">
|
||||||
|
等待任务理解智能体澄清完成...
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function IntentSectionIcon({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={cn("text-primary shrink-0", className)}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<circle cx="12" cy="12" r="6" />
|
||||||
|
<circle cx="12" cy="12" r="2" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CollapsedSidebarSection({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<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",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRoleAvatar(
|
||||||
|
role: RoleEntry,
|
||||||
|
agentNameToRole: Record<
|
||||||
|
string,
|
||||||
|
{ speakerId: number; avatarType: string; displayName: string }
|
||||||
|
>,
|
||||||
|
size: string,
|
||||||
|
) {
|
||||||
|
if (role.type === "human") {
|
||||||
|
return <Man1Avatar size={size} />;
|
||||||
|
}
|
||||||
|
return getAvatar(
|
||||||
|
role.agentId ? agentNameToRole[role.agentId]?.avatarType ?? "blue" : "blue",
|
||||||
|
size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RoleAvatarBadge({
|
||||||
|
role,
|
||||||
|
agentNameToRole,
|
||||||
|
size = "h-7 w-7",
|
||||||
|
showSpeakingAnimation = false,
|
||||||
|
}: {
|
||||||
|
role: RoleEntry;
|
||||||
|
agentNameToRole: Record<
|
||||||
|
string,
|
||||||
|
{ speakerId: number; avatarType: string; displayName: string }
|
||||||
|
>;
|
||||||
|
size?: string;
|
||||||
|
showSpeakingAnimation?: boolean;
|
||||||
|
}) {
|
||||||
|
const isOngoing = role.status === "研讨中";
|
||||||
|
const animate = showSpeakingAnimation && isOngoing;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
title={`${role.name} · ${role.status}`}
|
||||||
|
className={cn(
|
||||||
|
"relative shrink-0 rounded-full",
|
||||||
|
animate && "ring-2 ring-primary/50 ring-offset-2 ring-offset-white",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{animate && (
|
||||||
|
<span className="pointer-events-none absolute inset-0 rounded-full border-2 border-primary/35 animate-ping" />
|
||||||
|
)}
|
||||||
|
<div className={cn("relative rounded-full", animate && "animate-pulse")}>
|
||||||
|
{renderRoleAvatar(role, agentNameToRole, size)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function RoleRow({
|
function RoleRow({
|
||||||
role,
|
role,
|
||||||
agentNameToRole,
|
agentNameToRole,
|
||||||
@@ -949,14 +1286,7 @@ function RoleRow({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
{role.type === "human" ? (
|
<RoleAvatarBadge role={role} agentNameToRole={agentNameToRole} />
|
||||||
<Man1Avatar size="h-7 w-7" />
|
|
||||||
) : (
|
|
||||||
getAvatar(
|
|
||||||
role.agentId ? agentNameToRole[role.agentId]?.avatarType ?? "blue" : "blue",
|
|
||||||
"h-7 w-7",
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-xs font-bold text-slate-800 tracking-tight">{role.name}</span>
|
<span className="text-xs font-bold text-slate-800 tracking-tight">{role.name}</span>
|
||||||
{role.type === "agent" && (
|
{role.type === "agent" && (
|
||||||
@@ -1024,89 +1354,21 @@ function RightSidebar({
|
|||||||
timelineEntries,
|
timelineEntries,
|
||||||
triggerToast,
|
triggerToast,
|
||||||
}: RightSidebarProps) {
|
}: RightSidebarProps) {
|
||||||
if (currentStep === 3) return null;
|
// Step 2 侧栏内容已迁至左侧;Step 3 沿用原逻辑不展示右侧栏。
|
||||||
|
if (currentStep !== 1) return null;
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
id="right-sidebar"
|
id="right-sidebar"
|
||||||
className="w-[300px] shrink-0 flex flex-col gap-4 overflow-y-auto pl-1"
|
className="w-[300px] shrink-0 flex flex-col gap-4 overflow-y-auto pl-1"
|
||||||
>
|
>
|
||||||
{/* 推理模型选择器 —— 全局,与 Persona 抽屉的 per-seat 覆盖叠加(后者优先)。 */}
|
<ModelSelectorCard
|
||||||
<div className="bg-white border border-slate-200/50 rounded-2xl p-4 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
selectedModel={selectedModel}
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-2.5">
|
setSelectedModel={setSelectedModel}
|
||||||
<Cpu className="w-4 h-4 text-primary" strokeWidth={2.5} />
|
availableModels={availableModels}
|
||||||
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">推理模型</h2>
|
modelsLoading={modelsLoading}
|
||||||
</div>
|
/>
|
||||||
<select
|
|
||||||
id="global-model-select"
|
|
||||||
value={selectedModel}
|
|
||||||
onChange={(e) => setSelectedModel(e.target.value)}
|
|
||||||
disabled={modelsLoading || availableModels.length === 0}
|
|
||||||
title={
|
|
||||||
modelsLoading
|
|
||||||
? "正在加载模型列表…"
|
|
||||||
: availableModels.length === 0
|
|
||||||
? "未在 config.yaml 中配置任何模型"
|
|
||||||
: "切换全局推理模型;不影响 Persona 抽屉里已设置的 per-seat 覆盖"
|
|
||||||
}
|
|
||||||
className="w-full bg-slate-50/50 border border-slate-100 rounded-xl px-3 py-2 text-xs font-bold text-slate-700 cursor-pointer focus:outline-none focus:border-primary focus:ring-1 focus:ring-[var(--brand-200)]/50 disabled:opacity-50 disabled:cursor-not-allowed dark:bg-secondary dark:text-foreground dark:border-border"
|
|
||||||
>
|
|
||||||
{availableModels.length === 0 && (
|
|
||||||
<option value="">{modelsLoading ? "加载中…" : "未配置模型"}</option>
|
|
||||||
)}
|
|
||||||
{availableModels.map((m) => (
|
|
||||||
<option key={m.name} value={m.name}>
|
|
||||||
{m.display_name && m.display_name !== m.name
|
|
||||||
? `${m.display_name} (${m.name})`
|
|
||||||
: m.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 当前意图摘要 —— 由 intent agent 输出的结构化 summary 驱动。 */}
|
<IntentSummaryCard intentReady={intentReady} />
|
||||||
<div className="bg-white border border-slate-200/50 rounded-2xl p-4.5 shadow-[0_2px_8px_rgba(0,0,0,0.01)]">
|
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 pb-2.5 mb-2.5">
|
|
||||||
<svg className="w-4 h-4 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
||||||
<circle cx="12" cy="12" r="10" />
|
|
||||||
<circle cx="12" cy="12" r="6" />
|
|
||||||
<circle cx="12" cy="12" r="2" />
|
|
||||||
</svg>
|
|
||||||
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">当前意图摘要</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-3 bg-slate-50/50 border border-slate-100 rounded-xl space-y-3 mr-0.5">
|
|
||||||
{intentReady ? (
|
|
||||||
<ul className="text-xs text-slate-700 space-y-2.5 font-normal">
|
|
||||||
<li className="flex items-baseline gap-2 leading-relaxed">
|
|
||||||
<span className="text-primary text-base leading-none">•</span>
|
|
||||||
<span>
|
|
||||||
<b className="text-slate-900 font-bold">目标</b>: {intentReady.objective}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
{intentReady.constraints.map((c, i) => (
|
|
||||||
<li key={`c-${i}`} className="flex items-baseline gap-2 leading-relaxed">
|
|
||||||
<span className="text-primary text-base leading-none">•</span>
|
|
||||||
<span>
|
|
||||||
<b className="text-slate-900 font-bold">约束</b>: {c}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
{(intentReady.assumptions ?? []).map((a, i) => (
|
|
||||||
<li key={`a-${i}`} className="flex items-baseline gap-2 leading-relaxed">
|
|
||||||
<span className="text-slate-400 text-base leading-none">•</span>
|
|
||||||
<span className="text-slate-500">
|
|
||||||
<b className="text-slate-700 font-bold">假设</b>: {a}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
) : (
|
|
||||||
<p className="text-xs text-slate-400 italic leading-relaxed">
|
|
||||||
等待任务理解智能体澄清完成...
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 澄清记录 Timeline */}
|
{/* 澄清记录 Timeline */}
|
||||||
<div className="bg-white border border-slate-200/50 rounded-2xl p-4.5 shadow-[0_2px_8px_rgba(0,0,0,0.01)] flex-1 flex flex-col min-h-[300px]">
|
<div className="bg-white border border-slate-200/50 rounded-2xl p-4.5 shadow-[0_2px_8px_rgba(0,0,0,0.01)] flex-1 flex flex-col min-h-[300px]">
|
||||||
|
|||||||
Reference in new issue
Block a user