Browse Source

styling share

Jay V 9 months ago
parent
commit
37082b2176

+ 8 - 0
app/bun.lock

@@ -3,6 +3,10 @@
   "workspaces": {
     "": {
       "name": "opencontrol",
+      "dependencies": {
+        "@types/luxon": "^3.6.2",
+        "luxon": "^3.6.1",
+      },
       "devDependencies": {
         "@tsconfig/node22": "22.0.0",
         "@types/node": "^22.13.9",
@@ -325,6 +329,8 @@
 
     "@types/js-yaml": ["@types/[email protected]", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
 
+    "@types/luxon": ["@types/[email protected]", "", {}, "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw=="],
+
     "@types/mdast": ["@types/[email protected]", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
 
     "@types/mdx": ["@types/[email protected]", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
@@ -785,6 +791,8 @@
 
     "lru-cache": ["[email protected]", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
 
+    "luxon": ["[email protected]", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="],
+
     "magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
 
     "magicast": ["[email protected]", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="],

+ 5 - 1
app/package.json

@@ -36,5 +36,9 @@
     "esbuild",
     "protobufjs",
     "sharp"
-  ]
+  ],
+  "dependencies": {
+    "@types/luxon": "^3.6.2",
+    "luxon": "^3.6.1"
+  }
 }

+ 108 - 17
app/packages/web/src/components/Share.tsx

@@ -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]}>&#9679;</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 &gt;</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 &mdash;</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={{

File diff suppressed because it is too large
+ 3258 - 0
app/packages/web/src/components/icons/index.tsx


+ 87 - 32
app/packages/web/src/components/share.module.css

@@ -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,53 +47,98 @@
       &[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);
+        }
+      }
+    }
+  }
+
+  [data-section="date"] {
+    span {
+      font-size: 0.875rem;
+      color: var(--sl-color-text);
+
+      &[data-placeholder] {
+        color: var(--sl-color-text-dimmed);
+      }
     }
   }
 }
 
-.stats {
-  list-style-type: none;
-  padding: 0;
-  margin: 0;
+.parts {
   display: flex;
-  gap: 1rem;
+  flex-direction: column;
+  gap: 0.625rem;
 
-  li {
+  [data-section="part"] {
     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;
+  [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 {
-      &[data-placeholder] {
-        color: var(--sl-color-text-dimmed);
-      }
+      font-size: 0.75rem;
+      color: var(--sl-color-text-dimmed);
     }
   }
 }
 
-.context {
-  button {
-    appearance: none;
-    background: none;
-    border: none;
-    padding: 0;
-    margin: 0;
+[data-element-message-text] {
+  pre {
     font-size: 0.875rem;
-    color: var(--sl-color-text-dimmed);
-    cursor: pointer;
-
-    &:hover {
-      color: var(--sl-color-primary);
-    }
+    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;
   }
 }

Some files were not shown because too many files changed in this diff