fix(agent): Agent 模型选择 - 复用创造页选择器并只展示文本模型

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ddCat
2026-09-16 23:45:53 +08:00
parent 24ba365af6
commit 77f6346f3f
6 changed files with 48 additions and 5 deletions

View File

@@ -5,6 +5,12 @@ description: 当前项目已实现功能的手工验证清单与未确认边界
# 待测试
## Agent 对话模型选择器复用创造页样式
- Agent 底部 Composer 与设置页「对话模型」改为复用画布节点 / 创造页的 `ModelPicker` `variant="creation"`:先选品牌、再选模型,不再把品牌挤成一行。
- 目录只展示文本模型;图片、视频、音频模型不进入 Agent 对话选择。当前选中若不是文本模型,回退到第一个可用文本模型。
- 待登录后打开画布 Agent:核对品牌列表为纵向卡片、点入后出现双栏模型列表且高度跟品牌列表走、不再被 300px 最小高度撑空;设置页同样;深浅主题与窄面板不裁切。
## 设置页页头与生成偏好精简
- 设置页去掉「创作偏好 / 配置默认模型、生成参数和素材存储。」页头;仅在 `?continue=1` 时保留返回创作按钮。

View File

@@ -21,7 +21,7 @@ import type { AgentMediaSettings } from "@/services/api/agent";
import { CanvasAgentImageApprovalSettings } from "./canvas-agent-image-approval-settings";
import { addSkill, listAddedSkills, listSkills, type Skill, type SkillCategory } from "@/services/api/skills";
import { clearCloudAgentPendingSubmission, cloudAgentConversationTitle, loadCloudAgentConversations, loadCloudAgentPendingSubmission, saveCloudAgentConversations, saveCloudAgentPendingSubmission, type CloudAgentConversation, type CloudAgentPendingSubmission } from "@/services/cloud-agent-conversations";
import { logicalModelIDForConfig, modelOptionName, resolveModelRequestConfig, useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
import { logicalModelIDForConfig, modelOptionName, resolveModelRequestConfig, selectableModelsByCapability, useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store";
import { applyAgentCanvasPatches, refreshCanvasAfterAgent, saveRemoteUserDataNow } from "@/services/user-data-sync";
import { createAgentCanvasSync } from "@/services/agent-canvas-sync";
@@ -99,7 +99,11 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
const currentScope = useRef(conversationScope);
currentScope.current = conversationScope;
const running = Boolean(run?.cleanupPending) || run?.status === "running" || run?.status === "queued" || run?.status === "waiting_approval";
const selectedModel = config.textModel || config.model || "";
const selectedModel = useMemo(() => {
const textModels = selectableModelsByCapability(config, "text");
const preferred = config.textModel || config.model || "";
return textModels.includes(preferred) ? preferred : (textModels[0] || "");
}, [config]);
const reasoningSupported = Boolean(modelCapabilityConfigFor(config, selectedModel).text?.thinking);
useEffect(() => { if (!reasoningSupported && reasoningMode !== "off") setReasoningMode("off"); }, [reasoningSupported, reasoningMode]);
const installedSkills = useMemo(() => skills.filter((skill) => skill.isAdded), [skills]);
@@ -946,11 +950,12 @@ function ComposerControls({
value={selectedModel}
capability="text"
onChange={onModelChange}
variant="creation"
className="!h-8 !min-w-0 !w-36 !max-w-full !border-0 !bg-transparent !px-1.5 !shadow-none"
popoverClassName="agent-model-picker-popover"
showSelectedPrice={false}
showOptionPrices
placeholder="选择模型"
placeholder="选择文本模型"
/>
{reasoningSupported ? <Dropdown trigger={["click"]} placement="topLeft" menu={{ items: reasoningMenuItems(reasoningMode, onReasoningModeChange) }}>
<button type="button" aria-label="选择 Agent 推理模式" title="推理模式:只用于规划和工具选择" className="flex h-8 shrink-0 items-center gap-1 rounded-md px-2 text-[11px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/25" style={{ color: reasoningMode === "off" ? theme.node.muted : theme.accent.primary, background: reasoningMode === "off" ? "transparent" : theme.node.fill }}>

View File

@@ -102,7 +102,7 @@ function SettingsHome({ props, theme, onOpen }: { props: AgentSettingsProps; the
<section>
<SettingLabel label="对话模型" hint="新运行生效" />
<div className="min-w-0 rounded-xl px-2 py-1.5" style={{ background: theme.node.fill }}>
<ModelPicker config={props.config} value={props.selectedModel} capability="text" onChange={props.onModelChange} fullWidth showSelectedPrice showOptionPrices placeholder="选择 Agent 模型" className="!border-0 !bg-transparent !shadow-none" />
<ModelPicker config={props.config} value={props.selectedModel} capability="text" onChange={props.onModelChange} variant="creation" fullWidth showSelectedPrice showOptionPrices placeholder="选择文本模型" className="!border-0 !bg-transparent !shadow-none" popoverClassName="agent-model-picker-popover" />
</div>
</section>
<section>

View File

@@ -3,6 +3,11 @@
overscroll-behavior: contain;
}
/* Agent 浮窗高于普通画布层,模型菜单必须叠在对话窗之上。 */
.agent-model-picker-popover {
z-index: calc(var(--z-modal-overlay) + 1000) !important;
}
/* Flex 子页必须允许收缩,长模型名和 Skill 文案不能决定窗口宽度。 */
.canvas-agent-panel > div,
.canvas-agent-settings,

View File

@@ -563,7 +563,7 @@
.creation-model-picker-surface .creation-model-picker-menu.is-brand-list { width: min(360px, calc(100vw - 24px)) !important; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list { width: min(720px, calc(100vw - 24px)) !important; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-two-pane { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 14px; min-height: 300px; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-two-pane { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 14px; min-height: 0; align-items: start; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-brand-rail { display: grid; align-content: start; gap: 5px; border-right: 1px solid var(--workspace-border); padding-right: 14px; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-brand { width: 100%; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-brand.is-active { border-color: var(--workspace-border); background: var(--workspace-surface-hover); }

View File

@@ -0,0 +1,27 @@
import { expect, test } from "bun:test";
test("Agent 对话和设置复用创作页模型选择器,并且只展示文本模型", async () => {
const panel = await Bun.file(new URL("../src/components/canvas/canvas-cloud-agent-panel.tsx", import.meta.url)).text();
const settings = await Bun.file(new URL("../src/components/canvas/canvas-cloud-agent-settings.tsx", import.meta.url)).text();
const css = await Bun.file(new URL("../src/components/canvas/canvas-cloud-agent.css", import.meta.url)).text();
const pickerCss = await Bun.file(new URL("../src/styles/workspace-product.css", import.meta.url)).text();
expect(panel).toContain('capability="text"');
expect(panel).toContain('variant="creation"');
expect(panel).toContain('popoverClassName="agent-model-picker-popover"');
expect(panel).toContain('selectableModelsByCapability(config, "text")');
expect(panel).toContain('placeholder="选择文本模型"');
expect(settings).toContain('capability="text"');
expect(settings).toContain('variant="creation"');
expect(settings).toContain('popoverClassName="agent-model-picker-popover"');
expect(settings).toContain('placeholder="选择文本模型"');
expect(css).toContain(".agent-model-picker-popover");
expect(css).toContain("z-index: calc(var(--z-modal-overlay) + 1000)");
const twoPane = pickerCss.match(/\.creation-model-picker-menu\.is-model-list \.canvas-model-picker-two-pane \{[^}]+\}/)?.[0] || "";
expect(twoPane).toContain("min-height: 0");
expect(twoPane).toContain("align-items: start");
expect(twoPane).not.toContain("min-height: 300px");
});