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