Skip to content

Add file management: upload, download, and send_file tool - #38

Merged
pufit merged 4 commits into
mainfrom
pufit/file-management
Apr 8, 2026
Merged

Add file management: upload, download, and send_file tool#38
pufit merged 4 commits into
mainfrom
pufit/file-management

Conversation

@pufit

@pufitpufit commented Apr 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Upload files/images via web chat: paperclip button, drag-and-drop, clipboard paste (Ctrl+V) with preview strip and upload progress
  • Download workspace files: authenticated GET /api/files/download endpoint with path traversal protection
  • send_file tool: agent can send files as inline download cards in chat — persists across page reload
  • Auth fix: JWT token accepted via query param for <img src> and <a download> that can't set headers

Backend

  • DB migration v022: uploaded_files table + FileStore mixin
  • POST /api/files/upload: multipart upload → disk + DB index
  • GET /api/files/uploads/{id}: serve uploaded files for chat history
  • GET /api/files/download: serve workspace files with path traversal protection
  • WebSocket message handler accepts file_ids, loads files and passes to engine.run(images=...) reusing the Telegram pipeline
  • engine.py stores image refs in user message blocks column for persistence
  • send_file session-scoped tool in tools.py
  • Session deletion cleans up uploaded files from disk and DB

Frontend

  • ImageBlockData and FileBlockData types added to MessageBlock union
  • ChatInput: paperclip file picker, drag-and-drop overlay, clipboard paste (onPaste handler)
  • UserMessage: renders attached images as thumbnails, files as download cards
  • BlockRenderer: handles image and file block types
  • ToolCallBlock: SendFileBlock renders send_file tool calls as clean download cards (no generic tool chrome)
  • chatStore.sendMessage extended with fileIds and imageBlocks
  • hydrateMessage: restores image/file blocks from DB for chat history

Test plan

  • All 326 tests pass
  • Frontend builds clean (tsc + vite)
  • Upload image via file picker → appears in chat as thumbnail → Claude receives and describes it
  • Paste screenshot (Ctrl+V) → same flow
  • send_file tool → download card appears inline → download works
  • Reload page → uploaded images and sent files still display
  • No secrets in diff (scanned)

Generated by Nerve

pufit added 4 commits April 7, 2026 19:17
…-produced files
Backend:
- New uploaded_files table (v022 migration) + FileStore DB mixin
- POST /api/files/upload: multipart upload, stores on disk + DB index
- GET /api/files/uploads/{id}: serve uploaded files for chat history
- GET /api/files/download: serve workspace files with path traversal protection
- WebSocket message handler extended to accept file_ids, loads files
and passes them to engine.run(images=...) reusing the Telegram pipeline
- engine.py stores image refs in user message blocks column for persistence
- Session deletion cleans up uploaded files from disk and DB
Frontend:
- New ImageBlockData and FileBlockData types in MessageBlock union
- ChatInput: paperclip file picker, drag-and-drop, clipboard paste (Ctrl+V)
with preview strip showing upload progress
- UserMessage: renders attached images as thumbnails, files as download cards
- BlockRenderer: handles image and file block types in assistant messages
- chatStore.sendMessage extended with fileIds and imageBlocks params
- hydrateMessage: restores image/file blocks from DB for chat history
- api/client.ts: uploadFiles method with multipart FormData
- api/websocket.ts: sendMessage accepts optional fileIds
Browser <img src> and <a download> can't set Authorization headers,
so file/image URLs need ?token= query parameter. Added query param
support to get_token_from_request() and appended token to all image
and file URLs in the frontend.
New session-scoped `send_file` tool that broadcasts a `file_attachment`
WS event. The frontend handler injects a downloadable file block into
the streaming assistant message, rendering it as a clickable card with
filename and download link.
Reverted the auto-inject approach (file_changed → file block) in favor
of this explicit tool — agent decides when to send files.
- Added SendFileBlock renderer in ToolCallBlock router — send_file
tool calls render as a clean download card instead of generic tool UI
- Removed file_attachment WS event approach — the tool_call block
itself is persisted in DB, so downloads survive page reload
- Cleaned up unused handleFileAttachment handler and WS type
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@pufit