mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-28 21:24:19 +01:00
styling share
This commit is contained in:
@@ -1,4 +1,15 @@
|
||||
import { createSignal, onCleanup, onMount, Show, For, createMemo } from "solid-js"
|
||||
import {
|
||||
For,
|
||||
Show,
|
||||
Match,
|
||||
Switch,
|
||||
onMount,
|
||||
onCleanup,
|
||||
createMemo,
|
||||
createSignal,
|
||||
} from "solid-js"
|
||||
import { DateTime } from "luxon"
|
||||
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"
|
||||
@@ -8,17 +19,17 @@ type Status = "disconnected" | "connecting" | "connected" | "error" | "reconnect
|
||||
|
||||
type SessionMessage = UIMessage<{
|
||||
time: {
|
||||
created: number;
|
||||
completed?: number;
|
||||
};
|
||||
sessionID: string;
|
||||
created: number
|
||||
completed?: number
|
||||
}
|
||||
sessionID: string
|
||||
tool: Record<string, {
|
||||
properties: Record<string, any>;
|
||||
properties: Record<string, any>
|
||||
time: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
}>;
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
}>
|
||||
}>
|
||||
|
||||
type SessionInfo = {
|
||||
@@ -41,6 +52,14 @@ function getStatusText(status: [Status, string?]): string {
|
||||
}
|
||||
}
|
||||
|
||||
function TextPart(props: { text: string }) {
|
||||
return (
|
||||
<div data-element-message-text>
|
||||
<pre>{props.text}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Share(props: { api: string }) {
|
||||
let params = new URLSearchParams(document.location.search)
|
||||
const sessionId = params.get("id")
|
||||
@@ -151,6 +170,16 @@ export default function Share(props: { api: string }) {
|
||||
})
|
||||
})
|
||||
|
||||
function renderTime(time: number) {
|
||||
return (
|
||||
<span title={
|
||||
DateTime.fromMillis(time).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)
|
||||
}>
|
||||
{DateTime.fromMillis(time).toLocaleString(DateTime.TIME_WITH_SECONDS)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<main class={`${styles.root} not-content`}>
|
||||
<div class={styles.header}>
|
||||
@@ -158,13 +187,13 @@ export default function Share(props: { api: string }) {
|
||||
<h1>{store.info?.title}</h1>
|
||||
<p>
|
||||
<span data-status={connectionStatus()[0]}>●</span>
|
||||
<span>{getStatusText(connectionStatus())}</span>
|
||||
<span data-element-label>{getStatusText(connectionStatus())}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div data-section="row">
|
||||
<ul class={styles.stats}>
|
||||
<ul data-section="stats">
|
||||
<li>
|
||||
<span>Input Tokens</span>
|
||||
<span data-element-label>Input Tokens</span>
|
||||
{store.info?.tokens?.input ?
|
||||
<span>{store.info?.tokens?.input}</span>
|
||||
:
|
||||
@@ -172,7 +201,7 @@ export default function Share(props: { api: string }) {
|
||||
}
|
||||
</li>
|
||||
<li>
|
||||
<span>Output Tokens</span>
|
||||
<span data-element-label>Output Tokens</span>
|
||||
{store.info?.tokens?.output ?
|
||||
<span>{store.info?.tokens?.output}</span>
|
||||
:
|
||||
@@ -180,7 +209,7 @@ export default function Share(props: { api: string }) {
|
||||
}
|
||||
</li>
|
||||
<li>
|
||||
<span>Reasoning Tokens</span>
|
||||
<span data-element-label>Reasoning Tokens</span>
|
||||
{store.info?.tokens?.reasoning ?
|
||||
<span>{store.info?.tokens?.reasoning}</span>
|
||||
:
|
||||
@@ -188,12 +217,74 @@ export default function Share(props: { api: string }) {
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
<div class={styles.context}>
|
||||
<button>View Context ></button>
|
||||
<div data-section="date">
|
||||
{messages().length > 0 && messages()[0].metadata?.time.created ?
|
||||
<span title={
|
||||
DateTime.fromMillis(
|
||||
messages()[0].metadata?.time.created || 0
|
||||
).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)
|
||||
}>
|
||||
{DateTime.fromMillis(
|
||||
messages()[0].metadata?.time.created || 0
|
||||
).toLocaleString(DateTime.DATE_MED)}
|
||||
</span>
|
||||
:
|
||||
<span data-element-label data-placeholder>Started at —</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Show
|
||||
when={messages().length > 0}
|
||||
fallback={<p>Waiting for messages...</p>}
|
||||
>
|
||||
<div class={styles.parts}>
|
||||
<For each={messages()}>
|
||||
{(msg) => (
|
||||
<For each={msg.parts}>
|
||||
{(part) => (
|
||||
<div
|
||||
data-section="part"
|
||||
data-message-role={msg.role}
|
||||
data-part-type={part.type}
|
||||
>
|
||||
<div data-section="decoration">
|
||||
<div>
|
||||
<Switch fallback={
|
||||
<IconWrenchScrewdriver width={16} height={16} />
|
||||
}>
|
||||
<Match when={msg.role === "assistant" && (part.type === "text" || part.type === "step-start")}>
|
||||
<IconSparkles width={18} height={18} />
|
||||
</Match>
|
||||
<Match when={msg.role === "system"}>
|
||||
<IconCpuChip width={18} height={18} />
|
||||
</Match>
|
||||
<Match when={msg.role === "user"}>
|
||||
<IconUserCircle width={18} height={18} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div data-section="content">
|
||||
<TextPart text={JSON.stringify(part, null, 2)} />
|
||||
{renderTime(
|
||||
msg.metadata?.time.completed
|
||||
|| msg.metadata?.time.created
|
||||
|| 0
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div style={{ margin: "2rem 0" }}>
|
||||
<div
|
||||
style={{
|
||||
|
||||
6101
app/packages/web/src/components/icons/index.tsx
Normal file
6101
app/packages/web/src/components/icons/index.tsx
Normal file
File diff suppressed because one or more lines are too long
@@ -1,7 +1,17 @@
|
||||
.root {
|
||||
padding-top: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
[data-element-label] {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--sl-color-text-dimmed);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -37,33 +47,34 @@
|
||||
&[data-status="reconnecting"] { color: var(--sl-color-orange); }
|
||||
&[data-status="error"] { color: var(--sl-color-red); }
|
||||
}
|
||||
span:last-child {
|
||||
color: var(--sl-color-text-dimmed);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
[data-section="stats"] {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
|
||||
span:last-child {
|
||||
&[data-placeholder] {
|
||||
color: var(--sl-color-text-dimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
[data-section="date"] {
|
||||
span {
|
||||
font-size: 0.875rem;
|
||||
color: var(--sl-color-text);
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
|
||||
span:first-child {
|
||||
color: var(--sl-color-text-dimmed);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
span:last-child {
|
||||
&[data-placeholder] {
|
||||
color: var(--sl-color-text-dimmed);
|
||||
}
|
||||
@@ -71,19 +82,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
.context {
|
||||
button {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--sl-color-text-dimmed);
|
||||
cursor: pointer;
|
||||
.parts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
|
||||
&:hover {
|
||||
color: var(--sl-color-primary);
|
||||
[data-section="part"] {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
[data-section="decoration"] {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
div:first-child {
|
||||
flex: 0 0 auto;
|
||||
width: 18px;
|
||||
svg {
|
||||
color: var(--sl-color-text-secondary);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
border-radius: 1px;
|
||||
background-color: var(--sl-color-hairline);
|
||||
}
|
||||
}
|
||||
|
||||
[data-section="content"] {
|
||||
flex: 1 1 auto;
|
||||
padding: 0.125rem 0 0.375rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
span:last-child {
|
||||
font-size: 0.75rem;
|
||||
color: var(--sl-color-text-dimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-element-message-text] {
|
||||
pre {
|
||||
font-size: 0.875rem;
|
||||
color: var(--sl-color-text);
|
||||
background-color: var(--sl-color-bg-nav);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user