@llamaindex/workflow-debugger 0.2.0 → 0.2.1

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.
@@ -1,342 +0,0 @@
1
- import {
2
- useWorkflowsClient,
3
- Input,
4
- Button,
5
- Select,
6
- SelectContent,
7
- SelectItem,
8
- SelectTrigger,
9
- SelectValue,
10
- Popover,
11
- PopoverContent,
12
- PopoverTrigger,
13
- useWorkflows,
14
- } from "@llamaindex/ui";
15
- import { useState, useEffect, useCallback } from "react";
16
- import { getHealth } from "@llamaindex/workflows-client";
17
- import { WorkflowConfigPanel } from "./workflow-config-panel";
18
- import { RunListPanel } from "./run-list-panel";
19
- import { RunDetailsPanel } from "./run-details-panel";
20
- import {
21
- Settings,
22
- PanelLeftClose,
23
- PanelLeftOpen,
24
- PanelRightOpen,
25
- } from "lucide-react";
26
-
27
- // Utility to handle keyboard shortcuts
28
- function useKeyboardShortcut(key: string, callback: () => void, ctrl = true) {
29
- useEffect(() => {
30
- const handler = (e: KeyboardEvent) => {
31
- if (ctrl && e.ctrlKey && e.key.toLowerCase() === key.toLowerCase()) {
32
- e.preventDefault();
33
- callback();
34
- } else if (!ctrl && e.key.toLowerCase() === key.toLowerCase()) {
35
- e.preventDefault();
36
- callback();
37
- }
38
- };
39
- window.addEventListener("keydown", handler);
40
- return () => window.removeEventListener("keydown", handler);
41
- }, [key, callback, ctrl]);
42
- }
43
-
44
- // Utility to resolve base URL from query param or default
45
- function resolveBaseUrl(): string {
46
- const urlParams = new URLSearchParams(window.location.search);
47
- const apiParam = urlParams.get("api");
48
-
49
- if (apiParam) {
50
- return apiParam.endsWith("/") ? apiParam.slice(0, -1) : apiParam;
51
- }
52
-
53
- return "http://localhost:8000";
54
- }
55
-
56
- export function WorkflowDebugger() {
57
- const [baseUrl, setBaseUrl] = useState<string>(resolveBaseUrl);
58
- const [editingUrl, setEditingUrl] = useState<string>(baseUrl);
59
- const [selectedWorkflow, setSelectedWorkflow] = useState<string | null>(null);
60
- const [activeHandlerId, setActiveHandlerId] = useState<string | null>(null);
61
- const { state: workflowsState, sync: syncWorkflows } = useWorkflows();
62
- // Default to a 3/5 ratio (left/right) => 37.5% / 62.5%
63
- const [leftPanelWidth, setLeftPanelWidth] = useState(37.5); // percentage
64
- const [isDragging, setIsDragging] = useState(false);
65
- const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
66
- const [configPanelCollapsed, setConfigPanelCollapsed] = useState(false);
67
- const [isServerHealthy, setIsServerHealthy] = useState<boolean | null>(null);
68
- const [connectionError, setConnectionError] = useState<string | null>(null);
69
-
70
- const workflows = workflowsState.workflows;
71
- const workflowsClient = useWorkflowsClient();
72
-
73
- const checkHealth = useCallback(async (): Promise<void> => {
74
- try {
75
- setConnectionError(null);
76
- const { data, error } = await getHealth({ client: workflowsClient });
77
- if (error) {
78
- throw new Error(String(error));
79
- }
80
- if (data && (data as { status?: string }).status) {
81
- setIsServerHealthy(true);
82
- } else {
83
- setIsServerHealthy(false);
84
- setConnectionError("Workflow server is unreachable");
85
- }
86
- } catch {
87
- setIsServerHealthy(false);
88
- setConnectionError("Workflow server is unreachable");
89
- }
90
- }, [workflowsClient]);
91
-
92
- // Update client config when baseUrl changes
93
- useEffect(() => {
94
- workflowsClient.setConfig({ baseUrl });
95
- checkHealth();
96
- syncWorkflows();
97
- }, [baseUrl, workflowsClient, checkHealth, syncWorkflows]);
98
-
99
- const handleUrlSave = () => {
100
- const normalizedUrl = editingUrl.endsWith("/")
101
- ? editingUrl.slice(0, -1)
102
- : editingUrl;
103
- setBaseUrl(normalizedUrl);
104
- };
105
-
106
- const handleUrlReset = () => {
107
- const defaultUrl = "http://localhost:8000";
108
- setBaseUrl(defaultUrl);
109
- setEditingUrl(defaultUrl);
110
- };
111
-
112
- const handleRunStart = (handlerId: string) => {
113
- setActiveHandlerId(handlerId);
114
- };
115
-
116
- const handleMouseDown = (e: React.MouseEvent) => {
117
- e.preventDefault();
118
- setIsDragging(true);
119
- };
120
-
121
- const handleMouseMove = useCallback((e: MouseEvent) => {
122
- const container = document.querySelector(".main-content-area");
123
- if (!container) return;
124
-
125
- const containerRect = container.getBoundingClientRect();
126
- const newLeftWidth =
127
- ((e.clientX - containerRect.left) / containerRect.width) * 100;
128
-
129
- // Constrain between 20% and 80%
130
- const clampedWidth = Math.max(20, Math.min(80, newLeftWidth));
131
- setLeftPanelWidth(clampedWidth);
132
- }, []);
133
-
134
- const handleMouseUp = useCallback(() => {
135
- setIsDragging(false);
136
- }, []);
137
-
138
- useEffect(() => {
139
- if (isDragging) {
140
- document.addEventListener("mousemove", handleMouseMove);
141
- document.addEventListener("mouseup", handleMouseUp);
142
- document.body.style.cursor = "col-resize";
143
- document.body.style.userSelect = "none";
144
-
145
- return () => {
146
- document.removeEventListener("mousemove", handleMouseMove);
147
- document.removeEventListener("mouseup", handleMouseUp);
148
- document.body.style.cursor = "";
149
- document.body.style.userSelect = "";
150
- };
151
- }
152
- }, [isDragging, handleMouseMove, handleMouseUp]);
153
-
154
- // Keyboard shortcut: Ctrl+B to toggle config panel
155
- useKeyboardShortcut(
156
- "b",
157
- useCallback(() => {
158
- setConfigPanelCollapsed((prev) => !prev);
159
- }, []),
160
- true,
161
- );
162
-
163
- return (
164
- <div
165
- className={`h-screen flex flex-col bg-background ${
166
- isDragging ? "resize-active" : ""
167
- }`}
168
- >
169
- {/* Slim Titlebar */}
170
- <div className="flex items-center justify-between h-12 px-4 bg-card border-b border-border">
171
- <div className="flex items-center gap-4">
172
- {/* Sidebar Toggle */}
173
- <Button
174
- variant="ghost"
175
- size="sm"
176
- onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
177
- title={sidebarCollapsed ? "Show sidebar" : "Hide sidebar"}
178
- >
179
- {sidebarCollapsed ? (
180
- <PanelLeftOpen className="h-4 w-4" />
181
- ) : (
182
- <PanelLeftClose className="h-4 w-4" />
183
- )}
184
- </Button>
185
-
186
- <h1 className="text-md font-semibold">Workflow Debugger</h1>
187
- </div>
188
-
189
- {/* Centered Workflow Dropdown */}
190
- <div className="flex items-center gap-2">
191
- <label className="text-xs text-muted-foreground font-medium">
192
- Workflow:
193
- </label>
194
- <Select
195
- value={selectedWorkflow || ""}
196
- onValueChange={setSelectedWorkflow}
197
- >
198
- <SelectTrigger className="w-48">
199
- <SelectValue placeholder="Select workflow..." />
200
- </SelectTrigger>
201
- <SelectContent>
202
- {Object.keys(workflows).map((workflow) => (
203
- <SelectItem key={workflow} value={workflow}>
204
- {workflow}
205
- </SelectItem>
206
- ))}
207
- </SelectContent>
208
- </Select>
209
- </div>
210
-
211
- {/* Connection + Settings */}
212
- <div className="flex items-center gap-2">
213
- {isServerHealthy !== null && (
214
- <span
215
- className={`${
216
- isServerHealthy ? "bg-green-500" : "bg-red-500"
217
- } h-2 w-2 rounded-full`}
218
- />
219
- )}
220
- {isServerHealthy === false && (
221
- <span className="text-destructive text-xs">
222
- {connectionError || "Workflow server is unreachable"}
223
- </span>
224
- )}
225
- <Popover>
226
- <PopoverTrigger asChild>
227
- <Button variant="ghost" size="sm">
228
- <Settings className="h-4 w-4" />
229
- </Button>
230
- </PopoverTrigger>
231
- <PopoverContent className="w-80">
232
- <div className="space-y-4">
233
- <div className="space-y-2">
234
- <h4 className="font-medium text-sm">API Configuration</h4>
235
- <Input
236
- value={editingUrl}
237
- onChange={(e) => setEditingUrl(e.target.value)}
238
- placeholder="http://localhost:8000"
239
- />
240
- <div className="flex gap-2">
241
- <Button
242
- onClick={handleUrlSave}
243
- variant="outline"
244
- size="sm"
245
- disabled={editingUrl === baseUrl}
246
- >
247
- Save
248
- </Button>
249
- <Button
250
- onClick={handleUrlReset}
251
- variant="outline"
252
- size="sm"
253
- >
254
- Reset
255
- </Button>
256
- </div>
257
- <p className="text-xs text-muted-foreground">
258
- Current: {baseUrl}
259
- </p>
260
- </div>
261
- </div>
262
- </PopoverContent>
263
- </Popover>
264
- </div>
265
- </div>
266
-
267
- {/* Main Layout */}
268
- <div className="flex flex-1 overflow-hidden">
269
- {/* Sidebar - Recent Runs */}
270
- {!sidebarCollapsed && (
271
- <div className="w-48 border-r border-border bg-card overflow-hidden transition-all duration-200">
272
- <RunListPanel
273
- activeHandlerId={activeHandlerId}
274
- onHandlerSelect={setActiveHandlerId}
275
- />
276
- </div>
277
- )}
278
-
279
- {/* Main Content Area */}
280
- <div className="flex-1 flex overflow-hidden main-content-area">
281
- {/* Left Panel - Form */}
282
- {!configPanelCollapsed ? (
283
- <>
284
- <div
285
- className="border-r border-border overflow-auto"
286
- style={{ width: `${leftPanelWidth}%` }}
287
- >
288
- {selectedWorkflow && (
289
- <WorkflowConfigPanel
290
- selectedWorkflow={selectedWorkflow}
291
- onRunStart={handleRunStart}
292
- activeHandlerId={activeHandlerId}
293
- onCollapse={() => setConfigPanelCollapsed(true)}
294
- />
295
- )}
296
- </div>
297
-
298
- {/* Resizable Gutter */}
299
- <div
300
- className={`w-2 hover:bg-gray-500/20 hover:shadow-lg cursor-col-resize flex-shrink-0 transition-all duration-200 relative group border-l border-r border-border ${
301
- isDragging ? "shadow-xl" : ""
302
- }`}
303
- onMouseDown={handleMouseDown}
304
- title="Drag to resize panels"
305
- ></div>
306
- </>
307
- ) : (
308
- /* Collapsed Config Panel Trigger */
309
- <div className="w-10 border-r border-border bg-muted/30 flex flex-col items-center justify-center hover:bg-muted/50 transition-colors">
310
- <Button
311
- variant="ghost"
312
- size="sm"
313
- onClick={() => setConfigPanelCollapsed(false)}
314
- title="Show configuration panel (Ctrl+B)"
315
- className="h-10 w-10 p-0"
316
- >
317
- <PanelRightOpen className="h-5 w-5" />
318
- </Button>
319
- </div>
320
- )}
321
-
322
- {/* Right Panel - Results */}
323
- <div
324
- className="overflow-auto"
325
- style={{
326
- width: configPanelCollapsed
327
- ? "calc(100% - 40px)"
328
- : `${100 - leftPanelWidth}%`,
329
- }}
330
- >
331
- {activeHandlerId && (
332
- <RunDetailsPanel
333
- handlerId={activeHandlerId}
334
- selectedWorkflow={selectedWorkflow}
335
- />
336
- )}
337
- </div>
338
- </div>
339
- </div>
340
- </div>
341
- );
342
- }