summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJay V <[email protected]>2025-07-03 13:15:18 -0400
committerJay V <[email protected]>2025-07-03 13:15:21 -0400
commit3e2a0c7281d449452d6808fdff80e2a1d8cf998d (patch)
treeed48f85259cee6e41ea110895a0bf8d47487d603
parent5a0910ea79b3f219c64f922fc775636b2bfdf07c (diff)
downloadopencode-3e2a0c7281d449452d6808fdff80e2a1d8cf998d.tar.gz
opencode-3e2a0c7281d449452d6808fdff80e2a1d8cf998d.zip
docs: share handle slow loading pages
-rw-r--r--packages/web/src/components/CodeBlock.tsx28
-rw-r--r--packages/web/src/components/Share.tsx1890
2 files changed, 943 insertions, 975 deletions
diff --git a/packages/web/src/components/CodeBlock.tsx b/packages/web/src/components/CodeBlock.tsx
index 4c6aab48e..6a6846891 100644
--- a/packages/web/src/components/CodeBlock.tsx
+++ b/packages/web/src/components/CodeBlock.tsx
@@ -1,8 +1,6 @@
import {
type JSX,
- onCleanup,
splitProps,
- createEffect,
createResource,
} from "solid-js"
import { codeToHtml } from "shiki"
@@ -12,15 +10,15 @@ import { transformerNotationDiff } from "@shikijs/transformers"
interface CodeBlockProps extends JSX.HTMLAttributes<HTMLDivElement> {
code: string
lang?: string
- onRendered?: () => void
}
function CodeBlock(props: CodeBlockProps) {
- const [local, rest] = splitProps(props, ["code", "lang", "onRendered"])
- let containerRef!: HTMLDivElement
+ const [local, rest] = splitProps(props, ["code", "lang"])
const [html] = createResource(
() => [local.code, local.lang],
async ([code, lang]) => {
+ // TODO: For testing delays
+ // await new Promise((resolve) => setTimeout(resolve, 3000))
return (await codeToHtml(code || "", {
lang: lang || "text",
themes: {
@@ -32,25 +30,7 @@ function CodeBlock(props: CodeBlockProps) {
},
)
- onCleanup(() => {
- if (containerRef) containerRef.innerHTML = ""
- })
-
- createEffect(() => {
- if (html() && containerRef) {
- containerRef.innerHTML = html() as string
-
- local.onRendered?.()
- }
- })
-
- return (
- <>
- {html() ? (
- <div ref={containerRef} class={styles.codeblock} {...rest}></div>
- ) : null}
- </>
- )
+ return <div innerHTML={html()} class={styles.codeblock} {...rest}></div >
}
export default CodeBlock
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index fd828629e..f1a1f1aa4 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -5,11 +5,13 @@ import {
Match,
Switch,
onMount,
+ Suspense,
onCleanup,
splitProps,
createMemo,
createEffect,
createSignal,
+ SuspenseList,
} from "solid-js"
import map from "lang-map"
import { DateTime } from "luxon"
@@ -22,7 +24,6 @@ import {
IconAnthropic,
} from "./icons/custom"
import {
- IconFolder,
IconHashtag,
IconSparkles,
IconGlobeAlt,
@@ -486,6 +487,7 @@ function TerminalPart(props: TerminalPartProps) {
}
onMount(() => {
+ checkOverflow()
window.addEventListener("resize", checkOverflow)
})
@@ -510,7 +512,6 @@ function TerminalPart(props: TerminalPartProps) {
<CodeBlock
data-section="error"
lang="text"
- onRendered={checkOverflow}
ref={(el) => (preEl = el)}
code={local.error || ""}
/>
@@ -518,7 +519,6 @@ function TerminalPart(props: TerminalPartProps) {
<Match when={local.result}>
<CodeBlock
lang="console"
- onRendered={checkOverflow}
ref={(el) => (preEl = el)}
code={local.result || ""}
/>
@@ -596,7 +596,6 @@ export default function Share(props: {
messages: Record<string, Message.Info>
}) {
let lastScrollY = 0
- let hasScrolled = false
let scrollTimeout: number | undefined
const id = props.id
@@ -606,12 +605,6 @@ export default function Share(props: {
const [showScrollButton, setShowScrollButton] = createSignal(false)
const [isButtonHovered, setIsButtonHovered] = createSignal(false)
- const anchorId = createMemo<string | null>(() => {
- const raw = window.location.hash.slice(1)
- const [id] = raw.split("-")
- return id
- })
-
const [store, setStore] = createStore<{
info?: Session.Info
messages: Record<string, Message.Info>
@@ -677,11 +670,6 @@ export default function Share(props: {
if (type === "message") {
const [, messageID] = splits
setStore("messages", messageID, reconcile(d.content))
-
- if (!hasScrolled && messageID === anchorId()) {
- scrollToAnchor(window.location.hash.slice(1))
- hasScrolled = true
- }
}
} catch (error) {
console.error("Error parsing WebSocket message:", error)
@@ -789,20 +777,8 @@ export default function Share(props: {
for (let i = 0; i < messages().length; i++) {
const msg = messages()[i]
- // TODO: Cleanup
- // const system = result.messages.length === 0 && msg.role === "system"
const assistant = msg.metadata?.assistant
- // if (system) {
- // for (const part of msg.parts) {
- // if (part.type === "text") {
- // result.system.push(part.text)
- // }
- // }
- // result.created = msg.metadata?.time.created
- // continue
- // }
-
result.messages.push(msg)
if (assistant) {
@@ -889,994 +865,1006 @@ export default function Share(props: {
fallback={<p>Waiting for messages...</p>}
>
<div class={styles.parts}>
- <For each={data().messages}>
- {(msg, msgIndex) => (
- <For each={msg.parts}>
- {(part, partIndex) => {
- if (
- (part.type === "step-start" &&
- (partIndex() > 0 || !msg.metadata?.assistant)) ||
- (msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "todoread")
- )
- return null
-
- const anchor = createMemo(() => `${msg.id}-${partIndex()}`)
- const [showResults, setShowResults] = createSignal(false)
- const isLastPart = createMemo(
- () =>
- data().messages.length === msgIndex() + 1 &&
- msg.parts.length === partIndex() + 1,
- )
- const toolData = createMemo(() => {
- if (
- msg.role !== "assistant" ||
- part.type !== "tool-invocation"
- )
- return {}
-
- const metadata =
- msg.metadata?.tool[part.toolInvocation.toolCallId]
- const args = part.toolInvocation.args
- const result =
- part.toolInvocation.state === "result" &&
- part.toolInvocation.result
- const duration = DateTime.fromMillis(
- metadata?.time.end || 0,
- )
- .diff(DateTime.fromMillis(metadata?.time.start || 0))
- .toMillis()
-
- return { metadata, args, result, duration }
- })
- return (
- <Switch>
- {/* User text */}
- <Match
- when={
- msg.role === "user" && part.type === "text" && part
- }
- >
- {(part) => (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="user-text"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconUserCircle width={18} height={18} />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <TextPart
- invert
- text={part().text}
- expand={isLastPart()}
- />
- </div>
- </div>
- )}
- </Match>
- {/* AI text */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "text" &&
- part
+ <SuspenseList>
+ <For each={data().messages}>
+ {(msg, msgIndex) => (
+ <Suspense>
+ <For each={msg.parts}>
+ {(part, partIndex) => {
+ if (
+ (part.type === "step-start" &&
+ (partIndex() > 0 || !msg.metadata?.assistant)) ||
+ (msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "todoread")
+ )
+ return null
+
+ const anchor = createMemo(() => `${msg.id}-${partIndex()}`)
+ const [showResults, setShowResults] = createSignal(false)
+ const isLastPart = createMemo(
+ () =>
+ data().messages.length === msgIndex() + 1 &&
+ msg.parts.length === partIndex() + 1,
+ )
+ const toolData = createMemo(() => {
+ if (
+ msg.role !== "assistant" ||
+ part.type !== "tool-invocation"
+ )
+ return {}
+
+ const metadata =
+ msg.metadata?.tool[part.toolInvocation.toolCallId]
+ const args = part.toolInvocation.args
+ const result =
+ part.toolInvocation.state === "result" &&
+ part.toolInvocation.result
+ const duration = DateTime.fromMillis(
+ metadata?.time.end || 0,
+ )
+ .diff(DateTime.fromMillis(metadata?.time.start || 0))
+ .toMillis()
+
+ return { metadata, args, result, duration }
+ })
+
+ onMount(() => {
+ const hash = window.location.hash.slice(1)
+ if (hash !== "" && hash === anchor()) {
+ scrollToAnchor(hash)
}
- >
- {(part) => (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="ai-text"
+ })
+
+ return (
+ <Switch>
+ {/* User text */}
+ <Match
+ when={
+ msg.role === "user" && part.type === "text" && part
+ }
>
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconSparkles width={18} height={18} />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <MarkdownPart
- highlight
- expand={isLastPart()}
- text={stripEnclosingTag(part().text)}
- />
- <Show when={isLastPart() && data().completed}>
- <span
- data-part-footer
- title={DateTime.fromMillis(
- data().completed || 0,
- ).toLocaleString(
- DateTime.DATETIME_FULL_WITH_SECONDS,
- )}
- >
- {DateTime.fromMillis(
- data().completed || 0,
- ).toLocaleString(DateTime.DATETIME_MED)}
- </span>
- </Show>
- </div>
- </div>
- )}
- </Match>
- {/* AI model */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "step-start" &&
- msg.metadata?.assistant
- }
- >
- {(assistant) => {
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="ai-model"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <ProviderIcon
- size={18}
- provider={assistant().providerID}
+ {(part) => (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="user-text"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconUserCircle width={18} height={18} />
+ </AnchorIcon>
+ <div></div>
+ </div>
+ <div data-section="content">
+ <TextPart
+ invert
+ text={part().text}
+ expand={isLastPart()}
/>
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>
- {assistant().providerID}
- </span>
- </div>
- <span data-part-model>
- {assistant().modelID}
- </span>
</div>
</div>
- </div>
- )
- }}
- </Match>
-
- {/* Grep tool */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "grep" &&
- part
- }
- >
- {(_part) => {
- const matches = () => toolData()?.metadata?.matches
- const splitArgs = () => {
- const { pattern, ...rest } = toolData()?.args
- return { pattern, rest }
- }
-
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-grep"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconDocumentMagnifyingGlass
- width={18}
- height={18}
+ )}
+ </Match>
+ {/* AI text */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "text" &&
+ part
+ }
+ >
+ {(part) => (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="ai-text"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconSparkles width={18} height={18} />
+ </AnchorIcon>
+ <div></div>
+ </div>
+ <div data-section="content">
+ <MarkdownPart
+ highlight
+ expand={isLastPart()}
+ text={stripEnclosingTag(part().text)}
/>
- </AnchorIcon>
- <div></div>
+ <Show when={isLastPart() && data().completed}>
+ <span
+ data-part-footer
+ title={DateTime.fromMillis(
+ data().completed || 0,
+ ).toLocaleString(
+ DateTime.DATETIME_FULL_WITH_SECONDS,
+ )}
+ >
+ {DateTime.fromMillis(
+ data().completed || 0,
+ ).toLocaleString(DateTime.DATETIME_MED)}
+ </span>
+ </Show>
+ </div>
</div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>Grep</span>
- <b>&ldquo;{splitArgs().pattern}&rdquo;</b>
+ )}
+ </Match>
+ {/* AI model */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "step-start" &&
+ msg.metadata?.assistant
+ }
+ >
+ {(assistant) => {
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="ai-model"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <ProviderIcon
+ size={18}
+ provider={assistant().providerID}
+ />
+ </AnchorIcon>
+ <div></div>
</div>
- <Show
- when={
- Object.keys(splitArgs().rest).length > 0
- }
- >
- <div data-part-tool-args>
- <For
- each={flattenToolArgs(
- splitArgs().rest,
- )}
- >
- {([name, value]) => (
- <>
- <div></div>
- <div>{name}</div>
- <div>{value}</div>
- </>
- )}
- </For>
- </div>
- </Show>
- <Switch>
- <Match when={matches() > 0}>
- <div data-part-tool-result>
- <ResultsButton
- showCopy={
- matches() === 1
- ? "1 match"
- : `${matches()} matches`
- }
- hideCopy="Hide matches"
- results={showResults()}
- onClick={() =>
- setShowResults((e) => !e)
- }
- />
- <Show when={showResults()}>
- <TextPart
- expand
- data-size="sm"
- data-color="dimmed"
- text={toolData()?.result}
- />
- </Show>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>
+ {assistant().providerID}
+ </span>
</div>
- </Match>
- <Match when={toolData()?.result}>
- <div data-part-tool-result>
- <TextPart
- expand
- data-size="sm"
- data-color="dimmed"
- text={toolData()?.result}
- />
- </div>
- </Match>
- </Switch>
+ <span data-part-model>
+ {assistant().modelID}
+ </span>
+ </div>
+ </div>
</div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* Glob tool */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "glob" &&
- part
- }
- >
- {(_part) => {
- const count = () => toolData()?.metadata?.count
- const pattern = () => toolData()?.args.pattern
-
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-glob"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconMagnifyingGlass
- width={18}
- height={18}
- />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>Glob</span>
- <b>&ldquo;{pattern()}&rdquo;</b>
+ )
+ }}
+ </Match>
+
+ {/* Grep tool */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "grep" &&
+ part
+ }
+ >
+ {(_part) => {
+ const matches = () => toolData()?.metadata?.matches
+ const splitArgs = () => {
+ const { pattern, ...rest } = toolData()?.args
+ return { pattern, rest }
+ }
+
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-grep"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconDocumentMagnifyingGlass
+ width={18}
+ height={18}
+ />
+ </AnchorIcon>
+ <div></div>
</div>
- <Switch>
- <Match when={count() > 0}>
- <div data-part-tool-result>
- <ResultsButton
- showCopy={
- count() === 1
- ? "1 result"
- : `${count()} results`
- }
- results={showResults()}
- onClick={() =>
- setShowResults((e) => !e)
- }
- />
- <Show when={showResults()}>
- <TextPart
- expand
- text={toolData()?.result}
- data-size="sm"
- data-color="dimmed"
- />
- </Show>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>Grep</span>
+ <b>&ldquo;{splitArgs().pattern}&rdquo;</b>
</div>
- </Match>
- <Match when={toolData()?.result}>
- <div data-part-tool-result>
- <TextPart
- expand
- text={toolData()?.result}
- data-size="sm"
- data-color="dimmed"
- />
+ <Show
+ when={
+ Object.keys(splitArgs().rest).length > 0
+ }
+ >
+ <div data-part-tool-args>
+ <For
+ each={flattenToolArgs(
+ splitArgs().rest,
+ )}
+ >
+ {([name, value]) => (
+ <>
+ <div></div>
+ <div>{name}</div>
+ <div>{value}</div>
+ </>
+ )}
+ </For>
+ </div>
+ </Show>
+ <Switch>
+ <Match when={matches() > 0}>
+ <div data-part-tool-result>
+ <ResultsButton
+ showCopy={
+ matches() === 1
+ ? "1 match"
+ : `${matches()} matches`
+ }
+ hideCopy="Hide matches"
+ results={showResults()}
+ onClick={() =>
+ setShowResults((e) => !e)
+ }
+ />
+ <Show when={showResults()}>
+ <TextPart
+ expand
+ data-size="sm"
+ data-color="dimmed"
+ text={toolData()?.result}
+ />
+ </Show>
+ </div>
+ </Match>
+ <Match when={toolData()?.result}>
+ <div data-part-tool-result>
+ <TextPart
+ expand
+ data-size="sm"
+ data-color="dimmed"
+ text={toolData()?.result}
+ />
+ </div>
+ </Match>
+ </Switch>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
+ </div>
+ </div>
+ )
+ }}
+ </Match>
+ {/* Glob tool */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "glob" &&
+ part
+ }
+ >
+ {(_part) => {
+ const count = () => toolData()?.metadata?.count
+ const pattern = () => toolData()?.args.pattern
+
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-glob"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconMagnifyingGlass
+ width={18}
+ height={18}
+ />
+ </AnchorIcon>
+ <div></div>
+ </div>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>Glob</span>
+ <b>&ldquo;{pattern()}&rdquo;</b>
</div>
- </Match>
- </Switch>
+ <Switch>
+ <Match when={count() > 0}>
+ <div data-part-tool-result>
+ <ResultsButton
+ showCopy={
+ count() === 1
+ ? "1 result"
+ : `${count()} results`
+ }
+ results={showResults()}
+ onClick={() =>
+ setShowResults((e) => !e)
+ }
+ />
+ <Show when={showResults()}>
+ <TextPart
+ expand
+ text={toolData()?.result}
+ data-size="sm"
+ data-color="dimmed"
+ />
+ </Show>
+ </div>
+ </Match>
+ <Match when={toolData()?.result}>
+ <div data-part-tool-result>
+ <TextPart
+ expand
+ text={toolData()?.result}
+ data-size="sm"
+ data-color="dimmed"
+ />
+ </div>
+ </Match>
+ </Switch>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
+ </div>
</div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* LS tool */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "list" &&
- part
- }
- >
- {(_part) => {
- const path = createMemo(() =>
- toolData()?.args?.path !== data().rootDir
- ? stripWorkingDirectory(
- toolData()?.args?.path,
- data().rootDir,
)
- : toolData()?.args?.path,
- )
+ }}
+ </Match>
+ {/* LS tool */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "list" &&
+ part
+ }
+ >
+ {(_part) => {
+ const path = createMemo(() =>
+ toolData()?.args?.path !== data().rootDir
+ ? stripWorkingDirectory(
+ toolData()?.args?.path,
+ data().rootDir,
+ )
+ : toolData()?.args?.path,
+ )
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-list"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconRectangleStack
- width={18}
- height={18}
- />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>LS</span>
- <b title={toolData()?.args?.path}>
- {path()}
- </b>
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-list"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconRectangleStack
+ width={18}
+ height={18}
+ />
+ </AnchorIcon>
+ <div></div>
</div>
- <Switch>
- <Match when={toolData()?.result}>
- <div data-part-tool-result>
- <ResultsButton
- results={showResults()}
- onClick={() =>
- setShowResults((e) => !e)
- }
- />
- <Show when={showResults()}>
- <TextPart
- expand
- data-size="sm"
- data-color="dimmed"
- text={toolData()?.result}
- />
- </Show>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>LS</span>
+ <b title={toolData()?.args?.path}>
+ {path()}
+ </b>
</div>
- </Match>
- </Switch>
+ <Switch>
+ <Match when={toolData()?.result}>
+ <div data-part-tool-result>
+ <ResultsButton
+ results={showResults()}
+ onClick={() =>
+ setShowResults((e) => !e)
+ }
+ />
+ <Show when={showResults()}>
+ <TextPart
+ expand
+ data-size="sm"
+ data-color="dimmed"
+ text={toolData()?.result}
+ />
+ </Show>
+ </div>
+ </Match>
+ </Switch>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
+ </div>
</div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* Read tool */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "read" &&
- part
- }
- >
- {(_part) => {
- const filePath = createMemo(() =>
- stripWorkingDirectory(
- toolData()?.args?.filePath,
- data().rootDir,
- ),
- )
- const hasError = () => toolData()?.metadata?.error
- const preview = () => toolData()?.metadata?.preview
-
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-read"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconDocument width={18} height={18} />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>Read</span>
- <b title={toolData()?.args?.filePath}>
- {filePath()}
- </b>
+ )
+ }}
+ </Match>
+ {/* Read tool */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "read" &&
+ part
+ }
+ >
+ {(_part) => {
+ const filePath = createMemo(() =>
+ stripWorkingDirectory(
+ toolData()?.args?.filePath,
+ data().rootDir,
+ ),
+ )
+ const hasError = () => toolData()?.metadata?.error
+ const preview = () => toolData()?.metadata?.preview
+
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-read"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconDocument width={18} height={18} />
+ </AnchorIcon>
+ <div></div>
</div>
- <Switch>
- <Match when={hasError()}>
- <div data-part-tool-result>
- <ErrorPart>
- {formatErrorString(
- toolData()?.result,
- )}
- </ErrorPart>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>Read</span>
+ <b title={toolData()?.args?.filePath}>
+ {filePath()}
+ </b>
</div>
- </Match>
- {/* Always try to show CodeBlock if preview is available (even if empty string) */}
- <Match when={typeof preview() === 'string'}>
- <div data-part-tool-result>
- <ResultsButton
- showCopy="Show preview"
- hideCopy="Hide preview"
- results={showResults()}
- onClick={() =>
- setShowResults((e) => !e)
- }
- />
- <Show when={showResults()}>
- <div data-part-tool-code>
- <CodeBlock
- lang={getShikiLang(filePath())}
- code={preview()}
+ <Switch>
+ <Match when={hasError()}>
+ <div data-part-tool-result>
+ <ErrorPart>
+ {formatErrorString(
+ toolData()?.result,
+ )}
+ </ErrorPart>
+ </div>
+ </Match>
+ {/* Always try to show CodeBlock if preview is available (even if empty string) */}
+ <Match when={typeof preview() === 'string'}>
+ <div data-part-tool-result>
+ <ResultsButton
+ showCopy="Show preview"
+ hideCopy="Hide preview"
+ results={showResults()}
+ onClick={() =>
+ setShowResults((e) => !e)
+ }
/>
+ <Show when={showResults()}>
+ <div data-part-tool-code>
+ <CodeBlock
+ lang={getShikiLang(filePath())}
+ code={preview()}
+ />
+ </div>
+ </Show>
</div>
- </Show>
- </div>
- </Match>
- {/* Fallback to TextPart if preview is not a string (e.g. undefined) AND result exists */}
- <Match when={typeof preview() !== 'string' && toolData()?.result}>
- <div data-part-tool-result>
- <ResultsButton
- results={showResults()}
- onClick={() =>
- setShowResults((e) => !e)
- }
- />
- <Show when={showResults()}>
- <TextPart
- expand
- text={toolData()?.result}
- data-size="sm"
- data-color="dimmed"
- />
- </Show>
+ </Match>
+ {/* Fallback to TextPart if preview is not a string (e.g. undefined) AND result exists */}
+ <Match when={typeof preview() !== 'string' && toolData()?.result}>
+ <div data-part-tool-result>
+ <ResultsButton
+ results={showResults()}
+ onClick={() =>
+ setShowResults((e) => !e)
+ }
+ />
+ <Show when={showResults()}>
+ <TextPart
+ expand
+ text={toolData()?.result}
+ data-size="sm"
+ data-color="dimmed"
+ />
+ </Show>
+ </div>
+ </Match>
+ </Switch>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
+ </div>
+ </div>
+ )
+ }}
+ </Match>
+ {/* Write tool */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "write" &&
+ part
+ }
+ >
+ {(_part) => {
+ const filePath = createMemo(() =>
+ stripWorkingDirectory(
+ toolData()?.args?.filePath,
+ data().rootDir,
+ ),
+ )
+ const hasError = () => toolData()?.metadata?.error
+ const content = () => toolData()?.args?.content
+ const diagnostics = createMemo(() =>
+ getDiagnostics(
+ toolData()?.metadata?.diagnostics,
+ toolData()?.args.filePath,
+ ),
+ )
+
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-write"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconDocumentPlus width={18} height={18} />
+ </AnchorIcon>
+ <div></div>
+ </div>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>Write</span>
+ <b title={toolData()?.args?.filePath}>
+ {filePath()}
+ </b>
</div>
- </Match>
- </Switch>
+ <Show when={diagnostics().length > 0}>
+ <ErrorPart>{diagnostics()}</ErrorPart>
+ </Show>
+ <Switch>
+ <Match when={hasError()}>
+ <div data-part-tool-result>
+ <ErrorPart>
+ {formatErrorString(
+ toolData()?.result
+ )}
+ </ErrorPart>
+ </div>
+ </Match>
+ <Match when={content()}>
+ <div data-part-tool-result>
+ <ResultsButton
+ showCopy="Show contents"
+ hideCopy="Hide contents"
+ results={showResults()}
+ onClick={() =>
+ setShowResults((e) => !e)
+ }
+ />
+ <Show when={showResults()}>
+ <div data-part-tool-code>
+ <CodeBlock
+ lang={getShikiLang(filePath())}
+ code={toolData()?.args?.content}
+ />
+ </div>
+ </Show>
+ </div>
+ </Match>
+ </Switch>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
+ </div>
</div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* Write tool */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "write" &&
- part
- }
- >
- {(_part) => {
- const filePath = createMemo(() =>
- stripWorkingDirectory(
- toolData()?.args?.filePath,
- data().rootDir,
- ),
- )
- const hasError = () => toolData()?.metadata?.error
- const content = () => toolData()?.args?.content
- const diagnostics = createMemo(() =>
- getDiagnostics(
- toolData()?.metadata?.diagnostics,
- toolData()?.args.filePath,
- ),
- )
-
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-write"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconDocumentPlus width={18} height={18} />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>Write</span>
- <b title={toolData()?.args?.filePath}>
- {filePath()}
- </b>
+ )
+ }}
+ </Match>
+ {/* Edit tool */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "edit" &&
+ part
+ }
+ >
+ {(_part) => {
+ const diff = () => toolData()?.metadata?.diff
+ const message = () => toolData()?.metadata?.message
+ const hasError = () => toolData()?.metadata?.error
+ const filePath = createMemo(() =>
+ stripWorkingDirectory(
+ toolData()?.args.filePath,
+ data().rootDir,
+ ),
+ )
+ const diagnostics = createMemo(() =>
+ getDiagnostics(
+ toolData()?.metadata?.diagnostics,
+ toolData()?.args.filePath,
+ ),
+ )
+
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-edit"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconPencilSquare width={18} height={18} />
+ </AnchorIcon>
+ <div></div>
</div>
- <Show when={diagnostics().length > 0}>
- <ErrorPart>{diagnostics()}</ErrorPart>
- </Show>
- <Switch>
- <Match when={hasError()}>
- <div data-part-tool-result>
- <ErrorPart>
- {formatErrorString(
- toolData()?.result
- )}
- </ErrorPart>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>Edit</span>
+ <b title={toolData()?.args?.filePath}>
+ {filePath()}
+ </b>
</div>
- </Match>
- <Match when={content()}>
- <div data-part-tool-result>
- <ResultsButton
- showCopy="Show contents"
- hideCopy="Hide contents"
- results={showResults()}
- onClick={() =>
- setShowResults((e) => !e)
- }
- />
- <Show when={showResults()}>
- <div data-part-tool-code>
- <CodeBlock
+ <Switch>
+ <Match when={hasError()}>
+ <div data-part-tool-result>
+ <ErrorPart>
+ {formatErrorString(message())}
+ </ErrorPart>
+ </div>
+ </Match>
+ <Match when={diff()}>
+ <div data-part-tool-edit>
+ <DiffView
+ class={styles["diff-code-block"]}
+ diff={diff()}
lang={getShikiLang(filePath())}
- code={toolData()?.args?.content}
/>
</div>
- </Show>
- </div>
- </Match>
- </Switch>
+ </Match>
+ </Switch>
+ <Show when={diagnostics().length > 0}>
+ <ErrorPart>{diagnostics()}</ErrorPart>
+ </Show>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
+ </div>
</div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* Edit tool */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "edit" &&
- part
- }
- >
- {(_part) => {
- const diff = () => toolData()?.metadata?.diff
- const message = () => toolData()?.metadata?.message
- const hasError = () => toolData()?.metadata?.error
- const filePath = createMemo(() =>
- stripWorkingDirectory(
- toolData()?.args.filePath,
- data().rootDir,
- ),
- )
- const diagnostics = createMemo(() =>
- getDiagnostics(
- toolData()?.metadata?.diagnostics,
- toolData()?.args.filePath,
- ),
- )
-
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-edit"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconPencilSquare width={18} height={18} />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>Edit</span>
- <b title={toolData()?.args?.filePath}>
- {filePath()}
- </b>
+ )
+ }}
+ </Match>
+ {/* Bash tool */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "bash" &&
+ part
+ }
+ >
+ {(_part) => {
+ const command = () => toolData()?.metadata?.title
+ const desc = () => toolData()?.metadata?.description
+ const result = () => toolData()?.metadata?.stdout
+ const error = () => toolData()?.metadata?.stderr
+
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-bash"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconCommandLine width={18} height={18} />
+ </AnchorIcon>
+ <div></div>
</div>
- <Switch>
- <Match when={hasError()}>
- <div data-part-tool-result>
- <ErrorPart>
- {formatErrorString(message())}
- </ErrorPart>
- </div>
- </Match>
- <Match when={diff()}>
- <div data-part-tool-edit>
- <DiffView
- class={styles["diff-code-block"]}
- diff={diff()}
- lang={getShikiLang(filePath())}
+ <div data-section="content">
+ {command() && (
+ <div data-part-tool-body>
+ <TerminalPart
+ desc={desc()}
+ data-size="sm"
+ command={command()!}
+ result={result()}
+ error={error()}
/>
</div>
- </Match>
- </Switch>
- <Show when={diagnostics().length > 0}>
- <ErrorPart>{diagnostics()}</ErrorPart>
- </Show>
+ )}
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
+ </div>
</div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* Bash tool */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "bash" &&
- part
- }
- >
- {(_part) => {
- const command = () => toolData()?.metadata?.title
- const desc = () => toolData()?.metadata?.description
- const result = () => toolData()?.metadata?.stdout
- const error = () => toolData()?.metadata?.stderr
-
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-bash"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconCommandLine width={18} height={18} />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- {command() && (
- <div data-part-tool-body>
- <TerminalPart
- desc={desc()}
- data-size="sm"
- command={command()!}
- result={result()}
- error={error()}
+ )
+ }}
+ </Match>
+ {/* Todo write */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "todowrite" &&
+ part
+ }
+ >
+ {(_part) => {
+ const todos = createMemo(() =>
+ sortTodosByStatus(toolData()?.args?.todos ?? []),
+ )
+ const starting = () =>
+ todos().every((t) => t.status === "pending")
+ const finished = () =>
+ todos().every((t) => t.status === "completed")
+
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-todo"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconQueueList width={18} height={18} />
+ </AnchorIcon>
+ <div></div>
+ </div>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>
+ <Switch fallback="Updating plan">
+ <Match when={starting()}>
+ Creating plan
+ </Match>
+ <Match when={finished()}>
+ Completing plan
+ </Match>
+ </Switch>
+ </span>
+ </div>
+ <Show when={todos().length > 0}>
+ <ul class={styles.todos}>
+ <For each={todos()}>
+ {(todo) => (
+ <li data-status={todo.status}>
+ <span></span>
+ {todo.content}
+ </li>
+ )}
+ </For>
+ </ul>
+ </Show>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
/>
</div>
- )}
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* Todo write */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "todowrite" &&
- part
- }
- >
- {(_part) => {
- const todos = createMemo(() =>
- sortTodosByStatus(toolData()?.args?.todos ?? []),
- )
- const starting = () =>
- todos().every((t) => t.status === "pending")
- const finished = () =>
- todos().every((t) => t.status === "completed")
-
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-todo"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconQueueList width={18} height={18} />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>
- <Switch fallback="Updating plan">
- <Match when={starting()}>
- Creating plan
+ </div>
+ )
+ }}
+ </Match>
+ {/* Fetch tool */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part.toolInvocation.toolName === "webfetch" &&
+ part
+ }
+ >
+ {(_part) => {
+ const url = () => toolData()?.args.url
+ const format = () => toolData()?.args.format
+ const hasError = () => toolData()?.metadata?.error
+
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-fetch"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconGlobeAlt width={18} height={18} />
+ </AnchorIcon>
+ <div></div>
+ </div>
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>Fetch</span>
+ <b>{url()}</b>
+ </div>
+ <Switch>
+ <Match when={hasError()}>
+ <div data-part-tool-result>
+ <ErrorPart>
+ {formatErrorString(
+ toolData()?.result,
+ )}
+ </ErrorPart>
+ </div>
</Match>
- <Match when={finished()}>
- Completing plan
+ <Match when={toolData()?.result}>
+ <div data-part-tool-result>
+ <ResultsButton
+ results={showResults()}
+ onClick={() =>
+ setShowResults((e) => !e)
+ }
+ />
+ <Show when={showResults()}>
+ <div data-part-tool-code>
+ <CodeBlock
+ lang={format() || "text"}
+ code={toolData()?.result}
+ />
+ </div>
+ </Show>
+ </div>
</Match>
</Switch>
- </span>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
</div>
- <Show when={todos().length > 0}>
- <ul class={styles.todos}>
- <For each={todos()}>
- {(todo) => (
- <li data-status={todo.status}>
- <span></span>
- {todo.content}
- </li>
- )}
- </For>
- </ul>
- </Show>
</div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* Fetch tool */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part.toolInvocation.toolName === "webfetch" &&
- part
- }
- >
- {(_part) => {
- const url = () => toolData()?.args.url
- const format = () => toolData()?.args.format
- const hasError = () => toolData()?.metadata?.error
-
- return (
- <div
- id={anchor()}
- data-section="part"
- data-part-type="tool-fetch"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <IconGlobeAlt width={18} height={18} />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>Fetch</span>
- <b>{url()}</b>
+ )
+ }}
+ </Match>
+ {/* Tool call */}
+ <Match
+ when={
+ msg.role === "assistant" &&
+ part.type === "tool-invocation" &&
+ part
+ }
+ >
+ {(part) => {
+ return (
+ <div
+ id={anchor()}
+ data-section="part"
+ data-part-type="tool-fallback"
+ >
+ <div data-section="decoration">
+ <AnchorIcon id={anchor()}>
+ <IconWrenchScrewdriver
+ width={18}
+ height={18}
+ />
+ </AnchorIcon>
+ <div></div>
</div>
- <Switch>
- <Match when={hasError()}>
- <div data-part-tool-result>
- <ErrorPart>
- {formatErrorString(
- toolData()?.result,
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ {part().toolInvocation.toolName}
+ </div>
+ <div data-part-tool-args>
+ <For
+ each={flattenToolArgs(
+ part().toolInvocation.args,
+ )}
+ >
+ {(arg) => (
+ <>
+ <div></div>
+ <div>{arg[0]}</div>
+ <div>{arg[1]}</div>
+ </>
)}
- </ErrorPart>
+ </For>
</div>
- </Match>
- <Match when={toolData()?.result}>
- <div data-part-tool-result>
- <ResultsButton
- results={showResults()}
- onClick={() =>
- setShowResults((e) => !e)
- }
- />
- <Show when={showResults()}>
- <div data-part-tool-code>
- <CodeBlock
- lang={format() || "text"}
- code={toolData()?.result}
+ <Switch>
+ <Match when={toolData()?.result}>
+ <div data-part-tool-result>
+ <ResultsButton
+ results={showResults()}
+ onClick={() =>
+ setShowResults((e) => !e)
+ }
/>
+ <Show when={showResults()}>
+ <TextPart
+ expand
+ data-size="sm"
+ data-color="dimmed"
+ text={toolData()?.result}
+ />
+ </Show>
</div>
- </Show>
- </div>
- </Match>
- </Switch>
+ </Match>
+ <Match
+ when={
+ part().toolInvocation.state === "call"
+ }
+ >
+ <TextPart
+ data-size="sm"
+ data-color="dimmed"
+ text="Calling..."
+ />
+ </Match>
+ </Switch>
+ </div>
+ <ToolFooter
+ time={toolData()?.duration || 0}
+ />
+ </div>
</div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
- </div>
- </div>
- )
- }}
- </Match>
- {/* Tool call */}
- <Match
- when={
- msg.role === "assistant" &&
- part.type === "tool-invocation" &&
- part
- }
- >
- {(part) => {
- return (
+ )
+ }}
+ </Match>
+ {/* Fallback */}
+ <Match when={true}>
<div
id={anchor()}
data-section="part"
- data-part-type="tool-fallback"
+ data-part-type="fallback"
>
<div data-section="decoration">
<AnchorIcon id={anchor()}>
- <IconWrenchScrewdriver
- width={18}
- height={18}
- />
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- {part().toolInvocation.toolName}
- </div>
- <div data-part-tool-args>
- <For
- each={flattenToolArgs(
- part().toolInvocation.args,
- )}
- >
- {(arg) => (
- <>
- <div></div>
- <div>{arg[0]}</div>
- <div>{arg[1]}</div>
- </>
- )}
- </For>
- </div>
- <Switch>
- <Match when={toolData()?.result}>
- <div data-part-tool-result>
- <ResultsButton
- results={showResults()}
- onClick={() =>
- setShowResults((e) => !e)
- }
- />
- <Show when={showResults()}>
- <TextPart
- expand
- data-size="sm"
- data-color="dimmed"
- text={toolData()?.result}
- />
- </Show>
- </div>
- </Match>
+ <Switch
+ fallback={
+ <IconWrenchScrewdriver
+ width={16}
+ height={16}
+ />
+ }
+ >
<Match
when={
- part().toolInvocation.state === "call"
+ msg.role === "assistant" &&
+ part.type !== "tool-invocation"
}
>
- <TextPart
- data-size="sm"
- data-color="dimmed"
- text="Calling..."
- />
+ <IconSparkles width={18} height={18} />
+ </Match>
+
+ <Match when={msg.role === "user"}>
+ <IconUserCircle width={18} height={18} />
</Match>
</Switch>
- </div>
- <ToolFooter
- time={toolData()?.duration || 0}
- />
+ </AnchorIcon>
+ <div></div>
</div>
- </div>
- )
- }}
- </Match>
- {/* Fallback */}
- <Match when={true}>
- <div
- id={anchor()}
- data-section="part"
- data-part-type="fallback"
- >
- <div data-section="decoration">
- <AnchorIcon id={anchor()}>
- <Switch
- fallback={
- <IconWrenchScrewdriver
- width={16}
- height={16}
+ <div data-section="content">
+ <div data-part-tool-body>
+ <div data-part-title>
+ <span data-element-label>{part.type}</span>
+ </div>
+ <TextPart
+ text={JSON.stringify(part, null, 2)}
/>
- }
- >
- <Match
- when={
- msg.role === "assistant" &&
- part.type !== "tool-invocation"
- }
- >
- <IconSparkles width={18} height={18} />
- </Match>
-
- <Match when={msg.role === "user"}>
- <IconUserCircle width={18} height={18} />
- </Match>
- </Switch>
- </AnchorIcon>
- <div></div>
- </div>
- <div data-section="content">
- <div data-part-tool-body>
- <div data-part-title>
- <span data-element-label>{part.type}</span>
+ </div>
</div>
- <TextPart
- text={JSON.stringify(part, null, 2)}
- />
</div>
- </div>
- </div>
- </Match>
- </Switch>
- )
- }}
- </For>
- )}
- </For>
+ </Match>
+ </Switch>
+ )
+ }}
+ </For>
+ </Suspense>
+ )}
+ </For>
+ </SuspenseList>
<div data-section="part" data-part-type="summary">
<div data-section="decoration">
<span data-status={connectionStatus()[0]}></span>