mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 14:43:37 -04:00
fix(tui): show encrypted reasoning status
This commit is contained in:
@@ -1629,10 +1629,11 @@ function SessionReasoningGroupView(props: {
|
||||
const message = props.message(ref.messageID)
|
||||
if (message?.type !== "assistant") return []
|
||||
const part = resolvePart(message, ref.partID)
|
||||
if (part?.type !== "reasoning" || !reasoningContent(part)) return []
|
||||
if (part?.type !== "reasoning" || (!reasoningContent(part) && !part.state)) return []
|
||||
return [{ message, part }]
|
||||
}),
|
||||
)
|
||||
const opaque = createMemo(() => parts().length > 0 && parts().every((item) => !reasoningContent(item.part)))
|
||||
const latest = createMemo((previous: string | null) => {
|
||||
const item = parts().at(-1)
|
||||
if (!item) return previous
|
||||
@@ -1657,7 +1658,7 @@ function SessionReasoningGroupView(props: {
|
||||
>
|
||||
<box flexDirection="column" flexShrink={0}>
|
||||
<InlineToolRow
|
||||
icon={expanded() ? "-" : "+"}
|
||||
icon={opaque() ? "" : expanded() ? "-" : "+"}
|
||||
color={
|
||||
!props.completed
|
||||
? theme.text.default
|
||||
@@ -1673,19 +1674,29 @@ function SessionReasoningGroupView(props: {
|
||||
complete={props.completed}
|
||||
pending={latest() ? `Thinking: ${latest()}` : "Thinking"}
|
||||
spinner={!props.completed}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOver={() => !opaque() && setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onMouseUp={() => {
|
||||
if (renderer.getSelection()?.getSelectedText()) return
|
||||
if (renderer.getSelection()?.getSelectedText() || opaque()) return
|
||||
setExpanded((value) => !value)
|
||||
}}
|
||||
>
|
||||
{props.completed ? "Thought" : latest() ? `Thinking: ${latest()}` : "Thinking"}
|
||||
<Show when={props.completed && !expanded() && latest()}>: {latest()}</Show>
|
||||
<Show when={props.completed && parts().length > 1}> · {parts().length} steps</Show>
|
||||
<Show when={props.completed && duration()}> · {Locale.duration(duration())}</Show>
|
||||
<Show
|
||||
when={opaque() && props.completed}
|
||||
fallback={
|
||||
<>
|
||||
{props.completed ? "Thought" : latest() ? `Thinking: ${latest()}` : "Thinking"}
|
||||
<Show when={props.completed && !expanded() && latest()}>: {latest()}</Show>
|
||||
<Show when={props.completed && parts().length > 1}> · {parts().length} steps</Show>
|
||||
<Show when={props.completed && duration()}> · {Locale.duration(duration())}</Show>
|
||||
</>
|
||||
}
|
||||
>
|
||||
Thought
|
||||
<Show when={duration()}> · {Locale.duration(duration())}</Show> · encrypted
|
||||
</Show>
|
||||
</InlineToolRow>
|
||||
<Show when={expanded()}>
|
||||
<Show when={expanded() && !opaque()}>
|
||||
<box paddingLeft={3}>
|
||||
<For each={props.refs}>
|
||||
{(ref) => {
|
||||
@@ -2296,6 +2307,7 @@ function ReasoningPart(props: {
|
||||
const [expanded, setExpanded] = createSignal(false)
|
||||
|
||||
const content = createMemo(() => reasoningContent(props.part))
|
||||
const opaque = createMemo(() => !content() && Boolean(props.part.state))
|
||||
const isDone = createMemo(
|
||||
() => props.part.time?.completed !== undefined || props.message.time.completed !== undefined,
|
||||
)
|
||||
@@ -2307,12 +2319,12 @@ function ReasoningPart(props: {
|
||||
})
|
||||
const summary = createMemo(() => reasoningSummary(content()))
|
||||
const toggle = () => {
|
||||
if (!inMinimal()) return
|
||||
if (!inMinimal() || opaque()) return
|
||||
setExpanded((prev) => !prev)
|
||||
}
|
||||
|
||||
return (
|
||||
<Show when={content()}>
|
||||
<Show when={content() || opaque()}>
|
||||
<box paddingLeft={3} flexDirection="column" flexShrink={0}>
|
||||
<box
|
||||
border={!inMinimal() || expanded() ? ["left"] : undefined}
|
||||
@@ -2322,15 +2334,16 @@ function ReasoningPart(props: {
|
||||
>
|
||||
<box onMouseUp={toggle}>
|
||||
<ReasoningHeader
|
||||
toggleable={inMinimal()}
|
||||
toggleable={inMinimal() && !opaque()}
|
||||
open={!inMinimal() || expanded()}
|
||||
done={isDone()}
|
||||
title={inMinimal() && !expanded() ? summary().title : null}
|
||||
duration={isDone() ? Locale.duration(duration()) : undefined}
|
||||
encrypted={opaque()}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
<Show when={!inMinimal() || expanded()}>
|
||||
<Show when={!opaque() && (!inMinimal() || expanded())}>
|
||||
<box marginTop={1}>
|
||||
<box
|
||||
border={["left"]}
|
||||
@@ -2366,6 +2379,7 @@ function ReasoningHeader(props: {
|
||||
done: boolean
|
||||
title: string | null
|
||||
duration?: string
|
||||
encrypted?: boolean
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const fg = () =>
|
||||
@@ -2387,21 +2401,34 @@ function ReasoningHeader(props: {
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<text fg={fg()} wrapMode="none">
|
||||
<Show when={props.toggleable}>
|
||||
<span>{props.open ? "- " : "+ "}</span>
|
||||
</Show>
|
||||
<span>Thought</span>
|
||||
<Show when={props.title || props.duration}>
|
||||
<span>: </span>
|
||||
</Show>
|
||||
<Show when={props.title}>
|
||||
<span>{props.title}</span>
|
||||
</Show>
|
||||
<Show when={props.duration}>
|
||||
<span>
|
||||
{props.title ? " · " : ""}
|
||||
{props.duration}
|
||||
</span>
|
||||
<Show
|
||||
when={props.encrypted}
|
||||
fallback={
|
||||
<>
|
||||
<Show when={props.toggleable}>
|
||||
<span>{props.open ? "- " : "+ "}</span>
|
||||
</Show>
|
||||
<span>Thought</span>
|
||||
<Show when={props.title || props.duration}>
|
||||
<span>: </span>
|
||||
</Show>
|
||||
<Show when={props.title}>
|
||||
<span>{props.title}</span>
|
||||
</Show>
|
||||
<Show when={props.duration}>
|
||||
<span>
|
||||
{props.title ? " · " : ""}
|
||||
{props.duration}
|
||||
</span>
|
||||
</Show>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<span>Thought</span>
|
||||
<Show when={props.duration}>
|
||||
<span> · {props.duration}</span>
|
||||
</Show>
|
||||
<span> · encrypted</span>
|
||||
</Show>
|
||||
</text>
|
||||
</Match>
|
||||
|
||||
@@ -239,6 +239,13 @@ export function createSessionRows(sessionID: Accessor<string>, onSynced?: (sessi
|
||||
if (event.data.sessionID === sessionID() && event.data.text.trim())
|
||||
appendPart({ messageID: event.data.assistantMessageID, partID: `text:${event.data.ordinal}` }, { type: "text" })
|
||||
}),
|
||||
data.on("session.reasoning.started", (event) => {
|
||||
if (event.data.sessionID === sessionID() && event.data.state)
|
||||
appendPart(
|
||||
{ messageID: event.data.assistantMessageID, partID: `reasoning:${event.data.ordinal}` },
|
||||
{ type: "reasoning" },
|
||||
)
|
||||
}),
|
||||
data.on("session.reasoning.delta", (event) => {
|
||||
if (event.data.sessionID === sessionID() && event.data.delta.trim())
|
||||
appendPart(
|
||||
@@ -305,7 +312,12 @@ export function reduceSessionRows(messages: SessionMessageInfo[], inputs = new S
|
||||
const ordinals = { text: 0, reasoning: 0 }
|
||||
message.content.forEach((part) => {
|
||||
const partID = part.type === "tool" ? part.id : `${part.type}:${ordinals[part.type]++}`
|
||||
if ((part.type === "text" || part.type === "reasoning") && !part.text.trim()) return
|
||||
if (
|
||||
(part.type === "text" || part.type === "reasoning") &&
|
||||
!part.text.trim() &&
|
||||
!(part.type === "reasoning" && part.state)
|
||||
)
|
||||
return
|
||||
append(rows, { messageID: message.id, partID }, part)
|
||||
})
|
||||
const terminal = (message.finish && !["tool-calls", "unknown"].includes(message.finish)) || message.error
|
||||
|
||||
@@ -266,6 +266,28 @@ test("groups across empty assistant reasoning parts", () => {
|
||||
])
|
||||
})
|
||||
|
||||
test("keeps empty reasoning with provider state", () => {
|
||||
const message = assistant("assistant-1", [
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
state: { reasoningEncryptedContent: "opaque" },
|
||||
time: { created: 1_000, completed: 4_200 },
|
||||
},
|
||||
])
|
||||
message.finish = "stop"
|
||||
|
||||
expect(reduceSessionRows([message])).toEqual([
|
||||
{
|
||||
type: "group",
|
||||
kind: "reasoning",
|
||||
completed: true,
|
||||
refs: [{ messageID: "assistant-1", partID: "reasoning:0" }],
|
||||
},
|
||||
{ type: "assistant-footer", messageID: "assistant-1" },
|
||||
])
|
||||
})
|
||||
|
||||
test("completes exploration groups when another row follows", () => {
|
||||
const finished = assistant("assistant-2", [
|
||||
{ type: "tool", id: "grep-1", name: "grep", state: pending(), time: { created: 3 } },
|
||||
|
||||
Reference in New Issue
Block a user