@kb-labs/studio-app 0.7.0 → 2.9.0
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/dist/index.html +1 -0
- package/dist/main.80b70197.css +2 -0
- package/dist/main.80b70197.css.map +1 -0
- package/dist/main.ad42ef4a.js +522 -0
- package/dist/main.ad42ef4a.js.map +1 -0
- package/dist/mf-manifest.json +241 -0
- package/dist/mf-stats.json +271 -0
- package/package.json +56 -39
- package/server.js +68 -0
- package/.env.example +0 -10
- package/UI-KIT-COMPONENTS.md +0 -783
- package/UI-KIT-PLAN.md +0 -326
- package/eslint-rules/no-direct-antd-import.js +0 -151
- package/eslint.config.ts +0 -27
- package/index.html +0 -17
- package/postcss.config.js +0 -7
- package/public/favicon.ico +0 -0
- package/rspack.config.mjs +0 -118
- package/src/App.tsx +0 -46
- package/src/components/devtools/devtools-log-feed.module.css +0 -165
- package/src/components/devtools/devtools-log-feed.tsx +0 -300
- package/src/components/devtools/devtools-panel.module.css +0 -121
- package/src/components/devtools/devtools-panel.tsx +0 -115
- package/src/components/empty-state.tsx +0 -28
- package/src/components/error-boundary.tsx +0 -210
- package/src/components/health-banner.module.css +0 -103
- package/src/components/health-banner.tsx +0 -134
- package/src/components/health-indicator.tsx +0 -12
- package/src/components/page-header.tsx +0 -49
- package/src/components/page-transition.tsx +0 -24
- package/src/components/role-switcher.tsx +0 -148
- package/src/components/run-badge.tsx +0 -13
- package/src/components/ui/icons.tsx +0 -509
- package/src/components/ui/index.ts +0 -25
- package/src/components/ui/kb-config-provider.tsx +0 -130
- package/src/components/ui/kb-header.module.css +0 -51
- package/src/components/ui/kb-header.tsx +0 -112
- package/src/components/ui/kb-notification-bell.module.css +0 -103
- package/src/components/ui/kb-notification-bell.tsx +0 -199
- package/src/components/ui/kb-page-layout.tsx +0 -66
- package/src/components/ui/kb-quick-search.module.css +0 -86
- package/src/components/ui/kb-quick-search.tsx +0 -196
- package/src/components/ui/kb-sidebar.module.css +0 -47
- package/src/components/ui/kb-sidebar.tsx +0 -196
- package/src/components/ui/kb-stat-card.module.css +0 -21
- package/src/components/ui/kb-stat-card.tsx +0 -22
- package/src/components/ui/kb-stat-grid.tsx +0 -22
- package/src/components/ui/kb-status-bar.module.css +0 -49
- package/src/components/ui/kb-status-bar.tsx +0 -145
- package/src/components/ui/kb-system-health-indicator.tsx +0 -229
- package/src/components/ui/kb-theme-toggle.tsx +0 -42
- package/src/components/ui/theme-adapter.ts +0 -812
- package/src/components/ui/theme-transition-overlay.tsx +0 -70
- package/src/components/ui/utils.ts +0 -6
- package/src/components/widget-error-boundary.tsx +0 -172
- package/src/config/env.ts +0 -5
- package/src/config/feature-flags.ts +0 -482
- package/src/config/studio.config.schema.ts +0 -88
- package/src/config/studio.config.ts +0 -84
- package/src/env.d.ts +0 -23
- package/src/hooks/use-command-palette.ts +0 -6
- package/src/hooks/use-devtools-store.ts +0 -25
- package/src/hooks/use-feature-flags.ts +0 -101
- package/src/index.css +0 -10
- package/src/layouts/dashboard-grid.tsx +0 -122
- package/src/layouts/index.ts +0 -12
- package/src/layouts/two-pane.tsx +0 -134
- package/src/main.tsx +0 -15
- package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +0 -257
- package/src/modules/analytics/components/analytics-date-range-picker.tsx +0 -121
- package/src/modules/analytics/pages/analytics-cache-page.tsx +0 -285
- package/src/modules/analytics/pages/analytics-embeddings-page.tsx +0 -274
- package/src/modules/analytics/pages/analytics-events-page.tsx +0 -425
- package/src/modules/analytics/pages/analytics-llm-page.tsx +0 -434
- package/src/modules/analytics/pages/analytics-overview-page.tsx +0 -336
- package/src/modules/analytics/pages/analytics-page.tsx +0 -20
- package/src/modules/analytics/pages/analytics-storage-page.tsx +0 -344
- package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +0 -279
- package/src/modules/analytics/routes/index.tsx +0 -133
- package/src/modules/analytics/utils/formatters.test.ts +0 -148
- package/src/modules/analytics/utils/formatters.ts +0 -100
- package/src/modules/auth/pages/login-page.module.css +0 -113
- package/src/modules/auth/pages/login-page.tsx +0 -80
- package/src/modules/auth/routes/index.tsx +0 -24
- package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +0 -199
- package/src/modules/dashboard/components/AiInsightsWidget.tsx +0 -358
- package/src/modules/dashboard/components/AnimatedMetricCard.css +0 -88
- package/src/modules/dashboard/components/AnimatedMetricCard.tsx +0 -125
- package/src/modules/dashboard/components/HeroMetricCard.css +0 -114
- package/src/modules/dashboard/components/HeroMetricCard.tsx +0 -152
- package/src/modules/dashboard/components/HeroMetricsWidget.tsx +0 -146
- package/src/modules/dashboard/components/SystemResourcesWidget.tsx +0 -197
- package/src/modules/dashboard/components/dashboard-empty.tsx +0 -22
- package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +0 -59
- package/src/modules/dashboard/components/dashboard-kpis.tsx +0 -13
- package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +0 -85
- package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +0 -78
- package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +0 -67
- package/src/modules/dashboard/components/dashboard-requests-widget.tsx +0 -62
- package/src/modules/dashboard/layouts/dashboard-layout.tsx +0 -16
- package/src/modules/dashboard/pages/ai-insights-page.tsx +0 -563
- package/src/modules/dashboard/pages/dashboard-animations.css +0 -317
- package/src/modules/dashboard/pages/dashboard-page.tsx +0 -13
- package/src/modules/dashboard/routes/index.tsx +0 -70
- package/src/modules/observability/pages/devkit-page.tsx +0 -263
- package/src/modules/observability/pages/incident-detail-page.tsx +0 -571
- package/src/modules/observability/pages/incidents-page.tsx +0 -370
- package/src/modules/observability/pages/log-detail-page.tsx +0 -508
- package/src/modules/observability/pages/logs-page.tsx +0 -1129
- package/src/modules/observability/pages/prometheus-metrics-page.tsx +0 -486
- package/src/modules/observability/pages/state-broker-page.tsx +0 -229
- package/src/modules/observability/pages/system-events-page.tsx +0 -255
- package/src/modules/observability/routes/index.tsx +0 -133
- package/src/modules/plugins/components/marketplace-card.tsx +0 -153
- package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +0 -374
- package/src/modules/plugins/components/plugin-card.tsx +0 -136
- package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +0 -101
- package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +0 -101
- package/src/modules/plugins/components/plugin-detail/index.ts +0 -7
- package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +0 -64
- package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +0 -30
- package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +0 -68
- package/src/modules/plugins/components/plugin-detail/permissions/index.ts +0 -6
- package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +0 -43
- package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +0 -242
- package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +0 -96
- package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +0 -55
- package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +0 -47
- package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +0 -169
- package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +0 -159
- package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +0 -40
- package/src/modules/plugins/components/plugin-grid.tsx +0 -20
- package/src/modules/plugins/components/plugin-table.tsx +0 -178
- package/src/modules/plugins/pages/marketplace-page.tsx +0 -200
- package/src/modules/plugins/pages/plugin-detail-page.tsx +0 -1145
- package/src/modules/plugins/routes/index.tsx +0 -48
- package/src/modules/plugins/utils/index.ts +0 -1
- package/src/modules/plugins/utils/schema-helpers.ts +0 -21
- package/src/modules/settings/components/api-routes-viewer.tsx +0 -415
- package/src/modules/settings/components/appearance-settings.tsx +0 -200
- package/src/modules/settings/components/configuration-settings.tsx +0 -321
- package/src/modules/settings/components/data-privacy-settings.tsx +0 -111
- package/src/modules/settings/components/experimental-settings.tsx +0 -243
- package/src/modules/settings/components/navigation-settings.tsx +0 -198
- package/src/modules/settings/components/notifications-settings.tsx +0 -40
- package/src/modules/settings/pages/settings-page.tsx +0 -298
- package/src/modules/settings/routes/index.tsx +0 -47
- package/src/pages/README-assistant.md +0 -91
- package/src/pages/login-page.tsx +0 -177
- package/src/pages/not-found-page.tsx +0 -85
- package/src/providers/auth-provider.tsx +0 -40
- package/src/providers/command-palette-provider.tsx +0 -193
- package/src/providers/data-sources-provider.tsx +0 -27
- package/src/providers/devtools-provider.tsx +0 -72
- package/src/providers/navigation-items-provider.tsx +0 -38
- package/src/providers/registry-v2-provider.tsx +0 -93
- package/src/providers/settings-provider.tsx +0 -158
- package/src/router.tsx +0 -302
- package/src/routes/helpers.tsx +0 -25
- package/src/routes/plugin-page-v2.tsx +0 -52
- package/src/routes/types.ts +0 -18
- package/src/styles/antd-overrides.css +0 -211
- package/src/styles/base.css +0 -190
- package/src/styles/settings.css +0 -75
- package/src/styles/variables.css +0 -94
- package/src/utils/analytics.ts +0 -183
- package/src/utils/apply-navigation-categories.ts +0 -83
- package/src/utils/commands.ts +0 -252
- package/src/utils/export.ts +0 -174
- package/src/utils/format-date.ts +0 -166
- package/src/utils/logger.ts +0 -110
- package/tailwind.config.ts +0 -13
- package/tsconfig.json +0 -13
- package/vitest-setup.ts +0 -1
- /package/{public → dist}/favicon.svg +0 -0
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import { UIButton, UITypographyText, UITypographyParagraph, UITitle, UIAvatar, UISpin, UITag, UICard, UIInputTextArea } from '@kb-labs/studio-ui-kit';
|
|
3
|
-
import {
|
|
4
|
-
RobotOutlined,
|
|
5
|
-
SendOutlined,
|
|
6
|
-
UserOutlined,
|
|
7
|
-
BulbOutlined,
|
|
8
|
-
CopyOutlined,
|
|
9
|
-
CheckOutlined,
|
|
10
|
-
} from '@ant-design/icons';
|
|
11
|
-
import { useDataSources } from '../../../providers/data-sources-provider';
|
|
12
|
-
import { usePrometheusMetrics, useAdaptersLLMUsage } from '@kb-labs/studio-data-client';
|
|
13
|
-
|
|
14
|
-
interface Message {
|
|
15
|
-
id: string;
|
|
16
|
-
role: 'user' | 'assistant';
|
|
17
|
-
content: string;
|
|
18
|
-
timestamp: number;
|
|
19
|
-
context?: string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface SuggestedQuestion {
|
|
23
|
-
text: string;
|
|
24
|
-
category: 'cost' | 'performance' | 'health' | 'optimization';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const SUGGESTED_QUESTIONS: SuggestedQuestion[] = [
|
|
28
|
-
{ text: 'Why is LLM cost higher than usual?', category: 'cost' },
|
|
29
|
-
{ text: "What's causing latency spikes?", category: 'performance' },
|
|
30
|
-
{ text: 'Is the system healthy?', category: 'health' },
|
|
31
|
-
{ text: 'How can I reduce API costs?', category: 'optimization' },
|
|
32
|
-
{ text: 'Which endpoints are slowest?', category: 'performance' },
|
|
33
|
-
{ text: 'Are there any anomalies?', category: 'health' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
// Mock AI response generator based on context
|
|
37
|
-
const generateMockResponse = (question: string, context: {
|
|
38
|
-
totalRequests: number;
|
|
39
|
-
errorRate: number;
|
|
40
|
-
avgLatency: number;
|
|
41
|
-
totalCost: number;
|
|
42
|
-
topModel: string;
|
|
43
|
-
}): { content: string; contextUsed: string[] } => {
|
|
44
|
-
const q = question.toLowerCase();
|
|
45
|
-
const contextUsed: string[] = [];
|
|
46
|
-
|
|
47
|
-
if (q.includes('cost') || q.includes('expensive') || q.includes('spending')) {
|
|
48
|
-
contextUsed.push('LLM Usage', 'Cost Attribution');
|
|
49
|
-
return {
|
|
50
|
-
content: `Based on your current metrics, total costs are **$${context.totalCost.toFixed(2)}** today.
|
|
51
|
-
|
|
52
|
-
**Key findings:**
|
|
53
|
-
- Your most expensive model is **${context.topModel}** accounting for ~65% of costs
|
|
54
|
-
- Cost per 1K requests: $${(context.totalCost / context.totalRequests * 1000).toFixed(4)}
|
|
55
|
-
|
|
56
|
-
**Recommendations:**
|
|
57
|
-
1. Consider using a smaller model for simple queries
|
|
58
|
-
2. Implement response caching for repeated questions
|
|
59
|
-
3. Batch similar requests to reduce API overhead`,
|
|
60
|
-
contextUsed,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (q.includes('latency') || q.includes('slow') || q.includes('performance')) {
|
|
65
|
-
contextUsed.push('Prometheus Metrics', 'Performance Data');
|
|
66
|
-
return {
|
|
67
|
-
content: `Current average latency is **${context.avgLatency.toFixed(0)}ms**.
|
|
68
|
-
|
|
69
|
-
**Analysis:**
|
|
70
|
-
- P95 latency is approximately ${(context.avgLatency * 1.5).toFixed(0)}ms
|
|
71
|
-
- ${context.avgLatency > 200 ? '⚠️ Latency is above recommended threshold (200ms)' : '✅ Latency is within acceptable range'}
|
|
72
|
-
|
|
73
|
-
**Potential causes:**
|
|
74
|
-
1. LLM API response times (external dependency)
|
|
75
|
-
2. Database query complexity
|
|
76
|
-
3. Network latency to vector store
|
|
77
|
-
|
|
78
|
-
**Suggestions:**
|
|
79
|
-
- Enable response streaming for large outputs
|
|
80
|
-
- Implement request timeouts at 5s
|
|
81
|
-
- Consider caching frequent queries`,
|
|
82
|
-
contextUsed,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (q.includes('health') || q.includes('status') || q.includes('ok')) {
|
|
87
|
-
contextUsed.push('System Health', 'Error Rates');
|
|
88
|
-
const healthStatus = context.errorRate < 1 ? 'healthy' : context.errorRate < 5 ? 'degraded' : 'critical';
|
|
89
|
-
return {
|
|
90
|
-
content: `System status: **${healthStatus.toUpperCase()}**
|
|
91
|
-
|
|
92
|
-
**Current metrics:**
|
|
93
|
-
- Error rate: ${context.errorRate.toFixed(2)}% ${context.errorRate < 1 ? '✅' : '⚠️'}
|
|
94
|
-
- Total requests: ${context.totalRequests.toLocaleString()}
|
|
95
|
-
- Uptime: ${(100 - context.errorRate).toFixed(2)}%
|
|
96
|
-
|
|
97
|
-
**Health breakdown:**
|
|
98
|
-
- API Gateway: ✅ Healthy
|
|
99
|
-
- LLM Adapter: ${context.avgLatency < 300 ? '✅ Healthy' : '⚠️ Slow'}
|
|
100
|
-
- Cache Layer: ✅ Healthy
|
|
101
|
-
- Vector Store: ✅ Healthy`,
|
|
102
|
-
contextUsed,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (q.includes('reduce') || q.includes('optimize') || q.includes('improve')) {
|
|
107
|
-
contextUsed.push('Usage Patterns', 'Cost Analysis');
|
|
108
|
-
return {
|
|
109
|
-
content: `Here are optimization recommendations based on your usage:
|
|
110
|
-
|
|
111
|
-
**Cost Optimization:**
|
|
112
|
-
1. Switch to gpt-3.5-turbo for classification tasks (-40% cost)
|
|
113
|
-
2. Implement semantic caching for similar queries (-25% requests)
|
|
114
|
-
3. Use shorter system prompts (-15% tokens)
|
|
115
|
-
|
|
116
|
-
**Performance Optimization:**
|
|
117
|
-
1. Enable connection pooling for DB queries
|
|
118
|
-
2. Implement request batching for embeddings
|
|
119
|
-
3. Add Redis caching for hot paths
|
|
120
|
-
|
|
121
|
-
**Estimated savings:** $${(context.totalCost * 0.3).toFixed(2)}/day with these changes`,
|
|
122
|
-
contextUsed,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Default response
|
|
127
|
-
contextUsed.push('General Metrics');
|
|
128
|
-
return {
|
|
129
|
-
content: `Based on your dashboard data:
|
|
130
|
-
|
|
131
|
-
- **Total Requests:** ${context.totalRequests.toLocaleString()}
|
|
132
|
-
- **Error Rate:** ${context.errorRate.toFixed(2)}%
|
|
133
|
-
- **Avg Latency:** ${context.avgLatency.toFixed(0)}ms
|
|
134
|
-
- **Total Cost:** $${context.totalCost.toFixed(2)}
|
|
135
|
-
|
|
136
|
-
Is there something specific you'd like me to analyze? Try asking about:
|
|
137
|
-
- Cost breakdown and optimization
|
|
138
|
-
- Performance and latency issues
|
|
139
|
-
- System health status
|
|
140
|
-
- Specific endpoint analysis`,
|
|
141
|
-
contextUsed,
|
|
142
|
-
};
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
export function AIInsightsWidget() {
|
|
146
|
-
const sources = useDataSources();
|
|
147
|
-
const metrics = usePrometheusMetrics(sources.observability);
|
|
148
|
-
const llmUsage = useAdaptersLLMUsage(sources.adapters);
|
|
149
|
-
|
|
150
|
-
const [messages, setMessages] = useState<Message[]>([]);
|
|
151
|
-
const [input, setInput] = useState('');
|
|
152
|
-
const [isTyping, setIsTyping] = useState(false);
|
|
153
|
-
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
154
|
-
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
155
|
-
|
|
156
|
-
// Auto-scroll to bottom
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
159
|
-
}, [messages]);
|
|
160
|
-
|
|
161
|
-
// Build context from real data
|
|
162
|
-
const buildContext = () => ({
|
|
163
|
-
totalRequests: metrics.data?.requests?.total ?? 0,
|
|
164
|
-
errorRate: metrics.data?.requests?.total
|
|
165
|
-
? ((metrics.data.requests.clientErrors + metrics.data.requests.serverErrors) / metrics.data.requests.total) * 100
|
|
166
|
-
: 0,
|
|
167
|
-
avgLatency: (metrics.data?.perPlugin?.reduce((sum, p) => sum + (p.latency?.average ?? 0), 0) ?? 0) /
|
|
168
|
-
(metrics.data?.perPlugin?.length || 1),
|
|
169
|
-
totalCost: llmUsage.data?.totalCost ?? 0,
|
|
170
|
-
topModel: Object.entries(llmUsage.data?.byModel ?? {})
|
|
171
|
-
.sort(([, a], [, b]) => (b.cost ?? 0) - (a.cost ?? 0))[0]?.[0] ?? 'gpt-4',
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const sendMessage = async (text: string) => {
|
|
175
|
-
if (!text.trim()) {return;}
|
|
176
|
-
|
|
177
|
-
const userMessage: Message = {
|
|
178
|
-
id: `user-${Date.now()}`,
|
|
179
|
-
role: 'user',
|
|
180
|
-
content: text,
|
|
181
|
-
timestamp: Date.now(),
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
setMessages(prev => [...prev, userMessage]);
|
|
185
|
-
setInput('');
|
|
186
|
-
setIsTyping(true);
|
|
187
|
-
|
|
188
|
-
// Simulate AI thinking delay
|
|
189
|
-
await new Promise<void>(resolve => { setTimeout(resolve, 1000 + Math.random() * 1000); });
|
|
190
|
-
|
|
191
|
-
const context = buildContext();
|
|
192
|
-
const { content, contextUsed } = generateMockResponse(text, context);
|
|
193
|
-
|
|
194
|
-
const assistantMessage: Message = {
|
|
195
|
-
id: `assistant-${Date.now()}`,
|
|
196
|
-
role: 'assistant',
|
|
197
|
-
content,
|
|
198
|
-
timestamp: Date.now(),
|
|
199
|
-
context: contextUsed,
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
setMessages(prev => [...prev, assistantMessage]);
|
|
203
|
-
setIsTyping(false);
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const copyToClipboard = (text: string, id: string) => {
|
|
207
|
-
navigator.clipboard.writeText(text);
|
|
208
|
-
setCopiedId(id);
|
|
209
|
-
setTimeout(() => setCopiedId(null), 2000);
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const getCategoryColor = (category: string) => {
|
|
213
|
-
switch (category) {
|
|
214
|
-
case 'cost': return 'gold';
|
|
215
|
-
case 'performance': return 'blue';
|
|
216
|
-
case 'health': return 'green';
|
|
217
|
-
case 'optimization': return 'purple';
|
|
218
|
-
default: return 'default';
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
return (
|
|
223
|
-
<UICard
|
|
224
|
-
title={
|
|
225
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
226
|
-
<RobotOutlined />
|
|
227
|
-
<span>AI Insights</span>
|
|
228
|
-
<UITag color="blue" style={{ marginLeft: 8 }}>Beta</UITag>
|
|
229
|
-
</div>
|
|
230
|
-
}
|
|
231
|
-
style={{ height: '100%' }}
|
|
232
|
-
styles={{ body: { padding: 0, display: 'flex', flexDirection: 'column', height: 450 } }}
|
|
233
|
-
>
|
|
234
|
-
{/* Messages Area */}
|
|
235
|
-
<div style={{
|
|
236
|
-
flex: 1,
|
|
237
|
-
overflowY: 'auto',
|
|
238
|
-
padding: 16,
|
|
239
|
-
display: 'flex',
|
|
240
|
-
flexDirection: 'column',
|
|
241
|
-
gap: 12,
|
|
242
|
-
}}>
|
|
243
|
-
{messages.length === 0 ? (
|
|
244
|
-
<div style={{ textAlign: 'center', padding: '40px 20px' }}>
|
|
245
|
-
<BulbOutlined style={{ fontSize: 48, color: 'var(--border-primary)', marginBottom: 16 }} />
|
|
246
|
-
<UITitle level={5}>Ask me anything about your system</UITitle>
|
|
247
|
-
<UITypographyParagraph type="secondary" style={{ fontSize: 13 }}>
|
|
248
|
-
I can analyze costs, performance, health metrics, and suggest optimizations.
|
|
249
|
-
</UITypographyParagraph>
|
|
250
|
-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, justifyContent: 'center', marginTop: 16 }}>
|
|
251
|
-
{SUGGESTED_QUESTIONS.slice(0, 4).map((q, i) => (
|
|
252
|
-
<UITag
|
|
253
|
-
key={i}
|
|
254
|
-
color={getCategoryColor(q.category)}
|
|
255
|
-
style={{ cursor: 'pointer', padding: '4px 12px' }}
|
|
256
|
-
onClick={() => sendMessage(q.text)}
|
|
257
|
-
>
|
|
258
|
-
{q.text}
|
|
259
|
-
</UITag>
|
|
260
|
-
))}
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
263
|
-
) : (
|
|
264
|
-
messages.map(msg => (
|
|
265
|
-
<div
|
|
266
|
-
key={msg.id}
|
|
267
|
-
style={{
|
|
268
|
-
display: 'flex',
|
|
269
|
-
gap: 12,
|
|
270
|
-
flexDirection: msg.role === 'user' ? 'row-reverse' : 'row',
|
|
271
|
-
}}
|
|
272
|
-
>
|
|
273
|
-
<UIAvatar
|
|
274
|
-
size={32}
|
|
275
|
-
icon={msg.role === 'user' ? <UserOutlined /> : <RobotOutlined />}
|
|
276
|
-
style={{
|
|
277
|
-
backgroundColor: msg.role === 'user' ? 'var(--info)' : 'var(--link)',
|
|
278
|
-
flexShrink: 0,
|
|
279
|
-
}}
|
|
280
|
-
/>
|
|
281
|
-
<div style={{
|
|
282
|
-
maxWidth: '80%',
|
|
283
|
-
padding: '10px 14px',
|
|
284
|
-
borderRadius: 12,
|
|
285
|
-
backgroundColor: msg.role === 'user' ? 'var(--info)' : 'var(--bg-tertiary)',
|
|
286
|
-
color: msg.role === 'user' ? 'var(--text-inverse)' : 'var(--text-primary)',
|
|
287
|
-
}}>
|
|
288
|
-
<div style={{ whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 }}>
|
|
289
|
-
{msg.content.split('**').map((part, i) =>
|
|
290
|
-
i % 2 === 1 ? <strong key={i}>{part}</strong> : part
|
|
291
|
-
)}
|
|
292
|
-
</div>
|
|
293
|
-
{msg.context && msg.context.length > 0 && (
|
|
294
|
-
<div style={{ marginTop: 8, display: 'flex', gap: 4, flexWrap: 'wrap' }}>
|
|
295
|
-
{msg.context.map((ctx, i) => (
|
|
296
|
-
<UITag key={i} style={{ fontSize: 10, margin: 0 }}>{ctx}</UITag>
|
|
297
|
-
))}
|
|
298
|
-
</div>
|
|
299
|
-
)}
|
|
300
|
-
{msg.role === 'assistant' && (
|
|
301
|
-
<UIButton
|
|
302
|
-
variant="text"
|
|
303
|
-
size="small"
|
|
304
|
-
icon={copiedId === msg.id ? <CheckOutlined /> : <CopyOutlined />}
|
|
305
|
-
style={{ marginTop: 4, padding: '0 4px', height: 20 }}
|
|
306
|
-
onClick={() => copyToClipboard(msg.content, msg.id)}
|
|
307
|
-
/>
|
|
308
|
-
)}
|
|
309
|
-
</div>
|
|
310
|
-
</div>
|
|
311
|
-
))
|
|
312
|
-
)}
|
|
313
|
-
|
|
314
|
-
{isTyping && (
|
|
315
|
-
<div style={{ display: 'flex', gap: 12 }}>
|
|
316
|
-
<UIAvatar size={32} icon={<RobotOutlined />} style={{ backgroundColor: 'var(--link)' }} />
|
|
317
|
-
<div style={{
|
|
318
|
-
padding: '10px 14px',
|
|
319
|
-
borderRadius: 12,
|
|
320
|
-
backgroundColor: 'var(--bg-tertiary)',
|
|
321
|
-
}}>
|
|
322
|
-
<UISpin size="small" /> <UITypographyText type="secondary" style={{ marginLeft: 8 }}>Analyzing...</UITypographyText>
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
325
|
-
)}
|
|
326
|
-
<div ref={messagesEndRef} />
|
|
327
|
-
</div>
|
|
328
|
-
|
|
329
|
-
{/* Input Area */}
|
|
330
|
-
<div style={{
|
|
331
|
-
padding: 12,
|
|
332
|
-
borderTop: '1px solid var(--border-primary)',
|
|
333
|
-
display: 'flex',
|
|
334
|
-
gap: 8,
|
|
335
|
-
}}>
|
|
336
|
-
<UIInputTextArea
|
|
337
|
-
value={input}
|
|
338
|
-
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setInput(e.target.value)}
|
|
339
|
-
placeholder="Ask about costs, performance, health..."
|
|
340
|
-
autoSize={{ minRows: 1, maxRows: 3 }}
|
|
341
|
-
onPressEnter={(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
342
|
-
if (!e.shiftKey) {
|
|
343
|
-
e.preventDefault();
|
|
344
|
-
sendMessage(input);
|
|
345
|
-
}
|
|
346
|
-
}}
|
|
347
|
-
style={{ flex: 1 }}
|
|
348
|
-
/>
|
|
349
|
-
<UIButton
|
|
350
|
-
variant="primary"
|
|
351
|
-
icon={<SendOutlined />}
|
|
352
|
-
onClick={() => sendMessage(input)}
|
|
353
|
-
disabled={!input.trim() || isTyping}
|
|
354
|
-
/>
|
|
355
|
-
</div>
|
|
356
|
-
</UICard>
|
|
357
|
-
);
|
|
358
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
.animated-metric-card {
|
|
2
|
-
position: relative;
|
|
3
|
-
overflow: hidden;
|
|
4
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
5
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.animated-metric-card:hover {
|
|
9
|
-
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
10
|
-
transform: translateY(-2px);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.animated-metric-card.animating::before {
|
|
14
|
-
content: '';
|
|
15
|
-
position: absolute;
|
|
16
|
-
top: 0;
|
|
17
|
-
left: 0;
|
|
18
|
-
right: 0;
|
|
19
|
-
height: 2px;
|
|
20
|
-
background: linear-gradient(
|
|
21
|
-
90deg,
|
|
22
|
-
transparent 0%,
|
|
23
|
-
currentColor 50%,
|
|
24
|
-
transparent 100%
|
|
25
|
-
);
|
|
26
|
-
animation: shimmer 1s ease-in-out;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@keyframes shimmer {
|
|
30
|
-
0% {
|
|
31
|
-
transform: translateX(-100%);
|
|
32
|
-
opacity: 0;
|
|
33
|
-
}
|
|
34
|
-
50% {
|
|
35
|
-
opacity: 1;
|
|
36
|
-
}
|
|
37
|
-
100% {
|
|
38
|
-
transform: translateX(100%);
|
|
39
|
-
opacity: 0;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.animated-metric-card:hover .metric-icon {
|
|
44
|
-
opacity: 0.3 !important;
|
|
45
|
-
transform: scale(1.1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.metric-icon {
|
|
49
|
-
transition: all 0.3s ease;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/* Pulse animation for real-time updates */
|
|
53
|
-
@keyframes pulse {
|
|
54
|
-
0% {
|
|
55
|
-
box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.4);
|
|
56
|
-
}
|
|
57
|
-
70% {
|
|
58
|
-
box-shadow: 0 0 0 10px rgba(24, 144, 255, 0);
|
|
59
|
-
}
|
|
60
|
-
100% {
|
|
61
|
-
box-shadow: 0 0 0 0 rgba(24, 144, 255, 0);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.animated-metric-card.live {
|
|
66
|
-
animation: pulse 2s infinite;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/* Loading skeleton animation */
|
|
70
|
-
@keyframes skeleton-loading {
|
|
71
|
-
0% {
|
|
72
|
-
background-position: -200px 0;
|
|
73
|
-
}
|
|
74
|
-
100% {
|
|
75
|
-
background-position: calc(200px + 100%) 0;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.animated-metric-card .ant-skeleton {
|
|
80
|
-
background: linear-gradient(
|
|
81
|
-
90deg,
|
|
82
|
-
#f0f0f0 25%,
|
|
83
|
-
#e0e0e0 50%,
|
|
84
|
-
#f0f0f0 75%
|
|
85
|
-
);
|
|
86
|
-
background-size: 200px 100%;
|
|
87
|
-
animation: skeleton-loading 1.5s ease-in-out infinite;
|
|
88
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { UICard, UIStatistic } from '@kb-labs/studio-ui-kit';
|
|
3
|
-
import './AnimatedMetricCard.css';
|
|
4
|
-
|
|
5
|
-
export interface AnimatedMetricCardProps {
|
|
6
|
-
title: string;
|
|
7
|
-
value: number;
|
|
8
|
-
previousValue?: number;
|
|
9
|
-
suffix?: string;
|
|
10
|
-
prefix?: string;
|
|
11
|
-
precision?: number;
|
|
12
|
-
icon?: React.ReactNode;
|
|
13
|
-
color?: string;
|
|
14
|
-
animationDuration?: number; // ms
|
|
15
|
-
loading?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function AnimatedMetricCard({
|
|
19
|
-
title,
|
|
20
|
-
value,
|
|
21
|
-
previousValue,
|
|
22
|
-
suffix,
|
|
23
|
-
prefix,
|
|
24
|
-
precision = 0,
|
|
25
|
-
icon,
|
|
26
|
-
color = 'var(--info)',
|
|
27
|
-
animationDuration = 1000,
|
|
28
|
-
loading = false,
|
|
29
|
-
}: AnimatedMetricCardProps) {
|
|
30
|
-
const [displayValue, setDisplayValue] = useState(previousValue ?? value);
|
|
31
|
-
const [isAnimating, setIsAnimating] = useState(false);
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
if (value === displayValue) {return;}
|
|
35
|
-
|
|
36
|
-
setIsAnimating(true);
|
|
37
|
-
const startValue = displayValue;
|
|
38
|
-
const endValue = value;
|
|
39
|
-
const startTime = Date.now();
|
|
40
|
-
const duration = animationDuration;
|
|
41
|
-
|
|
42
|
-
const animate = () => {
|
|
43
|
-
const now = Date.now();
|
|
44
|
-
const elapsed = now - startTime;
|
|
45
|
-
const progress = Math.min(elapsed / duration, 1);
|
|
46
|
-
|
|
47
|
-
// Easing function (easeOutCubic)
|
|
48
|
-
const eased = 1 - Math.pow(1 - progress, 3);
|
|
49
|
-
const current = startValue + (endValue - startValue) * eased;
|
|
50
|
-
|
|
51
|
-
setDisplayValue(current);
|
|
52
|
-
|
|
53
|
-
if (progress < 1) {
|
|
54
|
-
requestAnimationFrame(animate);
|
|
55
|
-
} else {
|
|
56
|
-
setIsAnimating(false);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
requestAnimationFrame(animate);
|
|
61
|
-
}, [value, animationDuration, displayValue]);
|
|
62
|
-
|
|
63
|
-
const hasChange = previousValue !== undefined && previousValue !== value;
|
|
64
|
-
const changePercent = hasChange ? ((value - previousValue!) / previousValue!) * 100 : 0;
|
|
65
|
-
const isIncrease = changePercent > 0;
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<UICard
|
|
69
|
-
variant="borderless"
|
|
70
|
-
className={`animated-metric-card ${isAnimating ? 'animating' : ''}`}
|
|
71
|
-
style={{
|
|
72
|
-
borderLeft: `4px solid ${color}`,
|
|
73
|
-
transition: 'all 0.3s ease',
|
|
74
|
-
}}
|
|
75
|
-
loading={loading}
|
|
76
|
-
>
|
|
77
|
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
78
|
-
<div style={{ flex: 1 }}>
|
|
79
|
-
<UIStatistic
|
|
80
|
-
title={title}
|
|
81
|
-
value={displayValue}
|
|
82
|
-
precision={precision}
|
|
83
|
-
suffix={suffix}
|
|
84
|
-
prefix={prefix}
|
|
85
|
-
valueStyle={{
|
|
86
|
-
fontSize: 32,
|
|
87
|
-
fontWeight: 600,
|
|
88
|
-
color,
|
|
89
|
-
transition: 'color 0.3s ease',
|
|
90
|
-
}}
|
|
91
|
-
/>
|
|
92
|
-
{hasChange && (
|
|
93
|
-
<div
|
|
94
|
-
style={{
|
|
95
|
-
marginTop: 8,
|
|
96
|
-
fontSize: 12,
|
|
97
|
-
color: isIncrease ? 'var(--success)' : 'var(--error)',
|
|
98
|
-
display: 'flex',
|
|
99
|
-
alignItems: 'center',
|
|
100
|
-
gap: 4,
|
|
101
|
-
}}
|
|
102
|
-
>
|
|
103
|
-
<span>{isIncrease ? '↑' : '↓'}</span>
|
|
104
|
-
<span>{Math.abs(changePercent).toFixed(1)}%</span>
|
|
105
|
-
<span style={{ color: 'var(--text-tertiary)' }}>from previous</span>
|
|
106
|
-
</div>
|
|
107
|
-
)}
|
|
108
|
-
</div>
|
|
109
|
-
{icon && (
|
|
110
|
-
<div
|
|
111
|
-
style={{
|
|
112
|
-
fontSize: 48,
|
|
113
|
-
color,
|
|
114
|
-
opacity: 0.2,
|
|
115
|
-
transition: 'opacity 0.3s ease',
|
|
116
|
-
}}
|
|
117
|
-
className="metric-icon"
|
|
118
|
-
>
|
|
119
|
-
{icon}
|
|
120
|
-
</div>
|
|
121
|
-
)}
|
|
122
|
-
</div>
|
|
123
|
-
</UICard>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
.hero-metric-card {
|
|
2
|
-
transition: all 0.2s ease;
|
|
3
|
-
position: relative;
|
|
4
|
-
overflow: hidden;
|
|
5
|
-
box-shadow: none;
|
|
6
|
-
height: 100%;
|
|
7
|
-
display: flex;
|
|
8
|
-
flex-direction: column;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.hero-metric-card.clickable:hover {
|
|
13
|
-
border-color: var(--border-secondary);
|
|
14
|
-
box-shadow: 0 2px 8px var(--shadow-hover);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.hero-metric-card.pulsing {
|
|
18
|
-
/* Subtle pulse - just the dot, no shadow animation */
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.hero-metric-header {
|
|
22
|
-
display: flex;
|
|
23
|
-
align-items: center;
|
|
24
|
-
gap: 8px;
|
|
25
|
-
margin-bottom: 12px;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.hero-metric-icon {
|
|
29
|
-
font-size: 20px;
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.hero-metric-title {
|
|
36
|
-
font-size: 14px;
|
|
37
|
-
font-weight: 500;
|
|
38
|
-
color: var(--text-secondary);
|
|
39
|
-
flex: 1;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.hero-metric-pulse {
|
|
43
|
-
width: 8px;
|
|
44
|
-
height: 8px;
|
|
45
|
-
border-radius: 50%;
|
|
46
|
-
animation: pulse-dot 2s infinite;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@keyframes pulse-dot {
|
|
50
|
-
0%, 100% {
|
|
51
|
-
opacity: 1;
|
|
52
|
-
transform: scale(1);
|
|
53
|
-
}
|
|
54
|
-
50% {
|
|
55
|
-
opacity: 0.5;
|
|
56
|
-
transform: scale(1.2);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.hero-metric-value {
|
|
61
|
-
font-size: 36px;
|
|
62
|
-
font-weight: 700;
|
|
63
|
-
line-height: 1.1;
|
|
64
|
-
margin-bottom: 4px;
|
|
65
|
-
font-family: var(--font-heading);
|
|
66
|
-
letter-spacing: -0.02em;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.hero-metric-subtitle {
|
|
70
|
-
font-size: 12px;
|
|
71
|
-
color: var(--text-tertiary);
|
|
72
|
-
margin-bottom: 12px;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.hero-metric-footer {
|
|
76
|
-
display: flex;
|
|
77
|
-
align-items: center;
|
|
78
|
-
justify-content: space-between;
|
|
79
|
-
gap: 12px;
|
|
80
|
-
margin-top: auto;
|
|
81
|
-
padding-top: 12px;
|
|
82
|
-
border-top: 1px solid var(--border-primary);
|
|
83
|
-
min-height: 42px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.hero-metric-sparkline {
|
|
87
|
-
flex: 1;
|
|
88
|
-
min-width: 0;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.sparkline {
|
|
92
|
-
display: block;
|
|
93
|
-
width: 100%;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.hero-metric-trend {
|
|
97
|
-
flex-shrink: 0;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.trend-indicator {
|
|
101
|
-
display: flex;
|
|
102
|
-
align-items: center;
|
|
103
|
-
gap: 4px;
|
|
104
|
-
font-size: 14px;
|
|
105
|
-
font-weight: 600;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.trend-arrow {
|
|
109
|
-
font-size: 16px;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.trend-value {
|
|
113
|
-
font-size: 12px;
|
|
114
|
-
}
|