summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJay V <[email protected]>2025-07-11 14:24:18 -0400
committerJay V <[email protected]>2025-07-11 14:24:20 -0400
commit2f1acee5a12b3e60b66cd337690d46f0e36b46be (patch)
tree37996106dca0a0b8c46ff30da11a6b9df79f48c4
parent9ca54020acc402472cfd76fe5ed65d1564743761 (diff)
downloadopencode-2f1acee5a12b3e60b66cd337690d46f0e36b46be.tar.gz
opencode-2f1acee5a12b3e60b66cd337690d46f0e36b46be.zip
docs: share page add time footer back
-rw-r--r--packages/web/src/components/Share.tsx1
-rw-r--r--packages/web/src/components/share/common.tsx17
-rw-r--r--packages/web/src/components/share/part.module.css6
-rw-r--r--packages/web/src/components/share/part.tsx190
4 files changed, 128 insertions, 86 deletions
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index 53a249d5b..84e0a1ffb 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -364,7 +364,6 @@ export default function Share(props: {
<div data-section="part" data-part-type="summary">
<div data-section="decoration">
<span data-status={connectionStatus()[0]}></span>
- <div></div>
</div>
<div data-section="content">
<p data-section="copy">{getStatusText(connectionStatus())}</p>
diff --git a/packages/web/src/components/share/common.tsx b/packages/web/src/components/share/common.tsx
index 9f5221de9..cab2dbdb0 100644
--- a/packages/web/src/components/share/common.tsx
+++ b/packages/web/src/components/share/common.tsx
@@ -58,3 +58,20 @@ export function createOverflow() {
},
}
}
+
+export function formatDuration(ms: number): string {
+ const ONE_SECOND = 1000
+ const ONE_MINUTE = 60 * ONE_SECOND
+
+ if (ms >= ONE_MINUTE) {
+ const minutes = Math.floor(ms / ONE_MINUTE)
+ return minutes === 1 ? `1min` : `${minutes}mins`
+ }
+
+ if (ms >= ONE_SECOND) {
+ const seconds = Math.floor(ms / ONE_SECOND)
+ return `${seconds}s`
+ }
+
+ return `${ms}ms`
+}
diff --git a/packages/web/src/components/share/part.module.css b/packages/web/src/components/share/part.module.css
index df8102f5d..5ffb83f6b 100644
--- a/packages/web/src/components/share/part.module.css
+++ b/packages/web/src/components/share/part.module.css
@@ -101,7 +101,12 @@
}
[data-component="content"] {
+ flex: 1 1 auto;
min-width: 0;
+ padding: 0 0 0.375rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
}
[data-component="spacer"] {
@@ -209,7 +214,6 @@
flex-direction: column;
align-items: flex-start;
gap: 0.375rem;
- padding-bottom: 1rem;
&[data-tool="bash"] {
max-width: var(--sm-tool-width);
diff --git a/packages/web/src/components/share/part.tsx b/packages/web/src/components/share/part.tsx
index e155332ad..443e4a795 100644
--- a/packages/web/src/components/share/part.tsx
+++ b/packages/web/src/components/share/part.tsx
@@ -20,6 +20,7 @@ import {
IconDocumentMagnifyingGlass,
} from "../icons"
import { IconMeta, IconOpenAI, IconGemini, IconAnthropic } from "../icons/custom"
+import { formatDuration } from "../share/common"
import { ContentCode } from "./content-code"
import { ContentDiff } from "./content-diff"
import { ContentText } from "./content-text"
@@ -31,6 +32,8 @@ import type { Diagnostic } from "vscode-languageserver-types"
import styles from "./part.module.css"
+const MIN_DURATION = 2
+
export interface PartProps {
index: number
message: MessageV2.Info
@@ -161,95 +164,104 @@ export function Part(props: PartProps) {
{props.part.type === "tool" && props.part.state.status === "error" && (
<div data-component="tool" data-tool="error">
<ContentError>{formatErrorString(props.part.state.error)}</ContentError>
+ <Spacer />
</div>
)}
{props.part.type === "tool" &&
props.part.state.status === "completed" &&
props.message.role === "assistant" && (
- <div data-component="tool" data-tool={props.part.tool}>
- <Switch>
- <Match when={props.part.tool === "grep"}>
- <GrepTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- <Match when={props.part.tool === "glob"}>
- <GlobTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- <Match when={props.part.tool === "list"}>
- <ListTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- <Match when={props.part.tool === "read"}>
- <ReadTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- <Match when={props.part.tool === "write"}>
- <WriteTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- <Match when={props.part.tool === "edit"}>
- <EditTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- <Match when={props.part.tool === "bash"}>
- <BashTool
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- message={props.message}
- />
- </Match>
- <Match when={props.part.tool === "todowrite"}>
- <TodoWriteTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- <Match when={props.part.tool === "webfetch"}>
- <WebFetchTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- <Match when={true}>
- <FallbackTool
- message={props.message}
- id={props.part.id}
- tool={props.part.tool}
- state={props.part.state}
- />
- </Match>
- </Switch>
- </div>
+ <>
+ <div data-component="tool" data-tool={props.part.tool}>
+ <Switch>
+ <Match when={props.part.tool === "grep"}>
+ <GrepTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ <Match when={props.part.tool === "glob"}>
+ <GlobTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ <Match when={props.part.tool === "list"}>
+ <ListTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ <Match when={props.part.tool === "read"}>
+ <ReadTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ <Match when={props.part.tool === "write"}>
+ <WriteTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ <Match when={props.part.tool === "edit"}>
+ <EditTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ <Match when={props.part.tool === "bash"}>
+ <BashTool
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ message={props.message}
+ />
+ </Match>
+ <Match when={props.part.tool === "todowrite"}>
+ <TodoWriteTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ <Match when={props.part.tool === "webfetch"}>
+ <WebFetchTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ <Match when={true}>
+ <FallbackTool
+ message={props.message}
+ id={props.part.id}
+ tool={props.part.tool}
+ state={props.part.state}
+ />
+ </Match>
+ </Switch>
+ </div>
+ <ToolFooter
+ time={
+ DateTime.fromMillis(props.message.time.completed || 0)
+ .diff(DateTime.fromMillis(props.message.time.created || 0))
+ .toMillis()
+ } />
+ </>
)}
</div>
</div>
@@ -623,6 +635,16 @@ function Footer(props: ParentProps<{ title: string }>) {
)
}
+function ToolFooter(props: { time: number }) {
+ return props.time > MIN_DURATION ? (
+ <Footer title={`${props.time}ms`}>
+ {formatDuration(props.time)}
+ </Footer>
+ ) : (
+ <Spacer />
+ )
+}
+
export function FallbackTool(props: ToolProps) {
return (
<>