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.
@@ -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,7 +1,7 @@
1
1
  .container {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- height: 100%;
4
+ flex-shrink: 0;
5
5
  }
6
6
 
7
7
  .title {
@@ -14,8 +14,8 @@
14
14
  }
15
15
 
16
16
  .toolList {
17
- flex: 1;
18
17
  overflow-y: auto;
18
+ max-height: 400px;
19
19
  }
20
20
 
21
21
  .toolItem {
@@ -1,102 +1,40 @@
1
1
  .container {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- gap: 1.5rem;
4
+ height: 100%;
5
+ gap: 0;
5
6
  }
6
7
 
7
- .form {
8
- display: flex;
9
- flex-direction: column;
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-radius: 4px;
41
- font-size: 0.9rem;
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
- .checkboxLabel {
16
+ .graphHistoryContainer {
65
17
  display: flex;
66
- align-items: center;
67
- gap: 0.5rem;
68
- font-weight: 500;
69
- color: #333;
70
- font-size: 0.9rem;
71
- cursor: pointer;
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
- .submitButton:hover:not(:disabled) {
94
- background-color: #1976d2;
26
+ .graphSection {
27
+ flex: 1;
28
+ min-width: 0;
29
+ background-color: #fff;
95
30
  }
96
31
 
97
- .submitButton:disabled {
98
- background-color: #ccc;
99
- cursor: not-allowed;
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
-