polish(web): one intro tip naming both ways a preset is created

Copying an existing preset — or drafting one in Creator mode — replaces
the copy-minimal skeleton hint: the creator entry exists now, so the intro
names the two real paths instead of steering everyone at one source.
This commit is contained in:
Yichen Jiang
2026-08-09 01:14:35 +08:00
parent 236fe9759d
commit bf31797e95
9 changed files with 15 additions and 16 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-agent-preset/README.md
README.md: b3537da1a9607345a2d4edb73a7fa9a1f939f44f
README.zh.md: e0a1db85d3c2762660ebc3579e7a25e98a272390
README.md: 35358b1cbecffa3ad8214375ca2f929fed2d4b60
README.zh.md: c6cce1aaf19748e44f21fde7b3f3fb3c8319e5a7

View File

@@ -34,7 +34,7 @@ A fourth surface, its own settings page (`settings.section` id `agent-presets`,
The browser edits no composition text. Editing YAML in a web textarea was a weak surface (no completion, no highlighting, no diff), so a new preset is a host-side copy of an existing one — the dialog collects an id (it becomes the directory name, which is why it must be named up front and cannot change later) and an optional display name, and `{ from, id, name? }` is all that crosses the wire. Everything else — description, composition, skills — is edited in the preset's own files, and the page's other job is getting the user TO those files: the copy completes by opening the new directory, and every custom row keeps a location action. Where the host has no desktop opener (`hasDocument: false` on the roster; remote and container deployments), the same actions answer the directory as text on the row instead of offering a button that would spawn into nothing.
A shipped preset opens in the read-only viewer. It is the known-good composition a copy starts from, so reading it is the point; it offers no location and no delete — its install is overwritten by upgrades and is not the user's to manage. The intro carries the guidance a create button used to imply: to start from the smallest skeleton, duplicate 极简模式 (31 lines against standard's 233).
A shipped preset opens in the read-only viewer. It is the known-good composition a copy starts from, so reading it is the point; it offers no location and no delete — its install is overwritten by upgrades and is not the user's to manage. The intro carries the guidance a create button used to imply: duplicate an existing preset and make it yours, or let the agent draft one in Creator mode.
Beside copying sits the conversational entry: when the roster carries the self-referential `cordis` preset, a dashed add-card (the Models page's affordance) stages it and starts a new session — the section closes the settings panel through the shell's owner-prop `close` and the new-session chip's own applier composes the blank session the workspace flow produces. The seat keeps a late roster load from regressing the display: staged pick first, then the composition the current session already carries, then the deployment default.

View File

@@ -34,7 +34,7 @@ chip 以部署默认值打开,其选择是**暂存**的——该界面先于
浏览器不再编辑任何组装文本。在网页文本域里编 YAML 是弱功能(无补全、无高亮、无 diff因此新 preset 是宿主端对既有 preset 的一次复制——对话框只收集一个 id它将成为目录名所以必须当场取好、事后无法更改与一个可选显示名跨越传输层的只有 `{ from, id, name? }`。其余一切——描述、组装、skills——都在 preset 自己的文件里编辑,而本页的另一职责正是把用户送到那些文件面前:复制以打开新目录作为收尾,每张自定义卡片也保有一个位置操作。宿主没有桌面打开器时(名单上的 `hasDocument: false`;远程与容器部署),同样的操作改为把目录以文本显示在卡片上,而不是提供一个点了没反应的按钮。
随附 preset 在只读查看器中打开。它是副本据以出发的已知良好组装,因此能读到它正是意义所在;它不提供位置也不提供删除——它的安装目录会被升级覆盖,不归用户管理。开篇引导语承担了从前创建按钮所暗示的信息:想从最小的骨架开始就复制极简模式31 行,对照 standard 的 233 行)
随附 preset 在只读查看器中打开。它是副本据以出发的已知良好组装,因此能读到它正是意义所在;它不提供位置也不提供删除——它的安装目录会被升级覆盖,不归用户管理。开篇引导语承担了从前创建按钮所暗示的信息:复制一份既有预设改成自己的,或用「创造模式」让 Agent 帮你创建
复制旁边是对话式入口:名单携带自指的 `cordis` preset 时,一张虚线添加卡(模型页的同款样式)会暂存它并开启新会话——分区经外壳的 owner-prop `close` 关闭设置面板,新会话 chip 自己的应用器负责组装工作区流程产出的空白会话。seat 会防止晚到的名单加载回退显示:暂存选择优先,其次是当前会话已携带的组装,最后才是部署默认值。

View File

@@ -167,7 +167,7 @@ export function AgentPresetSection(props: AgentPresetSectionProps): ReactNode {
return (
<div className={css.section}>
<h2 className={css.title}>{t('nav')}</h2>
<p className={css.intro}>{`${t('sectionIntro')} ${t('copyHint')}`}</p>
<p className={css.intro}>{t('sectionIntro')}</p>
{state.error === null ? null : <p className={css.error} role="alert">{state.error}</p>}
{([['system', t('builtInGroup')], ['user', t('customGroup')]] as const).map(([trust, heading]) => {
const group = state.rows.filter(row => row.trust === trust)

View File

@@ -3,7 +3,7 @@
/** Locale keys these surfaces render. */
export type AgentPresetSettingsKey =
| 'title' | 'description' | 'loading' | 'error' | 'userTrust' | 'seatHint' | 'headerHint'
| 'nav' | 'sectionIntro' | 'copyHint' | 'builtIn' | 'setDefault' | 'view'
| 'nav' | 'sectionIntro' | 'builtIn' | 'setDefault' | 'view'
| 'duplicate' | 'duplicateUnavailable' | 'delete' | 'presetId' | 'presetIdPlaceholder' | 'copyOf'
| 'displayName' | 'displayNamePlaceholder'
| 'inUse' | 'noDescription' | 'builtInGroup' | 'customGroup'
@@ -25,8 +25,7 @@ export const en: Record<AgentPresetSettingsKey, string> = {
nav: 'Agent presets',
sectionIntro:
'A preset is the plugin composition one session\'s agent runs — its tools, prompt, and capabilities. '
+ 'Duplicate one to make your own, then edit its files directly.',
copyHint: 'To start from the smallest skeleton, duplicate Minimal.',
+ 'Duplicate an existing one and make it yours, or let the agent draft one for you in Creator mode.',
builtIn: 'Built-in',
setDefault: 'Set as default',
view: 'View',
@@ -76,8 +75,7 @@ export const zh: Record<AgentPresetSettingsKey, string> = {
seatHint: '即将开始的这个会话所用的 Agent 预设',
headerHint: '本会话运行的 Agent 预设,开始时即固定',
nav: 'Agent 预设',
sectionIntro: '预设即一个会话的 Agent 所运行的插件组装 —— 它的工具、提示词与能力。复制一份即可改成自己的,之后直接编辑它的文件。',
copyHint: '想从最小的骨架开始,就复制「极简模式」。',
sectionIntro: '预设即一个会话的 Agent 所运行的插件组装 —— 它的工具、提示词与能力。复制一份既有预设改成自己的,或用「创造模式」让 Agent 帮你创建。',
builtIn: '内置',
setDefault: '设为默认',
view: '查看',

View File

@@ -122,12 +122,13 @@ describe('the preset list', () => {
expect(screen.queryByRole('heading', { name: en.customGroup })).toBeNull()
})
it('leads with the guidance that creation starts from a copy', () => {
it('leads with the two ways a preset is created', () => {
renderSection()
// The page has no create button: the intro is what tells a first-time
// reader that duplicating a built-in preset IS the way to make one.
expect(screen.getByText(new RegExp(en.copyHint))).toBeTruthy()
// reader that copying an existing preset — or drafting one in Creator
// mode — IS the way to make one.
expect(screen.getByText(new RegExp('Creator mode'))).toBeTruthy()
})
it('picks a preset by clicking its card, and the one in use is inert', () => {