diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock index 1f68710e..9dfbb9d5 100644 --- a/packages/desktop/src-tauri/Cargo.lock +++ b/packages/desktop/src-tauri/Cargo.lock @@ -2994,7 +2994,7 @@ dependencies = [ [[package]] name = "openchamber-desktop" -version = "1.5.9" +version = "1.6.0" dependencies = [ "anyhow", "axum", diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index c736886a..2cd9d88b 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -4,7 +4,6 @@ import { RiAddCircleLine, RiAiAgentLine, RiAttachment2, - RiCloseCircleLine, RiFileUploadLine, RiSendPlane2Line, } from '@remixicon/react'; @@ -31,6 +30,7 @@ import { toast } from '@/components/ui'; import { useFileStore } from '@/stores/fileStore'; import { isVSCodeRuntime } from '@/lib/desktop'; import { isIMECompositionEvent } from '@/lib/ime'; +import { StopIcon } from '@/components/icons/StopIcon'; import { DropdownMenu, DropdownMenuContent, @@ -121,7 +121,6 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo const abortCurrentOperation = useSessionStore((state) => state.abortCurrentOperation); const acknowledgeSessionAbort = useSessionStore((state) => state.acknowledgeSessionAbort); const abortPromptSessionId = useSessionStore((state) => state.abortPromptSessionId); - const abortPromptExpiresAt = useSessionStore((state) => state.abortPromptExpiresAt); const clearAbortPrompt = useSessionStore((state) => state.clearAbortPrompt); const sessionAbortFlags = useSessionStore((state) => state.sessionAbortFlags); const attachedFiles = useSessionStore((state) => state.attachedFiles); @@ -297,11 +296,6 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo const canAbort = working.isWorking; - const isAbortPromptActive = React.useMemo(() => { - if (!currentSessionId) return false; - return abortPromptSessionId === currentSessionId && Boolean(abortPromptExpiresAt); - }, [abortPromptSessionId, abortPromptExpiresAt, currentSessionId]); - // Add message to queue instead of sending const handleQueueMessage = React.useCallback(() => { if (!hasContent || !currentSessionId) return; @@ -1138,7 +1132,7 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo event.target.value = ''; }, [attachFiles]); - const footerGapClass = 'gap-x-1.5 gap-y-0'; + const footerGapClass = isMobile ? 'gap-x-0.5 gap-y-0' : 'gap-x-1.5 gap-y-0'; const isVSCode = isVSCodeRuntime(); const footerPaddingClass = isMobile ? 'px-1.5 py-1.5' : (isVSCode ? 'px-1.5 py-1' : 'px-2.5 py-1.5'); const footerHeightClass = isMobile ? 'h-9 w-9' : (isVSCode ? 'h-[22px] w-[22px]' : 'h-7 w-7'); @@ -1149,22 +1143,21 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo 'flex items-center justify-center text-muted-foreground transition-none outline-none focus:outline-none flex-shrink-0' ); - // Desktop and VSCode: show abort button in footer when Esc triggered - const showAbortInFooter = !isMobile && isAbortPromptActive && canAbort; - - const actionButton = showAbortInFooter ? ( + const cancelButton = canAbort ? ( - ) : ( + ) : null; + + const sendButton = ( )} @@ -2261,21 +2283,15 @@ export const ModelControls: React.FC = ({ className }) => { type="button" onClick={() => setActiveMobilePanel('variant')} className={cn( - 'model-controls__variant-trigger flex items-center gap-1.5 transition-opacity min-w-0 focus:outline-none', + 'model-controls__variant-trigger flex items-center justify-center transition-opacity focus:outline-none', buttonHeight, + 'w-9', 'cursor-pointer hover:opacity-70', )} + aria-label={`Thinking: ${displayVariant}`} + title={`Thinking: ${displayVariant}`} > - - {displayVariant} - ); } @@ -2430,11 +2446,12 @@ export const ModelControls: React.FC = ({ className }) => { onTouchStart={() => handleLongPressStart('agent')} onTouchEnd={handleLongPressEnd} onTouchCancel={handleLongPressEnd} - className={cn( - 'model-controls__agent-trigger flex items-center gap-1.5 transition-opacity min-w-0 focus:outline-none', - buttonHeight, - 'cursor-pointer hover:opacity-70', - )} + className={cn( + 'model-controls__agent-trigger flex items-center gap-1 transition-opacity min-w-0 focus:outline-none', + buttonHeight, + 'cursor-pointer hover:opacity-70', + 'px-1', + )} > = ({ className }) => { }; const inlineClassName = cn( - '@container/model-controls flex items-center min-w-0', + (isMobile ? '@container' : '@container/model-controls'), + 'flex items-center min-w-0', // Only force full-width + truncation behaviors on true mobile layouts. - // VS Code also uses "compact" mode, but should keep its right-aligned inline sizing. + // VS Code uses desktop dropdowns. isMobile && 'w-full', className, ); @@ -2472,14 +2490,14 @@ export const ModelControls: React.FC = ({ className }) => {
- {renderVariantSelector()} - {renderModelSelector()} {renderAgentSelector()} + {renderModelSelector()} + {renderVariantSelector()}
diff --git a/packages/ui/src/components/chat/StatusRow.tsx b/packages/ui/src/components/chat/StatusRow.tsx index 8a44123a..1b0db201 100644 --- a/packages/ui/src/components/chat/StatusRow.tsx +++ b/packages/ui/src/components/chat/StatusRow.tsx @@ -55,9 +55,6 @@ interface StatusRowProps { abortActive?: boolean; completionId?: string | null; isComplete?: boolean; - // Abort state (for mobile/vscode) - showAbort?: boolean; - onAbort?: () => void; // Abort status display showAbortStatus?: boolean; } @@ -71,8 +68,6 @@ export const StatusRow: React.FC = ({ abortActive, completionId, isComplete, - showAbort, - onAbort, showAbortStatus, }) => { const [isExpanded, setIsExpanded] = React.useState(false); @@ -158,18 +153,6 @@ export const StatusRow: React.FC = ({ const toggleExpanded = () => setIsExpanded((prev) => !prev); - // Abort button for mobile/vscode - const abortButton = showAbort && onAbort ? ( - - ) : null; - // Todo trigger button const todoTrigger = hasActiveTodos ? (