entropia-template-ui 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts.map +1 -1
- package/dist/components/MenuBar.d.ts +2 -1
- package/dist/components/MenuBar.d.ts.map +1 -1
- package/dist/components/WorkspaceTabs.d.ts +2 -1
- package/dist/components/WorkspaceTabs.d.ts.map +1 -1
- package/dist/index.js +1005 -983
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { jsxs as p, Fragment as
|
|
2
|
-
import { useState as
|
|
3
|
-
import { create as
|
|
4
|
-
import { addEdge as
|
|
5
|
-
import { createPortal as
|
|
6
|
-
const
|
|
1
|
+
import { jsxs as p, Fragment as K, jsx as a } from "react/jsx-runtime";
|
|
2
|
+
import { useState as E, useMemo as Y, useRef as D, useEffect as W, Fragment as je } from "react";
|
|
3
|
+
import { create as $e } from "zustand";
|
|
4
|
+
import { addEdge as Ye, applyEdgeChanges as Xe, applyNodeChanges as Qe, Handle as _e, Position as xe, ReactFlowProvider as Ze, useReactFlow as en, ReactFlow as nn, Background as tn, ViewportPortal as an } from "@xyflow/react";
|
|
5
|
+
import { createPortal as Le } from "react-dom";
|
|
6
|
+
const sn = [
|
|
7
7
|
{
|
|
8
8
|
type: "constant",
|
|
9
9
|
label: "Constant",
|
|
@@ -34,31 +34,31 @@ const an = [
|
|
|
34
34
|
outputs: [{ name: "result", label: "Result", dataKind: "tensor", direction: "out" }],
|
|
35
35
|
parameters: []
|
|
36
36
|
}
|
|
37
|
-
],
|
|
38
|
-
let
|
|
39
|
-
function
|
|
37
|
+
], te = "0.2.3";
|
|
38
|
+
let rn = 0;
|
|
39
|
+
function ue(n) {
|
|
40
40
|
return n.charAt(0).toUpperCase() + n.slice(1);
|
|
41
41
|
}
|
|
42
|
-
function
|
|
42
|
+
function on(n) {
|
|
43
43
|
return {
|
|
44
44
|
type: n.type,
|
|
45
45
|
label: n.label,
|
|
46
46
|
category: n.category,
|
|
47
47
|
inputs: n.inputs.map((e) => ({
|
|
48
48
|
name: e.name,
|
|
49
|
-
label:
|
|
49
|
+
label: ue(e.name),
|
|
50
50
|
dataKind: e.data_kind,
|
|
51
51
|
direction: "in"
|
|
52
52
|
})),
|
|
53
53
|
outputs: n.outputs.map((e) => ({
|
|
54
54
|
name: e.name,
|
|
55
|
-
label:
|
|
55
|
+
label: ue(e.name),
|
|
56
56
|
dataKind: e.data_kind,
|
|
57
57
|
direction: "out"
|
|
58
58
|
})),
|
|
59
59
|
parameters: n.parameters.map((e) => ({
|
|
60
60
|
name: e.name,
|
|
61
|
-
label:
|
|
61
|
+
label: ue(e.name),
|
|
62
62
|
kind: e.kind,
|
|
63
63
|
default: e.default,
|
|
64
64
|
required: e.required,
|
|
@@ -67,11 +67,11 @@ function rn(n) {
|
|
|
67
67
|
}))
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
let
|
|
71
|
-
function
|
|
72
|
-
return
|
|
70
|
+
let Se = 0;
|
|
71
|
+
function q(n) {
|
|
72
|
+
return Se += 1, `${n}_${Se}`;
|
|
73
73
|
}
|
|
74
|
-
function
|
|
74
|
+
function U(n, e) {
|
|
75
75
|
let t = "untitled", s = "", r = "";
|
|
76
76
|
try {
|
|
77
77
|
t = g.getState().activeFileName ?? "untitled", s = localStorage.getItem("entropia_riko_theme") ?? "", r = localStorage.getItem("entropia_riko_background") ?? "";
|
|
@@ -79,7 +79,7 @@ function M(n, e) {
|
|
|
79
79
|
}
|
|
80
80
|
return {
|
|
81
81
|
version: "1.0",
|
|
82
|
-
metadata: { name: t, app: "entropia-riko", appVersion:
|
|
82
|
+
metadata: { name: t, app: "entropia-riko", appVersion: te },
|
|
83
83
|
nodes: n.map((o) => ({
|
|
84
84
|
id: o.id,
|
|
85
85
|
type_name: o.data.type,
|
|
@@ -100,7 +100,7 @@ function M(n, e) {
|
|
|
100
100
|
settings: { theme: s, backgroundImage: r }
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
-
function
|
|
103
|
+
function ae(n, e) {
|
|
104
104
|
const t = (n.nodes || []).map((r) => {
|
|
105
105
|
var i, d;
|
|
106
106
|
const o = e.find((h) => h.type === r.type_name);
|
|
@@ -127,7 +127,7 @@ function ee(n, e) {
|
|
|
127
127
|
}));
|
|
128
128
|
return { nodes: t, edges: s };
|
|
129
129
|
}
|
|
130
|
-
async function
|
|
130
|
+
async function ln(n) {
|
|
131
131
|
const e = (n || "").trim();
|
|
132
132
|
if (!e) return null;
|
|
133
133
|
const t = [];
|
|
@@ -139,23 +139,23 @@ async function on(n) {
|
|
|
139
139
|
}
|
|
140
140
|
return null;
|
|
141
141
|
}
|
|
142
|
-
const
|
|
143
|
-
function
|
|
142
|
+
const De = "entropia_riko_recent";
|
|
143
|
+
function cn() {
|
|
144
144
|
try {
|
|
145
|
-
const n = localStorage.getItem(
|
|
145
|
+
const n = localStorage.getItem(De), e = n ? JSON.parse(n) : [];
|
|
146
146
|
return Array.isArray(e) ? e.filter((t) => typeof t == "string") : [];
|
|
147
147
|
} catch {
|
|
148
148
|
return [];
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
function
|
|
151
|
+
function dn(n) {
|
|
152
152
|
try {
|
|
153
|
-
localStorage.setItem(
|
|
153
|
+
localStorage.setItem(De, JSON.stringify(n.slice(0, 10)));
|
|
154
154
|
} catch {
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
const g =
|
|
158
|
-
nodeDefs:
|
|
157
|
+
const g = $e((n, e) => ({
|
|
158
|
+
nodeDefs: sn,
|
|
159
159
|
workflows: [],
|
|
160
160
|
activeWorkflowId: null,
|
|
161
161
|
nodes: [],
|
|
@@ -171,7 +171,7 @@ const g = Ie((n, e) => ({
|
|
|
171
171
|
activeFilePath: null,
|
|
172
172
|
dirty: !1,
|
|
173
173
|
losses: [],
|
|
174
|
-
recentFiles:
|
|
174
|
+
recentFiles: cn(),
|
|
175
175
|
welcomeOpen: !0,
|
|
176
176
|
prefsOpen: !1,
|
|
177
177
|
aboutOpen: !1,
|
|
@@ -182,20 +182,20 @@ const g = Ie((n, e) => ({
|
|
|
182
182
|
codeBuffer: "",
|
|
183
183
|
toasts: [],
|
|
184
184
|
onNodesChange: (t) => {
|
|
185
|
-
const s =
|
|
185
|
+
const s = Qe(t, e().nodes), r = t.some((o) => o.type !== "select");
|
|
186
186
|
n(r ? { nodes: s, dirty: !0 } : { nodes: s });
|
|
187
187
|
},
|
|
188
188
|
onEdgesChange: (t) => {
|
|
189
|
-
const s =
|
|
189
|
+
const s = Xe(t, e().edges), r = t.some((o) => o.type !== "select");
|
|
190
190
|
n(r ? { edges: s, dirty: !0 } : { edges: s });
|
|
191
191
|
},
|
|
192
192
|
onConnect: (t) => {
|
|
193
193
|
if (!t.source || !t.target) return;
|
|
194
|
-
const s =
|
|
195
|
-
n({ edges:
|
|
194
|
+
const s = q("edge");
|
|
195
|
+
n({ edges: Ye({ ...t, id: s }, e().edges), dirty: !0 }), e().log(`连接 ${t.source}.${t.sourceHandle} → ${t.target}.${t.targetHandle}`);
|
|
196
196
|
},
|
|
197
197
|
addNode: (t, s) => {
|
|
198
|
-
const r = e().nodeDefs.find((h) => h.type === t), o =
|
|
198
|
+
const r = e().nodeDefs.find((h) => h.type === t), o = q(t), i = {};
|
|
199
199
|
if (r)
|
|
200
200
|
for (const h of r.parameters) i[h.name] = h.default;
|
|
201
201
|
const d = {
|
|
@@ -228,7 +228,7 @@ const g = Ie((n, e) => ({
|
|
|
228
228
|
dirty: !0
|
|
229
229
|
}),
|
|
230
230
|
addFrame: (t, s) => {
|
|
231
|
-
const r =
|
|
231
|
+
const r = q("frame");
|
|
232
232
|
n({
|
|
233
233
|
frames: [
|
|
234
234
|
...e().frames,
|
|
@@ -251,7 +251,7 @@ const g = Ie((n, e) => ({
|
|
|
251
251
|
e().pushToast("error", "该节点没有 file/module 参数");
|
|
252
252
|
return;
|
|
253
253
|
}
|
|
254
|
-
const o = await
|
|
254
|
+
const o = await ln(String(r));
|
|
255
255
|
if (!o) {
|
|
256
256
|
e().pushToast("error", `找不到子图: ${r}`);
|
|
257
257
|
return;
|
|
@@ -262,13 +262,13 @@ const g = Ie((n, e) => ({
|
|
|
262
262
|
e().pushToast("error", h.error ?? "打开子图失败");
|
|
263
263
|
return;
|
|
264
264
|
}
|
|
265
|
-
const { nodes: c, edges:
|
|
266
|
-
|
|
265
|
+
const { nodes: c, edges: m } = ae(h.doc, e().nodeDefs), u = [...e().graphStack], b = u[u.length - 1];
|
|
266
|
+
u[u.length - 1] = { ...b, nodes: e().nodes, edges: e().edges };
|
|
267
267
|
const l = ((i = o.split("/").pop()) == null ? void 0 : i.replace(/\.(riko|ric)$/i, "")) ?? o;
|
|
268
|
-
|
|
269
|
-
graphStack:
|
|
268
|
+
u.push({ name: l, path: o, nodes: c, edges: m }), n({
|
|
269
|
+
graphStack: u,
|
|
270
270
|
nodes: c,
|
|
271
|
-
edges:
|
|
271
|
+
edges: m,
|
|
272
272
|
selectedNodeId: null,
|
|
273
273
|
results: {},
|
|
274
274
|
status: "idle",
|
|
@@ -293,7 +293,7 @@ const g = Ie((n, e) => ({
|
|
|
293
293
|
run: async () => {
|
|
294
294
|
n({ status: "running" }), e().log("开始执行图(调用 Python runtime)...");
|
|
295
295
|
try {
|
|
296
|
-
const t =
|
|
296
|
+
const t = U(e().nodes, e().edges), r = await (await fetch("/api/execute", {
|
|
297
297
|
method: "POST",
|
|
298
298
|
headers: { "Content-Type": "application/json" },
|
|
299
299
|
body: JSON.stringify(t)
|
|
@@ -302,15 +302,15 @@ const g = Ie((n, e) => ({
|
|
|
302
302
|
const o = {}, i = r.outputs;
|
|
303
303
|
for (const [d, h] of Object.entries(i)) {
|
|
304
304
|
o[d] = {};
|
|
305
|
-
for (const [c,
|
|
305
|
+
for (const [c, m] of Object.entries(h))
|
|
306
306
|
o[d][c] = {
|
|
307
|
-
summary:
|
|
308
|
-
shape:
|
|
309
|
-
dtype:
|
|
310
|
-
device:
|
|
311
|
-
dataPreview:
|
|
312
|
-
dataKind:
|
|
313
|
-
image:
|
|
307
|
+
summary: m.summary,
|
|
308
|
+
shape: m.shape,
|
|
309
|
+
dtype: m.dtype,
|
|
310
|
+
device: m.device,
|
|
311
|
+
dataPreview: m.data === null || m.data === void 0 ? "" : JSON.stringify(m.data).slice(0, 120),
|
|
312
|
+
dataKind: m.data_kind,
|
|
313
|
+
image: m.image
|
|
314
314
|
};
|
|
315
315
|
}
|
|
316
316
|
n({
|
|
@@ -333,38 +333,38 @@ const g = Ie((n, e) => ({
|
|
|
333
333
|
},
|
|
334
334
|
loadNodeDefs: async () => {
|
|
335
335
|
try {
|
|
336
|
-
const r = (await (await fetch("/api/nodes")).json()).nodes.map(
|
|
336
|
+
const r = (await (await fetch("/api/nodes")).json()).nodes.map(on);
|
|
337
337
|
r.length > 0 && (n({ nodeDefs: r }), e().log(`从 API 加载 ${r.length} 个节点定义`));
|
|
338
338
|
} catch (t) {
|
|
339
339
|
e().log(`API 节点加载失败: ${t instanceof Error ? t.message : String(t)}。请启动 API server: uvicorn src.server.app:app --port 8000`);
|
|
340
340
|
}
|
|
341
341
|
},
|
|
342
342
|
save: () => {
|
|
343
|
-
const t =
|
|
343
|
+
const t = U(e().nodes, e().edges), s = new Blob([JSON.stringify(t, null, 2)], {
|
|
344
344
|
type: "application/json"
|
|
345
345
|
}), r = URL.createObjectURL(s), o = document.createElement("a");
|
|
346
346
|
o.href = r, o.download = "workflow.riko", o.click(), URL.revokeObjectURL(r), n({ dirty: !1 }), e().log("已保存工作流 (workflow.riko)");
|
|
347
347
|
},
|
|
348
348
|
exportRiko: () => {
|
|
349
|
-
const t =
|
|
349
|
+
const t = U(e().nodes, e().edges), s = (e().activeFileName ?? "workflow").replace(/\.(riko|ric)$/i, ""), r = new Blob([JSON.stringify(t, null, 2)], { type: "application/json" }), o = URL.createObjectURL(r), i = document.createElement("a");
|
|
350
350
|
i.href = o, i.download = `${s}.riko`, i.click(), URL.revokeObjectURL(o), n({ dirty: !1 }), e().log(`已导出 ${s}.riko`);
|
|
351
351
|
},
|
|
352
352
|
exportBinary: async () => {
|
|
353
353
|
try {
|
|
354
|
-
const t =
|
|
354
|
+
const t = U(e().nodes, e().edges), r = await (await fetch("/api/export_binary", {
|
|
355
355
|
method: "POST",
|
|
356
356
|
headers: { "Content-Type": "application/json" },
|
|
357
357
|
body: JSON.stringify(t)
|
|
358
358
|
})).json();
|
|
359
359
|
if (r.status !== "success") throw new Error(r.error);
|
|
360
|
-
const o = (e().activeFileName ?? "workflow").replace(/\.(riko|ric)$/i, ""), i = Uint8Array.from(atob(r.base64), (
|
|
360
|
+
const o = (e().activeFileName ?? "workflow").replace(/\.(riko|ric)$/i, ""), i = Uint8Array.from(atob(r.base64), (m) => m.charCodeAt(0)), d = new Blob([i], { type: "application/octet-stream" }), h = URL.createObjectURL(d), c = document.createElement("a");
|
|
361
361
|
c.href = h, c.download = `${o}.ric`, c.click(), URL.revokeObjectURL(h), n({ dirty: !1 }), e().log(`已导出 ${o}.ric`);
|
|
362
362
|
} catch (t) {
|
|
363
363
|
e().log(`导出 .ric 失败: ${t instanceof Error ? t.message : String(t)}`);
|
|
364
364
|
}
|
|
365
365
|
},
|
|
366
366
|
exportProjectTo: async (t) => {
|
|
367
|
-
const s =
|
|
367
|
+
const s = U(e().nodes, e().edges);
|
|
368
368
|
try {
|
|
369
369
|
const o = await (await fetch("/api/export_project", {
|
|
370
370
|
method: "POST",
|
|
@@ -394,14 +394,14 @@ const g = Ie((n, e) => ({
|
|
|
394
394
|
});
|
|
395
395
|
s = JSON.parse(i);
|
|
396
396
|
}
|
|
397
|
-
const { nodes: r, edges: o } =
|
|
397
|
+
const { nodes: r, edges: o } = ae(s, e().nodeDefs);
|
|
398
398
|
n({ nodes: r, edges: o, frames: [], graphStack: [{ name: t.name.replace(/\.(riko|ric)$/i, "") || "workflow", path: t.name, nodes: r, edges: [] }], selectedNodeId: null, results: {}, status: "idle", dirty: !1 }), e().log(`已加载工作流(${r.length} 节点, ${o.length} 连接)`);
|
|
399
399
|
} catch (s) {
|
|
400
400
|
e().log(`加载失败: ${s instanceof Error ? s.message : String(s)}`);
|
|
401
401
|
}
|
|
402
402
|
},
|
|
403
403
|
exportPython: async () => {
|
|
404
|
-
const t =
|
|
404
|
+
const t = U(e().nodes, e().edges);
|
|
405
405
|
try {
|
|
406
406
|
const r = await (await fetch("/api/export_python", {
|
|
407
407
|
method: "POST",
|
|
@@ -418,7 +418,7 @@ const g = Ie((n, e) => ({
|
|
|
418
418
|
}
|
|
419
419
|
},
|
|
420
420
|
exportKeras: async () => {
|
|
421
|
-
const t =
|
|
421
|
+
const t = U(e().nodes, e().edges);
|
|
422
422
|
try {
|
|
423
423
|
const r = await (await fetch("/api/export_keras", {
|
|
424
424
|
method: "POST",
|
|
@@ -435,7 +435,7 @@ const g = Ie((n, e) => ({
|
|
|
435
435
|
}
|
|
436
436
|
},
|
|
437
437
|
generatePythonCode: async () => {
|
|
438
|
-
const t =
|
|
438
|
+
const t = U(e().nodes, e().edges);
|
|
439
439
|
try {
|
|
440
440
|
const r = await (await fetch("/api/export_python", {
|
|
441
441
|
method: "POST",
|
|
@@ -467,7 +467,7 @@ const g = Ie((n, e) => ({
|
|
|
467
467
|
newWorkflow: (t) => {
|
|
468
468
|
const s = e().activeWorkflowId, r = s ? e().workflows.map(
|
|
469
469
|
(i) => i.id === s ? { ...i, nodes: e().nodes, edges: e().edges } : i
|
|
470
|
-
) : e().workflows, o =
|
|
470
|
+
) : e().workflows, o = q("wf");
|
|
471
471
|
n({
|
|
472
472
|
workflows: [...r, { id: o, name: t, nodes: [], edges: [] }],
|
|
473
473
|
activeWorkflowId: o,
|
|
@@ -510,10 +510,10 @@ const g = Ie((n, e) => ({
|
|
|
510
510
|
try {
|
|
511
511
|
const h = await (await fetch(`/api/files/content?path=${encodeURIComponent(t)}`)).json();
|
|
512
512
|
if (h.status === "success") {
|
|
513
|
-
const { nodes: c, edges:
|
|
513
|
+
const { nodes: c, edges: m } = ae(h.doc, e().nodeDefs);
|
|
514
514
|
n({
|
|
515
515
|
nodes: c,
|
|
516
|
-
edges:
|
|
516
|
+
edges: m,
|
|
517
517
|
selectedNodeId: null,
|
|
518
518
|
results: {},
|
|
519
519
|
status: "idle",
|
|
@@ -522,8 +522,8 @@ const g = Ie((n, e) => ({
|
|
|
522
522
|
graphStack: [{ name: ((i = (o = h.doc) == null ? void 0 : o.metadata) == null ? void 0 : i.name) ?? t.split("/").pop() ?? t, path: t, nodes: c, edges: [] }],
|
|
523
523
|
dirty: !1
|
|
524
524
|
});
|
|
525
|
-
const
|
|
526
|
-
|
|
525
|
+
const u = [t, ...e().recentFiles.filter((b) => b !== t)].slice(0, 10);
|
|
526
|
+
dn(u), n({ recentFiles: u }), e().log(`已打开 ${t}`);
|
|
527
527
|
} else
|
|
528
528
|
e().log(`打开失败: ${h.error}`);
|
|
529
529
|
} catch (d) {
|
|
@@ -535,7 +535,7 @@ const g = Ie((n, e) => ({
|
|
|
535
535
|
try {
|
|
536
536
|
const o = await (await fetch(`/api/project/open?path=${encodeURIComponent(t)}`)).json();
|
|
537
537
|
if (o.status === "success") {
|
|
538
|
-
const { nodes: i, edges: d } =
|
|
538
|
+
const { nodes: i, edges: d } = ae(o.doc, e().nodeDefs), h = ((s = t.split("/").pop()) == null ? void 0 : s.replace(/\.(riko|ric)$/, "")) ?? t;
|
|
539
539
|
n({
|
|
540
540
|
nodes: i,
|
|
541
541
|
edges: d,
|
|
@@ -562,44 +562,44 @@ const g = Ie((n, e) => ({
|
|
|
562
562
|
}
|
|
563
563
|
const o = r.doc, i = e().nodes;
|
|
564
564
|
let d = 0;
|
|
565
|
-
for (const
|
|
566
|
-
const h = {}, c = (o.nodes ?? []).map((
|
|
567
|
-
var
|
|
568
|
-
const
|
|
569
|
-
h[
|
|
570
|
-
const l = e().nodeDefs.find((
|
|
565
|
+
for (const u of i) d = Math.max(d, u.position.y + 120);
|
|
566
|
+
const h = {}, c = (o.nodes ?? []).map((u) => {
|
|
567
|
+
var v, f;
|
|
568
|
+
const b = q("n");
|
|
569
|
+
h[u.id] = b;
|
|
570
|
+
const l = e().nodeDefs.find((C) => C.type === u.type_name);
|
|
571
571
|
return {
|
|
572
|
-
id:
|
|
572
|
+
id: b,
|
|
573
573
|
type: "thnNode",
|
|
574
|
-
position: { x: ((
|
|
574
|
+
position: { x: ((v = u.position) == null ? void 0 : v[0]) ?? 0, y: (((f = u.position) == null ? void 0 : f[1]) ?? 0) + d },
|
|
575
575
|
data: {
|
|
576
|
-
type:
|
|
577
|
-
label:
|
|
578
|
-
category:
|
|
579
|
-
params:
|
|
576
|
+
type: u.type_name,
|
|
577
|
+
label: u.label || (l == null ? void 0 : l.label) || u.type_name,
|
|
578
|
+
category: u.category || (l == null ? void 0 : l.category) || "",
|
|
579
|
+
params: u.parameters || {},
|
|
580
580
|
inputs: (l == null ? void 0 : l.inputs) ?? [],
|
|
581
581
|
outputs: (l == null ? void 0 : l.outputs) ?? [],
|
|
582
582
|
hasError: !1
|
|
583
583
|
}
|
|
584
584
|
};
|
|
585
|
-
}),
|
|
586
|
-
id:
|
|
587
|
-
source: h[
|
|
588
|
-
target: h[
|
|
589
|
-
sourceHandle:
|
|
590
|
-
targetHandle:
|
|
591
|
-
})).filter((
|
|
585
|
+
}), m = (o.edges ?? []).map((u) => ({
|
|
586
|
+
id: q("edge"),
|
|
587
|
+
source: h[u.source_node],
|
|
588
|
+
target: h[u.target_node],
|
|
589
|
+
sourceHandle: u.source_port,
|
|
590
|
+
targetHandle: u.target_port
|
|
591
|
+
})).filter((u) => u.source && u.target);
|
|
592
592
|
n({
|
|
593
593
|
nodes: [...i, ...c],
|
|
594
|
-
edges: [...e().edges, ...
|
|
594
|
+
edges: [...e().edges, ...m],
|
|
595
595
|
dirty: !0
|
|
596
|
-
}), e().log(`已展开 ${t}(${c.length} 节点, ${
|
|
596
|
+
}), e().log(`已展开 ${t}(${c.length} 节点, ${m.length} 连接)`);
|
|
597
597
|
} catch (s) {
|
|
598
598
|
e().log(`展开失败: ${s instanceof Error ? s.message : String(s)}`);
|
|
599
599
|
}
|
|
600
600
|
},
|
|
601
601
|
saveFileToDisk: async (t, s = "ascii") => {
|
|
602
|
-
const o = { ...
|
|
602
|
+
const o = { ...U(e().nodes, e().edges), metadata: { name: t, description: "" } };
|
|
603
603
|
try {
|
|
604
604
|
const d = await (await fetch("/api/files/save", {
|
|
605
605
|
method: "POST",
|
|
@@ -613,7 +613,7 @@ const g = Ie((n, e) => ({
|
|
|
613
613
|
},
|
|
614
614
|
saveToPath: async (t, s = "ascii") => {
|
|
615
615
|
var o;
|
|
616
|
-
const r =
|
|
616
|
+
const r = U(e().nodes, e().edges);
|
|
617
617
|
try {
|
|
618
618
|
const d = await (await fetch("/api/fs/save", {
|
|
619
619
|
method: "POST",
|
|
@@ -635,7 +635,7 @@ const g = Ie((n, e) => ({
|
|
|
635
635
|
e().log("未找到 import / graph_reference 节点定义,请确认 API server 已启动");
|
|
636
636
|
return;
|
|
637
637
|
}
|
|
638
|
-
const o =
|
|
638
|
+
const o = q(r.type), i = {};
|
|
639
639
|
for (const h of r.parameters) i[h.name] = h.default;
|
|
640
640
|
i[r.type === "import" ? "module" : "file"] = t;
|
|
641
641
|
const d = {
|
|
@@ -657,7 +657,7 @@ const g = Ie((n, e) => ({
|
|
|
657
657
|
train: async (t = 20, s = 1e-3) => {
|
|
658
658
|
n({ status: "running", losses: [] }), e().log(`开始训练 (${t} 步, lr=${s})...`);
|
|
659
659
|
try {
|
|
660
|
-
const r =
|
|
660
|
+
const r = U(e().nodes, e().edges), o = await fetch("/api/train/stream", {
|
|
661
661
|
method: "POST",
|
|
662
662
|
headers: { "Content-Type": "application/json" },
|
|
663
663
|
body: JSON.stringify({ doc: r, steps: t, lr: s })
|
|
@@ -666,18 +666,18 @@ const g = Ie((n, e) => ({
|
|
|
666
666
|
const i = o.body.getReader(), d = new TextDecoder();
|
|
667
667
|
let h = "";
|
|
668
668
|
for (; ; ) {
|
|
669
|
-
const { done:
|
|
670
|
-
if (
|
|
671
|
-
h += d.decode(
|
|
672
|
-
const
|
|
669
|
+
const { done: m, value: u } = await i.read();
|
|
670
|
+
if (m) break;
|
|
671
|
+
h += d.decode(u, { stream: !0 });
|
|
672
|
+
const b = h.split(`
|
|
673
673
|
`);
|
|
674
|
-
h =
|
|
675
|
-
for (const l of
|
|
676
|
-
const
|
|
677
|
-
if (!
|
|
674
|
+
h = b.pop() ?? "";
|
|
675
|
+
for (const l of b) {
|
|
676
|
+
const v = l.trim();
|
|
677
|
+
if (!v) continue;
|
|
678
678
|
let f;
|
|
679
679
|
try {
|
|
680
|
-
f = JSON.parse(
|
|
680
|
+
f = JSON.parse(v);
|
|
681
681
|
} catch {
|
|
682
682
|
continue;
|
|
683
683
|
}
|
|
@@ -716,7 +716,7 @@ const g = Ie((n, e) => ({
|
|
|
716
716
|
bumpProject: () => n({ projectVersion: e().projectVersion + 1 }),
|
|
717
717
|
setCodeBuffer: (t) => n({ codeBuffer: t }),
|
|
718
718
|
pushToast: (t, s) => {
|
|
719
|
-
const r = ++
|
|
719
|
+
const r = ++rn;
|
|
720
720
|
n({ toasts: [...e().toasts, { id: r, kind: t, message: s }].slice(-6) }), t !== "error" && setTimeout(() => e().dismissToast(r), 6e3);
|
|
721
721
|
},
|
|
722
722
|
dismissToast: (t) => n({ toasts: e().toasts.filter((s) => s.id !== t) }),
|
|
@@ -724,52 +724,52 @@ const g = Ie((n, e) => ({
|
|
|
724
724
|
const s = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
725
725
|
n({ logs: [...e().logs, `[${s}] ${t}`].slice(-80) });
|
|
726
726
|
}
|
|
727
|
-
})),
|
|
728
|
-
function
|
|
727
|
+
})), Ae = "entropia_riko_theme", fe = "entropia_riko_background";
|
|
728
|
+
function pn() {
|
|
729
729
|
try {
|
|
730
|
-
const n = localStorage.getItem(
|
|
730
|
+
const n = localStorage.getItem(Ae);
|
|
731
731
|
return n === "light" || n === "dark" || n === "glass" ? n : "system";
|
|
732
732
|
} catch {
|
|
733
733
|
return "system";
|
|
734
734
|
}
|
|
735
735
|
}
|
|
736
|
-
function
|
|
736
|
+
function Me(n) {
|
|
737
737
|
n === "system" ? delete document.documentElement.dataset.theme : document.documentElement.dataset.theme = n;
|
|
738
738
|
}
|
|
739
|
-
function
|
|
739
|
+
function We(n) {
|
|
740
740
|
const e = document.body;
|
|
741
741
|
n ? (e.style.backgroundImage = `url("${n}")`, e.style.backgroundSize = "cover", e.style.backgroundPosition = "center") : (e.style.backgroundImage = "", e.style.backgroundSize = "", e.style.backgroundPosition = "");
|
|
742
742
|
}
|
|
743
|
-
function
|
|
743
|
+
function un() {
|
|
744
744
|
try {
|
|
745
|
-
return localStorage.getItem(
|
|
745
|
+
return localStorage.getItem(fe) ?? "";
|
|
746
746
|
} catch {
|
|
747
747
|
return "";
|
|
748
748
|
}
|
|
749
749
|
}
|
|
750
|
-
const
|
|
751
|
-
theme:
|
|
750
|
+
const V = $e((n) => ({
|
|
751
|
+
theme: pn(),
|
|
752
752
|
setTheme: (e) => {
|
|
753
|
-
|
|
753
|
+
Me(e);
|
|
754
754
|
try {
|
|
755
|
-
localStorage.setItem(
|
|
755
|
+
localStorage.setItem(Ae, e);
|
|
756
756
|
} catch {
|
|
757
757
|
}
|
|
758
758
|
n({ theme: e });
|
|
759
759
|
},
|
|
760
|
-
backgroundImage:
|
|
760
|
+
backgroundImage: un(),
|
|
761
761
|
setBackgroundImage: (e) => {
|
|
762
|
-
|
|
762
|
+
We(e);
|
|
763
763
|
try {
|
|
764
|
-
e ? localStorage.setItem(
|
|
764
|
+
e ? localStorage.setItem(fe, e) : localStorage.removeItem(fe);
|
|
765
765
|
} catch {
|
|
766
766
|
}
|
|
767
767
|
n({ backgroundImage: e });
|
|
768
768
|
}
|
|
769
769
|
}));
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
function
|
|
770
|
+
Me(V.getState().theme);
|
|
771
|
+
We(V.getState().backgroundImage);
|
|
772
|
+
function re({
|
|
773
773
|
entries: n,
|
|
774
774
|
x: e,
|
|
775
775
|
y: t,
|
|
@@ -777,18 +777,18 @@ function te({
|
|
|
777
777
|
searchable: r = !1,
|
|
778
778
|
maxWidth: o = 280
|
|
779
779
|
}) {
|
|
780
|
-
const [i, d] =
|
|
781
|
-
const l = i.trim().toLowerCase(),
|
|
780
|
+
const [i, d] = E(""), [h, c] = E({}), m = Y(() => {
|
|
781
|
+
const l = i.trim().toLowerCase(), v = l ? n.filter(
|
|
782
782
|
(y) => !y.separator && (y.label.toLowerCase().includes(l) || (y.description ?? "").toLowerCase().includes(l))
|
|
783
|
-
) : n, f = {},
|
|
784
|
-
for (const y of
|
|
785
|
-
const
|
|
786
|
-
|
|
783
|
+
) : n, f = {}, C = [];
|
|
784
|
+
for (const y of v) {
|
|
785
|
+
const F = y.category ?? "";
|
|
786
|
+
F in f || (f[F] = [], C.push(F)), f[F].push(y);
|
|
787
787
|
}
|
|
788
|
-
return { map: f, order:
|
|
789
|
-
}, [n, i]), { map:
|
|
790
|
-
return
|
|
791
|
-
/* @__PURE__ */ p(
|
|
788
|
+
return { map: f, order: C };
|
|
789
|
+
}, [n, i]), { map: u, order: b } = m;
|
|
790
|
+
return Le(
|
|
791
|
+
/* @__PURE__ */ p(K, { children: [
|
|
792
792
|
/* @__PURE__ */ a(
|
|
793
793
|
"div",
|
|
794
794
|
{
|
|
@@ -810,8 +810,8 @@ function te({
|
|
|
810
810
|
}
|
|
811
811
|
) }),
|
|
812
812
|
/* @__PURE__ */ p("div", { className: "popup-list", children: [
|
|
813
|
-
|
|
814
|
-
const
|
|
813
|
+
b.map((l) => {
|
|
814
|
+
const v = !i && !h[l];
|
|
815
815
|
return /* @__PURE__ */ p("div", { className: "popup-category", children: [
|
|
816
816
|
l !== "" && /* @__PURE__ */ p(
|
|
817
817
|
"div",
|
|
@@ -819,20 +819,20 @@ function te({
|
|
|
819
819
|
className: "popup-cat-header",
|
|
820
820
|
onClick: () => c((f) => ({ ...f, [l]: !f[l] })),
|
|
821
821
|
children: [
|
|
822
|
-
/* @__PURE__ */ a("span", { className: "popup-cat-toggle", children:
|
|
822
|
+
/* @__PURE__ */ a("span", { className: "popup-cat-toggle", children: v ? "▸" : "▾" }),
|
|
823
823
|
/* @__PURE__ */ a("span", { className: "popup-cat-label", children: l }),
|
|
824
|
-
/* @__PURE__ */ a("span", { className: "popup-cat-count", children:
|
|
824
|
+
/* @__PURE__ */ a("span", { className: "popup-cat-count", children: u[l].length })
|
|
825
825
|
]
|
|
826
826
|
}
|
|
827
827
|
),
|
|
828
|
-
!
|
|
828
|
+
!v && u[l].map(
|
|
829
829
|
(f) => f.separator ? /* @__PURE__ */ a("div", { className: "popup-sep" }, f.id) : /* @__PURE__ */ p(
|
|
830
830
|
"div",
|
|
831
831
|
{
|
|
832
832
|
className: `popup-item ${f.danger ? "danger" : ""} ${f.disabled ? "disabled" : ""}`,
|
|
833
833
|
onClick: () => {
|
|
834
|
-
var
|
|
835
|
-
f.disabled || ((
|
|
834
|
+
var C;
|
|
835
|
+
f.disabled || ((C = f.onSelect) == null || C.call(f), s());
|
|
836
836
|
},
|
|
837
837
|
children: [
|
|
838
838
|
/* @__PURE__ */ a("span", { className: "popup-item-label", children: f.label }),
|
|
@@ -844,169 +844,169 @@ function te({
|
|
|
844
844
|
)
|
|
845
845
|
] }, l);
|
|
846
846
|
}),
|
|
847
|
-
|
|
847
|
+
b.length === 0 && /* @__PURE__ */ a("div", { className: "popup-item disabled", children: "No matches" })
|
|
848
848
|
] })
|
|
849
849
|
] })
|
|
850
850
|
] }),
|
|
851
851
|
document.body
|
|
852
852
|
);
|
|
853
853
|
}
|
|
854
|
-
let
|
|
855
|
-
const
|
|
854
|
+
let mn = 0;
|
|
855
|
+
const Be = () => `area_${++mn}`, L = (n) => ({
|
|
856
856
|
kind: "leaf",
|
|
857
|
-
id:
|
|
857
|
+
id: Be(),
|
|
858
858
|
type: n
|
|
859
|
-
}),
|
|
860
|
-
function
|
|
861
|
-
const n =
|
|
859
|
+
}), M = (n, e, t, s = 0.5) => ({ kind: "split", id: Be(), direction: n, first: e, second: t, ratio: s });
|
|
860
|
+
function hn() {
|
|
861
|
+
const n = M("column", L("nodes"), L("files"), 0.72), e = M(
|
|
862
862
|
"row",
|
|
863
|
-
|
|
864
|
-
|
|
863
|
+
L("canvas"),
|
|
864
|
+
M("column", L("project"), L("inspector"), 0.3),
|
|
865
865
|
0.68
|
|
866
|
-
), t =
|
|
867
|
-
return
|
|
866
|
+
), t = M("row", n, e, 0.26), s = M("row", L("status"), L("loss"), 0.55);
|
|
867
|
+
return M("column", t, s, 0.82);
|
|
868
868
|
}
|
|
869
|
-
function
|
|
870
|
-
const e = n.leftExtra ?
|
|
871
|
-
let t =
|
|
872
|
-
n.rightTop && (t =
|
|
873
|
-
const s =
|
|
874
|
-
return
|
|
869
|
+
function H(n) {
|
|
870
|
+
const e = n.leftExtra ? M("column", L("nodes"), L(n.leftExtra), 0.7) : L("nodes");
|
|
871
|
+
let t = L(n.right);
|
|
872
|
+
n.rightTop && (t = M("column", L(n.rightTop), t, n.rightTopRatio ?? 0.28));
|
|
873
|
+
const s = M("row", L("canvas"), t, n.canvasRatio ?? 0.68), r = M("row", e, s, n.leftRatio ?? 0.26), o = n.bottom.length === 1 ? L(n.bottom[0]) : M("row", L(n.bottom[0]), L(n.bottom[1]), 0.55);
|
|
874
|
+
return M("column", r, o, n.colRatio ?? 0.84);
|
|
875
875
|
}
|
|
876
|
-
const
|
|
876
|
+
const ge = [
|
|
877
877
|
{
|
|
878
878
|
id: "layout",
|
|
879
879
|
label: "Layout",
|
|
880
880
|
category: "General",
|
|
881
881
|
description: "Full IDE: nodes, asset library, code editor, canvas, inspector.",
|
|
882
|
-
build:
|
|
882
|
+
build: hn
|
|
883
883
|
},
|
|
884
884
|
{
|
|
885
885
|
id: "code",
|
|
886
886
|
label: "Code",
|
|
887
887
|
category: "General",
|
|
888
888
|
description: "Big code editor + canvas for previewing exported PyTorch.",
|
|
889
|
-
build: () =>
|
|
889
|
+
build: () => H({ right: "code", bottom: ["status", "loss"], leftExtra: "files", canvasRatio: 0.6 })
|
|
890
890
|
},
|
|
891
891
|
{
|
|
892
892
|
id: "inference",
|
|
893
893
|
label: "Inference",
|
|
894
894
|
category: "General",
|
|
895
895
|
description: "Large canvas + inspector, no training.",
|
|
896
|
-
build: () =>
|
|
896
|
+
build: () => H({ right: "inspector", bottom: ["status", "docs"], leftExtra: "files", canvasRatio: 0.74 })
|
|
897
897
|
},
|
|
898
898
|
{
|
|
899
899
|
id: "training",
|
|
900
900
|
label: "Train",
|
|
901
901
|
category: "Training",
|
|
902
902
|
description: "Live loss curve + inspector for training runs.",
|
|
903
|
-
build: () =>
|
|
903
|
+
build: () => H({ right: "loss", bottom: ["status", "inspector"], leftExtra: "files", canvasRatio: 0.6 })
|
|
904
904
|
},
|
|
905
905
|
{
|
|
906
906
|
id: "tuning",
|
|
907
907
|
label: "Hyperparameter Tuning",
|
|
908
908
|
category: "Training",
|
|
909
909
|
description: "Loss + docs; inspector below.",
|
|
910
|
-
build: () =>
|
|
910
|
+
build: () => H({ right: "loss", bottom: ["inspector", "docs"], canvasRatio: 0.62 })
|
|
911
911
|
},
|
|
912
912
|
{
|
|
913
913
|
id: "mnist_studio",
|
|
914
914
|
label: "MNIST Studio",
|
|
915
915
|
category: "Vision",
|
|
916
916
|
description: "Handwriting pad above the inspector — draw a digit and infer.",
|
|
917
|
-
build: () =>
|
|
917
|
+
build: () => H({ right: "inspector", rightTop: "pad", bottom: ["status", "loss"], leftExtra: "files", canvasRatio: 0.58 })
|
|
918
918
|
},
|
|
919
919
|
{
|
|
920
920
|
id: "image_classifier",
|
|
921
921
|
label: "Image Classifier",
|
|
922
922
|
category: "Vision",
|
|
923
923
|
description: "Image previews + loss.",
|
|
924
|
-
build: () =>
|
|
924
|
+
build: () => H({ right: "inspector", bottom: ["status", "loss"], leftExtra: "files", canvasRatio: 0.68 })
|
|
925
925
|
},
|
|
926
926
|
{
|
|
927
927
|
id: "image_gen",
|
|
928
928
|
label: "Text → Image",
|
|
929
929
|
category: "Vision",
|
|
930
930
|
description: "Project explorer above inspector for Diffusers generation.",
|
|
931
|
-
build: () =>
|
|
931
|
+
build: () => H({ right: "inspector", rightTop: "project", bottom: ["status"], canvasRatio: 0.62 })
|
|
932
932
|
},
|
|
933
933
|
{
|
|
934
934
|
id: "detection",
|
|
935
935
|
label: "Object Detection",
|
|
936
936
|
category: "Vision",
|
|
937
937
|
description: "Canvas + inspector + docs.",
|
|
938
|
-
build: () =>
|
|
938
|
+
build: () => H({ right: "inspector", bottom: ["status", "docs"], leftExtra: "files", canvasRatio: 0.7 })
|
|
939
939
|
},
|
|
940
940
|
{
|
|
941
941
|
id: "text_classifier",
|
|
942
942
|
label: "Text Classifier",
|
|
943
943
|
category: "Language",
|
|
944
944
|
description: "Text previews + docs.",
|
|
945
|
-
build: () =>
|
|
945
|
+
build: () => H({ right: "inspector", bottom: ["status", "docs"], leftExtra: "files", canvasRatio: 0.7 })
|
|
946
946
|
},
|
|
947
947
|
{
|
|
948
948
|
id: "text_gen",
|
|
949
949
|
label: "Text Gen",
|
|
950
950
|
category: "Language",
|
|
951
951
|
description: "Text preview for Transformers.",
|
|
952
|
-
build: () =>
|
|
952
|
+
build: () => H({ right: "inspector", bottom: ["status"], rightTop: "project", canvasRatio: 0.68 })
|
|
953
953
|
},
|
|
954
954
|
{
|
|
955
955
|
id: "embeddings",
|
|
956
956
|
label: "Embeddings / JEPA",
|
|
957
957
|
category: "Language",
|
|
958
958
|
description: "Pretrained encoder embeddings.",
|
|
959
|
-
build: () =>
|
|
959
|
+
build: () => H({ right: "inspector", bottom: ["status", "docs"], canvasRatio: 0.72 })
|
|
960
960
|
}
|
|
961
|
-
],
|
|
962
|
-
function
|
|
963
|
-
return n.kind === "leaf" ? n.id === e ?
|
|
961
|
+
], fn = (n, e, t) => Math.min(t, Math.max(e, n));
|
|
962
|
+
function oe(n, e, t) {
|
|
963
|
+
return n.kind === "leaf" ? n.id === e ? M(t, n, L(n.type), 0.5) : n : {
|
|
964
964
|
...n,
|
|
965
|
-
first:
|
|
966
|
-
second:
|
|
965
|
+
first: oe(n.first, e, t),
|
|
966
|
+
second: oe(n.second, e, t)
|
|
967
967
|
};
|
|
968
968
|
}
|
|
969
|
-
function
|
|
969
|
+
function ye(n, e) {
|
|
970
970
|
return n.kind === "split" ? n.first.id === e ? n.second : n.second.id === e ? n.first : {
|
|
971
971
|
...n,
|
|
972
|
-
first:
|
|
973
|
-
second:
|
|
972
|
+
first: ye(n.first, e),
|
|
973
|
+
second: ye(n.second, e)
|
|
974
974
|
} : n;
|
|
975
975
|
}
|
|
976
|
-
function
|
|
976
|
+
function be(n, e) {
|
|
977
977
|
return n.kind === "split" ? n.first.id === e ? n.first : n.second.id === e ? n.second : {
|
|
978
978
|
...n,
|
|
979
|
-
first:
|
|
980
|
-
second:
|
|
979
|
+
first: be(n.first, e),
|
|
980
|
+
second: be(n.second, e)
|
|
981
981
|
} : n;
|
|
982
982
|
}
|
|
983
|
-
function
|
|
983
|
+
function ve(n, e) {
|
|
984
984
|
if (n.kind === "split") {
|
|
985
985
|
if (n.first.id === e) return n.second.id;
|
|
986
986
|
if (n.second.id === e) return n.first.id;
|
|
987
|
-
const t =
|
|
988
|
-
return t !== null ? t :
|
|
987
|
+
const t = ve(n.first, e);
|
|
988
|
+
return t !== null ? t : ve(n.second, e);
|
|
989
989
|
}
|
|
990
990
|
return null;
|
|
991
991
|
}
|
|
992
|
-
function
|
|
993
|
-
return n.kind === "split" ? n.id === e ? { ...n, ratio:
|
|
992
|
+
function ie(n, e, t) {
|
|
993
|
+
return n.kind === "split" ? n.id === e ? { ...n, ratio: fn(n.ratio + t, 0.08, 0.92) } : {
|
|
994
994
|
...n,
|
|
995
|
-
first:
|
|
996
|
-
second:
|
|
995
|
+
first: ie(n.first, e, t),
|
|
996
|
+
second: ie(n.second, e, t)
|
|
997
997
|
} : n;
|
|
998
998
|
}
|
|
999
|
-
function
|
|
999
|
+
function Ne(n, e, t) {
|
|
1000
1000
|
return n.kind === "leaf" ? n.id === e ? { ...n, type: t } : n : {
|
|
1001
1001
|
...n,
|
|
1002
|
-
first:
|
|
1003
|
-
second:
|
|
1002
|
+
first: Ne(n.first, e, t),
|
|
1003
|
+
second: Ne(n.second, e, t)
|
|
1004
1004
|
};
|
|
1005
1005
|
}
|
|
1006
|
-
function
|
|
1007
|
-
return n.kind === "leaf" ? 1 :
|
|
1006
|
+
function ke(n) {
|
|
1007
|
+
return n.kind === "leaf" ? 1 : ke(n.first) + ke(n.second);
|
|
1008
1008
|
}
|
|
1009
|
-
function
|
|
1009
|
+
function gn({
|
|
1010
1010
|
workspaces: n,
|
|
1011
1011
|
activeId: e,
|
|
1012
1012
|
onSwitch: t,
|
|
@@ -1014,79 +1014,91 @@ function fn({
|
|
|
1014
1014
|
onRemove: r,
|
|
1015
1015
|
onRename: o,
|
|
1016
1016
|
onDuplicate: i,
|
|
1017
|
-
onMove: d
|
|
1017
|
+
onMove: d,
|
|
1018
|
+
onReorder: h
|
|
1018
1019
|
}) {
|
|
1019
|
-
const [
|
|
1020
|
-
|
|
1021
|
-
const
|
|
1022
|
-
if (!
|
|
1023
|
-
const
|
|
1024
|
-
return
|
|
1020
|
+
const [c, m] = E(!1), [u, b] = E(null), [l, v] = E(null), [f, C] = E(!1), [y, F] = E(null), x = D(null);
|
|
1021
|
+
W(() => {
|
|
1022
|
+
const T = x.current;
|
|
1023
|
+
if (!T) return;
|
|
1024
|
+
const R = () => C(T.scrollWidth > T.clientWidth + 1);
|
|
1025
|
+
return R(), window.addEventListener("resize", R), () => window.removeEventListener("resize", R);
|
|
1025
1026
|
}, [n]);
|
|
1026
|
-
const
|
|
1027
|
-
() =>
|
|
1028
|
-
id:
|
|
1029
|
-
label:
|
|
1030
|
-
description:
|
|
1031
|
-
category:
|
|
1032
|
-
onSelect: () => s(
|
|
1027
|
+
const O = n.length > 1, $ = Y(
|
|
1028
|
+
() => ge.map((T) => ({
|
|
1029
|
+
id: T.id,
|
|
1030
|
+
label: T.label,
|
|
1031
|
+
description: T.description,
|
|
1032
|
+
category: T.category,
|
|
1033
|
+
onSelect: () => s(T.id)
|
|
1033
1034
|
})),
|
|
1034
1035
|
[s]
|
|
1035
|
-
),
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
},
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
},
|
|
1042
|
-
{ id: "rename", label: "Rename", onSelect: () =>
|
|
1043
|
-
{ id: "duplicate", label: "Duplicate", onSelect: () => i(
|
|
1036
|
+
), w = (T) => {
|
|
1037
|
+
const R = T.currentTarget.getBoundingClientRect();
|
|
1038
|
+
b({ x: R.left, y: R.bottom + 6 }), m(!0);
|
|
1039
|
+
}, _ = (T) => {
|
|
1040
|
+
const R = n.find((z) => z.id === T), B = window.prompt("Rename workspace", (R == null ? void 0 : R.name) ?? "");
|
|
1041
|
+
B && B.trim() && o(T, B.trim());
|
|
1042
|
+
}, k = (T) => [
|
|
1043
|
+
{ id: "rename", label: "Rename", onSelect: () => _(T) },
|
|
1044
|
+
{ id: "duplicate", label: "Duplicate", onSelect: () => i(T) },
|
|
1044
1045
|
{ id: "sep1", label: "", separator: !0 },
|
|
1045
|
-
{ id: "left", label: "← Move Left", onSelect: () => d(
|
|
1046
|
-
{ id: "right", label: "Move Right →", onSelect: () => d(
|
|
1046
|
+
{ id: "left", label: "← Move Left", onSelect: () => d(T, -1) },
|
|
1047
|
+
{ id: "right", label: "Move Right →", onSelect: () => d(T, 1) },
|
|
1047
1048
|
{ id: "sep2", label: "", separator: !0 },
|
|
1048
|
-
...
|
|
1049
|
+
...O ? [{ id: "close", label: "Close", danger: !0, onSelect: () => r(T) }] : []
|
|
1049
1050
|
];
|
|
1050
1051
|
return /* @__PURE__ */ p("div", { className: "workspace-tabs-wrap", children: [
|
|
1051
|
-
/* @__PURE__ */ a("div", { ref: x, className: `workspace-tabs ${
|
|
1052
|
+
/* @__PURE__ */ a("div", { ref: x, className: `workspace-tabs ${f ? "overflowing" : ""}`, children: n.map((T) => /* @__PURE__ */ p(
|
|
1052
1053
|
"div",
|
|
1053
1054
|
{
|
|
1054
|
-
className: `workspace-tab ${
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1055
|
+
className: `workspace-tab ${T.id === e ? "active" : ""} ${T.id === y ? "dragging" : ""}`,
|
|
1056
|
+
draggable: !0,
|
|
1057
|
+
onDragStart: (R) => {
|
|
1058
|
+
F(T.id), R.dataTransfer.effectAllowed = "move";
|
|
1059
|
+
},
|
|
1060
|
+
onDragOver: (R) => {
|
|
1061
|
+
R.preventDefault(), R.dataTransfer.dropEffect = "move";
|
|
1062
|
+
},
|
|
1063
|
+
onDrop: (R) => {
|
|
1064
|
+
R.preventDefault(), y && y !== T.id && h(y, T.id), F(null);
|
|
1065
|
+
},
|
|
1066
|
+
onDragEnd: () => F(null),
|
|
1067
|
+
onClick: () => t(T.id),
|
|
1068
|
+
onContextMenu: (R) => {
|
|
1069
|
+
R.preventDefault(), v({ id: T.id, x: R.clientX, y: R.clientY });
|
|
1058
1070
|
},
|
|
1059
1071
|
children: [
|
|
1060
|
-
/* @__PURE__ */ a("span", { children:
|
|
1061
|
-
|
|
1072
|
+
/* @__PURE__ */ a("span", { children: T.name }),
|
|
1073
|
+
O && /* @__PURE__ */ a(
|
|
1062
1074
|
"span",
|
|
1063
1075
|
{
|
|
1064
1076
|
className: "workspace-tab-close",
|
|
1065
1077
|
title: "Close workspace",
|
|
1066
|
-
onClick: (
|
|
1067
|
-
|
|
1078
|
+
onClick: (R) => {
|
|
1079
|
+
R.stopPropagation(), r(T.id);
|
|
1068
1080
|
},
|
|
1069
1081
|
children: "✕"
|
|
1070
1082
|
}
|
|
1071
1083
|
)
|
|
1072
1084
|
]
|
|
1073
1085
|
},
|
|
1074
|
-
|
|
1086
|
+
T.id
|
|
1075
1087
|
)) }),
|
|
1076
|
-
/* @__PURE__ */ a("button", { className: "workspace-add", title: "Add workspace", onClick:
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1088
|
+
/* @__PURE__ */ a("button", { className: "workspace-add", title: "Add workspace", onClick: w, children: "+" }),
|
|
1089
|
+
c && u && /* @__PURE__ */ a(re, { entries: $, x: u.x, y: u.y, onClose: () => m(!1) }),
|
|
1090
|
+
l && /* @__PURE__ */ a(
|
|
1091
|
+
re,
|
|
1080
1092
|
{
|
|
1081
|
-
entries:
|
|
1082
|
-
x:
|
|
1083
|
-
y:
|
|
1084
|
-
onClose: () =>
|
|
1093
|
+
entries: k(l.id),
|
|
1094
|
+
x: l.x,
|
|
1095
|
+
y: l.y,
|
|
1096
|
+
onClose: () => v(null)
|
|
1085
1097
|
}
|
|
1086
1098
|
)
|
|
1087
1099
|
] });
|
|
1088
1100
|
}
|
|
1089
|
-
function
|
|
1101
|
+
function ce({
|
|
1090
1102
|
title: n,
|
|
1091
1103
|
onClose: e,
|
|
1092
1104
|
x: t = 140,
|
|
@@ -1095,60 +1107,60 @@ function oe({
|
|
|
1095
1107
|
zIndex: o = 1e3,
|
|
1096
1108
|
children: i
|
|
1097
1109
|
}) {
|
|
1098
|
-
const [d, h] =
|
|
1099
|
-
c.current = { dx:
|
|
1100
|
-
const
|
|
1101
|
-
c.current && h({ x:
|
|
1110
|
+
const [d, h] = E({ x: t, y: s }), c = D(null), m = (u) => {
|
|
1111
|
+
c.current = { dx: u.clientX - d.x, dy: u.clientY - d.y };
|
|
1112
|
+
const b = (v) => {
|
|
1113
|
+
c.current && h({ x: v.clientX - c.current.dx, y: v.clientY - c.current.dy });
|
|
1102
1114
|
}, l = () => {
|
|
1103
|
-
c.current = null, document.removeEventListener("mousemove",
|
|
1115
|
+
c.current = null, document.removeEventListener("mousemove", b), document.removeEventListener("mouseup", l);
|
|
1104
1116
|
};
|
|
1105
|
-
document.addEventListener("mousemove",
|
|
1117
|
+
document.addEventListener("mousemove", b), document.addEventListener("mouseup", l);
|
|
1106
1118
|
};
|
|
1107
1119
|
return /* @__PURE__ */ p("div", { className: "float-window", style: { left: d.x, top: d.y, width: r, zIndex: o }, children: [
|
|
1108
|
-
/* @__PURE__ */ p("div", { className: "float-titlebar", onMouseDown:
|
|
1120
|
+
/* @__PURE__ */ p("div", { className: "float-titlebar", onMouseDown: m, children: [
|
|
1109
1121
|
/* @__PURE__ */ a("span", { className: "float-title", children: n }),
|
|
1110
1122
|
/* @__PURE__ */ a("button", { className: "float-close", onClick: e, children: "✕" })
|
|
1111
1123
|
] }),
|
|
1112
1124
|
/* @__PURE__ */ a("div", { className: "float-body", children: i })
|
|
1113
1125
|
] });
|
|
1114
1126
|
}
|
|
1115
|
-
let
|
|
1116
|
-
function
|
|
1117
|
-
return
|
|
1118
|
-
|
|
1127
|
+
let ne = null, Ge = "";
|
|
1128
|
+
function Q(n, e) {
|
|
1129
|
+
return Ge = (e == null ? void 0 : e.defaultName) ?? "", g.getState().setFilePicker(!0, n), new Promise((t) => {
|
|
1130
|
+
ne = t;
|
|
1119
1131
|
});
|
|
1120
1132
|
}
|
|
1121
|
-
function
|
|
1122
|
-
g.getState().setFilePicker(!1, "import"),
|
|
1133
|
+
function ee(n) {
|
|
1134
|
+
g.getState().setFilePicker(!1, "import"), ne == null || ne(n), ne = null;
|
|
1123
1135
|
}
|
|
1124
|
-
const
|
|
1125
|
-
function
|
|
1136
|
+
const Ue = "entropia_riko_recent_folders";
|
|
1137
|
+
function we() {
|
|
1126
1138
|
try {
|
|
1127
|
-
const n = localStorage.getItem(
|
|
1139
|
+
const n = localStorage.getItem(Ue), e = n ? JSON.parse(n) : [];
|
|
1128
1140
|
return Array.isArray(e) ? e.filter((t) => typeof t == "string") : [];
|
|
1129
1141
|
} catch {
|
|
1130
1142
|
return [];
|
|
1131
1143
|
}
|
|
1132
1144
|
}
|
|
1133
|
-
function
|
|
1145
|
+
function yn(n) {
|
|
1134
1146
|
try {
|
|
1135
|
-
const e = [n, ...
|
|
1136
|
-
localStorage.setItem(
|
|
1147
|
+
const e = [n, ...we().filter((t) => t !== n)].slice(0, 8);
|
|
1148
|
+
localStorage.setItem(Ue, JSON.stringify(e));
|
|
1137
1149
|
} catch {
|
|
1138
1150
|
}
|
|
1139
1151
|
}
|
|
1140
|
-
function
|
|
1141
|
-
const n = g((k) => k.filePickerOpen), e = g((k) => k.filePickerMode), t = g((k) => k.pushToast), [s, r] =
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1152
|
+
function bn() {
|
|
1153
|
+
const n = g((k) => k.filePickerOpen), e = g((k) => k.filePickerMode), t = g((k) => k.pushToast), [s, r] = E("~"), [o, i] = E([]), [d, h] = E(null), [c, m] = E("~"), [u, b] = E(we()), [l, v] = E([]), [f, C] = E([]), [y, F] = E(""), x = async (k, T = !0) => {
|
|
1154
|
+
const B = await (await fetch(`/api/fs/list?path=${encodeURIComponent(k)}`)).json();
|
|
1155
|
+
B.status === "success" ? (T && (v((z) => [...z, s]), C([])), r(B.path), i(B.entries ?? []), h(null), yn(B.path), b(we())) : t("error", B.error ?? "list failed");
|
|
1144
1156
|
};
|
|
1145
|
-
if (
|
|
1146
|
-
n && (e === "save" &&
|
|
1147
|
-
typeof k.root == "string" &&
|
|
1157
|
+
if (W(() => {
|
|
1158
|
+
n && (e === "save" && F(Ge), x("~", !1), fetch("/api/project/tree").then((k) => k.json()).then((k) => {
|
|
1159
|
+
typeof k.root == "string" && m(k.root);
|
|
1148
1160
|
}).catch(() => {
|
|
1149
1161
|
}));
|
|
1150
1162
|
}, [n]), !n) return null;
|
|
1151
|
-
const
|
|
1163
|
+
const O = [
|
|
1152
1164
|
{ label: "Home", path: "~" },
|
|
1153
1165
|
{ label: "Desktop", path: "~/Desktop" },
|
|
1154
1166
|
{ label: "Documents", path: "~/Documents" },
|
|
@@ -1156,10 +1168,10 @@ function yn() {
|
|
|
1156
1168
|
{ label: "Working Folder", path: c }
|
|
1157
1169
|
], $ = () => {
|
|
1158
1170
|
const k = l[l.length - 1];
|
|
1159
|
-
k && (
|
|
1160
|
-
},
|
|
1171
|
+
k && (v(l.slice(0, -1)), C((T) => [s, ...T]), x(k, !1));
|
|
1172
|
+
}, w = () => {
|
|
1161
1173
|
const k = f[0];
|
|
1162
|
-
k && (
|
|
1174
|
+
k && (C(f.slice(1)), v((T) => [...T, s]), x(k, !1));
|
|
1163
1175
|
}, _ = () => {
|
|
1164
1176
|
if (e === "save") {
|
|
1165
1177
|
const k = y.trim() || "untitled";
|
|
@@ -1168,21 +1180,21 @@ function yn() {
|
|
|
1168
1180
|
return e === "export" ? d && d.type === "dir" ? d.path : s : d ? d.path : s;
|
|
1169
1181
|
};
|
|
1170
1182
|
return /* @__PURE__ */ a(
|
|
1171
|
-
|
|
1183
|
+
ce,
|
|
1172
1184
|
{
|
|
1173
1185
|
title: e === "import" ? "Import File / Folder" : e === "export" ? "Export To Folder" : "Save File",
|
|
1174
|
-
onClose: () =>
|
|
1186
|
+
onClose: () => ee(null),
|
|
1175
1187
|
width: 780,
|
|
1176
1188
|
zIndex: 1002,
|
|
1177
1189
|
children: /* @__PURE__ */ p("div", { className: "picker-inner", children: [
|
|
1178
1190
|
/* @__PURE__ */ p("div", { className: "picker-side", children: [
|
|
1179
1191
|
/* @__PURE__ */ a("div", { className: "picker-side-title", children: "Quick access" }),
|
|
1180
|
-
|
|
1192
|
+
O.map((k) => /* @__PURE__ */ p("div", { className: "picker-side-item", onClick: () => x(k.path), children: [
|
|
1181
1193
|
/* @__PURE__ */ a("span", { className: "picker-side-icon", children: "▦" }),
|
|
1182
1194
|
k.label
|
|
1183
1195
|
] }, k.label)),
|
|
1184
1196
|
/* @__PURE__ */ a("div", { className: "picker-side-title", style: { marginTop: 12 }, children: "Recent" }),
|
|
1185
|
-
|
|
1197
|
+
u.map((k) => /* @__PURE__ */ p("div", { className: "picker-side-item", title: k, onClick: () => x(k), children: [
|
|
1186
1198
|
/* @__PURE__ */ a("span", { className: "picker-side-icon", children: "🕘" }),
|
|
1187
1199
|
/* @__PURE__ */ a("span", { className: "picker-side-name", children: k })
|
|
1188
1200
|
] }, k))
|
|
@@ -1190,8 +1202,8 @@ function yn() {
|
|
|
1190
1202
|
/* @__PURE__ */ p("div", { className: "picker-main", children: [
|
|
1191
1203
|
/* @__PURE__ */ p("div", { className: "picker-topbar", children: [
|
|
1192
1204
|
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: $, title: "Back", children: "←" }),
|
|
1193
|
-
/* @__PURE__ */ a("button", { className: "panel-btn", onClick:
|
|
1194
|
-
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () =>
|
|
1205
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: w, title: "Forward", children: "→" }),
|
|
1206
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () => x(s, !1), title: "Refresh", children: "↻" }),
|
|
1195
1207
|
/* @__PURE__ */ a(
|
|
1196
1208
|
"input",
|
|
1197
1209
|
{
|
|
@@ -1199,7 +1211,7 @@ function yn() {
|
|
|
1199
1211
|
value: s,
|
|
1200
1212
|
onChange: (k) => r(k.target.value),
|
|
1201
1213
|
onKeyDown: (k) => {
|
|
1202
|
-
k.key === "Enter" &&
|
|
1214
|
+
k.key === "Enter" && x(s);
|
|
1203
1215
|
}
|
|
1204
1216
|
}
|
|
1205
1217
|
)
|
|
@@ -1210,7 +1222,7 @@ function yn() {
|
|
|
1210
1222
|
{
|
|
1211
1223
|
className: `picker-row ${(d == null ? void 0 : d.path) === k.path ? "sel" : ""}`,
|
|
1212
1224
|
onClick: () => h(k),
|
|
1213
|
-
onDoubleClick: () => k.type === "dir" &&
|
|
1225
|
+
onDoubleClick: () => k.type === "dir" && x(k.path),
|
|
1214
1226
|
children: [
|
|
1215
1227
|
/* @__PURE__ */ a("span", { className: "explorer-icon", children: k.type === "dir" ? "▦" : "▤" }),
|
|
1216
1228
|
/* @__PURE__ */ a("span", { className: "explorer-name", children: k.name }),
|
|
@@ -1229,42 +1241,42 @@ function yn() {
|
|
|
1229
1241
|
className: "prefs-input",
|
|
1230
1242
|
value: e === "save" ? y : (d == null ? void 0 : d.name) ?? "",
|
|
1231
1243
|
onChange: (k) => {
|
|
1232
|
-
e === "save" &&
|
|
1244
|
+
e === "save" && F(k.target.value);
|
|
1233
1245
|
},
|
|
1234
1246
|
onKeyDown: (k) => {
|
|
1235
|
-
k.key === "Enter" &&
|
|
1247
|
+
k.key === "Enter" && ee(_()), k.key === "Escape" && ee(null);
|
|
1236
1248
|
},
|
|
1237
1249
|
readOnly: e !== "save",
|
|
1238
1250
|
placeholder: e === "save" ? "name.riko / name.ric" : "(select a file or folder)"
|
|
1239
1251
|
}
|
|
1240
1252
|
),
|
|
1241
|
-
/* @__PURE__ */ a("button", { className: "btn btn-primary", onClick: () =>
|
|
1242
|
-
/* @__PURE__ */ a("button", { className: "btn", onClick: () =>
|
|
1253
|
+
/* @__PURE__ */ a("button", { className: "btn btn-primary", onClick: () => ee(_()), children: "Save" }),
|
|
1254
|
+
/* @__PURE__ */ a("button", { className: "btn", onClick: () => ee(null), children: "Cancel" })
|
|
1243
1255
|
] })
|
|
1244
1256
|
] })
|
|
1245
1257
|
] })
|
|
1246
1258
|
}
|
|
1247
1259
|
);
|
|
1248
1260
|
}
|
|
1249
|
-
const
|
|
1250
|
-
function
|
|
1261
|
+
const Ce = ["/brand/logo.ico", "/brand/logo.png", "/brand/logo.svg"];
|
|
1262
|
+
function de({
|
|
1251
1263
|
className: n,
|
|
1252
1264
|
alt: e = "Entropia Riko logo",
|
|
1253
1265
|
title: t
|
|
1254
1266
|
}) {
|
|
1255
|
-
const [s, r] =
|
|
1267
|
+
const [s, r] = E(0);
|
|
1256
1268
|
return /* @__PURE__ */ a(
|
|
1257
1269
|
"img",
|
|
1258
1270
|
{
|
|
1259
1271
|
className: n,
|
|
1260
|
-
src:
|
|
1272
|
+
src: Ce[s],
|
|
1261
1273
|
alt: e,
|
|
1262
1274
|
title: t,
|
|
1263
|
-
onError: () => r((o) => Math.min(o + 1,
|
|
1275
|
+
onError: () => r((o) => Math.min(o + 1, Ce.length - 1))
|
|
1264
1276
|
}
|
|
1265
1277
|
);
|
|
1266
1278
|
}
|
|
1267
|
-
function
|
|
1279
|
+
function vn({
|
|
1268
1280
|
workspaces: n,
|
|
1269
1281
|
activeWorkspaceId: e,
|
|
1270
1282
|
onSwitchWorkspace: t,
|
|
@@ -1272,53 +1284,54 @@ function bn({
|
|
|
1272
1284
|
onRemoveWorkspace: r,
|
|
1273
1285
|
onRenameWorkspace: o,
|
|
1274
1286
|
onDuplicateWorkspace: i,
|
|
1275
|
-
onMoveWorkspace: d
|
|
1287
|
+
onMoveWorkspace: d,
|
|
1288
|
+
onReorderWorkspace: h
|
|
1276
1289
|
}) {
|
|
1277
|
-
const [
|
|
1278
|
-
g((
|
|
1279
|
-
const
|
|
1280
|
-
const
|
|
1281
|
-
if (!
|
|
1282
|
-
const
|
|
1290
|
+
const [c, m] = E(null), u = g((P) => P.status), b = g((P) => P.run), l = g((P) => P.train);
|
|
1291
|
+
g((P) => P.save), g((P) => P.exportRiko), g((P) => P.exportBinary);
|
|
1292
|
+
const v = g((P) => P.load), f = g((P) => P.exportPython), C = g((P) => P.exportKeras), y = g((P) => P.newWorkflow), F = g((P) => P.addNode), x = g((P) => P.pushToast), O = V((P) => P.theme), $ = V((P) => P.setTheme), w = D(null), _ = u === "running", k = async () => {
|
|
1293
|
+
const P = await Q("import");
|
|
1294
|
+
if (!P) return;
|
|
1295
|
+
const I = await (await fetch("/api/fs/import", {
|
|
1283
1296
|
method: "POST",
|
|
1284
1297
|
headers: { "Content-Type": "application/json" },
|
|
1285
|
-
body: JSON.stringify({ src:
|
|
1298
|
+
body: JSON.stringify({ src: P })
|
|
1286
1299
|
})).json();
|
|
1287
|
-
|
|
1288
|
-
},
|
|
1289
|
-
const
|
|
1290
|
-
if (!
|
|
1291
|
-
|
|
1300
|
+
I.status === "success" ? (x("success", `Imported ${I.path}`), g.getState().bumpProject()) : x("error", I.error ?? "import failed");
|
|
1301
|
+
}, T = async () => {
|
|
1302
|
+
const P = g.getState().activeFilePath;
|
|
1303
|
+
if (!P) {
|
|
1304
|
+
x("error", "No open file to export");
|
|
1292
1305
|
return;
|
|
1293
1306
|
}
|
|
1294
|
-
const
|
|
1295
|
-
if (!
|
|
1296
|
-
const
|
|
1307
|
+
const N = await Q("export");
|
|
1308
|
+
if (!N) return;
|
|
1309
|
+
const S = await (await fetch("/api/fs/export", {
|
|
1297
1310
|
method: "POST",
|
|
1298
1311
|
headers: { "Content-Type": "application/json" },
|
|
1299
|
-
body: JSON.stringify({ src:
|
|
1312
|
+
body: JSON.stringify({ src: P, dest: N })
|
|
1300
1313
|
})).json();
|
|
1301
|
-
|
|
1302
|
-
},
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1314
|
+
S.status === "success" ? x("success", `Exported to ${S.path}`) : x("error", S.error ?? "export failed");
|
|
1315
|
+
}, R = async (P) => {
|
|
1316
|
+
const I = (g.getState().activeFileName ?? "untitled") + (P === "binary" ? ".ric" : ".riko"), S = await Q("save", { defaultName: I });
|
|
1317
|
+
S && await g.getState().saveToPath(S, P);
|
|
1305
1318
|
};
|
|
1306
|
-
return /* @__PURE__ */ p("div", { className: "menubar", onClick: (
|
|
1319
|
+
return /* @__PURE__ */ p("div", { className: "menubar", onClick: (P) => P.stopPropagation(), children: [
|
|
1307
1320
|
/* @__PURE__ */ p(
|
|
1308
1321
|
"div",
|
|
1309
1322
|
{
|
|
1310
|
-
className: `menu-item app-logo-item ${
|
|
1311
|
-
onClick: () => c
|
|
1323
|
+
className: `menu-item app-logo-item ${c === "__app__" ? "active" : ""}`,
|
|
1324
|
+
onClick: () => m(c === "__app__" ? null : "__app__"),
|
|
1312
1325
|
title: "Entropia Riko",
|
|
1313
1326
|
children: [
|
|
1314
|
-
/* @__PURE__ */ a(
|
|
1315
|
-
|
|
1327
|
+
/* @__PURE__ */ a(de, { className: "menubar-logo", alt: "Entropia Riko" }),
|
|
1328
|
+
c === "__app__" && /* @__PURE__ */ p("div", { className: "dropdown app-menu", onClick: (P) => P.stopPropagation(), children: [
|
|
1316
1329
|
/* @__PURE__ */ a(
|
|
1317
1330
|
"div",
|
|
1318
1331
|
{
|
|
1319
1332
|
className: "dropdown-item",
|
|
1320
1333
|
onClick: () => {
|
|
1321
|
-
g.getState().setAboutOpen(!0),
|
|
1334
|
+
g.getState().setAboutOpen(!0), m(null);
|
|
1322
1335
|
},
|
|
1323
1336
|
children: "About Entropia Riko"
|
|
1324
1337
|
}
|
|
@@ -1328,7 +1341,7 @@ function bn({
|
|
|
1328
1341
|
{
|
|
1329
1342
|
className: "dropdown-item",
|
|
1330
1343
|
onClick: () => {
|
|
1331
|
-
g.getState().setWelcomeOpen(!0),
|
|
1344
|
+
g.getState().setWelcomeOpen(!0), m(null);
|
|
1332
1345
|
},
|
|
1333
1346
|
children: "Welcome Screen"
|
|
1334
1347
|
}
|
|
@@ -1338,7 +1351,7 @@ function bn({
|
|
|
1338
1351
|
{
|
|
1339
1352
|
className: "dropdown-item",
|
|
1340
1353
|
onClick: () => {
|
|
1341
|
-
g.getState().setPrefsOpen(!0),
|
|
1354
|
+
g.getState().setPrefsOpen(!0), m(null);
|
|
1342
1355
|
},
|
|
1343
1356
|
children: "Preferences…"
|
|
1344
1357
|
}
|
|
@@ -1351,36 +1364,36 @@ function bn({
|
|
|
1351
1364
|
{
|
|
1352
1365
|
label: "File",
|
|
1353
1366
|
items: [
|
|
1354
|
-
{ label: "New Workflow", onClick: () =>
|
|
1367
|
+
{ label: "New Workflow", onClick: () => y("Untitled") },
|
|
1355
1368
|
{ label: "New Project…", onClick: async () => {
|
|
1356
|
-
const
|
|
1357
|
-
if (!
|
|
1358
|
-
const
|
|
1369
|
+
const P = await Q("save", { defaultName: "my_project" });
|
|
1370
|
+
if (!P) return;
|
|
1371
|
+
const I = await (await fetch("/api/project/new", {
|
|
1359
1372
|
method: "POST",
|
|
1360
1373
|
headers: { "Content-Type": "application/json" },
|
|
1361
|
-
body: JSON.stringify({ dir:
|
|
1374
|
+
body: JSON.stringify({ dir: P })
|
|
1362
1375
|
})).json();
|
|
1363
|
-
|
|
1376
|
+
I.status === "success" ? (g.getState().bumpProject(), x("success", `Created project ${I.root}`)) : x("error", I.error ?? "new project failed");
|
|
1364
1377
|
} },
|
|
1365
|
-
{ label: "Save .riko…", onClick: () =>
|
|
1366
|
-
{ label: "Save .ric…", onClick: () =>
|
|
1378
|
+
{ label: "Save .riko…", onClick: () => R("ascii"), disabled: _ },
|
|
1379
|
+
{ label: "Save .ric…", onClick: () => R("binary"), disabled: _ },
|
|
1367
1380
|
{
|
|
1368
1381
|
label: "Import / Export",
|
|
1369
1382
|
submenu: [
|
|
1370
|
-
{ label: "Import…", onClick:
|
|
1371
|
-
{ label: "Export…", onClick:
|
|
1383
|
+
{ label: "Import…", onClick: k },
|
|
1384
|
+
{ label: "Export…", onClick: T },
|
|
1372
1385
|
{ label: "Import Working Folder…", onClick: () => g.getState().setImportFolderOpen(!0) }
|
|
1373
1386
|
]
|
|
1374
1387
|
},
|
|
1375
1388
|
{
|
|
1376
1389
|
label: "Export Code",
|
|
1377
1390
|
submenu: [
|
|
1378
|
-
{ label: "Export .py (PyTorch)", onClick:
|
|
1379
|
-
{ label: "Export .py (Keras/TF)", onClick:
|
|
1391
|
+
{ label: "Export .py (PyTorch)", onClick: f, disabled: _ },
|
|
1392
|
+
{ label: "Export .py (Keras/TF)", onClick: C, disabled: _ },
|
|
1380
1393
|
{ label: "Export Project… (multi-file)", onClick: async () => {
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
}, disabled:
|
|
1394
|
+
const P = g.getState().activeFileName ?? "graph_project", N = await Q("save", { defaultName: P });
|
|
1395
|
+
N && await g.getState().exportProjectTo(N);
|
|
1396
|
+
}, disabled: _ }
|
|
1384
1397
|
]
|
|
1385
1398
|
},
|
|
1386
1399
|
{ label: "Preferences…", onClick: () => g.getState().setPrefsOpen(!0) }
|
|
@@ -1389,29 +1402,29 @@ function bn({
|
|
|
1389
1402
|
{
|
|
1390
1403
|
label: "Run",
|
|
1391
1404
|
items: [
|
|
1392
|
-
{ label: "Inference", onClick:
|
|
1393
|
-
{ label: "Train (20 steps)", onClick: () =>
|
|
1394
|
-
{ label: "Train (100 steps)", onClick: () =>
|
|
1405
|
+
{ label: "Inference", onClick: b, disabled: _ },
|
|
1406
|
+
{ label: "Train (20 steps)", onClick: () => l(20, 1e-3), disabled: _ },
|
|
1407
|
+
{ label: "Train (100 steps)", onClick: () => l(100, 1e-3), disabled: _ }
|
|
1395
1408
|
]
|
|
1396
1409
|
},
|
|
1397
1410
|
{
|
|
1398
1411
|
label: "Data",
|
|
1399
1412
|
items: [
|
|
1400
|
-
{ label: "MNIST Loader", onClick: () =>
|
|
1401
|
-
{ label: "CIFAR10 Loader", onClick: () =>
|
|
1402
|
-
{ label: "CSV Loader", onClick: () =>
|
|
1403
|
-
{ label: "Image Folder Loader", onClick: () =>
|
|
1404
|
-
{ label: "Tensor File Loader", onClick: () =>
|
|
1405
|
-
{ label: "Generic Data Loader", onClick: () =>
|
|
1413
|
+
{ label: "MNIST Loader", onClick: () => F("mnist_loader", { x: 200, y: 150 }) },
|
|
1414
|
+
{ label: "CIFAR10 Loader", onClick: () => F("cifar10_loader", { x: 200, y: 150 }) },
|
|
1415
|
+
{ label: "CSV Loader", onClick: () => F("csv_loader", { x: 200, y: 150 }) },
|
|
1416
|
+
{ label: "Image Folder Loader", onClick: () => F("image_folder_loader", { x: 200, y: 150 }) },
|
|
1417
|
+
{ label: "Tensor File Loader", onClick: () => F("tensor_file_loader", { x: 200, y: 150 }) },
|
|
1418
|
+
{ label: "Generic Data Loader", onClick: () => F("dataloader", { x: 200, y: 150 }) }
|
|
1406
1419
|
]
|
|
1407
1420
|
},
|
|
1408
1421
|
{
|
|
1409
1422
|
label: "View",
|
|
1410
1423
|
items: [
|
|
1411
|
-
{ label: `Theme: Light ${
|
|
1412
|
-
{ label: `Theme: Dark ${
|
|
1413
|
-
{ label: `Theme: System ${
|
|
1414
|
-
{ label: `Theme: Liquid Glass ${
|
|
1424
|
+
{ label: `Theme: Light ${O === "light" ? "✓" : ""}`, onClick: () => $("light") },
|
|
1425
|
+
{ label: `Theme: Dark ${O === "dark" ? "✓" : ""}`, onClick: () => $("dark") },
|
|
1426
|
+
{ label: `Theme: System ${O === "system" ? "✓" : ""}`, onClick: () => $("system") },
|
|
1427
|
+
{ label: `Theme: Liquid Glass ${O === "glass" ? "✓" : ""}`, onClick: () => $("glass") }
|
|
1415
1428
|
]
|
|
1416
1429
|
},
|
|
1417
1430
|
{
|
|
@@ -1421,51 +1434,51 @@ function bn({
|
|
|
1421
1434
|
{ label: "About Entropia Riko", onClick: () => g.getState().setAboutOpen(!0) }
|
|
1422
1435
|
]
|
|
1423
1436
|
}
|
|
1424
|
-
].map((
|
|
1437
|
+
].map((P) => /* @__PURE__ */ p(
|
|
1425
1438
|
"div",
|
|
1426
1439
|
{
|
|
1427
|
-
className: `menu-item ${
|
|
1428
|
-
onClick: () => c
|
|
1440
|
+
className: `menu-item ${c === P.label ? "active" : ""}`,
|
|
1441
|
+
onClick: () => m(c === P.label ? null : P.label),
|
|
1429
1442
|
children: [
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
(
|
|
1443
|
+
P.label,
|
|
1444
|
+
c === P.label && /* @__PURE__ */ a("div", { className: "dropdown", onClick: (N) => N.stopPropagation(), children: P.items.map(
|
|
1445
|
+
(N, I) => N.submenu ? /* @__PURE__ */ p("div", { className: "dropdown-item has-submenu", children: [
|
|
1433
1446
|
/* @__PURE__ */ p("span", { className: "submenu-label", children: [
|
|
1434
|
-
|
|
1447
|
+
N.label,
|
|
1435
1448
|
" ▸"
|
|
1436
1449
|
] }),
|
|
1437
|
-
/* @__PURE__ */ a("div", { className: "submenu", children:
|
|
1450
|
+
/* @__PURE__ */ a("div", { className: "submenu", children: N.submenu.map((S, j) => /* @__PURE__ */ a(
|
|
1438
1451
|
"div",
|
|
1439
1452
|
{
|
|
1440
|
-
className: `dropdown-item ${
|
|
1453
|
+
className: `dropdown-item ${S.disabled ? "disabled" : ""}`,
|
|
1441
1454
|
onClick: () => {
|
|
1442
|
-
var
|
|
1443
|
-
|
|
1455
|
+
var A;
|
|
1456
|
+
S.disabled || ((A = S.onClick) == null || A.call(S), m(null));
|
|
1444
1457
|
},
|
|
1445
|
-
children:
|
|
1458
|
+
children: S.label
|
|
1446
1459
|
},
|
|
1447
|
-
|
|
1460
|
+
j
|
|
1448
1461
|
)) })
|
|
1449
|
-
] },
|
|
1462
|
+
] }, I) : /* @__PURE__ */ a(
|
|
1450
1463
|
"div",
|
|
1451
1464
|
{
|
|
1452
|
-
className: `dropdown-item ${
|
|
1465
|
+
className: `dropdown-item ${N.disabled ? "disabled" : ""}`,
|
|
1453
1466
|
onClick: () => {
|
|
1454
|
-
var
|
|
1455
|
-
|
|
1467
|
+
var S;
|
|
1468
|
+
N.disabled || ((S = N.onClick) == null || S.call(N), m(null));
|
|
1456
1469
|
},
|
|
1457
|
-
children:
|
|
1470
|
+
children: N.label
|
|
1458
1471
|
},
|
|
1459
|
-
|
|
1472
|
+
I
|
|
1460
1473
|
)
|
|
1461
1474
|
) })
|
|
1462
1475
|
]
|
|
1463
1476
|
},
|
|
1464
|
-
|
|
1477
|
+
P.label
|
|
1465
1478
|
)),
|
|
1466
1479
|
/* @__PURE__ */ a("div", { className: "menubar-sep" }),
|
|
1467
1480
|
/* @__PURE__ */ a(
|
|
1468
|
-
|
|
1481
|
+
gn,
|
|
1469
1482
|
{
|
|
1470
1483
|
workspaces: n,
|
|
1471
1484
|
activeId: e,
|
|
@@ -1474,20 +1487,21 @@ function bn({
|
|
|
1474
1487
|
onRemove: r,
|
|
1475
1488
|
onRename: o,
|
|
1476
1489
|
onDuplicate: i,
|
|
1477
|
-
onMove: d
|
|
1490
|
+
onMove: d,
|
|
1491
|
+
onReorder: h
|
|
1478
1492
|
}
|
|
1479
1493
|
),
|
|
1480
1494
|
/* @__PURE__ */ a(
|
|
1481
1495
|
"input",
|
|
1482
1496
|
{
|
|
1483
|
-
ref:
|
|
1497
|
+
ref: w,
|
|
1484
1498
|
type: "file",
|
|
1485
1499
|
accept: ".riko,.ric,.json,application/json,application/octet-stream",
|
|
1486
1500
|
style: { display: "none" },
|
|
1487
|
-
onChange: (
|
|
1488
|
-
var
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1501
|
+
onChange: (P) => {
|
|
1502
|
+
var I;
|
|
1503
|
+
const N = (I = P.target.files) == null ? void 0 : I[0];
|
|
1504
|
+
N && v(N), P.target.value = "";
|
|
1491
1505
|
}
|
|
1492
1506
|
}
|
|
1493
1507
|
),
|
|
@@ -1506,7 +1520,7 @@ function bn({
|
|
|
1506
1520
|
] })
|
|
1507
1521
|
] });
|
|
1508
1522
|
}
|
|
1509
|
-
function
|
|
1523
|
+
function Nn() {
|
|
1510
1524
|
const n = g((t) => t.dirty), e = g((t) => t.activeFileName);
|
|
1511
1525
|
return /* @__PURE__ */ p("div", { className: "titlebar", children: [
|
|
1512
1526
|
/* @__PURE__ */ a("span", { className: `titlebar-dirty ${n ? "visible" : ""}`, "aria-hidden": !n, children: "*" }),
|
|
@@ -1515,12 +1529,12 @@ function vn() {
|
|
|
1515
1529
|
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
1516
1530
|
/* @__PURE__ */ p("span", { className: "titlebar-version", children: [
|
|
1517
1531
|
"v",
|
|
1518
|
-
|
|
1532
|
+
te
|
|
1519
1533
|
] })
|
|
1520
1534
|
] });
|
|
1521
1535
|
}
|
|
1522
|
-
function
|
|
1523
|
-
const t =
|
|
1536
|
+
function He(n, e) {
|
|
1537
|
+
const t = D(null);
|
|
1524
1538
|
return (r) => {
|
|
1525
1539
|
r.preventDefault(), t.current = { x: r.clientX, y: r.clientY };
|
|
1526
1540
|
const o = (d) => {
|
|
@@ -1531,30 +1545,30 @@ function Be(n, e) {
|
|
|
1531
1545
|
document.body.style.cursor = e, document.addEventListener("mousemove", o), document.addEventListener("mouseup", i);
|
|
1532
1546
|
};
|
|
1533
1547
|
}
|
|
1534
|
-
function
|
|
1535
|
-
const e =
|
|
1548
|
+
function kn({ onDrag: n }) {
|
|
1549
|
+
const e = D(null), t = He((s) => {
|
|
1536
1550
|
var o, i;
|
|
1537
1551
|
const r = ((i = (o = e.current) == null ? void 0 : o.parentElement) == null ? void 0 : i.clientWidth) ?? 1;
|
|
1538
1552
|
n(s, r);
|
|
1539
1553
|
}, "col-resize");
|
|
1540
1554
|
return /* @__PURE__ */ a("div", { ref: e, className: "splitter-v", onMouseDown: t });
|
|
1541
1555
|
}
|
|
1542
|
-
function
|
|
1543
|
-
const e =
|
|
1556
|
+
function wn({ onDrag: n }) {
|
|
1557
|
+
const e = D(null), t = He((s, r) => {
|
|
1544
1558
|
var i, d;
|
|
1545
1559
|
const o = ((d = (i = e.current) == null ? void 0 : i.parentElement) == null ? void 0 : d.clientHeight) ?? 1;
|
|
1546
1560
|
n(r, o);
|
|
1547
1561
|
}, "row-resize");
|
|
1548
1562
|
return /* @__PURE__ */ a("div", { ref: e, className: "splitter-h", onMouseDown: t });
|
|
1549
1563
|
}
|
|
1550
|
-
function
|
|
1551
|
-
const [n, e] =
|
|
1564
|
+
function _n() {
|
|
1565
|
+
const [n, e] = E(""), t = g((d) => d.nodeDefs), s = g((d) => d.addNode), r = g((d) => d.addFrame), o = Y(() => {
|
|
1552
1566
|
const d = t.filter(
|
|
1553
1567
|
(c) => c.label.toLowerCase().includes(n.toLowerCase()) || c.type.toLowerCase().includes(n.toLowerCase())
|
|
1554
1568
|
), h = {};
|
|
1555
1569
|
for (const c of d) {
|
|
1556
|
-
const
|
|
1557
|
-
(h[
|
|
1570
|
+
const m = c.category || "Other";
|
|
1571
|
+
(h[m] ?? (h[m] = [])).push(c);
|
|
1558
1572
|
}
|
|
1559
1573
|
return h;
|
|
1560
1574
|
}, [t, n]), i = Object.keys(o).sort();
|
|
@@ -1580,7 +1594,7 @@ function wn() {
|
|
|
1580
1594
|
] }),
|
|
1581
1595
|
/* @__PURE__ */ p("div", { className: "node-list", children: [
|
|
1582
1596
|
i.map((d) => /* @__PURE__ */ a(
|
|
1583
|
-
|
|
1597
|
+
xn,
|
|
1584
1598
|
{
|
|
1585
1599
|
category: d,
|
|
1586
1600
|
nodes: o[d],
|
|
@@ -1597,12 +1611,12 @@ function wn() {
|
|
|
1597
1611
|
] })
|
|
1598
1612
|
] });
|
|
1599
1613
|
}
|
|
1600
|
-
function
|
|
1614
|
+
function xn({
|
|
1601
1615
|
category: n,
|
|
1602
1616
|
nodes: e,
|
|
1603
1617
|
onAdd: t
|
|
1604
1618
|
}) {
|
|
1605
|
-
const [s, r] =
|
|
1619
|
+
const [s, r] = E(!0);
|
|
1606
1620
|
return /* @__PURE__ */ p("div", { className: "node-category", children: [
|
|
1607
1621
|
/* @__PURE__ */ p(
|
|
1608
1622
|
"div",
|
|
@@ -1627,27 +1641,27 @@ function _n({
|
|
|
1627
1641
|
))
|
|
1628
1642
|
] });
|
|
1629
1643
|
}
|
|
1630
|
-
function
|
|
1631
|
-
const n = g((
|
|
1632
|
-
|
|
1644
|
+
function Sn() {
|
|
1645
|
+
const n = g((u) => u.fileList), e = g((u) => u.activeFileName), t = g((u) => u.refreshFiles), s = g((u) => u.openFile), r = g((u) => u.saveFileToDisk), o = g((u) => u.importFileInline), i = g((u) => u.previewAssetCode), [d, h] = E("");
|
|
1646
|
+
W(() => {
|
|
1633
1647
|
t();
|
|
1634
1648
|
}, [t]);
|
|
1635
|
-
const c =
|
|
1636
|
-
const
|
|
1637
|
-
for (const
|
|
1638
|
-
if (
|
|
1639
|
-
const f =
|
|
1640
|
-
f in
|
|
1649
|
+
const c = Y(() => {
|
|
1650
|
+
const u = d.trim().toLowerCase(), b = {}, l = [];
|
|
1651
|
+
for (const v of n) {
|
|
1652
|
+
if (u && !v.name.toLowerCase().includes(u) && !v.path.toLowerCase().includes(u)) continue;
|
|
1653
|
+
const f = v.path.includes("/") ? v.path.slice(0, v.path.indexOf("/")) : "(root)";
|
|
1654
|
+
f in b || (b[f] = [], l.push(f)), b[f].push(v);
|
|
1641
1655
|
}
|
|
1642
|
-
return l.sort(), { map:
|
|
1656
|
+
return l.sort(), { map: b, order: l };
|
|
1643
1657
|
}, [n, d]);
|
|
1644
1658
|
return /* @__PURE__ */ p("div", { className: "file-manager", children: [
|
|
1645
1659
|
/* @__PURE__ */ p("div", { className: "panel-head", children: [
|
|
1646
1660
|
/* @__PURE__ */ a("span", { className: "panel-head-title", children: "Asset Library" }),
|
|
1647
1661
|
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
1648
1662
|
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Save current graph", onClick: () => {
|
|
1649
|
-
const
|
|
1650
|
-
|
|
1663
|
+
const u = window.prompt("Save current graph as", e ?? "untitled");
|
|
1664
|
+
u && u.trim() && r(u.trim());
|
|
1651
1665
|
}, children: "💾" }),
|
|
1652
1666
|
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Refresh", onClick: t, children: "↻" })
|
|
1653
1667
|
] }),
|
|
@@ -1656,29 +1670,29 @@ function xn() {
|
|
|
1656
1670
|
{
|
|
1657
1671
|
placeholder: "Search assets…",
|
|
1658
1672
|
value: d,
|
|
1659
|
-
onChange: (
|
|
1673
|
+
onChange: (u) => h(u.target.value)
|
|
1660
1674
|
}
|
|
1661
1675
|
) }),
|
|
1662
1676
|
/* @__PURE__ */ p("div", { className: "fm-tree", children: [
|
|
1663
|
-
c.order.map((
|
|
1664
|
-
/* @__PURE__ */ a("div", { className: "fm-group-header", children:
|
|
1665
|
-
c.map[
|
|
1677
|
+
c.order.map((u) => /* @__PURE__ */ p("div", { className: "fm-group", children: [
|
|
1678
|
+
/* @__PURE__ */ a("div", { className: "fm-group-header", children: u }),
|
|
1679
|
+
c.map[u].map((b) => /* @__PURE__ */ p(
|
|
1666
1680
|
"div",
|
|
1667
1681
|
{
|
|
1668
|
-
className: `fm-file ${e ===
|
|
1669
|
-
onClick: () => s(
|
|
1670
|
-
title:
|
|
1682
|
+
className: `fm-file ${e === b.name ? "active" : ""}`,
|
|
1683
|
+
onClick: () => s(b.path),
|
|
1684
|
+
title: b.path,
|
|
1671
1685
|
children: [
|
|
1672
1686
|
/* @__PURE__ */ a("span", { className: "fm-toggle", children: "▤" }),
|
|
1673
|
-
/* @__PURE__ */ a("span", { className: "fm-name", children:
|
|
1674
|
-
|
|
1687
|
+
/* @__PURE__ */ a("span", { className: "fm-name", children: b.name }),
|
|
1688
|
+
b.format === "binary" && /* @__PURE__ */ a("span", { className: "fm-format-badge", title: "binary .ric", children: "BIN" }),
|
|
1675
1689
|
/* @__PURE__ */ a(
|
|
1676
1690
|
"button",
|
|
1677
1691
|
{
|
|
1678
1692
|
className: "fm-btn",
|
|
1679
1693
|
title: "Preview PyTorch code",
|
|
1680
1694
|
onClick: (l) => {
|
|
1681
|
-
l.stopPropagation(), i(
|
|
1695
|
+
l.stopPropagation(), i(b.path);
|
|
1682
1696
|
},
|
|
1683
1697
|
children: "⤡"
|
|
1684
1698
|
}
|
|
@@ -1689,16 +1703,16 @@ function xn() {
|
|
|
1689
1703
|
className: "fm-btn",
|
|
1690
1704
|
title: "Expand full nodes into canvas",
|
|
1691
1705
|
onClick: (l) => {
|
|
1692
|
-
l.stopPropagation(), o(
|
|
1706
|
+
l.stopPropagation(), o(b.path);
|
|
1693
1707
|
},
|
|
1694
1708
|
children: "⤢"
|
|
1695
1709
|
}
|
|
1696
1710
|
)
|
|
1697
1711
|
]
|
|
1698
1712
|
},
|
|
1699
|
-
|
|
1713
|
+
b.path
|
|
1700
1714
|
))
|
|
1701
|
-
] },
|
|
1715
|
+
] }, u)),
|
|
1702
1716
|
n.length === 0 && /* @__PURE__ */ a("div", { className: "category", style: { padding: 8 }, children: "No .riko files found." }),
|
|
1703
1717
|
n.length > 0 && c.order.length === 0 && /* @__PURE__ */ p("div", { className: "category", style: { padding: 8 }, children: [
|
|
1704
1718
|
"No matches for “",
|
|
@@ -1708,64 +1722,64 @@ function xn() {
|
|
|
1708
1722
|
] })
|
|
1709
1723
|
] });
|
|
1710
1724
|
}
|
|
1711
|
-
let
|
|
1712
|
-
function
|
|
1713
|
-
|
|
1725
|
+
let le = [];
|
|
1726
|
+
function Cn(n) {
|
|
1727
|
+
le = n;
|
|
1714
1728
|
}
|
|
1715
|
-
function
|
|
1716
|
-
return
|
|
1729
|
+
function Te() {
|
|
1730
|
+
return le;
|
|
1717
1731
|
}
|
|
1718
|
-
let
|
|
1719
|
-
function
|
|
1720
|
-
return
|
|
1732
|
+
let Pe = 0;
|
|
1733
|
+
function Fe(n) {
|
|
1734
|
+
return Pe += 1, `${n}_${Date.now()}_${Pe}`;
|
|
1721
1735
|
}
|
|
1722
|
-
async function
|
|
1723
|
-
const n = g.getState(), e =
|
|
1736
|
+
async function Tn() {
|
|
1737
|
+
const n = g.getState(), e = le.filter((l) => n.nodes.some((v) => v.id === l));
|
|
1724
1738
|
if (e.length === 0) {
|
|
1725
1739
|
n.pushToast("error", "先框选一些节点再创建子图");
|
|
1726
1740
|
return;
|
|
1727
1741
|
}
|
|
1728
1742
|
const t = window.prompt("Subgraph name", "subgraph");
|
|
1729
1743
|
if (!t || !t.trim()) return;
|
|
1730
|
-
const s = new Set(e), r = n.nodes.filter((l) => s.has(l.id)), o = n.edges.filter((l) => s.has(l.source) && s.has(l.target)), i = n.edges.filter((l) => !s.has(l.source) && s.has(l.target)), d = n.edges.filter((l) => s.has(l.source) && !s.has(l.target)), h = [], c = [],
|
|
1731
|
-
i.forEach((l,
|
|
1732
|
-
const f =
|
|
1744
|
+
const s = new Set(e), r = n.nodes.filter((l) => s.has(l.id)), o = n.edges.filter((l) => s.has(l.source) && s.has(l.target)), i = n.edges.filter((l) => !s.has(l.source) && s.has(l.target)), d = n.edges.filter((l) => s.has(l.source) && !s.has(l.target)), h = [], c = [], m = (l) => l === 0 ? "input" : `input_${l + 1}`, u = (l) => l === 0 ? "output" : `output_${l + 1}`;
|
|
1745
|
+
i.forEach((l, v) => {
|
|
1746
|
+
const f = m(v), C = `gin_${v}`;
|
|
1733
1747
|
h.push({
|
|
1734
|
-
id:
|
|
1748
|
+
id: C,
|
|
1735
1749
|
type_name: "graph_input",
|
|
1736
|
-
label: `Input ${
|
|
1750
|
+
label: `Input ${v}`,
|
|
1737
1751
|
category: "Subgraph",
|
|
1738
|
-
position: [0,
|
|
1752
|
+
position: [0, v * 90],
|
|
1739
1753
|
parameters: { name: f, data_kind: "tensor" },
|
|
1740
1754
|
inputs: [],
|
|
1741
1755
|
outputs: []
|
|
1742
1756
|
}), c.push({
|
|
1743
|
-
id: `be_in_${
|
|
1744
|
-
source_node:
|
|
1757
|
+
id: `be_in_${v}`,
|
|
1758
|
+
source_node: C,
|
|
1745
1759
|
source_port: "value",
|
|
1746
1760
|
target_node: l.target,
|
|
1747
1761
|
target_port: l.targetHandle
|
|
1748
1762
|
});
|
|
1749
|
-
}), d.forEach((l,
|
|
1750
|
-
const f =
|
|
1763
|
+
}), d.forEach((l, v) => {
|
|
1764
|
+
const f = u(v), C = `gout_${v}`;
|
|
1751
1765
|
h.push({
|
|
1752
|
-
id:
|
|
1766
|
+
id: C,
|
|
1753
1767
|
type_name: "graph_output",
|
|
1754
|
-
label: `Output ${
|
|
1768
|
+
label: `Output ${v}`,
|
|
1755
1769
|
category: "Subgraph",
|
|
1756
|
-
position: [1200,
|
|
1770
|
+
position: [1200, v * 90],
|
|
1757
1771
|
parameters: { name: f, data_kind: "tensor" },
|
|
1758
1772
|
inputs: [],
|
|
1759
1773
|
outputs: []
|
|
1760
1774
|
}), c.push({
|
|
1761
|
-
id: `be_out_${
|
|
1775
|
+
id: `be_out_${v}`,
|
|
1762
1776
|
source_node: l.source,
|
|
1763
1777
|
source_port: l.sourceHandle,
|
|
1764
|
-
target_node:
|
|
1778
|
+
target_node: C,
|
|
1765
1779
|
target_port: "value"
|
|
1766
1780
|
});
|
|
1767
1781
|
});
|
|
1768
|
-
const
|
|
1782
|
+
const b = {
|
|
1769
1783
|
version: "1.0",
|
|
1770
1784
|
metadata: { name: t.trim(), description: "" },
|
|
1771
1785
|
nodes: [
|
|
@@ -1794,19 +1808,19 @@ async function Cn() {
|
|
|
1794
1808
|
settings: {}
|
|
1795
1809
|
};
|
|
1796
1810
|
try {
|
|
1797
|
-
const
|
|
1811
|
+
const v = await (await fetch("/api/files/save", {
|
|
1798
1812
|
method: "POST",
|
|
1799
1813
|
headers: { "Content-Type": "application/json" },
|
|
1800
|
-
body: JSON.stringify({ name: t.trim(), format: "ascii", doc:
|
|
1814
|
+
body: JSON.stringify({ name: t.trim(), format: "ascii", doc: b })
|
|
1801
1815
|
})).json();
|
|
1802
|
-
if (
|
|
1803
|
-
n.pushToast("error",
|
|
1816
|
+
if (v.status !== "success") {
|
|
1817
|
+
n.pushToast("error", v.error ?? "子图保存失败");
|
|
1804
1818
|
return;
|
|
1805
1819
|
}
|
|
1806
|
-
const f = r.reduce((_, k) => _ + k.position.x, 0) / r.length,
|
|
1820
|
+
const f = r.reduce((_, k) => _ + k.position.x, 0) / r.length, C = r.reduce((_, k) => _ + k.position.y, 0) / r.length, y = n.nodeDefs.find((_) => _.type === "import") ?? n.nodeDefs.find((_) => _.type === "graph_reference"), F = {
|
|
1807
1821
|
id: `imp_${Date.now()}`,
|
|
1808
1822
|
type: "thnNode",
|
|
1809
|
-
position: { x: f, y:
|
|
1823
|
+
position: { x: f, y: C },
|
|
1810
1824
|
data: {
|
|
1811
1825
|
type: (y == null ? void 0 : y.type) ?? "import",
|
|
1812
1826
|
label: t.trim(),
|
|
@@ -1816,51 +1830,51 @@ async function Cn() {
|
|
|
1816
1830
|
outputs: (y == null ? void 0 : y.outputs) ?? [],
|
|
1817
1831
|
hasError: !1
|
|
1818
1832
|
}
|
|
1819
|
-
},
|
|
1833
|
+
}, x = n.edges.filter(
|
|
1820
1834
|
(_) => !s.has(_.source) && !s.has(_.target)
|
|
1821
|
-
),
|
|
1822
|
-
i.slice(0,
|
|
1823
|
-
|
|
1824
|
-
id:
|
|
1835
|
+
), O = 3;
|
|
1836
|
+
i.slice(0, O).forEach((_, k) => {
|
|
1837
|
+
x.push({
|
|
1838
|
+
id: Fe("rwin"),
|
|
1825
1839
|
source: _.source,
|
|
1826
|
-
target:
|
|
1840
|
+
target: F.id,
|
|
1827
1841
|
sourceHandle: _.sourceHandle,
|
|
1828
|
-
targetHandle:
|
|
1842
|
+
targetHandle: m(k)
|
|
1829
1843
|
});
|
|
1830
|
-
}), d.slice(0,
|
|
1831
|
-
|
|
1832
|
-
id:
|
|
1833
|
-
source:
|
|
1844
|
+
}), d.slice(0, O).forEach((_, k) => {
|
|
1845
|
+
x.push({
|
|
1846
|
+
id: Fe("rwout"),
|
|
1847
|
+
source: F.id,
|
|
1834
1848
|
target: _.target,
|
|
1835
|
-
sourceHandle:
|
|
1849
|
+
sourceHandle: u(k),
|
|
1836
1850
|
targetHandle: _.targetHandle
|
|
1837
1851
|
});
|
|
1838
1852
|
}), g.setState({
|
|
1839
|
-
nodes: [...n.nodes.filter((_) => !s.has(_.id)),
|
|
1840
|
-
edges:
|
|
1853
|
+
nodes: [...n.nodes.filter((_) => !s.has(_.id)), F],
|
|
1854
|
+
edges: x,
|
|
1841
1855
|
selectedNodeId: null,
|
|
1842
1856
|
dirty: !0
|
|
1843
|
-
}),
|
|
1844
|
-
const $ = Math.max(0, i.length -
|
|
1845
|
-
$ ||
|
|
1857
|
+
}), le = [];
|
|
1858
|
+
const $ = Math.max(0, i.length - O), w = Math.max(0, d.length - O);
|
|
1859
|
+
$ || w ? n.pushToast(
|
|
1846
1860
|
"info",
|
|
1847
|
-
`子图已创建,但超过 3 入/3 出的边界连线有 ${$} 入/${
|
|
1861
|
+
`子图已创建,但超过 3 入/3 出的边界连线有 ${$} 入/${w} 出 未保留`
|
|
1848
1862
|
) : n.pushToast("success", `已创建子图 ${t.trim()}(边界连线已保留)`), n.refreshFiles();
|
|
1849
1863
|
} catch (l) {
|
|
1850
1864
|
n.pushToast("error", l instanceof Error ? l.message : String(l));
|
|
1851
1865
|
}
|
|
1852
1866
|
}
|
|
1853
|
-
function
|
|
1867
|
+
function Pn({ id: n, data: e, selected: t }) {
|
|
1854
1868
|
const s = e, r = g((o) => o.results[n]);
|
|
1855
1869
|
return /* @__PURE__ */ p("div", { className: `thn-node ${t ? "selected" : ""}`, children: [
|
|
1856
1870
|
/* @__PURE__ */ a("div", { className: "header", children: s.label }),
|
|
1857
1871
|
/* @__PURE__ */ p("div", { className: "ports", children: [
|
|
1858
1872
|
s.inputs.map((o) => /* @__PURE__ */ p("div", { className: "port in", children: [
|
|
1859
1873
|
/* @__PURE__ */ a(
|
|
1860
|
-
|
|
1874
|
+
_e,
|
|
1861
1875
|
{
|
|
1862
1876
|
type: "target",
|
|
1863
|
-
position:
|
|
1877
|
+
position: xe.Left,
|
|
1864
1878
|
id: o.name,
|
|
1865
1879
|
style: { left: -5 }
|
|
1866
1880
|
}
|
|
@@ -1870,10 +1884,10 @@ function Tn({ id: n, data: e, selected: t }) {
|
|
|
1870
1884
|
s.outputs.map((o) => /* @__PURE__ */ p("div", { className: "port out", children: [
|
|
1871
1885
|
/* @__PURE__ */ a("span", { className: "label", children: o.label }),
|
|
1872
1886
|
/* @__PURE__ */ a(
|
|
1873
|
-
|
|
1887
|
+
_e,
|
|
1874
1888
|
{
|
|
1875
1889
|
type: "source",
|
|
1876
|
-
position:
|
|
1890
|
+
position: xe.Right,
|
|
1877
1891
|
id: o.name,
|
|
1878
1892
|
style: { right: -5 }
|
|
1879
1893
|
}
|
|
@@ -1898,16 +1912,16 @@ function Tn({ id: n, data: e, selected: t }) {
|
|
|
1898
1912
|
] }, o))
|
|
1899
1913
|
] });
|
|
1900
1914
|
}
|
|
1901
|
-
function
|
|
1902
|
-
const t = g((i) => i.nodeDefs), s = g((i) => i.addNode), r = g((i) => i.addFrame), o =
|
|
1915
|
+
function Fn({ state: n, onClose: e }) {
|
|
1916
|
+
const t = g((i) => i.nodeDefs), s = g((i) => i.addNode), r = g((i) => i.addFrame), o = Y(
|
|
1903
1917
|
() => [
|
|
1904
1918
|
{
|
|
1905
1919
|
id: "create_subgraph",
|
|
1906
|
-
label: `⤵ Create Subgraph (${
|
|
1920
|
+
label: `⤵ Create Subgraph (${Te().length} selected)`,
|
|
1907
1921
|
description: "collapse selection into an import node",
|
|
1908
1922
|
category: "Subgraph",
|
|
1909
|
-
disabled:
|
|
1910
|
-
onSelect: () =>
|
|
1923
|
+
disabled: Te().length === 0,
|
|
1924
|
+
onSelect: () => Tn()
|
|
1911
1925
|
},
|
|
1912
1926
|
{
|
|
1913
1927
|
id: "add_frame",
|
|
@@ -1926,54 +1940,54 @@ function Pn({ state: n, onClose: e }) {
|
|
|
1926
1940
|
],
|
|
1927
1941
|
[t, s, r, n.x, n.y]
|
|
1928
1942
|
);
|
|
1929
|
-
return n.visible ? /* @__PURE__ */ a(
|
|
1943
|
+
return n.visible ? /* @__PURE__ */ a(re, { entries: o, x: n.x, y: n.y, onClose: e, searchable: !0 }) : null;
|
|
1930
1944
|
}
|
|
1931
|
-
const
|
|
1932
|
-
function
|
|
1933
|
-
const n = g((f) => f.nodes), e = g((f) => f.edges), t = g((f) => f.frames), s = g((f) => f.updateFrame), r = g((f) => f.removeFrame), o = g((f) => f.onNodesChange), i = g((f) => f.onEdgesChange), d = g((f) => f.onConnect), h = g((f) => f.onSelectNode), c = g((f) => f.enterSubgraph), { zoomIn:
|
|
1945
|
+
const En = { thnNode: Pn };
|
|
1946
|
+
function In() {
|
|
1947
|
+
const n = g((f) => f.nodes), e = g((f) => f.edges), t = g((f) => f.frames), s = g((f) => f.updateFrame), r = g((f) => f.removeFrame), o = g((f) => f.onNodesChange), i = g((f) => f.onEdgesChange), d = g((f) => f.onConnect), h = g((f) => f.onSelectNode), c = g((f) => f.enterSubgraph), { zoomIn: m, zoomOut: u, fitView: b, getViewport: l } = en(), v = (f, C) => {
|
|
1934
1948
|
if (f.target.closest("button")) return;
|
|
1935
1949
|
f.stopPropagation();
|
|
1936
|
-
const y = f.clientX,
|
|
1950
|
+
const y = f.clientX, F = f.clientY, x = C.x, O = C.y, $ = (_) => {
|
|
1937
1951
|
const k = l().zoom || 1;
|
|
1938
|
-
s(
|
|
1939
|
-
x:
|
|
1940
|
-
y:
|
|
1952
|
+
s(C.id, {
|
|
1953
|
+
x: x + (_.clientX - y) / k,
|
|
1954
|
+
y: O + (_.clientY - F) / k
|
|
1941
1955
|
});
|
|
1942
|
-
},
|
|
1943
|
-
document.removeEventListener("mousemove", $), document.removeEventListener("mouseup",
|
|
1956
|
+
}, w = () => {
|
|
1957
|
+
document.removeEventListener("mousemove", $), document.removeEventListener("mouseup", w);
|
|
1944
1958
|
};
|
|
1945
|
-
document.addEventListener("mousemove", $), document.addEventListener("mouseup",
|
|
1959
|
+
document.addEventListener("mousemove", $), document.addEventListener("mouseup", w);
|
|
1946
1960
|
};
|
|
1947
|
-
return /* @__PURE__ */ p(
|
|
1961
|
+
return /* @__PURE__ */ p(K, { children: [
|
|
1948
1962
|
/* @__PURE__ */ p(
|
|
1949
|
-
|
|
1963
|
+
nn,
|
|
1950
1964
|
{
|
|
1951
1965
|
nodes: n,
|
|
1952
1966
|
edges: e,
|
|
1953
|
-
nodeTypes:
|
|
1967
|
+
nodeTypes: En,
|
|
1954
1968
|
onNodesChange: o,
|
|
1955
1969
|
onEdgesChange: i,
|
|
1956
1970
|
onConnect: d,
|
|
1957
|
-
onNodeClick: (f,
|
|
1958
|
-
onNodeDoubleClick: (f,
|
|
1959
|
-
(
|
|
1971
|
+
onNodeClick: (f, C) => h(C.id),
|
|
1972
|
+
onNodeDoubleClick: (f, C) => {
|
|
1973
|
+
(C.data.type === "graph_reference" || C.data.type === "import") && c(C.id);
|
|
1960
1974
|
},
|
|
1961
1975
|
onPaneClick: () => h(null),
|
|
1962
|
-
onSelectionChange: ({ nodes: f }) =>
|
|
1976
|
+
onSelectionChange: ({ nodes: f }) => Cn(f.map((C) => C.id)),
|
|
1963
1977
|
selectionOnDrag: !0,
|
|
1964
1978
|
panOnDrag: [2],
|
|
1965
1979
|
fitView: !0,
|
|
1966
1980
|
children: [
|
|
1967
|
-
/* @__PURE__ */ a(
|
|
1968
|
-
/* @__PURE__ */ a(
|
|
1981
|
+
/* @__PURE__ */ a(tn, {}),
|
|
1982
|
+
/* @__PURE__ */ a(an, { children: t.map((f) => /* @__PURE__ */ a(
|
|
1969
1983
|
"div",
|
|
1970
1984
|
{
|
|
1971
1985
|
className: "graph-frame",
|
|
1972
1986
|
style: { left: f.x, top: f.y, width: f.width, height: f.height },
|
|
1973
|
-
onMouseDown: (
|
|
1987
|
+
onMouseDown: (C) => v(C, f),
|
|
1974
1988
|
onDoubleClick: () => {
|
|
1975
|
-
const
|
|
1976
|
-
|
|
1989
|
+
const C = window.prompt("Frame title", f.title);
|
|
1990
|
+
C !== null && s(f.id, { title: C });
|
|
1977
1991
|
},
|
|
1978
1992
|
children: /* @__PURE__ */ p("div", { className: "graph-frame-title", children: [
|
|
1979
1993
|
f.title,
|
|
@@ -1986,14 +2000,14 @@ function En() {
|
|
|
1986
2000
|
}
|
|
1987
2001
|
),
|
|
1988
2002
|
/* @__PURE__ */ p("div", { className: "graph-zoom", children: [
|
|
1989
|
-
/* @__PURE__ */ a("button", { onClick: () =>
|
|
1990
|
-
/* @__PURE__ */ a("button", { onClick: () =>
|
|
1991
|
-
/* @__PURE__ */ a("button", { onClick: () =>
|
|
2003
|
+
/* @__PURE__ */ a("button", { onClick: () => m(), title: "Zoom in", children: "+" }),
|
|
2004
|
+
/* @__PURE__ */ a("button", { onClick: () => b(), title: "Fit view", children: "⤢" }),
|
|
2005
|
+
/* @__PURE__ */ a("button", { onClick: () => u(), title: "Zoom out", children: "−" })
|
|
1992
2006
|
] })
|
|
1993
2007
|
] });
|
|
1994
2008
|
}
|
|
1995
2009
|
function On() {
|
|
1996
|
-
const [n, e] =
|
|
2010
|
+
const [n, e] = E({ x: 0, y: 0, visible: !1 }), t = g((r) => r.graphStack), s = g((r) => r.exitToLevel);
|
|
1997
2011
|
return /* @__PURE__ */ p(
|
|
1998
2012
|
"div",
|
|
1999
2013
|
{
|
|
@@ -2002,7 +2016,7 @@ function On() {
|
|
|
2002
2016
|
r.preventDefault(), e({ x: r.clientX, y: r.clientY, visible: !0 });
|
|
2003
2017
|
},
|
|
2004
2018
|
children: [
|
|
2005
|
-
/* @__PURE__ */ a(
|
|
2019
|
+
/* @__PURE__ */ a(Ze, { children: /* @__PURE__ */ a(In, {}) }),
|
|
2006
2020
|
/* @__PURE__ */ a("div", { className: "graph-breadcrumb", children: t.map((r, o) => /* @__PURE__ */ p("span", { className: "graph-breadcrumb-wrap", children: [
|
|
2007
2021
|
o > 0 && /* @__PURE__ */ a("span", { className: "graph-breadcrumb-sep", children: "/" }),
|
|
2008
2022
|
/* @__PURE__ */ a(
|
|
@@ -2016,51 +2030,51 @@ function On() {
|
|
|
2016
2030
|
}
|
|
2017
2031
|
)
|
|
2018
2032
|
] }, o)) }),
|
|
2019
|
-
/* @__PURE__ */ a(
|
|
2033
|
+
/* @__PURE__ */ a(Fn, { state: n, onClose: () => e({ x: 0, y: 0, visible: !1 }) })
|
|
2020
2034
|
]
|
|
2021
2035
|
}
|
|
2022
2036
|
);
|
|
2023
2037
|
}
|
|
2024
|
-
const
|
|
2038
|
+
const Rn = [
|
|
2025
2039
|
{ id: "summary", label: "Summary", icon: "▤" },
|
|
2026
2040
|
{ id: "params", label: "Params", icon: "⚙" },
|
|
2027
2041
|
{ id: "inputs", label: "Inputs", icon: "→" },
|
|
2028
2042
|
{ id: "outputs", label: "Outputs", icon: "←" },
|
|
2029
2043
|
{ id: "preview", label: "Preview", icon: "◉" }
|
|
2030
2044
|
];
|
|
2031
|
-
function
|
|
2032
|
-
const e = g((
|
|
2045
|
+
function jn({ style: n }) {
|
|
2046
|
+
const e = g((m) => m.selectedNodeId), t = g((m) => m.nodes), s = g((m) => m.results), r = g((m) => m.updateParam), o = g((m) => m.removeNode), i = g((m) => m.nodeDefs), [d, h] = E("summary"), c = t.find((m) => m.id === e);
|
|
2033
2047
|
return /* @__PURE__ */ p("div", { className: "side-inspector", style: n, children: [
|
|
2034
|
-
/* @__PURE__ */ a("div", { className: "inspector-tags", children:
|
|
2048
|
+
/* @__PURE__ */ a("div", { className: "inspector-tags", children: Rn.map((m) => /* @__PURE__ */ p(
|
|
2035
2049
|
"button",
|
|
2036
2050
|
{
|
|
2037
|
-
className: `inspector-tag ${d ===
|
|
2038
|
-
onClick: () => h(
|
|
2039
|
-
title:
|
|
2051
|
+
className: `inspector-tag ${d === m.id ? "active" : ""}`,
|
|
2052
|
+
onClick: () => h(m.id),
|
|
2053
|
+
title: m.label,
|
|
2040
2054
|
children: [
|
|
2041
|
-
/* @__PURE__ */ a("span", { className: "inspector-tag-icon", children:
|
|
2042
|
-
/* @__PURE__ */ a("span", { className: "inspector-tag-label", children:
|
|
2055
|
+
/* @__PURE__ */ a("span", { className: "inspector-tag-icon", children: m.icon }),
|
|
2056
|
+
/* @__PURE__ */ a("span", { className: "inspector-tag-label", children: m.label })
|
|
2043
2057
|
]
|
|
2044
2058
|
},
|
|
2045
|
-
|
|
2059
|
+
m.id
|
|
2046
2060
|
)) }),
|
|
2047
|
-
/* @__PURE__ */ a("div", { className: "inspector-body", children: c ? /* @__PURE__ */ p(
|
|
2048
|
-
d === "summary" && /* @__PURE__ */ a(
|
|
2061
|
+
/* @__PURE__ */ a("div", { className: "inspector-body", children: c ? /* @__PURE__ */ p(K, { children: [
|
|
2062
|
+
d === "summary" && /* @__PURE__ */ a($n, { node: c, onDelete: () => o(c.id) }),
|
|
2049
2063
|
d === "params" && /* @__PURE__ */ a(
|
|
2050
|
-
|
|
2064
|
+
Ln,
|
|
2051
2065
|
{
|
|
2052
2066
|
node: c,
|
|
2053
|
-
def: i.find((
|
|
2054
|
-
onParam: (
|
|
2067
|
+
def: i.find((m) => m.type === c.data.type),
|
|
2068
|
+
onParam: (m, u) => r(c.id, m, u)
|
|
2055
2069
|
}
|
|
2056
2070
|
),
|
|
2057
|
-
d === "inputs" && /* @__PURE__ */ a(
|
|
2058
|
-
d === "outputs" && /* @__PURE__ */ a(
|
|
2059
|
-
d === "preview" && /* @__PURE__ */ a(
|
|
2071
|
+
d === "inputs" && /* @__PURE__ */ a(Ee, { title: "Inputs", ports: c.data.inputs, dir: "in" }),
|
|
2072
|
+
d === "outputs" && /* @__PURE__ */ a(Ee, { title: "Outputs", ports: c.data.outputs, dir: "out" }),
|
|
2073
|
+
d === "preview" && /* @__PURE__ */ a(Dn, { result: s[c.id] })
|
|
2060
2074
|
] }) : /* @__PURE__ */ a("div", { className: "empty", children: "Select a node to edit its parameters" }) })
|
|
2061
2075
|
] });
|
|
2062
2076
|
}
|
|
2063
|
-
function
|
|
2077
|
+
function $n({
|
|
2064
2078
|
node: n,
|
|
2065
2079
|
onDelete: e
|
|
2066
2080
|
}) {
|
|
@@ -2084,7 +2098,7 @@ function jn({
|
|
|
2084
2098
|
)
|
|
2085
2099
|
] });
|
|
2086
2100
|
}
|
|
2087
|
-
function
|
|
2101
|
+
function Ln({
|
|
2088
2102
|
node: n,
|
|
2089
2103
|
def: e,
|
|
2090
2104
|
onParam: t
|
|
@@ -2107,7 +2121,7 @@ function $n({
|
|
|
2107
2121
|
{
|
|
2108
2122
|
className: "btn",
|
|
2109
2123
|
onClick: async () => {
|
|
2110
|
-
const r = s.browse === "save", o = await
|
|
2124
|
+
const r = s.browse === "save", o = await Q(
|
|
2111
2125
|
r ? "save" : "import",
|
|
2112
2126
|
r ? { defaultName: "model.safetensors" } : void 0
|
|
2113
2127
|
);
|
|
@@ -2129,7 +2143,7 @@ function $n({
|
|
|
2129
2143
|
] }, s.name)) : /* @__PURE__ */ a("div", { className: "port-kind", children: "No parameters" })
|
|
2130
2144
|
] });
|
|
2131
2145
|
}
|
|
2132
|
-
function
|
|
2146
|
+
function Ee({
|
|
2133
2147
|
title: n,
|
|
2134
2148
|
ports: e,
|
|
2135
2149
|
dir: t
|
|
@@ -2146,7 +2160,7 @@ function Te({
|
|
|
2146
2160
|
] }, s.name))
|
|
2147
2161
|
] });
|
|
2148
2162
|
}
|
|
2149
|
-
function
|
|
2163
|
+
function Dn({ result: n }) {
|
|
2150
2164
|
return /* @__PURE__ */ p("div", { className: "section", children: [
|
|
2151
2165
|
/* @__PURE__ */ a("h3", { children: "Preview" }),
|
|
2152
2166
|
n ? /* @__PURE__ */ a("div", { className: "preview", children: Object.entries(n).map(([e, t]) => /* @__PURE__ */ p("div", { style: { marginBottom: 6 }, children: [
|
|
@@ -2167,7 +2181,7 @@ function Ln({ result: n }) {
|
|
|
2167
2181
|
alt: "preview",
|
|
2168
2182
|
style: { maxWidth: "100%", marginTop: 4, borderRadius: 4, border: "1px solid var(--color-border)" }
|
|
2169
2183
|
}
|
|
2170
|
-
) : /* @__PURE__ */ p(
|
|
2184
|
+
) : /* @__PURE__ */ p(K, { children: [
|
|
2171
2185
|
/* @__PURE__ */ a("div", { children: t.summary }),
|
|
2172
2186
|
t.dataPreview && /* @__PURE__ */ p("div", { style: { color: "var(--color-text-secondary)", whiteSpace: "pre-wrap" }, children: [
|
|
2173
2187
|
t.dataPreview,
|
|
@@ -2177,8 +2191,8 @@ function Ln({ result: n }) {
|
|
|
2177
2191
|
] }, e)) }) : /* @__PURE__ */ a("div", { className: "port-kind", children: "Not executed" })
|
|
2178
2192
|
] });
|
|
2179
2193
|
}
|
|
2180
|
-
function
|
|
2181
|
-
const [e, t] =
|
|
2194
|
+
function An({ style: n }) {
|
|
2195
|
+
const [e, t] = E("logs"), s = g((i) => i.logs), r = g((i) => i.nodes), o = g((i) => i.status);
|
|
2182
2196
|
return /* @__PURE__ */ p("div", { className: "status-panel", style: n, children: [
|
|
2183
2197
|
/* @__PURE__ */ p("div", { className: "tabs", children: [
|
|
2184
2198
|
/* @__PURE__ */ a(
|
|
@@ -2204,14 +2218,14 @@ function Dn({ style: n }) {
|
|
|
2204
2218
|
] }, i.id)) })
|
|
2205
2219
|
] });
|
|
2206
2220
|
}
|
|
2207
|
-
function
|
|
2208
|
-
var
|
|
2209
|
-
const n = g((
|
|
2221
|
+
function Mn() {
|
|
2222
|
+
var u;
|
|
2223
|
+
const n = g((b) => b.losses), e = g((b) => b.train), t = g((b) => b.status), [s, r] = E(20), [o, i] = E(1e-3), d = 300, h = 160, c = 10, m = (() => {
|
|
2210
2224
|
if (n.length === 0) return "";
|
|
2211
|
-
const
|
|
2212
|
-
return n.map((f,
|
|
2213
|
-
const y = c +
|
|
2214
|
-
return `${y.toFixed(1)},${
|
|
2225
|
+
const b = Math.min(...n), v = Math.max(...n) - b || 1;
|
|
2226
|
+
return n.map((f, C) => {
|
|
2227
|
+
const y = c + C / Math.max(n.length - 1, 1) * (d - c * 2), F = c + (1 - (f - b) / v) * (h - c * 2);
|
|
2228
|
+
return `${y.toFixed(1)},${F.toFixed(1)}`;
|
|
2215
2229
|
}).join(" ");
|
|
2216
2230
|
})();
|
|
2217
2231
|
return /* @__PURE__ */ p("div", { className: "loss-panel", children: [
|
|
@@ -2225,7 +2239,7 @@ function An() {
|
|
|
2225
2239
|
min: 1,
|
|
2226
2240
|
step: 1,
|
|
2227
2241
|
value: s,
|
|
2228
|
-
onChange: (
|
|
2242
|
+
onChange: (b) => r(Math.max(1, Math.floor(Number(b.target.value) || 1)))
|
|
2229
2243
|
}
|
|
2230
2244
|
)
|
|
2231
2245
|
] }),
|
|
@@ -2238,7 +2252,7 @@ function An() {
|
|
|
2238
2252
|
min: 0,
|
|
2239
2253
|
step: "any",
|
|
2240
2254
|
value: o,
|
|
2241
|
-
onChange: (
|
|
2255
|
+
onChange: (b) => i(Number(b.target.value) || 0)
|
|
2242
2256
|
}
|
|
2243
2257
|
)
|
|
2244
2258
|
] }),
|
|
@@ -2252,11 +2266,11 @@ function An() {
|
|
|
2252
2266
|
}
|
|
2253
2267
|
)
|
|
2254
2268
|
] }),
|
|
2255
|
-
n.length === 0 ? /* @__PURE__ */ a("div", { className: "port-kind", style: { padding: 8 }, children: "No loss history. Add a loss node (e.g. cross_entropy_loss) + a data loader, set steps / LR above, then click Train." }) : /* @__PURE__ */ p(
|
|
2269
|
+
n.length === 0 ? /* @__PURE__ */ a("div", { className: "port-kind", style: { padding: 8 }, children: "No loss history. Add a loss node (e.g. cross_entropy_loss) + a data loader, set steps / LR above, then click Train." }) : /* @__PURE__ */ p(K, { children: [
|
|
2256
2270
|
/* @__PURE__ */ a("svg", { viewBox: `0 0 ${d} ${h}`, className: "loss-svg", preserveAspectRatio: "none", children: /* @__PURE__ */ a(
|
|
2257
2271
|
"polyline",
|
|
2258
2272
|
{
|
|
2259
|
-
points:
|
|
2273
|
+
points: m,
|
|
2260
2274
|
fill: "none",
|
|
2261
2275
|
stroke: "var(--color-primary)",
|
|
2262
2276
|
strokeWidth: "2"
|
|
@@ -2269,39 +2283,39 @@ function An() {
|
|
|
2269
2283
|
] }),
|
|
2270
2284
|
/* @__PURE__ */ p("span", { children: [
|
|
2271
2285
|
"last loss: ",
|
|
2272
|
-
(
|
|
2286
|
+
(u = n[n.length - 1]) == null ? void 0 : u.toFixed(4)
|
|
2273
2287
|
] })
|
|
2274
2288
|
] })
|
|
2275
2289
|
] })
|
|
2276
2290
|
] });
|
|
2277
2291
|
}
|
|
2278
|
-
const Mn = '# Entropia Riko 用户指南\n\n> 版本:v0.1 · 节点数:195+(含插件)· 后端:PyTorch(默认)+ TensorFlow/Keras(可选)\n> 本文档是面向用户的完整使用手册,覆盖界面、节点、构图、训练、导出与开发。\n\n---\n\n## 目录\n\n1. [项目简介](#1-项目简介)\n2. [快速开始](#2-快速开始)\n3. [界面总览](#3-界面总览)\n4. [模块化面板(Blender 风格)](#4-模块化面板blender-风格)\n5. [欢迎窗口](#5-欢迎窗口)\n6. [文件与插件](#6-文件与插件)\n7. [节点系统(191 节点)](#7-节点系统191-节点)\n8. [构图与执行](#8-构图与执行)\n9. [数据输入与预览节点](#9-数据输入与预览节点)\n10. [Wrangle 代码节点](#10-wrangle-代码节点)\n11. [训练与实时 Loss 曲线](#11-训练与实时-loss-曲线)\n12. [导出代码](#12-导出代码)\n13. [.riko 文件格式](#13-tns-文件格式)\n14. [API 端点](#14-api-端点)\n15. [后端架构](#15-后端架构)\n16. [开发与测试](#16-开发与测试)\n17. [已知限制与后续方向](#17-已知限制与后续方向)\n\n---\n\n## 1. 项目简介\n\nEntropia Riko 是一个**独立的节点图编辑器**,用 ComfyUI 风格的画布搭建并运行\nPyTorch(及可选 TensorFlow/Keras)张量工作流。它不是 Houdini 插件。\n\n核心特性:\n\n- **191 个节点**:数学、张量操作、神经网络激活/层、注意力、归一化、归约、形状、\n 创建、损失、数据加载、模型推理、子图引用、Wrangle 代码、预览,以及 TF/Keras。\n- **Blender 风格模块化面板**:拖角分离/合并、任意切换窗口类型、实时缩放。\n- **实时训练 + Loss 曲线**:自包含图(数据加载 + 损失)可直接训练并流式显示损失。\n- **代码导出**:导出干净的 `torch.nn.Module` 或 `tf.keras.Model` 脚本。\n- **子图 import**:一个 `.riko` 图可像 `import xx` 一样引用另一个图。\n- **数据预览**:图片缩略图、文本、JSON 直接在检查器里显示。\n\n---\n\n## 2. 快速开始\n\n### 前置条件\n- Python 3.10+\n- Node.js 18+\n- PyTorch(CPU 版即可;CUDA/MPS 可选)\n\n### 启动(两个终端)\n\n```bash\ncd ~/Documents/torch-node/entropia-riko\n\n# 终端 1:API 服务器(必须先启动)\n.venv/bin/python -m uvicorn src.server.app:app --reload --port 8000\n\n# 终端 2:前端开发服务器\nnpm run dev # → http://localhost:5173\n```\n\n打开 **http://localhost:5173**(务必通过 5173 访问,`/api` 走 Vite 代理)。\n\n> ⚠️ 若看不到 190+ 节点,说明 API 服务器没启动或旧进程仍在(无 `--reload` 的旧进程\n> 不会加载新代码,需杀掉重启)。改代码后浏览器需硬刷新 `Cmd+Shift+R`。\n\n---\n\n## 3. 界面总览\n\n```\n┌ 标题栏(* 未保存 · 文件名 - Riko · 版本号)────────────────────┐\n├ 菜单栏(logo · File / Run / Data / Help,右侧状态徽章)────────┤\n├ 模块化面板区域(Blender 风格区域树)──────────────────────────┤\n│ [节点库] [Asset Library] [New File] [画布] [检查器] │\n│ [状态/日志] [Loss 曲线] │\n└──────────────────────────────────────────────────────────────┘\n```\n\n- **标题栏**:未保存时显示 `*`,随后 `文件名 - Riko`,右侧版本号。\n- **菜单栏**:左上角 **logo**(点开 About / Welcome / Preferences);File(Import/Export\n → 内置 **File Explorer** 导入导出文件/文件夹;Export Code 导出 .py)、Run、Data、\n View(含 **Liquid Glass** 主题)、Help。\n- **节点库**:按分类分组、可折叠、可搜索,点击节点加入画布。\n- **画布**:React Flow 节点图,右键弹搜索式节点菜单。\n- **检查器**:左侧 tag 切换(Summary/Params/Inputs/Outputs/Preview)。\n- **New File**:右上角 mini 文件管理器(右键新建/删除/预览代码/展开节点)。\n- **Asset Library**:`.riko`/`.ric` 资产树(打开 / **展开完整节点** / 保存)。\n- **Code Editor**:代码编辑器窗口(类似记事本,编辑/复制/全选/保存/生成)。\n- **Handwriting Pad**:28×28 手写板,画数字 → 生成 `constant` 节点喂 MNIST。\n- **状态栏**:Logs(日志)与 Queue(节点队列)两个标签。\n- **Loss 曲线**:训练损失的实时 SVG 折线图。\n- **Plugins**:插件管理面板(右上角 `+` 从 `.py` 加载,每个插件带启用/禁用开关)。\n\n---\n\n## 4. 模块化面板(Blender 风格)\n\n每个面板右上角有一个**类型下拉**(切换该窗口显示的内容)和 **✕**(关闭当前面板);\n右下角有一个**角抓手**。\n\n| 操作 | 手势 | 效果 |\n|------|------|------|\n| 切换窗口类型 | 点右上角 `类型 ▾` | Node Library / New File / Asset Library / Graph / Inspector / Status / Loss Curve / Docs / Plugins / Code Editor / Handwriting Pad 十一选一 |\n| 分离(split) | 角抓手**向左/向上拖** | 蓝色预览线跟随鼠标(只在该面板内),松手在 50% 处分出新面板,方向=拖动轴向 |\n| 合并(merge) | 角抓手**向右/向下拖** | 相邻窗口被**蓝色阴影 + 加粗边框**高亮,松手它被并掉、当前面板扩大 |\n| 关闭当前面板 | 点 **✕** | 当前面板被移除,邻居吸收空间 |\n| 缩放 | 拖分隔条 | 所有分隔条可同时独立拖动 |\n\n默认布局:`[节点库/Asset Library 上下] | [画布 | (New File 上 / 检查器)]`,下方\n`[状态 | Loss 曲线]`(New File 只占据最右列、检查器上方,不占用画布区域)。\n\n---\n\n## 5. 欢迎窗口\n\n启动时弹出 Blender 风格欢迎窗口:\n\n- **头图**:上半部为可替换 **JPEG 位图**(`public/brand/hero.jpg`,960×220),右上角显示\n 版本号;左下角为 logo(`public/brand/logo.svg`)。\n- **New File**:`General`(空白图)+ 从 `examples/` 读出的**预制模板**\n (cnn / classifier / mlp / mnist_cnn / transformer / …),点击即载入。\n- **Recent Files**:`localStorage` 记录最近打开过的 `.riko`(去重、最多 10 条)。\n\n点 ✕ 或选择任意项关闭;**Help → Welcome Screen** 或 logo 菜单可随时重新打开。\n\n---\n\n## 6. 文件与插件\n\n**New File**(最右列、检查器上方)是一个 **mini 文件管理器**,映射**工作目录**的\n目录树(可展开/折叠子目录)。**右键**弹出菜单:新建文件/新建文件夹/重命名/删除、\n打开、**预览 PyTorch 代码**(`/api/project/code` 导出 `nn.Module`)、导入为节点。\n**File → Import Working Folder…** 可把工作目录指向任意本地文件夹,并在其中创建\n`.riko` 缓存文件夹存放工具自身状态。\n\n**Asset Library**(原 Files 面板,Blender-outliner 风格资产树)在其基础上增加保存与\nimport 依赖视图,每个文件可展开显示其 `import` 依赖(✓ 已解析 / ✗ 未解析)。\n\n- **打开**:点文件名载入画布。\n- **导入为节点**:点 `⤵` 添加一个引用该文件的 `import` 节点。\n- **保存**:在输入框命名后点 `+ Save .riko`(或 `⇩ .ric`),存到 `workflows/<name>`。\n\n**子图引用**:一个 `.riko` 可被 `graph_reference`(按 `file` 路径)或 `import`\n(按模块名,如 `mlp`,在 `workflows/`、`examples/`、`examples/models/` 中解析)引用。\n被引用图需含 `graph_input(name="input")` 与 `graph_output(name="output")`。\n\n**Plugins 面板**:右上角 `+` 从本地 `.py` 文件加载插件(安装到 `plugins/<name>/`),\n每个插件带启用/禁用开关(禁用即从节点库移除其节点)。内置插件:\n`example_plugin`(`plugin_double`)、`math_extra`(`plugin_square`、`plugin_scale`)、\n`stat_extra`(`plugin_shift_scale`、`plugin_neg`)。\n\n---\n\n## 7. 节点系统(195+ 节点)\n\n### 输入\n`constant`\n\n### 数学(纯 Python)\n`add`, `multiply`\n\n### 数学(torch)\n`torch_add`, `torch_multiply`, `sub`, `div`, `pow`, `matmul`, `mm`, `maximum`,\n`minimum`, `fmod`, `remainder`, `atan2`\n\n### 张量(单目)\n`abs`, `exp`, `log`, `log2`, `log10`, `log1p`, `expm1`, `sqrt`, `erf`, `erfc`,\n`neg`, `sign`, `reciprocal`, `floor`, `ceil`, `round`, `square`, `cos`, `sin`\n\n### 神经网络(激活)\n`relu`, `leaky_relu`, `sigmoid`, `tanh`, `gelu`, `silu`, `selu`, `elu`, `mish`,\n`hardswish`, `hardsigmoid`, `hardtanh`, `softsign`, `tanhshrink`, `softplus`,\n`relu6`, `glu`, `softmax`, `log_softmax`, `log_sigmoid`\n\n### 注意力\n`multihead_attention`, `sdpa`(scaled dot-product attention)\n\n### 神经网络(层)\n`linear`, `conv2d`, `conv1d`, `conv_transpose2d`, `maxpool2d`, `avgpool2d`,\n`embedding`, `dropout`, `batchnorm1d`, `batchnorm2d`, `layernorm`, `groupnorm`,\n`rmsnorm`, `transformer_encoder`\n\n### 归约\n`sum`, `mean`, `amax`, `amin`, `logsumexp`, `median`, `max_reduce`, `min_reduce`,\n`prod`, `std`, `var`, `norm`, `argmax`, `argmin`, `cumsum`, `topk`, `sort`\n\n### 形状\n`reshape`, `view`, `transpose`, `swapaxes`, `movedim`, `permute`, `flatten`,\n`squeeze`, `unsqueeze`, `concat`, `stack`, `flip`, `expand`, `expand_as`,\n`broadcast_to`, `tile`, `repeat`, `repeat_interleave`, `tril`, `triu`,\n`diagonal`, `narrow`, `roll`, `index_select`, `gather`, `interpolate`\n\n### 创建\n`zeros`, `ones`, `rand`, `randn`, `randint`, `randperm`, `empty`, `zeros_like`,\n`ones_like`, `randn_like`, `rand_like`, `eye`, `arange`, `linspace`, `full`,\n`positional_encoding`\n\n### 张量算子\n`bmm`, `dot`, `outer`, `xlogy`, `cross`, `addmm`, `einsum`, `clamp`,\n`contiguous`, `clone`, `detach`, `masked_fill`, `where`\n\n### 损失\n`mse_loss`, `cross_entropy_loss`, `l1_loss`, `smooth_l1_loss`,\n`binary_cross_entropy`, `kl_div`, `nll_loss`, `hinge_embedding_loss`,\n`cosine_embedding_loss`\n\n### 设备\n`to_device`\n\n### 模型\n`model_loader`(加载 .pt)、`inference`(跑 forward)\n\n### 子图\n`graph_input`, `graph_output`, `graph_reference`(按路径), `import`(按模块名)\n\n### 数据(数据集加载)\n`mnist_loader`, `cifar10_loader`, `csv_loader`, `image_folder_loader`,\n`tensor_file_loader`, `dataloader`, `text_loader`, `json_loader`, `image_loader`\n\n### Wrangle 与预览\n`wrangle`(内联 Python 代码), `text_preview`, `json_preview`, `image_preview`\n\n### TensorFlow / Keras(可选后端)\n`keras_dense`, `keras_conv2d`, `keras_flatten`, `keras_embedding`,\n`keras_layernorm`, `keras_dropout`, `keras_relu`, `keras_sigmoid`, `keras_tanh`,\n`keras_gelu`, `keras_softmax`, `keras_maxpool2d`, `keras_avgpool2d`, `tf_add`,\n`tf_multiply`, `tf_matmul`, `tf_concat`, `tf_reshape`, `tf_transpose`, `tf_reduce`\n\n> Keras 卷积用 channels-last `(B,H,W,C)`,与 torch 的 channels-first 不同。\n\n---\n\n## 8. 构图与执行\n\n1. 从节点库点击/拖入节点(或画布右键搜索添加)。\n2. 拖端口连线;点击节点在右侧检查器改参数。\n3. **Run → Inference** 执行整图,结果显示在节点卡片和检查器预览里。\n\n执行后端:校验(端口/必需输入/环检测)→ Kahn 拓扑排序 → 逐节点 `execute`。\n`TensorValue` 是框架无关的中间表示(纯 Python 数据),torch/TF 节点在 `execute` 内\n惰性 import 并做转换。\n\n---\n\n## 9. 数据输入与预览节点\n\n- **输入**:`text_loader`(读文本)、`json_loader`(读 JSON)、`image_loader`\n (用 PIL 读图片,输出 H×W×3 image_tensor)、`csv_loader`、`mnist_loader` 等。\n- **预览**:\n - `text_preview`:把任意值转成文本。\n - `json_preview`:把张量/值序列化成缩进 JSON。\n - `image_preview`:把图像张量转成 **base64 PNG 缩略图**,在检查器里直接显示\n `<img>`。\n\n`TensorValue` 支持 `kind`:`scalar` / `tensor` / `image_tensor` / `text` / `json` /\n`model`。文本/JSON 不做数值 shape 推断。\n\n---\n\n## 10. Wrangle 代码节点\n\n`wrangle` 节点用于解决内置节点覆盖不到的逻辑。参数 `code` 是任意 Python:\n\n```python\n# 可用变量:x / a / b(已连接输入张量,未连接为 None)、torch、F、nn\nresult = x * 2 + 1\n# 或:\n# result = torch.softmax(x, dim=-1)\n# result = F.gelu(x)\n```\n\n`execute` 把输入转成 torch 张量后 `exec(code)`,取 `result`(或 `y`)作为输出。\n导出时会把代码内联进 `forward`。\n\n---\n\n## 11. 训练与实时 Loss 曲线\n\n**可训练图** = 数据加载节点(如 `dataloader`/`mnist_loader`)+ 损失节点\n(`cross_entropy_loss` 等),且不含 `graph_input`。\n\n- **Run → Train 20/100 steps** 或 Loss 面板按钮开始训练。\n- 后端用 codegen 生成 `nn.Module` → `AdamW` 优化器逐步 `backward()`/`step()`,\n 通过 **SSE(`/api/train/stream`)逐步回传 loss**,前端 SVG 曲线实时增长。\n\n注意:执行器本身仍用 `no_grad()` 逐次重建层;真正的训练走「导出 → 执行」这条路径。\n\n---\n\n## 12. 导出代码\n\n- **File → Export .py (PyTorch)**:生成干净的 `torch.nn.Module`。\n - 可学习层进 `__init__`,内联算子进 `forward`。\n - `graph_input` → `forward` 参数;`graph_output` → `return`。\n - `import`/`graph_reference` 递归内联为嵌套 `nn.Module` 类。\n - 数据加载节点生成真实可运行代码(torchvision + 随机回退)。\n - 自包含训练图附带 `train()` + `__main__` 训练循环。\n- **File → Export .py (Keras/TF)**:对 TF/Keras 节点图生成 `tf.keras.Model`\n (层进 `__init__`,算子进 `call`)。\n\n---\n\n## 13. .riko 文件格式\n\n`.riko` 是 JSON,保存完整工作流:\n\n```json\n{\n "version": "0.1",\n "metadata": { "name": "classifier", "description": "...",\n "inputs": [{"name": "input", "data_kind": "tensor"}],\n "outputs": [{"name": "output", "data_kind": "tensor"}] },\n "nodes": [\n {"id": "a", "type_name": "constant", "label": "Constant", "category": "Inputs",\n "position": [100, 200], "parameters": {"value": 2.0}, "inputs": [], "outputs": []}\n ],\n "edges": [\n {"id": "e1", "source_node": "a", "source_port": "value",\n "target_node": "b", "target_port": "left"}\n ],\n "settings": {}\n}\n```\n\n- Save 下载 `workflow.riko`;Load 接受 `.riko`/`.json`。\n- 文件管理器另提供 `POST /api/files/save` 存到 `workflows/`。\n\n---\n\n## 14. API 端点\n\n| 方法 | 路径 | 说明 |\n|------|------|------|\n| GET | `/api/health` | 健康检查 |\n| GET | `/api/nodes` | 全部节点定义(含已启用插件) |\n| POST | `/api/execute` | 执行 GraphDocument JSON,返回各节点端口输出 |\n| POST | `/api/export_python` | 生成 `torch.nn.Module` 代码 |\n| POST | `/api/export_keras` | 生成 `tf.keras.Model` 代码 |\n| POST | `/api/export_binary` | 将图文档编码为 `.ric` 二进制(base64 下载) |\n| GET | `/api/files` | 列出 `.riko` 文件及其 import 依赖 |\n| GET | `/api/files/content?path=` | 读取 `.riko` 内容 |\n| POST | `/api/files/save` | 保存 `.riko` 到 `workflows/` |\n| GET | `/api/plugins` | 列出插件(enabled/disabled + 注册节点) |\n| POST | `/api/plugins/toggle` | 启用/禁用插件(`{name, enabled}`) |\n| POST | `/api/plugins/upload` | 从 Python 源码安装插件(`{name, code}`) |\n| GET | `/api/project/tree` | 返回工作目录的递归目录树(隐藏 `.riko` 缓存) |\n| POST | `/api/project/set_root` | 导入工作文件夹(`{path}`),创建 `.riko` 缓存并持久化 |\n| POST | `/api/project/create` | 在工作目录新建空 `.riko`(`{name, dir?}`) |\n| POST | `/api/project/mkdir` | 在工作目录新建文件夹(`{name, dir?}`) |\n| POST | `/api/project/rename` | 重命名文件/文件夹(`{path, newName}`) |\n| POST | `/api/project/delete` | 删除文件/空文件夹(`{path}`) |\n| POST | `/api/project/code` | 返回该 `.riko` 导出的 PyTorch 代码(`{path}`) |\n| GET | `/api/project/open?path=` | 读取工作目录内 `.riko`/`.ric` 内容 |\n| POST | `/api/train` | 训练并一次性返回 loss 列表 |\n| POST | `/api/train/stream` | 训练并流式(NDJSON)返回逐步 loss |\n\n---\n\n## 15. 后端架构\n\n```\nReact UI (@xyflow/react) ──HTTP──▶ FastAPI\n │ │\n └─ 图文档 JSON ──▶ Runtime(registry + executor + codegen + trainer + subgraph)\n │\n Tensor IR(core/tensor.py TensorValue + document.py)\n │\n Backend(device/converter torch ↔ TensorValue;\n tf_converter tf ↔ TensorValue)\n```\n\n分层与依赖方向:\n\n- `core`(纯 Python,绝不 import torch/TF)\n- `runtime`(注册表 / 执行器 / codegen / codegen_tf / trainer / subgraph)\n- `nodes`(math 纯 Python;torch_ops、tf_ops 惰性 import)\n- `server`(FastAPI)\n- `backend`(torch/TF 转换,均 optional,缺依赖时报清晰错误)\n\n---\n\n## 16. 开发与测试\n\n```bash\n# Python 测试(97 tests,stdlib unittest)\n.venv/bin/python -m unittest discover -s tests -t .\n\n# 前端测试(8 tests,vitest)\nnpm test\n\n# 前端类型检查 + 构建\nnpm run build\n\n# 验证节点数(内置 194;加上插件后 199+)\n.venv/bin/python -c "import src.nodes; from src.runtime.registry import default_registry; print(len(default_registry().list()))"\n# 应打印 194(未加载插件时)\n```\n\n### 新增一个节点\n\n```python\n# src/nodes/.../my_node.py\nfrom ..base import BaseNode, NodeInput, NodeOutput, Parameter\nfrom ...runtime.registry import register\n\n@register("my_node")\nclass MyNode(BaseNode):\n type_name = "my_node"\n label = "My Node"\n category = "Utility"\n inputs = [NodeInput("x", data_kind="tensor", required=True)]\n outputs = [NodeOutput("result", data_kind="tensor")]\n parameters = [Parameter("scale", default=1.0, dtype="float")]\n\n def execute(self, inputs, params, context):\n ...\n return {"result": ...}\n```\n\n然后在对应 `__init__.py` 里 `from . import my_node`,并在 codegen 里加导出分支。\n\n---\n\n## 17. 已知限制与后续方向\n\n1. **执行器级训练未原生实现**:训练走「codegen → exec」路径;原生优化器节点待加。\n2. **子图固定 input/output 端口**:`graph_reference`/`import` 目前只支持单输入单输出。\n3. **Keras 卷积布局**:channels-last,与 torch 不同,混用时需 transpose。\n4. **TF 未随包安装**:`requirements.txt` 中注释了 `tensorflow>=2.10`,启用需自行安装。\n5. **无 undo/redo、无键盘快捷键、无画布内搜索**(右键菜单有搜索)。\n6. **`Toolbar.tsx` 与 `.doc_backups/` 遗留**:可安全删除。\n\n后续方向:原生训练循环、优化器/梯度裁剪节点、多端口子图、模型动物园(ResNet/VGG/BERT\n预制 `.riko`)、JAX 后端、分组卷积/谱分解(svd/qr/eig)等更多 torch API 覆盖。\n', Wn = '# Entropia Riko User Guide\n\n> Version: v0.1 · Nodes: 191 · Backends: PyTorch (default) + TensorFlow/Keras (optional)\n> This is the complete user manual: interface, nodes, graph building, training, export and development.\n\n---\n\n## Table of Contents\n\n1. [Overview](#1-overview)\n2. [Quick Start](#2-quick-start)\n3. [Interface](#3-interface)\n4. [Modular Panels (Blender-style)](#4-modular-panels-blender-style)\n5. [Welcome Screen](#5-welcome-screen)\n6. [File Manager & Import](#6-file-manager--import)\n7. [Node System (191 nodes)](#7-node-system-191-nodes)\n8. [Building & Running Graphs](#8-building--running-graphs)\n9. [Data Input & Preview Nodes](#9-data-input--preview-nodes)\n10. [Wrangle Code Node](#10-wrangle-code-node)\n11. [Training & Real-time Loss Curve](#11-training--real-time-loss-curve)\n12. [Exporting Code](#12-exporting-code)\n13. [.riko File Format](#13-tns-file-format)\n14. [API Endpoints](#14-api-endpoints)\n15. [Backend Architecture](#15-backend-architecture)\n16. [Development & Testing](#16-development--testing)\n17. [Known Limitations](#17-known-limitations)\n\n---\n\n## 1. Overview\n\nEntropia Riko is a **standalone node-graph editor** for building and running\nPyTorch (and optionally TensorFlow/Keras) tensor workflows, with a ComfyUI-style\ncanvas and a Material-inspired UI. It is **not** a Houdini plugin.\n\nKey features:\n\n- **191 nodes**: math, tensor ops, neural activations/layers, attention,\n normalization, reductions, shape ops, creation, losses, data loading, model\n inference, subgraph references, Wrangle code, previews, and TF/Keras.\n- **Blender-style modular panels**: drag a corner to split/merge, switch any\n window\'s type, and resize everything simultaneously.\n- **Real-time training + loss curve**: self-contained graphs (data + loss)\n train live with a streaming SVG loss chart.\n- **Code export**: generate clean `torch.nn.Module` or `tf.keras.Model` scripts.\n- **Subgraph import**: a `.riko` graph can reference another like `import xx`.\n- **Data previews**: image thumbnails, text and JSON rendered in the inspector.\n\n---\n\n## 2. Quick Start\n\n### Prerequisites\n- Python 3.10+\n- Node.js 18+\n- PyTorch (CPU build is enough; CUDA/MPS optional)\n\n### Run (two terminals)\n\n```bash\ncd ~/Documents/torch-node/entropia-riko\n\n# Terminal 1: API server (start FIRST)\n.venv/bin/python -m uvicorn src.server.app:app --reload --port 8000\n\n# Terminal 2: frontend dev server\nnpm run dev # → http://localhost:5173\n```\n\nOpen **http://localhost:5173** (the `/api` proxy only works through 5173).\n\n> ⚠️ If you don\'t see 190+ nodes, the API server isn\'t running, or a stale\n> process started without `--reload` is still serving old code — kill and\n> restart it. After code changes, hard-refresh the browser with `Cmd+Shift+R`.\n\n---\n\n## 3. Interface\n\n```\n┌ Menu bar (File / Run / Data / Help, status badge) ────────────────┐\n├ Modular panel area (Blender-style area tree) ─────────────────────┤\n│ [Node Library] [Files] [Canvas (React Flow)] [Inspector] │\n│ [Status / Logs] [Loss Curve] │\n└────────────────────────────────────────────────────────────────────┘\n```\n\n- **Menu bar**: File (new / save / load / export PyTorch / export Keras),\n Run (inference / Train 20 / 100 steps), Data (quick-add dataset nodes),\n Help (welcome screen / about).\n- **Node Library**: grouped, collapsible, searchable; click to add a node.\n- **Canvas**: React Flow node graph; right-click opens a searchable node menu.\n- **Inspector**: parameters / inputs / outputs / preview of the selected node.\n- **Status**: Logs and Queue tabs.\n- **Loss Curve**: live training loss as an SVG line chart.\n\n---\n\n## 4. Modular Panels (Blender-style)\n\nEach panel has a **type dropdown** (top-right) to switch what it shows and a\n**✕** (close) button; the bottom-right corner is a **grip**.\n\n| Action | Gesture | Effect |\n|--------|---------|--------|\n| Switch type | click `type ▾` | Node Library / Files / Graph / Inspector / Status / Loss Curve / Documentation |\n| Split | drag grip **up/left** | a blue preview line follows the mouse (clipped to the panel); release splits at 50%, orientation = drag axis |\n| Merge | drag grip **down/right** | the sibling panel is **shaded + bold-bordered**; release merges it away, current expands |\n| Close current | click **✕** | current panel removed, neighbor absorbs the space |\n| Resize | drag a splitter | all splitters resize independently and simultaneously |\n\nDefault layout: `[Node Library / Files stacked] | [Graph | Inspector]` over\n`[Status | Loss Curve]`.\n\n---\n\n## 5. Welcome Screen\n\nA Blender-style splash opens on startup, with two columns:\n\n- **New File**: `General` (blank graph) + preset templates read from\n `examples/` (cnn / classifier / mlp / mnist_cnn / transformer / …).\n- **Recent Files**: the last opened `.riko` files (deduped, up to 10, stored in\n `localStorage`).\n\nClose with ✕ or by picking an item; reopen via **Help → Welcome Screen**.\n\n---\n\n## 6. File Manager & Import\n\nThe **Files** panel is a Blender-outliner-style tree of `.riko` files under\n`workflows/` and `examples/`. Each file expands to show its `import`\ndependencies (✓ resolved / ✗ missing).\n\n- **Open**: click a file name to load it.\n- **Import as node**: click `⤵` to add an `import` node referencing it.\n- **Save**: name it and click `+ Save` → `workflows/<name>.riko`.\n\n**Subgraph references**: a `.riko` can be referenced by `graph_reference`\n(by `file` path) or `import` (by module name, e.g. `mlp`, resolved under\n`workflows/`, `examples/`, `examples/models/`). The referenced graph must\ncontain `graph_input(name="input")` and `graph_output(name="output")`.\n\n---\n\n## 7. Node System (191 nodes)\n\n### Inputs\n`constant`\n\n### Math (pure Python)\n`add`, `multiply`\n\n### Math (torch)\n`torch_add`, `torch_multiply`, `sub`, `div`, `pow`, `matmul`, `mm`, `maximum`,\n`minimum`, `fmod`, `remainder`, `atan2`\n\n### Tensor (unary)\n`abs`, `exp`, `log`, `log2`, `log10`, `log1p`, `expm1`, `sqrt`, `erf`, `erfc`,\n`neg`, `sign`, `reciprocal`, `floor`, `ceil`, `round`, `square`, `cos`, `sin`\n\n### Neural (activations)\n`relu`, `leaky_relu`, `sigmoid`, `tanh`, `gelu`, `silu`, `selu`, `elu`, `mish`,\n`hardswish`, `hardsigmoid`, `hardtanh`, `softsign`, `tanhshrink`, `softplus`,\n`relu6`, `glu`, `softmax`, `log_softmax`, `log_sigmoid`\n\n### Attention\n`multihead_attention`, `sdpa` (scaled dot-product attention)\n\n### Neural (layers)\n`linear`, `conv2d`, `conv1d`, `conv_transpose2d`, `maxpool2d`, `avgpool2d`,\n`embedding`, `dropout`, `batchnorm1d`, `batchnorm2d`, `layernorm`, `groupnorm`,\n`rmsnorm`, `transformer_encoder`\n\n### Reduce\n`sum`, `mean`, `amax`, `amin`, `logsumexp`, `median`, `max_reduce`, `min_reduce`,\n`prod`, `std`, `var`, `norm`, `argmax`, `argmin`, `cumsum`, `topk`, `sort`\n\n### Shape\n`reshape`, `view`, `transpose`, `swapaxes`, `movedim`, `permute`, `flatten`,\n`squeeze`, `unsqueeze`, `concat`, `stack`, `flip`, `expand`, `expand_as`,\n`broadcast_to`, `tile`, `repeat`, `repeat_interleave`, `tril`, `triu`,\n`diagonal`, `narrow`, `roll`, `index_select`, `gather`, `interpolate`\n\n### Creation\n`zeros`, `ones`, `rand`, `randn`, `randint`, `randperm`, `empty`, `zeros_like`,\n`ones_like`, `randn_like`, `rand_like`, `eye`, `arange`, `linspace`, `full`,\n`positional_encoding`\n\n### Tensor ops\n`bmm`, `dot`, `outer`, `xlogy`, `cross`, `addmm`, `einsum`, `clamp`,\n`contiguous`, `clone`, `detach`, `masked_fill`, `where`\n\n### Loss\n`mse_loss`, `cross_entropy_loss`, `l1_loss`, `smooth_l1_loss`,\n`binary_cross_entropy`, `kl_div`, `nll_loss`, `hinge_embedding_loss`,\n`cosine_embedding_loss`\n\n### Device\n`to_device`\n\n### Model\n`model_loader` (load .pt), `inference` (run forward)\n\n### Subgraph\n`graph_input`, `graph_output`, `graph_reference` (by path), `import` (by module)\n\n### Data (dataset loaders)\n`mnist_loader`, `cifar10_loader`, `csv_loader`, `image_folder_loader`,\n`tensor_file_loader`, `dataloader`, `text_loader`, `json_loader`, `image_loader`\n\n### Wrangle & Preview\n`wrangle` (inline Python code), `text_preview`, `json_preview`, `image_preview`\n\n### TensorFlow / Keras (optional backend)\n`keras_dense`, `keras_conv2d`, `keras_flatten`, `keras_embedding`,\n`keras_layernorm`, `keras_dropout`, `keras_relu`, `keras_sigmoid`, `keras_tanh`,\n`keras_gelu`, `keras_softmax`, `keras_maxpool2d`, `keras_avgpool2d`, `tf_add`,\n`tf_multiply`, `tf_matmul`, `tf_concat`, `tf_reshape`, `tf_transpose`, `tf_reduce`\n\n> Keras convolutions use channels-last `(B,H,W,C)`, unlike torch\'s channels-first.\n\n---\n\n## 8. Building & Running Graphs\n\n1. Add nodes from the library (or right-click the canvas to search).\n2. Drag between ports to connect; click a node to edit parameters in the inspector.\n3. **Run → Inference** executes the whole graph; results appear on node cards\n and in the inspector preview.\n\nExecution backend: validation (ports / required inputs / cycle detection) →\nKahn topological sort → per-node `execute`. `TensorValue` is a framework-neutral\nIR (pure-Python data); torch/TF nodes lazily import and convert inside `execute`.\n\n---\n\n## 9. Data Input & Preview Nodes\n\n- **Inputs**: `text_loader` (read text), `json_loader` (parse JSON),\n `image_loader` (PIL → H×W×3 image_tensor), `csv_loader`, `mnist_loader`, …\n- **Previews**:\n - `text_preview`: convert any value to text.\n - `json_preview`: serialize a tensor/value to indented JSON.\n - `image_preview`: render an image tensor as a **base64 PNG thumbnail** shown\n in the inspector.\n\n`TensorValue` supports `kind`: `scalar` / `tensor` / `image_tensor` / `text` /\n`json` / `model`. Text/JSON payloads skip numeric shape inference.\n\n---\n\n## 10. Wrangle Code Node\n\nThe `wrangle` node covers whatever built-in nodes can\'t. Its `code` parameter\nis arbitrary Python:\n\n```python\n# available: x / a / b (connected input tensors, None if unconnected), torch, F, nn\nresult = x * 2 + 1\n# result = torch.softmax(x, dim=-1)\n# result = F.gelu(x)\n```\n\n`execute` converts inputs to torch tensors, runs `exec(code)`, and returns\n`result` (or `y`). Export inlines the code into `forward`.\n\n---\n\n## 11. Training & Real-time Loss Curve\n\nA **trainable graph** = a data loader node (e.g. `dataloader`/`mnist_loader`) +\na loss node (e.g. `cross_entropy_loss`), with no `graph_input`.\n\n- **Run → Train 20/100 steps** (or the Loss panel buttons) starts training.\n- The backend generates an `nn.Module` via codegen, runs `AdamW` steps with\n `backward()`/`step()`, and streams each loss over **SSE\n (`/api/train/stream`)**; the frontend SVG curve grows in real time.\n\nNote: the executor itself still uses `no_grad()` and rebuilds layers per call;\nreal training goes through the "export → exec" path.\n\n---\n\n## 12. Exporting Code\n\n- **File → Export .py (PyTorch)**: emits a clean `torch.nn.Module`.\n - learnable layers → `__init__`, inline ops → `forward`.\n - `graph_input` → `forward` parameter; `graph_output` → `return`.\n - `import`/`graph_reference` are inlined recursively as nested modules.\n - data loaders emit real runnable code (torchvision + random fallback).\n - self-contained training graphs get a `train()` + `__main__` loop.\n- **File → Export .py (Keras/TF)**: emits a subclassed `tf.keras.Model`\n (layers → `__init__`, ops → `call`).\n\n---\n\n## 13. .riko File Format\n\n`.riko` is JSON storing a complete workflow:\n\n```json\n{\n "version": "0.1",\n "metadata": { "name": "classifier", "description": "...",\n "inputs": [{"name": "input", "data_kind": "tensor"}],\n "outputs": [{"name": "output", "data_kind": "tensor"}] },\n "nodes": [\n {"id": "a", "type_name": "constant", "label": "Constant", "category": "Inputs",\n "position": [100, 200], "parameters": {"value": 2.0}, "inputs": [], "outputs": []}\n ],\n "edges": [\n {"id": "e1", "source_node": "a", "source_port": "value",\n "target_node": "b", "target_port": "left"}\n ],\n "settings": {}\n}\n```\n\n- Save downloads `workflow.riko`; Load accepts `.riko`/`.json`.\n- The file manager additionally saves to `workflows/` via `POST /api/files/save`.\n\n---\n\n## 14. API Endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| GET | `/api/health` | health check |\n| GET | `/api/nodes` | all 191 node definitions |\n| POST | `/api/execute` | execute a GraphDocument JSON, return per-node outputs |\n| POST | `/api/export_python` | generate `torch.nn.Module` code |\n| POST | `/api/export_keras` | generate `tf.keras.Model` code |\n| GET | `/api/files` | list `.riko` files + their import dependencies |\n| GET | `/api/files/content?path=` | read a `.riko` file |\n| POST | `/api/files/save` | save a `.riko` to `workflows/` |\n| POST | `/api/train` | train and return the loss list |\n| POST | `/api/train/stream` | train and stream per-step losses (NDJSON) |\n\n---\n\n## 15. Backend Architecture\n\n```\nReact UI (@xyflow/react) ──HTTP──▶ FastAPI\n │ │\n └─ graph JSON ──▶ Runtime (registry + executor + codegen + trainer + subgraph)\n │\n Tensor IR (core/tensor.py TensorValue + document.py)\n │\n Backend (device/converter torch ↔ TensorValue;\n tf_converter tf ↔ TensorValue)\n```\n\nLayers & dependency direction:\n\n- `core` (pure Python — never imports torch/TF)\n- `runtime` (registry / executor / codegen / codegen_tf / trainer / subgraph)\n- `nodes` (math pure-Python; torch_ops, tf_ops lazy-import)\n- `server` (FastAPI)\n- `backend` (torch/TF converters, both optional with clear errors)\n\n---\n\n## 16. Development & Testing\n\n```bash\n# Python tests (89 tests, stdlib unittest)\n.venv/bin/python -m unittest discover -s tests -t .\n\n# Frontend tests (8 tests, vitest)\nnpm test\n\n# Frontend type-check + build\nnpm run build\n\n# Verify node count\n.venv/bin/python -c "import src.nodes; from src.runtime.registry import default_registry; print(len(default_registry().list()))"\n# should print 191\n```\n\n### Adding a node\n\n```python\n# src/nodes/.../my_node.py\nfrom ..base import BaseNode, NodeInput, NodeOutput, Parameter\nfrom ...runtime.registry import register\n\n@register("my_node")\nclass MyNode(BaseNode):\n type_name = "my_node"\n label = "My Node"\n category = "Utility"\n inputs = [NodeInput("x", data_kind="tensor", required=True)]\n outputs = [NodeOutput("result", data_kind="tensor")]\n parameters = [Parameter("scale", default=1.0, dtype="float")]\n\n def execute(self, inputs, params, context):\n ...\n return {"result": ...}\n```\n\nThen `from . import my_node` in the package `__init__.py`, and add an export\nbranch in codegen.\n\n---\n\n## 17. Known Limitations\n\n1. **No native executor-level training**: training uses the codegen→exec path;\n an in-executor optimizer node is still pending.\n2. **Fixed subgraph input/output ports**: `graph_reference`/`import` support a\n single input and a single output only.\n3. **Keras convolution layout**: channels-last, differs from torch.\n4. **TF not bundled**: `requirements.txt` comments out `tensorflow>=2.10`;\n install it yourself to enable TF nodes.\n5. **No undo/redo, no keyboard shortcuts, no in-canvas search** (right-click menu\n has search).\n6. **`Toolbar.tsx` and `.doc_backups/` are legacy**: safe to delete.\n\nFuture: native training loop, optimizer/gradient-clipping nodes, multi-port\nsubgraphs, a model zoo (ResNet/VGG/BERT presets), a JAX backend, and more torch\nAPI coverage (grouped conv, spectral decompositions svd/qr/eig, …).\n';
|
|
2279
|
-
function
|
|
2292
|
+
const Wn = '# Entropia Riko 用户指南\n\n> 版本:v0.1 · 节点数:195+(含插件)· 后端:PyTorch(默认)+ TensorFlow/Keras(可选)\n> 本文档是面向用户的完整使用手册,覆盖界面、节点、构图、训练、导出与开发。\n\n---\n\n## 目录\n\n1. [项目简介](#1-项目简介)\n2. [快速开始](#2-快速开始)\n3. [界面总览](#3-界面总览)\n4. [模块化面板(Blender 风格)](#4-模块化面板blender-风格)\n5. [欢迎窗口](#5-欢迎窗口)\n6. [文件与插件](#6-文件与插件)\n7. [节点系统(191 节点)](#7-节点系统191-节点)\n8. [构图与执行](#8-构图与执行)\n9. [数据输入与预览节点](#9-数据输入与预览节点)\n10. [Wrangle 代码节点](#10-wrangle-代码节点)\n11. [训练与实时 Loss 曲线](#11-训练与实时-loss-曲线)\n12. [导出代码](#12-导出代码)\n13. [.riko 文件格式](#13-tns-文件格式)\n14. [API 端点](#14-api-端点)\n15. [后端架构](#15-后端架构)\n16. [开发与测试](#16-开发与测试)\n17. [已知限制与后续方向](#17-已知限制与后续方向)\n\n---\n\n## 1. 项目简介\n\nEntropia Riko 是一个**独立的节点图编辑器**,用 ComfyUI 风格的画布搭建并运行\nPyTorch(及可选 TensorFlow/Keras)张量工作流。它不是 Houdini 插件。\n\n核心特性:\n\n- **191 个节点**:数学、张量操作、神经网络激活/层、注意力、归一化、归约、形状、\n 创建、损失、数据加载、模型推理、子图引用、Wrangle 代码、预览,以及 TF/Keras。\n- **Blender 风格模块化面板**:拖角分离/合并、任意切换窗口类型、实时缩放。\n- **实时训练 + Loss 曲线**:自包含图(数据加载 + 损失)可直接训练并流式显示损失。\n- **代码导出**:导出干净的 `torch.nn.Module` 或 `tf.keras.Model` 脚本。\n- **子图 import**:一个 `.riko` 图可像 `import xx` 一样引用另一个图。\n- **数据预览**:图片缩略图、文本、JSON 直接在检查器里显示。\n\n---\n\n## 2. 快速开始\n\n### 前置条件\n- Python 3.10+\n- Node.js 18+\n- PyTorch(CPU 版即可;CUDA/MPS 可选)\n\n### 启动(两个终端)\n\n```bash\ncd ~/Documents/torch-node/entropia-riko\n\n# 终端 1:API 服务器(必须先启动)\n.venv/bin/python -m uvicorn src.server.app:app --reload --port 8000\n\n# 终端 2:前端开发服务器\nnpm run dev # → http://localhost:5173\n```\n\n打开 **http://localhost:5173**(务必通过 5173 访问,`/api` 走 Vite 代理)。\n\n> ⚠️ 若看不到 190+ 节点,说明 API 服务器没启动或旧进程仍在(无 `--reload` 的旧进程\n> 不会加载新代码,需杀掉重启)。改代码后浏览器需硬刷新 `Cmd+Shift+R`。\n\n---\n\n## 3. 界面总览\n\n```\n┌ 标题栏(* 未保存 · 文件名 - Riko · 版本号)────────────────────┐\n├ 菜单栏(logo · File / Run / Data / Help,右侧状态徽章)────────┤\n├ 模块化面板区域(Blender 风格区域树)──────────────────────────┤\n│ [节点库] [Asset Library] [New File] [画布] [检查器] │\n│ [状态/日志] [Loss 曲线] │\n└──────────────────────────────────────────────────────────────┘\n```\n\n- **标题栏**:未保存时显示 `*`,随后 `文件名 - Riko`,右侧版本号。\n- **菜单栏**:左上角 **logo**(点开 About / Welcome / Preferences);File(Import/Export\n → 内置 **File Explorer** 导入导出文件/文件夹;Export Code 导出 .py)、Run、Data、\n View(含 **Liquid Glass** 主题)、Help。\n- **节点库**:按分类分组、可折叠、可搜索,点击节点加入画布。\n- **画布**:React Flow 节点图,右键弹搜索式节点菜单。\n- **检查器**:左侧 tag 切换(Summary/Params/Inputs/Outputs/Preview)。\n- **New File**:右上角 mini 文件管理器(右键新建/删除/预览代码/展开节点)。\n- **Asset Library**:`.riko`/`.ric` 资产树(打开 / **展开完整节点** / 保存)。\n- **Code Editor**:代码编辑器窗口(类似记事本,编辑/复制/全选/保存/生成)。\n- **Handwriting Pad**:28×28 手写板,画数字 → 生成 `constant` 节点喂 MNIST。\n- **状态栏**:Logs(日志)与 Queue(节点队列)两个标签。\n- **Loss 曲线**:训练损失的实时 SVG 折线图。\n- **Plugins**:插件管理面板(右上角 `+` 从 `.py` 加载,每个插件带启用/禁用开关)。\n\n---\n\n## 4. 模块化面板(Blender 风格)\n\n每个面板右上角有一个**类型下拉**(切换该窗口显示的内容)和 **✕**(关闭当前面板);\n右下角有一个**角抓手**。\n\n| 操作 | 手势 | 效果 |\n|------|------|------|\n| 切换窗口类型 | 点右上角 `类型 ▾` | Node Library / New File / Asset Library / Graph / Inspector / Status / Loss Curve / Docs / Plugins / Code Editor / Handwriting Pad 十一选一 |\n| 分离(split) | 角抓手**向左/向上拖** | 蓝色预览线跟随鼠标(只在该面板内),松手在 50% 处分出新面板,方向=拖动轴向 |\n| 合并(merge) | 角抓手**向右/向下拖** | 相邻窗口被**蓝色阴影 + 加粗边框**高亮,松手它被并掉、当前面板扩大 |\n| 关闭当前面板 | 点 **✕** | 当前面板被移除,邻居吸收空间 |\n| 缩放 | 拖分隔条 | 所有分隔条可同时独立拖动 |\n\n默认布局:`[节点库/Asset Library 上下] | [画布 | (New File 上 / 检查器)]`,下方\n`[状态 | Loss 曲线]`(New File 只占据最右列、检查器上方,不占用画布区域)。\n\n---\n\n## 5. 欢迎窗口\n\n启动时弹出 Blender 风格欢迎窗口:\n\n- **头图**:上半部为可替换 **JPEG 位图**(`public/brand/hero.jpg`,960×220),右上角显示\n 版本号;左下角为 logo(`public/brand/logo.svg`)。\n- **New File**:`General`(空白图)+ 从 `examples/` 读出的**预制模板**\n (cnn / classifier / mlp / mnist_cnn / transformer / …),点击即载入。\n- **Recent Files**:`localStorage` 记录最近打开过的 `.riko`(去重、最多 10 条)。\n\n点 ✕ 或选择任意项关闭;**Help → Welcome Screen** 或 logo 菜单可随时重新打开。\n\n---\n\n## 6. 文件与插件\n\n**New File**(最右列、检查器上方)是一个 **mini 文件管理器**,映射**工作目录**的\n目录树(可展开/折叠子目录)。**右键**弹出菜单:新建文件/新建文件夹/重命名/删除、\n打开、**预览 PyTorch 代码**(`/api/project/code` 导出 `nn.Module`)、导入为节点。\n**File → Import Working Folder…** 可把工作目录指向任意本地文件夹,并在其中创建\n`.riko` 缓存文件夹存放工具自身状态。\n\n**Asset Library**(原 Files 面板,Blender-outliner 风格资产树)在其基础上增加保存与\nimport 依赖视图,每个文件可展开显示其 `import` 依赖(✓ 已解析 / ✗ 未解析)。\n\n- **打开**:点文件名载入画布。\n- **导入为节点**:点 `⤵` 添加一个引用该文件的 `import` 节点。\n- **保存**:在输入框命名后点 `+ Save .riko`(或 `⇩ .ric`),存到 `workflows/<name>`。\n\n**子图引用**:一个 `.riko` 可被 `graph_reference`(按 `file` 路径)或 `import`\n(按模块名,如 `mlp`,在 `workflows/`、`examples/`、`examples/models/` 中解析)引用。\n被引用图需含 `graph_input(name="input")` 与 `graph_output(name="output")`。\n\n**Plugins 面板**:右上角 `+` 从本地 `.py` 文件加载插件(安装到 `plugins/<name>/`),\n每个插件带启用/禁用开关(禁用即从节点库移除其节点)。内置插件:\n`example_plugin`(`plugin_double`)、`math_extra`(`plugin_square`、`plugin_scale`)、\n`stat_extra`(`plugin_shift_scale`、`plugin_neg`)。\n\n---\n\n## 7. 节点系统(195+ 节点)\n\n### 输入\n`constant`\n\n### 数学(纯 Python)\n`add`, `multiply`\n\n### 数学(torch)\n`torch_add`, `torch_multiply`, `sub`, `div`, `pow`, `matmul`, `mm`, `maximum`,\n`minimum`, `fmod`, `remainder`, `atan2`\n\n### 张量(单目)\n`abs`, `exp`, `log`, `log2`, `log10`, `log1p`, `expm1`, `sqrt`, `erf`, `erfc`,\n`neg`, `sign`, `reciprocal`, `floor`, `ceil`, `round`, `square`, `cos`, `sin`\n\n### 神经网络(激活)\n`relu`, `leaky_relu`, `sigmoid`, `tanh`, `gelu`, `silu`, `selu`, `elu`, `mish`,\n`hardswish`, `hardsigmoid`, `hardtanh`, `softsign`, `tanhshrink`, `softplus`,\n`relu6`, `glu`, `softmax`, `log_softmax`, `log_sigmoid`\n\n### 注意力\n`multihead_attention`, `sdpa`(scaled dot-product attention)\n\n### 神经网络(层)\n`linear`, `conv2d`, `conv1d`, `conv_transpose2d`, `maxpool2d`, `avgpool2d`,\n`embedding`, `dropout`, `batchnorm1d`, `batchnorm2d`, `layernorm`, `groupnorm`,\n`rmsnorm`, `transformer_encoder`\n\n### 归约\n`sum`, `mean`, `amax`, `amin`, `logsumexp`, `median`, `max_reduce`, `min_reduce`,\n`prod`, `std`, `var`, `norm`, `argmax`, `argmin`, `cumsum`, `topk`, `sort`\n\n### 形状\n`reshape`, `view`, `transpose`, `swapaxes`, `movedim`, `permute`, `flatten`,\n`squeeze`, `unsqueeze`, `concat`, `stack`, `flip`, `expand`, `expand_as`,\n`broadcast_to`, `tile`, `repeat`, `repeat_interleave`, `tril`, `triu`,\n`diagonal`, `narrow`, `roll`, `index_select`, `gather`, `interpolate`\n\n### 创建\n`zeros`, `ones`, `rand`, `randn`, `randint`, `randperm`, `empty`, `zeros_like`,\n`ones_like`, `randn_like`, `rand_like`, `eye`, `arange`, `linspace`, `full`,\n`positional_encoding`\n\n### 张量算子\n`bmm`, `dot`, `outer`, `xlogy`, `cross`, `addmm`, `einsum`, `clamp`,\n`contiguous`, `clone`, `detach`, `masked_fill`, `where`\n\n### 损失\n`mse_loss`, `cross_entropy_loss`, `l1_loss`, `smooth_l1_loss`,\n`binary_cross_entropy`, `kl_div`, `nll_loss`, `hinge_embedding_loss`,\n`cosine_embedding_loss`\n\n### 设备\n`to_device`\n\n### 模型\n`model_loader`(加载 .pt)、`inference`(跑 forward)\n\n### 子图\n`graph_input`, `graph_output`, `graph_reference`(按路径), `import`(按模块名)\n\n### 数据(数据集加载)\n`mnist_loader`, `cifar10_loader`, `csv_loader`, `image_folder_loader`,\n`tensor_file_loader`, `dataloader`, `text_loader`, `json_loader`, `image_loader`\n\n### Wrangle 与预览\n`wrangle`(内联 Python 代码), `text_preview`, `json_preview`, `image_preview`\n\n### TensorFlow / Keras(可选后端)\n`keras_dense`, `keras_conv2d`, `keras_flatten`, `keras_embedding`,\n`keras_layernorm`, `keras_dropout`, `keras_relu`, `keras_sigmoid`, `keras_tanh`,\n`keras_gelu`, `keras_softmax`, `keras_maxpool2d`, `keras_avgpool2d`, `tf_add`,\n`tf_multiply`, `tf_matmul`, `tf_concat`, `tf_reshape`, `tf_transpose`, `tf_reduce`\n\n> Keras 卷积用 channels-last `(B,H,W,C)`,与 torch 的 channels-first 不同。\n\n---\n\n## 8. 构图与执行\n\n1. 从节点库点击/拖入节点(或画布右键搜索添加)。\n2. 拖端口连线;点击节点在右侧检查器改参数。\n3. **Run → Inference** 执行整图,结果显示在节点卡片和检查器预览里。\n\n执行后端:校验(端口/必需输入/环检测)→ Kahn 拓扑排序 → 逐节点 `execute`。\n`TensorValue` 是框架无关的中间表示(纯 Python 数据),torch/TF 节点在 `execute` 内\n惰性 import 并做转换。\n\n---\n\n## 9. 数据输入与预览节点\n\n- **输入**:`text_loader`(读文本)、`json_loader`(读 JSON)、`image_loader`\n (用 PIL 读图片,输出 H×W×3 image_tensor)、`csv_loader`、`mnist_loader` 等。\n- **预览**:\n - `text_preview`:把任意值转成文本。\n - `json_preview`:把张量/值序列化成缩进 JSON。\n - `image_preview`:把图像张量转成 **base64 PNG 缩略图**,在检查器里直接显示\n `<img>`。\n\n`TensorValue` 支持 `kind`:`scalar` / `tensor` / `image_tensor` / `text` / `json` /\n`model`。文本/JSON 不做数值 shape 推断。\n\n---\n\n## 10. Wrangle 代码节点\n\n`wrangle` 节点用于解决内置节点覆盖不到的逻辑。参数 `code` 是任意 Python:\n\n```python\n# 可用变量:x / a / b(已连接输入张量,未连接为 None)、torch、F、nn\nresult = x * 2 + 1\n# 或:\n# result = torch.softmax(x, dim=-1)\n# result = F.gelu(x)\n```\n\n`execute` 把输入转成 torch 张量后 `exec(code)`,取 `result`(或 `y`)作为输出。\n导出时会把代码内联进 `forward`。\n\n---\n\n## 11. 训练与实时 Loss 曲线\n\n**可训练图** = 数据加载节点(如 `dataloader`/`mnist_loader`)+ 损失节点\n(`cross_entropy_loss` 等),且不含 `graph_input`。\n\n- **Run → Train 20/100 steps** 或 Loss 面板按钮开始训练。\n- 后端用 codegen 生成 `nn.Module` → `AdamW` 优化器逐步 `backward()`/`step()`,\n 通过 **SSE(`/api/train/stream`)逐步回传 loss**,前端 SVG 曲线实时增长。\n\n注意:执行器本身仍用 `no_grad()` 逐次重建层;真正的训练走「导出 → 执行」这条路径。\n\n---\n\n## 12. 导出代码\n\n- **File → Export .py (PyTorch)**:生成干净的 `torch.nn.Module`。\n - 可学习层进 `__init__`,内联算子进 `forward`。\n - `graph_input` → `forward` 参数;`graph_output` → `return`。\n - `import`/`graph_reference` 递归内联为嵌套 `nn.Module` 类。\n - 数据加载节点生成真实可运行代码(torchvision + 随机回退)。\n - 自包含训练图附带 `train()` + `__main__` 训练循环。\n- **File → Export .py (Keras/TF)**:对 TF/Keras 节点图生成 `tf.keras.Model`\n (层进 `__init__`,算子进 `call`)。\n\n---\n\n## 13. .riko 文件格式\n\n`.riko` 是 JSON,保存完整工作流:\n\n```json\n{\n "version": "0.1",\n "metadata": { "name": "classifier", "description": "...",\n "inputs": [{"name": "input", "data_kind": "tensor"}],\n "outputs": [{"name": "output", "data_kind": "tensor"}] },\n "nodes": [\n {"id": "a", "type_name": "constant", "label": "Constant", "category": "Inputs",\n "position": [100, 200], "parameters": {"value": 2.0}, "inputs": [], "outputs": []}\n ],\n "edges": [\n {"id": "e1", "source_node": "a", "source_port": "value",\n "target_node": "b", "target_port": "left"}\n ],\n "settings": {}\n}\n```\n\n- Save 下载 `workflow.riko`;Load 接受 `.riko`/`.json`。\n- 文件管理器另提供 `POST /api/files/save` 存到 `workflows/`。\n\n---\n\n## 14. API 端点\n\n| 方法 | 路径 | 说明 |\n|------|------|------|\n| GET | `/api/health` | 健康检查 |\n| GET | `/api/nodes` | 全部节点定义(含已启用插件) |\n| POST | `/api/execute` | 执行 GraphDocument JSON,返回各节点端口输出 |\n| POST | `/api/export_python` | 生成 `torch.nn.Module` 代码 |\n| POST | `/api/export_keras` | 生成 `tf.keras.Model` 代码 |\n| POST | `/api/export_binary` | 将图文档编码为 `.ric` 二进制(base64 下载) |\n| GET | `/api/files` | 列出 `.riko` 文件及其 import 依赖 |\n| GET | `/api/files/content?path=` | 读取 `.riko` 内容 |\n| POST | `/api/files/save` | 保存 `.riko` 到 `workflows/` |\n| GET | `/api/plugins` | 列出插件(enabled/disabled + 注册节点) |\n| POST | `/api/plugins/toggle` | 启用/禁用插件(`{name, enabled}`) |\n| POST | `/api/plugins/upload` | 从 Python 源码安装插件(`{name, code}`) |\n| GET | `/api/project/tree` | 返回工作目录的递归目录树(隐藏 `.riko` 缓存) |\n| POST | `/api/project/set_root` | 导入工作文件夹(`{path}`),创建 `.riko` 缓存并持久化 |\n| POST | `/api/project/create` | 在工作目录新建空 `.riko`(`{name, dir?}`) |\n| POST | `/api/project/mkdir` | 在工作目录新建文件夹(`{name, dir?}`) |\n| POST | `/api/project/rename` | 重命名文件/文件夹(`{path, newName}`) |\n| POST | `/api/project/delete` | 删除文件/空文件夹(`{path}`) |\n| POST | `/api/project/code` | 返回该 `.riko` 导出的 PyTorch 代码(`{path}`) |\n| GET | `/api/project/open?path=` | 读取工作目录内 `.riko`/`.ric` 内容 |\n| POST | `/api/train` | 训练并一次性返回 loss 列表 |\n| POST | `/api/train/stream` | 训练并流式(NDJSON)返回逐步 loss |\n\n---\n\n## 15. 后端架构\n\n```\nReact UI (@xyflow/react) ──HTTP──▶ FastAPI\n │ │\n └─ 图文档 JSON ──▶ Runtime(registry + executor + codegen + trainer + subgraph)\n │\n Tensor IR(core/tensor.py TensorValue + document.py)\n │\n Backend(device/converter torch ↔ TensorValue;\n tf_converter tf ↔ TensorValue)\n```\n\n分层与依赖方向:\n\n- `core`(纯 Python,绝不 import torch/TF)\n- `runtime`(注册表 / 执行器 / codegen / codegen_tf / trainer / subgraph)\n- `nodes`(math 纯 Python;torch_ops、tf_ops 惰性 import)\n- `server`(FastAPI)\n- `backend`(torch/TF 转换,均 optional,缺依赖时报清晰错误)\n\n---\n\n## 16. 开发与测试\n\n```bash\n# Python 测试(97 tests,stdlib unittest)\n.venv/bin/python -m unittest discover -s tests -t .\n\n# 前端测试(8 tests,vitest)\nnpm test\n\n# 前端类型检查 + 构建\nnpm run build\n\n# 验证节点数(内置 194;加上插件后 199+)\n.venv/bin/python -c "import src.nodes; from src.runtime.registry import default_registry; print(len(default_registry().list()))"\n# 应打印 194(未加载插件时)\n```\n\n### 新增一个节点\n\n```python\n# src/nodes/.../my_node.py\nfrom ..base import BaseNode, NodeInput, NodeOutput, Parameter\nfrom ...runtime.registry import register\n\n@register("my_node")\nclass MyNode(BaseNode):\n type_name = "my_node"\n label = "My Node"\n category = "Utility"\n inputs = [NodeInput("x", data_kind="tensor", required=True)]\n outputs = [NodeOutput("result", data_kind="tensor")]\n parameters = [Parameter("scale", default=1.0, dtype="float")]\n\n def execute(self, inputs, params, context):\n ...\n return {"result": ...}\n```\n\n然后在对应 `__init__.py` 里 `from . import my_node`,并在 codegen 里加导出分支。\n\n---\n\n## 17. 已知限制与后续方向\n\n1. **执行器级训练未原生实现**:训练走「codegen → exec」路径;原生优化器节点待加。\n2. **子图固定 input/output 端口**:`graph_reference`/`import` 目前只支持单输入单输出。\n3. **Keras 卷积布局**:channels-last,与 torch 不同,混用时需 transpose。\n4. **TF 未随包安装**:`requirements.txt` 中注释了 `tensorflow>=2.10`,启用需自行安装。\n5. **无 undo/redo、无键盘快捷键、无画布内搜索**(右键菜单有搜索)。\n6. **`Toolbar.tsx` 与 `.doc_backups/` 遗留**:可安全删除。\n\n后续方向:原生训练循环、优化器/梯度裁剪节点、多端口子图、模型动物园(ResNet/VGG/BERT\n预制 `.riko`)、JAX 后端、分组卷积/谱分解(svd/qr/eig)等更多 torch API 覆盖。\n', Bn = '# Entropia Riko User Guide\n\n> Version: v0.1 · Nodes: 191 · Backends: PyTorch (default) + TensorFlow/Keras (optional)\n> This is the complete user manual: interface, nodes, graph building, training, export and development.\n\n---\n\n## Table of Contents\n\n1. [Overview](#1-overview)\n2. [Quick Start](#2-quick-start)\n3. [Interface](#3-interface)\n4. [Modular Panels (Blender-style)](#4-modular-panels-blender-style)\n5. [Welcome Screen](#5-welcome-screen)\n6. [File Manager & Import](#6-file-manager--import)\n7. [Node System (191 nodes)](#7-node-system-191-nodes)\n8. [Building & Running Graphs](#8-building--running-graphs)\n9. [Data Input & Preview Nodes](#9-data-input--preview-nodes)\n10. [Wrangle Code Node](#10-wrangle-code-node)\n11. [Training & Real-time Loss Curve](#11-training--real-time-loss-curve)\n12. [Exporting Code](#12-exporting-code)\n13. [.riko File Format](#13-tns-file-format)\n14. [API Endpoints](#14-api-endpoints)\n15. [Backend Architecture](#15-backend-architecture)\n16. [Development & Testing](#16-development--testing)\n17. [Known Limitations](#17-known-limitations)\n\n---\n\n## 1. Overview\n\nEntropia Riko is a **standalone node-graph editor** for building and running\nPyTorch (and optionally TensorFlow/Keras) tensor workflows, with a ComfyUI-style\ncanvas and a Material-inspired UI. It is **not** a Houdini plugin.\n\nKey features:\n\n- **191 nodes**: math, tensor ops, neural activations/layers, attention,\n normalization, reductions, shape ops, creation, losses, data loading, model\n inference, subgraph references, Wrangle code, previews, and TF/Keras.\n- **Blender-style modular panels**: drag a corner to split/merge, switch any\n window\'s type, and resize everything simultaneously.\n- **Real-time training + loss curve**: self-contained graphs (data + loss)\n train live with a streaming SVG loss chart.\n- **Code export**: generate clean `torch.nn.Module` or `tf.keras.Model` scripts.\n- **Subgraph import**: a `.riko` graph can reference another like `import xx`.\n- **Data previews**: image thumbnails, text and JSON rendered in the inspector.\n\n---\n\n## 2. Quick Start\n\n### Prerequisites\n- Python 3.10+\n- Node.js 18+\n- PyTorch (CPU build is enough; CUDA/MPS optional)\n\n### Run (two terminals)\n\n```bash\ncd ~/Documents/torch-node/entropia-riko\n\n# Terminal 1: API server (start FIRST)\n.venv/bin/python -m uvicorn src.server.app:app --reload --port 8000\n\n# Terminal 2: frontend dev server\nnpm run dev # → http://localhost:5173\n```\n\nOpen **http://localhost:5173** (the `/api` proxy only works through 5173).\n\n> ⚠️ If you don\'t see 190+ nodes, the API server isn\'t running, or a stale\n> process started without `--reload` is still serving old code — kill and\n> restart it. After code changes, hard-refresh the browser with `Cmd+Shift+R`.\n\n---\n\n## 3. Interface\n\n```\n┌ Menu bar (File / Run / Data / Help, status badge) ────────────────┐\n├ Modular panel area (Blender-style area tree) ─────────────────────┤\n│ [Node Library] [Files] [Canvas (React Flow)] [Inspector] │\n│ [Status / Logs] [Loss Curve] │\n└────────────────────────────────────────────────────────────────────┘\n```\n\n- **Menu bar**: File (new / save / load / export PyTorch / export Keras),\n Run (inference / Train 20 / 100 steps), Data (quick-add dataset nodes),\n Help (welcome screen / about).\n- **Node Library**: grouped, collapsible, searchable; click to add a node.\n- **Canvas**: React Flow node graph; right-click opens a searchable node menu.\n- **Inspector**: parameters / inputs / outputs / preview of the selected node.\n- **Status**: Logs and Queue tabs.\n- **Loss Curve**: live training loss as an SVG line chart.\n\n---\n\n## 4. Modular Panels (Blender-style)\n\nEach panel has a **type dropdown** (top-right) to switch what it shows and a\n**✕** (close) button; the bottom-right corner is a **grip**.\n\n| Action | Gesture | Effect |\n|--------|---------|--------|\n| Switch type | click `type ▾` | Node Library / Files / Graph / Inspector / Status / Loss Curve / Documentation |\n| Split | drag grip **up/left** | a blue preview line follows the mouse (clipped to the panel); release splits at 50%, orientation = drag axis |\n| Merge | drag grip **down/right** | the sibling panel is **shaded + bold-bordered**; release merges it away, current expands |\n| Close current | click **✕** | current panel removed, neighbor absorbs the space |\n| Resize | drag a splitter | all splitters resize independently and simultaneously |\n\nDefault layout: `[Node Library / Files stacked] | [Graph | Inspector]` over\n`[Status | Loss Curve]`.\n\n---\n\n## 5. Welcome Screen\n\nA Blender-style splash opens on startup, with two columns:\n\n- **New File**: `General` (blank graph) + preset templates read from\n `examples/` (cnn / classifier / mlp / mnist_cnn / transformer / …).\n- **Recent Files**: the last opened `.riko` files (deduped, up to 10, stored in\n `localStorage`).\n\nClose with ✕ or by picking an item; reopen via **Help → Welcome Screen**.\n\n---\n\n## 6. File Manager & Import\n\nThe **Files** panel is a Blender-outliner-style tree of `.riko` files under\n`workflows/` and `examples/`. Each file expands to show its `import`\ndependencies (✓ resolved / ✗ missing).\n\n- **Open**: click a file name to load it.\n- **Import as node**: click `⤵` to add an `import` node referencing it.\n- **Save**: name it and click `+ Save` → `workflows/<name>.riko`.\n\n**Subgraph references**: a `.riko` can be referenced by `graph_reference`\n(by `file` path) or `import` (by module name, e.g. `mlp`, resolved under\n`workflows/`, `examples/`, `examples/models/`). The referenced graph must\ncontain `graph_input(name="input")` and `graph_output(name="output")`.\n\n---\n\n## 7. Node System (191 nodes)\n\n### Inputs\n`constant`\n\n### Math (pure Python)\n`add`, `multiply`\n\n### Math (torch)\n`torch_add`, `torch_multiply`, `sub`, `div`, `pow`, `matmul`, `mm`, `maximum`,\n`minimum`, `fmod`, `remainder`, `atan2`\n\n### Tensor (unary)\n`abs`, `exp`, `log`, `log2`, `log10`, `log1p`, `expm1`, `sqrt`, `erf`, `erfc`,\n`neg`, `sign`, `reciprocal`, `floor`, `ceil`, `round`, `square`, `cos`, `sin`\n\n### Neural (activations)\n`relu`, `leaky_relu`, `sigmoid`, `tanh`, `gelu`, `silu`, `selu`, `elu`, `mish`,\n`hardswish`, `hardsigmoid`, `hardtanh`, `softsign`, `tanhshrink`, `softplus`,\n`relu6`, `glu`, `softmax`, `log_softmax`, `log_sigmoid`\n\n### Attention\n`multihead_attention`, `sdpa` (scaled dot-product attention)\n\n### Neural (layers)\n`linear`, `conv2d`, `conv1d`, `conv_transpose2d`, `maxpool2d`, `avgpool2d`,\n`embedding`, `dropout`, `batchnorm1d`, `batchnorm2d`, `layernorm`, `groupnorm`,\n`rmsnorm`, `transformer_encoder`\n\n### Reduce\n`sum`, `mean`, `amax`, `amin`, `logsumexp`, `median`, `max_reduce`, `min_reduce`,\n`prod`, `std`, `var`, `norm`, `argmax`, `argmin`, `cumsum`, `topk`, `sort`\n\n### Shape\n`reshape`, `view`, `transpose`, `swapaxes`, `movedim`, `permute`, `flatten`,\n`squeeze`, `unsqueeze`, `concat`, `stack`, `flip`, `expand`, `expand_as`,\n`broadcast_to`, `tile`, `repeat`, `repeat_interleave`, `tril`, `triu`,\n`diagonal`, `narrow`, `roll`, `index_select`, `gather`, `interpolate`\n\n### Creation\n`zeros`, `ones`, `rand`, `randn`, `randint`, `randperm`, `empty`, `zeros_like`,\n`ones_like`, `randn_like`, `rand_like`, `eye`, `arange`, `linspace`, `full`,\n`positional_encoding`\n\n### Tensor ops\n`bmm`, `dot`, `outer`, `xlogy`, `cross`, `addmm`, `einsum`, `clamp`,\n`contiguous`, `clone`, `detach`, `masked_fill`, `where`\n\n### Loss\n`mse_loss`, `cross_entropy_loss`, `l1_loss`, `smooth_l1_loss`,\n`binary_cross_entropy`, `kl_div`, `nll_loss`, `hinge_embedding_loss`,\n`cosine_embedding_loss`\n\n### Device\n`to_device`\n\n### Model\n`model_loader` (load .pt), `inference` (run forward)\n\n### Subgraph\n`graph_input`, `graph_output`, `graph_reference` (by path), `import` (by module)\n\n### Data (dataset loaders)\n`mnist_loader`, `cifar10_loader`, `csv_loader`, `image_folder_loader`,\n`tensor_file_loader`, `dataloader`, `text_loader`, `json_loader`, `image_loader`\n\n### Wrangle & Preview\n`wrangle` (inline Python code), `text_preview`, `json_preview`, `image_preview`\n\n### TensorFlow / Keras (optional backend)\n`keras_dense`, `keras_conv2d`, `keras_flatten`, `keras_embedding`,\n`keras_layernorm`, `keras_dropout`, `keras_relu`, `keras_sigmoid`, `keras_tanh`,\n`keras_gelu`, `keras_softmax`, `keras_maxpool2d`, `keras_avgpool2d`, `tf_add`,\n`tf_multiply`, `tf_matmul`, `tf_concat`, `tf_reshape`, `tf_transpose`, `tf_reduce`\n\n> Keras convolutions use channels-last `(B,H,W,C)`, unlike torch\'s channels-first.\n\n---\n\n## 8. Building & Running Graphs\n\n1. Add nodes from the library (or right-click the canvas to search).\n2. Drag between ports to connect; click a node to edit parameters in the inspector.\n3. **Run → Inference** executes the whole graph; results appear on node cards\n and in the inspector preview.\n\nExecution backend: validation (ports / required inputs / cycle detection) →\nKahn topological sort → per-node `execute`. `TensorValue` is a framework-neutral\nIR (pure-Python data); torch/TF nodes lazily import and convert inside `execute`.\n\n---\n\n## 9. Data Input & Preview Nodes\n\n- **Inputs**: `text_loader` (read text), `json_loader` (parse JSON),\n `image_loader` (PIL → H×W×3 image_tensor), `csv_loader`, `mnist_loader`, …\n- **Previews**:\n - `text_preview`: convert any value to text.\n - `json_preview`: serialize a tensor/value to indented JSON.\n - `image_preview`: render an image tensor as a **base64 PNG thumbnail** shown\n in the inspector.\n\n`TensorValue` supports `kind`: `scalar` / `tensor` / `image_tensor` / `text` /\n`json` / `model`. Text/JSON payloads skip numeric shape inference.\n\n---\n\n## 10. Wrangle Code Node\n\nThe `wrangle` node covers whatever built-in nodes can\'t. Its `code` parameter\nis arbitrary Python:\n\n```python\n# available: x / a / b (connected input tensors, None if unconnected), torch, F, nn\nresult = x * 2 + 1\n# result = torch.softmax(x, dim=-1)\n# result = F.gelu(x)\n```\n\n`execute` converts inputs to torch tensors, runs `exec(code)`, and returns\n`result` (or `y`). Export inlines the code into `forward`.\n\n---\n\n## 11. Training & Real-time Loss Curve\n\nA **trainable graph** = a data loader node (e.g. `dataloader`/`mnist_loader`) +\na loss node (e.g. `cross_entropy_loss`), with no `graph_input`.\n\n- **Run → Train 20/100 steps** (or the Loss panel buttons) starts training.\n- The backend generates an `nn.Module` via codegen, runs `AdamW` steps with\n `backward()`/`step()`, and streams each loss over **SSE\n (`/api/train/stream`)**; the frontend SVG curve grows in real time.\n\nNote: the executor itself still uses `no_grad()` and rebuilds layers per call;\nreal training goes through the "export → exec" path.\n\n---\n\n## 12. Exporting Code\n\n- **File → Export .py (PyTorch)**: emits a clean `torch.nn.Module`.\n - learnable layers → `__init__`, inline ops → `forward`.\n - `graph_input` → `forward` parameter; `graph_output` → `return`.\n - `import`/`graph_reference` are inlined recursively as nested modules.\n - data loaders emit real runnable code (torchvision + random fallback).\n - self-contained training graphs get a `train()` + `__main__` loop.\n- **File → Export .py (Keras/TF)**: emits a subclassed `tf.keras.Model`\n (layers → `__init__`, ops → `call`).\n\n---\n\n## 13. .riko File Format\n\n`.riko` is JSON storing a complete workflow:\n\n```json\n{\n "version": "0.1",\n "metadata": { "name": "classifier", "description": "...",\n "inputs": [{"name": "input", "data_kind": "tensor"}],\n "outputs": [{"name": "output", "data_kind": "tensor"}] },\n "nodes": [\n {"id": "a", "type_name": "constant", "label": "Constant", "category": "Inputs",\n "position": [100, 200], "parameters": {"value": 2.0}, "inputs": [], "outputs": []}\n ],\n "edges": [\n {"id": "e1", "source_node": "a", "source_port": "value",\n "target_node": "b", "target_port": "left"}\n ],\n "settings": {}\n}\n```\n\n- Save downloads `workflow.riko`; Load accepts `.riko`/`.json`.\n- The file manager additionally saves to `workflows/` via `POST /api/files/save`.\n\n---\n\n## 14. API Endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| GET | `/api/health` | health check |\n| GET | `/api/nodes` | all 191 node definitions |\n| POST | `/api/execute` | execute a GraphDocument JSON, return per-node outputs |\n| POST | `/api/export_python` | generate `torch.nn.Module` code |\n| POST | `/api/export_keras` | generate `tf.keras.Model` code |\n| GET | `/api/files` | list `.riko` files + their import dependencies |\n| GET | `/api/files/content?path=` | read a `.riko` file |\n| POST | `/api/files/save` | save a `.riko` to `workflows/` |\n| POST | `/api/train` | train and return the loss list |\n| POST | `/api/train/stream` | train and stream per-step losses (NDJSON) |\n\n---\n\n## 15. Backend Architecture\n\n```\nReact UI (@xyflow/react) ──HTTP──▶ FastAPI\n │ │\n └─ graph JSON ──▶ Runtime (registry + executor + codegen + trainer + subgraph)\n │\n Tensor IR (core/tensor.py TensorValue + document.py)\n │\n Backend (device/converter torch ↔ TensorValue;\n tf_converter tf ↔ TensorValue)\n```\n\nLayers & dependency direction:\n\n- `core` (pure Python — never imports torch/TF)\n- `runtime` (registry / executor / codegen / codegen_tf / trainer / subgraph)\n- `nodes` (math pure-Python; torch_ops, tf_ops lazy-import)\n- `server` (FastAPI)\n- `backend` (torch/TF converters, both optional with clear errors)\n\n---\n\n## 16. Development & Testing\n\n```bash\n# Python tests (89 tests, stdlib unittest)\n.venv/bin/python -m unittest discover -s tests -t .\n\n# Frontend tests (8 tests, vitest)\nnpm test\n\n# Frontend type-check + build\nnpm run build\n\n# Verify node count\n.venv/bin/python -c "import src.nodes; from src.runtime.registry import default_registry; print(len(default_registry().list()))"\n# should print 191\n```\n\n### Adding a node\n\n```python\n# src/nodes/.../my_node.py\nfrom ..base import BaseNode, NodeInput, NodeOutput, Parameter\nfrom ...runtime.registry import register\n\n@register("my_node")\nclass MyNode(BaseNode):\n type_name = "my_node"\n label = "My Node"\n category = "Utility"\n inputs = [NodeInput("x", data_kind="tensor", required=True)]\n outputs = [NodeOutput("result", data_kind="tensor")]\n parameters = [Parameter("scale", default=1.0, dtype="float")]\n\n def execute(self, inputs, params, context):\n ...\n return {"result": ...}\n```\n\nThen `from . import my_node` in the package `__init__.py`, and add an export\nbranch in codegen.\n\n---\n\n## 17. Known Limitations\n\n1. **No native executor-level training**: training uses the codegen→exec path;\n an in-executor optimizer node is still pending.\n2. **Fixed subgraph input/output ports**: `graph_reference`/`import` support a\n single input and a single output only.\n3. **Keras convolution layout**: channels-last, differs from torch.\n4. **TF not bundled**: `requirements.txt` comments out `tensorflow>=2.10`;\n install it yourself to enable TF nodes.\n5. **No undo/redo, no keyboard shortcuts, no in-canvas search** (right-click menu\n has search).\n6. **`Toolbar.tsx` and `.doc_backups/` are legacy**: safe to delete.\n\nFuture: native training loop, optimizer/gradient-clipping nodes, multi-port\nsubgraphs, a model zoo (ResNet/VGG/BERT presets), a JAX backend, and more torch\nAPI coverage (grouped conv, spectral decompositions svd/qr/eig, …).\n';
|
|
2293
|
+
function J(n) {
|
|
2280
2294
|
return n.split(/(`[^`]+`|\*\*[^*]+\*\*|\[[^\]]+\]\([^)]+\))/g).map((t, s) => {
|
|
2281
2295
|
if (t.startsWith("`") && t.endsWith("`") && t.length > 2)
|
|
2282
2296
|
return /* @__PURE__ */ a("code", { children: t.slice(1, -1) }, s);
|
|
2283
2297
|
if (t.startsWith("**") && t.endsWith("**") && t.length > 4)
|
|
2284
2298
|
return /* @__PURE__ */ a("strong", { children: t.slice(2, -2) }, s);
|
|
2285
2299
|
const r = t.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
2286
|
-
return r ? /* @__PURE__ */ a("a", { href: r[2], target: "_blank", rel: "noreferrer", children: r[1] }, s) : /* @__PURE__ */ a(
|
|
2300
|
+
return r ? /* @__PURE__ */ a("a", { href: r[2], target: "_blank", rel: "noreferrer", children: r[1] }, s) : /* @__PURE__ */ a(je, { children: t }, s);
|
|
2287
2301
|
});
|
|
2288
2302
|
}
|
|
2289
|
-
function
|
|
2303
|
+
function Ie(n) {
|
|
2290
2304
|
return /^#{1,4}\s/.test(n);
|
|
2291
2305
|
}
|
|
2292
|
-
function
|
|
2306
|
+
function me(n) {
|
|
2293
2307
|
return /^\s*[-*]\s/.test(n) || /^\s*\d+\.\s/.test(n);
|
|
2294
2308
|
}
|
|
2295
|
-
function
|
|
2309
|
+
function Gn(n) {
|
|
2296
2310
|
return /^\s*\|[\s:|-]+\|\s*$/.test(n);
|
|
2297
2311
|
}
|
|
2298
|
-
function
|
|
2312
|
+
function se(n) {
|
|
2299
2313
|
const e = n.replace(/\r\n/g, `
|
|
2300
2314
|
`).split(`
|
|
2301
2315
|
`), t = [];
|
|
2302
2316
|
let s = 0;
|
|
2303
2317
|
const r = (o, i) => {
|
|
2304
|
-
t.push(/* @__PURE__ */ a(
|
|
2318
|
+
t.push(/* @__PURE__ */ a(je, { children: o }, i));
|
|
2305
2319
|
};
|
|
2306
2320
|
for (; s < e.length; ) {
|
|
2307
2321
|
const o = e[s];
|
|
@@ -2317,9 +2331,9 @@ function ne(n) {
|
|
|
2317
2331
|
`) }) }), s);
|
|
2318
2332
|
continue;
|
|
2319
2333
|
}
|
|
2320
|
-
if (
|
|
2334
|
+
if (Ie(o)) {
|
|
2321
2335
|
const d = o.match(/^(#+)/)[1].length, h = o.replace(/^#+\s*/, "").trim(), c = s;
|
|
2322
|
-
r(d === 1 ? /* @__PURE__ */ a("h1", { children:
|
|
2336
|
+
r(d === 1 ? /* @__PURE__ */ a("h1", { children: J(h) }) : d === 2 ? /* @__PURE__ */ a("h2", { children: J(h) }) : d === 3 ? /* @__PURE__ */ a("h3", { children: J(h) }) : /* @__PURE__ */ a("h4", { children: J(h) }), c), s++;
|
|
2323
2337
|
continue;
|
|
2324
2338
|
}
|
|
2325
2339
|
if (/^\s*-{3,}\s*$/.test(o)) {
|
|
@@ -2330,11 +2344,11 @@ function ne(n) {
|
|
|
2330
2344
|
const d = [];
|
|
2331
2345
|
for (; s < e.length && e[s].startsWith(">"); )
|
|
2332
2346
|
d.push(e[s].replace(/^>\s?/, "")), s++;
|
|
2333
|
-
r(/* @__PURE__ */ a("blockquote", { children:
|
|
2347
|
+
r(/* @__PURE__ */ a("blockquote", { children: se(d.join(`
|
|
2334
2348
|
`)) }), s);
|
|
2335
2349
|
continue;
|
|
2336
2350
|
}
|
|
2337
|
-
if (o.trim().startsWith("|") && e[s + 1] &&
|
|
2351
|
+
if (o.trim().startsWith("|") && e[s + 1] && Gn(e[s + 1])) {
|
|
2338
2352
|
const d = o.trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim());
|
|
2339
2353
|
s += 2;
|
|
2340
2354
|
const h = [];
|
|
@@ -2342,33 +2356,33 @@ function ne(n) {
|
|
|
2342
2356
|
h.push(e[s].trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim())), s++;
|
|
2343
2357
|
r(
|
|
2344
2358
|
/* @__PURE__ */ p("table", { children: [
|
|
2345
|
-
/* @__PURE__ */ a("thead", { children: /* @__PURE__ */ a("tr", { children: d.map((c,
|
|
2346
|
-
/* @__PURE__ */ a("tbody", { children: h.map((c,
|
|
2359
|
+
/* @__PURE__ */ a("thead", { children: /* @__PURE__ */ a("tr", { children: d.map((c, m) => /* @__PURE__ */ a("th", { children: J(c) }, m)) }) }),
|
|
2360
|
+
/* @__PURE__ */ a("tbody", { children: h.map((c, m) => /* @__PURE__ */ a("tr", { children: c.map((u, b) => /* @__PURE__ */ a("td", { children: J(u) }, b)) }, m)) })
|
|
2347
2361
|
] }),
|
|
2348
2362
|
s
|
|
2349
2363
|
);
|
|
2350
2364
|
continue;
|
|
2351
2365
|
}
|
|
2352
|
-
if (
|
|
2366
|
+
if (me(o)) {
|
|
2353
2367
|
const d = /^\s*\d+\.\s/.test(o), h = [];
|
|
2354
|
-
for (; s < e.length &&
|
|
2368
|
+
for (; s < e.length && me(e[s]); )
|
|
2355
2369
|
h.push(e[s].replace(/^\s*(?:[-*]|\d+\.)\s*/, "")), s++;
|
|
2356
2370
|
const c = s;
|
|
2357
2371
|
r(
|
|
2358
|
-
d ? /* @__PURE__ */ a("ol", { children: h.map((
|
|
2372
|
+
d ? /* @__PURE__ */ a("ol", { children: h.map((m, u) => /* @__PURE__ */ a("li", { children: J(m) }, u)) }) : /* @__PURE__ */ a("ul", { children: h.map((m, u) => /* @__PURE__ */ a("li", { children: J(m) }, u)) }),
|
|
2359
2373
|
c
|
|
2360
2374
|
);
|
|
2361
2375
|
continue;
|
|
2362
2376
|
}
|
|
2363
2377
|
const i = [];
|
|
2364
|
-
for (; s < e.length && e[s].trim() !== "" && !
|
|
2378
|
+
for (; s < e.length && e[s].trim() !== "" && !Ie(e[s]) && !e[s].trim().startsWith("```") && !e[s].trim().startsWith("|") && !e[s].startsWith(">") && !me(e[s]) && !/^\s*-{3,}\s*$/.test(e[s]); )
|
|
2365
2379
|
i.push(e[s]), s++;
|
|
2366
|
-
i.length && r(/* @__PURE__ */ a("p", { children:
|
|
2380
|
+
i.length && r(/* @__PURE__ */ a("p", { children: J(i.join(" ")) }), s);
|
|
2367
2381
|
}
|
|
2368
2382
|
return t;
|
|
2369
2383
|
}
|
|
2370
|
-
const
|
|
2371
|
-
function
|
|
2384
|
+
const Ke = "entropia_riko_doc_lang";
|
|
2385
|
+
function Un(n) {
|
|
2372
2386
|
const e = n.split(`
|
|
2373
2387
|
`);
|
|
2374
2388
|
let t = "";
|
|
@@ -2390,38 +2404,38 @@ function Gn(n) {
|
|
|
2390
2404
|
return r && s.push(r), { title: t, intro: o.join(`
|
|
2391
2405
|
`), sections: s };
|
|
2392
2406
|
}
|
|
2393
|
-
function
|
|
2407
|
+
function Hn() {
|
|
2394
2408
|
try {
|
|
2395
|
-
return localStorage.getItem(
|
|
2409
|
+
return localStorage.getItem(Ke) === "zh" ? "zh" : "en";
|
|
2396
2410
|
} catch {
|
|
2397
2411
|
return "en";
|
|
2398
2412
|
}
|
|
2399
2413
|
}
|
|
2400
|
-
function
|
|
2401
|
-
var
|
|
2402
|
-
const [n, e] =
|
|
2403
|
-
() =>
|
|
2414
|
+
function Kn() {
|
|
2415
|
+
var b;
|
|
2416
|
+
const [n, e] = E(Hn), t = Y(
|
|
2417
|
+
() => Un(n === "zh" ? Wn : Bn),
|
|
2404
2418
|
[n]
|
|
2405
|
-
), [s, r] =
|
|
2406
|
-
|
|
2419
|
+
), [s, r] = E(""), [o, i] = E(((b = t.sections[0]) == null ? void 0 : b.id) ?? "");
|
|
2420
|
+
W(() => {
|
|
2407
2421
|
try {
|
|
2408
|
-
localStorage.setItem(
|
|
2422
|
+
localStorage.setItem(Ke, n);
|
|
2409
2423
|
} catch {
|
|
2410
2424
|
}
|
|
2411
|
-
}, [n]),
|
|
2425
|
+
}, [n]), W(() => {
|
|
2412
2426
|
i(
|
|
2413
2427
|
(l) => {
|
|
2414
|
-
var
|
|
2415
|
-
return t.sections.some((f) => f.id === l) ? l : ((
|
|
2428
|
+
var v;
|
|
2429
|
+
return t.sections.some((f) => f.id === l) ? l : ((v = t.sections[0]) == null ? void 0 : v.id) ?? "";
|
|
2416
2430
|
}
|
|
2417
2431
|
);
|
|
2418
2432
|
}, [t]);
|
|
2419
|
-
const d =
|
|
2433
|
+
const d = Y(() => {
|
|
2420
2434
|
const l = s.trim().toLowerCase();
|
|
2421
2435
|
return l ? t.sections.filter(
|
|
2422
|
-
(
|
|
2436
|
+
(v) => v.title.toLowerCase().includes(l) || v.body.toLowerCase().includes(l)
|
|
2423
2437
|
) : t.sections;
|
|
2424
|
-
}, [t.sections, s]), h = t.sections.findIndex((l) => l.id === o), c = t.sections[h] ?? t.sections[0],
|
|
2438
|
+
}, [t.sections, s]), h = t.sections.findIndex((l) => l.id === o), c = t.sections[h] ?? t.sections[0], m = h > 0 ? t.sections[h - 1] : null, u = h >= 0 && h < t.sections.length - 1 ? t.sections[h + 1] : null;
|
|
2425
2439
|
return /* @__PURE__ */ p("div", { className: "docs-panel", children: [
|
|
2426
2440
|
/* @__PURE__ */ p("div", { className: "docs-sidebar", children: [
|
|
2427
2441
|
/* @__PURE__ */ a("div", { className: "docs-search", children: /* @__PURE__ */ a(
|
|
@@ -2467,57 +2481,57 @@ function Hn() {
|
|
|
2467
2481
|
)
|
|
2468
2482
|
] })
|
|
2469
2483
|
] }),
|
|
2470
|
-
s ? /* @__PURE__ */ a("h1", { children: n === "zh" ? "搜索结果" : "Search Results" }) : /* @__PURE__ */ p(
|
|
2484
|
+
s ? /* @__PURE__ */ a("h1", { children: n === "zh" ? "搜索结果" : "Search Results" }) : /* @__PURE__ */ p(K, { children: [
|
|
2471
2485
|
/* @__PURE__ */ a("h1", { children: t.title }),
|
|
2472
|
-
/* @__PURE__ */ a("div", { className: "docs-intro", children:
|
|
2486
|
+
/* @__PURE__ */ a("div", { className: "docs-intro", children: se(t.intro) }),
|
|
2473
2487
|
/* @__PURE__ */ a("hr", {})
|
|
2474
2488
|
] }),
|
|
2475
2489
|
s ? d.map((l) => /* @__PURE__ */ p("div", { className: "docs-result", children: [
|
|
2476
2490
|
/* @__PURE__ */ a("h2", { onClick: () => {
|
|
2477
2491
|
r(""), i(l.id);
|
|
2478
2492
|
}, children: l.title }),
|
|
2479
|
-
|
|
2480
|
-
] }, l.id)) : c && /* @__PURE__ */ p(
|
|
2493
|
+
se(l.body.slice(0, 400))
|
|
2494
|
+
] }, l.id)) : c && /* @__PURE__ */ p(K, { children: [
|
|
2481
2495
|
/* @__PURE__ */ a("h1", { children: c.title }),
|
|
2482
|
-
|
|
2496
|
+
se(c.body)
|
|
2483
2497
|
] }),
|
|
2484
2498
|
!s && /* @__PURE__ */ p("div", { className: "docs-pager", children: [
|
|
2485
|
-
|
|
2499
|
+
m && /* @__PURE__ */ p("button", { className: "btn btn-sm", onClick: () => i(m.id), children: [
|
|
2486
2500
|
"← ",
|
|
2487
|
-
|
|
2501
|
+
m.title
|
|
2488
2502
|
] }),
|
|
2489
2503
|
/* @__PURE__ */ a("div", { className: "docs-pager-spacer" }),
|
|
2490
|
-
|
|
2491
|
-
|
|
2504
|
+
u && /* @__PURE__ */ p("button", { className: "btn btn-sm", onClick: () => i(u.id), children: [
|
|
2505
|
+
u.title,
|
|
2492
2506
|
" →"
|
|
2493
2507
|
] })
|
|
2494
2508
|
] })
|
|
2495
2509
|
] }) })
|
|
2496
2510
|
] });
|
|
2497
2511
|
}
|
|
2498
|
-
function
|
|
2499
|
-
const [n, e] =
|
|
2512
|
+
function Je() {
|
|
2513
|
+
const [n, e] = E([]), [t, s] = E(!1), r = D(null), o = g((c) => c.pushToast), i = async () => {
|
|
2500
2514
|
try {
|
|
2501
|
-
const
|
|
2502
|
-
e(Array.isArray(
|
|
2515
|
+
const m = await (await fetch("/api/plugins")).json();
|
|
2516
|
+
e(Array.isArray(m.plugins) ? m.plugins : []);
|
|
2503
2517
|
} catch {
|
|
2504
2518
|
e([]);
|
|
2505
2519
|
}
|
|
2506
2520
|
};
|
|
2507
|
-
|
|
2521
|
+
W(() => {
|
|
2508
2522
|
i();
|
|
2509
2523
|
}, []);
|
|
2510
|
-
const d = async (c,
|
|
2524
|
+
const d = async (c, m) => {
|
|
2511
2525
|
s(!0);
|
|
2512
2526
|
try {
|
|
2513
|
-
const
|
|
2527
|
+
const b = await (await fetch("/api/plugins/toggle", {
|
|
2514
2528
|
method: "POST",
|
|
2515
2529
|
headers: { "Content-Type": "application/json" },
|
|
2516
|
-
body: JSON.stringify({ name: c.dir ?? c.name, enabled:
|
|
2530
|
+
body: JSON.stringify({ name: c.dir ?? c.name, enabled: m })
|
|
2517
2531
|
})).json();
|
|
2518
|
-
|
|
2519
|
-
} catch (
|
|
2520
|
-
o("error",
|
|
2532
|
+
b.status === "success" ? (e(Array.isArray(b.plugins) ? b.plugins : []), g.getState().loadNodeDefs(), o("success", `${c.name} ${m ? "enabled" : "disabled"}`)) : o("error", b.error ?? "toggle failed");
|
|
2533
|
+
} catch (u) {
|
|
2534
|
+
o("error", u instanceof Error ? u.message : String(u));
|
|
2521
2535
|
} finally {
|
|
2522
2536
|
s(!1);
|
|
2523
2537
|
}
|
|
@@ -2548,26 +2562,26 @@ function Ue() {
|
|
|
2548
2562
|
accept: ".py,text/x-python",
|
|
2549
2563
|
style: { display: "none" },
|
|
2550
2564
|
onChange: (c) => {
|
|
2551
|
-
var
|
|
2552
|
-
const
|
|
2553
|
-
if (c.target.value = "", !
|
|
2554
|
-
const
|
|
2555
|
-
|
|
2556
|
-
const l = String(
|
|
2565
|
+
var b;
|
|
2566
|
+
const m = (b = c.target.files) == null ? void 0 : b[0];
|
|
2567
|
+
if (c.target.value = "", !m) return;
|
|
2568
|
+
const u = new FileReader();
|
|
2569
|
+
u.onload = async () => {
|
|
2570
|
+
const l = String(u.result ?? ""), v = m.name.replace(/\.py$/i, "") || "plugin";
|
|
2557
2571
|
s(!0);
|
|
2558
2572
|
try {
|
|
2559
|
-
const
|
|
2573
|
+
const C = await (await fetch("/api/plugins/upload", {
|
|
2560
2574
|
method: "POST",
|
|
2561
2575
|
headers: { "Content-Type": "application/json" },
|
|
2562
|
-
body: JSON.stringify({ name:
|
|
2576
|
+
body: JSON.stringify({ name: v, code: l })
|
|
2563
2577
|
})).json();
|
|
2564
|
-
|
|
2578
|
+
C.status === "success" ? (e(Array.isArray(C.plugins) ? C.plugins : []), g.getState().loadNodeDefs(), o("success", `Loaded plugin ${v}`)) : o("error", C.error ?? "upload failed");
|
|
2565
2579
|
} catch (f) {
|
|
2566
2580
|
o("error", f instanceof Error ? f.message : String(f));
|
|
2567
2581
|
} finally {
|
|
2568
2582
|
s(!1);
|
|
2569
2583
|
}
|
|
2570
|
-
},
|
|
2584
|
+
}, u.readAsText(m);
|
|
2571
2585
|
}
|
|
2572
2586
|
}
|
|
2573
2587
|
)
|
|
@@ -2592,7 +2606,7 @@ function Ue() {
|
|
|
2592
2606
|
type: "checkbox",
|
|
2593
2607
|
checked: !!c.enabled,
|
|
2594
2608
|
disabled: t || c.status === "error",
|
|
2595
|
-
onChange: (
|
|
2609
|
+
onChange: (m) => d(c, m.target.checked)
|
|
2596
2610
|
}
|
|
2597
2611
|
),
|
|
2598
2612
|
/* @__PURE__ */ a("span", { className: "plugin-toggle-track" })
|
|
@@ -2601,8 +2615,8 @@ function Ue() {
|
|
|
2601
2615
|
] })
|
|
2602
2616
|
] });
|
|
2603
2617
|
}
|
|
2604
|
-
function
|
|
2605
|
-
const n = g((
|
|
2618
|
+
function Jn() {
|
|
2619
|
+
const n = g((w) => w.pushToast), e = g((w) => w.openProjectFile), t = g((w) => w.importModule), s = g((w) => w.projectVersion), [r, o] = E([]), [i, d] = E({}), [h, c] = E(null), [m, u] = E(null), b = async () => {
|
|
2606
2620
|
try {
|
|
2607
2621
|
const _ = await (await fetch("/api/project/tree")).json();
|
|
2608
2622
|
o(Array.isArray(_.tree) ? _.tree : []);
|
|
@@ -2610,62 +2624,62 @@ function Kn() {
|
|
|
2610
2624
|
o([]);
|
|
2611
2625
|
}
|
|
2612
2626
|
};
|
|
2613
|
-
|
|
2614
|
-
|
|
2627
|
+
W(() => {
|
|
2628
|
+
b();
|
|
2615
2629
|
}, [s]);
|
|
2616
|
-
const l = async (
|
|
2630
|
+
const l = async (w, _) => (await fetch(w, {
|
|
2617
2631
|
method: "POST",
|
|
2618
2632
|
headers: { "Content-Type": "application/json" },
|
|
2619
2633
|
body: JSON.stringify(_)
|
|
2620
|
-
})).json(),
|
|
2634
|
+
})).json(), v = async (w) => {
|
|
2621
2635
|
const _ = window.prompt("New file name", "untitled.riko");
|
|
2622
2636
|
if (!_) return;
|
|
2623
|
-
const k = await l("/api/project/create", { name: _, dir:
|
|
2624
|
-
k.status === "success" ? (await
|
|
2625
|
-
}, f = async (
|
|
2637
|
+
const k = await l("/api/project/create", { name: _, dir: w });
|
|
2638
|
+
k.status === "success" ? (await b(), e(k.path)) : n("error", k.error ?? "create failed");
|
|
2639
|
+
}, f = async (w) => {
|
|
2626
2640
|
const _ = window.prompt("New folder name");
|
|
2627
2641
|
if (!_) return;
|
|
2628
|
-
const k = await l("/api/project/mkdir", { name: _, dir:
|
|
2629
|
-
k.status === "success" ? (d((
|
|
2630
|
-
},
|
|
2631
|
-
const _ = window.prompt("Rename",
|
|
2642
|
+
const k = await l("/api/project/mkdir", { name: _, dir: w });
|
|
2643
|
+
k.status === "success" ? (d((T) => ({ ...T, [w]: !0 })), await b()) : n("error", k.error ?? "mkdir failed");
|
|
2644
|
+
}, C = async (w) => {
|
|
2645
|
+
const _ = window.prompt("Rename", w.name);
|
|
2632
2646
|
if (!_) return;
|
|
2633
|
-
const k = await l("/api/project/rename", { path:
|
|
2634
|
-
k.status === "success" ? await
|
|
2635
|
-
}, y = async (
|
|
2636
|
-
if (!window.confirm(`Delete ${
|
|
2637
|
-
const _ = await l("/api/project/delete", { path:
|
|
2638
|
-
_.status === "success" ? (await
|
|
2639
|
-
},
|
|
2640
|
-
const _ = await l("/api/project/code", { path:
|
|
2647
|
+
const k = await l("/api/project/rename", { path: w.path, newName: _ });
|
|
2648
|
+
k.status === "success" ? await b() : n("error", k.error ?? "rename failed");
|
|
2649
|
+
}, y = async (w) => {
|
|
2650
|
+
if (!window.confirm(`Delete ${w.path}?`)) return;
|
|
2651
|
+
const _ = await l("/api/project/delete", { path: w.path });
|
|
2652
|
+
_.status === "success" ? (await b(), n("success", `Deleted ${w.name}`)) : n("error", _.error ?? "delete failed");
|
|
2653
|
+
}, F = async (w) => {
|
|
2654
|
+
const _ = await l("/api/project/code", { path: w.path });
|
|
2641
2655
|
_.status === "success" ? (g.getState().setCodeBuffer(_.code ?? ""), n("success", "Code → Code Editor panel")) : n("error", _.error ?? "code generation failed");
|
|
2642
|
-
},
|
|
2643
|
-
const k = await l("/api/project/move", { path:
|
|
2644
|
-
k.status === "success" ? (await
|
|
2645
|
-
},
|
|
2646
|
-
const
|
|
2647
|
-
return
|
|
2648
|
-
{ id: "open", label: "Open", onSelect: () => e(
|
|
2649
|
-
{ id: "code", label: "Preview PyTorch Code", description: "→ Code Editor panel", onSelect: () =>
|
|
2650
|
-
{ id: "import", label: "Import as Node", description: "reference this graph", onSelect: () => t(
|
|
2656
|
+
}, x = async (w, _) => {
|
|
2657
|
+
const k = await l("/api/project/move", { path: w, targetDir: _ });
|
|
2658
|
+
k.status === "success" ? (await b(), n("success", `Moved ${w} → ${_ || "(root)"}`)) : n("error", k.error ?? "move failed");
|
|
2659
|
+
}, O = () => {
|
|
2660
|
+
const w = (h == null ? void 0 : h.target) ?? null;
|
|
2661
|
+
return w && w.type === "file" ? [
|
|
2662
|
+
{ id: "open", label: "Open", onSelect: () => e(w.path) },
|
|
2663
|
+
{ id: "code", label: "Preview PyTorch Code", description: "→ Code Editor panel", onSelect: () => F(w) },
|
|
2664
|
+
{ id: "import", label: "Import as Node", description: "reference this graph", onSelect: () => t(w.name.replace(/\.(riko|ric)$/i, ""), { x: 260, y: 150 }) },
|
|
2651
2665
|
{ id: "sep1", label: "", separator: !0 },
|
|
2652
|
-
{ id: "rename", label: "Rename", onSelect: () =>
|
|
2653
|
-
{ id: "delete", label: "Delete", danger: !0, onSelect: () => y(
|
|
2654
|
-
] :
|
|
2655
|
-
{ id: "newfile", label: "New File", onSelect: () =>
|
|
2656
|
-
{ id: "newfolder", label: "New Folder", onSelect: () => f(
|
|
2666
|
+
{ id: "rename", label: "Rename", onSelect: () => C(w) },
|
|
2667
|
+
{ id: "delete", label: "Delete", danger: !0, onSelect: () => y(w) }
|
|
2668
|
+
] : w && w.type === "dir" ? [
|
|
2669
|
+
{ id: "newfile", label: "New File", onSelect: () => v(w.path) },
|
|
2670
|
+
{ id: "newfolder", label: "New Folder", onSelect: () => f(w.path) },
|
|
2657
2671
|
{ id: "sep1", label: "", separator: !0 },
|
|
2658
|
-
{ id: "rename", label: "Rename", onSelect: () =>
|
|
2659
|
-
{ id: "delete", label: "Delete", danger: !0, onSelect: () => y(
|
|
2672
|
+
{ id: "rename", label: "Rename", onSelect: () => C(w) },
|
|
2673
|
+
{ id: "delete", label: "Delete", danger: !0, onSelect: () => y(w) }
|
|
2660
2674
|
] : [
|
|
2661
|
-
{ id: "newfile", label: "New File", onSelect: () =>
|
|
2675
|
+
{ id: "newfile", label: "New File", onSelect: () => v("") },
|
|
2662
2676
|
{ id: "newfolder", label: "New Folder", onSelect: () => f("") },
|
|
2663
2677
|
{ id: "sep1", label: "", separator: !0 },
|
|
2664
|
-
{ id: "refresh", label: "Refresh", onSelect:
|
|
2678
|
+
{ id: "refresh", label: "Refresh", onSelect: b }
|
|
2665
2679
|
];
|
|
2666
|
-
}, $ = (
|
|
2667
|
-
if (
|
|
2668
|
-
const k = !!i[
|
|
2680
|
+
}, $ = (w, _) => {
|
|
2681
|
+
if (w.type === "dir") {
|
|
2682
|
+
const k = !!i[w.path];
|
|
2669
2683
|
return /* @__PURE__ */ p("div", { children: [
|
|
2670
2684
|
/* @__PURE__ */ p(
|
|
2671
2685
|
"div",
|
|
@@ -2673,28 +2687,28 @@ function Kn() {
|
|
|
2673
2687
|
className: "proj-row",
|
|
2674
2688
|
style: { paddingLeft: 8 + _ * 12 },
|
|
2675
2689
|
draggable: !0,
|
|
2676
|
-
onDragStart: (
|
|
2677
|
-
|
|
2690
|
+
onDragStart: (T) => {
|
|
2691
|
+
u(w.path), T.dataTransfer.effectAllowed = "move";
|
|
2678
2692
|
},
|
|
2679
|
-
onDragOver: (
|
|
2680
|
-
|
|
2693
|
+
onDragOver: (T) => {
|
|
2694
|
+
T.preventDefault(), T.dataTransfer.dropEffect = "move";
|
|
2681
2695
|
},
|
|
2682
|
-
onDrop: (
|
|
2683
|
-
|
|
2696
|
+
onDrop: (T) => {
|
|
2697
|
+
T.preventDefault(), T.stopPropagation(), m && m !== w.path && x(m, w.path), u(null);
|
|
2684
2698
|
},
|
|
2685
|
-
onClick: () => d((
|
|
2686
|
-
onContextMenu: (
|
|
2687
|
-
|
|
2699
|
+
onClick: () => d((T) => ({ ...T, [w.path]: !k })),
|
|
2700
|
+
onContextMenu: (T) => {
|
|
2701
|
+
T.preventDefault(), T.stopPropagation(), c({ x: T.clientX, y: T.clientY, target: w });
|
|
2688
2702
|
},
|
|
2689
2703
|
children: [
|
|
2690
2704
|
/* @__PURE__ */ a("span", { className: "proj-caret", children: k ? "▾" : "▸" }),
|
|
2691
2705
|
/* @__PURE__ */ a("span", { className: "proj-icon", children: "▦" }),
|
|
2692
|
-
/* @__PURE__ */ a("span", { className: "proj-name", children:
|
|
2706
|
+
/* @__PURE__ */ a("span", { className: "proj-name", children: w.name })
|
|
2693
2707
|
]
|
|
2694
2708
|
}
|
|
2695
2709
|
),
|
|
2696
|
-
k && (
|
|
2697
|
-
] },
|
|
2710
|
+
k && (w.children ?? []).map((T) => $(T, _ + 1))
|
|
2711
|
+
] }, w.path);
|
|
2698
2712
|
}
|
|
2699
2713
|
return /* @__PURE__ */ p(
|
|
2700
2714
|
"div",
|
|
@@ -2703,71 +2717,71 @@ function Kn() {
|
|
|
2703
2717
|
style: { paddingLeft: 8 + _ * 12 },
|
|
2704
2718
|
draggable: !0,
|
|
2705
2719
|
onDragStart: (k) => {
|
|
2706
|
-
|
|
2720
|
+
u(w.path), k.dataTransfer.effectAllowed = "move";
|
|
2707
2721
|
},
|
|
2708
|
-
onClick: () => e(
|
|
2722
|
+
onClick: () => e(w.path),
|
|
2709
2723
|
onContextMenu: (k) => {
|
|
2710
|
-
k.preventDefault(), k.stopPropagation(), c({ x: k.clientX, y: k.clientY, target:
|
|
2724
|
+
k.preventDefault(), k.stopPropagation(), c({ x: k.clientX, y: k.clientY, target: w });
|
|
2711
2725
|
},
|
|
2712
2726
|
children: [
|
|
2713
2727
|
/* @__PURE__ */ a("span", { className: "proj-caret", children: "·" }),
|
|
2714
2728
|
/* @__PURE__ */ a("span", { className: "proj-icon", children: "▤" }),
|
|
2715
|
-
/* @__PURE__ */ a("span", { className: "proj-name", children:
|
|
2729
|
+
/* @__PURE__ */ a("span", { className: "proj-name", children: w.name })
|
|
2716
2730
|
]
|
|
2717
2731
|
},
|
|
2718
|
-
|
|
2732
|
+
w.path
|
|
2719
2733
|
);
|
|
2720
2734
|
};
|
|
2721
2735
|
return /* @__PURE__ */ p("div", { className: "project-panel", children: [
|
|
2722
2736
|
/* @__PURE__ */ p("div", { className: "panel-head", children: [
|
|
2723
2737
|
/* @__PURE__ */ a("span", { className: "panel-head-title", children: "New File" }),
|
|
2724
2738
|
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
2725
|
-
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Refresh", onClick:
|
|
2739
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Refresh", onClick: b, children: "↻" })
|
|
2726
2740
|
] }),
|
|
2727
2741
|
/* @__PURE__ */ p(
|
|
2728
2742
|
"div",
|
|
2729
2743
|
{
|
|
2730
2744
|
className: "proj-tree",
|
|
2731
|
-
onDragOver: (
|
|
2732
|
-
onDrop: (
|
|
2733
|
-
|
|
2745
|
+
onDragOver: (w) => w.preventDefault(),
|
|
2746
|
+
onDrop: (w) => {
|
|
2747
|
+
w.preventDefault(), m && x(m, ""), u(null);
|
|
2734
2748
|
},
|
|
2735
|
-
onContextMenu: (
|
|
2736
|
-
|
|
2749
|
+
onContextMenu: (w) => {
|
|
2750
|
+
w.preventDefault(), c({ x: w.clientX, y: w.clientY, target: null });
|
|
2737
2751
|
},
|
|
2738
2752
|
children: [
|
|
2739
|
-
r.map((
|
|
2753
|
+
r.map((w) => $(w, 0)),
|
|
2740
2754
|
r.length === 0 && /* @__PURE__ */ a("div", { className: "welcome-empty", children: "Right-click to create a file." })
|
|
2741
2755
|
]
|
|
2742
2756
|
}
|
|
2743
2757
|
),
|
|
2744
|
-
h && /* @__PURE__ */ a(
|
|
2758
|
+
h && /* @__PURE__ */ a(re, { entries: O(), x: h.x, y: h.y, onClose: () => c(null) })
|
|
2745
2759
|
] });
|
|
2746
2760
|
}
|
|
2747
|
-
function
|
|
2748
|
-
const n = g((y) => y.codeBuffer), e = g((y) => y.setCodeBuffer), t = g((y) => y.generatePythonCode), s = g((y) => y.pushToast), r =
|
|
2761
|
+
function zn() {
|
|
2762
|
+
const n = g((y) => y.codeBuffer), e = g((y) => y.setCodeBuffer), t = g((y) => y.generatePythonCode), s = g((y) => y.pushToast), r = D(null), o = D(null), [i, d] = E(null), h = () => {
|
|
2749
2763
|
var y;
|
|
2750
2764
|
return (y = r.current) == null ? void 0 : y.focus();
|
|
2751
2765
|
}, c = (y) => {
|
|
2752
2766
|
h(), document.execCommand(y);
|
|
2753
|
-
},
|
|
2767
|
+
}, m = async () => {
|
|
2754
2768
|
try {
|
|
2755
2769
|
await navigator.clipboard.writeText(n), s("success", "Copied");
|
|
2756
2770
|
} catch {
|
|
2757
2771
|
c("copy");
|
|
2758
2772
|
}
|
|
2759
|
-
},
|
|
2773
|
+
}, u = () => {
|
|
2760
2774
|
var y;
|
|
2761
2775
|
h(), (y = r.current) == null || y.select();
|
|
2762
|
-
},
|
|
2763
|
-
const y = new Blob([n], { type: "text/x-python" }),
|
|
2764
|
-
|
|
2765
|
-
}, l = () => e(""),
|
|
2766
|
-
var
|
|
2767
|
-
const
|
|
2768
|
-
if (y.target.value = "", !
|
|
2769
|
-
const
|
|
2770
|
-
|
|
2776
|
+
}, b = () => {
|
|
2777
|
+
const y = new Blob([n], { type: "text/x-python" }), F = URL.createObjectURL(y), x = document.createElement("a");
|
|
2778
|
+
x.href = F, x.download = "code.py", x.click(), URL.revokeObjectURL(F);
|
|
2779
|
+
}, l = () => e(""), v = (y) => {
|
|
2780
|
+
var O;
|
|
2781
|
+
const F = (O = y.target.files) == null ? void 0 : O[0];
|
|
2782
|
+
if (y.target.value = "", !F) return;
|
|
2783
|
+
const x = new FileReader();
|
|
2784
|
+
x.onload = () => e(String(x.result ?? "")), x.readAsText(F);
|
|
2771
2785
|
}, f = [
|
|
2772
2786
|
{
|
|
2773
2787
|
label: "File",
|
|
@@ -2778,7 +2792,7 @@ function Jn() {
|
|
|
2778
2792
|
return (y = o.current) == null ? void 0 : y.click();
|
|
2779
2793
|
} },
|
|
2780
2794
|
{ label: "Generate from Graph", run: t },
|
|
2781
|
-
{ label: "Save .py", run:
|
|
2795
|
+
{ label: "Save .py", run: b }
|
|
2782
2796
|
]
|
|
2783
2797
|
},
|
|
2784
2798
|
{
|
|
@@ -2787,25 +2801,25 @@ function Jn() {
|
|
|
2787
2801
|
{ label: "Undo", run: () => c("undo") },
|
|
2788
2802
|
{ label: "Redo", run: () => c("redo") },
|
|
2789
2803
|
{ label: "Cut", run: () => c("cut") },
|
|
2790
|
-
{ label: "Copy", run:
|
|
2804
|
+
{ label: "Copy", run: m },
|
|
2791
2805
|
{ label: "Paste", run: () => c("paste") },
|
|
2792
|
-
{ label: "Select All", run:
|
|
2806
|
+
{ label: "Select All", run: u },
|
|
2793
2807
|
{ label: "Clear", run: l }
|
|
2794
2808
|
]
|
|
2795
2809
|
}
|
|
2796
|
-
],
|
|
2810
|
+
], C = [
|
|
2797
2811
|
{ icon: "+", title: "New", run: l },
|
|
2798
2812
|
{ icon: "📂", title: "Open", run: () => {
|
|
2799
2813
|
var y;
|
|
2800
2814
|
return (y = o.current) == null ? void 0 : y.click();
|
|
2801
2815
|
} },
|
|
2802
|
-
{ icon: "💾", title: "Save .py", run:
|
|
2816
|
+
{ icon: "💾", title: "Save .py", run: b },
|
|
2803
2817
|
{ icon: "↶", title: "Undo", run: () => c("undo") },
|
|
2804
2818
|
{ icon: "↷", title: "Redo", run: () => c("redo") },
|
|
2805
2819
|
{ icon: "✂", title: "Cut", run: () => c("cut") },
|
|
2806
|
-
{ icon: "⧉", title: "Copy", run:
|
|
2820
|
+
{ icon: "⧉", title: "Copy", run: m },
|
|
2807
2821
|
{ icon: "📋", title: "Paste", run: () => c("paste") },
|
|
2808
|
-
{ icon: "▦", title: "Select All", run:
|
|
2822
|
+
{ icon: "▦", title: "Select All", run: u }
|
|
2809
2823
|
];
|
|
2810
2824
|
return /* @__PURE__ */ p("div", { className: "code-editor", children: [
|
|
2811
2825
|
/* @__PURE__ */ a("div", { className: "code-menubar", children: f.map((y) => /* @__PURE__ */ p(
|
|
@@ -2815,22 +2829,22 @@ function Jn() {
|
|
|
2815
2829
|
onClick: () => d(i === y.label ? null : y.label),
|
|
2816
2830
|
children: [
|
|
2817
2831
|
y.label,
|
|
2818
|
-
i === y.label && /* @__PURE__ */ a("div", { className: "code-menu-dropdown", onClick: (
|
|
2832
|
+
i === y.label && /* @__PURE__ */ a("div", { className: "code-menu-dropdown", onClick: (F) => F.stopPropagation(), children: y.items.map((F, x) => /* @__PURE__ */ a(
|
|
2819
2833
|
"div",
|
|
2820
2834
|
{
|
|
2821
2835
|
className: "code-menu-entry",
|
|
2822
2836
|
onClick: () => {
|
|
2823
|
-
|
|
2837
|
+
F.run(), d(null);
|
|
2824
2838
|
},
|
|
2825
|
-
children:
|
|
2839
|
+
children: F.label
|
|
2826
2840
|
},
|
|
2827
|
-
|
|
2841
|
+
x
|
|
2828
2842
|
)) })
|
|
2829
2843
|
]
|
|
2830
2844
|
},
|
|
2831
2845
|
y.label
|
|
2832
2846
|
)) }),
|
|
2833
|
-
/* @__PURE__ */ a("div", { className: "code-toolbar", children:
|
|
2847
|
+
/* @__PURE__ */ a("div", { className: "code-toolbar", children: C.map((y, F) => /* @__PURE__ */ a("button", { className: "panel-btn", title: y.title, onClick: y.run, children: y.icon }, F)) }),
|
|
2834
2848
|
/* @__PURE__ */ a(
|
|
2835
2849
|
"textarea",
|
|
2836
2850
|
{
|
|
@@ -2849,30 +2863,30 @@ function Jn() {
|
|
|
2849
2863
|
type: "file",
|
|
2850
2864
|
accept: ".py,.txt,.riko,.ric,text/plain,text/x-python",
|
|
2851
2865
|
style: { display: "none" },
|
|
2852
|
-
onChange:
|
|
2866
|
+
onChange: v
|
|
2853
2867
|
}
|
|
2854
2868
|
)
|
|
2855
2869
|
] });
|
|
2856
2870
|
}
|
|
2857
|
-
function
|
|
2858
|
-
const n =
|
|
2859
|
-
|
|
2860
|
-
const
|
|
2861
|
-
if (!
|
|
2862
|
-
const
|
|
2863
|
-
|
|
2871
|
+
function Vn() {
|
|
2872
|
+
const n = D(null), e = D(!1), t = g((u) => u.addNode), s = g((u) => u.updateParam), r = g((u) => u.pushToast);
|
|
2873
|
+
W(() => {
|
|
2874
|
+
const u = n.current;
|
|
2875
|
+
if (!u) return;
|
|
2876
|
+
const b = u.getContext("2d");
|
|
2877
|
+
b && (b.fillStyle = "#fff", b.fillRect(0, 0, u.width, u.height), b.lineWidth = 12, b.lineCap = "round", b.lineJoin = "round", b.strokeStyle = "#000");
|
|
2864
2878
|
}, []);
|
|
2865
|
-
const o = (
|
|
2879
|
+
const o = (u) => {
|
|
2866
2880
|
const l = n.current.getBoundingClientRect();
|
|
2867
|
-
return { x:
|
|
2868
|
-
}, i = (
|
|
2881
|
+
return { x: u.clientX - l.left, y: u.clientY - l.top };
|
|
2882
|
+
}, i = (u) => {
|
|
2869
2883
|
e.current = !0;
|
|
2870
|
-
const l = n.current.getContext("2d"), { x:
|
|
2871
|
-
l.beginPath(), l.moveTo(
|
|
2872
|
-
}, d = (
|
|
2884
|
+
const l = n.current.getContext("2d"), { x: v, y: f } = o(u);
|
|
2885
|
+
l.beginPath(), l.moveTo(v, f), l.lineTo(v, f), l.stroke();
|
|
2886
|
+
}, d = (u) => {
|
|
2873
2887
|
if (!e.current) return;
|
|
2874
|
-
const l = n.current.getContext("2d"), { x:
|
|
2875
|
-
l.lineTo(
|
|
2888
|
+
const l = n.current.getContext("2d"), { x: v, y: f } = o(u);
|
|
2889
|
+
l.lineTo(v, f), l.stroke();
|
|
2876
2890
|
}, h = () => {
|
|
2877
2891
|
e.current = !1;
|
|
2878
2892
|
};
|
|
@@ -2881,25 +2895,25 @@ function zn() {
|
|
|
2881
2895
|
/* @__PURE__ */ a("span", { className: "panel-head-title", children: "Handwriting Pad" }),
|
|
2882
2896
|
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
2883
2897
|
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () => {
|
|
2884
|
-
const
|
|
2885
|
-
|
|
2898
|
+
const u = n.current, b = u.getContext("2d");
|
|
2899
|
+
b.fillStyle = "#fff", b.fillRect(0, 0, u.width, u.height);
|
|
2886
2900
|
}, children: "Clear" }),
|
|
2887
2901
|
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () => {
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
const l =
|
|
2891
|
-
l.drawImage(
|
|
2892
|
-
const
|
|
2893
|
-
for (let
|
|
2894
|
-
const
|
|
2895
|
-
f.push(Math.round(
|
|
2902
|
+
const u = n.current, b = document.createElement("canvas");
|
|
2903
|
+
b.width = 28, b.height = 28;
|
|
2904
|
+
const l = b.getContext("2d");
|
|
2905
|
+
l.drawImage(u, 0, 0, 28, 28);
|
|
2906
|
+
const v = l.getImageData(0, 0, 28, 28).data, f = [];
|
|
2907
|
+
for (let x = 0; x < 28 * 28; x++) {
|
|
2908
|
+
const O = 1 - (v[x * 4] + v[x * 4 + 1] + v[x * 4 + 2]) / 765;
|
|
2909
|
+
f.push(Math.round(O * 1e3) / 1e3);
|
|
2896
2910
|
}
|
|
2897
|
-
const
|
|
2898
|
-
for (let
|
|
2899
|
-
const y = [[
|
|
2911
|
+
const C = [];
|
|
2912
|
+
for (let x = 0; x < 28; x++) C.push(f.slice(x * 28, x * 28 + 28));
|
|
2913
|
+
const y = [[C]];
|
|
2900
2914
|
t("constant", { x: 260, y: 150 });
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2915
|
+
const F = g.getState().selectedNodeId;
|
|
2916
|
+
F && (s(F, "value", y), r("success", "Digit → constant node (1×28×28)"));
|
|
2903
2917
|
}, children: "Send → Node" })
|
|
2904
2918
|
] }),
|
|
2905
2919
|
/* @__PURE__ */ a("div", { className: "handpad-canvas", children: /* @__PURE__ */ a(
|
|
@@ -2921,7 +2935,7 @@ function zn() {
|
|
|
2921
2935
|
] })
|
|
2922
2936
|
] });
|
|
2923
2937
|
}
|
|
2924
|
-
const
|
|
2938
|
+
const ze = [
|
|
2925
2939
|
{ value: "canvas", label: "Graph", category: "Editor" },
|
|
2926
2940
|
{ value: "inspector", label: "Inspector", category: "Editor" },
|
|
2927
2941
|
{ value: "status", label: "Status / Logs", category: "Editor" },
|
|
@@ -2933,36 +2947,36 @@ const He = [
|
|
|
2933
2947
|
{ value: "pad", label: "Handwriting Pad", category: "Tools" },
|
|
2934
2948
|
{ value: "docs", label: "Documentation", category: "Tools" },
|
|
2935
2949
|
{ value: "plugins", label: "Plugins", category: "Tools" }
|
|
2936
|
-
],
|
|
2937
|
-
function
|
|
2950
|
+
], qn = ["Editor", "Data", "Tools"];
|
|
2951
|
+
function Yn(n) {
|
|
2938
2952
|
var e;
|
|
2939
|
-
return ((e =
|
|
2953
|
+
return ((e = ze.find((t) => t.value === n)) == null ? void 0 : e.label) ?? n;
|
|
2940
2954
|
}
|
|
2941
|
-
const
|
|
2942
|
-
nodes: () => /* @__PURE__ */ a(
|
|
2943
|
-
files: () => /* @__PURE__ */ a(
|
|
2944
|
-
project: () => /* @__PURE__ */ a(
|
|
2955
|
+
const Ve = {
|
|
2956
|
+
nodes: () => /* @__PURE__ */ a(_n, {}),
|
|
2957
|
+
files: () => /* @__PURE__ */ a(Sn, {}),
|
|
2958
|
+
project: () => /* @__PURE__ */ a(Jn, {}),
|
|
2945
2959
|
canvas: () => /* @__PURE__ */ a(On, {}),
|
|
2946
|
-
inspector: () => /* @__PURE__ */ a(
|
|
2947
|
-
status: () => /* @__PURE__ */ a(
|
|
2948
|
-
loss: () => /* @__PURE__ */ a(
|
|
2949
|
-
docs: () => /* @__PURE__ */ a(
|
|
2950
|
-
plugins: () => /* @__PURE__ */ a(
|
|
2951
|
-
code: () => /* @__PURE__ */ a(
|
|
2952
|
-
pad: () => /* @__PURE__ */ a(
|
|
2960
|
+
inspector: () => /* @__PURE__ */ a(jn, {}),
|
|
2961
|
+
status: () => /* @__PURE__ */ a(An, {}),
|
|
2962
|
+
loss: () => /* @__PURE__ */ a(Mn, {}),
|
|
2963
|
+
docs: () => /* @__PURE__ */ a(Kn, {}),
|
|
2964
|
+
plugins: () => /* @__PURE__ */ a(Je, {}),
|
|
2965
|
+
code: () => /* @__PURE__ */ a(zn, {}),
|
|
2966
|
+
pad: () => /* @__PURE__ */ a(Vn, {})
|
|
2953
2967
|
};
|
|
2954
|
-
function
|
|
2955
|
-
|
|
2968
|
+
function mt(n, e) {
|
|
2969
|
+
Ve[n] = e;
|
|
2956
2970
|
}
|
|
2957
|
-
function
|
|
2958
|
-
const e =
|
|
2971
|
+
function Xn(n) {
|
|
2972
|
+
const e = Ve[n];
|
|
2959
2973
|
return e ? e() : null;
|
|
2960
2974
|
}
|
|
2961
|
-
function
|
|
2975
|
+
function Qn({
|
|
2962
2976
|
type: n,
|
|
2963
2977
|
onType: e
|
|
2964
2978
|
}) {
|
|
2965
|
-
const [t, s] =
|
|
2979
|
+
const [t, s] = E(!1), [r, o] = E(null), i = D(null);
|
|
2966
2980
|
return /* @__PURE__ */ p("div", { className: "type-dropdown", children: [
|
|
2967
2981
|
/* @__PURE__ */ p("button", { ref: i, className: "panel-btn", title: "Switch window type", onClick: () => {
|
|
2968
2982
|
if (!t && i.current) {
|
|
@@ -2971,15 +2985,15 @@ function Xn({
|
|
|
2971
2985
|
}
|
|
2972
2986
|
s((h) => !h);
|
|
2973
2987
|
}, children: [
|
|
2974
|
-
|
|
2988
|
+
Yn(n),
|
|
2975
2989
|
" ▾"
|
|
2976
2990
|
] }),
|
|
2977
|
-
t &&
|
|
2978
|
-
/* @__PURE__ */ p(
|
|
2991
|
+
t && Le(
|
|
2992
|
+
/* @__PURE__ */ p(K, { children: [
|
|
2979
2993
|
/* @__PURE__ */ a("div", { className: "type-menu-overlay", onClick: () => s(!1) }),
|
|
2980
|
-
/* @__PURE__ */ a("div", { className: "type-menu", style: r ? { position: "fixed", top: r.top, left: r.left } : void 0, children:
|
|
2994
|
+
/* @__PURE__ */ a("div", { className: "type-menu", style: r ? { position: "fixed", top: r.top, left: r.left } : void 0, children: qn.map((h) => /* @__PURE__ */ p("div", { className: "type-menu-col", children: [
|
|
2981
2995
|
/* @__PURE__ */ a("div", { className: "type-menu-cat", children: h }),
|
|
2982
|
-
|
|
2996
|
+
ze.filter((c) => c.category === h).map((c) => /* @__PURE__ */ a(
|
|
2983
2997
|
"div",
|
|
2984
2998
|
{
|
|
2985
2999
|
className: `type-menu-item ${c.value === n ? "active" : ""}`,
|
|
@@ -2996,14 +3010,14 @@ function Xn({
|
|
|
2996
3010
|
)
|
|
2997
3011
|
] });
|
|
2998
3012
|
}
|
|
2999
|
-
function
|
|
3013
|
+
function Oe(n, e) {
|
|
3000
3014
|
return Math.abs(n) < 14 && Math.abs(e) < 14 ? null : Math.abs(n) > Math.abs(e) ? n < 0 ? "split-row" : "merge" : e < 0 ? "split-column" : "merge";
|
|
3001
3015
|
}
|
|
3002
|
-
function
|
|
3016
|
+
function Zn({
|
|
3003
3017
|
onPreview: n,
|
|
3004
3018
|
onCommit: e
|
|
3005
3019
|
}) {
|
|
3006
|
-
const t =
|
|
3020
|
+
const t = D(null);
|
|
3007
3021
|
return /* @__PURE__ */ a(
|
|
3008
3022
|
"div",
|
|
3009
3023
|
{
|
|
@@ -3015,23 +3029,23 @@ function Qn({
|
|
|
3015
3029
|
t.current = { x: r.clientX, y: r.clientY };
|
|
3016
3030
|
const d = (c) => {
|
|
3017
3031
|
if (!t.current) return;
|
|
3018
|
-
const
|
|
3032
|
+
const m = c.clientX - t.current.x, u = c.clientY - t.current.y, b = Oe(m, u);
|
|
3019
3033
|
n(
|
|
3020
|
-
|
|
3034
|
+
b ? { mode: b, x: c.clientX - ((i == null ? void 0 : i.left) ?? 0), y: c.clientY - ((i == null ? void 0 : i.top) ?? 0) } : null
|
|
3021
3035
|
);
|
|
3022
3036
|
}, h = (c) => {
|
|
3023
3037
|
if (!t.current) return;
|
|
3024
|
-
const
|
|
3038
|
+
const m = c.clientX - t.current.x, u = c.clientY - t.current.y;
|
|
3025
3039
|
t.current = null, document.removeEventListener("mousemove", d), document.removeEventListener("mouseup", h), document.body.style.cursor = "";
|
|
3026
|
-
const
|
|
3027
|
-
n(null),
|
|
3040
|
+
const b = Oe(m, u);
|
|
3041
|
+
n(null), b && e(b);
|
|
3028
3042
|
};
|
|
3029
3043
|
document.body.style.cursor = "crosshair", document.addEventListener("mousemove", d), document.addEventListener("mouseup", h);
|
|
3030
3044
|
}
|
|
3031
3045
|
}
|
|
3032
3046
|
);
|
|
3033
3047
|
}
|
|
3034
|
-
function
|
|
3048
|
+
function et({
|
|
3035
3049
|
id: n,
|
|
3036
3050
|
type: e,
|
|
3037
3051
|
onType: t,
|
|
@@ -3042,59 +3056,59 @@ function Zn({
|
|
|
3042
3056
|
canMerge: d,
|
|
3043
3057
|
mergeTarget: h,
|
|
3044
3058
|
preview: c,
|
|
3045
|
-
onPreview:
|
|
3059
|
+
onPreview: m
|
|
3046
3060
|
}) {
|
|
3047
3061
|
return /* @__PURE__ */ p("div", { className: `panel-slot ${h ? "merge-target" : ""}`, children: [
|
|
3048
3062
|
/* @__PURE__ */ p("div", { className: "panel-bar", children: [
|
|
3049
3063
|
/* @__PURE__ */ a("div", { className: "panel-bar-spacer" }),
|
|
3050
|
-
/* @__PURE__ */ a(
|
|
3064
|
+
/* @__PURE__ */ a(Qn, { type: e, onType: t }),
|
|
3051
3065
|
d && /* @__PURE__ */ a("button", { className: "panel-btn", title: "Close / merge panel away", onClick: i, children: "✕" })
|
|
3052
3066
|
] }),
|
|
3053
|
-
/* @__PURE__ */ a("div", { className: "panel-body", children:
|
|
3054
|
-
c && c.mode === "split-row" && /* @__PURE__ */ p(
|
|
3067
|
+
/* @__PURE__ */ a("div", { className: "panel-body", children: Xn(e) }),
|
|
3068
|
+
c && c.mode === "split-row" && /* @__PURE__ */ p(K, { children: [
|
|
3055
3069
|
/* @__PURE__ */ a("div", { className: "split-preview", style: { left: 0, top: 0, bottom: 0, width: Math.max(0, c.x - 4) } }),
|
|
3056
3070
|
/* @__PURE__ */ a("div", { className: "split-preview", style: { left: c.x + 4, top: 0, bottom: 0, right: 0 } })
|
|
3057
3071
|
] }),
|
|
3058
|
-
c && c.mode === "split-column" && /* @__PURE__ */ p(
|
|
3072
|
+
c && c.mode === "split-column" && /* @__PURE__ */ p(K, { children: [
|
|
3059
3073
|
/* @__PURE__ */ a("div", { className: "split-preview", style: { left: 0, right: 0, top: 0, height: Math.max(0, c.y - 4) } }),
|
|
3060
3074
|
/* @__PURE__ */ a("div", { className: "split-preview", style: { left: 0, right: 0, top: c.y + 4, bottom: 0 } })
|
|
3061
3075
|
] }),
|
|
3062
3076
|
/* @__PURE__ */ a(
|
|
3063
|
-
|
|
3077
|
+
Zn,
|
|
3064
3078
|
{
|
|
3065
|
-
onPreview:
|
|
3066
|
-
onCommit: (
|
|
3079
|
+
onPreview: m,
|
|
3080
|
+
onCommit: (u) => u === "split-row" ? s() : u === "split-column" ? r() : o()
|
|
3067
3081
|
}
|
|
3068
3082
|
)
|
|
3069
3083
|
] });
|
|
3070
3084
|
}
|
|
3071
|
-
function
|
|
3072
|
-
const e = g((l) => l.recentFiles), t = g((l) => l.openFile), s = g((l) => l.newBlank), r = g((l) => l.load), o = g((l) => l.pushToast), [i, d] =
|
|
3073
|
-
|
|
3085
|
+
function nt({ onClose: n }) {
|
|
3086
|
+
const e = g((l) => l.recentFiles), t = g((l) => l.openFile), s = g((l) => l.newBlank), r = g((l) => l.load), o = g((l) => l.pushToast), [i, d] = E([]), h = D(null);
|
|
3087
|
+
W(() => {
|
|
3074
3088
|
fetch("/api/files").then((l) => l.json()).then((l) => {
|
|
3075
3089
|
Array.isArray(l.files) && d(
|
|
3076
|
-
l.files.filter((
|
|
3090
|
+
l.files.filter((v) => v.path.startsWith("examples")).sort((v, f) => v.name.localeCompare(f.name))
|
|
3077
3091
|
);
|
|
3078
3092
|
}).catch(() => {
|
|
3079
3093
|
});
|
|
3080
3094
|
}, []);
|
|
3081
3095
|
const c = (l) => {
|
|
3082
|
-
var
|
|
3083
|
-
return ((
|
|
3084
|
-
},
|
|
3096
|
+
var v;
|
|
3097
|
+
return ((v = l.split("/").pop()) == null ? void 0 : v.replace(/\.riko$/, "")) ?? l;
|
|
3098
|
+
}, m = (l) => {
|
|
3085
3099
|
l(), n();
|
|
3086
|
-
},
|
|
3100
|
+
}, u = () => {
|
|
3087
3101
|
const l = e[0];
|
|
3088
|
-
l ?
|
|
3089
|
-
},
|
|
3102
|
+
l ? m(() => t(l)) : o("error", "No previous session to recover");
|
|
3103
|
+
}, b = (l) => {
|
|
3090
3104
|
var f;
|
|
3091
|
-
const
|
|
3092
|
-
l.target.value = "",
|
|
3105
|
+
const v = (f = l.target.files) == null ? void 0 : f[0];
|
|
3106
|
+
l.target.value = "", v && (r(v), n());
|
|
3093
3107
|
};
|
|
3094
3108
|
return /* @__PURE__ */ a("div", { className: "welcome-overlay", children: /* @__PURE__ */ p("div", { className: "welcome", children: [
|
|
3095
3109
|
/* @__PURE__ */ p("div", { className: "welcome-hero", children: [
|
|
3096
3110
|
/* @__PURE__ */ a(
|
|
3097
|
-
|
|
3111
|
+
de,
|
|
3098
3112
|
{
|
|
3099
3113
|
className: "welcome-logo",
|
|
3100
3114
|
title: "Logo slot — replace public/brand/logo.png (bitmap) or logo.svg (vector)"
|
|
@@ -3102,7 +3116,7 @@ function et({ onClose: n }) {
|
|
|
3102
3116
|
),
|
|
3103
3117
|
/* @__PURE__ */ p("span", { className: "welcome-version", children: [
|
|
3104
3118
|
"v",
|
|
3105
|
-
|
|
3119
|
+
te
|
|
3106
3120
|
] })
|
|
3107
3121
|
] }),
|
|
3108
3122
|
/* @__PURE__ */ p("div", { className: "welcome-header", children: [
|
|
@@ -3112,7 +3126,7 @@ function et({ onClose: n }) {
|
|
|
3112
3126
|
/* @__PURE__ */ p("div", { className: "welcome-body", children: [
|
|
3113
3127
|
/* @__PURE__ */ p("div", { className: "welcome-col", children: [
|
|
3114
3128
|
/* @__PURE__ */ a("div", { className: "welcome-col-title", children: "New File" }),
|
|
3115
|
-
/* @__PURE__ */ p("button", { className: "welcome-item welcome-blank", onClick: () =>
|
|
3129
|
+
/* @__PURE__ */ p("button", { className: "welcome-item welcome-blank", onClick: () => m(s), children: [
|
|
3116
3130
|
/* @__PURE__ */ a("span", { className: "welcome-item-icon", children: "⬚" }),
|
|
3117
3131
|
/* @__PURE__ */ a("span", { children: "General" }),
|
|
3118
3132
|
/* @__PURE__ */ a("span", { className: "welcome-item-hint", children: "blank graph" })
|
|
@@ -3120,7 +3134,7 @@ function et({ onClose: n }) {
|
|
|
3120
3134
|
/* @__PURE__ */ a("div", { className: "welcome-section-label", children: "Presets" }),
|
|
3121
3135
|
/* @__PURE__ */ p("div", { className: "welcome-scroll", children: [
|
|
3122
3136
|
i.length === 0 && /* @__PURE__ */ a("div", { className: "welcome-empty", children: "No presets (start the API server)" }),
|
|
3123
|
-
i.map((l) => /* @__PURE__ */ p("div", { className: "welcome-item", onClick: () =>
|
|
3137
|
+
i.map((l) => /* @__PURE__ */ p("div", { className: "welcome-item", onClick: () => m(() => t(l.path)), children: [
|
|
3124
3138
|
/* @__PURE__ */ a("span", { className: "welcome-item-icon", children: "▦" }),
|
|
3125
3139
|
/* @__PURE__ */ a("span", { children: l.name }),
|
|
3126
3140
|
/* @__PURE__ */ a("span", { className: "welcome-item-hint", children: l.imports.length ? `imports ${l.imports.length}` : "" })
|
|
@@ -3129,7 +3143,7 @@ function et({ onClose: n }) {
|
|
|
3129
3143
|
] }),
|
|
3130
3144
|
/* @__PURE__ */ p("div", { className: "welcome-col", children: [
|
|
3131
3145
|
/* @__PURE__ */ a("div", { className: "welcome-col-title", children: "Recent Files" }),
|
|
3132
|
-
/* @__PURE__ */ a("div", { className: "welcome-scroll", children: e.length === 0 ? /* @__PURE__ */ a("div", { className: "welcome-empty", children: "No recent files yet" }) : e.map((l) => /* @__PURE__ */ p("div", { className: "welcome-item", onClick: () =>
|
|
3146
|
+
/* @__PURE__ */ a("div", { className: "welcome-scroll", children: e.length === 0 ? /* @__PURE__ */ a("div", { className: "welcome-empty", children: "No recent files yet" }) : e.map((l) => /* @__PURE__ */ p("div", { className: "welcome-item", onClick: () => m(() => t(l)), children: [
|
|
3133
3147
|
/* @__PURE__ */ a("span", { className: "welcome-item-icon", children: "▤" }),
|
|
3134
3148
|
/* @__PURE__ */ a("span", { children: c(l) }),
|
|
3135
3149
|
/* @__PURE__ */ a("span", { className: "welcome-item-hint", children: l })
|
|
@@ -3137,7 +3151,7 @@ function et({ onClose: n }) {
|
|
|
3137
3151
|
] })
|
|
3138
3152
|
] }),
|
|
3139
3153
|
/* @__PURE__ */ p("div", { className: "welcome-footer", children: [
|
|
3140
|
-
/* @__PURE__ */ a("button", { className: "welcome-footer-btn", onClick:
|
|
3154
|
+
/* @__PURE__ */ a("button", { className: "welcome-footer-btn", onClick: u, children: "↩ Recover Last Session" }),
|
|
3141
3155
|
/* @__PURE__ */ a("button", { className: "welcome-footer-btn", onClick: () => {
|
|
3142
3156
|
var l;
|
|
3143
3157
|
return (l = h.current) == null ? void 0 : l.click();
|
|
@@ -3149,20 +3163,20 @@ function et({ onClose: n }) {
|
|
|
3149
3163
|
type: "file",
|
|
3150
3164
|
accept: ".riko,.ric,.json,application/json,application/octet-stream",
|
|
3151
3165
|
style: { display: "none" },
|
|
3152
|
-
onChange:
|
|
3166
|
+
onChange: b
|
|
3153
3167
|
}
|
|
3154
3168
|
)
|
|
3155
3169
|
] })
|
|
3156
3170
|
] }) });
|
|
3157
3171
|
}
|
|
3158
|
-
const
|
|
3172
|
+
const tt = [
|
|
3159
3173
|
{ id: "appearance", label: "Appearance", icon: "◧" },
|
|
3160
3174
|
{ id: "plugins", label: "Plugins", icon: "⬡" },
|
|
3161
3175
|
{ id: "about", label: "About", icon: "ⓘ" }
|
|
3162
3176
|
];
|
|
3163
|
-
function
|
|
3164
|
-
const e =
|
|
3165
|
-
|
|
3177
|
+
function at({ onClose: n }) {
|
|
3178
|
+
const e = V((m) => m.theme), t = V((m) => m.setTheme), s = V((m) => m.backgroundImage), r = V((m) => m.setBackgroundImage), [o, i] = E("appearance"), [d, h] = E(s);
|
|
3179
|
+
W(() => {
|
|
3166
3180
|
h(s);
|
|
3167
3181
|
}, [s]);
|
|
3168
3182
|
const c = [
|
|
@@ -3171,44 +3185,44 @@ function tt({ onClose: n }) {
|
|
|
3171
3185
|
{ value: "system", label: "System", hint: "Follow the OS color scheme" },
|
|
3172
3186
|
{ value: "glass", label: "Liquid Glass", hint: "Apple-style translucent glass" }
|
|
3173
3187
|
];
|
|
3174
|
-
return /* @__PURE__ */ a(
|
|
3175
|
-
/* @__PURE__ */ a("nav", { className: "prefs-sidebar", children:
|
|
3188
|
+
return /* @__PURE__ */ a(ce, { title: "Preferences", onClose: n, width: 720, zIndex: 1001, children: /* @__PURE__ */ p("div", { className: "prefs-layout", children: [
|
|
3189
|
+
/* @__PURE__ */ a("nav", { className: "prefs-sidebar", children: tt.map((m) => /* @__PURE__ */ p(
|
|
3176
3190
|
"button",
|
|
3177
3191
|
{
|
|
3178
|
-
className: `prefs-nav ${o ===
|
|
3179
|
-
onClick: () => i(
|
|
3192
|
+
className: `prefs-nav ${o === m.id ? "active" : ""}`,
|
|
3193
|
+
onClick: () => i(m.id),
|
|
3180
3194
|
children: [
|
|
3181
|
-
/* @__PURE__ */ a("span", { className: "prefs-nav-icon", children:
|
|
3182
|
-
|
|
3195
|
+
/* @__PURE__ */ a("span", { className: "prefs-nav-icon", children: m.icon }),
|
|
3196
|
+
m.label
|
|
3183
3197
|
]
|
|
3184
3198
|
},
|
|
3185
|
-
|
|
3199
|
+
m.id
|
|
3186
3200
|
)) }),
|
|
3187
3201
|
/* @__PURE__ */ p("div", { className: "prefs-content", children: [
|
|
3188
|
-
o === "appearance" && /* @__PURE__ */ p(
|
|
3202
|
+
o === "appearance" && /* @__PURE__ */ p(K, { children: [
|
|
3189
3203
|
/* @__PURE__ */ p("section", { className: "prefs-section", children: [
|
|
3190
3204
|
/* @__PURE__ */ a("h3", { children: "Theme" }),
|
|
3191
3205
|
/* @__PURE__ */ a("p", { className: "prefs-desc", children: "Choose how the interface looks." }),
|
|
3192
|
-
/* @__PURE__ */ a("div", { className: "prefs-theme-list", children: c.map((
|
|
3206
|
+
/* @__PURE__ */ a("div", { className: "prefs-theme-list", children: c.map((m) => /* @__PURE__ */ p(
|
|
3193
3207
|
"label",
|
|
3194
3208
|
{
|
|
3195
|
-
className: `prefs-theme ${e ===
|
|
3209
|
+
className: `prefs-theme ${e === m.value ? "active" : ""}`,
|
|
3196
3210
|
children: [
|
|
3197
3211
|
/* @__PURE__ */ a(
|
|
3198
3212
|
"input",
|
|
3199
3213
|
{
|
|
3200
3214
|
type: "radio",
|
|
3201
3215
|
name: "theme",
|
|
3202
|
-
checked: e ===
|
|
3203
|
-
onChange: () => t(
|
|
3216
|
+
checked: e === m.value,
|
|
3217
|
+
onChange: () => t(m.value)
|
|
3204
3218
|
}
|
|
3205
3219
|
),
|
|
3206
|
-
/* @__PURE__ */ a("span", { className: "prefs-theme-swatch", "data-theme":
|
|
3207
|
-
/* @__PURE__ */ a("span", { className: "prefs-theme-label", children:
|
|
3208
|
-
/* @__PURE__ */ a("span", { className: "prefs-theme-hint", children:
|
|
3220
|
+
/* @__PURE__ */ a("span", { className: "prefs-theme-swatch", "data-theme": m.value }),
|
|
3221
|
+
/* @__PURE__ */ a("span", { className: "prefs-theme-label", children: m.label }),
|
|
3222
|
+
/* @__PURE__ */ a("span", { className: "prefs-theme-hint", children: m.hint })
|
|
3209
3223
|
]
|
|
3210
3224
|
},
|
|
3211
|
-
|
|
3225
|
+
m.value
|
|
3212
3226
|
)) })
|
|
3213
3227
|
] }),
|
|
3214
3228
|
/* @__PURE__ */ p("section", { className: "prefs-section", children: [
|
|
@@ -3221,9 +3235,9 @@ function tt({ onClose: n }) {
|
|
|
3221
3235
|
className: "prefs-input",
|
|
3222
3236
|
placeholder: "Image URL (https://…)",
|
|
3223
3237
|
value: d,
|
|
3224
|
-
onChange: (
|
|
3225
|
-
onKeyDown: (
|
|
3226
|
-
|
|
3238
|
+
onChange: (m) => h(m.target.value),
|
|
3239
|
+
onKeyDown: (m) => {
|
|
3240
|
+
m.key === "Enter" && r(d.trim());
|
|
3227
3241
|
}
|
|
3228
3242
|
}
|
|
3229
3243
|
),
|
|
@@ -3241,13 +3255,13 @@ function tt({ onClose: n }) {
|
|
|
3241
3255
|
] })
|
|
3242
3256
|
] })
|
|
3243
3257
|
] }),
|
|
3244
|
-
o === "plugins" && /* @__PURE__ */ a("section", { className: "prefs-section", style: { marginBottom: 0 }, children: /* @__PURE__ */ a(
|
|
3258
|
+
o === "plugins" && /* @__PURE__ */ a("section", { className: "prefs-section", style: { marginBottom: 0 }, children: /* @__PURE__ */ a(Je, {}) }),
|
|
3245
3259
|
o === "about" && /* @__PURE__ */ a("section", { className: "prefs-section", children: /* @__PURE__ */ p("div", { className: "prefs-about", children: [
|
|
3246
|
-
/* @__PURE__ */ a(
|
|
3260
|
+
/* @__PURE__ */ a(de, { className: "prefs-about-logo" }),
|
|
3247
3261
|
/* @__PURE__ */ a("h3", { children: "Entropia Riko" }),
|
|
3248
3262
|
/* @__PURE__ */ p("div", { className: "prefs-about-version", children: [
|
|
3249
3263
|
"Version ",
|
|
3250
|
-
|
|
3264
|
+
te
|
|
3251
3265
|
] }),
|
|
3252
3266
|
/* @__PURE__ */ a("p", { className: "prefs-desc", children: "A professional node-graph deep-learning editor. Build models visually, run inference, train with live loss curves, and export to clean PyTorch or TensorFlow/Keras code." }),
|
|
3253
3267
|
/* @__PURE__ */ p("div", { className: "prefs-about-meta", children: [
|
|
@@ -3258,14 +3272,14 @@ function tt({ onClose: n }) {
|
|
|
3258
3272
|
] })
|
|
3259
3273
|
] }) });
|
|
3260
3274
|
}
|
|
3261
|
-
function
|
|
3262
|
-
return /* @__PURE__ */ p(
|
|
3275
|
+
function st({ onClose: n }) {
|
|
3276
|
+
return /* @__PURE__ */ p(ce, { title: "About Entropia Riko", onClose: n, width: 440, children: [
|
|
3263
3277
|
/* @__PURE__ */ p("div", { className: "about-hero", children: [
|
|
3264
|
-
/* @__PURE__ */ a(
|
|
3278
|
+
/* @__PURE__ */ a(de, { className: "about-logo" }),
|
|
3265
3279
|
/* @__PURE__ */ a("h2", { children: "Entropia Riko" }),
|
|
3266
3280
|
/* @__PURE__ */ p("div", { className: "about-version", children: [
|
|
3267
3281
|
"Version ",
|
|
3268
|
-
|
|
3282
|
+
te
|
|
3269
3283
|
] })
|
|
3270
3284
|
] }),
|
|
3271
3285
|
/* @__PURE__ */ p("div", { className: "about-body", children: [
|
|
@@ -3296,8 +3310,8 @@ function at({ onClose: n }) {
|
|
|
3296
3310
|
] })
|
|
3297
3311
|
] });
|
|
3298
3312
|
}
|
|
3299
|
-
function
|
|
3300
|
-
const [e, t] =
|
|
3313
|
+
function rt({ onClose: n }) {
|
|
3314
|
+
const [e, t] = E(""), [s, r] = E(!1), o = g((d) => d.pushToast), i = async () => {
|
|
3301
3315
|
const d = e.trim();
|
|
3302
3316
|
if (d) {
|
|
3303
3317
|
r(!0);
|
|
@@ -3315,7 +3329,7 @@ function st({ onClose: n }) {
|
|
|
3315
3329
|
}
|
|
3316
3330
|
}
|
|
3317
3331
|
};
|
|
3318
|
-
return /* @__PURE__ */ a(
|
|
3332
|
+
return /* @__PURE__ */ a(ce, { title: "Import Working Folder", onClose: n, width: 480, children: /* @__PURE__ */ p("div", { className: "import-folder-body", children: [
|
|
3319
3333
|
/* @__PURE__ */ p("p", { className: "prefs-desc", children: [
|
|
3320
3334
|
"Point the file manager at a local folder. A ",
|
|
3321
3335
|
/* @__PURE__ */ a("code", { children: ".riko" }),
|
|
@@ -3340,149 +3354,157 @@ function st({ onClose: n }) {
|
|
|
3340
3354
|
] })
|
|
3341
3355
|
] }) });
|
|
3342
3356
|
}
|
|
3343
|
-
function
|
|
3357
|
+
function ot() {
|
|
3344
3358
|
const n = g((t) => t.toasts), e = g((t) => t.dismissToast);
|
|
3345
3359
|
return /* @__PURE__ */ a("div", { className: "toast-stack", children: n.map((t) => /* @__PURE__ */ p("div", { className: `toast ${t.kind}`, children: [
|
|
3346
3360
|
/* @__PURE__ */ a("span", { className: "toast-msg", children: t.message }),
|
|
3347
3361
|
/* @__PURE__ */ a("button", { className: "toast-close", title: "Dismiss", onClick: () => e(t.id), children: "✕" })
|
|
3348
3362
|
] }, t.id)) });
|
|
3349
3363
|
}
|
|
3350
|
-
let
|
|
3351
|
-
const
|
|
3364
|
+
let it = 0;
|
|
3365
|
+
const he = () => `ws_${++it}`, Re = [
|
|
3366
|
+
{ preset: "layout", graph: "examples/models/mnist.riko" },
|
|
3352
3367
|
{ preset: "training", graph: "examples/models/mnist_cnn.riko" },
|
|
3353
3368
|
{ preset: "inference", graph: "examples/models/mnist_infer.riko" },
|
|
3354
|
-
{ preset: "code", graph: "examples/models/mnist.riko" }
|
|
3355
|
-
{ preset: "layout", graph: "examples/models/mnist.riko" }
|
|
3369
|
+
{ preset: "code", graph: "examples/models/mnist.riko" }
|
|
3356
3370
|
];
|
|
3357
|
-
function
|
|
3358
|
-
var
|
|
3359
|
-
const n = g((
|
|
3360
|
-
|
|
3361
|
-
(async () => (await n(), await e(
|
|
3371
|
+
function ht() {
|
|
3372
|
+
var P;
|
|
3373
|
+
const n = g((N) => N.loadNodeDefs), e = g((N) => N.openFile), t = g((N) => N.welcomeOpen), s = g((N) => N.setWelcomeOpen), r = g((N) => N.prefsOpen), o = g((N) => N.setPrefsOpen), i = g((N) => N.aboutOpen), d = g((N) => N.setAboutOpen), h = g((N) => N.importFolderOpen), c = g((N) => N.setImportFolderOpen);
|
|
3374
|
+
W(() => {
|
|
3375
|
+
(async () => (await n(), await e(Re[0].graph)))();
|
|
3362
3376
|
}, [n, e]);
|
|
3363
|
-
const [
|
|
3364
|
-
() =>
|
|
3365
|
-
const
|
|
3366
|
-
return { id:
|
|
3377
|
+
const [m, u] = E(
|
|
3378
|
+
() => Re.map((N) => {
|
|
3379
|
+
const I = ge.find((S) => S.id === N.preset);
|
|
3380
|
+
return { id: he(), name: I.label, root: I.build(), graph: N.graph };
|
|
3367
3381
|
})
|
|
3368
|
-
), [
|
|
3369
|
-
|
|
3370
|
-
}, $ = (
|
|
3371
|
-
l(
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
},
|
|
3375
|
-
const
|
|
3376
|
-
if (!
|
|
3377
|
-
const
|
|
3378
|
-
|
|
3379
|
-
}, _ = (
|
|
3380
|
-
|
|
3381
|
-
if (
|
|
3382
|
-
const
|
|
3383
|
-
return
|
|
3382
|
+
), [b, l] = E(null), [v, f] = E(null), C = b ?? ((P = m[0]) == null ? void 0 : P.id) ?? "", F = (m.find((N) => N.id === C) ?? m[0]).root, x = ke(F) > 1, O = (N) => {
|
|
3383
|
+
u((I) => I.map((S) => S.id === C ? { ...S, root: N(S.root) } : S));
|
|
3384
|
+
}, $ = (N) => {
|
|
3385
|
+
l(N);
|
|
3386
|
+
const I = m.find((S) => S.id === N);
|
|
3387
|
+
I != null && I.graph && e(I.graph);
|
|
3388
|
+
}, w = (N) => {
|
|
3389
|
+
const I = ge.find((j) => j.id === N);
|
|
3390
|
+
if (!I) return;
|
|
3391
|
+
const S = { id: he(), name: I.label, root: I.build() };
|
|
3392
|
+
u((j) => [...j, S]), l(S.id);
|
|
3393
|
+
}, _ = (N) => {
|
|
3394
|
+
u((I) => {
|
|
3395
|
+
if (I.length <= 1) return I;
|
|
3396
|
+
const S = I.filter((j) => j.id !== N);
|
|
3397
|
+
return N === C && l(S[0].id), S;
|
|
3398
|
+
});
|
|
3399
|
+
}, k = (N, I) => {
|
|
3400
|
+
u((S) => S.map((j) => j.id === N ? { ...j, name: I } : j));
|
|
3401
|
+
}, T = (N) => {
|
|
3402
|
+
const I = m.find((G) => G.id === N);
|
|
3403
|
+
if (!I) return;
|
|
3404
|
+
const S = {
|
|
3405
|
+
id: he(),
|
|
3406
|
+
name: `${I.name} copy`,
|
|
3407
|
+
root: JSON.parse(JSON.stringify(I.root))
|
|
3408
|
+
}, j = m.findIndex((G) => G.id === N), A = [...m];
|
|
3409
|
+
A.splice(j + 1, 0, S), u(A), l(S.id);
|
|
3410
|
+
}, R = (N, I) => {
|
|
3411
|
+
u((S) => {
|
|
3412
|
+
const j = S.findIndex((Z) => Z.id === N), A = j + I;
|
|
3413
|
+
if (j < 0 || A < 0 || A >= S.length) return S;
|
|
3414
|
+
const G = [...S], [pe] = G.splice(j, 1);
|
|
3415
|
+
return G.splice(A, 0, pe), G;
|
|
3384
3416
|
});
|
|
3385
|
-
},
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
id: pe(),
|
|
3392
|
-
name: `${E.name} copy`,
|
|
3393
|
-
root: JSON.parse(JSON.stringify(E.root))
|
|
3394
|
-
}, R = u.findIndex((z) => z.id === w), U = [...u];
|
|
3395
|
-
U.splice(R + 1, 0, T), m(U), l(T.id);
|
|
3396
|
-
}, le = (w, E) => {
|
|
3397
|
-
m((T) => {
|
|
3398
|
-
const R = T.findIndex((Ve) => Ve.id === w), U = R + E;
|
|
3399
|
-
if (R < 0 || U < 0 || U >= T.length) return T;
|
|
3400
|
-
const z = [...T], [ze] = z.splice(R, 1);
|
|
3401
|
-
return z.splice(U, 0, ze), z;
|
|
3417
|
+
}, B = (N, I) => {
|
|
3418
|
+
u((S) => {
|
|
3419
|
+
const j = S.findIndex((Z) => Z.id === N), A = S.findIndex((Z) => Z.id === I);
|
|
3420
|
+
if (j < 0 || A < 0 || j === A) return S;
|
|
3421
|
+
const G = [...S], [pe] = G.splice(j, 1);
|
|
3422
|
+
return G.splice(A, 0, pe), G;
|
|
3402
3423
|
});
|
|
3403
|
-
},
|
|
3404
|
-
if (
|
|
3405
|
-
const
|
|
3424
|
+
}, z = (v == null ? void 0 : v.mode) === "merge" ? ve(F, v.leafId) : null, X = (N) => {
|
|
3425
|
+
if (N.kind === "leaf") {
|
|
3426
|
+
const I = v && v.leafId === N.id && v.mode !== "merge" ? v : null;
|
|
3406
3427
|
return /* @__PURE__ */ a(
|
|
3407
|
-
|
|
3428
|
+
et,
|
|
3408
3429
|
{
|
|
3409
|
-
id:
|
|
3410
|
-
type:
|
|
3411
|
-
canMerge:
|
|
3412
|
-
mergeTarget:
|
|
3413
|
-
preview:
|
|
3414
|
-
onType: (
|
|
3415
|
-
onSplitRow: () =>
|
|
3416
|
-
onSplitColumn: () =>
|
|
3417
|
-
onMerge: () =>
|
|
3418
|
-
onClose: () =>
|
|
3419
|
-
onPreview: (
|
|
3430
|
+
id: N.id,
|
|
3431
|
+
type: N.type,
|
|
3432
|
+
canMerge: x,
|
|
3433
|
+
mergeTarget: N.id === z,
|
|
3434
|
+
preview: I,
|
|
3435
|
+
onType: (S) => O((j) => Ne(j, N.id, S)),
|
|
3436
|
+
onSplitRow: () => O((S) => oe(S, N.id, "row")),
|
|
3437
|
+
onSplitColumn: () => O((S) => oe(S, N.id, "column")),
|
|
3438
|
+
onMerge: () => O((S) => be(S, N.id)),
|
|
3439
|
+
onClose: () => O((S) => ye(S, N.id)),
|
|
3440
|
+
onPreview: (S) => f(S ? { ...S, leafId: N.id } : null)
|
|
3420
3441
|
},
|
|
3421
|
-
|
|
3442
|
+
N.id
|
|
3422
3443
|
);
|
|
3423
3444
|
}
|
|
3424
|
-
return
|
|
3445
|
+
return N.direction === "row" ? /* @__PURE__ */ p(
|
|
3425
3446
|
"div",
|
|
3426
3447
|
{
|
|
3427
|
-
className:
|
|
3448
|
+
className: N.id === z ? "merge-target" : void 0,
|
|
3428
3449
|
style: { display: "flex", flex: "1 1 0", minWidth: 0, minHeight: 0 },
|
|
3429
3450
|
children: [
|
|
3430
|
-
/* @__PURE__ */ a("div", { style: { flex:
|
|
3451
|
+
/* @__PURE__ */ a("div", { style: { flex: N.ratio, minWidth: 0, minHeight: 0, display: "flex" }, children: X(N.first) }),
|
|
3431
3452
|
/* @__PURE__ */ a(
|
|
3432
|
-
|
|
3453
|
+
kn,
|
|
3433
3454
|
{
|
|
3434
|
-
onDrag: (
|
|
3455
|
+
onDrag: (I, S) => O((j) => ie(j, N.id, I / S))
|
|
3435
3456
|
}
|
|
3436
3457
|
),
|
|
3437
|
-
/* @__PURE__ */ a("div", { style: { flex: 1 -
|
|
3458
|
+
/* @__PURE__ */ a("div", { style: { flex: 1 - N.ratio, minWidth: 0, minHeight: 0, display: "flex" }, children: X(N.second) })
|
|
3438
3459
|
]
|
|
3439
3460
|
},
|
|
3440
|
-
|
|
3461
|
+
N.id
|
|
3441
3462
|
) : /* @__PURE__ */ p(
|
|
3442
3463
|
"div",
|
|
3443
3464
|
{
|
|
3444
|
-
className:
|
|
3465
|
+
className: N.id === z ? "merge-target" : void 0,
|
|
3445
3466
|
style: { display: "flex", flexDirection: "column", flex: "1 1 0", minWidth: 0, minHeight: 0 },
|
|
3446
3467
|
children: [
|
|
3447
|
-
/* @__PURE__ */ a("div", { style: { flex:
|
|
3468
|
+
/* @__PURE__ */ a("div", { style: { flex: N.ratio, minWidth: 0, minHeight: 0, display: "flex" }, children: X(N.first) }),
|
|
3448
3469
|
/* @__PURE__ */ a(
|
|
3449
|
-
|
|
3470
|
+
wn,
|
|
3450
3471
|
{
|
|
3451
|
-
onDrag: (
|
|
3472
|
+
onDrag: (I, S) => O((j) => ie(j, N.id, I / S))
|
|
3452
3473
|
}
|
|
3453
3474
|
),
|
|
3454
|
-
/* @__PURE__ */ a("div", { style: { flex: 1 -
|
|
3475
|
+
/* @__PURE__ */ a("div", { style: { flex: 1 - N.ratio, minWidth: 0, minHeight: 0, display: "flex" }, children: X(N.second) })
|
|
3455
3476
|
]
|
|
3456
3477
|
},
|
|
3457
|
-
|
|
3478
|
+
N.id
|
|
3458
3479
|
);
|
|
3459
3480
|
};
|
|
3460
3481
|
return /* @__PURE__ */ p("div", { className: "app", children: [
|
|
3461
|
-
/* @__PURE__ */ a(
|
|
3482
|
+
/* @__PURE__ */ a(Nn, {}),
|
|
3462
3483
|
/* @__PURE__ */ a(
|
|
3463
|
-
|
|
3484
|
+
vn,
|
|
3464
3485
|
{
|
|
3465
|
-
workspaces:
|
|
3466
|
-
activeWorkspaceId:
|
|
3486
|
+
workspaces: m,
|
|
3487
|
+
activeWorkspaceId: C,
|
|
3467
3488
|
onSwitchWorkspace: $,
|
|
3468
|
-
onAddWorkspace:
|
|
3489
|
+
onAddWorkspace: w,
|
|
3469
3490
|
onRemoveWorkspace: _,
|
|
3470
3491
|
onRenameWorkspace: k,
|
|
3471
|
-
onDuplicateWorkspace:
|
|
3472
|
-
onMoveWorkspace:
|
|
3492
|
+
onDuplicateWorkspace: T,
|
|
3493
|
+
onMoveWorkspace: R,
|
|
3494
|
+
onReorderWorkspace: B
|
|
3473
3495
|
}
|
|
3474
3496
|
),
|
|
3475
|
-
/* @__PURE__ */ a("div", { className: "app-body", children:
|
|
3476
|
-
t && /* @__PURE__ */ a(
|
|
3477
|
-
r && /* @__PURE__ */ a(
|
|
3478
|
-
i && /* @__PURE__ */ a(
|
|
3479
|
-
h && /* @__PURE__ */ a(
|
|
3480
|
-
/* @__PURE__ */ a(
|
|
3481
|
-
/* @__PURE__ */ a(
|
|
3497
|
+
/* @__PURE__ */ a("div", { className: "app-body", children: X(F) }),
|
|
3498
|
+
t && /* @__PURE__ */ a(nt, { onClose: () => s(!1) }),
|
|
3499
|
+
r && /* @__PURE__ */ a(at, { onClose: () => o(!1) }),
|
|
3500
|
+
i && /* @__PURE__ */ a(st, { onClose: () => d(!1) }),
|
|
3501
|
+
h && /* @__PURE__ */ a(rt, { onClose: () => c(!1) }),
|
|
3502
|
+
/* @__PURE__ */ a(bn, {}),
|
|
3503
|
+
/* @__PURE__ */ a(ot, {})
|
|
3482
3504
|
] });
|
|
3483
3505
|
}
|
|
3484
|
-
function
|
|
3485
|
-
const n = g((i) => i.status), e = g((i) => i.run), t = g((i) => i.save), s = g((i) => i.load), r = g((i) => i.exportPython), o =
|
|
3506
|
+
function ft() {
|
|
3507
|
+
const n = g((i) => i.status), e = g((i) => i.run), t = g((i) => i.save), s = g((i) => i.load), r = g((i) => i.exportPython), o = D(null);
|
|
3486
3508
|
return /* @__PURE__ */ p("div", { className: "toolbar", children: [
|
|
3487
3509
|
/* @__PURE__ */ a("h1", { children: "Entropia Riko" }),
|
|
3488
3510
|
/* @__PURE__ */ a(
|
|
@@ -3547,40 +3569,40 @@ function ht() {
|
|
|
3547
3569
|
/* @__PURE__ */ a("span", { className: `status-badge ${n}`, children: n })
|
|
3548
3570
|
] });
|
|
3549
3571
|
}
|
|
3550
|
-
function
|
|
3572
|
+
function qe(n, e = []) {
|
|
3551
3573
|
for (const t of n)
|
|
3552
|
-
t.type === "file" ? e.push(t.path) : t.children &&
|
|
3574
|
+
t.type === "file" ? e.push(t.path) : t.children && qe(t.children, e);
|
|
3553
3575
|
return e;
|
|
3554
3576
|
}
|
|
3555
|
-
function
|
|
3556
|
-
const e = g((y) => y.pushToast), t = g((y) => y.bumpProject), [s, r] =
|
|
3557
|
-
const
|
|
3558
|
-
|
|
3559
|
-
},
|
|
3577
|
+
function gt({ onClose: n }) {
|
|
3578
|
+
const e = g((y) => y.pushToast), t = g((y) => y.bumpProject), [s, r] = E("~"), [o, i] = E([]), [d, h] = E(""), [c, m] = E([]), [u, b] = E(""), l = async (y) => {
|
|
3579
|
+
const x = await (await fetch(`/api/fs/list?path=${encodeURIComponent(y)}`)).json();
|
|
3580
|
+
x.status === "success" ? (r(x.path), h(x.parent), i(x.entries ?? [])) : e("error", x.error ?? "list failed");
|
|
3581
|
+
}, v = async () => {
|
|
3560
3582
|
try {
|
|
3561
|
-
const
|
|
3562
|
-
|
|
3583
|
+
const F = await (await fetch("/api/project/tree")).json(), x = qe(Array.isArray(F.tree) ? F.tree : []);
|
|
3584
|
+
m(x), x.length && !u && b(x[0]);
|
|
3563
3585
|
} catch {
|
|
3564
3586
|
}
|
|
3565
3587
|
};
|
|
3566
|
-
|
|
3567
|
-
l("~"),
|
|
3588
|
+
W(() => {
|
|
3589
|
+
l("~"), v();
|
|
3568
3590
|
}, []);
|
|
3569
3591
|
const f = async (y) => {
|
|
3570
|
-
const
|
|
3592
|
+
const x = await (await fetch("/api/fs/import", {
|
|
3571
3593
|
method: "POST",
|
|
3572
3594
|
headers: { "Content-Type": "application/json" },
|
|
3573
3595
|
body: JSON.stringify({ src: y.path })
|
|
3574
3596
|
})).json();
|
|
3575
|
-
|
|
3576
|
-
},
|
|
3577
|
-
if (!
|
|
3578
|
-
const
|
|
3597
|
+
x.status === "success" ? (e("success", `Imported ${y.name}`), t(), v()) : e("error", x.error ?? "import failed");
|
|
3598
|
+
}, C = async () => {
|
|
3599
|
+
if (!u) return;
|
|
3600
|
+
const F = await (await fetch("/api/fs/export", {
|
|
3579
3601
|
method: "POST",
|
|
3580
3602
|
headers: { "Content-Type": "application/json" },
|
|
3581
|
-
body: JSON.stringify({ src:
|
|
3603
|
+
body: JSON.stringify({ src: u, dest: s })
|
|
3582
3604
|
})).json();
|
|
3583
|
-
|
|
3605
|
+
F.status === "success" ? e("success", `Exported to ${F.path}`) : e("error", F.error ?? "export failed");
|
|
3584
3606
|
};
|
|
3585
3607
|
return /* @__PURE__ */ a("div", { className: "prefs-overlay", onClick: n, children: /* @__PURE__ */ p("div", { className: "explorer", onClick: (y) => y.stopPropagation(), children: [
|
|
3586
3608
|
/* @__PURE__ */ p("div", { className: "prefs-header", children: [
|
|
@@ -3622,19 +3644,19 @@ function ft({ onClose: n }) {
|
|
|
3622
3644
|
] }),
|
|
3623
3645
|
/* @__PURE__ */ p("div", { className: "explorer-export", children: [
|
|
3624
3646
|
/* @__PURE__ */ a("div", { className: "prefs-desc", style: { margin: 0 }, children: "Export a working-folder file to the current folder:" }),
|
|
3625
|
-
/* @__PURE__ */ a("select", { className: "prefs-input", value:
|
|
3626
|
-
/* @__PURE__ */ a("button", { className: "btn", disabled: !
|
|
3647
|
+
/* @__PURE__ */ a("select", { className: "prefs-input", value: u, onChange: (y) => b(y.target.value), children: c.map((y) => /* @__PURE__ */ a("option", { value: y, children: y }, y)) }),
|
|
3648
|
+
/* @__PURE__ */ a("button", { className: "btn", disabled: !u, onClick: C, children: "Export here" })
|
|
3627
3649
|
] })
|
|
3628
3650
|
] })
|
|
3629
3651
|
] }) });
|
|
3630
3652
|
}
|
|
3631
|
-
function
|
|
3653
|
+
function yt({
|
|
3632
3654
|
path: n,
|
|
3633
3655
|
name: e,
|
|
3634
3656
|
onClose: t
|
|
3635
3657
|
}) {
|
|
3636
|
-
const [s, r] =
|
|
3637
|
-
return
|
|
3658
|
+
const [s, r] = E(""), [o, i] = E(null), d = g((c) => c.pushToast);
|
|
3659
|
+
return W(() => {
|
|
3638
3660
|
fetch("/api/project/code", {
|
|
3639
3661
|
method: "POST",
|
|
3640
3662
|
headers: { "Content-Type": "application/json" },
|
|
@@ -3662,57 +3684,57 @@ function gt({
|
|
|
3662
3684
|
] }) });
|
|
3663
3685
|
}
|
|
3664
3686
|
export {
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3687
|
+
te as APP_VERSION,
|
|
3688
|
+
st as AboutPanel,
|
|
3689
|
+
ht as App,
|
|
3690
|
+
de as BrandLogo,
|
|
3691
|
+
zn as CodeEditor,
|
|
3692
|
+
yt as CodePreview,
|
|
3693
|
+
Fn as ContextMenu,
|
|
3694
|
+
Kn as DocsPanel,
|
|
3695
|
+
gt as FileExplorer,
|
|
3696
|
+
Sn as FileManager,
|
|
3697
|
+
bn as FilePickerHost,
|
|
3698
|
+
ce as FloatingWindow,
|
|
3677
3699
|
On as GraphCanvas,
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3700
|
+
wn as HSplitter,
|
|
3701
|
+
Vn as HandwritingPad,
|
|
3702
|
+
rt as ImportFolderPanel,
|
|
3703
|
+
Mn as LossPanel,
|
|
3704
|
+
vn as MenuBar,
|
|
3705
|
+
Pn as NodeCard,
|
|
3706
|
+
_n as NodeLibrary,
|
|
3707
|
+
ze as PANEL_TYPES,
|
|
3708
|
+
et as PanelSlot,
|
|
3709
|
+
Je as PluginPanel,
|
|
3710
|
+
re as PopupMenu,
|
|
3711
|
+
at as PreferencesPanel,
|
|
3712
|
+
Jn as ProjectPanel,
|
|
3713
|
+
jn as SideInspector,
|
|
3714
|
+
An as StatusPanel,
|
|
3715
|
+
Nn as Titlebar,
|
|
3716
|
+
ot as ToastStack,
|
|
3717
|
+
ft as Toolbar,
|
|
3718
|
+
kn as VSplitter,
|
|
3719
|
+
ge as WORKSPACE_PRESETS,
|
|
3720
|
+
nt as WelcomePanel,
|
|
3721
|
+
gn as WorkspaceTabs,
|
|
3722
|
+
We as applyBackground,
|
|
3723
|
+
Me as applyTheme,
|
|
3724
|
+
ye as closeLeaf,
|
|
3725
|
+
ke as countLeaves,
|
|
3726
|
+
hn as defaultLayout,
|
|
3727
|
+
L as leaf,
|
|
3728
|
+
pn as loadTheme,
|
|
3729
|
+
be as mergeLeaf,
|
|
3730
|
+
Q as openFilePicker,
|
|
3731
|
+
Yn as panelLabel,
|
|
3732
|
+
mt as registerPanelContent,
|
|
3733
|
+
Xn as renderPanelContent,
|
|
3734
|
+
ie as resizeSplit,
|
|
3735
|
+
Ne as setLeafType,
|
|
3736
|
+
ve as siblingNodeId,
|
|
3737
|
+
M as split,
|
|
3738
|
+
oe as splitLeaf,
|
|
3717
3739
|
g as useGraphStore
|
|
3718
3740
|
};
|