From 1d782dc19aa523a8ae81a6c589036291124e8bd9 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 8 Jun 2025 01:17:54 -0400 Subject: [PATCH] Share: load server data on page load --- packages/function/src/api.ts | 21 ++ packages/web/src/components/Share.tsx | 432 ++++++++++++++++++-------- packages/web/src/pages/s/index.astro | 8 +- 3 files changed, 329 insertions(+), 132 deletions(-) diff --git a/packages/function/src/api.ts b/packages/function/src/api.ts index 3e2ce9ec..b058321c 100644 --- a/packages/function/src/api.ts +++ b/packages/function/src/api.ts @@ -69,6 +69,15 @@ export class SyncServer extends DurableObject { 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() { return this.ctx.storage.get("secret") } @@ -163,5 +172,17 @@ export default { const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id)) 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" }, + }) + } }, } diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index 6eb5003f..b292021c 100644 --- a/packages/web/src/components/Share.tsx +++ b/packages/web/src/components/Share.tsx @@ -139,12 +139,10 @@ function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> { entries.push([arrayPath, item]) } }) - } - else { + } else { entries.push(...flattenToolArgs(value, path)) } - } - else { + } else { entries.push([path, value]) } } @@ -360,7 +358,9 @@ function TerminalPart(props: TerminalPartProps) { {...rest} >
-
{local.desc}
+
+ {local.desc} +
MIN_DURATION - ? - {formatDuration(props.time)} - - :
+ return props.time > MIN_DURATION ? ( + + {formatDuration(props.time)} + + ) : ( +
) } -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) const id = params.get("id") const [store, setStore] = createStore<{ info?: SessionInfo messages: Record - }>({ - messages: {}, - }) + }>({ messages: {} }) const messages = createMemo(() => 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?] >(["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(() => { const apiUrl = props.api @@ -424,6 +438,10 @@ export default function Share(props: { api: string }) { return } + for (const datum of props.data) { + processDatum(datum) + } + let reconnectTimer: number | undefined let socket: WebSocket | null = null @@ -454,17 +472,7 @@ export default function Share(props: { api: string }) { socket.onmessage = (event) => { console.log("WebSocket message received") try { - const data = 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)) - } + processDatum(JSON.parse(event.data)) } catch (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.reasoning += assistant.tokens.reasoning - result.models.push([ - assistant.providerID, - assistant.modelID, - ]) + result.models.push([assistant.providerID, assistant.modelID]) } } return result }) const [showingSystemPrompt, showSystemPrompt] = createSignal(false) + console.log(data()) + return (
@@ -563,9 +570,9 @@ export default function Share(props: { api: string }) { data().created || 0, ).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)} > - {DateTime.fromMillis( - data().created || 0, - ).toLocaleString(DateTime.DATE_MED)} + {DateTime.fromMillis(data().created || 0).toLocaleString( + DateTime.DATE_MED, + )} ) : ( @@ -575,7 +582,9 @@ export default function Share(props: { api: string }) {

- {getStatusText(connectionStatus())} + + {getStatusText(connectionStatus())} +

@@ -645,11 +654,9 @@ export default function Share(props: { api: string }) { onClick={() => showSystemPrompt((e) => !e)} > - { - showingSystemPrompt() - ? "Hide system prompt" - : "Show system prompt" - } + {showingSystemPrompt() + ? "Hide system prompt" + : "Show system prompt"} {(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 result = part().toolInvocation.state === "result" && part().toolInvocation.result + const result = + part().toolInvocation.state === "result" && + part().toolInvocation.result const matches = metadata()?.matches const { pattern, ...rest } = args const duration = createMemo(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( -
+
@@ -873,13 +893,16 @@ export default function Share(props: { api: string }) { 0}>
showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> {(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 result = part().toolInvocation.state === "result" && part().toolInvocation.result + const result = + part().toolInvocation.state === "result" && + part().toolInvocation.result const count = metadata()?.count const pattern = args.pattern const duration = createMemo(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( -
+
- +
@@ -951,12 +989,15 @@ export default function Share(props: { api: string }) { 0}>
showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> {(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 path = args.path const duration = createMemo(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( -
+
- +
@@ -1026,21 +1080,25 @@ export default function Share(props: { api: string }) {
showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } />
@@ -1063,21 +1121,35 @@ export default function Share(props: { api: string }) { } > {(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 filePath = args.filePath const hasError = metadata()?.error 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(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( -
+
@@ -1107,7 +1179,9 @@ export default function Share(props: { api: string }) { showCopy="Show preview" hideCopy="Hide preview" results={results()} - onClick={() => showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } />
@@ -1123,7 +1197,9 @@ export default function Share(props: { api: string }) {
showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> {(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 filePath = args.filePath const content = args.content 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(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( -
+
@@ -1197,7 +1287,9 @@ export default function Share(props: { api: string }) { showCopy="Show contents" hideCopy="Hide contents" results={results()} - onClick={() => showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } />
@@ -1227,14 +1319,23 @@ export default function Share(props: { api: string }) { } > {(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 filePath = args.filePath const duration = createMemo(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( @@ -1278,17 +1379,29 @@ export default function Share(props: { api: string }) { } > {(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 desc = part().toolInvocation.args.description 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(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( @@ -1307,7 +1420,9 @@ export default function Share(props: { api: string }) {
@@ -1321,17 +1436,27 @@ export default function Share(props: { api: string }) { when={ msg.role === "assistant" && part.type === "tool-invocation" && - part.toolInvocation.toolName === "opencode_todoread" && + part.toolInvocation.toolName === + "opencode_todoread" && part } > {(part) => { - const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) + const metadata = createMemo( + () => + msg.metadata?.tool[ + part().toolInvocation.toolCallId + ], + ) const duration = createMemo(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( @@ -1362,24 +1487,39 @@ export default function Share(props: { api: string }) { when={ msg.role === "assistant" && part.type === "tool-invocation" && - part.toolInvocation.toolName === "opencode_todowrite" && + part.toolInvocation.toolName === + "opencode_todowrite" && part } > {(part) => { - const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) - - 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 metadata = createMemo( + () => + msg.metadata?.tool[ + part().toolInvocation.toolCallId + ], + ) + 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(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( @@ -1408,12 +1548,12 @@ export default function Share(props: { api: string }) { 0}>
    - {({ status, content }) => + {({ status, content }) => (
  • {content}
  • - } + )}
@@ -1429,26 +1569,41 @@ export default function Share(props: { api: string }) { when={ msg.role === "assistant" && part.type === "tool-invocation" && - part.toolInvocation.toolName === "opencode_webfetch" && + part.toolInvocation.toolName === + "opencode_webfetch" && 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 url = args.url const format = args.format 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(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( -
+
@@ -1476,7 +1631,9 @@ export default function Share(props: { api: string }) {
showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } />
@@ -1505,12 +1662,21 @@ export default function Share(props: { api: string }) { } > {(part) => { - const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) + const metadata = createMemo( + () => + msg.metadata?.tool[ + part().toolInvocation.toolCallId + ], + ) const duration = createMemo(() => - DateTime.fromMillis(metadata()?.time.end || 0).diff( - DateTime.fromMillis(metadata()?.time.start || 0), - ).toMillis(), + DateTime.fromMillis(metadata()?.time.end || 0) + .diff( + DateTime.fromMillis( + metadata()?.time.start || 0, + ), + ) + .toMillis(), ) return ( @@ -1551,21 +1717,25 @@ export default function Share(props: { api: string }) {
showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } />
diff --git a/packages/web/src/pages/s/index.astro b/packages/web/src/pages/s/index.astro index b678c0db..e4c5c5df 100644 --- a/packages/web/src/pages/s/index.astro +++ b/packages/web/src/pages/s/index.astro @@ -4,6 +4,12 @@ import config from "virtual:starlight/user-config"; import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; 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(); + --- - +