SSE API
Import Progress
連線資訊
| 項目 | 值 |
|---|---|
| 基礎路徑 | https://vas-poc.vurbo.ai/api/v1/sse |
| 協定 | HTTP + Server-Sent Events (SSE) |
| 資料格式 | text/event-stream |
| 認證方式 | Header X-API-Key: {KEY} |
注意:瀏覽器原生 EventSource API 不支援自訂 Header,需使用 fetch API 搭配 ReadableStream,或使用支援 Header 的 SSE 客戶端套件。
端點總覽
| 方法 | 端點 | 說明 |
|---|---|---|
| GET | /api/v1/sse/imports/{importId}/progress | 即時追蹤音檔匯入進度 |
GET /api/v1/sse/imports/{importId}/progress
功能說明
即時追蹤音檔匯入的處理進度。連線後透過 SSE 串流持續推送進度更新,直到匯入完成、失敗或連線超時。
使用場景
- 上傳音檔後即時顯示處理進度條
- 追蹤音檔轉換、轉錄、翻譯、摘要等各階段進展
認證方式
Header:X-API-Key(詳見 認證機制)
請求參數
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
importId | path | string | 是 | 匯入任務 ID(UUID) |
請求範例
curl -N "https://vas-poc.vurbo.ai/api/v1/sse/imports/550e8400-e29b-41d4-a716-446655440000/progress" \
-H "X-API-Key: vas_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1vW"
// 使用 fetch API(因 EventSource 不支援 Header)
async function connectSSE(importId, apiKey) {
const response = await fetch(
`https://vas-poc.vurbo.ai/api/v1/sse/imports/${importId}/progress`,
{
headers: {
'X-API-Key': apiKey
}
}
);
const reader = response.body.getReader();
// ... 處理 SSE 事件
}
事件序列
情境一:匯入尚在處理中
┌────────────────────────────────────────────────────┐
│ 1. connected → 連線確認 │
│ 2. progress → 發送目前進度 │
│ 3. progress ×N → 進度有變化時持續推送 │
│ heartbeat ×N → 每 15 秒無進度變化時發送心跳 │
│ 4. completed → 匯入成功,連線結束 │
│ 或 failed → 匯入失敗,連線結束 │
│ 或 timeout → 超過 15 分鐘,連線結束 │
└────────────────────────────────────────────────────┘
情境二:匯入已完成(終態)
┌────────────────────────────────────────────────────┐
│ 1. connected → 連線確認 │
│ 2. progress → 發送最終進度 │
│ 3. completed → 直接發送完成事件並結束 │
│ 或 failed → 直接發送失敗事件並結束 │
└────────────────────────────────────────────────────┘
事件格式
connected
連線成功確認。
event: connected
data: {"message":"匯入進度服務已連線 (importId: 550e8400-e29b-41d4-a716-446655440000)"}
{
"message": "匯入進度服務已連線 (importId: xxx)"
}
| 欄位 | 類型 | 說明 |
|---|---|---|
message | string | 連線確認訊息 |
progress
處理進度更新。當進度百分比有變化時發送。
event: progress
data: {"import_id":"550e8400-...","status":"processing","stage":"transcribing","progress":45,"message":"轉錄中..."}
{
"import_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"stage": "transcribing",
"progress": 45,
"message": "轉錄中..."
}
| 欄位 | 類型 | 說明 |
|---|---|---|
import_id | string | 匯入任務 ID(UUID) |
status | string | 匯入狀態,見下方「狀態與階段」 |
stage | string | null | 目前處理階段,見下方「狀態與階段」 |
progress | integer | 進度百分比(0-100) |
message | string | 可讀的進度訊息 |
狀態(status)值:
| 值 | 說明 |
|---|---|
pending | 等待處理 |
processing | 處理中 |
completed | 已完成 |
failed | 已失敗 |
階段(stage)值與對應進度範圍:
| 值 | 說明 | 進度範圍 |
|---|---|---|
converting | 音檔格式轉換 | 0% - 10% |
transcribing | 語音轉文字 | 10% - 60% |
translating | 文字翻譯 | 60% - 85% |
summarizing | 產生摘要 | 85% - 100% |
null | 尚未開始(pending 狀態) | — |
completed
匯入處理完成。收到此事件後連線將自動結束。
event: completed
data: {"import_id":"550e8400-...","status":"completed","task_id":"abc123-...","message":"處理完成"}
{
"import_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"task_id": "abc123-e29b-41d4-a716-446655440000",
"message": "處理完成"
}
| 欄位 | 類型 | 說明 |
|---|---|---|
import_id | string | 匯入任務 ID(UUID) |
status | string | 固定為 completed |
task_id | string | 產生的錄音 ID(recording_id),可用於後續查詢 |
message | string | 固定為 處理完成 |
邊界情境(v1.3.5):若音檔因靜音、音量過小、雜訊或辨識語言與音檔不符導致無法辨識出任何語音內容,仍會以
completed事件結束(不是failed),task_id正常產出,但後續透過GET /api/v1/sse/history/transcribe/{taskId}載入的逐字稿句子數為0。客戶端應以句子數判斷是否顯示「無語音內容」空狀態,而非將其視為錯誤。詳見 音檔匯入指南 – 音檔無法辨識時的行為。
failed
匯入處理失敗。收到此事件後連線將自動結束。
event: failed
data: {"import_id":"550e8400-...","status":"failed","error_code":"import_invalid_format","error_message":"不支援的音檔格式"}
{
"import_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "failed",
"error_code": "import_invalid_format",
"error_message": "不支援的音檔格式"
}
| 欄位 | 類型 | 說明 |
|---|---|---|
import_id | string | 匯入任務 ID(UUID) |
status | string | 固定為 failed |
error_code | string | 錯誤代碼 |
error_message | string | 可讀的錯誤訊息 |
heartbeat
心跳事件,每 15 秒在進度無變化時發送,用於保持連線活躍。
event: heartbeat
data: {"timestamp":1708761600}
{
"timestamp": 1708761600
}
| 欄位 | 類型 | 說明 |
|---|---|---|
timestamp | integer | Unix 時間戳 |
timeout
連線超時事件。超過 15 分鐘未完成時發送,連線將自動結束。
event: timeout
data: {"message":"連線超時"}
{
"message": "連線超時"
}
| 欄位 | 類型 | 說明 |
|---|---|---|
message | string | 固定為 連線超時 |
特有錯誤碼
連線建立後,若匯入任務不存在,會透過 error 事件回傳:
| 錯誤碼 | 說明 | 處理建議 |
|---|---|---|
import_not_found | 找不到指定的匯入任務 | 確認 importId 正確 |
前端範例
async function trackImportProgress(importId, apiKey) {
const response = await fetch(
`https://vas-poc.vurbo.ai/api/v1/sse/imports/${importId}/progress`,
{
headers: {
'X-API-Key': apiKey
}
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 解析 SSE 格式:event: xxx\ndata: {...}\n\n
const events = buffer.split('\n\n');
buffer = events.pop(); // 保留未完成的部分
for (const eventStr of events) {
if (!eventStr.trim()) continue;
const lines = eventStr.split('\n');
let eventType = '';
let eventData = '';
for (const line of lines) {
if (line.startsWith('event: ')) {
eventType = line.slice(7);
} else if (line.startsWith('data: ')) {
eventData = line.slice(6);
}
}
if (!eventType || !eventData) continue;
const data = JSON.parse(eventData);
switch (eventType) {
case 'connected':
console.log('已連線:', data.message);
break;
case 'progress':
console.log(`[${data.stage}] ${data.progress}% - ${data.message}`);
// 更新 UI 進度條
updateProgressBar(data.progress, data.stage, data.message);
break;
case 'completed':
console.log('匯入完成! 錄音 ID:', data.task_id);
// 導向錄音詳情頁
navigateToRecording(data.task_id);
break;
case 'failed':
console.error('匯入失敗:', data.error_code, data.error_message);
// 顯示錯誤訊息
showError(data.error_message);
break;
case 'heartbeat':
console.log('心跳:', new Date(data.timestamp * 1000));
break;
case 'timeout':
console.warn('連線超時:', data.message);
break;
case 'error':
console.error('錯誤:', data);
break;
}
}
}
}
版本:V1.5.7 最後更新:2026-05-20