diff options
| author | Frank <[email protected]> | 2025-06-08 01:17:54 -0400 |
|---|---|---|
| committer | Frank <[email protected]> | 2025-06-08 01:17:54 -0400 |
| commit | 1d782dc19aa523a8ae81a6c589036291124e8bd9 (patch) | |
| tree | 05d9bf73f1bd4b7c2fe44db3533882ff86cdafab /packages/web/src/components | |
| parent | 879d02f86c2c45860f064611b643ed1d2af4de0a (diff) | |
| download | opencode-1d782dc19aa523a8ae81a6c589036291124e8bd9.tar.gz opencode-1d782dc19aa523a8ae81a6c589036291124e8bd9.zip | |
Share: load server data on page load
Diffstat (limited to 'packages/web/src/components')
| -rw-r--r-- | packages/web/src/components/Share.tsx | 432 |
1 files changed, 301 insertions, 131 deletions
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index 6eb5003fb..b292021c0 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} > <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"> <CodeBlock lang="ansi" @@ -384,25 +384,26 @@ function TerminalPart(props: TerminalPartProps) { } function ToolFooter(props: { time: number }) { - return ( - props.time > MIN_DURATION - ? <span data-part-footer title={`${props.time}ms`}> - {formatDuration(props.time)} - </span> - : <div data-part-footer="spacer"></div> + return props.time > MIN_DURATION ? ( + <span data-part-footer title={`${props.time}ms`}> + {formatDuration(props.time)} + </span> + ) : ( + <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) const id = params.get("id") const [store, setStore] = createStore<{ info?: SessionInfo messages: Record<string, SessionMessage> - }>({ - 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 ( <main class={`${styles.root} not-content`}> <div class={styles.header}> @@ -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, + )} </span> ) : ( <span data-element-label data-placeholder> @@ -575,7 +582,9 @@ export default function Share(props: { api: string }) { </div> <p data-section="status"> <span data-status={connectionStatus()[0]}>●</span> - <span data-element-label>{getStatusText(connectionStatus())}</span> + <span data-element-label> + {getStatusText(connectionStatus())} + </span> </p> </div> </div> @@ -645,11 +654,9 @@ export default function Share(props: { api: string }) { onClick={() => showSystemPrompt((e) => !e)} > <span> - { - showingSystemPrompt() - ? "Hide system prompt" - : "Show system prompt" - } + {showingSystemPrompt() + ? "Hide system prompt" + : "Show system prompt"} </span> <span data-button-icon> <Show @@ -825,27 +832,40 @@ 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 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 ( - <div data-section="part" data-part-type="tool-grep"> + <div + data-section="part" + data-part-type="tool-grep" + > <div data-section="decoration"> <div title="Grep files"> <IconDocumentMagnifyingGlass - width={18} height={18} + width={18} + height={18} /> </div> <div></div> @@ -873,13 +893,16 @@ export default function Share(props: { api: string }) { <Match when={matches > 0}> <div data-part-tool-result> <ResultsButton - showCopy={matches === 1 - ? "1 match" - : `${matches} matches` + showCopy={ + matches === 1 + ? "1 match" + : `${matches} matches` } hideCopy="Hide matches" results={results()} - onClick={() => showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> <Show when={results()}> <TextPart @@ -919,25 +942,40 @@ 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 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 ( - <div data-section="part" data-part-type="tool-glob"> + <div + data-section="part" + data-part-type="tool-glob" + > <div data-section="decoration"> <div title="Glob files"> - <IconMagnifyingGlass width={18} height={18} /> + <IconMagnifyingGlass + width={18} + height={18} + /> </div> <div></div> </div> @@ -951,12 +989,15 @@ export default function Share(props: { api: string }) { <Match when={count > 0}> <div data-part-tool-result> <ResultsButton - showCopy={count === 1 - ? "1 result" - : `${count} results` + showCopy={ + count === 1 + ? "1 result" + : `${count} results` } results={results()} - onClick={() => showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> <Show when={results()}> <TextPart @@ -996,23 +1037,36 @@ 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 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 ( - <div data-section="part" data-part-type="tool-list"> + <div + data-section="part" + data-part-type="tool-list" + > <div data-section="decoration"> <div title="List files"> - <IconRectangleStack width={18} height={18} /> + <IconRectangleStack + width={18} + height={18} + /> </div> <div></div> </div> @@ -1026,21 +1080,25 @@ export default function Share(props: { api: string }) { <Match when={ part().toolInvocation.state === - "result" && + "result" && part().toolInvocation.result } > <div data-part-tool-result> <ResultsButton results={results()} - onClick={() => showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> <Show when={results()}> <TextPart expand data-size="sm" data-color="dimmed" - text={part().toolInvocation.result} + text={ + part().toolInvocation.result + } /> </Show> </div> @@ -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 ( - <div data-section="part" data-part-type="tool-read"> + <div + data-section="part" + data-part-type="tool-read" + > <div data-section="decoration"> <div title="Read file"> <IconDocument width={18} height={18} /> @@ -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) + } /> <Show when={results()}> <div data-part-tool-code> @@ -1123,7 +1197,9 @@ export default function Share(props: { api: string }) { <div data-part-tool-result> <ResultsButton results={results()} - onClick={() => showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> <Show when={results()}> <TextPart @@ -1153,21 +1229,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 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 ( - <div data-section="part" data-part-type="tool-write"> + <div + data-section="part" + data-part-type="tool-write" + > <div data-section="decoration"> <div title="Write file"> <IconDocumentPlus width={18} height={18} /> @@ -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) + } /> <Show when={results()}> <div data-part-tool-code> @@ -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 }) { <TerminalPart desc={desc} data-size="sm" - text={command + (result ? `\n${result}` : "")} + text={ + command + (result ? `\n${result}` : "") + } /> </div> <ToolFooter time={duration()} /> @@ -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 }) { <Show when={todos().length > 0}> <ul class={styles.todos}> <For each={todos()}> - {({ status, content }) => + {({ status, content }) => ( <li data-status={status}> <span></span> {content} </li> - } + )} </For> </ul> </Show> @@ -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 ( - <div data-section="part" data-part-type="tool-fetch"> + <div + data-section="part" + data-part-type="tool-fetch" + > <div data-section="decoration"> <div title="Web fetch"> <IconGlobeAlt width={18} height={18} /> @@ -1476,7 +1631,9 @@ export default function Share(props: { api: string }) { <div data-part-tool-result> <ResultsButton results={results()} - onClick={() => showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> <Show when={results()}> <div data-part-tool-code> @@ -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 }) { <Match when={ part().toolInvocation.state === - "result" && + "result" && part().toolInvocation.result } > <div data-part-tool-result> <ResultsButton results={results()} - onClick={() => showResults((e) => !e)} + onClick={() => + showResults((e) => !e) + } /> <Show when={results()}> <TextPart expand data-size="sm" data-color="dimmed" - text={part().toolInvocation.result} + text={ + part().toolInvocation.result + } /> </Show> </div> |
