Initial Daily Campaign Agent release
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { Check, Circle, FileDown, FileSpreadsheet, LoaderCircle, MessageSquareText, Sparkles, TriangleAlert } from "lucide-react";
|
||||
import type { HarnessEvent } from "@agent-studio/shared";
|
||||
import { currentRunEvents } from "./analysisProgressState.js";
|
||||
|
||||
type StageStatus = "pending" | "running" | "complete" | "error";
|
||||
|
||||
const eventLabel: Partial<Record<HarnessEvent["type"], string>> = {
|
||||
"attachment.uploaded": "报表上传完成",
|
||||
"skill.requested": "正在读取日报分析规则",
|
||||
"skill.loaded": "日报分析规则已加载",
|
||||
"tool.requested": "开始解析广告数据",
|
||||
"tool.completed": "数据解析完成",
|
||||
"tool.failed": "数据解析失败",
|
||||
"assistant.delta": "正在撰写日报结论",
|
||||
"assistant.completed": "日报内容生成完成",
|
||||
"artifact.ready": "Word 报告已生成",
|
||||
"artifact.failed": "Word 报告生成失败",
|
||||
"run.completed": "本轮任务已完成",
|
||||
"run.failed": "本轮任务失败"
|
||||
};
|
||||
|
||||
function getEventLabel(event: HarnessEvent) {
|
||||
if (event.type === "tool.requested" && event.payload.toolName === "build_daily_brief") return "正在套用 Skill 日报模板";
|
||||
if (event.type === "tool.completed" && event.payload.toolName === "build_daily_brief") return "Skill 模板 Word 生成完成";
|
||||
if (event.type === "tool.failed" && event.payload.toolName === "build_daily_brief") return "Skill 模板 Word 生成失败";
|
||||
return eventLabel[event.type];
|
||||
}
|
||||
|
||||
function has(events: HarnessEvent[], type: HarnessEvent["type"], predicate?: (event: HarnessEvent) => boolean) {
|
||||
return events.some((event) => event.type === type && (!predicate || predicate(event)));
|
||||
}
|
||||
|
||||
function stageStatus(events: HarnessEvent[], stage: "upload" | "skill" | "analysis" | "report" | "word"): StageStatus {
|
||||
const runEvents = currentRunEvents(events);
|
||||
const failed = has(runEvents, "run.failed");
|
||||
if (stage === "upload") return has(events, "attachment.uploaded") ? "complete" : runEvents.length ? "running" : "pending";
|
||||
if (stage === "skill") return has(events, "skill.loaded") ? "complete" : has(runEvents, "skill.requested") ? "running" : failed ? "error" : "pending";
|
||||
if (stage === "analysis") {
|
||||
if (has(runEvents, "tool.failed", (event) => event.type === "tool.failed" && event.payload.toolName === "analyze_uploaded_report")) return "error";
|
||||
if (has(runEvents, "tool.completed", (event) => event.type === "tool.completed" && event.payload.toolName === "analyze_uploaded_report")) return "complete";
|
||||
return has(runEvents, "tool.requested", (event) => event.type === "tool.requested" && event.payload.toolName === "analyze_uploaded_report") ? "running" : "pending";
|
||||
}
|
||||
if (stage === "report") return has(runEvents, "tool.requested", (event) => event.type === "tool.requested" && event.payload.toolName === "build_daily_brief") ? "complete" : has(runEvents, "assistant.delta") || has(runEvents, "tool.completed", (event) => event.type === "tool.completed" && event.payload.toolName === "analyze_uploaded_report") ? "running" : "pending";
|
||||
if (has(runEvents, "artifact.failed")) return "error";
|
||||
if (has(runEvents, "artifact.ready")) return "complete";
|
||||
return has(runEvents, "tool.requested", (event) => event.type === "tool.requested" && event.payload.toolName === "build_daily_brief") ? "running" : "pending";
|
||||
}
|
||||
|
||||
const stages = [
|
||||
{ id: "upload" as const, label: "接收广告报表", detail: "Excel / CSV 数据", Icon: FileSpreadsheet },
|
||||
{ id: "skill" as const, label: "加载分析规则", detail: "daily-campaign-triage", Icon: Sparkles },
|
||||
{ id: "analysis" as const, label: "解析与计算", detail: "指标、异常与趋势", Icon: LoaderCircle },
|
||||
{ id: "report" as const, label: "生成日报", detail: "结论与行动建议", Icon: MessageSquareText },
|
||||
{ id: "word" as const, label: "输出 Word", detail: "可下载报告文件", Icon: FileDown }
|
||||
];
|
||||
|
||||
export function AnalysisProgress({ events, running, reconnecting }: { events: HarnessEvent[]; running: boolean; reconnecting: boolean }) {
|
||||
const recent = events.filter((event) => getEventLabel(event)).slice(-6).reverse();
|
||||
return <aside className="side-panel progress-panel" aria-label="分析任务进度">
|
||||
<div className="panel-heading"><span>分析任务</span><i className={running ? "is-running" : ""}>{reconnecting ? "重连中" : running ? "运行中" : "就绪"}</i></div>
|
||||
<div className="progress-summary">
|
||||
<span className="progress-summary__eyebrow">唯一内置能力</span>
|
||||
<strong>Daily Campaign Triage</strong>
|
||||
<p>自动读取广告报表,识别异常并输出日报。</p>
|
||||
</div>
|
||||
<ol className="stage-list">
|
||||
{stages.map(({ id, label, detail, Icon }) => { const status = stageStatus(events, id); return <li key={id} className={`stage stage--${status}`}>
|
||||
<span className="stage__icon"><Icon size={16}/></span>
|
||||
<div><strong>{label}</strong><small>{detail}</small></div>
|
||||
<span className="stage__state" aria-label={status}>{status === "complete" ? <Check size={14}/> : status === "running" ? <LoaderCircle size={14}/> : status === "error" ? <TriangleAlert size={14}/> : <Circle size={9}/>}</span>
|
||||
</li>; })}
|
||||
</ol>
|
||||
<div className="task-log"><h3>最近动态</h3>{recent.length === 0 ? <p className="panel-state">上传报表后,过程会显示在这里。</p> : <ol>{recent.map((event) => <li key={event.eventId}><span/><div><strong>{getEventLabel(event)}</strong><time>{new Date(event.timestamp).toLocaleTimeString("zh-CN", { hour12: false })}</time></div></li>)}</ol>}</div>
|
||||
</aside>;
|
||||
}
|
||||
Reference in New Issue
Block a user