diff options
Diffstat (limited to 'www/src/components')
| -rw-r--r-- | www/src/components/Header.astro | 57 | ||||
| -rw-r--r-- | www/src/components/Hero.astro | 11 | ||||
| -rw-r--r-- | www/src/components/Lander.astro | 269 | ||||
| -rw-r--r-- | www/src/components/Share.tsx | 353 |
4 files changed, 0 insertions, 690 deletions
diff --git a/www/src/components/Header.astro b/www/src/components/Header.astro deleted file mode 100644 index 85cdc2ea5..000000000 --- a/www/src/components/Header.astro +++ /dev/null @@ -1,57 +0,0 @@ ---- -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/") - ? <div class="header sl-flex"> - <div class="title-wrapper sl-flex"> - <SiteTitle {...Astro.props} /> - </div> - <div class="middle-group sl-flex"> - <HeaderLinks {...Astro.props} /> - </div> - </div> - : <Default {...Astro.props}><slot /></Default> -} - -<style> - .header { - justify-content: space-between; - align-items: center; - height: 100%; - } - - .title-wrapper { - /* Prevent long titles overflowing and covering the search and menu buttons on narrow viewports. */ - overflow: clip; - /* Avoid clipping focus ring around link inside title wrapper. */ - padding: calc(0.25rem + 2px) 0.25rem calc(0.25rem - 2px); - margin: -0.25rem; - } - - .middle-group { - justify-content: flex-end; - gap: var(--sl-nav-gap); - } - @media (max-width: 50rem) { - :global(:root[data-has-sidebar]) { - .middle-group { - display: none; - } - } - } - @media (min-width: 50rem) { - .middle-group { - display: flex; - } - } -</style> - diff --git a/www/src/components/Hero.astro b/www/src/components/Hero.astro deleted file mode 100644 index f80f85266..000000000 --- a/www/src/components/Hero.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -import Default from '@astrojs/starlight/components/Hero.astro'; -import Lander from './Lander.astro'; - -const { slug } = Astro.locals.starlightRoute.entry; ---- - -{ slug === "" - ? <Lander {...Astro.props} /> - : <Default {...Astro.props}><slot /></Default> -} diff --git a/www/src/components/Lander.astro b/www/src/components/Lander.astro deleted file mode 100644 index d27358f8f..000000000 --- a/www/src/components/Lander.astro +++ /dev/null @@ -1,269 +0,0 @@ ---- -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; - } -} ---- -<div class="hero"> - <section class="top"> - <div class="logo"> - <Image - src={darkImage} - {...imageAttrs} - class:list={{ 'light:sl-hidden': Boolean(lightImage) }} - /> - <Image src={lightImage} {...imageAttrs} class="dark:sl-hidden" /> - </div> - <h1>The AI coding agent built for the terminal.</h1> - </section> - - <section class="cta"> - <div class="col1"> - <a href="/docs">View the docs</a> - </div> - <div class="col2"> - <button class="command" data-command={`${command} ${pkg}`}> - <code>{command} <span class="highlight">{pkg}</span></code> - <span class="copy"> - <CopyIcon /> - <CheckIcon /> - </span> - </button> - </div> - <div class="col3"> - <a href={github.href}>Star on GitHub</a> - </div> - </section> - - <section class="content"> - <ul> - <li><b>Native TUI</b>: A native terminal UI for a smoother, snappier experience.</li> - <li><b>LSP enabled</b>: Loads the right LSPs for your codebase. Helps the LLM make fewer mistakes.</li> - <li><b>Multi-session</b>: Start multiple conversations in a project to have agents working in parallel.</li> - <li><b>Use any model</b>: Supports all the models from OpenAI, Anthropic, Google, OpenRouter, and more.</li> - <li><b>Change tracking</b>: View the file changes from the current conversation in the sidebar.</li> - <li><b>Edit with Vim</b>: Use Vim as an external editor to compose longer messages.</li> - </ul> - </section> - - <section class="footer"> - <div class="col1"> - <span>Version: Beta</span> - </div> - <div class="col2"> - <span>Author: <a href="https://sst.dev">SST</a></span> - </div> - </section> -</div> - -<style> -.hero { - --padding: 3rem; - --vertical-padding: 2rem; - --heading-font-size: var(--sl-text-3xl); - - margin: 1rem; - border: 2px solid var(--sl-color-white); -} -@media (max-width: 30rem) { - .hero { - --padding: 1rem; - --vertical-padding: 1rem; - --heading-font-size: var(--sl-text-2xl); - - margin: 0.5rem; - } -} - -section.top { - padding: var(--padding); - - h1 { - margin-top: calc(var(--vertical-padding) / 8); - font-size: var(--heading-font-size); - line-height: 1.25; - text-transform: uppercase; - } - - img { - height: auto; - width: clamp(200px, 70vw, 400px); - } -} - -section.cta { - display: flex; - flex-direction: row; - justify-content: space-between; - border-top: 2px solid var(--sl-color-white); - - & > div { - flex: 1; - line-height: 1.4; - padding: calc(var(--padding) / 2) 0.5rem; - } - & > div:not(.col2) { - text-align: center; - text-transform: uppercase; - } - - @media (max-width: 30rem) { - & > div { - padding-bottom: calc(var(--padding) / 2 + 4px); - } - } - - & > div + div { - border-left: 2px solid var(--sl-color-white); - } - - .command { - all: unset; - display: flex; - align-items: center; - gap: 0.625rem; - justify-content: center; - cursor: pointer; - width: 100%; - - code { - color: var(--sl-color-text-secondary); - font-size: 1.125rem; - } - code .highlight { - color: var(--sl-color-text); - font-weight: 500; - } - - .copy { - line-height: 1; - padding: 0; - } - .copy svg { - width: 1rem; - height: 1rem; - vertical-align: middle; - } - .copy svg:first-child { - color: var(--sl-color-text-dimmed); - } - .copy svg:last-child { - color: var(--sl-color-text); - display: none; - } - &.success .copy { - pointer-events: none; - } - &.success .copy svg:first-child { - display: none; - } - &.success .copy svg:last-child { - display: inline; - } - } -} - -section.content { - border-top: 2px solid var(--sl-color-white); - padding: var(--padding); - - ul { - padding-left: 1rem; - - li + li { - margin-top: calc(var(--vertical-padding) / 2); - } - - li b { - text-transform: uppercase; - } - } -} - -section.approach { - border-top: 2px solid var(--sl-color-white); - padding: var(--padding); - - p + p { - margin-top: var(--vertical-padding); - } -} - -section.footer { - border-top: 2px solid var(--sl-color-white); - display: flex; - flex-direction: row; - - & > div { - flex: 1; - text-align: center; - text-transform: uppercase; - padding: calc(var(--padding) / 2) 0.5rem; - } - - & > div + div { - border-left: 2px solid var(--sl-color-white); - } -} -</style> - -<style is:global> -:root[data-has-hero] { - header.header { - display: none; - } - .main-frame { - padding-top: 0; - - .main-pane > main { - padding: 0; - } - } - main > .content-panel .sl-markdown-content { - margin-top: 0; - } -} -</style> - -<script> - const button = document.querySelector("button.command") as HTMLButtonElement; - - button?.addEventListener("click", () => { - navigator.clipboard.writeText(button.dataset.command!); - button.classList.toggle("success"); - setTimeout(() => { - button.classList.toggle("success"); - }, 1500); - }); -</script> diff --git a/www/src/components/Share.tsx b/www/src/components/Share.tsx deleted file mode 100644 index 66748bcab..000000000 --- a/www/src/components/Share.tsx +++ /dev/null @@ -1,353 +0,0 @@ -import { createSignal, onCleanup, onMount, Show, For, createMemo } from "solid-js" - -type MessagePart = { - type: string - text?: string - [key: string]: any -} - -type MessageContent = { - role?: string - parts?: MessagePart[] - metadata?: { - time?: { - created?: number - } - } - [key: string]: any -} - -type Message = { - key: string - content: string -} - -type SessionInfo = { - tokens?: { - input?: number - output?: number - reasoning?: number - } -} - -export default function Share(props: { id: string, api: string }) { - const [messages, setMessages] = createSignal<Message[]>([]) - const [connectionStatus, setConnectionStatus] = createSignal("Disconnected") - const [sessionInfo, setSessionInfo] = createSignal<SessionInfo | null>(null) - - onMount(() => { - // Get the API URL from environment - const apiUrl = props.api - const shareId = props.id - - 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/")) { - 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] - } - }) - } - } 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 ( - <main> - <h1>Share: {props.id}</h1> - - <div style={{ margin: "2rem 0" }}> - <h2>WebSocket Connection</h2> - <p> - Status: <strong>{connectionStatus()}</strong> - </p> - - <h3>Live Updates</h3> - - <Show when={sessionInfo()}> - <div - style={{ - backgroundColor: "#f8f9fa", - padding: "1rem", - borderRadius: "0.5rem", - marginBottom: "1rem", - border: "1px solid #dee2e6" - }} - > - <h4 style={{ margin: "0 0 0.75rem 0" }}>Session Information</h4> - <div style={{ display: "flex", gap: "1.5rem" }}> - <div> - <strong>Input Tokens:</strong> {sessionInfo()?.tokens?.input || 0} - </div> - <div> - <strong>Output Tokens:</strong> {sessionInfo()?.tokens?.output || 0} - </div> - <div> - <strong>Reasoning Tokens:</strong> {sessionInfo()?.tokens?.reasoning || 0} - </div> - </div> - </div> - </Show> - - <div - style={{ - border: "1px solid #ccc", - padding: "1rem", - borderRadius: "0.5rem", - maxHeight: "500px", - overflowY: "auto", - }} - > - <Show - when={messages().length > 0} - fallback={<p>Waiting for messages...</p>} - > - <ul style={{ listStyleType: "none", padding: 0 }}> - <For each={messages()}> - {(msg) => ( - <li - style={{ - padding: "0.75rem", - margin: "0.75rem 0", - backgroundColor: "#f5f5f5", - borderRadius: "0.5rem", - boxShadow: "0 1px 3px rgba(0,0,0,0.1)", - }} - > - <div> - <strong>Key:</strong> {msg.key} - </div> - - {(() => { - try { - const parsed = JSON.parse(msg.content) as MessageContent; - const createdTime = parsed.metadata?.time?.created - ? new Date(parsed.metadata.time.created).toLocaleString() - : 'Unknown time'; - - return ( - <> - <div style={{ marginTop: "0.5rem" }}> - <strong>Full Content:</strong> - <pre - style={{ - backgroundColor: "#f0f0f0", - padding: "0.5rem", - borderRadius: "0.25rem", - overflow: "auto", - maxHeight: "150px", - whiteSpace: "pre-wrap", - wordBreak: "break-word", - fontSize: "0.85rem", - }} - > - {JSON.stringify(parsed, null, 2)} - </pre> - </div> - - {parsed.parts && parsed.parts.length > 0 && ( - <div style={{ marginTop: "0.75rem" }}> - <div style={{ - display: "flex", - justifyContent: "space-between", - alignItems: "center", - padding: "0.25rem 0.5rem", - backgroundColor: "#e9ecef", - borderRadius: "0.25rem", - marginBottom: "0.5rem" - }}> - <strong>Role: {parsed.role || 'Unknown'}</strong> - <span style={{ fontSize: "0.8rem", color: "#6c757d" }}> - {createdTime} - </span> - </div> - - <div style={{ - backgroundColor: "#fff", - padding: "0.75rem", - borderRadius: "0.25rem", - border: "1px solid #dee2e6" - }}> - <For each={parsed.parts}> - {(part, index) => ( - <div style={{ marginBottom: index() < parsed.parts!.length - 1 ? "0.75rem" : "0" }}> - {part.type === "text" ? ( - <pre style={{ - whiteSpace: "pre-wrap", - wordBreak: "break-word", - fontFamily: "inherit", - margin: 0, - padding: 0, - backgroundColor: "transparent", - border: "none", - fontSize: "inherit", - overflow: "visible" - }}> - {part.text} - </pre> - ) : ( - <div> - <div style={{ - fontSize: "0.85rem", - fontWeight: "bold", - marginBottom: "0.25rem", - color: "#495057" - }}> - Part type: {part.type} - </div> - <pre - style={{ - backgroundColor: "#f8f9fa", - padding: "0.5rem", - borderRadius: "0.25rem", - overflow: "auto", - maxHeight: "200px", - whiteSpace: "pre-wrap", - wordBreak: "break-word", - fontSize: "0.85rem", - margin: 0 - }} - > - {JSON.stringify(part, null, 2)} - </pre> - </div> - )} - </div> - )} - </For> - </div> - </div> - )} - </> - ); - } catch (e) { - return ( - <div> - <strong>Content:</strong> - <pre - style={{ - backgroundColor: "#f0f0f0", - padding: "0.5rem", - borderRadius: "0.25rem", - overflow: "auto", - maxHeight: "200px", - whiteSpace: "pre-wrap", - wordBreak: "break-word", - }} - > - {msg.content} - </pre> - </div> - ); - } - })()} - </li> - )} - </For> - </ul> - </Show> - </div> - </div> - </main> - ) -} - |
