import { AssistantMessage } from "@opencode-ai/sdk" import { useData } from "../context" import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createEffect, createMemo, createSignal, For, Match, ParentProps, Show, Switch } from "solid-js" import { createStore } from "solid-js/store" import { DiffChanges } from "./diff-changes" import { Spinner } from "./spinner" import { Typewriter } from "./typewriter" import { Message } from "./message-part" import { Markdown } from "./markdown" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" import { FileIcon } from "./file-icon" import { Icon } from "./icon" import { Diff } from "./diff" import { Card } from "./card" import { MessageProgress } from "./message-progress" import { Collapsible } from "./collapsible" export function SessionTimeline( props: ParentProps<{ sessionID: string classes?: { root?: string content?: string container?: string } expanded?: boolean }>, ) { const data = useData() const [store, setStore] = createStore({ messageId: undefined as string | undefined, }) const match = Binary.search(data.session, props.sessionID, (s) => s.id) if (!match.found) throw new Error(`Session ${props.sessionID} not found`) // const info = createMemo(() => data.session[match.index]) const messages = createMemo(() => (props.sessionID ? (data.message[props.sessionID] ?? []) : [])) const userMessages = createMemo(() => messages() .filter((m) => m.role === "user") .sort((a, b) => b.id.localeCompare(a.id)), ) const lastUserMessage = createMemo(() => { return userMessages()?.at(0) }) const activeMessage = createMemo(() => { if (!store.messageId) return lastUserMessage() return userMessages()?.find((m) => m.id === store.messageId) }) const status = createMemo( () => data.session_status[props.sessionID] ?? { type: "idle", }, ) const working = createMemo(() => status()?.type !== "idle") return (
1}>
{(message) => { const isActive = createMemo(() => activeMessage()?.id === message.id) const titleSeen = createMemo(() => true) const contentSeen = createMemo(() => true) { /* const titleSeen = createSeen(`message-title-${message.id}`) */ } { /* const contentSeen = createSeen(`message-content-${message.id}`) */ } const [titled, setTitled] = createSignal(titleSeen()) const assistantMessages = createMemo(() => { return messages()?.filter((m) => m.role === "assistant" && m.parentID == message.id) as AssistantMessage[] }) const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error) const [detailsExpanded, setDetailsExpanded] = createSignal(false) const parts = createMemo(() => data.part[message.id]) const hasToolPart = createMemo(() => assistantMessages() ?.flatMap((m) => data.part[m.id]) .some((p) => p?.type === "tool"), ) const messageWorking = createMemo(() => message.id === lastUserMessage()?.id && working()) const initialCompleted = !(message.id === lastUserMessage()?.id && working()) const [completed, setCompleted] = createSignal(initialCompleted) // allowing time for the animations to finish createEffect(() => { if (titleSeen()) return const title = message.summary?.title if (title) setTimeout(() => setTitled(true), 10_000) }) createEffect(() => { const completed = !messageWorking() setTimeout(() => setCompleted(completed), 1200) }) return (
{/* Title */}
} >

{message.summary?.title}

{/* Summary */}

Summary Response

{(summary) => ( )}
{(diff) => (
{getDirectory(diff.file)}‎ {getFilename(diff.file)}
)}
{error()?.data?.message as string} {/* Response */}
Hide details Show details
{(assistantMessage) => { const parts = createMemo(() => data.part[assistantMessage.id]) return }} {error()?.data?.message as string}
) }}
{props.children}
) }