@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.
- package/dist/debugger.v0.2.1.css +10 -0
- package/dist/debugger.v0.2.1.js +633 -0
- package/package.json +7 -1
- package/CHANGELOG.md +0 -205
- package/eslint.config.js +0 -26
- package/index.html +0 -12
- package/postcss.config.js +0 -6
- package/src/App.tsx +0 -18
- package/src/components/code-block.tsx +0 -48
- package/src/components/error-boundary.tsx +0 -85
- package/src/components/json-schema-editor.tsx +0 -291
- package/src/components/run-details-panel.tsx +0 -290
- package/src/components/run-list-panel.tsx +0 -83
- package/src/components/send-event-dialog.tsx +0 -299
- package/src/components/workflow-config-panel.tsx +0 -247
- package/src/components/workflow-debugger.tsx +0 -342
- package/src/components/workflow-visualization.tsx +0 -720
- package/src/index.css +0 -86
- package/src/main.tsx +0 -24
- package/tailwind.config.js +0 -9
- package/tests/json-schema-editor.test.tsx +0 -62
- package/tests/test-setup.ts +0 -1
- package/tsconfig.build.json +0 -5
- package/tsconfig.json +0 -16
- package/ui_sample.png +0 -0
- package/vite.config.ts +0 -46
- package/vitest.config.ts +0 -16
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
4
|
-
Dialog,
|
|
5
|
-
DialogContent,
|
|
6
|
-
DialogDescription,
|
|
7
|
-
DialogFooter,
|
|
8
|
-
DialogHeader,
|
|
9
|
-
DialogTitle,
|
|
10
|
-
DialogTrigger,
|
|
11
|
-
Select,
|
|
12
|
-
SelectContent,
|
|
13
|
-
SelectItem,
|
|
14
|
-
SelectTrigger,
|
|
15
|
-
SelectValue,
|
|
16
|
-
useWorkflowsClient,
|
|
17
|
-
} from "@llamaindex/ui";
|
|
18
|
-
import { Send } from "lucide-react";
|
|
19
|
-
import { JsonSchemaEditor } from "./json-schema-editor";
|
|
20
|
-
import type { JSONValue } from "./workflow-config-panel";
|
|
21
|
-
import {
|
|
22
|
-
postEventsByHandlerId,
|
|
23
|
-
getWorkflowsByNameEvents,
|
|
24
|
-
} from "@llamaindex/workflows-client";
|
|
25
|
-
|
|
26
|
-
interface EventSchema {
|
|
27
|
-
title?: string;
|
|
28
|
-
type?: string;
|
|
29
|
-
properties?: Record<string, unknown>;
|
|
30
|
-
required?: string[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface SendEventDialogProps {
|
|
34
|
-
handlerId: string | null;
|
|
35
|
-
workflowName: string | null;
|
|
36
|
-
disabled?: boolean;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function SendEventDialog({
|
|
40
|
-
handlerId,
|
|
41
|
-
workflowName,
|
|
42
|
-
disabled,
|
|
43
|
-
}: SendEventDialogProps) {
|
|
44
|
-
const [open, setOpen] = useState(false);
|
|
45
|
-
const [eventSchemas, setEventSchemas] = useState<EventSchema[]>([]);
|
|
46
|
-
const [selectedEventType, setSelectedEventType] = useState<string | null>(
|
|
47
|
-
null,
|
|
48
|
-
);
|
|
49
|
-
const [eventData, setEventData] = useState<Record<string, JSONValue>>({});
|
|
50
|
-
const [loading, setLoading] = useState(false);
|
|
51
|
-
const [schemaErrors, setSchemaErrors] = useState<Record<
|
|
52
|
-
string,
|
|
53
|
-
string | null
|
|
54
|
-
> | null>(null);
|
|
55
|
-
const [sending, setSending] = useState(false);
|
|
56
|
-
const [sendError, setSendError] = useState<string | null>(null);
|
|
57
|
-
const [sendSuccess, setSendSuccess] = useState(false);
|
|
58
|
-
|
|
59
|
-
const workflowsClient = useWorkflowsClient();
|
|
60
|
-
|
|
61
|
-
// Fetch event schemas when dialog opens
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (!open || !workflowName) return;
|
|
64
|
-
|
|
65
|
-
const fetchEventSchemas = async (): Promise<void> => {
|
|
66
|
-
setLoading(true);
|
|
67
|
-
setSendError(null);
|
|
68
|
-
try {
|
|
69
|
-
const response = await getWorkflowsByNameEvents({
|
|
70
|
-
client: workflowsClient,
|
|
71
|
-
path: { name: workflowName },
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const data = response.data;
|
|
75
|
-
if (!data) {
|
|
76
|
-
setEventSchemas([]);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (!data.events) {
|
|
80
|
-
setEventSchemas([]);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
setEventSchemas(data.events);
|
|
85
|
-
|
|
86
|
-
// Auto-select first event if available
|
|
87
|
-
if (data.events && data.events.length > 0) {
|
|
88
|
-
const firstEvent = data.events[0];
|
|
89
|
-
const title = firstEvent.title as string;
|
|
90
|
-
setSelectedEventType(title || null);
|
|
91
|
-
}
|
|
92
|
-
} catch (error) {
|
|
93
|
-
console.error("Failed to fetch event schemas:", error);
|
|
94
|
-
setSendError(
|
|
95
|
-
error instanceof Error ? error.message : "Failed to fetch events",
|
|
96
|
-
);
|
|
97
|
-
setEventSchemas([]);
|
|
98
|
-
} finally {
|
|
99
|
-
setLoading(false);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
void fetchEventSchemas();
|
|
104
|
-
}, [open, workflowName, workflowsClient]);
|
|
105
|
-
|
|
106
|
-
// Reset state when dialog closes
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
if (!open) {
|
|
109
|
-
setSelectedEventType(null);
|
|
110
|
-
setEventData({});
|
|
111
|
-
setSchemaErrors(null);
|
|
112
|
-
setSendError(null);
|
|
113
|
-
setSendSuccess(false);
|
|
114
|
-
}
|
|
115
|
-
}, [open]);
|
|
116
|
-
|
|
117
|
-
// Reset event data when event type changes
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
setEventData({});
|
|
120
|
-
setSendError(null);
|
|
121
|
-
setSendSuccess(false);
|
|
122
|
-
}, [selectedEventType]);
|
|
123
|
-
|
|
124
|
-
const selectedSchema = useMemo(() => {
|
|
125
|
-
if (!selectedEventType) return null;
|
|
126
|
-
return (
|
|
127
|
-
eventSchemas.find((schema) => schema.title === selectedEventType) || null
|
|
128
|
-
);
|
|
129
|
-
}, [selectedEventType, eventSchemas]);
|
|
130
|
-
|
|
131
|
-
const hasSchemaErrors = useMemo(() => {
|
|
132
|
-
if (!schemaErrors) return false;
|
|
133
|
-
return Object.values(schemaErrors).some((error) => error !== null);
|
|
134
|
-
}, [schemaErrors]);
|
|
135
|
-
|
|
136
|
-
const handleSendEvent = async () => {
|
|
137
|
-
if (!handlerId || !selectedEventType) return;
|
|
138
|
-
|
|
139
|
-
setSending(true);
|
|
140
|
-
setSendError(null);
|
|
141
|
-
setSendSuccess(false);
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const payload = {
|
|
145
|
-
type: selectedEventType,
|
|
146
|
-
data: eventData,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const { error } = await postEventsByHandlerId({
|
|
150
|
-
client: workflowsClient,
|
|
151
|
-
path: { handler_id: handlerId },
|
|
152
|
-
body: {
|
|
153
|
-
event: JSON.stringify(payload),
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
if (error) {
|
|
158
|
-
throw new Error(
|
|
159
|
-
typeof error === "string" ? error : JSON.stringify(error),
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
setSendSuccess(true);
|
|
164
|
-
|
|
165
|
-
// Close dialog after a short delay to show success message
|
|
166
|
-
setTimeout(() => {
|
|
167
|
-
setOpen(false);
|
|
168
|
-
}, 1000);
|
|
169
|
-
} catch (error) {
|
|
170
|
-
console.error("Failed to send event:", error);
|
|
171
|
-
setSendError(
|
|
172
|
-
error instanceof Error ? error.message : "Failed to send event",
|
|
173
|
-
);
|
|
174
|
-
} finally {
|
|
175
|
-
setSending(false);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const canSend =
|
|
180
|
-
!sending &&
|
|
181
|
-
!hasSchemaErrors &&
|
|
182
|
-
selectedEventType &&
|
|
183
|
-
handlerId &&
|
|
184
|
-
!sendSuccess;
|
|
185
|
-
|
|
186
|
-
return (
|
|
187
|
-
<Dialog open={open} onOpenChange={setOpen}>
|
|
188
|
-
<DialogTrigger asChild>
|
|
189
|
-
<Button
|
|
190
|
-
variant="outline"
|
|
191
|
-
size="sm"
|
|
192
|
-
disabled={disabled || !handlerId || !workflowName}
|
|
193
|
-
title={
|
|
194
|
-
!handlerId
|
|
195
|
-
? "No active handler"
|
|
196
|
-
: !workflowName
|
|
197
|
-
? "No workflow selected"
|
|
198
|
-
: "Send event to workflow"
|
|
199
|
-
}
|
|
200
|
-
>
|
|
201
|
-
<Send className="h-4 w-4 mr-2" />
|
|
202
|
-
Send Event
|
|
203
|
-
</Button>
|
|
204
|
-
</DialogTrigger>
|
|
205
|
-
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
|
206
|
-
<DialogHeader>
|
|
207
|
-
<DialogTitle>Send Event to Workflow</DialogTitle>
|
|
208
|
-
<DialogDescription>
|
|
209
|
-
Send a custom event to the running workflow handler.
|
|
210
|
-
</DialogDescription>
|
|
211
|
-
</DialogHeader>
|
|
212
|
-
|
|
213
|
-
<div className="space-y-4 py-4">
|
|
214
|
-
{loading ? (
|
|
215
|
-
<div className="text-center text-muted-foreground">
|
|
216
|
-
Loading event types...
|
|
217
|
-
</div>
|
|
218
|
-
) : eventSchemas.length === 0 ? (
|
|
219
|
-
<div className="text-center text-muted-foreground">
|
|
220
|
-
{sendError || "No events available for this workflow"}
|
|
221
|
-
</div>
|
|
222
|
-
) : (
|
|
223
|
-
<>
|
|
224
|
-
{/* Event Type Selector */}
|
|
225
|
-
<div className="space-y-2">
|
|
226
|
-
<label className="text-sm font-medium">Event Type</label>
|
|
227
|
-
<Select
|
|
228
|
-
value={selectedEventType || ""}
|
|
229
|
-
onValueChange={setSelectedEventType}
|
|
230
|
-
>
|
|
231
|
-
<SelectTrigger>
|
|
232
|
-
<SelectValue placeholder="Select event type..." />
|
|
233
|
-
</SelectTrigger>
|
|
234
|
-
<SelectContent>
|
|
235
|
-
{eventSchemas.map((schema) => (
|
|
236
|
-
<SelectItem
|
|
237
|
-
key={schema.title || "unknown"}
|
|
238
|
-
value={schema.title || ""}
|
|
239
|
-
>
|
|
240
|
-
{schema.title || "Unnamed Event"}
|
|
241
|
-
</SelectItem>
|
|
242
|
-
))}
|
|
243
|
-
</SelectContent>
|
|
244
|
-
</Select>
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
{/* Event Data Editor */}
|
|
248
|
-
{selectedSchema && (
|
|
249
|
-
<div className="space-y-2">
|
|
250
|
-
<label className="text-sm font-medium">Event Data</label>
|
|
251
|
-
<JsonSchemaEditor
|
|
252
|
-
schema={{
|
|
253
|
-
properties: selectedSchema.properties as Record<
|
|
254
|
-
string,
|
|
255
|
-
{
|
|
256
|
-
type?: string;
|
|
257
|
-
title?: string;
|
|
258
|
-
description?: string;
|
|
259
|
-
}
|
|
260
|
-
>,
|
|
261
|
-
required: selectedSchema.required,
|
|
262
|
-
}}
|
|
263
|
-
values={eventData}
|
|
264
|
-
onChange={setEventData}
|
|
265
|
-
onErrorsChange={setSchemaErrors}
|
|
266
|
-
className="space-y-3 border border-border rounded-lg p-4 bg-muted/30"
|
|
267
|
-
/>
|
|
268
|
-
</div>
|
|
269
|
-
)}
|
|
270
|
-
|
|
271
|
-
{/* Error Messages */}
|
|
272
|
-
{sendError && (
|
|
273
|
-
<div className="text-destructive text-sm p-3 bg-destructive/10 border border-destructive/20 rounded">
|
|
274
|
-
{sendError}
|
|
275
|
-
</div>
|
|
276
|
-
)}
|
|
277
|
-
|
|
278
|
-
{/* Success Message */}
|
|
279
|
-
{sendSuccess && (
|
|
280
|
-
<div className="text-green-600 text-sm p-3 bg-green-50 border border-green-200 rounded">
|
|
281
|
-
Event sent successfully!
|
|
282
|
-
</div>
|
|
283
|
-
)}
|
|
284
|
-
</>
|
|
285
|
-
)}
|
|
286
|
-
</div>
|
|
287
|
-
|
|
288
|
-
<DialogFooter>
|
|
289
|
-
<Button variant="outline" onClick={() => setOpen(false)}>
|
|
290
|
-
Cancel
|
|
291
|
-
</Button>
|
|
292
|
-
<Button onClick={handleSendEvent} disabled={!canSend}>
|
|
293
|
-
{sending ? "Sending..." : "Send Event"}
|
|
294
|
-
</Button>
|
|
295
|
-
</DialogFooter>
|
|
296
|
-
</DialogContent>
|
|
297
|
-
</Dialog>
|
|
298
|
-
);
|
|
299
|
-
}
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import {
|
|
3
|
-
useWorkflowsClient,
|
|
4
|
-
Button,
|
|
5
|
-
Textarea,
|
|
6
|
-
Skeleton,
|
|
7
|
-
useWorkflow,
|
|
8
|
-
useHandlers,
|
|
9
|
-
} from "@llamaindex/ui";
|
|
10
|
-
import { PanelRightClose } from "lucide-react";
|
|
11
|
-
import { JsonSchemaEditor } from "./json-schema-editor";
|
|
12
|
-
import { getWorkflowsByNameSchema } from "@llamaindex/workflows-client";
|
|
13
|
-
|
|
14
|
-
export type JSONValue =
|
|
15
|
-
| null
|
|
16
|
-
| string
|
|
17
|
-
| number
|
|
18
|
-
| boolean
|
|
19
|
-
| { [key: string]: JSONValue }
|
|
20
|
-
| Array<JSONValue>;
|
|
21
|
-
|
|
22
|
-
interface WorkflowConfigPanelProps {
|
|
23
|
-
selectedWorkflow: string;
|
|
24
|
-
onRunStart: (handlerId: string) => void;
|
|
25
|
-
activeHandlerId: string | null;
|
|
26
|
-
onCollapse?: () => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface SchemaProperty {
|
|
30
|
-
type: string;
|
|
31
|
-
title?: string;
|
|
32
|
-
description?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface Schema {
|
|
36
|
-
properties?: Record<string, SchemaProperty>;
|
|
37
|
-
required?: string[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function WorkflowConfigPanel({
|
|
41
|
-
selectedWorkflow,
|
|
42
|
-
onRunStart,
|
|
43
|
-
onCollapse,
|
|
44
|
-
}: WorkflowConfigPanelProps) {
|
|
45
|
-
const [schema, setSchema] = useState<Schema | null>(null);
|
|
46
|
-
const [formData, setFormData] = useState<{ [key: string]: JSONValue }>({});
|
|
47
|
-
const [loading, setLoading] = useState(false);
|
|
48
|
-
const [error, setError] = useState<string | null>(null);
|
|
49
|
-
const [rawInput, setRawInput] = useState<string>("");
|
|
50
|
-
const [rawInputError, setRawInputError] = useState<string | null>(null);
|
|
51
|
-
const [rawJsonErrors, setRawJsonErrors] = useState<
|
|
52
|
-
Record<string, string | null>
|
|
53
|
-
>({});
|
|
54
|
-
const [isCreating, setIsCreating] = useState(false);
|
|
55
|
-
|
|
56
|
-
const workflowsClient = useWorkflowsClient();
|
|
57
|
-
const { createHandler } = useWorkflow(selectedWorkflow);
|
|
58
|
-
const { setHandler } = useHandlers();
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
const fetchSchema = async () => {
|
|
62
|
-
if (!selectedWorkflow) return;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
setLoading(true);
|
|
66
|
-
setError(null);
|
|
67
|
-
const { data, error } = await getWorkflowsByNameSchema({
|
|
68
|
-
client: workflowsClient,
|
|
69
|
-
path: { name: selectedWorkflow },
|
|
70
|
-
});
|
|
71
|
-
if (data) {
|
|
72
|
-
setSchema((data as { start?: Schema | null })?.start ?? null);
|
|
73
|
-
} else {
|
|
74
|
-
throw new Error(error ? String(error) : "Failed to fetch schema");
|
|
75
|
-
}
|
|
76
|
-
setFormData({});
|
|
77
|
-
setRawInput(JSON.stringify({}, null, 2));
|
|
78
|
-
setRawInputError(null);
|
|
79
|
-
setRawJsonErrors({});
|
|
80
|
-
} catch (err) {
|
|
81
|
-
setError(err instanceof Error ? err.message : "Failed to fetch schema");
|
|
82
|
-
setSchema(null);
|
|
83
|
-
} finally {
|
|
84
|
-
setLoading(false);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
if (selectedWorkflow) {
|
|
89
|
-
fetchSchema();
|
|
90
|
-
} else {
|
|
91
|
-
setSchema(null);
|
|
92
|
-
setFormData({});
|
|
93
|
-
setRawInput("");
|
|
94
|
-
setRawInputError(null);
|
|
95
|
-
setRawJsonErrors({});
|
|
96
|
-
}
|
|
97
|
-
}, [selectedWorkflow, workflowsClient]);
|
|
98
|
-
|
|
99
|
-
const handleRunWorkflow = async () => {
|
|
100
|
-
if (!selectedWorkflow) return;
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
setIsCreating(true);
|
|
104
|
-
const handler = await createHandler(formData);
|
|
105
|
-
setHandler(handler);
|
|
106
|
-
onRunStart(handler.handler_id);
|
|
107
|
-
|
|
108
|
-
// Auto-collapse the config panel after starting a run
|
|
109
|
-
if (onCollapse) {
|
|
110
|
-
onCollapse();
|
|
111
|
-
}
|
|
112
|
-
} catch (err) {
|
|
113
|
-
console.error("Failed to run workflow:", err);
|
|
114
|
-
} finally {
|
|
115
|
-
setIsCreating(false);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const hasSchemaFields = Boolean(
|
|
120
|
-
schema?.properties && Object.keys(schema.properties).length > 0,
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
if (!selectedWorkflow) {
|
|
124
|
-
return (
|
|
125
|
-
<div className="h-full flex flex-col">
|
|
126
|
-
<div className="p-4 border-b border-border flex items-center justify-between">
|
|
127
|
-
<h2 className="font-semibold text-sm">Workflow Configuration</h2>
|
|
128
|
-
{onCollapse && (
|
|
129
|
-
<Button
|
|
130
|
-
variant="ghost"
|
|
131
|
-
size="sm"
|
|
132
|
-
onClick={onCollapse}
|
|
133
|
-
title="Hide configuration panel (Ctrl+B)"
|
|
134
|
-
>
|
|
135
|
-
<PanelRightClose className="h-4 w-4" />
|
|
136
|
-
</Button>
|
|
137
|
-
)}
|
|
138
|
-
</div>
|
|
139
|
-
<div className="flex-1 flex items-center justify-center">
|
|
140
|
-
<p className="text-muted-foreground text-center">
|
|
141
|
-
Select a workflow to configure and run
|
|
142
|
-
</p>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return (
|
|
149
|
-
<div className="h-full flex flex-col">
|
|
150
|
-
{/* Header */}
|
|
151
|
-
<div className="p-4 border-b border-border flex items-center justify-between">
|
|
152
|
-
<h2 className="font-semibold text-sm">Configure: {selectedWorkflow}</h2>
|
|
153
|
-
{onCollapse && (
|
|
154
|
-
<Button
|
|
155
|
-
variant="ghost"
|
|
156
|
-
size="sm"
|
|
157
|
-
onClick={onCollapse}
|
|
158
|
-
title="Hide configuration panel (Ctrl+B)"
|
|
159
|
-
>
|
|
160
|
-
<PanelRightClose className="h-4 w-4" />
|
|
161
|
-
</Button>
|
|
162
|
-
)}
|
|
163
|
-
</div>
|
|
164
|
-
|
|
165
|
-
{/* Content */}
|
|
166
|
-
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
167
|
-
{loading && (
|
|
168
|
-
<div className="space-y-4">
|
|
169
|
-
<Skeleton className="h-4 w-32" />
|
|
170
|
-
<Skeleton className="h-20 w-full" />
|
|
171
|
-
<Skeleton className="h-4 w-32" />
|
|
172
|
-
<Skeleton className="h-20 w-full" />
|
|
173
|
-
</div>
|
|
174
|
-
)}
|
|
175
|
-
|
|
176
|
-
{error && (
|
|
177
|
-
<div className="text-destructive text-sm p-3 bg-destructive/10 border border-destructive/20 rounded">
|
|
178
|
-
Error loading schema: {error}
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
|
|
182
|
-
{!loading && !error && (
|
|
183
|
-
<>
|
|
184
|
-
{/* Form Fields */}
|
|
185
|
-
<div className="space-y-4">
|
|
186
|
-
{hasSchemaFields ? (
|
|
187
|
-
<JsonSchemaEditor
|
|
188
|
-
schema={schema}
|
|
189
|
-
values={formData}
|
|
190
|
-
onChange={setFormData}
|
|
191
|
-
onErrorsChange={setRawJsonErrors}
|
|
192
|
-
/>
|
|
193
|
-
) : (
|
|
194
|
-
<div className="space-y-2">
|
|
195
|
-
<label htmlFor="raw-input" className="text-sm font-medium">
|
|
196
|
-
Input (JSON)
|
|
197
|
-
</label>
|
|
198
|
-
<Textarea
|
|
199
|
-
id="raw-input"
|
|
200
|
-
value={rawInput}
|
|
201
|
-
onChange={(e) => {
|
|
202
|
-
setRawInput(e.target.value);
|
|
203
|
-
try {
|
|
204
|
-
const parsed = JSON.parse(e.target.value);
|
|
205
|
-
setFormData(parsed);
|
|
206
|
-
setRawInputError(null);
|
|
207
|
-
} catch {
|
|
208
|
-
// Keep editing raw input until valid JSON
|
|
209
|
-
setRawInputError("Invalid JSON");
|
|
210
|
-
}
|
|
211
|
-
}}
|
|
212
|
-
placeholder='{"key": "value"}'
|
|
213
|
-
className={`font-mono ${rawInputError ? "border-destructive focus-visible:ring-destructive" : ""}`}
|
|
214
|
-
rows={8}
|
|
215
|
-
/>
|
|
216
|
-
{rawInputError && (
|
|
217
|
-
<p className="text-xs text-destructive mt-1">
|
|
218
|
-
{rawInputError}
|
|
219
|
-
</p>
|
|
220
|
-
)}
|
|
221
|
-
</div>
|
|
222
|
-
)}
|
|
223
|
-
</div>
|
|
224
|
-
</>
|
|
225
|
-
)}
|
|
226
|
-
</div>
|
|
227
|
-
|
|
228
|
-
{/* Footer with Run Button */}
|
|
229
|
-
{!loading && !error && (
|
|
230
|
-
<div className="p-4 border-t border-border">
|
|
231
|
-
{Object.values(rawJsonErrors).some((e) => e) && (
|
|
232
|
-
<p className="text-xs text-destructive mb-2">
|
|
233
|
-
Fix invalid JSON fields before running.
|
|
234
|
-
</p>
|
|
235
|
-
)}
|
|
236
|
-
<Button
|
|
237
|
-
onClick={handleRunWorkflow}
|
|
238
|
-
disabled={isCreating || Object.values(rawJsonErrors).some((e) => e)}
|
|
239
|
-
className="w-full"
|
|
240
|
-
>
|
|
241
|
-
{isCreating ? "Starting..." : "Run Workflow"}
|
|
242
|
-
</Button>
|
|
243
|
-
</div>
|
|
244
|
-
)}
|
|
245
|
-
</div>
|
|
246
|
-
);
|
|
247
|
-
}
|