plugin-agent-orchestrator 1.0.22 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/214.723affb37c13bf7a.js +10 -0
- package/dist/client-v2/264.0533912e6c5ea2d7.js +10 -0
- package/dist/client-v2/41.1805b2edfaa4afe2.js +10 -0
- package/dist/client-v2/418.5ae055abf141820e.js +10 -0
- package/dist/client-v2/619.d99d3c9e61c99064.js +10 -0
- package/dist/client-v2/70.a15d7fcec7c41768.js +10 -0
- package/dist/client-v2/892.72db4161511c8a16.js +10 -0
- package/dist/client-v2/926.87f660b670d85bcc.js +10 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/externalVersion.js +8 -6
- package/dist/locale/en-US.json +7 -0
- package/dist/locale/vi-VN.json +7 -0
- package/dist/locale/zh-CN.json +27 -0
- package/dist/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.js +63 -0
- package/dist/server/plugin.js +32 -1
- package/dist/server/services/AgentHarness.js +52 -27
- package/dist/server/services/AgentLoopController.js +8 -2
- package/dist/server/services/AgentLoopService.js +1 -1
- package/dist/server/services/AgentRegistryService.js +53 -42
- package/dist/server/services/CircuitBreaker.js +7 -2
- package/dist/server/services/CodeValidator.js +48 -14
- package/dist/server/services/SandboxRunner.js +18 -14
- package/dist/server/skill-hub/plugin.js +44 -17
- package/dist/server/tools/delegate-task.js +7 -2
- package/dist/server/tools/skill-execute.js +33 -2
- package/dist/server/utils/ai-manager.js +51 -0
- package/dist/server/utils/ctx-utils.js +11 -0
- package/dist/server/utils/skill-settings.js +122 -0
- package/package.json +49 -45
- package/src/client/AIEmployeesContext.tsx +60 -19
- package/src/client/AgentRunsTab.tsx +769 -764
- package/src/client/HarnessProfilesTab.tsx +257 -247
- package/src/client/RulesTab.tsx +787 -716
- package/src/client/TracingTab.tsx +9 -6
- package/src/client/plugin.tsx +34 -27
- package/src/client/skill-hub/components/ExecutionHistory.tsx +9 -8
- package/src/client/skill-hub/components/GitSkillImport.tsx +12 -5
- package/src/client/skill-hub/components/LoopSettings.tsx +2 -2
- package/src/client/skill-hub/components/SkillEditor.tsx +2 -2
- package/src/client/skill-hub/components/SkillManager.tsx +2 -2
- package/src/client/skill-hub/components/SkillMetrics.tsx +157 -124
- package/src/client/skill-hub/components/SkillTestPanel.tsx +14 -13
- package/src/client/skill-hub/index.tsx +58 -51
- package/src/client/skill-hub/locale.ts +1 -1
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +132 -99
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +71 -58
- package/src/client/tools/PlanApprovalCard.tsx +3 -2
- package/src/client/tools/registerOrchestratorCards.ts +17 -7
- package/src/client-v2/components/AIEmployeeSelect.tsx +47 -0
- package/src/client-v2/components/AIEmployeesContext.tsx +110 -0
- package/src/client-v2/components/AgentRunsTab.tsx +767 -0
- package/src/client-v2/components/HarnessProfilesTab.tsx +254 -0
- package/src/client-v2/components/RulesTab.tsx +782 -0
- package/src/client-v2/components/TracingTab.tsx +432 -0
- package/src/client-v2/hooks/useApiRequest.ts +114 -0
- package/src/client-v2/index.tsx +1 -0
- package/src/client-v2/pages/AgentRunsPage.tsx +13 -0
- package/src/client-v2/pages/ExecutionHistoryPage.tsx +10 -0
- package/src/client-v2/pages/HarnessProfilesPage.tsx +10 -0
- package/src/client-v2/pages/LoopSettingsPage.tsx +10 -0
- package/src/client-v2/pages/RulesPage.tsx +13 -0
- package/src/client-v2/pages/SkillDefinitionsPage.tsx +10 -0
- package/src/client-v2/pages/SkillMetricsPage.tsx +10 -0
- package/src/client-v2/pages/TracingPage.tsx +13 -0
- package/src/client-v2/plugin.tsx +70 -0
- package/src/client-v2/skill-hub/components/ExecutionHistory.tsx +196 -0
- package/src/client-v2/skill-hub/components/FileLinkList.tsx +37 -0
- package/src/client-v2/skill-hub/components/GitSkillImport.tsx +539 -0
- package/src/client-v2/skill-hub/components/LoopSettings.tsx +331 -0
- package/src/client-v2/skill-hub/components/SkillEditor.tsx +453 -0
- package/src/client-v2/skill-hub/components/SkillManager.tsx +174 -0
- package/src/client-v2/skill-hub/components/SkillMetrics.tsx +157 -0
- package/src/client-v2/skill-hub/components/SkillTestPanel.tsx +135 -0
- package/src/client-v2/skill-hub/locale.ts +13 -0
- package/src/client-v2/skill-hub/tools/loopTemplates.ts +52 -0
- package/src/client-v2/skill-hub/utils/jsonFields.ts +41 -0
- package/src/client-v2/utils/jsonFields.ts +41 -0
- package/src/locale/en-US.json +7 -0
- package/src/locale/vi-VN.json +7 -0
- package/src/locale/zh-CN.json +27 -0
- package/src/server/__tests__/agent-registry-service.test.ts +147 -0
- package/src/server/__tests__/code-validator.test.ts +63 -0
- package/src/server/__tests__/skill-execute.test.ts +33 -0
- package/src/server/__tests__/skill-settings.test.ts +63 -0
- package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +39 -0
- package/src/server/plugin.ts +62 -21
- package/src/server/services/AgentHarness.ts +49 -22
- package/src/server/services/AgentLoopController.ts +17 -6
- package/src/server/services/AgentLoopService.ts +1 -1
- package/src/server/services/AgentPlannerService.ts +10 -0
- package/src/server/services/AgentRegistryService.ts +89 -47
- package/src/server/services/CircuitBreaker.ts +10 -0
- package/src/server/services/CodeValidator.ts +237 -159
- package/src/server/services/SandboxRunner.ts +203 -189
- package/src/server/skill-hub/plugin.ts +933 -898
- package/src/server/tools/delegate-task.ts +12 -9
- package/src/server/tools/skill-execute.ts +194 -160
- package/src/server/utils/ai-manager.ts +24 -0
- package/src/server/utils/ctx-utils.ts +14 -0
- package/src/server/utils/skill-settings.ts +116 -0
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
Form,
|
|
19
19
|
} from 'antd';
|
|
20
20
|
import { EyeOutlined, CheckCircleOutlined, CloseCircleOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
21
|
-
import {
|
|
21
|
+
import { useRequest } from 'ahooks';
|
|
22
|
+
import { useApp } from '@nocobase/client-v2';
|
|
22
23
|
import { useAIEmployees } from './AIEmployeesContext';
|
|
23
24
|
|
|
24
25
|
const { Text, Paragraph } = Typography;
|
|
@@ -32,7 +33,7 @@ type FilterState = {
|
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
export const TracingTab: React.FC = () => {
|
|
35
|
-
const api =
|
|
36
|
+
const api = useApp().apiClient;
|
|
36
37
|
const [selectedLog, setSelectedLog] = useState<any>(null);
|
|
37
38
|
const [detailLoading, setDetailLoading] = useState(false);
|
|
38
39
|
|
|
@@ -61,10 +62,11 @@ export const TracingTab: React.FC = () => {
|
|
|
61
62
|
}, [page, pageSize, filters]);
|
|
62
63
|
|
|
63
64
|
const { data, loading, refresh } = useRequest(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
() =>
|
|
66
|
+
api.request({
|
|
67
|
+
url: 'orchestratorTracing:list',
|
|
68
|
+
params: requestParams,
|
|
69
|
+
}),
|
|
68
70
|
{
|
|
69
71
|
refreshDeps: [requestParams],
|
|
70
72
|
},
|
|
@@ -269,6 +271,7 @@ export const TracingTab: React.FC = () => {
|
|
|
269
271
|
dataSource={logs}
|
|
270
272
|
columns={columns}
|
|
271
273
|
size="middle"
|
|
274
|
+
scroll={{ x: 'max-content' }}
|
|
272
275
|
pagination={{
|
|
273
276
|
current: page,
|
|
274
277
|
pageSize,
|
package/src/client/plugin.tsx
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
|
-
import { Plugin } from '@nocobase/client';
|
|
2
|
-
import { OrchestratorSettings } from './OrchestratorSettings';
|
|
3
|
-
import { InteractionSchemasProvider } from './skill-hub/tools/InteractionSchemasProvider';
|
|
4
|
-
import { registerSkillLoopCards } from './skill-hub/tools/registerSkillLoopCards';
|
|
5
|
-
import { registerOrchestratorCards } from './tools/registerOrchestratorCards';
|
|
6
|
-
|
|
7
|
-
export class PluginAgentOrchestratorClient extends Plugin {
|
|
8
|
-
async load() {
|
|
9
|
-
(this as any).app.use(InteractionSchemasProvider);
|
|
10
|
-
|
|
11
|
-
// Register under the "AI" settings group for consistency with other AI plugins
|
|
12
|
-
(this as any).app.pluginSettingsManager.add('ai.orchestrator', {
|
|
13
|
-
title: 'Agent Orchestrator',
|
|
14
|
-
icon: 'ApartmentOutlined',
|
|
15
|
-
Component: OrchestratorSettings,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
import { Plugin } from '@nocobase/client';
|
|
2
|
+
import { OrchestratorSettings } from './OrchestratorSettings';
|
|
3
|
+
import { InteractionSchemasProvider } from './skill-hub/tools/InteractionSchemasProvider';
|
|
4
|
+
import { registerSkillLoopCards } from './skill-hub/tools/registerSkillLoopCards';
|
|
5
|
+
import { registerOrchestratorCards } from './tools/registerOrchestratorCards';
|
|
6
|
+
|
|
7
|
+
export class PluginAgentOrchestratorClient extends Plugin {
|
|
8
|
+
async load() {
|
|
9
|
+
(this as any).app.use(InteractionSchemasProvider);
|
|
10
|
+
|
|
11
|
+
// Register under the "AI" settings group for consistency with other AI plugins
|
|
12
|
+
(this as any).app.pluginSettingsManager.add('ai.orchestrator', {
|
|
13
|
+
title: 'Agent Orchestrator',
|
|
14
|
+
icon: 'ApartmentOutlined',
|
|
15
|
+
Component: OrchestratorSettings,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
(this as any).app.eventBus?.addEventListener?.('auth:tokenChanged', (event: Event) => {
|
|
19
|
+
const token = (event as CustomEvent<{ token?: string | null }>).detail?.token;
|
|
20
|
+
if (token) {
|
|
21
|
+
registerSkillLoopCards((this as any).app);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await this.registerSkillUiCards();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private async registerSkillUiCards() {
|
|
29
|
+
await registerOrchestratorCards((this as any).app);
|
|
30
|
+
await registerSkillLoopCards((this as any).app);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default PluginAgentOrchestratorClient;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { Card, Table, Tag, Button, Typography, Space, Tooltip, Popconfirm, message } from 'antd';
|
|
3
3
|
import { ReloadOutlined, DownloadOutlined, DeleteOutlined } from '@ant-design/icons';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { Upload } from '@nocobase/client';
|
|
5
|
+
import { useApp } from '@nocobase/client-v2';
|
|
6
|
+
import { useT } from '../locale';
|
|
7
|
+
import { parseJsonText } from '../utils/jsonFields';
|
|
7
8
|
|
|
8
9
|
const STATUS_COLORS: Record<string, string> = {
|
|
9
10
|
pending: 'default',
|
|
@@ -15,7 +16,7 @@ const STATUS_COLORS: Record<string, string> = {
|
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export const ExecutionHistory: React.FC = () => {
|
|
18
|
-
const api =
|
|
19
|
+
const api = useApp().apiClient;
|
|
19
20
|
const t = useT();
|
|
20
21
|
const [executions, setExecutions] = useState<any[]>([]);
|
|
21
22
|
const [loading, setLoading] = useState(false);
|
|
@@ -96,12 +97,12 @@ export const ExecutionHistory: React.FC = () => {
|
|
|
96
97
|
},
|
|
97
98
|
{
|
|
98
99
|
title: t('Files'),
|
|
99
|
-
dataIndex: 'outputFiles',
|
|
100
|
+
dataIndex: 'outputFiles',
|
|
100
101
|
key: 'files',
|
|
101
102
|
width: 250,
|
|
102
|
-
render: (files: any[], record: any) => {
|
|
103
|
-
files = parseJsonText(files, []);
|
|
104
|
-
if (!Array.isArray(files) || !files.length) return '-';
|
|
103
|
+
render: (files: any[], record: any) => {
|
|
104
|
+
files = parseJsonText(files, []);
|
|
105
|
+
if (!Array.isArray(files) || !files.length) return '-';
|
|
105
106
|
const formattedFiles = files.map((f, i) => ({
|
|
106
107
|
id: `${record.id}-${f.name}-${i}`,
|
|
107
108
|
title: f.name,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
DatabaseOutlined,
|
|
26
26
|
SearchOutlined,
|
|
27
27
|
} from '@ant-design/icons';
|
|
28
|
-
import {
|
|
28
|
+
import { useApp } from '@nocobase/client-v2';
|
|
29
29
|
import { useT } from '../locale';
|
|
30
30
|
|
|
31
31
|
const { Text } = Typography;
|
|
@@ -39,7 +39,7 @@ interface GitSkillImportProps {
|
|
|
39
39
|
export const GitSkillImport: React.FC<GitSkillImportProps> = ({ open, onClose }) => {
|
|
40
40
|
const t = useT();
|
|
41
41
|
const { token } = useToken();
|
|
42
|
-
const api =
|
|
42
|
+
const api = useApp().apiClient;
|
|
43
43
|
|
|
44
44
|
const [step, setStep] = useState(0);
|
|
45
45
|
|
|
@@ -178,8 +178,7 @@ export const GitSkillImport: React.FC<GitSkillImportProps> = ({ open, onClose })
|
|
|
178
178
|
const lower = searchText.toLowerCase();
|
|
179
179
|
return skills.filter(
|
|
180
180
|
(s) =>
|
|
181
|
-
(s.title || s.name || '').toLowerCase().includes(lower) ||
|
|
182
|
-
(s.description || '').toLowerCase().includes(lower),
|
|
181
|
+
(s.title || s.name || '').toLowerCase().includes(lower) || (s.description || '').toLowerCase().includes(lower),
|
|
183
182
|
);
|
|
184
183
|
}, [skills, searchText]);
|
|
185
184
|
|
|
@@ -531,6 +530,7 @@ export const GitSkillImport: React.FC<GitSkillImportProps> = ({ open, onClose })
|
|
|
531
530
|
selectedRowKeys: selectedSkills,
|
|
532
531
|
onChange: (keys) => setSelectedSkills(keys as string[]),
|
|
533
532
|
}}
|
|
533
|
+
scroll={{ x: 'max-content' }}
|
|
534
534
|
/>
|
|
535
535
|
</div>
|
|
536
536
|
)}
|
|
@@ -547,7 +547,14 @@ export const GitSkillImport: React.FC<GitSkillImportProps> = ({ open, onClose })
|
|
|
547
547
|
syncResults.filter((r) => r.status === 'updated').length
|
|
548
548
|
} ${t('updated')}, ${syncResults.filter((r) => r.status === 'skipped').length} ${t('skipped')}`}
|
|
549
549
|
/>
|
|
550
|
-
<Table
|
|
550
|
+
<Table
|
|
551
|
+
dataSource={syncResults}
|
|
552
|
+
columns={resultColumns}
|
|
553
|
+
rowKey="folder"
|
|
554
|
+
size="small"
|
|
555
|
+
pagination={false}
|
|
556
|
+
scroll={{ x: 'max-content' }}
|
|
557
|
+
/>
|
|
551
558
|
</div>
|
|
552
559
|
)}
|
|
553
560
|
</Modal>
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
Typography,
|
|
17
17
|
} from 'antd';
|
|
18
18
|
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
|
19
|
-
import {
|
|
19
|
+
import { useApp } from '@nocobase/client-v2';
|
|
20
20
|
import { useT } from '../locale';
|
|
21
21
|
import { formatJsonText, parseJsonText, stringifyJsonText } from '../utils/jsonFields';
|
|
22
22
|
import { getLoopTemplate, LOOP_TEMPLATES } from '../tools/loopTemplates';
|
|
@@ -30,8 +30,8 @@ const extractList = (data: any) => {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export const LoopSettings: React.FC = () => {
|
|
33
|
-
const api = useAPIClient();
|
|
34
33
|
const app = useApp();
|
|
34
|
+
const api = app.apiClient;
|
|
35
35
|
const t = useT();
|
|
36
36
|
const [form] = Form.useForm();
|
|
37
37
|
const [skills, setSkills] = useState<any[]>([]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { Modal, Form, Input, Select, InputNumber, Switch, message, Upload, Radio, Button, Space, theme } from 'antd';
|
|
3
3
|
import { InboxOutlined, CloseOutlined, SaveOutlined } from '@ant-design/icons';
|
|
4
|
-
import {
|
|
4
|
+
import { useApp } from '@nocobase/client-v2';
|
|
5
5
|
import { useT } from '../locale';
|
|
6
6
|
import { formatJsonText, stringifyJsonText, parseJsonText } from '../utils/jsonFields';
|
|
7
7
|
|
|
@@ -14,7 +14,7 @@ interface SkillEditorProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export const SkillEditor: React.FC<SkillEditorProps> = ({ skill, onClose }) => {
|
|
17
|
-
const api =
|
|
17
|
+
const api = useApp().apiClient;
|
|
18
18
|
const t = useT();
|
|
19
19
|
const { token } = useToken();
|
|
20
20
|
const [form] = Form.useForm();
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
Tooltip,
|
|
18
18
|
} from 'antd';
|
|
19
19
|
import { PlusOutlined, EditOutlined, DeleteOutlined, PlayCircleOutlined, BranchesOutlined } from '@ant-design/icons';
|
|
20
|
-
import {
|
|
20
|
+
import { useApp } from '@nocobase/client-v2';
|
|
21
21
|
import { useT } from '../locale';
|
|
22
22
|
import { SkillEditor } from './SkillEditor';
|
|
23
23
|
import { SkillTestPanel } from './SkillTestPanel';
|
|
@@ -26,7 +26,7 @@ import { GitSkillImport } from './GitSkillImport';
|
|
|
26
26
|
const { TextArea } = Input;
|
|
27
27
|
|
|
28
28
|
export const SkillManager: React.FC = () => {
|
|
29
|
-
const api =
|
|
29
|
+
const api = useApp().apiClient;
|
|
30
30
|
const t = useT();
|
|
31
31
|
const [skills, setSkills] = useState<any[]>([]);
|
|
32
32
|
const [loading, setLoading] = useState(false);
|
|
@@ -1,124 +1,157 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
-
import { Card, Table, Typography, Space, Row, Col, Statistic, Progress } from 'antd';
|
|
3
|
-
import { SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, ClockCircleOutlined } from '@ant-design/icons';
|
|
4
|
-
import {
|
|
5
|
-
import { useT } from '../locale';
|
|
6
|
-
|
|
7
|
-
const { Title, Text } = Typography;
|
|
8
|
-
|
|
9
|
-
export const SkillMetrics: React.FC = () => {
|
|
10
|
-
const api =
|
|
11
|
-
const t = useT();
|
|
12
|
-
const [executions, setExecutions] = useState<any[]>([]);
|
|
13
|
-
const [loading, setLoading] = useState(false);
|
|
14
|
-
|
|
15
|
-
const fetchExecutions = useCallback(async () => {
|
|
16
|
-
setLoading(true);
|
|
17
|
-
try {
|
|
18
|
-
// Fetch up to 1000 recent executions to calculate basic metrics
|
|
19
|
-
const { data } = await api.request({
|
|
20
|
-
url: 'skillExecutions:list',
|
|
21
|
-
params: {
|
|
22
|
-
pageSize: 1000,
|
|
23
|
-
sort: ['-createdAt'],
|
|
24
|
-
appends: ['skill'],
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
const rawData = data?.data?.data ?? data?.data ?? [];
|
|
28
|
-
setExecutions(Array.isArray(rawData) ? rawData : []);
|
|
29
|
-
} catch {
|
|
30
|
-
// ignore
|
|
31
|
-
} finally {
|
|
32
|
-
setLoading(false);
|
|
33
|
-
}
|
|
34
|
-
}, [api]);
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
fetchExecutions();
|
|
38
|
-
}, [fetchExecutions]);
|
|
39
|
-
|
|
40
|
-
const metrics = useMemo(() => {
|
|
41
|
-
const total = executions.length;
|
|
42
|
-
const succeeded = executions.filter((e) => e.status === 'succeeded').length;
|
|
43
|
-
const failed = executions.filter((e) => e.status === 'failed').length;
|
|
44
|
-
const timeout = executions.filter((e) => e.status === 'timeout').length;
|
|
45
|
-
const canceled = executions.filter((e) => e.status === 'canceled').length;
|
|
46
|
-
|
|
47
|
-
// Group by skill
|
|
48
|
-
const bySkill: Record<string, any> = {};
|
|
49
|
-
executions.forEach((e) => {
|
|
50
|
-
const skillName = e.skill?.title || e.skill?.name || 'Unknown';
|
|
51
|
-
if (!bySkill[skillName]) {
|
|
52
|
-
bySkill[skillName] = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{ title: t('
|
|
84
|
-
{ title: t('
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
+
import { Card, Table, Typography, Space, Row, Col, Statistic, Progress } from 'antd';
|
|
3
|
+
import { SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, ClockCircleOutlined } from '@ant-design/icons';
|
|
4
|
+
import { useApp } from '@nocobase/client-v2';
|
|
5
|
+
import { useT } from '../locale';
|
|
6
|
+
|
|
7
|
+
const { Title, Text } = Typography;
|
|
8
|
+
|
|
9
|
+
export const SkillMetrics: React.FC = () => {
|
|
10
|
+
const api = useApp().apiClient;
|
|
11
|
+
const t = useT();
|
|
12
|
+
const [executions, setExecutions] = useState<any[]>([]);
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
|
|
15
|
+
const fetchExecutions = useCallback(async () => {
|
|
16
|
+
setLoading(true);
|
|
17
|
+
try {
|
|
18
|
+
// Fetch up to 1000 recent executions to calculate basic metrics
|
|
19
|
+
const { data } = await api.request({
|
|
20
|
+
url: 'skillExecutions:list',
|
|
21
|
+
params: {
|
|
22
|
+
pageSize: 1000,
|
|
23
|
+
sort: ['-createdAt'],
|
|
24
|
+
appends: ['skill'],
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const rawData = data?.data?.data ?? data?.data ?? [];
|
|
28
|
+
setExecutions(Array.isArray(rawData) ? rawData : []);
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore
|
|
31
|
+
} finally {
|
|
32
|
+
setLoading(false);
|
|
33
|
+
}
|
|
34
|
+
}, [api]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
fetchExecutions();
|
|
38
|
+
}, [fetchExecutions]);
|
|
39
|
+
|
|
40
|
+
const metrics = useMemo(() => {
|
|
41
|
+
const total = executions.length;
|
|
42
|
+
const succeeded = executions.filter((e) => e.status === 'succeeded').length;
|
|
43
|
+
const failed = executions.filter((e) => e.status === 'failed').length;
|
|
44
|
+
const timeout = executions.filter((e) => e.status === 'timeout').length;
|
|
45
|
+
const canceled = executions.filter((e) => e.status === 'canceled').length;
|
|
46
|
+
|
|
47
|
+
// Group by skill
|
|
48
|
+
const bySkill: Record<string, any> = {};
|
|
49
|
+
executions.forEach((e) => {
|
|
50
|
+
const skillName = e.skill?.title || e.skill?.name || 'Unknown';
|
|
51
|
+
if (!bySkill[skillName]) {
|
|
52
|
+
bySkill[skillName] = {
|
|
53
|
+
name: skillName,
|
|
54
|
+
total: 0,
|
|
55
|
+
succeeded: 0,
|
|
56
|
+
failed: 0,
|
|
57
|
+
timeout: 0,
|
|
58
|
+
canceled: 0,
|
|
59
|
+
totalDuration: 0,
|
|
60
|
+
durationCount: 0,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
bySkill[skillName].total += 1;
|
|
64
|
+
bySkill[skillName][e.status] = (bySkill[skillName][e.status] || 0) + 1;
|
|
65
|
+
if (e.durationMs) {
|
|
66
|
+
bySkill[skillName].totalDuration += e.durationMs;
|
|
67
|
+
bySkill[skillName].durationCount += 1;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const skillData = Object.values(bySkill)
|
|
72
|
+
.map((s) => ({
|
|
73
|
+
...s,
|
|
74
|
+
successRate: s.total > 0 ? (s.succeeded / s.total) * 100 : 0,
|
|
75
|
+
avgDuration: s.durationCount > 0 ? (s.totalDuration / s.durationCount / 1000).toFixed(2) : 0,
|
|
76
|
+
}))
|
|
77
|
+
.sort((a: any, b: any) => b.total - a.total);
|
|
78
|
+
|
|
79
|
+
return { total, succeeded, failed, timeout, canceled, skillData };
|
|
80
|
+
}, [executions]);
|
|
81
|
+
|
|
82
|
+
const columns = [
|
|
83
|
+
{ title: t('Skill'), dataIndex: 'name', key: 'name', width: 200 },
|
|
84
|
+
{ title: t('Total Runs'), dataIndex: 'total', key: 'total', width: 100 },
|
|
85
|
+
{
|
|
86
|
+
title: t('Success Rate'),
|
|
87
|
+
dataIndex: 'successRate',
|
|
88
|
+
key: 'successRate',
|
|
89
|
+
width: 150,
|
|
90
|
+
render: (val: number) => (
|
|
91
|
+
<Progress
|
|
92
|
+
percent={Math.round(val)}
|
|
93
|
+
size="small"
|
|
94
|
+
status={val === 100 ? 'success' : val > 50 ? 'active' : 'exception'}
|
|
95
|
+
/>
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
{ title: t('Success'), dataIndex: 'succeeded', key: 'succeeded', width: 100 },
|
|
99
|
+
{ title: t('Failed'), dataIndex: 'failed', key: 'failed', width: 100 },
|
|
100
|
+
{ title: t('Timeout'), dataIndex: 'timeout', key: 'timeout', width: 100 },
|
|
101
|
+
{ title: t('Avg Duration (s)'), dataIndex: 'avgDuration', key: 'avgDuration', width: 120 },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Space direction="vertical" size="large" style={{ width: '100%', padding: '0 16px' }}>
|
|
106
|
+
<Row gutter={16}>
|
|
107
|
+
<Col span={6}>
|
|
108
|
+
<Card size="small">
|
|
109
|
+
<Statistic title="Total Executions (Recent)" value={metrics.total} prefix={<SyncOutlined />} />
|
|
110
|
+
</Card>
|
|
111
|
+
</Col>
|
|
112
|
+
<Col span={6}>
|
|
113
|
+
<Card size="small">
|
|
114
|
+
<Statistic
|
|
115
|
+
title="Succeeded"
|
|
116
|
+
value={metrics.succeeded}
|
|
117
|
+
valueStyle={{ color: '#3f8600' }}
|
|
118
|
+
prefix={<CheckCircleOutlined />}
|
|
119
|
+
/>
|
|
120
|
+
</Card>
|
|
121
|
+
</Col>
|
|
122
|
+
<Col span={6}>
|
|
123
|
+
<Card size="small">
|
|
124
|
+
<Statistic
|
|
125
|
+
title="Failed"
|
|
126
|
+
value={metrics.failed}
|
|
127
|
+
valueStyle={{ color: '#cf1322' }}
|
|
128
|
+
prefix={<CloseCircleOutlined />}
|
|
129
|
+
/>
|
|
130
|
+
</Card>
|
|
131
|
+
</Col>
|
|
132
|
+
<Col span={6}>
|
|
133
|
+
<Card size="small">
|
|
134
|
+
<Statistic
|
|
135
|
+
title="Timeout/Canceled"
|
|
136
|
+
value={metrics.timeout + metrics.canceled}
|
|
137
|
+
valueStyle={{ color: '#faad14' }}
|
|
138
|
+
prefix={<ClockCircleOutlined />}
|
|
139
|
+
/>
|
|
140
|
+
</Card>
|
|
141
|
+
</Col>
|
|
142
|
+
</Row>
|
|
143
|
+
|
|
144
|
+
<Card title={t('Metrics by Skill (Recent)')}>
|
|
145
|
+
<Table
|
|
146
|
+
dataSource={metrics.skillData}
|
|
147
|
+
columns={columns}
|
|
148
|
+
rowKey="name"
|
|
149
|
+
loading={loading}
|
|
150
|
+
pagination={false}
|
|
151
|
+
size="middle"
|
|
152
|
+
scroll={{ x: 'max-content' }}
|
|
153
|
+
/>
|
|
154
|
+
</Card>
|
|
155
|
+
</Space>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Modal, Input, Button, Alert, Typography, Space, Spin } from 'antd';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { Upload } from '@nocobase/client';
|
|
4
|
+
import { useApp } from '@nocobase/client-v2';
|
|
5
|
+
import { useT } from '../locale';
|
|
6
|
+
import { parseJsonText } from '../utils/jsonFields';
|
|
6
7
|
|
|
7
8
|
const { TextArea } = Input;
|
|
8
9
|
|
|
@@ -11,16 +12,16 @@ interface SkillTestPanelProps {
|
|
|
11
12
|
onClose: () => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export const SkillTestPanel: React.FC<SkillTestPanelProps> = ({ skill, onClose }) => {
|
|
15
|
-
const api =
|
|
16
|
-
const t = useT();
|
|
17
|
-
const inputSchema = parseJsonText(skill.inputSchema, null);
|
|
18
|
-
const [input, setInput] = useState(
|
|
19
|
-
inputSchema?.properties
|
|
20
|
-
? JSON.stringify(
|
|
21
|
-
Object.fromEntries(
|
|
22
|
-
Object.keys(inputSchema.properties).map((k) => [k, '']),
|
|
23
|
-
),
|
|
15
|
+
export const SkillTestPanel: React.FC<SkillTestPanelProps> = ({ skill, onClose }) => {
|
|
16
|
+
const api = useApp().apiClient;
|
|
17
|
+
const t = useT();
|
|
18
|
+
const inputSchema = parseJsonText(skill.inputSchema, null);
|
|
19
|
+
const [input, setInput] = useState(
|
|
20
|
+
inputSchema?.properties
|
|
21
|
+
? JSON.stringify(
|
|
22
|
+
Object.fromEntries(
|
|
23
|
+
Object.keys(inputSchema.properties).map((k) => [k, '']),
|
|
24
|
+
),
|
|
24
25
|
null,
|
|
25
26
|
2,
|
|
26
27
|
)
|