export type ChatRole = 'user' | 'assistant' | 'system' | 'tool' export type ChatMessageStatus = 'streaming' | 'complete' | 'error' export type ChatMessageKind = 'text' | 'tool' | 'status' /** * A reference attachment already persisted on a message (server shape). Rendered * as a chip/thumbnail on the message that carries it. */ export interface ChatMessageAttachment { id: string /** Asset url (image thumbnails / download link); may be null while pending. */ fileUrl: string | null filename: string contentType: string } export interface ChatMessageData { id: string role: ChatRole content: string /** Display name for the author (defaults derived from role). */ author?: string timestamp?: string | number | Date /** `streaming` shows a live indicator; `error` styles the bubble as failed. */ status?: ChatMessageStatus /** `tool`/`status` render as compact system rows rather than chat bubbles. */ kind?: ChatMessageKind /** Name of the tool when kind === 'tool'. */ toolName?: string /** Reference attachments carried by this message (rendered as chips). */ attachments?: ChatMessageAttachment[] } /** * A composer-side pending attachment. The APP owns upload state and feeds these * down as a controlled prop; the composer renders them as chips with progress. */ export interface PendingAttachment { id: string name: string /** * `queued` = parked client-side, waiting for a document to land on (a brand-new * deck has no document until the first turn creates it). It is NOT actively * uploading — surfacing it as `uploading` at 0% reads as a stuck upload. */ status: 'queued' | 'uploading' | 'ready' | 'error' /** Upload progress 0..1 while `status === 'uploading'`. */ progress?: number /** Object/asset url for an image preview thumbnail. */ thumbnailUrl?: string sizeBytes?: number /** Human-readable failure reason shown on `status === 'error'` chips. */ error?: string }