Share: load server data on page load

This commit is contained in:
Frank 2025-06-08 01:17:54 -04:00
parent 879d02f86c
commit 1d782dc19a
3 changed files with 329 additions and 132 deletions

View file

@ -69,6 +69,15 @@ export class SyncServer extends DurableObject<Env> {
return secret return secret
} }
public async messages() {
const data = await this.ctx.storage.list()
const messages = []
for (const [key, content] of data.entries()) {
messages.push({ key, content })
}
return messages
}
private async getSecret() { private async getSecret() {
return this.ctx.storage.get<string>("secret") return this.ctx.storage.get<string>("secret")
} }
@ -163,5 +172,17 @@ export default {
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id)) const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
return stub.fetch(request) return stub.fetch(request)
} }
if (request.method === "GET" && method === "share_messages") {
const id = url.searchParams.get("id")
console.log("share_messages", id)
if (!id)
return new Response("Error: Share ID is required", { status: 400 })
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
const messages = await stub.messages()
return new Response(JSON.stringify({ messages }), {
headers: { "Content-Type": "application/json" },
})
}
}, },
} }

View file

@ -139,12 +139,10 @@ function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
entries.push([arrayPath, item]) entries.push([arrayPath, item])
} }
}) })
} } else {
else {
entries.push(...flattenToolArgs(value, path)) entries.push(...flattenToolArgs(value, path))
} }
} } else {
else {
entries.push([path, value]) entries.push([path, value])
} }
} }
@ -360,7 +358,9 @@ function TerminalPart(props: TerminalPartProps) {
{...rest} {...rest}
> >
<div data-section="body"> <div data-section="body">
<div data-section="header"><span>{local.desc}</span></div> <div data-section="header">
<span>{local.desc}</span>
</div>
<div data-section="content"> <div data-section="content">
<CodeBlock <CodeBlock
lang="ansi" lang="ansi"
@ -384,25 +384,26 @@ function TerminalPart(props: TerminalPartProps) {
} }
function ToolFooter(props: { time: number }) { function ToolFooter(props: { time: number }) {
return ( return props.time > MIN_DURATION ? (
props.time > MIN_DURATION <span data-part-footer title={`${props.time}ms`}>
? <span data-part-footer title={`${props.time}ms`}> {formatDuration(props.time)}
{formatDuration(props.time)} </span>
</span> ) : (
: <div data-part-footer="spacer"></div> <div data-part-footer="spacer"></div>
) )
} }
export default function Share(props: { api: string }) { export default function Share(props: {
api: string
data: { key: string; content: SessionMessage | SessionInfo }[]
}) {
let params = new URLSearchParams(document.location.search) let params = new URLSearchParams(document.location.search)
const id = params.get("id") const id = params.get("id")
const [store, setStore] = createStore<{ const [store, setStore] = createStore<{
info?: SessionInfo info?: SessionInfo
messages: Record<string, SessionMessage> messages: Record<string, SessionMessage>
}>({ }>({ messages: {} })
messages: {},
})
const messages = createMemo(() => const messages = createMemo(() =>
Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)), Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)),
) )
@ -410,6 +411,19 @@ export default function Share(props: { api: string }) {
[Status, string?] [Status, string?]
>(["disconnected", "Disconnected"]) >(["disconnected", "Disconnected"])
const processDatum = (d: any) => {
const [root, type, ...splits] = d.key.split("/")
if (root !== "session") return
if (type === "info") {
setStore("info", reconcile(d.content))
return
}
if (type === "message") {
const [, messageID] = splits
setStore("messages", messageID, reconcile(d.content))
}
}
onMount(() => { onMount(() => {
const apiUrl = props.api const apiUrl = props.api
@ -424,6 +438,10 @@ export default function Share(props: { api: string }) {
return return
} }
for (const datum of props.data) {
processDatum(datum)
}
let reconnectTimer: number | undefined let reconnectTimer: number | undefined
let socket: WebSocket | null = null let socket: WebSocket | null = null
@ -454,17 +472,7 @@ export default function Share(props: { api: string }) {
socket.onmessage = (event) => { socket.onmessage = (event) => {
console.log("WebSocket message received") console.log("WebSocket message received")
try { try {
const data = JSON.parse(event.data) processDatum(JSON.parse(event.data))
const [root, type, ...splits] = data.key.split("/")
if (root !== "session") return
if (type === "info") {
setStore("info", reconcile(data.content))
return
}
if (type === "message") {
const [, messageID] = splits
setStore("messages", messageID, reconcile(data.content))
}
} catch (error) { } catch (error) {
console.error("Error parsing WebSocket message:", error) console.error("Error parsing WebSocket message:", error)
} }
@ -540,16 +548,15 @@ export default function Share(props: { api: string }) {
result.tokens.output += assistant.tokens.output result.tokens.output += assistant.tokens.output
result.tokens.reasoning += assistant.tokens.reasoning result.tokens.reasoning += assistant.tokens.reasoning
result.models.push([ result.models.push([assistant.providerID, assistant.modelID])
assistant.providerID,
assistant.modelID,
])
} }
} }
return result return result
}) })
const [showingSystemPrompt, showSystemPrompt] = createSignal(false) const [showingSystemPrompt, showSystemPrompt] = createSignal(false)
console.log(data())
return ( return (
<main class={`${styles.root} not-content`}> <main class={`${styles.root} not-content`}>
<div class={styles.header}> <div class={styles.header}>
@ -563,9 +570,9 @@ export default function Share(props: { api: string }) {
data().created || 0, data().created || 0,
).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)} ).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
> >
{DateTime.fromMillis( {DateTime.fromMillis(data().created || 0).toLocaleString(
data().created || 0, DateTime.DATE_MED,
).toLocaleString(DateTime.DATE_MED)} )}
</span> </span>
) : ( ) : (
<span data-element-label data-placeholder> <span data-element-label data-placeholder>
@ -575,7 +582,9 @@ export default function Share(props: { api: string }) {
</div> </div>
<p data-section="status"> <p data-section="status">
<span data-status={connectionStatus()[0]}>&#9679;</span> <span data-status={connectionStatus()[0]}>&#9679;</span>
<span data-element-label>{getStatusText(connectionStatus())}</span> <span data-element-label>
{getStatusText(connectionStatus())}
</span>
</p> </p>
</div> </div>
</div> </div>
@ -645,11 +654,9 @@ export default function Share(props: { api: string }) {
onClick={() => showSystemPrompt((e) => !e)} onClick={() => showSystemPrompt((e) => !e)}
> >
<span> <span>
{ {showingSystemPrompt()
showingSystemPrompt() ? "Hide system prompt"
? "Hide system prompt" : "Show system prompt"}
: "Show system prompt"
}
</span> </span>
<span data-button-icon> <span data-button-icon>
<Show <Show
@ -825,27 +832,40 @@ export default function Share(props: { api: string }) {
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => const metadata = createMemo(
msg.metadata?.tool[part().toolInvocation.toolCallId] () =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
const result = part().toolInvocation.state === "result" && part().toolInvocation.result const result =
part().toolInvocation.state === "result" &&
part().toolInvocation.result
const matches = metadata()?.matches const matches = metadata()?.matches
const { pattern, ...rest } = args const { pattern, ...rest } = args
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
<div data-section="part" data-part-type="tool-grep"> <div
data-section="part"
data-part-type="tool-grep"
>
<div data-section="decoration"> <div data-section="decoration">
<div title="Grep files"> <div title="Grep files">
<IconDocumentMagnifyingGlass <IconDocumentMagnifyingGlass
width={18} height={18} width={18}
height={18}
/> />
</div> </div>
<div></div> <div></div>
@ -873,13 +893,16 @@ export default function Share(props: { api: string }) {
<Match when={matches > 0}> <Match when={matches > 0}>
<div data-part-tool-result> <div data-part-tool-result>
<ResultsButton <ResultsButton
showCopy={matches === 1 showCopy={
? "1 match" matches === 1
: `${matches} matches` ? "1 match"
: `${matches} matches`
} }
hideCopy="Hide matches" hideCopy="Hide matches"
results={results()} results={results()}
onClick={() => showResults((e) => !e)} onClick={() =>
showResults((e) => !e)
}
/> />
<Show when={results()}> <Show when={results()}>
<TextPart <TextPart
@ -919,25 +942,40 @@ export default function Share(props: { api: string }) {
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => const metadata = createMemo(
msg.metadata?.tool[part().toolInvocation.toolCallId] () =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
const result = part().toolInvocation.state === "result" && part().toolInvocation.result const result =
part().toolInvocation.state === "result" &&
part().toolInvocation.result
const count = metadata()?.count const count = metadata()?.count
const pattern = args.pattern const pattern = args.pattern
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
<div data-section="part" data-part-type="tool-glob"> <div
data-section="part"
data-part-type="tool-glob"
>
<div data-section="decoration"> <div data-section="decoration">
<div title="Glob files"> <div title="Glob files">
<IconMagnifyingGlass width={18} height={18} /> <IconMagnifyingGlass
width={18}
height={18}
/>
</div> </div>
<div></div> <div></div>
</div> </div>
@ -951,12 +989,15 @@ export default function Share(props: { api: string }) {
<Match when={count > 0}> <Match when={count > 0}>
<div data-part-tool-result> <div data-part-tool-result>
<ResultsButton <ResultsButton
showCopy={count === 1 showCopy={
? "1 result" count === 1
: `${count} results` ? "1 result"
: `${count} results`
} }
results={results()} results={results()}
onClick={() => showResults((e) => !e)} onClick={() =>
showResults((e) => !e)
}
/> />
<Show when={results()}> <Show when={results()}>
<TextPart <TextPart
@ -996,23 +1037,36 @@ export default function Share(props: { api: string }) {
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => const metadata = createMemo(
msg.metadata?.tool[part().toolInvocation.toolCallId] () =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
const path = args.path const path = args.path
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
<div data-section="part" data-part-type="tool-list"> <div
data-section="part"
data-part-type="tool-list"
>
<div data-section="decoration"> <div data-section="decoration">
<div title="List files"> <div title="List files">
<IconRectangleStack width={18} height={18} /> <IconRectangleStack
width={18}
height={18}
/>
</div> </div>
<div></div> <div></div>
</div> </div>
@ -1026,21 +1080,25 @@ export default function Share(props: { api: string }) {
<Match <Match
when={ when={
part().toolInvocation.state === part().toolInvocation.state ===
"result" && "result" &&
part().toolInvocation.result part().toolInvocation.result
} }
> >
<div data-part-tool-result> <div data-part-tool-result>
<ResultsButton <ResultsButton
results={results()} results={results()}
onClick={() => showResults((e) => !e)} onClick={() =>
showResults((e) => !e)
}
/> />
<Show when={results()}> <Show when={results()}>
<TextPart <TextPart
expand expand
data-size="sm" data-size="sm"
data-color="dimmed" data-color="dimmed"
text={part().toolInvocation.result} text={
part().toolInvocation.result
}
/> />
</Show> </Show>
</div> </div>
@ -1063,21 +1121,35 @@ export default function Share(props: { api: string }) {
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) const metadata = createMemo(
() =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
)
const args = part().toolInvocation.args const args = part().toolInvocation.args
const filePath = args.filePath const filePath = args.filePath
const hasError = metadata()?.error const hasError = metadata()?.error
const preview = metadata()?.preview const preview = metadata()?.preview
const result = part().toolInvocation.state === "result" && part().toolInvocation.result const result =
part().toolInvocation.state === "result" &&
part().toolInvocation.result
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
<div data-section="part" data-part-type="tool-read"> <div
data-section="part"
data-part-type="tool-read"
>
<div data-section="decoration"> <div data-section="decoration">
<div title="Read file"> <div title="Read file">
<IconDocument width={18} height={18} /> <IconDocument width={18} height={18} />
@ -1107,7 +1179,9 @@ export default function Share(props: { api: string }) {
showCopy="Show preview" showCopy="Show preview"
hideCopy="Hide preview" hideCopy="Hide preview"
results={results()} results={results()}
onClick={() => showResults((e) => !e)} onClick={() =>
showResults((e) => !e)
}
/> />
<Show when={results()}> <Show when={results()}>
<div data-part-tool-code> <div data-part-tool-code>
@ -1123,7 +1197,9 @@ export default function Share(props: { api: string }) {
<div data-part-tool-result> <div data-part-tool-result>
<ResultsButton <ResultsButton
results={results()} results={results()}
onClick={() => showResults((e) => !e)} onClick={() =>
showResults((e) => !e)
}
/> />
<Show when={results()}> <Show when={results()}>
<TextPart <TextPart
@ -1153,21 +1229,35 @@ export default function Share(props: { api: string }) {
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) const metadata = createMemo(
() =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
)
const args = part().toolInvocation.args const args = part().toolInvocation.args
const filePath = args.filePath const filePath = args.filePath
const content = args.content const content = args.content
const hasError = metadata()?.error const hasError = metadata()?.error
const result = part().toolInvocation.state === "result" && part().toolInvocation.result const result =
part().toolInvocation.state === "result" &&
part().toolInvocation.result
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
<div data-section="part" data-part-type="tool-write"> <div
data-section="part"
data-part-type="tool-write"
>
<div data-section="decoration"> <div data-section="decoration">
<div title="Write file"> <div title="Write file">
<IconDocumentPlus width={18} height={18} /> <IconDocumentPlus width={18} height={18} />
@ -1197,7 +1287,9 @@ export default function Share(props: { api: string }) {
showCopy="Show contents" showCopy="Show contents"
hideCopy="Hide contents" hideCopy="Hide contents"
results={results()} results={results()}
onClick={() => showResults((e) => !e)} onClick={() =>
showResults((e) => !e)
}
/> />
<Show when={results()}> <Show when={results()}>
<div data-part-tool-code> <div data-part-tool-code>
@ -1227,14 +1319,23 @@ export default function Share(props: { api: string }) {
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) const metadata = createMemo(
() =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
)
const args = part().toolInvocation.args const args = part().toolInvocation.args
const filePath = args.filePath const filePath = args.filePath
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
@ -1278,17 +1379,29 @@ export default function Share(props: { api: string }) {
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) const metadata = createMemo(
() =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
)
const command = part().toolInvocation.args.command const command = part().toolInvocation.args.command
const desc = part().toolInvocation.args.description const desc = part().toolInvocation.args.description
const stdout = metadata()?.stdout const stdout = metadata()?.stdout
const result = stdout || (part().toolInvocation.state === "result" && part().toolInvocation.result) const result =
stdout ||
(part().toolInvocation.state === "result" &&
part().toolInvocation.result)
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
@ -1307,7 +1420,9 @@ export default function Share(props: { api: string }) {
<TerminalPart <TerminalPart
desc={desc} desc={desc}
data-size="sm" data-size="sm"
text={command + (result ? `\n${result}` : "")} text={
command + (result ? `\n${result}` : "")
}
/> />
</div> </div>
<ToolFooter time={duration()} /> <ToolFooter time={duration()} />
@ -1321,17 +1436,27 @@ export default function Share(props: { api: string }) {
when={ when={
msg.role === "assistant" && msg.role === "assistant" &&
part.type === "tool-invocation" && part.type === "tool-invocation" &&
part.toolInvocation.toolName === "opencode_todoread" && part.toolInvocation.toolName ===
"opencode_todoread" &&
part part
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) const metadata = createMemo(
() =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
)
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
@ -1362,24 +1487,39 @@ export default function Share(props: { api: string }) {
when={ when={
msg.role === "assistant" && msg.role === "assistant" &&
part.type === "tool-invocation" && part.type === "tool-invocation" &&
part.toolInvocation.toolName === "opencode_todowrite" && part.toolInvocation.toolName ===
"opencode_todowrite" &&
part part
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) const metadata = createMemo(
() =>
const todos = createMemo(() => sortTodosByStatus( msg.metadata?.tool[
part().toolInvocation.args.todos part().toolInvocation.toolCallId
)) ],
const starting = todos().every(t => t.status === "pending") )
const finished = todos().every(t => t.status === "completed")
const todos = createMemo(() =>
sortTodosByStatus(
part().toolInvocation.args.todos,
),
)
const starting = todos().every(
(t) => t.status === "pending",
)
const finished = todos().every(
(t) => t.status === "completed",
)
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
@ -1408,12 +1548,12 @@ export default function Share(props: { api: string }) {
<Show when={todos().length > 0}> <Show when={todos().length > 0}>
<ul class={styles.todos}> <ul class={styles.todos}>
<For each={todos()}> <For each={todos()}>
{({ status, content }) => {({ status, content }) => (
<li data-status={status}> <li data-status={status}>
<span></span> <span></span>
{content} {content}
</li> </li>
} )}
</For> </For>
</ul> </ul>
</Show> </Show>
@ -1429,26 +1569,41 @@ export default function Share(props: { api: string }) {
when={ when={
msg.role === "assistant" && msg.role === "assistant" &&
part.type === "tool-invocation" && part.type === "tool-invocation" &&
part.toolInvocation.toolName === "opencode_webfetch" && part.toolInvocation.toolName ===
"opencode_webfetch" &&
part part
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) const metadata = createMemo(
() =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
)
const args = part().toolInvocation.args const args = part().toolInvocation.args
const url = args.url const url = args.url
const format = args.format const format = args.format
const hasError = metadata()?.error const hasError = metadata()?.error
const result = part().toolInvocation.state === "result" && part().toolInvocation.result const result =
part().toolInvocation.state === "result" &&
part().toolInvocation.result
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
<div data-section="part" data-part-type="tool-fetch"> <div
data-section="part"
data-part-type="tool-fetch"
>
<div data-section="decoration"> <div data-section="decoration">
<div title="Web fetch"> <div title="Web fetch">
<IconGlobeAlt width={18} height={18} /> <IconGlobeAlt width={18} height={18} />
@ -1476,7 +1631,9 @@ export default function Share(props: { api: string }) {
<div data-part-tool-result> <div data-part-tool-result>
<ResultsButton <ResultsButton
results={results()} results={results()}
onClick={() => showResults((e) => !e)} onClick={() =>
showResults((e) => !e)
}
/> />
<Show when={results()}> <Show when={results()}>
<div data-part-tool-code> <div data-part-tool-code>
@ -1505,12 +1662,21 @@ export default function Share(props: { api: string }) {
} }
> >
{(part) => { {(part) => {
const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) const metadata = createMemo(
() =>
msg.metadata?.tool[
part().toolInvocation.toolCallId
],
)
const duration = createMemo(() => const duration = createMemo(() =>
DateTime.fromMillis(metadata()?.time.end || 0).diff( DateTime.fromMillis(metadata()?.time.end || 0)
DateTime.fromMillis(metadata()?.time.start || 0), .diff(
).toMillis(), DateTime.fromMillis(
metadata()?.time.start || 0,
),
)
.toMillis(),
) )
return ( return (
@ -1551,21 +1717,25 @@ export default function Share(props: { api: string }) {
<Match <Match
when={ when={
part().toolInvocation.state === part().toolInvocation.state ===
"result" && "result" &&
part().toolInvocation.result part().toolInvocation.result
} }
> >
<div data-part-tool-result> <div data-part-tool-result>
<ResultsButton <ResultsButton
results={results()} results={results()}
onClick={() => showResults((e) => !e)} onClick={() =>
showResults((e) => !e)
}
/> />
<Show when={results()}> <Show when={results()}>
<TextPart <TextPart
expand expand
data-size="sm" data-size="sm"
data-color="dimmed" data-color="dimmed"
text={part().toolInvocation.result} text={
part().toolInvocation.result
}
/> />
</Show> </Show>
</div> </div>

View file

@ -4,6 +4,12 @@ import config from "virtual:starlight/user-config";
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
import Share from "../../components/Share.tsx"; import Share from "../../components/Share.tsx";
const apiUrl = import.meta.env.VITE_API_URL;
const id = Astro.url.searchParams.get('id')
const res = await fetch(`${apiUrl}/share_messages?id=${id}`);
const data = await res.json();
--- ---
<StarlightPage <StarlightPage
@ -15,7 +21,7 @@ import Share from "../../components/Share.tsx";
tableOfContents: false, tableOfContents: false,
}} }}
> >
<Share api={import.meta.env.VITE_API_URL} client:only="solid" /> <Share api={apiUrl} data={data.messages} client:only="solid" />
</StarlightPage> </StarlightPage>
<style is:global> <style is:global>