Chuyển tới nội dung chính

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"
}
FieldMô tả
pathĐường dẫn virtual trong workspace (ví dụ /report.md)
icon_typeLoại file: md, pdf, code, image, csv, xlsx, v.v.
source"generated" (agent tạo) hoặc "upload" (user upload)
message_idID 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 path sẽ đượ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 file upload (user gửi kèm message) đều xuất hiện trong panel, phân biệt bằng source field.

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_typeOutput
"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');
}
}