This commit is contained in:
Frank 2025-06-09 23:07:29 -04:00
parent fdaa7f287c
commit 177875f624
3 changed files with 66 additions and 56 deletions

View file

@ -69,7 +69,7 @@ export class SyncServer extends DurableObject<Env> {
return secret return secret
} }
public async messages() { public async getData() {
const data = await this.ctx.storage.list() const data = await this.ctx.storage.list()
const messages = [] const messages = []
for (const [key, content] of data.entries()) { for (const [key, content] of data.entries()) {
@ -173,14 +173,29 @@ export default {
return stub.fetch(request) return stub.fetch(request)
} }
if (request.method === "GET" && method === "share_messages") { if (request.method === "GET" && method === "share_data") {
const id = url.searchParams.get("id") const id = url.searchParams.get("id")
console.log("share_messages", id) console.log("share_data", id)
if (!id) if (!id)
return new Response("Error: Share ID is required", { status: 400 }) return new Response("Error: Share ID is required", { status: 400 })
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id)) const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
const messages = await stub.messages() const data = await stub.getData()
return new Response(JSON.stringify({ messages }), { let info
const messages = {}
data.forEach((d) => {
const [root, type, ...splits] = d.key.split("/")
if (root !== "session") return
if (type === "info") {
info = d.content
return
}
if (type === "message") {
const [, messageID] = splits
messages[messageID] = d.content
}
})
return new Response(JSON.stringify({ info, messages }), {
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}) })
} }

View file

@ -156,23 +156,23 @@ function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
* "ERROR [65:20] Property 'x' does not exist on type 'Y'" * "ERROR [65:20] Property 'x' does not exist on type 'Y'"
*/ */
export function getDiagnostics( export function getDiagnostics(
diagnosticsByFile: Record<string, Diagnostic[]> diagnosticsByFile: Record<string, Diagnostic[]>,
): string[] { ): string[] {
const result: string[] = []; const result: string[] = []
for (const diags of Object.values(diagnosticsByFile)) { for (const diags of Object.values(diagnosticsByFile)) {
for (const d of diags) { for (const d of diags) {
// Only keep diagnostics explicitly marked as Error (severity === 1) // Only keep diagnostics explicitly marked as Error (severity === 1)
if (d.severity !== 1) continue; if (d.severity !== 1) continue
const line = d.range.start.line + 1; // 1-based const line = d.range.start.line + 1 // 1-based
const column = d.range.start.character + 1; // 1-based const column = d.range.start.character + 1 // 1-based
result.push(`ERROR [${line}:${column}] ${d.message}`); result.push(`ERROR [${line}:${column}] ${d.message}`)
} }
} }
return result; return result
} }
function stripEnclosingTag(text: string): string { function stripEnclosingTag(text: string): string {
@ -426,7 +426,8 @@ function ToolFooter(props: { time: number }) {
export default function Share(props: { export default function Share(props: {
api: string api: string
data: { key: string; content: SessionMessage | SessionInfo }[] info: SessionInfo
messages: Record<string, SessionMessage>
}) { }) {
let params = new URLSearchParams(document.location.search) let params = new URLSearchParams(document.location.search)
const id = params.get("id") const id = params.get("id")
@ -434,7 +435,7 @@ export default function Share(props: {
const [store, setStore] = createStore<{ const [store, setStore] = createStore<{
info?: SessionInfo info?: SessionInfo
messages: Record<string, SessionMessage> messages: Record<string, SessionMessage>
}>({ messages: {} }) }>({ info: props.info, messages: props.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)),
) )
@ -442,19 +443,6 @@ export default function Share(props: {
[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
@ -469,10 +457,6 @@ export default function Share(props: {
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
@ -503,7 +487,17 @@ export default function Share(props: {
socket.onmessage = (event) => { socket.onmessage = (event) => {
console.log("WebSocket message received") console.log("WebSocket message received")
try { try {
processDatum(JSON.parse(event.data)) const d = JSON.parse(event.data)
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))
}
} catch (error) { } catch (error) {
console.error("Error parsing WebSocket message:", error) console.error("Error parsing WebSocket message:", error)
} }
@ -579,7 +573,10 @@ export default function Share(props: {
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[`${assistant.providerID} ${assistant.modelID}`] = [assistant.providerID, assistant.modelID] result.models[`${assistant.providerID} ${assistant.modelID}`] = [
assistant.providerID,
assistant.modelID,
]
} }
} }
return result return result
@ -864,7 +861,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
@ -974,7 +971,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
@ -1069,7 +1066,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
@ -1109,7 +1106,7 @@ export default function Share(props: {
<Match <Match
when={ when={
part().toolInvocation.state === part().toolInvocation.state ===
"result" && "result" &&
part().toolInvocation.result part().toolInvocation.result
} }
> >
@ -1153,7 +1150,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
@ -1261,7 +1258,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
@ -1272,7 +1269,7 @@ export default function Share(props: {
part().toolInvocation.state === "result" && part().toolInvocation.state === "result" &&
part().toolInvocation.result part().toolInvocation.result
const diagnostics = createMemo(() => const diagnostics = createMemo(() =>
getDiagnostics(metadata()?.diagnostics) getDiagnostics(metadata()?.diagnostics),
) )
const duration = createMemo(() => const duration = createMemo(() =>
@ -1358,13 +1355,13 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
const filePath = args.filePath const filePath = args.filePath
const diagnostics = createMemo(() => const diagnostics = createMemo(() =>
getDiagnostics(metadata()?.diagnostics) getDiagnostics(metadata()?.diagnostics),
) )
const duration = createMemo(() => const duration = createMemo(() =>
@ -1425,7 +1422,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
@ -1480,7 +1477,7 @@ export default function Share(props: {
msg.role === "assistant" && msg.role === "assistant" &&
part.type === "tool-invocation" && part.type === "tool-invocation" &&
part.toolInvocation.toolName === part.toolInvocation.toolName ===
"opencode_todoread" && "opencode_todoread" &&
part part
} }
> >
@ -1488,7 +1485,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
@ -1531,7 +1528,7 @@ export default function Share(props: {
msg.role === "assistant" && msg.role === "assistant" &&
part.type === "tool-invocation" && part.type === "tool-invocation" &&
part.toolInvocation.toolName === part.toolInvocation.toolName ===
"opencode_todowrite" && "opencode_todowrite" &&
part part
} }
> >
@ -1539,7 +1536,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
@ -1613,7 +1610,7 @@ export default function Share(props: {
msg.role === "assistant" && msg.role === "assistant" &&
part.type === "tool-invocation" && part.type === "tool-invocation" &&
part.toolInvocation.toolName === part.toolInvocation.toolName ===
"opencode_webfetch" && "opencode_webfetch" &&
part part
} }
> >
@ -1621,7 +1618,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
const args = part().toolInvocation.args const args = part().toolInvocation.args
@ -1708,7 +1705,7 @@ export default function Share(props: {
const metadata = createMemo( const metadata = createMemo(
() => () =>
msg.metadata?.tool[ msg.metadata?.tool[
part().toolInvocation.toolCallId part().toolInvocation.toolCallId
], ],
) )
@ -1760,7 +1757,7 @@ export default function Share(props: {
<Match <Match
when={ when={
part().toolInvocation.state === part().toolInvocation.state ===
"result" && "result" &&
part().toolInvocation.result part().toolInvocation.result
} }
> >

View file

@ -8,12 +8,10 @@ import Share from "../../components/Share.tsx";
const apiUrl = import.meta.env.VITE_API_URL; const apiUrl = import.meta.env.VITE_API_URL;
const id = Astro.url.searchParams.get('id') const id = Astro.url.searchParams.get('id')
const res = await fetch(`${apiUrl}/share_messages?id=${id}`); const res = await fetch(`${apiUrl}/share_data?id=${id}`);
const data = await res.json(); const data = await res.json();
console.log(data.info) const title = data.info.title;
const title = "Share";
const encodedTitle = encodeURIComponent( const encodedTitle = encodeURIComponent(
Base64.encode( Base64.encode(
@ -58,7 +56,7 @@ const ogImageUrl = `${cardService}/opencode-share/${encodedTitle}.png?cost=${cos
], ],
}} }}
> >
<Share api={apiUrl} data={data.messages} client:only="solid" /> <Share api={apiUrl} info={data.info} messages={data.messages} client:only="solid" />
</StarlightPage> </StarlightPage>
<style is:global> <style is:global>