Compare commits

..

2 Commits

Author SHA1 Message Date
Shantur Rathore
abe96a7a8b fix(ui): keep submit disabled for empty custom answer 2026-01-28 15:32:12 +00:00
bizzkoot
01921e3454 fix(ui): improve question tool UX (enter key & autofocus) 2026-01-28 21:01:49 +08:00

View File

@@ -1,4 +1,4 @@
import { createMemo, Show, For, type Accessor } from "solid-js" import { createMemo, Show, For, createEffect, type Accessor } from "solid-js"
import type { ToolState } from "@opencode-ai/sdk" import type { ToolState } from "@opencode-ai/sdk"
import type { QuestionRequest } from "@opencode-ai/sdk/v2" import type { QuestionRequest } from "@opencode-ai/sdk/v2"
import { useI18n } from "../../lib/i18n" import { useI18n } from "../../lib/i18n"
@@ -28,6 +28,13 @@ export type QuestionToolBlockProps = {
export function QuestionToolBlock(props: QuestionToolBlockProps) { export function QuestionToolBlock(props: QuestionToolBlockProps) {
const { t } = useI18n() const { t } = useI18n()
let firstInputRef: HTMLInputElement | undefined
createEffect(() => {
if (props.active() && firstInputRef) {
firstInputRef.focus()
}
})
const requestId = createMemo(() => { const requestId = createMemo(() => {
const state = props.toolState() const state = props.toolState()
@@ -105,7 +112,6 @@ export function QuestionToolBlock(props: QuestionToolBlockProps) {
if (!props.active()) return if (!props.active()) return
const rawValue = input?.value ?? "" const rawValue = input?.value ?? ""
const value = rawValue const value = rawValue
if (value.trim().length === 0) return
const info = questions()[questionIndex] const info = questions()[questionIndex]
const multi = info?.multiple === true const multi = info?.multiple === true
@@ -114,6 +120,19 @@ export function QuestionToolBlock(props: QuestionToolBlockProps) {
updateAnswer(questionIndex, []) updateAnswer(questionIndex, [])
} }
// If the custom field is empty, treat it as unanswered.
// This prevents submitting a previously selected option when the user
// has explicitly switched focus to the custom input.
if (value.trim().length === 0) return
if (multi) {
const existing = answers()[questionIndex] ?? []
if (!existing.includes(value)) {
updateAnswer(questionIndex, [...existing, value])
}
return
}
toggleOption(questionIndex, value) toggleOption(questionIndex, value)
} }
@@ -206,7 +225,7 @@ export function QuestionToolBlock(props: QuestionToolBlockProps) {
<div class="mt-3 flex flex-col gap-1"> <div class="mt-3 flex flex-col gap-1">
<For each={q?.options ?? []}> <For each={q?.options ?? []}>
{(opt) => { {(opt, optIndex) => {
const checked = () => selected().includes(opt.label) const checked = () => selected().includes(opt.label)
return ( return (
<label <label
@@ -214,6 +233,9 @@ export function QuestionToolBlock(props: QuestionToolBlockProps) {
title={opt.description} title={opt.description}
> >
<input <input
ref={(el) => {
if (i() === 0 && optIndex() === 0) firstInputRef = el
}}
type={inputType()} type={inputType()}
name={groupName()} name={groupName()}
checked={checked()} checked={checked()}
@@ -234,6 +256,9 @@ export function QuestionToolBlock(props: QuestionToolBlockProps) {
title={t("toolCall.question.custom.title")} title={t("toolCall.question.custom.title")}
> >
<input <input
ref={(el) => {
if (i() === 0 && (q?.options?.length ?? 0) === 0) firstInputRef = el
}}
type={inputType()} type={inputType()}
name={groupName()} name={groupName()}
checked={customChecked()} checked={customChecked()}
@@ -266,6 +291,16 @@ export function QuestionToolBlock(props: QuestionToolBlockProps) {
toggleFromCustomInput(i(), e.currentTarget) toggleFromCustomInput(i(), e.currentTarget)
}} }}
onInput={(e) => handleCustomTyping(i(), e.currentTarget)} onInput={(e) => handleCustomTyping(i(), e.currentTarget)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.isComposing) {
// Don't submit if the custom field is empty (common when switching to it).
if (e.currentTarget.value.trim().length === 0) return
e.preventDefault()
if (!submitDisabled()) {
props.onSubmit()
}
}
}}
/> />
</div> </div>
</label> </label>