mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(web): compact terminal tool cards by default (#601)
This commit is contained in:
@@ -10,6 +10,7 @@ import { HappyUserMessage } from '@/components/AssistantChat/messages/UserMessag
|
||||
import { HappySystemMessage } from '@/components/AssistantChat/messages/SystemMessage'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Spinner } from '@/components/Spinner'
|
||||
import { useTerminalToolDisplayMode } from '@/hooks/useTerminalToolDisplayMode'
|
||||
import { useTranslation } from '@/lib/use-translation'
|
||||
import { CloseIcon } from '@/components/icons'
|
||||
|
||||
@@ -264,6 +265,7 @@ export function HappyThread(props: {
|
||||
onOutlineItemClick?: (item: ConversationOutlineItem) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { terminalToolDisplayMode } = useTerminalToolDisplayMode()
|
||||
const viewportRef = useRef<HTMLDivElement | null>(null)
|
||||
const contentRef = useRef<HTMLDivElement | null>(null)
|
||||
const topSentinelRef = useRef<HTMLDivElement | null>(null)
|
||||
@@ -676,6 +678,7 @@ export function HappyThread(props: {
|
||||
api: props.api,
|
||||
sessionId: props.sessionId,
|
||||
metadata: props.metadata,
|
||||
terminalToolDisplayMode,
|
||||
disabled: props.disabled,
|
||||
onRefresh: props.onRefresh,
|
||||
onRetryMessage: props.onRetryMessage
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { createContext, useContext } from 'react'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
import type { TerminalToolDisplayMode } from '@/hooks/useTerminalToolDisplayMode'
|
||||
import type { SessionMetadataSummary } from '@/types/api'
|
||||
|
||||
export type HappyChatContextValue = {
|
||||
api: ApiClient
|
||||
sessionId: string
|
||||
metadata: SessionMetadataSummary | null
|
||||
terminalToolDisplayMode: TerminalToolDisplayMode
|
||||
disabled: boolean
|
||||
onRefresh: () => void
|
||||
onRetryMessage?: (localId: string) => void
|
||||
|
||||
@@ -120,6 +120,7 @@ function HappyNestedBlockList(props: {
|
||||
api={ctx.api}
|
||||
sessionId={ctx.sessionId}
|
||||
metadata={ctx.metadata}
|
||||
terminalToolDisplayMode={ctx.terminalToolDisplayMode}
|
||||
disabled={ctx.disabled}
|
||||
onDone={ctx.onRefresh}
|
||||
block={block}
|
||||
@@ -211,6 +212,7 @@ export function HappyToolMessage(props: ToolCallMessagePartProps) {
|
||||
api={ctx.api}
|
||||
sessionId={ctx.sessionId}
|
||||
metadata={ctx.metadata}
|
||||
terminalToolDisplayMode={ctx.terminalToolDisplayMode}
|
||||
disabled={ctx.disabled}
|
||||
onDone={ctx.onRefresh}
|
||||
block={block}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { shouldShowInlineToolCardBody, shouldUseCompactTerminalToolCard } from '@/components/ToolCard/ToolCard'
|
||||
|
||||
describe('ToolCard terminal display mode helpers', () => {
|
||||
it('treats terminal-related cards as compact by default', () => {
|
||||
expect(shouldUseCompactTerminalToolCard('CodexBash', 'compact')).toBe(true)
|
||||
expect(shouldUseCompactTerminalToolCard('shell_command', 'compact')).toBe(true)
|
||||
expect(shouldUseCompactTerminalToolCard('run_shell_command', 'compact')).toBe(true)
|
||||
expect(shouldUseCompactTerminalToolCard('Read', 'compact')).toBe(false)
|
||||
})
|
||||
|
||||
it('hides inline terminal previews in compact mode', () => {
|
||||
expect(shouldShowInlineToolCardBody('CodexBash', false, 'compact')).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps inline terminal previews in detailed mode', () => {
|
||||
expect(shouldShowInlineToolCardBody('CodexBash', false, 'detailed')).toBe(true)
|
||||
expect(shouldShowInlineToolCardBody('Bash', true, 'detailed')).toBe(true)
|
||||
expect(shouldShowInlineToolCardBody('shell_command', true, 'detailed')).toBe(true)
|
||||
expect(shouldShowInlineToolCardBody('run_shell_command', true, 'detailed')).toBe(true)
|
||||
})
|
||||
|
||||
it('still hides inline bodies for minimal and Task/Agent subagent cards', () => {
|
||||
expect(shouldShowInlineToolCardBody('Task', false, 'detailed')).toBe(false)
|
||||
expect(shouldShowInlineToolCardBody('Agent', false, 'detailed')).toBe(false)
|
||||
expect(shouldShowInlineToolCardBody('Read', true, 'detailed')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -17,6 +17,7 @@ import { getToolPresentation } from '@/components/ToolCard/knownTools'
|
||||
import { getToolFullViewComponent, getToolViewComponent } from '@/components/ToolCard/views/_all'
|
||||
import { getToolResultViewComponent } from '@/components/ToolCard/views/_results'
|
||||
import { formatTaskChildLabel, TaskStateIcon } from '@/components/ToolCard/helpers'
|
||||
import type { TerminalToolDisplayMode } from '@/hooks/useTerminalToolDisplayMode'
|
||||
import { usePointerFocusRing } from '@/hooks/usePointerFocusRing'
|
||||
import { getInputString, getInputStringAny, truncate } from '@/lib/toolInputUtils'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -25,6 +26,23 @@ import { TraceSection } from '@/components/ToolCard/trace'
|
||||
import { isSubagentToolName } from '@/chat/subagentTool'
|
||||
|
||||
const ELAPSED_INTERVAL_MS = 1000
|
||||
const TERMINAL_RELATED_TOOL_NAMES = new Set(['Bash', 'CodexBash', 'shell_command', 'run_shell_command'])
|
||||
|
||||
export function shouldUseCompactTerminalToolCard(toolName: string, terminalToolDisplayMode: TerminalToolDisplayMode): boolean {
|
||||
return TERMINAL_RELATED_TOOL_NAMES.has(toolName) && terminalToolDisplayMode === 'compact'
|
||||
}
|
||||
|
||||
export function shouldShowInlineToolCardBody(
|
||||
toolName: string,
|
||||
presentationMinimal: boolean,
|
||||
terminalToolDisplayMode: TerminalToolDisplayMode
|
||||
): boolean {
|
||||
if (isSubagentToolName(toolName)) return false
|
||||
if (TERMINAL_RELATED_TOOL_NAMES.has(toolName)) {
|
||||
return terminalToolDisplayMode === 'detailed'
|
||||
}
|
||||
return !presentationMinimal
|
||||
}
|
||||
|
||||
function ElapsedView(props: { from: number; active: boolean }) {
|
||||
const [now, setNow] = useState(() => Date.now())
|
||||
@@ -260,6 +278,7 @@ type ToolCardProps = {
|
||||
api: ApiClient
|
||||
sessionId: string
|
||||
metadata: SessionMetadataSummary | null
|
||||
terminalToolDisplayMode: TerminalToolDisplayMode
|
||||
disabled: boolean
|
||||
onDone: () => void
|
||||
block: ToolCallBlock
|
||||
@@ -289,7 +308,9 @@ function ToolCardInner(props: ToolCardProps) {
|
||||
const subtitle = presentation.subtitle ?? props.block.tool.description
|
||||
const taskSummary = renderTaskSummary(props.block, props.metadata, t)
|
||||
const runningFrom = props.block.tool.startedAt ?? props.block.tool.createdAt
|
||||
const showInline = !presentation.minimal && !isSubagentToolName(toolName)
|
||||
const isCodexAgentCard = toolName === 'CodexAgent'
|
||||
const useCompactTerminalCard = shouldUseCompactTerminalToolCard(toolName, props.terminalToolDisplayMode)
|
||||
const showInline = shouldShowInlineToolCardBody(toolName, presentation.minimal, props.terminalToolDisplayMode)
|
||||
const CompactToolView = showInline ? getToolViewComponent(toolName) : null
|
||||
const FullToolView = getToolFullViewComponent(toolName)
|
||||
const ResultToolView = getToolResultViewComponent(toolName)
|
||||
@@ -297,7 +318,6 @@ function ToolCardInner(props: ToolCardProps) {
|
||||
const isAskUserQuestion = isAskUserQuestionToolName(toolName)
|
||||
const isRequestUserInput = isRequestUserInputToolName(toolName)
|
||||
const isQuestionTool = isAskUserQuestion || isRequestUserInput
|
||||
const isCodexAgentCard = toolName === 'CodexAgent'
|
||||
const showsPermissionFooter = Boolean(permission && (
|
||||
permission.status === 'pending'
|
||||
|| ((permission.status === 'denied' || permission.status === 'canceled') && Boolean(permission.reason))
|
||||
@@ -324,7 +344,7 @@ function ToolCardInner(props: ToolCardProps) {
|
||||
{subtitle ? (
|
||||
<CardDescription className={cn(
|
||||
'font-mono text-xs text-[var(--app-tool-card-subtitle)]',
|
||||
isCodexAgentCard ? 'truncate whitespace-nowrap' : 'break-all'
|
||||
isCodexAgentCard || useCompactTerminalCard ? 'truncate whitespace-nowrap' : 'break-all'
|
||||
)}>
|
||||
{truncate(subtitle, 160)}
|
||||
</CardDescription>
|
||||
|
||||
Reference in New Issue
Block a user