191 lines
6.9 KiB
TypeScript
191 lines
6.9 KiB
TypeScript
/**
|
|
* Persona 抽屉。
|
|
*
|
|
* Step 2 圆桌可视化里点击智能体头像后展开。允许覆写该席位的:
|
|
* - System Prompt(本地记录,后端覆写未开放)
|
|
* - 推理模型(下一轮派活时由 `runOrchestration` 读 `agentConfigs[id].model`)
|
|
* - Temperature(本地记录)
|
|
*
|
|
* 「参数覆盖并重启动算研讨」按钮 = `onSave`,不会立即重启编排,只更新本地配置;
|
|
* 真正生效在该席位下一次被 leader 派活时。
|
|
*/
|
|
import { X, Sparkles } from "lucide-react";
|
|
|
|
import { getAvatar } from "@/roundtable-planning/components/Avatars";
|
|
import type {
|
|
AgentConfig,
|
|
} from "@/roundtable-planning/hooks/useStep2Orchestration";
|
|
import type {
|
|
RoleEntry,
|
|
} from "@/roundtable-planning/lib/roundtable-constants";
|
|
|
|
interface ModelOption {
|
|
name: string;
|
|
display_name?: string;
|
|
}
|
|
|
|
export interface PersonaDrawerProps {
|
|
seatId: number;
|
|
roles: RoleEntry[];
|
|
agentConfigs: Record<number, AgentConfig>;
|
|
setAgentConfigs: React.Dispatch<
|
|
React.SetStateAction<Record<number, AgentConfig>>
|
|
>;
|
|
selectedModel: string;
|
|
availableModels: ModelOption[];
|
|
modelsLoading: boolean;
|
|
onClose: () => void;
|
|
onSave: (id: number) => void;
|
|
}
|
|
|
|
/** 席位 id → 头像配色(与圆桌可视化里的硬编码保持一致)。 */
|
|
function avatarTypeForSeat(seatId: number): string {
|
|
switch (seatId) {
|
|
case 2:
|
|
return "blue";
|
|
case 3:
|
|
return "purple";
|
|
case 4:
|
|
return "emerald";
|
|
case 5:
|
|
return "human";
|
|
case 6:
|
|
return "amber";
|
|
case 7:
|
|
return "pink";
|
|
default:
|
|
return "blue";
|
|
}
|
|
}
|
|
|
|
export function PersonaDrawer({
|
|
seatId,
|
|
roles,
|
|
agentConfigs,
|
|
setAgentConfigs,
|
|
selectedModel,
|
|
availableModels,
|
|
modelsLoading,
|
|
onClose,
|
|
onSave,
|
|
}: PersonaDrawerProps) {
|
|
const cfg = agentConfigs[seatId];
|
|
const roleName = roles.find((r) => r.id === seatId)?.name ?? "专家智能体";
|
|
|
|
return (
|
|
<div className="mx-6 mb-4 p-4.5 bg-gradient-to-r from-[var(--brand-200)]/50 via-[var(--brand-200)]/15 to-white border-2 border-primary rounded-2xl shadow-[0_8px_30px_rgba(245,34,45,0.06)] relative animate-fade-in">
|
|
<button
|
|
onClick={onClose}
|
|
className="absolute top-3.5 right-3.5 text-slate-400 hover:text-slate-650 hover:bg-slate-100 p-1.5 rounded-full transition cursor-pointer"
|
|
title="关闭 Override"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
|
|
<div className="flex flex-col lg:flex-row gap-5 text-xs text-slate-600">
|
|
<div className="flex-1 space-y-3">
|
|
<div className="flex items-center gap-3">
|
|
{getAvatar(avatarTypeForSeat(seatId), "h-10 w-10")}
|
|
<div>
|
|
<div className="flex items-center gap-1.5">
|
|
<h4 className="text-sm font-extrabold text-slate-900">
|
|
{roleName} 人设专有策略调制
|
|
</h4>
|
|
<span className="text-[9px] font-black bg-[var(--brand-200)] text-primary px-2 py-0.5 rounded-full scale-90">
|
|
系统就绪
|
|
</span>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 font-bold">
|
|
配置此席位智能体的运行大语言模型及系统 System Prompt 指令
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<label className="text-[9px] font-black text-slate-500 uppercase block tracking-wider">
|
|
该席位系统提示词指令 override (System Instruction)
|
|
</label>
|
|
<textarea
|
|
value={cfg?.prompt || ""}
|
|
onChange={(e) => {
|
|
const val = e.target.value;
|
|
setAgentConfigs((prev) => ({
|
|
...prev,
|
|
[seatId]: { ...prev[seatId], prompt: val },
|
|
}));
|
|
}}
|
|
className="w-full text-xs text-slate-700 bg-white border border-slate-200 focus:border-primary focus:ring-1 focus:ring-[var(--brand-200)]/50 rounded-xl px-3 py-2.5 h-[65px] resize-none focus:outline-none font-medium leading-relaxed"
|
|
placeholder="请输入覆盖的专家提示词内容..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="w-full lg:w-[260px] flex flex-col justify-between gap-3 border-t lg:border-t-0 lg:border-l border-slate-100 lg:pl-5 pt-3.5 lg:pt-0">
|
|
<div className="space-y-3">
|
|
<div className="space-y-1">
|
|
<label className="text-[9px] font-black text-slate-500 uppercase tracking-wider block">
|
|
推理本源模型
|
|
</label>
|
|
<select
|
|
value={cfg?.model || selectedModel || ""}
|
|
onChange={(e) => {
|
|
const val = e.target.value;
|
|
setAgentConfigs((prev) => ({
|
|
...prev,
|
|
[seatId]: { ...prev[seatId], model: val },
|
|
}));
|
|
}}
|
|
className="w-full bg-white border border-slate-200 rounded-xl px-2.5 py-1.5 text-xs text-slate-700 font-bold focus:outline-none"
|
|
disabled={modelsLoading}
|
|
>
|
|
{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>
|
|
|
|
<div className="space-y-1">
|
|
<div className="flex items-center justify-between text-[9px] font-black text-slate-500">
|
|
<span>随机创造性倾向 (Temp)</span>
|
|
<span className="font-mono text-primary font-bold">{cfg?.temp || 0.2}</span>
|
|
</div>
|
|
<input
|
|
type="range"
|
|
min={0.1}
|
|
max={1.0}
|
|
step={0.1}
|
|
value={cfg?.temp || 0.2}
|
|
onChange={(e) => {
|
|
const val = parseFloat(e.target.value);
|
|
setAgentConfigs((prev) => ({
|
|
...prev,
|
|
[seatId]: { ...prev[seatId], temp: val },
|
|
}));
|
|
}}
|
|
className="w-full h-1 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-primary"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => onSave(seatId)}
|
|
className="w-full bg-primary hover:bg-[var(--brand-900)] text-white text-[10.5px] font-black py-2 rounded-xl transition shadow active:scale-95 cursor-pointer flex items-center justify-center gap-1"
|
|
>
|
|
<Sparkles className="w-3.5 h-3.5 text-red-200" />
|
|
参数覆盖并重启动算研讨
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|