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}
)
}