From dae4bfa413e44751926ba9e72cd991b79beb99df Mon Sep 17 00:00:00 2001 From: Magolor Date: Wed, 21 Oct 2026 20:11:39 +0800 Subject: [PATCH] feat(settings): use a three-line model reorder handle --- .../models-settings/reordered.expected.md | 72 ------------------- apps/web/tests/models-settings.e2e.ts | 8 +-- .../client/ui-primitives/README.i18n.yaml | 4 +- packages/client/ui-primitives/README.md | 2 +- packages/client/ui-primitives/README.zh.md | 2 +- .../client/ui-primitives/src/icons/index.tsx | 17 +++++ .../ui-primitives/tests/icons.client.spec.tsx | 4 +- .../src/client/ModelRow.tsx | 4 +- .../tests/model-order.client.spec.tsx | 2 +- 9 files changed, 30 insertions(+), 85 deletions(-) delete mode 100644 apps/web/tests/expected/models-settings/reordered.expected.md diff --git a/apps/web/tests/expected/models-settings/reordered.expected.md b/apps/web/tests/expected/models-settings/reordered.expected.md deleted file mode 100644 index 130de2d25a..0000000000 --- a/apps/web/tests/expected/models-settings/reordered.expected.md +++ /dev/null @@ -1,72 +0,0 @@ -- dialog "设置": - - text: 设置 - - navigation: - - button "通用设置" - - button "模型" - - button "内置插件" - - button "Agent 预设" - - button "打开配置文件" - - button "关闭" - - heading "模型" [level=2] - - paragraph: 填入各提供商的 API 密钥即可使用其模型。 - - list: - - listitem: - - text: minimax-cn - - img "API 密钥已配置" - - button "编辑 minimax-cn": 编辑 - - button "删除 minimax-cn": 删除 - - listitem: - - text: Acme 网关 自定义 - - button "编辑 Acme 网关 (acme-gateway)": 编辑 - - button "删除 Acme 网关 (acme-gateway)": 删除 - - text: Acme 网关 acme-gateway API 密钥 - - textbox "API 密钥": - - /placeholder: 输入 API 密钥 - - group: - - text: 自定义设置 显示名称 - - textbox "显示名称": - - /placeholder: acme-gateway - - text: Acme 网关 - - text: API 地址 - - textbox "API 地址": - - /placeholder: https://gateway.acme.example/v1 - - text: https://gateway.acme.example/v1 - - text: API 协议 - - combobox "API 协议": - - option "OpenAI Chat Completions" - - option "OpenAI Responses" - - option "Anthropic Messages" [selected] - - region "模型目录": - - text: 模型目录 已自定义模型目录 - - button "恢复默认模型" - - button "获取可用模型" - - button "调整模型顺序 1" - - textbox "模型 ID 1": - - /placeholder: 模型 ID - - text: order-second - - textbox "显示名称 1": - - /placeholder: 显示名称 - - text: order-second - - button "模型选项 1" - - button "删除模型 1" - - button "调整模型顺序 2" - - textbox "模型 ID 2": - - /placeholder: 模型 ID - - text: order-third - - textbox "显示名称 2": - - /placeholder: 显示名称 - - text: order-third - - button "模型选项 2" - - button "删除模型 2" - - button "调整模型顺序 3" - - textbox "模型 ID 3": - - /placeholder: 模型 ID - - text: acme-large - - textbox "显示名称 3": - - /placeholder: 显示名称 - - button "模型选项 3" - - button "删除模型 3" - - button "添加模型" - - button "取消" - - button "保存" - - button "添加模型提供商" diff --git a/apps/web/tests/models-settings.e2e.ts b/apps/web/tests/models-settings.e2e.ts index b4cec75da7..568a0f0803 100644 --- a/apps/web/tests/models-settings.e2e.ts +++ b/apps/web/tests/models-settings.e2e.ts @@ -586,9 +586,9 @@ describe('web e2e: Models settings page configures a dormant provider', () => { } }) await edit.click() await dialog.getByText('自定义设置').click() - expect(await dialog.getByLabel('模型 ID 1', { exact: true }).inputValue()).toBe('order-second') - await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'reordered.expected.md'), - await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd), MODE) + expect(await Promise.all([1, 2, 3].map(position => + dialog.getByLabel(`模型 ID ${String(position)}`, { exact: true }).inputValue(), + ))).toEqual(['order-second', 'order-third', 'acme-large']) const viewport = page.viewportSize() if (viewport === null) throw new Error('Model ordering checks require an explicit viewport') try { @@ -645,7 +645,7 @@ describe('web e2e: Models settings page configures a dormant provider', () => { 'configured.expected.md', 'declared-edit.expected.md', 'declared.expected.md', 'delete.expected.md', 'empty.expected.md', 'model-picker.expected.md', 'native-delete.expected.md', 'native-keyless.expected.md', 'catalog-inputs.expected.md', - 'refreshing.expected.md', 'image-only.expected.md', 'reordered.expected.md', + 'refreshing.expected.md', 'image-only.expected.md', ]) }) }) diff --git a/packages/client/ui-primitives/README.i18n.yaml b/packages/client/ui-primitives/README.i18n.yaml index 9a13d1d0a6..a709ad8518 100644 --- a/packages/client/ui-primitives/README.i18n.yaml +++ b/packages/client/ui-primitives/README.i18n.yaml @@ -18,8 +18,8 @@ en: d2f5701274be5110 zh: d60e2617136a6f26 /deepseek-ai-dsh-client-ui-primitives/use-this-package/component-catalog: - en: cb874a0ca53a6747 - zh: 31179193dd1d6e12 + en: a3d646d5179348aa + zh: 592a0abe6788541f /deepseek-ai-dsh-client-ui-primitives/use-this-package/controls-and-icons: en: fff68b60f4abdc7e zh: 5cf5952ec78f7f91 diff --git a/packages/client/ui-primitives/README.md b/packages/client/ui-primitives/README.md index ecd3fd4e29..56c2db14d8 100644 --- a/packages/client/ui-primitives/README.md +++ b/packages/client/ui-primitives/README.md @@ -70,7 +70,7 @@ Check this table before writing a control in a feature package. A plugin cannot | `UserTextRun`, `ReferenceChip` | Uninterpreted inline text and business-resolved reference labels or activation buttons. | | `CommandText` | Selectable command or argument text; only horizontal overflow adds a named keyboard focus target. | | `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, `WebBlock` | The agent-output card matching each tool-result intent. | -| `icons/*`, `FishLogo`, `BrandWordmark`, `ReferenceIconRegular`/`ReferenceIconMedium`, `LinkIconRegular`/`LinkIconMedium` | Glyphs and brand marks. The thinking glyph keeps an inset orbit within its 16px view box. `IconAppsAddOutlineRegular`/`Medium`, the More launcher glyph, take `compact` to draw their tiles closer together at card size. Use `LinkIconMedium` for 14px clickable-link categories and known-site marks. | +| `icons/*`, `FishLogo`, `BrandWordmark`, `ReferenceIconRegular`/`ReferenceIconMedium`, `LinkIconRegular`/`LinkIconMedium` | Glyphs and brand marks. `IconDragHandleOutlineRegular`/`Medium` draw a three-line grip for reordering. The thinking glyph keeps an inset orbit within its 16px view box. `IconAppsAddOutlineRegular`/`Medium`, the More launcher glyph, take `compact` to draw their tiles closer together at card size. Use `LinkIconMedium` for 14px clickable-link categories and known-site marks. | | `PermissionIconReadOnlyRegular`/`Medium`, `PermissionIconWorkspaceWriteRegular`/`Medium`, `PermissionIconFullAccessRegular`/`Medium`, `PermissionIconAutoReviewRegular`/`Medium` | Permission-mode glyphs for read-only, workspace-write, full-access, and auto-review choices. | | `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | Fixed-palette 36×36 plugin artwork; `Terminal` supplies the light-blue prompt in plugin cards and sidebar guide entries. `Default` marks plugins without artwork of their own. Def ids are per-instance, so the same artwork repeats safely on one page. | | `GuideArtworkBrowser`/`Files` | Fixed-palette 36×36 browser and folder artwork for sidebar guide entries. | diff --git a/packages/client/ui-primitives/README.zh.md b/packages/client/ui-primitives/README.zh.md index 3de121ab2c..f6bfaaf7a5 100644 --- a/packages/client/ui-primitives/README.zh.md +++ b/packages/client/ui-primitives/README.zh.md @@ -70,7 +70,7 @@ kind: "package-library" | `UserTextRun`、`ReferenceChip` | 不做解析的行内文本,以及业务已解析的引用标签或激活按钮。 | | `CommandText` | 可选中的命令或参数文本;仅横向溢出时添加具名的键盘聚焦目标。 | | `TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock`、`WebBlock` | 与各类工具结果意图对应的 agent 输出卡片。 | -| `icons/*`、`FishLogo`、`BrandWordmark`、`ReferenceIconRegular`/`ReferenceIconMedium`、`LinkIconRegular`/`LinkIconMedium` | 字形与品牌标识。思考图标的轨道在 16px 视口内保留内边距。“更多”入口使用的 `IconAppsAddOutlineRegular`/`Medium` 接受 `compact`,在卡片尺寸下让方块更靠近。`LinkIconMedium` 用于 14px 的可点击链接分类及已知站点标记。 | +| `icons/*`、`FishLogo`、`BrandWordmark`、`ReferenceIconRegular`/`ReferenceIconMedium`、`LinkIconRegular`/`LinkIconMedium` | 字形与品牌标识。`IconDragHandleOutlineRegular`/`Medium` 以三条横线表示可拖动的排序手柄。思考图标的轨道在 16px 视口内保留内边距。“更多”入口使用的 `IconAppsAddOutlineRegular`/`Medium` 接受 `compact`,在卡片尺寸下让方块更靠近。`LinkIconMedium` 用于 14px 的可点击链接分类及已知站点标记。 | | `PermissionIconReadOnlyRegular`/`Medium`、`PermissionIconWorkspaceWriteRegular`/`Medium`、`PermissionIconFullAccessRegular`/`Medium`、`PermissionIconAutoReviewRegular`/`Medium` | 只读、工作区写入、完全访问与自动审查选项使用的权限模式图形。 | | `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | 固定配色的 36×36 插件插画;`Terminal` 为插件卡片和侧边栏开始页入口提供浅蓝色提示符。`Default` 用于没有自有插画的插件。def id 按实例生成,同一插画可在一页中安全重复。 | | `GuideArtworkBrowser`/`Files` | 固定配色的 36×36 浏览器与文件夹插画,用于侧栏引导入口。 | diff --git a/packages/client/ui-primitives/src/icons/index.tsx b/packages/client/ui-primitives/src/icons/index.tsx index cdfb4faf5e..1674d03b5c 100644 --- a/packages/client/ui-primitives/src/icons/index.tsx +++ b/packages/client/ui-primitives/src/icons/index.tsx @@ -1571,6 +1571,23 @@ export const IconChevronsUpDownOutlineMedium = (props: IconProps) => ( ) +const IconDragHandleOutlineArtwork = ({ size = 16, className, strokeWidth }: WeightedIconProps) => ( + +) + +/** Regular one-pixel IconDragHandleOutline artwork. */ +export const IconDragHandleOutlineRegular = (props: IconProps) => ( + +) + +/** Medium IconDragHandleOutline artwork with a 1.3px stroke. */ +export const IconDragHandleOutlineMedium = (props: IconProps) => ( + +) + const IconArchiveOffOutlineArtwork = ({ size = 16, className, strokeWidth }: WeightedIconProps) => { // The mask carves whitespace along the slash out of the box strokes, so the // id must stay unique per rendered instance. diff --git a/packages/client/ui-primitives/tests/icons.client.spec.tsx b/packages/client/ui-primitives/tests/icons.client.spec.tsx index b79e3d4856..97bc3cc02c 100644 --- a/packages/client/ui-primitives/tests/icons.client.spec.tsx +++ b/packages/client/ui-primitives/tests/icons.client.spec.tsx @@ -17,8 +17,8 @@ const icons = Object.fromEntries( const iconNames = Object.keys(icons) describe('product icon set', () => { - it('exports regular and medium variants for all 95 public glyphs', () => { - expect(iconNames.length).toBe(190) + it('exports regular and medium variants for all 96 public glyphs', () => { + expect(iconNames.length).toBe(192) expect(iconNames.some(name => /\d+$/.test(name))).toBe(false) const regular = iconNames.filter(name => name.endsWith('Regular')).map(name => name.slice(0, -'Regular'.length)) const medium = iconNames.filter(name => name.endsWith('Medium')).map(name => name.slice(0, -'Medium'.length)) diff --git a/packages/client/ui-settings-models/src/client/ModelRow.tsx b/packages/client/ui-settings-models/src/client/ModelRow.tsx index af567b65fe..b0252635c0 100644 --- a/packages/client/ui-settings-models/src/client/ModelRow.tsx +++ b/packages/client/ui-settings-models/src/client/ModelRow.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from 'react' import { - IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, IconChevronsUpDownOutlineRegular, + IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, IconDragHandleOutlineRegular, IconTrashOutlineRegular, Tooltip, } from '@deepseek-ai/dsh-client-ui-primitives' import type { DeepSeekModelDraft } from './DeepSeekModelsEditor.tsx' @@ -89,7 +89,7 @@ export function ModelRow(props: ModelRowProps): ReactNode { props.order.onMove(event.key === 'ArrowUp' ? -1 : 1) }} > - + {(['id', 'name'] as const).map(field => ( diff --git a/packages/client/ui-settings-models/tests/model-order.client.spec.tsx b/packages/client/ui-settings-models/tests/model-order.client.spec.tsx index 1dfd141951..f108a95531 100644 --- a/packages/client/ui-settings-models/tests/model-order.client.spec.tsx +++ b/packages/client/ui-settings-models/tests/model-order.client.spec.tsx @@ -42,7 +42,7 @@ function row(button: HTMLButtonElement): HTMLElement { } function transfer() { - return { setData: vi.fn(), setDragImage: vi.fn(), effectAllowed: '', dropEffect: '' } + return { setData: vi.fn(), effectAllowed: '', dropEffect: '' } } describe.each(['deepseek', 'pi-ai'] as const)('%s model ordering', (family) => {