代码修改
This commit is contained in:
1 parent
a16dd889cf
commit
4142029f48
7 files changed
+121
-45
No files matched your search
@@ -27,7 +27,7 @@ export interface StreamRecommendResult {
|
||||
* `done` → picks parsed and validated, ready to populate the dialog as
|
||||
* default-checked selections.
|
||||
* `asking` → recommender failed to emit a usable [RECOMMEND_READY] block,
|
||||
* caller should pick from the live candidate pool as fallback.
|
||||
* caller should apply its fallback list (typically the 6 roundtable-* seats).
|
||||
* `error` → upstream / network failure.
|
||||
*/
|
||||
status: "done" | "asking" | "error";
|
||||
|
||||
@@ -202,6 +202,8 @@ export interface ReasoningBlockProps {
|
||||
/** 用于显示「思考用时」的可选时长(秒)。流式期间不显示。 */
|
||||
durationSec?: number;
|
||||
className?: string;
|
||||
/** 展开内容区额外 class,例如 max-h + overflow-y-auto。 */
|
||||
contentClassName?: string;
|
||||
}
|
||||
|
||||
const REASONING_AUTO_CLOSE_DELAY_MS = 1000;
|
||||
@@ -211,6 +213,7 @@ export function ReasoningBlock({
|
||||
streaming = false,
|
||||
durationSec,
|
||||
className,
|
||||
contentClassName,
|
||||
}: ReasoningBlockProps) {
|
||||
// open 初始值跟 streaming 同步:流式中默认展开方便观察思考过程,完成后会自动收起一次。
|
||||
const [open, setOpen] = useState<boolean>(streaming);
|
||||
@@ -260,7 +263,12 @@ export function ReasoningBlock({
|
||||
/>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="rounded-md border border-slate-200 bg-slate-50/60 px-3 py-2 text-[11.5px] text-slate-600 leading-relaxed">
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-md border border-slate-200 bg-slate-50/60 px-3 py-2 text-[11.5px] text-slate-600 leading-relaxed",
|
||||
contentClassName,
|
||||
)}
|
||||
>
|
||||
<MarkdownContent>{children}</MarkdownContent>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -62,6 +62,20 @@ export interface RecommendAgentsDialogProps {
|
||||
const MIN_SELECTED = 2;
|
||||
const MAX_SELECTED = 8;
|
||||
|
||||
/**
|
||||
* Default selection when the recommender fails or returns < 2 valid picks.
|
||||
* Intersected with the live candidate pool at runtime so missing built-ins
|
||||
* don't break the dialog.
|
||||
*/
|
||||
const FALLBACK_AGENT_IDS = [
|
||||
"roundtable-intelligence",
|
||||
"roundtable-environment",
|
||||
"roundtable-solution-design",
|
||||
"roundtable-risk-review",
|
||||
"roundtable-execution-plan",
|
||||
"roundtable-summary",
|
||||
];
|
||||
|
||||
type DialogPhase =
|
||||
| "idle"
|
||||
| "loading_agents"
|
||||
@@ -305,17 +319,12 @@ export function RecommendAgentsDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: status === "asking" or picks too few — pick from live pool.
|
||||
const seed = candidates.slice(0, MIN_SELECTED).map((c) => c.agent_id);
|
||||
if (seed.length < MIN_SELECTED) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
phase: "error",
|
||||
streamPhase: null,
|
||||
errorMessage: `候选智能体不足 ${MIN_SELECTED} 个,无法进入圆桌研讨。请先发布更多智能体。`,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
// Fallback: status === "asking" or picks too few. Use the 6 built-in
|
||||
// roundtable seats, intersected against the live pool.
|
||||
const fallback = intersectInOrder(FALLBACK_AGENT_IDS, candidates);
|
||||
const seed = fallback.length >= MIN_SELECTED
|
||||
? fallback
|
||||
: candidates.slice(0, MIN_SELECTED).map((c) => c.agent_id);
|
||||
setSelectedIds(new Set(seed));
|
||||
const { rationale } = parseRecommendStreamContent(result.content || "");
|
||||
setState((prev) => ({
|
||||
@@ -325,7 +334,7 @@ export function RecommendAgentsDialog({
|
||||
recommendedIds: seed,
|
||||
reasons,
|
||||
streamPhase: null,
|
||||
warning: "智能推荐未生成有效结果,已为你勾选候选池中的前几位智能体,可手动调整。",
|
||||
warning: "智能推荐未生成有效结果,已为你勾选默认的圆桌席位,可手动调整。",
|
||||
}));
|
||||
} catch (err) {
|
||||
if (myGen !== generationRef.current) return;
|
||||
@@ -421,8 +430,8 @@ export function RecommendAgentsDialog({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="sm:max-w-3xl max-h-[85vh] flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogContent className="sm:max-w-3xl max-h-[85vh] flex flex-col overflow-hidden gap-3">
|
||||
<DialogHeader className="shrink-0">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<SparklesIcon className="size-5 text-purple-500" />
|
||||
选择参会智能体
|
||||
@@ -457,7 +466,7 @@ export function RecommendAgentsDialog({
|
||||
<CheckIcon className="size-3 text-emerald-500" />
|
||||
推荐参会智能体
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto pr-1 -mr-1 space-y-4">
|
||||
<div className="flex-1 min-h-0 overflow-y-auto custom-scrollbar pr-1 -mr-1 space-y-4">
|
||||
{state.phase === "error" ? (
|
||||
<div className="text-sm text-destructive p-4 border border-destructive/30 rounded-md bg-destructive/5">
|
||||
{state.errorMessage ?? "未知错误"}
|
||||
@@ -527,7 +536,7 @@ export function RecommendAgentsDialog({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex-row sm:justify-between items-center gap-2 flex-wrap">
|
||||
<DialogFooter className="shrink-0 flex-row sm:justify-between items-center gap-2 flex-wrap border-t pt-3 mt-1">
|
||||
<div className="flex items-center gap-3 text-xs">
|
||||
<span
|
||||
className={cn(
|
||||
@@ -635,14 +644,14 @@ function CollapsibleModelOutput({
|
||||
})();
|
||||
|
||||
return (
|
||||
<section className="border rounded-md bg-muted/20 shrink-0">
|
||||
<section className="border rounded-md bg-muted/20 shrink-0 max-h-[38vh] flex flex-col min-h-0 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
userTouchedRef.current = true;
|
||||
setOpen((v) => !v);
|
||||
}}
|
||||
className="w-full flex items-center gap-2 px-3 py-2 text-[10px] font-bold text-slate-500 uppercase tracking-wider hover:bg-muted/40 transition-colors cursor-pointer"
|
||||
className="w-full shrink-0 flex items-center gap-2 px-3 py-2 text-[10px] font-bold text-slate-500 uppercase tracking-wider hover:bg-muted/40 transition-colors cursor-pointer"
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2Icon className="size-3 text-purple-500 shrink-0 animate-spin" />
|
||||
@@ -667,7 +676,11 @@ function CollapsibleModelOutput({
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
{open && <div className="px-3 pb-3">{children}</div>}
|
||||
{open && (
|
||||
<div className="px-3 pb-3 min-h-0 flex-1 overflow-y-auto custom-scrollbar">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -736,7 +749,12 @@ function RecommenderOutput({ state }: { state: InternalState }) {
|
||||
)}
|
||||
|
||||
{hasReasoning && (
|
||||
<ReasoningBlock streaming={isStreaming}>{reasoning!}</ReasoningBlock>
|
||||
<ReasoningBlock
|
||||
streaming={isStreaming}
|
||||
contentClassName="max-h-48 overflow-y-auto custom-scrollbar"
|
||||
>
|
||||
{reasoning!}
|
||||
</ReasoningBlock>
|
||||
)}
|
||||
|
||||
{!hasRationale && !hasReasoning && isStreaming && (
|
||||
@@ -744,7 +762,7 @@ function RecommenderOutput({ state }: { state: InternalState }) {
|
||||
)}
|
||||
|
||||
{hasRationale && (
|
||||
<div className="max-h-72 overflow-y-auto rounded-md border border-slate-200 bg-white px-3 py-2.5 text-sm text-slate-700 leading-relaxed">
|
||||
<div className="max-h-40 overflow-y-auto custom-scrollbar rounded-md border border-slate-200 bg-white px-3 py-2.5 text-sm text-slate-700 leading-relaxed">
|
||||
<MarkdownContent>{cleanedRationale}</MarkdownContent>
|
||||
{isStreaming && <StreamingCursor />}
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
type ThreadIdMap,
|
||||
} from "@/roundtable-planning/api/multi-agent";
|
||||
import {
|
||||
DEFAULT_SELECTED_AGENTS,
|
||||
MAX_CYCLES,
|
||||
buildAgentNameToRole,
|
||||
buildRoleTemplates,
|
||||
@@ -98,8 +99,18 @@ export type Step2Snapshot = {
|
||||
seatStubMessages: Record<string, StubAiMessage[]>;
|
||||
};
|
||||
|
||||
/** 席位 Persona 配置;初始为空,用户在 Persona 抽屉保存后写入。 */
|
||||
const DEFAULT_AGENT_CONFIGS: Record<number, AgentConfig> = {};
|
||||
/**
|
||||
* 6 个默认席位的初始 Persona 配置。model 留空 → 运行时 `agentConfigs[seat]?.model
|
||||
* || selectedModel || undefined`,即「席位覆盖优先,否则跟随全局模型」。
|
||||
*/
|
||||
const DEFAULT_AGENT_CONFIGS: Record<number, AgentConfig> = {
|
||||
2: { model: "", weight: 90, temp: 0.2, prompt: "你负责解析核心任务、资源极限和用户目标偏好,监督全局研商收敛。" },
|
||||
3: { model: "", weight: 85, temp: 0.3, prompt: "你负责搜集电力、通信系统、历史极端部署条件情报,支持备灾架构体系。" },
|
||||
4: { model: "", weight: 88, temp: 0.1, prompt: "你评估服务器机房热载荷、算力并发承载极限,预报运行瓶颈与风险时钟。" },
|
||||
5: { model: "", weight: 92, temp: 0.4, prompt: "你负责双全栈主备建模设计,高内聚低耦合,规避不可预知停机崩溃。" },
|
||||
6: { model: "", weight: 95, temp: 0.1, prompt: "你作为安全主管,严厉把控预算开销与合规通过率防线,识别计划漏洞。" },
|
||||
7: { model: "", weight: 80, temp: 0.4, prompt: "你负责对72小时里程碑执行路径规划、重排和资源调配演练序列。" },
|
||||
};
|
||||
|
||||
export interface UseStep2OrchestrationOptions {
|
||||
/** 当前页面 currentStep,===2 触发自动 init。 */
|
||||
@@ -242,7 +253,9 @@ export function useStep2Orchestration(
|
||||
Record<string, StubAiMessage[]>
|
||||
>({});
|
||||
const seatStubMessagesRef = useRef<Record<string, StubAiMessage[]>>({});
|
||||
const [selectedAgents, setSelectedAgents] = useState<SelectedAgent[]>([]);
|
||||
const [selectedAgents, setSelectedAgents] = useState<SelectedAgent[]>(
|
||||
DEFAULT_SELECTED_AGENTS,
|
||||
);
|
||||
const [threadIds, setThreadIds] = useState<ThreadIdMap | null>(null);
|
||||
const [coordinatorName, setCoordinatorName] = useState<string | null>(null);
|
||||
const [isInitializing, setIsInitializing] = useState<boolean>(false);
|
||||
|
||||
@@ -24,7 +24,23 @@ export type RoleEntry = {
|
||||
/** 用户在推荐弹窗选定的一个研讨席位。 */
|
||||
export type SelectedAgent = { agent_id: string; name: string };
|
||||
|
||||
/** 研讨席位的头像配色轮换池。 */
|
||||
/**
|
||||
* 6 个内置 roundtable 席位的展示元数据。用户保留默认席位时,
|
||||
* 头像 / 配色 / 中文显示名都按这里走;非内置(用户自建)走 FALLBACK_AVATAR_TYPES。
|
||||
*/
|
||||
export const BUILTIN_AGENT_META: Record<
|
||||
string,
|
||||
{ avatarType: string; displayName: string }
|
||||
> = {
|
||||
"roundtable-intelligence": { avatarType: "purple", displayName: "情报收集" },
|
||||
"roundtable-environment": { avatarType: "emerald", displayName: "环境评估" },
|
||||
"roundtable-solution-design": { avatarType: "human", displayName: "方案设计" },
|
||||
"roundtable-risk-review": { avatarType: "amber", displayName: "风险审查" },
|
||||
"roundtable-execution-plan": { avatarType: "pink", displayName: "执行规划" },
|
||||
"roundtable-summary": { avatarType: "blue", displayName: "总结陈述" },
|
||||
};
|
||||
|
||||
/** 非内置 agent 的头像配色轮换池。 */
|
||||
export const FALLBACK_AVATAR_TYPES = [
|
||||
"blue",
|
||||
"purple",
|
||||
@@ -33,12 +49,28 @@ export const FALLBACK_AVATAR_TYPES = [
|
||||
"pink",
|
||||
] as const;
|
||||
|
||||
export function getAvatarTypeFor(_agentId: string, index: number): string {
|
||||
return FALLBACK_AVATAR_TYPES[index % FALLBACK_AVATAR_TYPES.length];
|
||||
/**
|
||||
* 用户未确认推荐弹窗时,Step 2 默认使用的 6 个内置席位。
|
||||
* 保留默认时页面与原硬编码行为完全一致。
|
||||
*/
|
||||
export const DEFAULT_SELECTED_AGENTS: SelectedAgent[] = [
|
||||
{ agent_id: "roundtable-summary", name: "总结陈述" },
|
||||
{ agent_id: "roundtable-intelligence", name: "情报收集" },
|
||||
{ agent_id: "roundtable-environment", name: "环境评估" },
|
||||
{ agent_id: "roundtable-solution-design", name: "方案设计" },
|
||||
{ agent_id: "roundtable-risk-review", name: "风险审查" },
|
||||
{ agent_id: "roundtable-execution-plan", name: "执行规划" },
|
||||
];
|
||||
|
||||
export function getAvatarTypeFor(agentId: string, index: number): string {
|
||||
return (
|
||||
BUILTIN_AGENT_META[agentId]?.avatarType ??
|
||||
FALLBACK_AVATAR_TYPES[index % FALLBACK_AVATAR_TYPES.length]
|
||||
);
|
||||
}
|
||||
|
||||
export function getDisplayNameFor(agent: SelectedAgent): string {
|
||||
return agent.name;
|
||||
return BUILTIN_AGENT_META[agent.agent_id]?.displayName ?? agent.name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
import { useStep2Orchestration } from "@/roundtable-planning/hooks/useStep2Orchestration";
|
||||
import { useDraftPersistence } from "@/roundtable-planning/hooks/useDraftPersistence";
|
||||
import {
|
||||
BUILTIN_AGENT_META,
|
||||
getAvatarTypeFor,
|
||||
getDisplayNameFor,
|
||||
makeRolesFromTemplates,
|
||||
@@ -259,7 +260,8 @@ export default function RoundtablePlanningPage() {
|
||||
(agents: Array<{ agent_id: string; name: string }>) => {
|
||||
const normalized: SelectedAgent[] = agents.map(({ agent_id, name }) => ({
|
||||
agent_id,
|
||||
name: name ?? agent_id,
|
||||
// 内置 agent 走 BUILTIN_AGENT_META;非内置用候选池传过来的 name。
|
||||
name: BUILTIN_AGENT_META[agent_id]?.displayName ?? name ?? agent_id,
|
||||
}));
|
||||
step2.setSelectedAgents(normalized);
|
||||
step2.resetInitGate();
|
||||
@@ -893,7 +895,9 @@ function LeftSidebar({
|
||||
...selectedAgents.map((agent, i) => ({
|
||||
id: agent.agent_id,
|
||||
name: getDisplayNameFor(agent),
|
||||
role: "圆桌席位",
|
||||
role: BUILTIN_AGENT_META[agent.agent_id]
|
||||
? `${BUILTIN_AGENT_META[agent.agent_id].displayName} 席位`
|
||||
: "圆桌席位",
|
||||
icon: getAvatar(getAvatarTypeFor(agent.agent_id, i), "h-7 w-7"),
|
||||
})),
|
||||
].map((contrib, idx) => (
|
||||
|
||||
@@ -406,7 +406,7 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
|
||||
> 以下两个示例覆盖**不同任务类型**,目的是教你「思路要呼应任务本身」,**不要**把任何一个示例的具体职能链当模板。
|
||||
|
||||
### 示例 A:合规改造任务(领域候选池)
|
||||
### 示例 A:合规改造任务(职能型候选池)
|
||||
|
||||
**用户输入**:
|
||||
```
|
||||
@@ -415,11 +415,12 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
关键假设:现有系统具备基础数字化能力;允许预定窗口停机
|
||||
|
||||
候选智能体池:
|
||||
- compliance-research (合规调研):检索同类历史案例、行业最佳实践与监管口径
|
||||
- infra-capacity (基础设施评估):评估硬件容量与系统量化承载力
|
||||
- solution-architect (方案架构):产出主备方案与关键路径
|
||||
- risk-auditor (风险审查):合规红线检查与收敛建议
|
||||
- execution-planner (执行规划):里程碑与资源排布
|
||||
- roundtable-intelligence (情报收集):检索同类历史案例、行业最佳实践与外部情报
|
||||
- roundtable-environment (环境评估):评估硬件容量与系统量化承载力
|
||||
- roundtable-solution-design (方案设计):产出主备方案与关键路径
|
||||
- roundtable-risk-review (风险审查):合规红线检查与收敛建议
|
||||
- roundtable-execution-plan (执行规划):里程碑与资源排布
|
||||
- roundtable-summary (总结陈述):归纳冲突标记与盖印
|
||||
- my-cost-calc (成本测算):基于预算和资源单价计算总成本
|
||||
```
|
||||
|
||||
@@ -430,10 +431,10 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
```json
|
||||
{
|
||||
"picks": [
|
||||
{"agent_id": "compliance-research", "reason": "检索同类等保 2.0 改造案例与监管口径,直接支撑本次合规对标"},
|
||||
{"agent_id": "solution-architect", "reason": "在 6 个月工期约束下产出主备改造方案与关键路径"},
|
||||
{"agent_id": "risk-auditor", "reason": "对照等保 2.0 合规红线审查方案,避免后期返工"},
|
||||
{"agent_id": "execution-planner", "reason": "把改造拆成可在 6 个月内交付的里程碑与资源排布"},
|
||||
{"agent_id": "roundtable-intelligence", "reason": "检索同类等保 2.0 改造案例与监管口径,直接支撑本次合规对标"},
|
||||
{"agent_id": "roundtable-solution-design", "reason": "在 6 个月工期约束下产出主备改造方案与关键路径"},
|
||||
{"agent_id": "roundtable-risk-review", "reason": "对照等保 2.0 合规红线审查方案,避免后期返工"},
|
||||
{"agent_id": "roundtable-execution-plan", "reason": "把改造拆成可在 6 个月内交付的里程碑与资源排布"},
|
||||
{"agent_id": "my-cost-calc", "reason": "校验整体方案落在 500 万元预算硬约束内"}
|
||||
]
|
||||
}
|
||||
@@ -452,7 +453,7 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
- channel-planner (渠道规划师):按预算和受众设计跨平台投放组合
|
||||
- audience-insight (受众洞察):画像、关键词、情绪点研究
|
||||
- creative-copy (创意文案):产出落地页与社媒文案变体
|
||||
- ad-compliance (广告合规):审查文案与素材合规风险
|
||||
- roundtable-risk-review (风险审查):合规红线检查与收敛建议
|
||||
```
|
||||
|
||||
**你的输出**:
|
||||
@@ -470,8 +471,8 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
}
|
||||
```
|
||||
|
||||
> **关键观察**:两个示例都从候选池里按任务语义挑选,**不要**套用固定职能链(如"调研→设计→风险→执行")。
|
||||
> 候选池里的智能体能不能匹配任务,只看 description 和任务的实际语义。如果候选池里有"广告设计智能体"且任务是广告设计,**你必须挑它**,而不是机械复制示例 A 的链路。
|
||||
> **关键观察**:示例 A 选了 `roundtable-intelligence / solution-design / execution-plan` 等"职能型"智能体,示例 B **完全没用任何 `roundtable-*` 内置**,而是从用户自建的领域智能体里挑。
|
||||
> 这是因为:**候选池里的智能体能不能匹配任务,只看 description 和任务的实际语义,不看 id 是不是 `roundtable-` 开头**。如果候选池里有"广告设计智能体"且任务是广告设计,**你必须挑它**,而不是套用示例 A 的职能链。
|
||||
"""
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user