From db5744bbc4bd0915447c28e26621ad252b1f8986 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 26 May 2025 16:35:02 -0400 Subject: Share: sync --- app/packages/web/package.json | 1 + app/packages/web/src/app.tsx | 592 +++++++++++++++++++++++++++++++++--------- 2 files changed, 466 insertions(+), 127 deletions(-) (limited to 'app/packages/web') diff --git a/app/packages/web/package.json b/app/packages/web/package.json index fde040111..7d3a1674d 100644 --- a/app/packages/web/package.json +++ b/app/packages/web/package.json @@ -11,6 +11,7 @@ }, "license": "MIT", "devDependencies": { + "ai": "^5.0.0-alpha.2", "typescript": "5.8.2", "vite": "6.2.2", "vite-plugin-solid": "2.11.6" diff --git a/app/packages/web/src/app.tsx b/app/packages/web/src/app.tsx index 015d44c44..369bcfa41 100644 --- a/app/packages/web/src/app.tsx +++ b/app/packages/web/src/app.tsx @@ -1,22 +1,6 @@ -import { createSignal, onCleanup, onMount, Show, For, createMemo } from "solid-js" +import { createSignal, onCleanup, onMount, Show, For } from "solid-js" import { useParams } from "@solidjs/router" - -type MessagePart = { - type: string - text?: string - [key: string]: any -} - -type MessageContent = { - role?: string - parts?: MessagePart[] - metadata?: { - time?: { - created?: number - } - } - [key: string]: any -} +import { type UIMessage } from "ai" type Message = { key: string @@ -33,9 +17,11 @@ type SessionInfo = { export default function App() { const params = useParams<{ id: string }>() - const [messages, setMessages] = createSignal([]) const [connectionStatus, setConnectionStatus] = createSignal("Disconnected") const [sessionInfo, setSessionInfo] = createSignal(null) + const [systemMessage, setSystemMessage] = createSignal(null) + const [messages, setMessages] = createSignal([]) + const [expandedSystemMessage, setExpandedSystemMessage] = createSignal(false) onMount(() => { // Get the API URL from environment @@ -56,7 +42,7 @@ export default function App() { setConnectionStatus("Error: API URL not found") return } - + let reconnectTimer: number | undefined let socket: WebSocket | null = null @@ -88,33 +74,37 @@ export default function App() { console.log("WebSocket message received") try { const data = JSON.parse(event.data) as Message - + // Check if this is a session info message if (data.key.startsWith("session/info/")) { - try { - const infoContent = JSON.parse(data.content) as SessionInfo; - setSessionInfo(infoContent); - console.log("Session info updated:", infoContent); - } catch (e) { - console.error("Error parsing session info:", e); - } - } else { - // For all other messages - setMessages((prev) => { - // Check if message with this key already exists - const existingIndex = prev.findIndex(msg => msg.key === data.key) - - if (existingIndex >= 0) { - // Update existing message - const updated = [...prev] - updated[existingIndex] = data - return updated - } else { - // Add new message - return [...prev, data] - } - }) + const infoContent = JSON.parse(data.content) as SessionInfo + setSessionInfo(infoContent) + console.log("Session info updated:", infoContent) + return } + + // Check if it's a system message + const msgContent = JSON.parse(data.content) as UIMessage + if (msgContent.role === "system") { + setSystemMessage(data) + console.log("System message updated:", data) + return + } + + // Non-system messages + setMessages((prev) => { + // Check if message with this key already exists + const existingIndex = prev.findIndex((msg) => msg.key === data.key) + if (existingIndex >= 0) { + // Update existing message + const updated = [...prev] + updated[existingIndex] = data + return updated + } else { + // Add new message + return [...prev, data] + } + }) } catch (error) { console.error("Error parsing WebSocket message:", error) } @@ -164,37 +154,126 @@ export default function App() {

Live Updates

- + -

Session Information

- Input Tokens: {sessionInfo()?.tokens?.input || 0} + Input Tokens:{" "} + {sessionInfo()?.tokens?.input || 0}
- Output Tokens: {sessionInfo()?.tokens?.output || 0} + Output Tokens:{" "} + {sessionInfo()?.tokens?.output || 0}
- Reasoning Tokens: {sessionInfo()?.tokens?.reasoning || 0} + Reasoning Tokens:{" "} + {sessionInfo()?.tokens?.reasoning || 0}
- + + {/* Display system message as context in the Session Information block */} + +
+

Context

+ {(() => { + try { + const parsed = JSON.parse( + systemMessage()?.content || "", + ) as UIMessage + if ( + parsed.parts && + parsed.parts.length > 0 && + parsed.parts[0].type === "text" + ) { + const text = parsed.parts[0].text || "" + const lines = text.split("\n") + const visibleLines = expandedSystemMessage() + ? lines + : lines.slice(0, 5) + const hasMoreLines = lines.length > 5 + + return ( + <> +
+ {/* Create a modified version of the text part for the system message */} + {(() => { + // Create a modified part with truncated text + const modifiedPart = { + ...parsed.parts[0], + text: visibleLines.join("\n"), + } + + return ( + <> +
{modifiedPart.text}
+ {hasMoreLines && !expandedSystemMessage() && ( +
+ {lines.length - 5} more lines... +
+ )} + + ) + })()} +
+ {hasMoreLines && ( + + )} + + ) + } + } catch (e) { + return
Error parsing system message
+ } + + return null + })()} +
+
+
Key: {msg.key}
- + {(() => { try { - const parsed = JSON.parse(msg.content) as MessageContent; - const createdTime = parsed.metadata?.time?.created - ? new Date(parsed.metadata.time.created).toLocaleString() - : 'Unknown time'; - + const parsed = JSON.parse(msg.content) as UIMessage + const createdTime = parsed.metadata?.time?.created + ? new Date( + parsed.metadata.time.created, + ).toLocaleString() + : "Unknown time" + return ( <>
Full Content:
                             
- + {parsed.parts && parsed.parts.length > 0 && (
-
- Role: {parsed.role || 'Unknown'} - +
+ + Role: {parsed.role || "Unknown"} + + {createdTime}
- -
- - {(part, index) => ( -
- {part.type === "text" ? ( -
-                                            {part.text}
+
+                                
+ part.type !== "step-start", + )} + > + {(part) => { + if (part.type === "text") { + //{ + // "type": "text", + // "text": "Hello! How can I help you today?" + //} + return ( +
+                                            [{part.type}] {part.text}{" "}
                                           
- ) : ( + ) + } + if (part.type === "reasoning") { + //{ + // "type": "reasoning", + // "text": "The user asked for a weather forecast. I should call the 'getWeather' tool with the location 'San Francisco'.", + // "providerMetadata": { "step_id": "reason_step_1" } + //} + return ( +
+                                            [{part.type}] {part.text}
+                                          
+ ) + } + if (part.type === "tool-invocation") { + return (
-
- Part type: {part.type} -
-
-                                              {JSON.stringify(part, null, 2)}
-                                            
+ +
+                                                  [{part.type}]
+                                                
{" "} + Tool:{" "} + + {part.toolInvocation.toolName} + +
+ {parsed.metadata?.tool?.[ + part.toolInvocation.toolCallId + ]?.time?.start && + parsed.metadata?.tool?.[ + part.toolInvocation.toolCallId + ]?.time?.end && ( + + {( + (new Date( + parsed.metadata?.tool?.[ + part.toolInvocation.toolCallId + ].time.end, + ) - + new Date( + parsed.metadata?.tool?.[ + part.toolInvocation.toolCallId + ].time.start, + )) / + 1000 + ).toFixed(2)} + s + + )} +
+ {(() => { + if ( + part.toolInvocation.state === + "partial-call" + ) { + //{ + // "type": "tool-invocation", + // "toolInvocation": { + // "state": "partial-call", + // "toolCallId": "tool_abc123", + // "toolName": "searchWeb", + // "argsTextDelta": "{\"query\":\"latest AI news" + // } + //} + return ( + <> +
+                                                      {
+                                                        part.toolInvocation
+                                                          .argsTextDelta
+                                                      }
+                                                    
+ ... + + ) + } + if ( + part.toolInvocation.state === + "call" + ) { + //{ + // "type": "tool-invocation", + // "toolInvocation": { + // "state": "call", + // "toolCallId": "tool_abc123", + // "toolName": "searchWeb", + // "args": { "query": "latest AI news", "count": 3 } + // } + //} + return ( +
+                                                    {JSON.stringify(
+                                                      part.toolInvocation.args,
+                                                      null,
+                                                      2,
+                                                    )}
+                                                  
+ ) + } + if ( + part.toolInvocation.state === + "result" + ) { + //{ + // "type": "tool-invocation", + // "toolInvocation": { + // "state": "result", + // "toolCallId": "tool_abc123", + // "toolName": "searchWeb", + // "args": { "query": "latest AI news", "count": 3 }, + // "result": [ + // { "title": "AI SDK v5 Announced", "url": "..." }, + // { "title": "New LLM Achieves SOTA", "url": "..." } + // ] + // } + //} + return ( + <> +
+                                                      {JSON.stringify(
+                                                        part.toolInvocation
+                                                          .args,
+                                                        null,
+                                                        2,
+                                                      )}
+                                                    
+
+                                                      {JSON.stringify(
+                                                        part.toolInvocation
+                                                          .result,
+                                                        null,
+                                                        2,
+                                                      )}
+                                                    
+ + ) + } + if ( + part.toolInvocation.state === + "error" + ) { + //{ + // "type": "tool-invocation", + // "toolInvocation": { + // "state": "error", + // "toolCallId": "tool_abc123", + // "toolName": "searchWeb", + // "args": { "query": "latest AI news", "count": 3 }, + // "errorMessage": "API limit exceeded for searchWeb tool." + // } + //} + return ( + <> +
+                                                      {JSON.stringify(
+                                                        part.toolInvocation
+                                                          .args,
+                                                        null,
+                                                        2,
+                                                      )}
+                                                    
+
+                                                      {
+                                                        part.toolInvocation
+                                                          .errorMessage
+                                                      }
+                                                    
+ + ) + } + })()}
- )} -
- )} + ) + } + if (part.type === "source") { + //{ + // "type": "source", + // "source": { + // "sourceType": "url", + // "id": "doc_xyz789", + // "url": "https://example.com/research-paper.pdf", + // "title": "Groundbreaking AI Research Paper" + // } + //} + return ( +
+
+ +
[{part.type}]
+
+ + Source:{" "} + {part.source.title || + part.source.id} + +
+ {part.source.url && ( + + )} + {part.source.sourceType && ( +
+ Type: {part.source.sourceType} +
+ )} +
+ ) + } + if (part.type === "file") { + //{ + // "type": "file", + // "mediaType": "image/jpeg", + // "filename": "cat_photo.jpg", + // "url": "https://example-files.com/cats/cat_photo.jpg" + //} + const isImage = + part.mediaType?.startsWith("image/") + + return ( +
+
+ +
[{part.type}]
+
+ File: {part.filename} + {part.mediaType} +
+ + {isImage && part.url ? ( +
+ { +
+ ) : ( +
+ {part.url ? ( + + Download: {part.filename} + + ) : ( +
+ File attachment (no URL + available) +
+ )} +
+ )} +
+ ) + } + return null + }}
)} - ); + ) } catch (e) { return (
Content:
                           
- ); + ) } })()} -- cgit v1.2.3