diff --git a/packages/tui/src/context/data.tsx b/packages/tui/src/context/data.tsx index 1c92ec25a2..e0aaa385be 100644 --- a/packages/tui/src/context/data.tsx +++ b/packages/tui/src/context/data.tsx @@ -10,7 +10,6 @@ import type { PermissionSavedInfo, PermissionV2Request, ProviderV2Info, - QuestionV2Request, ReferenceInfo, SessionMessage, SessionMessageAssistant, @@ -55,7 +54,6 @@ type Data = { status: Record message: Record permission: Record - question: Record // Pending forms keyed by owner: a session ID or the temporary "global" elicitation sentinel. form: Record } @@ -91,7 +89,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ status: {}, message: {}, permission: {}, - question: {}, form: {}, }, project: { @@ -576,24 +573,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ ), ) break - case "question.v2.asked": - if (store.session.question[event.data.sessionID]?.some((request) => request.id === event.data.id)) break - setStore("session", "question", event.data.sessionID, [ - ...(store.session.question[event.data.sessionID] ?? []), - event.data, - ]) - break - case "question.v2.replied": - case "question.v2.rejected": - setStore( - "session", - "question", - event.data.sessionID, - (store.session.question[event.data.sessionID] ?? []).filter( - (request) => request.id !== event.data.requestID, - ), - ) - break case "form.created": if (store.session.form[event.data.form.sessionID]?.some((form) => form.id === event.data.form.id)) break setStore("session", "form", event.data.form.sessionID, [ @@ -729,14 +708,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ setStore("session", "permission", sessionID, mutable(await sdk.api.permission.list({ sessionID }))) }, }, - question: { - list(sessionID: string) { - return store.session.question[sessionID] - }, - async refresh(sessionID: string) { - setStore("session", "question", sessionID, mutable(await sdk.api.question.list({ sessionID }))) - }, - }, form: { list(sessionID: string) { return store.session.form[sessionID] diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index ac7538034e..2f69eff3dd 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -58,7 +58,6 @@ import { usePromptRef } from "../../context/prompt" import { useEpilogue } from "../../context/epilogue" import { normalizePath } from "../../util/path" import { PermissionPrompt } from "./permission" -import { QuestionPrompt } from "./question" import { FormPrompt } from "./form" import { DialogExportOptions } from "../../ui/dialog-export-options" import { sessionEpilogue } from "../../util/presentation" @@ -182,10 +181,6 @@ export function Session() { (sessionID) => data.session.permission.list(sessionID) ?? [], ) }) - const questions = createMemo(() => { - if (session()?.parentID) return [] - return [route.sessionID, ...descendantSessionIDs()].flatMap((sessionID) => data.session.question.list(sessionID) ?? []) - }) const forms = createMemo(() => { if (session()?.parentID) return [] return [ @@ -197,7 +192,7 @@ export function Session() { open: false, tab: undefined as string | undefined, }) - const disabled = createMemo(() => permissions().length > 0 || questions().length > 0 || forms().length > 0) + const disabled = createMemo(() => permissions().length > 0 || forms().length > 0) const pending = createMemo(() => { const completed = messages().findLast((x) => x.type === "assistant" && x.time.completed)?.id @@ -247,7 +242,6 @@ export function Session() { void Promise.all( sessionIDs.flatMap((sessionID) => [ data.session.permission.refresh(sessionID), - data.session.question.refresh(sessionID), data.session.form.refresh(sessionID), ]), ) @@ -260,7 +254,6 @@ export function Session() { await Promise.all([ data.session.refresh(sessionID), data.session.permission.refresh(sessionID), - data.session.question.refresh(sessionID), data.session.form.refresh(sessionID), data.session.form.refresh("global"), ]) @@ -955,9 +948,6 @@ export function Session() { 0}> - 0}> - - 0}> {(form) => } diff --git a/packages/tui/src/routes/session/question.tsx b/packages/tui/src/routes/session/question.tsx deleted file mode 100644 index 3123b0740f..0000000000 --- a/packages/tui/src/routes/session/question.tsx +++ /dev/null @@ -1,516 +0,0 @@ -import { createStore } from "solid-js/store" -import { createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js" -import { useRenderer } from "@opentui/solid" -import type { TextareaRenderable } from "@opentui/core" -import { selectedForeground, tint, useTheme } from "../../context/theme" -import type { QuestionV2Answer, QuestionV2Request } from "@opencode-ai/sdk/v2" -import { useSDK } from "../../context/sdk" -import { SplitBorder } from "../../ui/border" -import { useTuiConfig } from "../../config" -import { useBindings, useOpencodeModeStack } from "../../keymap" - -const QUESTION_MODE = "question" - -export function QuestionPrompt(props: { request: QuestionV2Request; directory?: string }) { - const sdk = useSDK() - const { theme } = useTheme() - const renderer = useRenderer() - const tuiConfig = useTuiConfig() - const modeStack = useOpencodeModeStack() - - const questions = createMemo(() => props.request.questions) - const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true) - const tabs = createMemo(() => (single() ? 1 : questions().length + 1)) - const [tabHover, setTabHover] = createSignal(null) - const [store, setStore] = createStore({ - tab: 0, - answers: [] as QuestionV2Answer[], - custom: [] as string[], - selected: 0, - editing: false, - }) - - let textarea: TextareaRenderable | undefined - - const question = createMemo(() => questions()[store.tab]) - const confirm = createMemo(() => !single() && store.tab === questions().length) - const options = createMemo(() => question()?.options ?? []) - const custom = createMemo(() => question()?.custom !== false) - const other = createMemo(() => custom() && store.selected === options().length) - const input = createMemo(() => store.custom[store.tab] ?? "") - const multi = createMemo(() => question()?.multiple === true) - const customPicked = createMemo(() => { - const value = input() - if (!value) return false - return store.answers[store.tab]?.includes(value) ?? false - }) - - function pick(answer: string, custom: boolean = false) { - const answers = [...store.answers] - answers[store.tab] = [answer] - setStore("answers", answers) - if (custom) { - const inputs = [...store.custom] - inputs[store.tab] = answer - setStore("custom", inputs) - } - if (single()) { - void sdk.api.question.reply({ - sessionID: props.request.sessionID, - requestID: props.request.id, - answers: [[answer]], - }) - return - } - setStore("tab", store.tab + 1) - setStore("selected", 0) - } - - function toggle(answer: string) { - const existing = store.answers[store.tab] ?? [] - const next = [...existing] - const index = next.indexOf(answer) - if (index === -1) next.push(answer) - if (index !== -1) next.splice(index, 1) - const answers = [...store.answers] - answers[store.tab] = next - setStore("answers", answers) - } - - function selectTab(index: number) { - setStore("tab", index) - setStore("selected", 0) - } - - function selectOption() { - if (other()) { - if (!multi()) { - setStore("editing", true) - return - } - const value = input() - if (value && customPicked()) { - toggle(value) - return - } - setStore("editing", true) - return - } - const opt = options()[store.selected] - if (!opt) return - if (multi()) { - toggle(opt.label) - return - } - pick(opt.label) - } - - onMount(() => onCleanup(modeStack.push(QUESTION_MODE))) - - useBindings(() => ({ - mode: QUESTION_MODE, - enabled: store.editing && !confirm(), - commands: [ - { - name: "prompt.clear", - title: "Clear answer edit", - category: "Question", - run() { - const text = textarea?.plainText ?? "" - if (!text) { - setStore("editing", false) - return - } - textarea?.setText("") - }, - }, - ], - bindings: [ - { - key: "escape", - desc: "Cancel answer edit", - group: "Question", - cmd: () => { - setStore("editing", false) - }, - }, - ...tuiConfig.keybinds.get("prompt.clear"), - { - key: "return", - desc: "Submit answer edit", - group: "Question", - cmd: () => { - const text = textarea?.plainText?.trim() ?? "" - const prev = store.custom[store.tab] - - if (!text) { - if (prev) { - const inputs = [...store.custom] - inputs[store.tab] = "" - setStore("custom", inputs) - - const answers = [...store.answers] - answers[store.tab] = (answers[store.tab] ?? []).filter((x) => x !== prev) - setStore("answers", answers) - } - setStore("editing", false) - return - } - - if (multi()) { - const inputs = [...store.custom] - inputs[store.tab] = text - setStore("custom", inputs) - - const existing = store.answers[store.tab] ?? [] - const next = [...existing] - if (prev) { - const index = next.indexOf(prev) - if (index !== -1) next.splice(index, 1) - } - if (!next.includes(text)) next.push(text) - const answers = [...store.answers] - answers[store.tab] = next - setStore("answers", answers) - setStore("editing", false) - return - } - - pick(text, true) - setStore("editing", false) - }, - }, - ], - })) - - useBindings(() => { - const opts = options() - const total = opts.length + (custom() ? 1 : 0) - const max = Math.min(total, 9) - - return { - mode: QUESTION_MODE, - enabled: !store.editing, - commands: [ - { - name: "app.exit", - title: "Reject question", - category: "Question", - run() { - void sdk.api.question.reject({ sessionID: props.request.sessionID, requestID: props.request.id }) - }, - }, - ], - bindings: [ - { - key: "left", - desc: "Previous question", - group: "Question", - cmd: () => selectTab((store.tab - 1 + tabs()) % tabs()), - }, - { - key: "h", - desc: "Previous question", - group: "Question", - cmd: () => selectTab((store.tab - 1 + tabs()) % tabs()), - }, - { key: "right", desc: "Next question", group: "Question", cmd: () => selectTab((store.tab + 1) % tabs()) }, - { key: "l", desc: "Next question", group: "Question", cmd: () => selectTab((store.tab + 1) % tabs()) }, - { - key: "tab", - desc: "Next question", - group: "Question", - cmd: ({ event }: { event: { shift: boolean } }) => { - selectTab((store.tab + (event.shift ? -1 : 1) + tabs()) % tabs()) - }, - }, - ...(confirm() - ? [ - { - key: "return", - desc: "Submit answer", - group: "Question", - cmd: () => { - void sdk.api.question.reply({ - sessionID: props.request.sessionID, - requestID: props.request.id, - answers: questions().map((_, i) => store.answers[i] ?? []), - }) - }, - }, - { - key: "escape", - desc: "Reject question", - group: "Question", - cmd: () => { - void sdk.api.question.reject({ sessionID: props.request.sessionID, requestID: props.request.id }) - }, - }, - ...tuiConfig.keybinds.get("app.exit"), - ] - : [ - ...Array.from({ length: max }, (_, index) => ({ - key: String(index + 1), - desc: `Select answer ${index + 1}`, - group: "Question", - cmd: () => { - setStore("selected", index) - selectOption() - }, - })), - { - key: "up", - desc: "Previous answer", - group: "Question", - cmd: () => setStore("selected", (store.selected - 1 + total) % total), - }, - { - key: "k", - desc: "Previous answer", - group: "Question", - cmd: () => setStore("selected", (store.selected - 1 + total) % total), - }, - { key: "down", desc: "Next answer", group: "Question", cmd: () => setStore("selected", (store.selected + 1) % total) }, - { key: "j", desc: "Next answer", group: "Question", cmd: () => setStore("selected", (store.selected + 1) % total) }, - { key: "return", desc: "Select answer", group: "Question", cmd: () => selectOption() }, - { - key: "escape", - desc: "Reject question", - group: "Question", - cmd: () => { - void sdk.api.question.reject({ sessionID: props.request.sessionID, requestID: props.request.id }) - }, - }, - ...tuiConfig.keybinds.get("app.exit"), - ]), - ], - } - }) - - return ( - - - - - - {(q, index) => { - const isActive = () => index() === store.tab - const isAnswered = () => { - return (store.answers[index()]?.length ?? 0) > 0 - } - return ( - setTabHover(index())} - onMouseOut={() => setTabHover(null)} - onMouseUp={() => { - if (renderer.getSelection()?.getSelectedText()) return - selectTab(index()) - }} - > - - {q.header} - - - ) - }} - - setTabHover("confirm")} - onMouseOut={() => setTabHover(null)} - onMouseUp={() => { - if (renderer.getSelection()?.getSelectedText()) return - selectTab(questions().length) - }} - > - Confirm - - - - - - - - - {question()?.question} - {multi() ? " (select all that apply)" : ""} - - - - - {(opt, i) => { - const active = () => i() === store.selected - const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false - return ( - setStore("selected", i())} - onMouseDown={() => setStore("selected", i())} - onMouseUp={() => { - if (renderer.getSelection()?.getSelectedText()) return - selectOption() - }} - > - - - - {`${i() + 1}.`} - - - - - {multi() ? `[${picked() ? "✓" : " "}] ${opt.label}` : opt.label} - - - - {picked() ? " ✓" : ""} - - - - - {opt.description} - - - ) - }} - - - setStore("selected", options().length)} - onMouseDown={() => setStore("selected", options().length)} - onMouseUp={() => { - if (renderer.getSelection()?.getSelectedText()) return - selectOption() - }} - > - - - - {`${options().length + 1}.`} - - - - - {multi() ? `[${customPicked() ? "✓" : " "}] Type your own answer` : "Type your own answer"} - - - - - {customPicked() ? " ✓" : ""} - - - - -