91 lines
4.3 KiB
Markdown
91 lines
4.3 KiB
Markdown
---
|
|
phase: 25-file-system
|
|
plan: "03"
|
|
subsystem: chat-files-ui
|
|
tags: [file-preview, chat-ui, file-upload, server-query]
|
|
dependency_graph:
|
|
requires: ["25-01", "25-02"]
|
|
provides: ["ChatFilePreview", "ChatFileCard", "file-render-in-message", "attach-files-on-send"]
|
|
affects: ["ChatMessage", "ChatPanel", "server/chat.ts"]
|
|
tech_stack:
|
|
added: []
|
|
patterns:
|
|
- ChatFilePreview delegates to ChatFileCard for all non-image types
|
|
- inArray batch query for files after listMessages, merged in-memory
|
|
- completedFileIds captured before clearCompleted, parallel PATCH for each fileId
|
|
key_files:
|
|
created:
|
|
- ui/src/components/ChatFileCard.tsx
|
|
- ui/src/components/ChatFilePreview.tsx
|
|
modified:
|
|
- ui/src/components/ChatMessage.tsx
|
|
- ui/src/components/ChatMessageList.tsx
|
|
- ui/src/components/ChatPanel.tsx
|
|
- ui/src/api/chat.ts
|
|
- server/src/services/chat.ts
|
|
decisions:
|
|
- "ChatFilePreview shows inline image with max-h-[300px] + ChatFileCard below for download; non-image types render ChatFileCard only"
|
|
- "listMessages fetches chatFiles with inArray(messageId) as second query; merged in-memory (no join, no schema change)"
|
|
- "completedFileIds captured before clearCompleted in handleSend to avoid race with state update"
|
|
- "addMessage returns files: [] for consistency with listMessages shape"
|
|
metrics:
|
|
duration: "3 minutes"
|
|
completed_date: "2026-04-01"
|
|
tasks: 2
|
|
files_modified: 7
|
|
---
|
|
|
|
# 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 `download` attribute and `target="_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>` with `loading="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 `files` array to each message
|
|
- Imports: `inArray` from drizzle-orm, `chatFiles` from @paperclipai/db
|
|
- `addMessage` returns `{ ...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`, `onFilesPicked` to ChatInput
|
|
- `handleSend`: captures `completedFileIds` before clearing, calls `chatApi.attachFilesToMessage` after postMessage, then `clearCompleted()`, 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`.
|
|
|
|
## Self-Check: PASSED
|