summaryrefslogtreecommitdiffhomepage
path: root/app/packages/web/src/components/Share.tsx
diff options
context:
space:
mode:
authorJay V <[email protected]>2025-05-28 18:32:38 -0400
committerJay V <[email protected]>2025-05-28 18:32:40 -0400
commit3d61cc5d2b6550aa22e2c2cad75b32a74b769559 (patch)
treec95c6712fb3abdda597e020ccfd2ae9d5988f44d /app/packages/web/src/components/Share.tsx
parenta22a2f0f374dbcd7efe891a8fe881b9a3758f15a (diff)
downloadopencode-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.tsx117
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>&mdash;</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>