fix(app): hide prompt input when there are perms or questions (#12339)
This commit is contained in:
295
packages/app/src/components/question-dock.tsx
Normal file
295
packages/app/src/components/question-dock.tsx
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
import { For, Show, createMemo, type Component } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
|
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useSDK } from "@/context/sdk"
|
||||||
|
|
||||||
|
export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => {
|
||||||
|
const sdk = useSDK()
|
||||||
|
const language = useLanguage()
|
||||||
|
|
||||||
|
const questions = createMemo(() => props.request.questions)
|
||||||
|
const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true)
|
||||||
|
|
||||||
|
const [store, setStore] = createStore({
|
||||||
|
tab: 0,
|
||||||
|
answers: [] as QuestionAnswer[],
|
||||||
|
custom: [] as string[],
|
||||||
|
editing: false,
|
||||||
|
sending: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const question = createMemo(() => questions()[store.tab])
|
||||||
|
const confirm = createMemo(() => !single() && store.tab === questions().length)
|
||||||
|
const options = createMemo(() => question()?.options ?? [])
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
const fail = (err: unknown) => {
|
||||||
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||||
|
}
|
||||||
|
|
||||||
|
const reply = (answers: QuestionAnswer[]) => {
|
||||||
|
if (store.sending) return
|
||||||
|
|
||||||
|
setStore("sending", true)
|
||||||
|
sdk.client.question
|
||||||
|
.reply({ requestID: props.request.id, answers })
|
||||||
|
.catch(fail)
|
||||||
|
.finally(() => setStore("sending", false))
|
||||||
|
}
|
||||||
|
|
||||||
|
const reject = () => {
|
||||||
|
if (store.sending) return
|
||||||
|
|
||||||
|
setStore("sending", true)
|
||||||
|
sdk.client.question
|
||||||
|
.reject({ requestID: props.request.id })
|
||||||
|
.catch(fail)
|
||||||
|
.finally(() => setStore("sending", false))
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
reply(questions().map((_, i) => store.answers[i] ?? []))
|
||||||
|
}
|
||||||
|
|
||||||
|
const 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()) {
|
||||||
|
reply([[answer]])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStore("tab", store.tab + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectTab = (index: number) => {
|
||||||
|
setStore("tab", index)
|
||||||
|
setStore("editing", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectOption = (optIndex: number) => {
|
||||||
|
if (store.sending) return
|
||||||
|
|
||||||
|
if (optIndex === options().length) {
|
||||||
|
setStore("editing", true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const opt = options()[optIndex]
|
||||||
|
if (!opt) return
|
||||||
|
if (multi()) {
|
||||||
|
toggle(opt.label)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pick(opt.label)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCustomSubmit = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (store.sending) return
|
||||||
|
|
||||||
|
const value = input().trim()
|
||||||
|
if (!value) {
|
||||||
|
setStore("editing", false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multi()) {
|
||||||
|
const existing = store.answers[store.tab] ?? []
|
||||||
|
const next = [...existing]
|
||||||
|
if (!next.includes(value)) next.push(value)
|
||||||
|
|
||||||
|
const answers = [...store.answers]
|
||||||
|
answers[store.tab] = next
|
||||||
|
setStore("answers", answers)
|
||||||
|
setStore("editing", false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pick(value, true)
|
||||||
|
setStore("editing", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-component="question-prompt">
|
||||||
|
<Show when={!single()}>
|
||||||
|
<div data-slot="question-tabs">
|
||||||
|
<For each={questions()}>
|
||||||
|
{(q, index) => {
|
||||||
|
const active = () => index() === store.tab
|
||||||
|
const answered = () => (store.answers[index()]?.length ?? 0) > 0
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
data-slot="question-tab"
|
||||||
|
data-active={active()}
|
||||||
|
data-answered={answered()}
|
||||||
|
disabled={store.sending}
|
||||||
|
onClick={() => selectTab(index())}
|
||||||
|
>
|
||||||
|
{q.header}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
<button
|
||||||
|
data-slot="question-tab"
|
||||||
|
data-active={confirm()}
|
||||||
|
disabled={store.sending}
|
||||||
|
onClick={() => selectTab(questions().length)}
|
||||||
|
>
|
||||||
|
{language.t("ui.common.confirm")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!confirm()}>
|
||||||
|
<div data-slot="question-content">
|
||||||
|
<div data-slot="question-text">
|
||||||
|
{question()?.question}
|
||||||
|
{multi() ? " " + language.t("ui.question.multiHint") : ""}
|
||||||
|
</div>
|
||||||
|
<div data-slot="question-options">
|
||||||
|
<For each={options()}>
|
||||||
|
{(opt, i) => {
|
||||||
|
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
data-slot="question-option"
|
||||||
|
data-picked={picked()}
|
||||||
|
disabled={store.sending}
|
||||||
|
onClick={() => selectOption(i())}
|
||||||
|
>
|
||||||
|
<span data-slot="option-label">{opt.label}</span>
|
||||||
|
<Show when={opt.description}>
|
||||||
|
<span data-slot="option-description">{opt.description}</span>
|
||||||
|
</Show>
|
||||||
|
<Show when={picked()}>
|
||||||
|
<Icon name="check-small" size="normal" />
|
||||||
|
</Show>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
<button
|
||||||
|
data-slot="question-option"
|
||||||
|
data-picked={customPicked()}
|
||||||
|
disabled={store.sending}
|
||||||
|
onClick={() => selectOption(options().length)}
|
||||||
|
>
|
||||||
|
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
|
||||||
|
<Show when={!store.editing && input()}>
|
||||||
|
<span data-slot="option-description">{input()}</span>
|
||||||
|
</Show>
|
||||||
|
<Show when={customPicked()}>
|
||||||
|
<Icon name="check-small" size="normal" />
|
||||||
|
</Show>
|
||||||
|
</button>
|
||||||
|
<Show when={store.editing}>
|
||||||
|
<form data-slot="custom-input-form" onSubmit={handleCustomSubmit}>
|
||||||
|
<input
|
||||||
|
ref={(el) => setTimeout(() => el.focus(), 0)}
|
||||||
|
type="text"
|
||||||
|
data-slot="custom-input"
|
||||||
|
placeholder={language.t("ui.question.custom.placeholder")}
|
||||||
|
value={input()}
|
||||||
|
disabled={store.sending}
|
||||||
|
onInput={(e) => {
|
||||||
|
const inputs = [...store.custom]
|
||||||
|
inputs[store.tab] = e.currentTarget.value
|
||||||
|
setStore("custom", inputs)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type="submit" variant="primary" size="small" disabled={store.sending}>
|
||||||
|
{multi() ? language.t("ui.common.add") : language.t("ui.common.submit")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="small"
|
||||||
|
disabled={store.sending}
|
||||||
|
onClick={() => setStore("editing", false)}
|
||||||
|
>
|
||||||
|
{language.t("ui.common.cancel")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={confirm()}>
|
||||||
|
<div data-slot="question-review">
|
||||||
|
<div data-slot="review-title">{language.t("ui.messagePart.review.title")}</div>
|
||||||
|
<For each={questions()}>
|
||||||
|
{(q, index) => {
|
||||||
|
const value = () => store.answers[index()]?.join(", ") ?? ""
|
||||||
|
const answered = () => Boolean(value())
|
||||||
|
return (
|
||||||
|
<div data-slot="review-item">
|
||||||
|
<span data-slot="review-label">{q.question}</span>
|
||||||
|
<span data-slot="review-value" data-answered={answered()}>
|
||||||
|
{answered() ? value() : language.t("ui.question.review.notAnswered")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<div data-slot="question-actions">
|
||||||
|
<Button variant="ghost" size="small" onClick={reject} disabled={store.sending}>
|
||||||
|
{language.t("ui.common.dismiss")}
|
||||||
|
</Button>
|
||||||
|
<Show when={!single()}>
|
||||||
|
<Show when={confirm()}>
|
||||||
|
<Button variant="primary" size="small" onClick={submit} disabled={store.sending}>
|
||||||
|
{language.t("ui.common.submit")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
<Show when={!confirm() && multi()}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => selectTab(store.tab + 1)}
|
||||||
|
disabled={store.sending || (store.answers[store.tab]?.length ?? 0) === 0}
|
||||||
|
>
|
||||||
|
{language.t("ui.common.next")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ import { BasicTool } from "@opencode-ai/ui/basic-tool"
|
|||||||
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
||||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||||
import { Mark } from "@opencode-ai/ui/logo"
|
import { Mark } from "@opencode-ai/ui/logo"
|
||||||
|
import { QuestionDock } from "@/components/question-dock"
|
||||||
|
|
||||||
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
|
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
|
||||||
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
||||||
@@ -270,15 +271,20 @@ export default function Page() {
|
|||||||
const comments = useComments()
|
const comments = useComments()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
|
|
||||||
const request = createMemo(() => {
|
const permRequest = createMemo(() => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
const next = sync.data.permission[sessionID]?.[0]
|
return sync.data.permission[sessionID]?.[0]
|
||||||
if (!next) return
|
|
||||||
if (next.tool) return
|
|
||||||
return next
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const questionRequest = createMemo(() => {
|
||||||
|
const sessionID = params.id
|
||||||
|
if (!sessionID) return
|
||||||
|
return sync.data.question[sessionID]?.[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
const blocked = createMemo(() => !!permRequest() || !!questionRequest())
|
||||||
|
|
||||||
const [ui, setUi] = createStore({
|
const [ui, setUi] = createStore({
|
||||||
responding: false,
|
responding: false,
|
||||||
pendingMessage: undefined as string | undefined,
|
pendingMessage: undefined as string | undefined,
|
||||||
@@ -292,14 +298,14 @@ export default function Page() {
|
|||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => request()?.id,
|
() => permRequest()?.id,
|
||||||
() => setUi("responding", false),
|
() => setUi("responding", false),
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const decide = (response: "once" | "always" | "reject") => {
|
const decide = (response: "once" | "always" | "reject") => {
|
||||||
const perm = request()
|
const perm = permRequest()
|
||||||
if (!perm) return
|
if (!perm) return
|
||||||
if (ui.responding) return
|
if (ui.responding) return
|
||||||
|
|
||||||
@@ -1351,6 +1357,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) {
|
if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) {
|
||||||
|
if (blocked()) return
|
||||||
inputRef?.focus()
|
inputRef?.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2693,7 +2700,31 @@ export default function Page() {
|
|||||||
"md:max-w-200 3xl:max-w-[1200px] 4xl:max-w-[1600px] 5xl:max-w-[1900px]": centered(),
|
"md:max-w-200 3xl:max-w-[1200px] 4xl:max-w-[1600px] 5xl:max-w-[1900px]": centered(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={request()} keyed>
|
<Show when={questionRequest()} keyed>
|
||||||
|
{(req) => {
|
||||||
|
const count = req.questions.length
|
||||||
|
const subtitle =
|
||||||
|
count === 0
|
||||||
|
? ""
|
||||||
|
: `${count} ${language.t(count > 1 ? "ui.common.question.other" : "ui.common.question.one")}`
|
||||||
|
return (
|
||||||
|
<div data-component="tool-part-wrapper" data-question="true" class="mb-3">
|
||||||
|
<BasicTool
|
||||||
|
icon="bubble-5"
|
||||||
|
locked
|
||||||
|
defaultOpen
|
||||||
|
trigger={{
|
||||||
|
title: language.t("ui.tool.questions"),
|
||||||
|
subtitle,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<QuestionDock request={req} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={permRequest()} keyed>
|
||||||
{(perm) => (
|
{(perm) => (
|
||||||
<div data-component="tool-part-wrapper" data-permission="true" class="mb-3">
|
<div data-component="tool-part-wrapper" data-permission="true" class="mb-3">
|
||||||
<BasicTool
|
<BasicTool
|
||||||
@@ -2743,25 +2774,27 @@ export default function Page() {
|
|||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show
|
<Show when={!blocked()}>
|
||||||
when={prompt.ready()}
|
<Show
|
||||||
fallback={
|
when={prompt.ready()}
|
||||||
<div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none">
|
fallback={
|
||||||
{handoff.session.get(sessionKey())?.prompt || language.t("prompt.loading")}
|
<div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none">
|
||||||
</div>
|
{handoff.session.get(sessionKey())?.prompt || language.t("prompt.loading")}
|
||||||
}
|
</div>
|
||||||
>
|
}
|
||||||
<PromptInput
|
>
|
||||||
ref={(el) => {
|
<PromptInput
|
||||||
inputRef = el
|
ref={(el) => {
|
||||||
}}
|
inputRef = el
|
||||||
newSessionWorktree={newSessionWorktree()}
|
}}
|
||||||
onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")}
|
newSessionWorktree={newSessionWorktree()}
|
||||||
onSubmit={() => {
|
onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")}
|
||||||
comments.clear()
|
onSubmit={() => {
|
||||||
resumeScroll()
|
comments.clear()
|
||||||
}}
|
resumeScroll()
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { useData } from "../context"
|
import { useData } from "../context"
|
||||||
import { type UiI18nKey, type UiI18nParams, useI18n } from "../context/i18n"
|
import { type UiI18nKey, type UiI18nParams, useI18n } from "../context/i18n"
|
||||||
import { findLast } from "@opencode-ai/util/array"
|
|
||||||
|
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { createEffect, createMemo, createSignal, For, Match, on, onCleanup, ParentProps, Show, Switch } from "solid-js"
|
import { createEffect, createMemo, createSignal, For, Match, on, onCleanup, ParentProps, Show, Switch } from "solid-js"
|
||||||
@@ -84,6 +83,7 @@ function AssistantMessageItem(props: {
|
|||||||
responsePartId: string | undefined
|
responsePartId: string | undefined
|
||||||
hideResponsePart: boolean
|
hideResponsePart: boolean
|
||||||
hideReasoning: boolean
|
hideReasoning: boolean
|
||||||
|
hidden?: () => readonly { messageID: string; callID: string }[]
|
||||||
}) {
|
}) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const emptyParts: PartType[] = []
|
const emptyParts: PartType[] = []
|
||||||
@@ -104,13 +104,22 @@ function AssistantMessageItem(props: {
|
|||||||
parts = parts.filter((part) => part?.type !== "reasoning")
|
parts = parts.filter((part) => part?.type !== "reasoning")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.hideResponsePart) return parts
|
if (props.hideResponsePart) {
|
||||||
|
const responsePartId = props.responsePartId
|
||||||
|
if (responsePartId && responsePartId === lastTextPart()?.id) {
|
||||||
|
parts = parts.filter((part) => part?.id !== responsePartId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const responsePartId = props.responsePartId
|
const hidden = props.hidden?.() ?? []
|
||||||
if (!responsePartId) return parts
|
if (hidden.length === 0) return parts
|
||||||
if (responsePartId !== lastTextPart()?.id) return parts
|
|
||||||
|
|
||||||
return parts.filter((part) => part?.id !== responsePartId)
|
const id = props.message.id
|
||||||
|
return parts.filter((part) => {
|
||||||
|
if (part?.type !== "tool") return true
|
||||||
|
const tool = part as ToolPart
|
||||||
|
return !hidden.some((h) => h.messageID === id && h.callID === tool.callID)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return <Message message={props.message} parts={filteredParts()} />
|
return <Message message={props.message} parts={filteredParts()} />
|
||||||
@@ -140,7 +149,6 @@ export function SessionTurn(
|
|||||||
const emptyFiles: FilePart[] = []
|
const emptyFiles: FilePart[] = []
|
||||||
const emptyAssistant: AssistantMessage[] = []
|
const emptyAssistant: AssistantMessage[] = []
|
||||||
const emptyPermissions: PermissionRequest[] = []
|
const emptyPermissions: PermissionRequest[] = []
|
||||||
const emptyPermissionParts: { part: ToolPart; message: AssistantMessage }[] = []
|
|
||||||
const emptyQuestions: QuestionRequest[] = []
|
const emptyQuestions: QuestionRequest[] = []
|
||||||
const emptyQuestionParts: { part: ToolPart; message: AssistantMessage }[] = []
|
const emptyQuestionParts: { part: ToolPart; message: AssistantMessage }[] = []
|
||||||
const idle = { type: "idle" as const }
|
const idle = { type: "idle" as const }
|
||||||
@@ -253,48 +261,18 @@ export function SessionTurn(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const permissions = createMemo(() => data.store.permission?.[props.sessionID] ?? emptyPermissions)
|
const permissions = createMemo(() => data.store.permission?.[props.sessionID] ?? emptyPermissions)
|
||||||
const permissionCount = createMemo(() => permissions().length)
|
|
||||||
const nextPermission = createMemo(() => permissions()[0])
|
const nextPermission = createMemo(() => permissions()[0])
|
||||||
|
|
||||||
const permissionParts = createMemo(() => {
|
|
||||||
if (props.stepsExpanded) return emptyPermissionParts
|
|
||||||
|
|
||||||
const next = nextPermission()
|
|
||||||
if (!next || !next.tool) return emptyPermissionParts
|
|
||||||
|
|
||||||
const message = findLast(assistantMessages(), (m) => m.id === next.tool!.messageID)
|
|
||||||
if (!message) return emptyPermissionParts
|
|
||||||
|
|
||||||
const parts = data.store.part[message.id] ?? emptyParts
|
|
||||||
for (const part of parts) {
|
|
||||||
if (part?.type !== "tool") continue
|
|
||||||
const tool = part as ToolPart
|
|
||||||
if (tool.callID === next.tool?.callID) return [{ part: tool, message }]
|
|
||||||
}
|
|
||||||
|
|
||||||
return emptyPermissionParts
|
|
||||||
})
|
|
||||||
|
|
||||||
const questions = createMemo(() => data.store.question?.[props.sessionID] ?? emptyQuestions)
|
const questions = createMemo(() => data.store.question?.[props.sessionID] ?? emptyQuestions)
|
||||||
const nextQuestion = createMemo(() => questions()[0])
|
const nextQuestion = createMemo(() => questions()[0])
|
||||||
|
|
||||||
const questionParts = createMemo(() => {
|
const hidden = createMemo(() => {
|
||||||
if (props.stepsExpanded) return emptyQuestionParts
|
const out: { messageID: string; callID: string }[] = []
|
||||||
|
const perm = nextPermission()
|
||||||
const next = nextQuestion()
|
if (perm?.tool) out.push(perm.tool)
|
||||||
if (!next || !next.tool) return emptyQuestionParts
|
const question = nextQuestion()
|
||||||
|
if (question?.tool) out.push(question.tool)
|
||||||
const message = findLast(assistantMessages(), (m) => m.id === next.tool!.messageID)
|
return out
|
||||||
if (!message) return emptyQuestionParts
|
|
||||||
|
|
||||||
const parts = data.store.part[message.id] ?? emptyParts
|
|
||||||
for (const part of parts) {
|
|
||||||
if (part?.type !== "tool") continue
|
|
||||||
const tool = part as ToolPart
|
|
||||||
if (tool.callID === next.tool?.callID) return [{ part: tool, message }]
|
|
||||||
}
|
|
||||||
|
|
||||||
return emptyQuestionParts
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const answeredQuestionParts = createMemo(() => {
|
const answeredQuestionParts = createMemo(() => {
|
||||||
@@ -499,14 +477,6 @@ export function SessionTurn(
|
|||||||
onCleanup(() => clearInterval(timer))
|
onCleanup(() => clearInterval(timer))
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(permissionCount, (count, prev) => {
|
|
||||||
if (!count) return
|
|
||||||
if (prev !== undefined && count <= prev) return
|
|
||||||
autoScroll.forceScrollToBottom()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
let lastStatusChange = Date.now()
|
let lastStatusChange = Date.now()
|
||||||
let statusTimeout: number | undefined
|
let statusTimeout: number | undefined
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -664,6 +634,7 @@ export function SessionTurn(
|
|||||||
responsePartId={responsePartId()}
|
responsePartId={responsePartId()}
|
||||||
hideResponsePart={hideResponsePart()}
|
hideResponsePart={hideResponsePart()}
|
||||||
hideReasoning={!working()}
|
hideReasoning={!working()}
|
||||||
|
hidden={hidden}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -674,20 +645,6 @@ export function SessionTurn(
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!props.stepsExpanded && permissionParts().length > 0}>
|
|
||||||
<div data-slot="session-turn-permission-parts">
|
|
||||||
<For each={permissionParts()}>
|
|
||||||
{({ part, message }) => <Part part={part} message={message} />}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<Show when={!props.stepsExpanded && questionParts().length > 0}>
|
|
||||||
<div data-slot="session-turn-question-parts">
|
|
||||||
<For each={questionParts()}>
|
|
||||||
{({ part, message }) => <Part part={part} message={message} />}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<Show when={!props.stepsExpanded && answeredQuestionParts().length > 0}>
|
<Show when={!props.stepsExpanded && answeredQuestionParts().length > 0}>
|
||||||
<div data-slot="session-turn-answered-question-parts">
|
<div data-slot="session-turn-answered-question-parts">
|
||||||
<For each={answeredQuestionParts()}>
|
<For each={answeredQuestionParts()}>
|
||||||
|
|||||||
Reference in New Issue
Block a user