refine(plugin-manager): per-plugin artwork, trailing load-failure notice, friendlier copy

This commit is contained in:
Yif committed 2026-09-18 11:38:45 +08:00
1 parent 831ea36f2c
commit c8b17dd4d0
6 files changed
+204 -42

No files matched your search

@@ -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);
}
@@ -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<string, (props: IconProps) => 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<string, (props: IconProps) => 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 <Artwork size={CARD_ARTWORK_SIZE} />
}
/** 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 <Artwork size={CARD_ARTWORK_SIZE} />
}
/** 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' }}
>
<div className={css.rowLine}>
<span className={css.rowIcon} aria-hidden="true"><IconCordisPluginOutlineRegular /></span>
<span className={css.rowIcon} aria-hidden="true"><PluginArtworkSubagent size={ROW_ARTWORK_SIZE} /></span>
<div className={css.rowMain}>
{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 (
<div className={css.cardHead}>
<span className={css.cardIcon} aria-hidden="true"><IconPluginPinwheelOutlineRegular size={20} /></span>
<span className={css.cardIcon} aria-hidden="true">{icon}</span>
<div className={css.cardMain}>
<div className={css.titleRow}>
<button type="button" className={`${css.cardTitle} ${css.cardOpen}`} aria-label={t('openDetail', { name: title })} onClick={onOpen}>{title}</button>
@@ -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 }: {
<span>{crumbText}</span>
</button>
<div className={css.detailHead}>
<span className={css.cardIcon} aria-hidden="true">{icon ?? <IconPluginPinwheelOutlineRegular size={20} />}</span>
<span className={css.cardIcon} aria-hidden="true">{icon}</span>
{actions}
</div>
</>
@@ -314,6 +349,7 @@ function PackageCard({ pkg, t, busy, highlighted, onOpen, onSetEnabled }: {
title={title}
t={t}
onOpen={onOpen}
icon={packageArtwork(pkg.name)}
tags={(
<>
{beta ? <Tag className={css.statusTag} tone="info">{t('statusBeta')}</Tag> : null}
@@ -339,7 +375,7 @@ function ItemCard({ item, t, onOpen, renderSlot }: {
}): ReactNode {
return (
<li className={`${css.card} ${css.cardLink}`} data-plugin-item={item.id}>
<CardHead title={item.label} t={t} onOpen={onOpen} description={renderSlot('plugins.item', { view: 'summary' }, { only: item.id })} />
<CardHead title={item.label} t={t} onOpen={onOpen} icon={itemArtwork(item.id)} description={renderSlot('plugins.item', { view: 'summary' }, { only: item.id })} />
</li>
)
}
@@ -353,7 +389,7 @@ function ItemDetail({ item, t, onBack, renderSlot }: {
}): ReactNode {
return (
<div className={css.detail} data-plugin-item-detail={item.id}>
<DetailTop crumbLabel={t('backToList')} crumbText={t('crumbRoot')} onBack={onBack} />
<DetailTop crumbLabel={t('backToList')} crumbText={t('crumbRoot')} onBack={onBack} icon={itemArtwork(item.id)} />
<div className={css.detailMain}>
<div className={css.titleRow}>
<h3 className={css.detailTitle}>{item.label}</h3>
@@ -382,7 +418,7 @@ function RowDetail({ pkg, row, t, onBack, renderSlot }: {
const key = rowConfigKey(pkg.name, row.rowId)
return (
<div className={css.detail} data-plugin-row-detail={key}>
<DetailTop crumbLabel={t('backToPackage', { name: title })} crumbText={title} onBack={onBack} icon={<IconCordisPluginOutlineRegular size={20} />} />
<DetailTop crumbLabel={t('backToPackage', { name: title })} crumbText={title} onBack={onBack} icon={<PluginArtworkSubagent size={CARD_ARTWORK_SIZE} />} />
<div className={css.detailMain}>
<div className={css.titleRow}>
<h3 className={css.detailTitle}>{row.rowId}</h3>
@@ -431,6 +467,7 @@ function PackageDetail({
crumbLabel={t('backToList')}
crumbText={t('crumbRoot')}
onBack={onBack}
icon={packageArtwork(pkg.name)}
actions={(
<div className={css.detailActions}>
{pkg.installed
@@ -918,31 +955,21 @@ export function PluginManagerPage(props: PluginManagerPageProps): ReactNode {
<button type="button" className={css.iconButton} aria-label={t('refresh')} title={t('refresh')} disabled={!loaded} onClick={props.refresh}>
<span className={css.iconWrap} aria-hidden="true"><IconRefreshOutlineRegular /></span>
</button>
<Button variant="primary" size="sm" icon={<IconPlusOutlineRegular size={13} />} disabled={!loaded} onClick={props.openInstall}>{t('addPlugin')}</Button>
<Button variant="primary" size="sm" className={css.addButton} icon={<IconPlusOutlineRegular size={13} />} disabled={!loaded} onClick={props.openInstall}>{t('addPlugin')}</Button>
</div>
</header>
)
: null}
{state.status === 'loading' ? (
{showsCards && state.status === 'loading' ? (
<p className={`${css.status} ${css.statusWithDot}`} role="status">
<StateDot state="ongoing" />{t('loading')}
</p>
) : null}
{state.status === 'unavailable' ? (
{showsCards && state.status === 'unavailable' ? (
<p className={`${css.status} ${css.statusWithDot}`} role="status">
<StateDot state="idle" />{t('unavailable')}
</p>
) : null}
{state.status === 'error'
? (
<div className={css.failure}>
<p className={css.statusWithDot} role="alert">
<StateDot state="error" />{t('error')}
</p>
<Button variant="outline" size="sm" onClick={props.refresh}>{t('retry')}</Button>
</div>
)
: null}
{state.notice === null || noticeLine === null
? null
: (
@@ -986,12 +1013,24 @@ export function PluginManagerPage(props: PluginManagerPageProps): ReactNode {
? <ItemDetail item={openItem} t={t} renderSlot={renderSlot} onBack={() => { setView({ kind: 'list' }) }} />
: null}
{loaded && showsCards
? officialCards.length === 0 && mine.length === 0
? officialCards.length === 0 && mine.length === 0 && state.status !== 'error'
? <p className={css.empty}>{t('empty')}</p>
: (
<>
{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'
? (
<div className={css.failure}>
<p className={css.statusWithDot} role="alert">
<StateDot state="error" />{t('error')}
</p>
<Button variant="outline" size="sm" onClick={props.refresh}>{t('retry')}</Button>
</div>
)
: null}
</>
)
: null}
@@ -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',
@@ -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'
@@ -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) => (
<svg width={size} height={size} className={className} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3027 17.1077H9.03125V12.8372H13.3027V17.1077ZM17.5742 12.8372C17.574 15.1958 15.6623 17.1075 13.3037 17.1077V8.56567H17.5742V12.8372Z" fill="url(#dsh_plugin_art_team_a)" />
<path d="M18.5229 12.8372V8.56567H22.7944V12.8372H27.0659V17.1077H22.7944C20.4357 17.1077 18.5232 15.1959 18.5229 12.8372Z" fill="#F2AF63" />
<path d="M17.5742 26.6072H13.3037V18.0642C15.6625 18.0644 17.5742 19.9769 17.5742 22.3357V26.6072ZM13.3027 22.3357H9.03125V18.0642H13.3027V22.3357Z" fill="#7CB7FF" />
<path d="M22.7944 26.6072H18.5229V22.3357C18.5229 19.9768 20.4355 18.0642 22.7944 18.0642H27.0659V22.3357H22.7944V26.6072Z" fill="#45D9E7" />
<defs>
<linearGradient id="dsh_plugin_art_team_a" x1="16.1247" y1="6.6613" x2="9.03125" y2="19.2206" gradientUnits="userSpaceOnUse">
<stop stopColor="#7CB7FF" />
<stop offset="1" stopColor="#145AF3" />
</linearGradient>
</defs>
</svg>
)
/** Terminal plugin artwork (prompt chevron and cursor bar). */
export const PluginArtworkTerminal = ({ size = 36, className }: IconProps) => (
<svg width={size} height={size} className={className} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 11L16.606 17.606C16.6841 17.6841 16.6841 17.8107 16.606 17.8888L10 24.4948" stroke="#145AF3" strokeWidth="3.5" />
<path d="M20.1211 24.4946H26.8685" stroke="#145AF3" strokeWidth="3.5" />
</svg>
)
/** Agent-loop plugin artwork (four leaves circling a center). */
export const PluginArtworkLoop = ({ size = 36, className }: IconProps) => (
<svg width={size} height={size} className={className} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.0486 28.4901C12.0858 28.4901 7.25195 23.6562 7.25195 17.6934C13.2148 17.6934 18.0486 22.5272 18.0486 28.4901Z" fill="#A797FC" />
<path d="M18.0486 6.89667C12.0858 6.89667 7.25195 11.7305 7.25195 17.6934C13.2148 17.6934 18.0486 12.8595 18.0486 6.89667Z" fill="url(#dsh_plugin_art_loop_a)" />
<path d="M18.0485 28.4901C24.0114 28.4901 28.8452 23.6562 28.8452 17.6934C22.8824 17.6934 18.0485 22.5272 18.0485 28.4901Z" fill="#4561EE" />
<path d="M18.0485 6.89667C24.0114 6.89667 28.8452 11.7305 28.8452 17.6934C22.8824 17.6934 18.0485 12.8595 18.0485 6.89667Z" fill="#658EFF" />
<defs>
<linearGradient id="dsh_plugin_art_loop_a" x1="16.7911" y1="16.1655" x2="8.98192" y2="8.74289" gradientUnits="userSpaceOnUse">
<stop stopColor="#A23AE7" />
<stop offset="1" stopColor="#E2E2E2" />
</linearGradient>
</defs>
</svg>
)
/** Subagent plugin artwork (two stacked rounded squares); also marks one row inside a bundle. */
export const PluginArtworkSubagent = ({ size = 36, className }: IconProps) => (
<svg width={size} height={size} className={className} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="14.9893" y="15.1877" width="12.2365" height="12.2365" rx="2" fill="url(#dsh_plugin_art_sub_a)" fillOpacity="0.8" />
<rect x="8.87109" y="9.0697" width="12.2365" height="12.2365" rx="2" fill="url(#dsh_plugin_art_sub_b)" fillOpacity="0.8" />
<defs>
<linearGradient id="dsh_plugin_art_sub_a" x1="21.1075" y1="15.1877" x2="21.1075" y2="27.4243" gradientUnits="userSpaceOnUse">
<stop stopColor="#45E7A4" />
<stop offset="1" stopColor="#05909D" />
</linearGradient>
<linearGradient id="dsh_plugin_art_sub_b" x1="14.9894" y1="9.0697" x2="14.9894" y2="21.3063" gradientUnits="userSpaceOnUse">
<stop stopColor="#69B9FF" />
<stop offset="1" stopColor="#324DE2" />
</linearGradient>
</defs>
</svg>
)
/**
* 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 `<img>` or mask context.
*/
export const PluginArtworkSearch = ({ size = 36, className }: IconProps) => (
<svg width={size} height={size} className={className} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.5362 26.9865L22.3813 22.8317" stroke="#2F2295" strokeWidth="3" />
<g clipPath="url(#dsh_plugin_art_search_ring)">
<g transform="matrix(0.0119394 -0.00173904 0.00173904 0.0119394 15.7661 16.2159)">
<foreignObject x="-958.94" y="-958.94" width="1917.88" height="1917.88">
<div
style={{
background: 'conic-gradient(from 90deg, rgb(65, 225, 172) 0deg, rgb(85, 71, 210) 62.0619deg, rgb(65, 225, 172) 360deg)',
height: '100%',
width: '100%',
}}
/>
</foreignObject>
</g>
</g>
<defs>
<clipPath id="dsh_plugin_art_search_ring">
<path d="M21.9717 16.2159H19.9717C19.9717 18.5386 18.0888 20.4215 15.7661 20.4215V22.4215V24.4215C20.2979 24.4215 23.9717 20.7478 23.9717 16.2159H21.9717ZM15.7661 22.4215V20.4215C13.4434 20.4215 11.5605 18.5386 11.5605 16.2159H9.56055H7.56055C7.56055 20.7478 11.2343 24.4215 15.7661 24.4215V22.4215ZM9.56055 16.2159H11.5605C11.5605 13.8933 13.4434 12.0104 15.7661 12.0104V10.0104V8.01038C11.2343 8.01038 7.56055 11.6841 7.56055 16.2159H9.56055ZM15.7661 10.0104V12.0104C18.0888 12.0104 19.9717 13.8933 19.9717 16.2159H21.9717H23.9717C23.9717 11.6841 20.2979 8.01038 15.7661 8.01038V10.0104Z" />
</clipPath>
</defs>
</svg>
)
/** Default plugin artwork for plugins without one of their own (connector blocks and a node). */
export const PluginArtworkDefault = ({ size = 36, className }: IconProps) => (
<svg width={size} height={size} className={className} viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.6294 8.63696C26.2862 8.63705 27.6294 9.98016 27.6294 11.637V12.7825C27.6292 14.4391 26.2861 15.7824 24.6294 15.7825H23.4839C22.6519 15.7825 21.5454 16.3459 21.5454 17.1778V18.1573C21.5454 18.7757 22.216 19.1966 22.8345 19.1965H24.6294C26.2861 19.1965 27.6292 20.5398 27.6294 22.1965V23.9924C27.6292 25.6491 26.2861 26.9924 24.6294 26.9924H22.8345C21.1778 26.9924 19.8347 25.649 19.8345 23.9924V22.1965C19.8345 21.7148 19.4957 21.2327 19.014 21.2327H16.4792C15.7975 21.2327 15.3374 22.0061 15.3374 22.6877V23.8333C15.3373 25.49 13.9942 26.8333 12.3374 26.8333H11.1919C9.53509 26.8333 8.19198 25.49 8.19189 23.8333V22.6877C8.19189 21.0309 9.53504 19.6877 11.1919 19.6877H12.3374C13.1964 19.6877 14.3999 19.0959 14.3999 18.2369V16.0185C14.3999 14.9518 15.2646 14.0872 16.3312 14.0872H19.435C20.0596 14.0872 20.484 13.4071 20.4839 12.7825V11.637C20.4839 9.98011 21.827 8.63696 23.4839 8.63696H24.6294ZM13.4116 11.2468C13.4116 12.6882 12.2431 13.8567 10.8018 13.8567C9.36037 13.8567 8.19189 12.6882 8.19189 11.2468C8.19189 9.80544 9.36037 8.63696 10.8018 8.63696C12.2431 8.63696 13.4116 9.80544 13.4116 11.2468Z" fill="url(#dsh_plugin_art_default_a)" />
<defs>
<linearGradient id="dsh_plugin_art_default_a" x1="15.1481" y1="9.94188" x2="15.1481" y2="13.6375" gradientUnits="userSpaceOnUse">
<stop stopColor="#54ECE7" />
<stop offset="1" stopColor="#658EFF" />
</linearGradient>
</defs>
</svg>
)
@@ -42,17 +42,17 @@ export const en: Record<PluginsSettingsLocaleKey, string> = {
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<PluginsSettingsLocaleKey, string> = {
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: '已配置密钥。',