/** * Attachments + sources attached to a chat message. */ /** * Rich-preview payload for a localpreview-style attachment card. Serializable * JSON only (no React nodes) so it survives history persistence + SSE — the * render seam already exists (`AttachmentRendererMap`); this is the DATA that * feeds it. One generic bag instead of a typed field per media kind: a host * populates `kind` + `fields[]` (duration, dimensions, pages, EXIF…) and the * default/per-type renderer reads it. Absent → renderer falls back to the * name+thumbnail chip. */ export interface AttachmentPreview { /** Drives which rich card the renderer mounts (audio/image/pdf/doc/…). */ kind: string; title?: string; /** Cover / first-page thumbnail (audio art, pdf page-1). */ thumbnail?: string; /** Ordered label/value rows the card renders (duration, dims, pages, EXIF…). */ fields?: { label: string; value: string }[]; } export interface ChatAttachment { id: string; type: 'image' | 'file' | 'audio' | 'video' | 'text'; /** * Object-URL / remote URL for binary attachments. Empty string for * `type:'text'` chunks — those carry their payload in `text`, not a URL. */ url: string; thumbnailUrl?: string; name?: string; mimeType?: string; sizeBytes?: number; status?: 'uploading' | 'ready' | 'error'; /** 0..1 while uploading. */ progress?: number; /** * Raw text payload for `type:'text'` chunks — the "Pasted text" * attachment minted when a long paste is converted into a chip * instead of dumped into the textarea (ChatGPT/Claude behaviour). * Preserves exact content: newlines, indentation, stack traces. */ text?: string; /** * Optional rich-preview payload for a localpreview-style card (audio * cover+duration, image thumbnail+EXIF, pdf pages, OCR metadata…). * Serializable JSON; persists through history/SSE. Absent → the default * renderer falls back to the name+thumbnail chip. */ preview?: AttachmentPreview; } export interface ChatSource { title: string; url: string; snippet?: string; chunkIndex?: number; }