summaryrefslogtreecommitdiffhomepage
path: root/www/src/components
diff options
context:
space:
mode:
authorJay V <[email protected]>2025-05-26 17:25:06 -0400
committerJay V <[email protected]>2025-05-26 17:25:12 -0400
commit66b18959ebc7b699a74ce69d3adfb4c4dcaa5fd1 (patch)
tree5ef9340b1c496076dd1e12156fdc35c3f3c35933 /www/src/components
parentdeacf5991abfb777aae7823a8e7e352fbefdabd0 (diff)
downloadopencode-66b18959ebc7b699a74ce69d3adfb4c4dcaa5fd1.tar.gz
opencode-66b18959ebc7b699a74ce69d3adfb4c4dcaa5fd1.zip
Merging docs and share app
Diffstat (limited to 'www/src/components')
-rw-r--r--www/src/components/Header.astro57
-rw-r--r--www/src/components/Hero.astro11
-rw-r--r--www/src/components/Lander.astro269
-rw-r--r--www/src/components/Share.tsx353
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}&nbsp;<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>
- )
-}
-