ignore: fix share page
Some checks are pending
deploy / deploy (push) Waiting to run

This commit is contained in:
Dax Raad 2025-08-11 16:12:26 -04:00
parent 7a67fe7dde
commit 0ce7d92a8b
4 changed files with 223 additions and 206 deletions

View file

@ -1,7 +1,6 @@
import { For, Show, onMount, Suspense, onCleanup, createMemo, createSignal, SuspenseList, createEffect } from "solid-js"
import { DateTime } from "luxon"
import { createStore, reconcile, unwrap } from "solid-js/store"
import { mapValues } from "remeda"
import { IconArrowDown } from "./icons"
import { IconOpencode } from "./icons/custom"
import styles from "./share.module.css"
@ -42,7 +41,6 @@ export default function Share(props: {
id: string
api: string
info: Session.Info
messages: Record<string, MessageWithParts>
}) {
let lastScrollY = 0
let hasScrolledToAnchor = false
@ -50,7 +48,6 @@ export default function Share(props: {
let scrollSentinel: HTMLElement | undefined
let scrollObserver: IntersectionObserver | undefined
const id = props.id
const params = new URLSearchParams(window.location.search)
const debug = params.get("debug") === "true"
@ -61,17 +58,27 @@ export default function Share(props: {
const [store, setStore] = createStore<{
info?: Session.Info
messages: Record<string, MessageWithParts>
}>({ info: props.info, messages: {} })
}>({
info: {
id: props.id,
title: props.info.title,
version: props.info.version,
time: {
created: props.info.time.created,
updated: props.info.time.updated,
},
}, messages: {}
})
const messages = createMemo(() => Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)))
const [connectionStatus, setConnectionStatus] = createSignal<[Status, string?]>(["disconnected", "Disconnected"])
// createEffect(() => {
// console.log(unwrap(store))
// })
createEffect(() => {
console.log(unwrap(store))
})
onMount(() => {
const apiUrl = props.api
if (!id) {
if (!props.id) {
setConnectionStatus(["error", "id not found"])
return
}
@ -96,7 +103,7 @@ export default function Share(props: {
// Always use secure WebSocket protocol (wss)
const wsBaseUrl = apiUrl.replace(/^https?:\/\//, "wss://")
const wsUrl = `${wsBaseUrl}/share_poll?id=${id}`
const wsUrl = `${wsBaseUrl}/share_poll?id=${props.id}`
console.log("Connecting to WebSocket URL:", wsUrl)
// Create WebSocket connection
@ -261,7 +268,9 @@ export default function Share(props: {
},
}
result.created = props.info.time.created
if (!store.info) return result
result.created = store.info.time.created
const msgs = messages()
for (let i = 0; i < msgs.length; i++) {
@ -290,197 +299,199 @@ export default function Share(props: {
})
return (
<main classList={{ [styles.root]: true, "not-content": true }}>
<div data-component="header">
<h1 data-component="header-title">{store.info?.title}</h1>
<div data-component="header-details">
<ul data-component="header-stats">
<li title="opencode version" data-slot="item">
<div data-slot="icon" title="opencode">
<IconOpencode width={16} height={16} />
</div>
<Show when={store.info?.version} fallback="v0.0.1">
<span>v{store.info?.version}</span>
</Show>
</li>
{Object.values(data().models).length > 0 ? (
<For each={Object.values(data().models)}>
{([provider, model]) => (
<li data-slot="item">
<div data-slot="icon" title={provider}>
<ProviderIcon model={model} />
</div>
<span data-slot="model">{model}</span>
</li>
)}
</For>
) : (
<li>
<span data-element-label>Models</span>
<span data-placeholder>&mdash;</span>
<Show when={store.info}>
<main classList={{ [styles.root]: true, "not-content": true }}>
<div data-component="header">
<h1 data-component="header-title">{store.info?.title}</h1>
<div data-component="header-details">
<ul data-component="header-stats">
<li title="opencode version" data-slot="item">
<div data-slot="icon" title="opencode">
<IconOpencode width={16} height={16} />
</div>
<Show when={store.info?.version} fallback="v0.0.1">
<span>v{store.info?.version}</span>
</Show>
</li>
)}
</ul>
<div
data-component="header-time"
title={DateTime.fromMillis(data().created || 0).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
>
{DateTime.fromMillis(data().created || 0).toLocaleString(DateTime.DATETIME_MED)}
</div>
</div>
</div>
<div>
<Show when={data().messages.length > 0} fallback={<p>Waiting for messages...</p>}>
<div class={styles.parts}>
<SuspenseList revealOrder="forwards">
<For each={data().messages}>
{(msg, msgIndex) => {
const filteredParts = createMemo(() =>
msg.parts.filter((x, index) => {
if (x.type === "step-start" && index > 0) return false
if (x.type === "snapshot") return false
if (x.type === "patch") return false
if (x.type === "step-finish") return false
if (x.type === "text" && x.synthetic === true) return false
if (x.type === "tool" && x.tool === "todoread") return false
if (x.type === "text" && !x.text) return false
if (x.type === "tool" && (x.state.status === "pending" || x.state.status === "running"))
return false
return true
}),
)
return (
<Suspense>
<For each={filteredParts()}>
{(part, partIndex) => {
const last = createMemo(
() =>
data().messages.length === msgIndex() + 1 && filteredParts().length === partIndex() + 1,
)
onMount(() => {
const hash = window.location.hash.slice(1)
// Wait till all parts are loaded
if (
hash !== "" &&
!hasScrolledToAnchor &&
filteredParts().length === partIndex() + 1 &&
data().messages.length === msgIndex() + 1
) {
hasScrolledToAnchor = true
scrollToAnchor(hash)
}
})
return <Part last={last()} part={part} index={partIndex()} message={msg} />
}}
</For>
</Suspense>
)
}}
</For>
</SuspenseList>
<div data-section="part" data-part-type="summary">
<div data-section="decoration">
<span data-status={connectionStatus()[0]}></span>
</div>
<div data-section="content">
<p data-section="copy">{getStatusText(connectionStatus())}</p>
<ul data-section="stats">
<li>
<span data-element-label>Cost</span>
{data().cost !== undefined ? (
<span>${data().cost.toFixed(2)}</span>
) : (
<span data-placeholder>&mdash;</span>
)}
</li>
<li>
<span data-element-label>Input Tokens</span>
{data().tokens.input ? <span>{data().tokens.input}</span> : <span data-placeholder>&mdash;</span>}
</li>
<li>
<span data-element-label>Output Tokens</span>
{data().tokens.output ? <span>{data().tokens.output}</span> : <span data-placeholder>&mdash;</span>}
</li>
<li>
<span data-element-label>Reasoning Tokens</span>
{data().tokens.reasoning ? (
<span>{data().tokens.reasoning}</span>
) : (
<span data-placeholder>&mdash;</span>
)}
</li>
</ul>
</div>
</div>
</div>
</Show>
</div>
<Show when={debug}>
<div style={{ margin: "2rem 0" }}>
<div
style={{
border: "1px solid #ccc",
padding: "1rem",
"overflow-y": "auto",
}}
>
<Show when={data().messages.length > 0} fallback={<p>Waiting for messages...</p>}>
<ul style={{ "list-style-type": "none", padding: 0 }}>
<For each={data().messages}>
{(msg) => (
<li
style={{
padding: "0.75rem",
margin: "0.75rem 0",
"box-shadow": "0 1px 3px rgba(0,0,0,0.1)",
}}
>
<div>
<strong>Key:</strong> {msg.id}
{Object.values(data().models).length > 0 ? (
<For each={Object.values(data().models)}>
{([provider, model]) => (
<li data-slot="item">
<div data-slot="icon" title={provider}>
<ProviderIcon model={model} />
</div>
<pre>{JSON.stringify(msg, null, 2)}</pre>
<span data-slot="model">{model}</span>
</li>
)}
</For>
</ul>
</Show>
) : (
<li>
<span data-element-label>Models</span>
<span data-placeholder>&mdash;</span>
</li>
)}
</ul>
<div
data-component="header-time"
title={DateTime.fromMillis(data().created || 0).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
>
{DateTime.fromMillis(data().created || 0).toLocaleString(DateTime.DATETIME_MED)}
</div>
</div>
</div>
</Show>
<Show when={showScrollButton()}>
<button
type="button"
class={styles["scroll-button"]}
onClick={() => document.body.scrollIntoView({ behavior: "smooth", block: "end" })}
onMouseEnter={() => {
setIsButtonHovered(true)
if (scrollTimeout) {
clearTimeout(scrollTimeout)
}
}}
onMouseLeave={() => {
setIsButtonHovered(false)
if (showScrollButton()) {
scrollTimeout = window.setTimeout(() => {
if (!isButtonHovered()) {
setShowScrollButton(false)
}
}, 3000)
}
}}
title="Scroll to bottom"
aria-label="Scroll to bottom"
>
<IconArrowDown width={20} height={20} />
</button>
</Show>
</main>
<div>
<Show when={data().messages.length > 0} fallback={<p>Waiting for messages...</p>}>
<div class={styles.parts}>
<SuspenseList revealOrder="forwards">
<For each={data().messages}>
{(msg, msgIndex) => {
const filteredParts = createMemo(() =>
msg.parts.filter((x, index) => {
if (x.type === "step-start" && index > 0) return false
if (x.type === "snapshot") return false
if (x.type === "patch") return false
if (x.type === "step-finish") return false
if (x.type === "text" && x.synthetic === true) return false
if (x.type === "tool" && x.tool === "todoread") return false
if (x.type === "text" && !x.text) return false
if (x.type === "tool" && (x.state.status === "pending" || x.state.status === "running"))
return false
return true
}),
)
return (
<Suspense>
<For each={filteredParts()}>
{(part, partIndex) => {
const last = createMemo(
() =>
data().messages.length === msgIndex() + 1 && filteredParts().length === partIndex() + 1,
)
onMount(() => {
const hash = window.location.hash.slice(1)
// Wait till all parts are loaded
if (
hash !== "" &&
!hasScrolledToAnchor &&
filteredParts().length === partIndex() + 1 &&
data().messages.length === msgIndex() + 1
) {
hasScrolledToAnchor = true
scrollToAnchor(hash)
}
})
return <Part last={last()} part={part} index={partIndex()} message={msg} />
}}
</For>
</Suspense>
)
}}
</For>
</SuspenseList>
<div data-section="part" data-part-type="summary">
<div data-section="decoration">
<span data-status={connectionStatus()[0]}></span>
</div>
<div data-section="content">
<p data-section="copy">{getStatusText(connectionStatus())}</p>
<ul data-section="stats">
<li>
<span data-element-label>Cost</span>
{data().cost !== undefined ? (
<span>${data().cost.toFixed(2)}</span>
) : (
<span data-placeholder>&mdash;</span>
)}
</li>
<li>
<span data-element-label>Input Tokens</span>
{data().tokens.input ? <span>{data().tokens.input}</span> : <span data-placeholder>&mdash;</span>}
</li>
<li>
<span data-element-label>Output Tokens</span>
{data().tokens.output ? <span>{data().tokens.output}</span> : <span data-placeholder>&mdash;</span>}
</li>
<li>
<span data-element-label>Reasoning Tokens</span>
{data().tokens.reasoning ? (
<span>{data().tokens.reasoning}</span>
) : (
<span data-placeholder>&mdash;</span>
)}
</li>
</ul>
</div>
</div>
</div>
</Show>
</div>
<Show when={debug}>
<div style={{ margin: "2rem 0" }}>
<div
style={{
border: "1px solid #ccc",
padding: "1rem",
"overflow-y": "auto",
}}
>
<Show when={data().messages.length > 0} fallback={<p>Waiting for messages...</p>}>
<ul style={{ "list-style-type": "none", padding: 0 }}>
<For each={data().messages}>
{(msg) => (
<li
style={{
padding: "0.75rem",
margin: "0.75rem 0",
"box-shadow": "0 1px 3px rgba(0,0,0,0.1)",
}}
>
<div>
<strong>Key:</strong> {msg.id}
</div>
<pre>{JSON.stringify(msg, null, 2)}</pre>
</li>
)}
</For>
</ul>
</Show>
</div>
</div>
</Show>
<Show when={showScrollButton()}>
<button
type="button"
class={styles["scroll-button"]}
onClick={() => document.body.scrollIntoView({ behavior: "smooth", block: "end" })}
onMouseEnter={() => {
setIsButtonHovered(true)
if (scrollTimeout) {
clearTimeout(scrollTimeout)
}
}}
onMouseLeave={() => {
setIsButtonHovered(false)
if (showScrollButton()) {
scrollTimeout = window.setTimeout(() => {
if (!isButtonHovered()) {
setShowScrollButton(false)
}
}, 3000)
}
}}
title="Scroll to bottom"
aria-label="Scroll to bottom"
>
<IconArrowDown width={20} height={20} />
</button>
</Show>
</main>
</Show>
)
}