diff --git a/client/src/components/Chat/Input/AskUserQuestionPopover.tsx b/client/src/components/Chat/Input/AskUserQuestionPopover.tsx index 1df592e213..8493258705 100644 --- a/client/src/components/Chat/Input/AskUserQuestionPopover.tsx +++ b/client/src/components/Chat/Input/AskUserQuestionPopover.tsx @@ -1,11 +1,11 @@ import { memo, useEffect, useRef } from 'react'; import { useWatch } from 'react-hook-form'; -import { Button } from '@librechat/client'; -import { Check, ChevronDown, CornerDownLeft, TriangleAlert, X } from 'lucide-react'; +import { Button, TooltipAnchor } from '@librechat/client'; +import { ChevronDown, CornerDownLeft, TriangleAlert } from 'lucide-react'; import useAskAnswerMode from '~/hooks/Input/useAskAnswerMode'; +import AskOptions from '~/components/Chat/ask/options'; import { useChatFormContext } from '~/Providers'; import { useLocalize } from '~/hooks'; -import { cn } from '~/utils'; /** * Composer popover for a live `ask_user_question` pause. Single-select rows @@ -15,10 +15,10 @@ import { cn } from '~/utils'; * Submit confirms — * folding in any free-form text typed in the composer, exactly like Enter * would. The footer hint is a button that focuses the composer — the - * free-form answer box. Collapse (chevron) hides the popover but keeps - * answer mode live via the chat card; × dismisses it entirely. Pure - * rendering off {@link useAskAnswerMode}; disappears the moment an answer - * submits from any surface, and locks while one is in flight. + * free-form answer box. The chevron moves the question to the chat card and + * releases the composer (the card's chevron moves it back). Pure rendering + * off {@link useAskAnswerMode}; disappears the moment an answer submits from + * any surface, and locks while one is in flight. */ function AskUserQuestionPopoverContent({ conversationId, @@ -66,7 +66,6 @@ function AskUserQuestionPopoverPanel({ submit, submitOption, skip, - dismiss, collapse, handlePopoverKeyDown, } = ask; @@ -108,7 +107,7 @@ function AskUserQuestionPopoverPanel({ scroll region: the panel is absolutely positioned, so anything that overflows it is unreachable by page scroll. */}
@@ -122,60 +121,34 @@ function AskUserQuestionPopoverPanel({

)}
-
- - -
-
-
- {options.map((option, index) => { - const isChecked = multiSelect && checked.includes(index); - return ( + {/* Single exit: moves the question to the chat card and hands the + composer back for normal messages. */} + { - optionRefs.current[index] = el; - }} type="button" - role={multiSelect ? 'checkbox' : undefined} - aria-checked={multiSelect ? isChecked : undefined} - disabled={locked} - className={cn( - 'flex w-full items-center gap-3 rounded-lg p-2 text-left text-sm text-text-primary', - selected === index ? 'bg-surface-active' : 'hover:bg-surface-hover', - locked ? 'cursor-not-allowed opacity-60' : '', - )} - onClick={() => (multiSelect ? toggleChecked(index) : submitOption(index))} + aria-label={localize('com_ui_ask_move_to_chat')} + className="rounded-md p-1 text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy" + onClick={collapse} > - - {isChecked ? - {option.label} +
+ (multiSelect ? toggleChecked(index) : submitOption(index))} + optionRefs={optionRefs} + listRef={listRef} + className="relative min-h-0 flex-1 overflow-y-auto" + /> {/** A failed submission keeps the question answerable (controls stay * enabled), but the chat card that would show the error is hidden * while the popover is up — so surface it here for retry guidance. */} @@ -188,7 +161,7 @@ function AskUserQuestionPopoverPanel({
+ +
- {question.description != null && question.description.length > 0 && ( -

- {question.description} -

- )} {choices.length > 0 && ( -
- {choices.map((option, index) => ( - - ))} -
+ (multiSelect ? toggleIndex(index) : submitSingle(index))} + /> )}
- + {(status === 'expired' || status === 'error') && ( + + + )} + - {status === 'expired' && ( - - - )} - {status === 'error' && ( - - - )} +
+ + ); + + if (!showPlaceholder) { + return card; + } + + /** Popover has the question: the reserved card sits hidden underneath the + * same compact in-progress row the other tools use, so the turn still + * shows the call is running. */ + return ( +
+ {card} +
+
); diff --git a/client/src/components/Chat/ask/options.tsx b/client/src/components/Chat/ask/options.tsx new file mode 100644 index 0000000000..a9e13a2ccf --- /dev/null +++ b/client/src/components/Chat/ask/options.tsx @@ -0,0 +1,76 @@ +import { Check } from 'lucide-react'; +import { cn } from '~/utils'; + +export interface AskOption { + label: string; + value: string; +} + +/** + * The single option-row list shared by every `ask_user_question` surface + * (composer popover and chat card), so the same question looks and behaves + * identically wherever it renders. Numbered badges double as the popover's + * digit shortcuts; multi-select swaps the number for a check. + */ +export default function AskOptions({ + options, + multiSelect, + checked, + selected, + locked, + onActivate, + optionRefs, + listRef, + className, +}: { + options: AskOption[]; + multiSelect: boolean; + checked: number[]; + selected?: number | null; + locked: boolean; + onActivate: (index: number) => void; + optionRefs?: React.MutableRefObject<(HTMLButtonElement | null)[]>; + listRef?: React.RefObject; + className?: string; +}) { + return ( +
+ {options.map((option, index) => { + const isChecked = multiSelect && checked.includes(index); + return ( + + ); + })} +
+ ); +} diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index a5ea0235b3..d2113da337 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -872,6 +872,8 @@ "com_ui_artifacts_subtext": "Lets the agent render React, HTML, SVG, Markdown, and Mermaid as interactive artifacts in a side panel instead of plain code blocks.", "com_ui_ascending": "Asc", "com_ui_ask_answer_error": "Your answer couldn't be sent. Try again.", + "com_ui_ask_move_to_chat": "Answer later in the chat", + "com_ui_ask_move_to_composer": "Answer from the message box", "com_ui_ask_type_below": "Or type your answer below", "com_ui_ask_type_below_only": "Type your answer below", "com_ui_ask_user": "Ask User",