claude-code-best/src/components/tasks/InProcessTeammateDetailDialog.tsx
2026-04-07 15:05:03 +08:00

194 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useMemo } from 'react'
import type { DeepImmutable } from 'src/types/utils.js'
import { useElapsedTime } from '../../hooks/useElapsedTime.js'
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'
import { Box, Text, useTheme } from '../../ink.js'
import { useKeybindings } from '../../keybindings/useKeybinding.js'
import { getEmptyToolPermissionContext } from '../../Tool.js'
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js'
import { getTools } from '../../tools.js'
import { formatNumber, truncateToWidth } from '../../utils/format.js'
import { toInkColor } from '../../utils/ink.js'
import { Byline } from '../design-system/Byline.js'
import { Dialog } from '../design-system/Dialog.js'
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'
import { renderToolActivity } from './renderToolActivity.js'
import { describeTeammateActivity } from './taskStatusUtils.js'
type Props = {
teammate: DeepImmutable<InProcessTeammateTaskState>
onDone: () => void
onKill?: () => void
onBack?: () => void
onForeground?: () => void
}
export function InProcessTeammateDetailDialog({
teammate,
onDone,
onKill,
onBack,
onForeground,
}: Props): React.ReactNode {
const [theme] = useTheme()
const tools = useMemo(() => getTools(getEmptyToolPermissionContext()), [])
const elapsedTime = useElapsedTime(
teammate.startTime,
teammate.status === 'running',
1000,
teammate.totalPausedMs ?? 0,
)
// Restore confirm:yes (Enter/y) dismissal — Dialog handles confirm:no (Esc)
useKeybindings(
{
'confirm:yes': onDone,
},
{ context: 'Confirmation' },
)
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === ' ') {
e.preventDefault()
onDone()
} else if (e.key === 'left' && onBack) {
e.preventDefault()
onBack()
} else if (e.key === 'x' && teammate.status === 'running' && onKill) {
e.preventDefault()
onKill()
} else if (e.key === 'f' && teammate.status === 'running' && onForeground) {
e.preventDefault()
onForeground()
}
}
const activity = describeTeammateActivity(teammate)
const tokenCount =
teammate.result?.totalTokens ?? teammate.progress?.tokenCount
const toolUseCount =
teammate.result?.totalToolUseCount ?? teammate.progress?.toolUseCount
const displayPrompt = truncateToWidth(teammate.prompt, 300)
const title = (
<Text>
<Text color={toInkColor(teammate.identity.color)}>
@{teammate.identity.agentName}
</Text>
{activity && <Text dimColor> ({activity})</Text>}
</Text>
)
const subtitle = (
<Text>
{teammate.status !== 'running' && (
<Text
color={
teammate.status === 'completed'
? 'success'
: teammate.status === 'killed'
? 'warning'
: 'error'
}
>
{teammate.status === 'completed'
? 'Completed'
: teammate.status === 'failed'
? 'Failed'
: 'Stopped'}
{' · '}
</Text>
)}
<Text dimColor>
{elapsedTime}
{tokenCount !== undefined && tokenCount > 0 && (
<> · {formatNumber(tokenCount)} tokens</>
)}
{toolUseCount !== undefined && toolUseCount > 0 && (
<>
{' '}
· {toolUseCount} {toolUseCount === 1 ? 'tool' : 'tools'}
</>
)}
</Text>
</Text>
)
return (
<Box
flexDirection="column"
tabIndex={0}
autoFocus
onKeyDown={handleKeyDown}
>
<Dialog
title={title}
subtitle={subtitle}
onCancel={onDone}
color="background"
inputGuide={exitState =>
exitState.pending ? (
<Text>Press {exitState.keyName} again to exit</Text>
) : (
<Byline>
{onBack && <KeyboardShortcutHint shortcut="←" action="go back" />}
<KeyboardShortcutHint shortcut="Esc/Enter/Space" action="close" />
{teammate.status === 'running' && onKill && (
<KeyboardShortcutHint shortcut="x" action="stop" />
)}
{teammate.status === 'running' && onForeground && (
<KeyboardShortcutHint shortcut="f" action="foreground" />
)}
</Byline>
)
}
>
{/* Recent activities for running teammates */}
{teammate.status === 'running' &&
teammate.progress?.recentActivities &&
teammate.progress.recentActivities.length > 0 && (
<Box flexDirection="column">
<Text bold dimColor>
Progress
</Text>
{teammate.progress.recentActivities.map((activity, i) => (
<Text
key={i}
dimColor={i < teammate.progress!.recentActivities!.length - 1}
wrap="truncate-end"
>
{i === teammate.progress!.recentActivities!.length - 1
? ' '
: ' '}
{renderToolActivity(activity, tools, theme)}
</Text>
))}
</Box>
)}
{/* Prompt section */}
<Box flexDirection="column" marginTop={1}>
<Text bold dimColor>
Prompt
</Text>
<Text wrap="wrap">{displayPrompt}</Text>
</Box>
{/* Error details if failed */}
{teammate.status === 'failed' && teammate.error && (
<Box flexDirection="column" marginTop={1}>
<Text bold color="error">
Error
</Text>
<Text color="error" wrap="wrap">
{teammate.error}
</Text>
</Box>
)}
</Dialog>
</Box>
)
}