From 8019cb72f50ef2f7c475112c75ebab4a53e2d740 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Thu, 7 May 2026 11:26:59 +0800 Subject: [PATCH] fix(web-ui): restore inline ACP file approval actions --- .../tool-cards/FileOperationToolCard.scss | 31 ++++++- .../tool-cards/FileOperationToolCard.tsx | 91 +++++++++++++++++-- 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.scss b/src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.scss index 877dbf807..94984b1d1 100644 --- a/src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.scss +++ b/src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.scss @@ -347,4 +347,33 @@ color: var(--color-text-muted); white-space: pre-wrap; word-break: break-word; -} \ No newline at end of file +} + +.file-op-header-actions { + gap: 4px; +} + +.file-op-header-action.icon-btn--xs { + width: 18px; + height: 18px; +} + +.file-op-confirm-btn { + color: var(--color-success); + + &:hover:not(:disabled) { + background: var(--color-success-bg); + color: var(--color-success); + box-shadow: 0 2px 8px var(--color-success-bg); + } +} + +.file-op-reject-btn { + color: var(--color-error); + + &:hover:not(:disabled) { + background: var(--color-error-bg); + color: var(--color-error); + box-shadow: 0 2px 8px var(--color-error-bg); + } +} diff --git a/src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.tsx b/src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.tsx index c95dea660..22a6765c3 100644 --- a/src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.tsx +++ b/src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.tsx @@ -26,8 +26,9 @@ import { Loader2, Clock, Check, + X, } from 'lucide-react'; -import { CubeLoading } from '../../component-library'; +import { CubeLoading, IconButton } from '../../component-library'; import type { ToolCardProps } from '../types/flow-chat'; import { BaseToolCard, ToolCardHeader } from './BaseToolCard'; import { useSnapshotState } from '../../tools/snapshot_system/hooks/useSnapshotState'; @@ -45,6 +46,7 @@ import { useToolCardHeightContract } from './useToolCardHeightContract'; import { hasNonFileUriScheme } from '@/shared/utils/pathUtils'; import { notificationService } from '@/shared/notification-system'; import { useGitState } from '@/tools/git/hooks/useGitState'; +import { ToolCardHeaderActions } from './ToolCardHeaderActions'; import './FileOperationToolCard.scss'; const log = createLogger('FileOperationToolCard'); @@ -59,10 +61,20 @@ export const FileOperationToolCard: React.FC = ({ toolItem, config, sessionId, - onOpenInEditor + onOpenInEditor, + onConfirm, + onReject, }) => { const { t } = useTranslation('flow-chat'); - const { toolCall, toolResult, status, isParamsStreaming, partialParams } = toolItem; + const { + toolCall, + toolResult, + status, + isParamsStreaming, + partialParams, + requiresConfirmation, + userConfirmed, + } = toolItem; const toolId = toolItem.id ?? toolCall?.id; const [isErrorExpanded, setIsErrorExpanded] = useState(false); @@ -128,6 +140,13 @@ export const FileOperationToolCard: React.FC = ({ const contentPreview = getContent(); const isFailed = status === 'error' || (toolResult && 'success' in toolResult && !toolResult.success); + const showConfirmationActions = Boolean( + requiresConfirmation && + !userConfirmed && + status !== 'completed' && + status !== 'cancelled' && + status !== 'error' + ); const fileName = currentFilePath ? (currentFilePath.split(/[/\\]/).pop() || t('context.file')) : @@ -443,7 +462,7 @@ export const FileOperationToolCard: React.FC = ({ if ( (e.target as HTMLElement).closest( - '.file-op-diff-pill, .file-op-open-full-button', + '.file-op-diff-pill, .file-op-open-full-button, .tool-card-header-actions', ) ) { return; @@ -468,6 +487,18 @@ export const FileOperationToolCard: React.FC = ({ toolItem.toolName, ]); + const handleConfirmClick = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + onConfirm?.(toolCall?.input); + }, [onConfirm, toolCall?.input]); + + const handleRejectClick = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + onReject?.(); + }, [onReject]); + const handleOpenFullCodeClick = useCallback((e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -720,12 +751,11 @@ export const FileOperationToolCard: React.FC = ({ const expandedContent = renderExpandedContent(); const hasExpandableContent = !isFailed && - !isDeleteTool && Boolean(expandedContent); const isCardContentExpanded = - !isDeleteTool && !isFailed && + !isDeleteTool && isContentExpanded; const renderHeader = () => { @@ -793,12 +823,34 @@ export const FileOperationToolCard: React.FC = ({ ) } extra={ - <> + {isParamsStreaming && (status === 'preparing' || status === 'streaming') && ( {currentFilePath ? t('toolCards.file.receivingParams') : t('toolCards.file.analyzing')} )} + {showConfirmationActions && ( + <> + + + + + + + + )} {canOpenFullCode && ( )} - + } statusIcon={isDeleteTool ? null : renderStatusIcon()} /> @@ -829,6 +881,28 @@ export const FileOperationToolCard: React.FC = ({ + + + + + + + + ) : undefined} /> } /> @@ -846,6 +920,7 @@ export const FileOperationToolCard: React.FC = ({ expandedContent={expandedContent} errorContent={isFailed && isErrorExpanded ? renderErrorContent() : null} isFailed={isFailed} + requiresConfirmation={showConfirmationActions} headerExpandAffordance={hasExpandableContent} headerAffordanceKind="expand" />