mcpgraph-ux 0.1.1 → 0.1.3
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/README.md +2 -0
- package/app/api/execution/breakpoints/route.ts +106 -0
- package/app/api/execution/context/route.ts +69 -0
- package/app/api/execution/controller/route.ts +121 -0
- package/app/api/execution/history/route.ts +40 -0
- package/app/api/execution/history-with-indices/route.ts +57 -0
- package/app/api/execution/stream/route.ts +34 -0
- package/app/api/graph/route.ts +63 -29
- package/app/api/tools/[toolName]/route.ts +268 -17
- package/app/api/tools/route.ts +3 -15
- package/app/page.module.css +64 -18
- package/app/page.tsx +38 -15
- package/components/DebugControls.module.css +124 -0
- package/components/DebugControls.tsx +209 -0
- package/components/ExecutionHistory.module.css +268 -0
- package/components/ExecutionHistory.tsx +197 -0
- package/components/GraphVisualization.module.css +11 -0
- package/components/GraphVisualization.tsx +350 -21
- package/components/InputForm.module.css +115 -0
- package/components/InputForm.tsx +271 -0
- package/components/ServerDetails.module.css +118 -0
- package/components/ServerDetails.tsx +116 -0
- package/components/TelemetryDashboard.module.css +177 -0
- package/components/TelemetryDashboard.tsx +154 -0
- package/components/ToolList.module.css +2 -2
- package/components/ToolTester.module.css +24 -119
- package/components/ToolTester.tsx +627 -229
- package/package.json +2 -2
- package/server.ts +24 -10
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import styles from './TelemetryDashboard.module.css';
|
|
4
|
+
|
|
5
|
+
export interface ExecutionTelemetry {
|
|
6
|
+
totalDuration: number;
|
|
7
|
+
nodeDurations: Record<string, number>;
|
|
8
|
+
nodeCounts: Record<string, number>;
|
|
9
|
+
errorCount: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TelemetryDashboardProps {
|
|
13
|
+
telemetry: ExecutionTelemetry | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function TelemetryDashboard({ telemetry }: TelemetryDashboardProps) {
|
|
17
|
+
if (!telemetry) {
|
|
18
|
+
return (
|
|
19
|
+
<div className={styles.container}>
|
|
20
|
+
<h3 className={styles.title}>Performance Metrics</h3>
|
|
21
|
+
<div className={styles.empty}>No telemetry data available</div>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const formatDuration = (ms: number) => {
|
|
27
|
+
if (ms < 1) return `${(ms * 1000).toFixed(0)}μs`;
|
|
28
|
+
if (ms < 1000) return `${ms.toFixed(2)}ms`;
|
|
29
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const formatPercentage = (value: number, total: number) => {
|
|
33
|
+
if (total === 0) return '0%';
|
|
34
|
+
return `${((value / total) * 100).toFixed(1)}%`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Calculate averages per node type
|
|
38
|
+
const nodeTypeStats: Record<string, {
|
|
39
|
+
totalDuration: number;
|
|
40
|
+
count: number;
|
|
41
|
+
averageDuration: number;
|
|
42
|
+
}> = {};
|
|
43
|
+
|
|
44
|
+
Object.entries(telemetry.nodeDurations).forEach(([nodeId, duration]) => {
|
|
45
|
+
// Extract node type from nodeId (simplified - in real implementation, we'd need node metadata)
|
|
46
|
+
// For now, we'll group by the pattern we see in nodeIds
|
|
47
|
+
const nodeType = nodeId.split('_')[0] || 'unknown';
|
|
48
|
+
|
|
49
|
+
if (!nodeTypeStats[nodeType]) {
|
|
50
|
+
nodeTypeStats[nodeType] = {
|
|
51
|
+
totalDuration: 0,
|
|
52
|
+
count: 0,
|
|
53
|
+
averageDuration: 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
nodeTypeStats[nodeType].totalDuration += duration;
|
|
58
|
+
nodeTypeStats[nodeType].count += 1;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Calculate averages
|
|
62
|
+
Object.keys(nodeTypeStats).forEach(nodeType => {
|
|
63
|
+
const stats = nodeTypeStats[nodeType];
|
|
64
|
+
stats.averageDuration = stats.totalDuration / stats.count;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Sort nodes by duration (descending)
|
|
68
|
+
const sortedNodes = Object.entries(telemetry.nodeDurations)
|
|
69
|
+
.sort(([, a], [, b]) => b - a)
|
|
70
|
+
.slice(0, 10); // Top 10 slowest nodes
|
|
71
|
+
|
|
72
|
+
// Sort node types by total duration
|
|
73
|
+
const sortedNodeTypes = Object.entries(nodeTypeStats)
|
|
74
|
+
.sort(([, a], [, b]) => b.totalDuration - a.totalDuration);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className={styles.container}>
|
|
78
|
+
<h3 className={styles.title}>Performance Metrics</h3>
|
|
79
|
+
|
|
80
|
+
<div className={styles.metrics}>
|
|
81
|
+
<div className={styles.metricCard}>
|
|
82
|
+
<div className={styles.metricLabel}>Total Duration</div>
|
|
83
|
+
<div className={styles.metricValue}>{formatDuration(telemetry.totalDuration)}</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div className={styles.metricCard}>
|
|
87
|
+
<div className={styles.metricLabel}>Nodes Executed</div>
|
|
88
|
+
<div className={styles.metricValue}>
|
|
89
|
+
{Object.values(telemetry.nodeCounts).reduce((sum, count) => sum + count, 0)}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className={styles.metricCard}>
|
|
94
|
+
<div className={styles.metricLabel}>Errors</div>
|
|
95
|
+
<div className={`${styles.metricValue} ${telemetry.errorCount > 0 ? styles.error : ''}`}>
|
|
96
|
+
{telemetry.errorCount}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{sortedNodeTypes.length > 0 && (
|
|
102
|
+
<div className={styles.section}>
|
|
103
|
+
<h4 className={styles.sectionTitle}>Duration by Node Type</h4>
|
|
104
|
+
<div className={styles.nodeTypeList}>
|
|
105
|
+
{sortedNodeTypes.map(([nodeType, stats]) => (
|
|
106
|
+
<div key={nodeType} className={styles.nodeTypeItem}>
|
|
107
|
+
<div className={styles.nodeTypeHeader}>
|
|
108
|
+
<span className={styles.nodeTypeName}>{nodeType}</span>
|
|
109
|
+
<span className={styles.nodeTypeCount}>{stats.count} nodes</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div className={styles.progressBar}>
|
|
112
|
+
<div
|
|
113
|
+
className={styles.progressFill}
|
|
114
|
+
style={{
|
|
115
|
+
width: `${(stats.totalDuration / telemetry.totalDuration) * 100}%`,
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
<div className={styles.nodeTypeStats}>
|
|
120
|
+
<span>Total: {formatDuration(stats.totalDuration)}</span>
|
|
121
|
+
<span>Avg: {formatDuration(stats.averageDuration)}</span>
|
|
122
|
+
<span>{formatPercentage(stats.totalDuration, telemetry.totalDuration)}</span>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
{sortedNodes.length > 0 && (
|
|
131
|
+
<div className={styles.section}>
|
|
132
|
+
<h4 className={styles.sectionTitle}>Slowest Nodes</h4>
|
|
133
|
+
<div className={styles.slowNodesList}>
|
|
134
|
+
{sortedNodes.map(([nodeId, duration]) => (
|
|
135
|
+
<div key={nodeId} className={styles.slowNodeItem}>
|
|
136
|
+
<span className={styles.slowNodeId}>{nodeId}</span>
|
|
137
|
+
<span className={styles.slowNodeDuration}>{formatDuration(duration)}</span>
|
|
138
|
+
<div className={styles.slowNodeBar}>
|
|
139
|
+
<div
|
|
140
|
+
className={styles.slowNodeFill}
|
|
141
|
+
style={{
|
|
142
|
+
width: `${(duration / telemetry.totalDuration) * 100}%`,
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
@@ -1,102 +1,40 @@
|
|
|
1
1
|
.container {
|
|
2
2
|
display: flex;
|
|
3
3
|
flex-direction: column;
|
|
4
|
-
|
|
4
|
+
height: 100%;
|
|
5
|
+
gap: 0;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
gap: 1rem;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.inputs {
|
|
14
|
-
display: flex;
|
|
15
|
-
flex-direction: column;
|
|
16
|
-
gap: 1rem;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.field {
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
|
-
gap: 0.5rem;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.label {
|
|
26
|
-
font-weight: 500;
|
|
27
|
-
color: #333;
|
|
28
|
-
font-size: 0.9rem;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.required {
|
|
32
|
-
color: #d32f2f;
|
|
33
|
-
margin-left: 0.25rem;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.input,
|
|
37
|
-
.textarea {
|
|
38
|
-
padding: 0.75rem;
|
|
8
|
+
.debugControlsHeader {
|
|
9
|
+
border-radius: 8px 8px 0 0;
|
|
10
|
+
background-color: #fff;
|
|
39
11
|
border: 1px solid #ddd;
|
|
40
|
-
border-
|
|
41
|
-
|
|
42
|
-
font-family: inherit;
|
|
43
|
-
transition: border-color 0.2s;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.input:focus,
|
|
47
|
-
.textarea:focus {
|
|
48
|
-
outline: none;
|
|
49
|
-
border-color: #2196f3;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.textarea {
|
|
53
|
-
resize: vertical;
|
|
54
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
55
|
-
font-size: 0.85rem;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.hint {
|
|
59
|
-
font-size: 0.8rem;
|
|
60
|
-
color: #666;
|
|
61
|
-
font-style: italic;
|
|
12
|
+
border-bottom: none;
|
|
13
|
+
padding: 0;
|
|
62
14
|
}
|
|
63
15
|
|
|
64
|
-
.
|
|
16
|
+
.graphHistoryContainer {
|
|
65
17
|
display: flex;
|
|
66
|
-
|
|
67
|
-
gap: 0
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.checkbox {
|
|
75
|
-
width: 18px;
|
|
76
|
-
height: 18px;
|
|
77
|
-
cursor: pointer;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.submitButton {
|
|
81
|
-
padding: 0.75rem 1.5rem;
|
|
82
|
-
background-color: #2196f3;
|
|
83
|
-
color: white;
|
|
84
|
-
border: none;
|
|
85
|
-
border-radius: 4px;
|
|
86
|
-
font-size: 1rem;
|
|
87
|
-
font-weight: 500;
|
|
88
|
-
cursor: pointer;
|
|
89
|
-
transition: background-color 0.2s;
|
|
90
|
-
align-self: flex-start;
|
|
18
|
+
flex: 1;
|
|
19
|
+
gap: 0;
|
|
20
|
+
border-radius: 0 0 8px 8px;
|
|
21
|
+
border: 1px solid #ddd;
|
|
22
|
+
border-top: none;
|
|
23
|
+
overflow: hidden;
|
|
91
24
|
}
|
|
92
25
|
|
|
93
|
-
.
|
|
94
|
-
|
|
26
|
+
.graphSection {
|
|
27
|
+
flex: 1;
|
|
28
|
+
min-width: 0;
|
|
29
|
+
background-color: #fff;
|
|
95
30
|
}
|
|
96
31
|
|
|
97
|
-
.
|
|
98
|
-
|
|
99
|
-
|
|
32
|
+
.historySection {
|
|
33
|
+
flex: 1;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
background-color: #fff;
|
|
36
|
+
border-left: 1px solid #ddd;
|
|
37
|
+
overflow-y: auto;
|
|
100
38
|
}
|
|
101
39
|
|
|
102
40
|
.error {
|
|
@@ -107,36 +45,3 @@
|
|
|
107
45
|
color: #c62828;
|
|
108
46
|
}
|
|
109
47
|
|
|
110
|
-
.result {
|
|
111
|
-
padding: 1rem;
|
|
112
|
-
background-color: #f5f5f5;
|
|
113
|
-
border: 1px solid #ddd;
|
|
114
|
-
border-radius: 4px;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.result h3 {
|
|
118
|
-
margin-bottom: 0.75rem;
|
|
119
|
-
font-size: 1rem;
|
|
120
|
-
font-weight: 600;
|
|
121
|
-
color: #333;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.resultContent {
|
|
125
|
-
background-color: #fff;
|
|
126
|
-
padding: 1rem;
|
|
127
|
-
border-radius: 4px;
|
|
128
|
-
overflow-x: auto;
|
|
129
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
130
|
-
font-size: 0.85rem;
|
|
131
|
-
line-height: 1.5;
|
|
132
|
-
color: #333;
|
|
133
|
-
max-height: 400px;
|
|
134
|
-
overflow-y: auto;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.loading {
|
|
138
|
-
padding: 2rem;
|
|
139
|
-
text-align: center;
|
|
140
|
-
color: #666;
|
|
141
|
-
}
|
|
142
|
-
|