import type { ChatBlock } from '@/chat/types'
import type { MessageStatus } from '@/types/api'
import type { ApiClient } from '@/api/client'
import type { SessionMetadataSummary } from '@/types/api'
import { MarkdownRenderer } from '@/components/MarkdownRenderer'
import { ToolCard } from '@/components/ToolCard/ToolCard'
function ErrorIcon() {
return (
)
}
function MessageStatusIndicator(props: {
status?: MessageStatus
onRetry?: () => void
}) {
if (props.status !== 'failed') {
return null
}
return (
{props.onRetry ? (
) : null}
)
}
function formatUnixTimestamp(value: number): string {
const ms = value < 1_000_000_000_000 ? value * 1000 : value
const date = new Date(ms)
if (Number.isNaN(date.getTime())) return String(value)
return date.toLocaleString()
}
function renderEventLabel(event: ChatBlock & { kind: 'agent-event' }): string {
const data = event.event as { type: string; [key: string]: unknown }
if (data.type === 'switch') {
const mode = data.mode === 'local' ? 'local' : 'remote'
return `🔄 Switched to ${mode}`
}
if (data.type === 'title-changed') {
const title = typeof data.title === 'string' ? data.title : ''
return title ? `Title changed to "${title}"` : 'Title changed'
}
if (data.type === 'permission-mode-changed') {
const mode = typeof data.mode === 'string' ? data.mode : 'default'
return `🔐 Permission mode: ${mode}`
}
if (data.type === 'limit-reached') {
const endsAt = typeof data.endsAt === 'number' ? data.endsAt : null
return endsAt ? `⏳ Usage limit reached until ${formatUnixTimestamp(endsAt)}` : '⏳ Usage limit reached'
}
if (data.type === 'message') {
return typeof data.message === 'string' ? data.message : 'Message'
}
try {
return JSON.stringify(data)
} catch {
return 'Event'
}
}
export function ChatBlockList(props: {
api: ApiClient
sessionId: string
metadata: SessionMetadataSummary | null
disabled: boolean
onRefresh: () => void
blocks: ChatBlock[]
onRetryMessage?: (localId: string) => void
}) {
return (
{props.blocks.map((block) => {
if (block.kind === 'user-text') {
const userBubbleClass = 'w-fit max-w-[92%] ml-auto rounded-xl border border-[var(--app-border)] bg-[var(--app-secondary-bg)] px-3 py-2 text-[var(--app-fg)] shadow-sm'
const status = block.status
const onRetry = block.localId && status === 'failed' && props.onRetryMessage
? () => props.onRetryMessage!(block.localId!)
: undefined
return (
)
}
if (block.kind === 'agent-text') {
return (
)
}
if (block.kind === 'agent-event') {
return (
{renderEventLabel(block)}
)
}
if (block.kind === 'tool-call') {
const isTask = block.tool.name === 'Task'
return (
{block.children.length > 0 ? (
isTask ? (
Task details ({block.children.length})
) : (
)
) : null}
)
}
return null
})}
)
}