Workspace Files
Workspace là không gian file riêng của mỗi session. Agent tạo file (báo cáo, kết quả phân tích, code...) qua các tool write_file / edit_file và lưu vào đây. User có thể xem và tải về.
Cấu Trúc File
{
"id": "uuid",
"path": "/report.md",
"filename": "report.md",
"icon_type": "md",
"source": "generated",
"created_at": "2024-03-17T10:00:00Z",
"url": "workspace/user123/report.md",
"message_id": "msg-uuid"
}
| Field | Mô tả |
|---|---|
path | Đường dẫn virtual trong workspace (ví dụ /report.md) |
icon_type | Loại file: md, pdf, code, image, csv, xlsx, v.v. |
source | "generated" (agent tạo) hoặc "upload" (user upload) |
message_id | ID message trong turn agent tạo file — dùng để group theo turn |
Realtime Stream — Workspace Panel
Khi agent gọi write_file hoặc edit_file, server emit tool_result block kèm artifact chứa thông tin file. Frontend lắng nghe sự kiện này để cập nhật WorkingFolderPanel ngay lập tức.
SSE event:
{
"type": "content_block_start",
"index": 3,
"content_block": {
"type": "tool_result",
"tool_use_id": "toolu_01",
"name": "write_file",
"status": "success",
"artifact": {
"path": "/report.md",
"filename": "report.md",
"icon_type": "md",
"source": "generated"
}
}
}
Frontend handler:
function handleWorkspaceFilesSSEEvent(data, setWorkspaceFiles) {
if (data.type === 'content_block_start' && data.content_block?.type === 'tool_result') {
const name = data.content_block.name;
if ((name === 'write_file' || name === 'edit_file') && data.content_block.artifact) {
const fileInfo = data.content_block.artifact;
if (fileInfo.path && fileInfo.filename) {
setWorkspaceFiles(prev => {
const current = prev || [];
const filtered = current.filter(f => f.path !== fileInfo.path);
return [...filtered, fileInfo];
});
}
}
}
}
File cùng
pathsẽ được replace (upsert) thay vì append — đảm bảo không có file trùng.
Realtime Stream — FilesBlock Trong AI Message
Sau khi agent hoàn thành một turn, nếu có file chưa được gửi về client, FileResponseMiddleware emit thêm một attachments block vào cuối message AI. Block này render thành FilesBlock — danh sách file card trong message bubble.
SSE event:
{
"type": "content_block_start",
"index": 5,
"content_block": {
"type": "attachments",
"files": [
{
"path": "/report.md",
"filename": "report.md",
"icon_type": "md",
"source": "generated"
}
]
}
}
Followed by content_block_stop ngay sau:
{
"type": "content_block_stop",
"index": 5
}
Frontend render FilesBlock — mỗi file là một card có thể click để mở FileViewer.
History (F5 Reload)
Workspace Panel
WorkingFolderPanel restore từ workspace object trong history response:
{
"workspace": {
"workspace_files": [
{
"path": "/report.md",
"filename": "report.md",
"icon_type": "md",
"source": "generated",
"url": "workspace/user123/report.md",
"message_id": "msg-uuid"
},
{
"path": "s3://bucket/uploads/user123/file456/bao-cao.pdf",
"filename": "bao-cao.pdf",
"icon_type": "pdf",
"source": "upload",
"url": "uploads/user123/file456/bao-cao.pdf"
}
]
}
}
const restoredFiles = historyData?.workspace?.workspace_files || null;
setWorkspaceFiles(restoredFiles);
Cả file
generated(agent tạo) lẫn fileupload(user gửi kèm message) đều xuất hiện trong panel, phân biệt bằngsourcefield.
FilesBlock Trong AI Message
attachments block trong AI message được restore từ msg.attachments trong history response:
{
"messages": [
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Tôi đã tạo xong báo cáo:"
}
],
"attachments": [
{
"path": "/report.md",
"filename": "report.md",
"icon_type": "md",
"source": "generated"
}
]
}
]
}
parseV2History() append vào cuối blocks array của AI message:
// Trong parseV2History() — sau khi build blocks từ content
if (msg.attachments && msg.attachments.length > 0) {
blocks.push({
type: 'attachments',
files: msg.attachments,
});
}
// Kết quả AI message object
{
id: "session-history-1",
type: "ai_answer",
isStreaming: false,
blocks: [
{ type: "text", content: "Tôi đã tạo xong báo cáo:" },
{
type: "attachments",
files: [
{ path: "/report.md", filename: "report.md", icon_type: "md", source: "generated" }
]
}
]
}
FilesBlock render từ blocks array này — y hệt như lúc stream realtime.
API: Đọc Nội Dung File
GET /v2/sessions/{session_id}/files/content?file_path=/report.md
Authorization: Bearer {token}
Response — File text (md, txt, csv, json):
{
"content": "# Báo cáo phân tích\n\nNội dung...",
"filename": "report.md",
"file_path": "/report.md",
"download_url": null
}
Response — File nhị phân (pdf, xlsx, v.v.):
{
"content": null,
"filename": "report.pdf",
"file_path": "/report.pdf",
"download_url": "https://s3.amazonaws.com/...?X-Amz-Expires=3600..."
}
Download URL có hạn 1 giờ.
API: Chuyển Đổi Định Dạng
Chuyển markdown thành PDF hoặc DOCX:
POST /v2/sessions/{session_id}/files/convert
Authorization: Bearer {token}
Content-Type: application/json
{
"file_path": "/report.md",
"convert_type": "pdf"
}
convert_type | Output |
|---|---|
"pdf" | Markdown → PDF |
"docx" | Markdown → Word Document |
"md" | Download raw markdown |
Response:
{
"download_url": "https://s3.amazonaws.com/converted/...?expires=3600",
"filename": "report.pdf"
}
Click Vào File
Click vào file trong WorkingFolderPanel hoặc FilesBlock → mở FileViewer modal.
Chi tiết FileViewer xem tại File Viewer.
Ví Dụ: Fetch Workspace Files
async function viewWorkspaceFile(sessionId, filePath) {
const res = await fetch(
`/v2/sessions/${sessionId}/files/content?file_path=${encodeURIComponent(filePath)}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const { content, download_url, filename } = await res.json();
if (content) {
return <MarkdownPreview content={content} />;
} else {
window.open(download_url, '_blank');
}
}