entropia-template-ui 0.2.2 → 0.2.4

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