From c8b17dd4d0cf54755bdd30c3e6943c79069eae55 Mon Sep 17 00:00:00 2001 From: Yif <877193178@qq.com> Date: Fri, 18 Sep 2026 11:30:35 +0800 Subject: [PATCH] refine(plugin-manager): per-plugin artwork, trailing load-failure notice, friendlier copy --- .../src/client/PluginManagerPage.module.css | 19 +++- .../src/client/PluginManagerPage.tsx | 93 ++++++++++----- .../ui-plugin-manager/src/client/locales.ts | 12 +- packages/client/ui-primitives/src/index.ts | 3 + .../ui-primitives/src/plugin-artwork.tsx | 107 ++++++++++++++++++ .../ui-settings-plugins/src/client/locales.ts | 12 +- 6 files changed, 204 insertions(+), 42 deletions(-) create mode 100644 packages/client/ui-primitives/src/plugin-artwork.tsx diff --git a/packages/client/ui-plugin-manager/src/client/PluginManagerPage.module.css b/packages/client/ui-plugin-manager/src/client/PluginManagerPage.module.css index 38875cb7c6..63f55bd302 100644 --- a/packages/client/ui-plugin-manager/src/client/PluginManagerPage.module.css +++ b/packages/client/ui-plugin-manager/src/client/PluginManagerPage.module.css @@ -117,8 +117,9 @@ /* Center the status label geometrically: beside larger titles the capsule's line-box offset shows. */ .statusTag { - height: 20px; - padding: 0 8px; + height: 18px; + padding: 0 7px; + font-size: 10px; line-height: 1; } @@ -238,7 +239,10 @@ border: 0; background: transparent; color: inherit; + /* font: inherit resets the size, so the card title's own size follows. */ font: inherit; + font-size: 14px; + line-height: 20px; font-weight: 500; text-align: left; cursor: pointer; @@ -303,6 +307,15 @@ cursor: default; } +/* The toolbar's Add plugin action, one step up from the 28px sm capsule. */ +.addButton { + height: 32px; + font-size: 13px; + line-height: 20px; + padding: 0 12px; + border-radius: 16px; +} + .iconWrap { display: inline-flex; } @@ -384,7 +397,7 @@ /* Wider than the default dialog: pnpm's lines are long, and the terminal keeps them unwrapped. The card never outgrows the viewport; the running screens scroll inside it so the head's controls stay reachable. */ .installDialog { - width: min(600px, 100%); + width: min(540px, 100%); max-height: calc(100vh - 48px); } diff --git a/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx b/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx index 65eece8d3f..f27252c775 100644 --- a/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx +++ b/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx @@ -13,10 +13,12 @@ import { useEffect, useId, useState, type ReactNode } from 'react' import type { PluginInstallFailureKind } from '@deepseek-ai/dsh-api-remotes/client' import { Button, IconChevronDownOutlineRegular, IconChevronLeftOutlineRegular, - IconChevronRightOutlineRegular, IconCloseOutlineRegular, IconCordisPluginOutlineRegular, - IconPluginPinwheelOutlineRegular, IconPlusOutlineRegular, IconRefreshOutlineRegular, IconTrashOutlineRegular, - IconWarningOutlineRegular, Input, Modal, StateDot, Switch, Tag, TerminalBlock, Toast, - type StateDotState, type TerminalBlockLabels, + IconChevronRightOutlineRegular, IconCloseOutlineRegular, + IconPlusOutlineRegular, IconRefreshOutlineRegular, IconTrashOutlineRegular, + IconWarningOutlineRegular, Input, Modal, + PluginArtworkDefault, PluginArtworkLoop, PluginArtworkSearch, PluginArtworkSubagent, PluginArtworkTeam, PluginArtworkTerminal, + StateDot, Switch, Tag, TerminalBlock, Toast, + type IconProps, type StateDotState, type TerminalBlockLabels, } from '@deepseek-ai/dsh-client-ui-primitives' import type { InjectFace, PropsLocale, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' import { rowConfigKey, type OfficialItem } from './config-ledger.ts' @@ -112,6 +114,38 @@ interface RowConfigure { /** Rows beyond this count get a filter box above the list. */ const ROW_FILTER_THRESHOLD = 10 +/** The size the 36-viewBox plugin artwork renders at inside a card's 48px frame. */ +const CARD_ARTWORK_SIZE = 36 + +/** The size the artwork renders at inside a row's 40px frame. */ +const ROW_ARTWORK_SIZE = 30 + +/** The artwork of the official plugins that registered their configuration, by registration id. */ +const ITEM_ARTWORK = new Map ReactNode>([ + ['bash', PluginArtworkTerminal], + ['agent-loop', PluginArtworkLoop], + ['subagent', PluginArtworkSubagent], + ['web-search', PluginArtworkSearch], +]) + +/** The artwork of the official bundles with artwork of their own, by package name. */ +const PACKAGE_ARTWORK = new Map ReactNode>([ + ['@deepseek-ai/dsh-experimental-agent-team-profile', PluginArtworkTeam], + ['@deepseek-ai/dsh-experimental-agent-team-web-profile', PluginArtworkTeam], +]) + +/** An official plugin's card and page artwork; plugins without their own get the default. */ +function itemArtwork(id: string): ReactNode { + const Artwork = ITEM_ARTWORK.get(id) ?? PluginArtworkDefault + return +} + +/** A package's card and page artwork; packages without their own get the default. */ +function packageArtwork(name: string): ReactNode { + const Artwork = PACKAGE_ARTWORK.get(name) ?? PluginArtworkDefault + return +} + /** A row's switch: locked, saying why, when the Host refuses to address the row through the profile patch. */ function RowSwitch({ row, t, busy, onChange }: { readonly row: PackageRow @@ -190,7 +224,7 @@ function RowsSection({ rows, t, toggle, configure }: { {...row.phase === 'failed' ? { 'data-state': 'failed' } : row.enabled ? {} : { 'data-state': 'off' }} >
- +
{configure?.has(row) === true ? ( @@ -246,18 +280,19 @@ function packageStatus(pkg: PackageView): 'running' | 'disabled' | 'problem' { return pkg.enabled ? 'running' : 'disabled' } -/** The head every card shares: the pinwheel icon, the name that opens the page beside its tags, its one-liner, and what sits at the end. */ -function CardHead({ title, t, onOpen, tags, description, end }: { +/** The head every card shares: the artwork, the name that opens the page beside its tags, its one-liner, and what sits at the end. */ +function CardHead({ title, t, onOpen, icon, tags, description, end }: { readonly title: string readonly t: Translate readonly onOpen: () => void + readonly icon: ReactNode readonly tags?: ReactNode readonly description: ReactNode readonly end?: ReactNode }): ReactNode { return (
- +
@@ -275,7 +310,7 @@ function DetailTop({ crumbLabel, crumbText, onBack, icon, actions }: { readonly crumbLabel: string readonly crumbText: string readonly onBack: () => void - readonly icon?: ReactNode + readonly icon: ReactNode readonly actions?: ReactNode }): ReactNode { return ( @@ -285,7 +320,7 @@ function DetailTop({ crumbLabel, crumbText, onBack, icon, actions }: { {crumbText}
- + {actions}
@@ -314,6 +349,7 @@ function PackageCard({ pkg, t, busy, highlighted, onOpen, onSetEnabled }: { title={title} t={t} onOpen={onOpen} + icon={packageArtwork(pkg.name)} tags={( <> {beta ? {t('statusBeta')} : null} @@ -339,7 +375,7 @@ function ItemCard({ item, t, onOpen, renderSlot }: { }): ReactNode { return (
  • - +
  • ) } @@ -353,7 +389,7 @@ function ItemDetail({ item, t, onBack, renderSlot }: { }): ReactNode { return (
    - +

    {item.label}

    @@ -382,7 +418,7 @@ function RowDetail({ pkg, row, t, onBack, renderSlot }: { const key = rowConfigKey(pkg.name, row.rowId) return (
    - } /> + } />

    {row.rowId}

    @@ -431,6 +467,7 @@ function PackageDetail({ crumbLabel={t('backToList')} crumbText={t('crumbRoot')} onBack={onBack} + icon={packageArtwork(pkg.name)} actions={(
    {pkg.installed @@ -918,31 +955,21 @@ export function PluginManagerPage(props: PluginManagerPageProps): ReactNode { - +
    ) : null} - {state.status === 'loading' ? ( + {showsCards && state.status === 'loading' ? (

    {t('loading')}

    ) : null} - {state.status === 'unavailable' ? ( + {showsCards && state.status === 'unavailable' ? (

    {t('unavailable')}

    ) : null} - {state.status === 'error' - ? ( -
    -

    - {t('error')} -

    - -
    - ) - : null} {state.notice === null || noticeLine === null ? null : ( @@ -986,12 +1013,24 @@ export function PluginManagerPage(props: PluginManagerPageProps): ReactNode { ? { setView({ kind: 'list' }) }} /> : null} {loaded && showsCards - ? officialCards.length === 0 && mine.length === 0 + ? officialCards.length === 0 && mine.length === 0 && state.status !== 'error' ?

    {t('empty')}

    : ( <> {renderGroup('official', t('officialTitle'), officialCards)} {renderGroup('bundles', t('bundlesTitle'), mine.map(packageCard))} + {/* A failed package read trails the groups it left incomplete: right under Official on a + first-load failure, and after the kept cards when a refresh fails over stale data. */} + {state.status === 'error' + ? ( +
    +

    + {t('error')} +

    + +
    + ) + : null} ) : null} diff --git a/packages/client/ui-plugin-manager/src/client/locales.ts b/packages/client/ui-plugin-manager/src/client/locales.ts index 63071a5cb7..f8f666a219 100644 --- a/packages/client/ui-plugin-manager/src/client/locales.ts +++ b/packages/client/ui-plugin-manager/src/client/locales.ts @@ -6,7 +6,7 @@ export const zh = { title: '插件', intro: '添加和管理插件', loading: '正在读取插件…', - error: '暂时无法读取插件。', + error: '可能由于网络问题,无法读取全部插件', unavailable: '本部署没有可管理的 profile,无法安装或启停插件。', retry: '重试', refresh: '刷新', @@ -17,9 +17,9 @@ export const zh = { bundlesTitle: '已安装', officialTitle: '官方', builtinAgentTeamTitle: '智能体团队', - builtinAgentTeamDescription: '启用智能体团队协作与团队工具。', + builtinAgentTeamDescription: '把一个任务交给一支团队:主智能体按需创建队友,成员之间发消息沟通、共同维护一块任务看板,各自认领工作;中途退出也不丢进度。', builtinAgentTeamWebTitle: '智能体团队 Web 界面', - builtinAgentTeamWebDescription: '在浏览器中查看团队成员、任务看板和成员会话。', + builtinAgentTeamWebDescription: '在浏览器里看团队干活:谁在忙、谁空闲一目了然,任务看板实时更新,点开任意成员就能进入它的会话。', builtinAutoReviewTitle: '自动授权审查', builtinAutoReviewDescription: '提供自动审查权限模式,由模型在每次工具调用前判断是否授权。', statusProblem: '异常', @@ -168,7 +168,7 @@ export const en = { title: 'Plugins', intro: 'Add and manage plugins', loading: 'Reading plugins…', - error: 'Plugins are temporarily unavailable.', + error: 'Could not read all plugins, possibly due to a network problem', unavailable: 'This deployment runs without a manageable profile, so plugins cannot be installed or switched here.', retry: 'Retry', refresh: 'Refresh', @@ -179,9 +179,9 @@ export const en = { bundlesTitle: 'Installed', officialTitle: 'Official', builtinAgentTeamTitle: 'Agent Teams', - builtinAgentTeamDescription: 'Enable agent team collaboration and team tools.', + builtinAgentTeamDescription: 'Hand a task to a team: the lead agent creates teammates as needed, members message each other and claim work on a shared task board, and progress survives even if someone drops off mid-way.', builtinAgentTeamWebTitle: 'Agent Teams Web UI', - builtinAgentTeamWebDescription: 'View team members, the task board, and teammate sessions in the browser.', + builtinAgentTeamWebDescription: 'Watch the team at work in the browser: see who is busy and who is idle at a glance, follow the task board live, and open any member\'s own session with a click.', builtinAutoReviewTitle: 'Auto Authorization Review', builtinAutoReviewDescription: 'Add an Auto review permission mode that uses the model to assess authorization before each tool call.', statusProblem: 'Problem', diff --git a/packages/client/ui-primitives/src/index.ts b/packages/client/ui-primitives/src/index.ts index 3e84437d9f..1e8fc33423 100644 --- a/packages/client/ui-primitives/src/index.ts +++ b/packages/client/ui-primitives/src/index.ts @@ -80,3 +80,6 @@ export type { MarkdownCodeLabels, MarkdownFileMentions, MarkdownLabels, Markdown export { extractMarkdownPlainText } from './markdown/plain-text.ts' export type { MarkdownPlainTextMode, MarkdownPlainTextOptions } from './markdown/plain-text.ts' export * from './icons/index.tsx' +export { + PluginArtworkTeam, PluginArtworkTerminal, PluginArtworkLoop, PluginArtworkSubagent, PluginArtworkSearch, PluginArtworkDefault, +} from './plugin-artwork.tsx' diff --git a/packages/client/ui-primitives/src/plugin-artwork.tsx b/packages/client/ui-primitives/src/plugin-artwork.tsx new file mode 100644 index 0000000000..2e70e6a71e --- /dev/null +++ b/packages/client/ui-primitives/src/plugin-artwork.tsx @@ -0,0 +1,107 @@ +/** + * Fixed-palette plugin artwork for the plugin management surfaces. Unlike the + * ic_ds_* set these glyphs carry their own brand colors and gradients instead + * of riding currentColor; all draw on a 36×36 viewBox and take {size, className}. + */ +import type { IconProps } from './icons/props.ts' + +/** Agent-team plugin artwork (four interlocking quadrants). */ +export const PluginArtworkTeam = ({ size = 36, className }: IconProps) => ( + + + + + + + + + + + + +) + +/** Terminal plugin artwork (prompt chevron and cursor bar). */ +export const PluginArtworkTerminal = ({ size = 36, className }: IconProps) => ( + + + + +) + +/** Agent-loop plugin artwork (four leaves circling a center). */ +export const PluginArtworkLoop = ({ size = 36, className }: IconProps) => ( + + + + + + + + + + + + +) + +/** Subagent plugin artwork (two stacked rounded squares); also marks one row inside a bundle. */ +export const PluginArtworkSubagent = ({ size = 36, className }: IconProps) => ( + + + + + + + + + + + + + + +) + +/** + * Web-search plugin artwork (conic-gradient ring and handle). SVG has no + * native conic gradient, so the ring clips an HTML div painted with CSS + * `conic-gradient` — the same emulation Figma exports; it renders inline in + * the browser UI but would stay empty in an `` or mask context. + */ +export const PluginArtworkSearch = ({ size = 36, className }: IconProps) => ( + + + + + +
    + + + + + + + + + +) + +/** Default plugin artwork for plugins without one of their own (connector blocks and a node). */ +export const PluginArtworkDefault = ({ size = 36, className }: IconProps) => ( + + + + + + + + + +) diff --git a/packages/client/ui-settings-plugins/src/client/locales.ts b/packages/client/ui-settings-plugins/src/client/locales.ts index e45dbb5cd0..562ed2d1e8 100644 --- a/packages/client/ui-settings-plugins/src/client/locales.ts +++ b/packages/client/ui-settings-plugins/src/client/locales.ts @@ -42,17 +42,17 @@ export const en: Record = { saveFailed: 'The deployment did not accept these values; they were left for you to correct.', invalidNumber: 'Enter a number, or leave blank to use the default.', bashTitle: 'Shell', - bashDescription: 'Limits every command the agent runs.', + bashDescription: 'Limit how long each command may run and how much it may output.', bashTimeoutMs: 'Command timeout (ms)', bashTimeoutMsHint: 'How long one command may run before it is terminated.', bashMaxOutputBytes: 'Output cap per stream (bytes)', bashMaxOutputBytesHint: 'Output beyond this spills to a temporary file rather than being lost.', agentLoopTitle: 'Agent loop', - agentLoopDescription: 'How the agent dispatches tool calls.', + agentLoopDescription: 'Control how the Agent dispatches tool calls.', agentLoopMaxParallel: 'Parallel tool calls', agentLoopMaxParallelHint: 'Upper bound on parallel-safe calls running at once within one step.', webSearchTitle: 'Web search', - webSearchDescription: 'The DeepSeek search provider.', + webSearchDescription: 'Set up the DeepSeek search provider.', webSearchApiKey: 'API key', webSearchApiKeyHint: 'Stored outside the settings file. Leave blank to keep the current key.', webSearchApiKeySet: 'A key is configured.', @@ -107,17 +107,17 @@ export const zh: Record = { saveFailed: '本部署没有接受这些值,已保留供你修改。', invalidNumber: '请填数字;留空表示使用默认值。', bashTitle: '终端', - bashDescription: '限制 agent 运行的每一条命令。', + bashDescription: '限制每条命令最多能跑多久、最多输出多少内容。', bashTimeoutMs: '命令超时(毫秒)', bashTimeoutMsHint: '单条命令允许运行多久,超时即终止。', bashMaxOutputBytes: '单流输出上限(字节)', bashMaxOutputBytesHint: '超出部分会转存到临时文件,而不是被丢弃。', agentLoopTitle: 'Agent 循环', - agentLoopDescription: 'Agent 如何派发工具调用。', + agentLoopDescription: '控制 Agent 派发工具调用的方式。', agentLoopMaxParallel: '并行工具调用数', agentLoopMaxParallelHint: '同一步内最多同时运行多少个可并行的调用。', webSearchTitle: '网页搜索', - webSearchDescription: 'DeepSeek 搜索提供方。', + webSearchDescription: '设置 DeepSeek 的搜索提供方。', webSearchApiKey: 'API Key', webSearchApiKeyHint: '不写入设置文件。留空表示保持当前密钥。', webSearchApiKeySet: '已配置密钥。',