@contractspec/lib.example-shared-ui 6.0.5 → 6.0.7
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/.turbo/turbo-build.log +90 -84
- package/AGENTS.md +43 -25
- package/CHANGELOG.md +11 -0
- package/README.md +63 -35
- package/dist/EvolutionDashboard.js +9 -9
- package/dist/EvolutionSidebar.js +15 -15
- package/dist/LocalDataIndicator.js +3 -3
- package/dist/MarkdownView.d.ts +0 -7
- package/dist/MarkdownView.js +76 -172
- package/dist/PersonalizationInsights.js +12 -12
- package/dist/SaveToStudioButton.js +2 -2
- package/dist/SpecDrivenTemplateShell.d.ts +1 -1
- package/dist/SpecDrivenTemplateShell.js +10 -10
- package/dist/SpecEditorPanel.js +3 -3
- package/dist/TemplateShell.js +10 -10
- package/dist/browser/EvolutionDashboard.js +9 -9
- package/dist/browser/EvolutionSidebar.js +15 -15
- package/dist/browser/LocalDataIndicator.js +3 -3
- package/dist/browser/MarkdownView.js +76 -172
- package/dist/browser/PersonalizationInsights.js +12 -12
- package/dist/browser/SaveToStudioButton.js +2 -2
- package/dist/browser/SpecDrivenTemplateShell.js +10 -10
- package/dist/browser/SpecEditorPanel.js +3 -3
- package/dist/browser/TemplateShell.js +10 -10
- package/dist/browser/hooks/index.js +29 -29
- package/dist/browser/index.js +193 -286
- package/dist/browser/lib/component-registry.js +1 -1
- package/dist/browser/markdown/formatPresentationName.js +9 -0
- package/dist/browser/markdown/useMarkdownPresentation.js +65 -0
- package/dist/hooks/index.d.ts +3 -3
- package/dist/hooks/index.js +29 -29
- package/dist/index.d.ts +12 -11
- package/dist/index.js +193 -286
- package/dist/lib/component-registry.js +1 -1
- package/dist/markdown/formatPresentationName.d.ts +1 -0
- package/dist/markdown/formatPresentationName.js +10 -0
- package/dist/markdown/useMarkdownPresentation.d.ts +21 -0
- package/dist/markdown/useMarkdownPresentation.js +66 -0
- package/dist/node/EvolutionDashboard.js +9 -9
- package/dist/node/EvolutionSidebar.js +15 -15
- package/dist/node/LocalDataIndicator.js +3 -3
- package/dist/node/MarkdownView.js +76 -172
- package/dist/node/PersonalizationInsights.js +12 -12
- package/dist/node/SaveToStudioButton.js +2 -2
- package/dist/node/SpecDrivenTemplateShell.js +10 -10
- package/dist/node/SpecEditorPanel.js +3 -3
- package/dist/node/TemplateShell.js +10 -10
- package/dist/node/hooks/index.js +29 -29
- package/dist/node/index.js +193 -286
- package/dist/node/lib/component-registry.js +1 -1
- package/dist/node/markdown/formatPresentationName.js +9 -0
- package/dist/node/markdown/useMarkdownPresentation.js +65 -0
- package/dist/utils/index.d.ts +1 -1
- package/package.json +40 -13
- package/src/EvolutionDashboard.tsx +415 -415
- package/src/EvolutionSidebar.tsx +245 -245
- package/src/LocalDataIndicator.tsx +28 -28
- package/src/MarkdownView.tsx +119 -372
- package/src/OverlayContextProvider.tsx +272 -272
- package/src/PersonalizationInsights.tsx +232 -232
- package/src/SaveToStudioButton.tsx +51 -51
- package/src/SpecDrivenTemplateShell.tsx +59 -59
- package/src/SpecEditorPanel.tsx +138 -138
- package/src/TemplateShell.tsx +50 -50
- package/src/bundles/ExampleTemplateBundle.ts +78 -78
- package/src/hooks/index.ts +3 -3
- package/src/hooks/useBehaviorTracking.ts +252 -252
- package/src/hooks/useEvolution.ts +437 -437
- package/src/hooks/useRegistryTemplates.ts +42 -42
- package/src/hooks/useSpecContent.ts +214 -214
- package/src/hooks/useWorkflowComposer.ts +567 -567
- package/src/index.ts +12 -11
- package/src/lib/component-registry.tsx +40 -40
- package/src/lib/runtime-context.tsx +31 -31
- package/src/lib/types.ts +57 -57
- package/src/markdown/formatPresentationName.ts +9 -0
- package/src/markdown/useMarkdownPresentation.ts +107 -0
- package/src/overlay-types.ts +15 -15
- package/src/utils/fetchPresentationData.ts +13 -13
- package/src/utils/generateSpecFromTemplate.ts +29 -29
- package/src/utils/index.ts +1 -1
- package/tsconfig.json +8 -8
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useCallback, useMemo } from 'react';
|
|
4
3
|
import { Button, LoaderBlock } from '@contractspec/lib.design-system';
|
|
5
|
-
import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
|
|
6
4
|
import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
|
|
7
|
-
import
|
|
5
|
+
import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
|
|
6
|
+
import { useCallback, useMemo } from 'react';
|
|
8
7
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
type OptimizationHint,
|
|
9
|
+
type SpecAnomaly,
|
|
10
|
+
type SpecSuggestion,
|
|
11
|
+
type SpecUsageStats,
|
|
12
|
+
useEvolution,
|
|
14
13
|
} from './hooks/useEvolution';
|
|
14
|
+
import type { TemplateId } from './lib/types';
|
|
15
15
|
|
|
16
16
|
export interface EvolutionDashboardProps {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
templateId: TemplateId;
|
|
18
|
+
onLog?: (message: string) => void;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -23,458 +23,458 @@ export interface EvolutionDashboardProps {
|
|
|
23
23
|
* Shows usage statistics, anomalies, AI suggestions, and optimization hints.
|
|
24
24
|
*/
|
|
25
25
|
export function EvolutionDashboard({
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
templateId,
|
|
27
|
+
onLog,
|
|
28
28
|
}: EvolutionDashboardProps) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
const {
|
|
30
|
+
usageStats,
|
|
31
|
+
anomalies,
|
|
32
|
+
suggestions,
|
|
33
|
+
hints,
|
|
34
|
+
loading,
|
|
35
|
+
trackOperation,
|
|
36
|
+
analyzeUsage,
|
|
37
|
+
generateSuggestions,
|
|
38
|
+
approveSuggestion,
|
|
39
|
+
rejectSuggestion,
|
|
40
|
+
clear,
|
|
41
|
+
operationCount,
|
|
42
|
+
} = useEvolution(templateId);
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
44
|
+
// Simulate operations for demo
|
|
45
|
+
const handleSimulateOperations = useCallback(() => {
|
|
46
|
+
// Simulate various operations with different outcomes
|
|
47
|
+
const operations = [
|
|
48
|
+
{ name: `${templateId}.list`, duration: 150, success: true },
|
|
49
|
+
{ name: `${templateId}.list`, duration: 180, success: true },
|
|
50
|
+
{ name: `${templateId}.create`, duration: 350, success: true },
|
|
51
|
+
{
|
|
52
|
+
name: `${templateId}.create`,
|
|
53
|
+
duration: 420,
|
|
54
|
+
success: false,
|
|
55
|
+
error: 'VALIDATION_ERROR',
|
|
56
|
+
},
|
|
57
|
+
{ name: `${templateId}.list`, duration: 200, success: true },
|
|
58
|
+
{ name: `${templateId}.get`, duration: 80, success: true },
|
|
59
|
+
{ name: `${templateId}.update`, duration: 280, success: true },
|
|
60
|
+
{ name: `${templateId}.list`, duration: 950, success: true }, // Slow
|
|
61
|
+
{
|
|
62
|
+
name: `${templateId}.delete`,
|
|
63
|
+
duration: 150,
|
|
64
|
+
success: false,
|
|
65
|
+
error: 'NOT_FOUND',
|
|
66
|
+
},
|
|
67
|
+
{ name: `${templateId}.create`, duration: 380, success: true },
|
|
68
|
+
];
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
for (const op of operations) {
|
|
71
|
+
trackOperation(op.name, op.duration, op.success, op.error);
|
|
72
|
+
}
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
onLog?.(`Simulated ${operations.length} operations`);
|
|
75
|
+
// Auto-analyze after simulation
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
analyzeUsage();
|
|
78
|
+
onLog?.('Analysis complete');
|
|
79
|
+
}, 100);
|
|
80
|
+
}, [templateId, trackOperation, analyzeUsage, onLog]);
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
const handleGenerateSuggestions = useCallback(async () => {
|
|
83
|
+
await generateSuggestions();
|
|
84
|
+
onLog?.('AI suggestions generated');
|
|
85
|
+
}, [generateSuggestions, onLog]);
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
const handleApproveSuggestion = useCallback(
|
|
88
|
+
(id: string) => {
|
|
89
|
+
approveSuggestion(id);
|
|
90
|
+
onLog?.(`Suggestion ${id.slice(0, 8)} approved`);
|
|
91
|
+
},
|
|
92
|
+
[approveSuggestion, onLog]
|
|
93
|
+
);
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
const handleRejectSuggestion = useCallback(
|
|
96
|
+
(id: string) => {
|
|
97
|
+
rejectSuggestion(id);
|
|
98
|
+
onLog?.(`Suggestion ${id.slice(0, 8)} rejected`);
|
|
99
|
+
},
|
|
100
|
+
[rejectSuggestion, onLog]
|
|
101
|
+
);
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
const handleClear = useCallback(() => {
|
|
104
|
+
clear();
|
|
105
|
+
onLog?.('Evolution data cleared');
|
|
106
|
+
}, [clear, onLog]);
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
const pendingSuggestions = useMemo(
|
|
109
|
+
() => suggestions.filter((s) => s.status === 'pending'),
|
|
110
|
+
[suggestions]
|
|
111
|
+
);
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
113
|
+
return (
|
|
114
|
+
<div className="space-y-6">
|
|
115
|
+
{/* Header */}
|
|
116
|
+
<div className="flex items-center justify-between">
|
|
117
|
+
<div>
|
|
118
|
+
<h2 className="font-semibold text-xl">AI Evolution Engine</h2>
|
|
119
|
+
<p className="text-muted-foreground text-sm">
|
|
120
|
+
Analyze usage patterns and get AI-powered suggestions
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
<div className="flex items-center gap-2">
|
|
124
|
+
<Badge variant="secondary">{operationCount} ops tracked</Badge>
|
|
125
|
+
<Button variant="ghost" size="sm" onPress={handleClear}>
|
|
126
|
+
Clear
|
|
127
|
+
</Button>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
131
|
+
{/* Actions */}
|
|
132
|
+
<Card className="p-4">
|
|
133
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
134
|
+
<Button
|
|
135
|
+
variant="default"
|
|
136
|
+
size="sm"
|
|
137
|
+
onPress={handleSimulateOperations}
|
|
138
|
+
>
|
|
139
|
+
Simulate Operations
|
|
140
|
+
</Button>
|
|
141
|
+
<Button
|
|
142
|
+
variant="outline"
|
|
143
|
+
size="sm"
|
|
144
|
+
onPress={analyzeUsage}
|
|
145
|
+
disabled={operationCount < 5}
|
|
146
|
+
>
|
|
147
|
+
Analyze Usage
|
|
148
|
+
</Button>
|
|
149
|
+
<Button
|
|
150
|
+
variant="outline"
|
|
151
|
+
size="sm"
|
|
152
|
+
onPress={handleGenerateSuggestions}
|
|
153
|
+
disabled={anomalies.length === 0 || loading}
|
|
154
|
+
>
|
|
155
|
+
{loading ? 'Generating...' : 'Generate AI Suggestions'}
|
|
156
|
+
</Button>
|
|
157
|
+
</div>
|
|
158
|
+
<p className="mt-2 text-muted-foreground text-xs">
|
|
159
|
+
Simulate sandbox operations, analyze patterns, and generate AI
|
|
160
|
+
improvement suggestions.
|
|
161
|
+
</p>
|
|
162
|
+
</Card>
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
{loading && <LoaderBlock label="Generating AI suggestions..." />}
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
{/* Usage Statistics */}
|
|
167
|
+
{usageStats.length > 0 && (
|
|
168
|
+
<Card className="p-4">
|
|
169
|
+
<h3 className="mb-3 font-semibold">Usage Statistics</h3>
|
|
170
|
+
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
|
171
|
+
{usageStats.map((stat) => (
|
|
172
|
+
<UsageStatCard key={stat.operation.name} stat={stat} />
|
|
173
|
+
))}
|
|
174
|
+
</div>
|
|
175
|
+
</Card>
|
|
176
|
+
)}
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
178
|
+
{/* Anomalies */}
|
|
179
|
+
{anomalies.length > 0 && (
|
|
180
|
+
<Card className="p-4">
|
|
181
|
+
<div className="mb-3 flex items-center justify-between">
|
|
182
|
+
<h3 className="font-semibold">Detected Anomalies</h3>
|
|
183
|
+
<Badge
|
|
184
|
+
variant="secondary"
|
|
185
|
+
className="border-amber-500/30 bg-amber-500/20 text-amber-400"
|
|
186
|
+
>
|
|
187
|
+
{anomalies.length} issues
|
|
188
|
+
</Badge>
|
|
189
|
+
</div>
|
|
190
|
+
<div className="space-y-2">
|
|
191
|
+
{anomalies.map((anomaly, index) => (
|
|
192
|
+
<AnomalyCard
|
|
193
|
+
key={`${anomaly.operation.name}-${index}`}
|
|
194
|
+
anomaly={anomaly}
|
|
195
|
+
/>
|
|
196
|
+
))}
|
|
197
|
+
</div>
|
|
198
|
+
</Card>
|
|
199
|
+
)}
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
201
|
+
{/* AI Suggestions */}
|
|
202
|
+
{suggestions.length > 0 && (
|
|
203
|
+
<Card className="p-4">
|
|
204
|
+
<div className="mb-3 flex items-center justify-between">
|
|
205
|
+
<h3 className="font-semibold">AI Suggestions</h3>
|
|
206
|
+
<div className="flex items-center gap-2">
|
|
207
|
+
{pendingSuggestions.length > 0 && (
|
|
208
|
+
<Badge
|
|
209
|
+
variant="secondary"
|
|
210
|
+
className="border-amber-500/30 bg-amber-500/20 text-amber-400"
|
|
211
|
+
>
|
|
212
|
+
{pendingSuggestions.length} pending
|
|
213
|
+
</Badge>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div className="space-y-3">
|
|
218
|
+
{suggestions.map((suggestion) => (
|
|
219
|
+
<SuggestionCard
|
|
220
|
+
key={suggestion.id}
|
|
221
|
+
suggestion={suggestion}
|
|
222
|
+
onApprove={handleApproveSuggestion}
|
|
223
|
+
onReject={handleRejectSuggestion}
|
|
224
|
+
/>
|
|
225
|
+
))}
|
|
226
|
+
</div>
|
|
227
|
+
</Card>
|
|
228
|
+
)}
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
230
|
+
{/* Optimization Hints */}
|
|
231
|
+
{hints.length > 0 && (
|
|
232
|
+
<Card className="p-4">
|
|
233
|
+
<h3 className="mb-3 font-semibold">Optimization Hints</h3>
|
|
234
|
+
<div className="space-y-2">
|
|
235
|
+
{hints.map((hint, index) => (
|
|
236
|
+
<HintCard key={`${hint.operation.name}-${index}`} hint={hint} />
|
|
237
|
+
))}
|
|
238
|
+
</div>
|
|
239
|
+
</Card>
|
|
240
|
+
)}
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
242
|
+
{/* Empty State */}
|
|
243
|
+
{usageStats.length === 0 &&
|
|
244
|
+
anomalies.length === 0 &&
|
|
245
|
+
suggestions.length === 0 && (
|
|
246
|
+
<Card className="p-8 text-center">
|
|
247
|
+
<p className="text-muted-foreground">
|
|
248
|
+
Click "Simulate Operations" to generate sample data for
|
|
249
|
+
analysis.
|
|
250
|
+
</p>
|
|
251
|
+
</Card>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
/**
|
|
258
258
|
* Usage statistics card component
|
|
259
259
|
*/
|
|
260
260
|
function UsageStatCard({ stat }: { stat: SpecUsageStats }) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
261
|
+
return (
|
|
262
|
+
<div className="rounded-lg border border-violet-500/20 bg-violet-500/5 p-3">
|
|
263
|
+
<div className="mb-2 flex items-center justify-between">
|
|
264
|
+
<span className="font-medium font-mono text-sm">
|
|
265
|
+
{stat.operation.name}
|
|
266
|
+
</span>
|
|
267
|
+
<Badge
|
|
268
|
+
variant={stat.errorRate > 0.1 ? 'destructive' : 'default'}
|
|
269
|
+
className={
|
|
270
|
+
stat.errorRate > 0.1
|
|
271
|
+
? ''
|
|
272
|
+
: 'border-green-500/30 bg-green-500/20 text-green-400'
|
|
273
|
+
}
|
|
274
|
+
>
|
|
275
|
+
{((1 - stat.errorRate) * 100).toFixed(0)}% success
|
|
276
|
+
</Badge>
|
|
277
|
+
</div>
|
|
278
|
+
<div className="grid grid-cols-2 gap-2 text-xs">
|
|
279
|
+
<div>
|
|
280
|
+
<span className="text-muted-foreground">Total Calls:</span>{' '}
|
|
281
|
+
<span className="font-medium">{stat.totalCalls}</span>
|
|
282
|
+
</div>
|
|
283
|
+
<div>
|
|
284
|
+
<span className="text-muted-foreground">Avg Latency:</span>{' '}
|
|
285
|
+
<span className="font-medium">
|
|
286
|
+
{stat.averageLatencyMs.toFixed(0)}ms
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
<div>
|
|
290
|
+
<span className="text-muted-foreground">P95:</span>{' '}
|
|
291
|
+
<span className="font-medium">{stat.p95LatencyMs.toFixed(0)}ms</span>
|
|
292
|
+
</div>
|
|
293
|
+
<div>
|
|
294
|
+
<span className="text-muted-foreground">P99:</span>{' '}
|
|
295
|
+
<span className="font-medium">{stat.p99LatencyMs.toFixed(0)}ms</span>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
/**
|
|
303
303
|
* Anomaly card component
|
|
304
304
|
*/
|
|
305
305
|
function AnomalyCard({ anomaly }: { anomaly: SpecAnomaly }) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
306
|
+
const severityColors = {
|
|
307
|
+
low: 'text-amber-400',
|
|
308
|
+
medium: 'text-orange-400',
|
|
309
|
+
high: 'text-red-400',
|
|
310
|
+
};
|
|
311
311
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
312
|
+
return (
|
|
313
|
+
<div className="flex items-center justify-between rounded-lg border border-amber-500/30 bg-amber-500/5 p-3">
|
|
314
|
+
<div className="flex items-center gap-3">
|
|
315
|
+
<span
|
|
316
|
+
className={`text-lg ${severityColors[anomaly.severity]}`}
|
|
317
|
+
title={`${anomaly.severity} severity`}
|
|
318
|
+
>
|
|
319
|
+
{anomaly.severity === 'high'
|
|
320
|
+
? '🔴'
|
|
321
|
+
: anomaly.severity === 'medium'
|
|
322
|
+
? '🟠'
|
|
323
|
+
: '🟡'}
|
|
324
|
+
</span>
|
|
325
|
+
<div>
|
|
326
|
+
<p className="font-medium text-sm">{anomaly.description}</p>
|
|
327
|
+
<p className="text-muted-foreground text-xs">
|
|
328
|
+
{anomaly.operation.name} • {anomaly.metric}
|
|
329
|
+
</p>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
<Badge
|
|
333
|
+
variant={anomaly.severity === 'high' ? 'destructive' : 'secondary'}
|
|
334
|
+
className={
|
|
335
|
+
anomaly.severity === 'medium'
|
|
336
|
+
? 'border-amber-500/30 bg-amber-500/20 text-amber-400'
|
|
337
|
+
: ''
|
|
338
|
+
}
|
|
339
|
+
>
|
|
340
|
+
{anomaly.severity}
|
|
341
|
+
</Badge>
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
/**
|
|
347
347
|
* Suggestion card component
|
|
348
348
|
*/
|
|
349
349
|
function SuggestionCard({
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
350
|
+
suggestion,
|
|
351
|
+
onApprove,
|
|
352
|
+
onReject,
|
|
353
353
|
}: {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
suggestion: SpecSuggestion;
|
|
355
|
+
onApprove: (id: string) => void;
|
|
356
|
+
onReject: (id: string) => void;
|
|
357
357
|
}) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
358
|
+
const getStatusStyles = (status: string) => {
|
|
359
|
+
switch (status) {
|
|
360
|
+
case 'pending':
|
|
361
|
+
return {
|
|
362
|
+
variant: 'secondary' as const,
|
|
363
|
+
className: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
|
|
364
|
+
};
|
|
365
|
+
case 'approved':
|
|
366
|
+
return {
|
|
367
|
+
variant: 'default' as const,
|
|
368
|
+
className: 'bg-green-500/20 text-green-400 border-green-500/30',
|
|
369
|
+
};
|
|
370
|
+
case 'rejected':
|
|
371
|
+
return { variant: 'destructive' as const, className: '' };
|
|
372
|
+
default:
|
|
373
|
+
return { variant: 'secondary' as const, className: '' };
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
376
|
|
|
377
|
-
|
|
377
|
+
const statusStyle = getStatusStyles(suggestion.status);
|
|
378
378
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
379
|
+
return (
|
|
380
|
+
<div className="rounded-lg border border-violet-500/30 bg-violet-500/5 p-4">
|
|
381
|
+
<div className="mb-2 flex items-start justify-between">
|
|
382
|
+
<div className="flex-1">
|
|
383
|
+
<div className="flex items-center gap-2">
|
|
384
|
+
<span className="text-lg">
|
|
385
|
+
{suggestion.intent.type === 'latency-regression'
|
|
386
|
+
? '⚡'
|
|
387
|
+
: suggestion.intent.type === 'error-spike'
|
|
388
|
+
? '🔥'
|
|
389
|
+
: '📉'}
|
|
390
|
+
</span>
|
|
391
|
+
<h4 className="font-medium">{suggestion.proposal.summary}</h4>
|
|
392
|
+
</div>
|
|
393
|
+
<p className="mt-1 text-muted-foreground text-sm">
|
|
394
|
+
{suggestion.proposal.rationale}
|
|
395
|
+
</p>
|
|
396
|
+
</div>
|
|
397
|
+
<Badge variant={statusStyle.variant} className={statusStyle.className}>
|
|
398
|
+
{suggestion.status}
|
|
399
|
+
</Badge>
|
|
400
|
+
</div>
|
|
401
401
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
402
|
+
{suggestion.proposal.recommendedActions &&
|
|
403
|
+
suggestion.proposal.recommendedActions.length > 0 && (
|
|
404
|
+
<div className="mt-3">
|
|
405
|
+
<p className="mb-1 font-semibold text-violet-400 text-xs uppercase">
|
|
406
|
+
Recommended Actions
|
|
407
|
+
</p>
|
|
408
|
+
<ul className="list-inside list-disc space-y-1 text-xs">
|
|
409
|
+
{suggestion.proposal.recommendedActions
|
|
410
|
+
.slice(0, 3)
|
|
411
|
+
.map((action, i) => (
|
|
412
|
+
<li key={i}>{action}</li>
|
|
413
|
+
))}
|
|
414
|
+
</ul>
|
|
415
|
+
</div>
|
|
416
|
+
)}
|
|
417
417
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
418
|
+
<div className="mt-3 flex items-center justify-between">
|
|
419
|
+
<div className="flex items-center gap-2 text-xs">
|
|
420
|
+
<span className="text-muted-foreground">
|
|
421
|
+
Confidence: {(suggestion.confidence * 100).toFixed(0)}%
|
|
422
|
+
</span>
|
|
423
|
+
<span className="text-muted-foreground">•</span>
|
|
424
|
+
<Badge variant="secondary">{suggestion.priority}</Badge>
|
|
425
|
+
</div>
|
|
426
|
+
{suggestion.status === 'pending' && (
|
|
427
|
+
<div className="flex items-center gap-2">
|
|
428
|
+
<Button
|
|
429
|
+
variant="outline"
|
|
430
|
+
size="sm"
|
|
431
|
+
onPress={() => onReject(suggestion.id)}
|
|
432
|
+
>
|
|
433
|
+
Reject
|
|
434
|
+
</Button>
|
|
435
|
+
<Button
|
|
436
|
+
variant="default"
|
|
437
|
+
size="sm"
|
|
438
|
+
onPress={() => onApprove(suggestion.id)}
|
|
439
|
+
>
|
|
440
|
+
Approve
|
|
441
|
+
</Button>
|
|
442
|
+
</div>
|
|
443
|
+
)}
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
);
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
/**
|
|
450
450
|
* Optimization hint card component
|
|
451
451
|
*/
|
|
452
452
|
function HintCard({ hint }: { hint: OptimizationHint }) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
453
|
+
const categoryIcons = {
|
|
454
|
+
schema: '📐',
|
|
455
|
+
policy: '🔒',
|
|
456
|
+
performance: '⚡',
|
|
457
|
+
'error-handling': '🛡️',
|
|
458
|
+
};
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
460
|
+
return (
|
|
461
|
+
<div className="rounded-lg border border-blue-500/20 bg-blue-500/5 p-3">
|
|
462
|
+
<div className="flex items-start gap-3">
|
|
463
|
+
<span className="text-lg">{categoryIcons[hint.category]}</span>
|
|
464
|
+
<div className="flex-1">
|
|
465
|
+
<p className="font-medium">{hint.summary}</p>
|
|
466
|
+
<p className="mt-1 text-muted-foreground text-xs">
|
|
467
|
+
{hint.justification}
|
|
468
|
+
</p>
|
|
469
|
+
{hint.recommendedActions.length > 0 && (
|
|
470
|
+
<ul className="mt-2 list-inside list-disc text-xs">
|
|
471
|
+
{hint.recommendedActions.slice(0, 2).map((action, i) => (
|
|
472
|
+
<li key={i}>{action}</li>
|
|
473
|
+
))}
|
|
474
|
+
</ul>
|
|
475
|
+
)}
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
);
|
|
480
480
|
}
|