代码修改
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 主面板 —— 多智能体圆桌研讨。
|
||||
*
|
||||
* 包含 4 块:
|
||||
* 1) 顶部 Headline + 挂起/恢复按钮
|
||||
* 1) 顶部 Headline + 推理模型下拉 + 挂起/恢复按钮
|
||||
* 2) Persona 抽屉(点击席位头像后展开,见 `PersonaDrawer.tsx`)
|
||||
* 3) 对话流(初始化占位 + 气泡列表 + ChainOfThought 折叠卡 + markdown)
|
||||
* 4) 底部澄清条 + 用户干预输入 + 「达成共识」入口
|
||||
@@ -68,6 +68,7 @@ export interface Step2PanelProps {
|
||||
selectedModel: string;
|
||||
availableModels: ModelOption[];
|
||||
modelsLoading: boolean;
|
||||
setSelectedModel: React.Dispatch<React.SetStateAction<string>>;
|
||||
// ─── Setters ──────────────────────────────────────────────────
|
||||
setIsRoundTableRunning: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setClarificationDraft: React.Dispatch<React.SetStateAction<string>>;
|
||||
@@ -100,6 +101,7 @@ export function Step2Panel(props: Step2PanelProps) {
|
||||
selectedModel,
|
||||
availableModels,
|
||||
modelsLoading,
|
||||
setSelectedModel,
|
||||
setIsRoundTableRunning,
|
||||
setClarificationDraft,
|
||||
setCustomAgentInput,
|
||||
@@ -129,7 +131,32 @@ export function Step2Panel(props: Step2PanelProps) {
|
||||
</p>
|
||||
</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 ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
*
|
||||
* 重构后的主文件只负责"组合 + 编排":
|
||||
* - 顶栏(步骤切换 + 草稿菜单 + 新建任务 + 设置)
|
||||
* - 左侧栏(任务概览 + 角色面板 + 人工参与)
|
||||
* - 中间视区:Step1Panel / Step2Panel / Step3Panel(`currentStep` 切换)
|
||||
* - 右侧栏(推理模型 / 当前意图摘要 / 澄清记录)
|
||||
* - 左侧栏:Step1/2 为任务概览 + 角色 + 人工参与;Step2 侧栏可整体收起为图标条,含可收起的意图理解
|
||||
* - 中间视区:Step1Panel / Step2Panel / Step3Panel(`currentStep` 切换);Step2 顶栏含推理模型下拉
|
||||
* - 右侧栏:仅 Step1(推理模型 / 当前意图摘要 / 澄清记录);Step2/3 隐藏以让中间区自适应
|
||||
* - 推荐弹窗 + Toast
|
||||
*
|
||||
* 业务状态全部下沉到 hook:
|
||||
@@ -27,6 +27,10 @@ import {
|
||||
Check,
|
||||
CheckCircle,
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
PanelLeftClose,
|
||||
PanelLeftOpen,
|
||||
Clipboard,
|
||||
Clock,
|
||||
Cpu,
|
||||
@@ -423,6 +427,7 @@ export default function RoundtablePlanningPage() {
|
||||
agentNameToRole={step2.agentNameToRole}
|
||||
selectedAgents={step2.selectedAgents}
|
||||
budgetLimit={step2.budgetLimit}
|
||||
intentReady={step1.intentReady}
|
||||
/>
|
||||
|
||||
{/* MIDDLE VIEWPORT */}
|
||||
@@ -472,6 +477,7 @@ export default function RoundtablePlanningPage() {
|
||||
selectedModel={selectedModel}
|
||||
availableModels={availableModels}
|
||||
modelsLoading={modelsLoading}
|
||||
setSelectedModel={setSelectedModel}
|
||||
setIsRoundTableRunning={step2.setIsRoundTableRunning}
|
||||
setClarificationDraft={step2.setClarificationDraft}
|
||||
setCustomAgentInput={step2.setCustomAgentInput}
|
||||
@@ -787,6 +793,7 @@ interface LeftSidebarProps {
|
||||
>;
|
||||
selectedAgents: SelectedAgent[];
|
||||
budgetLimit: number;
|
||||
intentReady: ReturnType<typeof useStep1Intent>["intentReady"];
|
||||
}
|
||||
|
||||
function LeftSidebar({
|
||||
@@ -795,12 +802,31 @@ function LeftSidebar({
|
||||
agentNameToRole,
|
||||
selectedAgents,
|
||||
budgetLimit: _budgetLimit,
|
||||
intentReady,
|
||||
}: LeftSidebarProps) {
|
||||
const [sidebarExpanded, setSidebarExpanded] = useState(true);
|
||||
const isStep2 = currentStep === 2;
|
||||
|
||||
return (
|
||||
<section
|
||||
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 ? (
|
||||
<>
|
||||
{/* Step 3 时只保留「参与与结论贡献」面板;原文件里其它两个 hidden 块未启用,
|
||||
@@ -849,41 +875,67 @@ function LeftSidebar({
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* 任务概览 */}
|
||||
<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>
|
||||
) : currentStep === 2 ? (
|
||||
sidebarExpanded ? (
|
||||
<>
|
||||
<IntentSummaryCard
|
||||
intentReady={intentReady}
|
||||
title="意图理解"
|
||||
collapsible
|
||||
sidebarToggle={{
|
||||
expanded: sidebarExpanded,
|
||||
onToggle: () => setSidebarExpanded((v) => !v),
|
||||
}}
|
||||
/>
|
||||
|
||||
<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]">
|
||||
<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} />
|
||||
<h2 className="text-xs font-bold text-slate-900 uppercase tracking-widest">参与角色</h2>
|
||||
</div>
|
||||
<div className="space-y-2.5 overflow-y-auto flex-1 pr-1 custom-scrollbar">
|
||||
{roles.map((role) => (
|
||||
<RoleRow key={role.id} role={role} agentNameToRole={agentNameToRole} />
|
||||
))}
|
||||
</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="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} />
|
||||
@@ -896,32 +948,317 @@ function LeftSidebar({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 人工参与 */}
|
||||
<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>
|
||||
<HumanParticipationCard />
|
||||
</>
|
||||
)}
|
||||
</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({
|
||||
role,
|
||||
agentNameToRole,
|
||||
@@ -949,14 +1286,7 @@ function RoleRow({
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
{role.type === "human" ? (
|
||||
<Man1Avatar size="h-7 w-7" />
|
||||
) : (
|
||||
getAvatar(
|
||||
role.agentId ? agentNameToRole[role.agentId]?.avatarType ?? "blue" : "blue",
|
||||
"h-7 w-7",
|
||||
)
|
||||
)}
|
||||
<RoleAvatarBadge role={role} agentNameToRole={agentNameToRole} />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-bold text-slate-800 tracking-tight">{role.name}</span>
|
||||
{role.type === "agent" && (
|
||||
@@ -1024,89 +1354,21 @@ function RightSidebar({
|
||||
timelineEntries,
|
||||
triggerToast,
|
||||
}: RightSidebarProps) {
|
||||
if (currentStep === 3) return null;
|
||||
// Step 2 侧栏内容已迁至左侧;Step 3 沿用原逻辑不展示右侧栏。
|
||||
if (currentStep !== 1) return null;
|
||||
return (
|
||||
<section
|
||||
id="right-sidebar"
|
||||
className="w-[300px] shrink-0 flex flex-col gap-4 overflow-y-auto pl-1"
|
||||
>
|
||||
{/* 推理模型选择器 —— 全局,与 Persona 抽屉的 per-seat 覆盖叠加(后者优先)。 */}
|
||||
<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>
|
||||
<ModelSelectorCard
|
||||
selectedModel={selectedModel}
|
||||
setSelectedModel={setSelectedModel}
|
||||
availableModels={availableModels}
|
||||
modelsLoading={modelsLoading}
|
||||
/>
|
||||
|
||||
{/* 当前意图摘要 —— 由 intent agent 输出的结构化 summary 驱动。 */}
|
||||
<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>
|
||||
<IntentSummaryCard intentReady={intentReady} />
|
||||
|
||||
{/* 澄清记录 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]">
|
||||
|
||||
Reference in new issue
Block a user