diff options
| author | Frank <[email protected]> | 2025-06-18 22:26:51 -0400 |
|---|---|---|
| committer | Frank <[email protected]> | 2025-06-18 22:26:51 -0400 |
| commit | 1e8a681de923518e3828306c4897e72ac803b6dd (patch) | |
| tree | be734f89621691381237816714b6d98650ca66b1 | |
| parent | a834bedc176140990d8d457f633e6d9c47aaf886 (diff) | |
| download | opencode-1e8a681de923518e3828306c4897e72ac803b6dd.tar.gz opencode-1e8a681de923518e3828306c4897e72ac803b6dd.zip | |
Render version
| -rw-r--r-- | packages/web/src/components/Share.tsx | 235 | ||||
| -rw-r--r-- | packages/web/src/pages/s/[id].astro | 2 |
2 files changed, 143 insertions, 94 deletions
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index e91048047..a76de4fcb 100644 --- a/packages/web/src/components/Share.tsx +++ b/packages/web/src/components/Share.tsx @@ -18,7 +18,7 @@ import { IconOpenAI, IconGemini, IconOpencode, - IconAnthropic + IconAnthropic, } from "./icons/custom" import { IconFolder, @@ -87,10 +87,10 @@ function scrollToAnchor(id: string) { function stripWorkingDirectory(filePath: string, workingDir?: string) { if (workingDir === undefined) return filePath - const prefix = workingDir.endsWith('/') ? workingDir : workingDir + '/' + const prefix = workingDir.endsWith("/") ? workingDir : workingDir + "/" if (filePath === workingDir) { - return '' + return "" } if (filePath.startsWith(prefix)) { @@ -162,21 +162,25 @@ function formatErrorString(error: string): JSX.Element { <span>{error.slice(errorMarker.length)}</span> </pre> ) : ( - <pre><span data-color="dimmed">{error}</span></pre> + <pre> + <span data-color="dimmed">{error}</span> + </pre> ) } function getDiagnostics( diagnosticsByFile: Record<string, Diagnostic[]>, - currentFile: string + currentFile: string, ): JSX.Element[] { // Return a flat array of error diagnostics, in the format: // "Error [65:20] Property 'x' does not exist on type 'Y'" const result: JSX.Element[] = [] if ( - diagnosticsByFile === undefined || diagnosticsByFile[currentFile] === undefined - ) return result + diagnosticsByFile === undefined || + diagnosticsByFile[currentFile] === undefined + ) + return result for (const diags of Object.values(diagnosticsByFile)) { for (const d of diags) { @@ -188,12 +192,14 @@ function getDiagnostics( result.push( <pre> - <span data-color="red" data-marker="label">Error</span> + <span data-color="red" data-marker="label"> + Error + </span> <span data-color="dimmed" data-separator> [{line}:{column}] </span> <span>{d.message}</span> - </pre> + </pre>, ) } } @@ -279,7 +285,12 @@ interface TextPartProps extends JSX.HTMLAttributes<HTMLDivElement> { highlight?: boolean } function TextPart(props: TextPartProps) { - const [local, rest] = splitProps(props, ["text", "expand", "invert", "highlight"]) + const [local, rest] = splitProps(props, [ + "text", + "expand", + "invert", + "highlight", + ]) const [expanded, setExpanded] = createSignal(false) const [overflowed, setOverflowed] = createSignal(false) let preEl: HTMLPreElement | undefined @@ -440,7 +451,12 @@ interface TerminalPartProps extends JSX.HTMLAttributes<HTMLDivElement> { expand?: boolean } function TerminalPart(props: TerminalPartProps) { - const [local, rest] = splitProps(props, ["command", "result", "desc", "expand"]) + const [local, rest] = splitProps(props, [ + "command", + "result", + "desc", + "expand", + ]) const [expanded, setExpanded] = createSignal(false) const [overflowed, setOverflowed] = createSignal(false) let preEl: HTMLElement | undefined @@ -522,7 +538,7 @@ function AnchorIcon(props: AnchorProps) { > <a href={`#${local.id}`} - onClick={e => { + onClick={(e) => { e.preventDefault() const anchor = e.currentTarget @@ -569,7 +585,7 @@ export default function Share(props: { messages: Record<string, Message.Info> }>({ info: props.info, messages: props.messages }) 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)), ) const [connectionStatus, setConnectionStatus] = createSignal< [Status, string?] @@ -785,7 +801,7 @@ export default function Share(props: { <div data-stat-icon title="opencode"> <IconOpencode width={16} height={16} /> </div> - <span>v0.1.1</span> + <span>v${store.info?.version}</span> </li> </ul> </Show> @@ -811,12 +827,12 @@ export default function Share(props: { <div data-section="time"> {data().created ? ( <span - title={DateTime.fromMillis( - data().created || 0, - ).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)} + title={DateTime.fromMillis(data().created || 0).toLocaleString( + DateTime.DATETIME_FULL_WITH_SECONDS, + )} > {DateTime.fromMillis(data().created || 0).toLocaleString( - DateTime.DATETIME_MED + DateTime.DATETIME_MED, )} </span> ) : ( @@ -838,14 +854,13 @@ export default function Share(props: { {(msg, msgIndex) => ( <For each={msg.parts}> {(part, partIndex) => { - if (( - part.type === "step-start" && - (partIndex() > 0 || !msg.metadata?.assistant) - ) || ( - msg.role === "assistant" && + if ( + (part.type === "step-start" && + (partIndex() > 0 || !msg.metadata?.assistant)) || + (msg.role === "assistant" && part.type === "tool-invocation" && - part.toolInvocation.toolName === "opencode_todoread" - )) + part.toolInvocation.toolName === "opencode_todoread") + ) return null const anchor = createMemo(() => `${msg.id}-${partIndex()}`) @@ -857,14 +872,20 @@ export default function Share(props: { ) const toolData = createMemo(() => { if ( - msg.role !== "assistant" || part.type !== "tool-invocation" - ) return {} + msg.role !== "assistant" || + part.type !== "tool-invocation" + ) + return {} - const metadata = msg.metadata?.tool[part.toolInvocation.toolCallId] + const metadata = + 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 duration = DateTime.fromMillis( - metadata?.time.end || 0) + metadata?.time.end || 0, + ) .diff(DateTime.fromMillis(metadata?.time.start || 0)) .toMillis() @@ -927,14 +948,17 @@ export default function Share(props: { text={stripEnclosingTag(part().text)} /> <Show when={isLastPart() && data().completed}> - <span data-part-footer + <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 + ).toLocaleString( + DateTime.DATETIME_FULL_WITH_SECONDS, )} + > + {DateTime.fromMillis( + data().completed || 0, + ).toLocaleString(DateTime.DATETIME_MED)} </span> </Show> </div> @@ -953,7 +977,8 @@ export default function Share(props: { const system = createMemo(() => { const prompts = assistant().system || [] return prompts.filter( - (p: string) => !p.startsWith("You are Claude Code") + (p: string) => + !p.startsWith("You are Claude Code"), ) }) return ( @@ -1079,13 +1104,17 @@ export default function Share(props: { <span data-element-label>Grep</span> <b>“{splitArgs().pattern}”</b> </div> - <Show when={ - Object.keys(splitArgs().rest).length > 0 - }> + <Show + when={ + Object.keys(splitArgs().rest).length > 0 + } + > <div data-part-tool-args> - <For each={ - flattenToolArgs(splitArgs().rest) - }> + <For + each={flattenToolArgs( + splitArgs().rest, + )} + > {([name, value]) => ( <> <div></div> @@ -1133,7 +1162,9 @@ export default function Share(props: { </Match> </Switch> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1209,7 +1240,9 @@ export default function Share(props: { </Match> </Switch> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1225,13 +1258,13 @@ export default function Share(props: { } > {(_part) => { - const path = createMemo( - () => toolData()?.args.path !== data().rootDir + const path = createMemo(() => + toolData()?.args.path !== data().rootDir ? stripWorkingDirectory( - toolData()?.args.path, - data().rootDir - ) - : toolData()?.args.path + toolData()?.args.path, + data().rootDir, + ) + : toolData()?.args.path, ) return ( @@ -1276,7 +1309,9 @@ export default function Share(props: { </Match> </Switch> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1292,11 +1327,11 @@ export default function Share(props: { } > {(_part) => { - const filePath = createMemo( - () => stripWorkingDirectory( + const filePath = createMemo(() => + stripWorkingDirectory( toolData()?.args.filePath, - data().rootDir - ) + data().rootDir, + ), ) const hasError = () => toolData()?.metadata?.error const preview = () => toolData()?.metadata?.preview @@ -1323,7 +1358,9 @@ export default function Share(props: { <Match when={hasError()}> <div data-part-tool-result> <ErrorPart> - {formatErrorString(toolData()?.result)} + {formatErrorString( + toolData()?.result, + )} </ErrorPart> </div> </Match> @@ -1367,7 +1404,9 @@ export default function Share(props: { </Match> </Switch> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1383,19 +1422,19 @@ export default function Share(props: { } > {(_part) => { - const filePath = createMemo( - () => stripWorkingDirectory( + const filePath = createMemo(() => + stripWorkingDirectory( toolData()?.args.filePath, - data().rootDir - ) + data().rootDir, + ), ) const hasError = () => toolData()?.metadata?.error const content = () => toolData()?.args?.content const diagnostics = createMemo(() => getDiagnostics( toolData()?.metadata?.diagnostics, - toolData()?.args.filePath - ) + toolData()?.args.filePath, + ), ) return ( @@ -1423,7 +1462,9 @@ export default function Share(props: { <Match when={hasError()}> <div data-part-tool-result> <ErrorPart> - {formatErrorString(toolData()?.result)} + {formatErrorString( + toolData()?.result, + )} </ErrorPart> </div> </Match> @@ -1449,7 +1490,9 @@ export default function Share(props: { </Match> </Switch> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1468,17 +1511,17 @@ export default function Share(props: { const diff = () => toolData()?.metadata?.diff const message = () => toolData()?.metadata?.message const hasError = () => toolData()?.metadata?.error - const filePath = createMemo( - () => stripWorkingDirectory( + const filePath = createMemo(() => + stripWorkingDirectory( toolData()?.args.filePath, - data().rootDir - ) + data().rootDir, + ), ) const diagnostics = createMemo(() => getDiagnostics( toolData()?.metadata?.diagnostics, - toolData()?.args.filePath - ) + toolData()?.args.filePath, + ), ) return ( @@ -1521,7 +1564,9 @@ export default function Share(props: { <ErrorPart>{diagnostics()}</ErrorPart> </Show> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1561,7 +1606,9 @@ export default function Share(props: { result={toolData()?.result} /> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1573,20 +1620,18 @@ export default function Share(props: { msg.role === "assistant" && part.type === "tool-invocation" && part.toolInvocation.toolName === - "opencode_todowrite" && + "opencode_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" + 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 @@ -1627,7 +1672,9 @@ export default function Share(props: { </ul> </Show> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1639,7 +1686,7 @@ export default function Share(props: { msg.role === "assistant" && part.type === "tool-invocation" && part.toolInvocation.toolName === - "opencode_webfetch" && + "opencode_webfetch" && part } > @@ -1670,7 +1717,9 @@ export default function Share(props: { <Match when={hasError()}> <div data-part-tool-result> <ErrorPart> - {formatErrorString(toolData()?.result)} + {formatErrorString( + toolData()?.result, + )} </ErrorPart> </div> </Match> @@ -1694,7 +1743,9 @@ export default function Share(props: { </Match> </Switch> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1776,7 +1827,9 @@ export default function Share(props: { </Match> </Switch> </div> - <ToolFooter time={toolData()?.duration || 0} /> + <ToolFooter + time={toolData()?.duration || 0} + /> </div> </div> ) @@ -1820,9 +1873,7 @@ export default function Share(props: { <div data-section="content"> <div data-part-tool-body> <div data-part-title> - <span data-element-label> - {part.type} - </span> + <span data-element-label>{part.type}</span> </div> <TextPart text={JSON.stringify(part, null, 2)} @@ -1843,9 +1894,7 @@ export default function Share(props: { <div></div> </div> <div data-section="content"> - <span> - {getStatusText(connectionStatus())} - </span> + <span>{getStatusText(connectionStatus())}</span> </div> </div> </div> diff --git a/packages/web/src/pages/s/[id].astro b/packages/web/src/pages/s/[id].astro index 2b7cc8a95..62f0c39d1 100644 --- a/packages/web/src/pages/s/[id].astro +++ b/packages/web/src/pages/s/[id].astro @@ -13,7 +13,7 @@ const data = await res.json(); let cost = 0; const models: Set<string> = new Set(); -const version = "v0.1.1"; +const version = `v${data.info.version}`; Object.values(data.messages).forEach((d) => { const assistant = d.metadata?.assistant; if (assistant) { |
