From 13aa4b03c72fa84a25080e492b462f14d7f2a355 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Fri, 5 Dec 2025 18:03:29 +0800 Subject: [PATCH] feat(vscode-ide-companion/ui): improve permission drawer UI and logic --- .../webview/components/PermissionDrawer.tsx | 189 +++++++++++------- 1 file changed, 120 insertions(+), 69 deletions(-) diff --git a/packages/vscode-ide-companion/src/webview/components/PermissionDrawer.tsx b/packages/vscode-ide-companion/src/webview/components/PermissionDrawer.tsx index d692108e..0e87320e 100644 --- a/packages/vscode-ide-companion/src/webview/components/PermissionDrawer.tsx +++ b/packages/vscode-ide-companion/src/webview/components/PermissionDrawer.tsx @@ -29,17 +29,40 @@ export const PermissionDrawer: React.FC = ({ const [focusedIndex, setFocusedIndex] = useState(0); const [customMessage, setCustomMessage] = useState(''); const containerRef = useRef(null); - const customInputRef = useRef(null); + // 将自定义输入的 ref 类型修正为 HTMLInputElement,避免后续强转 + const customInputRef = useRef(null); console.log('PermissionDrawer rendered with isOpen:', isOpen, toolCall); + // Prefer file name from locations, fall back to content[].path if present + const getAffectedFileName = (): string => { + const fromLocations = toolCall.locations?.[0]?.path; + if (fromLocations) { + return fromLocations.split('/').pop() || fromLocations; + } + // Some tool calls (e.g. write/edit with diff content) only include path in content + const fromContent = Array.isArray(toolCall.content) + ? ( + toolCall.content.find( + (c: unknown) => + typeof c === 'object' && + c !== null && + 'path' in (c as Record), + ) as { path?: unknown } | undefined + )?.path + : undefined; + if (typeof fromContent === 'string' && fromContent.length > 0) { + return fromContent.split('/').pop() || fromContent; + } + return 'file'; + }; + // Get the title for the permission request const getTitle = () => { if (toolCall.kind === 'edit' || toolCall.kind === 'write') { - const fileName = - toolCall.locations?.[0]?.path?.split('/').pop() || 'file'; + const fileName = getAffectedFileName(); return ( <> - Allow write to{' '} + Make this edit to{' '} {fileName} @@ -51,8 +74,7 @@ export const PermissionDrawer: React.FC = ({ return 'Allow this bash command?'; } if (toolCall.kind === 'read') { - const fileName = - toolCall.locations?.[0]?.path?.split('/').pop() || 'file'; + const fileName = getAffectedFileName(); return ( <> Allow read from{' '} @@ -126,6 +148,13 @@ export const PermissionDrawer: React.FC = ({ } }, [isOpen]); + // Reset focus to the first option when the drawer opens or the options change + useEffect(() => { + if (isOpen) { + setFocusedIndex(0); + } + }, [isOpen, options.length]); + if (!isOpen) { return null; } @@ -135,7 +164,7 @@ export const PermissionDrawer: React.FC = ({ {/* Main container */}
= ({ style={{ backgroundColor: 'var(--app-input-background)' }} /> - {/* Title */} + {/* Title + Description (from toolCall.title) */}
-
+
{getTitle()}
+ {(toolCall.kind === 'edit' || + toolCall.kind === 'write' || + toolCall.kind === 'read' || + toolCall.kind === 'execute' || + toolCall.kind === 'bash') && + toolCall.title && ( +
+ {toolCall.title} +
+ )}
{/* Options */} @@ -164,10 +207,10 @@ export const PermissionDrawer: React.FC = ({ return (
- + {/* Moved slide-up keyframes to Tailwind theme (tailwind.config.js) */} ); }; + +/** + * CustomMessageInputRow: 复用的自定义输入行组件(无 hooks) + */ +interface CustomMessageInputRowProps { + isFocused: boolean; + customMessage: string; + setCustomMessage: (val: string) => void; + onFocusRow: () => void; // 鼠标移入或输入框 focus 时设置焦点 + onSubmitReject: () => void; // Enter 提交时触发(选择 reject 选项) + inputRef: React.RefObject; +} + +const CustomMessageInputRow: React.FC = ({ + isFocused, + customMessage, + setCustomMessage, + onFocusRow, + onSubmitReject, + inputRef, +}) => ( +
inputRef.current?.focus()} + > + {/* 输入行不显示序号徽标 */} + {/* Input field */} + setCustomMessage(e.target.value)} + onFocus={onFocusRow} + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey && customMessage.trim()) { + e.preventDefault(); + onSubmitReject(); + } + }} + /> +
+);