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