entropia-template-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/App.d.ts +2 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/areas.d.ts +54 -0
- package/dist/areas.d.ts.map +1 -0
- package/dist/components/AboutPanel.d.ts +4 -0
- package/dist/components/AboutPanel.d.ts.map +1 -0
- package/dist/components/BrandLogo.d.ts +6 -0
- package/dist/components/BrandLogo.d.ts.map +1 -0
- package/dist/components/CodeEditor.d.ts +2 -0
- package/dist/components/CodeEditor.d.ts.map +1 -0
- package/dist/components/CodePreview.d.ts +6 -0
- package/dist/components/CodePreview.d.ts.map +1 -0
- package/dist/components/ContextMenu.d.ts +10 -0
- package/dist/components/ContextMenu.d.ts.map +1 -0
- package/dist/components/DocsPanel.d.ts +2 -0
- package/dist/components/DocsPanel.d.ts.map +1 -0
- package/dist/components/FileExplorer.d.ts +4 -0
- package/dist/components/FileExplorer.d.ts.map +1 -0
- package/dist/components/FileManager.d.ts +2 -0
- package/dist/components/FileManager.d.ts.map +1 -0
- package/dist/components/FilePicker.d.ts +5 -0
- package/dist/components/FilePicker.d.ts.map +1 -0
- package/dist/components/FloatingWindow.d.ts +10 -0
- package/dist/components/FloatingWindow.d.ts.map +1 -0
- package/dist/components/GraphCanvas.d.ts +2 -0
- package/dist/components/GraphCanvas.d.ts.map +1 -0
- package/dist/components/HandwritingPad.d.ts +2 -0
- package/dist/components/HandwritingPad.d.ts.map +1 -0
- package/dist/components/ImportFolderPanel.d.ts +4 -0
- package/dist/components/ImportFolderPanel.d.ts.map +1 -0
- package/dist/components/LossPanel.d.ts +2 -0
- package/dist/components/LossPanel.d.ts.map +1 -0
- package/dist/components/MenuBar.d.ts +12 -0
- package/dist/components/MenuBar.d.ts.map +1 -0
- package/dist/components/NodeCard.d.ts +4 -0
- package/dist/components/NodeCard.d.ts.map +1 -0
- package/dist/components/NodeLibrary.d.ts +2 -0
- package/dist/components/NodeLibrary.d.ts.map +1 -0
- package/dist/components/Panel.d.ts +38 -0
- package/dist/components/Panel.d.ts.map +1 -0
- package/dist/components/PluginPanel.d.ts +2 -0
- package/dist/components/PluginPanel.d.ts.map +1 -0
- package/dist/components/PopupMenu.d.ts +19 -0
- package/dist/components/PopupMenu.d.ts.map +1 -0
- package/dist/components/PreferencesPanel.d.ts +4 -0
- package/dist/components/PreferencesPanel.d.ts.map +1 -0
- package/dist/components/ProjectPanel.d.ts +2 -0
- package/dist/components/ProjectPanel.d.ts.map +1 -0
- package/dist/components/SideInspector.d.ts +7 -0
- package/dist/components/SideInspector.d.ts.map +1 -0
- package/dist/components/Splitter.d.ts +7 -0
- package/dist/components/Splitter.d.ts.map +1 -0
- package/dist/components/StatusPanel.d.ts +6 -0
- package/dist/components/StatusPanel.d.ts.map +1 -0
- package/dist/components/Titlebar.d.ts +2 -0
- package/dist/components/Titlebar.d.ts.map +1 -0
- package/dist/components/ToastStack.d.ts +2 -0
- package/dist/components/ToastStack.d.ts.map +1 -0
- package/dist/components/Toolbar.d.ts +2 -0
- package/dist/components/Toolbar.d.ts.map +1 -0
- package/dist/components/WelcomePanel.d.ts +4 -0
- package/dist/components/WelcomePanel.d.ts.map +1 -0
- package/dist/components/WorkspaceTabs.d.ts +12 -0
- package/dist/components/WorkspaceTabs.d.ts.map +1 -0
- package/dist/docs/Markdown.d.ts +2 -0
- package/dist/docs/Markdown.d.ts.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3685 -0
- package/dist/nodes/nodeDefinitions.d.ts +29 -0
- package/dist/nodes/nodeDefinitions.d.ts.map +1 -0
- package/dist/store/graphStore.d.ts +166 -0
- package/dist/store/graphStore.d.ts.map +1 -0
- package/dist/style.css +1 -0
- package/dist/subgraphActions.d.ts +4 -0
- package/dist/subgraphActions.d.ts.map +1 -0
- package/dist/theme.d.ts +11 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3685 @@
|
|
|
1
|
+
import { jsxs as p, Fragment as B, jsx as a } from "react/jsx-runtime";
|
|
2
|
+
import { useState as P, useMemo as z, useRef as $, useEffect as D, Fragment as Fe } from "react";
|
|
3
|
+
import { create as Ee } from "zustand";
|
|
4
|
+
import { addEdge as ze, applyEdgeChanges as Ve, applyNodeChanges as qe, Handle as ve, Position as Ne, ReactFlowProvider as Ye, useReactFlow as Xe, ReactFlow as Qe, Background as Ze, ViewportPortal as en } from "@xyflow/react";
|
|
5
|
+
import { createPortal as Oe } from "react-dom";
|
|
6
|
+
const nn = [
|
|
7
|
+
{
|
|
8
|
+
type: "constant",
|
|
9
|
+
label: "Constant",
|
|
10
|
+
category: "Inputs",
|
|
11
|
+
inputs: [],
|
|
12
|
+
outputs: [{ name: "value", label: "Value", dataKind: "tensor", direction: "out" }],
|
|
13
|
+
parameters: [{ name: "value", label: "Value", kind: "any", default: 1, required: !0, dtype: "float" }]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type: "add",
|
|
17
|
+
label: "Add",
|
|
18
|
+
category: "Math",
|
|
19
|
+
inputs: [
|
|
20
|
+
{ name: "left", label: "Left", dataKind: "tensor", direction: "in" },
|
|
21
|
+
{ name: "right", label: "Right", dataKind: "tensor", direction: "in" }
|
|
22
|
+
],
|
|
23
|
+
outputs: [{ name: "result", label: "Result", dataKind: "tensor", direction: "out" }],
|
|
24
|
+
parameters: []
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: "multiply",
|
|
28
|
+
label: "Multiply",
|
|
29
|
+
category: "Math",
|
|
30
|
+
inputs: [
|
|
31
|
+
{ name: "left", label: "Left", dataKind: "tensor", direction: "in" },
|
|
32
|
+
{ name: "right", label: "Right", dataKind: "tensor", direction: "in" }
|
|
33
|
+
],
|
|
34
|
+
outputs: [{ name: "result", label: "Result", dataKind: "tensor", direction: "out" }],
|
|
35
|
+
parameters: []
|
|
36
|
+
}
|
|
37
|
+
], Z = "0.1.0";
|
|
38
|
+
let tn = 0;
|
|
39
|
+
function le(n) {
|
|
40
|
+
return n.charAt(0).toUpperCase() + n.slice(1);
|
|
41
|
+
}
|
|
42
|
+
function an(n) {
|
|
43
|
+
return {
|
|
44
|
+
type: n.type,
|
|
45
|
+
label: n.label,
|
|
46
|
+
category: n.category,
|
|
47
|
+
inputs: n.inputs.map((e) => ({
|
|
48
|
+
name: e.name,
|
|
49
|
+
label: le(e.name),
|
|
50
|
+
dataKind: e.data_kind,
|
|
51
|
+
direction: "in"
|
|
52
|
+
})),
|
|
53
|
+
outputs: n.outputs.map((e) => ({
|
|
54
|
+
name: e.name,
|
|
55
|
+
label: le(e.name),
|
|
56
|
+
dataKind: e.data_kind,
|
|
57
|
+
direction: "out"
|
|
58
|
+
})),
|
|
59
|
+
parameters: n.parameters.map((e) => ({
|
|
60
|
+
name: e.name,
|
|
61
|
+
label: le(e.name),
|
|
62
|
+
kind: e.kind,
|
|
63
|
+
default: e.default,
|
|
64
|
+
required: e.required,
|
|
65
|
+
dtype: e.dtype ?? void 0
|
|
66
|
+
}))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
let ke = 0;
|
|
70
|
+
function J(n) {
|
|
71
|
+
return ke += 1, `${n}_${ke}`;
|
|
72
|
+
}
|
|
73
|
+
function M(n, e) {
|
|
74
|
+
let t = "untitled", s = "", r = "";
|
|
75
|
+
try {
|
|
76
|
+
t = y.getState().activeFileName ?? "untitled", s = localStorage.getItem("entropia_riko_theme") ?? "", r = localStorage.getItem("entropia_riko_background") ?? "";
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
version: "1.0",
|
|
81
|
+
metadata: { name: t, app: "entropia-riko", appVersion: Z },
|
|
82
|
+
nodes: n.map((o) => ({
|
|
83
|
+
id: o.id,
|
|
84
|
+
type_name: o.data.type,
|
|
85
|
+
label: o.data.label,
|
|
86
|
+
category: o.data.category,
|
|
87
|
+
position: [o.position.x, o.position.y],
|
|
88
|
+
parameters: o.data.params,
|
|
89
|
+
inputs: [],
|
|
90
|
+
outputs: []
|
|
91
|
+
})),
|
|
92
|
+
edges: e.map((o) => ({
|
|
93
|
+
id: o.id,
|
|
94
|
+
source_node: o.source,
|
|
95
|
+
source_port: o.sourceHandle,
|
|
96
|
+
target_node: o.target,
|
|
97
|
+
target_port: o.targetHandle
|
|
98
|
+
})),
|
|
99
|
+
settings: { theme: s, backgroundImage: r }
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function ee(n, e) {
|
|
103
|
+
const t = (n.nodes || []).map((r) => {
|
|
104
|
+
var i, d;
|
|
105
|
+
const o = e.find((m) => m.type === r.type_name);
|
|
106
|
+
return {
|
|
107
|
+
id: r.id,
|
|
108
|
+
type: "thnNode",
|
|
109
|
+
position: { x: ((i = r.position) == null ? void 0 : i[0]) ?? 0, y: ((d = r.position) == null ? void 0 : d[1]) ?? 0 },
|
|
110
|
+
data: {
|
|
111
|
+
type: r.type_name,
|
|
112
|
+
label: r.label || (o == null ? void 0 : o.label) || r.type_name,
|
|
113
|
+
category: r.category || (o == null ? void 0 : o.category) || "",
|
|
114
|
+
params: r.parameters || {},
|
|
115
|
+
inputs: (o == null ? void 0 : o.inputs) || [],
|
|
116
|
+
outputs: (o == null ? void 0 : o.outputs) || [],
|
|
117
|
+
hasError: !1
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}), s = (n.edges || []).map((r) => ({
|
|
121
|
+
id: r.id,
|
|
122
|
+
source: r.source_node,
|
|
123
|
+
target: r.target_node,
|
|
124
|
+
sourceHandle: r.source_port,
|
|
125
|
+
targetHandle: r.target_port
|
|
126
|
+
}));
|
|
127
|
+
return { nodes: t, edges: s };
|
|
128
|
+
}
|
|
129
|
+
async function sn(n) {
|
|
130
|
+
const e = (n || "").trim();
|
|
131
|
+
if (!e) return null;
|
|
132
|
+
const t = [];
|
|
133
|
+
(e.includes("/") || e.endsWith(".riko") || e.endsWith(".ric")) && t.push(e), t.push(`examples/models/${e}.riko`), t.push(`examples/${e}.riko`), t.push(`workflows/${e}.riko`), t.push(`${e}.riko`);
|
|
134
|
+
for (const s of t)
|
|
135
|
+
try {
|
|
136
|
+
if ((await (await fetch(`/api/files/content?path=${encodeURIComponent(s)}`)).json()).status === "success") return s;
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const Ie = "entropia_riko_recent";
|
|
142
|
+
function rn() {
|
|
143
|
+
try {
|
|
144
|
+
const n = localStorage.getItem(Ie), e = n ? JSON.parse(n) : [];
|
|
145
|
+
return Array.isArray(e) ? e.filter((t) => typeof t == "string") : [];
|
|
146
|
+
} catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function on(n) {
|
|
151
|
+
try {
|
|
152
|
+
localStorage.setItem(Ie, JSON.stringify(n.slice(0, 10)));
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const y = Ee((n, e) => ({
|
|
157
|
+
nodeDefs: nn,
|
|
158
|
+
workflows: [],
|
|
159
|
+
activeWorkflowId: null,
|
|
160
|
+
nodes: [],
|
|
161
|
+
edges: [],
|
|
162
|
+
frames: [],
|
|
163
|
+
graphStack: [{ name: "root", path: "", nodes: [], edges: [] }],
|
|
164
|
+
selectedNodeId: null,
|
|
165
|
+
status: "idle",
|
|
166
|
+
logs: [],
|
|
167
|
+
results: {},
|
|
168
|
+
fileList: [],
|
|
169
|
+
activeFileName: null,
|
|
170
|
+
activeFilePath: null,
|
|
171
|
+
dirty: !1,
|
|
172
|
+
losses: [],
|
|
173
|
+
recentFiles: rn(),
|
|
174
|
+
welcomeOpen: !0,
|
|
175
|
+
prefsOpen: !1,
|
|
176
|
+
aboutOpen: !1,
|
|
177
|
+
importFolderOpen: !1,
|
|
178
|
+
filePickerOpen: !1,
|
|
179
|
+
filePickerMode: "import",
|
|
180
|
+
projectVersion: 0,
|
|
181
|
+
codeBuffer: "",
|
|
182
|
+
toasts: [],
|
|
183
|
+
onNodesChange: (t) => {
|
|
184
|
+
const s = qe(t, e().nodes), r = t.some((o) => o.type !== "select");
|
|
185
|
+
n(r ? { nodes: s, dirty: !0 } : { nodes: s });
|
|
186
|
+
},
|
|
187
|
+
onEdgesChange: (t) => {
|
|
188
|
+
const s = Ve(t, e().edges), r = t.some((o) => o.type !== "select");
|
|
189
|
+
n(r ? { edges: s, dirty: !0 } : { edges: s });
|
|
190
|
+
},
|
|
191
|
+
onConnect: (t) => {
|
|
192
|
+
if (!t.source || !t.target) return;
|
|
193
|
+
const s = J("edge");
|
|
194
|
+
n({ edges: ze({ ...t, id: s }, e().edges), dirty: !0 }), e().log(`连接 ${t.source}.${t.sourceHandle} → ${t.target}.${t.targetHandle}`);
|
|
195
|
+
},
|
|
196
|
+
addNode: (t, s) => {
|
|
197
|
+
const r = e().nodeDefs.find((m) => m.type === t), o = J(t), i = {};
|
|
198
|
+
if (r)
|
|
199
|
+
for (const m of r.parameters) i[m.name] = m.default;
|
|
200
|
+
const d = {
|
|
201
|
+
id: o,
|
|
202
|
+
type: "thnNode",
|
|
203
|
+
position: s,
|
|
204
|
+
data: {
|
|
205
|
+
type: t,
|
|
206
|
+
label: (r == null ? void 0 : r.label) ?? t,
|
|
207
|
+
category: (r == null ? void 0 : r.category) ?? "Other",
|
|
208
|
+
params: i,
|
|
209
|
+
inputs: (r == null ? void 0 : r.inputs) ?? [],
|
|
210
|
+
outputs: (r == null ? void 0 : r.outputs) ?? [{ name: "result", label: "Result", dataKind: "tensor", direction: "out" }],
|
|
211
|
+
hasError: !1
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
n({ nodes: [...e().nodes, d], selectedNodeId: o, dirty: !0 }), e().log(`添加节点 ${(r == null ? void 0 : r.label) ?? t} (${o})`);
|
|
215
|
+
},
|
|
216
|
+
onSelectNode: (t) => n({ selectedNodeId: t }),
|
|
217
|
+
updateParam: (t, s, r) => n({
|
|
218
|
+
nodes: e().nodes.map(
|
|
219
|
+
(o) => o.id === t ? { ...o, data: { ...o.data, params: { ...o.data.params, [s]: r } } } : o
|
|
220
|
+
),
|
|
221
|
+
dirty: !0
|
|
222
|
+
}),
|
|
223
|
+
removeNode: (t) => n({
|
|
224
|
+
nodes: e().nodes.filter((s) => s.id !== t),
|
|
225
|
+
edges: e().edges.filter((s) => s.source !== t && s.target !== t),
|
|
226
|
+
selectedNodeId: e().selectedNodeId === t ? null : e().selectedNodeId,
|
|
227
|
+
dirty: !0
|
|
228
|
+
}),
|
|
229
|
+
addFrame: (t, s) => {
|
|
230
|
+
const r = J("frame");
|
|
231
|
+
n({
|
|
232
|
+
frames: [
|
|
233
|
+
...e().frames,
|
|
234
|
+
{ id: r, title: t.trim() || "Frame", x: s.x, y: s.y, width: 360, height: 220 }
|
|
235
|
+
],
|
|
236
|
+
dirty: !0
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
updateFrame: (t, s) => n({
|
|
240
|
+
frames: e().frames.map((r) => r.id === t ? { ...r, ...s } : r),
|
|
241
|
+
dirty: !0
|
|
242
|
+
}),
|
|
243
|
+
removeFrame: (t) => n({ frames: e().frames.filter((s) => s.id !== t), dirty: !0 }),
|
|
244
|
+
enterSubgraph: async (t) => {
|
|
245
|
+
var i;
|
|
246
|
+
const s = e().nodes.find((d) => d.id === t);
|
|
247
|
+
if (!s) return;
|
|
248
|
+
const r = s.data.params.file ?? s.data.params.module;
|
|
249
|
+
if (!r) {
|
|
250
|
+
e().pushToast("error", "该节点没有 file/module 参数");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const o = await sn(String(r));
|
|
254
|
+
if (!o) {
|
|
255
|
+
e().pushToast("error", `找不到子图: ${r}`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const m = await (await fetch(`/api/files/content?path=${encodeURIComponent(o)}`)).json();
|
|
260
|
+
if (m.status !== "success") {
|
|
261
|
+
e().pushToast("error", m.error ?? "打开子图失败");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const { nodes: c, edges: u } = ee(m.doc, e().nodeDefs), h = [...e().graphStack], v = h[h.length - 1];
|
|
265
|
+
h[h.length - 1] = { ...v, nodes: e().nodes, edges: e().edges };
|
|
266
|
+
const l = ((i = o.split("/").pop()) == null ? void 0 : i.replace(/\.(riko|ric)$/i, "")) ?? o;
|
|
267
|
+
h.push({ name: l, path: o, nodes: c, edges: u }), n({
|
|
268
|
+
graphStack: h,
|
|
269
|
+
nodes: c,
|
|
270
|
+
edges: u,
|
|
271
|
+
selectedNodeId: null,
|
|
272
|
+
results: {},
|
|
273
|
+
status: "idle",
|
|
274
|
+
dirty: !1
|
|
275
|
+
}), e().log(`进入子图 ${o}`);
|
|
276
|
+
} catch (d) {
|
|
277
|
+
e().pushToast("error", d instanceof Error ? d.message : String(d));
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
exitToLevel: (t) => {
|
|
281
|
+
const s = e().graphStack.slice(0, t + 1), r = s[t];
|
|
282
|
+
r && (n({
|
|
283
|
+
graphStack: s,
|
|
284
|
+
nodes: r.nodes,
|
|
285
|
+
edges: r.edges,
|
|
286
|
+
selectedNodeId: null,
|
|
287
|
+
results: {},
|
|
288
|
+
status: "idle",
|
|
289
|
+
dirty: !1
|
|
290
|
+
}), e().log(`返回层级 ${r.name}`));
|
|
291
|
+
},
|
|
292
|
+
run: async () => {
|
|
293
|
+
n({ status: "running" }), e().log("开始执行图(调用 Python runtime)...");
|
|
294
|
+
try {
|
|
295
|
+
const t = M(e().nodes, e().edges), r = await (await fetch("/api/execute", {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: { "Content-Type": "application/json" },
|
|
298
|
+
body: JSON.stringify(t)
|
|
299
|
+
})).json();
|
|
300
|
+
if (r.status === "success") {
|
|
301
|
+
const o = {}, i = r.outputs;
|
|
302
|
+
for (const [d, m] of Object.entries(i)) {
|
|
303
|
+
o[d] = {};
|
|
304
|
+
for (const [c, u] of Object.entries(m))
|
|
305
|
+
o[d][c] = {
|
|
306
|
+
summary: u.summary,
|
|
307
|
+
shape: u.shape,
|
|
308
|
+
dtype: u.dtype,
|
|
309
|
+
device: u.device,
|
|
310
|
+
dataPreview: u.data === null || u.data === void 0 ? "" : JSON.stringify(u.data).slice(0, 120),
|
|
311
|
+
dataKind: u.data_kind,
|
|
312
|
+
image: u.image
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
n({
|
|
316
|
+
results: o,
|
|
317
|
+
status: "success",
|
|
318
|
+
nodes: e().nodes.map((d) => ({
|
|
319
|
+
...d,
|
|
320
|
+
data: { ...d.data, hasError: !1, errorMsg: void 0 }
|
|
321
|
+
}))
|
|
322
|
+
}), e().log("执行成功");
|
|
323
|
+
} else {
|
|
324
|
+
n({ status: "error" });
|
|
325
|
+
for (const o of r.errors)
|
|
326
|
+
e().log(`错误: ${o}`), e().pushToast("error", o);
|
|
327
|
+
}
|
|
328
|
+
} catch (t) {
|
|
329
|
+
const s = t instanceof Error ? t.message : String(t);
|
|
330
|
+
n({ status: "error" }), e().log(`请求失败: ${s}(请确认 API server 已启动)`), e().pushToast("error", `请求失败: ${s}`);
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
loadNodeDefs: async () => {
|
|
334
|
+
try {
|
|
335
|
+
const r = (await (await fetch("/api/nodes")).json()).nodes.map(an);
|
|
336
|
+
r.length > 0 && (n({ nodeDefs: r }), e().log(`从 API 加载 ${r.length} 个节点定义`));
|
|
337
|
+
} catch (t) {
|
|
338
|
+
e().log(`API 节点加载失败: ${t instanceof Error ? t.message : String(t)}。请启动 API server: uvicorn src.server.app:app --port 8000`);
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
save: () => {
|
|
342
|
+
const t = M(e().nodes, e().edges), s = new Blob([JSON.stringify(t, null, 2)], {
|
|
343
|
+
type: "application/json"
|
|
344
|
+
}), r = URL.createObjectURL(s), o = document.createElement("a");
|
|
345
|
+
o.href = r, o.download = "workflow.riko", o.click(), URL.revokeObjectURL(r), n({ dirty: !1 }), e().log("已保存工作流 (workflow.riko)");
|
|
346
|
+
},
|
|
347
|
+
exportRiko: () => {
|
|
348
|
+
const t = M(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");
|
|
349
|
+
i.href = o, i.download = `${s}.riko`, i.click(), URL.revokeObjectURL(o), n({ dirty: !1 }), e().log(`已导出 ${s}.riko`);
|
|
350
|
+
},
|
|
351
|
+
exportBinary: async () => {
|
|
352
|
+
try {
|
|
353
|
+
const t = M(e().nodes, e().edges), r = await (await fetch("/api/export_binary", {
|
|
354
|
+
method: "POST",
|
|
355
|
+
headers: { "Content-Type": "application/json" },
|
|
356
|
+
body: JSON.stringify(t)
|
|
357
|
+
})).json();
|
|
358
|
+
if (r.status !== "success") throw new Error(r.error);
|
|
359
|
+
const o = (e().activeFileName ?? "workflow").replace(/\.(riko|ric)$/i, ""), i = Uint8Array.from(atob(r.base64), (u) => u.charCodeAt(0)), d = new Blob([i], { type: "application/octet-stream" }), m = URL.createObjectURL(d), c = document.createElement("a");
|
|
360
|
+
c.href = m, c.download = `${o}.ric`, c.click(), URL.revokeObjectURL(m), n({ dirty: !1 }), e().log(`已导出 ${o}.ric`);
|
|
361
|
+
} catch (t) {
|
|
362
|
+
e().log(`导出 .ric 失败: ${t instanceof Error ? t.message : String(t)}`);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
exportProjectTo: async (t) => {
|
|
366
|
+
const s = M(e().nodes, e().edges);
|
|
367
|
+
try {
|
|
368
|
+
const o = await (await fetch("/api/export_project", {
|
|
369
|
+
method: "POST",
|
|
370
|
+
headers: { "Content-Type": "application/json" },
|
|
371
|
+
body: JSON.stringify({ doc: s, dir: t })
|
|
372
|
+
})).json();
|
|
373
|
+
o.status === "success" ? (e().pushToast("success", `Exported project → ${o.root}`), e().log(`已导出多文件 PyTorch 工程: ${o.files.join(", ")}`)) : e().pushToast("error", o.error ?? "导出工程失败");
|
|
374
|
+
} catch (r) {
|
|
375
|
+
e().pushToast("error", r instanceof Error ? r.message : String(r));
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
load: async (t) => {
|
|
379
|
+
try {
|
|
380
|
+
let s;
|
|
381
|
+
if (t.name.toLowerCase().endsWith(".ric")) {
|
|
382
|
+
const i = await t.arrayBuffer(), m = await (await fetch("/api/files/decode", {
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
385
|
+
body: i
|
|
386
|
+
})).json();
|
|
387
|
+
if (m.status !== "success") throw new Error(m.error);
|
|
388
|
+
s = m.doc;
|
|
389
|
+
} else {
|
|
390
|
+
const i = await new Promise((d, m) => {
|
|
391
|
+
const c = new FileReader();
|
|
392
|
+
c.onload = () => d(String(c.result)), c.onerror = () => m(c.error), c.readAsText(t);
|
|
393
|
+
});
|
|
394
|
+
s = JSON.parse(i);
|
|
395
|
+
}
|
|
396
|
+
const { nodes: r, edges: o } = ee(s, e().nodeDefs);
|
|
397
|
+
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} 连接)`);
|
|
398
|
+
} catch (s) {
|
|
399
|
+
e().log(`加载失败: ${s instanceof Error ? s.message : String(s)}`);
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
exportPython: async () => {
|
|
403
|
+
const t = M(e().nodes, e().edges);
|
|
404
|
+
try {
|
|
405
|
+
const r = await (await fetch("/api/export_python", {
|
|
406
|
+
method: "POST",
|
|
407
|
+
headers: { "Content-Type": "application/json" },
|
|
408
|
+
body: JSON.stringify(t)
|
|
409
|
+
})).json();
|
|
410
|
+
if (r.status === "success") {
|
|
411
|
+
const o = new Blob([r.code], { type: "text/x-python" }), i = URL.createObjectURL(o), d = document.createElement("a");
|
|
412
|
+
d.href = i, d.download = "workflow.py", d.click(), URL.revokeObjectURL(i), e().log("已导出 Python (workflow.py)");
|
|
413
|
+
} else
|
|
414
|
+
e().log(`导出失败: ${r.error}`);
|
|
415
|
+
} catch (s) {
|
|
416
|
+
e().log(`导出失败: ${s instanceof Error ? s.message : String(s)}`);
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
exportKeras: async () => {
|
|
420
|
+
const t = M(e().nodes, e().edges);
|
|
421
|
+
try {
|
|
422
|
+
const r = await (await fetch("/api/export_keras", {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: { "Content-Type": "application/json" },
|
|
425
|
+
body: JSON.stringify(t)
|
|
426
|
+
})).json();
|
|
427
|
+
if (r.status === "success") {
|
|
428
|
+
const o = new Blob([r.code], { type: "text/x-python" }), i = URL.createObjectURL(o), d = document.createElement("a");
|
|
429
|
+
d.href = i, d.download = "workflow_keras.py", d.click(), URL.revokeObjectURL(i), e().log("已导出 Keras (workflow_keras.py)");
|
|
430
|
+
} else
|
|
431
|
+
e().log(`导出失败: ${r.error}`);
|
|
432
|
+
} catch (s) {
|
|
433
|
+
e().log(`导出失败: ${s instanceof Error ? s.message : String(s)}`);
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
generatePythonCode: async () => {
|
|
437
|
+
const t = M(e().nodes, e().edges);
|
|
438
|
+
try {
|
|
439
|
+
const r = await (await fetch("/api/export_python", {
|
|
440
|
+
method: "POST",
|
|
441
|
+
headers: { "Content-Type": "application/json" },
|
|
442
|
+
body: JSON.stringify(t)
|
|
443
|
+
})).json();
|
|
444
|
+
r.status === "success" ? (n({ codeBuffer: r.code }), e().log("已生成 PyTorch 代码(代码编辑器窗口)")) : e().pushToast("error", r.error ?? "代码生成失败");
|
|
445
|
+
} catch (s) {
|
|
446
|
+
e().pushToast("error", s instanceof Error ? s.message : String(s));
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
previewAssetCode: async (t) => {
|
|
450
|
+
try {
|
|
451
|
+
const r = await (await fetch(`/api/files/content?path=${encodeURIComponent(t)}`)).json();
|
|
452
|
+
if (r.status !== "success") {
|
|
453
|
+
e().pushToast("error", r.error ?? "读取失败");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const i = await (await fetch("/api/export_python", {
|
|
457
|
+
method: "POST",
|
|
458
|
+
headers: { "Content-Type": "application/json" },
|
|
459
|
+
body: JSON.stringify(r.doc)
|
|
460
|
+
})).json();
|
|
461
|
+
i.status === "success" ? (n({ codeBuffer: i.code }), e().pushToast("success", "Code → Code Editor")) : e().pushToast("error", i.error ?? "代码生成失败");
|
|
462
|
+
} catch (s) {
|
|
463
|
+
e().pushToast("error", s instanceof Error ? s.message : String(s));
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
newWorkflow: (t) => {
|
|
467
|
+
const s = e().activeWorkflowId, r = s ? e().workflows.map(
|
|
468
|
+
(i) => i.id === s ? { ...i, nodes: e().nodes, edges: e().edges } : i
|
|
469
|
+
) : e().workflows, o = J("wf");
|
|
470
|
+
n({
|
|
471
|
+
workflows: [...r, { id: o, name: t, nodes: [], edges: [] }],
|
|
472
|
+
activeWorkflowId: o,
|
|
473
|
+
nodes: [],
|
|
474
|
+
edges: [],
|
|
475
|
+
selectedNodeId: null,
|
|
476
|
+
results: {},
|
|
477
|
+
status: "idle",
|
|
478
|
+
dirty: !1
|
|
479
|
+
}), e().log(`新建工作流: ${t} (${o})`);
|
|
480
|
+
},
|
|
481
|
+
switchWorkflow: (t) => {
|
|
482
|
+
const s = e().activeWorkflowId;
|
|
483
|
+
let r = e().workflows;
|
|
484
|
+
s && (r = r.map(
|
|
485
|
+
(i) => i.id === s ? { ...i, nodes: e().nodes, edges: e().edges } : i
|
|
486
|
+
));
|
|
487
|
+
const o = r.find((i) => i.id === t);
|
|
488
|
+
o && (n({
|
|
489
|
+
workflows: r,
|
|
490
|
+
activeWorkflowId: t,
|
|
491
|
+
nodes: o.nodes,
|
|
492
|
+
edges: o.edges,
|
|
493
|
+
selectedNodeId: null,
|
|
494
|
+
results: {},
|
|
495
|
+
status: "idle",
|
|
496
|
+
dirty: !1
|
|
497
|
+
}), e().log(`切换到工作流: ${o.name}`));
|
|
498
|
+
},
|
|
499
|
+
refreshFiles: async () => {
|
|
500
|
+
try {
|
|
501
|
+
const s = await (await fetch("/api/files")).json();
|
|
502
|
+
Array.isArray(s.files) ? n({ fileList: s.files }) : e().log("文件列表格式错误");
|
|
503
|
+
} catch (t) {
|
|
504
|
+
e().log(`文件列表加载失败: ${t instanceof Error ? t.message : String(t)}`);
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
openFile: async (t) => {
|
|
508
|
+
var s, r, o, i;
|
|
509
|
+
try {
|
|
510
|
+
const m = await (await fetch(`/api/files/content?path=${encodeURIComponent(t)}`)).json();
|
|
511
|
+
if (m.status === "success") {
|
|
512
|
+
const { nodes: c, edges: u } = ee(m.doc, e().nodeDefs);
|
|
513
|
+
n({
|
|
514
|
+
nodes: c,
|
|
515
|
+
edges: u,
|
|
516
|
+
selectedNodeId: null,
|
|
517
|
+
results: {},
|
|
518
|
+
status: "idle",
|
|
519
|
+
activeFileName: ((r = (s = m.doc) == null ? void 0 : s.metadata) == null ? void 0 : r.name) ?? t,
|
|
520
|
+
activeFilePath: t,
|
|
521
|
+
graphStack: [{ name: ((i = (o = m.doc) == null ? void 0 : o.metadata) == null ? void 0 : i.name) ?? t.split("/").pop() ?? t, path: t, nodes: c, edges: [] }],
|
|
522
|
+
dirty: !1
|
|
523
|
+
});
|
|
524
|
+
const h = [t, ...e().recentFiles.filter((v) => v !== t)].slice(0, 10);
|
|
525
|
+
on(h), n({ recentFiles: h }), e().log(`已打开 ${t}`);
|
|
526
|
+
} else
|
|
527
|
+
e().log(`打开失败: ${m.error}`);
|
|
528
|
+
} catch (d) {
|
|
529
|
+
e().log(`打开失败: ${d instanceof Error ? d.message : String(d)}`);
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
openProjectFile: async (t) => {
|
|
533
|
+
var s;
|
|
534
|
+
try {
|
|
535
|
+
const o = await (await fetch(`/api/project/open?path=${encodeURIComponent(t)}`)).json();
|
|
536
|
+
if (o.status === "success") {
|
|
537
|
+
const { nodes: i, edges: d } = ee(o.doc, e().nodeDefs), m = ((s = t.split("/").pop()) == null ? void 0 : s.replace(/\.(riko|ric)$/, "")) ?? t;
|
|
538
|
+
n({
|
|
539
|
+
nodes: i,
|
|
540
|
+
edges: d,
|
|
541
|
+
selectedNodeId: null,
|
|
542
|
+
results: {},
|
|
543
|
+
status: "idle",
|
|
544
|
+
activeFileName: m,
|
|
545
|
+
activeFilePath: t,
|
|
546
|
+
graphStack: [{ name: m, path: t, nodes: i, edges: [] }],
|
|
547
|
+
dirty: !1
|
|
548
|
+
}), e().log(`已打开 ${t}`);
|
|
549
|
+
} else
|
|
550
|
+
e().log(`打开失败: ${o.error}`);
|
|
551
|
+
} catch (r) {
|
|
552
|
+
e().log(`打开失败: ${r instanceof Error ? r.message : String(r)}`);
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
importFileInline: async (t) => {
|
|
556
|
+
try {
|
|
557
|
+
const r = await (await fetch(`/api/files/content?path=${encodeURIComponent(t)}`)).json();
|
|
558
|
+
if (r.status !== "success") {
|
|
559
|
+
e().log(`展开失败: ${r.error}`);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const o = r.doc, i = e().nodes;
|
|
563
|
+
let d = 0;
|
|
564
|
+
for (const h of i) d = Math.max(d, h.position.y + 120);
|
|
565
|
+
const m = {}, c = (o.nodes ?? []).map((h) => {
|
|
566
|
+
var w, f;
|
|
567
|
+
const v = J("n");
|
|
568
|
+
m[h.id] = v;
|
|
569
|
+
const l = e().nodeDefs.find((S) => S.type === h.type_name);
|
|
570
|
+
return {
|
|
571
|
+
id: v,
|
|
572
|
+
type: "thnNode",
|
|
573
|
+
position: { x: ((w = h.position) == null ? void 0 : w[0]) ?? 0, y: (((f = h.position) == null ? void 0 : f[1]) ?? 0) + d },
|
|
574
|
+
data: {
|
|
575
|
+
type: h.type_name,
|
|
576
|
+
label: h.label || (l == null ? void 0 : l.label) || h.type_name,
|
|
577
|
+
category: h.category || (l == null ? void 0 : l.category) || "",
|
|
578
|
+
params: h.parameters || {},
|
|
579
|
+
inputs: (l == null ? void 0 : l.inputs) ?? [],
|
|
580
|
+
outputs: (l == null ? void 0 : l.outputs) ?? [],
|
|
581
|
+
hasError: !1
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}), u = (o.edges ?? []).map((h) => ({
|
|
585
|
+
id: J("edge"),
|
|
586
|
+
source: m[h.source_node],
|
|
587
|
+
target: m[h.target_node],
|
|
588
|
+
sourceHandle: h.source_port,
|
|
589
|
+
targetHandle: h.target_port
|
|
590
|
+
})).filter((h) => h.source && h.target);
|
|
591
|
+
n({
|
|
592
|
+
nodes: [...i, ...c],
|
|
593
|
+
edges: [...e().edges, ...u],
|
|
594
|
+
dirty: !0
|
|
595
|
+
}), e().log(`已展开 ${t}(${c.length} 节点, ${u.length} 连接)`);
|
|
596
|
+
} catch (s) {
|
|
597
|
+
e().log(`展开失败: ${s instanceof Error ? s.message : String(s)}`);
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
saveFileToDisk: async (t, s = "ascii") => {
|
|
601
|
+
const o = { ...M(e().nodes, e().edges), metadata: { name: t, description: "" } };
|
|
602
|
+
try {
|
|
603
|
+
const d = await (await fetch("/api/files/save", {
|
|
604
|
+
method: "POST",
|
|
605
|
+
headers: { "Content-Type": "application/json" },
|
|
606
|
+
body: JSON.stringify({ name: t, format: s, doc: o })
|
|
607
|
+
})).json();
|
|
608
|
+
d.status === "success" ? (n({ activeFileName: t, dirty: !1 }), e().log(`已保存到 ${d.path} (${d.format === "binary" ? ".ric 二进制" : ".riko ASCII"})`), e().refreshFiles()) : e().log(`保存失败: ${d.error ?? "未知错误"}`);
|
|
609
|
+
} catch (i) {
|
|
610
|
+
e().log(`保存失败: ${i instanceof Error ? i.message : String(i)}`);
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
saveToPath: async (t, s = "ascii") => {
|
|
614
|
+
var o;
|
|
615
|
+
const r = M(e().nodes, e().edges);
|
|
616
|
+
try {
|
|
617
|
+
const d = await (await fetch("/api/fs/save", {
|
|
618
|
+
method: "POST",
|
|
619
|
+
headers: { "Content-Type": "application/json" },
|
|
620
|
+
body: JSON.stringify({ path: t, format: s, doc: r })
|
|
621
|
+
})).json();
|
|
622
|
+
if (d.status === "success") {
|
|
623
|
+
const m = d.name ?? ((o = t.split("/").pop()) == null ? void 0 : o.replace(/\.(riko|ric)$/i, "")) ?? "workflow";
|
|
624
|
+
n({ activeFileName: m, activeFilePath: t, dirty: !1 }), e().log(`已保存到 ${d.path}`), e().pushToast("success", `Saved ${m}`), e().refreshFiles();
|
|
625
|
+
} else
|
|
626
|
+
e().pushToast("error", d.error ?? "保存失败");
|
|
627
|
+
} catch (i) {
|
|
628
|
+
e().pushToast("error", i instanceof Error ? i.message : String(i));
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
importModule: (t, s) => {
|
|
632
|
+
const r = e().nodeDefs.find((m) => m.type === "import") ?? e().nodeDefs.find((m) => m.type === "graph_reference");
|
|
633
|
+
if (!r) {
|
|
634
|
+
e().log("未找到 import / graph_reference 节点定义,请确认 API server 已启动");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
const o = J(r.type), i = {};
|
|
638
|
+
for (const m of r.parameters) i[m.name] = m.default;
|
|
639
|
+
i[r.type === "import" ? "module" : "file"] = t;
|
|
640
|
+
const d = {
|
|
641
|
+
id: o,
|
|
642
|
+
type: "thnNode",
|
|
643
|
+
position: s,
|
|
644
|
+
data: {
|
|
645
|
+
type: r.type,
|
|
646
|
+
label: r.label,
|
|
647
|
+
category: r.category,
|
|
648
|
+
params: i,
|
|
649
|
+
inputs: r.inputs,
|
|
650
|
+
outputs: r.outputs,
|
|
651
|
+
hasError: !1
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
n({ nodes: [...e().nodes, d], selectedNodeId: o, dirty: !0 }), e().log(`导入 ${t} (${r.type})`);
|
|
655
|
+
},
|
|
656
|
+
train: async (t = 20, s = 1e-3) => {
|
|
657
|
+
n({ status: "running", losses: [] }), e().log(`开始训练 (${t} 步, lr=${s})...`);
|
|
658
|
+
try {
|
|
659
|
+
const r = M(e().nodes, e().edges), o = await fetch("/api/train/stream", {
|
|
660
|
+
method: "POST",
|
|
661
|
+
headers: { "Content-Type": "application/json" },
|
|
662
|
+
body: JSON.stringify({ doc: r, steps: t, lr: s })
|
|
663
|
+
});
|
|
664
|
+
if (!o.body) throw new Error("响应无流 body");
|
|
665
|
+
const i = o.body.getReader(), d = new TextDecoder();
|
|
666
|
+
let m = "";
|
|
667
|
+
for (; ; ) {
|
|
668
|
+
const { done: u, value: h } = await i.read();
|
|
669
|
+
if (u) break;
|
|
670
|
+
m += d.decode(h, { stream: !0 });
|
|
671
|
+
const v = m.split(`
|
|
672
|
+
`);
|
|
673
|
+
m = v.pop() ?? "";
|
|
674
|
+
for (const l of v) {
|
|
675
|
+
const w = l.trim();
|
|
676
|
+
if (!w) continue;
|
|
677
|
+
let f;
|
|
678
|
+
try {
|
|
679
|
+
f = JSON.parse(w);
|
|
680
|
+
} catch {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (f.error) {
|
|
684
|
+
e().log(`训练失败: ${f.error}`), e().pushToast("error", `训练失败: ${f.error}`), n({ status: "error" });
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
typeof f.loss == "number" && n({ losses: [...e().losses, f.loss] }), f.done && n({ status: "success" });
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const c = e().losses[e().losses.length - 1];
|
|
691
|
+
e().log(`训练完成,最终 loss=${(c == null ? void 0 : c.toFixed(4)) ?? "n/a"}`), n({ status: "success" });
|
|
692
|
+
} catch (r) {
|
|
693
|
+
n({ status: "error" }), e().log(`训练失败: ${r instanceof Error ? r.message : String(r)}`), e().pushToast("error", `训练失败: ${r instanceof Error ? r.message : String(r)}`);
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
newBlank: () => {
|
|
697
|
+
n({
|
|
698
|
+
nodes: [],
|
|
699
|
+
edges: [],
|
|
700
|
+
frames: [],
|
|
701
|
+
graphStack: [{ name: "root", path: "", nodes: [], edges: [] }],
|
|
702
|
+
selectedNodeId: null,
|
|
703
|
+
results: {},
|
|
704
|
+
status: "idle",
|
|
705
|
+
activeFileName: null,
|
|
706
|
+
activeFilePath: null,
|
|
707
|
+
dirty: !1
|
|
708
|
+
}), e().log("新建空白工作流");
|
|
709
|
+
},
|
|
710
|
+
setWelcomeOpen: (t) => n({ welcomeOpen: t }),
|
|
711
|
+
setPrefsOpen: (t) => n({ prefsOpen: t }),
|
|
712
|
+
setAboutOpen: (t) => n({ aboutOpen: t }),
|
|
713
|
+
setImportFolderOpen: (t) => n({ importFolderOpen: t }),
|
|
714
|
+
setFilePicker: (t, s) => n({ filePickerOpen: t, filePickerMode: s }),
|
|
715
|
+
bumpProject: () => n({ projectVersion: e().projectVersion + 1 }),
|
|
716
|
+
setCodeBuffer: (t) => n({ codeBuffer: t }),
|
|
717
|
+
pushToast: (t, s) => {
|
|
718
|
+
const r = ++tn;
|
|
719
|
+
n({ toasts: [...e().toasts, { id: r, kind: t, message: s }].slice(-6) }), t !== "error" && setTimeout(() => e().dismissToast(r), 6e3);
|
|
720
|
+
},
|
|
721
|
+
dismissToast: (t) => n({ toasts: e().toasts.filter((s) => s.id !== t) }),
|
|
722
|
+
log: (t) => {
|
|
723
|
+
const s = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
724
|
+
n({ logs: [...e().logs, `[${s}] ${t}`].slice(-80) });
|
|
725
|
+
}
|
|
726
|
+
})), Re = "entropia_riko_theme", pe = "entropia_riko_background";
|
|
727
|
+
function ln() {
|
|
728
|
+
try {
|
|
729
|
+
const n = localStorage.getItem(Re);
|
|
730
|
+
return n === "light" || n === "dark" || n === "glass" ? n : "system";
|
|
731
|
+
} catch {
|
|
732
|
+
return "system";
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
function je(n) {
|
|
736
|
+
n === "system" ? delete document.documentElement.dataset.theme : document.documentElement.dataset.theme = n;
|
|
737
|
+
}
|
|
738
|
+
function $e(n) {
|
|
739
|
+
const e = document.body;
|
|
740
|
+
n ? (e.style.backgroundImage = `url("${n}")`, e.style.backgroundSize = "cover", e.style.backgroundPosition = "center") : (e.style.backgroundImage = "", e.style.backgroundSize = "", e.style.backgroundPosition = "");
|
|
741
|
+
}
|
|
742
|
+
function cn() {
|
|
743
|
+
try {
|
|
744
|
+
return localStorage.getItem(pe) ?? "";
|
|
745
|
+
} catch {
|
|
746
|
+
return "";
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const K = Ee((n) => ({
|
|
750
|
+
theme: ln(),
|
|
751
|
+
setTheme: (e) => {
|
|
752
|
+
je(e);
|
|
753
|
+
try {
|
|
754
|
+
localStorage.setItem(Re, e);
|
|
755
|
+
} catch {
|
|
756
|
+
}
|
|
757
|
+
n({ theme: e });
|
|
758
|
+
},
|
|
759
|
+
backgroundImage: cn(),
|
|
760
|
+
setBackgroundImage: (e) => {
|
|
761
|
+
$e(e);
|
|
762
|
+
try {
|
|
763
|
+
e ? localStorage.setItem(pe, e) : localStorage.removeItem(pe);
|
|
764
|
+
} catch {
|
|
765
|
+
}
|
|
766
|
+
n({ backgroundImage: e });
|
|
767
|
+
}
|
|
768
|
+
}));
|
|
769
|
+
je(K.getState().theme);
|
|
770
|
+
$e(K.getState().backgroundImage);
|
|
771
|
+
function te({
|
|
772
|
+
entries: n,
|
|
773
|
+
x: e,
|
|
774
|
+
y: t,
|
|
775
|
+
onClose: s,
|
|
776
|
+
searchable: r = !1,
|
|
777
|
+
maxWidth: o = 280
|
|
778
|
+
}) {
|
|
779
|
+
const [i, d] = P(""), [m, c] = P({}), u = z(() => {
|
|
780
|
+
const l = i.trim().toLowerCase(), w = l ? n.filter(
|
|
781
|
+
(b) => !b.separator && (b.label.toLowerCase().includes(l) || (b.description ?? "").toLowerCase().includes(l))
|
|
782
|
+
) : n, f = {}, S = [];
|
|
783
|
+
for (const b of w) {
|
|
784
|
+
const T = b.category ?? "";
|
|
785
|
+
T in f || (f[T] = [], S.push(T)), f[T].push(b);
|
|
786
|
+
}
|
|
787
|
+
return { map: f, order: S };
|
|
788
|
+
}, [n, i]), { map: h, order: v } = u;
|
|
789
|
+
return Oe(
|
|
790
|
+
/* @__PURE__ */ p(B, { children: [
|
|
791
|
+
/* @__PURE__ */ a(
|
|
792
|
+
"div",
|
|
793
|
+
{
|
|
794
|
+
className: "popup-overlay",
|
|
795
|
+
onClick: s,
|
|
796
|
+
onContextMenu: (l) => {
|
|
797
|
+
l.preventDefault(), s();
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
),
|
|
801
|
+
/* @__PURE__ */ p("div", { className: "popup-menu", style: { left: e, top: t, maxWidth: o }, children: [
|
|
802
|
+
r && /* @__PURE__ */ a("div", { className: "popup-search", children: /* @__PURE__ */ a(
|
|
803
|
+
"input",
|
|
804
|
+
{
|
|
805
|
+
autoFocus: !0,
|
|
806
|
+
placeholder: "Search…",
|
|
807
|
+
value: i,
|
|
808
|
+
onChange: (l) => d(l.target.value)
|
|
809
|
+
}
|
|
810
|
+
) }),
|
|
811
|
+
/* @__PURE__ */ p("div", { className: "popup-list", children: [
|
|
812
|
+
v.map((l) => {
|
|
813
|
+
const w = !i && !m[l];
|
|
814
|
+
return /* @__PURE__ */ p("div", { className: "popup-category", children: [
|
|
815
|
+
l !== "" && /* @__PURE__ */ p(
|
|
816
|
+
"div",
|
|
817
|
+
{
|
|
818
|
+
className: "popup-cat-header",
|
|
819
|
+
onClick: () => c((f) => ({ ...f, [l]: !f[l] })),
|
|
820
|
+
children: [
|
|
821
|
+
/* @__PURE__ */ a("span", { className: "popup-cat-toggle", children: w ? "▸" : "▾" }),
|
|
822
|
+
/* @__PURE__ */ a("span", { className: "popup-cat-label", children: l }),
|
|
823
|
+
/* @__PURE__ */ a("span", { className: "popup-cat-count", children: h[l].length })
|
|
824
|
+
]
|
|
825
|
+
}
|
|
826
|
+
),
|
|
827
|
+
!w && h[l].map(
|
|
828
|
+
(f) => f.separator ? /* @__PURE__ */ a("div", { className: "popup-sep" }, f.id) : /* @__PURE__ */ p(
|
|
829
|
+
"div",
|
|
830
|
+
{
|
|
831
|
+
className: `popup-item ${f.danger ? "danger" : ""} ${f.disabled ? "disabled" : ""}`,
|
|
832
|
+
onClick: () => {
|
|
833
|
+
var S;
|
|
834
|
+
f.disabled || ((S = f.onSelect) == null || S.call(f), s());
|
|
835
|
+
},
|
|
836
|
+
children: [
|
|
837
|
+
/* @__PURE__ */ a("span", { className: "popup-item-label", children: f.label }),
|
|
838
|
+
f.description && /* @__PURE__ */ a("span", { className: "popup-item-desc", children: f.description })
|
|
839
|
+
]
|
|
840
|
+
},
|
|
841
|
+
f.id
|
|
842
|
+
)
|
|
843
|
+
)
|
|
844
|
+
] }, l);
|
|
845
|
+
}),
|
|
846
|
+
v.length === 0 && /* @__PURE__ */ a("div", { className: "popup-item disabled", children: "No matches" })
|
|
847
|
+
] })
|
|
848
|
+
] })
|
|
849
|
+
] }),
|
|
850
|
+
document.body
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
let dn = 0;
|
|
854
|
+
const Le = () => `area_${++dn}`, R = (n) => ({
|
|
855
|
+
kind: "leaf",
|
|
856
|
+
id: Le(),
|
|
857
|
+
type: n
|
|
858
|
+
}), L = (n, e, t, s = 0.5) => ({ kind: "split", id: Le(), direction: n, first: e, second: t, ratio: s });
|
|
859
|
+
function pn() {
|
|
860
|
+
const n = L("column", R("nodes"), R("files"), 0.72), e = L(
|
|
861
|
+
"row",
|
|
862
|
+
R("canvas"),
|
|
863
|
+
L("column", R("project"), R("inspector"), 0.3),
|
|
864
|
+
0.68
|
|
865
|
+
), t = L("row", n, e, 0.26), s = L("row", R("status"), R("loss"), 0.55);
|
|
866
|
+
return L("column", t, s, 0.82);
|
|
867
|
+
}
|
|
868
|
+
function W(n) {
|
|
869
|
+
const e = n.leftExtra ? L("column", R("nodes"), R(n.leftExtra), 0.7) : R("nodes");
|
|
870
|
+
let t = R(n.right);
|
|
871
|
+
n.rightTop && (t = L("column", R(n.rightTop), t, n.rightTopRatio ?? 0.28));
|
|
872
|
+
const s = L("row", R("canvas"), t, n.canvasRatio ?? 0.68), r = L("row", e, s, n.leftRatio ?? 0.26), o = n.bottom.length === 1 ? R(n.bottom[0]) : L("row", R(n.bottom[0]), R(n.bottom[1]), 0.55);
|
|
873
|
+
return L("column", r, o, n.colRatio ?? 0.84);
|
|
874
|
+
}
|
|
875
|
+
const ue = [
|
|
876
|
+
{
|
|
877
|
+
id: "layout",
|
|
878
|
+
label: "Layout",
|
|
879
|
+
category: "General",
|
|
880
|
+
description: "Full IDE: nodes, asset library, code editor, canvas, inspector.",
|
|
881
|
+
build: pn
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
id: "code",
|
|
885
|
+
label: "Code",
|
|
886
|
+
category: "General",
|
|
887
|
+
description: "Big code editor + canvas for previewing exported PyTorch.",
|
|
888
|
+
build: () => W({ right: "code", bottom: ["status", "loss"], leftExtra: "files", canvasRatio: 0.6 })
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
id: "inference",
|
|
892
|
+
label: "Inference",
|
|
893
|
+
category: "General",
|
|
894
|
+
description: "Large canvas + inspector, no training.",
|
|
895
|
+
build: () => W({ right: "inspector", bottom: ["status", "docs"], leftExtra: "files", canvasRatio: 0.74 })
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
id: "training",
|
|
899
|
+
label: "Training",
|
|
900
|
+
category: "Training",
|
|
901
|
+
description: "Live loss curve + inspector for training runs.",
|
|
902
|
+
build: () => W({ right: "loss", bottom: ["status", "inspector"], leftExtra: "files", canvasRatio: 0.6 })
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
id: "tuning",
|
|
906
|
+
label: "Hyperparameter Tuning",
|
|
907
|
+
category: "Training",
|
|
908
|
+
description: "Loss + docs; inspector below.",
|
|
909
|
+
build: () => W({ right: "loss", bottom: ["inspector", "docs"], canvasRatio: 0.62 })
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
id: "mnist_studio",
|
|
913
|
+
label: "MNIST Studio",
|
|
914
|
+
category: "Vision",
|
|
915
|
+
description: "Handwriting pad above the inspector — draw a digit and infer.",
|
|
916
|
+
build: () => W({ right: "inspector", rightTop: "pad", bottom: ["status", "loss"], leftExtra: "files", canvasRatio: 0.58 })
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
id: "image_classifier",
|
|
920
|
+
label: "Image Classifier",
|
|
921
|
+
category: "Vision",
|
|
922
|
+
description: "Image previews + loss.",
|
|
923
|
+
build: () => W({ right: "inspector", bottom: ["status", "loss"], leftExtra: "files", canvasRatio: 0.68 })
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
id: "image_gen",
|
|
927
|
+
label: "Text → Image",
|
|
928
|
+
category: "Vision",
|
|
929
|
+
description: "Project explorer above inspector for Diffusers generation.",
|
|
930
|
+
build: () => W({ right: "inspector", rightTop: "project", bottom: ["status"], canvasRatio: 0.62 })
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
id: "detection",
|
|
934
|
+
label: "Object Detection",
|
|
935
|
+
category: "Vision",
|
|
936
|
+
description: "Canvas + inspector + docs.",
|
|
937
|
+
build: () => W({ right: "inspector", bottom: ["status", "docs"], leftExtra: "files", canvasRatio: 0.7 })
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
id: "text_classifier",
|
|
941
|
+
label: "Text Classifier",
|
|
942
|
+
category: "Language",
|
|
943
|
+
description: "Text previews + docs.",
|
|
944
|
+
build: () => W({ right: "inspector", bottom: ["status", "docs"], leftExtra: "files", canvasRatio: 0.7 })
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
id: "text_gen",
|
|
948
|
+
label: "Text Gen",
|
|
949
|
+
category: "Language",
|
|
950
|
+
description: "Text preview for Transformers.",
|
|
951
|
+
build: () => W({ right: "inspector", bottom: ["status"], rightTop: "project", canvasRatio: 0.68 })
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
id: "embeddings",
|
|
955
|
+
label: "Embeddings / JEPA",
|
|
956
|
+
category: "Language",
|
|
957
|
+
description: "Pretrained encoder embeddings.",
|
|
958
|
+
build: () => W({ right: "inspector", bottom: ["status", "docs"], canvasRatio: 0.72 })
|
|
959
|
+
}
|
|
960
|
+
], un = (n, e, t) => Math.min(t, Math.max(e, n));
|
|
961
|
+
function ae(n, e, t) {
|
|
962
|
+
return n.kind === "leaf" ? n.id === e ? L(t, n, R(n.type), 0.5) : n : {
|
|
963
|
+
...n,
|
|
964
|
+
first: ae(n.first, e, t),
|
|
965
|
+
second: ae(n.second, e, t)
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function me(n, e) {
|
|
969
|
+
return n.kind === "split" ? n.first.id === e ? n.second : n.second.id === e ? n.first : {
|
|
970
|
+
...n,
|
|
971
|
+
first: me(n.first, e),
|
|
972
|
+
second: me(n.second, e)
|
|
973
|
+
} : n;
|
|
974
|
+
}
|
|
975
|
+
function he(n, e) {
|
|
976
|
+
return n.kind === "split" ? n.first.id === e ? n.first : n.second.id === e ? n.second : {
|
|
977
|
+
...n,
|
|
978
|
+
first: he(n.first, e),
|
|
979
|
+
second: he(n.second, e)
|
|
980
|
+
} : n;
|
|
981
|
+
}
|
|
982
|
+
function fe(n, e) {
|
|
983
|
+
if (n.kind === "split") {
|
|
984
|
+
if (n.first.id === e) return n.second.id;
|
|
985
|
+
if (n.second.id === e) return n.first.id;
|
|
986
|
+
const t = fe(n.first, e);
|
|
987
|
+
return t !== null ? t : fe(n.second, e);
|
|
988
|
+
}
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
function se(n, e, t) {
|
|
992
|
+
return n.kind === "split" ? n.id === e ? { ...n, ratio: un(n.ratio + t, 0.08, 0.92) } : {
|
|
993
|
+
...n,
|
|
994
|
+
first: se(n.first, e, t),
|
|
995
|
+
second: se(n.second, e, t)
|
|
996
|
+
} : n;
|
|
997
|
+
}
|
|
998
|
+
function ge(n, e, t) {
|
|
999
|
+
return n.kind === "leaf" ? n.id === e ? { ...n, type: t } : n : {
|
|
1000
|
+
...n,
|
|
1001
|
+
first: ge(n.first, e, t),
|
|
1002
|
+
second: ge(n.second, e, t)
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
function ye(n) {
|
|
1006
|
+
return n.kind === "leaf" ? 1 : ye(n.first) + ye(n.second);
|
|
1007
|
+
}
|
|
1008
|
+
function mn({
|
|
1009
|
+
workspaces: n,
|
|
1010
|
+
activeId: e,
|
|
1011
|
+
onSwitch: t,
|
|
1012
|
+
onAdd: s,
|
|
1013
|
+
onRemove: r,
|
|
1014
|
+
onRename: o,
|
|
1015
|
+
onDuplicate: i,
|
|
1016
|
+
onMove: d
|
|
1017
|
+
}) {
|
|
1018
|
+
const [m, c] = P(!1), [u, h] = P(null), [v, l] = P(null), [w, f] = P(!1), S = $(null);
|
|
1019
|
+
D(() => {
|
|
1020
|
+
const N = S.current;
|
|
1021
|
+
if (!N) return;
|
|
1022
|
+
const _ = () => f(N.scrollWidth > N.clientWidth + 1);
|
|
1023
|
+
return _(), window.addEventListener("resize", _), () => window.removeEventListener("resize", _);
|
|
1024
|
+
}, [n]);
|
|
1025
|
+
const b = n.length > 1, T = z(
|
|
1026
|
+
() => ue.map((N) => ({
|
|
1027
|
+
id: N.id,
|
|
1028
|
+
label: N.label,
|
|
1029
|
+
description: N.description,
|
|
1030
|
+
category: N.category,
|
|
1031
|
+
onSelect: () => s(N.id)
|
|
1032
|
+
})),
|
|
1033
|
+
[s]
|
|
1034
|
+
), x = (N) => {
|
|
1035
|
+
const _ = N.currentTarget.getBoundingClientRect();
|
|
1036
|
+
h({ x: _.left, y: _.bottom + 6 }), c(!0);
|
|
1037
|
+
}, I = (N) => {
|
|
1038
|
+
const _ = n.find((O) => O.id === N), k = window.prompt("Rename workspace", (_ == null ? void 0 : _.name) ?? "");
|
|
1039
|
+
k && k.trim() && o(N, k.trim());
|
|
1040
|
+
}, j = (N) => [
|
|
1041
|
+
{ id: "rename", label: "Rename", onSelect: () => I(N) },
|
|
1042
|
+
{ id: "duplicate", label: "Duplicate", onSelect: () => i(N) },
|
|
1043
|
+
{ id: "sep1", label: "", separator: !0 },
|
|
1044
|
+
{ id: "left", label: "← Move Left", onSelect: () => d(N, -1) },
|
|
1045
|
+
{ id: "right", label: "Move Right →", onSelect: () => d(N, 1) },
|
|
1046
|
+
{ id: "sep2", label: "", separator: !0 },
|
|
1047
|
+
...b ? [{ id: "close", label: "Close", danger: !0, onSelect: () => r(N) }] : []
|
|
1048
|
+
];
|
|
1049
|
+
return /* @__PURE__ */ p("div", { className: "workspace-tabs-wrap", children: [
|
|
1050
|
+
/* @__PURE__ */ a("div", { ref: S, className: `workspace-tabs ${w ? "overflowing" : ""}`, children: n.map((N) => /* @__PURE__ */ p(
|
|
1051
|
+
"div",
|
|
1052
|
+
{
|
|
1053
|
+
className: `workspace-tab ${N.id === e ? "active" : ""}`,
|
|
1054
|
+
onClick: () => t(N.id),
|
|
1055
|
+
onContextMenu: (_) => {
|
|
1056
|
+
_.preventDefault(), l({ id: N.id, x: _.clientX, y: _.clientY });
|
|
1057
|
+
},
|
|
1058
|
+
children: [
|
|
1059
|
+
/* @__PURE__ */ a("span", { children: N.name }),
|
|
1060
|
+
b && /* @__PURE__ */ a(
|
|
1061
|
+
"span",
|
|
1062
|
+
{
|
|
1063
|
+
className: "workspace-tab-close",
|
|
1064
|
+
title: "Close workspace",
|
|
1065
|
+
onClick: (_) => {
|
|
1066
|
+
_.stopPropagation(), r(N.id);
|
|
1067
|
+
},
|
|
1068
|
+
children: "✕"
|
|
1069
|
+
}
|
|
1070
|
+
)
|
|
1071
|
+
]
|
|
1072
|
+
},
|
|
1073
|
+
N.id
|
|
1074
|
+
)) }),
|
|
1075
|
+
/* @__PURE__ */ a("button", { className: "workspace-add", title: "Add workspace", onClick: x, children: "+" }),
|
|
1076
|
+
m && u && /* @__PURE__ */ a(te, { entries: T, x: u.x, y: u.y, onClose: () => c(!1) }),
|
|
1077
|
+
v && /* @__PURE__ */ a(
|
|
1078
|
+
te,
|
|
1079
|
+
{
|
|
1080
|
+
entries: j(v.id),
|
|
1081
|
+
x: v.x,
|
|
1082
|
+
y: v.y,
|
|
1083
|
+
onClose: () => l(null)
|
|
1084
|
+
}
|
|
1085
|
+
)
|
|
1086
|
+
] });
|
|
1087
|
+
}
|
|
1088
|
+
function oe({
|
|
1089
|
+
title: n,
|
|
1090
|
+
onClose: e,
|
|
1091
|
+
x: t = 140,
|
|
1092
|
+
y: s = 90,
|
|
1093
|
+
width: r = 520,
|
|
1094
|
+
zIndex: o = 1e3,
|
|
1095
|
+
children: i
|
|
1096
|
+
}) {
|
|
1097
|
+
const [d, m] = P({ x: t, y: s }), c = $(null), u = (h) => {
|
|
1098
|
+
c.current = { dx: h.clientX - d.x, dy: h.clientY - d.y };
|
|
1099
|
+
const v = (w) => {
|
|
1100
|
+
c.current && m({ x: w.clientX - c.current.dx, y: w.clientY - c.current.dy });
|
|
1101
|
+
}, l = () => {
|
|
1102
|
+
c.current = null, document.removeEventListener("mousemove", v), document.removeEventListener("mouseup", l);
|
|
1103
|
+
};
|
|
1104
|
+
document.addEventListener("mousemove", v), document.addEventListener("mouseup", l);
|
|
1105
|
+
};
|
|
1106
|
+
return /* @__PURE__ */ p("div", { className: "float-window", style: { left: d.x, top: d.y, width: r, zIndex: o }, children: [
|
|
1107
|
+
/* @__PURE__ */ p("div", { className: "float-titlebar", onMouseDown: u, children: [
|
|
1108
|
+
/* @__PURE__ */ a("span", { className: "float-title", children: n }),
|
|
1109
|
+
/* @__PURE__ */ a("button", { className: "float-close", onClick: e, children: "✕" })
|
|
1110
|
+
] }),
|
|
1111
|
+
/* @__PURE__ */ a("div", { className: "float-body", children: i })
|
|
1112
|
+
] });
|
|
1113
|
+
}
|
|
1114
|
+
let Q = null, De = "";
|
|
1115
|
+
function Y(n, e) {
|
|
1116
|
+
return De = (e == null ? void 0 : e.defaultName) ?? "", y.getState().setFilePicker(!0, n), new Promise((t) => {
|
|
1117
|
+
Q = t;
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
function X(n) {
|
|
1121
|
+
y.getState().setFilePicker(!1, "import"), Q == null || Q(n), Q = null;
|
|
1122
|
+
}
|
|
1123
|
+
const Ae = "entropia_riko_recent_folders";
|
|
1124
|
+
function be() {
|
|
1125
|
+
try {
|
|
1126
|
+
const n = localStorage.getItem(Ae), e = n ? JSON.parse(n) : [];
|
|
1127
|
+
return Array.isArray(e) ? e.filter((t) => typeof t == "string") : [];
|
|
1128
|
+
} catch {
|
|
1129
|
+
return [];
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
function hn(n) {
|
|
1133
|
+
try {
|
|
1134
|
+
const e = [n, ...be().filter((t) => t !== n)].slice(0, 8);
|
|
1135
|
+
localStorage.setItem(Ae, JSON.stringify(e));
|
|
1136
|
+
} catch {
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
function fn() {
|
|
1140
|
+
const n = y((k) => k.filePickerOpen), e = y((k) => k.filePickerMode), t = y((k) => k.pushToast), [s, r] = P("~"), [o, i] = P([]), [d, m] = P(null), [c, u] = P("~"), [h, v] = P(be()), [l, w] = P([]), [f, S] = P([]), [b, T] = P(""), x = async (k, O = !0) => {
|
|
1141
|
+
const A = await (await fetch(`/api/fs/list?path=${encodeURIComponent(k)}`)).json();
|
|
1142
|
+
A.status === "success" ? (O && (w((q) => [...q, s]), S([])), r(A.path), i(A.entries ?? []), m(null), hn(A.path), v(be())) : t("error", A.error ?? "list failed");
|
|
1143
|
+
};
|
|
1144
|
+
if (D(() => {
|
|
1145
|
+
n && (e === "save" && T(De), x("~", !1), fetch("/api/project/tree").then((k) => k.json()).then((k) => {
|
|
1146
|
+
typeof k.root == "string" && u(k.root);
|
|
1147
|
+
}).catch(() => {
|
|
1148
|
+
}));
|
|
1149
|
+
}, [n]), !n) return null;
|
|
1150
|
+
const I = [
|
|
1151
|
+
{ label: "Home", path: "~" },
|
|
1152
|
+
{ label: "Desktop", path: "~/Desktop" },
|
|
1153
|
+
{ label: "Documents", path: "~/Documents" },
|
|
1154
|
+
{ label: "Downloads", path: "~/Downloads" },
|
|
1155
|
+
{ label: "Working Folder", path: c }
|
|
1156
|
+
], j = () => {
|
|
1157
|
+
const k = l[l.length - 1];
|
|
1158
|
+
k && (w(l.slice(0, -1)), S((O) => [s, ...O]), x(k, !1));
|
|
1159
|
+
}, N = () => {
|
|
1160
|
+
const k = f[0];
|
|
1161
|
+
k && (S(f.slice(1)), w((O) => [...O, s]), x(k, !1));
|
|
1162
|
+
}, _ = () => {
|
|
1163
|
+
if (e === "save") {
|
|
1164
|
+
const k = b.trim() || "untitled";
|
|
1165
|
+
return s.endsWith("/") ? s + k : s + "/" + k;
|
|
1166
|
+
}
|
|
1167
|
+
return e === "export" ? d && d.type === "dir" ? d.path : s : d ? d.path : s;
|
|
1168
|
+
};
|
|
1169
|
+
return /* @__PURE__ */ a(
|
|
1170
|
+
oe,
|
|
1171
|
+
{
|
|
1172
|
+
title: e === "import" ? "Import File / Folder" : e === "export" ? "Export To Folder" : "Save File",
|
|
1173
|
+
onClose: () => X(null),
|
|
1174
|
+
width: 780,
|
|
1175
|
+
zIndex: 1002,
|
|
1176
|
+
children: /* @__PURE__ */ p("div", { className: "picker-inner", children: [
|
|
1177
|
+
/* @__PURE__ */ p("div", { className: "picker-side", children: [
|
|
1178
|
+
/* @__PURE__ */ a("div", { className: "picker-side-title", children: "Quick access" }),
|
|
1179
|
+
I.map((k) => /* @__PURE__ */ p("div", { className: "picker-side-item", onClick: () => x(k.path), children: [
|
|
1180
|
+
/* @__PURE__ */ a("span", { className: "picker-side-icon", children: "▦" }),
|
|
1181
|
+
k.label
|
|
1182
|
+
] }, k.label)),
|
|
1183
|
+
/* @__PURE__ */ a("div", { className: "picker-side-title", style: { marginTop: 12 }, children: "Recent" }),
|
|
1184
|
+
h.map((k) => /* @__PURE__ */ p("div", { className: "picker-side-item", title: k, onClick: () => x(k), children: [
|
|
1185
|
+
/* @__PURE__ */ a("span", { className: "picker-side-icon", children: "🕘" }),
|
|
1186
|
+
/* @__PURE__ */ a("span", { className: "picker-side-name", children: k })
|
|
1187
|
+
] }, k))
|
|
1188
|
+
] }),
|
|
1189
|
+
/* @__PURE__ */ p("div", { className: "picker-main", children: [
|
|
1190
|
+
/* @__PURE__ */ p("div", { className: "picker-topbar", children: [
|
|
1191
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: j, title: "Back", children: "←" }),
|
|
1192
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: N, title: "Forward", children: "→" }),
|
|
1193
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () => x(s, !1), title: "Refresh", children: "↻" }),
|
|
1194
|
+
/* @__PURE__ */ a(
|
|
1195
|
+
"input",
|
|
1196
|
+
{
|
|
1197
|
+
className: "prefs-input",
|
|
1198
|
+
value: s,
|
|
1199
|
+
onChange: (k) => r(k.target.value),
|
|
1200
|
+
onKeyDown: (k) => {
|
|
1201
|
+
k.key === "Enter" && x(s);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
)
|
|
1205
|
+
] }),
|
|
1206
|
+
/* @__PURE__ */ p("div", { className: "picker-list", children: [
|
|
1207
|
+
o.map((k) => /* @__PURE__ */ p(
|
|
1208
|
+
"div",
|
|
1209
|
+
{
|
|
1210
|
+
className: `picker-row ${(d == null ? void 0 : d.path) === k.path ? "sel" : ""}`,
|
|
1211
|
+
onClick: () => m(k),
|
|
1212
|
+
onDoubleClick: () => k.type === "dir" && x(k.path),
|
|
1213
|
+
children: [
|
|
1214
|
+
/* @__PURE__ */ a("span", { className: "explorer-icon", children: k.type === "dir" ? "▦" : "▤" }),
|
|
1215
|
+
/* @__PURE__ */ a("span", { className: "explorer-name", children: k.name }),
|
|
1216
|
+
/* @__PURE__ */ a("span", { className: "explorer-size", children: k.type === "file" ? `${Math.round(k.size / 1024)} KB` : "" })
|
|
1217
|
+
]
|
|
1218
|
+
},
|
|
1219
|
+
k.path
|
|
1220
|
+
)),
|
|
1221
|
+
o.length === 0 && /* @__PURE__ */ a("div", { className: "welcome-empty", children: "Empty folder." })
|
|
1222
|
+
] }),
|
|
1223
|
+
/* @__PURE__ */ p("div", { className: "picker-bottombar", children: [
|
|
1224
|
+
/* @__PURE__ */ a("span", { className: "picker-filename-label", children: "File name:" }),
|
|
1225
|
+
/* @__PURE__ */ a(
|
|
1226
|
+
"input",
|
|
1227
|
+
{
|
|
1228
|
+
className: "prefs-input",
|
|
1229
|
+
value: e === "save" ? b : (d == null ? void 0 : d.name) ?? "",
|
|
1230
|
+
onChange: (k) => {
|
|
1231
|
+
e === "save" && T(k.target.value);
|
|
1232
|
+
},
|
|
1233
|
+
onKeyDown: (k) => {
|
|
1234
|
+
k.key === "Enter" && X(_()), k.key === "Escape" && X(null);
|
|
1235
|
+
},
|
|
1236
|
+
readOnly: e !== "save",
|
|
1237
|
+
placeholder: e === "save" ? "name.riko / name.ric" : "(select a file or folder)"
|
|
1238
|
+
}
|
|
1239
|
+
),
|
|
1240
|
+
/* @__PURE__ */ a("button", { className: "btn btn-primary", onClick: () => X(_()), children: "Save" }),
|
|
1241
|
+
/* @__PURE__ */ a("button", { className: "btn", onClick: () => X(null), children: "Cancel" })
|
|
1242
|
+
] })
|
|
1243
|
+
] })
|
|
1244
|
+
] })
|
|
1245
|
+
}
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
const we = ["/brand/logo.ico", "/brand/logo.png", "/brand/logo.svg"];
|
|
1249
|
+
function ie({
|
|
1250
|
+
className: n,
|
|
1251
|
+
alt: e = "Entropia Riko logo",
|
|
1252
|
+
title: t
|
|
1253
|
+
}) {
|
|
1254
|
+
const [s, r] = P(0);
|
|
1255
|
+
return /* @__PURE__ */ a(
|
|
1256
|
+
"img",
|
|
1257
|
+
{
|
|
1258
|
+
className: n,
|
|
1259
|
+
src: we[s],
|
|
1260
|
+
alt: e,
|
|
1261
|
+
title: t,
|
|
1262
|
+
onError: () => r((o) => Math.min(o + 1, we.length - 1))
|
|
1263
|
+
}
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
function gn({
|
|
1267
|
+
workspaces: n,
|
|
1268
|
+
activeWorkspaceId: e,
|
|
1269
|
+
onSwitchWorkspace: t,
|
|
1270
|
+
onAddWorkspace: s,
|
|
1271
|
+
onRemoveWorkspace: r,
|
|
1272
|
+
onRenameWorkspace: o,
|
|
1273
|
+
onDuplicateWorkspace: i,
|
|
1274
|
+
onMoveWorkspace: d
|
|
1275
|
+
}) {
|
|
1276
|
+
const [m, c] = P(null), u = y((g) => g.status), h = y((g) => g.run), v = y((g) => g.train);
|
|
1277
|
+
y((g) => g.save), y((g) => g.exportRiko), y((g) => g.exportBinary);
|
|
1278
|
+
const l = y((g) => g.load), w = y((g) => g.exportPython), f = y((g) => g.exportKeras), S = y((g) => g.newWorkflow), b = y((g) => g.addNode), T = y((g) => g.pushToast), x = K((g) => g.theme), I = K((g) => g.setTheme), j = $(null), N = u === "running", _ = async () => {
|
|
1279
|
+
const g = await Y("import");
|
|
1280
|
+
if (!g) return;
|
|
1281
|
+
const C = await (await fetch("/api/fs/import", {
|
|
1282
|
+
method: "POST",
|
|
1283
|
+
headers: { "Content-Type": "application/json" },
|
|
1284
|
+
body: JSON.stringify({ src: g })
|
|
1285
|
+
})).json();
|
|
1286
|
+
C.status === "success" ? (T("success", `Imported ${C.path}`), y.getState().bumpProject()) : T("error", C.error ?? "import failed");
|
|
1287
|
+
}, k = async () => {
|
|
1288
|
+
const g = y.getState().activeFilePath;
|
|
1289
|
+
if (!g) {
|
|
1290
|
+
T("error", "No open file to export");
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
const F = await Y("export");
|
|
1294
|
+
if (!F) return;
|
|
1295
|
+
const E = await (await fetch("/api/fs/export", {
|
|
1296
|
+
method: "POST",
|
|
1297
|
+
headers: { "Content-Type": "application/json" },
|
|
1298
|
+
body: JSON.stringify({ src: g, dest: F })
|
|
1299
|
+
})).json();
|
|
1300
|
+
E.status === "success" ? T("success", `Exported to ${E.path}`) : T("error", E.error ?? "export failed");
|
|
1301
|
+
}, O = async (g) => {
|
|
1302
|
+
const C = (y.getState().activeFileName ?? "untitled") + (g === "binary" ? ".ric" : ".riko"), E = await Y("save", { defaultName: C });
|
|
1303
|
+
E && await y.getState().saveToPath(E, g);
|
|
1304
|
+
};
|
|
1305
|
+
return /* @__PURE__ */ p("div", { className: "menubar", onClick: (g) => g.stopPropagation(), children: [
|
|
1306
|
+
/* @__PURE__ */ p(
|
|
1307
|
+
"div",
|
|
1308
|
+
{
|
|
1309
|
+
className: `menu-item app-logo-item ${m === "__app__" ? "active" : ""}`,
|
|
1310
|
+
onClick: () => c(m === "__app__" ? null : "__app__"),
|
|
1311
|
+
title: "Entropia Riko",
|
|
1312
|
+
children: [
|
|
1313
|
+
/* @__PURE__ */ a(ie, { className: "menubar-logo", alt: "Entropia Riko" }),
|
|
1314
|
+
m === "__app__" && /* @__PURE__ */ p("div", { className: "dropdown app-menu", onClick: (g) => g.stopPropagation(), children: [
|
|
1315
|
+
/* @__PURE__ */ a(
|
|
1316
|
+
"div",
|
|
1317
|
+
{
|
|
1318
|
+
className: "dropdown-item",
|
|
1319
|
+
onClick: () => {
|
|
1320
|
+
y.getState().setAboutOpen(!0), c(null);
|
|
1321
|
+
},
|
|
1322
|
+
children: "About Entropia Riko"
|
|
1323
|
+
}
|
|
1324
|
+
),
|
|
1325
|
+
/* @__PURE__ */ a(
|
|
1326
|
+
"div",
|
|
1327
|
+
{
|
|
1328
|
+
className: "dropdown-item",
|
|
1329
|
+
onClick: () => {
|
|
1330
|
+
y.getState().setWelcomeOpen(!0), c(null);
|
|
1331
|
+
},
|
|
1332
|
+
children: "Welcome Screen"
|
|
1333
|
+
}
|
|
1334
|
+
),
|
|
1335
|
+
/* @__PURE__ */ a(
|
|
1336
|
+
"div",
|
|
1337
|
+
{
|
|
1338
|
+
className: "dropdown-item",
|
|
1339
|
+
onClick: () => {
|
|
1340
|
+
y.getState().setPrefsOpen(!0), c(null);
|
|
1341
|
+
},
|
|
1342
|
+
children: "Preferences…"
|
|
1343
|
+
}
|
|
1344
|
+
)
|
|
1345
|
+
] })
|
|
1346
|
+
]
|
|
1347
|
+
}
|
|
1348
|
+
),
|
|
1349
|
+
[
|
|
1350
|
+
{
|
|
1351
|
+
label: "File",
|
|
1352
|
+
items: [
|
|
1353
|
+
{ label: "New Workflow", onClick: () => S("Untitled") },
|
|
1354
|
+
{ label: "New Project…", onClick: async () => {
|
|
1355
|
+
const g = await Y("save", { defaultName: "my_project" });
|
|
1356
|
+
if (!g) return;
|
|
1357
|
+
const C = await (await fetch("/api/project/new", {
|
|
1358
|
+
method: "POST",
|
|
1359
|
+
headers: { "Content-Type": "application/json" },
|
|
1360
|
+
body: JSON.stringify({ dir: g })
|
|
1361
|
+
})).json();
|
|
1362
|
+
C.status === "success" ? (y.getState().bumpProject(), T("success", `Created project ${C.root}`)) : T("error", C.error ?? "new project failed");
|
|
1363
|
+
} },
|
|
1364
|
+
{ label: "Save .riko…", onClick: () => O("ascii"), disabled: N },
|
|
1365
|
+
{ label: "Save .ric…", onClick: () => O("binary"), disabled: N },
|
|
1366
|
+
{
|
|
1367
|
+
label: "Import / Export",
|
|
1368
|
+
submenu: [
|
|
1369
|
+
{ label: "Import…", onClick: _ },
|
|
1370
|
+
{ label: "Export…", onClick: k },
|
|
1371
|
+
{ label: "Import Working Folder…", onClick: () => y.getState().setImportFolderOpen(!0) }
|
|
1372
|
+
]
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
label: "Export Code",
|
|
1376
|
+
submenu: [
|
|
1377
|
+
{ label: "Export .py (PyTorch)", onClick: w, disabled: N },
|
|
1378
|
+
{ label: "Export .py (Keras/TF)", onClick: f, disabled: N },
|
|
1379
|
+
{ label: "Export Project… (multi-file)", onClick: async () => {
|
|
1380
|
+
const g = y.getState().activeFileName ?? "graph_project", F = await Y("save", { defaultName: g });
|
|
1381
|
+
F && await y.getState().exportProjectTo(F);
|
|
1382
|
+
}, disabled: N }
|
|
1383
|
+
]
|
|
1384
|
+
},
|
|
1385
|
+
{ label: "Preferences…", onClick: () => y.getState().setPrefsOpen(!0) }
|
|
1386
|
+
]
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
label: "Run",
|
|
1390
|
+
items: [
|
|
1391
|
+
{ label: "Inference", onClick: h, disabled: N },
|
|
1392
|
+
{ label: "Train (20 steps)", onClick: () => v(20, 1e-3), disabled: N },
|
|
1393
|
+
{ label: "Train (100 steps)", onClick: () => v(100, 1e-3), disabled: N }
|
|
1394
|
+
]
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
label: "Data",
|
|
1398
|
+
items: [
|
|
1399
|
+
{ label: "MNIST Loader", onClick: () => b("mnist_loader", { x: 200, y: 150 }) },
|
|
1400
|
+
{ label: "CIFAR10 Loader", onClick: () => b("cifar10_loader", { x: 200, y: 150 }) },
|
|
1401
|
+
{ label: "CSV Loader", onClick: () => b("csv_loader", { x: 200, y: 150 }) },
|
|
1402
|
+
{ label: "Image Folder Loader", onClick: () => b("image_folder_loader", { x: 200, y: 150 }) },
|
|
1403
|
+
{ label: "Tensor File Loader", onClick: () => b("tensor_file_loader", { x: 200, y: 150 }) },
|
|
1404
|
+
{ label: "Generic Data Loader", onClick: () => b("dataloader", { x: 200, y: 150 }) }
|
|
1405
|
+
]
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
label: "View",
|
|
1409
|
+
items: [
|
|
1410
|
+
{ label: `Theme: Light ${x === "light" ? "✓" : ""}`, onClick: () => I("light") },
|
|
1411
|
+
{ label: `Theme: Dark ${x === "dark" ? "✓" : ""}`, onClick: () => I("dark") },
|
|
1412
|
+
{ label: `Theme: System ${x === "system" ? "✓" : ""}`, onClick: () => I("system") },
|
|
1413
|
+
{ label: `Theme: Liquid Glass ${x === "glass" ? "✓" : ""}`, onClick: () => I("glass") }
|
|
1414
|
+
]
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
label: "Help",
|
|
1418
|
+
items: [
|
|
1419
|
+
{ label: "Welcome Screen", onClick: () => y.getState().setWelcomeOpen(!0) },
|
|
1420
|
+
{ label: "About Entropia Riko", onClick: () => y.getState().setAboutOpen(!0) }
|
|
1421
|
+
]
|
|
1422
|
+
}
|
|
1423
|
+
].map((g) => /* @__PURE__ */ p(
|
|
1424
|
+
"div",
|
|
1425
|
+
{
|
|
1426
|
+
className: `menu-item ${m === g.label ? "active" : ""}`,
|
|
1427
|
+
onClick: () => c(m === g.label ? null : g.label),
|
|
1428
|
+
children: [
|
|
1429
|
+
g.label,
|
|
1430
|
+
m === g.label && /* @__PURE__ */ a("div", { className: "dropdown", onClick: (F) => F.stopPropagation(), children: g.items.map(
|
|
1431
|
+
(F, C) => F.submenu ? /* @__PURE__ */ p("div", { className: "dropdown-item has-submenu", children: [
|
|
1432
|
+
/* @__PURE__ */ p("span", { className: "submenu-label", children: [
|
|
1433
|
+
F.label,
|
|
1434
|
+
" ▸"
|
|
1435
|
+
] }),
|
|
1436
|
+
/* @__PURE__ */ a("div", { className: "submenu", children: F.submenu.map((E, H) => /* @__PURE__ */ a(
|
|
1437
|
+
"div",
|
|
1438
|
+
{
|
|
1439
|
+
className: `dropdown-item ${E.disabled ? "disabled" : ""}`,
|
|
1440
|
+
onClick: () => {
|
|
1441
|
+
var G;
|
|
1442
|
+
E.disabled || ((G = E.onClick) == null || G.call(E), c(null));
|
|
1443
|
+
},
|
|
1444
|
+
children: E.label
|
|
1445
|
+
},
|
|
1446
|
+
H
|
|
1447
|
+
)) })
|
|
1448
|
+
] }, C) : /* @__PURE__ */ a(
|
|
1449
|
+
"div",
|
|
1450
|
+
{
|
|
1451
|
+
className: `dropdown-item ${F.disabled ? "disabled" : ""}`,
|
|
1452
|
+
onClick: () => {
|
|
1453
|
+
var E;
|
|
1454
|
+
F.disabled || ((E = F.onClick) == null || E.call(F), c(null));
|
|
1455
|
+
},
|
|
1456
|
+
children: F.label
|
|
1457
|
+
},
|
|
1458
|
+
C
|
|
1459
|
+
)
|
|
1460
|
+
) })
|
|
1461
|
+
]
|
|
1462
|
+
},
|
|
1463
|
+
g.label
|
|
1464
|
+
)),
|
|
1465
|
+
/* @__PURE__ */ a("div", { className: "menubar-sep" }),
|
|
1466
|
+
/* @__PURE__ */ a(
|
|
1467
|
+
mn,
|
|
1468
|
+
{
|
|
1469
|
+
workspaces: n,
|
|
1470
|
+
activeId: e,
|
|
1471
|
+
onSwitch: t,
|
|
1472
|
+
onAdd: s,
|
|
1473
|
+
onRemove: r,
|
|
1474
|
+
onRename: o,
|
|
1475
|
+
onDuplicate: i,
|
|
1476
|
+
onMove: d
|
|
1477
|
+
}
|
|
1478
|
+
),
|
|
1479
|
+
/* @__PURE__ */ a(
|
|
1480
|
+
"input",
|
|
1481
|
+
{
|
|
1482
|
+
ref: j,
|
|
1483
|
+
type: "file",
|
|
1484
|
+
accept: ".riko,.ric,.json,application/json,application/octet-stream",
|
|
1485
|
+
style: { display: "none" },
|
|
1486
|
+
onChange: (g) => {
|
|
1487
|
+
var C;
|
|
1488
|
+
const F = (C = g.target.files) == null ? void 0 : C[0];
|
|
1489
|
+
F && l(F), g.target.value = "";
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
),
|
|
1493
|
+
/* @__PURE__ */ a("div", { className: "spacer" }),
|
|
1494
|
+
/* @__PURE__ */ p("span", { className: `status-pill ${u}`, children: [
|
|
1495
|
+
u,
|
|
1496
|
+
u !== "idle" && /* @__PURE__ */ a(
|
|
1497
|
+
"button",
|
|
1498
|
+
{
|
|
1499
|
+
className: "status-pill-close",
|
|
1500
|
+
title: "Dismiss",
|
|
1501
|
+
onClick: () => y.setState({ status: "idle" }),
|
|
1502
|
+
children: "✕"
|
|
1503
|
+
}
|
|
1504
|
+
)
|
|
1505
|
+
] })
|
|
1506
|
+
] });
|
|
1507
|
+
}
|
|
1508
|
+
function yn() {
|
|
1509
|
+
const n = y((t) => t.dirty), e = y((t) => t.activeFileName);
|
|
1510
|
+
return /* @__PURE__ */ p("div", { className: "titlebar", children: [
|
|
1511
|
+
/* @__PURE__ */ a("span", { className: `titlebar-dirty ${n ? "visible" : ""}`, "aria-hidden": !n, children: "*" }),
|
|
1512
|
+
/* @__PURE__ */ a("span", { className: "titlebar-file", children: e ?? "Untitled" }),
|
|
1513
|
+
/* @__PURE__ */ a("span", { className: "titlebar-app", children: " - Riko" }),
|
|
1514
|
+
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
1515
|
+
/* @__PURE__ */ p("span", { className: "titlebar-version", children: [
|
|
1516
|
+
"v",
|
|
1517
|
+
Z
|
|
1518
|
+
] })
|
|
1519
|
+
] });
|
|
1520
|
+
}
|
|
1521
|
+
function Me(n, e) {
|
|
1522
|
+
const t = $(null);
|
|
1523
|
+
return (r) => {
|
|
1524
|
+
r.preventDefault(), t.current = { x: r.clientX, y: r.clientY };
|
|
1525
|
+
const o = (d) => {
|
|
1526
|
+
t.current && (n(d.clientX - t.current.x, d.clientY - t.current.y), t.current = { x: d.clientX, y: d.clientY });
|
|
1527
|
+
}, i = () => {
|
|
1528
|
+
document.removeEventListener("mousemove", o), document.removeEventListener("mouseup", i), document.body.style.cursor = "";
|
|
1529
|
+
};
|
|
1530
|
+
document.body.style.cursor = e, document.addEventListener("mousemove", o), document.addEventListener("mouseup", i);
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
function bn({ onDrag: n }) {
|
|
1534
|
+
const e = $(null), t = Me((s) => {
|
|
1535
|
+
var o, i;
|
|
1536
|
+
const r = ((i = (o = e.current) == null ? void 0 : o.parentElement) == null ? void 0 : i.clientWidth) ?? 1;
|
|
1537
|
+
n(s, r);
|
|
1538
|
+
}, "col-resize");
|
|
1539
|
+
return /* @__PURE__ */ a("div", { ref: e, className: "splitter-v", onMouseDown: t });
|
|
1540
|
+
}
|
|
1541
|
+
function vn({ onDrag: n }) {
|
|
1542
|
+
const e = $(null), t = Me((s, r) => {
|
|
1543
|
+
var i, d;
|
|
1544
|
+
const o = ((d = (i = e.current) == null ? void 0 : i.parentElement) == null ? void 0 : d.clientHeight) ?? 1;
|
|
1545
|
+
n(r, o);
|
|
1546
|
+
}, "row-resize");
|
|
1547
|
+
return /* @__PURE__ */ a("div", { ref: e, className: "splitter-h", onMouseDown: t });
|
|
1548
|
+
}
|
|
1549
|
+
function Nn() {
|
|
1550
|
+
const [n, e] = P(""), t = y((d) => d.nodeDefs), s = y((d) => d.addNode), r = y((d) => d.addFrame), o = z(() => {
|
|
1551
|
+
const d = t.filter(
|
|
1552
|
+
(c) => c.label.toLowerCase().includes(n.toLowerCase()) || c.type.toLowerCase().includes(n.toLowerCase())
|
|
1553
|
+
), m = {};
|
|
1554
|
+
for (const c of d) {
|
|
1555
|
+
const u = c.category || "Other";
|
|
1556
|
+
(m[u] ?? (m[u] = [])).push(c);
|
|
1557
|
+
}
|
|
1558
|
+
return m;
|
|
1559
|
+
}, [t, n]), i = Object.keys(o).sort();
|
|
1560
|
+
return /* @__PURE__ */ p("div", { className: "node-library", children: [
|
|
1561
|
+
/* @__PURE__ */ p("div", { className: "search", children: [
|
|
1562
|
+
/* @__PURE__ */ a(
|
|
1563
|
+
"input",
|
|
1564
|
+
{
|
|
1565
|
+
placeholder: "Search nodes…",
|
|
1566
|
+
value: n,
|
|
1567
|
+
onChange: (d) => e(d.target.value)
|
|
1568
|
+
}
|
|
1569
|
+
),
|
|
1570
|
+
/* @__PURE__ */ a(
|
|
1571
|
+
"button",
|
|
1572
|
+
{
|
|
1573
|
+
className: "btn btn-sm",
|
|
1574
|
+
style: { width: "100%", marginTop: 4 },
|
|
1575
|
+
onClick: () => r("Frame", { x: 200, y: 120 }),
|
|
1576
|
+
children: "+ Frame"
|
|
1577
|
+
}
|
|
1578
|
+
)
|
|
1579
|
+
] }),
|
|
1580
|
+
/* @__PURE__ */ p("div", { className: "node-list", children: [
|
|
1581
|
+
i.map((d) => /* @__PURE__ */ a(
|
|
1582
|
+
kn,
|
|
1583
|
+
{
|
|
1584
|
+
category: d,
|
|
1585
|
+
nodes: o[d],
|
|
1586
|
+
onAdd: (m) => s(m, { x: 200 + Math.random() * 200, y: 120 + Math.random() * 100 })
|
|
1587
|
+
},
|
|
1588
|
+
d
|
|
1589
|
+
)),
|
|
1590
|
+
i.length === 0 && /* @__PURE__ */ a("div", { className: "category", style: { padding: 8 }, children: "No matching nodes" }),
|
|
1591
|
+
t.length < 10 && /* @__PURE__ */ p("div", { style: { padding: "8px", color: "var(--color-error)", fontSize: 12, lineHeight: 1.5 }, children: [
|
|
1592
|
+
"⚠ 仅 ",
|
|
1593
|
+
t.length,
|
|
1594
|
+
" 个节点。请确保 API server 已启动。"
|
|
1595
|
+
] })
|
|
1596
|
+
] })
|
|
1597
|
+
] });
|
|
1598
|
+
}
|
|
1599
|
+
function kn({
|
|
1600
|
+
category: n,
|
|
1601
|
+
nodes: e,
|
|
1602
|
+
onAdd: t
|
|
1603
|
+
}) {
|
|
1604
|
+
const [s, r] = P(!0);
|
|
1605
|
+
return /* @__PURE__ */ p("div", { className: "node-category", children: [
|
|
1606
|
+
/* @__PURE__ */ p(
|
|
1607
|
+
"div",
|
|
1608
|
+
{
|
|
1609
|
+
className: "category-header",
|
|
1610
|
+
onClick: () => r(!s),
|
|
1611
|
+
children: [
|
|
1612
|
+
/* @__PURE__ */ a("span", { className: "category-toggle", children: s ? "▼" : "▶" }),
|
|
1613
|
+
/* @__PURE__ */ a("span", { className: "category-name", children: n }),
|
|
1614
|
+
/* @__PURE__ */ a("span", { className: "category-count", children: e.length })
|
|
1615
|
+
]
|
|
1616
|
+
}
|
|
1617
|
+
),
|
|
1618
|
+
s && e.map((o) => /* @__PURE__ */ a(
|
|
1619
|
+
"div",
|
|
1620
|
+
{
|
|
1621
|
+
className: "node-item",
|
|
1622
|
+
onClick: () => t(o.type),
|
|
1623
|
+
children: /* @__PURE__ */ a("div", { className: "name", children: o.label })
|
|
1624
|
+
},
|
|
1625
|
+
o.type
|
|
1626
|
+
))
|
|
1627
|
+
] });
|
|
1628
|
+
}
|
|
1629
|
+
function wn() {
|
|
1630
|
+
const n = y((h) => h.fileList), e = y((h) => h.activeFileName), t = y((h) => h.refreshFiles), s = y((h) => h.openFile), r = y((h) => h.saveFileToDisk), o = y((h) => h.importFileInline), i = y((h) => h.previewAssetCode), [d, m] = P("");
|
|
1631
|
+
D(() => {
|
|
1632
|
+
t();
|
|
1633
|
+
}, [t]);
|
|
1634
|
+
const c = z(() => {
|
|
1635
|
+
const h = d.trim().toLowerCase(), v = {}, l = [];
|
|
1636
|
+
for (const w of n) {
|
|
1637
|
+
if (h && !w.name.toLowerCase().includes(h) && !w.path.toLowerCase().includes(h)) continue;
|
|
1638
|
+
const f = w.path.includes("/") ? w.path.slice(0, w.path.indexOf("/")) : "(root)";
|
|
1639
|
+
f in v || (v[f] = [], l.push(f)), v[f].push(w);
|
|
1640
|
+
}
|
|
1641
|
+
return l.sort(), { map: v, order: l };
|
|
1642
|
+
}, [n, d]);
|
|
1643
|
+
return /* @__PURE__ */ p("div", { className: "file-manager", children: [
|
|
1644
|
+
/* @__PURE__ */ p("div", { className: "panel-head", children: [
|
|
1645
|
+
/* @__PURE__ */ a("span", { className: "panel-head-title", children: "Asset Library" }),
|
|
1646
|
+
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
1647
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Save current graph", onClick: () => {
|
|
1648
|
+
const h = window.prompt("Save current graph as", e ?? "untitled");
|
|
1649
|
+
h && h.trim() && r(h.trim());
|
|
1650
|
+
}, children: "💾" }),
|
|
1651
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Refresh", onClick: t, children: "↻" })
|
|
1652
|
+
] }),
|
|
1653
|
+
/* @__PURE__ */ a("div", { className: "asset-search", children: /* @__PURE__ */ a(
|
|
1654
|
+
"input",
|
|
1655
|
+
{
|
|
1656
|
+
placeholder: "Search assets…",
|
|
1657
|
+
value: d,
|
|
1658
|
+
onChange: (h) => m(h.target.value)
|
|
1659
|
+
}
|
|
1660
|
+
) }),
|
|
1661
|
+
/* @__PURE__ */ p("div", { className: "fm-tree", children: [
|
|
1662
|
+
c.order.map((h) => /* @__PURE__ */ p("div", { className: "fm-group", children: [
|
|
1663
|
+
/* @__PURE__ */ a("div", { className: "fm-group-header", children: h }),
|
|
1664
|
+
c.map[h].map((v) => /* @__PURE__ */ p(
|
|
1665
|
+
"div",
|
|
1666
|
+
{
|
|
1667
|
+
className: `fm-file ${e === v.name ? "active" : ""}`,
|
|
1668
|
+
onClick: () => s(v.path),
|
|
1669
|
+
title: v.path,
|
|
1670
|
+
children: [
|
|
1671
|
+
/* @__PURE__ */ a("span", { className: "fm-toggle", children: "▤" }),
|
|
1672
|
+
/* @__PURE__ */ a("span", { className: "fm-name", children: v.name }),
|
|
1673
|
+
v.format === "binary" && /* @__PURE__ */ a("span", { className: "fm-format-badge", title: "binary .ric", children: "BIN" }),
|
|
1674
|
+
/* @__PURE__ */ a(
|
|
1675
|
+
"button",
|
|
1676
|
+
{
|
|
1677
|
+
className: "fm-btn",
|
|
1678
|
+
title: "Preview PyTorch code",
|
|
1679
|
+
onClick: (l) => {
|
|
1680
|
+
l.stopPropagation(), i(v.path);
|
|
1681
|
+
},
|
|
1682
|
+
children: "⤡"
|
|
1683
|
+
}
|
|
1684
|
+
),
|
|
1685
|
+
/* @__PURE__ */ a(
|
|
1686
|
+
"button",
|
|
1687
|
+
{
|
|
1688
|
+
className: "fm-btn",
|
|
1689
|
+
title: "Expand full nodes into canvas",
|
|
1690
|
+
onClick: (l) => {
|
|
1691
|
+
l.stopPropagation(), o(v.path);
|
|
1692
|
+
},
|
|
1693
|
+
children: "⤢"
|
|
1694
|
+
}
|
|
1695
|
+
)
|
|
1696
|
+
]
|
|
1697
|
+
},
|
|
1698
|
+
v.path
|
|
1699
|
+
))
|
|
1700
|
+
] }, h)),
|
|
1701
|
+
n.length === 0 && /* @__PURE__ */ a("div", { className: "category", style: { padding: 8 }, children: "No .riko files found." }),
|
|
1702
|
+
n.length > 0 && c.order.length === 0 && /* @__PURE__ */ p("div", { className: "category", style: { padding: 8 }, children: [
|
|
1703
|
+
"No matches for “",
|
|
1704
|
+
d,
|
|
1705
|
+
"”."
|
|
1706
|
+
] })
|
|
1707
|
+
] })
|
|
1708
|
+
] });
|
|
1709
|
+
}
|
|
1710
|
+
let re = [];
|
|
1711
|
+
function _n(n) {
|
|
1712
|
+
re = n;
|
|
1713
|
+
}
|
|
1714
|
+
function _e() {
|
|
1715
|
+
return re;
|
|
1716
|
+
}
|
|
1717
|
+
let xe = 0;
|
|
1718
|
+
function Se(n) {
|
|
1719
|
+
return xe += 1, `${n}_${Date.now()}_${xe}`;
|
|
1720
|
+
}
|
|
1721
|
+
async function xn() {
|
|
1722
|
+
const n = y.getState(), e = re.filter((l) => n.nodes.some((w) => w.id === l));
|
|
1723
|
+
if (e.length === 0) {
|
|
1724
|
+
n.pushToast("error", "先框选一些节点再创建子图");
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
const t = window.prompt("Subgraph name", "subgraph");
|
|
1728
|
+
if (!t || !t.trim()) return;
|
|
1729
|
+
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)), m = [], c = [], u = (l) => l === 0 ? "input" : `input_${l + 1}`, h = (l) => l === 0 ? "output" : `output_${l + 1}`;
|
|
1730
|
+
i.forEach((l, w) => {
|
|
1731
|
+
const f = u(w), S = `gin_${w}`;
|
|
1732
|
+
m.push({
|
|
1733
|
+
id: S,
|
|
1734
|
+
type_name: "graph_input",
|
|
1735
|
+
label: `Input ${w}`,
|
|
1736
|
+
category: "Subgraph",
|
|
1737
|
+
position: [0, w * 90],
|
|
1738
|
+
parameters: { name: f, data_kind: "tensor" },
|
|
1739
|
+
inputs: [],
|
|
1740
|
+
outputs: []
|
|
1741
|
+
}), c.push({
|
|
1742
|
+
id: `be_in_${w}`,
|
|
1743
|
+
source_node: S,
|
|
1744
|
+
source_port: "value",
|
|
1745
|
+
target_node: l.target,
|
|
1746
|
+
target_port: l.targetHandle
|
|
1747
|
+
});
|
|
1748
|
+
}), d.forEach((l, w) => {
|
|
1749
|
+
const f = h(w), S = `gout_${w}`;
|
|
1750
|
+
m.push({
|
|
1751
|
+
id: S,
|
|
1752
|
+
type_name: "graph_output",
|
|
1753
|
+
label: `Output ${w}`,
|
|
1754
|
+
category: "Subgraph",
|
|
1755
|
+
position: [1200, w * 90],
|
|
1756
|
+
parameters: { name: f, data_kind: "tensor" },
|
|
1757
|
+
inputs: [],
|
|
1758
|
+
outputs: []
|
|
1759
|
+
}), c.push({
|
|
1760
|
+
id: `be_out_${w}`,
|
|
1761
|
+
source_node: l.source,
|
|
1762
|
+
source_port: l.sourceHandle,
|
|
1763
|
+
target_node: S,
|
|
1764
|
+
target_port: "value"
|
|
1765
|
+
});
|
|
1766
|
+
});
|
|
1767
|
+
const v = {
|
|
1768
|
+
version: "1.0",
|
|
1769
|
+
metadata: { name: t.trim(), description: "" },
|
|
1770
|
+
nodes: [
|
|
1771
|
+
...r.map((l) => ({
|
|
1772
|
+
id: l.id,
|
|
1773
|
+
type_name: l.data.type,
|
|
1774
|
+
label: l.data.label,
|
|
1775
|
+
category: l.data.category,
|
|
1776
|
+
position: [l.position.x, l.position.y],
|
|
1777
|
+
parameters: l.data.params,
|
|
1778
|
+
inputs: [],
|
|
1779
|
+
outputs: []
|
|
1780
|
+
})),
|
|
1781
|
+
...m
|
|
1782
|
+
],
|
|
1783
|
+
edges: [
|
|
1784
|
+
...o.map((l) => ({
|
|
1785
|
+
id: l.id,
|
|
1786
|
+
source_node: l.source,
|
|
1787
|
+
source_port: l.sourceHandle,
|
|
1788
|
+
target_node: l.target,
|
|
1789
|
+
target_port: l.targetHandle
|
|
1790
|
+
})),
|
|
1791
|
+
...c
|
|
1792
|
+
],
|
|
1793
|
+
settings: {}
|
|
1794
|
+
};
|
|
1795
|
+
try {
|
|
1796
|
+
const w = await (await fetch("/api/files/save", {
|
|
1797
|
+
method: "POST",
|
|
1798
|
+
headers: { "Content-Type": "application/json" },
|
|
1799
|
+
body: JSON.stringify({ name: t.trim(), format: "ascii", doc: v })
|
|
1800
|
+
})).json();
|
|
1801
|
+
if (w.status !== "success") {
|
|
1802
|
+
n.pushToast("error", w.error ?? "子图保存失败");
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
const f = r.reduce((_, k) => _ + k.position.x, 0) / r.length, S = r.reduce((_, k) => _ + k.position.y, 0) / r.length, b = n.nodeDefs.find((_) => _.type === "import") ?? n.nodeDefs.find((_) => _.type === "graph_reference"), T = {
|
|
1806
|
+
id: `imp_${Date.now()}`,
|
|
1807
|
+
type: "thnNode",
|
|
1808
|
+
position: { x: f, y: S },
|
|
1809
|
+
data: {
|
|
1810
|
+
type: (b == null ? void 0 : b.type) ?? "import",
|
|
1811
|
+
label: t.trim(),
|
|
1812
|
+
category: "Subgraph",
|
|
1813
|
+
params: (b == null ? void 0 : b.type) === "import" ? { module: t.trim() } : { file: `${t.trim()}.riko` },
|
|
1814
|
+
inputs: (b == null ? void 0 : b.inputs) ?? [],
|
|
1815
|
+
outputs: (b == null ? void 0 : b.outputs) ?? [],
|
|
1816
|
+
hasError: !1
|
|
1817
|
+
}
|
|
1818
|
+
}, x = n.edges.filter(
|
|
1819
|
+
(_) => !s.has(_.source) && !s.has(_.target)
|
|
1820
|
+
), I = 3;
|
|
1821
|
+
i.slice(0, I).forEach((_, k) => {
|
|
1822
|
+
x.push({
|
|
1823
|
+
id: Se("rwin"),
|
|
1824
|
+
source: _.source,
|
|
1825
|
+
target: T.id,
|
|
1826
|
+
sourceHandle: _.sourceHandle,
|
|
1827
|
+
targetHandle: u(k)
|
|
1828
|
+
});
|
|
1829
|
+
}), d.slice(0, I).forEach((_, k) => {
|
|
1830
|
+
x.push({
|
|
1831
|
+
id: Se("rwout"),
|
|
1832
|
+
source: T.id,
|
|
1833
|
+
target: _.target,
|
|
1834
|
+
sourceHandle: h(k),
|
|
1835
|
+
targetHandle: _.targetHandle
|
|
1836
|
+
});
|
|
1837
|
+
}), y.setState({
|
|
1838
|
+
nodes: [...n.nodes.filter((_) => !s.has(_.id)), T],
|
|
1839
|
+
edges: x,
|
|
1840
|
+
selectedNodeId: null,
|
|
1841
|
+
dirty: !0
|
|
1842
|
+
}), re = [];
|
|
1843
|
+
const j = Math.max(0, i.length - I), N = Math.max(0, d.length - I);
|
|
1844
|
+
j || N ? n.pushToast(
|
|
1845
|
+
"info",
|
|
1846
|
+
`子图已创建,但超过 3 入/3 出的边界连线有 ${j} 入/${N} 出 未保留`
|
|
1847
|
+
) : n.pushToast("success", `已创建子图 ${t.trim()}(边界连线已保留)`), n.refreshFiles();
|
|
1848
|
+
} catch (l) {
|
|
1849
|
+
n.pushToast("error", l instanceof Error ? l.message : String(l));
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
function Sn({ id: n, data: e, selected: t }) {
|
|
1853
|
+
const s = e, r = y((o) => o.results[n]);
|
|
1854
|
+
return /* @__PURE__ */ p("div", { className: `thn-node ${t ? "selected" : ""}`, children: [
|
|
1855
|
+
/* @__PURE__ */ a("div", { className: "header", children: s.label }),
|
|
1856
|
+
/* @__PURE__ */ p("div", { className: "ports", children: [
|
|
1857
|
+
s.inputs.map((o) => /* @__PURE__ */ p("div", { className: "port in", children: [
|
|
1858
|
+
/* @__PURE__ */ a(
|
|
1859
|
+
ve,
|
|
1860
|
+
{
|
|
1861
|
+
type: "target",
|
|
1862
|
+
position: Ne.Left,
|
|
1863
|
+
id: o.name,
|
|
1864
|
+
style: { left: -5 }
|
|
1865
|
+
}
|
|
1866
|
+
),
|
|
1867
|
+
/* @__PURE__ */ a("span", { className: "label", children: o.label })
|
|
1868
|
+
] }, o.name)),
|
|
1869
|
+
s.outputs.map((o) => /* @__PURE__ */ p("div", { className: "port out", children: [
|
|
1870
|
+
/* @__PURE__ */ a("span", { className: "label", children: o.label }),
|
|
1871
|
+
/* @__PURE__ */ a(
|
|
1872
|
+
ve,
|
|
1873
|
+
{
|
|
1874
|
+
type: "source",
|
|
1875
|
+
position: Ne.Right,
|
|
1876
|
+
id: o.name,
|
|
1877
|
+
style: { right: -5 }
|
|
1878
|
+
}
|
|
1879
|
+
)
|
|
1880
|
+
] }, o.name))
|
|
1881
|
+
] }),
|
|
1882
|
+
s.hasError && /* @__PURE__ */ p(
|
|
1883
|
+
"div",
|
|
1884
|
+
{
|
|
1885
|
+
className: "result",
|
|
1886
|
+
style: { color: "var(--color-error)" },
|
|
1887
|
+
children: [
|
|
1888
|
+
"⚠ ",
|
|
1889
|
+
s.errorMsg || "error"
|
|
1890
|
+
]
|
|
1891
|
+
}
|
|
1892
|
+
),
|
|
1893
|
+
r && Object.entries(r).map(([o, i]) => /* @__PURE__ */ p("div", { className: "result", children: [
|
|
1894
|
+
o,
|
|
1895
|
+
" = ",
|
|
1896
|
+
i.summary
|
|
1897
|
+
] }, o))
|
|
1898
|
+
] });
|
|
1899
|
+
}
|
|
1900
|
+
function Cn({ state: n, onClose: e }) {
|
|
1901
|
+
const t = y((i) => i.nodeDefs), s = y((i) => i.addNode), r = y((i) => i.addFrame), o = z(
|
|
1902
|
+
() => [
|
|
1903
|
+
{
|
|
1904
|
+
id: "create_subgraph",
|
|
1905
|
+
label: `⤵ Create Subgraph (${_e().length} selected)`,
|
|
1906
|
+
description: "collapse selection into an import node",
|
|
1907
|
+
category: "Subgraph",
|
|
1908
|
+
disabled: _e().length === 0,
|
|
1909
|
+
onSelect: () => xn()
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
id: "add_frame",
|
|
1913
|
+
label: "+ Add Frame",
|
|
1914
|
+
description: "visual grouping box",
|
|
1915
|
+
category: "Frame",
|
|
1916
|
+
onSelect: () => r("Frame", { x: n.x - 200, y: n.y - 150 })
|
|
1917
|
+
},
|
|
1918
|
+
...t.map((i) => ({
|
|
1919
|
+
id: i.type,
|
|
1920
|
+
label: i.label,
|
|
1921
|
+
description: i.type,
|
|
1922
|
+
category: i.category || "Other",
|
|
1923
|
+
onSelect: () => s(i.type, { x: n.x - 200, y: n.y - 150 })
|
|
1924
|
+
}))
|
|
1925
|
+
],
|
|
1926
|
+
[t, s, r, n.x, n.y]
|
|
1927
|
+
);
|
|
1928
|
+
return n.visible ? /* @__PURE__ */ a(te, { entries: o, x: n.x, y: n.y, onClose: e, searchable: !0 }) : null;
|
|
1929
|
+
}
|
|
1930
|
+
const Tn = { thnNode: Sn };
|
|
1931
|
+
function Pn() {
|
|
1932
|
+
const n = y((f) => f.nodes), e = y((f) => f.edges), t = y((f) => f.frames), s = y((f) => f.updateFrame), r = y((f) => f.removeFrame), o = y((f) => f.onNodesChange), i = y((f) => f.onEdgesChange), d = y((f) => f.onConnect), m = y((f) => f.onSelectNode), c = y((f) => f.enterSubgraph), { zoomIn: u, zoomOut: h, fitView: v, getViewport: l } = Xe(), w = (f, S) => {
|
|
1933
|
+
if (f.target.closest("button")) return;
|
|
1934
|
+
f.stopPropagation();
|
|
1935
|
+
const b = f.clientX, T = f.clientY, x = S.x, I = S.y, j = (_) => {
|
|
1936
|
+
const k = l().zoom || 1;
|
|
1937
|
+
s(S.id, {
|
|
1938
|
+
x: x + (_.clientX - b) / k,
|
|
1939
|
+
y: I + (_.clientY - T) / k
|
|
1940
|
+
});
|
|
1941
|
+
}, N = () => {
|
|
1942
|
+
document.removeEventListener("mousemove", j), document.removeEventListener("mouseup", N);
|
|
1943
|
+
};
|
|
1944
|
+
document.addEventListener("mousemove", j), document.addEventListener("mouseup", N);
|
|
1945
|
+
};
|
|
1946
|
+
return /* @__PURE__ */ p(B, { children: [
|
|
1947
|
+
/* @__PURE__ */ p(
|
|
1948
|
+
Qe,
|
|
1949
|
+
{
|
|
1950
|
+
nodes: n,
|
|
1951
|
+
edges: e,
|
|
1952
|
+
nodeTypes: Tn,
|
|
1953
|
+
onNodesChange: o,
|
|
1954
|
+
onEdgesChange: i,
|
|
1955
|
+
onConnect: d,
|
|
1956
|
+
onNodeClick: (f, S) => m(S.id),
|
|
1957
|
+
onNodeDoubleClick: (f, S) => {
|
|
1958
|
+
(S.data.type === "graph_reference" || S.data.type === "import") && c(S.id);
|
|
1959
|
+
},
|
|
1960
|
+
onPaneClick: () => m(null),
|
|
1961
|
+
onSelectionChange: ({ nodes: f }) => _n(f.map((S) => S.id)),
|
|
1962
|
+
selectionOnDrag: !0,
|
|
1963
|
+
panOnDrag: [2],
|
|
1964
|
+
fitView: !0,
|
|
1965
|
+
children: [
|
|
1966
|
+
/* @__PURE__ */ a(Ze, {}),
|
|
1967
|
+
/* @__PURE__ */ a(en, { children: t.map((f) => /* @__PURE__ */ a(
|
|
1968
|
+
"div",
|
|
1969
|
+
{
|
|
1970
|
+
className: "graph-frame",
|
|
1971
|
+
style: { left: f.x, top: f.y, width: f.width, height: f.height },
|
|
1972
|
+
onMouseDown: (S) => w(S, f),
|
|
1973
|
+
onDoubleClick: () => {
|
|
1974
|
+
const S = window.prompt("Frame title", f.title);
|
|
1975
|
+
S !== null && s(f.id, { title: S });
|
|
1976
|
+
},
|
|
1977
|
+
children: /* @__PURE__ */ p("div", { className: "graph-frame-title", children: [
|
|
1978
|
+
f.title,
|
|
1979
|
+
/* @__PURE__ */ a("button", { className: "graph-frame-close", onClick: () => r(f.id), children: "✕" })
|
|
1980
|
+
] })
|
|
1981
|
+
},
|
|
1982
|
+
f.id
|
|
1983
|
+
)) })
|
|
1984
|
+
]
|
|
1985
|
+
}
|
|
1986
|
+
),
|
|
1987
|
+
/* @__PURE__ */ p("div", { className: "graph-zoom", children: [
|
|
1988
|
+
/* @__PURE__ */ a("button", { onClick: () => u(), title: "Zoom in", children: "+" }),
|
|
1989
|
+
/* @__PURE__ */ a("button", { onClick: () => v(), title: "Fit view", children: "⤢" }),
|
|
1990
|
+
/* @__PURE__ */ a("button", { onClick: () => h(), title: "Zoom out", children: "−" })
|
|
1991
|
+
] })
|
|
1992
|
+
] });
|
|
1993
|
+
}
|
|
1994
|
+
function Fn() {
|
|
1995
|
+
const [n, e] = P({ x: 0, y: 0, visible: !1 }), t = y((r) => r.graphStack), s = y((r) => r.exitToLevel);
|
|
1996
|
+
return /* @__PURE__ */ p(
|
|
1997
|
+
"div",
|
|
1998
|
+
{
|
|
1999
|
+
className: "graph-canvas",
|
|
2000
|
+
onContextMenu: (r) => {
|
|
2001
|
+
r.preventDefault(), e({ x: r.clientX, y: r.clientY, visible: !0 });
|
|
2002
|
+
},
|
|
2003
|
+
children: [
|
|
2004
|
+
/* @__PURE__ */ a(Ye, { children: /* @__PURE__ */ a(Pn, {}) }),
|
|
2005
|
+
/* @__PURE__ */ a("div", { className: "graph-breadcrumb", children: t.map((r, o) => /* @__PURE__ */ p("span", { className: "graph-breadcrumb-wrap", children: [
|
|
2006
|
+
o > 0 && /* @__PURE__ */ a("span", { className: "graph-breadcrumb-sep", children: "/" }),
|
|
2007
|
+
/* @__PURE__ */ a(
|
|
2008
|
+
"span",
|
|
2009
|
+
{
|
|
2010
|
+
className: `graph-breadcrumb-crumb ${o === t.length - 1 ? "current" : ""}`,
|
|
2011
|
+
onClick: () => {
|
|
2012
|
+
o < t.length - 1 && s(o);
|
|
2013
|
+
},
|
|
2014
|
+
children: r.name || "root"
|
|
2015
|
+
}
|
|
2016
|
+
)
|
|
2017
|
+
] }, o)) }),
|
|
2018
|
+
/* @__PURE__ */ a(Cn, { state: n, onClose: () => e({ x: 0, y: 0, visible: !1 }) })
|
|
2019
|
+
]
|
|
2020
|
+
}
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
const En = [
|
|
2024
|
+
{ id: "summary", label: "Summary", icon: "▤" },
|
|
2025
|
+
{ id: "params", label: "Params", icon: "⚙" },
|
|
2026
|
+
{ id: "inputs", label: "Inputs", icon: "→" },
|
|
2027
|
+
{ id: "outputs", label: "Outputs", icon: "←" },
|
|
2028
|
+
{ id: "preview", label: "Preview", icon: "◉" }
|
|
2029
|
+
];
|
|
2030
|
+
function On({ style: n }) {
|
|
2031
|
+
const e = y((u) => u.selectedNodeId), t = y((u) => u.nodes), s = y((u) => u.results), r = y((u) => u.updateParam), o = y((u) => u.removeNode), i = y((u) => u.nodeDefs), [d, m] = P("summary"), c = t.find((u) => u.id === e);
|
|
2032
|
+
return /* @__PURE__ */ p("div", { className: "side-inspector", style: n, children: [
|
|
2033
|
+
/* @__PURE__ */ a("div", { className: "inspector-tags", children: En.map((u) => /* @__PURE__ */ p(
|
|
2034
|
+
"button",
|
|
2035
|
+
{
|
|
2036
|
+
className: `inspector-tag ${d === u.id ? "active" : ""}`,
|
|
2037
|
+
onClick: () => m(u.id),
|
|
2038
|
+
title: u.label,
|
|
2039
|
+
children: [
|
|
2040
|
+
/* @__PURE__ */ a("span", { className: "inspector-tag-icon", children: u.icon }),
|
|
2041
|
+
/* @__PURE__ */ a("span", { className: "inspector-tag-label", children: u.label })
|
|
2042
|
+
]
|
|
2043
|
+
},
|
|
2044
|
+
u.id
|
|
2045
|
+
)) }),
|
|
2046
|
+
/* @__PURE__ */ a("div", { className: "inspector-body", children: c ? /* @__PURE__ */ p(B, { children: [
|
|
2047
|
+
d === "summary" && /* @__PURE__ */ a(In, { node: c, onDelete: () => o(c.id) }),
|
|
2048
|
+
d === "params" && /* @__PURE__ */ a(
|
|
2049
|
+
Rn,
|
|
2050
|
+
{
|
|
2051
|
+
node: c,
|
|
2052
|
+
def: i.find((u) => u.type === c.data.type),
|
|
2053
|
+
onParam: (u, h) => r(c.id, u, h)
|
|
2054
|
+
}
|
|
2055
|
+
),
|
|
2056
|
+
d === "inputs" && /* @__PURE__ */ a(Ce, { title: "Inputs", ports: c.data.inputs, dir: "in" }),
|
|
2057
|
+
d === "outputs" && /* @__PURE__ */ a(Ce, { title: "Outputs", ports: c.data.outputs, dir: "out" }),
|
|
2058
|
+
d === "preview" && /* @__PURE__ */ a(jn, { result: s[c.id] })
|
|
2059
|
+
] }) : /* @__PURE__ */ a("div", { className: "empty", children: "Select a node to edit its parameters" }) })
|
|
2060
|
+
] });
|
|
2061
|
+
}
|
|
2062
|
+
function In({
|
|
2063
|
+
node: n,
|
|
2064
|
+
onDelete: e
|
|
2065
|
+
}) {
|
|
2066
|
+
const t = n.data;
|
|
2067
|
+
return /* @__PURE__ */ p("div", { className: "section", children: [
|
|
2068
|
+
/* @__PURE__ */ a("h3", { children: "Summary" }),
|
|
2069
|
+
/* @__PURE__ */ a("div", { children: t.label }),
|
|
2070
|
+
/* @__PURE__ */ p("div", { className: "port-kind", children: [
|
|
2071
|
+
t.type,
|
|
2072
|
+
" · ",
|
|
2073
|
+
t.category
|
|
2074
|
+
] }),
|
|
2075
|
+
/* @__PURE__ */ a(
|
|
2076
|
+
"button",
|
|
2077
|
+
{
|
|
2078
|
+
className: "btn",
|
|
2079
|
+
style: { marginTop: 8, color: "var(--color-error)", borderColor: "var(--color-error)" },
|
|
2080
|
+
onClick: e,
|
|
2081
|
+
children: "Delete"
|
|
2082
|
+
}
|
|
2083
|
+
)
|
|
2084
|
+
] });
|
|
2085
|
+
}
|
|
2086
|
+
function Rn({
|
|
2087
|
+
node: n,
|
|
2088
|
+
def: e,
|
|
2089
|
+
onParam: t
|
|
2090
|
+
}) {
|
|
2091
|
+
return /* @__PURE__ */ p("div", { className: "section", children: [
|
|
2092
|
+
/* @__PURE__ */ a("h3", { children: "Parameters" }),
|
|
2093
|
+
e && e.parameters.length > 0 ? e.parameters.map((s) => /* @__PURE__ */ p("div", { className: "field", children: [
|
|
2094
|
+
/* @__PURE__ */ a("label", { children: s.label }),
|
|
2095
|
+
/* @__PURE__ */ a(
|
|
2096
|
+
"input",
|
|
2097
|
+
{
|
|
2098
|
+
value: String(n.data.params[s.name] ?? ""),
|
|
2099
|
+
onChange: (r) => {
|
|
2100
|
+
const o = s.dtype === "float" || s.dtype === "int" ? Number(r.target.value) : r.target.value;
|
|
2101
|
+
t(s.name, o);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
)
|
|
2105
|
+
] }, s.name)) : /* @__PURE__ */ a("div", { className: "port-kind", children: "No parameters" })
|
|
2106
|
+
] });
|
|
2107
|
+
}
|
|
2108
|
+
function Ce({
|
|
2109
|
+
title: n,
|
|
2110
|
+
ports: e,
|
|
2111
|
+
dir: t
|
|
2112
|
+
}) {
|
|
2113
|
+
return /* @__PURE__ */ p("div", { className: "section", children: [
|
|
2114
|
+
/* @__PURE__ */ a("h3", { children: n }),
|
|
2115
|
+
e.length === 0 ? /* @__PURE__ */ a("div", { className: "port-kind", children: "None" }) : e.map((s) => /* @__PURE__ */ p("div", { className: "port-row", children: [
|
|
2116
|
+
/* @__PURE__ */ a("span", { children: s.label }),
|
|
2117
|
+
/* @__PURE__ */ p("span", { className: "port-kind", children: [
|
|
2118
|
+
t,
|
|
2119
|
+
" · ",
|
|
2120
|
+
s.dataKind
|
|
2121
|
+
] })
|
|
2122
|
+
] }, s.name))
|
|
2123
|
+
] });
|
|
2124
|
+
}
|
|
2125
|
+
function jn({ result: n }) {
|
|
2126
|
+
return /* @__PURE__ */ p("div", { className: "section", children: [
|
|
2127
|
+
/* @__PURE__ */ a("h3", { children: "Preview" }),
|
|
2128
|
+
n ? /* @__PURE__ */ a("div", { className: "preview", children: Object.entries(n).map(([e, t]) => /* @__PURE__ */ p("div", { style: { marginBottom: 6 }, children: [
|
|
2129
|
+
/* @__PURE__ */ p("div", { children: [
|
|
2130
|
+
/* @__PURE__ */ a("strong", { children: e }),
|
|
2131
|
+
":",
|
|
2132
|
+
" ",
|
|
2133
|
+
t.shape.length === 0 ? "scalar" : t.shape.join("×"),
|
|
2134
|
+
" ",
|
|
2135
|
+
t.dtype,
|
|
2136
|
+
" · ",
|
|
2137
|
+
t.device
|
|
2138
|
+
] }),
|
|
2139
|
+
t.image ? /* @__PURE__ */ a(
|
|
2140
|
+
"img",
|
|
2141
|
+
{
|
|
2142
|
+
src: t.image,
|
|
2143
|
+
alt: "preview",
|
|
2144
|
+
style: { maxWidth: "100%", marginTop: 4, borderRadius: 4, border: "1px solid var(--color-border)" }
|
|
2145
|
+
}
|
|
2146
|
+
) : /* @__PURE__ */ p(B, { children: [
|
|
2147
|
+
/* @__PURE__ */ a("div", { children: t.summary }),
|
|
2148
|
+
t.dataPreview && /* @__PURE__ */ p("div", { style: { color: "var(--color-text-secondary)", whiteSpace: "pre-wrap" }, children: [
|
|
2149
|
+
t.dataPreview,
|
|
2150
|
+
t.dataPreview.length >= 120 && "…"
|
|
2151
|
+
] })
|
|
2152
|
+
] })
|
|
2153
|
+
] }, e)) }) : /* @__PURE__ */ a("div", { className: "port-kind", children: "Not executed" })
|
|
2154
|
+
] });
|
|
2155
|
+
}
|
|
2156
|
+
function $n({ style: n }) {
|
|
2157
|
+
const [e, t] = P("logs"), s = y((i) => i.logs), r = y((i) => i.nodes), o = y((i) => i.status);
|
|
2158
|
+
return /* @__PURE__ */ p("div", { className: "status-panel", style: n, children: [
|
|
2159
|
+
/* @__PURE__ */ p("div", { className: "tabs", children: [
|
|
2160
|
+
/* @__PURE__ */ a(
|
|
2161
|
+
"div",
|
|
2162
|
+
{
|
|
2163
|
+
className: `tab ${e === "logs" ? "active" : ""}`,
|
|
2164
|
+
onClick: () => t("logs"),
|
|
2165
|
+
children: "Logs"
|
|
2166
|
+
}
|
|
2167
|
+
),
|
|
2168
|
+
/* @__PURE__ */ a(
|
|
2169
|
+
"div",
|
|
2170
|
+
{
|
|
2171
|
+
className: `tab ${e === "queue" ? "active" : ""}`,
|
|
2172
|
+
onClick: () => t("queue"),
|
|
2173
|
+
children: "Queue"
|
|
2174
|
+
}
|
|
2175
|
+
)
|
|
2176
|
+
] }),
|
|
2177
|
+
/* @__PURE__ */ a("div", { className: "content", children: e === "logs" ? s.length === 0 ? /* @__PURE__ */ a("div", { className: "log-line", children: "No logs yet. Add nodes and click Run." }) : s.map((i, d) => /* @__PURE__ */ a("div", { className: "log-line", children: i }, d)) : r.length === 0 ? /* @__PURE__ */ a("div", { className: "log-line", children: "Graph is empty" }) : r.map((i) => /* @__PURE__ */ p("div", { className: "queue-item", children: [
|
|
2178
|
+
/* @__PURE__ */ a("span", { children: i.data.label }),
|
|
2179
|
+
/* @__PURE__ */ a("span", { className: "port-kind", children: o })
|
|
2180
|
+
] }, i.id)) })
|
|
2181
|
+
] });
|
|
2182
|
+
}
|
|
2183
|
+
function Ln() {
|
|
2184
|
+
var h;
|
|
2185
|
+
const n = y((v) => v.losses), e = y((v) => v.train), t = y((v) => v.status), [s, r] = P(20), [o, i] = P(1e-3), d = 300, m = 160, c = 10, u = (() => {
|
|
2186
|
+
if (n.length === 0) return "";
|
|
2187
|
+
const v = Math.min(...n), w = Math.max(...n) - v || 1;
|
|
2188
|
+
return n.map((f, S) => {
|
|
2189
|
+
const b = c + S / Math.max(n.length - 1, 1) * (d - c * 2), T = c + (1 - (f - v) / w) * (m - c * 2);
|
|
2190
|
+
return `${b.toFixed(1)},${T.toFixed(1)}`;
|
|
2191
|
+
}).join(" ");
|
|
2192
|
+
})();
|
|
2193
|
+
return /* @__PURE__ */ p("div", { className: "loss-panel", children: [
|
|
2194
|
+
/* @__PURE__ */ p("div", { className: "loss-controls", children: [
|
|
2195
|
+
/* @__PURE__ */ p("label", { className: "loss-field", children: [
|
|
2196
|
+
"Steps",
|
|
2197
|
+
/* @__PURE__ */ a(
|
|
2198
|
+
"input",
|
|
2199
|
+
{
|
|
2200
|
+
type: "number",
|
|
2201
|
+
min: 1,
|
|
2202
|
+
step: 1,
|
|
2203
|
+
value: s,
|
|
2204
|
+
onChange: (v) => r(Math.max(1, Math.floor(Number(v.target.value) || 1)))
|
|
2205
|
+
}
|
|
2206
|
+
)
|
|
2207
|
+
] }),
|
|
2208
|
+
/* @__PURE__ */ p("label", { className: "loss-field", children: [
|
|
2209
|
+
"LR",
|
|
2210
|
+
/* @__PURE__ */ a(
|
|
2211
|
+
"input",
|
|
2212
|
+
{
|
|
2213
|
+
type: "number",
|
|
2214
|
+
min: 0,
|
|
2215
|
+
step: "any",
|
|
2216
|
+
value: o,
|
|
2217
|
+
onChange: (v) => i(Number(v.target.value) || 0)
|
|
2218
|
+
}
|
|
2219
|
+
)
|
|
2220
|
+
] }),
|
|
2221
|
+
/* @__PURE__ */ a(
|
|
2222
|
+
"button",
|
|
2223
|
+
{
|
|
2224
|
+
className: "btn btn-sm",
|
|
2225
|
+
onClick: () => e(s, o),
|
|
2226
|
+
disabled: t === "running",
|
|
2227
|
+
children: t === "running" ? "Training…" : `Train ${s} steps`
|
|
2228
|
+
}
|
|
2229
|
+
)
|
|
2230
|
+
] }),
|
|
2231
|
+
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(B, { children: [
|
|
2232
|
+
/* @__PURE__ */ a("svg", { viewBox: `0 0 ${d} ${m}`, className: "loss-svg", preserveAspectRatio: "none", children: /* @__PURE__ */ a(
|
|
2233
|
+
"polyline",
|
|
2234
|
+
{
|
|
2235
|
+
points: u,
|
|
2236
|
+
fill: "none",
|
|
2237
|
+
stroke: "var(--color-primary)",
|
|
2238
|
+
strokeWidth: "2"
|
|
2239
|
+
}
|
|
2240
|
+
) }),
|
|
2241
|
+
/* @__PURE__ */ p("div", { className: "loss-meta", children: [
|
|
2242
|
+
/* @__PURE__ */ p("span", { children: [
|
|
2243
|
+
"steps: ",
|
|
2244
|
+
n.length
|
|
2245
|
+
] }),
|
|
2246
|
+
/* @__PURE__ */ p("span", { children: [
|
|
2247
|
+
"last loss: ",
|
|
2248
|
+
(h = n[n.length - 1]) == null ? void 0 : h.toFixed(4)
|
|
2249
|
+
] })
|
|
2250
|
+
] })
|
|
2251
|
+
] })
|
|
2252
|
+
] });
|
|
2253
|
+
}
|
|
2254
|
+
const Dn = '# 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', An = '# 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';
|
|
2255
|
+
function U(n) {
|
|
2256
|
+
return n.split(/(`[^`]+`|\*\*[^*]+\*\*|\[[^\]]+\]\([^)]+\))/g).map((t, s) => {
|
|
2257
|
+
if (t.startsWith("`") && t.endsWith("`") && t.length > 2)
|
|
2258
|
+
return /* @__PURE__ */ a("code", { children: t.slice(1, -1) }, s);
|
|
2259
|
+
if (t.startsWith("**") && t.endsWith("**") && t.length > 4)
|
|
2260
|
+
return /* @__PURE__ */ a("strong", { children: t.slice(2, -2) }, s);
|
|
2261
|
+
const r = t.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
2262
|
+
return r ? /* @__PURE__ */ a("a", { href: r[2], target: "_blank", rel: "noreferrer", children: r[1] }, s) : /* @__PURE__ */ a(Fe, { children: t }, s);
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
function Te(n) {
|
|
2266
|
+
return /^#{1,4}\s/.test(n);
|
|
2267
|
+
}
|
|
2268
|
+
function ce(n) {
|
|
2269
|
+
return /^\s*[-*]\s/.test(n) || /^\s*\d+\.\s/.test(n);
|
|
2270
|
+
}
|
|
2271
|
+
function Mn(n) {
|
|
2272
|
+
return /^\s*\|[\s:|-]+\|\s*$/.test(n);
|
|
2273
|
+
}
|
|
2274
|
+
function ne(n) {
|
|
2275
|
+
const e = n.replace(/\r\n/g, `
|
|
2276
|
+
`).split(`
|
|
2277
|
+
`), t = [];
|
|
2278
|
+
let s = 0;
|
|
2279
|
+
const r = (o, i) => {
|
|
2280
|
+
t.push(/* @__PURE__ */ a(Fe, { children: o }, i));
|
|
2281
|
+
};
|
|
2282
|
+
for (; s < e.length; ) {
|
|
2283
|
+
const o = e[s];
|
|
2284
|
+
if (o.trim() === "") {
|
|
2285
|
+
s++;
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
if (o.trim().startsWith("```")) {
|
|
2289
|
+
const d = [];
|
|
2290
|
+
for (s++; s < e.length && !e[s].trim().startsWith("```"); )
|
|
2291
|
+
d.push(e[s]), s++;
|
|
2292
|
+
s++, r(/* @__PURE__ */ a("pre", { children: /* @__PURE__ */ a("code", { children: d.join(`
|
|
2293
|
+
`) }) }), s);
|
|
2294
|
+
continue;
|
|
2295
|
+
}
|
|
2296
|
+
if (Te(o)) {
|
|
2297
|
+
const d = o.match(/^(#+)/)[1].length, m = o.replace(/^#+\s*/, "").trim(), c = s;
|
|
2298
|
+
r(d === 1 ? /* @__PURE__ */ a("h1", { children: U(m) }) : d === 2 ? /* @__PURE__ */ a("h2", { children: U(m) }) : d === 3 ? /* @__PURE__ */ a("h3", { children: U(m) }) : /* @__PURE__ */ a("h4", { children: U(m) }), c), s++;
|
|
2299
|
+
continue;
|
|
2300
|
+
}
|
|
2301
|
+
if (/^\s*-{3,}\s*$/.test(o)) {
|
|
2302
|
+
r(/* @__PURE__ */ a("hr", {}), s), s++;
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
if (o.startsWith(">")) {
|
|
2306
|
+
const d = [];
|
|
2307
|
+
for (; s < e.length && e[s].startsWith(">"); )
|
|
2308
|
+
d.push(e[s].replace(/^>\s?/, "")), s++;
|
|
2309
|
+
r(/* @__PURE__ */ a("blockquote", { children: ne(d.join(`
|
|
2310
|
+
`)) }), s);
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
if (o.trim().startsWith("|") && e[s + 1] && Mn(e[s + 1])) {
|
|
2314
|
+
const d = o.trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim());
|
|
2315
|
+
s += 2;
|
|
2316
|
+
const m = [];
|
|
2317
|
+
for (; s < e.length && e[s].trim().startsWith("|"); )
|
|
2318
|
+
m.push(e[s].trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim())), s++;
|
|
2319
|
+
r(
|
|
2320
|
+
/* @__PURE__ */ p("table", { children: [
|
|
2321
|
+
/* @__PURE__ */ a("thead", { children: /* @__PURE__ */ a("tr", { children: d.map((c, u) => /* @__PURE__ */ a("th", { children: U(c) }, u)) }) }),
|
|
2322
|
+
/* @__PURE__ */ a("tbody", { children: m.map((c, u) => /* @__PURE__ */ a("tr", { children: c.map((h, v) => /* @__PURE__ */ a("td", { children: U(h) }, v)) }, u)) })
|
|
2323
|
+
] }),
|
|
2324
|
+
s
|
|
2325
|
+
);
|
|
2326
|
+
continue;
|
|
2327
|
+
}
|
|
2328
|
+
if (ce(o)) {
|
|
2329
|
+
const d = /^\s*\d+\.\s/.test(o), m = [];
|
|
2330
|
+
for (; s < e.length && ce(e[s]); )
|
|
2331
|
+
m.push(e[s].replace(/^\s*(?:[-*]|\d+\.)\s*/, "")), s++;
|
|
2332
|
+
const c = s;
|
|
2333
|
+
r(
|
|
2334
|
+
d ? /* @__PURE__ */ a("ol", { children: m.map((u, h) => /* @__PURE__ */ a("li", { children: U(u) }, h)) }) : /* @__PURE__ */ a("ul", { children: m.map((u, h) => /* @__PURE__ */ a("li", { children: U(u) }, h)) }),
|
|
2335
|
+
c
|
|
2336
|
+
);
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2339
|
+
const i = [];
|
|
2340
|
+
for (; s < e.length && e[s].trim() !== "" && !Te(e[s]) && !e[s].trim().startsWith("```") && !e[s].trim().startsWith("|") && !e[s].startsWith(">") && !ce(e[s]) && !/^\s*-{3,}\s*$/.test(e[s]); )
|
|
2341
|
+
i.push(e[s]), s++;
|
|
2342
|
+
i.length && r(/* @__PURE__ */ a("p", { children: U(i.join(" ")) }), s);
|
|
2343
|
+
}
|
|
2344
|
+
return t;
|
|
2345
|
+
}
|
|
2346
|
+
const We = "entropia_riko_doc_lang";
|
|
2347
|
+
function Wn(n) {
|
|
2348
|
+
const e = n.split(`
|
|
2349
|
+
`);
|
|
2350
|
+
let t = "";
|
|
2351
|
+
const s = [];
|
|
2352
|
+
let r = null;
|
|
2353
|
+
const o = [];
|
|
2354
|
+
for (const i of e) {
|
|
2355
|
+
if (i.startsWith("# ")) {
|
|
2356
|
+
t = i.slice(2).trim();
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
if (i.startsWith("## ")) {
|
|
2360
|
+
r && s.push(r), r = { id: i.slice(3).trim(), title: i.slice(3).trim(), body: "" };
|
|
2361
|
+
continue;
|
|
2362
|
+
}
|
|
2363
|
+
r ? r.body += i + `
|
|
2364
|
+
` : i.trim() !== "" && o.push(i);
|
|
2365
|
+
}
|
|
2366
|
+
return r && s.push(r), { title: t, intro: o.join(`
|
|
2367
|
+
`), sections: s };
|
|
2368
|
+
}
|
|
2369
|
+
function Bn() {
|
|
2370
|
+
try {
|
|
2371
|
+
return localStorage.getItem(We) === "zh" ? "zh" : "en";
|
|
2372
|
+
} catch {
|
|
2373
|
+
return "en";
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
function Gn() {
|
|
2377
|
+
var v;
|
|
2378
|
+
const [n, e] = P(Bn), t = z(
|
|
2379
|
+
() => Wn(n === "zh" ? Dn : An),
|
|
2380
|
+
[n]
|
|
2381
|
+
), [s, r] = P(""), [o, i] = P(((v = t.sections[0]) == null ? void 0 : v.id) ?? "");
|
|
2382
|
+
D(() => {
|
|
2383
|
+
try {
|
|
2384
|
+
localStorage.setItem(We, n);
|
|
2385
|
+
} catch {
|
|
2386
|
+
}
|
|
2387
|
+
}, [n]), D(() => {
|
|
2388
|
+
i(
|
|
2389
|
+
(l) => {
|
|
2390
|
+
var w;
|
|
2391
|
+
return t.sections.some((f) => f.id === l) ? l : ((w = t.sections[0]) == null ? void 0 : w.id) ?? "";
|
|
2392
|
+
}
|
|
2393
|
+
);
|
|
2394
|
+
}, [t]);
|
|
2395
|
+
const d = z(() => {
|
|
2396
|
+
const l = s.trim().toLowerCase();
|
|
2397
|
+
return l ? t.sections.filter(
|
|
2398
|
+
(w) => w.title.toLowerCase().includes(l) || w.body.toLowerCase().includes(l)
|
|
2399
|
+
) : t.sections;
|
|
2400
|
+
}, [t.sections, s]), m = t.sections.findIndex((l) => l.id === o), c = t.sections[m] ?? t.sections[0], u = m > 0 ? t.sections[m - 1] : null, h = m >= 0 && m < t.sections.length - 1 ? t.sections[m + 1] : null;
|
|
2401
|
+
return /* @__PURE__ */ p("div", { className: "docs-panel", children: [
|
|
2402
|
+
/* @__PURE__ */ p("div", { className: "docs-sidebar", children: [
|
|
2403
|
+
/* @__PURE__ */ a("div", { className: "docs-search", children: /* @__PURE__ */ a(
|
|
2404
|
+
"input",
|
|
2405
|
+
{
|
|
2406
|
+
placeholder: n === "zh" ? "搜索文档…" : "Search docs…",
|
|
2407
|
+
value: s,
|
|
2408
|
+
onChange: (l) => r(l.target.value)
|
|
2409
|
+
}
|
|
2410
|
+
) }),
|
|
2411
|
+
/* @__PURE__ */ p("div", { className: "docs-nav", children: [
|
|
2412
|
+
d.map((l) => /* @__PURE__ */ a(
|
|
2413
|
+
"div",
|
|
2414
|
+
{
|
|
2415
|
+
className: `docs-nav-item ${l.id === o ? "active" : ""}`,
|
|
2416
|
+
onClick: () => i(l.id),
|
|
2417
|
+
children: l.title
|
|
2418
|
+
},
|
|
2419
|
+
l.id
|
|
2420
|
+
)),
|
|
2421
|
+
d.length === 0 && /* @__PURE__ */ a("div", { className: "docs-nav-empty", children: n === "zh" ? "无匹配" : "No matches" })
|
|
2422
|
+
] })
|
|
2423
|
+
] }),
|
|
2424
|
+
/* @__PURE__ */ a("div", { className: "docs-content", children: /* @__PURE__ */ p("div", { className: "docs-content-inner", children: [
|
|
2425
|
+
/* @__PURE__ */ p("div", { className: "docs-langbar", children: [
|
|
2426
|
+
/* @__PURE__ */ a("div", { className: "docs-pager-spacer" }),
|
|
2427
|
+
/* @__PURE__ */ p("div", { className: "docs-lang-switch", children: [
|
|
2428
|
+
/* @__PURE__ */ a(
|
|
2429
|
+
"button",
|
|
2430
|
+
{
|
|
2431
|
+
className: `panel-btn ${n === "en" ? "active" : ""}`,
|
|
2432
|
+
onClick: () => e("en"),
|
|
2433
|
+
children: "EN"
|
|
2434
|
+
}
|
|
2435
|
+
),
|
|
2436
|
+
/* @__PURE__ */ a(
|
|
2437
|
+
"button",
|
|
2438
|
+
{
|
|
2439
|
+
className: `panel-btn ${n === "zh" ? "active" : ""}`,
|
|
2440
|
+
onClick: () => e("zh"),
|
|
2441
|
+
children: "中文"
|
|
2442
|
+
}
|
|
2443
|
+
)
|
|
2444
|
+
] })
|
|
2445
|
+
] }),
|
|
2446
|
+
s ? /* @__PURE__ */ a("h1", { children: n === "zh" ? "搜索结果" : "Search Results" }) : /* @__PURE__ */ p(B, { children: [
|
|
2447
|
+
/* @__PURE__ */ a("h1", { children: t.title }),
|
|
2448
|
+
/* @__PURE__ */ a("div", { className: "docs-intro", children: ne(t.intro) }),
|
|
2449
|
+
/* @__PURE__ */ a("hr", {})
|
|
2450
|
+
] }),
|
|
2451
|
+
s ? d.map((l) => /* @__PURE__ */ p("div", { className: "docs-result", children: [
|
|
2452
|
+
/* @__PURE__ */ a("h2", { onClick: () => {
|
|
2453
|
+
r(""), i(l.id);
|
|
2454
|
+
}, children: l.title }),
|
|
2455
|
+
ne(l.body.slice(0, 400))
|
|
2456
|
+
] }, l.id)) : c && /* @__PURE__ */ p(B, { children: [
|
|
2457
|
+
/* @__PURE__ */ a("h1", { children: c.title }),
|
|
2458
|
+
ne(c.body)
|
|
2459
|
+
] }),
|
|
2460
|
+
!s && /* @__PURE__ */ p("div", { className: "docs-pager", children: [
|
|
2461
|
+
u && /* @__PURE__ */ p("button", { className: "btn btn-sm", onClick: () => i(u.id), children: [
|
|
2462
|
+
"← ",
|
|
2463
|
+
u.title
|
|
2464
|
+
] }),
|
|
2465
|
+
/* @__PURE__ */ a("div", { className: "docs-pager-spacer" }),
|
|
2466
|
+
h && /* @__PURE__ */ p("button", { className: "btn btn-sm", onClick: () => i(h.id), children: [
|
|
2467
|
+
h.title,
|
|
2468
|
+
" →"
|
|
2469
|
+
] })
|
|
2470
|
+
] })
|
|
2471
|
+
] }) })
|
|
2472
|
+
] });
|
|
2473
|
+
}
|
|
2474
|
+
function Be() {
|
|
2475
|
+
const [n, e] = P([]), [t, s] = P(!1), r = $(null), o = y((c) => c.pushToast), i = async () => {
|
|
2476
|
+
try {
|
|
2477
|
+
const u = await (await fetch("/api/plugins")).json();
|
|
2478
|
+
e(Array.isArray(u.plugins) ? u.plugins : []);
|
|
2479
|
+
} catch {
|
|
2480
|
+
e([]);
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2483
|
+
D(() => {
|
|
2484
|
+
i();
|
|
2485
|
+
}, []);
|
|
2486
|
+
const d = async (c, u) => {
|
|
2487
|
+
s(!0);
|
|
2488
|
+
try {
|
|
2489
|
+
const v = await (await fetch("/api/plugins/toggle", {
|
|
2490
|
+
method: "POST",
|
|
2491
|
+
headers: { "Content-Type": "application/json" },
|
|
2492
|
+
body: JSON.stringify({ name: c.dir ?? c.name, enabled: u })
|
|
2493
|
+
})).json();
|
|
2494
|
+
v.status === "success" ? (e(Array.isArray(v.plugins) ? v.plugins : []), y.getState().loadNodeDefs(), o("success", `${c.name} ${u ? "enabled" : "disabled"}`)) : o("error", v.error ?? "toggle failed");
|
|
2495
|
+
} catch (h) {
|
|
2496
|
+
o("error", h instanceof Error ? h.message : String(h));
|
|
2497
|
+
} finally {
|
|
2498
|
+
s(!1);
|
|
2499
|
+
}
|
|
2500
|
+
};
|
|
2501
|
+
return /* @__PURE__ */ p("div", { className: "plugin-panel", children: [
|
|
2502
|
+
/* @__PURE__ */ p("div", { className: "panel-head", children: [
|
|
2503
|
+
/* @__PURE__ */ a("span", { className: "panel-head-title", children: "Plugins" }),
|
|
2504
|
+
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
2505
|
+
/* @__PURE__ */ a(
|
|
2506
|
+
"button",
|
|
2507
|
+
{
|
|
2508
|
+
className: "panel-btn",
|
|
2509
|
+
title: "Load plugin from a .py file",
|
|
2510
|
+
disabled: t,
|
|
2511
|
+
onClick: () => {
|
|
2512
|
+
var c;
|
|
2513
|
+
return (c = r.current) == null ? void 0 : c.click();
|
|
2514
|
+
},
|
|
2515
|
+
children: "+"
|
|
2516
|
+
}
|
|
2517
|
+
),
|
|
2518
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Refresh", onClick: i, children: "↻" }),
|
|
2519
|
+
/* @__PURE__ */ a(
|
|
2520
|
+
"input",
|
|
2521
|
+
{
|
|
2522
|
+
ref: r,
|
|
2523
|
+
type: "file",
|
|
2524
|
+
accept: ".py,text/x-python",
|
|
2525
|
+
style: { display: "none" },
|
|
2526
|
+
onChange: (c) => {
|
|
2527
|
+
var v;
|
|
2528
|
+
const u = (v = c.target.files) == null ? void 0 : v[0];
|
|
2529
|
+
if (c.target.value = "", !u) return;
|
|
2530
|
+
const h = new FileReader();
|
|
2531
|
+
h.onload = async () => {
|
|
2532
|
+
const l = String(h.result ?? ""), w = u.name.replace(/\.py$/i, "") || "plugin";
|
|
2533
|
+
s(!0);
|
|
2534
|
+
try {
|
|
2535
|
+
const S = await (await fetch("/api/plugins/upload", {
|
|
2536
|
+
method: "POST",
|
|
2537
|
+
headers: { "Content-Type": "application/json" },
|
|
2538
|
+
body: JSON.stringify({ name: w, code: l })
|
|
2539
|
+
})).json();
|
|
2540
|
+
S.status === "success" ? (e(Array.isArray(S.plugins) ? S.plugins : []), y.getState().loadNodeDefs(), o("success", `Loaded plugin ${w}`)) : o("error", S.error ?? "upload failed");
|
|
2541
|
+
} catch (f) {
|
|
2542
|
+
o("error", f instanceof Error ? f.message : String(f));
|
|
2543
|
+
} finally {
|
|
2544
|
+
s(!1);
|
|
2545
|
+
}
|
|
2546
|
+
}, h.readAsText(u);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
)
|
|
2550
|
+
] }),
|
|
2551
|
+
/* @__PURE__ */ p("div", { className: "plugin-list", children: [
|
|
2552
|
+
n.length === 0 && /* @__PURE__ */ a("div", { className: "prefs-empty", children: "No plugins installed." }),
|
|
2553
|
+
n.map((c) => /* @__PURE__ */ p("div", { className: "plugin-item", children: [
|
|
2554
|
+
/* @__PURE__ */ p("div", { className: "plugin-item-main", children: [
|
|
2555
|
+
/* @__PURE__ */ p("span", { className: "plugin-item-name", children: [
|
|
2556
|
+
c.name,
|
|
2557
|
+
" ",
|
|
2558
|
+
c.version ? `v${c.version}` : ""
|
|
2559
|
+
] }),
|
|
2560
|
+
/* @__PURE__ */ a("span", { className: `prefs-plugin-status ${c.status}`, children: c.status }),
|
|
2561
|
+
c.description && /* @__PURE__ */ a("span", { className: "plugin-item-desc", children: c.description }),
|
|
2562
|
+
c.error && /* @__PURE__ */ a("span", { className: "prefs-plugin-error", children: c.error })
|
|
2563
|
+
] }),
|
|
2564
|
+
/* @__PURE__ */ p("label", { className: "plugin-toggle", title: c.enabled ? "Disable" : "Enable", children: [
|
|
2565
|
+
/* @__PURE__ */ a(
|
|
2566
|
+
"input",
|
|
2567
|
+
{
|
|
2568
|
+
type: "checkbox",
|
|
2569
|
+
checked: !!c.enabled,
|
|
2570
|
+
disabled: t || c.status === "error",
|
|
2571
|
+
onChange: (u) => d(c, u.target.checked)
|
|
2572
|
+
}
|
|
2573
|
+
),
|
|
2574
|
+
/* @__PURE__ */ a("span", { className: "plugin-toggle-track" })
|
|
2575
|
+
] })
|
|
2576
|
+
] }, c.name))
|
|
2577
|
+
] })
|
|
2578
|
+
] });
|
|
2579
|
+
}
|
|
2580
|
+
function Un() {
|
|
2581
|
+
const n = y((N) => N.pushToast), e = y((N) => N.openProjectFile), t = y((N) => N.importModule), s = y((N) => N.projectVersion), [r, o] = P([]), [i, d] = P({}), [m, c] = P(null), [u, h] = P(null), v = async () => {
|
|
2582
|
+
try {
|
|
2583
|
+
const _ = await (await fetch("/api/project/tree")).json();
|
|
2584
|
+
o(Array.isArray(_.tree) ? _.tree : []);
|
|
2585
|
+
} catch {
|
|
2586
|
+
o([]);
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
D(() => {
|
|
2590
|
+
v();
|
|
2591
|
+
}, [s]);
|
|
2592
|
+
const l = async (N, _) => (await fetch(N, {
|
|
2593
|
+
method: "POST",
|
|
2594
|
+
headers: { "Content-Type": "application/json" },
|
|
2595
|
+
body: JSON.stringify(_)
|
|
2596
|
+
})).json(), w = async (N) => {
|
|
2597
|
+
const _ = window.prompt("New file name", "untitled.riko");
|
|
2598
|
+
if (!_) return;
|
|
2599
|
+
const k = await l("/api/project/create", { name: _, dir: N });
|
|
2600
|
+
k.status === "success" ? (await v(), e(k.path)) : n("error", k.error ?? "create failed");
|
|
2601
|
+
}, f = async (N) => {
|
|
2602
|
+
const _ = window.prompt("New folder name");
|
|
2603
|
+
if (!_) return;
|
|
2604
|
+
const k = await l("/api/project/mkdir", { name: _, dir: N });
|
|
2605
|
+
k.status === "success" ? (d((O) => ({ ...O, [N]: !0 })), await v()) : n("error", k.error ?? "mkdir failed");
|
|
2606
|
+
}, S = async (N) => {
|
|
2607
|
+
const _ = window.prompt("Rename", N.name);
|
|
2608
|
+
if (!_) return;
|
|
2609
|
+
const k = await l("/api/project/rename", { path: N.path, newName: _ });
|
|
2610
|
+
k.status === "success" ? await v() : n("error", k.error ?? "rename failed");
|
|
2611
|
+
}, b = async (N) => {
|
|
2612
|
+
if (!window.confirm(`Delete ${N.path}?`)) return;
|
|
2613
|
+
const _ = await l("/api/project/delete", { path: N.path });
|
|
2614
|
+
_.status === "success" ? (await v(), n("success", `Deleted ${N.name}`)) : n("error", _.error ?? "delete failed");
|
|
2615
|
+
}, T = async (N) => {
|
|
2616
|
+
const _ = await l("/api/project/code", { path: N.path });
|
|
2617
|
+
_.status === "success" ? (y.getState().setCodeBuffer(_.code ?? ""), n("success", "Code → Code Editor panel")) : n("error", _.error ?? "code generation failed");
|
|
2618
|
+
}, x = async (N, _) => {
|
|
2619
|
+
const k = await l("/api/project/move", { path: N, targetDir: _ });
|
|
2620
|
+
k.status === "success" ? (await v(), n("success", `Moved ${N} → ${_ || "(root)"}`)) : n("error", k.error ?? "move failed");
|
|
2621
|
+
}, I = () => {
|
|
2622
|
+
const N = (m == null ? void 0 : m.target) ?? null;
|
|
2623
|
+
return N && N.type === "file" ? [
|
|
2624
|
+
{ id: "open", label: "Open", onSelect: () => e(N.path) },
|
|
2625
|
+
{ id: "code", label: "Preview PyTorch Code", description: "→ Code Editor panel", onSelect: () => T(N) },
|
|
2626
|
+
{ id: "import", label: "Import as Node", description: "reference this graph", onSelect: () => t(N.name.replace(/\.(riko|ric)$/i, ""), { x: 260, y: 150 }) },
|
|
2627
|
+
{ id: "sep1", label: "", separator: !0 },
|
|
2628
|
+
{ id: "rename", label: "Rename", onSelect: () => S(N) },
|
|
2629
|
+
{ id: "delete", label: "Delete", danger: !0, onSelect: () => b(N) }
|
|
2630
|
+
] : N && N.type === "dir" ? [
|
|
2631
|
+
{ id: "newfile", label: "New File", onSelect: () => w(N.path) },
|
|
2632
|
+
{ id: "newfolder", label: "New Folder", onSelect: () => f(N.path) },
|
|
2633
|
+
{ id: "sep1", label: "", separator: !0 },
|
|
2634
|
+
{ id: "rename", label: "Rename", onSelect: () => S(N) },
|
|
2635
|
+
{ id: "delete", label: "Delete", danger: !0, onSelect: () => b(N) }
|
|
2636
|
+
] : [
|
|
2637
|
+
{ id: "newfile", label: "New File", onSelect: () => w("") },
|
|
2638
|
+
{ id: "newfolder", label: "New Folder", onSelect: () => f("") },
|
|
2639
|
+
{ id: "sep1", label: "", separator: !0 },
|
|
2640
|
+
{ id: "refresh", label: "Refresh", onSelect: v }
|
|
2641
|
+
];
|
|
2642
|
+
}, j = (N, _) => {
|
|
2643
|
+
if (N.type === "dir") {
|
|
2644
|
+
const k = !!i[N.path];
|
|
2645
|
+
return /* @__PURE__ */ p("div", { children: [
|
|
2646
|
+
/* @__PURE__ */ p(
|
|
2647
|
+
"div",
|
|
2648
|
+
{
|
|
2649
|
+
className: "proj-row",
|
|
2650
|
+
style: { paddingLeft: 8 + _ * 12 },
|
|
2651
|
+
draggable: !0,
|
|
2652
|
+
onDragStart: (O) => {
|
|
2653
|
+
h(N.path), O.dataTransfer.effectAllowed = "move";
|
|
2654
|
+
},
|
|
2655
|
+
onDragOver: (O) => {
|
|
2656
|
+
O.preventDefault(), O.dataTransfer.dropEffect = "move";
|
|
2657
|
+
},
|
|
2658
|
+
onDrop: (O) => {
|
|
2659
|
+
O.preventDefault(), O.stopPropagation(), u && u !== N.path && x(u, N.path), h(null);
|
|
2660
|
+
},
|
|
2661
|
+
onClick: () => d((O) => ({ ...O, [N.path]: !k })),
|
|
2662
|
+
onContextMenu: (O) => {
|
|
2663
|
+
O.preventDefault(), O.stopPropagation(), c({ x: O.clientX, y: O.clientY, target: N });
|
|
2664
|
+
},
|
|
2665
|
+
children: [
|
|
2666
|
+
/* @__PURE__ */ a("span", { className: "proj-caret", children: k ? "▾" : "▸" }),
|
|
2667
|
+
/* @__PURE__ */ a("span", { className: "proj-icon", children: "▦" }),
|
|
2668
|
+
/* @__PURE__ */ a("span", { className: "proj-name", children: N.name })
|
|
2669
|
+
]
|
|
2670
|
+
}
|
|
2671
|
+
),
|
|
2672
|
+
k && (N.children ?? []).map((O) => j(O, _ + 1))
|
|
2673
|
+
] }, N.path);
|
|
2674
|
+
}
|
|
2675
|
+
return /* @__PURE__ */ p(
|
|
2676
|
+
"div",
|
|
2677
|
+
{
|
|
2678
|
+
className: "proj-row proj-file",
|
|
2679
|
+
style: { paddingLeft: 8 + _ * 12 },
|
|
2680
|
+
draggable: !0,
|
|
2681
|
+
onDragStart: (k) => {
|
|
2682
|
+
h(N.path), k.dataTransfer.effectAllowed = "move";
|
|
2683
|
+
},
|
|
2684
|
+
onClick: () => e(N.path),
|
|
2685
|
+
onContextMenu: (k) => {
|
|
2686
|
+
k.preventDefault(), k.stopPropagation(), c({ x: k.clientX, y: k.clientY, target: N });
|
|
2687
|
+
},
|
|
2688
|
+
children: [
|
|
2689
|
+
/* @__PURE__ */ a("span", { className: "proj-caret", children: "·" }),
|
|
2690
|
+
/* @__PURE__ */ a("span", { className: "proj-icon", children: "▤" }),
|
|
2691
|
+
/* @__PURE__ */ a("span", { className: "proj-name", children: N.name })
|
|
2692
|
+
]
|
|
2693
|
+
},
|
|
2694
|
+
N.path
|
|
2695
|
+
);
|
|
2696
|
+
};
|
|
2697
|
+
return /* @__PURE__ */ p("div", { className: "project-panel", children: [
|
|
2698
|
+
/* @__PURE__ */ p("div", { className: "panel-head", children: [
|
|
2699
|
+
/* @__PURE__ */ a("span", { className: "panel-head-title", children: "New File" }),
|
|
2700
|
+
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
2701
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Refresh", onClick: v, children: "↻" })
|
|
2702
|
+
] }),
|
|
2703
|
+
/* @__PURE__ */ p(
|
|
2704
|
+
"div",
|
|
2705
|
+
{
|
|
2706
|
+
className: "proj-tree",
|
|
2707
|
+
onDragOver: (N) => N.preventDefault(),
|
|
2708
|
+
onDrop: (N) => {
|
|
2709
|
+
N.preventDefault(), u && x(u, ""), h(null);
|
|
2710
|
+
},
|
|
2711
|
+
onContextMenu: (N) => {
|
|
2712
|
+
N.preventDefault(), c({ x: N.clientX, y: N.clientY, target: null });
|
|
2713
|
+
},
|
|
2714
|
+
children: [
|
|
2715
|
+
r.map((N) => j(N, 0)),
|
|
2716
|
+
r.length === 0 && /* @__PURE__ */ a("div", { className: "welcome-empty", children: "Right-click to create a file." })
|
|
2717
|
+
]
|
|
2718
|
+
}
|
|
2719
|
+
),
|
|
2720
|
+
m && /* @__PURE__ */ a(te, { entries: I(), x: m.x, y: m.y, onClose: () => c(null) })
|
|
2721
|
+
] });
|
|
2722
|
+
}
|
|
2723
|
+
function Hn() {
|
|
2724
|
+
const n = y((b) => b.codeBuffer), e = y((b) => b.setCodeBuffer), t = y((b) => b.generatePythonCode), s = y((b) => b.pushToast), r = $(null), o = $(null), [i, d] = P(null), m = () => {
|
|
2725
|
+
var b;
|
|
2726
|
+
return (b = r.current) == null ? void 0 : b.focus();
|
|
2727
|
+
}, c = (b) => {
|
|
2728
|
+
m(), document.execCommand(b);
|
|
2729
|
+
}, u = async () => {
|
|
2730
|
+
try {
|
|
2731
|
+
await navigator.clipboard.writeText(n), s("success", "Copied");
|
|
2732
|
+
} catch {
|
|
2733
|
+
c("copy");
|
|
2734
|
+
}
|
|
2735
|
+
}, h = () => {
|
|
2736
|
+
var b;
|
|
2737
|
+
m(), (b = r.current) == null || b.select();
|
|
2738
|
+
}, v = () => {
|
|
2739
|
+
const b = new Blob([n], { type: "text/x-python" }), T = URL.createObjectURL(b), x = document.createElement("a");
|
|
2740
|
+
x.href = T, x.download = "code.py", x.click(), URL.revokeObjectURL(T);
|
|
2741
|
+
}, l = () => e(""), w = (b) => {
|
|
2742
|
+
var I;
|
|
2743
|
+
const T = (I = b.target.files) == null ? void 0 : I[0];
|
|
2744
|
+
if (b.target.value = "", !T) return;
|
|
2745
|
+
const x = new FileReader();
|
|
2746
|
+
x.onload = () => e(String(x.result ?? "")), x.readAsText(T);
|
|
2747
|
+
}, f = [
|
|
2748
|
+
{
|
|
2749
|
+
label: "File",
|
|
2750
|
+
items: [
|
|
2751
|
+
{ label: "New", run: l },
|
|
2752
|
+
{ label: "Open…", run: () => {
|
|
2753
|
+
var b;
|
|
2754
|
+
return (b = o.current) == null ? void 0 : b.click();
|
|
2755
|
+
} },
|
|
2756
|
+
{ label: "Generate from Graph", run: t },
|
|
2757
|
+
{ label: "Save .py", run: v }
|
|
2758
|
+
]
|
|
2759
|
+
},
|
|
2760
|
+
{
|
|
2761
|
+
label: "Edit",
|
|
2762
|
+
items: [
|
|
2763
|
+
{ label: "Undo", run: () => c("undo") },
|
|
2764
|
+
{ label: "Redo", run: () => c("redo") },
|
|
2765
|
+
{ label: "Cut", run: () => c("cut") },
|
|
2766
|
+
{ label: "Copy", run: u },
|
|
2767
|
+
{ label: "Paste", run: () => c("paste") },
|
|
2768
|
+
{ label: "Select All", run: h },
|
|
2769
|
+
{ label: "Clear", run: l }
|
|
2770
|
+
]
|
|
2771
|
+
}
|
|
2772
|
+
], S = [
|
|
2773
|
+
{ icon: "+", title: "New", run: l },
|
|
2774
|
+
{ icon: "📂", title: "Open", run: () => {
|
|
2775
|
+
var b;
|
|
2776
|
+
return (b = o.current) == null ? void 0 : b.click();
|
|
2777
|
+
} },
|
|
2778
|
+
{ icon: "💾", title: "Save .py", run: v },
|
|
2779
|
+
{ icon: "↶", title: "Undo", run: () => c("undo") },
|
|
2780
|
+
{ icon: "↷", title: "Redo", run: () => c("redo") },
|
|
2781
|
+
{ icon: "✂", title: "Cut", run: () => c("cut") },
|
|
2782
|
+
{ icon: "⧉", title: "Copy", run: u },
|
|
2783
|
+
{ icon: "📋", title: "Paste", run: () => c("paste") },
|
|
2784
|
+
{ icon: "▦", title: "Select All", run: h }
|
|
2785
|
+
];
|
|
2786
|
+
return /* @__PURE__ */ p("div", { className: "code-editor", children: [
|
|
2787
|
+
/* @__PURE__ */ a("div", { className: "code-menubar", children: f.map((b) => /* @__PURE__ */ p(
|
|
2788
|
+
"div",
|
|
2789
|
+
{
|
|
2790
|
+
className: `code-menu-item ${i === b.label ? "active" : ""}`,
|
|
2791
|
+
onClick: () => d(i === b.label ? null : b.label),
|
|
2792
|
+
children: [
|
|
2793
|
+
b.label,
|
|
2794
|
+
i === b.label && /* @__PURE__ */ a("div", { className: "code-menu-dropdown", onClick: (T) => T.stopPropagation(), children: b.items.map((T, x) => /* @__PURE__ */ a(
|
|
2795
|
+
"div",
|
|
2796
|
+
{
|
|
2797
|
+
className: "code-menu-entry",
|
|
2798
|
+
onClick: () => {
|
|
2799
|
+
T.run(), d(null);
|
|
2800
|
+
},
|
|
2801
|
+
children: T.label
|
|
2802
|
+
},
|
|
2803
|
+
x
|
|
2804
|
+
)) })
|
|
2805
|
+
]
|
|
2806
|
+
},
|
|
2807
|
+
b.label
|
|
2808
|
+
)) }),
|
|
2809
|
+
/* @__PURE__ */ a("div", { className: "code-toolbar", children: S.map((b, T) => /* @__PURE__ */ a("button", { className: "panel-btn", title: b.title, onClick: b.run, children: b.icon }, T)) }),
|
|
2810
|
+
/* @__PURE__ */ a(
|
|
2811
|
+
"textarea",
|
|
2812
|
+
{
|
|
2813
|
+
ref: r,
|
|
2814
|
+
className: "code-editor-area",
|
|
2815
|
+
value: n,
|
|
2816
|
+
onChange: (b) => e(b.target.value),
|
|
2817
|
+
placeholder: "// Right-click a .riko file → Preview PyTorch Code, or File → Generate from Graph.",
|
|
2818
|
+
spellCheck: !1
|
|
2819
|
+
}
|
|
2820
|
+
),
|
|
2821
|
+
/* @__PURE__ */ a(
|
|
2822
|
+
"input",
|
|
2823
|
+
{
|
|
2824
|
+
ref: o,
|
|
2825
|
+
type: "file",
|
|
2826
|
+
accept: ".py,.txt,.riko,.ric,text/plain,text/x-python",
|
|
2827
|
+
style: { display: "none" },
|
|
2828
|
+
onChange: w
|
|
2829
|
+
}
|
|
2830
|
+
)
|
|
2831
|
+
] });
|
|
2832
|
+
}
|
|
2833
|
+
function Kn() {
|
|
2834
|
+
const n = $(null), e = $(!1), t = y((h) => h.addNode), s = y((h) => h.updateParam), r = y((h) => h.pushToast);
|
|
2835
|
+
D(() => {
|
|
2836
|
+
const h = n.current;
|
|
2837
|
+
if (!h) return;
|
|
2838
|
+
const v = h.getContext("2d");
|
|
2839
|
+
v && (v.fillStyle = "#fff", v.fillRect(0, 0, h.width, h.height), v.lineWidth = 12, v.lineCap = "round", v.lineJoin = "round", v.strokeStyle = "#000");
|
|
2840
|
+
}, []);
|
|
2841
|
+
const o = (h) => {
|
|
2842
|
+
const l = n.current.getBoundingClientRect();
|
|
2843
|
+
return { x: h.clientX - l.left, y: h.clientY - l.top };
|
|
2844
|
+
}, i = (h) => {
|
|
2845
|
+
e.current = !0;
|
|
2846
|
+
const l = n.current.getContext("2d"), { x: w, y: f } = o(h);
|
|
2847
|
+
l.beginPath(), l.moveTo(w, f), l.lineTo(w, f), l.stroke();
|
|
2848
|
+
}, d = (h) => {
|
|
2849
|
+
if (!e.current) return;
|
|
2850
|
+
const l = n.current.getContext("2d"), { x: w, y: f } = o(h);
|
|
2851
|
+
l.lineTo(w, f), l.stroke();
|
|
2852
|
+
}, m = () => {
|
|
2853
|
+
e.current = !1;
|
|
2854
|
+
};
|
|
2855
|
+
return /* @__PURE__ */ p("div", { className: "handpad", children: [
|
|
2856
|
+
/* @__PURE__ */ p("div", { className: "panel-head", children: [
|
|
2857
|
+
/* @__PURE__ */ a("span", { className: "panel-head-title", children: "Handwriting Pad" }),
|
|
2858
|
+
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
2859
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () => {
|
|
2860
|
+
const h = n.current, v = h.getContext("2d");
|
|
2861
|
+
v.fillStyle = "#fff", v.fillRect(0, 0, h.width, h.height);
|
|
2862
|
+
}, children: "Clear" }),
|
|
2863
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () => {
|
|
2864
|
+
const h = n.current, v = document.createElement("canvas");
|
|
2865
|
+
v.width = 28, v.height = 28;
|
|
2866
|
+
const l = v.getContext("2d");
|
|
2867
|
+
l.drawImage(h, 0, 0, 28, 28);
|
|
2868
|
+
const w = l.getImageData(0, 0, 28, 28).data, f = [];
|
|
2869
|
+
for (let x = 0; x < 28 * 28; x++) {
|
|
2870
|
+
const I = 1 - (w[x * 4] + w[x * 4 + 1] + w[x * 4 + 2]) / 765;
|
|
2871
|
+
f.push(Math.round(I * 1e3) / 1e3);
|
|
2872
|
+
}
|
|
2873
|
+
const S = [];
|
|
2874
|
+
for (let x = 0; x < 28; x++) S.push(f.slice(x * 28, x * 28 + 28));
|
|
2875
|
+
const b = [[S]];
|
|
2876
|
+
t("constant", { x: 260, y: 150 });
|
|
2877
|
+
const T = y.getState().selectedNodeId;
|
|
2878
|
+
T && (s(T, "value", b), r("success", "Digit → constant node (1×28×28)"));
|
|
2879
|
+
}, children: "Send → Node" })
|
|
2880
|
+
] }),
|
|
2881
|
+
/* @__PURE__ */ a("div", { className: "handpad-canvas", children: /* @__PURE__ */ a(
|
|
2882
|
+
"canvas",
|
|
2883
|
+
{
|
|
2884
|
+
ref: n,
|
|
2885
|
+
width: 280,
|
|
2886
|
+
height: 280,
|
|
2887
|
+
onMouseDown: i,
|
|
2888
|
+
onMouseMove: d,
|
|
2889
|
+
onMouseUp: m,
|
|
2890
|
+
onMouseLeave: m
|
|
2891
|
+
}
|
|
2892
|
+
) }),
|
|
2893
|
+
/* @__PURE__ */ p("div", { className: "handpad-hint", children: [
|
|
2894
|
+
"Draw a digit → Send creates a ",
|
|
2895
|
+
/* @__PURE__ */ a("code", { children: "constant" }),
|
|
2896
|
+
" node (1×28×28) to feed the MNIST example."
|
|
2897
|
+
] })
|
|
2898
|
+
] });
|
|
2899
|
+
}
|
|
2900
|
+
const Ge = [
|
|
2901
|
+
{ value: "canvas", label: "Graph", category: "Editor" },
|
|
2902
|
+
{ value: "inspector", label: "Inspector", category: "Editor" },
|
|
2903
|
+
{ value: "status", label: "Status / Logs", category: "Editor" },
|
|
2904
|
+
{ value: "loss", label: "Loss Curve", category: "Editor" },
|
|
2905
|
+
{ value: "nodes", label: "Node Library", category: "Data" },
|
|
2906
|
+
{ value: "files", label: "Asset Library", category: "Data" },
|
|
2907
|
+
{ value: "project", label: "New File", category: "Data" },
|
|
2908
|
+
{ value: "code", label: "Code Editor", category: "Tools" },
|
|
2909
|
+
{ value: "pad", label: "Handwriting Pad", category: "Tools" },
|
|
2910
|
+
{ value: "docs", label: "Documentation", category: "Tools" },
|
|
2911
|
+
{ value: "plugins", label: "Plugins", category: "Tools" }
|
|
2912
|
+
], Jn = ["Editor", "Data", "Tools"];
|
|
2913
|
+
function zn(n) {
|
|
2914
|
+
var e;
|
|
2915
|
+
return ((e = Ge.find((t) => t.value === n)) == null ? void 0 : e.label) ?? n;
|
|
2916
|
+
}
|
|
2917
|
+
const Ue = {
|
|
2918
|
+
nodes: () => /* @__PURE__ */ a(Nn, {}),
|
|
2919
|
+
files: () => /* @__PURE__ */ a(wn, {}),
|
|
2920
|
+
project: () => /* @__PURE__ */ a(Un, {}),
|
|
2921
|
+
canvas: () => /* @__PURE__ */ a(Fn, {}),
|
|
2922
|
+
inspector: () => /* @__PURE__ */ a(On, {}),
|
|
2923
|
+
status: () => /* @__PURE__ */ a($n, {}),
|
|
2924
|
+
loss: () => /* @__PURE__ */ a(Ln, {}),
|
|
2925
|
+
docs: () => /* @__PURE__ */ a(Gn, {}),
|
|
2926
|
+
plugins: () => /* @__PURE__ */ a(Be, {}),
|
|
2927
|
+
code: () => /* @__PURE__ */ a(Hn, {}),
|
|
2928
|
+
pad: () => /* @__PURE__ */ a(Kn, {})
|
|
2929
|
+
};
|
|
2930
|
+
function pt(n, e) {
|
|
2931
|
+
Ue[n] = e;
|
|
2932
|
+
}
|
|
2933
|
+
function Vn(n) {
|
|
2934
|
+
const e = Ue[n];
|
|
2935
|
+
return e ? e() : null;
|
|
2936
|
+
}
|
|
2937
|
+
function qn({
|
|
2938
|
+
type: n,
|
|
2939
|
+
onType: e
|
|
2940
|
+
}) {
|
|
2941
|
+
const [t, s] = P(!1), [r, o] = P(null), i = $(null);
|
|
2942
|
+
return /* @__PURE__ */ p("div", { className: "type-dropdown", children: [
|
|
2943
|
+
/* @__PURE__ */ p("button", { ref: i, className: "panel-btn", title: "Switch window type", onClick: () => {
|
|
2944
|
+
if (!t && i.current) {
|
|
2945
|
+
const m = i.current.getBoundingClientRect();
|
|
2946
|
+
o({ top: m.bottom + 4, left: m.right - 300 });
|
|
2947
|
+
}
|
|
2948
|
+
s((m) => !m);
|
|
2949
|
+
}, children: [
|
|
2950
|
+
zn(n),
|
|
2951
|
+
" ▾"
|
|
2952
|
+
] }),
|
|
2953
|
+
t && Oe(
|
|
2954
|
+
/* @__PURE__ */ p(B, { children: [
|
|
2955
|
+
/* @__PURE__ */ a("div", { className: "type-menu-overlay", onClick: () => s(!1) }),
|
|
2956
|
+
/* @__PURE__ */ a("div", { className: "type-menu", style: r ? { position: "fixed", top: r.top, left: r.left } : void 0, children: Jn.map((m) => /* @__PURE__ */ p("div", { className: "type-menu-col", children: [
|
|
2957
|
+
/* @__PURE__ */ a("div", { className: "type-menu-cat", children: m }),
|
|
2958
|
+
Ge.filter((c) => c.category === m).map((c) => /* @__PURE__ */ a(
|
|
2959
|
+
"div",
|
|
2960
|
+
{
|
|
2961
|
+
className: `type-menu-item ${c.value === n ? "active" : ""}`,
|
|
2962
|
+
onClick: () => {
|
|
2963
|
+
e(c.value), s(!1);
|
|
2964
|
+
},
|
|
2965
|
+
children: c.label
|
|
2966
|
+
},
|
|
2967
|
+
c.value
|
|
2968
|
+
))
|
|
2969
|
+
] }, m)) })
|
|
2970
|
+
] }),
|
|
2971
|
+
document.body
|
|
2972
|
+
)
|
|
2973
|
+
] });
|
|
2974
|
+
}
|
|
2975
|
+
function Pe(n, e) {
|
|
2976
|
+
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";
|
|
2977
|
+
}
|
|
2978
|
+
function Yn({
|
|
2979
|
+
onPreview: n,
|
|
2980
|
+
onCommit: e
|
|
2981
|
+
}) {
|
|
2982
|
+
const t = $(null);
|
|
2983
|
+
return /* @__PURE__ */ a(
|
|
2984
|
+
"div",
|
|
2985
|
+
{
|
|
2986
|
+
className: "corner-grip",
|
|
2987
|
+
title: "拖左上:分离面板 · 拖右下:合并面板",
|
|
2988
|
+
onMouseDown: (r) => {
|
|
2989
|
+
r.preventDefault(), r.stopPropagation();
|
|
2990
|
+
const o = r.currentTarget.parentElement, i = (o == null ? void 0 : o.getBoundingClientRect()) ?? null;
|
|
2991
|
+
t.current = { x: r.clientX, y: r.clientY };
|
|
2992
|
+
const d = (c) => {
|
|
2993
|
+
if (!t.current) return;
|
|
2994
|
+
const u = c.clientX - t.current.x, h = c.clientY - t.current.y, v = Pe(u, h);
|
|
2995
|
+
n(
|
|
2996
|
+
v ? { mode: v, x: c.clientX - ((i == null ? void 0 : i.left) ?? 0), y: c.clientY - ((i == null ? void 0 : i.top) ?? 0) } : null
|
|
2997
|
+
);
|
|
2998
|
+
}, m = (c) => {
|
|
2999
|
+
if (!t.current) return;
|
|
3000
|
+
const u = c.clientX - t.current.x, h = c.clientY - t.current.y;
|
|
3001
|
+
t.current = null, document.removeEventListener("mousemove", d), document.removeEventListener("mouseup", m), document.body.style.cursor = "";
|
|
3002
|
+
const v = Pe(u, h);
|
|
3003
|
+
n(null), v && e(v);
|
|
3004
|
+
};
|
|
3005
|
+
document.body.style.cursor = "crosshair", document.addEventListener("mousemove", d), document.addEventListener("mouseup", m);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
);
|
|
3009
|
+
}
|
|
3010
|
+
function Xn({
|
|
3011
|
+
id: n,
|
|
3012
|
+
type: e,
|
|
3013
|
+
onType: t,
|
|
3014
|
+
onSplitRow: s,
|
|
3015
|
+
onSplitColumn: r,
|
|
3016
|
+
onMerge: o,
|
|
3017
|
+
onClose: i,
|
|
3018
|
+
canMerge: d,
|
|
3019
|
+
mergeTarget: m,
|
|
3020
|
+
preview: c,
|
|
3021
|
+
onPreview: u
|
|
3022
|
+
}) {
|
|
3023
|
+
return /* @__PURE__ */ p("div", { className: `panel-slot ${m ? "merge-target" : ""}`, children: [
|
|
3024
|
+
/* @__PURE__ */ p("div", { className: "panel-bar", children: [
|
|
3025
|
+
/* @__PURE__ */ a("div", { className: "panel-bar-spacer" }),
|
|
3026
|
+
/* @__PURE__ */ a(qn, { type: e, onType: t }),
|
|
3027
|
+
d && /* @__PURE__ */ a("button", { className: "panel-btn", title: "Close / merge panel away", onClick: i, children: "✕" })
|
|
3028
|
+
] }),
|
|
3029
|
+
/* @__PURE__ */ a("div", { className: "panel-body", children: Vn(e) }),
|
|
3030
|
+
c && c.mode === "split-row" && /* @__PURE__ */ p(B, { children: [
|
|
3031
|
+
/* @__PURE__ */ a("div", { className: "split-preview", style: { left: 0, top: 0, bottom: 0, width: Math.max(0, c.x - 4) } }),
|
|
3032
|
+
/* @__PURE__ */ a("div", { className: "split-preview", style: { left: c.x + 4, top: 0, bottom: 0, right: 0 } })
|
|
3033
|
+
] }),
|
|
3034
|
+
c && c.mode === "split-column" && /* @__PURE__ */ p(B, { children: [
|
|
3035
|
+
/* @__PURE__ */ a("div", { className: "split-preview", style: { left: 0, right: 0, top: 0, height: Math.max(0, c.y - 4) } }),
|
|
3036
|
+
/* @__PURE__ */ a("div", { className: "split-preview", style: { left: 0, right: 0, top: c.y + 4, bottom: 0 } })
|
|
3037
|
+
] }),
|
|
3038
|
+
/* @__PURE__ */ a(
|
|
3039
|
+
Yn,
|
|
3040
|
+
{
|
|
3041
|
+
onPreview: u,
|
|
3042
|
+
onCommit: (h) => h === "split-row" ? s() : h === "split-column" ? r() : o()
|
|
3043
|
+
}
|
|
3044
|
+
)
|
|
3045
|
+
] });
|
|
3046
|
+
}
|
|
3047
|
+
function Qn({ onClose: n }) {
|
|
3048
|
+
const e = y((l) => l.recentFiles), t = y((l) => l.openFile), s = y((l) => l.newBlank), r = y((l) => l.load), o = y((l) => l.pushToast), [i, d] = P([]), m = $(null);
|
|
3049
|
+
D(() => {
|
|
3050
|
+
fetch("/api/files").then((l) => l.json()).then((l) => {
|
|
3051
|
+
Array.isArray(l.files) && d(
|
|
3052
|
+
l.files.filter((w) => w.path.startsWith("examples")).sort((w, f) => w.name.localeCompare(f.name))
|
|
3053
|
+
);
|
|
3054
|
+
}).catch(() => {
|
|
3055
|
+
});
|
|
3056
|
+
}, []);
|
|
3057
|
+
const c = (l) => {
|
|
3058
|
+
var w;
|
|
3059
|
+
return ((w = l.split("/").pop()) == null ? void 0 : w.replace(/\.riko$/, "")) ?? l;
|
|
3060
|
+
}, u = (l) => {
|
|
3061
|
+
l(), n();
|
|
3062
|
+
}, h = () => {
|
|
3063
|
+
const l = e[0];
|
|
3064
|
+
l ? u(() => t(l)) : o("error", "No previous session to recover");
|
|
3065
|
+
}, v = (l) => {
|
|
3066
|
+
var f;
|
|
3067
|
+
const w = (f = l.target.files) == null ? void 0 : f[0];
|
|
3068
|
+
l.target.value = "", w && (r(w), n());
|
|
3069
|
+
};
|
|
3070
|
+
return /* @__PURE__ */ a("div", { className: "welcome-overlay", children: /* @__PURE__ */ p("div", { className: "welcome", children: [
|
|
3071
|
+
/* @__PURE__ */ p("div", { className: "welcome-hero", children: [
|
|
3072
|
+
/* @__PURE__ */ a(
|
|
3073
|
+
ie,
|
|
3074
|
+
{
|
|
3075
|
+
className: "welcome-logo",
|
|
3076
|
+
title: "Logo slot — replace public/brand/logo.png (bitmap) or logo.svg (vector)"
|
|
3077
|
+
}
|
|
3078
|
+
),
|
|
3079
|
+
/* @__PURE__ */ p("span", { className: "welcome-version", children: [
|
|
3080
|
+
"v",
|
|
3081
|
+
Z
|
|
3082
|
+
] })
|
|
3083
|
+
] }),
|
|
3084
|
+
/* @__PURE__ */ p("div", { className: "welcome-header", children: [
|
|
3085
|
+
/* @__PURE__ */ a("span", { className: "welcome-title", children: "Entropia Riko" }),
|
|
3086
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Close", onClick: n, children: "✕" })
|
|
3087
|
+
] }),
|
|
3088
|
+
/* @__PURE__ */ p("div", { className: "welcome-body", children: [
|
|
3089
|
+
/* @__PURE__ */ p("div", { className: "welcome-col", children: [
|
|
3090
|
+
/* @__PURE__ */ a("div", { className: "welcome-col-title", children: "New File" }),
|
|
3091
|
+
/* @__PURE__ */ p("button", { className: "welcome-item welcome-blank", onClick: () => u(s), children: [
|
|
3092
|
+
/* @__PURE__ */ a("span", { className: "welcome-item-icon", children: "⬚" }),
|
|
3093
|
+
/* @__PURE__ */ a("span", { children: "General" }),
|
|
3094
|
+
/* @__PURE__ */ a("span", { className: "welcome-item-hint", children: "blank graph" })
|
|
3095
|
+
] }),
|
|
3096
|
+
/* @__PURE__ */ a("div", { className: "welcome-section-label", children: "Presets" }),
|
|
3097
|
+
/* @__PURE__ */ p("div", { className: "welcome-scroll", children: [
|
|
3098
|
+
i.length === 0 && /* @__PURE__ */ a("div", { className: "welcome-empty", children: "No presets (start the API server)" }),
|
|
3099
|
+
i.map((l) => /* @__PURE__ */ p("div", { className: "welcome-item", onClick: () => u(() => t(l.path)), children: [
|
|
3100
|
+
/* @__PURE__ */ a("span", { className: "welcome-item-icon", children: "▦" }),
|
|
3101
|
+
/* @__PURE__ */ a("span", { children: l.name }),
|
|
3102
|
+
/* @__PURE__ */ a("span", { className: "welcome-item-hint", children: l.imports.length ? `imports ${l.imports.length}` : "" })
|
|
3103
|
+
] }, l.path))
|
|
3104
|
+
] })
|
|
3105
|
+
] }),
|
|
3106
|
+
/* @__PURE__ */ p("div", { className: "welcome-col", children: [
|
|
3107
|
+
/* @__PURE__ */ a("div", { className: "welcome-col-title", children: "Recent Files" }),
|
|
3108
|
+
/* @__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: () => u(() => t(l)), children: [
|
|
3109
|
+
/* @__PURE__ */ a("span", { className: "welcome-item-icon", children: "▤" }),
|
|
3110
|
+
/* @__PURE__ */ a("span", { children: c(l) }),
|
|
3111
|
+
/* @__PURE__ */ a("span", { className: "welcome-item-hint", children: l })
|
|
3112
|
+
] }, l)) })
|
|
3113
|
+
] })
|
|
3114
|
+
] }),
|
|
3115
|
+
/* @__PURE__ */ p("div", { className: "welcome-footer", children: [
|
|
3116
|
+
/* @__PURE__ */ a("button", { className: "welcome-footer-btn", onClick: h, children: "↩ Recover Last Session" }),
|
|
3117
|
+
/* @__PURE__ */ a("button", { className: "welcome-footer-btn", onClick: () => {
|
|
3118
|
+
var l;
|
|
3119
|
+
return (l = m.current) == null ? void 0 : l.click();
|
|
3120
|
+
}, children: "📂 Open" }),
|
|
3121
|
+
/* @__PURE__ */ a(
|
|
3122
|
+
"input",
|
|
3123
|
+
{
|
|
3124
|
+
ref: m,
|
|
3125
|
+
type: "file",
|
|
3126
|
+
accept: ".riko,.ric,.json,application/json,application/octet-stream",
|
|
3127
|
+
style: { display: "none" },
|
|
3128
|
+
onChange: v
|
|
3129
|
+
}
|
|
3130
|
+
)
|
|
3131
|
+
] })
|
|
3132
|
+
] }) });
|
|
3133
|
+
}
|
|
3134
|
+
const Zn = [
|
|
3135
|
+
{ id: "appearance", label: "Appearance", icon: "◧" },
|
|
3136
|
+
{ id: "plugins", label: "Plugins", icon: "⬡" },
|
|
3137
|
+
{ id: "about", label: "About", icon: "ⓘ" }
|
|
3138
|
+
];
|
|
3139
|
+
function et({ onClose: n }) {
|
|
3140
|
+
const e = K((u) => u.theme), t = K((u) => u.setTheme), s = K((u) => u.backgroundImage), r = K((u) => u.setBackgroundImage), [o, i] = P("appearance"), [d, m] = P(s);
|
|
3141
|
+
D(() => {
|
|
3142
|
+
m(s);
|
|
3143
|
+
}, [s]);
|
|
3144
|
+
const c = [
|
|
3145
|
+
{ value: "light", label: "Light", hint: "Always use the light palette" },
|
|
3146
|
+
{ value: "dark", label: "Dark", hint: "Always use the dark palette" },
|
|
3147
|
+
{ value: "system", label: "System", hint: "Follow the OS color scheme" },
|
|
3148
|
+
{ value: "glass", label: "Liquid Glass", hint: "Apple-style translucent glass" }
|
|
3149
|
+
];
|
|
3150
|
+
return /* @__PURE__ */ a(oe, { title: "Preferences", onClose: n, width: 720, zIndex: 1001, children: /* @__PURE__ */ p("div", { className: "prefs-layout", children: [
|
|
3151
|
+
/* @__PURE__ */ a("nav", { className: "prefs-sidebar", children: Zn.map((u) => /* @__PURE__ */ p(
|
|
3152
|
+
"button",
|
|
3153
|
+
{
|
|
3154
|
+
className: `prefs-nav ${o === u.id ? "active" : ""}`,
|
|
3155
|
+
onClick: () => i(u.id),
|
|
3156
|
+
children: [
|
|
3157
|
+
/* @__PURE__ */ a("span", { className: "prefs-nav-icon", children: u.icon }),
|
|
3158
|
+
u.label
|
|
3159
|
+
]
|
|
3160
|
+
},
|
|
3161
|
+
u.id
|
|
3162
|
+
)) }),
|
|
3163
|
+
/* @__PURE__ */ p("div", { className: "prefs-content", children: [
|
|
3164
|
+
o === "appearance" && /* @__PURE__ */ p(B, { children: [
|
|
3165
|
+
/* @__PURE__ */ p("section", { className: "prefs-section", children: [
|
|
3166
|
+
/* @__PURE__ */ a("h3", { children: "Theme" }),
|
|
3167
|
+
/* @__PURE__ */ a("p", { className: "prefs-desc", children: "Choose how the interface looks." }),
|
|
3168
|
+
/* @__PURE__ */ a("div", { className: "prefs-theme-list", children: c.map((u) => /* @__PURE__ */ p(
|
|
3169
|
+
"label",
|
|
3170
|
+
{
|
|
3171
|
+
className: `prefs-theme ${e === u.value ? "active" : ""}`,
|
|
3172
|
+
children: [
|
|
3173
|
+
/* @__PURE__ */ a(
|
|
3174
|
+
"input",
|
|
3175
|
+
{
|
|
3176
|
+
type: "radio",
|
|
3177
|
+
name: "theme",
|
|
3178
|
+
checked: e === u.value,
|
|
3179
|
+
onChange: () => t(u.value)
|
|
3180
|
+
}
|
|
3181
|
+
),
|
|
3182
|
+
/* @__PURE__ */ a("span", { className: "prefs-theme-swatch", "data-theme": u.value }),
|
|
3183
|
+
/* @__PURE__ */ a("span", { className: "prefs-theme-label", children: u.label }),
|
|
3184
|
+
/* @__PURE__ */ a("span", { className: "prefs-theme-hint", children: u.hint })
|
|
3185
|
+
]
|
|
3186
|
+
},
|
|
3187
|
+
u.value
|
|
3188
|
+
)) })
|
|
3189
|
+
] }),
|
|
3190
|
+
/* @__PURE__ */ p("section", { className: "prefs-section", children: [
|
|
3191
|
+
/* @__PURE__ */ a("h3", { children: "Background image" }),
|
|
3192
|
+
/* @__PURE__ */ a("p", { className: "prefs-desc", children: "A cover image behind the editor. URL is saved locally." }),
|
|
3193
|
+
/* @__PURE__ */ p("div", { className: "prefs-row", children: [
|
|
3194
|
+
/* @__PURE__ */ a(
|
|
3195
|
+
"input",
|
|
3196
|
+
{
|
|
3197
|
+
className: "prefs-input",
|
|
3198
|
+
placeholder: "Image URL (https://…)",
|
|
3199
|
+
value: d,
|
|
3200
|
+
onChange: (u) => m(u.target.value),
|
|
3201
|
+
onKeyDown: (u) => {
|
|
3202
|
+
u.key === "Enter" && r(d.trim());
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
),
|
|
3206
|
+
/* @__PURE__ */ a("button", { className: "btn btn-sm", onClick: () => r(d.trim()), children: "Apply" }),
|
|
3207
|
+
/* @__PURE__ */ a(
|
|
3208
|
+
"button",
|
|
3209
|
+
{
|
|
3210
|
+
className: "btn btn-sm",
|
|
3211
|
+
onClick: () => {
|
|
3212
|
+
m(""), r("");
|
|
3213
|
+
},
|
|
3214
|
+
children: "Clear"
|
|
3215
|
+
}
|
|
3216
|
+
)
|
|
3217
|
+
] })
|
|
3218
|
+
] })
|
|
3219
|
+
] }),
|
|
3220
|
+
o === "plugins" && /* @__PURE__ */ a("section", { className: "prefs-section", style: { marginBottom: 0 }, children: /* @__PURE__ */ a(Be, {}) }),
|
|
3221
|
+
o === "about" && /* @__PURE__ */ a("section", { className: "prefs-section", children: /* @__PURE__ */ p("div", { className: "prefs-about", children: [
|
|
3222
|
+
/* @__PURE__ */ a(ie, { className: "prefs-about-logo" }),
|
|
3223
|
+
/* @__PURE__ */ a("h3", { children: "Entropia Riko" }),
|
|
3224
|
+
/* @__PURE__ */ p("div", { className: "prefs-about-version", children: [
|
|
3225
|
+
"Version ",
|
|
3226
|
+
Z
|
|
3227
|
+
] }),
|
|
3228
|
+
/* @__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." }),
|
|
3229
|
+
/* @__PURE__ */ p("div", { className: "prefs-about-meta", children: [
|
|
3230
|
+
/* @__PURE__ */ a("span", { children: "Backends: PyTorch · TensorFlow/Keras" }),
|
|
3231
|
+
/* @__PURE__ */ a("span", { children: "Brand assets: public/brand/" })
|
|
3232
|
+
] })
|
|
3233
|
+
] }) })
|
|
3234
|
+
] })
|
|
3235
|
+
] }) });
|
|
3236
|
+
}
|
|
3237
|
+
function nt({ onClose: n }) {
|
|
3238
|
+
return /* @__PURE__ */ p(oe, { title: "About Entropia Riko", onClose: n, width: 440, children: [
|
|
3239
|
+
/* @__PURE__ */ p("div", { className: "about-hero", children: [
|
|
3240
|
+
/* @__PURE__ */ a(ie, { className: "about-logo" }),
|
|
3241
|
+
/* @__PURE__ */ a("h2", { children: "Entropia Riko" }),
|
|
3242
|
+
/* @__PURE__ */ p("div", { className: "about-version", children: [
|
|
3243
|
+
"Version ",
|
|
3244
|
+
Z
|
|
3245
|
+
] })
|
|
3246
|
+
] }),
|
|
3247
|
+
/* @__PURE__ */ p("div", { className: "about-body", children: [
|
|
3248
|
+
/* @__PURE__ */ a("p", { className: "prefs-desc", children: "A professional node-graph deep-learning editor. Build models visually, run inference, train with a live loss curve, and export to clean PyTorch or TensorFlow/Keras code." }),
|
|
3249
|
+
/* @__PURE__ */ a("table", { className: "about-meta", children: /* @__PURE__ */ p("tbody", { children: [
|
|
3250
|
+
/* @__PURE__ */ p("tr", { children: [
|
|
3251
|
+
/* @__PURE__ */ a("td", { children: "Backends" }),
|
|
3252
|
+
/* @__PURE__ */ a("td", { children: "PyTorch · TensorFlow / Keras" })
|
|
3253
|
+
] }),
|
|
3254
|
+
/* @__PURE__ */ p("tr", { children: [
|
|
3255
|
+
/* @__PURE__ */ a("td", { children: "File formats" }),
|
|
3256
|
+
/* @__PURE__ */ a("td", { children: ".riko (JSON) · .ric (binary zlib)" })
|
|
3257
|
+
] }),
|
|
3258
|
+
/* @__PURE__ */ p("tr", { children: [
|
|
3259
|
+
/* @__PURE__ */ a("td", { children: "Plugins" }),
|
|
3260
|
+
/* @__PURE__ */ a("td", { children: "plugins/*/plugin.json (Python node modules)" })
|
|
3261
|
+
] }),
|
|
3262
|
+
/* @__PURE__ */ p("tr", { children: [
|
|
3263
|
+
/* @__PURE__ */ a("td", { children: "License" }),
|
|
3264
|
+
/* @__PURE__ */ a("td", { children: "MIT" })
|
|
3265
|
+
] })
|
|
3266
|
+
] }) }),
|
|
3267
|
+
/* @__PURE__ */ p("div", { className: "about-assets", children: [
|
|
3268
|
+
/* @__PURE__ */ a("span", { children: "Brand assets (replace in place):" }),
|
|
3269
|
+
/* @__PURE__ */ a("code", { children: "public/brand/logo.svg" }),
|
|
3270
|
+
/* @__PURE__ */ a("code", { children: "public/brand/hero.jpg" })
|
|
3271
|
+
] })
|
|
3272
|
+
] })
|
|
3273
|
+
] });
|
|
3274
|
+
}
|
|
3275
|
+
function tt({ onClose: n }) {
|
|
3276
|
+
const [e, t] = P(""), [s, r] = P(!1), o = y((d) => d.pushToast), i = async () => {
|
|
3277
|
+
const d = e.trim();
|
|
3278
|
+
if (d) {
|
|
3279
|
+
r(!0);
|
|
3280
|
+
try {
|
|
3281
|
+
const c = await (await fetch("/api/project/set_root", {
|
|
3282
|
+
method: "POST",
|
|
3283
|
+
headers: { "Content-Type": "application/json" },
|
|
3284
|
+
body: JSON.stringify({ path: d })
|
|
3285
|
+
})).json();
|
|
3286
|
+
c.status === "success" ? (y.getState().bumpProject(), o("success", `Working folder: ${c.root}`), n()) : o("error", c.error ?? "import failed");
|
|
3287
|
+
} catch (m) {
|
|
3288
|
+
o("error", m instanceof Error ? m.message : String(m));
|
|
3289
|
+
} finally {
|
|
3290
|
+
r(!1);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
};
|
|
3294
|
+
return /* @__PURE__ */ a(oe, { title: "Import Working Folder", onClose: n, width: 480, children: /* @__PURE__ */ p("div", { className: "import-folder-body", children: [
|
|
3295
|
+
/* @__PURE__ */ p("p", { className: "prefs-desc", children: [
|
|
3296
|
+
"Point the file manager at a local folder. A ",
|
|
3297
|
+
/* @__PURE__ */ a("code", { children: ".riko" }),
|
|
3298
|
+
" cache folder is created inside it for the tool's own state."
|
|
3299
|
+
] }),
|
|
3300
|
+
/* @__PURE__ */ a(
|
|
3301
|
+
"input",
|
|
3302
|
+
{
|
|
3303
|
+
autoFocus: !0,
|
|
3304
|
+
className: "prefs-input",
|
|
3305
|
+
placeholder: "Absolute path, e.g. /Users/me/project",
|
|
3306
|
+
value: e,
|
|
3307
|
+
onChange: (d) => t(d.target.value),
|
|
3308
|
+
onKeyDown: (d) => {
|
|
3309
|
+
d.key === "Enter" && i(), d.key === "Escape" && n();
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
),
|
|
3313
|
+
/* @__PURE__ */ p("div", { className: "prefs-row", style: { marginTop: 12 }, children: [
|
|
3314
|
+
/* @__PURE__ */ a("button", { className: "btn btn-primary", disabled: s || !e.trim(), onClick: i, children: "Import" }),
|
|
3315
|
+
/* @__PURE__ */ a("button", { className: "btn", onClick: n, children: "Cancel" })
|
|
3316
|
+
] })
|
|
3317
|
+
] }) });
|
|
3318
|
+
}
|
|
3319
|
+
function at() {
|
|
3320
|
+
const n = y((t) => t.toasts), e = y((t) => t.dismissToast);
|
|
3321
|
+
return /* @__PURE__ */ a("div", { className: "toast-stack", children: n.map((t) => /* @__PURE__ */ p("div", { className: `toast ${t.kind}`, children: [
|
|
3322
|
+
/* @__PURE__ */ a("span", { className: "toast-msg", children: t.message }),
|
|
3323
|
+
/* @__PURE__ */ a("button", { className: "toast-close", title: "Dismiss", onClick: () => e(t.id), children: "✕" })
|
|
3324
|
+
] }, t.id)) });
|
|
3325
|
+
}
|
|
3326
|
+
let st = 0;
|
|
3327
|
+
const de = () => `ws_${++st}`, rt = ["layout", "training", "mnist_studio", "image_gen"];
|
|
3328
|
+
function ut() {
|
|
3329
|
+
var q;
|
|
3330
|
+
const n = y((g) => g.loadNodeDefs), e = y((g) => g.welcomeOpen), t = y((g) => g.setWelcomeOpen), s = y((g) => g.prefsOpen), r = y((g) => g.setPrefsOpen), o = y((g) => g.aboutOpen), i = y((g) => g.setAboutOpen), d = y((g) => g.importFolderOpen), m = y((g) => g.setImportFolderOpen);
|
|
3331
|
+
D(() => {
|
|
3332
|
+
n();
|
|
3333
|
+
}, [n]);
|
|
3334
|
+
const [c, u] = P(
|
|
3335
|
+
() => rt.map((g) => {
|
|
3336
|
+
const F = ue.find((C) => C.id === g);
|
|
3337
|
+
return { id: de(), name: F.label, root: F.build() };
|
|
3338
|
+
})
|
|
3339
|
+
), [h, v] = P(null), [l, w] = P(null), f = h ?? ((q = c[0]) == null ? void 0 : q.id) ?? "", b = (c.find((g) => g.id === f) ?? c[0]).root, T = ye(b) > 1, x = (g) => {
|
|
3340
|
+
u((F) => F.map((C) => C.id === f ? { ...C, root: g(C.root) } : C));
|
|
3341
|
+
}, I = (g) => v(g), j = (g) => {
|
|
3342
|
+
const F = ue.find((E) => E.id === g);
|
|
3343
|
+
if (!F) return;
|
|
3344
|
+
const C = { id: de(), name: F.label, root: F.build() };
|
|
3345
|
+
u((E) => [...E, C]), v(C.id);
|
|
3346
|
+
}, N = (g) => {
|
|
3347
|
+
u((F) => {
|
|
3348
|
+
if (F.length <= 1) return F;
|
|
3349
|
+
const C = F.filter((E) => E.id !== g);
|
|
3350
|
+
return g === f && v(C[0].id), C;
|
|
3351
|
+
});
|
|
3352
|
+
}, _ = (g, F) => {
|
|
3353
|
+
u((C) => C.map((E) => E.id === g ? { ...E, name: F } : E));
|
|
3354
|
+
}, k = (g) => {
|
|
3355
|
+
const F = c.find((G) => G.id === g);
|
|
3356
|
+
if (!F) return;
|
|
3357
|
+
const C = {
|
|
3358
|
+
id: de(),
|
|
3359
|
+
name: `${F.name} copy`,
|
|
3360
|
+
root: JSON.parse(JSON.stringify(F.root))
|
|
3361
|
+
}, E = c.findIndex((G) => G.id === g), H = [...c];
|
|
3362
|
+
H.splice(E + 1, 0, C), u(H), v(C.id);
|
|
3363
|
+
}, O = (g, F) => {
|
|
3364
|
+
u((C) => {
|
|
3365
|
+
const E = C.findIndex((Je) => Je.id === g), H = E + F;
|
|
3366
|
+
if (E < 0 || H < 0 || H >= C.length) return C;
|
|
3367
|
+
const G = [...C], [Ke] = G.splice(E, 1);
|
|
3368
|
+
return G.splice(H, 0, Ke), G;
|
|
3369
|
+
});
|
|
3370
|
+
}, V = (l == null ? void 0 : l.mode) === "merge" ? fe(b, l.leafId) : null, A = (g) => {
|
|
3371
|
+
if (g.kind === "leaf") {
|
|
3372
|
+
const F = l && l.leafId === g.id && l.mode !== "merge" ? l : null;
|
|
3373
|
+
return /* @__PURE__ */ a(
|
|
3374
|
+
Xn,
|
|
3375
|
+
{
|
|
3376
|
+
id: g.id,
|
|
3377
|
+
type: g.type,
|
|
3378
|
+
canMerge: T,
|
|
3379
|
+
mergeTarget: g.id === V,
|
|
3380
|
+
preview: F,
|
|
3381
|
+
onType: (C) => x((E) => ge(E, g.id, C)),
|
|
3382
|
+
onSplitRow: () => x((C) => ae(C, g.id, "row")),
|
|
3383
|
+
onSplitColumn: () => x((C) => ae(C, g.id, "column")),
|
|
3384
|
+
onMerge: () => x((C) => he(C, g.id)),
|
|
3385
|
+
onClose: () => x((C) => me(C, g.id)),
|
|
3386
|
+
onPreview: (C) => w(C ? { ...C, leafId: g.id } : null)
|
|
3387
|
+
},
|
|
3388
|
+
g.id
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3391
|
+
return g.direction === "row" ? /* @__PURE__ */ p(
|
|
3392
|
+
"div",
|
|
3393
|
+
{
|
|
3394
|
+
className: g.id === V ? "merge-target" : void 0,
|
|
3395
|
+
style: { display: "flex", flex: "1 1 0", minWidth: 0, minHeight: 0 },
|
|
3396
|
+
children: [
|
|
3397
|
+
/* @__PURE__ */ a("div", { style: { flex: g.ratio, minWidth: 0, minHeight: 0, display: "flex" }, children: A(g.first) }),
|
|
3398
|
+
/* @__PURE__ */ a(
|
|
3399
|
+
bn,
|
|
3400
|
+
{
|
|
3401
|
+
onDrag: (F, C) => x((E) => se(E, g.id, F / C))
|
|
3402
|
+
}
|
|
3403
|
+
),
|
|
3404
|
+
/* @__PURE__ */ a("div", { style: { flex: 1 - g.ratio, minWidth: 0, minHeight: 0, display: "flex" }, children: A(g.second) })
|
|
3405
|
+
]
|
|
3406
|
+
},
|
|
3407
|
+
g.id
|
|
3408
|
+
) : /* @__PURE__ */ p(
|
|
3409
|
+
"div",
|
|
3410
|
+
{
|
|
3411
|
+
className: g.id === V ? "merge-target" : void 0,
|
|
3412
|
+
style: { display: "flex", flexDirection: "column", flex: "1 1 0", minWidth: 0, minHeight: 0 },
|
|
3413
|
+
children: [
|
|
3414
|
+
/* @__PURE__ */ a("div", { style: { flex: g.ratio, minWidth: 0, minHeight: 0, display: "flex" }, children: A(g.first) }),
|
|
3415
|
+
/* @__PURE__ */ a(
|
|
3416
|
+
vn,
|
|
3417
|
+
{
|
|
3418
|
+
onDrag: (F, C) => x((E) => se(E, g.id, F / C))
|
|
3419
|
+
}
|
|
3420
|
+
),
|
|
3421
|
+
/* @__PURE__ */ a("div", { style: { flex: 1 - g.ratio, minWidth: 0, minHeight: 0, display: "flex" }, children: A(g.second) })
|
|
3422
|
+
]
|
|
3423
|
+
},
|
|
3424
|
+
g.id
|
|
3425
|
+
);
|
|
3426
|
+
};
|
|
3427
|
+
return /* @__PURE__ */ p("div", { className: "app", children: [
|
|
3428
|
+
/* @__PURE__ */ a(yn, {}),
|
|
3429
|
+
/* @__PURE__ */ a(
|
|
3430
|
+
gn,
|
|
3431
|
+
{
|
|
3432
|
+
workspaces: c,
|
|
3433
|
+
activeWorkspaceId: f,
|
|
3434
|
+
onSwitchWorkspace: I,
|
|
3435
|
+
onAddWorkspace: j,
|
|
3436
|
+
onRemoveWorkspace: N,
|
|
3437
|
+
onRenameWorkspace: _,
|
|
3438
|
+
onDuplicateWorkspace: k,
|
|
3439
|
+
onMoveWorkspace: O
|
|
3440
|
+
}
|
|
3441
|
+
),
|
|
3442
|
+
/* @__PURE__ */ a("div", { className: "app-body", children: A(b) }),
|
|
3443
|
+
e && /* @__PURE__ */ a(Qn, { onClose: () => t(!1) }),
|
|
3444
|
+
s && /* @__PURE__ */ a(et, { onClose: () => r(!1) }),
|
|
3445
|
+
o && /* @__PURE__ */ a(nt, { onClose: () => i(!1) }),
|
|
3446
|
+
d && /* @__PURE__ */ a(tt, { onClose: () => m(!1) }),
|
|
3447
|
+
/* @__PURE__ */ a(fn, {}),
|
|
3448
|
+
/* @__PURE__ */ a(at, {})
|
|
3449
|
+
] });
|
|
3450
|
+
}
|
|
3451
|
+
function mt() {
|
|
3452
|
+
const n = y((i) => i.status), e = y((i) => i.run), t = y((i) => i.save), s = y((i) => i.load), r = y((i) => i.exportPython), o = $(null);
|
|
3453
|
+
return /* @__PURE__ */ p("div", { className: "toolbar", children: [
|
|
3454
|
+
/* @__PURE__ */ a("h1", { children: "Entropia Riko" }),
|
|
3455
|
+
/* @__PURE__ */ a(
|
|
3456
|
+
"button",
|
|
3457
|
+
{
|
|
3458
|
+
className: "btn btn-primary",
|
|
3459
|
+
onClick: e,
|
|
3460
|
+
disabled: n === "running",
|
|
3461
|
+
children: n === "running" ? "Running…" : "Inference"
|
|
3462
|
+
}
|
|
3463
|
+
),
|
|
3464
|
+
/* @__PURE__ */ a(
|
|
3465
|
+
"button",
|
|
3466
|
+
{
|
|
3467
|
+
className: "btn",
|
|
3468
|
+
onClick: () => {
|
|
3469
|
+
y.getState().log("训练模式:单步执行..."), e();
|
|
3470
|
+
},
|
|
3471
|
+
disabled: n === "running",
|
|
3472
|
+
style: { borderColor: "var(--color-success)", color: "var(--color-success)" },
|
|
3473
|
+
children: "Train"
|
|
3474
|
+
}
|
|
3475
|
+
),
|
|
3476
|
+
/* @__PURE__ */ a("button", { className: "btn", onClick: r, disabled: n === "running", children: "Export .py" }),
|
|
3477
|
+
/* @__PURE__ */ a(
|
|
3478
|
+
"button",
|
|
3479
|
+
{
|
|
3480
|
+
className: "btn",
|
|
3481
|
+
onClick: () => y.getState().addNode("mnist_loader", { x: 300, y: 200 }),
|
|
3482
|
+
disabled: n === "running",
|
|
3483
|
+
children: "Load Data"
|
|
3484
|
+
}
|
|
3485
|
+
),
|
|
3486
|
+
/* @__PURE__ */ a("button", { className: "btn", onClick: t, disabled: n === "running", children: "Save" }),
|
|
3487
|
+
/* @__PURE__ */ a(
|
|
3488
|
+
"button",
|
|
3489
|
+
{
|
|
3490
|
+
className: "btn",
|
|
3491
|
+
onClick: () => {
|
|
3492
|
+
var i;
|
|
3493
|
+
return (i = o.current) == null ? void 0 : i.click();
|
|
3494
|
+
},
|
|
3495
|
+
disabled: n === "running",
|
|
3496
|
+
children: "Load"
|
|
3497
|
+
}
|
|
3498
|
+
),
|
|
3499
|
+
/* @__PURE__ */ a(
|
|
3500
|
+
"input",
|
|
3501
|
+
{
|
|
3502
|
+
ref: o,
|
|
3503
|
+
type: "file",
|
|
3504
|
+
accept: ".json,application/json",
|
|
3505
|
+
style: { display: "none" },
|
|
3506
|
+
onChange: (i) => {
|
|
3507
|
+
var m;
|
|
3508
|
+
const d = (m = i.target.files) == null ? void 0 : m[0];
|
|
3509
|
+
d && s(d), i.target.value = "";
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
),
|
|
3513
|
+
/* @__PURE__ */ a("div", { className: "spacer" }),
|
|
3514
|
+
/* @__PURE__ */ a("span", { className: `status-badge ${n}`, children: n })
|
|
3515
|
+
] });
|
|
3516
|
+
}
|
|
3517
|
+
function He(n, e = []) {
|
|
3518
|
+
for (const t of n)
|
|
3519
|
+
t.type === "file" ? e.push(t.path) : t.children && He(t.children, e);
|
|
3520
|
+
return e;
|
|
3521
|
+
}
|
|
3522
|
+
function ht({ onClose: n }) {
|
|
3523
|
+
const e = y((b) => b.pushToast), t = y((b) => b.bumpProject), [s, r] = P("~"), [o, i] = P([]), [d, m] = P(""), [c, u] = P([]), [h, v] = P(""), l = async (b) => {
|
|
3524
|
+
const x = await (await fetch(`/api/fs/list?path=${encodeURIComponent(b)}`)).json();
|
|
3525
|
+
x.status === "success" ? (r(x.path), m(x.parent), i(x.entries ?? [])) : e("error", x.error ?? "list failed");
|
|
3526
|
+
}, w = async () => {
|
|
3527
|
+
try {
|
|
3528
|
+
const T = await (await fetch("/api/project/tree")).json(), x = He(Array.isArray(T.tree) ? T.tree : []);
|
|
3529
|
+
u(x), x.length && !h && v(x[0]);
|
|
3530
|
+
} catch {
|
|
3531
|
+
}
|
|
3532
|
+
};
|
|
3533
|
+
D(() => {
|
|
3534
|
+
l("~"), w();
|
|
3535
|
+
}, []);
|
|
3536
|
+
const f = async (b) => {
|
|
3537
|
+
const x = await (await fetch("/api/fs/import", {
|
|
3538
|
+
method: "POST",
|
|
3539
|
+
headers: { "Content-Type": "application/json" },
|
|
3540
|
+
body: JSON.stringify({ src: b.path })
|
|
3541
|
+
})).json();
|
|
3542
|
+
x.status === "success" ? (e("success", `Imported ${b.name}`), t(), w()) : e("error", x.error ?? "import failed");
|
|
3543
|
+
}, S = async () => {
|
|
3544
|
+
if (!h) return;
|
|
3545
|
+
const T = await (await fetch("/api/fs/export", {
|
|
3546
|
+
method: "POST",
|
|
3547
|
+
headers: { "Content-Type": "application/json" },
|
|
3548
|
+
body: JSON.stringify({ src: h, dest: s })
|
|
3549
|
+
})).json();
|
|
3550
|
+
T.status === "success" ? e("success", `Exported to ${T.path}`) : e("error", T.error ?? "export failed");
|
|
3551
|
+
};
|
|
3552
|
+
return /* @__PURE__ */ a("div", { className: "prefs-overlay", onClick: n, children: /* @__PURE__ */ p("div", { className: "explorer", onClick: (b) => b.stopPropagation(), children: [
|
|
3553
|
+
/* @__PURE__ */ p("div", { className: "prefs-header", children: [
|
|
3554
|
+
/* @__PURE__ */ a("span", { className: "prefs-title", children: "File Explorer" }),
|
|
3555
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: n, children: "✕" })
|
|
3556
|
+
] }),
|
|
3557
|
+
/* @__PURE__ */ p("div", { className: "explorer-toolbar", children: [
|
|
3558
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", title: "Up", onClick: () => l(d), children: "↑" }),
|
|
3559
|
+
/* @__PURE__ */ a(
|
|
3560
|
+
"input",
|
|
3561
|
+
{
|
|
3562
|
+
className: "prefs-input",
|
|
3563
|
+
value: s,
|
|
3564
|
+
onChange: (b) => r(b.target.value),
|
|
3565
|
+
onKeyDown: (b) => {
|
|
3566
|
+
b.key === "Enter" && l(s);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
),
|
|
3570
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () => l(s), children: "Go" })
|
|
3571
|
+
] }),
|
|
3572
|
+
/* @__PURE__ */ p("div", { className: "explorer-body", children: [
|
|
3573
|
+
/* @__PURE__ */ p("div", { className: "explorer-list", children: [
|
|
3574
|
+
o.map((b) => /* @__PURE__ */ p(
|
|
3575
|
+
"div",
|
|
3576
|
+
{
|
|
3577
|
+
className: "explorer-row",
|
|
3578
|
+
onDoubleClick: () => b.type === "dir" && l(b.path),
|
|
3579
|
+
children: [
|
|
3580
|
+
/* @__PURE__ */ a("span", { className: "explorer-icon", children: b.type === "dir" ? "▦" : "▤" }),
|
|
3581
|
+
/* @__PURE__ */ a("span", { className: "explorer-name", children: b.name }),
|
|
3582
|
+
/* @__PURE__ */ a("span", { className: "explorer-size", children: b.type === "file" ? `${Math.round(b.size / 1024)} KB` : "" }),
|
|
3583
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: () => f(b), title: "Import into working folder", children: "⇥ Import" })
|
|
3584
|
+
]
|
|
3585
|
+
},
|
|
3586
|
+
b.path
|
|
3587
|
+
)),
|
|
3588
|
+
o.length === 0 && /* @__PURE__ */ a("div", { className: "welcome-empty", children: "Empty folder." })
|
|
3589
|
+
] }),
|
|
3590
|
+
/* @__PURE__ */ p("div", { className: "explorer-export", children: [
|
|
3591
|
+
/* @__PURE__ */ a("div", { className: "prefs-desc", style: { margin: 0 }, children: "Export a working-folder file to the current folder:" }),
|
|
3592
|
+
/* @__PURE__ */ a("select", { className: "prefs-input", value: h, onChange: (b) => v(b.target.value), children: c.map((b) => /* @__PURE__ */ a("option", { value: b, children: b }, b)) }),
|
|
3593
|
+
/* @__PURE__ */ a("button", { className: "btn", disabled: !h, onClick: S, children: "Export here" })
|
|
3594
|
+
] })
|
|
3595
|
+
] })
|
|
3596
|
+
] }) });
|
|
3597
|
+
}
|
|
3598
|
+
function ft({
|
|
3599
|
+
path: n,
|
|
3600
|
+
name: e,
|
|
3601
|
+
onClose: t
|
|
3602
|
+
}) {
|
|
3603
|
+
const [s, r] = P(""), [o, i] = P(null), d = y((c) => c.pushToast);
|
|
3604
|
+
return D(() => {
|
|
3605
|
+
fetch("/api/project/code", {
|
|
3606
|
+
method: "POST",
|
|
3607
|
+
headers: { "Content-Type": "application/json" },
|
|
3608
|
+
body: JSON.stringify({ path: n })
|
|
3609
|
+
}).then((c) => c.json()).then((c) => {
|
|
3610
|
+
c.status === "success" ? r(c.code ?? "") : i(c.error ?? "failed to generate code");
|
|
3611
|
+
}).catch((c) => i(c instanceof Error ? c.message : String(c)));
|
|
3612
|
+
}, [n]), /* @__PURE__ */ a("div", { className: "prefs-overlay", onClick: t, children: /* @__PURE__ */ p("div", { className: "code-preview", onClick: (c) => c.stopPropagation(), children: [
|
|
3613
|
+
/* @__PURE__ */ p("div", { className: "code-preview-head", children: [
|
|
3614
|
+
/* @__PURE__ */ p("span", { className: "code-preview-title", children: [
|
|
3615
|
+
"PyTorch · ",
|
|
3616
|
+
e
|
|
3617
|
+
] }),
|
|
3618
|
+
/* @__PURE__ */ a("span", { className: "spacer" }),
|
|
3619
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: async () => {
|
|
3620
|
+
try {
|
|
3621
|
+
await navigator.clipboard.writeText(s), d("success", "Copied PyTorch code");
|
|
3622
|
+
} catch {
|
|
3623
|
+
d("error", "Copy failed");
|
|
3624
|
+
}
|
|
3625
|
+
}, children: "Copy" }),
|
|
3626
|
+
/* @__PURE__ */ a("button", { className: "panel-btn", onClick: t, children: "✕" })
|
|
3627
|
+
] }),
|
|
3628
|
+
/* @__PURE__ */ a("pre", { className: "code-preview-body", children: o ? `// ${o}` : s || "// generating…" })
|
|
3629
|
+
] }) });
|
|
3630
|
+
}
|
|
3631
|
+
export {
|
|
3632
|
+
Z as APP_VERSION,
|
|
3633
|
+
nt as AboutPanel,
|
|
3634
|
+
ut as App,
|
|
3635
|
+
ie as BrandLogo,
|
|
3636
|
+
Hn as CodeEditor,
|
|
3637
|
+
ft as CodePreview,
|
|
3638
|
+
Cn as ContextMenu,
|
|
3639
|
+
Gn as DocsPanel,
|
|
3640
|
+
ht as FileExplorer,
|
|
3641
|
+
wn as FileManager,
|
|
3642
|
+
fn as FilePickerHost,
|
|
3643
|
+
oe as FloatingWindow,
|
|
3644
|
+
Fn as GraphCanvas,
|
|
3645
|
+
vn as HSplitter,
|
|
3646
|
+
Kn as HandwritingPad,
|
|
3647
|
+
tt as ImportFolderPanel,
|
|
3648
|
+
Ln as LossPanel,
|
|
3649
|
+
gn as MenuBar,
|
|
3650
|
+
Sn as NodeCard,
|
|
3651
|
+
Nn as NodeLibrary,
|
|
3652
|
+
Ge as PANEL_TYPES,
|
|
3653
|
+
Xn as PanelSlot,
|
|
3654
|
+
Be as PluginPanel,
|
|
3655
|
+
te as PopupMenu,
|
|
3656
|
+
et as PreferencesPanel,
|
|
3657
|
+
Un as ProjectPanel,
|
|
3658
|
+
On as SideInspector,
|
|
3659
|
+
$n as StatusPanel,
|
|
3660
|
+
yn as Titlebar,
|
|
3661
|
+
at as ToastStack,
|
|
3662
|
+
mt as Toolbar,
|
|
3663
|
+
bn as VSplitter,
|
|
3664
|
+
ue as WORKSPACE_PRESETS,
|
|
3665
|
+
Qn as WelcomePanel,
|
|
3666
|
+
mn as WorkspaceTabs,
|
|
3667
|
+
$e as applyBackground,
|
|
3668
|
+
je as applyTheme,
|
|
3669
|
+
me as closeLeaf,
|
|
3670
|
+
ye as countLeaves,
|
|
3671
|
+
pn as defaultLayout,
|
|
3672
|
+
R as leaf,
|
|
3673
|
+
ln as loadTheme,
|
|
3674
|
+
he as mergeLeaf,
|
|
3675
|
+
Y as openFilePicker,
|
|
3676
|
+
zn as panelLabel,
|
|
3677
|
+
pt as registerPanelContent,
|
|
3678
|
+
Vn as renderPanelContent,
|
|
3679
|
+
se as resizeSplit,
|
|
3680
|
+
ge as setLeafType,
|
|
3681
|
+
fe as siblingNodeId,
|
|
3682
|
+
L as split,
|
|
3683
|
+
ae as splitLeaf,
|
|
3684
|
+
y as useGraphStore
|
|
3685
|
+
};
|