@geenius/ai-workflow 0.1.0 → 0.5.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.
- package/CHANGELOG.md +13 -0
- package/README.md +69 -1
- package/package.json +60 -27
- package/packages/convex/dist/index.d.ts +363 -0
- package/packages/convex/dist/index.js +200 -0
- package/packages/convex/dist/index.js.map +1 -0
- package/packages/react/dist/index.d.ts +514 -0
- package/packages/react/dist/index.js +2633 -0
- package/packages/react/dist/index.js.map +1 -0
- package/packages/react-css/{src/styles.css → dist/index.css} +107 -253
- package/packages/react-css/dist/index.css.map +1 -0
- package/packages/react-css/dist/index.d.ts +533 -0
- package/packages/react-css/dist/index.js +2620 -0
- package/packages/react-css/dist/index.js.map +1 -0
- package/packages/shared/dist/index.d.ts +1305 -0
- package/packages/shared/dist/index.js +1594 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/solidjs/dist/index.d.ts +492 -0
- package/packages/solidjs/dist/index.js +2552 -0
- package/packages/solidjs/dist/index.js.map +1 -0
- package/packages/solidjs-css/{src/styles.css → dist/index.css} +107 -253
- package/packages/solidjs-css/dist/index.css.map +1 -0
- package/packages/solidjs-css/dist/index.d.ts +509 -0
- package/packages/solidjs-css/dist/index.js +2493 -0
- package/packages/solidjs-css/dist/index.js.map +1 -0
- package/.changeset/config.json +0 -11
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -23
- package/.github/workflows/release.yml +0 -29
- package/.nvmrc +0 -1
- package/.project/ACCOUNT.yaml +0 -4
- package/.project/IDEAS.yaml +0 -7
- package/.project/PROJECT.yaml +0 -11
- package/.project/ROADMAP.yaml +0 -15
- package/CODE_OF_CONDUCT.md +0 -16
- package/CONTRIBUTING.md +0 -26
- package/SECURITY.md +0 -15
- package/SUPPORT.md +0 -8
- package/packages/convex/README.md +0 -1
- package/packages/convex/package.json +0 -12
- package/packages/convex/src/convex.config.ts +0 -3
- package/packages/convex/src/index.ts +0 -3
- package/packages/convex/src/mutations.ts +0 -36
- package/packages/convex/src/queries.ts +0 -19
- package/packages/convex/src/schema.ts +0 -24
- package/packages/convex/tsconfig.json +0 -25
- package/packages/react/README.md +0 -1
- package/packages/react/package.json +0 -46
- package/packages/react/src/components/ApprovalModal.tsx +0 -47
- package/packages/react/src/components/StepConfigPanel.tsx +0 -67
- package/packages/react/src/components/StepConnector.tsx +0 -47
- package/packages/react/src/components/StepNode.tsx +0 -38
- package/packages/react/src/components/StepPalette.tsx +0 -48
- package/packages/react/src/components/WorkflowCanvas.tsx +0 -42
- package/packages/react/src/components/WorkflowRunPanel.tsx +0 -64
- package/packages/react/src/components/WorkflowToolbar.tsx +0 -43
- package/packages/react/src/components/index.ts +0 -9
- package/packages/react/src/hooks/index.ts +0 -10
- package/packages/react/src/hooks/useApprovalGate.ts +0 -59
- package/packages/react/src/hooks/useWorkflow.ts +0 -39
- package/packages/react/src/hooks/useWorkflowBuilder.ts +0 -121
- package/packages/react/src/hooks/useWorkflowRun.ts +0 -75
- package/packages/react/src/hooks/useWorkflowStep.ts +0 -52
- package/packages/react/src/hooks/useWorkflowTemplates.ts +0 -54
- package/packages/react/src/index.ts +0 -16
- package/packages/react/src/pages/WorkflowBuilderPage.tsx +0 -81
- package/packages/react/src/pages/WorkflowRunsPage.tsx +0 -59
- package/packages/react/src/pages/index.ts +0 -3
- package/packages/react/tsconfig.json +0 -1
- package/packages/react/tsup.config.ts +0 -7
- package/packages/react-css/README.md +0 -1
- package/packages/react-css/package.json +0 -44
- package/packages/react-css/src/components/ApprovalModal.tsx +0 -6
- package/packages/react-css/src/components/StepConfigPanel.tsx +0 -7
- package/packages/react-css/src/components/StepConnector.tsx +0 -6
- package/packages/react-css/src/components/StepNode.tsx +0 -7
- package/packages/react-css/src/components/StepPalette.tsx +0 -6
- package/packages/react-css/src/components/WorkflowCanvas.tsx +0 -6
- package/packages/react-css/src/components/WorkflowRunPanel.tsx +0 -9
- package/packages/react-css/src/components/WorkflowToolbar.tsx +0 -4
- package/packages/react-css/src/components/index.ts +0 -9
- package/packages/react-css/src/hooks/index.ts +0 -3
- package/packages/react-css/src/hooks/useWorkflow.ts +0 -39
- package/packages/react-css/src/hooks/useWorkflowBuilder.ts +0 -121
- package/packages/react-css/src/index.ts +0 -7
- package/packages/react-css/src/pages/WorkflowBuilderPage.tsx +0 -16
- package/packages/react-css/src/pages/WorkflowRunsPage.tsx +0 -6
- package/packages/react-css/src/pages/index.ts +0 -3
- package/packages/react-css/tsconfig.json +0 -26
- package/packages/react-css/tsup.config.ts +0 -2
- package/packages/shared/README.md +0 -1
- package/packages/shared/package.json +0 -56
- package/packages/shared/src/__tests__/ai-workflow.test.ts +0 -217
- package/packages/shared/src/config.ts +0 -49
- package/packages/shared/src/convex/index.ts +0 -2
- package/packages/shared/src/convex/schemas.ts +0 -42
- package/packages/shared/src/engine.test.ts +0 -1
- package/packages/shared/src/engine.ts +0 -295
- package/packages/shared/src/index.ts +0 -43
- package/packages/shared/src/steps.ts +0 -68
- package/packages/shared/src/templates.ts +0 -172
- package/packages/shared/src/types.ts +0 -237
- package/packages/shared/src/utils/cost.ts +0 -79
- package/packages/shared/src/utils/dag.ts +0 -133
- package/packages/shared/src/utils/index.ts +0 -5
- package/packages/shared/src/utils/interpolation.ts +0 -53
- package/packages/shared/src/validators.ts +0 -215
- package/packages/shared/tsconfig.json +0 -1
- package/packages/shared/tsup.config.ts +0 -5
- package/packages/shared/vitest.config.ts +0 -4
- package/packages/solidjs/README.md +0 -1
- package/packages/solidjs/package.json +0 -45
- package/packages/solidjs/src/components/ApprovalModal.tsx +0 -18
- package/packages/solidjs/src/components/StepConfigPanel.tsx +0 -14
- package/packages/solidjs/src/components/StepConnector.tsx +0 -11
- package/packages/solidjs/src/components/StepNode.tsx +0 -12
- package/packages/solidjs/src/components/StepPalette.tsx +0 -22
- package/packages/solidjs/src/components/WorkflowCanvas.tsx +0 -23
- package/packages/solidjs/src/components/WorkflowRunPanel.tsx +0 -18
- package/packages/solidjs/src/components/WorkflowToolbar.tsx +0 -13
- package/packages/solidjs/src/components/index.ts +0 -9
- package/packages/solidjs/src/index.ts +0 -7
- package/packages/solidjs/src/pages/WorkflowBuilderPage.tsx +0 -37
- package/packages/solidjs/src/pages/WorkflowRunsPage.tsx +0 -20
- package/packages/solidjs/src/pages/index.ts +0 -3
- package/packages/solidjs/src/primitives/createApprovalGate.ts +0 -29
- package/packages/solidjs/src/primitives/createWorkflow.ts +0 -28
- package/packages/solidjs/src/primitives/createWorkflowBuilder.ts +0 -56
- package/packages/solidjs/src/primitives/createWorkflowRun.ts +0 -32
- package/packages/solidjs/src/primitives/createWorkflowStep.ts +0 -23
- package/packages/solidjs/src/primitives/createWorkflowTemplates.ts +0 -28
- package/packages/solidjs/src/primitives/index.ts +0 -8
- package/packages/solidjs/tsconfig.json +0 -1
- package/packages/solidjs/tsup.config.ts +0 -7
- package/packages/solidjs-css/README.md +0 -1
- package/packages/solidjs-css/package.json +0 -43
- package/packages/solidjs-css/src/components/ApprovalModal.tsx +0 -6
- package/packages/solidjs-css/src/components/StepConfigPanel.tsx +0 -7
- package/packages/solidjs-css/src/components/StepConnector.tsx +0 -6
- package/packages/solidjs-css/src/components/StepNode.tsx +0 -7
- package/packages/solidjs-css/src/components/StepPalette.tsx +0 -7
- package/packages/solidjs-css/src/components/WorkflowCanvas.tsx +0 -7
- package/packages/solidjs-css/src/components/WorkflowRunPanel.tsx +0 -8
- package/packages/solidjs-css/src/components/WorkflowToolbar.tsx +0 -5
- package/packages/solidjs-css/src/components/index.ts +0 -9
- package/packages/solidjs-css/src/index.ts +0 -7
- package/packages/solidjs-css/src/pages/WorkflowBuilderPage.tsx +0 -2
- package/packages/solidjs-css/src/pages/WorkflowRunsPage.tsx +0 -7
- package/packages/solidjs-css/src/pages/index.ts +0 -3
- package/packages/solidjs-css/src/primitives/createWorkflow.ts +0 -28
- package/packages/solidjs-css/src/primitives/createWorkflowBuilder.ts +0 -56
- package/packages/solidjs-css/src/primitives/index.ts +0 -1
- package/packages/solidjs-css/tsconfig.json +0 -27
- package/packages/solidjs-css/tsup.config.ts +0 -2
- package/pnpm-workspace.yaml +0 -2
|
@@ -0,0 +1,2493 @@
|
|
|
1
|
+
import { GeeniusError, ErrorCode } from '@geenius/tools/errors';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
import { createSignal, createMemo, For, Show, createEffect } from 'solid-js';
|
|
4
|
+
import { jsxs, jsx, Fragment } from 'solid-js/h/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// ../shared/dist/index.js
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return Boolean(value && typeof value === "object");
|
|
9
|
+
}
|
|
10
|
+
function resolvePath(obj, path) {
|
|
11
|
+
return path.split(".").reduce((acc, key) => {
|
|
12
|
+
if (isRecord(acc)) {
|
|
13
|
+
return acc[key];
|
|
14
|
+
}
|
|
15
|
+
return void 0;
|
|
16
|
+
}, obj);
|
|
17
|
+
}
|
|
18
|
+
function interpolate(template, vars) {
|
|
19
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (match, expr) => {
|
|
20
|
+
const [path, defaultValue] = expr.trim().split("|").map((s) => s.trim());
|
|
21
|
+
const val = resolvePath(vars, path);
|
|
22
|
+
if (val !== void 0 && val !== null) {
|
|
23
|
+
return typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
24
|
+
}
|
|
25
|
+
return defaultValue ?? "";
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
var WorkflowError = class _WorkflowError extends GeeniusError {
|
|
29
|
+
/**
|
|
30
|
+
* Create a structured workflow error.
|
|
31
|
+
*
|
|
32
|
+
* @param options Error metadata, message, and optional structured context.
|
|
33
|
+
* @returns A typed workflow error instance.
|
|
34
|
+
*/
|
|
35
|
+
constructor(options) {
|
|
36
|
+
super({
|
|
37
|
+
code: options.code ?? ErrorCode.INTERNAL,
|
|
38
|
+
message: options.message,
|
|
39
|
+
cause: options.cause,
|
|
40
|
+
context: options.context
|
|
41
|
+
});
|
|
42
|
+
this.name = "WorkflowError";
|
|
43
|
+
Object.setPrototypeOf(this, _WorkflowError.prototype);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var WorkflowConfigurationError = class _WorkflowConfigurationError extends WorkflowError {
|
|
47
|
+
/**
|
|
48
|
+
* Create a configuration error for invalid workflow setup.
|
|
49
|
+
*
|
|
50
|
+
* @param message Human-readable explanation of the invalid configuration.
|
|
51
|
+
* @param context Optional structured metadata describing the failing config area.
|
|
52
|
+
* @returns A typed workflow configuration error.
|
|
53
|
+
*/
|
|
54
|
+
constructor(message, context) {
|
|
55
|
+
super({
|
|
56
|
+
code: ErrorCode.INVALID_CONFIG,
|
|
57
|
+
message,
|
|
58
|
+
context
|
|
59
|
+
});
|
|
60
|
+
this.name = "WorkflowConfigurationError";
|
|
61
|
+
Object.setPrototypeOf(this, _WorkflowConfigurationError.prototype);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var WorkflowExecutionError = class _WorkflowExecutionError extends WorkflowError {
|
|
65
|
+
/**
|
|
66
|
+
* Create an execution error for a workflow runtime failure.
|
|
67
|
+
*
|
|
68
|
+
* @param message Human-readable explanation of the runtime failure.
|
|
69
|
+
* @param context Optional structured metadata describing the failing execution path.
|
|
70
|
+
* @param cause Optional underlying exception that triggered the runtime failure.
|
|
71
|
+
* @returns A typed workflow execution error.
|
|
72
|
+
*/
|
|
73
|
+
constructor(message, context, cause) {
|
|
74
|
+
super({
|
|
75
|
+
code: ErrorCode.INTERNAL,
|
|
76
|
+
message,
|
|
77
|
+
cause,
|
|
78
|
+
context
|
|
79
|
+
});
|
|
80
|
+
this.name = "WorkflowExecutionError";
|
|
81
|
+
Object.setPrototypeOf(this, _WorkflowExecutionError.prototype);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var WorkflowGraphError = class _WorkflowGraphError extends WorkflowError {
|
|
85
|
+
/**
|
|
86
|
+
* Create a graph-validation error for an invalid workflow topology.
|
|
87
|
+
*
|
|
88
|
+
* @param message Human-readable explanation of the graph validation failure.
|
|
89
|
+
* @param context Optional structured metadata describing the invalid topology.
|
|
90
|
+
* @returns A typed workflow graph error.
|
|
91
|
+
*/
|
|
92
|
+
constructor(message, context) {
|
|
93
|
+
super({
|
|
94
|
+
code: ErrorCode.VALIDATION,
|
|
95
|
+
message,
|
|
96
|
+
context
|
|
97
|
+
});
|
|
98
|
+
this.name = "WorkflowGraphError";
|
|
99
|
+
Object.setPrototypeOf(this, _WorkflowGraphError.prototype);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var WorkflowExpressionError = class _WorkflowExpressionError extends WorkflowError {
|
|
103
|
+
/**
|
|
104
|
+
* Create an expression error for a failed transform or condition expression.
|
|
105
|
+
*
|
|
106
|
+
* @param message Human-readable explanation of the expression failure.
|
|
107
|
+
* @param context Optional structured metadata including the source expression.
|
|
108
|
+
* @param cause Optional underlying parse or runtime error.
|
|
109
|
+
* @returns A typed workflow expression error.
|
|
110
|
+
*/
|
|
111
|
+
constructor(message, context, cause) {
|
|
112
|
+
super({
|
|
113
|
+
code: ErrorCode.VALIDATION,
|
|
114
|
+
message,
|
|
115
|
+
cause,
|
|
116
|
+
context
|
|
117
|
+
});
|
|
118
|
+
this.name = "WorkflowExpressionError";
|
|
119
|
+
Object.setPrototypeOf(this, _WorkflowExpressionError.prototype);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function normalizeWorkflowError(error, fallbackMessage, context) {
|
|
123
|
+
if (error instanceof WorkflowError) {
|
|
124
|
+
return error;
|
|
125
|
+
}
|
|
126
|
+
if (error instanceof GeeniusError) {
|
|
127
|
+
return new WorkflowError({
|
|
128
|
+
code: error.code,
|
|
129
|
+
message: error.message,
|
|
130
|
+
cause: error,
|
|
131
|
+
context
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (error instanceof Error) {
|
|
135
|
+
return new WorkflowExecutionError(error.message, context, error);
|
|
136
|
+
}
|
|
137
|
+
return new WorkflowExecutionError(fallbackMessage, context, error);
|
|
138
|
+
}
|
|
139
|
+
function isRecord2(value) {
|
|
140
|
+
return Boolean(value && typeof value === "object");
|
|
141
|
+
}
|
|
142
|
+
function isExpressionOperand(value) {
|
|
143
|
+
return value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean";
|
|
144
|
+
}
|
|
145
|
+
function isRelationalOperand(value) {
|
|
146
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean";
|
|
147
|
+
}
|
|
148
|
+
function toPropertyKey(value) {
|
|
149
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "symbol") {
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
throw createExpressionError(
|
|
153
|
+
"Computed property access expects a string, number, or symbol key."
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
function createExpressionError(message, expression, cause) {
|
|
157
|
+
return new WorkflowExpressionError(
|
|
158
|
+
message,
|
|
159
|
+
expression ? { expression } : void 0,
|
|
160
|
+
cause
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
var FORBIDDEN_PROPERTIES = /* @__PURE__ */ new Set([
|
|
164
|
+
"__proto__",
|
|
165
|
+
"prototype",
|
|
166
|
+
"constructor",
|
|
167
|
+
"caller",
|
|
168
|
+
"callee",
|
|
169
|
+
"arguments"
|
|
170
|
+
]);
|
|
171
|
+
var SAFE_GLOBALS = Object.freeze({
|
|
172
|
+
Math,
|
|
173
|
+
JSON: Object.freeze({
|
|
174
|
+
parse: JSON.parse.bind(JSON),
|
|
175
|
+
stringify: JSON.stringify.bind(JSON)
|
|
176
|
+
}),
|
|
177
|
+
Date: Object.freeze({
|
|
178
|
+
now: Date.now.bind(Date),
|
|
179
|
+
parse: Date.parse.bind(Date),
|
|
180
|
+
UTC: Date.UTC.bind(Date)
|
|
181
|
+
}),
|
|
182
|
+
Number: (value) => Number(value),
|
|
183
|
+
String: (value) => String(value),
|
|
184
|
+
Boolean: (value) => Boolean(value)
|
|
185
|
+
});
|
|
186
|
+
function isIdentifierStart(char) {
|
|
187
|
+
return /[A-Za-z_$]/.test(char);
|
|
188
|
+
}
|
|
189
|
+
function isIdentifierPart(char) {
|
|
190
|
+
return /[A-Za-z0-9_$]/.test(char);
|
|
191
|
+
}
|
|
192
|
+
function isDigit(char) {
|
|
193
|
+
return /[0-9]/.test(char);
|
|
194
|
+
}
|
|
195
|
+
function tokenize(expression) {
|
|
196
|
+
const tokens = [];
|
|
197
|
+
let index = 0;
|
|
198
|
+
const push = (type, value) => {
|
|
199
|
+
tokens.push({ type, value });
|
|
200
|
+
};
|
|
201
|
+
while (index < expression.length) {
|
|
202
|
+
const char = expression[index];
|
|
203
|
+
if (/\s/.test(char)) {
|
|
204
|
+
index++;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const three = expression.slice(index, index + 3);
|
|
208
|
+
if (three === "...") {
|
|
209
|
+
push("operator", "...");
|
|
210
|
+
index += 3;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const multi = [
|
|
214
|
+
"===",
|
|
215
|
+
"!==",
|
|
216
|
+
">=",
|
|
217
|
+
"<=",
|
|
218
|
+
"&&",
|
|
219
|
+
"||",
|
|
220
|
+
"??",
|
|
221
|
+
"==",
|
|
222
|
+
"!=",
|
|
223
|
+
"**"
|
|
224
|
+
];
|
|
225
|
+
const matched = multi.find((op) => expression.startsWith(op, index));
|
|
226
|
+
if (matched) {
|
|
227
|
+
push("operator", matched);
|
|
228
|
+
index += matched.length;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (isDigit(char) || char === "." && isDigit(expression[index + 1] ?? "")) {
|
|
232
|
+
let end = index + 1;
|
|
233
|
+
while (end < expression.length && /[0-9.]/.test(expression[end])) end++;
|
|
234
|
+
push("number", expression.slice(index, end));
|
|
235
|
+
index = end;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (char === '"' || char === "'") {
|
|
239
|
+
const quote = char;
|
|
240
|
+
let end = index + 1;
|
|
241
|
+
let value = "";
|
|
242
|
+
while (end < expression.length) {
|
|
243
|
+
const current = expression[end];
|
|
244
|
+
if (current === "\\") {
|
|
245
|
+
const next = expression[end + 1];
|
|
246
|
+
if (next === void 0)
|
|
247
|
+
throw createExpressionError(
|
|
248
|
+
"Unterminated string literal.",
|
|
249
|
+
expression
|
|
250
|
+
);
|
|
251
|
+
const escapes = {
|
|
252
|
+
n: "\n",
|
|
253
|
+
r: "\r",
|
|
254
|
+
t: " ",
|
|
255
|
+
b: "\b",
|
|
256
|
+
f: "\f",
|
|
257
|
+
"\\": "\\",
|
|
258
|
+
'"': '"',
|
|
259
|
+
"'": "'"
|
|
260
|
+
};
|
|
261
|
+
value += escapes[next] ?? next;
|
|
262
|
+
end += 2;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (current === quote) break;
|
|
266
|
+
value += current;
|
|
267
|
+
end++;
|
|
268
|
+
}
|
|
269
|
+
if (expression[end] !== quote)
|
|
270
|
+
throw createExpressionError("Unterminated string literal.", expression);
|
|
271
|
+
push("string", value);
|
|
272
|
+
index = end + 1;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (isIdentifierStart(char)) {
|
|
276
|
+
let end = index + 1;
|
|
277
|
+
while (end < expression.length && isIdentifierPart(expression[end]))
|
|
278
|
+
end++;
|
|
279
|
+
push("identifier", expression.slice(index, end));
|
|
280
|
+
index = end;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const punctuators = ["(", ")", "{", "}", "[", "]", ".", ",", ":", "?"];
|
|
284
|
+
if (punctuators.includes(char)) {
|
|
285
|
+
push("punctuator", char);
|
|
286
|
+
index++;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const operators = ["+", "-", "*", "/", "%", "!", "<", ">", "="];
|
|
290
|
+
if (operators.includes(char)) {
|
|
291
|
+
push("operator", char);
|
|
292
|
+
index++;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
throw createExpressionError(
|
|
296
|
+
`Unsupported token in expression: ${char}`,
|
|
297
|
+
expression
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
tokens.push({ type: "eof", value: "" });
|
|
301
|
+
return tokens;
|
|
302
|
+
}
|
|
303
|
+
var ExpressionParser = class {
|
|
304
|
+
tokens;
|
|
305
|
+
index = 0;
|
|
306
|
+
context;
|
|
307
|
+
constructor(expression, context) {
|
|
308
|
+
this.tokens = tokenize(expression);
|
|
309
|
+
this.context = context;
|
|
310
|
+
}
|
|
311
|
+
evaluate() {
|
|
312
|
+
const result = this.parseConditional();
|
|
313
|
+
this.expect("eof");
|
|
314
|
+
return this.unwrap(result);
|
|
315
|
+
}
|
|
316
|
+
current() {
|
|
317
|
+
return this.tokens[this.index] ?? { type: "eof", value: "" };
|
|
318
|
+
}
|
|
319
|
+
advance() {
|
|
320
|
+
const token = this.current();
|
|
321
|
+
this.index++;
|
|
322
|
+
return token;
|
|
323
|
+
}
|
|
324
|
+
match(type, value) {
|
|
325
|
+
const token = this.current();
|
|
326
|
+
if (token.type !== type) return false;
|
|
327
|
+
if (value !== void 0 && token.value !== value) return false;
|
|
328
|
+
this.index++;
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
expect(type, value) {
|
|
332
|
+
const token = this.current();
|
|
333
|
+
if (token.type !== type || value !== void 0 && token.value !== value) {
|
|
334
|
+
throw createExpressionError(
|
|
335
|
+
`Unexpected token "${token.value}" in expression.`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
this.index++;
|
|
339
|
+
return token;
|
|
340
|
+
}
|
|
341
|
+
unwrap(value) {
|
|
342
|
+
if (this.isReference(value)) return value.value;
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
unwrapOperand(value) {
|
|
346
|
+
const operand = this.unwrap(value);
|
|
347
|
+
if (!isExpressionOperand(operand)) {
|
|
348
|
+
throw createExpressionError(
|
|
349
|
+
"Expression operand must resolve to a primitive value."
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return operand;
|
|
353
|
+
}
|
|
354
|
+
relationalOperand(value) {
|
|
355
|
+
const operand = this.unwrap(value);
|
|
356
|
+
if (!isRelationalOperand(operand)) {
|
|
357
|
+
throw createExpressionError(
|
|
358
|
+
"Relational comparisons require primitive operands."
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
return operand;
|
|
362
|
+
}
|
|
363
|
+
isReference(value) {
|
|
364
|
+
return isRecord2(value) && value.__ref === true;
|
|
365
|
+
}
|
|
366
|
+
toReference(base, key) {
|
|
367
|
+
if (typeof key === "string" && FORBIDDEN_PROPERTIES.has(key)) {
|
|
368
|
+
throw createExpressionError(
|
|
369
|
+
`Access to property "${key}" is not allowed.`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (base === null || base === void 0) {
|
|
373
|
+
throw createExpressionError(
|
|
374
|
+
`Cannot read property "${String(key)}" of ${base}.`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
__ref: true,
|
|
379
|
+
base,
|
|
380
|
+
key,
|
|
381
|
+
value: Reflect.get(Object(base), key)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
resolveIdentifier(name) {
|
|
385
|
+
if (name === "true") return true;
|
|
386
|
+
if (name === "false") return false;
|
|
387
|
+
if (name === "null") return null;
|
|
388
|
+
if (name === "undefined") return void 0;
|
|
389
|
+
if (Object.prototype.hasOwnProperty.call(this.context, name)) {
|
|
390
|
+
return this.context[name];
|
|
391
|
+
}
|
|
392
|
+
if (Object.prototype.hasOwnProperty.call(SAFE_GLOBALS, name)) {
|
|
393
|
+
return SAFE_GLOBALS[name];
|
|
394
|
+
}
|
|
395
|
+
throw createExpressionError(`Unknown identifier in expression: ${name}.`);
|
|
396
|
+
}
|
|
397
|
+
parseConditional() {
|
|
398
|
+
let test = this.parseNullish();
|
|
399
|
+
if (this.match("punctuator", "?")) {
|
|
400
|
+
const consequent = this.parseConditional();
|
|
401
|
+
this.expect("punctuator", ":");
|
|
402
|
+
const alternate = this.parseConditional();
|
|
403
|
+
test = this.unwrap(test) ? consequent : alternate;
|
|
404
|
+
}
|
|
405
|
+
return test;
|
|
406
|
+
}
|
|
407
|
+
parseNullish() {
|
|
408
|
+
let left = this.parseLogicalOr();
|
|
409
|
+
while (this.match("operator", "??")) {
|
|
410
|
+
const right = this.parseLogicalOr();
|
|
411
|
+
left = this.unwrap(left) ?? right;
|
|
412
|
+
}
|
|
413
|
+
return left;
|
|
414
|
+
}
|
|
415
|
+
parseLogicalOr() {
|
|
416
|
+
let left = this.parseLogicalAnd();
|
|
417
|
+
while (this.match("operator", "||")) {
|
|
418
|
+
const right = this.parseLogicalAnd();
|
|
419
|
+
left = this.unwrap(left) || right;
|
|
420
|
+
}
|
|
421
|
+
return left;
|
|
422
|
+
}
|
|
423
|
+
parseLogicalAnd() {
|
|
424
|
+
let left = this.parseEquality();
|
|
425
|
+
while (this.match("operator", "&&")) {
|
|
426
|
+
const right = this.parseEquality();
|
|
427
|
+
left = this.unwrap(left) && right;
|
|
428
|
+
}
|
|
429
|
+
return left;
|
|
430
|
+
}
|
|
431
|
+
parseEquality() {
|
|
432
|
+
let left = this.parseComparison();
|
|
433
|
+
while (true) {
|
|
434
|
+
if (this.match("operator", "===")) {
|
|
435
|
+
const right = this.parseComparison();
|
|
436
|
+
left = this.unwrap(left) === this.unwrap(right);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (this.match("operator", "!==")) {
|
|
440
|
+
const right = this.parseComparison();
|
|
441
|
+
left = this.unwrap(left) !== this.unwrap(right);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (this.match("operator", "==")) {
|
|
445
|
+
const right = this.parseComparison();
|
|
446
|
+
left = this.unwrap(left) == this.unwrap(right);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (this.match("operator", "!=")) {
|
|
450
|
+
const right = this.parseComparison();
|
|
451
|
+
left = this.unwrap(left) != this.unwrap(right);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
return left;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
compare(left, right, operator) {
|
|
458
|
+
const leftValue = this.relationalOperand(left);
|
|
459
|
+
const rightValue = this.relationalOperand(right);
|
|
460
|
+
switch (operator) {
|
|
461
|
+
case "<":
|
|
462
|
+
return leftValue < rightValue;
|
|
463
|
+
case "<=":
|
|
464
|
+
return leftValue <= rightValue;
|
|
465
|
+
case ">":
|
|
466
|
+
return leftValue > rightValue;
|
|
467
|
+
case ">=":
|
|
468
|
+
return leftValue >= rightValue;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
add(left, right) {
|
|
472
|
+
const leftValue = this.unwrapOperand(left);
|
|
473
|
+
const rightValue = this.unwrapOperand(right);
|
|
474
|
+
return typeof leftValue === "string" || typeof rightValue === "string" ? String(leftValue) + String(rightValue) : Number(leftValue) + Number(rightValue);
|
|
475
|
+
}
|
|
476
|
+
subtract(left, right) {
|
|
477
|
+
return Number(this.unwrapOperand(left)) - Number(this.unwrapOperand(right));
|
|
478
|
+
}
|
|
479
|
+
multiply(left, right) {
|
|
480
|
+
return Number(this.unwrapOperand(left)) * Number(this.unwrapOperand(right));
|
|
481
|
+
}
|
|
482
|
+
divide(left, right) {
|
|
483
|
+
return Number(this.unwrapOperand(left)) / Number(this.unwrapOperand(right));
|
|
484
|
+
}
|
|
485
|
+
remainder(left, right) {
|
|
486
|
+
return Number(this.unwrapOperand(left)) % Number(this.unwrapOperand(right));
|
|
487
|
+
}
|
|
488
|
+
exponent(left, right) {
|
|
489
|
+
return Number(this.unwrapOperand(left)) ** Number(this.unwrapOperand(right));
|
|
490
|
+
}
|
|
491
|
+
parseComparison() {
|
|
492
|
+
let left = this.parseAdditive();
|
|
493
|
+
while (true) {
|
|
494
|
+
if (this.match("operator", "<")) {
|
|
495
|
+
const right = this.parseAdditive();
|
|
496
|
+
left = this.compare(left, right, "<");
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (this.match("operator", "<=")) {
|
|
500
|
+
const right = this.parseAdditive();
|
|
501
|
+
left = this.compare(left, right, "<=");
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (this.match("operator", ">")) {
|
|
505
|
+
const right = this.parseAdditive();
|
|
506
|
+
left = this.compare(left, right, ">");
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (this.match("operator", ">=")) {
|
|
510
|
+
const right = this.parseAdditive();
|
|
511
|
+
left = this.compare(left, right, ">=");
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
return left;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
parseAdditive() {
|
|
518
|
+
let left = this.parseMultiplicative();
|
|
519
|
+
while (true) {
|
|
520
|
+
if (this.match("operator", "+")) {
|
|
521
|
+
const right = this.parseMultiplicative();
|
|
522
|
+
left = this.add(left, right);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (this.match("operator", "-")) {
|
|
526
|
+
const right = this.parseMultiplicative();
|
|
527
|
+
left = this.subtract(left, right);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
return left;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
parseMultiplicative() {
|
|
534
|
+
let left = this.parseUnary();
|
|
535
|
+
while (true) {
|
|
536
|
+
if (this.match("operator", "*")) {
|
|
537
|
+
const right = this.parseUnary();
|
|
538
|
+
left = this.multiply(left, right);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (this.match("operator", "/")) {
|
|
542
|
+
const right = this.parseUnary();
|
|
543
|
+
left = this.divide(left, right);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (this.match("operator", "%")) {
|
|
547
|
+
const right = this.parseUnary();
|
|
548
|
+
left = this.remainder(left, right);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (this.match("operator", "**")) {
|
|
552
|
+
const right = this.parseUnary();
|
|
553
|
+
left = this.exponent(left, right);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
return left;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
parseUnary() {
|
|
560
|
+
if (this.match("operator", "!")) {
|
|
561
|
+
return !this.unwrap(this.parseUnary());
|
|
562
|
+
}
|
|
563
|
+
if (this.match("operator", "+")) {
|
|
564
|
+
return Number(this.unwrap(this.parseUnary()));
|
|
565
|
+
}
|
|
566
|
+
if (this.match("operator", "-")) {
|
|
567
|
+
return -Number(this.unwrap(this.parseUnary()));
|
|
568
|
+
}
|
|
569
|
+
if (this.current().type === "identifier" && this.current().value === "typeof") {
|
|
570
|
+
this.advance();
|
|
571
|
+
return typeof this.unwrap(this.parseUnary());
|
|
572
|
+
}
|
|
573
|
+
return this.parseCallMember();
|
|
574
|
+
}
|
|
575
|
+
parseCallMember() {
|
|
576
|
+
let value = this.parsePrimary();
|
|
577
|
+
while (true) {
|
|
578
|
+
if (this.match("punctuator", ".")) {
|
|
579
|
+
const property = this.expect("identifier").value;
|
|
580
|
+
const target = this.isReference(value) ? value.value : value;
|
|
581
|
+
value = this.toReference(target, property);
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (this.match("punctuator", "[")) {
|
|
585
|
+
const property = this.parseConditional();
|
|
586
|
+
this.expect("punctuator", "]");
|
|
587
|
+
const target = this.isReference(value) ? value.value : value;
|
|
588
|
+
value = this.toReference(target, toPropertyKey(this.unwrap(property)));
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (this.match("punctuator", "(")) {
|
|
592
|
+
const args = [];
|
|
593
|
+
if (!this.match("punctuator", ")")) {
|
|
594
|
+
do {
|
|
595
|
+
args.push(this.unwrap(this.parseConditional()));
|
|
596
|
+
} while (this.match("punctuator", ","));
|
|
597
|
+
this.expect("punctuator", ")");
|
|
598
|
+
}
|
|
599
|
+
const callee = this.isReference(value) ? value.value : value;
|
|
600
|
+
if (typeof callee !== "function") {
|
|
601
|
+
throw createExpressionError(
|
|
602
|
+
"Expression attempted to call a non-function value."
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
const thisArg = this.isReference(value) ? value.base : void 0;
|
|
606
|
+
value = callee.apply(thisArg, args);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
return value;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
parsePrimary() {
|
|
613
|
+
const token = this.current();
|
|
614
|
+
if (token.type === "number") {
|
|
615
|
+
this.advance();
|
|
616
|
+
return Number(token.value);
|
|
617
|
+
}
|
|
618
|
+
if (token.type === "string") {
|
|
619
|
+
this.advance();
|
|
620
|
+
return token.value;
|
|
621
|
+
}
|
|
622
|
+
if (token.type === "identifier") {
|
|
623
|
+
this.advance();
|
|
624
|
+
return this.resolveIdentifier(token.value);
|
|
625
|
+
}
|
|
626
|
+
if (this.match("punctuator", "(")) {
|
|
627
|
+
const value = this.parseConditional();
|
|
628
|
+
this.expect("punctuator", ")");
|
|
629
|
+
return value;
|
|
630
|
+
}
|
|
631
|
+
if (this.match("punctuator", "[")) {
|
|
632
|
+
const items = [];
|
|
633
|
+
if (!this.match("punctuator", "]")) {
|
|
634
|
+
do {
|
|
635
|
+
if (this.match("operator", "...")) {
|
|
636
|
+
const spread = this.unwrap(this.parseConditional());
|
|
637
|
+
if (Array.isArray(spread)) {
|
|
638
|
+
items.push(...spread);
|
|
639
|
+
} else {
|
|
640
|
+
throw createExpressionError(
|
|
641
|
+
"Array spread expects an array value."
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
} else {
|
|
645
|
+
items.push(this.unwrap(this.parseConditional()));
|
|
646
|
+
}
|
|
647
|
+
} while (this.match("punctuator", ","));
|
|
648
|
+
this.expect("punctuator", "]");
|
|
649
|
+
}
|
|
650
|
+
return items;
|
|
651
|
+
}
|
|
652
|
+
if (this.match("punctuator", "{")) {
|
|
653
|
+
const out = {};
|
|
654
|
+
if (!this.match("punctuator", "}")) {
|
|
655
|
+
do {
|
|
656
|
+
if (this.match("operator", "...")) {
|
|
657
|
+
const spread = this.unwrap(this.parseConditional());
|
|
658
|
+
if (spread && typeof spread === "object") {
|
|
659
|
+
Object.assign(out, spread);
|
|
660
|
+
} else {
|
|
661
|
+
throw createExpressionError(
|
|
662
|
+
"Object spread expects an object value."
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
let key;
|
|
667
|
+
if (this.current().type === "string" || this.current().type === "number") {
|
|
668
|
+
key = String(this.advance().value);
|
|
669
|
+
} else {
|
|
670
|
+
key = this.expect("identifier").value;
|
|
671
|
+
}
|
|
672
|
+
if (this.match("punctuator", ":")) {
|
|
673
|
+
out[key] = this.unwrap(this.parseConditional());
|
|
674
|
+
} else {
|
|
675
|
+
out[key] = this.resolveIdentifier(key);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
} while (this.match("punctuator", ","));
|
|
679
|
+
this.expect("punctuator", "}");
|
|
680
|
+
}
|
|
681
|
+
return out;
|
|
682
|
+
}
|
|
683
|
+
throw createExpressionError(
|
|
684
|
+
`Unexpected token "${token.value}" in expression.`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
function evaluateExpression(expression, context) {
|
|
689
|
+
const trimmed = expression.trim();
|
|
690
|
+
if (!trimmed)
|
|
691
|
+
throw createExpressionError("Expression cannot be empty.", expression);
|
|
692
|
+
try {
|
|
693
|
+
return new ExpressionParser(trimmed, context).evaluate();
|
|
694
|
+
} catch (error) {
|
|
695
|
+
throw error instanceof WorkflowExpressionError ? error : createExpressionError(
|
|
696
|
+
"Failed to evaluate workflow expression.",
|
|
697
|
+
expression,
|
|
698
|
+
error
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function formatCost(usd) {
|
|
703
|
+
if (usd < 0.01) return `$${(usd * 100).toFixed(2)}\xA2`;
|
|
704
|
+
return `$${usd.toFixed(4)}`;
|
|
705
|
+
}
|
|
706
|
+
var WorkflowEngine = class {
|
|
707
|
+
options;
|
|
708
|
+
cancelled = false;
|
|
709
|
+
constructor(options) {
|
|
710
|
+
this.options = options;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Cancel the in-flight execution loop before the next step begins.
|
|
714
|
+
*/
|
|
715
|
+
cancel() {
|
|
716
|
+
this.cancelled = true;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Execute a workflow definition from start to finish and capture the full
|
|
720
|
+
* run state for UI or persistence layers.
|
|
721
|
+
*/
|
|
722
|
+
async execute(definition, input = {}) {
|
|
723
|
+
this.cancelled = false;
|
|
724
|
+
const run = {
|
|
725
|
+
id: crypto.randomUUID?.() ?? String(Date.now()),
|
|
726
|
+
workflowId: definition.id,
|
|
727
|
+
workflowVersion: definition.version,
|
|
728
|
+
status: "running",
|
|
729
|
+
input,
|
|
730
|
+
variables: { ...definition.variables, ...input },
|
|
731
|
+
stepResults: [],
|
|
732
|
+
currentStepIndex: 0,
|
|
733
|
+
triggeredBy: "",
|
|
734
|
+
startedAt: Date.now()
|
|
735
|
+
};
|
|
736
|
+
const sortedSteps = this.topoSort(definition);
|
|
737
|
+
for (let i = 0; i < sortedSteps.length; i++) {
|
|
738
|
+
if (this.cancelled) {
|
|
739
|
+
run.status = "cancelled";
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
run.currentStepIndex = i;
|
|
743
|
+
const stepDef = sortedSteps[i];
|
|
744
|
+
const result = await this.executeStep(stepDef, run.variables, definition);
|
|
745
|
+
run.stepResults.push(result);
|
|
746
|
+
this.options.onStepComplete?.(result);
|
|
747
|
+
if (result.status === "failed" && !stepDef.optional) {
|
|
748
|
+
run.status = "failed";
|
|
749
|
+
run.error = result.error;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
if (result.output !== void 0 && result.output !== null && "outputVar" in stepDef.config) {
|
|
753
|
+
if (stepDef.config.outputVar) {
|
|
754
|
+
run.variables[stepDef.config.outputVar] = result.output;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (stepDef.config.type === "condition") {
|
|
758
|
+
const nextStepId = Boolean(result.output) ? stepDef.config.trueStepId : stepDef.config.falseStepId;
|
|
759
|
+
const targetIndex = sortedSteps.findIndex((s) => s.id === nextStepId);
|
|
760
|
+
if (targetIndex > i) {
|
|
761
|
+
for (let j = i + 1; j < targetIndex; j++) {
|
|
762
|
+
run.stepResults.push({
|
|
763
|
+
stepId: sortedSteps[j].id,
|
|
764
|
+
stepName: sortedSteps[j].name,
|
|
765
|
+
type: sortedSteps[j].type,
|
|
766
|
+
status: "skipped",
|
|
767
|
+
durationMs: 0,
|
|
768
|
+
startedAt: Date.now()
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
i = targetIndex - 1;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (run.status === "running") {
|
|
776
|
+
run.status = "completed";
|
|
777
|
+
}
|
|
778
|
+
run.completedAt = Date.now();
|
|
779
|
+
return run;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Execute one step with retry handling and normalized result capture.
|
|
783
|
+
*/
|
|
784
|
+
async executeStep(stepDef, variables, _definition) {
|
|
785
|
+
const start = Date.now();
|
|
786
|
+
const baseResult = {
|
|
787
|
+
stepId: stepDef.id,
|
|
788
|
+
stepName: stepDef.name,
|
|
789
|
+
type: stepDef.type,
|
|
790
|
+
status: "running",
|
|
791
|
+
durationMs: 0,
|
|
792
|
+
startedAt: start
|
|
793
|
+
};
|
|
794
|
+
let attempts = 0;
|
|
795
|
+
const maxAttempts = stepDef.retries?.maxAttempts ?? 1;
|
|
796
|
+
while (attempts < maxAttempts) {
|
|
797
|
+
attempts++;
|
|
798
|
+
try {
|
|
799
|
+
const output = await this.runStepLogic(stepDef, variables);
|
|
800
|
+
return {
|
|
801
|
+
...baseResult,
|
|
802
|
+
status: "completed",
|
|
803
|
+
output,
|
|
804
|
+
durationMs: Date.now() - start,
|
|
805
|
+
completedAt: Date.now()
|
|
806
|
+
};
|
|
807
|
+
} catch (err) {
|
|
808
|
+
if (attempts >= maxAttempts) {
|
|
809
|
+
const workflowError = normalizeWorkflowError(
|
|
810
|
+
err,
|
|
811
|
+
`Workflow step "${stepDef.name}" failed.`,
|
|
812
|
+
{ stepId: stepDef.id, workflowId: _definition.id }
|
|
813
|
+
);
|
|
814
|
+
return {
|
|
815
|
+
...baseResult,
|
|
816
|
+
status: "failed",
|
|
817
|
+
error: workflowError.message,
|
|
818
|
+
durationMs: Date.now() - start,
|
|
819
|
+
completedAt: Date.now()
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
const backoff = stepDef.retries?.backoffMs ?? 1e3;
|
|
823
|
+
await new Promise((r) => setTimeout(r, backoff * 2 ** (attempts - 1)));
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
...baseResult,
|
|
828
|
+
status: "failed",
|
|
829
|
+
error: "Max retries exceeded",
|
|
830
|
+
durationMs: Date.now() - start
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Run the concrete logic for an individual step type.
|
|
835
|
+
*/
|
|
836
|
+
async runStepLogic(stepDef, variables) {
|
|
837
|
+
const config = stepDef.config;
|
|
838
|
+
switch (config.type) {
|
|
839
|
+
case "llm-call": {
|
|
840
|
+
if (!this.options.callLLM) {
|
|
841
|
+
throw new WorkflowConfigurationError(
|
|
842
|
+
"No LLM function provided for llm-call steps.",
|
|
843
|
+
{
|
|
844
|
+
stepId: stepDef.id
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
const userPrompt = this.interpolate(
|
|
849
|
+
config.userPromptTemplate,
|
|
850
|
+
variables
|
|
851
|
+
);
|
|
852
|
+
const result = await this.options.callLLM(
|
|
853
|
+
config.systemPrompt,
|
|
854
|
+
userPrompt,
|
|
855
|
+
config.model
|
|
856
|
+
);
|
|
857
|
+
if (config.parseJson) {
|
|
858
|
+
try {
|
|
859
|
+
return JSON.parse(result.content);
|
|
860
|
+
} catch {
|
|
861
|
+
return result.content;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return result.content;
|
|
865
|
+
}
|
|
866
|
+
case "transform": {
|
|
867
|
+
const scope = this.buildExpressionScope(config.inputVars, variables);
|
|
868
|
+
return evaluateExpression(config.expression, scope);
|
|
869
|
+
}
|
|
870
|
+
case "condition": {
|
|
871
|
+
return Boolean(evaluateExpression(config.expression, variables));
|
|
872
|
+
}
|
|
873
|
+
case "human-approval": {
|
|
874
|
+
if (!this.options.onApprovalRequired) {
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
return this.options.onApprovalRequired(config.message, stepDef.id);
|
|
878
|
+
}
|
|
879
|
+
case "webhook": {
|
|
880
|
+
if (!this.options.callWebhook) {
|
|
881
|
+
throw new WorkflowConfigurationError(
|
|
882
|
+
"No webhook function provided for webhook steps.",
|
|
883
|
+
{
|
|
884
|
+
stepId: stepDef.id
|
|
885
|
+
}
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
const body = config.bodyTemplate ? interpolate(config.bodyTemplate, variables) : void 0;
|
|
889
|
+
return this.options.callWebhook(
|
|
890
|
+
config.url,
|
|
891
|
+
config.method,
|
|
892
|
+
body,
|
|
893
|
+
config.headers
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
case "delay": {
|
|
897
|
+
await new Promise((r) => setTimeout(r, config.durationMs));
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
case "custom": {
|
|
901
|
+
const handler = this.options.customHandlers?.[config.handler];
|
|
902
|
+
if (!handler) {
|
|
903
|
+
throw new WorkflowConfigurationError(
|
|
904
|
+
`No handler registered for custom step "${config.handler}".`,
|
|
905
|
+
{ stepId: stepDef.id }
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
return handler(config.params, variables);
|
|
909
|
+
}
|
|
910
|
+
default:
|
|
911
|
+
throw new WorkflowExecutionError(
|
|
912
|
+
`Unsupported step type "${config.type}".`,
|
|
913
|
+
{
|
|
914
|
+
stepId: stepDef.id
|
|
915
|
+
}
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
/** Interpolate `{{variable}}` references in a string. */
|
|
920
|
+
interpolate(template, vars) {
|
|
921
|
+
return interpolate(template, vars);
|
|
922
|
+
}
|
|
923
|
+
buildExpressionScope(inputVars, variables) {
|
|
924
|
+
const scope = {};
|
|
925
|
+
for (const name of inputVars) {
|
|
926
|
+
scope[name] = variables[name];
|
|
927
|
+
}
|
|
928
|
+
return scope;
|
|
929
|
+
}
|
|
930
|
+
/** Topological sort of steps based on declared workflow connections. */
|
|
931
|
+
topoSort(def) {
|
|
932
|
+
const stepMap = new Map(def.steps.map((s) => [s.id, s]));
|
|
933
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
934
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
935
|
+
for (const step of def.steps) {
|
|
936
|
+
inDegree.set(step.id, 0);
|
|
937
|
+
adjacency.set(step.id, []);
|
|
938
|
+
}
|
|
939
|
+
for (const conn of def.connections) {
|
|
940
|
+
adjacency.get(conn.fromStepId)?.push(conn.toStepId);
|
|
941
|
+
inDegree.set(conn.toStepId, (inDegree.get(conn.toStepId) ?? 0) + 1);
|
|
942
|
+
}
|
|
943
|
+
const queue = [];
|
|
944
|
+
for (const [id, degree] of inDegree) {
|
|
945
|
+
if (degree === 0) queue.push(id);
|
|
946
|
+
}
|
|
947
|
+
const sorted = [];
|
|
948
|
+
while (queue.length > 0) {
|
|
949
|
+
const id = queue.shift();
|
|
950
|
+
const step = stepMap.get(id);
|
|
951
|
+
if (step) sorted.push(step);
|
|
952
|
+
for (const neighbor of adjacency.get(id) ?? []) {
|
|
953
|
+
const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
|
|
954
|
+
inDegree.set(neighbor, newDegree);
|
|
955
|
+
if (newDegree === 0) queue.push(neighbor);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (sorted.length !== def.steps.length) {
|
|
959
|
+
throw new WorkflowGraphError(
|
|
960
|
+
"Workflow definition contains a cycle and cannot be executed.",
|
|
961
|
+
{
|
|
962
|
+
workflowId: def.id
|
|
963
|
+
}
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
return sorted;
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
var llmCallConfigSchema = z.object({
|
|
970
|
+
type: z.literal("llm-call"),
|
|
971
|
+
model: z.string().optional(),
|
|
972
|
+
systemPrompt: z.string().min(1, "System prompt is required"),
|
|
973
|
+
userPromptTemplate: z.string().min(1, "User prompt template is required"),
|
|
974
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
975
|
+
maxTokens: z.number().int().positive().optional(),
|
|
976
|
+
parseJson: z.boolean().optional(),
|
|
977
|
+
outputVar: z.string().min(1, "Output variable name is required")
|
|
978
|
+
});
|
|
979
|
+
var transformConfigSchema = z.object({
|
|
980
|
+
type: z.literal("transform"),
|
|
981
|
+
expression: z.string().min(1, "Expression is required"),
|
|
982
|
+
inputVars: z.array(z.string()).min(1, "At least one input variable is required"),
|
|
983
|
+
outputVar: z.string().min(1)
|
|
984
|
+
});
|
|
985
|
+
var conditionConfigSchema = z.object({
|
|
986
|
+
type: z.literal("condition"),
|
|
987
|
+
expression: z.string().min(1),
|
|
988
|
+
trueStepId: z.string().min(1),
|
|
989
|
+
falseStepId: z.string().min(1)
|
|
990
|
+
});
|
|
991
|
+
var humanApprovalConfigSchema = z.object({
|
|
992
|
+
type: z.literal("human-approval"),
|
|
993
|
+
message: z.string().min(1, "Approval message is required"),
|
|
994
|
+
approvers: z.array(z.string()).optional(),
|
|
995
|
+
autoApproveAfterMs: z.number().positive().optional()
|
|
996
|
+
});
|
|
997
|
+
var webhookConfigSchema = z.object({
|
|
998
|
+
type: z.literal("webhook"),
|
|
999
|
+
url: z.string().url("Valid URL required"),
|
|
1000
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE"]),
|
|
1001
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
1002
|
+
bodyTemplate: z.string().optional(),
|
|
1003
|
+
outputVar: z.string().min(1)
|
|
1004
|
+
});
|
|
1005
|
+
var delayConfigSchema = z.object({
|
|
1006
|
+
type: z.literal("delay"),
|
|
1007
|
+
durationMs: z.number().int().positive("Duration must be positive")
|
|
1008
|
+
});
|
|
1009
|
+
var parallelConfigSchema = z.object({
|
|
1010
|
+
type: z.literal("parallel"),
|
|
1011
|
+
stepIds: z.array(z.string()).min(2, "Parallel needs at least 2 steps"),
|
|
1012
|
+
failurePolicy: z.enum(["fail-fast", "continue"])
|
|
1013
|
+
});
|
|
1014
|
+
var loopConfigSchema = z.object({
|
|
1015
|
+
type: z.literal("loop"),
|
|
1016
|
+
stepIds: z.array(z.string()).min(1),
|
|
1017
|
+
iterateVar: z.string().min(1),
|
|
1018
|
+
itemVar: z.string().min(1),
|
|
1019
|
+
maxIterations: z.number().int().positive().optional()
|
|
1020
|
+
});
|
|
1021
|
+
var subWorkflowConfigSchema = z.object({
|
|
1022
|
+
type: z.literal("sub-workflow"),
|
|
1023
|
+
workflowId: z.string().min(1),
|
|
1024
|
+
inputMapping: z.record(z.string(), z.string()),
|
|
1025
|
+
outputVar: z.string().min(1)
|
|
1026
|
+
});
|
|
1027
|
+
var customStepConfigSchema = z.object({
|
|
1028
|
+
type: z.literal("custom"),
|
|
1029
|
+
handler: z.string().min(1, "Handler name is required"),
|
|
1030
|
+
params: z.record(z.string(), z.unknown()),
|
|
1031
|
+
outputVar: z.string().optional()
|
|
1032
|
+
});
|
|
1033
|
+
var stepConfigSchema = z.discriminatedUnion("type", [
|
|
1034
|
+
llmCallConfigSchema,
|
|
1035
|
+
transformConfigSchema,
|
|
1036
|
+
conditionConfigSchema,
|
|
1037
|
+
humanApprovalConfigSchema,
|
|
1038
|
+
webhookConfigSchema,
|
|
1039
|
+
delayConfigSchema,
|
|
1040
|
+
parallelConfigSchema,
|
|
1041
|
+
loopConfigSchema,
|
|
1042
|
+
subWorkflowConfigSchema,
|
|
1043
|
+
customStepConfigSchema
|
|
1044
|
+
]);
|
|
1045
|
+
var workflowStepDefSchema = z.object({
|
|
1046
|
+
id: z.string().min(1),
|
|
1047
|
+
name: z.string().min(1, "Step name is required"),
|
|
1048
|
+
type: z.enum(["llm-call", "transform", "condition", "human-approval", "webhook", "delay", "parallel", "loop", "sub-workflow", "custom"]),
|
|
1049
|
+
config: stepConfigSchema,
|
|
1050
|
+
position: z.object({ x: z.number(), y: z.number() }).optional(),
|
|
1051
|
+
optional: z.boolean().optional(),
|
|
1052
|
+
retries: z.object({ maxAttempts: z.number().int().positive(), backoffMs: z.number().positive() }).optional()
|
|
1053
|
+
});
|
|
1054
|
+
var stepConnectionSchema = z.object({
|
|
1055
|
+
fromStepId: z.string().min(1),
|
|
1056
|
+
toStepId: z.string().min(1),
|
|
1057
|
+
label: z.string().optional(),
|
|
1058
|
+
condition: z.string().optional()
|
|
1059
|
+
});
|
|
1060
|
+
var workflowVariableSchema = z.object({
|
|
1061
|
+
name: z.string().min(1),
|
|
1062
|
+
type: z.enum(["string", "number", "boolean", "object", "array"]),
|
|
1063
|
+
description: z.string().optional(),
|
|
1064
|
+
required: z.boolean().optional(),
|
|
1065
|
+
default: z.unknown().optional()
|
|
1066
|
+
});
|
|
1067
|
+
var workflowDefinitionSchema = z.object({
|
|
1068
|
+
id: z.string().min(1),
|
|
1069
|
+
name: z.string().min(1, "Workflow name is required").max(120, "Name too long"),
|
|
1070
|
+
description: z.string().max(2e3).optional(),
|
|
1071
|
+
version: z.number().int().positive(),
|
|
1072
|
+
status: z.enum(["draft", "active", "paused", "archived"]),
|
|
1073
|
+
steps: z.array(workflowStepDefSchema).min(1, "At least one step is required"),
|
|
1074
|
+
connections: z.array(stepConnectionSchema),
|
|
1075
|
+
inputSchema: z.array(workflowVariableSchema).optional(),
|
|
1076
|
+
variables: z.record(z.string(), z.unknown()).optional(),
|
|
1077
|
+
tags: z.array(z.string()).optional(),
|
|
1078
|
+
createdBy: z.string(),
|
|
1079
|
+
createdAt: z.number(),
|
|
1080
|
+
updatedAt: z.number()
|
|
1081
|
+
});
|
|
1082
|
+
var stepResultSchema = z.object({
|
|
1083
|
+
stepId: z.string(),
|
|
1084
|
+
stepName: z.string(),
|
|
1085
|
+
type: z.enum(["llm-call", "transform", "condition", "human-approval", "webhook", "delay", "parallel", "loop", "sub-workflow", "custom"]),
|
|
1086
|
+
status: z.enum(["pending", "running", "completed", "failed", "skipped", "waiting-approval"]),
|
|
1087
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
1088
|
+
output: z.unknown().optional(),
|
|
1089
|
+
error: z.string().optional(),
|
|
1090
|
+
durationMs: z.number(),
|
|
1091
|
+
tokens: z.number().optional(),
|
|
1092
|
+
costUsd: z.number().optional(),
|
|
1093
|
+
startedAt: z.number(),
|
|
1094
|
+
completedAt: z.number().optional()
|
|
1095
|
+
});
|
|
1096
|
+
var workflowRunSchema = z.object({
|
|
1097
|
+
id: z.string(),
|
|
1098
|
+
workflowId: z.string(),
|
|
1099
|
+
workflowVersion: z.number(),
|
|
1100
|
+
status: z.enum(["pending", "running", "paused", "completed", "failed", "cancelled"]),
|
|
1101
|
+
input: z.record(z.string(), z.unknown()),
|
|
1102
|
+
variables: z.record(z.string(), z.unknown()),
|
|
1103
|
+
stepResults: z.array(stepResultSchema),
|
|
1104
|
+
currentStepIndex: z.number(),
|
|
1105
|
+
error: z.string().optional(),
|
|
1106
|
+
triggeredBy: z.string(),
|
|
1107
|
+
startedAt: z.number(),
|
|
1108
|
+
completedAt: z.number().optional()
|
|
1109
|
+
});
|
|
1110
|
+
function validateWorkflow(data) {
|
|
1111
|
+
return workflowDefinitionSchema.safeParse(data);
|
|
1112
|
+
}
|
|
1113
|
+
function validateRun(data) {
|
|
1114
|
+
return workflowRunSchema.safeParse(data);
|
|
1115
|
+
}
|
|
1116
|
+
function uid() {
|
|
1117
|
+
return `step_tpl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1118
|
+
}
|
|
1119
|
+
function wfId() {
|
|
1120
|
+
return `wf_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1121
|
+
}
|
|
1122
|
+
var contentPipeline = {
|
|
1123
|
+
id: "tpl-content-pipeline",
|
|
1124
|
+
name: "Content Pipeline",
|
|
1125
|
+
description: "Generate, review, and publish content with AI refinement and human approval.",
|
|
1126
|
+
category: "content",
|
|
1127
|
+
tags: ["content", "blog", "marketing"],
|
|
1128
|
+
create: (createdBy) => {
|
|
1129
|
+
const draft = uid(), refine = uid(), approve = uid(), publish = uid();
|
|
1130
|
+
const steps = [
|
|
1131
|
+
{ id: draft, name: "Generate Draft", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a content writer.", userPromptTemplate: "Write a blog post about {{topic}}. Tone: {{tone}}.", outputVar: "draft" }, position: { x: 100, y: 200 } },
|
|
1132
|
+
{ id: refine, name: "Refine Content", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are an editor. Improve clarity, grammar, and engagement.", userPromptTemplate: "Polish this draft:\n\n{{draft}}", outputVar: "polished" }, position: { x: 350, y: 200 } },
|
|
1133
|
+
{ id: approve, name: "Human Review", type: "human-approval", config: { type: "human-approval", message: "Please review the polished content before publishing." }, position: { x: 600, y: 200 } },
|
|
1134
|
+
{ id: publish, name: "Format for Publishing", type: "transform", config: { type: "transform", expression: '({ title: polished.split("\\n")[0], body: polished, publishedAt: Date.now() })', inputVars: ["polished"], outputVar: "publishData" }, position: { x: 850, y: 200 } }
|
|
1135
|
+
];
|
|
1136
|
+
const connections = [
|
|
1137
|
+
{ fromStepId: draft, toStepId: refine },
|
|
1138
|
+
{ fromStepId: refine, toStepId: approve },
|
|
1139
|
+
{ fromStepId: approve, toStepId: publish }
|
|
1140
|
+
];
|
|
1141
|
+
return { id: wfId(), name: "Content Pipeline", description: "AI-powered content generation with editorial review.", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "topic", type: "string", required: true }, { name: "tone", type: "string", required: false, default: "professional" }], tags: ["content"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
var dataEnrichment = {
|
|
1145
|
+
id: "tpl-data-enrichment",
|
|
1146
|
+
name: "Data Enrichment",
|
|
1147
|
+
description: "Fetch external data, enrich with AI analysis, and transform for downstream use.",
|
|
1148
|
+
category: "data",
|
|
1149
|
+
tags: ["data", "analytics", "enrichment"],
|
|
1150
|
+
create: (createdBy) => {
|
|
1151
|
+
const fetch_ = uid(), analyze = uid(), transform = uid();
|
|
1152
|
+
const steps = [
|
|
1153
|
+
{ id: fetch_, name: "Fetch Data", type: "webhook", config: { type: "webhook", url: "https://api.example.com/data-source", method: "GET", outputVar: "rawData" }, position: { x: 100, y: 200 } },
|
|
1154
|
+
{ id: analyze, name: "AI Analysis", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a data analyst. Analyze the data and extract key insights.", userPromptTemplate: "Analyze this dataset and return a JSON summary with key metrics:\n\n{{rawData}}", parseJson: true, outputVar: "analysis" }, position: { x: 400, y: 200 } },
|
|
1155
|
+
{ id: transform, name: "Transform Output", type: "transform", config: { type: "transform", expression: "({ ...analysis, enrichedAt: Date.now(), source: dataSourceUrl })", inputVars: ["analysis", "dataSourceUrl"], outputVar: "enrichedData" }, position: { x: 700, y: 200 } }
|
|
1156
|
+
];
|
|
1157
|
+
const connections = [
|
|
1158
|
+
{ fromStepId: fetch_, toStepId: analyze },
|
|
1159
|
+
{ fromStepId: analyze, toStepId: transform }
|
|
1160
|
+
];
|
|
1161
|
+
return { id: wfId(), name: "Data Enrichment", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "dataSourceUrl", type: "string", required: true }], tags: ["data"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
var approvalChain = {
|
|
1165
|
+
id: "tpl-approval-chain",
|
|
1166
|
+
name: "Approval Chain",
|
|
1167
|
+
description: "Multi-level approval workflow with conditional escalation.",
|
|
1168
|
+
category: "approval",
|
|
1169
|
+
tags: ["approval", "governance", "compliance"],
|
|
1170
|
+
create: (createdBy) => {
|
|
1171
|
+
const checkRisk = uid(), lowApproval = uid(), highApproval = uid(), finalize = uid();
|
|
1172
|
+
const steps = [
|
|
1173
|
+
{ id: checkRisk, name: "Assess Risk Level", type: "llm-call", config: { type: "llm-call", systemPrompt: 'Assess the risk level of this request. Respond with JSON: {"riskLevel": "low"|"medium"|"high", "reason": "..."}', userPromptTemplate: "{{request}}", parseJson: true, outputVar: "riskAssessment" }, position: { x: 100, y: 200 } },
|
|
1174
|
+
{ id: lowApproval, name: "Manager Approval", type: "human-approval", config: { type: "human-approval", message: "Low/medium risk: Manager approval needed for: {{request}}" }, position: { x: 400, y: 100 } },
|
|
1175
|
+
{ id: highApproval, name: "Executive Approval", type: "human-approval", config: { type: "human-approval", message: "High risk: Executive approval required for: {{request}}" }, position: { x: 400, y: 300 } },
|
|
1176
|
+
{ id: finalize, name: "Record Decision", type: "transform", config: { type: "transform", expression: "({ approved: true, approvedAt: Date.now(), riskLevel: riskAssessment.riskLevel })", inputVars: ["riskAssessment"], outputVar: "decision" }, position: { x: 700, y: 200 } }
|
|
1177
|
+
];
|
|
1178
|
+
const connections = [
|
|
1179
|
+
{ fromStepId: checkRisk, toStepId: lowApproval, condition: 'riskAssessment.riskLevel !== "high"' },
|
|
1180
|
+
{ fromStepId: checkRisk, toStepId: highApproval, condition: 'riskAssessment.riskLevel === "high"' },
|
|
1181
|
+
{ fromStepId: lowApproval, toStepId: finalize },
|
|
1182
|
+
{ fromStepId: highApproval, toStepId: finalize }
|
|
1183
|
+
];
|
|
1184
|
+
return { id: wfId(), name: "Approval Chain", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "request", type: "string", required: true }], tags: ["approval"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
var researchPipeline = {
|
|
1188
|
+
id: "tpl-research",
|
|
1189
|
+
name: "Research Pipeline",
|
|
1190
|
+
description: "Multi-source research with AI synthesis and summary generation.",
|
|
1191
|
+
category: "research",
|
|
1192
|
+
tags: ["research", "analysis", "summary"],
|
|
1193
|
+
create: (createdBy) => {
|
|
1194
|
+
const research = uid(), synthesize = uid(), summarize = uid();
|
|
1195
|
+
const steps = [
|
|
1196
|
+
{ id: research, name: "Deep Research", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a research assistant. Provide comprehensive, well-sourced research on the given topic.", userPromptTemplate: "Research the following topic in depth: {{topic}}\n\nFocus areas: {{focusAreas}}", outputVar: "researchOutput" }, position: { x: 100, y: 200 } },
|
|
1197
|
+
{ id: synthesize, name: "Synthesize Findings", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a synthesis expert. Combine research findings into structured insights with citations.", userPromptTemplate: "Synthesize these research findings into key themes and actionable insights:\n\n{{researchOutput}}", parseJson: true, outputVar: "synthesis" }, position: { x: 400, y: 200 } },
|
|
1198
|
+
{ id: summarize, name: "Executive Summary", type: "llm-call", config: { type: "llm-call", systemPrompt: "Write a concise executive summary (3-5 paragraphs) suitable for senior leadership.", userPromptTemplate: "Create an executive summary from:\n\n{{synthesis}}", outputVar: "executiveSummary" }, position: { x: 700, y: 200 } }
|
|
1199
|
+
];
|
|
1200
|
+
const connections = [
|
|
1201
|
+
{ fromStepId: research, toStepId: synthesize },
|
|
1202
|
+
{ fromStepId: synthesize, toStepId: summarize }
|
|
1203
|
+
];
|
|
1204
|
+
return { id: wfId(), name: "Research Pipeline", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "topic", type: "string", required: true }, { name: "focusAreas", type: "string", required: false, default: "general overview" }], tags: ["research"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
var codeReviewPipeline = {
|
|
1208
|
+
id: "tpl-code-review",
|
|
1209
|
+
name: "Code Review Pipeline",
|
|
1210
|
+
description: "Automated code review with AI analysis, security check, and human approval.",
|
|
1211
|
+
category: "devops",
|
|
1212
|
+
tags: ["code", "review", "security", "devops"],
|
|
1213
|
+
create: (createdBy) => {
|
|
1214
|
+
const analyze = uid(), security = uid(), approve = uid(), report = uid();
|
|
1215
|
+
const steps = [
|
|
1216
|
+
{ id: analyze, name: "AI Code Review", type: "llm-call", config: { type: "llm-call", systemPrompt: 'You are a senior code reviewer. Analyze the code for bugs, performance issues, and best practices. Return JSON: {"issues": [], "suggestions": [], "quality": 1-10}', userPromptTemplate: "Review this code:\n\n```{{language}}\n{{code}}\n```", parseJson: true, outputVar: "codeReview" }, position: { x: 100, y: 200 } },
|
|
1217
|
+
{ id: security, name: "Security Scan", type: "llm-call", config: { type: "llm-call", systemPrompt: 'You are a security auditor. Check for vulnerabilities (XSS, SQL injection, secrets exposure, etc). Return JSON: {"vulnerabilities": [], "riskLevel": "low"|"medium"|"high"}', userPromptTemplate: "Security audit for:\n\n```{{language}}\n{{code}}\n```", parseJson: true, outputVar: "securityReport" }, position: { x: 100, y: 400 } },
|
|
1218
|
+
{ id: approve, name: "Developer Approval", type: "human-approval", config: { type: "human-approval", message: "Review the AI code analysis and security scan before merging." }, position: { x: 450, y: 300 } },
|
|
1219
|
+
{ id: report, name: "Generate Report", type: "transform", config: { type: "transform", expression: "({ codeQuality: codeReview.quality, issues: codeReview.issues.length, vulnerabilities: securityReport.vulnerabilities.length, riskLevel: securityReport.riskLevel, reviewedAt: Date.now() })", inputVars: ["codeReview", "securityReport"], outputVar: "finalReport" }, position: { x: 700, y: 300 } }
|
|
1220
|
+
];
|
|
1221
|
+
const connections = [
|
|
1222
|
+
{ fromStepId: analyze, toStepId: approve },
|
|
1223
|
+
{ fromStepId: security, toStepId: approve },
|
|
1224
|
+
{ fromStepId: approve, toStepId: report }
|
|
1225
|
+
];
|
|
1226
|
+
return { id: wfId(), name: "Code Review Pipeline", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "code", type: "string", required: true }, { name: "language", type: "string", required: false, default: "typescript" }], tags: ["devops", "review"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
var leadQualification = {
|
|
1230
|
+
id: "tpl-lead-qualification",
|
|
1231
|
+
name: "Lead Qualification",
|
|
1232
|
+
description: "Score incoming leads, prepare a summary, and route them for follow-up.",
|
|
1233
|
+
category: "data",
|
|
1234
|
+
tags: ["sales", "crm", "lead-scoring"],
|
|
1235
|
+
create: (createdBy) => {
|
|
1236
|
+
const score = uid(), summarize = uid(), route = uid();
|
|
1237
|
+
const steps = [
|
|
1238
|
+
{ id: score, name: "Score Lead", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a sales operations analyst. Score the lead from 1-10 and return JSON with score, rationale, and nextAction.", userPromptTemplate: "Lead details:\n\n{{lead}}", parseJson: true, outputVar: "leadScore" }, position: { x: 100, y: 200 } },
|
|
1239
|
+
{ id: summarize, name: "Summarize Lead", type: "transform", config: { type: "transform", expression: '({ score: leadScore.score, summary: leadScore.rationale, priority: leadScore.score >= 8 ? "high" : "standard", updatedAt: Date.now() })', inputVars: ["leadScore"], outputVar: "leadSummary" }, position: { x: 400, y: 200 } },
|
|
1240
|
+
{ id: route, name: "Notify Sales", type: "webhook", config: { type: "webhook", url: "https://api.example.com/crm/webhook", method: "POST", bodyTemplate: "Lead summary: {{leadSummary}}", outputVar: "salesNotification" }, position: { x: 700, y: 200 } }
|
|
1241
|
+
];
|
|
1242
|
+
const connections = [
|
|
1243
|
+
{ fromStepId: score, toStepId: summarize },
|
|
1244
|
+
{ fromStepId: summarize, toStepId: route }
|
|
1245
|
+
];
|
|
1246
|
+
return {
|
|
1247
|
+
id: wfId(),
|
|
1248
|
+
name: "Lead Qualification",
|
|
1249
|
+
description: "AI-assisted lead scoring and sales handoff.",
|
|
1250
|
+
version: 1,
|
|
1251
|
+
status: "draft",
|
|
1252
|
+
steps,
|
|
1253
|
+
connections,
|
|
1254
|
+
inputSchema: [
|
|
1255
|
+
{ name: "lead", type: "string", required: true },
|
|
1256
|
+
{ name: "crmWebhookUrl", type: "string", required: false, default: "https://api.example.com/crm/webhook" }
|
|
1257
|
+
],
|
|
1258
|
+
tags: ["sales", "lead"],
|
|
1259
|
+
createdBy,
|
|
1260
|
+
createdAt: Date.now(),
|
|
1261
|
+
updatedAt: Date.now()
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
var incidentTriage = {
|
|
1266
|
+
id: "tpl-incident-triage",
|
|
1267
|
+
name: "Incident Triage",
|
|
1268
|
+
description: "Collect incident context, classify severity, and draft the response.",
|
|
1269
|
+
category: "devops",
|
|
1270
|
+
tags: ["incident", "ops", "triage"],
|
|
1271
|
+
create: (createdBy) => {
|
|
1272
|
+
const fetchIncident = uid(), classify = uid(), draftResponse = uid();
|
|
1273
|
+
const steps = [
|
|
1274
|
+
{ id: fetchIncident, name: "Fetch Incident", type: "webhook", config: { type: "webhook", url: "https://api.example.com/incidents", method: "GET", outputVar: "incidentData" }, position: { x: 100, y: 200 } },
|
|
1275
|
+
{ id: classify, name: "Classify Severity", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are an incident commander. Classify the incident severity and summarize the likely impact in JSON.", userPromptTemplate: "Incident context:\n\n{{incidentData}}", parseJson: true, outputVar: "classification" }, position: { x: 400, y: 200 } },
|
|
1276
|
+
{ id: draftResponse, name: "Draft Response", type: "transform", config: { type: "transform", expression: "({ severity: classification.severity, summary: classification.summary, draftedAt: Date.now() })", inputVars: ["classification"], outputVar: "responseDraft" }, position: { x: 700, y: 200 } }
|
|
1277
|
+
];
|
|
1278
|
+
const connections = [
|
|
1279
|
+
{ fromStepId: fetchIncident, toStepId: classify },
|
|
1280
|
+
{ fromStepId: classify, toStepId: draftResponse }
|
|
1281
|
+
];
|
|
1282
|
+
return {
|
|
1283
|
+
id: wfId(),
|
|
1284
|
+
name: "Incident Triage",
|
|
1285
|
+
description: "Automated incident classification and response drafting.",
|
|
1286
|
+
version: 1,
|
|
1287
|
+
status: "draft",
|
|
1288
|
+
steps,
|
|
1289
|
+
connections,
|
|
1290
|
+
inputSchema: [
|
|
1291
|
+
{ name: "incidentApiUrl", type: "string", required: false, default: "https://api.example.com/incidents" }
|
|
1292
|
+
],
|
|
1293
|
+
tags: ["incident", "ops"],
|
|
1294
|
+
createdBy,
|
|
1295
|
+
createdAt: Date.now(),
|
|
1296
|
+
updatedAt: Date.now()
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
var launchReadiness = {
|
|
1301
|
+
id: "tpl-launch-readiness",
|
|
1302
|
+
name: "Launch Readiness",
|
|
1303
|
+
description: "Prepare launch notes, request approval, and publish the rollout payload.",
|
|
1304
|
+
category: "approval",
|
|
1305
|
+
tags: ["launch", "release", "approval"],
|
|
1306
|
+
create: (createdBy) => {
|
|
1307
|
+
const prepare = uid(), approve = uid(), publish = uid();
|
|
1308
|
+
const steps = [
|
|
1309
|
+
{ id: prepare, name: "Prepare Launch Notes", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a product marketing lead. Create concise launch notes with risks and highlights.", userPromptTemplate: "Launch context:\n\n{{launchBrief}}", outputVar: "launchNotes" }, position: { x: 100, y: 200 } },
|
|
1310
|
+
{ id: approve, name: "Approval Gate", type: "human-approval", config: { type: "human-approval", message: "Review the launch notes before the rollout payload is published." }, position: { x: 400, y: 200 } },
|
|
1311
|
+
{ id: publish, name: "Build Payload", type: "transform", config: { type: "transform", expression: '({ notes: launchNotes, approvedAt: Date.now(), releaseChannel: releaseChannel || "stable" })', inputVars: ["launchNotes", "releaseChannel"], outputVar: "launchPayload" }, position: { x: 700, y: 200 } }
|
|
1312
|
+
];
|
|
1313
|
+
const connections = [
|
|
1314
|
+
{ fromStepId: prepare, toStepId: approve },
|
|
1315
|
+
{ fromStepId: approve, toStepId: publish }
|
|
1316
|
+
];
|
|
1317
|
+
return {
|
|
1318
|
+
id: wfId(),
|
|
1319
|
+
name: "Launch Readiness",
|
|
1320
|
+
description: "Human-approved release preparation workflow.",
|
|
1321
|
+
version: 1,
|
|
1322
|
+
status: "draft",
|
|
1323
|
+
steps,
|
|
1324
|
+
connections,
|
|
1325
|
+
inputSchema: [
|
|
1326
|
+
{ name: "launchBrief", type: "string", required: true },
|
|
1327
|
+
{ name: "releaseChannel", type: "string", required: false, default: "stable" }
|
|
1328
|
+
],
|
|
1329
|
+
tags: ["launch", "release"],
|
|
1330
|
+
createdBy,
|
|
1331
|
+
createdAt: Date.now(),
|
|
1332
|
+
updatedAt: Date.now()
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
var WORKFLOW_TEMPLATES = [
|
|
1337
|
+
contentPipeline,
|
|
1338
|
+
dataEnrichment,
|
|
1339
|
+
approvalChain,
|
|
1340
|
+
researchPipeline,
|
|
1341
|
+
codeReviewPipeline,
|
|
1342
|
+
leadQualification,
|
|
1343
|
+
incidentTriage,
|
|
1344
|
+
launchReadiness
|
|
1345
|
+
];
|
|
1346
|
+
function getTemplate(id) {
|
|
1347
|
+
return WORKFLOW_TEMPLATES.find((t) => t.id === id);
|
|
1348
|
+
}
|
|
1349
|
+
function getTemplatesByCategory(category) {
|
|
1350
|
+
return WORKFLOW_TEMPLATES.filter((t) => t.category === category);
|
|
1351
|
+
}
|
|
1352
|
+
function createApprovalGate() {
|
|
1353
|
+
const [pendingApprovals, setPendingApprovals] = createSignal([]);
|
|
1354
|
+
const [history, setHistory] = createSignal([]);
|
|
1355
|
+
const requestApproval = (stepId, message, approvers) => {
|
|
1356
|
+
const request = {
|
|
1357
|
+
id: `approval_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
1358
|
+
stepId,
|
|
1359
|
+
message,
|
|
1360
|
+
requestedAt: Date.now(),
|
|
1361
|
+
approvers
|
|
1362
|
+
};
|
|
1363
|
+
setPendingApprovals((current) => [...current, request]);
|
|
1364
|
+
return request.id;
|
|
1365
|
+
};
|
|
1366
|
+
const approve = (approvalId) => {
|
|
1367
|
+
const request = pendingApprovals().find((item) => item.id === approvalId);
|
|
1368
|
+
if (request) {
|
|
1369
|
+
setHistory((entries) => [
|
|
1370
|
+
...entries,
|
|
1371
|
+
{ ...request, decision: "approved", decidedAt: Date.now() }
|
|
1372
|
+
]);
|
|
1373
|
+
}
|
|
1374
|
+
setPendingApprovals(
|
|
1375
|
+
(current) => current.filter((item) => item.id !== approvalId)
|
|
1376
|
+
);
|
|
1377
|
+
};
|
|
1378
|
+
const reject = (approvalId) => {
|
|
1379
|
+
const request = pendingApprovals().find((item) => item.id === approvalId);
|
|
1380
|
+
if (request) {
|
|
1381
|
+
setHistory((entries) => [
|
|
1382
|
+
...entries,
|
|
1383
|
+
{ ...request, decision: "rejected", decidedAt: Date.now() }
|
|
1384
|
+
]);
|
|
1385
|
+
}
|
|
1386
|
+
setPendingApprovals(
|
|
1387
|
+
(current) => current.filter((item) => item.id !== approvalId)
|
|
1388
|
+
);
|
|
1389
|
+
};
|
|
1390
|
+
const clearHistory = () => {
|
|
1391
|
+
setHistory([]);
|
|
1392
|
+
};
|
|
1393
|
+
return {
|
|
1394
|
+
pendingApprovals,
|
|
1395
|
+
history,
|
|
1396
|
+
hasPending: () => pendingApprovals().length > 0,
|
|
1397
|
+
requestApproval,
|
|
1398
|
+
approve,
|
|
1399
|
+
reject,
|
|
1400
|
+
clearHistory
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function createWorkflow(options) {
|
|
1404
|
+
const [run, setRun] = createSignal(null);
|
|
1405
|
+
const [isRunning, setIsRunning] = createSignal(false);
|
|
1406
|
+
const [error, setError] = createSignal(null);
|
|
1407
|
+
const [stepResults, setStepResults] = createSignal([]);
|
|
1408
|
+
const engine = new WorkflowEngine({
|
|
1409
|
+
...options,
|
|
1410
|
+
onStepComplete: (result) => {
|
|
1411
|
+
setStepResults((current) => [...current, result]);
|
|
1412
|
+
options.onStepComplete?.(result);
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
const execute = async (definition, input) => {
|
|
1416
|
+
setIsRunning(true);
|
|
1417
|
+
setError(null);
|
|
1418
|
+
setStepResults([]);
|
|
1419
|
+
try {
|
|
1420
|
+
const result = await engine.execute(definition, input);
|
|
1421
|
+
setRun(result);
|
|
1422
|
+
return result;
|
|
1423
|
+
} catch (caughtError) {
|
|
1424
|
+
const normalizedError = normalizeWorkflowError(
|
|
1425
|
+
caughtError,
|
|
1426
|
+
"Workflow execution failed.",
|
|
1427
|
+
{ workflowId: definition.id }
|
|
1428
|
+
);
|
|
1429
|
+
setError(normalizedError);
|
|
1430
|
+
throw normalizedError;
|
|
1431
|
+
} finally {
|
|
1432
|
+
setIsRunning(false);
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
return {
|
|
1436
|
+
execute,
|
|
1437
|
+
cancel: () => engine.cancel(),
|
|
1438
|
+
run,
|
|
1439
|
+
isRunning,
|
|
1440
|
+
error,
|
|
1441
|
+
stepResults,
|
|
1442
|
+
reset: () => {
|
|
1443
|
+
setRun(null);
|
|
1444
|
+
setError(null);
|
|
1445
|
+
setStepResults([]);
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
function createEmptyDefinition() {
|
|
1450
|
+
return {
|
|
1451
|
+
id: crypto.randomUUID?.() ?? String(Date.now()),
|
|
1452
|
+
name: "New Workflow",
|
|
1453
|
+
version: 1,
|
|
1454
|
+
status: "draft",
|
|
1455
|
+
steps: [],
|
|
1456
|
+
connections: [],
|
|
1457
|
+
createdBy: "",
|
|
1458
|
+
createdAt: Date.now(),
|
|
1459
|
+
updatedAt: Date.now()
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
function createWorkflowBuilder(initial) {
|
|
1463
|
+
const [state, setState] = createSignal({
|
|
1464
|
+
definition: { ...createEmptyDefinition(), ...initial },
|
|
1465
|
+
selectedStepId: null,
|
|
1466
|
+
isDirty: false,
|
|
1467
|
+
undoStack: [],
|
|
1468
|
+
redoStack: []
|
|
1469
|
+
});
|
|
1470
|
+
const pushUndo = () => {
|
|
1471
|
+
setState((current) => ({
|
|
1472
|
+
...current,
|
|
1473
|
+
undoStack: [...current.undoStack.slice(-20), current.definition],
|
|
1474
|
+
redoStack: []
|
|
1475
|
+
}));
|
|
1476
|
+
};
|
|
1477
|
+
const addStep = (step) => {
|
|
1478
|
+
pushUndo();
|
|
1479
|
+
setState((current) => ({
|
|
1480
|
+
...current,
|
|
1481
|
+
isDirty: true,
|
|
1482
|
+
definition: {
|
|
1483
|
+
...current.definition,
|
|
1484
|
+
steps: [...current.definition.steps, step],
|
|
1485
|
+
updatedAt: Date.now()
|
|
1486
|
+
}
|
|
1487
|
+
}));
|
|
1488
|
+
};
|
|
1489
|
+
const removeStep = (stepId) => {
|
|
1490
|
+
pushUndo();
|
|
1491
|
+
setState((current) => ({
|
|
1492
|
+
...current,
|
|
1493
|
+
isDirty: true,
|
|
1494
|
+
definition: {
|
|
1495
|
+
...current.definition,
|
|
1496
|
+
steps: current.definition.steps.filter((step) => step.id !== stepId),
|
|
1497
|
+
connections: current.definition.connections.filter(
|
|
1498
|
+
(connection) => connection.fromStepId !== stepId && connection.toStepId !== stepId
|
|
1499
|
+
),
|
|
1500
|
+
updatedAt: Date.now()
|
|
1501
|
+
},
|
|
1502
|
+
selectedStepId: current.selectedStepId === stepId ? null : current.selectedStepId
|
|
1503
|
+
}));
|
|
1504
|
+
};
|
|
1505
|
+
const updateStep = (stepId, updates) => {
|
|
1506
|
+
pushUndo();
|
|
1507
|
+
setState((current) => ({
|
|
1508
|
+
...current,
|
|
1509
|
+
isDirty: true,
|
|
1510
|
+
definition: {
|
|
1511
|
+
...current.definition,
|
|
1512
|
+
steps: current.definition.steps.map(
|
|
1513
|
+
(step) => step.id === stepId ? { ...step, ...updates } : step
|
|
1514
|
+
),
|
|
1515
|
+
updatedAt: Date.now()
|
|
1516
|
+
}
|
|
1517
|
+
}));
|
|
1518
|
+
};
|
|
1519
|
+
const addConnection = (connection) => {
|
|
1520
|
+
pushUndo();
|
|
1521
|
+
setState((current) => ({
|
|
1522
|
+
...current,
|
|
1523
|
+
isDirty: true,
|
|
1524
|
+
definition: {
|
|
1525
|
+
...current.definition,
|
|
1526
|
+
connections: [...current.definition.connections, connection],
|
|
1527
|
+
updatedAt: Date.now()
|
|
1528
|
+
}
|
|
1529
|
+
}));
|
|
1530
|
+
};
|
|
1531
|
+
const removeConnection = (fromStepId, toStepId) => {
|
|
1532
|
+
pushUndo();
|
|
1533
|
+
setState((current) => ({
|
|
1534
|
+
...current,
|
|
1535
|
+
isDirty: true,
|
|
1536
|
+
definition: {
|
|
1537
|
+
...current.definition,
|
|
1538
|
+
connections: current.definition.connections.filter(
|
|
1539
|
+
(connection) => !(connection.fromStepId === fromStepId && connection.toStepId === toStepId)
|
|
1540
|
+
),
|
|
1541
|
+
updatedAt: Date.now()
|
|
1542
|
+
}
|
|
1543
|
+
}));
|
|
1544
|
+
};
|
|
1545
|
+
const selectStep = (stepId) => {
|
|
1546
|
+
setState((current) => ({ ...current, selectedStepId: stepId }));
|
|
1547
|
+
};
|
|
1548
|
+
const undo = () => {
|
|
1549
|
+
setState((current) => {
|
|
1550
|
+
if (current.undoStack.length === 0) {
|
|
1551
|
+
return current;
|
|
1552
|
+
}
|
|
1553
|
+
return {
|
|
1554
|
+
...current,
|
|
1555
|
+
definition: current.undoStack[current.undoStack.length - 1],
|
|
1556
|
+
undoStack: current.undoStack.slice(0, -1),
|
|
1557
|
+
redoStack: [current.definition, ...current.redoStack]
|
|
1558
|
+
};
|
|
1559
|
+
});
|
|
1560
|
+
};
|
|
1561
|
+
const redo = () => {
|
|
1562
|
+
setState((current) => {
|
|
1563
|
+
if (current.redoStack.length === 0) {
|
|
1564
|
+
return current;
|
|
1565
|
+
}
|
|
1566
|
+
return {
|
|
1567
|
+
...current,
|
|
1568
|
+
definition: current.redoStack[0],
|
|
1569
|
+
undoStack: [...current.undoStack, current.definition],
|
|
1570
|
+
redoStack: current.redoStack.slice(1)
|
|
1571
|
+
};
|
|
1572
|
+
});
|
|
1573
|
+
};
|
|
1574
|
+
const setDefinition = (definition) => {
|
|
1575
|
+
setState({
|
|
1576
|
+
definition,
|
|
1577
|
+
selectedStepId: null,
|
|
1578
|
+
isDirty: false,
|
|
1579
|
+
undoStack: [],
|
|
1580
|
+
redoStack: []
|
|
1581
|
+
});
|
|
1582
|
+
};
|
|
1583
|
+
return {
|
|
1584
|
+
state,
|
|
1585
|
+
addStep,
|
|
1586
|
+
removeStep,
|
|
1587
|
+
updateStep,
|
|
1588
|
+
addConnection,
|
|
1589
|
+
removeConnection,
|
|
1590
|
+
selectStep,
|
|
1591
|
+
undo,
|
|
1592
|
+
redo,
|
|
1593
|
+
setDefinition
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
function createWorkflowRun() {
|
|
1597
|
+
const [run, setRun] = createSignal(null);
|
|
1598
|
+
const [status, setStatus] = createSignal("pending");
|
|
1599
|
+
const [currentStepIndex, setCurrentStepIndex] = createSignal(0);
|
|
1600
|
+
const [completedSteps, setCompletedSteps] = createSignal([]);
|
|
1601
|
+
const [progress, setProgress] = createSignal(0);
|
|
1602
|
+
const [error, setError] = createSignal(null);
|
|
1603
|
+
const trackRun = (workflowRun) => {
|
|
1604
|
+
setRun(workflowRun);
|
|
1605
|
+
setStatus(workflowRun.status);
|
|
1606
|
+
setCurrentStepIndex(workflowRun.currentStepIndex);
|
|
1607
|
+
setCompletedSteps(workflowRun.stepResults);
|
|
1608
|
+
setError(workflowRun.error ?? null);
|
|
1609
|
+
const completedCount = workflowRun.stepResults.filter(
|
|
1610
|
+
(step) => step.status === "completed" || step.status === "skipped"
|
|
1611
|
+
).length;
|
|
1612
|
+
setProgress(
|
|
1613
|
+
workflowRun.stepResults.length > 0 ? Math.round(completedCount / workflowRun.stepResults.length * 100) : 0
|
|
1614
|
+
);
|
|
1615
|
+
};
|
|
1616
|
+
const updateStep = (result) => {
|
|
1617
|
+
setCompletedSteps((current) => {
|
|
1618
|
+
const existingIndex = current.findIndex(
|
|
1619
|
+
(step) => step.stepId === result.stepId
|
|
1620
|
+
);
|
|
1621
|
+
if (existingIndex >= 0) {
|
|
1622
|
+
const updatedSteps = [...current];
|
|
1623
|
+
updatedSteps[existingIndex] = result;
|
|
1624
|
+
return updatedSteps;
|
|
1625
|
+
}
|
|
1626
|
+
return [...current, result];
|
|
1627
|
+
});
|
|
1628
|
+
setCurrentStepIndex((current) => current + 1);
|
|
1629
|
+
};
|
|
1630
|
+
const reset = () => {
|
|
1631
|
+
setRun(null);
|
|
1632
|
+
setStatus("pending");
|
|
1633
|
+
setCurrentStepIndex(0);
|
|
1634
|
+
setCompletedSteps([]);
|
|
1635
|
+
setProgress(0);
|
|
1636
|
+
setError(null);
|
|
1637
|
+
};
|
|
1638
|
+
return {
|
|
1639
|
+
run,
|
|
1640
|
+
status,
|
|
1641
|
+
currentStepIndex,
|
|
1642
|
+
completedSteps,
|
|
1643
|
+
progress,
|
|
1644
|
+
error,
|
|
1645
|
+
trackRun,
|
|
1646
|
+
updateStep,
|
|
1647
|
+
reset,
|
|
1648
|
+
isRunning: () => status() === "running",
|
|
1649
|
+
isComplete: () => status() === "completed",
|
|
1650
|
+
isFailed: () => status() === "failed"
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
var STEP_ICONS = {
|
|
1654
|
+
"llm-call": "\u{1F916}",
|
|
1655
|
+
transform: "\u2699\uFE0F",
|
|
1656
|
+
condition: "\u{1F500}",
|
|
1657
|
+
"human-approval": "\u{1F464}",
|
|
1658
|
+
webhook: "\u{1F310}",
|
|
1659
|
+
delay: "\u23F1\uFE0F",
|
|
1660
|
+
parallel: "\u26A1",
|
|
1661
|
+
loop: "\u{1F504}",
|
|
1662
|
+
"sub-workflow": "\u{1F4CB}",
|
|
1663
|
+
custom: "\u{1F527}"
|
|
1664
|
+
};
|
|
1665
|
+
function createWorkflowStep(stepDef) {
|
|
1666
|
+
const [status, setStatus] = createSignal("pending");
|
|
1667
|
+
const [output, setOutput] = createSignal(null);
|
|
1668
|
+
const [error, setError] = createSignal(null);
|
|
1669
|
+
const [durationMs, setDurationMs] = createSignal(0);
|
|
1670
|
+
const icon = createMemo(() => STEP_ICONS[stepDef.type] ?? "\u{1F4E6}");
|
|
1671
|
+
const statusColor = createMemo(() => {
|
|
1672
|
+
const colorMap = {
|
|
1673
|
+
pending: "gray",
|
|
1674
|
+
running: "blue",
|
|
1675
|
+
completed: "green",
|
|
1676
|
+
failed: "red",
|
|
1677
|
+
skipped: "orange",
|
|
1678
|
+
"waiting-approval": "purple"
|
|
1679
|
+
};
|
|
1680
|
+
return colorMap[status()];
|
|
1681
|
+
});
|
|
1682
|
+
const updateFromResult = (result) => {
|
|
1683
|
+
setStatus(result.status);
|
|
1684
|
+
setOutput(result.output ?? null);
|
|
1685
|
+
setError(result.error ?? null);
|
|
1686
|
+
setDurationMs(result.durationMs);
|
|
1687
|
+
};
|
|
1688
|
+
const reset = () => {
|
|
1689
|
+
setStatus("pending");
|
|
1690
|
+
setOutput(null);
|
|
1691
|
+
setError(null);
|
|
1692
|
+
setDurationMs(0);
|
|
1693
|
+
};
|
|
1694
|
+
return {
|
|
1695
|
+
stepDef,
|
|
1696
|
+
status,
|
|
1697
|
+
output,
|
|
1698
|
+
error,
|
|
1699
|
+
durationMs,
|
|
1700
|
+
icon,
|
|
1701
|
+
statusColor,
|
|
1702
|
+
updateFromResult,
|
|
1703
|
+
reset,
|
|
1704
|
+
isComplete: () => status() === "completed",
|
|
1705
|
+
isRunning: () => status() === "running",
|
|
1706
|
+
isFailed: () => status() === "failed",
|
|
1707
|
+
isWaiting: () => status() === "waiting-approval",
|
|
1708
|
+
config: stepDef.config
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
function createWorkflowTemplates(userId) {
|
|
1712
|
+
const [selectedTemplateId, setSelectedTemplateId] = createSignal(null);
|
|
1713
|
+
const [filterCategory, setFilterCategory] = createSignal("all");
|
|
1714
|
+
const [searchQuery, setSearchQuery] = createSignal("");
|
|
1715
|
+
const templates = createMemo(() => {
|
|
1716
|
+
const category = filterCategory();
|
|
1717
|
+
let currentTemplates = category === "all" ? WORKFLOW_TEMPLATES : getTemplatesByCategory(category);
|
|
1718
|
+
const normalizedQuery = searchQuery().trim().toLowerCase();
|
|
1719
|
+
if (normalizedQuery) {
|
|
1720
|
+
currentTemplates = currentTemplates.filter(
|
|
1721
|
+
(template) => template.name.toLowerCase().includes(normalizedQuery) || template.description.toLowerCase().includes(normalizedQuery) || template.tags.some((tag) => tag.includes(normalizedQuery))
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
return currentTemplates;
|
|
1725
|
+
});
|
|
1726
|
+
const selectedTemplate = createMemo(() => {
|
|
1727
|
+
const templateId = selectedTemplateId();
|
|
1728
|
+
return templateId ? getTemplate(templateId) ?? null : null;
|
|
1729
|
+
});
|
|
1730
|
+
const categories = createMemo(() => {
|
|
1731
|
+
return [...new Set(WORKFLOW_TEMPLATES.map((template) => template.category))];
|
|
1732
|
+
});
|
|
1733
|
+
const createFromTemplate = (templateId) => {
|
|
1734
|
+
const template = getTemplate(templateId);
|
|
1735
|
+
return template ? template.create(userId) : null;
|
|
1736
|
+
};
|
|
1737
|
+
return {
|
|
1738
|
+
templates,
|
|
1739
|
+
allTemplates: WORKFLOW_TEMPLATES,
|
|
1740
|
+
selectedTemplate,
|
|
1741
|
+
selectedTemplateId,
|
|
1742
|
+
categories,
|
|
1743
|
+
filterCategory,
|
|
1744
|
+
searchQuery,
|
|
1745
|
+
setSelectedTemplateId,
|
|
1746
|
+
setFilterCategory,
|
|
1747
|
+
setSearchQuery,
|
|
1748
|
+
createFromTemplate
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
var NODE_WIDTH = 200;
|
|
1752
|
+
var NODE_HEIGHT = 80;
|
|
1753
|
+
function StepConnector(props) {
|
|
1754
|
+
const startX = () => props.from.x + NODE_WIDTH;
|
|
1755
|
+
const startY = () => props.from.y + NODE_HEIGHT / 2;
|
|
1756
|
+
const endX = () => props.to.x;
|
|
1757
|
+
const endY = () => props.to.y + NODE_HEIGHT / 2;
|
|
1758
|
+
const midX = () => (startX() + endX()) / 2;
|
|
1759
|
+
return /* @__PURE__ */ jsxs("g", { class: "ai-workflow__connector", children: [
|
|
1760
|
+
/* @__PURE__ */ jsx(
|
|
1761
|
+
"path",
|
|
1762
|
+
{
|
|
1763
|
+
d: `M ${startX()} ${startY()} C ${midX()} ${startY()}, ${midX()} ${endY()}, ${endX()} ${endY()}`,
|
|
1764
|
+
fill: "none",
|
|
1765
|
+
stroke: props.isActive ? "var(--primary)" : "var(--border)",
|
|
1766
|
+
"stroke-width": props.isActive ? 3 : 2
|
|
1767
|
+
}
|
|
1768
|
+
),
|
|
1769
|
+
props.label && /* @__PURE__ */ jsx(
|
|
1770
|
+
"text",
|
|
1771
|
+
{
|
|
1772
|
+
x: midX(),
|
|
1773
|
+
y: (startY() + endY()) / 2 - 8,
|
|
1774
|
+
"text-anchor": "middle",
|
|
1775
|
+
fill: "var(--muted-foreground)",
|
|
1776
|
+
"font-size": "12",
|
|
1777
|
+
children: props.label
|
|
1778
|
+
}
|
|
1779
|
+
)
|
|
1780
|
+
] });
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// src/utils/cx.ts
|
|
1784
|
+
function cx(...classes) {
|
|
1785
|
+
return classes.filter(Boolean).join(" ");
|
|
1786
|
+
}
|
|
1787
|
+
var ICONS = {
|
|
1788
|
+
"llm-call": "\u{1F916}",
|
|
1789
|
+
transform: "\u2699\uFE0F",
|
|
1790
|
+
condition: "\u{1F500}",
|
|
1791
|
+
"human-approval": "\u{1F464}",
|
|
1792
|
+
webhook: "\u{1F310}",
|
|
1793
|
+
delay: "\u23F1\uFE0F",
|
|
1794
|
+
parallel: "\u26A1",
|
|
1795
|
+
loop: "\u{1F504}",
|
|
1796
|
+
"sub-workflow": "\u{1F4CB}",
|
|
1797
|
+
custom: "\u{1F527}"
|
|
1798
|
+
};
|
|
1799
|
+
function StepNode(props) {
|
|
1800
|
+
const cls = () => cx(
|
|
1801
|
+
"ai-workflow__step-node",
|
|
1802
|
+
props.isSelected && "ai-workflow__step-node--selected",
|
|
1803
|
+
props.status && `ai-workflow__step-node--${props.status}`
|
|
1804
|
+
);
|
|
1805
|
+
return /* @__PURE__ */ jsxs(
|
|
1806
|
+
"div",
|
|
1807
|
+
{
|
|
1808
|
+
class: cx(cls(), "ai-workflow__focus-visible", props.class),
|
|
1809
|
+
role: "button",
|
|
1810
|
+
tabIndex: 0,
|
|
1811
|
+
"aria-label": `Workflow step ${props.step.name}`,
|
|
1812
|
+
"aria-pressed": props.isSelected,
|
|
1813
|
+
onClick: props.onClick,
|
|
1814
|
+
onKeyDown: (event) => {
|
|
1815
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1816
|
+
event.preventDefault();
|
|
1817
|
+
props.onClick?.();
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1820
|
+
style: props.step.position ? {
|
|
1821
|
+
position: "absolute",
|
|
1822
|
+
left: `${props.step.position.x}px`,
|
|
1823
|
+
top: `${props.step.position.y}px`
|
|
1824
|
+
} : void 0,
|
|
1825
|
+
children: [
|
|
1826
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__step-header", children: [
|
|
1827
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__step-icon", children: ICONS[props.step.type] }),
|
|
1828
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__step-name", children: props.step.name })
|
|
1829
|
+
] }),
|
|
1830
|
+
/* @__PURE__ */ jsx("div", { class: "ai-workflow__step-type-badge", children: props.step.type }),
|
|
1831
|
+
props.step.optional && /* @__PURE__ */ jsx("span", { class: "ai-workflow__step-optional-badge", children: "Optional" }),
|
|
1832
|
+
props.step.retries && /* @__PURE__ */ jsxs("span", { class: "ai-workflow__step-retry-badge", children: [
|
|
1833
|
+
"\u21BB ",
|
|
1834
|
+
props.step.retries.maxAttempts
|
|
1835
|
+
] })
|
|
1836
|
+
]
|
|
1837
|
+
}
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
function WorkflowCanvas(props) {
|
|
1841
|
+
return /* @__PURE__ */ jsxs("div", { class: "ai-workflow__canvas", role: "application", "aria-label": "Workflow canvas", children: [
|
|
1842
|
+
/* @__PURE__ */ jsx("svg", { style: { position: "absolute", inset: "0", "pointer-events": "none" }, children: /* @__PURE__ */ jsx(For, { each: props.definition.connections, children: (connection) => {
|
|
1843
|
+
const fromStep = () => props.definition.steps.find(
|
|
1844
|
+
(step) => step.id === connection.fromStepId
|
|
1845
|
+
);
|
|
1846
|
+
const toStep = () => props.definition.steps.find(
|
|
1847
|
+
(step) => step.id === connection.toStepId
|
|
1848
|
+
);
|
|
1849
|
+
return /* @__PURE__ */ jsx(Show, { when: fromStep()?.position && toStep()?.position, children: /* @__PURE__ */ jsx(
|
|
1850
|
+
StepConnector,
|
|
1851
|
+
{
|
|
1852
|
+
from: fromStep().position,
|
|
1853
|
+
to: toStep().position,
|
|
1854
|
+
label: connection.label
|
|
1855
|
+
}
|
|
1856
|
+
) });
|
|
1857
|
+
} }) }),
|
|
1858
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(For, { each: props.definition.steps, children: (step) => /* @__PURE__ */ jsx(
|
|
1859
|
+
StepNode,
|
|
1860
|
+
{
|
|
1861
|
+
step,
|
|
1862
|
+
isSelected: props.selectedStepId === step.id,
|
|
1863
|
+
onClick: () => props.onSelectStep(
|
|
1864
|
+
step.id === props.selectedStepId ? null : step.id
|
|
1865
|
+
)
|
|
1866
|
+
}
|
|
1867
|
+
) }) }),
|
|
1868
|
+
/* @__PURE__ */ jsx(Show, { when: props.definition.steps.length === 0, children: /* @__PURE__ */ jsxs("div", { class: "ai-workflow__canvas-empty", children: [
|
|
1869
|
+
/* @__PURE__ */ jsx("p", { children: "No steps yet" }),
|
|
1870
|
+
/* @__PURE__ */ jsx("p", { children: "Drag steps from the palette or use a template" })
|
|
1871
|
+
] }) })
|
|
1872
|
+
] });
|
|
1873
|
+
}
|
|
1874
|
+
function StepConfigPanel(props) {
|
|
1875
|
+
const [name, setName] = createSignal(props.step.name);
|
|
1876
|
+
const inputId = `workflow-step-name-${props.step.id}`;
|
|
1877
|
+
return /* @__PURE__ */ jsxs(
|
|
1878
|
+
"div",
|
|
1879
|
+
{
|
|
1880
|
+
class: "ai-workflow__config-panel",
|
|
1881
|
+
role: "complementary",
|
|
1882
|
+
"aria-label": "Step configuration",
|
|
1883
|
+
children: [
|
|
1884
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__config-header", children: [
|
|
1885
|
+
/* @__PURE__ */ jsx("h3", { class: "ai-workflow__config-title", children: "Configure Step" }),
|
|
1886
|
+
/* @__PURE__ */ jsx(
|
|
1887
|
+
"button",
|
|
1888
|
+
{
|
|
1889
|
+
type: "button",
|
|
1890
|
+
class: cx("ai-workflow__config-close", "ai-workflow__focus-visible"),
|
|
1891
|
+
onClick: props.onClose,
|
|
1892
|
+
"aria-label": "Close step configuration",
|
|
1893
|
+
children: "\u2715"
|
|
1894
|
+
}
|
|
1895
|
+
)
|
|
1896
|
+
] }),
|
|
1897
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__config-body", children: [
|
|
1898
|
+
/* @__PURE__ */ jsx("label", { class: "ai-workflow__config-label", for: inputId, children: "Step Name" }),
|
|
1899
|
+
/* @__PURE__ */ jsx(
|
|
1900
|
+
"input",
|
|
1901
|
+
{
|
|
1902
|
+
id: inputId,
|
|
1903
|
+
class: "ai-workflow__config-input",
|
|
1904
|
+
value: name(),
|
|
1905
|
+
onInput: (event) => setName(event.currentTarget.value),
|
|
1906
|
+
onBlur: () => {
|
|
1907
|
+
if (name().trim() && name() !== props.step.name)
|
|
1908
|
+
props.onUpdate(props.step.id, { name: name().trim() });
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
),
|
|
1912
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__config-field", children: [
|
|
1913
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__config-field-label", children: "Type" }),
|
|
1914
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__config-field-value", children: props.step.type })
|
|
1915
|
+
] }),
|
|
1916
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__config-field", children: [
|
|
1917
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__config-field-label", children: "ID" }),
|
|
1918
|
+
/* @__PURE__ */ jsx("code", { class: "ai-workflow__config-field-value", children: props.step.id })
|
|
1919
|
+
] }),
|
|
1920
|
+
props.step.optional !== void 0 && /* @__PURE__ */ jsxs("label", { class: "ai-workflow__config-checkbox", children: [
|
|
1921
|
+
/* @__PURE__ */ jsx(
|
|
1922
|
+
"input",
|
|
1923
|
+
{
|
|
1924
|
+
type: "checkbox",
|
|
1925
|
+
checked: props.step.optional,
|
|
1926
|
+
onInput: (event) => props.onUpdate(props.step.id, {
|
|
1927
|
+
optional: event.currentTarget.checked
|
|
1928
|
+
})
|
|
1929
|
+
}
|
|
1930
|
+
),
|
|
1931
|
+
"Optional (skip on failure)"
|
|
1932
|
+
] }),
|
|
1933
|
+
/* @__PURE__ */ jsx("pre", { class: "ai-workflow__config-json-view", children: JSON.stringify(props.step.config, null, 2) })
|
|
1934
|
+
] }),
|
|
1935
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
1936
|
+
"button",
|
|
1937
|
+
{
|
|
1938
|
+
type: "button",
|
|
1939
|
+
class: cx("ai-workflow__config-delete", "ai-workflow__focus-visible"),
|
|
1940
|
+
onClick: () => {
|
|
1941
|
+
props.onDelete(props.step.id);
|
|
1942
|
+
props.onClose();
|
|
1943
|
+
},
|
|
1944
|
+
children: "\u{1F5D1} Delete Step"
|
|
1945
|
+
}
|
|
1946
|
+
) })
|
|
1947
|
+
]
|
|
1948
|
+
}
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1951
|
+
var SI = {
|
|
1952
|
+
pending: "\u23F3",
|
|
1953
|
+
running: "\u{1F504}",
|
|
1954
|
+
completed: "\u2705",
|
|
1955
|
+
failed: "\u274C",
|
|
1956
|
+
skipped: "\u23ED\uFE0F",
|
|
1957
|
+
"waiting-approval": "\u{1F464}"
|
|
1958
|
+
};
|
|
1959
|
+
function WorkflowRunPanel(props) {
|
|
1960
|
+
return /* @__PURE__ */ jsxs("div", { class: "ai-workflow__run-panel", children: [
|
|
1961
|
+
/* @__PURE__ */ jsx(Show, { when: !props.run, children: /* @__PURE__ */ jsx("div", { class: "ai-workflow__run-empty", children: /* @__PURE__ */ jsx("p", { children: "No active run." }) }) }),
|
|
1962
|
+
/* @__PURE__ */ jsx(Show, { when: props.run, children: (run) => {
|
|
1963
|
+
const done = () => run().stepResults.filter((step) => step.status === "completed").length;
|
|
1964
|
+
const total = () => run().stepResults.length;
|
|
1965
|
+
const progress = () => total() > 0 ? Math.round(done() / total() * 100) : 0;
|
|
1966
|
+
const totalCost = () => run().stepResults.reduce(
|
|
1967
|
+
(sum, step) => sum + (step.costUsd ?? 0),
|
|
1968
|
+
0
|
|
1969
|
+
);
|
|
1970
|
+
const totalTokens = () => run().stepResults.reduce(
|
|
1971
|
+
(sum, step) => sum + (step.tokens ?? 0),
|
|
1972
|
+
0
|
|
1973
|
+
);
|
|
1974
|
+
const duration = () => (run().completedAt ?? Date.now()) - run().startedAt;
|
|
1975
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1976
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__run-header", children: [
|
|
1977
|
+
/* @__PURE__ */ jsxs("h3", { class: "ai-workflow__run-title", children: [
|
|
1978
|
+
SI[run().status],
|
|
1979
|
+
" Run ",
|
|
1980
|
+
run().status
|
|
1981
|
+
] }),
|
|
1982
|
+
/* @__PURE__ */ jsx(Show, { when: props.isRunning && props.onCancel, children: /* @__PURE__ */ jsx(
|
|
1983
|
+
"button",
|
|
1984
|
+
{
|
|
1985
|
+
type: "button",
|
|
1986
|
+
class: cx(
|
|
1987
|
+
"ai-workflow__run-cancel",
|
|
1988
|
+
"ai-workflow__focus-visible"
|
|
1989
|
+
),
|
|
1990
|
+
onClick: props.onCancel,
|
|
1991
|
+
children: "\u23F9 Cancel"
|
|
1992
|
+
}
|
|
1993
|
+
) })
|
|
1994
|
+
] }),
|
|
1995
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__run-progress", children: [
|
|
1996
|
+
/* @__PURE__ */ jsx("div", { class: "ai-workflow__run-progress-bar", children: /* @__PURE__ */ jsx(
|
|
1997
|
+
"div",
|
|
1998
|
+
{
|
|
1999
|
+
class: "ai-workflow__run-progress-fill",
|
|
2000
|
+
style: { width: `${progress()}%` }
|
|
2001
|
+
}
|
|
2002
|
+
) }),
|
|
2003
|
+
/* @__PURE__ */ jsxs("span", { class: "ai-workflow__run-progress-text", children: [
|
|
2004
|
+
done(),
|
|
2005
|
+
"/",
|
|
2006
|
+
total(),
|
|
2007
|
+
" (",
|
|
2008
|
+
progress(),
|
|
2009
|
+
"%)"
|
|
2010
|
+
] })
|
|
2011
|
+
] }),
|
|
2012
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__run-stats", children: [
|
|
2013
|
+
/* @__PURE__ */ jsxs("span", { class: "ai-workflow__run-stat", children: [
|
|
2014
|
+
"\u23F1 ",
|
|
2015
|
+
(duration() / 1e3).toFixed(1),
|
|
2016
|
+
"s"
|
|
2017
|
+
] }),
|
|
2018
|
+
/* @__PURE__ */ jsx(Show, { when: totalTokens() > 0, children: /* @__PURE__ */ jsxs("span", { class: "ai-workflow__run-stat", children: [
|
|
2019
|
+
"\u{1FA99} ",
|
|
2020
|
+
totalTokens().toLocaleString(),
|
|
2021
|
+
" tokens"
|
|
2022
|
+
] }) }),
|
|
2023
|
+
/* @__PURE__ */ jsx(Show, { when: totalCost() > 0, children: /* @__PURE__ */ jsxs("span", { class: "ai-workflow__run-stat", children: [
|
|
2024
|
+
"\u{1F4B0} ",
|
|
2025
|
+
formatCost(totalCost())
|
|
2026
|
+
] }) })
|
|
2027
|
+
] }),
|
|
2028
|
+
/* @__PURE__ */ jsx(Show, { when: run().error, children: /* @__PURE__ */ jsx("div", { class: "ai-workflow__run-error", role: "alert", children: run().error }) }),
|
|
2029
|
+
/* @__PURE__ */ jsx("ul", { class: "ai-workflow__run-steps", children: /* @__PURE__ */ jsx(For, { each: run().stepResults, children: (stepResult) => /* @__PURE__ */ jsxs(
|
|
2030
|
+
"li",
|
|
2031
|
+
{
|
|
2032
|
+
class: "ai-workflow__run-step-item",
|
|
2033
|
+
"data-status": stepResult.status,
|
|
2034
|
+
children: [
|
|
2035
|
+
/* @__PURE__ */ jsx("span", { children: SI[stepResult.status] }),
|
|
2036
|
+
/* @__PURE__ */ jsx("span", { children: stepResult.stepName }),
|
|
2037
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2038
|
+
stepResult.durationMs,
|
|
2039
|
+
"ms"
|
|
2040
|
+
] })
|
|
2041
|
+
]
|
|
2042
|
+
}
|
|
2043
|
+
) }) })
|
|
2044
|
+
] });
|
|
2045
|
+
} })
|
|
2046
|
+
] });
|
|
2047
|
+
}
|
|
2048
|
+
function ApprovalModal(props) {
|
|
2049
|
+
let dialogRef;
|
|
2050
|
+
createEffect(() => {
|
|
2051
|
+
props.request;
|
|
2052
|
+
{
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
return /* @__PURE__ */ jsx(Show, { when: props.request, children: (request) => {
|
|
2057
|
+
const titleId = `workflow-approval-title-${request().id}`;
|
|
2058
|
+
const messageId = `workflow-approval-message-${request().id}`;
|
|
2059
|
+
return /* @__PURE__ */ jsxs("div", { "data-workflow": "approval-modal", children: [
|
|
2060
|
+
/* @__PURE__ */ jsx("div", { class: "ai-workflow__approval-overlay", "aria-hidden": "true" }),
|
|
2061
|
+
/* @__PURE__ */ jsxs(
|
|
2062
|
+
"div",
|
|
2063
|
+
{
|
|
2064
|
+
ref: dialogRef,
|
|
2065
|
+
class: "ai-workflow__approval-dialog",
|
|
2066
|
+
role: "dialog",
|
|
2067
|
+
"aria-modal": "true",
|
|
2068
|
+
"aria-labelledby": titleId,
|
|
2069
|
+
"aria-describedby": messageId,
|
|
2070
|
+
tabIndex: -1,
|
|
2071
|
+
children: [
|
|
2072
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__approval-header", children: [
|
|
2073
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__approval-icon", children: "\u{1F464}" }),
|
|
2074
|
+
/* @__PURE__ */ jsx("h3", { id: titleId, class: "ai-workflow__approval-title", children: "Approval Required" })
|
|
2075
|
+
] }),
|
|
2076
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2077
|
+
/* @__PURE__ */ jsx("p", { id: messageId, class: "ai-workflow__approval-message", children: request().message }),
|
|
2078
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__approval-meta", children: [
|
|
2079
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2080
|
+
"Step: ",
|
|
2081
|
+
request().stepId
|
|
2082
|
+
] }),
|
|
2083
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2084
|
+
"Requested:",
|
|
2085
|
+
" ",
|
|
2086
|
+
new Date(request().requestedAt).toLocaleTimeString()
|
|
2087
|
+
] })
|
|
2088
|
+
] }),
|
|
2089
|
+
/* @__PURE__ */ jsx(
|
|
2090
|
+
Show,
|
|
2091
|
+
{
|
|
2092
|
+
when: request().approvers && request().approvers.length > 0,
|
|
2093
|
+
children: /* @__PURE__ */ jsxs("div", { children: [
|
|
2094
|
+
/* @__PURE__ */ jsx("span", { children: "Authorized approvers: " }),
|
|
2095
|
+
/* @__PURE__ */ jsx(For, { each: request().approvers, children: (approver) => /* @__PURE__ */ jsx("span", { class: "ai-workflow__approval-approver-badge", children: approver }) })
|
|
2096
|
+
] })
|
|
2097
|
+
}
|
|
2098
|
+
)
|
|
2099
|
+
] }),
|
|
2100
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__approval-actions", children: [
|
|
2101
|
+
/* @__PURE__ */ jsx(
|
|
2102
|
+
"button",
|
|
2103
|
+
{
|
|
2104
|
+
type: "button",
|
|
2105
|
+
class: cx(
|
|
2106
|
+
"ai-workflow__approval-reject",
|
|
2107
|
+
"ai-workflow__focus-visible"
|
|
2108
|
+
),
|
|
2109
|
+
onClick: () => props.onReject(request().id),
|
|
2110
|
+
children: "\u2715 Reject"
|
|
2111
|
+
}
|
|
2112
|
+
),
|
|
2113
|
+
/* @__PURE__ */ jsx(
|
|
2114
|
+
"button",
|
|
2115
|
+
{
|
|
2116
|
+
type: "button",
|
|
2117
|
+
class: cx(
|
|
2118
|
+
"ai-workflow__approval-approve",
|
|
2119
|
+
"ai-workflow__focus-visible"
|
|
2120
|
+
),
|
|
2121
|
+
onClick: () => props.onApprove(request().id),
|
|
2122
|
+
children: "\u2713 Approve"
|
|
2123
|
+
}
|
|
2124
|
+
)
|
|
2125
|
+
] })
|
|
2126
|
+
]
|
|
2127
|
+
}
|
|
2128
|
+
)
|
|
2129
|
+
] });
|
|
2130
|
+
} });
|
|
2131
|
+
}
|
|
2132
|
+
function WorkflowToolbar(props) {
|
|
2133
|
+
return /* @__PURE__ */ jsxs(
|
|
2134
|
+
"div",
|
|
2135
|
+
{
|
|
2136
|
+
class: cx("ai-workflow__toolbar", props.class),
|
|
2137
|
+
role: "toolbar",
|
|
2138
|
+
"aria-label": "Workflow actions",
|
|
2139
|
+
children: [
|
|
2140
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__toolbar-left", children: [
|
|
2141
|
+
/* @__PURE__ */ jsx("h2", { class: "ai-workflow__toolbar-title", children: props.workflowName }),
|
|
2142
|
+
/* @__PURE__ */ jsx(Show, { when: props.isDirty, children: /* @__PURE__ */ jsx("span", { class: "ai-workflow__toolbar-dirty", children: "\u25CF" }) })
|
|
2143
|
+
] }),
|
|
2144
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__toolbar-center", children: [
|
|
2145
|
+
/* @__PURE__ */ jsx(
|
|
2146
|
+
"button",
|
|
2147
|
+
{
|
|
2148
|
+
type: "button",
|
|
2149
|
+
class: cx("ai-workflow__toolbar-btn", "ai-workflow__focus-visible"),
|
|
2150
|
+
onClick: props.onUndo,
|
|
2151
|
+
disabled: !props.canUndo,
|
|
2152
|
+
"aria-label": "Undo",
|
|
2153
|
+
children: "\u21B6"
|
|
2154
|
+
}
|
|
2155
|
+
),
|
|
2156
|
+
/* @__PURE__ */ jsx(
|
|
2157
|
+
"button",
|
|
2158
|
+
{
|
|
2159
|
+
type: "button",
|
|
2160
|
+
class: cx("ai-workflow__toolbar-btn", "ai-workflow__focus-visible"),
|
|
2161
|
+
onClick: props.onRedo,
|
|
2162
|
+
disabled: !props.canRedo,
|
|
2163
|
+
"aria-label": "Redo",
|
|
2164
|
+
children: "\u21B7"
|
|
2165
|
+
}
|
|
2166
|
+
)
|
|
2167
|
+
] }),
|
|
2168
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__toolbar-right", children: [
|
|
2169
|
+
/* @__PURE__ */ jsx(Show, { when: props.onExport, children: /* @__PURE__ */ jsx(
|
|
2170
|
+
"button",
|
|
2171
|
+
{
|
|
2172
|
+
type: "button",
|
|
2173
|
+
class: cx(
|
|
2174
|
+
"ai-workflow__toolbar-btn-secondary",
|
|
2175
|
+
"ai-workflow__focus-visible"
|
|
2176
|
+
),
|
|
2177
|
+
onClick: props.onExport,
|
|
2178
|
+
"aria-label": "Export workflow",
|
|
2179
|
+
children: "\u{1F4E4} Export"
|
|
2180
|
+
}
|
|
2181
|
+
) }),
|
|
2182
|
+
/* @__PURE__ */ jsx(
|
|
2183
|
+
"button",
|
|
2184
|
+
{
|
|
2185
|
+
type: "button",
|
|
2186
|
+
class: cx(
|
|
2187
|
+
"ai-workflow__toolbar-btn-secondary",
|
|
2188
|
+
"ai-workflow__focus-visible"
|
|
2189
|
+
),
|
|
2190
|
+
onClick: props.onSave,
|
|
2191
|
+
disabled: !props.isDirty,
|
|
2192
|
+
"aria-label": "Save workflow",
|
|
2193
|
+
children: "\u{1F4BE} Save"
|
|
2194
|
+
}
|
|
2195
|
+
),
|
|
2196
|
+
/* @__PURE__ */ jsx(
|
|
2197
|
+
Show,
|
|
2198
|
+
{
|
|
2199
|
+
when: props.isRunning,
|
|
2200
|
+
fallback: /* @__PURE__ */ jsx(
|
|
2201
|
+
"button",
|
|
2202
|
+
{
|
|
2203
|
+
type: "button",
|
|
2204
|
+
class: cx(
|
|
2205
|
+
"ai-workflow__toolbar-btn-primary",
|
|
2206
|
+
"ai-workflow__focus-visible"
|
|
2207
|
+
),
|
|
2208
|
+
onClick: props.onRun,
|
|
2209
|
+
"aria-label": "Run workflow",
|
|
2210
|
+
children: "\u25B6 Run"
|
|
2211
|
+
}
|
|
2212
|
+
),
|
|
2213
|
+
children: /* @__PURE__ */ jsx(
|
|
2214
|
+
"button",
|
|
2215
|
+
{
|
|
2216
|
+
type: "button",
|
|
2217
|
+
class: cx(
|
|
2218
|
+
"ai-workflow__toolbar-btn-danger",
|
|
2219
|
+
"ai-workflow__focus-visible"
|
|
2220
|
+
),
|
|
2221
|
+
onClick: props.onCancel,
|
|
2222
|
+
"aria-label": "Stop workflow run",
|
|
2223
|
+
children: "\u23F9 Stop"
|
|
2224
|
+
}
|
|
2225
|
+
)
|
|
2226
|
+
}
|
|
2227
|
+
)
|
|
2228
|
+
] })
|
|
2229
|
+
]
|
|
2230
|
+
}
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
var ITEMS = [
|
|
2234
|
+
{ type: "llm-call", label: "LLM Call", icon: "\u{1F916}", desc: "Call an AI model" },
|
|
2235
|
+
{ type: "transform", label: "Transform", icon: "\u2699\uFE0F", desc: "Transform data" },
|
|
2236
|
+
{
|
|
2237
|
+
type: "condition",
|
|
2238
|
+
label: "Condition",
|
|
2239
|
+
icon: "\u{1F500}",
|
|
2240
|
+
desc: "If/else branching"
|
|
2241
|
+
},
|
|
2242
|
+
{
|
|
2243
|
+
type: "human-approval",
|
|
2244
|
+
label: "Approval",
|
|
2245
|
+
icon: "\u{1F464}",
|
|
2246
|
+
desc: "Human approval gate"
|
|
2247
|
+
},
|
|
2248
|
+
{ type: "webhook", label: "Webhook", icon: "\u{1F310}", desc: "HTTP request" },
|
|
2249
|
+
{ type: "delay", label: "Delay", icon: "\u23F1\uFE0F", desc: "Wait" },
|
|
2250
|
+
{ type: "parallel", label: "Parallel", icon: "\u26A1", desc: "Run in parallel" },
|
|
2251
|
+
{ type: "loop", label: "Loop", icon: "\u{1F504}", desc: "Iterate" },
|
|
2252
|
+
{
|
|
2253
|
+
type: "sub-workflow",
|
|
2254
|
+
label: "Sub-workflow",
|
|
2255
|
+
icon: "\u{1F4CB}",
|
|
2256
|
+
desc: "Run another workflow"
|
|
2257
|
+
},
|
|
2258
|
+
{ type: "custom", label: "Custom", icon: "\u{1F527}", desc: "Custom handler" }
|
|
2259
|
+
];
|
|
2260
|
+
function StepPalette(props) {
|
|
2261
|
+
return /* @__PURE__ */ jsxs(
|
|
2262
|
+
"div",
|
|
2263
|
+
{
|
|
2264
|
+
class: "ai-workflow__step-palette",
|
|
2265
|
+
role: "list",
|
|
2266
|
+
"aria-label": "Available workflow step types",
|
|
2267
|
+
children: [
|
|
2268
|
+
/* @__PURE__ */ jsx("h3", { class: "ai-workflow__palette-title", children: "Steps" }),
|
|
2269
|
+
/* @__PURE__ */ jsx(For, { each: ITEMS, children: (item) => /* @__PURE__ */ jsxs(
|
|
2270
|
+
"button",
|
|
2271
|
+
{
|
|
2272
|
+
type: "button",
|
|
2273
|
+
class: cx(
|
|
2274
|
+
"ai-workflow__palette-item",
|
|
2275
|
+
"ai-workflow__focus-visible"
|
|
2276
|
+
),
|
|
2277
|
+
role: "listitem",
|
|
2278
|
+
"aria-label": `Add ${item.label} step`,
|
|
2279
|
+
onClick: () => props.onAddStep(item.type),
|
|
2280
|
+
children: [
|
|
2281
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__palette-icon", children: item.icon }),
|
|
2282
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__palette-info", children: [
|
|
2283
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__palette-label", children: item.label }),
|
|
2284
|
+
/* @__PURE__ */ jsx("span", { class: "ai-workflow__palette-desc", children: item.desc })
|
|
2285
|
+
] })
|
|
2286
|
+
]
|
|
2287
|
+
}
|
|
2288
|
+
) })
|
|
2289
|
+
]
|
|
2290
|
+
}
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
function createStepConfig(type) {
|
|
2294
|
+
switch (type) {
|
|
2295
|
+
case "llm-call":
|
|
2296
|
+
return {
|
|
2297
|
+
type,
|
|
2298
|
+
systemPrompt: "",
|
|
2299
|
+
userPromptTemplate: "",
|
|
2300
|
+
outputVar: "result"
|
|
2301
|
+
};
|
|
2302
|
+
case "transform":
|
|
2303
|
+
return { type, expression: "", inputVars: [], outputVar: "result" };
|
|
2304
|
+
case "condition":
|
|
2305
|
+
return { type, expression: "", trueStepId: "", falseStepId: "" };
|
|
2306
|
+
case "human-approval":
|
|
2307
|
+
return { type, message: "" };
|
|
2308
|
+
case "webhook":
|
|
2309
|
+
return { type, url: "", method: "POST", outputVar: "result" };
|
|
2310
|
+
case "delay":
|
|
2311
|
+
return { type, durationMs: 0 };
|
|
2312
|
+
case "parallel":
|
|
2313
|
+
return { type, stepIds: [], failurePolicy: "continue" };
|
|
2314
|
+
case "loop":
|
|
2315
|
+
return {
|
|
2316
|
+
type,
|
|
2317
|
+
stepIds: [],
|
|
2318
|
+
iterateVar: "",
|
|
2319
|
+
itemVar: "",
|
|
2320
|
+
maxIterations: 1
|
|
2321
|
+
};
|
|
2322
|
+
case "sub-workflow":
|
|
2323
|
+
return { type, workflowId: "", inputMapping: {}, outputVar: "result" };
|
|
2324
|
+
case "custom":
|
|
2325
|
+
return { type, handler: "custom", params: {}, outputVar: "result" };
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
function WorkflowBuilderPage(props) {
|
|
2329
|
+
const builder = createWorkflowBuilder();
|
|
2330
|
+
const runTracker = createWorkflowRun();
|
|
2331
|
+
const workflowCount = () => props.workflows?.length ?? 0;
|
|
2332
|
+
const runCount = () => props.workflowRuns?.length ?? 0;
|
|
2333
|
+
const handleAddStep = (type) => {
|
|
2334
|
+
const step = {
|
|
2335
|
+
id: `step_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
2336
|
+
name: `New ${type} step`,
|
|
2337
|
+
type,
|
|
2338
|
+
config: createStepConfig(type),
|
|
2339
|
+
position: {
|
|
2340
|
+
x: 100 + builder.state().definition.steps.length * 250,
|
|
2341
|
+
y: 200
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
builder.addStep(step);
|
|
2345
|
+
};
|
|
2346
|
+
const handleRun = async () => {
|
|
2347
|
+
const engine = new WorkflowEngine({
|
|
2348
|
+
...props.engineOptions,
|
|
2349
|
+
onStepComplete: (result2) => {
|
|
2350
|
+
runTracker.updateStep(result2);
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2353
|
+
const result = await engine.execute(builder.state().definition);
|
|
2354
|
+
runTracker.trackRun(result);
|
|
2355
|
+
};
|
|
2356
|
+
const selectedStep = () => builder.state().selectedStepId ? builder.state().definition.steps.find(
|
|
2357
|
+
(step) => step.id === builder.state().selectedStepId
|
|
2358
|
+
) ?? null : null;
|
|
2359
|
+
return /* @__PURE__ */ jsxs(
|
|
2360
|
+
"div",
|
|
2361
|
+
{
|
|
2362
|
+
class: cx("ai-workflow__builder-page", props.class),
|
|
2363
|
+
"data-workflow": "builder-page",
|
|
2364
|
+
children: [
|
|
2365
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__builder-summary", "data-workflow": "builder-summary", children: [
|
|
2366
|
+
/* @__PURE__ */ jsxs("span", { "data-workflow": "builder-workflow-count", children: [
|
|
2367
|
+
workflowCount(),
|
|
2368
|
+
" workflows"
|
|
2369
|
+
] }),
|
|
2370
|
+
/* @__PURE__ */ jsxs("span", { "data-workflow": "builder-run-count", children: [
|
|
2371
|
+
runCount(),
|
|
2372
|
+
" runs"
|
|
2373
|
+
] })
|
|
2374
|
+
] }),
|
|
2375
|
+
/* @__PURE__ */ jsx(
|
|
2376
|
+
WorkflowToolbar,
|
|
2377
|
+
{
|
|
2378
|
+
workflowName: builder.state().definition.name,
|
|
2379
|
+
isDirty: builder.state().isDirty,
|
|
2380
|
+
isRunning: runTracker.isRunning(),
|
|
2381
|
+
canUndo: builder.state().undoStack.length > 0,
|
|
2382
|
+
canRedo: builder.state().redoStack.length > 0,
|
|
2383
|
+
onSave: () => props.onSave?.(builder.state().definition),
|
|
2384
|
+
onRun: handleRun,
|
|
2385
|
+
onUndo: builder.undo,
|
|
2386
|
+
onRedo: builder.redo
|
|
2387
|
+
}
|
|
2388
|
+
),
|
|
2389
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__builder-layout", children: [
|
|
2390
|
+
/* @__PURE__ */ jsx(StepPalette, { onAddStep: handleAddStep }),
|
|
2391
|
+
/* @__PURE__ */ jsx(
|
|
2392
|
+
WorkflowCanvas,
|
|
2393
|
+
{
|
|
2394
|
+
definition: builder.state().definition,
|
|
2395
|
+
selectedStepId: builder.state().selectedStepId,
|
|
2396
|
+
onSelectStep: builder.selectStep
|
|
2397
|
+
}
|
|
2398
|
+
),
|
|
2399
|
+
selectedStep() ? /* @__PURE__ */ jsx(
|
|
2400
|
+
StepConfigPanel,
|
|
2401
|
+
{
|
|
2402
|
+
step: selectedStep(),
|
|
2403
|
+
onUpdate: builder.updateStep,
|
|
2404
|
+
onDelete: builder.removeStep,
|
|
2405
|
+
onClose: () => builder.selectStep(null)
|
|
2406
|
+
}
|
|
2407
|
+
) : null,
|
|
2408
|
+
runTracker.run() ? /* @__PURE__ */ jsx(
|
|
2409
|
+
WorkflowRunPanel,
|
|
2410
|
+
{
|
|
2411
|
+
run: runTracker.run(),
|
|
2412
|
+
isRunning: runTracker.isRunning()
|
|
2413
|
+
}
|
|
2414
|
+
) : null
|
|
2415
|
+
] })
|
|
2416
|
+
]
|
|
2417
|
+
}
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
var SI2 = {
|
|
2421
|
+
pending: "\u23F3",
|
|
2422
|
+
running: "\u{1F504}",
|
|
2423
|
+
completed: "\u2705",
|
|
2424
|
+
failed: "\u274C",
|
|
2425
|
+
cancelled: "\u26D4",
|
|
2426
|
+
paused: "\u23F8\uFE0F"
|
|
2427
|
+
};
|
|
2428
|
+
function WorkflowRunsPage(props) {
|
|
2429
|
+
return /* @__PURE__ */ jsxs("div", { class: cx("ai-workflow__runs-page", props.class), children: [
|
|
2430
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__runs-header", children: [
|
|
2431
|
+
/* @__PURE__ */ jsx("h1", { class: "ai-workflow__runs-title", children: "Workflow Runs" }),
|
|
2432
|
+
/* @__PURE__ */ jsxs("span", { class: "ai-workflow__runs-count", children: [
|
|
2433
|
+
props.runs.length,
|
|
2434
|
+
" runs"
|
|
2435
|
+
] })
|
|
2436
|
+
] }),
|
|
2437
|
+
/* @__PURE__ */ jsx(
|
|
2438
|
+
Show,
|
|
2439
|
+
{
|
|
2440
|
+
when: props.runs.length > 0,
|
|
2441
|
+
fallback: /* @__PURE__ */ jsx("div", { class: "ai-workflow__runs-empty", children: /* @__PURE__ */ jsx("p", { children: "No runs yet." }) }),
|
|
2442
|
+
children: /* @__PURE__ */ jsx("div", { role: "list", children: /* @__PURE__ */ jsx(For, { each: props.runs, children: (run) => {
|
|
2443
|
+
const completedSteps = () => run.stepResults.filter((step) => step.status === "completed").length;
|
|
2444
|
+
return /* @__PURE__ */ jsxs(
|
|
2445
|
+
"div",
|
|
2446
|
+
{
|
|
2447
|
+
class: cx(
|
|
2448
|
+
"ai-workflow__run-card",
|
|
2449
|
+
"ai-workflow__focus-visible"
|
|
2450
|
+
),
|
|
2451
|
+
role: "listitem",
|
|
2452
|
+
tabIndex: 0,
|
|
2453
|
+
onClick: () => props.onSelectRun?.(run.id),
|
|
2454
|
+
onKeyDown: (event) => {
|
|
2455
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2456
|
+
event.preventDefault();
|
|
2457
|
+
props.onSelectRun?.(run.id);
|
|
2458
|
+
}
|
|
2459
|
+
},
|
|
2460
|
+
children: [
|
|
2461
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__run-card-header", children: [
|
|
2462
|
+
/* @__PURE__ */ jsxs("span", { class: "ai-workflow__run-card-status", children: [
|
|
2463
|
+
SI2[run.status],
|
|
2464
|
+
" ",
|
|
2465
|
+
run.status
|
|
2466
|
+
] }),
|
|
2467
|
+
/* @__PURE__ */ jsxs("span", { class: "ai-workflow__run-card-id", children: [
|
|
2468
|
+
run.id.slice(0, 8),
|
|
2469
|
+
"\u2026"
|
|
2470
|
+
] })
|
|
2471
|
+
] }),
|
|
2472
|
+
/* @__PURE__ */ jsxs("div", { class: "ai-workflow__run-card-meta", children: [
|
|
2473
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2474
|
+
completedSteps(),
|
|
2475
|
+
"/",
|
|
2476
|
+
run.stepResults.length,
|
|
2477
|
+
" steps"
|
|
2478
|
+
] }),
|
|
2479
|
+
/* @__PURE__ */ jsx("span", { children: new Date(run.startedAt).toLocaleString() })
|
|
2480
|
+
] }),
|
|
2481
|
+
/* @__PURE__ */ jsx(Show, { when: run.error, children: /* @__PURE__ */ jsx("p", { class: "ai-workflow__run-card-error", children: run.error }) })
|
|
2482
|
+
]
|
|
2483
|
+
}
|
|
2484
|
+
);
|
|
2485
|
+
} }) })
|
|
2486
|
+
}
|
|
2487
|
+
)
|
|
2488
|
+
] });
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
export { ApprovalModal, StepConfigPanel, StepConnector, StepNode, StepPalette, WORKFLOW_TEMPLATES, WorkflowBuilderPage, WorkflowCanvas, WorkflowEngine, WorkflowRunPanel, WorkflowRunsPage, WorkflowToolbar, createApprovalGate, createWorkflow, createWorkflowBuilder, createWorkflowRun, createWorkflowStep, createWorkflowTemplates, getTemplate, getTemplatesByCategory, validateRun, validateWorkflow };
|
|
2492
|
+
//# sourceMappingURL=index.js.map
|
|
2493
|
+
//# sourceMappingURL=index.js.map
|