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