diff --git a/apps/web/tests/snapshots/agent-preset-authoring/created.expected.md b/apps/web/tests/snapshots/agent-preset-authoring/created.expected.md index aece772fe4..b26cba28f4 100644 --- a/apps/web/tests/snapshots/agent-preset-authoring/created.expected.md +++ b/apps/web/tests/snapshots/agent-preset-authoring/created.expected.md @@ -10,9 +10,9 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img diff --git a/apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md b/apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md index 7620e679c8..14a6337736 100644 --- a/apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md +++ b/apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md @@ -10,9 +10,9 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img diff --git a/apps/web/tests/snapshots/agent-preset-authoring/section.expected.md b/apps/web/tests/snapshots/agent-preset-authoring/section.expected.md index dade3d7b84..7c35b40786 100644 --- a/apps/web/tests/snapshots/agent-preset-authoring/section.expected.md +++ b/apps/web/tests/snapshots/agent-preset-authoring/section.expected.md @@ -10,9 +10,9 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img diff --git a/apps/web/tests/snapshots/models-settings/configured.expected.md b/apps/web/tests/snapshots/models-settings/configured.expected.md index 3b57d5ac6f..e4fb6e13e8 100644 --- a/apps/web/tests/snapshots/models-settings/configured.expected.md +++ b/apps/web/tests/snapshots/models-settings/configured.expected.md @@ -10,9 +10,9 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img diff --git a/apps/web/tests/snapshots/models-settings/declared.expected.md b/apps/web/tests/snapshots/models-settings/declared.expected.md index dd3f03776f..b126a5025b 100644 --- a/apps/web/tests/snapshots/models-settings/declared.expected.md +++ b/apps/web/tests/snapshots/models-settings/declared.expected.md @@ -10,9 +10,9 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img diff --git a/apps/web/tests/snapshots/models-settings/empty.expected.md b/apps/web/tests/snapshots/models-settings/empty.expected.md index 16cc93581b..5a1dba54ee 100644 --- a/apps/web/tests/snapshots/models-settings/empty.expected.md +++ b/apps/web/tests/snapshots/models-settings/empty.expected.md @@ -10,9 +10,9 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img diff --git a/apps/web/tests/snapshots/onboarding-deepseek-config/models.expected.md b/apps/web/tests/snapshots/onboarding-deepseek-config/models.expected.md index b5f25aaaa6..2624f4db70 100644 --- a/apps/web/tests/snapshots/onboarding-deepseek-config/models.expected.md +++ b/apps/web/tests/snapshots/onboarding-deepseek-config/models.expected.md @@ -10,9 +10,9 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img diff --git a/apps/web/tests/snapshots/plugin-config/section.expected.md b/apps/web/tests/snapshots/plugin-config/section.expected.md index 54ef09851f..7d10d05cd1 100644 --- a/apps/web/tests/snapshots/plugin-config/section.expected.md +++ b/apps/web/tests/snapshots/plugin-config/section.expected.md @@ -10,25 +10,25 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img - text: 关闭 - heading "插件配置" [level=2] - - paragraph: 本部署所组装插件自己拥有的设置。你在这里设的值会覆盖组装默认值,并在下一次使用时生效。 + - paragraph: 配置本部署已安装的插件。 - list: - listitem: - - button "终端 限制 agent 运行的每一条命令。": - - img + - 'button "展开设置: 终端"': - text: 终端 限制 agent 运行的每一条命令。 - - listitem: - - button "Agent 循环 Agent 如何派发工具调用。": - img + - listitem: + - 'button "展开设置: Agent 循环"': - text: Agent 循环 Agent 如何派发工具调用。 - - listitem: - - button "网页搜索 DeepSeek 搜索提供方。": - img + - listitem: + - 'button "展开设置: 网页搜索"': - text: 网页搜索 DeepSeek 搜索提供方。 + - img diff --git a/apps/web/tests/snapshots/settings-chrome/dialog.expected.md b/apps/web/tests/snapshots/settings-chrome/dialog.expected.md index 3ac0245395..914293aee3 100644 --- a/apps/web/tests/snapshots/settings-chrome/dialog.expected.md +++ b/apps/web/tests/snapshots/settings-chrome/dialog.expected.md @@ -10,9 +10,9 @@ - button "Agent 预设": - img - text: Agent 预设 - - button "插件": + - button "插件配置": - img - - text: 插件 + - text: 插件配置 - button "打开配置文件" - button "关闭": - img diff --git a/packages/client/ui-plugin-config/package.json b/packages/client/ui-plugin-config/package.json index f3a9c04e62..61cec64110 100644 --- a/packages/client/ui-plugin-config/package.json +++ b/packages/client/ui-plugin-config/package.json @@ -67,5 +67,8 @@ "lib/invariant.js", "lib/client.js", "lib/types/**/*.d.ts" - ] + ], + "dependencies": { + "clsx": "^2.0.0" + } } diff --git a/packages/client/ui-plugin-config/src/client/AgentLoopCard.tsx b/packages/client/ui-plugin-config/src/client/AgentLoopCard.tsx index 99790467dc..1e47453eff 100644 --- a/packages/client/ui-plugin-config/src/client/AgentLoopCard.tsx +++ b/packages/client/ui-plugin-config/src/client/AgentLoopCard.tsx @@ -1,6 +1,5 @@ /** The agent-loop plugin's card: how many tool calls may run at once. */ -import { useState } from 'react' import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client' import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' import { NumberField } from './fields.tsx' @@ -34,17 +33,14 @@ export type AgentLoopCardProps = export function AgentLoopCard(props: AgentLoopCardProps) { const { t } = props const state = props.useAgentLoopCard(snapshot => snapshot) - const [open, setOpen] = useState(false) const disabled = !state.writable return ( { setOpen(!open) }} readOnly={disabled} - readOnlyLabel={t('readOnly')} > snapshot) - const [open, setOpen] = useState(false) const disabled = !state.writable return ( { setOpen(!open) }} readOnly={disabled} - readOnlyLabel={t('readOnly')} > string + /** Locale key of the plugin's name. */ + titleKey: PluginConfigKey + /** Locale key of the line describing what this plugin's settings govern. */ + descriptionKey: PluginConfigKey /** False while the namespace is not served to this client. */ available: boolean - /** Whether the card body is showing. */ - open: boolean - /** Toggle the card body. */ - onToggle: () => void - /** Copy shown when the settings document refuses writes. */ - readOnlyLabel?: string | undefined - /** True when the Host document is read-only. */ + /** True when the Host document is read-only, which disables the fields. */ readOnly: boolean /** The plugin's controls. */ children: ReactNode @@ -31,31 +37,36 @@ export interface PluginCardProps { /** * Render one plugin card. - * @param props - card chrome, disclosure state, and the plugin's controls. + * @param props - the plugin's copy keys, its availability, and its controls. * @returns the card, or nothing when the namespace is unavailable. */ export function PluginCard(props: PluginCardProps) { + const [open, setOpen] = useState(false) if (!props.available) return null + const title = props.t(props.titleKey) return ( -
  • - {props.description}} +
  • + + {open + ? ( +
    + {props.readOnly ?

    {props.t('readOnly')}

    : null} + {props.children} +
    + ) + : null}
  • ) } diff --git a/packages/client/ui-plugin-config/src/client/PluginConfigSection.module.css b/packages/client/ui-plugin-config/src/client/PluginConfigSection.module.css index 6f2af513e9..45c9a78f00 100644 --- a/packages/client/ui-plugin-config/src/client/PluginConfigSection.module.css +++ b/packages/client/ui-plugin-config/src/client/PluginConfigSection.module.css @@ -3,34 +3,34 @@ .section { display: flex; flex-direction: column; + gap: 12px; + max-width: 720px; + color: var(--dsw-alias-label-primary); } .heading { margin: 0; - font-size: 16px; - font-weight: 500; - line-height: 24px; - color: var(--dsw-alias-label-primary); + font-size: 18px; + font-weight: 600; } .intro { - margin: 8px 0 16px; - font-size: 12px; - font-weight: 400; - line-height: 18px; + margin: 0; + font-size: 13px; color: var(--dsw-alias-label-tertiary); } .cards { + list-style: none; margin: 0; padding: 0; - list-style: none; + display: flex; + flex-direction: column; + gap: 10px; } .empty { margin: 0; - padding: 16px 0; - font-size: 14px; - line-height: 22px; + font-size: 13px; color: var(--dsw-alias-label-tertiary); } diff --git a/packages/client/ui-plugin-config/src/client/WebSearchCard.tsx b/packages/client/ui-plugin-config/src/client/WebSearchCard.tsx index 72c11fa545..702a7a6627 100644 --- a/packages/client/ui-plugin-config/src/client/WebSearchCard.tsx +++ b/packages/client/ui-plugin-config/src/client/WebSearchCard.tsx @@ -4,7 +4,6 @@ * the settings section, so the literal never rides a response. */ -import { useState } from 'react' import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client' import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' import { NumberField, SecretField, TextField } from './fields.tsx' @@ -44,17 +43,14 @@ export type WebSearchCardProps = export function WebSearchCard(props: WebSearchCardProps) { const { t } = props const state = props.useWebSearchCard(snapshot => snapshot) - const [open, setOpen] = useState(false) const disabled = !state.writable return ( { setOpen(!open) }} readOnly={disabled} - readOnlyLabel={t('readOnly')} > + maxParallelToolCalls: CardField } /** The registration-side face the agent-loop card's slot entry injects. */ @@ -42,7 +42,7 @@ export class AgentLoopCardController extends CardController) { super(scope, snapshot => ({ ...shellOf(snapshot), - maxParallelToolCalls: fieldOf(snapshot, 'maxParallelToolCalls', 0), + maxParallelToolCalls: fieldOf(snapshot, 'maxParallelToolCalls', undefined), })) } diff --git a/packages/client/ui-plugin-config/src/client/bash-store.ts b/packages/client/ui-plugin-config/src/client/bash-store.ts index 91114669c1..837a6fab04 100644 --- a/packages/client/ui-plugin-config/src/client/bash-store.ts +++ b/packages/client/ui-plugin-config/src/client/bash-store.ts @@ -21,9 +21,9 @@ export interface BashSettings { /** What the shell card renders. */ export interface BashCardState extends CardShell { /** Command timeout in milliseconds. */ - timeoutMs: CardField + timeoutMs: CardField /** Per-stream output cap in bytes. */ - maxOutputBytes: CardField + maxOutputBytes: CardField } /** The registration-side face the shell card's slot entry injects. */ @@ -50,8 +50,8 @@ export class BashCardController extends CardController void }) { @@ -124,13 +127,13 @@ export function NumberField(props: FieldProps & { { const parsed = Number(draft) if (draft.trim() === '' || !Number.isFinite(parsed)) { - restore(String(props.value)) + restore(props.value === undefined ? '' : String(props.value)) return } if (parsed === props.value) return diff --git a/packages/client/ui-plugin-config/src/client/locales.ts b/packages/client/ui-plugin-config/src/client/locales.ts index 6a3804236c..7babcc1bdd 100644 --- a/packages/client/ui-plugin-config/src/client/locales.ts +++ b/packages/client/ui-plugin-config/src/client/locales.ts @@ -13,11 +13,9 @@ export type PluginConfigKey = /** English copy. */ export const en: Record = { - nav: 'Plugins', + nav: 'Plugin config', title: 'Plugin configuration', - intro: - 'Settings owned by the plugins this deployment composes. A value you set here layers over the ' - + 'composition default and applies to the next use.', + intro: 'Configure the plugins this deployment installed.', empty: 'This deployment exposes no plugin settings.', overridden: 'Overridden', reset: 'Reset to default', @@ -48,9 +46,9 @@ export const en: Record = { /** Simplified Chinese copy. */ export const zh: Record = { - nav: '插件', + nav: '插件配置', title: '插件配置', - intro: '本部署所组装插件自己拥有的设置。你在这里设的值会覆盖组装默认值,并在下一次使用时生效。', + intro: '配置本部署已安装的插件。', empty: '本部署没有开放任何插件设置。', overridden: '已覆盖', reset: '恢复默认', diff --git a/packages/client/ui-plugin-config/src/client/web-search-store.ts b/packages/client/ui-plugin-config/src/client/web-search-store.ts index c0242eed8b..2def74cb79 100644 --- a/packages/client/ui-plugin-config/src/client/web-search-store.ts +++ b/packages/client/ui-plugin-config/src/client/web-search-store.ts @@ -36,7 +36,7 @@ export interface WebSearchCardState extends CardShell { /** Provider endpoint. */ baseURL: CardField /** Searches allowed per request. */ - maxUses: CardField + maxUses: CardField /** Credential reference the key is written under. */ apiKeyRef: string /** Whether the Host reports a credential configured for that reference. */ @@ -78,7 +78,7 @@ export class WebSearchCardController extends CardController ({ ...shellOf(snapshot), baseURL: fieldOf(snapshot, 'baseURL', ''), - maxUses: fieldOf(snapshot, 'maxUses', 0), + maxUses: fieldOf(snapshot, 'maxUses', undefined), apiKeyRef: refOf(snapshot), apiKeyConfigured: credential.configured, })) diff --git a/packages/client/ui-plugin-config/tests/apply.spec.ts b/packages/client/ui-plugin-config/tests/apply.spec.ts index 86bb21606a..233f0cd337 100644 --- a/packages/client/ui-plugin-config/tests/apply.spec.ts +++ b/packages/client/ui-plugin-config/tests/apply.spec.ts @@ -48,7 +48,7 @@ describe('ui-plugin-config apply', () => { const section = slots.entries('settings.section')[0]! expect(section.options).toMatchObject({ id: 'plugins', order: 30 }) // The nav label is a locale-following thunk; owners resolve it at read time. - expect(resolveSlotLabel(section.options.label)).toBe('插件') + expect(resolveSlotLabel(section.options.label)).toBe('插件配置') expect(slots.spec('settings.plugin.item')).toMatchObject({ kind: 'list', scope: 'root' }) }) diff --git a/packages/client/ui-plugin-config/tests/fields.spec.tsx b/packages/client/ui-plugin-config/tests/fields.spec.tsx index d444ae14a8..0248fee10d 100644 --- a/packages/client/ui-plugin-config/tests/fields.spec.tsx +++ b/packages/client/ui-plugin-config/tests/fields.spec.tsx @@ -116,6 +116,22 @@ describe('NumberField', () => { expect(onCommit).not.toHaveBeenCalled() }) + it('renders an absent value as empty rather than as a number nobody chose', () => { + const onCommit = vi.fn() + render( + , + ) + const input = screen.getByLabelText('Command timeout') + expect(input).toHaveProperty('value', '') + + // A draft typed and then cleared restores the same emptiness, not a zero. + fireEvent.change(input, { target: { value: 'abc' } }) + fireEvent.blur(input) + + expect(input).toHaveProperty('value', '') + expect(onCommit).not.toHaveBeenCalled() + }) + it('suppresses every interaction while disabled', () => { const onCommit = vi.fn() const onReset = vi.fn() diff --git a/packages/client/ui-settings/src/client/SettingsRoot.tsx b/packages/client/ui-settings/src/client/SettingsRoot.tsx index 54e0e0dbb7..23f5421ff3 100644 --- a/packages/client/ui-settings/src/client/SettingsRoot.tsx +++ b/packages/client/ui-settings/src/client/SettingsRoot.tsx @@ -14,7 +14,8 @@ import { useCallback, useEffect, useId, useRef, useState } from 'react' import clsx from 'clsx' import { - IconCloseOutline16, IconDataOutline16, IconSettingsOutline16, IconThinkOutline16, + IconCloseOutline16, IconDataOutline16, IconPersonalizationOutline16, + IconSettingsOutline16, IconThinkOutline16, } from '@deepseek-ai/dsh-client-ui-primitives' import type { SettingsRootComponentProps, SettingsSectionRow } from './contract/slots.ts' import css from './SettingsRoot.module.css' @@ -23,6 +24,7 @@ import css from './SettingsRoot.module.css' function navIcon(id: string) { if (id === 'models') return if (id === 'agent-presets') return + if (id === 'plugins') return return } diff --git a/packages/web/web-search-deepseek/src/index.ts b/packages/web/web-search-deepseek/src/index.ts index 50d9c69e22..bb3a6efe0a 100644 --- a/packages/web/web-search-deepseek/src/index.ts +++ b/packages/web/web-search-deepseek/src/index.ts @@ -63,11 +63,14 @@ export interface Config { export const Config: z = z.object({ apiKey: z.string().role('secret'), apiKeyEnv: z.string().role('credential-ref').default(DEFAULT_API_KEY_ENV), + // Declared here rather than only at the use site: a configuration surface + // renders the resolved section, so a default the schema does not carry reads + // there as no value at all. baseURL: z.string(), - model: z.string(), - apiVersion: z.string(), - maxTokens: z.number().step(1).min(1), - maxUses: z.number().step(1).min(1), + model: z.string().default(DEEPSEEK_DEFAULT_MODEL), + apiVersion: z.string().default(DEEPSEEK_DEFAULT_API_VERSION), + maxTokens: z.number().step(1).min(1).default(DEEPSEEK_DEFAULT_MAX_TOKENS), + maxUses: z.number().step(1).min(1).default(DEEPSEEK_DEFAULT_MAX_USES), }) /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c1950a855..50b497608c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2291,6 +2291,10 @@ importers: version: 18.3.1 packages/client/ui-plugin-config: + dependencies: + clsx: + specifier: ^2.0.0 + version: 2.1.1 devDependencies: '@deepseek-ai/dsh-client-connection': specifier: workspace:^