diff options
| author | Jay V <[email protected]> | 2025-05-28 18:32:38 -0400 |
|---|---|---|
| committer | Jay V <[email protected]> | 2025-05-28 18:32:40 -0400 |
| commit | 3d61cc5d2b6550aa22e2c2cad75b32a74b769559 (patch) | |
| tree | c95c6712fb3abdda597e020ccfd2ae9d5988f44d /app/packages/web/src/components/Share.tsx | |
| parent | a22a2f0f374dbcd7efe891a8fe881b9a3758f15a (diff) | |
| download | opencode-3d61cc5d2b6550aa22e2c2cad75b32a74b769559.tar.gz opencode-3d61cc5d2b6550aa22e2c2cad75b32a74b769559.zip | |
styling share
Diffstat (limited to 'app/packages/web/src/components/Share.tsx')
| -rw-r--r-- | app/packages/web/src/components/Share.tsx | 117 |
1 files changed, 101 insertions, 16 deletions
diff --git a/app/packages/web/src/components/Share.tsx b/app/packages/web/src/components/Share.tsx index c1c82d4b3..aba324d2f 100644 --- a/app/packages/web/src/components/Share.tsx +++ b/app/packages/web/src/components/Share.tsx @@ -10,7 +10,17 @@ import { createSignal, } from "solid-js" import { DateTime } from "luxon" -import { IconCpuChip, IconSparkles, IconUserCircle, IconWrenchScrewdriver } from "./icons" +import { + IconOpenAI, + IconGemini, + IconAnthropic, +} from "./icons/custom" +import { + IconCpuChip, + IconSparkles, + IconUserCircle, + IconWrenchScrewdriver, +} from "./icons" import styles from "./share.module.css" import { type UIMessage } from "ai" import { createStore, reconcile } from "solid-js/store" @@ -48,14 +58,23 @@ type SessionInfo = { cost?: number } -function getPartTitle(role: string, type: string): string | undefined { - return role === "system" - ? role - : role === "user" - ? undefined - : type === "text" - ? undefined - : type +function ProviderIcon(props: { provider: string, size?: number }) { + const size = props.size || 16 + return ( + <Switch fallback={ + <IconSparkles width={size} height={size} /> + }> + <Match when={props.provider === "openai"}> + <IconOpenAI width={size} height={size} /> + </Match> + <Match when={props.provider === "anthropic"}> + <IconAnthropic width={size} height={size} /> + </Match> + <Match when={props.provider === "gemini"}> + <IconGemini width={size} height={size} /> + </Match> + </Switch> + ) } function getStatusText(status: [Status, string?]): string { @@ -118,9 +137,14 @@ function TextPart( function PartFooter(props: { time: number }) { return ( - <span title={ - DateTime.fromMillis(props.time).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS) - }> + <span + data-part-footer + title={ + DateTime.fromMillis(props.time).toLocaleString( + DateTime.DATETIME_FULL_WITH_SECONDS + ) + } + > {DateTime.fromMillis(props.time).toLocaleString(DateTime.TIME_WITH_SECONDS)} </span> ) @@ -236,6 +260,16 @@ export default function Share(props: { api: string }) { }) }) + const models = createMemo(() => { + const result: string[][] = [] + for (const msg of messages()) { + if (msg.role === "assistant" && msg.metadata?.assistant) { + result.push([msg.metadata.assistant.providerID, msg.metadata.assistant.modelID]) + } + } + return result + }) + const metrics = createMemo(() => { const result = { cost: 0, @@ -301,6 +335,25 @@ export default function Share(props: { api: string }) { } </li> </ul> + <ul data-section="stats" data-section-models> + {models().length > 0 ? + <For each={Array.from(models())}> + {([provider, model]) => ( + <li> + <div data-stat-model-icon title={provider}> + <ProviderIcon provider={provider} /> + </div> + <span data-stat-model>{model}</span> + </li> + )} + </For> + : + <li> + <span data-element-label>Models</span> + <span data-placeholder>—</span> + </li> + } + </ul> <div data-section="date"> {messages().length > 0 && messages()[0].metadata?.time.created ? <span title={ @@ -329,6 +382,8 @@ export default function Share(props: { api: string }) { {(msg, msgIndex) => ( <For each={msg.parts}> {(part, partIndex) => { + if (part.type === "step-start" && (partIndex() > 0 || !msg.metadata?.assistant)) return null + const isLastPart = createMemo(() => (messages().length === msgIndex() + 1) && (msg.parts.length === partIndex() + 1) @@ -388,6 +443,34 @@ export default function Share(props: { api: string }) { </> } </Match> + { /* AI model */} + <Match when={ + msg.role === "assistant" + && part.type === "step-start" + && msg.metadata?.assistant + }> + {assistant => + <> + <div data-section="decoration"> + <div> + <ProviderIcon + size={18} + provider={assistant().providerID} + /> + </div> + <div></div> + </div> + <div data-section="content"> + <span data-element-label data-part-title> + {assistant().providerID} + </span> + <span data-part-model> + {assistant().modelID} + </span> + </div> + </> + } + </Match> { /* System text */} <Match when={ msg.role === "system" @@ -403,7 +486,9 @@ export default function Share(props: { api: string }) { <div></div> </div> <div data-section="content"> - <span data-element-label>System</span> + <span data-element-label data-part-title> + System + </span> <TextPart text={part().text} expand={isLastPart()} @@ -413,8 +498,6 @@ export default function Share(props: { api: string }) { </> } </Match> - { /* Step start */} - <Match when={part.type === "step-start"}>{null}</Match> { /* Fallback */} <Match when={true}> <div data-section="decoration"> @@ -436,7 +519,9 @@ export default function Share(props: { api: string }) { <div></div> </div> <div data-section="content"> - <span data-element-label>{part.type}</span> + <span data-element-label data-part-title> + {part.type} + </span> <TextPart text={JSON.stringify(part, null, 2)} /> <PartFooter time={time} /> </div> |
