refactor(tasks): 移植主 Agent 同款合并活动折叠(数据层)
把上一轮验证过的数据层搬到工作图基座上,图/树两种模式共用:
- 新增 src/process-activity.ts:宿主 ui-chat 的 process group 算法移植
(activity() 分类表逐条照抄、liveToolDetail 键优先级与 160 字截断、
counts 计数排序与并列规则、preparing 语义、按 user/reply 边界切范围)。
按本轮设计裁剪掉逐阶段列表(阶段改由徽标 + 颜色表达)。新增一条
偏差:最新范围为空(子代理刚回复完)时回退到上一个「做过事」的范围,
否则已结算卡片的小条只会显示「已完成分析」。
- chatT:从宿主 chat 命名空间读 message.stepProcess.* 措辞(方法调用 +
try/catch 兜底,key 未命中即回退),4 个回归用例钉住「不许解绑 bind」。
- subagents.live 载荷换新:SidebarChildLiveView { running?, text?, summary?,
lastEventTime? },报告树内每个子节点(含已结算)与拓扑根;删除
expand/stages 机制;childActivity → childLive(「不在 map 里 = 未运行」
的读法随旧协议一起作废)。
- tasks-model 消费新形状;LiveLine 与节点详情浮窗改显示合并活动措辞。
- 删除 src/subagent-activity.ts 与 tests/subagent-activity.spec.ts。
验证:typecheck / lint 通过;pnpm test 1252 passed / 9 skipped。
(cherry picked from commit 5e5284f214846b0c366048acbee459f3a7a79456)
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type {
|
||||
Context,
|
||||
SidebarChildLiveView,
|
||||
SidebarJobView,
|
||||
SidebarSessionList,
|
||||
SidebarSubagentAddress,
|
||||
@@ -45,7 +46,6 @@ import {
|
||||
} from './subagent-detect.ts'
|
||||
import { subagentCatalogs } from './subagent-catalog.ts'
|
||||
import { treeSessionIds } from './subagent-lineage.ts'
|
||||
import { type LastActivity } from '../subagent-activity.ts'
|
||||
import { collectTreeJobs, orderJobs } from './subagent-jobs.ts'
|
||||
import { api, type TeamsViewResult } from './api.ts'
|
||||
import { usePolling } from './use-polling.ts'
|
||||
@@ -93,8 +93,8 @@ interface CatalogRefreshFace {
|
||||
function useSubagentLive(
|
||||
rootId: string | undefined,
|
||||
active: boolean,
|
||||
): Readonly<Record<string, LastActivity>> {
|
||||
const [live, setLive] = useState<Record<string, LastActivity>>({})
|
||||
): Readonly<Record<string, SidebarChildLiveView>> {
|
||||
const [live, setLive] = useState<Record<string, SidebarChildLiveView>>({})
|
||||
|
||||
// A new tree must never inherit another root's live previews.
|
||||
useEffect(() => { setLive({}) }, [rootId])
|
||||
|
||||
@@ -32,6 +32,7 @@ import type { TasksAgentNode, TasksWorkflowNode } from './tasks-model.ts'
|
||||
import {
|
||||
AgentGlyph, WorkflowGlyph, flatten, modeLabel, nodeDotState, taskDotState, taskStatusKey, workflowStatusKey,
|
||||
} from './tasks-shared.tsx'
|
||||
import { liveActivityLabel } from './process-labels.ts'
|
||||
import { t, type CopyKey } from './locales.ts'
|
||||
import css from './tasks-graph.module.css'
|
||||
|
||||
@@ -81,9 +82,9 @@ export function AgentNodePopover(props: {
|
||||
/** The row's mode word; `unknown` claims no mode and this row is omitted. */
|
||||
const nodeMode = modeLabel(node.mode)
|
||||
const liveText = node.live?.text !== undefined ? flatten(node.live.text) : undefined
|
||||
const liveTool = node.live?.tool !== undefined
|
||||
? `${node.live.tool.name}${node.live.tool.args === '' ? '' : ` ${node.live.tool.args}`}`
|
||||
: undefined
|
||||
// The merged activity the main conversation would show for this node's
|
||||
// newest range (category wording + the running call's detail).
|
||||
const liveTool = liveActivityLabel(node.live?.summary)
|
||||
return (
|
||||
<div className={css.popCard}>
|
||||
<div className={css.popHead}>
|
||||
|
||||
+5
-4
@@ -7,10 +7,10 @@
|
||||
* request). Failures surface as {@link SidebarApiError} with the wire code.
|
||||
*/
|
||||
import { encodeHtmlUrl } from '../html-route.ts'
|
||||
import type { LastActivity } from '../subagent-activity.ts'
|
||||
import type { SidechatLiveEvent, SidechatLogEvent, SidechatThreadInfo } from '../sidechat-core.ts'
|
||||
import type {
|
||||
SidebarCreateTeamTaskRequest,
|
||||
SidebarChildLiveView,
|
||||
SidebarJobView,
|
||||
SidebarSessionEvent,
|
||||
SidebarTeamMemberView,
|
||||
@@ -119,8 +119,8 @@ export interface JobOutputResult {
|
||||
read: boolean
|
||||
}
|
||||
|
||||
/** The `subagents.live` response: running child id → latest activity. */
|
||||
export type SubagentLiveResult = { live: Record<string, LastActivity> }
|
||||
/** The `subagents.live` response: one row per tree child (and the root). */
|
||||
export type SubagentLiveResult = { live: Record<string, SidebarChildLiveView> }
|
||||
|
||||
/** The `workflows.list` response: the tree's folded workflow runs. */
|
||||
export type WorkflowsListResult = { runs: WorkflowRunView[] }
|
||||
@@ -375,7 +375,8 @@ export const api = {
|
||||
/**
|
||||
* One batch live-preview fetch for the whole Subagent tree. The payload is
|
||||
* the already-resolved topology ROOT (not a session scope); the host
|
||||
* enumerates descendants once and folds running children's activity.
|
||||
* enumerates descendants once and folds every child's newest process range
|
||||
* into the main agent's merged-activity summary.
|
||||
*/
|
||||
subagentsLive: (rootSessionId: string, signal?: AbortSignal) =>
|
||||
call<SubagentLiveResult>('subagents.live', { rootSessionId }, signal),
|
||||
|
||||
+52
-2
@@ -976,7 +976,17 @@ export const LOCALE_NS = 'betterSidebar'
|
||||
// path most sessions never enter.
|
||||
|
||||
/** The DSH locale service attached by the client apply (absent → browser detection). */
|
||||
let localeService: { getSnapshot(): { active: string } } | undefined
|
||||
let localeService: {
|
||||
getSnapshot(): { active: string }
|
||||
/**
|
||||
* Bind one registered namespace to a translate function (DSH's
|
||||
* `LocaleRuntime.bind`). Optional because a consumer may attach only the
|
||||
* snapshot face. The untyped overload is what makes cross-package key reads
|
||||
* possible: an UNREGISTERED namespace yields a function that returns the key
|
||||
* itself, which is exactly how {@link chatT} detects "not available".
|
||||
*/
|
||||
bind?(ns: string): (key: string, params?: Record<string, string | number>) => string
|
||||
} | undefined
|
||||
|
||||
/**
|
||||
* The better-locale override store attached by the client apply
|
||||
@@ -1003,10 +1013,50 @@ let betterLocaleStore: {
|
||||
* `t()` function, and the Sidebar root's locale subscription re-renders the
|
||||
* whole tree on switches.
|
||||
*/
|
||||
export function attachLocale(service: { getSnapshot(): { active: string } } | undefined): void {
|
||||
export function attachLocale(service: {
|
||||
getSnapshot(): { active: string }
|
||||
/** Optional namespace binding ({@link chatT} needs it; a bare snapshot face is still valid). */
|
||||
bind?(ns: string): (key: string, params?: Record<string, string | number>) => string
|
||||
} | undefined): void {
|
||||
localeService = service
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a key in the HOST's own `chat` namespace — the wording the main
|
||||
* conversation's merged "process" header uses (`message.stepProcess.*`,
|
||||
* registered by `@deepseek-ai/dsh-client-ui-chat`). The Tasks page reads it so
|
||||
* a running node reads exactly like the main agent's own activity line instead
|
||||
* of inventing a second vocabulary (and without adding ~40 keys to 20 plugin
|
||||
* dictionaries).
|
||||
*
|
||||
* Deliberately untyped: these keys are NOT a documented host contract. A host
|
||||
* that renames one, or a deployment where the chat target never mounted,
|
||||
* returns the key unchanged — the caller then falls back to its own wording
|
||||
* ({@link chatT} answers undefined) rather than printing `message.…`.
|
||||
*
|
||||
* @param key - full `chat`-namespace key.
|
||||
* @param params - `{name}` placeholders to interpolate (the host does this).
|
||||
* @returns the translated text, or undefined when unavailable.
|
||||
*/
|
||||
export function chatT(key: string, params?: Record<string, string | number>): string | undefined {
|
||||
const service = localeService
|
||||
if (service === undefined) return undefined
|
||||
try {
|
||||
// MUST stay a METHOD call: DSH's `bind` reads `this.bound`, so a detached
|
||||
// reference (`const bind = service.bind; bind('chat')`) throws
|
||||
// "Cannot read properties of undefined (reading 'bound')" — caught by the
|
||||
// real-host mount lane, invisible to closure fakes in unit tests.
|
||||
const translate = service.bind?.('chat')
|
||||
if (typeof translate !== 'function') return undefined
|
||||
const text = translate(key, params)
|
||||
return typeof text === 'string' && text !== '' && text !== key ? text : undefined
|
||||
} catch {
|
||||
// The namespace seam is undocumented: a host that reshapes it must degrade
|
||||
// to the caller's own wording, never break a render.
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach (or detach, with undefined) the better-locale override store.
|
||||
* When attached with an active override, `t()` consults the store's
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* The two labels the Tasks page borrows from the main conversation's merged
|
||||
* activity display: the LIVE line ("正在运行命令 · npm run build") and the
|
||||
* DONE title for a settled range ("已读取文件、执行了命令").
|
||||
*
|
||||
* Both are ports of the host's own composition
|
||||
* (`dsh-client-ui-chat`'s `ProcessGroupHeader` + `step-process.js`
|
||||
* `processTitle`), reading the host's `chat` dictionary through
|
||||
* {@link chatT} so the wording — and every language the host ships — matches
|
||||
* what the main chat shows for the same work.
|
||||
*
|
||||
* The translator is injectable for tests; the default is the host bridge. When
|
||||
* a key does not resolve (a host that renamed it, or a deployment where the
|
||||
* chat target never mounted) the call answers undefined and the card falls
|
||||
* back to the tool detail it already has, never to a raw `message.…` key.
|
||||
*/
|
||||
import type { ProcessActivitySummary } from '../process-activity.ts'
|
||||
import { chatT } from './locales.ts'
|
||||
|
||||
/** A host-namespace translator; undefined means "this key is not available". */
|
||||
export type ChatTranslate = (key: string, params?: Record<string, string | number>) => string | undefined
|
||||
|
||||
/** The host's `' · '` line separator (`message.turnProcess.separator`). */
|
||||
function separator(t: ChatTranslate): string {
|
||||
return t('message.turnProcess.separator') ?? ' · '
|
||||
}
|
||||
|
||||
/**
|
||||
* The live line of one stage: the running category's `…ing` wording plus the
|
||||
* running call's detail, or the "thinking" default while nothing is dispatched
|
||||
* (exactly the host's `ProcessGroupHeader` label rule).
|
||||
*
|
||||
* @param summary - the newest stage's summary (absent while the child has none).
|
||||
* @param t - host translator (defaults to the `chat` namespace bridge).
|
||||
* @returns the line, or undefined when there is nothing to say.
|
||||
*/
|
||||
export function liveActivityLabel(
|
||||
summary: ProcessActivitySummary | undefined,
|
||||
t: ChatTranslate = chatT,
|
||||
): string | undefined {
|
||||
const activity = summary?.running ?? 'thinking'
|
||||
const preparing = summary?.preparing === true
|
||||
// The host maps its `thinking` fallback onto `prepare.tools` (there is no
|
||||
// `prepare.thinking` key).
|
||||
const label = preparing
|
||||
? t(`message.stepProcess.prepare.${activity === 'thinking' ? 'tools' : activity}`)
|
||||
: t(`message.stepProcess.${activity}`)
|
||||
if (label === undefined) {
|
||||
// No host wording for this category: the detail is the honest fallback.
|
||||
return summary?.runningDetail === undefined || summary.runningDetail === ''
|
||||
? undefined
|
||||
: summary.runningDetail
|
||||
}
|
||||
const detail = preparing ? '' : summary?.runningDetail ?? ''
|
||||
return detail === '' ? label : `${label}${separator(t)}${detail}`
|
||||
}
|
||||
|
||||
/**
|
||||
* The last-resort activity text: the ranked categories spelled out as
|
||||
* `kind ×count`. It keeps a card informative when the host's `chat` wording is
|
||||
* unavailable (renamed keys, or a deployment where the chat target never
|
||||
* mounted) without inventing a second vocabulary for the normal case — the
|
||||
* category names are the host's own, and they are language-neutral.
|
||||
*
|
||||
* @param summary - a stage's summary.
|
||||
* @returns the counts text, or undefined when the stage called nothing.
|
||||
*/
|
||||
export function activityCountsText(
|
||||
summary: ProcessActivitySummary | undefined,
|
||||
): string | undefined {
|
||||
const counts = summary?.counts ?? []
|
||||
if (counts.length === 0) return undefined
|
||||
return counts.map(entry => `${entry.kind} ×${entry.count}`).join(' · ')
|
||||
}
|
||||
|
||||
/**
|
||||
* The settled range's title: the top three categories' `done.*` wording joined
|
||||
* the way the host joins it (two labels share the "已" prefix; more than three
|
||||
* append the "等" suffix). Counts are never printed.
|
||||
*
|
||||
* @param summary - a stage's summary.
|
||||
* @param t - host translator (defaults to the `chat` namespace bridge).
|
||||
* @returns the title, or undefined when the host wording is unavailable.
|
||||
*/
|
||||
export function doneActivityTitle(
|
||||
summary: ProcessActivitySummary | undefined,
|
||||
t: ChatTranslate = chatT,
|
||||
): string | undefined {
|
||||
const counts = summary?.counts ?? []
|
||||
if (counts.length === 0) return t('message.stepProcess.done.thinking')
|
||||
const labels: string[] = []
|
||||
for (const { kind } of counts.slice(0, 3)) {
|
||||
const label = t(`message.stepProcess.done.${kind}`)
|
||||
if (label === undefined) return undefined
|
||||
labels.push(label)
|
||||
}
|
||||
const first = labels[0]
|
||||
if (first === undefined) return undefined
|
||||
const second = labels[1]
|
||||
if (second === undefined) return first
|
||||
// The host lowercases every label after the first for latin languages; a
|
||||
// Chinese label is unaffected by that.
|
||||
const continuation = (label: string): string => label.charAt(0).toLowerCase() + label.slice(1)
|
||||
if (labels.length === 2) {
|
||||
const prefix = t('message.stepProcess.sharedPrefix') ?? ''
|
||||
const stripped = prefix !== '' && first.startsWith(prefix) && second.startsWith(prefix)
|
||||
? second.slice(prefix.length)
|
||||
: second
|
||||
return t('message.stepProcess.joinTwo', { first, second: continuation(stripped) })
|
||||
?? `${first}${separator(t)}${continuation(stripped)}`
|
||||
}
|
||||
const joined = [first, ...labels.slice(1).map(continuation)].join(t('message.stepProcess.comma') ?? ', ')
|
||||
if (counts.length > 3) return t('message.stepProcess.more', { title: joined }) ?? joined
|
||||
return joined
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import type {
|
||||
SidebarSessionList,
|
||||
SidebarSubagentCatalogEntry,
|
||||
} from '../context-types.ts'
|
||||
import type { LastActivity } from '../subagent-activity.ts'
|
||||
import type { SidebarChildLiveView } from '../context-types.ts'
|
||||
|
||||
/** One parent's direct-child catalog, as the topology consumes it. */
|
||||
export interface SubagentCatalogView {
|
||||
@@ -89,16 +89,19 @@ export function isKnownLeaf(
|
||||
}
|
||||
|
||||
/**
|
||||
* The child's live state. The `subagents.live` route folds only RUNNING
|
||||
* children (a Side Chat thread and an idle child are absent), so absence is
|
||||
* the inactive answer rather than missing data.
|
||||
* One child's live view, or undefined when the batch has not reported it yet
|
||||
* (the first poll of a freshly rooted tree).
|
||||
*
|
||||
* @param live - the batch live map (`child id → latest activity`).
|
||||
* The `subagents.live` route reports EVERY tree child with an explicit
|
||||
* `running` flag — the previous "absent means not running" reading cannot
|
||||
* survive a route that also reports settled children's summaries.
|
||||
*
|
||||
* @param live - the batch live map (`session id → live view`).
|
||||
* @param childSessionId - the child row being drawn.
|
||||
*/
|
||||
export function childActivity(
|
||||
live: Readonly<Record<string, LastActivity | undefined>>,
|
||||
export function childLive(
|
||||
live: Readonly<Record<string, SidebarChildLiveView | undefined>>,
|
||||
childSessionId: string,
|
||||
): 'running' | 'inactive' {
|
||||
return live[childSessionId] === undefined ? 'inactive' : 'running'
|
||||
): SidebarChildLiveView | undefined {
|
||||
return live[childSessionId]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* the core bundle; <100-node trees need no virtualisation).
|
||||
*/
|
||||
import type { TasksNode } from './tasks-model.ts'
|
||||
import type { SidebarChildLiveView } from '../context-types.ts'
|
||||
import { tasksEdges } from './tasks-model.ts'
|
||||
|
||||
/** Geometry constants of the canvas (px, pre-scale). The design target is the
|
||||
@@ -115,12 +116,18 @@ export interface LayoutOptions {
|
||||
* The tallest shape therefore is base + live + live-tail + task, which stays
|
||||
* inside {@link GRAPH_ROW_STRIDE} (rows can never overlap).
|
||||
*/
|
||||
/** Whether a node's live view carries a merged-activity row to render. */
|
||||
function hasMergedActivity(live: SidebarChildLiveView | undefined): boolean {
|
||||
if (live === undefined) return false
|
||||
return (live.summary?.counts.length ?? 0) > 0 || live.summary?.running !== undefined
|
||||
}
|
||||
|
||||
function nodeHeight(node: TasksNode): number {
|
||||
if (node.kind !== 'agent') return GRAPH_NODE_H
|
||||
let height = GRAPH_NODE_H
|
||||
if (node.state === 'running') {
|
||||
height += GRAPH_LIVE_H
|
||||
if (node.live?.tool !== undefined && node.live.text !== undefined) height += GRAPH_LIVE_TAIL_H
|
||||
if (hasMergedActivity(node.live) && node.live?.text !== undefined) height += GRAPH_LIVE_TAIL_H
|
||||
}
|
||||
if ((node.tasks?.length ?? 0) > 0) height += GRAPH_TASK_H
|
||||
return height
|
||||
|
||||
@@ -26,10 +26,10 @@ import type {
|
||||
SidebarTeamMemberView,
|
||||
SidebarTeamTaskView,
|
||||
} from '../context-types.ts'
|
||||
import type { LastActivity } from '../subagent-activity.ts'
|
||||
import type { SidebarChildLiveView } from '../context-types.ts'
|
||||
import type { WorkflowRunView } from '../workflow-runs.ts'
|
||||
import { isSideThreadSummary } from './subagent-detect.ts'
|
||||
import { childActivity, isKnownLeaf, type SubagentCatalogView } from './subagent-catalog.ts'
|
||||
import { childLive, isKnownLeaf, type SubagentCatalogView } from './subagent-catalog.ts'
|
||||
|
||||
/** Display state of one agent node (drives the dot + fold candidacy). */
|
||||
export type TasksNodeState = 'running' | 'idle' | 'done' | 'error'
|
||||
@@ -59,8 +59,8 @@ export interface TasksAgentNode {
|
||||
activity: 'running' | 'inactive'
|
||||
/** The on-screen session (the "you are here" marker). */
|
||||
current: boolean
|
||||
/** Live tail of a running child (the icon + tool + args line). */
|
||||
live?: LastActivity
|
||||
/** Live tail of a running child (the merged activity + newest text). */
|
||||
live?: SidebarChildLiveView
|
||||
/** Team enrichment (membership view matched by session id). */
|
||||
team?: {
|
||||
role: 'lead' | 'teammate'
|
||||
@@ -108,7 +108,7 @@ export interface TasksModelInput {
|
||||
catalogs: Readonly<Record<string, SubagentCatalogView | undefined>>
|
||||
rootId: string
|
||||
currentSessionId: string
|
||||
live: Readonly<Record<string, LastActivity | undefined>>
|
||||
live: Readonly<Record<string, SidebarChildLiveView | undefined>>
|
||||
runs: readonly WorkflowRunView[]
|
||||
teamMembers: readonly SidebarTeamMemberView[]
|
||||
/** The team's shared tasks; each lands on its OWNER's node. */
|
||||
@@ -215,9 +215,10 @@ export function buildTasksModel(input: TasksModelInput): TasksNode[] {
|
||||
if (entry.label?.startsWith('Side: ') ?? false) continue
|
||||
const team = teamOf(entry.id)
|
||||
// DSH 0.1.7's catalog row carries the identity only: the live channel
|
||||
// folds RUNNING children (absence = not running) and a row keeps its
|
||||
// disclosure unless its own catalog is known-empty.
|
||||
const activity = childActivity(live, entry.id)
|
||||
// reports EVERY child with an explicit `running` flag, and a row keeps
|
||||
// its disclosure unless its own catalog is known-empty.
|
||||
const view = childLive(live, entry.id)
|
||||
const activity: 'running' | 'inactive' = view?.running === true ? 'running' : 'inactive'
|
||||
const state: TasksNodeState = activity === 'running'
|
||||
? 'running'
|
||||
: team !== undefined ? teamState(team.status) : 'done'
|
||||
@@ -232,7 +233,7 @@ export function buildTasksModel(input: TasksModelInput): TasksNode[] {
|
||||
state,
|
||||
activity,
|
||||
current: entry.id === currentSessionId,
|
||||
...(live[entry.id] !== undefined ? { live: live[entry.id] } : {}),
|
||||
...(view !== undefined ? { live: view } : {}),
|
||||
...(team !== undefined ? { team } : {}),
|
||||
...(tasksByNode.get(entry.id) !== undefined ? { tasks: tasksByNode.get(entry.id) } : {}),
|
||||
hasChildren: !isKnownLeaf(catalogs, entry.id),
|
||||
|
||||
+12
-10
@@ -16,7 +16,8 @@ import {
|
||||
IconAgentPresetOutlineRegular, IconBranchOutlineRegular, IconChecklistOutlineRegular, IconUserOutlineRegular,
|
||||
type StateDotState,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { LastActivity } from '../subagent-activity.ts'
|
||||
import type { SidebarChildLiveView } from '../context-types.ts'
|
||||
import { liveActivityLabel } from './process-labels.ts'
|
||||
import type { TasksAgentNode, TasksNodeState, TasksNodeTask, TasksWorkflowNode } from './tasks-model.ts'
|
||||
import { toolGlyph } from './tool-icons.tsx'
|
||||
import { t, type CopyKey } from './locales.ts'
|
||||
@@ -189,25 +190,26 @@ export function TaskLine(props: {
|
||||
}
|
||||
|
||||
/**
|
||||
* The live activity row of a RUNNING agent: the tool's own icon + tool name +
|
||||
* args (the approved format), plus the flattened last text line underneath.
|
||||
* The live activity row of a RUNNING agent: the merged process summary the
|
||||
* main conversation would show for the child's newest range (category wording
|
||||
* + the running call's detail), plus the flattened last text line underneath.
|
||||
* A running node with neither reads as thinking.
|
||||
*/
|
||||
export function LiveLine(props: { live: LastActivity | undefined }): ReactNode {
|
||||
export function LiveLine(props: { live: SidebarChildLiveView | undefined }): ReactNode {
|
||||
const { live } = props
|
||||
if (live?.text === undefined && live?.tool === undefined) {
|
||||
const activity = liveActivityLabel(live?.summary)
|
||||
if (activity === undefined && live?.text === undefined) {
|
||||
return <span className={css.nodeMeta}>{t('subagentThinking')}</span>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{live.tool !== undefined && (
|
||||
{activity !== undefined && (
|
||||
<span className={css.live}>
|
||||
<span className={css.liveGlyph} aria-hidden="true">{toolGlyph(live.tool.name)(9)}</span>
|
||||
<span className={css.liveTool}>{live.tool.name}</span>
|
||||
{live.tool.args !== '' && <span className={css.liveArgs}>{preview(live.tool.args)}</span>}
|
||||
<span className={css.liveGlyph} aria-hidden="true">{toolGlyph('bash')(9)}</span>
|
||||
<span className={css.liveTool} title={activity}>{activity}</span>
|
||||
</span>
|
||||
)}
|
||||
{live.text !== undefined && (
|
||||
{live?.text !== undefined && (
|
||||
<span className={css.liveText}>{preview(flatten(live.text), TEXT_PREVIEW)}</span>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user