diff --git a/design-qa.md b/design-qa.md index 17bf4c5b3f..92f87e2b1b 100644 --- a/design-qa.md +++ b/design-qa.md @@ -18,6 +18,11 @@ source is the user's attached `exec-e196c6a9-d8d9-437d-a6ee-0e99a3651845.png`, The image specifies a catalog followed by a separate content editor, not a drawer. The existing Settings shell remains the application-owned frame. +The latest editor instruction supersedes the original advanced disclosure: +the user's `codex-clipboard-c3951359-4571-4572-ad30-9f4517a2f7db.png` reference +places both textareas and the runtime-context switch directly in the editor. +Scope and ID are also exposed. Settings navigation now reads Persona / 人设. + 本节取代下方旧预设卡片的视觉验收。采用用户重新指定的首张效果图,列表与独立 编辑页切换,保留现有设置外框。编辑态为新建、名称与用途已填写、能力及高级设置 收起、个人范围、取消与保存操作固定在底部。 @@ -35,8 +40,8 @@ Settings shell remains the application-owned frame. contrast remains unverified. - Assets: existing product icons retained; no generated product assets or new handcrafted icon geometry. Source brand is supplied by the existing shell. -- Copy: name, purpose, persona, capabilities, scope and advanced hierarchy follows - the selected source, with Chinese/English labels. Existing backend limits remain +- Copy: name, purpose, persona, capabilities, scope and ID follow the selected + references, with Chinese/English labels. Existing backend limits remain authoritative rather than adopting illustrative mockup character limits. - Interaction regressions cover creation, copy, literal persona fields, invalid parameters, failed saves, navigation/close confirmation, pending saves and focus. @@ -61,6 +66,36 @@ Earlier passing sections below are historical evidence for their own changes. 待办:恢复浏览器访问后,在相同编辑状态比较整页与局部,并检查深浅主题、窄屏 滚动及控制台;解决 P0–P2 问题后再将视觉验收标记为通过。 +## Built-in cards and new-task default / 内置卡片与新任务默认 + +final result: blocked + +Source: the user's selected Harness screenshot +`codex-clipboard-be3c8df5-ada5-4683-a66d-81cdf31010e7.png`, 1178 × 760 image pixels. +It is a section crop; viewport, CSS size and density are unspecified. + +Implementation capture and full-view/focused comparison remain unavailable +because browser observation was rejected by the URL security policy. No +alternative browser surface was used to bypass that refusal. + +- Typography: inherited Studio font, scale and weight tokens. Rendering remains + unverified; source-specific font substitution was not introduced. +- Layout: responsive two-column cards, large rounded borders, equal-height bodies, + separate footer actions and single-column narrow layouts. Rendered rhythm and + scrolling remain unverified. +- Color: existing surface, border, text and selected tokens. The selected badge + uses the text/page pair. Theme contrast still needs browser inspection. +- Assets: supplied product icon library reused for copy; no new raster assets + or handcrafted icon geometry. +- Content: Standard/Minimal remain the shipped modes; IDs remain canonical. + Details, usage and copy remain separate from the card's default selection. +- Behavior: card clicks persist a machine-local built-in choice. New local and + remote requests carry it explicitly; history and explicit per-task choices + retain priority. Old server preferences alone remain inert. + +待浏览器恢复后,对照所选卡片截图检查字体、留白、颜色、图标和文案,并覆盖 +深浅主题、窄屏、默认状态及展开的使用说明。当前功能测试不能替代视觉验收。 + ## Historical palette verification / 历史配色验收 final result: passed diff --git a/desktop/frontend-next/src/actions.ts b/desktop/frontend-next/src/actions.ts index f160f76047..3a59d50bc6 100644 --- a/desktop/frontend-next/src/actions.ts +++ b/desktop/frontend-next/src/actions.ts @@ -44,6 +44,8 @@ export const ACTIONS: UIAction[] = [ {id:"agent-composition.new",kind:"kernel-mutation",target:"none",proof:"authority-effect"}, {id:"agent-preset.reload",kind:"view",target:"none",proof:"interaction"}, {id:"agent-preset.view",kind:"view",target:"none",proof:"interaction"}, + {id:"agent-preset.new-task-default",kind:"view",target:"entity",proof:"interaction"}, + {id:"agent-preset.usage",kind:"view",target:"entity",proof:"interaction"}, {id:"agent-preset.create",kind:"view",target:"none",proof:"interaction"}, {id:"agent-preset.leave",kind:"view",target:"none",proof:"interaction"}, {id:"agent-preset.leave-cancel",kind:"view",target:"none",proof:"interaction"}, diff --git a/desktop/frontend-next/src/i18n/en_composition.ts b/desktop/frontend-next/src/i18n/en_composition.ts index 216afb2f61..15b23f8b0e 100644 --- a/desktop/frontend-next/src/i18n/en_composition.ts +++ b/desktop/frontend-next/src/i18n/en_composition.ts @@ -1,4 +1,11 @@ export const EN_COMPOSITION: Record = { + "新任务默认": "New task default", + "将{name}设为新任务默认": "Set {name} as the new task default", + "如何使用 {name}": "How to use {name}", + "点击卡片,设置本机新任务的默认模式;已有会话保持当前配置。": "Click a card to set the default for new tasks on this machine. Existing conversations keep their configuration.", + "无法保存新任务默认模式,请检查本地存储权限。": "Could not save the new task default. Check local storage permissions.", + "标准模式默认启用,可直接输入任务;如需自定义,先复制此预设。": "Standard mode is the initial default. Enter a task directly, or duplicate this preset to customize it.", + "在新任务输入框选择精简模式,再发送首条消息。需要支持读图的模型;终端操作继续遵守权限设置。": "Choose Minimal mode in a new task's composer before sending the first message. A vision-capable model is required; terminal operations follow existing permissions.", "预设版本": "Preset version", "继承当前配置": "Inherit current configuration", "返回预设列表": "Back to presets", @@ -19,7 +26,7 @@ export const EN_COMPOSITION: Record = { "放弃更改": "Discard changes", "编辑标准模式人设": "Edit Standard-mode persona", "显式默认配置": "Explicit default configuration", -"仅在显式选择 default 时使用,不改变新会话的标准模式。": "Used only when explicitly selecting default. New conversations still start in Standard mode.", +"仅在显式选择 default 时使用,与本机卡片设置的新任务默认独立。": "Used only when explicitly selecting default, separately from this machine's new-task card selection.", "预设已删除,已有会话保持原版本。": "Preset deleted. Existing conversations keep their version.", "导入的预设": "Imported preset", "新会话默认使用标准模式。选择其他模式后才会启用对应预设。": "New conversations start in Standard mode. Other presets apply only when selected.", diff --git a/desktop/frontend-next/src/i18n/en_persona.ts b/desktop/frontend-next/src/i18n/en_persona.ts index 06b6490974..bcd1f4e792 100644 --- a/desktop/frontend-next/src/i18n/en_persona.ts +++ b/desktop/frontend-next/src/i18n/en_persona.ts @@ -22,7 +22,7 @@ export const EN_PERSONA: Record = { "补充要求": "Additional instructions", "放在内置系统指令之后,留空则不添加": "Placed after the built-in system instructions. Leave blank to add nothing.", "不注入可选运行时上下文": "Do not inject optional runtime context", - "关闭后不注入探测到的运行环境与离线提示;指令、记忆与技能照常写入。": "When on, the probed environment and the offline note are not injected; instructions, memory and skills are written as usual.", + "开启后不注入探测到的运行环境与离线提示;指令、记忆与技能照常写入。": "When on, the probed environment and the offline note are not injected; instructions, memory and skills are written as usual.", "写入用户配置,在新会话及现有会话下一次重建时生效;保存本身不会重建会话。": "Writes the user config; applies to new sessions and the next rebuild of an existing session. Saving itself does not rebuild it.", "已保存,下次会话构建时生效": "Saved; applies at the next session build", }; diff --git a/desktop/frontend-next/src/state/newTaskPreset.test.ts b/desktop/frontend-next/src/state/newTaskPreset.test.ts new file mode 100644 index 0000000000..0b4339bc12 --- /dev/null +++ b/desktop/frontend-next/src/state/newTaskPreset.test.ts @@ -0,0 +1,42 @@ +// @vitest-environment jsdom +import { afterEach, expect, it, vi } from "vitest"; +import { act, cleanup, renderHook } from "@testing-library/react"; +import { NEW_TASK_PRESET_KEY, newTaskComposition, newTaskPreset, setNewTaskPreset, useNewTaskPreset } from "./newTaskPreset"; + +afterEach(() => { cleanup(); vi.restoreAllMocks(); localStorage.removeItem(NEW_TASK_PRESET_KEY); }); + +it("leaves old installations unselected and ignores unsupported stored identities", () => { + expect(newTaskComposition()).toBeUndefined(); + for (const raw of ["", "default", "user:review", "project:review", "builtin:retired", "{bad"]) { + localStorage.setItem(NEW_TASK_PRESET_KEY, raw); + expect(newTaskPreset()).toBeUndefined(); + } + expect(setNewTaskPreset("project:review")).toBe(false); +}); + +it("round-trips explicit built-in choices while preserving request and history choices", () => { + for (const id of ["builtin:general", "builtin:minimal"]) { + expect(setNewTaskPreset(id)).toBe(true); + expect(newTaskComposition()).toBe(id); + expect(newTaskComposition("user:explicit")).toBe("user:explicit"); + expect(newTaskComposition(undefined, "/history/task.jsonl")).toBeUndefined(); + } +}); + +it("publishes local changes and cross-window storage changes to the card", () => { + const { result } = renderHook(useNewTaskPreset); + act(() => { setNewTaskPreset("builtin:minimal"); }); + expect(result.current).toBe("builtin:minimal"); + act(() => { + localStorage.setItem(NEW_TASK_PRESET_KEY, "builtin:general"); + window.dispatchEvent(new StorageEvent("storage", { key: NEW_TASK_PRESET_KEY })); + }); + expect(result.current).toBe("builtin:general"); +}); + +it("refuses an unpersisted selection when local storage is unavailable", () => { + setNewTaskPreset("builtin:general"); + vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => { throw new Error("storage denied"); }); + expect(setNewTaskPreset("builtin:minimal")).toBe(false); + expect(newTaskPreset()).toBe("builtin:general"); +}); diff --git a/desktop/frontend-next/src/state/newTaskPreset.ts b/desktop/frontend-next/src/state/newTaskPreset.ts new file mode 100644 index 0000000000..bc798f47a2 --- /dev/null +++ b/desktop/frontend-next/src/state/newTaskPreset.ts @@ -0,0 +1,37 @@ +import { useSyncExternalStore } from "react"; + +export const NEW_TASK_PRESET_KEY = "rx-new-task-agent-preset"; +const listeners = new Set<() => void>(); +type BuiltinPreset = "builtin:general" | "builtin:minimal"; +const valid = (value: unknown): value is BuiltinPreset => + value === "builtin:general" || value === "builtin:minimal"; + +export function newTaskPreset(): BuiltinPreset | undefined { + try { + const value = localStorage.getItem(NEW_TASK_PRESET_KEY); + return valid(value) ? value : undefined; + } catch { return undefined; } +} + +export function setNewTaskPreset(id: string): boolean { + if (!valid(id)) return false; + try { localStorage.setItem(NEW_TASK_PRESET_KEY, id); } + catch { return false; } + listeners.forEach(fn => fn()); + return true; +} + +function subscribe(fn: () => void) { + listeners.add(fn); + const refresh = (event: StorageEvent) => { + if (event.key === NEW_TASK_PRESET_KEY || event.key === null) fn(); + }; + window.addEventListener("storage", refresh); + return () => { listeners.delete(fn); window.removeEventListener("storage", refresh); }; +} + +export const useNewTaskPreset = () => useSyncExternalStore(subscribe, newTaskPreset, newTaskPreset); + +export function newTaskComposition(explicit?: string, sessionPath?: string): string | undefined { + return explicit !== undefined || sessionPath ? explicit : newTaskPreset(); +} diff --git a/desktop/frontend-next/src/styles/composition-presets.css b/desktop/frontend-next/src/styles/composition-presets.css index be09595533..5e965fc71a 100644 --- a/desktop/frontend-next/src/styles/composition-presets.css +++ b/desktop/frontend-next/src/styles/composition-presets.css @@ -11,6 +11,24 @@ .composition-default-note { color: var(--muted); margin: 0 0 28px; line-height: 1.7; } .composition-list-group { margin-bottom: 28px; } .composition-list-group h3 { color: var(--muted); font-size: var(--fs-ui); font-weight: var(--w-med); margin: 0 0 12px; } +.composition-builtin-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap: 20px; } +.composition-builtin-card { display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--border); border-radius: 22px; overflow: hidden; background: var(--surface); } +.composition-builtin-card[data-selected] { background: var(--overlay); border-color: var(--faint); } +.composition-builtin-card:focus-within { border-color: var(--focus); } +.composition-builtin-select { flex: 1; min-width: 0; padding: 24px; border: 0; background: transparent; color: var(--text); text-align: left; cursor: pointer; } +.composition-builtin-select:hover { background: var(--overlay); } +.composition-builtin-heading { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; } +.composition-builtin-heading strong { font-size: var(--fs-title); font-weight: var(--w-semi); } +.composition-builtin-heading code { margin-left: auto; color: var(--ghost); font: var(--w-reg) var(--fs-meta)/1.5 var(--mono); } +.composition-builtin-badge { padding: 2px 9px; border: 1px solid var(--border); border-radius: var(--r-pill); color: var(--muted); font: var(--w-med) var(--fs-meta)/1.5 var(--ui); white-space: nowrap; } +.composition-builtin-card[data-selected] .composition-builtin-badge { background: var(--text); border-color: var(--text); color: var(--page); } +.composition-builtin-description { display: block; margin-top: 20px; color: var(--muted); font-size: var(--fs-ui); line-height: 1.8; overflow-wrap: anywhere; } +.composition-builtin-footer { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; padding: 16px 24px; border-top: 1px solid var(--hair); } +.composition-builtin-footer button { border: 0; padding: 4px 0; background: transparent; color: var(--muted); font: var(--w-reg) var(--fs-ui)/1.5 var(--ui); cursor: pointer; } +.composition-builtin-footer button:hover { color: var(--text); } +.composition-builtin-footer .composition-builtin-copy { display: grid; place-items: center; margin-left: auto; width: 28px; min-height: 28px; padding: 4px; border-radius: var(--r-sm); } +.composition-builtin-copy svg { width: 18px; height: 18px; } +.composition-builtin-usage { margin: 0; padding: 16px 24px; border-top: 1px solid var(--hair); color: var(--muted); font-size: var(--fs-meta); line-height: 1.7; } .composition-row { display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--hair); padding-right: 12px; } .composition-row:first-of-type { border-top: 1px solid var(--hair); } .composition-row-main { display: flex; align-items: center; gap: 16px; flex: 1; min-width: 0; border: 0; background: transparent; color: var(--text); padding: 20px 12px; cursor: pointer; text-align: left; } @@ -48,16 +66,14 @@ .composition-capabilities > details { background: var(--overlay); padding-inline: 16px; } .composition-capabilities > details:first-of-type { border-radius: var(--r-md) var(--r-md) 0 0; } .composition-capabilities > details:last-of-type { border-radius: 0 0 var(--r-md) var(--r-md); } -.composition-capabilities summary, .composition-advanced > summary { display: flex; align-items: center; gap: 12px; min-height: 52px; cursor: pointer; list-style: none; font-weight: var(--w-med); } -.composition-capabilities summary::-webkit-details-marker, .composition-advanced > summary::-webkit-details-marker { display: none; } -.composition-capabilities summary > svg:last-child, .composition-advanced > summary > svg:last-child { margin-left: auto; color: var(--muted); } -.composition-capabilities details[open] > summary > svg:last-child, .composition-advanced[open] > summary > svg:last-child { transform: rotate(90deg); } -.composition-capabilities summary svg, .composition-advanced > summary svg { width: 18px; height: 18px; flex-shrink: 0; } +.composition-capabilities summary { display: flex; align-items: center; gap: 12px; min-height: 52px; cursor: pointer; list-style: none; font-weight: var(--w-med); } +.composition-capabilities summary::-webkit-details-marker { display: none; } +.composition-capabilities summary > svg:last-child { margin-left: auto; color: var(--muted); } +.composition-capabilities details[open] > summary > svg:last-child { transform: rotate(90deg); } +.composition-capabilities summary svg { width: 18px; height: 18px; flex-shrink: 0; } .composition-capabilities summary span { margin-left: auto; color: var(--muted); font-size: var(--fs-meta); font-weight: var(--w-reg); } .composition-capabilities summary:has(span) > svg:last-child { margin-left: 4px; } .composition-capabilities > details + details { border-top: 1px solid var(--hair); } -.composition-advanced { border-top: 1px solid var(--hair); } -.composition-advanced .pers-field { margin-block: 16px; } .composition-modules { border: 1px solid var(--border); border-radius: var(--r-sm); padding: 12px; margin: 0 0 16px; min-width: 0; } .composition-modules legend { font-weight: var(--w-med); padding-inline: 4px; } .composition-module { margin-block: 8px; } @@ -77,6 +93,9 @@ .preset-workspace :is(button, summary, input, select, textarea):focus-visible, .preset-confirm button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } .preset-workspace :is(input, select):disabled { opacity: .65; } @media (max-width: 700px) { + .composition-builtin-grid { grid-template-columns: minmax(0, 1fr); gap: 16px; } + .composition-builtin-select { padding: 20px; } + .composition-builtin-footer { padding: 14px 20px; } .composition-list-heading { column-gap: 12px; } .composition-row-main { gap: 10px; padding: 16px 4px; } .composition-row-icon { width: 32px; height: 32px; } diff --git a/desktop/frontend-next/src/ui/AgentPresets.editor.test.tsx b/desktop/frontend-next/src/ui/AgentPresets.editor.test.tsx index ce7b619ebb..9071e74562 100644 --- a/desktop/frontend-next/src/ui/AgentPresets.editor.test.tsx +++ b/desktop/frontend-next/src/ui/AgentPresets.editor.test.tsx @@ -62,7 +62,9 @@ it("starts with a catalog and opens creation in a separate editor", async () => expect(within(editor).getByRole("textbox", { name: "预设名称" })).toBe(document.activeElement); expect(within(editor).getByText("基于标准模式创建")).toBeTruthy(); expect(within(editor).getByRole("button", { name: "保存预设" }).disabled).toBe(true); - expect(within(editor).getByLabelText("预设 ID").closest("details")?.open).toBe(false); + expect(within(editor).getByLabelText("预设 ID").closest("details")).toBeNull(); + expect(within(editor).getByRole("textbox", { name: "补充要求" })).toBeTruthy(); + expect(within(editor).queryByText("高级设置")).toBeNull(); }); it("saves a named preset, returns to its catalog and leaves the conversation unbound", async () => { diff --git a/desktop/frontend-next/src/ui/AgentPresets.interaction.test.tsx b/desktop/frontend-next/src/ui/AgentPresets.interaction.test.tsx index 95846cc880..2f32e1cf72 100644 --- a/desktop/frontend-next/src/ui/AgentPresets.interaction.test.tsx +++ b/desktop/frontend-next/src/ui/AgentPresets.interaction.test.tsx @@ -1,6 +1,6 @@ // @vitest-environment jsdom import "./testkit"; -import { cleanup, render, screen, waitFor } from "@testing-library/react"; +import { cleanup, render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, describe, it, expect, vi } from "vitest"; import { MockPort } from "../port/mock"; @@ -8,8 +8,9 @@ import { AgentPresets } from "./AgentPresets"; import { boot, STORAGE, t } from "../i18n"; import { AgentCompositionPicker } from "./AgentCompositionPicker"; import { RunLine } from "./RunLine"; +import { NEW_TASK_PRESET_KEY } from "../state/newTaskPreset"; -afterEach(() => { cleanup(); localStorage.setItem(STORAGE, "zh"); boot(); }); +afterEach(() => { cleanup(); localStorage.removeItem(NEW_TASK_PRESET_KEY); localStorage.setItem(STORAGE, "zh"); boot(); }); it("keeps the mode picker visible without a visibility setting or automatic selection", async () => { const port = new MockPort(); @@ -75,7 +76,7 @@ describe("Agent preset management", () => { render(); expect(await screen.findByRole("button", { name: "查看 标准模式" })).toBeTruthy(); const minimal = screen.getByRole("button", { name: "查看 精简模式" }); - expect(document.querySelectorAll(".composition-row")).toHaveLength(2); + expect(within(screen.getByRole("region", { name: "内置" })).getAllByRole("article")).toHaveLength(2); expect(screen.queryByText("代码审查")).toBeNull(); expect(screen.queryByText("资料研究")).toBeNull(); await user.click(minimal); @@ -101,10 +102,9 @@ describe("Agent preset management", () => { await user.clear(screen.getByLabelText("预设名称")); await user.type(screen.getByLabelText("预设名称"), "My agent"); await user.type(screen.getByLabelText("人设与要求"), "Literal prefix"); - await user.click(screen.getByText("高级设置", { selector: "summary" })); await user.type(screen.getByLabelText("补充要求"), "Literal suffix"); expect(screen.queryByRole("switch", { name: /complete/ })).toBeNull(); - await user.click(screen.getByRole("switch", { name: "注入运行环境与离线提示" })); + await user.click(screen.getByRole("switch", { name: "不注入可选运行时上下文" })); await user.click(screen.getByRole("button", { name: "保存预设" })); expect((await screen.findByRole("status")).textContent).toContain("新会话使用新版"); expect((await port.agentPresets()).presets.some((v) => v.definition.name === "My agent")).toBe(true); diff --git a/desktop/frontend-next/src/ui/BuiltinPresetCard.tsx b/desktop/frontend-next/src/ui/BuiltinPresetCard.tsx new file mode 100644 index 0000000000..c0ca51f06d --- /dev/null +++ b/desktop/frontend-next/src/ui/BuiltinPresetCard.tsx @@ -0,0 +1,36 @@ +import { useId, useState } from "react"; +import type { AgentPresetVersion } from "../port/composition"; +import { t } from "../i18n"; +import { StudioIcon } from "./StudioIcon"; + +export function BuiltinPresetCard({ version: { definition: d }, selected, onDefault, onSelect, onCopy }: { + version: AgentPresetVersion; + selected: boolean; + onDefault: () => void; + onSelect: () => void; + onCopy: () => void; +}) { + const [usage, setUsage] = useState(false); + const helpId = useId(); + const name = t(d.name); + return
+ +
+ + + +
+ +
; +} diff --git a/desktop/frontend-next/src/ui/CompositionCatalog.tsx b/desktop/frontend-next/src/ui/CompositionCatalog.tsx index e815b3cfec..2f05d2eb4a 100644 --- a/desktop/frontend-next/src/ui/CompositionCatalog.tsx +++ b/desktop/frontend-next/src/ui/CompositionCatalog.tsx @@ -1,6 +1,9 @@ +import { useState } from "react"; import type { AgentPresetCatalog, AgentPresetVersion } from "../port/composition"; import { t } from "../i18n"; import { StudioIcon } from "./StudioIcon"; +import { BuiltinPresetCard } from "./BuiltinPresetCard"; +import { setNewTaskPreset, useNewTaskPreset } from "../state/newTaskPreset"; import "../styles/composition-presets.css"; export function CompositionCatalog({ catalog, onSelect, onCreate, onCopy }: { @@ -9,19 +12,26 @@ export function CompositionCatalog({ catalog, onSelect, onCreate, onCopy }: { onCreate: () => void; onCopy: (version: AgentPresetVersion) => void; }) { + const preferred = useNewTaskPreset() ?? "builtin:general"; + const [error, setError] = useState(""); return

{t("Agent 预设")}

{t("为不同会话组合人设、工具、技能与扩展。模型保持独立;已开始的会话保留原版本。")}

-

{t("新会话默认使用标准模式。选择其他模式后才会启用对应预设。")}

+

{t("点击卡片,设置本机新任务的默认模式;已有会话保持当前配置。")}

+ {error &&

{error}

} {(["builtin", "user", "project"] as const).map((scope) => { const items = catalog.presets.filter(v => v.definition.id.startsWith(scope + ":")); if (!items.length) return null; return

{t(scope === "builtin" ? "内置" : scope === "user" ? "个人" : "项目共享")}

- {items.map(v => onSelect(v)} onCopy={() => onCopy(v)} />)} + {scope === "builtin" ?
{items.map(v => + setError(setNewTaskPreset(v.definition.id) ? "" : t("无法保存新任务默认模式,请检查本地存储权限。"))} + onSelect={() => onSelect(v)} onCopy={() => onCopy(v)} />)}
+ : items.map(v => onSelect(v)} onCopy={() => onCopy(v)} />)}
; })}
{t("如何使用")}

{t("在输入框顶部选择 Agent 模式,首次提交前可切换。开始对话后,请用其他模式新建会话。精简模式的终端仍可修改文件,并非只读模式。")}

diff --git a/desktop/frontend-next/src/ui/CompositionEditor.tsx b/desktop/frontend-next/src/ui/CompositionEditor.tsx index c44a88dbef..567f707e12 100644 --- a/desktop/frontend-next/src/ui/CompositionEditor.tsx +++ b/desktop/frontend-next/src/ui/CompositionEditor.tsx @@ -66,18 +66,45 @@ export function CompositionEditor({