diff --git a/apps/desktop/src/components/assistant-ui/clarify-tool.tsx b/apps/desktop/src/components/assistant-ui/clarify-tool.tsx index ce72a8179fb..655ff389ec2 100644 --- a/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +++ b/apps/desktop/src/components/assistant-ui/clarify-tool.tsx @@ -2,7 +2,7 @@ import { type ToolCallMessagePartProps } from '@assistant-ui/react' import { useStore } from '@nanostores/react' -import { type FormEvent, type KeyboardEvent, useCallback, useMemo, useRef, useState } from 'react' +import { type FormEvent, type KeyboardEvent, useCallback, useMemo, useRef, useState, type ComponentProps } from 'react' import { ToolFallback } from '@/components/assistant-ui/tool-fallback' import { Button } from '@/components/ui/button' @@ -36,14 +36,30 @@ function readClarifyArgs(args: unknown): ClarifyArgs { } // Choice and "Other" rows share a layout; only color/hover differs. -const OPTION_ROW_CLASS = 'flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors' +const OPTION_ROW_CLASS = 'flex w-full items-start gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors' + +const CLARIFY_SHELL_CLASS = + 'relative mb-3 mt-2 rounded-[0.5rem] border border-border/70 bg-card/40 text-sm shadow-[inset_0_1px_0_color-mix(in_srgb,var(--foreground)_3%,transparent)]' + +function ClarifyShell({ + children, + className, + ...props +}: ComponentProps<'div'>) { + return ( +
+ + {children} +
+ ) +} function RadioDot({ selected }: { selected: boolean }) { return ( @@ -99,9 +115,11 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) { const textareaRef = useRef(null) // Race: tool.start fires a tick before clarify.request, so request_id - // arrives slightly after the tool block mounts. Show the question (from - // args) but disable submit until we have the request id from the gateway. + // arrives slightly after the tool block mounts. Hold the whole panel on a + // spinner until the gateway request is wired — showing disabled choices or + // a "loading question" stub is worse than a brief wait. const ready = Boolean(matchingRequest?.requestId) + const loading = !ready && !submitting const respond = useCallback( async (answer: string) => { @@ -138,7 +156,11 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) { const handleTextareaKey = useCallback( (event: KeyboardEvent) => { - if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { + if (event.nativeEvent.isComposing) { + return + } + + if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault() const trimmed = draft.trim() @@ -162,12 +184,20 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) { [draft, respond] ) + if (loading) { + return ( + + + + ) + } + return ( -
- +
- - {question || {copy.loadingQuestion}} - + {question}
{!typing && hasChoices && ( @@ -190,7 +218,7 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) { selectedChoice === choice && 'bg-accent/60' )} data-choice - disabled={!ready || submitting} + disabled={submitting} key={`${index}-${choice}`} onClick={() => { setSelectedChoice(choice) @@ -200,7 +228,7 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) { > {choice} - {selectedChoice === choice && } + {selectedChoice === choice && } ))} )} - -
@@ -270,7 +293,7 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) {
)} - + ) }