4.3 KiB
4.3 KiB
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 25-file-system | 03 | chat-files-ui |
|
|
|
|
|
|
Phase 25 Plan 03: File Preview and Full Flow Wiring Summary
One-liner: ChatFilePreview (inline images) and ChatFileCard (download cards) wired end-to-end: upload -> attach to message -> render previews with server-side file loading in listMessages.
Tasks Completed
| Task | Name | Commit | Files |
|---|---|---|---|
| 1 | Create ChatFilePreview and ChatFileCard | 6053f30e | ChatFilePreview.tsx, ChatFileCard.tsx |
| 2 | Wire files into ChatMessage, ChatPanel, server | 64ec8588 | ChatMessage.tsx, ChatMessageList.tsx, ChatPanel.tsx, chat.ts (api), chat.ts (server) |
What Was Built
ChatFileCard (ui/src/components/ChatFileCard.tsx)
- Compact file attachment card with lucide icon per category (ImageIcon, FileCode, FileText, File)
- Shows original filename (truncated), human-readable size (B/KB/MB via
formatFileSize) - Download anchor with
downloadattribute andtarget="_blank"for one-click download - Theme-aware:
bg-muted border-border rounded-lg— works across Catppuccin Mocha, Tokyo Night, Catppuccin Latte
ChatFilePreview (ui/src/components/ChatFilePreview.tsx)
- Images: inline
<img>withloading="lazy",max-h-[300px] rounded-lg object-contain, wrapped in clickable link for full-size - Images also render ChatFileCard below for download button
- All other categories (code, document, other): render ChatFileCard only
- No complexity of fetching file content for syntax highlighting — code files show as downloadable cards
Server: listMessages with files (server/src/services/chat.ts)
- After fetching messages, collects all message IDs
- Second query:
SELECT * FROM chat_files WHERE message_id IN (...)ordered by createdAt asc - Groups by messageId into a Map, attaches
filesarray to each message - Imports:
inArrayfrom drizzle-orm,chatFilesfrom @paperclipai/db addMessagereturns{ ...message, files: [] }for shape consistency
ChatMessage files rendering (ui/src/components/ChatMessage.tsx)
- Added
files?: ChatFile[]prop - Renders
<div className="mt-2 flex flex-col gap-2">with ChatFilePreview for each file - Applied to both user message bubble and assistant message sections
ChatPanel upload orchestration (ui/src/components/ChatPanel.tsx)
- Calls
useChatFileUpload(activeConversationId)for pendingFiles, addFile, removeFile, clearCompleted, completedFileIds - Passes
pendingFiles,onRemoveFile,onFilesPickedto ChatInput handleSend: capturescompletedFileIdsbefore clearing, callschatApi.attachFilesToMessageafter postMessage, thenclearCompleted(), then invalidates messages query
chatApi.attachFilesToMessage (ui/src/api/chat.ts)
Promise.all(fileIds.map(id => api.patch('/files/:id', { messageId })))— parallel PATCH calls
Deviations from Plan
None — plan executed exactly as written.
Known Stubs
None — all file data flows from server to UI. File previews render real content from /api/files/:id/content.