File size: 3,285 Bytes
b8bddd1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | import "./WorkflowTimeline.css";
/**
* Renders a timeline of workflow events.
* Constructed from workflow metadata (started_at, completed_at, status)
* and proposal data (created_at, reviewed_at, status).
*/
export function WorkflowTimeline({ workflow, proposals = [] }) {
const events = [];
// Workflow start
if (workflow.started_at) {
events.push({
time: new Date(workflow.started_at),
icon: "βΆ",
label: "Workflow started",
tone: "default",
});
}
// Infer extraction/processing from the delta
if (workflow.status !== "PENDING") {
events.push({
time: new Date(new Date(workflow.started_at).getTime() + 1000),
icon: "π",
label: "Document extracted and processed",
tone: "default",
});
}
// If waiting for review or completed with proposals
if (proposals.length > 0) {
const earliest = proposals.reduce(
(min, p) => (p.created_at && new Date(p.created_at) < min ? new Date(p.created_at) : min),
new Date()
);
events.push({
time: earliest,
icon: "π",
label: `${proposals.length} proposal(s) generated`,
tone: "default",
});
}
// Validation failed β review requested
if (workflow.status === "WAITING_FOR_REVIEW" || workflow.validation_results?.some((r) => r.status === "FAIL" || r.status === "WARNING")) {
events.push({
time: new Date(new Date(workflow.started_at).getTime() + 3000),
icon: "β οΈ",
label: "Validation triggered human review",
tone: "warning",
});
}
// Proposal decisions
for (const p of proposals) {
if (p.status === "APPROVED" && p.reviewed_at) {
events.push({
time: new Date(p.reviewed_at),
icon: "β",
label: `Approved: ${p.summary?.slice(0, 50) || "proposal"}`,
tone: "success",
});
}
if (p.status === "REJECTED" && p.reviewed_at) {
events.push({
time: new Date(p.reviewed_at),
icon: "β",
label: `Rejected: ${p.summary?.slice(0, 50) || "proposal"}`,
tone: "danger",
});
}
}
// Workflow completed
if (workflow.completed_at) {
events.push({
time: new Date(workflow.completed_at),
icon: "β
",
label: "Workflow completed",
tone: "success",
});
}
// Sort by time
events.sort((a, b) => a.time - b.time);
if (events.length === 0) return null;
return (
<div className="dw-timeline">
{events.map((event, i) => (
<div key={i} className={`dw-timeline__item dw-timeline__item--${event.tone}`}>
<span className="dw-timeline__time">
{event.time.toLocaleTimeString()}
</span>
<span className="dw-timeline__icon">{event.icon}</span>
<span className="dw-timeline__label">{event.label}</span>
</div>
))}
</div>
);
}
|