@kb-labs/studio-app 0.6.0 → 2.8.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.
Files changed (175) hide show
  1. package/dist/index.html +1 -0
  2. package/dist/main.1d68e922.js +522 -0
  3. package/dist/main.1d68e922.js.map +1 -0
  4. package/dist/main.80b70197.css +2 -0
  5. package/dist/main.80b70197.css.map +1 -0
  6. package/dist/mf-manifest.json +241 -0
  7. package/dist/mf-stats.json +271 -0
  8. package/package.json +56 -39
  9. package/server.js +68 -0
  10. package/.env.example +0 -10
  11. package/UI-KIT-COMPONENTS.md +0 -783
  12. package/UI-KIT-PLAN.md +0 -326
  13. package/eslint-rules/no-direct-antd-import.js +0 -151
  14. package/eslint.config.ts +0 -27
  15. package/index.html +0 -17
  16. package/postcss.config.js +0 -7
  17. package/public/favicon.ico +0 -0
  18. package/rspack.config.mjs +0 -118
  19. package/src/App.tsx +0 -46
  20. package/src/components/devtools/devtools-log-feed.module.css +0 -165
  21. package/src/components/devtools/devtools-log-feed.tsx +0 -300
  22. package/src/components/devtools/devtools-panel.module.css +0 -121
  23. package/src/components/devtools/devtools-panel.tsx +0 -115
  24. package/src/components/empty-state.tsx +0 -28
  25. package/src/components/error-boundary.tsx +0 -210
  26. package/src/components/health-banner.module.css +0 -103
  27. package/src/components/health-banner.tsx +0 -134
  28. package/src/components/health-indicator.tsx +0 -12
  29. package/src/components/page-header.tsx +0 -49
  30. package/src/components/page-transition.tsx +0 -24
  31. package/src/components/role-switcher.tsx +0 -148
  32. package/src/components/run-badge.tsx +0 -13
  33. package/src/components/ui/icons.tsx +0 -509
  34. package/src/components/ui/index.ts +0 -25
  35. package/src/components/ui/kb-config-provider.tsx +0 -130
  36. package/src/components/ui/kb-header.module.css +0 -51
  37. package/src/components/ui/kb-header.tsx +0 -112
  38. package/src/components/ui/kb-notification-bell.module.css +0 -103
  39. package/src/components/ui/kb-notification-bell.tsx +0 -199
  40. package/src/components/ui/kb-page-layout.tsx +0 -66
  41. package/src/components/ui/kb-quick-search.module.css +0 -86
  42. package/src/components/ui/kb-quick-search.tsx +0 -196
  43. package/src/components/ui/kb-sidebar.module.css +0 -47
  44. package/src/components/ui/kb-sidebar.tsx +0 -196
  45. package/src/components/ui/kb-stat-card.module.css +0 -21
  46. package/src/components/ui/kb-stat-card.tsx +0 -22
  47. package/src/components/ui/kb-stat-grid.tsx +0 -22
  48. package/src/components/ui/kb-status-bar.module.css +0 -49
  49. package/src/components/ui/kb-status-bar.tsx +0 -145
  50. package/src/components/ui/kb-system-health-indicator.tsx +0 -229
  51. package/src/components/ui/kb-theme-toggle.tsx +0 -42
  52. package/src/components/ui/theme-adapter.ts +0 -812
  53. package/src/components/ui/theme-transition-overlay.tsx +0 -70
  54. package/src/components/ui/utils.ts +0 -6
  55. package/src/components/widget-error-boundary.tsx +0 -172
  56. package/src/config/env.ts +0 -5
  57. package/src/config/feature-flags.ts +0 -482
  58. package/src/config/studio.config.schema.ts +0 -88
  59. package/src/config/studio.config.ts +0 -84
  60. package/src/env.d.ts +0 -23
  61. package/src/hooks/use-command-palette.ts +0 -6
  62. package/src/hooks/use-devtools-store.ts +0 -25
  63. package/src/hooks/use-feature-flags.ts +0 -101
  64. package/src/index.css +0 -10
  65. package/src/layouts/dashboard-grid.tsx +0 -122
  66. package/src/layouts/index.ts +0 -12
  67. package/src/layouts/two-pane.tsx +0 -134
  68. package/src/main.tsx +0 -15
  69. package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +0 -257
  70. package/src/modules/analytics/components/analytics-date-range-picker.tsx +0 -121
  71. package/src/modules/analytics/pages/analytics-cache-page.tsx +0 -285
  72. package/src/modules/analytics/pages/analytics-embeddings-page.tsx +0 -274
  73. package/src/modules/analytics/pages/analytics-events-page.tsx +0 -425
  74. package/src/modules/analytics/pages/analytics-llm-page.tsx +0 -434
  75. package/src/modules/analytics/pages/analytics-overview-page.tsx +0 -336
  76. package/src/modules/analytics/pages/analytics-page.tsx +0 -20
  77. package/src/modules/analytics/pages/analytics-storage-page.tsx +0 -344
  78. package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +0 -279
  79. package/src/modules/analytics/routes/index.tsx +0 -133
  80. package/src/modules/analytics/utils/formatters.test.ts +0 -148
  81. package/src/modules/analytics/utils/formatters.ts +0 -100
  82. package/src/modules/auth/pages/login-page.module.css +0 -113
  83. package/src/modules/auth/pages/login-page.tsx +0 -80
  84. package/src/modules/auth/routes/index.tsx +0 -24
  85. package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +0 -199
  86. package/src/modules/dashboard/components/AiInsightsWidget.tsx +0 -358
  87. package/src/modules/dashboard/components/AnimatedMetricCard.css +0 -88
  88. package/src/modules/dashboard/components/AnimatedMetricCard.tsx +0 -125
  89. package/src/modules/dashboard/components/HeroMetricCard.css +0 -114
  90. package/src/modules/dashboard/components/HeroMetricCard.tsx +0 -152
  91. package/src/modules/dashboard/components/HeroMetricsWidget.tsx +0 -146
  92. package/src/modules/dashboard/components/SystemResourcesWidget.tsx +0 -197
  93. package/src/modules/dashboard/components/dashboard-empty.tsx +0 -22
  94. package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +0 -59
  95. package/src/modules/dashboard/components/dashboard-kpis.tsx +0 -13
  96. package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +0 -85
  97. package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +0 -78
  98. package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +0 -67
  99. package/src/modules/dashboard/components/dashboard-requests-widget.tsx +0 -62
  100. package/src/modules/dashboard/layouts/dashboard-layout.tsx +0 -16
  101. package/src/modules/dashboard/pages/ai-insights-page.tsx +0 -563
  102. package/src/modules/dashboard/pages/dashboard-animations.css +0 -317
  103. package/src/modules/dashboard/pages/dashboard-page.tsx +0 -13
  104. package/src/modules/dashboard/routes/index.tsx +0 -70
  105. package/src/modules/observability/pages/devkit-page.tsx +0 -263
  106. package/src/modules/observability/pages/incident-detail-page.tsx +0 -571
  107. package/src/modules/observability/pages/incidents-page.tsx +0 -370
  108. package/src/modules/observability/pages/log-detail-page.tsx +0 -508
  109. package/src/modules/observability/pages/logs-page.tsx +0 -1129
  110. package/src/modules/observability/pages/prometheus-metrics-page.tsx +0 -486
  111. package/src/modules/observability/pages/state-broker-page.tsx +0 -229
  112. package/src/modules/observability/pages/system-events-page.tsx +0 -255
  113. package/src/modules/observability/routes/index.tsx +0 -133
  114. package/src/modules/plugins/components/marketplace-card.tsx +0 -153
  115. package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +0 -374
  116. package/src/modules/plugins/components/plugin-card.tsx +0 -136
  117. package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +0 -101
  118. package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +0 -101
  119. package/src/modules/plugins/components/plugin-detail/index.ts +0 -7
  120. package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +0 -64
  121. package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +0 -30
  122. package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +0 -68
  123. package/src/modules/plugins/components/plugin-detail/permissions/index.ts +0 -6
  124. package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +0 -43
  125. package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +0 -242
  126. package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +0 -96
  127. package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +0 -55
  128. package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +0 -47
  129. package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +0 -169
  130. package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +0 -159
  131. package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +0 -40
  132. package/src/modules/plugins/components/plugin-grid.tsx +0 -20
  133. package/src/modules/plugins/components/plugin-table.tsx +0 -178
  134. package/src/modules/plugins/pages/marketplace-page.tsx +0 -200
  135. package/src/modules/plugins/pages/plugin-detail-page.tsx +0 -1145
  136. package/src/modules/plugins/routes/index.tsx +0 -48
  137. package/src/modules/plugins/utils/index.ts +0 -1
  138. package/src/modules/plugins/utils/schema-helpers.ts +0 -21
  139. package/src/modules/settings/components/api-routes-viewer.tsx +0 -415
  140. package/src/modules/settings/components/appearance-settings.tsx +0 -200
  141. package/src/modules/settings/components/configuration-settings.tsx +0 -321
  142. package/src/modules/settings/components/data-privacy-settings.tsx +0 -111
  143. package/src/modules/settings/components/experimental-settings.tsx +0 -243
  144. package/src/modules/settings/components/navigation-settings.tsx +0 -198
  145. package/src/modules/settings/components/notifications-settings.tsx +0 -40
  146. package/src/modules/settings/pages/settings-page.tsx +0 -298
  147. package/src/modules/settings/routes/index.tsx +0 -47
  148. package/src/pages/README-assistant.md +0 -91
  149. package/src/pages/login-page.tsx +0 -177
  150. package/src/pages/not-found-page.tsx +0 -85
  151. package/src/providers/auth-provider.tsx +0 -40
  152. package/src/providers/command-palette-provider.tsx +0 -193
  153. package/src/providers/data-sources-provider.tsx +0 -27
  154. package/src/providers/devtools-provider.tsx +0 -72
  155. package/src/providers/navigation-items-provider.tsx +0 -38
  156. package/src/providers/registry-v2-provider.tsx +0 -93
  157. package/src/providers/settings-provider.tsx +0 -158
  158. package/src/router.tsx +0 -302
  159. package/src/routes/helpers.tsx +0 -25
  160. package/src/routes/plugin-page-v2.tsx +0 -52
  161. package/src/routes/types.ts +0 -18
  162. package/src/styles/antd-overrides.css +0 -211
  163. package/src/styles/base.css +0 -190
  164. package/src/styles/settings.css +0 -75
  165. package/src/styles/variables.css +0 -94
  166. package/src/utils/analytics.ts +0 -183
  167. package/src/utils/apply-navigation-categories.ts +0 -83
  168. package/src/utils/commands.ts +0 -252
  169. package/src/utils/export.ts +0 -174
  170. package/src/utils/format-date.ts +0 -166
  171. package/src/utils/logger.ts +0 -110
  172. package/tailwind.config.ts +0 -13
  173. package/tsconfig.json +0 -13
  174. package/vitest-setup.ts +0 -1
  175. /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
- }