@kb-labs/studio-app 0.6.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 (168) hide show
  1. package/.env.example +10 -0
  2. package/README.md +74 -0
  3. package/UI-KIT-COMPONENTS.md +783 -0
  4. package/UI-KIT-PLAN.md +326 -0
  5. package/eslint-rules/no-direct-antd-import.js +151 -0
  6. package/eslint.config.ts +27 -0
  7. package/index.html +17 -0
  8. package/package.json +72 -0
  9. package/postcss.config.js +7 -0
  10. package/public/favicon.ico +0 -0
  11. package/public/favicon.svg +7 -0
  12. package/rspack.config.mjs +118 -0
  13. package/src/App.tsx +46 -0
  14. package/src/components/devtools/devtools-log-feed.module.css +165 -0
  15. package/src/components/devtools/devtools-log-feed.tsx +300 -0
  16. package/src/components/devtools/devtools-panel.module.css +121 -0
  17. package/src/components/devtools/devtools-panel.tsx +115 -0
  18. package/src/components/empty-state.tsx +28 -0
  19. package/src/components/error-boundary.tsx +210 -0
  20. package/src/components/health-banner.module.css +103 -0
  21. package/src/components/health-banner.tsx +134 -0
  22. package/src/components/health-indicator.tsx +12 -0
  23. package/src/components/page-header.tsx +49 -0
  24. package/src/components/page-transition.tsx +24 -0
  25. package/src/components/role-switcher.tsx +148 -0
  26. package/src/components/run-badge.tsx +13 -0
  27. package/src/components/ui/icons.tsx +509 -0
  28. package/src/components/ui/index.ts +25 -0
  29. package/src/components/ui/kb-config-provider.tsx +130 -0
  30. package/src/components/ui/kb-header.module.css +51 -0
  31. package/src/components/ui/kb-header.tsx +112 -0
  32. package/src/components/ui/kb-notification-bell.module.css +103 -0
  33. package/src/components/ui/kb-notification-bell.tsx +199 -0
  34. package/src/components/ui/kb-page-layout.tsx +66 -0
  35. package/src/components/ui/kb-quick-search.module.css +86 -0
  36. package/src/components/ui/kb-quick-search.tsx +196 -0
  37. package/src/components/ui/kb-sidebar.module.css +47 -0
  38. package/src/components/ui/kb-sidebar.tsx +196 -0
  39. package/src/components/ui/kb-stat-card.module.css +21 -0
  40. package/src/components/ui/kb-stat-card.tsx +22 -0
  41. package/src/components/ui/kb-stat-grid.tsx +22 -0
  42. package/src/components/ui/kb-status-bar.module.css +49 -0
  43. package/src/components/ui/kb-status-bar.tsx +145 -0
  44. package/src/components/ui/kb-system-health-indicator.tsx +229 -0
  45. package/src/components/ui/kb-theme-toggle.tsx +42 -0
  46. package/src/components/ui/theme-adapter.ts +812 -0
  47. package/src/components/ui/theme-transition-overlay.tsx +70 -0
  48. package/src/components/ui/utils.ts +6 -0
  49. package/src/components/widget-error-boundary.tsx +172 -0
  50. package/src/config/env.ts +5 -0
  51. package/src/config/feature-flags.ts +482 -0
  52. package/src/config/studio.config.schema.ts +88 -0
  53. package/src/config/studio.config.ts +84 -0
  54. package/src/env.d.ts +23 -0
  55. package/src/hooks/use-command-palette.ts +6 -0
  56. package/src/hooks/use-devtools-store.ts +25 -0
  57. package/src/hooks/use-feature-flags.ts +101 -0
  58. package/src/index.css +10 -0
  59. package/src/layouts/dashboard-grid.tsx +122 -0
  60. package/src/layouts/index.ts +12 -0
  61. package/src/layouts/two-pane.tsx +134 -0
  62. package/src/main.tsx +15 -0
  63. package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +257 -0
  64. package/src/modules/analytics/components/analytics-date-range-picker.tsx +121 -0
  65. package/src/modules/analytics/pages/analytics-cache-page.tsx +285 -0
  66. package/src/modules/analytics/pages/analytics-embeddings-page.tsx +274 -0
  67. package/src/modules/analytics/pages/analytics-events-page.tsx +425 -0
  68. package/src/modules/analytics/pages/analytics-llm-page.tsx +434 -0
  69. package/src/modules/analytics/pages/analytics-overview-page.tsx +336 -0
  70. package/src/modules/analytics/pages/analytics-page.tsx +20 -0
  71. package/src/modules/analytics/pages/analytics-storage-page.tsx +344 -0
  72. package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +279 -0
  73. package/src/modules/analytics/routes/index.tsx +133 -0
  74. package/src/modules/analytics/utils/formatters.test.ts +148 -0
  75. package/src/modules/analytics/utils/formatters.ts +100 -0
  76. package/src/modules/auth/pages/login-page.module.css +113 -0
  77. package/src/modules/auth/pages/login-page.tsx +80 -0
  78. package/src/modules/auth/routes/index.tsx +24 -0
  79. package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +199 -0
  80. package/src/modules/dashboard/components/AiInsightsWidget.tsx +358 -0
  81. package/src/modules/dashboard/components/AnimatedMetricCard.css +88 -0
  82. package/src/modules/dashboard/components/AnimatedMetricCard.tsx +125 -0
  83. package/src/modules/dashboard/components/HeroMetricCard.css +114 -0
  84. package/src/modules/dashboard/components/HeroMetricCard.tsx +152 -0
  85. package/src/modules/dashboard/components/HeroMetricsWidget.tsx +146 -0
  86. package/src/modules/dashboard/components/SystemResourcesWidget.tsx +197 -0
  87. package/src/modules/dashboard/components/dashboard-empty.tsx +22 -0
  88. package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +59 -0
  89. package/src/modules/dashboard/components/dashboard-kpis.tsx +13 -0
  90. package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +85 -0
  91. package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +78 -0
  92. package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +67 -0
  93. package/src/modules/dashboard/components/dashboard-requests-widget.tsx +62 -0
  94. package/src/modules/dashboard/layouts/dashboard-layout.tsx +16 -0
  95. package/src/modules/dashboard/pages/ai-insights-page.tsx +563 -0
  96. package/src/modules/dashboard/pages/dashboard-animations.css +317 -0
  97. package/src/modules/dashboard/pages/dashboard-page.tsx +13 -0
  98. package/src/modules/dashboard/routes/index.tsx +70 -0
  99. package/src/modules/observability/pages/devkit-page.tsx +263 -0
  100. package/src/modules/observability/pages/incident-detail-page.tsx +571 -0
  101. package/src/modules/observability/pages/incidents-page.tsx +370 -0
  102. package/src/modules/observability/pages/log-detail-page.tsx +508 -0
  103. package/src/modules/observability/pages/logs-page.tsx +1129 -0
  104. package/src/modules/observability/pages/prometheus-metrics-page.tsx +486 -0
  105. package/src/modules/observability/pages/state-broker-page.tsx +229 -0
  106. package/src/modules/observability/pages/system-events-page.tsx +255 -0
  107. package/src/modules/observability/routes/index.tsx +133 -0
  108. package/src/modules/plugins/components/marketplace-card.tsx +153 -0
  109. package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +374 -0
  110. package/src/modules/plugins/components/plugin-card.tsx +136 -0
  111. package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +101 -0
  112. package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +101 -0
  113. package/src/modules/plugins/components/plugin-detail/index.ts +7 -0
  114. package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +64 -0
  115. package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +30 -0
  116. package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +68 -0
  117. package/src/modules/plugins/components/plugin-detail/permissions/index.ts +6 -0
  118. package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +43 -0
  119. package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +242 -0
  120. package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +96 -0
  121. package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +55 -0
  122. package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +47 -0
  123. package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +169 -0
  124. package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +159 -0
  125. package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +40 -0
  126. package/src/modules/plugins/components/plugin-grid.tsx +20 -0
  127. package/src/modules/plugins/components/plugin-table.tsx +178 -0
  128. package/src/modules/plugins/pages/marketplace-page.tsx +200 -0
  129. package/src/modules/plugins/pages/plugin-detail-page.tsx +1145 -0
  130. package/src/modules/plugins/routes/index.tsx +48 -0
  131. package/src/modules/plugins/utils/index.ts +1 -0
  132. package/src/modules/plugins/utils/schema-helpers.ts +21 -0
  133. package/src/modules/settings/components/api-routes-viewer.tsx +415 -0
  134. package/src/modules/settings/components/appearance-settings.tsx +200 -0
  135. package/src/modules/settings/components/configuration-settings.tsx +321 -0
  136. package/src/modules/settings/components/data-privacy-settings.tsx +111 -0
  137. package/src/modules/settings/components/experimental-settings.tsx +243 -0
  138. package/src/modules/settings/components/navigation-settings.tsx +198 -0
  139. package/src/modules/settings/components/notifications-settings.tsx +40 -0
  140. package/src/modules/settings/pages/settings-page.tsx +298 -0
  141. package/src/modules/settings/routes/index.tsx +47 -0
  142. package/src/pages/README-assistant.md +91 -0
  143. package/src/pages/login-page.tsx +177 -0
  144. package/src/pages/not-found-page.tsx +85 -0
  145. package/src/providers/auth-provider.tsx +40 -0
  146. package/src/providers/command-palette-provider.tsx +193 -0
  147. package/src/providers/data-sources-provider.tsx +27 -0
  148. package/src/providers/devtools-provider.tsx +72 -0
  149. package/src/providers/navigation-items-provider.tsx +38 -0
  150. package/src/providers/registry-v2-provider.tsx +93 -0
  151. package/src/providers/settings-provider.tsx +158 -0
  152. package/src/router.tsx +302 -0
  153. package/src/routes/helpers.tsx +25 -0
  154. package/src/routes/plugin-page-v2.tsx +52 -0
  155. package/src/routes/types.ts +18 -0
  156. package/src/styles/antd-overrides.css +211 -0
  157. package/src/styles/base.css +190 -0
  158. package/src/styles/settings.css +75 -0
  159. package/src/styles/variables.css +94 -0
  160. package/src/utils/analytics.ts +183 -0
  161. package/src/utils/apply-navigation-categories.ts +83 -0
  162. package/src/utils/commands.ts +252 -0
  163. package/src/utils/export.ts +174 -0
  164. package/src/utils/format-date.ts +166 -0
  165. package/src/utils/logger.ts +110 -0
  166. package/tailwind.config.ts +13 -0
  167. package/tsconfig.json +13 -0
  168. package/vitest-setup.ts +1 -0
@@ -0,0 +1,321 @@
1
+ /**
2
+ * @module studio/modules/settings/components/configuration-settings
3
+ * Platform configuration settings component
4
+ */
5
+
6
+ import * as React from 'react';
7
+ import {
8
+ UISpace,
9
+ UITypographyText,
10
+ UITitle,
11
+ UITypographyParagraph,
12
+ UITag,
13
+ UIButton,
14
+ UIMessage,
15
+ UIAlert,
16
+ UIAccordion,
17
+ UICard,
18
+ UIDescriptions,
19
+ UIDescriptionsItem,
20
+ UIIcon,
21
+ } from '@kb-labs/studio-ui-kit';
22
+ import { useDataSources } from '@/providers/data-sources-provider';
23
+ import { usePlatformConfig } from '@kb-labs/studio-data-client';
24
+ import { UISkeleton, UIStack } from '@kb-labs/studio-ui-kit';
25
+
26
+ export function ConfigurationSettings() {
27
+ const sources = useDataSources();
28
+ const { data, isLoading, error } = usePlatformConfig(sources.platform);
29
+ const [copiedKey, setCopiedKey] = React.useState<string | null>(null);
30
+
31
+ const handleCopy = (text: string, key: string) => {
32
+ navigator.clipboard.writeText(text);
33
+ setCopiedKey(key);
34
+ UIMessage.success('Copied to clipboard');
35
+ setTimeout(() => setCopiedKey(null), 2000);
36
+ };
37
+
38
+ if (isLoading) {
39
+ return (
40
+ <UIStack>
41
+ <UISkeleton active lines={5} />
42
+ </UIStack>
43
+ );
44
+ }
45
+
46
+ if (error) {
47
+ return (
48
+ <UIAlert
49
+ message="Failed to load platform configuration"
50
+ description={
51
+ <UISpace direction="vertical" size="small">
52
+ <UITypographyText>{error instanceof Error ? error.message : 'Unknown error'}</UITypographyText>
53
+ <UITypographyText type="secondary" style={{ fontSize: '0.875rem' }}>
54
+ Make sure the REST API server is running and accessible.
55
+ </UITypographyText>
56
+ </UISpace>
57
+ }
58
+ variant="error"
59
+ showIcon
60
+ />
61
+ );
62
+ }
63
+
64
+ if (!data) {
65
+ return (
66
+ <div>
67
+ <UITypographyParagraph type="secondary">No platform configuration available</UITypographyParagraph>
68
+ </div>
69
+ );
70
+ }
71
+
72
+ const adaptersArray = Object.entries(data.adapters || {});
73
+ const hasRedacted = data.redacted && data.redacted.length > 0;
74
+
75
+ // Helper to count adapters including nested ones
76
+ const countAdapters = (items: Array<[string, unknown]>): number => {
77
+ return items.reduce((count, [, packageName]) => {
78
+ // If it's an object (nested adapters), count its children
79
+ if (typeof packageName === 'object' && packageName !== null && !Array.isArray(packageName)) {
80
+ return count + Object.keys(packageName).length;
81
+ }
82
+ // Otherwise it's a single adapter
83
+ return count + 1;
84
+ }, 0);
85
+ };
86
+
87
+ // Count total adapters (including nested)
88
+ const totalAdaptersCount = countAdapters(adaptersArray);
89
+
90
+ // Count active adapters (non-null packages, including nested)
91
+ const activeAdaptersCount = adaptersArray.reduce((count, [, packageName]) => {
92
+ if (typeof packageName === 'object' && packageName !== null && !Array.isArray(packageName)) {
93
+ // Count non-null nested adapters
94
+ return count + Object.values(packageName).filter(pkg => pkg !== null).length;
95
+ }
96
+ return count + (packageName !== null ? 1 : 0);
97
+ }, 0);
98
+
99
+ // Adapter categories
100
+ const categories = [
101
+ {
102
+ name: 'AI & Intelligence',
103
+ icon: <UIIcon name="BulbOutlined" />,
104
+ color: 'purple',
105
+ adapters: ['llm', 'embeddings', 'vectorStore'],
106
+ },
107
+ {
108
+ name: 'Database',
109
+ icon: <UIIcon name="DatabaseOutlined" />,
110
+ color: 'blue',
111
+ adapters: ['database', 'db', 'documentDb'],
112
+ },
113
+ {
114
+ name: 'Infrastructure',
115
+ icon: <UIIcon name="CloudOutlined" />,
116
+ color: 'green',
117
+ adapters: ['storage', 'cache'],
118
+ },
119
+ {
120
+ name: 'Observability',
121
+ icon: <UIIcon name="ThunderboltOutlined" />,
122
+ color: 'orange',
123
+ adapters: ['logger', 'analytics', 'logRingBuffer', 'logPersistence'],
124
+ },
125
+ ];
126
+
127
+ // Group adapters by category
128
+ const groupedAdapters = categories.map((category) => {
129
+ const items = adaptersArray.filter(([name]) => category.adapters.includes(name));
130
+ return {
131
+ ...category,
132
+ items,
133
+ count: countAdapters(items),
134
+ };
135
+ });
136
+
137
+ return (
138
+ <UISpace direction="vertical" size="large" style={{ width: '100%' }}>
139
+ {/* System Information */}
140
+ <UICard size="small" title="System Information">
141
+ <UIDescriptions column={{ xs: 1, sm: 2 }} size="small">
142
+ <UIDescriptionsItem label="Execution Mode">
143
+ <UITag color="blue" icon={<UIIcon name="RocketOutlined" />}>
144
+ {data.execution.mode}
145
+ </UITag>
146
+ </UIDescriptionsItem>
147
+ <UIDescriptionsItem label="Total Adapters">
148
+ <UISpace size={4}>
149
+ <UITypographyText>{totalAdaptersCount}</UITypographyText>
150
+ <UITypographyText type="secondary">
151
+ ({activeAdaptersCount} active)
152
+ </UITypographyText>
153
+ </UISpace>
154
+ </UIDescriptionsItem>
155
+ </UIDescriptions>
156
+ </UICard>
157
+
158
+ {/* Platform Adapters by Category */}
159
+ <div>
160
+ <UITitle level={4}>Platform Adapters</UITitle>
161
+ <UITypographyParagraph type="secondary">
162
+ Adapters are organized by category for better clarity.
163
+ </UITypographyParagraph>
164
+
165
+ <UISpace direction="vertical" size="middle" style={{ width: '100%' }}>
166
+ {groupedAdapters.map((category) => (
167
+ <UICard
168
+ key={category.name}
169
+ size="small"
170
+ title={
171
+ <UISpace>
172
+ <span style={{ fontSize: '1.1rem', color: `var(--ant-${category.color}-6)` }}>
173
+ {category.icon}
174
+ </span>
175
+ <UITypographyText strong>{category.name}</UITypographyText>
176
+ <UITag color={category.color}>{category.count}</UITag>
177
+ </UISpace>
178
+ }
179
+ >
180
+ <UISpace direction="vertical" size="small" style={{ width: '100%' }}>
181
+ {category.items.map(([name, packageName]) => {
182
+ // Helper to render a single adapter item
183
+ const renderAdapterItem = (itemName: string, itemPackage: string, itemKey: string) => {
184
+ const isEnabled = itemPackage !== null;
185
+ return (
186
+ <div
187
+ key={itemKey}
188
+ style={{
189
+ display: 'flex',
190
+ alignItems: 'center',
191
+ justifyContent: 'space-between',
192
+ padding: '8px 12px',
193
+ background: 'var(--kb-bg-secondary)',
194
+ borderRadius: 4,
195
+ borderLeft: isEnabled ? `3px solid var(--ant-${category.color}-6)` : '3px solid var(--ant-border-color)',
196
+ }}
197
+ >
198
+ <UISpace style={{ flex: 1, minWidth: 0 }}>
199
+ <UITypographyText strong style={{ minWidth: 100 }}>{itemName}</UITypographyText>
200
+ <UITypographyText
201
+ code
202
+ style={{
203
+ fontSize: '0.85rem',
204
+ overflow: 'hidden',
205
+ textOverflow: 'ellipsis',
206
+ whiteSpace: 'nowrap',
207
+ }}
208
+ >
209
+ {itemPackage || 'null'}
210
+ </UITypographyText>
211
+ </UISpace>
212
+ <UISpace>
213
+ {isEnabled ? (
214
+ <>
215
+ <UIIcon name="CheckCircleOutlined" style={{ color: 'var(--ant-success-color)' }} />
216
+ <UIButton
217
+ size="small"
218
+ variant="text"
219
+ icon={copiedKey === itemKey ? <UIIcon name="CheckOutlined" /> : <UIIcon name="CopyOutlined" />}
220
+ onClick={() => handleCopy(itemPackage, itemKey)}
221
+ >
222
+ {copiedKey === itemKey ? 'Copied' : 'Copy'}
223
+ </UIButton>
224
+ </>
225
+ ) : (
226
+ <UIIcon name="CloseCircleOutlined" style={{ color: 'var(--ant-text-secondary)' }} />
227
+ )}
228
+ </UISpace>
229
+ </div>
230
+ );
231
+ };
232
+
233
+ // Handle nested adapters (object with sub-adapters)
234
+ if (typeof packageName === 'object' && packageName !== null && !Array.isArray(packageName)) {
235
+ return (
236
+ <UISpace key={name} direction="vertical" size="small" style={{ width: '100%' }}>
237
+ {Object.entries(packageName as Record<string, string>).map(([subName, subPackage]) =>
238
+ renderAdapterItem(subName, subPackage, `${name}.${subName}`)
239
+ )}
240
+ </UISpace>
241
+ );
242
+ }
243
+
244
+ // Regular adapter (string package name)
245
+ return renderAdapterItem(name, packageName as string, name);
246
+ })}
247
+ </UISpace>
248
+ </UICard>
249
+ ))}
250
+ </UISpace>
251
+ </div>
252
+
253
+ {/* Adapter Configuration */}
254
+ {data.adapterOptions && Object.keys(data.adapterOptions).length > 0 && (
255
+ <div>
256
+ <UITitle level={4}>Adapter Configuration</UITitle>
257
+ <UITypographyParagraph type="secondary">
258
+ Detailed configuration options for each adapter.
259
+ {hasRedacted && (
260
+ <UITypographyText type="warning" style={{ marginLeft: 8 }}>
261
+ Some sensitive values have been redacted for security.
262
+ </UITypographyText>
263
+ )}
264
+ </UITypographyParagraph>
265
+
266
+ <UIAccordion
267
+ accordion
268
+ items={Object.entries(data.adapterOptions).map(([adapterName, options]) => {
269
+ // Find category for this adapter
270
+ const category = categories.find((cat) => cat.adapters.includes(adapterName));
271
+ const _categoryColor = category?.color || 'blue';
272
+
273
+ const optionsCount = options ? Object.keys(options as object).length : 0;
274
+ return {
275
+ key: adapterName,
276
+ label: options ? `${adapterName} (${optionsCount} option${optionsCount !== 1 ? 's' : ''})` : adapterName,
277
+ children: options ? (
278
+ <pre
279
+ style={{
280
+ background: 'var(--kb-bg-secondary)',
281
+ padding: 12,
282
+ borderRadius: 4,
283
+ fontSize: '0.85rem',
284
+ overflow: 'auto',
285
+ margin: 0,
286
+ }}
287
+ >
288
+ {JSON.stringify(options, null, 2)}
289
+ </pre>
290
+ ) : (
291
+ <UITypographyText type="secondary">No options configured</UITypographyText>
292
+ ),
293
+ };
294
+ })}
295
+ />
296
+ </div>
297
+ )}
298
+
299
+ {/* Redacted Fields Info */}
300
+ {hasRedacted && (
301
+ <UIAlert
302
+ variant="warning"
303
+ showIcon
304
+ message="Security Notice"
305
+ description={
306
+ <UISpace direction="vertical" size="small">
307
+ <UITypographyText>The following sensitive fields were redacted from the configuration:</UITypographyText>
308
+ <UISpace wrap>
309
+ {data.redacted.map((field) => (
310
+ <UITag key={field} color="orange">
311
+ {field}
312
+ </UITag>
313
+ ))}
314
+ </UISpace>
315
+ </UISpace>
316
+ }
317
+ />
318
+ )}
319
+ </UISpace>
320
+ );
321
+ }
@@ -0,0 +1,111 @@
1
+ import * as React from 'react';
2
+ import {
3
+ UIButton,
4
+ UISpace,
5
+ UITypographyParagraph,
6
+ UITitle,
7
+ UIMessage,
8
+ UIUpload,
9
+ UIIcon,
10
+ } from '@kb-labs/studio-ui-kit';
11
+ import type { UploadProps } from 'antd/es/upload';
12
+ import { useSettings } from '@/providers/settings-provider';
13
+
14
+ export function DataPrivacySettings() {
15
+ const { exportSettings, importSettings, resetSettings } = useSettings();
16
+
17
+ const handleExport = () => {
18
+ try {
19
+ exportSettings();
20
+ UIMessage.success('Settings exported successfully');
21
+ } catch (error) {
22
+ UIMessage.error(`Failed to export settings: ${error instanceof Error ? error.message : String(error)}`);
23
+ }
24
+ };
25
+
26
+ const handleImport: UploadProps['beforeUpload'] = (file) => {
27
+ const reader = new FileReader();
28
+ reader.onload = (e) => {
29
+ try {
30
+ const content = e.target?.result as string;
31
+ importSettings(content);
32
+ UIMessage.success('Settings imported successfully');
33
+ } catch (error) {
34
+ UIMessage.error(`Failed to import settings: ${error instanceof Error ? error.message : String(error)}`);
35
+ }
36
+ };
37
+ reader.readAsText(file);
38
+ return false; // Prevent auto upload
39
+ };
40
+
41
+ const handleClearData = () => {
42
+ if (window.confirm('Are you sure you want to clear all local data? This will reset all settings to defaults.')) {
43
+ try {
44
+ resetSettings();
45
+ localStorage.clear();
46
+ UIMessage.success('All local data cleared successfully');
47
+ } catch (error) {
48
+ UIMessage.error(`Failed to clear data: ${error instanceof Error ? error.message : String(error)}`);
49
+ }
50
+ }
51
+ };
52
+
53
+ return (
54
+ <UISpace direction="vertical" size="large" style={{ width: '100%' }}>
55
+ {/* Export Settings */}
56
+ <div>
57
+ <UITitle level={5} style={{ marginBottom: 8 }}>
58
+ Export Settings
59
+ </UITitle>
60
+ <UITypographyParagraph type="secondary" style={{ marginBottom: 12 }}>
61
+ Download your current settings as a JSON file. You can use this to backup your preferences
62
+ or transfer them to another device.
63
+ </UITypographyParagraph>
64
+ <UIButton
65
+ icon={<UIIcon name="DownloadOutlined" />}
66
+ onClick={handleExport}
67
+ >
68
+ Export Settings
69
+ </UIButton>
70
+ </div>
71
+
72
+ {/* Import Settings */}
73
+ <div>
74
+ <UITitle level={5} style={{ marginBottom: 8 }}>
75
+ Import Settings
76
+ </UITitle>
77
+ <UITypographyParagraph type="secondary" style={{ marginBottom: 12 }}>
78
+ Restore your settings from a previously exported JSON file. This will overwrite your current settings.
79
+ </UITypographyParagraph>
80
+ <UIUpload
81
+ accept=".json"
82
+ showUploadList={false}
83
+ beforeUpload={handleImport}
84
+ >
85
+ <UIButton icon={<UIIcon name="UploadOutlined" />}>
86
+ Import Settings
87
+ </UIButton>
88
+ </UIUpload>
89
+ </div>
90
+
91
+ {/* Clear Local Data */}
92
+ <div>
93
+ <UITitle level={5} style={{ marginBottom: 8 }}>
94
+ Clear Local Data
95
+ </UITitle>
96
+ <UITypographyParagraph type="secondary" style={{ marginBottom: 12 }}>
97
+ Clear all locally stored data including settings, cache, and preferences.
98
+ This will reset the application to its default state.
99
+ </UITypographyParagraph>
100
+ <UIButton
101
+ icon={<UIIcon name="DeleteOutlined" />}
102
+ onClick={handleClearData}
103
+ variant="primary"
104
+ danger
105
+ >
106
+ Clear All Data
107
+ </UIButton>
108
+ </div>
109
+ </UISpace>
110
+ );
111
+ }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * @module @kb-labs/studio-app/modules/settings/components/experimental-settings
3
+ * Experimental features settings
4
+ */
5
+
6
+ import * as React from 'react';
7
+ import {
8
+ UISwitch,
9
+ UISpace,
10
+ UITypographyText,
11
+ UITypographyParagraph,
12
+ UITitle,
13
+ UIAlert,
14
+ UITag,
15
+ UITooltip,
16
+ UIAccordion,
17
+ UIIcon,
18
+ } from '@kb-labs/studio-ui-kit';
19
+ import { useFeatureFlags } from '@/hooks/use-feature-flags';
20
+ import {
21
+ FEATURE_FLAGS,
22
+ FEATURE_GROUPS,
23
+ getFeaturesByGroup,
24
+ type FeatureGroup,
25
+ type FeatureStatus,
26
+ type FeatureRisk,
27
+ } from '@/config/feature-flags';
28
+
29
+ const _GROUP_ICONS: Record<FeatureGroup, React.ReactNode> = {
30
+ 'ui-ux': <UIIcon name="BgColorsOutlined" />,
31
+ performance: <UIIcon name="ThunderboltOutlined" />,
32
+ ai: <UIIcon name="RobotOutlined" />,
33
+ data: <UIIcon name="DatabaseOutlined" />,
34
+ };
35
+
36
+ const STATUS_COLORS: Record<FeatureStatus, string> = {
37
+ alpha: 'red',
38
+ beta: 'orange',
39
+ stable: 'green',
40
+ deprecated: 'default',
41
+ };
42
+
43
+ const RISK_COLORS: Record<FeatureRisk, string> = {
44
+ low: 'success',
45
+ medium: 'warning',
46
+ high: 'error',
47
+ };
48
+
49
+ const RISK_ICONS: Record<FeatureRisk, React.ReactNode> = {
50
+ low: <UIIcon name="InfoCircleOutlined" />,
51
+ medium: <UIIcon name="WarningOutlined" />,
52
+ high: <UIIcon name="WarningOutlined" />,
53
+ };
54
+
55
+ function isNewFeature(addedAt?: string): boolean {
56
+ if (!addedAt) {return false;}
57
+ const added = new Date(addedAt);
58
+ const now = new Date();
59
+ const daysDiff = (now.getTime() - added.getTime()) / (1000 * 60 * 60 * 24);
60
+ return daysDiff <= 7; // Consider "new" if added within last 7 days
61
+ }
62
+
63
+ export function ExperimentalSettings() {
64
+ const { isEnabled, toggleFeature, enabledFeatures, areDependenciesMet } = useFeatureFlags();
65
+ const groupedFeatures = React.useMemo(() => getFeaturesByGroup(), []);
66
+
67
+ const enabledCount = enabledFeatures.length;
68
+ const totalCount = Object.keys(FEATURE_FLAGS).length;
69
+
70
+ return (
71
+ <UISpace direction="vertical" size="large" style={{ width: '100%' }}>
72
+ {/* Header */}
73
+ <div>
74
+ <UITitle level={4} style={{ marginBottom: 8 }}>
75
+ <UIIcon name="ExperimentOutlined" style={{ marginRight: 8 }} />
76
+ Experimental Features
77
+ </UITitle>
78
+ <UITypographyParagraph type="secondary">
79
+ Enable experimental features to test new functionality before it's generally available.
80
+ These features are still in development and may have bugs or incomplete functionality.
81
+ </UITypographyParagraph>
82
+ <UITypographyParagraph type="secondary" style={{ marginTop: 0 }}>
83
+ <strong>{enabledCount}</strong> of <strong>{totalCount}</strong> features enabled
84
+ </UITypographyParagraph>
85
+ </div>
86
+
87
+ {/* Warning Alert */}
88
+ <UIAlert
89
+ message="Experimental Features Notice"
90
+ description="Features marked as Alpha or Beta are actively being developed and may change or be removed without notice. High-risk features may affect application stability."
91
+ variant="warning"
92
+ showIcon
93
+ icon={<UIIcon name="WarningOutlined" />}
94
+ />
95
+
96
+ {/* Feature Groups */}
97
+ <UIAccordion
98
+ defaultActiveKey={['ui-ux', 'performance', 'ai', 'data']}
99
+ style={{ backgroundColor: 'transparent' }}
100
+ items={(Object.keys(groupedFeatures) as FeatureGroup[]).map((groupId) => {
101
+ const group = FEATURE_GROUPS[groupId];
102
+ const features = groupedFeatures[groupId];
103
+ const enabledInGroup = features.filter((f) => isEnabled(f.id)).length;
104
+
105
+ return {
106
+ key: groupId,
107
+ label: `${group.name} (${enabledInGroup}/${features.length})`,
108
+ style: { marginBottom: 8 },
109
+ children: (
110
+ <UISpace direction="vertical" size="middle" style={{ width: '100%' }}>
111
+ {features.map((feature) => {
112
+ const enabled = isEnabled(feature.id);
113
+ const depsNotMet = !areDependenciesMet(feature.id);
114
+ const isNew = isNewFeature(feature.addedAt);
115
+
116
+ return (
117
+ <div
118
+ key={feature.id}
119
+ style={{
120
+ padding: '12px 16px',
121
+ backgroundColor: enabled ? 'var(--accent-subtle)' : 'var(--bg-tertiary)',
122
+ borderRadius: 8,
123
+ border: `1px solid ${enabled ? 'var(--link)' : 'var(--border-secondary)'}`,
124
+ transition: 'all 200ms ease-in-out',
125
+ }}
126
+ >
127
+ <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
128
+ {/* Left: Switch */}
129
+ <div style={{ paddingTop: 2 }}>
130
+ <UISwitch
131
+ checked={enabled}
132
+ onChange={() => toggleFeature(feature.id)}
133
+ disabled={depsNotMet && !enabled}
134
+ />
135
+ </div>
136
+
137
+ {/* Middle: Content */}
138
+ <div style={{ flex: 1 }}>
139
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
140
+ <UITypographyText strong style={{ fontSize: 14 }}>
141
+ {feature.name}
142
+ </UITypographyText>
143
+ {isNew && <UITag color="cyan">New</UITag>}
144
+ <UITag color={STATUS_COLORS[feature.status]}>{feature.status.toUpperCase()}</UITag>
145
+ </div>
146
+
147
+ <UITypographyParagraph
148
+ type="secondary"
149
+ style={{ fontSize: 13, marginBottom: 8, marginTop: 0 }}
150
+ >
151
+ {feature.description}
152
+ </UITypographyParagraph>
153
+
154
+ {/* Details Tooltip */}
155
+ {feature.details && (
156
+ <UITooltip title={feature.details} placement="bottomLeft">
157
+ <UITypographyText
158
+ type="secondary"
159
+ style={{
160
+ fontSize: 12,
161
+ cursor: 'help',
162
+ textDecoration: 'underline',
163
+ textDecorationStyle: 'dotted',
164
+ }}
165
+ >
166
+ Learn more
167
+ </UITypographyText>
168
+ </UITooltip>
169
+ )}
170
+
171
+ {/* Dependencies warning */}
172
+ {depsNotMet && !enabled && feature.dependencies && (
173
+ <div style={{ marginTop: 8 }}>
174
+ <UIAlert
175
+ message={`Requires: ${feature.dependencies.map((d) => FEATURE_FLAGS[d].name).join(', ')}`}
176
+ variant="info"
177
+ showIcon
178
+ icon={<UIIcon name="InfoCircleOutlined" />}
179
+ style={{ fontSize: 12, padding: '4px 8px' }}
180
+ />
181
+ </div>
182
+ )}
183
+
184
+ {/* Deprecated warning */}
185
+ {feature.status === 'deprecated' && (
186
+ <div style={{ marginTop: 8 }}>
187
+ <UIAlert
188
+ message="This feature is deprecated and will be removed soon"
189
+ variant="warning"
190
+ showIcon
191
+ style={{ fontSize: 12, padding: '4px 8px' }}
192
+ />
193
+ </div>
194
+ )}
195
+ </div>
196
+
197
+ {/* Right: Risk Badge */}
198
+ <div style={{ paddingTop: 2 }}>
199
+ <UITooltip title={`Risk: ${feature.risk}`}>
200
+ <UITag
201
+ color={RISK_COLORS[feature.risk]}
202
+ icon={RISK_ICONS[feature.risk]}
203
+ style={{ margin: 0 }}
204
+ >
205
+ {feature.risk.toUpperCase()}
206
+ </UITag>
207
+ </UITooltip>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ );
212
+ })}
213
+ </UISpace>
214
+ ),
215
+ };
216
+ })}
217
+ />
218
+
219
+ {/* Footer Info */}
220
+ <UIAlert
221
+ message="Feature Stability Levels"
222
+ description={
223
+ <div>
224
+ <div style={{ marginBottom: 4 }}>
225
+ <UITag color="red">ALPHA</UITag> - Early development, expect breaking changes
226
+ </div>
227
+ <div style={{ marginBottom: 4 }}>
228
+ <UITag color="orange">BETA</UITag> - Feature-complete but needs testing
229
+ </div>
230
+ <div style={{ marginBottom: 4 }}>
231
+ <UITag color="green">STABLE</UITag> - Production-ready, safe to use
232
+ </div>
233
+ <div>
234
+ <UITag color="default">DEPRECATED</UITag> - Will be removed soon
235
+ </div>
236
+ </div>
237
+ }
238
+ variant="info"
239
+ showIcon
240
+ />
241
+ </UISpace>
242
+ );
243
+ }