File Viewer
FileViewer là modal dùng chung để xem tất cả loại file — từ attachment trong user message, workspace file trong AI message, đến file trong WorkingFolderPanel sidebar.
Entry Points
| Nguồn | Trigger | Component |
|---|---|---|
| User message attachment | Click AttachmentChip trong message bubble | handleAttachmentClick() → ChatApp root modal |
| AI message file (FilesBlock) | Click FileCard trong attachments block | FileViewer mở từ FilesBlock |
| Workspace sidebar | Click file trong WorkingFolderPanel | FileViewer mở từ panel |
Props
interface FileViewerProps {
file: {
path: string; // Đường dẫn file trong workspace
filename: string; // Tên hiển thị
icon_type: string; // 'pdf', 'md', 'xlsx', 'csv', 'zip', 'image', 'json', 'txt', 'code'
source?: string; // 'generated' | 'upload'
};
sessionId: string; // Dùng để gọi content API
onClose: () => void;
}
Content Loading
FileViewer gọi API để lấy nội dung file khi mở:
GET /v2/sessions/{session_id}/files/content?file_path={path}
Authorization: Bearer {token}
Response text file:
{
"content": "# Báo cáo\n\nNội dung...",
"filename": "report.md",
"file_path": "/report.md",
"download_url": null
}
Response binary file:
{
"content": null,
"filename": "report.pdf",
"file_path": "/report.pdf",
"download_url": "https://s3.amazonaws.com/...?X-Amz-Expires=3600..."
}
Render Theo Loại File
icon_type | Renderer | Chi tiết |
|---|---|---|
pdf | <iframe> | Embed PDF với native browser controls |
image, png, jpg, gif, webp | <img> | Centered, max-height 60vh, giữ aspect ratio |
md | ReactMarkdown + prose | GFM, tables, code blocks có copy button |
xlsx, xls, csv | SpreadsheetRenderer | Excel viewer, cell selection, sheet tabs |
zip | ZipViewer | Archive viewer, list files bên trong |
json, txt, code, py, js, v.v. | <pre><code> | Monospace, whitespace preserved |
| Unsupported | Placeholder | Icon + message + download button |
Tính Năng
Download
- Binary file (pdf, xlsx...): Tải thẳng từ
download_url - Text file (md, txt...): Tải nội dung dưới dạng raw file
Convert (Markdown only)
Khi file là .md, có dropdown menu chuyển đổi:
POST /v2/sessions/{session_id}/files/convert
Authorization: Bearer {token}
Content-Type: application/json
{
"file_path": "/report.md",
"convert_type": "pdf"
}
convert_type | Output |
|---|---|
"md" | Download raw markdown |
"pdf" | Chuyển thành PDF (có CSS styling) |
"docx" | Chuyển thành Word Document |
Response:
{
"download_url": "https://s3.amazonaws.com/converted/...?expires=3600",
"filename": "report.pdf"
}
Download URL có hạn 1 giờ.
Expand / Collapse
- Default:
w-[1200px] h-[85vh] - Expanded:
w-screen h-screen - Toggle qua nút expand ở header
Keyboard
Escape→ đóng modal- Backdrop click → đóng modal
Animation
- Mở: zoom-in + backdrop fade-in
- Đóng: zoom-out + backdrop fade-out
Ví Dụ: Mở FileViewer
function FileCard({ file, sessionId }) {
const [viewerOpen, setViewerOpen] = useState(false);
return (
<>
<div onClick={() => setViewerOpen(true)}>
{file.filename}
</div>
{viewerOpen && (
<FileViewer
file={file}
sessionId={sessionId}
onClose={() => setViewerOpen(false)}
/>
)}
</>
);
}
Ví Dụ: Flutter
Flutter không dùng inline viewer — mở link download trong browser hoặc dùng native PDF viewer:
Future<void> openFile(String sessionId, String filePath, String iconType) async {
final res = await authorizedGet(
'/v2/sessions/$sessionId/files/content?file_path=${Uri.encodeComponent(filePath)}'
);
final data = jsonDecode(res.body);
if (data['content'] != null) {
// Text file — hiển thị trong in-app viewer
Navigator.push(context, MaterialPageRoute(
builder: (_) => TextFileViewer(content: data['content'], filename: data['filename']),
));
} else if (data['download_url'] != null) {
// Binary — mở URL trong browser
await launchUrl(Uri.parse(data['download_url']));
}
}
Future<void> convertAndDownload(String sessionId, String filePath, String convertType) async {
final res = await authorizedPost(
'/v2/sessions/$sessionId/files/convert',
body: {'file_path': filePath, 'convert_type': convertType},
);
final data = jsonDecode(res.body);
await launchUrl(Uri.parse(data['download_url']));
}