mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-06 06:41:56 +00:00
feat(codex): preserve native exploration actions (#1139)
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { ToolGroupBlock } from '@/chat/toolGroups'
|
||||
import type { ToolCallBlock } from '@/chat/types'
|
||||
import { getCodexCommandActions, type CodexCommandAction } from '@/chat/codexCommandPresentation'
|
||||
import type { SessionMetadataSummary } from '@/types/api'
|
||||
import { useHappyChatContext } from '@/components/AssistantChat/context'
|
||||
import { ToolDetailDialogContent, ToolStatusIcon, toolStatusColorClass } from '@/components/ToolCard/ToolCard'
|
||||
import { getToolPresentation } from '@/components/ToolCard/knownTools'
|
||||
import { formatGroupedHeaderSubtitle, formatGroupedHeaderTitle } from '@/components/ToolCard/groupedPresentation'
|
||||
import { formatGroupedHeaderSubtitle, formatGroupedHeaderTitle, safeGroupedLabelValue } from '@/components/ToolCard/groupedPresentation'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -99,6 +100,64 @@ function RowLabel(props: { block: ToolCallBlock; metadata: SessionMetadataSummar
|
||||
)
|
||||
}
|
||||
|
||||
function basename(value: string): string {
|
||||
return value.replace(/\\/g, '/').split('/').filter(Boolean).at(-1) ?? value
|
||||
}
|
||||
|
||||
function codexActionLabel(
|
||||
action: CodexCommandAction,
|
||||
t: (key: string, params?: Record<string, string | number>) => string
|
||||
): { title: string; detail: string | null } {
|
||||
if (action.type === 'read') {
|
||||
const detail = safeGroupedLabelValue(action.name) ?? safeGroupedLabelValue(action.path)
|
||||
return { title: t('toolGroup.codex.read'), detail: detail ? basename(detail) : null }
|
||||
}
|
||||
if (action.type === 'listFiles') {
|
||||
return { title: t('toolGroup.codex.list'), detail: safeGroupedLabelValue(action.path) }
|
||||
}
|
||||
if (action.type === 'search') {
|
||||
const query = safeGroupedLabelValue(action.query)
|
||||
const path = safeGroupedLabelValue(action.path)
|
||||
return {
|
||||
title: t('toolGroup.codex.search'),
|
||||
detail: query && path
|
||||
? t('toolGroup.codex.searchIn', { query, path })
|
||||
: query ?? path
|
||||
}
|
||||
}
|
||||
return { title: t('toolGroup.friendly.genericCommand'), detail: null }
|
||||
}
|
||||
|
||||
function CodexExplorationRows(props: {
|
||||
tools: ToolCallBlock[]
|
||||
onSelect: (toolId: string) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
return props.tools.flatMap((tool) => (
|
||||
getCodexCommandActions(tool).map((action, index) => {
|
||||
const label = codexActionLabel(action, t)
|
||||
return (
|
||||
<button
|
||||
key={`${tool.id}:${index}`}
|
||||
type="button"
|
||||
className="flex min-w-0 items-start gap-2 rounded-lg px-2 py-1 text-left hover:bg-[var(--app-subtle-bg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--app-link)]"
|
||||
onClick={() => props.onSelect(tool.id)}
|
||||
>
|
||||
<span className="mt-1 text-xs text-[var(--app-hint)]">└</span>
|
||||
<span className="shrink-0 text-sm font-medium text-[var(--app-tool-card-accent)]">
|
||||
{label.title}
|
||||
</span>
|
||||
{label.detail ? (
|
||||
<span className="min-w-0 truncate text-sm text-[var(--app-fg)]">
|
||||
{label.detail}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
export function ToolGroupCard(props: {
|
||||
block: ToolGroupBlock
|
||||
metadata: SessionMetadataSummary | null
|
||||
@@ -219,7 +278,9 @@ export function ToolGroupCard(props: {
|
||||
}, [selectedTool, props.metadata, t])
|
||||
|
||||
const primaryTitle = formatGroupedHeaderTitle(props.block, t)
|
||||
const subtitle = formatGroupedHeaderSubtitle(props.block, t) ?? formatActionSummary(props.block, t)
|
||||
const subtitle = props.block.presentationMode === 'codex-exploration'
|
||||
? null
|
||||
: formatGroupedHeaderSubtitle(props.block, t) ?? formatActionSummary(props.block, t)
|
||||
const fileCount = props.block.summary.fileTargets.length
|
||||
|
||||
return (
|
||||
@@ -249,10 +310,12 @@ export function ToolGroupCard(props: {
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-2 self-center text-[var(--app-hint)]">
|
||||
<SummaryBadge
|
||||
className="bg-[var(--app-subtle-bg)] text-[var(--app-hint)]"
|
||||
text={t('toolGroup.toolCount', { n: props.block.tools.length })}
|
||||
/>
|
||||
{props.block.presentationMode !== 'codex-exploration' ? (
|
||||
<SummaryBadge
|
||||
className="bg-[var(--app-subtle-bg)] text-[var(--app-hint)]"
|
||||
text={t('toolGroup.toolCount', { n: props.block.tools.length })}
|
||||
/>
|
||||
) : null}
|
||||
{props.block.summary.runningCount > 0 ? (
|
||||
<SummaryBadge
|
||||
className="bg-sky-500/10 text-sky-600"
|
||||
@@ -285,7 +348,9 @@ export function ToolGroupCard(props: {
|
||||
{open ? (
|
||||
<CardContent className="px-3 pb-3 pt-1">
|
||||
<div className="flex flex-col gap-2">
|
||||
{props.block.tools.map((tool) => {
|
||||
{props.block.presentationMode === 'codex-exploration' ? (
|
||||
<CodexExplorationRows tools={props.block.tools} onSelect={setSelectedToolId} />
|
||||
) : props.block.tools.map((tool) => {
|
||||
return (
|
||||
<button
|
||||
key={tool.id}
|
||||
|
||||
Reference in New Issue
Block a user