@loom-framework/core 0.1.0-alpha.164 → 0.1.0-alpha.166

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 (57) hide show
  1. package/builtin-skills/loom/SKILL.md +9 -6
  2. package/builtin-skills/loom/references/eject.md +102 -0
  3. package/dist/cli/commands/eject.d.ts.map +1 -1
  4. package/dist/cli/commands/eject.js +52 -285
  5. package/dist/cli/commands/eject.js.map +1 -1
  6. package/dist/cli/commands/generate-system-settings.d.ts +3 -3
  7. package/dist/cli/commands/generate-system-settings.d.ts.map +1 -1
  8. package/dist/cli/commands/generate-system-settings.js +29 -204
  9. package/dist/cli/commands/generate-system-settings.js.map +1 -1
  10. package/dist/cli/commands/init.d.ts.map +1 -1
  11. package/dist/cli/commands/init.js +27 -44
  12. package/dist/cli/commands/init.js.map +1 -1
  13. package/dist/cli/helpers/app-tsx-utils.d.ts +12 -0
  14. package/dist/cli/helpers/app-tsx-utils.d.ts.map +1 -0
  15. package/dist/cli/helpers/app-tsx-utils.js +22 -0
  16. package/dist/cli/helpers/app-tsx-utils.js.map +1 -0
  17. package/dist/cli/helpers/app-tsx-wiring-engine.d.ts +70 -0
  18. package/dist/cli/helpers/app-tsx-wiring-engine.d.ts.map +1 -0
  19. package/dist/cli/helpers/app-tsx-wiring-engine.js +546 -0
  20. package/dist/cli/helpers/app-tsx-wiring-engine.js.map +1 -0
  21. package/dist/cli/helpers/app-tsx-wiring.d.ts +4 -39
  22. package/dist/cli/helpers/app-tsx-wiring.d.ts.map +1 -1
  23. package/dist/cli/helpers/app-tsx-wiring.js +5 -608
  24. package/dist/cli/helpers/app-tsx-wiring.js.map +1 -1
  25. package/dist/cli/helpers/system-page-config.d.ts +27 -0
  26. package/dist/cli/helpers/system-page-config.d.ts.map +1 -0
  27. package/dist/cli/helpers/system-page-config.js +73 -0
  28. package/dist/cli/helpers/system-page-config.js.map +1 -0
  29. package/dist/cli/templates/login-page.d.ts +2 -3
  30. package/dist/cli/templates/login-page.d.ts.map +1 -1
  31. package/dist/cli/templates/login-page.js +18 -29
  32. package/dist/cli/templates/login-page.js.map +1 -1
  33. package/dist/cli/templates/model-management-page.d.ts +2 -3
  34. package/dist/cli/templates/model-management-page.d.ts.map +1 -1
  35. package/dist/cli/templates/model-management-page.js +11 -9
  36. package/dist/cli/templates/model-management-page.js.map +1 -1
  37. package/dist/cli/templates/notification-center-page.d.ts +2 -3
  38. package/dist/cli/templates/notification-center-page.d.ts.map +1 -1
  39. package/dist/cli/templates/notification-center-page.js +25 -62
  40. package/dist/cli/templates/notification-center-page.js.map +1 -1
  41. package/dist/cli/templates/notification-detail-page.d.ts +2 -3
  42. package/dist/cli/templates/notification-detail-page.d.ts.map +1 -1
  43. package/dist/cli/templates/notification-detail-page.js +13 -41
  44. package/dist/cli/templates/notification-detail-page.js.map +1 -1
  45. package/dist/cli/templates/process-management-page.d.ts +3 -3
  46. package/dist/cli/templates/process-management-page.d.ts.map +1 -1
  47. package/dist/cli/templates/process-management-page.js +75 -565
  48. package/dist/cli/templates/process-management-page.js.map +1 -1
  49. package/dist/cli/templates/skill-management-page.d.ts +2 -3
  50. package/dist/cli/templates/skill-management-page.d.ts.map +1 -1
  51. package/dist/cli/templates/skill-management-page.js +12 -15
  52. package/dist/cli/templates/skill-management-page.js.map +1 -1
  53. package/dist/cli/templates/user-management-page.d.ts +2 -4
  54. package/dist/cli/templates/user-management-page.d.ts.map +1 -1
  55. package/dist/cli/templates/user-management-page.js +21 -22
  56. package/dist/cli/templates/user-management-page.js.map +1 -1
  57. package/package.json +4 -2
@@ -1,33 +1,30 @@
1
1
  /**
2
- * ProcessManagementPage Template
2
+ * Process Management page template
3
3
  *
4
- * Follows SkillManagementPage patterns: flex column layout, breadcrumb,
5
- * theme tokens, showLine/blockNode tree, i18n, Card wrapper.
4
+ * GENERATED by scripts/generate-templates.ts DO NOT EDIT MANUALLY.
5
+ * Source: packages/frontend-antd/src/components/pages/process-management-page.tsx
6
6
  */
7
7
  export function processManagementPageTemplate() {
8
- return `import React, { useState, useEffect, useCallback, useMemo, useContext, useRef } from 'react';
8
+ return `/**
9
+ * ProcessManagementPage — Orchestrator composing Process sub-components
10
+ */
11
+
12
+ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
9
13
  import {
10
14
  Card,
11
- Tree,
12
15
  Tabs,
13
- Table,
14
- Tag,
15
16
  Button,
16
17
  Modal,
17
18
  Spin,
19
+ Tooltip,
18
20
  Segmented,
19
21
  Dropdown,
20
22
  Flex,
21
23
  Breadcrumb,
22
24
  Space,
23
- Descriptions,
24
- Tooltip,
25
- Timeline,
26
25
  Empty,
27
26
  message,
28
- Typography,
29
27
  theme,
30
- DatePicker,
31
28
  } from 'antd';
32
29
  import {
33
30
  PlayCircleOutlined,
@@ -35,50 +32,22 @@ import {
35
32
  DeleteOutlined,
36
33
  ReloadOutlined,
37
34
  PlusOutlined,
38
- FileTextOutlined,
39
- FolderOutlined,
40
- ClockCircleOutlined,
41
- ThunderboltOutlined,
42
- CalendarOutlined,
43
- ExclamationCircleOutlined,
44
35
  MoreOutlined,
45
36
  HomeOutlined,
46
37
  EyeOutlined,
47
38
  CodeOutlined,
48
- MessageOutlined,
49
39
  } from '@ant-design/icons';
50
40
  import {
51
- useLocale, useLoomTheme, useAppShell, registerMessages,
52
- SkillFileViewer,
53
- fetchProcesses, fetchProcess,
54
- fetchProcessLogs, fetchProcessMetrics, fetchProcessMetricsConfig,
55
- fetchProcessMetricsSnapshots, fetchProcessMetricsViewConfig,
56
- fetchProcessFiles, fetchProcessFileContent,
57
- runProcess, pauseProcess, resumeProcess, deleteProcess,
58
- getFileIcon,
59
- AIContext,
60
- } from '@loom-framework/frontend-antd';
61
- import type {
62
- ProcessInfo, ProcessLogEntry, ProcessLogListResponse, ProcessMetricsAggregate, FileContent,
63
- ProcessMetricSnapshot, ProcessMetricsViewConfig,
64
- } from '@loom-framework/frontend-antd';
65
- import { DashboardChart } from '@loom-framework/frontend-antd';
66
- import type { Dayjs } from 'dayjs';
67
-
68
- const { Text } = Typography;
69
-
70
- interface ProcessTreeNode {
71
- key: string;
72
- title: string;
73
- icon: React.ReactNode;
74
- isLeaf: boolean;
75
- isFile?: boolean;
76
- isDir?: boolean;
77
- isProcess?: boolean;
78
- filePath?: string;
79
- processName?: string;
80
- children?: ProcessTreeNode[];
81
- }
41
+ import type { ProcessInfo, FileContent, ProcessTreeNode } from '@loom-framework/frontend-antd';
42
+ import { useLocale, registerMessages, useLoomTheme, useAppShell, SkillFileViewer, ProcessTree, buildTreeData, loadDirChildren, removeTreeNode, enrichTreeWithFiles, ProcessOverview, ProcessLogs, ProcessMetrics } from '@loom-framework/frontend-antd';
43
+ fetchProcesses,
44
+ fetchProcess,
45
+ fetchProcessFileContent,
46
+ runProcess,
47
+ pauseProcess,
48
+ resumeProcess,
49
+ deleteProcess,
50
+ } from './process-api.js';
82
51
 
83
52
  registerMessages('zh-CN', {
84
53
  'process.title': '过程管理',
@@ -106,11 +75,6 @@ registerMessages('zh-CN', {
106
75
  'process.noProcesses': '暂无过程',
107
76
  'process.noMetrics': '未配置指标',
108
77
  'process.noLogs': '暂无执行日志',
109
- 'process.timeRange': '时间范围',
110
- 'process.total': '共 {total} 条',
111
- 'process.runId': '运行 ID',
112
- 'process.triggerType': '触发方式',
113
- 'process.startedAt': '开始时间',
114
78
  'process.queue': '执行队列',
115
79
  'process.queueRunning': '运行中',
116
80
  'process.queueQueued': '等待中',
@@ -139,23 +103,6 @@ registerMessages('zh-CN', {
139
103
  'process.selectToView': '选择左侧过程或文件以查看详情',
140
104
  'process.preview': '预览',
141
105
  'process.source': '源码',
142
- 'process.colDate': '日期',
143
- 'process.colTotal': '总执行',
144
- 'process.colCompleted': '成功',
145
- 'process.colFailed': '失败',
146
- 'process.colTimeout': '超时',
147
- 'process.colAvgDuration': '平均耗时',
148
- 'process.colRunId': '执行 ID',
149
- 'process.colTriggerType': '触发类型',
150
- 'process.colResult': '结果',
151
- 'process.colTime': '时间',
152
- 'process.viewSession': '查看 Loom 会话',
153
- 'process.businessMetrics': '业务指标',
154
- 'process.operationalMetrics': '运行统计',
155
- 'process.noBusinessMetrics': '暂无业务指标数据',
156
- 'process.metricValue': '指标值',
157
- 'process.metricTarget': '目标',
158
- 'process.metricTrend': '趋势',
159
106
  });
160
107
 
161
108
  registerMessages('en-US', {
@@ -184,11 +131,6 @@ registerMessages('en-US', {
184
131
  'process.noProcesses': 'No processes',
185
132
  'process.noMetrics': 'No metrics configured',
186
133
  'process.noLogs': 'No execution logs',
187
- 'process.timeRange': 'Time Range',
188
- 'process.total': '{total} total',
189
- 'process.runId': 'Run ID',
190
- 'process.triggerType': 'Trigger',
191
- 'process.startedAt': 'Started At',
192
134
  'process.queue': 'Execution Queue',
193
135
  'process.queueRunning': 'Running',
194
136
  'process.queueQueued': 'Queued',
@@ -217,53 +159,13 @@ registerMessages('en-US', {
217
159
  'process.selectToView': 'Select a process or file to view details',
218
160
  'process.preview': 'Preview',
219
161
  'process.source': 'Source',
220
- 'process.colDate': 'Date',
221
- 'process.colTotal': 'Total',
222
- 'process.colCompleted': 'Completed',
223
- 'process.colFailed': 'Failed',
224
- 'process.colTimeout': 'Timeout',
225
- 'process.colAvgDuration': 'Avg Duration',
226
- 'process.colRunId': 'Run ID',
227
- 'process.colTriggerType': 'Trigger',
228
- 'process.colResult': 'Result',
229
- 'process.colTime': 'Time',
230
- 'process.viewSession': 'View Loom Session',
231
- 'process.businessMetrics': 'Business Metrics',
232
- 'process.operationalMetrics': 'Operational Metrics',
233
- 'process.noBusinessMetrics': 'No business metrics data',
234
- 'process.metricValue': 'Value',
235
- 'process.metricTarget': 'Target',
236
- 'process.metricTrend': 'Trend',
237
162
  });
238
163
 
239
- const STATUS_COLORS: Record<string, string> = {
240
- idle: 'default',
241
- running: 'processing',
242
- busy: 'orange',
243
- paused: 'warning',
244
- error: 'error',
245
- };
246
-
247
- const STATUS_ICONS: Record<string, React.ReactNode> = {
248
- idle: <ClockCircleOutlined />,
249
- running: <PlayCircleOutlined />,
250
- busy: <ThunderboltOutlined />,
251
- paused: <PauseCircleOutlined />,
252
- error: <ExclamationCircleOutlined />,
253
- };
254
-
255
- const TRIGGER_ICONS: Record<string, React.ReactNode> = {
256
- cron: <CalendarOutlined />,
257
- event: <ThunderboltOutlined />,
258
- manual: <PlayCircleOutlined />,
259
- };
260
-
261
- export default function ProcessManagementPage(): React.ReactElement {
164
+ function ProcessManagementPage(): React.ReactElement {
262
165
  const { mode } = useLoomTheme();
263
166
  const { t } = useLocale();
264
167
  const { breadcrumbs, onNavClick } = useAppShell();
265
168
  const { token } = theme.useToken();
266
- const aiContext = useContext(AIContext);
267
169
 
268
170
  const [processes, setProcesses] = useState<ProcessInfo[]>([]);
269
171
  const [treeData, setTreeData] = useState<ProcessTreeNode[]>([]);
@@ -275,110 +177,38 @@ export default function ProcessManagementPage(): React.ReactElement {
275
177
  const [loading, setLoading] = useState(false);
276
178
  const [viewMode, setViewMode] = useState<'preview' | 'source'>('preview');
277
179
  const [activeTab, setActiveTab] = useState('overview');
278
- const [logs, setLogs] = useState<ProcessLogEntry[]>([]);
279
- const [logsTotal, setLogsTotal] = useState(0);
280
- const [logsPagination, setLogsPagination] = useState({ current: 1, pageSize: 20 });
281
- const [logsDateRange, setLogsDateRange] = useState<[Dayjs | null, Dayjs | null] | null>(null);
282
- const [logsLoading, setLogsLoading] = useState(false);
283
- const [metrics, setMetrics] = useState<ProcessMetricsAggregate[]>([]);
284
- const [metricsConfig, setMetricsConfig] = useState<Record<string, unknown>[]>([]);
285
- const [metricSnapshots, setMetricSnapshots] = useState<ProcessMetricSnapshot[]>([]);
286
- const [metricsViewConfig, setMetricsViewConfig] = useState<ProcessMetricsViewConfig | null>(null);
287
180
  const [executing, setExecuting] = useState(false);
288
181
 
289
- const buildTreeData = useCallback((processList: ProcessInfo[]): ProcessTreeNode[] => {
290
- return processList.map((proc) => {
291
- const children: ProcessTreeNode[] = [];
292
-
293
- if (proc.hasProcessFile) {
294
- children.push({
295
- key: \`\${proc.name}/PROCESS.md\`,
296
- title: 'PROCESS.md',
297
- icon: <FileTextOutlined style={{ color: token.colorPrimary }} />,
298
- isLeaf: true,
299
- isFile: true,
300
- filePath: 'PROCESS.md',
301
- processName: proc.name,
302
- });
303
- }
304
-
305
- return {
306
- key: proc.name,
307
- title: proc.name,
308
- icon: proc.hasProcessFile ? <FolderOutlined /> : <ThunderboltOutlined style={{ color: token.colorWarning }} />,
309
- isLeaf: false,
310
- isFile: false,
311
- isProcess: true,
312
- children,
313
- processName: proc.name,
314
- };
315
- });
316
- }, [token]);
317
-
318
182
  const loadProcesses = useCallback(async () => {
319
183
  setLoading(true);
320
184
  try {
321
185
  const result = await fetchProcesses();
322
186
  const list = result.data ?? [];
323
187
  setProcesses(list);
324
- const initialTree = buildTreeData(list);
188
+ const initialTree = buildTreeData(list, token);
325
189
  setTreeData(initialTree);
326
190
  setExpandedKeys(list.map((p) => p.name));
327
191
  // Enrich tree with actual file structure (e.g. references/ directories)
328
- for (let i = 0; i < initialTree.length; i++) {
329
- const node = initialTree[i]!;
330
- if (!node.isProcess || !node.processName) continue;
331
- const proc = list.find(p => p.name === node.processName);
332
- if (!proc?.hasProcessFile) continue;
333
- const hasReferences = node.children?.some(c => c.filePath === 'references' && c.isDir);
334
- if (hasReferences) continue;
335
- try {
336
- const files = await fetchProcessFiles(node.processName);
337
- const flatFiles: typeof files = [];
338
- const flatten = (fs: typeof files) => { for (const f of fs) { if (f.isDirectory && f.children) { flatten(f.children); } else if (!f.isDirectory) { flatFiles.push(f); } } };
339
- flatten(files);
340
- const refFiles = flatFiles.filter(f => f.path.startsWith('references/'));
341
- if (refFiles.length > 0) {
342
- const { children: refChildren } = await loadDirChildren(node.processName, 'references');
343
- if (!refChildren.length) continue;
344
- const updated = { ...node, children: [...(node.children ?? []), {
345
- key: \`\${node.processName}/references\`,
346
- title: 'references/',
347
- icon: <FolderOutlined style={{ color: token.colorWarning }} />,
348
- isLeaf: false,
349
- isFile: false,
350
- isDir: true,
351
- filePath: 'references',
352
- children: refChildren,
353
- processName: node.processName,
354
- }] };
355
- initialTree[i] = updated;
356
- setTreeData([...initialTree]);
357
- }
358
- } catch { /* ignore */ }
359
- }
192
+ const enriched = await enrichTreeWithFiles(initialTree, list, token);
193
+ setTreeData(enriched);
360
194
  } catch {
361
195
  // error handled silently
362
196
  } finally {
363
197
  setLoading(false);
364
198
  }
365
- }, [buildTreeData, loadDirChildren]);
199
+ }, [token]);
366
200
 
367
201
  useEffect(() => { loadProcesses(); }, [loadProcesses]);
368
202
 
369
- // Auto-refresh every 5 seconds, pause when tab is hidden
203
+ // Auto-refresh every 5 seconds
370
204
  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
371
205
  useEffect(() => {
372
206
  const poll = () => {
373
- if (document.visibilityState === 'visible') {
374
- loadProcesses();
375
- }
207
+ if (document.visibilityState === 'visible') loadProcesses();
376
208
  };
377
209
  pollRef.current = setInterval(poll, 5000);
378
210
  const handleVisibility = () => {
379
- if (document.visibilityState === 'visible') {
380
- poll(); // Immediate refresh when tab becomes visible
381
- }
211
+ if (document.visibilityState === 'visible') poll();
382
212
  };
383
213
  document.addEventListener('visibilitychange', handleVisibility);
384
214
  return () => {
@@ -387,60 +217,21 @@ export default function ProcessManagementPage(): React.ReactElement {
387
217
  };
388
218
  }, [loadProcesses]);
389
219
 
390
- const loadDirChildren = useCallback(async (processName: string, dirName: string): Promise<{ children: ProcessTreeNode[]; empty: boolean }> => {
391
- const data = await fetchProcessFiles(processName);
392
- const flatFiles: typeof data = [];
393
- const flatten = (files: typeof data) => { for (const f of files) { if (f.isDirectory && f.children) { flatten(f.children); } else if (!f.isDirectory) { flatFiles.push(f); } } };
394
- flatten(data);
395
- const dirFiles = flatFiles.filter((f) => f.path.startsWith(\`\${dirName}/\`));
396
-
397
- if (dirFiles.length === 0) {
398
- return { children: [], empty: true };
399
- }
400
-
401
- const dirChildren: ProcessTreeNode[] = [];
402
- for (const f of dirFiles) {
403
- const relativePath = f.path.slice(dirName.length + 1);
404
- const parts = relativePath.split('/');
405
-
406
- let currentLevel = dirChildren;
407
- for (let i = 0; i < parts.length; i++) {
408
- const part = parts[i]!;
409
- const isLast = i === parts.length - 1;
410
- const partialPath = \`\${dirName}/\${parts.slice(0, i + 1).join('/')}\`;
411
-
412
- if (isLast) {
413
- currentLevel.push({
414
- key: \`\${processName}/\${f.path}\`,
415
- title: part,
416
- icon: getFileIcon(part, { primary: token.colorPrimary, warning: token.colorWarning, success: token.colorSuccess }),
417
- isLeaf: true,
418
- isFile: true,
419
- filePath: f.path,
420
- processName,
421
- });
422
- } else {
423
- let dirNode = currentLevel.find((n) => n.isDir && n.filePath === partialPath);
424
- if (!dirNode) {
425
- dirNode = {
426
- key: \`\${processName}/\${partialPath}\`,
427
- title: \`\${part}/\`,
428
- icon: <FolderOutlined style={{ color: token.colorWarning }} />,
429
- isLeaf: false,
430
- isFile: false,
431
- isDir: true,
432
- filePath: partialPath,
433
- children: [],
434
- processName,
435
- };
436
- currentLevel.push(dirNode);
437
- }
438
- currentLevel = dirNode.children!;
439
- }
220
+ const selectProcess = useCallback(async (name: string) => {
221
+ try {
222
+ const data = await fetchProcess(name);
223
+ setSelectedProcess(data);
224
+ setFileContent(null);
225
+ setProcessMdContent(null);
226
+ setActiveTab('overview');
227
+ if (data.hasProcessFile) {
228
+ try {
229
+ const mdContent = await fetchProcessFileContent(name, 'PROCESS.md');
230
+ setProcessMdContent({ content: mdContent, path: 'PROCESS.md' });
231
+ } catch { /* ignore */ }
440
232
  }
441
- }
442
- return { children: dirChildren, empty: false };
443
- }, [token]);
233
+ } catch { /* ignore */ }
234
+ }, []);
444
235
 
445
236
  const handleTreeExpand = useCallback(
446
237
  async (keys: React.Key[], info: { node: ProcessTreeNode; expanded: boolean }) => {
@@ -450,57 +241,24 @@ export default function ProcessManagementPage(): React.ReactElement {
450
241
  if (node.processName && node.isDir) {
451
242
  if (node.children && node.children.length > 0) return;
452
243
  const dirName = node.filePath || node.title.replace(/\\/$/, '');
453
- const result = await loadDirChildren(node.processName, dirName);
454
-
244
+ const result = await loadDirChildren(node.processName, dirName, token);
455
245
  if (result.empty) {
456
- const removeTreeNode = (nodes: ProcessTreeNode[], key: string): ProcessTreeNode[] =>
457
- nodes
458
- .filter((n) => n.key !== key)
459
- .map((n) => (n.children ? { ...n, children: removeTreeNode(n.children, key) } : n));
460
246
  setTreeData((prev) => removeTreeNode(prev, node.key as string));
461
247
  setExpandedKeys((prev) => prev.filter((k) => k !== node.key));
462
248
  } else {
463
249
  const updateNode = (nodes: ProcessTreeNode[]): ProcessTreeNode[] =>
464
250
  nodes.map((n) => {
465
- if (n.key === node.key) {
466
- return { ...n, children: result.children };
467
- }
468
- if (n.children) {
469
- return { ...n, children: updateNode(n.children) };
470
- }
251
+ if (n.key === node.key) return { ...n, children: result.children };
252
+ if (n.children) return { ...n, children: updateNode(n.children) };
471
253
  return n;
472
254
  });
473
-
474
255
  setTreeData((prev) => updateNode(prev));
475
256
  }
476
257
  }
477
258
  },
478
- [loadDirChildren],
259
+ [token],
479
260
  );
480
261
 
481
- const selectProcess = useCallback(async (name: string) => {
482
- try {
483
- const data = await fetchProcess(name);
484
- setSelectedProcess(data);
485
- setFileContent(null);
486
- setProcessMdContent(null);
487
- setLogs([]);
488
- setMetrics([]);
489
- setMetricsConfig([]);
490
- setMetricSnapshots([]);
491
- setMetricsViewConfig(null);
492
- setActiveTab('overview');
493
- if (data.hasProcessFile) {
494
- try {
495
- const mdContent = await fetchProcessFileContent(name, 'PROCESS.md');
496
- setProcessMdContent({ content: mdContent, path: 'PROCESS.md' });
497
- } catch { /* ignore */ }
498
- }
499
- } catch {
500
- // error handled silently
501
- }
502
- }, []);
503
-
504
262
  const handleTreeSelect = useCallback(
505
263
  async (keys: React.Key[], info: { node: ProcessTreeNode }) => {
506
264
  setSelectedKeys(keys);
@@ -510,7 +268,6 @@ export default function ProcessManagementPage(): React.ReactElement {
510
268
  return;
511
269
  }
512
270
  const node = info.node;
513
-
514
271
  if (node.isFile && node.processName && node.filePath) {
515
272
  setLoading(true);
516
273
  try {
@@ -522,9 +279,7 @@ export default function ProcessManagementPage(): React.ReactElement {
522
279
  setSelectedProcess(null);
523
280
  setProcessMdContent(null);
524
281
  }
525
- } catch {
526
- // error handled silently
527
- } finally {
282
+ } catch { /* ignore */ } finally {
528
283
  setLoading(false);
529
284
  }
530
285
  } else if (node.isProcess && node.processName) {
@@ -540,7 +295,6 @@ export default function ProcessManagementPage(): React.ReactElement {
540
295
  try {
541
296
  const result = await runProcess(selectedProcess.name);
542
297
  message.success(t('process.runTriggered', { runId: result.runId }));
543
- // Immediate refresh to show Running status
544
298
  await loadProcesses();
545
299
  selectProcess(selectedProcess.name);
546
300
  } catch {
@@ -587,66 +341,16 @@ export default function ProcessManagementPage(): React.ReactElement {
587
341
  [loadProcesses, t],
588
342
  );
589
343
 
590
- const loadLogs = useCallback(async (page?: number, pageSize?: number, from?: string, to?: string) => {
591
- if (!selectedProcess) return;
592
- setLogsLoading(true);
593
- try {
594
- const p = page ?? logsPagination.current;
595
- const ps = pageSize ?? logsPagination.pageSize;
596
- const result = await fetchProcessLogs(selectedProcess.name, {
597
- limit: ps,
598
- offset: (p - 1) * ps,
599
- from,
600
- to,
601
- });
602
- setLogs(result.data);
603
- setLogsTotal(result.total);
604
- } catch {
605
- setLogs([]);
606
- setLogsTotal(0);
607
- } finally {
608
- setLogsLoading(false);
609
- }
610
- }, [selectedProcess, logsPagination.current, logsPagination.pageSize]);
611
-
612
- const loadMetrics = useCallback(async () => {
613
- if (!selectedProcess) return;
614
- try {
615
- const [m, c, snapshots, viewConfig] = await Promise.all([
616
- fetchProcessMetrics(selectedProcess.name),
617
- fetchProcessMetricsConfig(selectedProcess.name),
618
- fetchProcessMetricsSnapshots(selectedProcess.name),
619
- fetchProcessMetricsViewConfig(selectedProcess.name),
620
- ]);
621
- setMetrics(m);
622
- setMetricsConfig(c);
623
- setMetricSnapshots(snapshots);
624
- setMetricsViewConfig(viewConfig);
625
- } catch { /* ignore */ }
626
- }, [selectedProcess]);
627
-
628
- const handleTabChange = useCallback((key: string) => {
629
- setActiveTab(key);
630
- if (key === 'logs') loadLogs(1, logsPagination.pageSize);
631
- if (key === 'metrics') loadMetrics();
632
- }, [loadLogs, loadMetrics]);
633
-
634
344
  const rightTitle = useMemo(() => {
635
345
  if (selectedProcess) return \`\${selectedProcess.name}\`;
636
346
  if (fileContent) return fileContent.path;
637
347
  return t('process.processDetail');
638
348
  }, [selectedProcess, fileContent, t]);
639
349
 
640
- // Build status tag for tree node
641
- const getStatusColor = useCallback((proc: ProcessInfo) => {
642
- const status = proc.status ?? 'idle';
643
- return STATUS_COLORS[status] ?? 'default';
644
- }, []);
645
-
646
350
  return (
647
351
  <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
648
352
  <Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
649
- <Breadcrumb items={[{ title: <HomeOutlined onClick={() => onNavClick?.('')} style={{ cursor: 'pointer' }} /> }, ...(breadcrumbs || []).map(b => ({ title: b.path ? <a onClick={() => onNavClick?.(b.path!)}>{b.title}</a> : b.title }))]} />
353
+ <Breadcrumb items={[{ title: <HomeOutlined onClick={() => onNavClick?.('')} style={{ cursor: 'pointer' }} /> }, ...(breadcrumbs || []).map((b: { title: string; path?: string }) => ({ title: b.path ? <a onClick={() => onNavClick?.(b.path!)}>{b.title}</a> : b.title }))]} />
650
354
  <Space>
651
355
  <Button icon={<ReloadOutlined />} onClick={loadProcesses} loading={loading}>
652
356
  {t('process.refresh')}
@@ -688,36 +392,21 @@ export default function ProcessManagementPage(): React.ReactElement {
688
392
  <Spin spinning={loading && !selectedProcess && !fileContent}>
689
393
  <div style={{ display: 'flex', height: '100%', padding: 16, gap: 16, overflow: 'hidden' }}>
690
394
  <div style={{ width: 300, minWidth: 240, maxWidth: 500, overflow: 'auto', flexShrink: 0 }}>
691
- <Tree<ProcessTreeNode>
692
- treeData={treeData as unknown as any}
395
+ <ProcessTree
396
+ processes={processes}
397
+ treeData={treeData}
693
398
  selectedKeys={selectedKeys}
694
399
  expandedKeys={expandedKeys}
695
- showLine={{ showLeafIcon: false }}
696
- blockNode
697
- onSelect={handleTreeSelect}
698
400
  onExpand={handleTreeExpand}
699
- style={{ fontSize: 14 }}
700
- titleRender={(node: any) => (
701
- <Flex align="center" gap={4}>
702
- {node.icon}
703
- <span>{node.title}</span>
704
- {node.isProcess && (() => {
705
- const proc = processes.find(p => p.name === node.processName);
706
- if (!proc) return null;
707
- const status = proc.status ?? 'idle';
708
- const statusLabel = status === 'running' ? t('process.statusRunning')
709
- : status === 'busy' ? t('process.statusBusy')
710
- : status === 'paused' ? t('process.statusPaused')
711
- : status === 'error' ? t('process.statusError')
712
- : t('process.statusIdle');
713
- return (
714
- <Tag color={STATUS_COLORS[status]} icon={STATUS_ICONS[status]} style={{ fontSize: 11, lineHeight: '16px', padding: '0 4px', marginRight: 0 }}>
715
- {statusLabel}
716
- </Tag>
717
- );
718
- })()}
719
- </Flex>
720
- )}
401
+ onSelect={handleTreeSelect}
402
+ onProcessSelect={selectProcess}
403
+ onFileSelect={async (pn, fp) => {
404
+ const content = await fetchProcessFileContent(pn, fp);
405
+ setFileContent({ content, path: fp });
406
+ setSelectedProcess(null);
407
+ setProcessMdContent(null);
408
+ }}
409
+ token={token}
721
410
  />
722
411
  </div>
723
412
 
@@ -746,213 +435,35 @@ export default function ProcessManagementPage(): React.ReactElement {
746
435
  {selectedProcess ? (
747
436
  <Tabs
748
437
  activeKey={activeTab}
749
- onChange={handleTabChange}
438
+ onChange={setActiveTab}
750
439
  items={[
751
440
  {
752
441
  key: 'overview',
753
442
  label: t('process.overview'),
754
443
  children: (
755
- <Space direction="vertical" style={{ width: '100%' }} size="middle">
756
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
757
- <Space>
758
- <Tag color={STATUS_COLORS[selectedProcess.status ?? 'idle']} icon={STATUS_ICONS[selectedProcess.status ?? 'idle']}>
759
- {(() => {
760
- const s = selectedProcess.status ?? 'idle';
761
- return s === 'running' ? t('process.statusRunning')
762
- : s === 'busy' ? t('process.statusBusy')
763
- : s === 'paused' ? t('process.statusPaused')
764
- : s === 'error' ? t('process.statusError')
765
- : t('process.statusIdle');
766
- })()}
767
- </Tag>
768
- <Tag>{selectedProcess.hasProcessFile ? t('process.modeFull') : t('process.modeLightweight')}</Tag>
769
- </Space>
770
- <Space>
771
- <Tooltip title={selectedProcess.paused ? t('process.resume') : t('process.pause')}>
772
- <Button icon={selectedProcess.paused ? <PlayCircleOutlined /> : <PauseCircleOutlined />}
773
- onClick={selectedProcess.paused ? handleResume : handlePause} />
774
- </Tooltip>
775
- <Tooltip title={t('process.run')}>
776
- <Button type="primary" icon={<PlayCircleOutlined />} onClick={handleRun} loading={executing} />
777
- </Tooltip>
778
- </Space>
779
- </div>
780
-
781
- <Descriptions bordered column={2} size="small"
782
- styles={{ label: { width: 120, fontWeight: 500, backgroundColor: token.colorFillQuaternary } }}
783
- items={[
784
- { key: 'trigger', label: t('process.trigger'), children: (
785
- <Space>
786
- {selectedProcess.cron && <Tag icon={<CalendarOutlined />}>{selectedProcess.cron}</Tag>}
787
- {selectedProcess.onEvent && <Tag icon={<ThunderboltOutlined />}>{selectedProcess.onEvent}</Tag>}
788
- {!selectedProcess.cron && !selectedProcess.onEvent && <Tag>{t('process.manual')}</Tag>}
789
- </Space>
790
- )},
791
- { key: 'owner', label: t('process.owner'), children: selectedProcess.owner },
792
- ...(selectedProcess.description ? [{ key: 'desc', label: t('process.description'), children: selectedProcess.description }] : []),
793
- ...(selectedProcess.prompt ? [{ key: 'prompt', label: t('process.prompt'), children: <Text code style={{ whiteSpace: 'pre-wrap', fontSize: 12, maxWidth: 400, display: 'inline-block' }}>{selectedProcess.prompt}</Text> }] : []),
794
- ]}
795
- />
796
-
797
- {selectedProcess.hasProcessFile && processMdContent && (
798
- <SkillFileViewer
799
- skillDetail={null}
800
- fileContent={processMdContent}
801
- viewMode={viewMode}
802
- onViewModeChange={setViewMode}
803
- mode={mode}
804
- token={token}
805
- t={t}
806
- />
807
- )}
808
- </Space>
444
+ <ProcessOverview
445
+ process={selectedProcess}
446
+ processMdContent={processMdContent}
447
+ viewMode={viewMode}
448
+ onViewModeChange={setViewMode}
449
+ onRun={handleRun}
450
+ onPause={handlePause}
451
+ onResume={handleResume}
452
+ executing={executing}
453
+ mode={mode}
454
+ token={token}
455
+ />
809
456
  ),
810
457
  },
811
458
  {
812
459
  key: 'logs',
813
460
  label: t('process.logs'),
814
- children: (
815
- <Space direction="vertical" style={{ width: '100%' }} size="middle">
816
- <Flex justify="flex-end" align="center">
817
- <Space>
818
- <DatePicker.RangePicker
819
- placeholder={[t('process.timeRange'), '']}
820
- onChange={(dates: [Dayjs | null, Dayjs | null] | null) => {
821
- setLogsDateRange(dates);
822
- setLogsPagination(prev => ({ ...prev, current: 1 }));
823
- const from = dates?.[0]?.startOf('day')?.toISOString();
824
- const to = dates?.[1]?.endOf('day')?.toISOString();
825
- loadLogs(1, logsPagination.pageSize, from, to);
826
- }}
827
- size="small"
828
- value={logsDateRange}
829
- />
830
- <Button icon={<ReloadOutlined />} onClick={() => { setLogsPagination(prev => ({ ...prev, current: 1 })); const from = logsDateRange?.[0]?.startOf('day')?.toISOString(); const to = logsDateRange?.[1]?.endOf('day')?.toISOString(); loadLogs(1, logsPagination.pageSize, from, to); }} size="small" loading={logsLoading}>
831
- {t('process.refresh')}
832
- </Button>
833
- </Space>
834
- </Flex>
835
- <Table<ProcessLogEntry>
836
- dataSource={logs}
837
- columns={[
838
- { title: t('process.runId'), dataIndex: 'runId', key: 'runId', width: 180, render: (runId: string) => <Tooltip title={runId}><Text strong style={{ fontFamily: 'monospace', fontSize: 12 }}>{runId.slice(0, 16)}</Text></Tooltip> },
839
- { title: t('process.triggerType'), dataIndex: 'triggerType', key: 'triggerType', width: 120, render: (triggerType: ProcessLogEntry['triggerType']) => <Tag icon={TRIGGER_ICONS[triggerType]}>{triggerType}</Tag> },
840
- { title: t('process.status'), dataIndex: 'status', key: 'status', width: 100, render: (status: ProcessLogEntry['status']) => <Tag color={status === 'completed' ? 'success' : status === 'failed' ? 'error' : status === 'timeout' ? 'warning' : 'processing'}>{status}</Tag> },
841
- { title: t('process.startedAt'), dataIndex: 'startedAt', key: 'startedAt', width: 180, render: (startedAt: string) => <Text type="secondary">{new Date(startedAt).toLocaleString()}</Text> },
842
- { title: t('process.duration'), dataIndex: 'durationMs', key: 'durationMs', width: 100, render: (durationMs: number) => durationMs > 0 ? <Text type="secondary">{(durationMs / 1000).toFixed(1)}s</Text> : <Text type="secondary">—</Text> },
843
- { title: '', key: 'actions', width: 48, render: (_: unknown, record: ProcessLogEntry) => record.sessionId && aiContext?.switchToSession ? <Tooltip title={t('process.viewSession')}><Button type="link" size="small" icon={<MessageOutlined />} onClick={() => aiContext.switchToSession!(record.sessionId)} /></Tooltip> : null },
844
- ]}
845
- rowKey="runId"
846
- size="small"
847
- loading={logsLoading}
848
- pagination={{
849
- current: logsPagination.current,
850
- pageSize: logsPagination.pageSize,
851
- total: logsTotal,
852
- showSizeChanger: true,
853
- showTotal: (tot: number) => t('process.total').replace('{total}', String(tot)),
854
- onChange: (page: number, pageSize: number) => {
855
- setLogsPagination({ current: page, pageSize });
856
- const from = logsDateRange?.[0]?.startOf('day')?.toISOString();
857
- const to = logsDateRange?.[1]?.endOf('day')?.toISOString();
858
- loadLogs(page, pageSize, from, to);
859
- },
860
- }}
861
- locale={{ emptyText: t('process.noLogs') }}
862
- />
863
- </Space>
864
- ),
461
+ children: <ProcessLogs processName={selectedProcess.name} />,
865
462
  },
866
463
  ...(selectedProcess.hasProcessFile ? [{
867
464
  key: 'metrics',
868
465
  label: t('process.metrics'),
869
- children: (
870
- <Space direction="vertical" style={{ width: '100%' }} size="large">
871
- {/* Business Metrics — agent-reported */}
872
- {metricsViewConfig && metricsViewConfig.metrics.length > 0 && (
873
- <div>
874
- <Text strong style={{ fontSize: 14, marginBottom: 8, display: 'block' }}>{t('process.businessMetrics')}</Text>
875
- <div style={{ display: 'grid', gridTemplateColumns: \`repeat(auto-fill, minmax(200px, 1fr))\`, gap: 12, marginBottom: 16 }}>
876
- {metricsViewConfig.metrics.map((m) => {
877
- const latestSnapshot = [...metricSnapshots].filter(s => s.metricName === m.metricName).sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
878
- const trend = (() => {
879
- const ms = metricSnapshots.filter(s => s.metricName === m.metricName).sort((a, b) => a.timestamp.localeCompare(b.timestamp));
880
- if (ms.length < 2) return null;
881
- const diff = ms[ms.length - 1]!.value - ms[0]!.value;
882
- if (diff === 0) return { color: 'default', label: '→' };
883
- const good = m.direction === 'decreasing' ? diff < 0 : m.direction === 'increasing' ? diff > 0 : true;
884
- return { color: good ? 'success' : 'error', label: diff > 0 ? '↑' : '↓' };
885
- })();
886
- return (
887
- <Card key={m.metricName} size="small">
888
- <div style={{ fontSize: 12, color: token.colorTextSecondary }}>{m.title}</div>
889
- <div style={{ fontSize: 24, fontWeight: 600, marginTop: 4 }}>
890
- {latestSnapshot ? \`\${latestSnapshot.value}\${latestSnapshot.unit ?? m.unit ?? ''}\` : '-'}
891
- {trend && <Tag color={trend.color} style={{ marginLeft: 6, fontSize: 12 }}>{trend.label}</Tag>}
892
- </div>
893
- {m.target !== undefined && <div style={{ fontSize: 11, color: token.colorTextTertiary }}>Target: {m.target}{m.unit ?? ''}</div>}
894
- </Card>
895
- );
896
- })}
897
- </div>
898
- {metricsViewConfig.charts.length > 0 && metricSnapshots.length > 0 && (
899
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
900
- {metricsViewConfig.charts.map((chart, i) => {
901
- const chartMetricName = (chart as Record<string, unknown>).metricName as string | undefined;
902
- const chartTitle = (chart as Record<string, unknown>).title as string | undefined;
903
- const filteredSnapshots = chartMetricName
904
- ? metricSnapshots
905
- .filter(s => s.metricName === chartMetricName)
906
- .sort((a, b) => a.timestamp.localeCompare(b.timestamp))
907
- : metricSnapshots;
908
- const { metricName: _mn, title: _t, ...chartSpec } = chart as Record<string, unknown>;
909
- return (
910
- <Card key={i} size="small" {...(chartTitle ? { title: chartTitle } : {})}>
911
- <DashboardChart spec={{ ...chartSpec, data: filteredSnapshots }} />
912
- </Card>
913
- );
914
- })}
915
- </div>
916
- )}
917
- </div>
918
- )}
919
- {!metricsViewConfig && metricSnapshots.length > 0 && (
920
- <div>
921
- <Text strong style={{ fontSize: 14, marginBottom: 8, display: 'block' }}>{t('process.businessMetrics')}</Text>
922
- <Text type="secondary">{t('process.noBusinessMetrics')}</Text>
923
- </div>
924
- )}
925
- {/* Operational Metrics — system aggregates */}
926
- <div>
927
- <Text strong style={{ fontSize: 14, marginBottom: 8, display: 'block' }}>{t('process.operationalMetrics')}</Text>
928
- {metricsConfig.length === 0 && metrics.length === 0 ? (
929
- <Empty description={t('process.noMetrics')} />
930
- ) : (
931
- <>
932
- {metricsConfig.map((widget, i) => (
933
- <DashboardChart key={\`op-\${i}\`} spec={{ ...widget, data: metrics }} />
934
- ))}
935
- {metrics.length > 0 && (
936
- <Table
937
- dataSource={metrics}
938
- rowKey="date"
939
- size="small"
940
- pagination={{ pageSize: 7 }}
941
- columns={[
942
- { title: t('process.colDate'), dataIndex: 'date', key: 'date' },
943
- { title: t('process.colTotal'), dataIndex: 'totalRuns', key: 'totalRuns' },
944
- { title: t('process.colCompleted'), dataIndex: 'completedRuns', key: 'completedRuns' },
945
- { title: t('process.colFailed'), dataIndex: 'failedRuns', key: 'failedRuns' },
946
- { title: t('process.colTimeout'), dataIndex: 'timeoutRuns', key: 'timeoutRuns' },
947
- { title: t('process.colAvgDuration'), dataIndex: 'avgDurationMs', key: 'avgDurationMs', render: (ms: number) => \`\${(ms / 1000).toFixed(1)}s\` },
948
- ]}
949
- />
950
- )}
951
- </>
952
- )}
953
- </div>
954
- </Space>
955
- ),
466
+ children: <ProcessMetrics processName={selectedProcess.name} hasProcessFile={selectedProcess.hasProcessFile} />,
956
467
  }] : []),
957
468
  ]}
958
469
  />
@@ -979,7 +490,6 @@ export default function ProcessManagementPage(): React.ReactElement {
979
490
  </Spin>
980
491
  </div>
981
492
  );
982
- }
983
- `;
493
+ }`;
984
494
  }
985
495
  //# sourceMappingURL=process-management-page.js.map