import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } from "@opencode-ai/sdk/v2" import { SessionTurn } from "@opencode-ai/ui/session-turn" import { SessionReview } from "@opencode-ai/ui/session-review" import { DataProvider } from "@opencode-ai/ui/context" import { DiffComponentProvider } from "@opencode-ai/ui/context/diff" import { CodeComponentProvider } from "@opencode-ai/ui/context/code" import { WorkerPoolProvider } from "@opencode-ai/ui/context/worker-pool" import { createAsync, query, useParams } from "@solidjs/router" import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Switch } from "solid-js" import { Share } from "~/core/share" import { Logo, Mark } from "@opencode-ai/ui/logo" import { IconButton } from "@opencode-ai/ui/icon-button" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { createDefaultOptions } from "@opencode-ai/ui/pierre" import { iife } from "@opencode-ai/util/iife" import { Binary } from "@opencode-ai/util/binary" import { NamedError } from "@opencode-ai/util/error" import { DateTime } from "luxon" import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail" import { createStore } from "solid-js/store" import z from "zod" import NotFound from "../[...404]" import { Tabs } from "@opencode-ai/ui/tabs" import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" import { Diff as SSRDiff } from "@opencode-ai/ui/diff-ssr" import { clientOnly } from "@solidjs/start" import { type IconName } from "@opencode-ai/ui/icons/provider" import { Meta } from "@solidjs/meta" import { Base64 } from "js-base64" const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff }))) const ClientOnlyCode = clientOnly(() => import("@opencode-ai/ui/code").then((m) => ({ default: m.Code }))) const ClientOnlyWorkerPoolProvider = clientOnly(() => import("@opencode-ai/ui/pierre/worker").then((m) => ({ default: (props: { children: any }) => ( {props.children} ), })), ) const SessionDataMissingError = NamedError.create( "SessionDataMissingError", z.object({ sessionID: z.string(), message: z.string().optional(), }), ) const getData = query(async (shareID) => { "use server" const share = await Share.get(shareID) if (!share) throw new SessionDataMissingError({ sessionID: shareID }) const data = await Share.data(shareID) const result: { sessionID: string shareID: string session: Session[] session_diff: { [sessionID: string]: FileDiff[] } session_diff_preload: { [sessionID: string]: PreloadMultiFileDiffResult[] } session_diff_preload_split: { [sessionID: string]: PreloadMultiFileDiffResult[] } session_status: { [sessionID: string]: SessionStatus } message: { [sessionID: string]: Message[] } part: { [messageID: string]: Part[] } model: { [sessionID: string]: Model[] } } = { sessionID: share.sessionID, shareID, session: [], session_diff: { [share.sessionID]: [], }, session_diff_preload: { [share.sessionID]: [], }, session_diff_preload_split: { [share.sessionID]: [], }, session_status: { [share.sessionID]: { type: "idle", }, }, message: {}, part: {}, model: {}, } for (const item of data) { switch (item.type) { case "session": result.session.push(item.data) break case "session_diff": result.session_diff[share.sessionID] = item.data await Promise.all([ Promise.all( item.data.map(async (diff) => preloadMultiFileDiff({ oldFile: { name: diff.file, contents: diff.before }, newFile: { name: diff.file, contents: diff.after }, options: createDefaultOptions("unified"), // annotations, }), ), ).then((r) => (result.session_diff_preload[share.sessionID] = r)), Promise.all( item.data.map(async (diff) => preloadMultiFileDiff({ oldFile: { name: diff.file, contents: diff.before }, newFile: { name: diff.file, contents: diff.after }, options: createDefaultOptions("split"), // annotations, }), ), ).then((r) => (result.session_diff_preload_split[share.sessionID] = r)), ]) break case "message": result.message[item.data.sessionID] = result.message[item.data.sessionID] ?? [] result.message[item.data.sessionID].push(item.data) break case "part": result.part[item.data.messageID] = result.part[item.data.messageID] ?? [] result.part[item.data.messageID].push(item.data) break case "model": result.model[share.sessionID] = item.data break } } const match = Binary.search(result.session, share.sessionID, (s) => s.id) if (!match.found) throw new SessionDataMissingError({ sessionID: share.sessionID }) return result }, "getShareData") export default function () { const params = useParams() const data = createAsync(async () => { if (!params.shareID) throw new Error("Missing shareID") const now = Date.now() const data = getData(params.shareID) console.log("getData", Date.now() - now) return data }) createEffect(() => { console.log(data()) }) return ( { return ( ) }} > {(data) => { const match = createMemo(() => Binary.search(data().session, data().sessionID, (s) => s.id)) if (!match().found) throw new Error(`Session ${data().sessionID} not found`) const info = createMemo(() => data().session[match().index]) const ogImage = createMemo(() => { const models = new Set() const messages = data().message[data().sessionID] ?? [] for (const msg of messages) { if (msg.role === "assistant" && msg.modelID) { models.add(msg.modelID) } } const modelIDs = Array.from(models) const encodedTitle = encodeURIComponent(Base64.encode(encodeURIComponent(info().title.substring(0, 700)))) let modelParam: string if (modelIDs.length === 1) { modelParam = modelIDs[0] } else if (modelIDs.length === 2) { modelParam = encodeURIComponent(`${modelIDs[0]} & ${modelIDs[1]}`) } else if (modelIDs.length > 2) { modelParam = encodeURIComponent(`${modelIDs[0]} & ${modelIDs.length - 1} others`) } else { modelParam = "unknown" } const version = `v${info().version}` return `https://social-cards.sst.dev/opencode-share/${encodedTitle}.png?model=${modelParam}&version=${version}&id=${data().shareID}` }) return ( <> {iife(() => { const [store, setStore] = createStore({ messageId: undefined as string | undefined, expandedSteps: {} as Record, }) const messages = createMemo(() => data().sessionID ? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort( (a, b) => a.time.created - b.time.created, ) : [], ) const firstUserMessage = createMemo(() => messages().at(0)) const activeMessage = createMemo( () => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(), ) function setActiveMessage(message: UserMessage | undefined) { if (message) { setStore("messageId", message.id) } else { setStore("messageId", undefined) } } const provider = createMemo(() => activeMessage()?.model?.providerID) const modelID = createMemo(() => activeMessage()?.model?.modelID) const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID())) const diffs = createMemo(() => { const diffs = data().session_diff[data().sessionID] ?? [] const preloaded = data().session_diff_preload[data().sessionID] ?? [] return diffs.map((diff) => ({ ...diff, preloaded: preloaded.find((d) => d.newFile.name === diff.file), })) }) const splitDiffs = createMemo(() => { const diffs = data().session_diff[data().sessionID] ?? [] const preloaded = data().session_diff_preload_split[data().sessionID] ?? [] return diffs.map((diff) => ({ ...diff, preloaded: preloaded.find((d) => d.newFile.name === diff.file), })) }) const title = () => (
v{info().version}
{model()?.name ?? modelID()}
{DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")}
{info().title}
) const turns = () => (
{title()}
{(message) => ( setStore("expandedSteps", message.id, (v) => !v)} classes={{ root: "min-w-0 w-full relative", content: "flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]", container: "px-4", }} /> )}
) const wide = createMemo(() => diffs().length === 0) return (
1, "px-6": !wide() && messages().length === 1, }} > {title()}
{ const id = store.messageId ?? firstUserMessage()!.id! setStore("expandedSteps", id, (v) => !v) }} classes={{ root: "grow", content: "flex flex-col justify-between", container: "w-full pb-20 " + (wide() ? "max-w-200 mx-auto px-6" : messages().length > 1 ? "pr-6 pl-18" : "px-6"), }} >
0}>
0}> Session {diffs().length} Files Changed {turns()}
{turns()}
) })}
) }}
) }