# Export & Utility ### Health Check ``` GET /health → { "status": "ok" } ``` Public, no auth. Also available at `/api/v1/health`. ### Export Convert markdown to PDF or DOCX via pandoc: ``` POST /export ``` ```json { "content": "# My Document\n\nBody text...", "format": "pdf|docx", "filename": "my-document" } ``` Returns the binary file with appropriate Content-Type. Requires `pandoc` in the container (available in the unified and backend Docker images). --- ## 17b. Files & Storage All binary content in Chat Switchboard flows through a single **ObjectStore** abstraction backed by PVC (filesystem) or S3. All file metadata lives in one `files` table. The old `attachments` table is dropped. ### Storage Backend | Operation | Description | |-----------|-------------| | `Put(key, reader, size, contentType)` | Store a blob | | `Get(key)` → reader, size, contentType | Retrieve a blob | | `Delete(key)` | Remove a blob | | `DeletePrefix(prefix)` | Bulk remove (channel deletion) | | `Exists(key)` | Check without reading | | `Healthy()` | Backend health check | | `Stats()` | Aggregate metrics | Backends: `pvc` (`STORAGE_PATH=/data/storage`) or `s3` (`S3_ENDPOINT`, `S3_BUCKET`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`). ### Key Namespacing | Prefix | Pattern | What | |--------|---------|------| | `attachments/` | `attachments/{channel_id}/{file_id}_{filename}` | User uploads + AI outputs (legacy prefix retained for existing blobs) | | `kb/` | `kb/{kb_id}/{doc_id}_{filename}` | Knowledge base documents | | `exports/` | `exports/{user_id}/{export_id}_{filename}` | Chat/data exports | | `workspace/` | `workspace/{workspace_id}/{path}` | Workspace files | ### File Object ```sql CREATE TABLE files ( id UUID PRIMARY KEY, channel_id UUID REFERENCES channels(id) ON DELETE CASCADE, message_id UUID REFERENCES messages(id) ON DELETE SET NULL, user_id UUID REFERENCES users(id) ON DELETE SET NULL, project_id UUID REFERENCES projects(id) ON DELETE SET NULL, origin TEXT NOT NULL DEFAULT 'user_upload' CHECK (origin IN ('user_upload','tool_output','system')), filename TEXT NOT NULL, content_type TEXT NOT NULL DEFAULT 'application/octet-stream', size_bytes BIGINT NOT NULL DEFAULT 0, storage_key TEXT NOT NULL, display_hint TEXT NOT NULL DEFAULT 'download' CHECK (display_hint IN ('inline','download','thumbnail')), extracted_text TEXT, metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); ``` **Origin:** | Value | Who creates | Examples | |-------|-------------|---------| | `user_upload` | User via upload UI | PDF, image, doc attached to message | | `tool_output` | Tool execution during completion | DALL-E image, video gen, code artifact | | `system` | Platform | Export, avatar stored as file | **Display hint:** | Value | Frontend behavior | |-------|-------------------| | `inline` | Render in message stream (images, video, audio, HTML) | | `download` | Filename + size + download button | | `thumbnail` | Thumbnail preview, click to expand | ### Tool Output Flow 1. Tool executes (image gen, video render, etc.) 2. Tool writes blob via `ObjectStore.Put` 3. Tool creates file record: `POST /files` with `origin: "tool_output"`, `message_id` = current assistant message 4. Tool result includes `file_id` reference 5. WebSocket emits `file.created` event: ```json { "event": "file.created", "channel_id": "uuid", "message_id": "uuid", "file": { ...file object... } } ``` 6. Frontend renders inline based on `display_hint` + `content_type` The event fires during streaming so the user sees the artifact before the completion finishes. ### Content Type → Display Hint Defaults | Content Type | Default Hint | Rendering | |-------------|-------------|-----------| | `image/*` | `inline` | `` in message, lightbox on click | | `video/*` | `inline` | `