@fieldwangai/agentflow 0.1.42 → 0.1.43
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/bin/lib/ui-server.mjs +152 -2
- package/builtin/web-ui/dist/assets/index-BCquw8dA.js +218 -0
- package/builtin/web-ui/dist/assets/index-CXO3VpkR.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-CuOti87V.css +0 -1
- package/builtin/web-ui/dist/assets/index-D9T2uM0l.js +0 -218
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -108,8 +108,13 @@ const MIME = {
|
|
|
108
108
|
".js": "text/javascript; charset=utf-8",
|
|
109
109
|
".css": "text/css; charset=utf-8",
|
|
110
110
|
".json": "application/json; charset=utf-8",
|
|
111
|
-
".
|
|
111
|
+
".png": "image/png",
|
|
112
|
+
".jpg": "image/jpeg",
|
|
113
|
+
".jpeg": "image/jpeg",
|
|
114
|
+
".gif": "image/gif",
|
|
115
|
+
".webp": "image/webp",
|
|
112
116
|
".svg": "image/svg+xml",
|
|
117
|
+
".ico": "image/x-icon",
|
|
113
118
|
};
|
|
114
119
|
|
|
115
120
|
const RUN_CONFIG_FILENAME = "run-config.json";
|
|
@@ -1125,6 +1130,7 @@ const WORKSPACE_TEXT_EXTS = new Set([
|
|
|
1125
1130
|
".mjs",
|
|
1126
1131
|
".cjs",
|
|
1127
1132
|
]);
|
|
1133
|
+
const WORKSPACE_IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"]);
|
|
1128
1134
|
|
|
1129
1135
|
function resolveWorkspaceFilePath(workspaceRoot, relPath) {
|
|
1130
1136
|
const root = path.resolve(workspaceRoot);
|
|
@@ -1144,9 +1150,33 @@ function workspaceFileIcon(fileName, isDir = false) {
|
|
|
1144
1150
|
if ([".yaml", ".yml", ".json"].includes(ext)) return "data_object";
|
|
1145
1151
|
if (ext === ".css") return "palette";
|
|
1146
1152
|
if (ext === ".html") return "web";
|
|
1153
|
+
if (WORKSPACE_IMAGE_EXTS.has(ext)) return "image";
|
|
1147
1154
|
return "draft";
|
|
1148
1155
|
}
|
|
1149
1156
|
|
|
1157
|
+
function sanitizeWorkspaceUploadName(filename) {
|
|
1158
|
+
const parsed = path.parse(String(filename || "image").replace(/\\/g, "/").split("/").pop() || "image");
|
|
1159
|
+
const stem = (parsed.name || "image")
|
|
1160
|
+
.trim()
|
|
1161
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
1162
|
+
.replace(/^-+|-+$/g, "")
|
|
1163
|
+
.slice(0, 80) || "image";
|
|
1164
|
+
const ext = String(parsed.ext || "").toLowerCase();
|
|
1165
|
+
return `${stem}${WORKSPACE_IMAGE_EXTS.has(ext) ? ext : ".png"}`;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function uniqueWorkspaceRelPath(workspaceRoot, relPath) {
|
|
1169
|
+
let { abs, rel } = resolveWorkspaceFilePath(workspaceRoot, relPath);
|
|
1170
|
+
if (!fs.existsSync(abs)) return { abs, rel };
|
|
1171
|
+
const parsed = path.parse(rel);
|
|
1172
|
+
for (let i = 1; i < 1000; i += 1) {
|
|
1173
|
+
const candidate = path.posix.join(parsed.dir, `${parsed.name}-${i}${parsed.ext}`);
|
|
1174
|
+
const resolved = resolveWorkspaceFilePath(workspaceRoot, candidate);
|
|
1175
|
+
if (!fs.existsSync(resolved.abs)) return resolved;
|
|
1176
|
+
}
|
|
1177
|
+
return { abs, rel };
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1150
1180
|
function readWorkspaceFilesRecursive(dir, root, depth = 0, maxDepth = 3, budget = { count: 0 }) {
|
|
1151
1181
|
if (depth > maxDepth || budget.count > 500) return [];
|
|
1152
1182
|
let entries;
|
|
@@ -1174,7 +1204,7 @@ function readWorkspaceFilesRecursive(dir, root, depth = 0, maxDepth = 3, budget
|
|
|
1174
1204
|
} else if (entry.isFile()) {
|
|
1175
1205
|
if (WORKSPACE_FILE_SKIP_FILES.has(entry.name)) continue;
|
|
1176
1206
|
const ext = path.extname(entry.name).toLowerCase();
|
|
1177
|
-
if (!WORKSPACE_TEXT_EXTS.has(ext)) continue;
|
|
1207
|
+
if (!WORKSPACE_TEXT_EXTS.has(ext) && !WORKSPACE_IMAGE_EXTS.has(ext)) continue;
|
|
1178
1208
|
let size = 0;
|
|
1179
1209
|
try { size = fs.statSync(abs).size; } catch {}
|
|
1180
1210
|
budget.count++;
|
|
@@ -2655,6 +2685,47 @@ function parseFlowsImportForm(req) {
|
|
|
2655
2685
|
});
|
|
2656
2686
|
}
|
|
2657
2687
|
|
|
2688
|
+
function parseWorkspaceUploadForm(req) {
|
|
2689
|
+
return new Promise((resolve, reject) => {
|
|
2690
|
+
const bb = busboy({
|
|
2691
|
+
headers: req.headers,
|
|
2692
|
+
limits: { files: 1, fileSize: 10 * 1024 * 1024, parts: 32 },
|
|
2693
|
+
});
|
|
2694
|
+
const fields = {};
|
|
2695
|
+
const chunks = [];
|
|
2696
|
+
let filename = "";
|
|
2697
|
+
let mimeType = "";
|
|
2698
|
+
let gotFile = false;
|
|
2699
|
+
bb.on("field", (name, val) => {
|
|
2700
|
+
fields[String(name || "")] = String(val || "");
|
|
2701
|
+
});
|
|
2702
|
+
bb.on("file", (name, file, info) => {
|
|
2703
|
+
if (name !== "file") {
|
|
2704
|
+
file.resume();
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
gotFile = true;
|
|
2708
|
+
filename = info.filename || "";
|
|
2709
|
+
mimeType = info.mimeType || "";
|
|
2710
|
+
file.on("data", (d) => chunks.push(d));
|
|
2711
|
+
file.on("limit", () => {
|
|
2712
|
+
reject(new Error("FILE_TOO_LARGE"));
|
|
2713
|
+
});
|
|
2714
|
+
});
|
|
2715
|
+
bb.on("finish", () => {
|
|
2716
|
+
resolve({
|
|
2717
|
+
fields,
|
|
2718
|
+
file: Buffer.concat(chunks),
|
|
2719
|
+
filename,
|
|
2720
|
+
mimeType,
|
|
2721
|
+
gotFile,
|
|
2722
|
+
});
|
|
2723
|
+
});
|
|
2724
|
+
bb.on("error", reject);
|
|
2725
|
+
req.pipe(bb);
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2658
2729
|
/** GET 读 flow / nodes / SSE 等 */
|
|
2659
2730
|
function isValidFlowSourceRead(s) {
|
|
2660
2731
|
return s === "builtin" || s === "admin" || s === "user" || s === "workspace";
|
|
@@ -3422,6 +3493,37 @@ export function startUiServer({
|
|
|
3422
3493
|
return;
|
|
3423
3494
|
}
|
|
3424
3495
|
|
|
3496
|
+
if (req.method === "GET" && url.pathname === "/api/workspace/file/raw") {
|
|
3497
|
+
try {
|
|
3498
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
3499
|
+
flowId: url.searchParams.get("flowId") || "",
|
|
3500
|
+
flowSource: url.searchParams.get("flowSource") || "user",
|
|
3501
|
+
archived: url.searchParams.get("archived") === "1",
|
|
3502
|
+
}, userCtx);
|
|
3503
|
+
if (scoped.error) {
|
|
3504
|
+
json(res, 400, { error: scoped.error });
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3507
|
+
const { abs } = resolveWorkspaceFilePath(scoped.root, url.searchParams.get("path") || "");
|
|
3508
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
|
|
3509
|
+
json(res, 404, { error: "File not found" });
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
const ext = path.extname(abs).toLowerCase();
|
|
3513
|
+
const type = MIME[ext] || "application/octet-stream";
|
|
3514
|
+
const data = fs.readFileSync(abs);
|
|
3515
|
+
res.writeHead(200, {
|
|
3516
|
+
"Content-Type": type,
|
|
3517
|
+
"Content-Length": data.length,
|
|
3518
|
+
"Cache-Control": "no-store",
|
|
3519
|
+
});
|
|
3520
|
+
res.end(data);
|
|
3521
|
+
} catch (e) {
|
|
3522
|
+
json(res, /traversal/i.test(String(e.message || e)) ? 403 : 500, { error: (e && e.message) || String(e) });
|
|
3523
|
+
}
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3425
3527
|
if (req.method === "POST" && url.pathname === "/api/workspace/file") {
|
|
3426
3528
|
let payload;
|
|
3427
3529
|
try {
|
|
@@ -3458,6 +3560,54 @@ export function startUiServer({
|
|
|
3458
3560
|
return;
|
|
3459
3561
|
}
|
|
3460
3562
|
|
|
3563
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/upload") {
|
|
3564
|
+
let parsed;
|
|
3565
|
+
try {
|
|
3566
|
+
parsed = await parseWorkspaceUploadForm(req);
|
|
3567
|
+
} catch (e) {
|
|
3568
|
+
json(res, /FILE_TOO_LARGE/.test(String(e.message || e)) ? 413 : 400, { error: (e && e.message) || String(e) });
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
try {
|
|
3572
|
+
if (!parsed.gotFile || !parsed.file.length) {
|
|
3573
|
+
json(res, 400, { error: "Missing upload file" });
|
|
3574
|
+
return;
|
|
3575
|
+
}
|
|
3576
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
3577
|
+
flowId: parsed.fields.flowId || "",
|
|
3578
|
+
flowSource: parsed.fields.flowSource || "user",
|
|
3579
|
+
archived: parsed.fields.archived === "1" || parsed.fields.archived === "true" || parsed.fields.flowArchived === "true",
|
|
3580
|
+
}, userCtx);
|
|
3581
|
+
if (scoped.error) {
|
|
3582
|
+
json(res, 400, { error: scoped.error });
|
|
3583
|
+
return;
|
|
3584
|
+
}
|
|
3585
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
|
|
3586
|
+
json(res, 400, { error: "Cannot write to builtin or archived pipeline workspace" });
|
|
3587
|
+
return;
|
|
3588
|
+
}
|
|
3589
|
+
const safeName = sanitizeWorkspaceUploadName(parsed.filename);
|
|
3590
|
+
const ext = path.extname(safeName).toLowerCase();
|
|
3591
|
+
if (!WORKSPACE_IMAGE_EXTS.has(ext) || (parsed.mimeType && !/^image\//i.test(parsed.mimeType))) {
|
|
3592
|
+
json(res, 400, { error: "Only image uploads are supported" });
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
const targetDir = String(parsed.fields.dir || "img").trim().replace(/^[/\\]+/, "") || "img";
|
|
3596
|
+
const target = uniqueWorkspaceRelPath(scoped.root, path.posix.join(targetDir.replace(/\\/g, "/"), safeName));
|
|
3597
|
+
fs.mkdirSync(path.dirname(target.abs), { recursive: true });
|
|
3598
|
+
fs.writeFileSync(target.abs, parsed.file);
|
|
3599
|
+
json(res, 200, {
|
|
3600
|
+
ok: true,
|
|
3601
|
+
path: target.rel,
|
|
3602
|
+
size: parsed.file.length,
|
|
3603
|
+
mimeType: parsed.mimeType,
|
|
3604
|
+
});
|
|
3605
|
+
} catch (e) {
|
|
3606
|
+
json(res, /traversal/i.test(String(e.message || e)) ? 403 : 500, { error: (e && e.message) || String(e) });
|
|
3607
|
+
}
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3461
3611
|
if (req.method === "POST" && url.pathname === "/api/workspace/folder") {
|
|
3462
3612
|
let payload;
|
|
3463
3613
|
try {
|