From 66b18959ebc7b699a74ce69d3adfb4c4dcaa5fd1 Mon Sep 17 00:00:00 2001 From: Jay V Date: Mon, 26 May 2025 17:25:06 -0400 Subject: Merging docs and share app --- app/packages/web/src/components/Header.astro | 57 +++ app/packages/web/src/components/Hero.astro | 11 + app/packages/web/src/components/Lander.astro | 269 +++++++++++ app/packages/web/src/components/Share.tsx | 691 +++++++++++++++++++++++++++ 4 files changed, 1028 insertions(+) create mode 100644 app/packages/web/src/components/Header.astro create mode 100644 app/packages/web/src/components/Hero.astro create mode 100644 app/packages/web/src/components/Lander.astro create mode 100644 app/packages/web/src/components/Share.tsx (limited to 'app/packages/web/src/components') diff --git a/app/packages/web/src/components/Header.astro b/app/packages/web/src/components/Header.astro new file mode 100644 index 000000000..f027d7274 --- /dev/null +++ b/app/packages/web/src/components/Header.astro @@ -0,0 +1,57 @@ +--- +import config from 'virtual:starlight/user-config'; +import { Icon } from '@astrojs/starlight/components'; +import { HeaderLinks } from 'toolbeam-docs-theme/components'; +import Default from 'toolbeam-docs-theme/overrides/Header.astro'; +import SiteTitle from '@astrojs/starlight/components/SiteTitle.astro'; + +const path = Astro.url.pathname; + +const links = config.social || []; +--- + +{ path.startsWith("/share") + ?
+
+ +
+
+ +
+
+ : +} + + + diff --git a/app/packages/web/src/components/Hero.astro b/app/packages/web/src/components/Hero.astro new file mode 100644 index 000000000..f80f85266 --- /dev/null +++ b/app/packages/web/src/components/Hero.astro @@ -0,0 +1,11 @@ +--- +import Default from '@astrojs/starlight/components/Hero.astro'; +import Lander from './Lander.astro'; + +const { slug } = Astro.locals.starlightRoute.entry; +--- + +{ slug === "" + ? + : +} diff --git a/app/packages/web/src/components/Lander.astro b/app/packages/web/src/components/Lander.astro new file mode 100644 index 000000000..d27358f8f --- /dev/null +++ b/app/packages/web/src/components/Lander.astro @@ -0,0 +1,269 @@ +--- +import { Image } from 'astro:assets'; +import config from "virtual:starlight/user-config"; +import type { Props } from '@astrojs/starlight/props'; + +import CopyIcon from "../assets/lander/copy.svg"; +import CheckIcon from "../assets/lander/check.svg"; + +const { data } = Astro.locals.starlightRoute.entry; +const { title = data.title, tagline, image, actions = [] } = data.hero || {}; + +const imageAttrs = { + loading: 'eager' as const, + decoding: 'async' as const, + width: 400, + alt: image?.alt || '', +}; + +const github = config.social.filter(s => s.icon === 'github')[0]; + +const command = "npm i -g"; +const pkg = "opencode"; + +let darkImage: ImageMetadata | undefined; +let lightImage: ImageMetadata | undefined; +let rawHtml: string | undefined; +if (image) { + if ('file' in image) { + darkImage = image.file; + } else if ('dark' in image) { + darkImage = image.dark; + lightImage = image.light; + } else { + rawHtml = image.html; + } +} +--- +
+
+ +

The AI coding agent built for the terminal.

+
+ +
+ +
+ +
+ +
+ +
+
    +
  • Native TUI: A native terminal UI for a smoother, snappier experience.
  • +
  • LSP enabled: Loads the right LSPs for your codebase. Helps the LLM make fewer mistakes.
  • +
  • Multi-session: Start multiple conversations in a project to have agents working in parallel.
  • +
  • Use any model: Supports all the models from OpenAI, Anthropic, Google, OpenRouter, and more.
  • +
  • Change tracking: View the file changes from the current conversation in the sidebar.
  • +
  • Edit with Vim: Use Vim as an external editor to compose longer messages.
  • +
+
+ + +
+ + + + + + diff --git a/app/packages/web/src/components/Share.tsx b/app/packages/web/src/components/Share.tsx new file mode 100644 index 000000000..906a9ab9c --- /dev/null +++ b/app/packages/web/src/components/Share.tsx @@ -0,0 +1,691 @@ +import { createSignal, onCleanup, onMount, Show, For } from "solid-js" +import { type UIMessage } from "ai" + +type Message = { + key: string + content: string +} + +type SessionInfo = { + tokens?: { + input?: number + output?: number + reasoning?: number + } +} + +export default function Share(props: { api: string }) { + let params = new URLSearchParams(document.location.search) + const shareId = params.get("id") + + 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(() => { + const apiUrl = props.api + + console.log("Mounting Share component with ID:", shareId) + console.log("API URL:", apiUrl) + + if (!shareId) { + console.error("Share ID not found in environment variables") + setConnectionStatus("Error: Share ID not found") + return + } + + if (!apiUrl) { + console.error("API URL not found in environment variables") + setConnectionStatus("Error: API URL not found") + return + } + + let reconnectTimer: number | undefined + let socket: WebSocket | null = null + + // Function to create and set up WebSocket with auto-reconnect + const setupWebSocket = () => { + // Close any existing connection + if (socket) { + socket.close() + } + + setConnectionStatus("Connecting...") + + // Always use secure WebSocket protocol (wss) + const wsBaseUrl = apiUrl.replace(/^https?:\/\//, "wss://") + const wsUrl = `${wsBaseUrl}/share_poll?shareID=${shareId}` + console.log("Connecting to WebSocket URL:", wsUrl) + + // Create WebSocket connection + socket = new WebSocket(wsUrl) + + // Handle connection opening + socket.onopen = () => { + setConnectionStatus("Connected") + console.log("WebSocket connection established") + } + + // Handle incoming messages + socket.onmessage = (event) => { + 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/")) { + 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) + } + } + + // Handle errors + socket.onerror = (error) => { + console.error("WebSocket error:", error) + setConnectionStatus("Error: Connection failed") + } + + // Handle connection close and reconnection + socket.onclose = (event) => { + console.log(`WebSocket closed: ${event.code} ${event.reason}`) + setConnectionStatus("Disconnected, reconnecting...") + + // Try to reconnect after 2 seconds + clearTimeout(reconnectTimer) + reconnectTimer = window.setTimeout( + setupWebSocket, + 2000, + ) as unknown as number + } + } + + // Initial connection + setupWebSocket() + + // Clean up on component unmount + onCleanup(() => { + console.log("Cleaning up WebSocket connection") + if (socket) { + socket.close() + } + clearTimeout(reconnectTimer) + }) + }) + + return ( +
+

Share: {shareId}

+ +
+

WebSocket Connection

+

+ Status: {connectionStatus()} +

+ +

Live Updates

+ + +
+

Session Information

+
+
+ Input Tokens:{" "} + {sessionInfo()?.tokens?.input || 0} +
+
+ Output Tokens:{" "} + {sessionInfo()?.tokens?.output || 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 + })()} +
+
+ +
+ 0} + fallback={

Waiting for messages...

} + > +
    + + {(msg) => ( +
  • +
    + Key: {msg.key} +
    + + {(() => { + try { + 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: +
    +                                {JSON.stringify(parsed, null, 2)}
    +                              
    +
    + + {parsed.parts && parsed.parts.length > 0 && ( +
    +
    + + Role: {parsed.role || "Unknown"} + + + {createdTime} + +
    + +
    + 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}]
    +                                                
    {" "} + 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: +
    +                              {msg.content}
    +                            
    +
    + ) + } + })()} +
  • + )} +
    +
+
+
+
+
+ ) +} -- cgit v1.2.3