import { AssistantMessage } from "@opencode-ai/sdk" import { useData } from "../context" import { useDiffComponent } from "../context/diff" import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { checksum } from "@opencode-ai/util/encode" import { createEffect, createMemo, createSignal, For, Match, onMount, ParentProps, Show, Switch } from "solid-js" import { DiffChanges } from "./diff-changes" 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 { Card } from "./card" import { MessageProgress } from "./message-progress" import { Collapsible } from "./collapsible" import { Dynamic } from "solid-js/web" export function SessionTurn( props: ParentProps<{ sessionID: string messageID: string classes?: { root?: string content?: string container?: string } }>, ) { const data = useData() const diffComponent = useDiffComponent() const match = Binary.search(data.store.session, props.sessionID, (s) => s.id) if (!match.found) throw new Error(`Session ${props.sessionID} not found`) const sanitizer = createMemo(() => (data.directory ? new RegExp(`${data.directory}/`, "g") : undefined)) const messages = createMemo(() => (props.sessionID ? (data.store.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 message = createMemo(() => userMessages()?.find((m) => m.id === props.messageID)) const status = createMemo( () => data.store.session_status[props.sessionID] ?? { type: "idle", }, ) const working = createMemo(() => status()?.type !== "idle") return (
{(msg) => { const titleKey = `app:seen:session:${props.sessionID}:${msg().id}:title` const contentKey = `app:seen:session:${props.sessionID}:${msg().id}:content` const [detailsExpanded, setDetailsExpanded] = createSignal(false) const [titled, setTitled] = createSignal(true) const [faded, setFaded] = createSignal(true) const assistantMessages = createMemo(() => { return messages()?.filter((m) => m.role === "assistant" && m.parentID == msg().id) as AssistantMessage[] }) const assistantMessageParts = createMemo(() => assistantMessages()?.flatMap((m) => data.store.part[m.id])) const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error) const parts = createMemo(() => data.store.part[msg().id]) const lastTextPart = createMemo(() => assistantMessageParts() .filter((p) => p?.type === "text") ?.at(-1), ) const hasToolPart = createMemo(() => assistantMessageParts().some((p) => p?.type === "tool")) const messageWorking = createMemo(() => msg().id === lastUserMessage()?.id && working()) const initialCompleted = !(msg().id === lastUserMessage()?.id && working()) const [completed, setCompleted] = createSignal(initialCompleted) const summary = createMemo(() => msg().summary?.body ?? lastTextPart()?.text) const lastTextPartShown = createMemo(() => !msg().summary?.body && (lastTextPart()?.text?.length ?? 0) > 0) // allowing time for the animations to finish onMount(() => { const titleSeen = sessionStorage.getItem(titleKey) === "true" const contentSeen = sessionStorage.getItem(contentKey) === "true" if (!titleSeen) { setTitled(false) const title = msg().summary?.title if (title) setTimeout(() => setTitled(true), 10_000) setTimeout(() => sessionStorage.setItem(titleKey, "true"), 1000) } if (!contentSeen) { setFaded(false) setTimeout(() => sessionStorage.setItem(contentKey, "true"), 1000) } }) createEffect(() => { const completed = !messageWorking() setTimeout(() => setCompleted(completed), 1200) }) return (
{/* Title */}
} >

{msg().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.store.part[assistantMessage.id]) const last = createMemo(() => parts() .filter((p) => p?.type === "text") .at(-1), ) if (lastTextPartShown() && lastTextPart()?.id === last()?.id) { return ( p?.id !== last()?.id)} sanitize={sanitizer()} /> ) } return }} {error()?.data?.message as string}
) }}
{props.children}
) }