Animation Smorgasbord (#15637)

Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
This commit is contained in:
Kit Langton
2026-03-02 17:24:32 -05:00
committed by GitHub
parent 78069369e2
commit 9d7852b5c3
62 changed files with 5231 additions and 710 deletions

View File

@@ -10,8 +10,9 @@ import { Dialog } from "@opencode-ai/ui/dialog"
import { InlineInput } from "@opencode-ai/ui/inline-input"
import { SessionTurn } from "@opencode-ai/ui/session-turn"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import type { Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2"
import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2"
import { showToast } from "@opencode-ai/ui/toast"
import { Binary } from "@opencode-ai/util/binary"
import { getFilename } from "@opencode-ai/util/path"
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
import { SessionContextUsage } from "@/components/session-context-usage"
@@ -31,6 +32,9 @@ type MessageComment = {
}
}
const emptyMessages: MessageType[] = []
const idle = { type: "idle" as const }
const messageComments = (parts: Part[]): MessageComment[] =>
parts.flatMap((part) => {
if (part.type !== "text" || !(part as TextPart).synthetic) return []
@@ -213,8 +217,34 @@ export function MessageTimeline(props: {
const dialog = useDialog()
const language = useLanguage()
const rendered = createMemo(() => props.renderedUserMessages.map((message) => message.id))
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const sessionID = createMemo(() => params.id)
const sessionMessages = createMemo(() => {
const id = sessionID()
if (!id) return emptyMessages
return sync.data.message[id] ?? emptyMessages
})
const pending = createMemo(() =>
sessionMessages().findLast(
(item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number",
),
)
const activeMessageID = createMemo(() => {
const parentID = pending()?.parentID
if (!parentID) return
const messages = sessionMessages()
const result = Binary.search(messages, parentID, (message) => message.id)
const message = result.found ? messages[result.index] : messages.find((item) => item.id === parentID)
if (!message || message.role !== "user") return
return message.id
})
const sessionStatus = createMemo(() => {
const id = sessionID()
if (!id) return idle
return sync.data.session_status[id] ?? idle
})
const info = createMemo(() => {
const id = sessionID()
if (!id) return
@@ -651,17 +681,23 @@ export function MessageTimeline(props: {
</Button>
</div>
</Show>
<For each={staging.messages()}>
{(message) => {
const comments = createMemo(() => messageComments(sync.data.part[message.id] ?? []))
<For each={rendered()}>
{(messageID) => {
const active = createMemo(() => activeMessageID() === messageID)
const queued = createMemo(() => {
const item = pending()
if (!item || active()) return false
return messageID > item.id
})
const comments = createMemo(() => messageComments(sync.data.part[messageID] ?? []))
const commentCount = createMemo(() => comments().length)
return (
<div
id={props.anchor(message.id)}
data-message-id={message.id}
id={props.anchor(messageID)}
data-message-id={messageID}
ref={(el) => {
props.onRegisterMessage(el, message.id)
onCleanup(() => props.onUnregisterMessage(message.id))
props.onRegisterMessage(el, messageID)
onCleanup(() => props.onUnregisterMessage(messageID))
}}
classList={{
"min-w-0 w-full max-w-full": true,
@@ -701,7 +737,10 @@ export function MessageTimeline(props: {
</Show>
<SessionTurn
sessionID={sessionID() ?? ""}
messageID={message.id}
messageID={messageID}
active={active()}
queued={queued()}
status={active() ? sessionStatus() : undefined}
showReasoningSummaries={settings.general.showReasoningSummaries()}
shellToolDefaultOpen={settings.general.shellToolPartsExpanded()}
editToolDefaultOpen={settings.general.editToolPartsExpanded()}