跳转到内容

Progress Bar — 长任务进度条

LLM 调用、文件处理、数据导出等长任务前发个 progress 卡,让用户看到 “还在跑” + “ETA”,胜过沉默 30 秒后突然吐结果。

前提

代码

import { HasheeAgent } from "@hasheeai/agent-sdk-ts";
import { ulid } from "ulid";
const agent = await HasheeAgent.init({
agentId: process.env.HASHEE_AGENT_ID!,
token: process.env.HASHEE_AGENT_TOKEN!,
baseUrl: "https://api.hashee.ai",
connectionMode: "websocket",
});
async function withProgress<T>(
convId: string,
title: string,
totalSteps: number,
task: (step: (n: number, msg?: string) => Promise<void>) => Promise<T>,
): Promise<T> {
const artifactId = ulid();
let revision = 0;
await agent.sendArtifact(convId, {
artifact: {
artifact_id: artifactId,
subtype: "progress",
title,
payload: { percent: 0, status: "starting", eta_s: null },
},
});
const startedAt = Date.now();
const updateStep = async (current: number, message?: string) => {
const percent = Math.min(100, Math.round((current / totalSteps) * 100));
const elapsed = (Date.now() - startedAt) / 1000;
const eta = current > 0 ? Math.round((elapsed / current) * (totalSteps - current)) : null;
revision++;
try {
await agent.updateArtifact(convId, {
ref_artifact: artifactId,
based_on_revision: revision - 1,
artifact: {
payload: {
percent,
status: message ?? (percent < 100 ? "running" : "done"),
eta_s: eta,
},
},
});
} catch (e: any) {
if (e.code !== "ARTIFACT_REVISION_CONFLICT") throw e;
}
};
const result = await task(updateStep);
// 完成
revision++;
await agent.updateArtifact(convId, {
ref_artifact: artifactId,
based_on_revision: revision - 1,
artifact: { payload: { percent: 100, status: "done", eta_s: 0 } },
});
return result;
}
// 用法:处理 100 个文件
agent.addMessageHandler(async (msg) => {
if (msg.payload?.type !== "text") return;
if (msg.payload.text !== "/process") return;
const files = Array.from({ length: 50 }, (_, i) => `file-${i}.csv`);
const summary = await withProgress(
msg.conversation_id,
`处理 ${files.length} 个文件`,
files.length,
async (step) => {
const results = [];
for (let i = 0; i < files.length; i++) {
// 模拟处理
await new Promise((r) => setTimeout(r, 100));
results.push({ file: files[i], ok: true });
if (i % 5 === 0 || i === files.length - 1) {
await step(i + 1, `处理中: ${files[i]}`);
}
}
return results;
},
);
await agent.send(msg.conversation_id, {
type: "text",
text: `✓ 全部 ${summary.length} 个文件处理完成`,
});
});
console.log("[progress-bot] up");

UI 渲染

┌─────────────────────────────────────┐
│ 处理 50 个文件 │
├─────────────────────────────────────┤
│ ████████░░░░░░░ 60% (30/50) │
│ 处理中: file-30.csv │
│ ETA: 8 秒 │
└─────────────────────────────────────┘

性能建议

  • 不要每个 step 都 update — 5-10% 节奏即可(每秒 update 不超过 5 次)
  • 加 throttle / debounce 防止短任务洪水
  • try/catch 容忍 ARTIFACT_REVISION_CONFLICT(并发场景)

进阶