feat: thinking blocks rendered in tui and share page

This commit is contained in:
adamdotdevin 2025-08-10 19:24:16 -05:00
parent 20e818ad05
commit b8d2aebf09
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
17 changed files with 324 additions and 55 deletions

View file

@ -61,7 +61,7 @@ export default function Share(props: {
const [store, setStore] = createStore<{
info?: Session.Info
messages: Record<string, MessageWithParts>
}>({ info: props.info, messages: mapValues(props.messages, (x: any) => "metadata" in x ? fromV1(x) : x) })
}>({ info: props.info, messages: mapValues(props.messages, (x: any) => ("metadata" in x ? fromV1(x) : x)) })
const messages = createMemo(() => Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)))
const [connectionStatus, setConnectionStatus] = createSignal<[Status, string?]>(["disconnected", "Disconnected"])
createEffect(() => {
@ -128,12 +128,10 @@ export default function Share(props: {
setStore("messages", messageID, reconcile(d.content))
}
if (type === "part") {
setStore("messages", d.content.messageID, "parts", arr => {
setStore("messages", d.content.messageID, "parts", (arr) => {
const index = arr.findIndex((x) => x.id === d.content.id)
if (index === -1)
arr.push(d.content)
if (index > -1)
arr[index] = d.content
if (index === -1) arr.push(d.content)
if (index > -1) arr[index] = d.content
return [...arr]
})
}
@ -350,7 +348,7 @@ export default function Share(props: {
if (x.type === "tool" && (x.state.status === "pending" || x.state.status === "running"))
return false
return true
})
}),
)
return (

View file

@ -54,7 +54,10 @@ export function IconOpencode(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
export function IconMeta(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M16.92 4.5c-1.851 0-3.298 1.394-4.608 3.165C10.512 5.373 9.007 4.5 7.206 4.5C3.534 4.5.72 9.28.72 14.338c0 3.165 1.531 5.162 4.096 5.162c1.846 0 3.174-.87 5.535-4.997c0 0 .984-1.737 1.66-2.934q.356.574.75 1.238l1.107 1.862c2.156 3.608 3.358 4.831 5.534 4.831c2.5 0 3.89-2.024 3.89-5.255c0-5.297-2.877-9.745-6.372-9.745m-8.37 8.886c-1.913 3-2.575 3.673-3.64 3.673c-1.097 0-1.749-.963-1.749-2.68c0-3.672 1.831-7.427 4.014-7.427c1.182 0 2.17.682 3.683 2.848c-1.437 2.204-2.307 3.586-2.307 3.586m7.224-.377L14.45 10.8a45 45 0 0 0-1.032-1.608c1.193-1.841 2.176-2.759 3.347-2.759c2.43 0 4.375 3.58 4.375 7.976c0 1.676-.549 2.649-1.686 2.649c-1.09 0-1.61-.72-3.68-4.05" />
<path
fill="currentColor"
d="M16.92 4.5c-1.851 0-3.298 1.394-4.608 3.165C10.512 5.373 9.007 4.5 7.206 4.5C3.534 4.5.72 9.28.72 14.338c0 3.165 1.531 5.162 4.096 5.162c1.846 0 3.174-.87 5.535-4.997c0 0 .984-1.737 1.66-2.934q.356.574.75 1.238l1.107 1.862c2.156 3.608 3.358 4.831 5.534 4.831c2.5 0 3.89-2.024 3.89-5.255c0-5.297-2.877-9.745-6.372-9.745m-8.37 8.886c-1.913 3-2.575 3.673-3.64 3.673c-1.097 0-1.749-.963-1.749-2.68c0-3.672 1.831-7.427 4.014-7.427c1.182 0 2.17.682 3.683 2.848c-1.437 2.204-2.307 3.586-2.307 3.586m7.224-.377L14.45 10.8a45 45 0 0 0-1.032-1.608c1.193-1.841 2.176-2.759 3.347-2.759c2.43 0 4.375 3.58 4.375 7.976c0 1.676-.549 2.649-1.686 2.649c-1.09 0-1.61-.72-3.68-4.05"
/>
</svg>
)
}
@ -63,6 +66,22 @@ export function IconMeta(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
export function IconRobot(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M13.5 2c0 .444-.193.843-.5 1.118V5h5a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3h5V3.118A1.5 1.5 0 1 1 13.5 2M6 7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1zm-4 3H0v6h2zm20 0h2v6h-2zM9 14.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m6 0a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3" /></svg>
<path
fill="currentColor"
d="M13.5 2c0 .444-.193.843-.5 1.118V5h5a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3h5V3.118A1.5 1.5 0 1 1 13.5 2M6 7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1zm-4 3H0v6h2zm20 0h2v6h-2zM9 14.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m6 0a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3"
/>
</svg>
)
}
// https://icones.js.org/collection/ri?s=brain&icon=ri:brain-2-line
export function IconBrain(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M7 6q.001.357.115.67a1 1 0 0 1-1 1.333L6 8a2 2 0 0 0-1.491 3.333a1 1 0 0 1 0 1.334a2 2 0 0 0 .864 3.233a1 1 0 0 1 .67 1.135a2.5 2.5 0 1 0 4.932.824q.009-.063.025-.123V6a2 2 0 1 0-4 0m6 11.736q.016.06.025.122a2.5 2.5 0 1 0 4.932-.823a1 1 0 0 1 .67-1.135a2 2 0 0 0 .864-3.233a1 1 0 0 1 0-1.334a2 2 0 0 0-1.607-3.33a1 1 0 0 1-.999-1.333q.113-.313.115-.67a2 2 0 1 0-4 0zM9 2a4 4 0 0 1 3 1.354a4 4 0 0 1 6.998 2.771A4.002 4.002 0 0 1 21.465 12A3.997 3.997 0 0 1 20 17.465v.035a4.5 4.5 0 0 1-8 2.828A4.5 4.5 0 0 1 4 17.5v-.035A3.997 3.997 0 0 1 2.535 12a4.002 4.002 0 0 1 2.467-5.874L5 6a4 4 0 0 1 4-4"
/>
</svg>
)
}

View file

@ -128,6 +128,29 @@
max-width: var(--md-tool-width);
}
[data-component="assistant-reasoning"] {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1rem;
flex-grow: 1;
max-width: var(--md-tool-width);
& > [data-component="assistant-reasoning-markdown"] {
align-self: flex-start;
font-size: 0.875rem;
border: 1px solid var(--sl-color-blue-high);
padding: 0.5rem calc(0.5rem + 3px);
border-radius: 0.25rem;
position: relative;
[data-component="copy-button"] {
top: 0.5rem;
right: calc(0.5rem - 1px);
}
}
}
[data-component="assistant-text"] {
min-width: 0;
display: flex;

View file

@ -19,7 +19,7 @@ import {
IconMagnifyingGlass,
IconDocumentMagnifyingGlass,
} from "../icons"
import { IconMeta, IconRobot, IconOpenAI, IconGemini, IconAnthropic } from "../icons/custom"
import { IconMeta, IconRobot, IconOpenAI, IconGemini, IconAnthropic, IconBrain } from "../icons/custom"
import { ContentCode } from "./content-code"
import { ContentDiff } from "./content-diff"
import { ContentText } from "./content-text"
@ -83,6 +83,9 @@ export function Part(props: PartProps) {
>
{(model) => <ProviderIcon model={model()} size={18} />}
</Match>
<Match when={props.part.type === "reasoning" && props.message.role === "assistant"}>
<IconBrain width={18} height={18} />
</Match>
<Match when={props.part.type === "tool" && props.part.tool === "todowrite"}>
<IconQueueList width={18} height={18} />
</Match>
@ -157,6 +160,13 @@ export function Part(props: PartProps) {
)}
</div>
)}
{props.message.role === "assistant" && props.part.type === "reasoning" && (
<div data-component="assistant-reasoning">
<div data-component="assistant-reasoning-markdown">
<ContentMarkdown expand={props.last} text={props.part.text || "Thinking..."} />
</div>
</div>
)}
{props.message.role === "user" && props.part.type === "file" && (
<div data-component="attachment">
<div data-slot="copy">Attachment</div>