@eidentic/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+ // Copied by `eidentic add component` — yours to edit.
2
+ "use client";
3
+
4
+ import React from "react";
5
+ import { useWorkflowRun } from "@eidentic/react";
6
+ import type { StepTrace } from "@eidentic/react";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Props
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export interface WorkflowTraceProps {
13
+ /** Workflow run ID to display, or null to show empty state. */
14
+ id: string | null;
15
+ /** Base URL of the Eidentic server. Defaults to same-origin (""). */
16
+ baseUrl?: string;
17
+ /** Class name applied to the outermost container. */
18
+ className?: string;
19
+ }
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Step row
23
+ // ---------------------------------------------------------------------------
24
+
25
+ function StatusBadge({ status }: { status: "ok" | "error" }) {
26
+ if (status === "ok") {
27
+ return (
28
+ <span
29
+ className="inline-flex items-center rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700 ring-1 ring-inset ring-emerald-200"
30
+ aria-label="Step passed"
31
+ >
32
+ ok
33
+ </span>
34
+ );
35
+ }
36
+ return (
37
+ <span
38
+ className="inline-flex items-center rounded-full bg-red-50 px-2 py-0.5 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-200"
39
+ aria-label="Step errored"
40
+ >
41
+ error
42
+ </span>
43
+ );
44
+ }
45
+
46
+ function StepRow({ step }: { step: StepTrace }) {
47
+ const indent = step.path.length;
48
+ // Each level of nesting adds 16px of left padding.
49
+ const paddingLeft = `${indent * 16 + 8}px`;
50
+
51
+ const durationLabel =
52
+ step.durationMs >= 1000
53
+ ? `${(step.durationMs / 1000).toFixed(2)}s`
54
+ : `${step.durationMs}ms`;
55
+
56
+ return (
57
+ <li
58
+ className="border-b border-zinc-100 last:border-0"
59
+ aria-label={`Step ${step.name}: ${step.status}`}
60
+ >
61
+ <div
62
+ className="flex items-start gap-3 py-2 pr-4"
63
+ style={{ paddingLeft }}
64
+ >
65
+ {/* Tree connector */}
66
+ {indent > 0 && (
67
+ <span className="mt-1.5 h-2 w-2 shrink-0 rounded-full border border-zinc-300" aria-hidden="true" />
68
+ )}
69
+
70
+ {/* Name */}
71
+ <span className="flex-1 truncate font-mono text-sm text-zinc-700">
72
+ {step.name}
73
+ </span>
74
+
75
+ {/* Duration */}
76
+ <span className="shrink-0 text-xs text-zinc-400">{durationLabel}</span>
77
+
78
+ {/* Badge */}
79
+ <StatusBadge status={step.status} />
80
+ </div>
81
+
82
+ {/* Inline error */}
83
+ {step.status === "error" && step.error && (
84
+ <p
85
+ className="px-3 pb-2 text-xs text-red-600"
86
+ style={{ paddingLeft: `calc(${paddingLeft} + 20px)` }}
87
+ role="alert"
88
+ >
89
+ {step.error}
90
+ </p>
91
+ )}
92
+ </li>
93
+ );
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // WorkflowTrace
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * Renders the step trace of a workflow run as an indented tree.
102
+ *
103
+ * Usage:
104
+ * <WorkflowTrace id={runId} baseUrl="http://localhost:3000" />
105
+ */
106
+ export function WorkflowTrace({ id, baseUrl = "", className = "" }: WorkflowTraceProps) {
107
+ const { run, trace, loading, error } = useWorkflowRun(id, { baseUrl });
108
+
109
+ return (
110
+ <div
111
+ className={`rounded-2xl border border-zinc-200 bg-white shadow-sm ${className}`}
112
+ role="region"
113
+ aria-label="Workflow trace"
114
+ aria-busy={loading}
115
+ >
116
+ {/* Header */}
117
+ <div className="flex items-center gap-3 border-b border-zinc-100 px-4 py-3">
118
+ <span className="text-sm font-medium text-zinc-800">
119
+ {run ? run.name : "Workflow trace"}
120
+ </span>
121
+ {run && (
122
+ <StatusBadge status={run.status} />
123
+ )}
124
+ {loading && (
125
+ <span className="ml-auto text-xs text-zinc-400" aria-live="polite">
126
+ Loading…
127
+ </span>
128
+ )}
129
+ </div>
130
+
131
+ {/* Body */}
132
+ <div className="p-2">
133
+ {/* Error fetching */}
134
+ {error && (
135
+ <div
136
+ role="alert"
137
+ className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700"
138
+ >
139
+ {error}
140
+ </div>
141
+ )}
142
+
143
+ {/* Empty / no id */}
144
+ {!id && !loading && !error && (
145
+ <p className="py-6 text-center text-sm text-zinc-400">
146
+ No workflow run selected.
147
+ </p>
148
+ )}
149
+
150
+ {/* Loading placeholder */}
151
+ {id && loading && trace.length === 0 && (
152
+ <ul aria-label="Loading steps" className="space-y-1 px-2 py-2">
153
+ {[1, 2, 3].map((n) => (
154
+ <li key={n} className="flex items-center gap-3 rounded-lg bg-zinc-50 px-3 py-2">
155
+ <span className="h-3 flex-1 animate-pulse rounded bg-zinc-200" />
156
+ <span className="h-3 w-12 animate-pulse rounded bg-zinc-200" />
157
+ </li>
158
+ ))}
159
+ </ul>
160
+ )}
161
+
162
+ {/* Step list */}
163
+ {trace.length > 0 && (
164
+ <ul aria-label="Workflow steps">
165
+ {trace.map((step, i) => (
166
+ <StepRow key={`${step.name}-${i}`} step={step} />
167
+ ))}
168
+ </ul>
169
+ )}
170
+
171
+ {/* Loaded but empty trace */}
172
+ {id && !loading && !error && trace.length === 0 && run && (
173
+ <p className="py-6 text-center text-sm text-zinc-400">No steps recorded.</p>
174
+ )}
175
+ </div>
176
+
177
+ {/* Footer: total duration */}
178
+ {run && (
179
+ <div className="border-t border-zinc-100 px-4 py-2 text-xs text-zinc-400">
180
+ {run.stepCount} step{run.stepCount !== 1 ? "s" : ""} &middot;{" "}
181
+ {run.durationMs >= 1000
182
+ ? `${(run.durationMs / 1000).toFixed(2)}s`
183
+ : `${run.durationMs}ms`}{" "}
184
+ total
185
+ </div>
186
+ )}
187
+ </div>
188
+ );
189
+ }