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*