diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index e0a73edc..e3efd663 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -29,6 +29,8 @@ import styles from "./share.module.css"
import { type UIMessage } from "ai"
import { createStore, reconcile } from "solid-js/store"
+const MIN_DURATION = 2
+
type Status =
| "disconnected"
| "connecting"
@@ -73,6 +75,23 @@ function getFileType(path: string) {
return path.split(".").pop()
}
+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`
+}
+
// Converts `{a:{b:{c:1}}` to `[['a.b.c', 1]]`
function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
const entries: Array<[string, any]> = []
@@ -260,18 +279,13 @@ function TerminalPart(props: TerminalPartProps) {
)
}
-function PartFooter(props: { time: number }) {
+function ToolFooter(props: { time: number }) {
return (
-
- {DateTime.fromMillis(props.time).toLocaleString(
- DateTime.TIME_WITH_SECONDS,
- )}
-
+ props.time > MIN_DURATION
+ ?
+ {formatDuration(props.time)}
+
+ :
)
}
@@ -550,7 +564,6 @@ export default function Share(props: { api: string }) {
text={part().text}
expand={isLastPart()}
/>
-
)}
@@ -576,7 +589,6 @@ export default function Share(props: { api: string }) {
text={part().text}
expand={isLastPart()}
/>
-
)}
@@ -647,7 +659,6 @@ export default function Share(props: { api: string }) {
data-color="dimmed"
/>
-
)}
@@ -665,6 +676,13 @@ export default function Share(props: { api: string }) {
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(),
+ )
+
return (
-
+
)
@@ -706,14 +724,23 @@ export default function Share(props: { api: string }) {
}
>
{(part) => {
+ const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
+
const id = part().toolInvocation.toolCallId
const command = part().toolInvocation.args.command
- const stdout = msg.metadata?.tool[id]?.stdout
+ const stdout = metadata()?.stdout
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(),
+ )
+
return (
@@ -728,7 +755,7 @@ export default function Share(props: { api: string }) {
text={command + (result ? `\n${result}` : "")}
/>
-
+
)
@@ -742,80 +769,90 @@ export default function Share(props: { api: string }) {
part
}
>
- {(part) => (
-
-
-
-
-
- {part().toolInvocation.toolName}
-
-
-
- {([name, value]) => (
- <>
-
- {name}
- {value}
- >
- )}
-
+ {(part) => {
+ 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(),
+ )
+
+ return (
+
+
+
+
-
-
-
- showResults((e) => !e)}
- />
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ {part().toolInvocation.toolName}
+
+
+
+ {([name, value]) => (
+ <>
+
+ {name}
+ {value}
+ >
+ )}
+
+
+
+
+
+ showResults((e) => !e)}
+ />
+
+
+
+
+
+
+
+
+
+
+
-
-
- )}
+ )
+ }}
{/* Fallback */}
@@ -857,7 +894,6 @@ export default function Share(props: { api: string }) {
text={JSON.stringify(part, null, 2)}
/>
-
diff --git a/packages/web/src/components/share.module.css b/packages/web/src/components/share.module.css
index d8c0ef7d..a393de49 100644
--- a/packages/web/src/components/share.module.css
+++ b/packages/web/src/components/share.module.css
@@ -272,6 +272,17 @@
}
}
+ /* Part types */
+ [data-part-type="user-text"],
+ [data-part-type="ai-text"],
+ [data-part-type="ai-model"],
+ [data-part-type="system-text"],
+ [data-part-type="fallback"] {
+ & > [data-section="content"] {
+ padding-bottom: 1rem;
+ }
+ }
+
[data-part-type="tool-edit"] {
[data-part-tool-body] {
gap: 0.5rem;