diff --git a/web/src/components/ChatView.jsx b/web/src/components/ChatView.jsx index 94e1634c..5655281c 100644 --- a/web/src/components/ChatView.jsx +++ b/web/src/components/ChatView.jsx @@ -5412,6 +5412,7 @@ export function ChatView({ children, sessionRef, sessionId, homeLogoEffectEnable request={questionForView} onResolve={resolveQuestion} originLabel={questionOriginLabel} + className="mx-2.5" /> ) : ( <> diff --git a/web/src/components/QuestionPicker.jsx b/web/src/components/QuestionPicker.jsx index 93e18259..1c4a3e1c 100644 --- a/web/src/components/QuestionPicker.jsx +++ b/web/src/components/QuestionPicker.jsx @@ -16,6 +16,7 @@ import { selectAnswerCustom, setAnswerCustom, toggleAnswerSelection, + unselectAnswerCustom, } from '../lib/questionPicker.js'; const READABLE_TEXT_STYLE = { overflowWrap: 'anywhere', wordBreak: 'break-word' }; @@ -53,7 +54,7 @@ async function copyText(text) { } } -export function QuestionPicker({ request, onResolve, originLabel = '' }) { +export function QuestionPicker({ request, onResolve, originLabel = '', className = '' }) { const normalized = useMemo(() => normalizeQuestionRequest(request), [request]); const { questions } = normalized; @@ -194,6 +195,15 @@ export function QuestionPicker({ request, onResolve, originLabel = '' }) { updateAnswer(currentIndex, (item) => setAnswerCustom(item, next, isMulti)); }, [currentIndex, isMulti, updateAnswer]); + const toggleCustom = useCallback(() => { + updateAnswer(currentIndex, (item) => item.customSelected + ? unselectAnswerCustom(item) + : selectAnswerCustom(item, isMulti)); + setFocusIndex(customIndex); + setEditingCustom(false); + focusSoon(rootRef); + }, [currentIndex, customIndex, isMulti, updateAnswer]); + const moveFocus = useCallback((delta) => { const count = optionCount + 1; setFocusIndex(Math.min(count - 1, Math.max(0, activeOptionIndex + delta))); @@ -340,7 +350,7 @@ export function QuestionPicker({ request, onResolve, originLabel = '' }) { tabIndex={-1} onKeyDown={onKeyDown} aria-label="AskUserQuestion" - className="mb-2 shrink min-h-0 rounded-[14px] border border-border bg-surface ace-shadow-lg outline-none overflow-hidden flex flex-col" + className={clsx('mb-2 shrink min-h-0 rounded-[14px] border border-border bg-surface ace-shadow-lg outline-none overflow-hidden flex flex-col', className)} >
@@ -406,7 +416,8 @@ export function QuestionPicker({ request, onResolve, originLabel = '' }) {
{question.options.map((opt, index) => { const selected = answer.selected?.includes(opt.value); - const focused = activeOptionIndex === index; + const focused = focusIndex === index; + const hovered = hoverIndex === index; const copied = copiedIndex === index; return (
@@ -498,27 +511,30 @@ export function QuestionPicker({ request, onResolve, originLabel = '' }) { className={clsx( 'flex items-center gap-3 rounded-lg px-3 py-2.5 transition', customActive - ? 'bg-accent-bg border border-accent' + ? 'bg-accent-bg border border-transparent' : focusIndex === customIndex - ? 'bg-accent-bg border border-accent' + ? 'bg-accent-bg border border-transparent' : 'border border-transparent hover:bg-accent-bg', )} > - {customActive ? ( ) : ( {customIndex + 1} )} - + { + const picker = harness([q1]); + input(picker.render()).props.onChange({ target: { value: 'retained draft' } }); + button(picker.render(), '取消自定义答案').props.onClick(); + const draftInput = input(picker.render()); + assert.match(draftInput.props.className, /text-fg-mute/); + assert.equal(draftInput.props.value, 'retained draft'); + key(picker, 'Enter', { ctrlKey: true }); + assert.equal(picker.sent[0].answers[0].custom_text, undefined); + picker.unmount(); +}); + run('refocusing a retained multi-select custom draft reactivates it', () => { const picker = harness([q1]); input(picker.render()).props.onChange({ target: { value: 'retained draft' } }); - key(picker, 'Escape'); + button(picker.render(), '取消自定义答案').props.onClick(); input(picker.render()).props.onFocus(); key(picker, 'Enter', { ctrlKey: true }); assert.equal(picker.sent[0].answers[0].custom_text, 'retained draft'); diff --git a/web/src/lib/questionPickerLayout.test.js b/web/src/lib/questionPickerLayout.test.js index 97b1d8fe..3a3da683 100644 --- a/web/src/lib/questionPickerLayout.test.js +++ b/web/src/lib/questionPickerLayout.test.js @@ -7,7 +7,7 @@ const srcRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const picker = fs.readFileSync(path.join(srcRoot, 'components/QuestionPicker.jsx'), 'utf8'); const chatView = fs.readFileSync(path.join(srcRoot, 'components/ChatView.jsx'), 'utf8'); const toolBlock = fs.readFileSync(path.join(srcRoot, 'components/ToolBlock.jsx'), 'utf8'); -const rootClasses = picker.match(/className="([^"]*border-border[^\"]*overflow-hidden[^\"]*)"/)?.[1] || ''; +const rootClasses = picker.match(/clsx\('([^']*rounded-\[14px\][^']*)'/)?.[1] || ''; assert.ok(rootClasses, 'QuestionPicker root classes must be discoverable'); assert.match(rootClasses, /rounded-\[14px\]/); @@ -32,8 +32,9 @@ const dockEnd = chatView.indexOf('\s*