/** * @license * Copyright 2025 Qwen Team * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { EditPencilIcon, AutoEditIcon, PlanModeIcon, CodeBracketsIcon, HideContextIcon, ThinkingIcon, SlashCommandIcon, LinkIcon, ArrowUpIcon, StopIcon, } from '../icons/index.js'; import { CompletionMenu } from '../layout/CompletionMenu.js'; import type { CompletionItem } from '../../../types/completionItemTypes.js'; import { getApprovalModeInfoFromString } from '../../../types/acpTypes.js'; import type { ApprovalModeValue } from '../../../types/acpTypes.js'; interface InputFormProps { inputText: string; // Note: RefObject carries nullability in its `current` property, so the // generic should be `HTMLDivElement` (not `HTMLDivElement | null`). inputFieldRef: React.RefObject; isStreaming: boolean; isWaitingForResponse: boolean; isComposing: boolean; editMode: ApprovalModeValue; thinkingEnabled: boolean; activeFileName: string | null; activeSelection: { startLine: number; endLine: number } | null; // Whether to auto-load the active editor selection/path into context skipAutoActiveContext: boolean; onInputChange: (text: string) => void; onCompositionStart: () => void; onCompositionEnd: () => void; onKeyDown: (e: React.KeyboardEvent) => void; onSubmit: (e: React.FormEvent) => void; onCancel: () => void; onToggleEditMode: () => void; onToggleThinking: () => void; onFocusActiveEditor: () => void; onToggleSkipAutoActiveContext: () => void; onShowCommandMenu: () => void; onAttachContext: () => void; completionIsOpen: boolean; completionItems?: CompletionItem[]; onCompletionSelect?: (item: CompletionItem) => void; onCompletionClose?: () => void; } // Get edit mode display info using helper function const getEditModeInfo = (editMode: ApprovalModeValue) => { const info = getApprovalModeInfoFromString(editMode); // Map icon types to actual icons let icon = null; switch (info.iconType) { case 'edit': icon = ; break; case 'auto': icon = ; break; case 'plan': icon = ; break; case 'yolo': icon = ; break; default: icon = null; break; } return { text: info.label, title: info.title, icon, }; }; export const InputForm: React.FC = ({ inputText, inputFieldRef, isStreaming, isWaitingForResponse, isComposing, editMode, thinkingEnabled, activeFileName, activeSelection, skipAutoActiveContext, onInputChange, onCompositionStart, onCompositionEnd, onKeyDown, onSubmit, onCancel, onToggleEditMode, onToggleThinking, onToggleSkipAutoActiveContext, onShowCommandMenu, onAttachContext, completionIsOpen, completionItems, onCompletionSelect, onCompletionClose, }) => { const editModeInfo = getEditModeInfo(editMode); const composerDisabled = isStreaming || isWaitingForResponse; const handleKeyDown = (e: React.KeyboardEvent) => { // ESC should cancel the current interaction (stop generation) if (e.key === 'Escape') { e.preventDefault(); onCancel(); return; } // If composing (Chinese IME input), don't process Enter key if (e.key === 'Enter' && !e.shiftKey && !isComposing) { // If CompletionMenu is open, let it handle Enter key if (completionIsOpen) { return; } e.preventDefault(); onSubmit(e); } onKeyDown(e); }; // Selection label like "6 lines selected"; no line numbers const selectedLinesCount = activeSelection ? Math.max(1, activeSelection.endLine - activeSelection.startLine + 1) : 0; const selectedLinesText = selectedLinesCount > 0 ? `${selectedLinesCount} ${selectedLinesCount === 1 ? 'line' : 'lines'} selected` : ''; return (
{/* Inner background layer */}
{/* Banner area */}
{completionIsOpen && completionItems && completionItems.length > 0 && onCompletionSelect && onCompletionClose && ( )}
into contentEditable (so :empty no longer matches) data-empty={ inputText.replace(/\u200B/g, '').trim().length === 0 ? 'true' : 'false' } onInput={(e) => { if (composerDisabled) { return; } const target = e.target as HTMLDivElement; // Filter out zero-width space that we use to maintain height const text = target.textContent?.replace(/\u200B/g, '') || ''; onInputChange(text); }} onCompositionStart={onCompositionStart} onCompositionEnd={onCompositionEnd} onKeyDown={handleKeyDown} suppressContentEditableWarning />
{/* Edit mode button */} {/* Active file indicator */} {activeFileName && ( )} {/* Spacer */}
{/* Thinking button */} {/* Command button */} {/* Attach button */} {/* Send/Stop button */} {isStreaming || isWaitingForResponse ? ( ) : ( )}
); };