|
|
@@ -7,7 +7,8 @@ import type {
|
|
|
import {
|
|
|
IconChevronDownOutline14, IconChevronRightOutline14, IconRefreshOutline14, StateDot,
|
|
|
} from '@deepseek-ai/dsh-client-ui-primitives'
|
|
|
-import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
|
+import type { PropsLocale, PropsRuntime, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
|
|
+import { NS } from './locales.ts'
|
|
|
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
|
import css from './SubagentCatalogAction.module.css'
|
|
|
|
|
|
@@ -23,7 +24,7 @@ export interface SubagentCatalogInjected {
|
|
|
|
|
|
/** Full props for the session-header catalog action. */
|
|
|
export type SubagentCatalogActionProps =
|
|
|
- PropsRuntime<'conversation.session.header.actions'> & SubagentCatalogInjected
|
|
|
+ PropsRuntime<'conversation.session.header.actions'> & SubagentCatalogInjected & PropsLocale<typeof NS>
|
|
|
|
|
|
interface CatalogRowsProps {
|
|
|
parentSessionId: SessionId
|
|
|
@@ -39,11 +40,14 @@ interface CatalogRowsProps {
|
|
|
closeCatalog: () => void
|
|
|
}
|
|
|
|
|
|
-function diagnosticReason(entry: Extract<CatalogEntry, { kind: 'diagnostic' }>): string {
|
|
|
+function diagnosticReason(
|
|
|
+ entry: Extract<CatalogEntry, { kind: 'diagnostic' }>,
|
|
|
+ t: TranslateNS<typeof NS>,
|
|
|
+): string {
|
|
|
switch (entry.reason) {
|
|
|
- case 'corrupt': return '会话记录损坏'
|
|
|
- case 'unsupported': return '子代理记录版本不受支持'
|
|
|
- case 'unavailable': return '会话记录暂不可用'
|
|
|
+ case 'corrupt': return t('diagnostic.corrupt')
|
|
|
+ case 'unsupported': return t('diagnostic.unsupported')
|
|
|
+ case 'unavailable': return t('diagnostic.unavailable')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -54,18 +58,22 @@ function treeItems(root: HTMLDivElement | null): HTMLElement[] {
|
|
|
}
|
|
|
|
|
|
/** Compact trailing activity time for a catalog row. */
|
|
|
-function relativeTime(updatedAt: number | undefined, now: number): string | undefined {
|
|
|
+function relativeTime(
|
|
|
+ updatedAt: number | undefined,
|
|
|
+ now: number,
|
|
|
+ t: TranslateNS<typeof NS>,
|
|
|
+): string | undefined {
|
|
|
if (updatedAt === undefined) return undefined
|
|
|
const minute = 60_000
|
|
|
const hour = 60 * minute
|
|
|
const day = 24 * hour
|
|
|
const diff = Math.max(0, now - updatedAt)
|
|
|
- if (diff < minute) return '刚刚'
|
|
|
- if (diff < hour) return `${Math.floor(diff / minute)}分钟`
|
|
|
- if (diff < day) return `${Math.floor(diff / hour)}小时`
|
|
|
- if (diff < 30 * day) return `${Math.floor(diff / day)}天`
|
|
|
- if (diff < 365 * day) return `${Math.floor(diff / (30 * day))}个月`
|
|
|
- return `${Math.floor(diff / (365 * day))}年`
|
|
|
+ if (diff < minute) return t('time.justNow')
|
|
|
+ if (diff < hour) return t('time.minutes', { n: Math.floor(diff / minute) })
|
|
|
+ if (diff < day) return t('time.hours', { n: Math.floor(diff / hour) })
|
|
|
+ if (diff < 30 * day) return t('time.days', { n: Math.floor(diff / day) })
|
|
|
+ if (diff < 365 * day) return t('time.months', { n: Math.floor(diff / (30 * day)) })
|
|
|
+ return t('time.years', { n: Math.floor(diff / (365 * day)) })
|
|
|
}
|
|
|
|
|
|
/** Aggregate the complete subagent-only descendant subtree from flat summaries. */
|
|
|
@@ -98,28 +106,30 @@ function CatalogLoadingRows({
|
|
|
parentSessionId,
|
|
|
summaries,
|
|
|
level,
|
|
|
+ t,
|
|
|
}: {
|
|
|
parentSessionId: SessionId
|
|
|
summaries: Readonly<Record<SessionId, SessionSummary>>
|
|
|
level: number
|
|
|
+ t: TranslateNS<typeof NS>
|
|
|
}) {
|
|
|
const children = Object.values(summaries).filter(summary => (
|
|
|
summary.origin === 'subagent' && summary.parentId === parentSessionId
|
|
|
))
|
|
|
- if (children.length === 0) return <div className={css.notice}>正在加载子代理…</div>
|
|
|
+ if (children.length === 0) return <div className={css.notice}>{t('loading.label')}</div>
|
|
|
return children.map(summary => (
|
|
|
<div key={summary.id} className={css.node}>
|
|
|
<div
|
|
|
role="treeitem"
|
|
|
aria-disabled="true"
|
|
|
aria-level={level}
|
|
|
- aria-label="正在加载子代理"
|
|
|
+ aria-label={t('loading.aria')}
|
|
|
className={`${css.row} ${css.disabled} ${css.loadingRow}`}
|
|
|
>
|
|
|
<span className={css.disclosureSpace} />
|
|
|
<StateDot state={summary.running ? 'ongoing' : 'done'} />
|
|
|
<span className={css.content}>
|
|
|
- <span className={css.label}>正在加载子代理…</span>
|
|
|
+ <span className={css.label}>{t('loading.label')}</span>
|
|
|
</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -129,8 +139,8 @@ function CatalogLoadingRows({
|
|
|
/** Render one catalog level and recurse only through explicitly expanded rows. */
|
|
|
function CatalogRows({
|
|
|
parentSessionId, catalog, catalogs, summaries, expanded, level, now,
|
|
|
- openChild, refresh, toggleBranch, closeCatalog,
|
|
|
-}: CatalogRowsProps) {
|
|
|
+ openChild, refresh, toggleBranch, closeCatalog, t,
|
|
|
+}: CatalogRowsProps & { t: TranslateNS<typeof NS> }) {
|
|
|
const emptyLoading = catalog.state === 'loading' && catalog.entries.length === 0
|
|
|
return (
|
|
|
<>
|
|
|
@@ -139,24 +149,25 @@ function CatalogRows({
|
|
|
parentSessionId={parentSessionId}
|
|
|
summaries={summaries}
|
|
|
level={level}
|
|
|
+ t={t}
|
|
|
/>
|
|
|
)}
|
|
|
{catalog.state === 'error' && (
|
|
|
<div className={css.error}>
|
|
|
- <span>{catalog.error?.message ?? '无法加载子代理'}</span>
|
|
|
+ <span>{catalog.error?.message ?? t('load.error')}</span>
|
|
|
<button
|
|
|
type="button"
|
|
|
className={css.refresh}
|
|
|
onClick={() => { refresh(parentSessionId) }}
|
|
|
>
|
|
|
<IconRefreshOutline14 />
|
|
|
- 重试
|
|
|
+ {t('retry')}
|
|
|
</button>
|
|
|
</div>
|
|
|
)}
|
|
|
{catalog.entries.map((entry) => {
|
|
|
if (entry.kind === 'diagnostic') {
|
|
|
- const reason = diagnosticReason(entry)
|
|
|
+ const reason = diagnosticReason(entry, t)
|
|
|
return (
|
|
|
<div key={entry.id} className={css.node}>
|
|
|
<div
|
|
|
@@ -185,12 +196,12 @@ function CatalogRows({
|
|
|
|| (childCatalog.state === 'loading' && childCatalog.entries.length === 0)
|
|
|
const summary = summaries[entry.id]
|
|
|
const label = entry.label ?? entry.id
|
|
|
- const mode = entry.mode === 'one-shot' ? '一次性' : '可继续'
|
|
|
- const activity = entry.activity === 'running' ? '正在运行' : '当前未运行'
|
|
|
+ const mode = entry.mode === 'one-shot' ? t('mode.oneShot') : t('mode.continuable')
|
|
|
+ const activity = entry.activity === 'running' ? t('activity.running') : t('activity.inactive')
|
|
|
const secondary = [summary?.title, mode, activity]
|
|
|
.filter(value => value !== undefined)
|
|
|
.join(' · ')
|
|
|
- const time = relativeTime(summary?.updatedAt, now)
|
|
|
+ const time = relativeTime(summary?.updatedAt, now, t)
|
|
|
|
|
|
const open = (): void => {
|
|
|
openChild({ parentSessionId, childSessionId: entry.id, mode: entry.mode })
|
|
|
@@ -235,7 +246,7 @@ function CatalogRows({
|
|
|
type="button"
|
|
|
tabIndex={-1}
|
|
|
className={`${css.disclosure} ${isExpanded ? css.disclosureOpen : ''}`}
|
|
|
- aria-label={`${isExpanded ? '收起' : '展开'} ${label} 的下级子代理`}
|
|
|
+ aria-label={t(isExpanded ? 'branch.collapse' : 'branch.expand', { label })}
|
|
|
onClick={toggle}
|
|
|
>
|
|
|
<IconChevronRightOutline14 />
|
|
|
@@ -262,6 +273,7 @@ function CatalogRows({
|
|
|
parentSessionId={entry.id}
|
|
|
summaries={summaries}
|
|
|
level={level + 1}
|
|
|
+ t={t}
|
|
|
/>
|
|
|
)
|
|
|
: (
|
|
|
@@ -277,6 +289,7 @@ function CatalogRows({
|
|
|
refresh={refresh}
|
|
|
toggleBranch={toggleBranch}
|
|
|
closeCatalog={closeCatalog}
|
|
|
+ t={t}
|
|
|
/>
|
|
|
)}
|
|
|
</div>
|
|
|
@@ -294,7 +307,7 @@ function CatalogRows({
|
|
|
* @returns The action only after a non-empty catalog arrives.
|
|
|
*/
|
|
|
export function SubagentCatalogAction({
|
|
|
- sessionId, useSessions, openChild, refresh, setCatalogOpen,
|
|
|
+ sessionId, useSessions, openChild, refresh, setCatalogOpen, t,
|
|
|
}: SubagentCatalogActionProps) {
|
|
|
const catalogs = useSessions(state => state.subagentsByParent)
|
|
|
const summaries = useSessions(state => state.byId)
|
|
|
@@ -419,7 +432,7 @@ export function SubagentCatalogAction({
|
|
|
className={css.trigger}
|
|
|
aria-haspopup="tree"
|
|
|
aria-expanded={open}
|
|
|
- aria-label={`${descendantCount} 个子代理${descendants.running ? ',正在运行' : ''}`}
|
|
|
+ aria-label={t(descendants.running ? 'count.running' : 'count.total', { count: descendantCount })}
|
|
|
onClick={() => { changeOpen(!open) }}
|
|
|
onKeyDown={(event) => {
|
|
|
if (event.key !== 'ArrowDown') return
|
|
|
@@ -431,11 +444,11 @@ export function SubagentCatalogAction({
|
|
|
<span className={css.activitySlot}>
|
|
|
{descendants.running && <StateDot state="ongoing" />}
|
|
|
</span>
|
|
|
- <span className={css.count}>{descendantCount} 个子代理</span>
|
|
|
+ <span className={css.count}>{t('count.total', { count: descendantCount })}</span>
|
|
|
<IconChevronDownOutline14 className={open ? css.triggerOpen : undefined} />
|
|
|
</button>
|
|
|
{open && (
|
|
|
- <div className={css.menu} role="tree" aria-label="子代理会话">
|
|
|
+ <div className={css.menu} role="tree" aria-label={t('tree.aria')}>
|
|
|
<CatalogRows
|
|
|
parentSessionId={sessionId}
|
|
|
catalog={catalog}
|
|
|
@@ -448,6 +461,7 @@ export function SubagentCatalogAction({
|
|
|
refresh={refresh}
|
|
|
toggleBranch={toggleBranch}
|
|
|
closeCatalog={() => { changeOpen(false) }}
|
|
|
+ t={t}
|
|
|
/>
|
|
|
</div>
|
|
|
)}
|