@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.
- package/.env.example +10 -0
- package/README.md +74 -0
- package/UI-KIT-COMPONENTS.md +783 -0
- package/UI-KIT-PLAN.md +326 -0
- package/eslint-rules/no-direct-antd-import.js +151 -0
- package/eslint.config.ts +27 -0
- package/index.html +17 -0
- package/package.json +72 -0
- package/postcss.config.js +7 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +7 -0
- package/rspack.config.mjs +118 -0
- package/src/App.tsx +46 -0
- package/src/components/devtools/devtools-log-feed.module.css +165 -0
- package/src/components/devtools/devtools-log-feed.tsx +300 -0
- package/src/components/devtools/devtools-panel.module.css +121 -0
- package/src/components/devtools/devtools-panel.tsx +115 -0
- package/src/components/empty-state.tsx +28 -0
- package/src/components/error-boundary.tsx +210 -0
- package/src/components/health-banner.module.css +103 -0
- package/src/components/health-banner.tsx +134 -0
- package/src/components/health-indicator.tsx +12 -0
- package/src/components/page-header.tsx +49 -0
- package/src/components/page-transition.tsx +24 -0
- package/src/components/role-switcher.tsx +148 -0
- package/src/components/run-badge.tsx +13 -0
- package/src/components/ui/icons.tsx +509 -0
- package/src/components/ui/index.ts +25 -0
- package/src/components/ui/kb-config-provider.tsx +130 -0
- package/src/components/ui/kb-header.module.css +51 -0
- package/src/components/ui/kb-header.tsx +112 -0
- package/src/components/ui/kb-notification-bell.module.css +103 -0
- package/src/components/ui/kb-notification-bell.tsx +199 -0
- package/src/components/ui/kb-page-layout.tsx +66 -0
- package/src/components/ui/kb-quick-search.module.css +86 -0
- package/src/components/ui/kb-quick-search.tsx +196 -0
- package/src/components/ui/kb-sidebar.module.css +47 -0
- package/src/components/ui/kb-sidebar.tsx +196 -0
- package/src/components/ui/kb-stat-card.module.css +21 -0
- package/src/components/ui/kb-stat-card.tsx +22 -0
- package/src/components/ui/kb-stat-grid.tsx +22 -0
- package/src/components/ui/kb-status-bar.module.css +49 -0
- package/src/components/ui/kb-status-bar.tsx +145 -0
- package/src/components/ui/kb-system-health-indicator.tsx +229 -0
- package/src/components/ui/kb-theme-toggle.tsx +42 -0
- package/src/components/ui/theme-adapter.ts +812 -0
- package/src/components/ui/theme-transition-overlay.tsx +70 -0
- package/src/components/ui/utils.ts +6 -0
- package/src/components/widget-error-boundary.tsx +172 -0
- package/src/config/env.ts +5 -0
- package/src/config/feature-flags.ts +482 -0
- package/src/config/studio.config.schema.ts +88 -0
- package/src/config/studio.config.ts +84 -0
- package/src/env.d.ts +23 -0
- package/src/hooks/use-command-palette.ts +6 -0
- package/src/hooks/use-devtools-store.ts +25 -0
- package/src/hooks/use-feature-flags.ts +101 -0
- package/src/index.css +10 -0
- package/src/layouts/dashboard-grid.tsx +122 -0
- package/src/layouts/index.ts +12 -0
- package/src/layouts/two-pane.tsx +134 -0
- package/src/main.tsx +15 -0
- package/src/modules/analytics/components/analytics-daily-stats-chart.tsx +257 -0
- package/src/modules/analytics/components/analytics-date-range-picker.tsx +121 -0
- package/src/modules/analytics/pages/analytics-cache-page.tsx +285 -0
- package/src/modules/analytics/pages/analytics-embeddings-page.tsx +274 -0
- package/src/modules/analytics/pages/analytics-events-page.tsx +425 -0
- package/src/modules/analytics/pages/analytics-llm-page.tsx +434 -0
- package/src/modules/analytics/pages/analytics-overview-page.tsx +336 -0
- package/src/modules/analytics/pages/analytics-page.tsx +20 -0
- package/src/modules/analytics/pages/analytics-storage-page.tsx +344 -0
- package/src/modules/analytics/pages/analytics-vectorstore-page.tsx +279 -0
- package/src/modules/analytics/routes/index.tsx +133 -0
- package/src/modules/analytics/utils/formatters.test.ts +148 -0
- package/src/modules/analytics/utils/formatters.ts +100 -0
- package/src/modules/auth/pages/login-page.module.css +113 -0
- package/src/modules/auth/pages/login-page.tsx +80 -0
- package/src/modules/auth/routes/index.tsx +24 -0
- package/src/modules/dashboard/components/ActivityTimelineWidget.tsx +199 -0
- package/src/modules/dashboard/components/AiInsightsWidget.tsx +358 -0
- package/src/modules/dashboard/components/AnimatedMetricCard.css +88 -0
- package/src/modules/dashboard/components/AnimatedMetricCard.tsx +125 -0
- package/src/modules/dashboard/components/HeroMetricCard.css +114 -0
- package/src/modules/dashboard/components/HeroMetricCard.tsx +152 -0
- package/src/modules/dashboard/components/HeroMetricsWidget.tsx +146 -0
- package/src/modules/dashboard/components/SystemResourcesWidget.tsx +197 -0
- package/src/modules/dashboard/components/dashboard-empty.tsx +22 -0
- package/src/modules/dashboard/components/dashboard-kpis-widget.tsx +59 -0
- package/src/modules/dashboard/components/dashboard-kpis.tsx +13 -0
- package/src/modules/dashboard/components/dashboard-latency-percentiles-widget.tsx +85 -0
- package/src/modules/dashboard/components/dashboard-metrics-widget.tsx +78 -0
- package/src/modules/dashboard/components/dashboard-plugins-widget.tsx +67 -0
- package/src/modules/dashboard/components/dashboard-requests-widget.tsx +62 -0
- package/src/modules/dashboard/layouts/dashboard-layout.tsx +16 -0
- package/src/modules/dashboard/pages/ai-insights-page.tsx +563 -0
- package/src/modules/dashboard/pages/dashboard-animations.css +317 -0
- package/src/modules/dashboard/pages/dashboard-page.tsx +13 -0
- package/src/modules/dashboard/routes/index.tsx +70 -0
- package/src/modules/observability/pages/devkit-page.tsx +263 -0
- package/src/modules/observability/pages/incident-detail-page.tsx +571 -0
- package/src/modules/observability/pages/incidents-page.tsx +370 -0
- package/src/modules/observability/pages/log-detail-page.tsx +508 -0
- package/src/modules/observability/pages/logs-page.tsx +1129 -0
- package/src/modules/observability/pages/prometheus-metrics-page.tsx +486 -0
- package/src/modules/observability/pages/state-broker-page.tsx +229 -0
- package/src/modules/observability/pages/system-events-page.tsx +255 -0
- package/src/modules/observability/routes/index.tsx +133 -0
- package/src/modules/plugins/components/marketplace-card.tsx +153 -0
- package/src/modules/plugins/components/plugin-ai-assistant-modal.tsx +374 -0
- package/src/modules/plugins/components/plugin-card.tsx +136 -0
- package/src/modules/plugins/components/plugin-detail/capabilities-summary.tsx +101 -0
- package/src/modules/plugins/components/plugin-detail/cli-commands-table.tsx +101 -0
- package/src/modules/plugins/components/plugin-detail/index.ts +7 -0
- package/src/modules/plugins/components/plugin-detail/jobs-table.tsx +64 -0
- package/src/modules/plugins/components/plugin-detail/permissions/env-permissions.tsx +30 -0
- package/src/modules/plugins/components/plugin-detail/permissions/filesystem-permissions.tsx +68 -0
- package/src/modules/plugins/components/plugin-detail/permissions/index.ts +6 -0
- package/src/modules/plugins/components/plugin-detail/permissions/network-permissions.tsx +43 -0
- package/src/modules/plugins/components/plugin-detail/permissions/platform-services.tsx +242 -0
- package/src/modules/plugins/components/plugin-detail/permissions/resource-quotas.tsx +96 -0
- package/src/modules/plugins/components/plugin-detail/permissions/shell-permissions.tsx +55 -0
- package/src/modules/plugins/components/plugin-detail/permissions-view.tsx +47 -0
- package/src/modules/plugins/components/plugin-detail/plugin-header.tsx +169 -0
- package/src/modules/plugins/components/plugin-detail/rest-routes-table.tsx +159 -0
- package/src/modules/plugins/components/plugin-detail/workflows-table.tsx +40 -0
- package/src/modules/plugins/components/plugin-grid.tsx +20 -0
- package/src/modules/plugins/components/plugin-table.tsx +178 -0
- package/src/modules/plugins/pages/marketplace-page.tsx +200 -0
- package/src/modules/plugins/pages/plugin-detail-page.tsx +1145 -0
- package/src/modules/plugins/routes/index.tsx +48 -0
- package/src/modules/plugins/utils/index.ts +1 -0
- package/src/modules/plugins/utils/schema-helpers.ts +21 -0
- package/src/modules/settings/components/api-routes-viewer.tsx +415 -0
- package/src/modules/settings/components/appearance-settings.tsx +200 -0
- package/src/modules/settings/components/configuration-settings.tsx +321 -0
- package/src/modules/settings/components/data-privacy-settings.tsx +111 -0
- package/src/modules/settings/components/experimental-settings.tsx +243 -0
- package/src/modules/settings/components/navigation-settings.tsx +198 -0
- package/src/modules/settings/components/notifications-settings.tsx +40 -0
- package/src/modules/settings/pages/settings-page.tsx +298 -0
- package/src/modules/settings/routes/index.tsx +47 -0
- package/src/pages/README-assistant.md +91 -0
- package/src/pages/login-page.tsx +177 -0
- package/src/pages/not-found-page.tsx +85 -0
- package/src/providers/auth-provider.tsx +40 -0
- package/src/providers/command-palette-provider.tsx +193 -0
- package/src/providers/data-sources-provider.tsx +27 -0
- package/src/providers/devtools-provider.tsx +72 -0
- package/src/providers/navigation-items-provider.tsx +38 -0
- package/src/providers/registry-v2-provider.tsx +93 -0
- package/src/providers/settings-provider.tsx +158 -0
- package/src/router.tsx +302 -0
- package/src/routes/helpers.tsx +25 -0
- package/src/routes/plugin-page-v2.tsx +52 -0
- package/src/routes/types.ts +18 -0
- package/src/styles/antd-overrides.css +211 -0
- package/src/styles/base.css +190 -0
- package/src/styles/settings.css +75 -0
- package/src/styles/variables.css +94 -0
- package/src/utils/analytics.ts +183 -0
- package/src/utils/apply-navigation-categories.ts +83 -0
- package/src/utils/commands.ts +252 -0
- package/src/utils/export.ts +174 -0
- package/src/utils/format-date.ts +166 -0
- package/src/utils/logger.ts +110 -0
- package/tailwind.config.ts +13 -0
- package/tsconfig.json +13 -0
- package/vitest-setup.ts +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { UITooltip, UIButton, UIIcon } from '@kb-labs/studio-ui-kit';
|
|
3
|
+
import type { PluginManifestEntry } from '@kb-labs/studio-data-client';
|
|
4
|
+
|
|
5
|
+
interface MarketplaceCardProps {
|
|
6
|
+
plugin: PluginManifestEntry;
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SURFACE_CONFIG = [
|
|
11
|
+
{ key: 'cli', label: 'CLI', color: '#6366f1', bg: 'rgba(99,102,241,0.10)', getCount: (m: any) => m.cli?.commands?.length ?? 0 },
|
|
12
|
+
{ key: 'studio', label: 'Studio', color: '#0c66ff', bg: 'rgba(12,102,255,0.10)', getCount: (m: any) => m.studio?.pages?.length ?? 0 },
|
|
13
|
+
{ key: 'rest', label: 'REST', color: '#10b981', bg: 'rgba(16,185,129,0.10)', getCount: (m: any) => m.rest?.routes?.length ?? 0 },
|
|
14
|
+
{ key: 'workflows', label: 'Workflows',color: '#8b5cf6', bg: 'rgba(139,92,246,0.10)', getCount: (m: any) => m.workflows?.handlers?.length ?? 0 },
|
|
15
|
+
{ key: 'jobs', label: 'Jobs', color: '#f59e0b', bg: 'rgba(245,158,11,0.10)', getCount: (m: any) => m.jobs?.handlers?.length ?? 0 },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function MarketplaceCard({ plugin, onClick }: MarketplaceCardProps) {
|
|
19
|
+
const [hovered, setHovered] = useState(false);
|
|
20
|
+
const { manifest } = plugin;
|
|
21
|
+
const display = manifest.display;
|
|
22
|
+
|
|
23
|
+
const surfaces = SURFACE_CONFIG.filter(s => s.getCount(manifest) > 0);
|
|
24
|
+
const hasPermWarning = !!manifest.permissions;
|
|
25
|
+
const hasError = plugin.validation && !plugin.validation.valid;
|
|
26
|
+
|
|
27
|
+
const name = display?.name || manifest.id;
|
|
28
|
+
const iconLetter = name.charAt(0).toUpperCase();
|
|
29
|
+
const iconBg = stringToColor(manifest.id);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
onClick={onClick}
|
|
34
|
+
onMouseEnter={() => setHovered(true)}
|
|
35
|
+
onMouseLeave={() => setHovered(false)}
|
|
36
|
+
style={{
|
|
37
|
+
background: 'var(--bg-secondary)',
|
|
38
|
+
border: `1px solid ${hovered ? 'var(--border-secondary)' : 'var(--border-primary)'}`,
|
|
39
|
+
borderRadius: 12,
|
|
40
|
+
padding: '20px',
|
|
41
|
+
cursor: 'pointer',
|
|
42
|
+
transition: 'border-color 150ms ease, box-shadow 150ms ease, transform 150ms ease',
|
|
43
|
+
boxShadow: hovered ? '0 4px 16px rgba(0,0,0,0.08)' : '0 1px 3px rgba(0,0,0,0.04)',
|
|
44
|
+
transform: hovered ? 'translateY(-1px)' : 'none',
|
|
45
|
+
display: 'flex',
|
|
46
|
+
flexDirection: 'column',
|
|
47
|
+
gap: 14,
|
|
48
|
+
height: '100%',
|
|
49
|
+
boxSizing: 'border-box',
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{/* Top: icon + status badge */}
|
|
53
|
+
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
|
54
|
+
<div style={{
|
|
55
|
+
width: 44, height: 44, borderRadius: 11, flexShrink: 0,
|
|
56
|
+
background: iconBg,
|
|
57
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
58
|
+
fontSize: 18, fontWeight: 700, color: '#fff',
|
|
59
|
+
fontFamily: 'var(--font-heading)',
|
|
60
|
+
}}>
|
|
61
|
+
{iconLetter}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
{hasError ? (
|
|
65
|
+
<StatusBadge color="#ff4d4f" bg="rgba(255,77,79,0.10)">Invalid</StatusBadge>
|
|
66
|
+
) : (
|
|
67
|
+
<StatusBadge color="#52c41a" bg="rgba(82,196,26,0.10)">Installed</StatusBadge>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* Name + version */}
|
|
72
|
+
<div>
|
|
73
|
+
<div style={{
|
|
74
|
+
fontSize: 15, fontWeight: 600, color: 'var(--text-primary)',
|
|
75
|
+
lineHeight: 1.3, marginBottom: 3,
|
|
76
|
+
fontFamily: 'var(--font-heading)',
|
|
77
|
+
}}>
|
|
78
|
+
{name}
|
|
79
|
+
</div>
|
|
80
|
+
<div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>
|
|
81
|
+
v{manifest.version}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{/* Description */}
|
|
86
|
+
<div style={{
|
|
87
|
+
fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.55,
|
|
88
|
+
display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
|
|
89
|
+
overflow: 'hidden', flex: 1,
|
|
90
|
+
}}>
|
|
91
|
+
{display?.description || 'No description'}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Footer: surfaces + warning */}
|
|
95
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
|
96
|
+
<div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>
|
|
97
|
+
{surfaces.map(s => (
|
|
98
|
+
<UITooltip key={s.key} title={`${s.getCount(manifest)} ${s.label}`}>
|
|
99
|
+
<span style={{
|
|
100
|
+
fontSize: 11, fontWeight: 500, padding: '3px 8px',
|
|
101
|
+
borderRadius: 20, color: s.color, background: s.bg,
|
|
102
|
+
}}>
|
|
103
|
+
{s.label}
|
|
104
|
+
</span>
|
|
105
|
+
</UITooltip>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
|
|
110
|
+
{hasPermWarning && (
|
|
111
|
+
<UITooltip title="Requires permissions">
|
|
112
|
+
<UIIcon name="WarningOutlined" style={{ fontSize: 13, color: 'var(--warning)' }} />
|
|
113
|
+
</UITooltip>
|
|
114
|
+
)}
|
|
115
|
+
{hovered && (
|
|
116
|
+
<UIButton
|
|
117
|
+
size="small"
|
|
118
|
+
style={{ fontSize: 12, height: 26, padding: '0 10px' }}
|
|
119
|
+
onClick={e => { e.stopPropagation(); onClick(); }}
|
|
120
|
+
>
|
|
121
|
+
Open
|
|
122
|
+
</UIButton>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function StatusBadge({ color, bg, children }: { color: string; bg: string; children: string }) {
|
|
131
|
+
return (
|
|
132
|
+
<span style={{
|
|
133
|
+
fontSize: 11, fontWeight: 600, padding: '3px 9px',
|
|
134
|
+
borderRadius: 20, color, background: bg,
|
|
135
|
+
letterSpacing: '0.01em',
|
|
136
|
+
}}>
|
|
137
|
+
{children}
|
|
138
|
+
</span>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function stringToColor(str: string): string {
|
|
143
|
+
const colors = [
|
|
144
|
+
'#6366f1', '#0c66ff', '#10b981', '#8b5cf6',
|
|
145
|
+
'#f59e0b', '#ef4444', '#06b6d4', '#ec4899',
|
|
146
|
+
'#84cc16', '#f97316',
|
|
147
|
+
];
|
|
148
|
+
let hash = 0;
|
|
149
|
+
for (let i = 0; i < str.length; i++) {
|
|
150
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
151
|
+
}
|
|
152
|
+
return colors[Math.abs(hash) % colors.length]!;
|
|
153
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Assistant Modal for Plugin Inspector
|
|
3
|
+
*
|
|
4
|
+
* Allows users to ask questions about a plugin and get AI-generated answers
|
|
5
|
+
* based on the plugin's manifest.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
|
+
import {
|
|
10
|
+
UIModal,
|
|
11
|
+
UIInputTextArea,
|
|
12
|
+
UIButton,
|
|
13
|
+
UISpace,
|
|
14
|
+
UITypographyText,
|
|
15
|
+
UICard,
|
|
16
|
+
UISpin,
|
|
17
|
+
UIAlert,
|
|
18
|
+
UITag,
|
|
19
|
+
UIDivider,
|
|
20
|
+
UIRow,
|
|
21
|
+
UICol,
|
|
22
|
+
UIIcon,
|
|
23
|
+
} from '@kb-labs/studio-ui-kit';
|
|
24
|
+
import type { PluginAskResponse } from '@kb-labs/studio-data-client';
|
|
25
|
+
import { UIMarkdownViewer } from '@kb-labs/studio-ui-kit';
|
|
26
|
+
|
|
27
|
+
interface PluginAIAssistantModalProps {
|
|
28
|
+
open: boolean;
|
|
29
|
+
onClose: () => void;
|
|
30
|
+
pluginId: string;
|
|
31
|
+
pluginName: string;
|
|
32
|
+
onAsk: (question: string) => Promise<PluginAskResponse>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface QAItem {
|
|
36
|
+
question: string;
|
|
37
|
+
answer: string;
|
|
38
|
+
timestamp: Date;
|
|
39
|
+
usage: {
|
|
40
|
+
promptTokens: number;
|
|
41
|
+
completionTokens: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const QUESTION_TEMPLATES = [
|
|
46
|
+
{ label: 'What permissions?', value: 'What permissions does this plugin need and why?' },
|
|
47
|
+
{ label: 'Show CLI commands', value: 'What CLI commands does this plugin provide? List them with their flags and usage.' },
|
|
48
|
+
{ label: 'Show REST APIs', value: 'What REST API endpoints does this plugin expose? Describe their paths, methods, and purpose.' },
|
|
49
|
+
{ label: 'What does it do?', value: 'What does this plugin do? Explain its main features and use cases.' },
|
|
50
|
+
{ label: 'How to configure?', value: 'How do I configure this plugin? What options are available?' },
|
|
51
|
+
{ label: 'What files created?', value: 'What files or directories does this plugin create or modify?' },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export function PluginAIAssistantModal({
|
|
55
|
+
open,
|
|
56
|
+
onClose,
|
|
57
|
+
pluginId,
|
|
58
|
+
pluginName,
|
|
59
|
+
onAsk,
|
|
60
|
+
}: PluginAIAssistantModalProps) {
|
|
61
|
+
const [customQuestion, setCustomQuestion] = useState('');
|
|
62
|
+
const [history, setHistory] = useState<QAItem[]>([]);
|
|
63
|
+
const [loading, setLoading] = useState(false);
|
|
64
|
+
const [error, setError] = useState<string | null>(null);
|
|
65
|
+
|
|
66
|
+
const handleAsk = async (question: string) => {
|
|
67
|
+
if (!question.trim()) {return;}
|
|
68
|
+
|
|
69
|
+
setLoading(true);
|
|
70
|
+
setError(null);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await onAsk(question);
|
|
74
|
+
|
|
75
|
+
setHistory((prev) => [
|
|
76
|
+
{
|
|
77
|
+
question,
|
|
78
|
+
answer: response.answer,
|
|
79
|
+
timestamp: new Date(),
|
|
80
|
+
usage: response.usage,
|
|
81
|
+
},
|
|
82
|
+
...prev,
|
|
83
|
+
]);
|
|
84
|
+
setCustomQuestion('');
|
|
85
|
+
} catch (err) {
|
|
86
|
+
setError(err instanceof Error ? err.message : 'Failed to get AI response');
|
|
87
|
+
} finally {
|
|
88
|
+
setLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleTemplateClick = (question: string) => {
|
|
93
|
+
setCustomQuestion(question);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleSubmit = () => {
|
|
97
|
+
handleAsk(customQuestion);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<UIModal
|
|
102
|
+
title={
|
|
103
|
+
<UISpace>
|
|
104
|
+
<UIIcon name="RobotOutlined" />
|
|
105
|
+
<span>AI Assistant</span>
|
|
106
|
+
</UISpace>
|
|
107
|
+
}
|
|
108
|
+
open={open}
|
|
109
|
+
onCancel={onClose}
|
|
110
|
+
width={800}
|
|
111
|
+
footer={[
|
|
112
|
+
<UIButton key="cancel" onClick={onClose}>
|
|
113
|
+
Close
|
|
114
|
+
</UIButton>,
|
|
115
|
+
<UIButton
|
|
116
|
+
key="ask"
|
|
117
|
+
variant="primary"
|
|
118
|
+
icon={<UIIcon name="ThunderboltOutlined" />}
|
|
119
|
+
loading={loading}
|
|
120
|
+
onClick={handleSubmit}
|
|
121
|
+
disabled={!customQuestion.trim()}
|
|
122
|
+
>
|
|
123
|
+
Ask Question
|
|
124
|
+
</UIButton>,
|
|
125
|
+
]}
|
|
126
|
+
>
|
|
127
|
+
<UISpace direction="vertical" style={{ width: '100%' }} size="large">
|
|
128
|
+
{/* Plugin Preview */}
|
|
129
|
+
<UICard size="small" style={{ backgroundColor: '#fafafa' }}>
|
|
130
|
+
<UIRow gutter={16}>
|
|
131
|
+
<UICol span={12}>
|
|
132
|
+
<UITypographyText type="secondary">Plugin:</UITypographyText>
|
|
133
|
+
<div>
|
|
134
|
+
<UITypographyText strong>{pluginName}</UITypographyText>
|
|
135
|
+
</div>
|
|
136
|
+
</UICol>
|
|
137
|
+
<UICol span={12}>
|
|
138
|
+
<UITypographyText type="secondary">Plugin ID:</UITypographyText>
|
|
139
|
+
<div>
|
|
140
|
+
<UITypographyText code style={{ fontSize: 12 }}>{pluginId}</UITypographyText>
|
|
141
|
+
</div>
|
|
142
|
+
</UICol>
|
|
143
|
+
</UIRow>
|
|
144
|
+
</UICard>
|
|
145
|
+
|
|
146
|
+
{/* Quick Templates */}
|
|
147
|
+
<div>
|
|
148
|
+
<UITypographyText strong>Quick Questions:</UITypographyText>
|
|
149
|
+
<div style={{ marginTop: 8 }}>
|
|
150
|
+
<UISpace wrap>
|
|
151
|
+
{QUESTION_TEMPLATES.map((template) => (
|
|
152
|
+
<UIButton
|
|
153
|
+
key={template.label}
|
|
154
|
+
size="small"
|
|
155
|
+
onClick={() => handleTemplateClick(template.value)}
|
|
156
|
+
>
|
|
157
|
+
{template.label}
|
|
158
|
+
</UIButton>
|
|
159
|
+
))}
|
|
160
|
+
</UISpace>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<UIDivider style={{ margin: '8px 0' }} />
|
|
165
|
+
|
|
166
|
+
{/* Custom Question */}
|
|
167
|
+
<div>
|
|
168
|
+
<UITypographyText strong>Your Question:</UITypographyText>
|
|
169
|
+
<UIInputTextArea
|
|
170
|
+
value={customQuestion}
|
|
171
|
+
onChange={(e) => setCustomQuestion(e.target.value)}
|
|
172
|
+
placeholder="Ask anything about this plugin... (e.g., 'How do I use this plugin?')"
|
|
173
|
+
rows={3}
|
|
174
|
+
style={{ marginTop: 8 }}
|
|
175
|
+
onPressEnter={(e) => {
|
|
176
|
+
if (e.ctrlKey || e.metaKey) {
|
|
177
|
+
handleSubmit();
|
|
178
|
+
}
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
181
|
+
<UITypographyText type="secondary" style={{ fontSize: 12, marginTop: 4, display: 'block' }}>
|
|
182
|
+
Press Ctrl/Cmd + Enter to ask
|
|
183
|
+
</UITypographyText>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
{/* Error Display */}
|
|
187
|
+
{error && (
|
|
188
|
+
<UIAlert
|
|
189
|
+
message="Error"
|
|
190
|
+
description={error}
|
|
191
|
+
variant="error"
|
|
192
|
+
closable
|
|
193
|
+
onClose={() => setError(null)}
|
|
194
|
+
showIcon
|
|
195
|
+
/>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{/* Loading State */}
|
|
199
|
+
{loading && (
|
|
200
|
+
<div style={{ textAlign: 'center', padding: '24px 0' }}>
|
|
201
|
+
<UISpin tip="AI is thinking..." size="large" />
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
204
|
+
|
|
205
|
+
{/* Q&A History */}
|
|
206
|
+
{history.length > 0 && !loading && (
|
|
207
|
+
<div>
|
|
208
|
+
<UIDivider orientation="left" style={{ margin: '16px 0 8px 0' }}>
|
|
209
|
+
<UITypographyText strong>Conversation History</UITypographyText>
|
|
210
|
+
</UIDivider>
|
|
211
|
+
<UISpace direction="vertical" style={{ width: '100%' }} size="middle">
|
|
212
|
+
{history.map((item, index) => (
|
|
213
|
+
<UICard
|
|
214
|
+
key={index}
|
|
215
|
+
size="small"
|
|
216
|
+
style={{
|
|
217
|
+
backgroundColor: '#fafafa',
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
{/* Question */}
|
|
221
|
+
<div style={{ marginBottom: 12 }}>
|
|
222
|
+
<UISpace style={{ marginBottom: 4 }}>
|
|
223
|
+
<UIIcon name="QuestionCircleOutlined" style={{ color: '#1890ff' }} />
|
|
224
|
+
<UITypographyText strong>{item.question}</UITypographyText>
|
|
225
|
+
</UISpace>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
{/* Answer */}
|
|
229
|
+
<div
|
|
230
|
+
style={{
|
|
231
|
+
background: '#fff',
|
|
232
|
+
padding: 12,
|
|
233
|
+
borderRadius: 4,
|
|
234
|
+
border: '1px solid #e8e8e8',
|
|
235
|
+
marginBottom: 8,
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
<UIMarkdownViewer content={item.answer} className="plugin-assistant-markdown" />
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Metadata */}
|
|
242
|
+
<div style={{ textAlign: 'right' }}>
|
|
243
|
+
<UISpace size="small">
|
|
244
|
+
<UITag icon={<UIIcon name="ClockCircleOutlined" />} color="blue">
|
|
245
|
+
{item.timestamp.toLocaleTimeString()}
|
|
246
|
+
</UITag>
|
|
247
|
+
<UITag color="green">
|
|
248
|
+
{item.usage.promptTokens + item.usage.completionTokens} tokens
|
|
249
|
+
</UITag>
|
|
250
|
+
</UISpace>
|
|
251
|
+
</div>
|
|
252
|
+
</UICard>
|
|
253
|
+
))}
|
|
254
|
+
</UISpace>
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
{/* Empty State */}
|
|
259
|
+
{history.length === 0 && !loading && (
|
|
260
|
+
<div
|
|
261
|
+
style={{
|
|
262
|
+
textAlign: 'center',
|
|
263
|
+
padding: 40,
|
|
264
|
+
backgroundColor: '#fafafa',
|
|
265
|
+
borderRadius: 4,
|
|
266
|
+
border: '1px dashed #d9d9d9',
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
<UIIcon name="RobotOutlined" style={{ fontSize: 48, color: '#d9d9d9', marginBottom: 16 }} />
|
|
270
|
+
<div style={{ fontSize: 14, color: '#999' }}>
|
|
271
|
+
Select a quick question or type your own to get started
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
)}
|
|
275
|
+
</UISpace>
|
|
276
|
+
|
|
277
|
+
{/* Custom styles for markdown in modal context */}
|
|
278
|
+
<style>{`
|
|
279
|
+
.plugin-assistant-markdown {
|
|
280
|
+
padding: 0;
|
|
281
|
+
background: transparent;
|
|
282
|
+
border-radius: 0;
|
|
283
|
+
}
|
|
284
|
+
.plugin-assistant-markdown p {
|
|
285
|
+
margin: 0 0 8px 0;
|
|
286
|
+
}
|
|
287
|
+
.plugin-assistant-markdown p:last-child {
|
|
288
|
+
margin-bottom: 0;
|
|
289
|
+
}
|
|
290
|
+
.plugin-assistant-markdown h1,
|
|
291
|
+
.plugin-assistant-markdown h2,
|
|
292
|
+
.plugin-assistant-markdown h3,
|
|
293
|
+
.plugin-assistant-markdown h4 {
|
|
294
|
+
margin-top: 12px;
|
|
295
|
+
margin-bottom: 8px;
|
|
296
|
+
font-size: 1em;
|
|
297
|
+
font-weight: 600;
|
|
298
|
+
border: none;
|
|
299
|
+
padding: 0;
|
|
300
|
+
}
|
|
301
|
+
.plugin-assistant-markdown h1 {
|
|
302
|
+
font-size: 1.15em;
|
|
303
|
+
}
|
|
304
|
+
.plugin-assistant-markdown h2 {
|
|
305
|
+
font-size: 1.1em;
|
|
306
|
+
}
|
|
307
|
+
.plugin-assistant-markdown h3 {
|
|
308
|
+
font-size: 1.05em;
|
|
309
|
+
}
|
|
310
|
+
.plugin-assistant-markdown ul,
|
|
311
|
+
.plugin-assistant-markdown ol {
|
|
312
|
+
margin: 8px 0;
|
|
313
|
+
padding-left: 20px;
|
|
314
|
+
}
|
|
315
|
+
.plugin-assistant-markdown li {
|
|
316
|
+
margin: 4px 0;
|
|
317
|
+
}
|
|
318
|
+
.plugin-assistant-markdown code {
|
|
319
|
+
background: rgba(0, 0, 0, 0.06);
|
|
320
|
+
padding: 2px 6px;
|
|
321
|
+
border-radius: 4px;
|
|
322
|
+
font-size: 0.9em;
|
|
323
|
+
font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace;
|
|
324
|
+
}
|
|
325
|
+
.plugin-assistant-markdown pre {
|
|
326
|
+
background: #f5f5f5;
|
|
327
|
+
padding: 12px 16px;
|
|
328
|
+
border-radius: 6px;
|
|
329
|
+
margin: 12px 0;
|
|
330
|
+
overflow-x: auto;
|
|
331
|
+
}
|
|
332
|
+
.plugin-assistant-markdown pre code {
|
|
333
|
+
background: none;
|
|
334
|
+
padding: 0;
|
|
335
|
+
font-size: 0.85em;
|
|
336
|
+
}
|
|
337
|
+
.plugin-assistant-markdown table {
|
|
338
|
+
margin: 12px 0;
|
|
339
|
+
font-size: 0.9em;
|
|
340
|
+
width: 100%;
|
|
341
|
+
border-collapse: collapse;
|
|
342
|
+
}
|
|
343
|
+
.plugin-assistant-markdown th,
|
|
344
|
+
.plugin-assistant-markdown td {
|
|
345
|
+
padding: 8px 12px;
|
|
346
|
+
border: 1px solid #e8e8e8;
|
|
347
|
+
text-align: left;
|
|
348
|
+
}
|
|
349
|
+
.plugin-assistant-markdown th {
|
|
350
|
+
background: #fafafa;
|
|
351
|
+
font-weight: 600;
|
|
352
|
+
}
|
|
353
|
+
.plugin-assistant-markdown blockquote {
|
|
354
|
+
margin: 12px 0;
|
|
355
|
+
padding: 8px 16px;
|
|
356
|
+
border-left: 4px solid #1890ff;
|
|
357
|
+
background: #f6f8fa;
|
|
358
|
+
border-radius: 0 4px 4px 0;
|
|
359
|
+
}
|
|
360
|
+
.plugin-assistant-markdown strong {
|
|
361
|
+
font-weight: 600;
|
|
362
|
+
}
|
|
363
|
+
.plugin-assistant-markdown a {
|
|
364
|
+
color: #1890ff;
|
|
365
|
+
}
|
|
366
|
+
.plugin-assistant-markdown hr {
|
|
367
|
+
margin: 16px 0;
|
|
368
|
+
border: none;
|
|
369
|
+
border-top: 1px solid #e8e8e8;
|
|
370
|
+
}
|
|
371
|
+
`}</style>
|
|
372
|
+
</UIModal>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UICard,
|
|
3
|
+
UITag,
|
|
4
|
+
UISpace,
|
|
5
|
+
UITypographyText,
|
|
6
|
+
UITypographyParagraph,
|
|
7
|
+
UITooltip,
|
|
8
|
+
UIIcon,
|
|
9
|
+
} from '@kb-labs/studio-ui-kit';
|
|
10
|
+
import type { PluginManifestEntry } from '@kb-labs/studio-data-client';
|
|
11
|
+
|
|
12
|
+
interface PluginCardProps {
|
|
13
|
+
plugin: PluginManifestEntry;
|
|
14
|
+
onClick: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function PluginCard({ plugin, onClick }: PluginCardProps) {
|
|
18
|
+
const { manifest } = plugin;
|
|
19
|
+
const display = manifest.display;
|
|
20
|
+
|
|
21
|
+
const cliCount = manifest.cli?.commands.length ?? 0;
|
|
22
|
+
const restCount = manifest.rest?.routes.length ?? 0;
|
|
23
|
+
const workflowCount = manifest.workflows?.handlers.length ?? 0;
|
|
24
|
+
const jobCount = manifest.jobs?.handlers.length ?? 0;
|
|
25
|
+
|
|
26
|
+
const hasPermissions = !!manifest.permissions;
|
|
27
|
+
const requiresPlatform = manifest.platform?.requires && manifest.platform.requires.length > 0;
|
|
28
|
+
const hasValidationErrors = plugin.validation && !plugin.validation.valid;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<UICard
|
|
32
|
+
hoverable
|
|
33
|
+
onClick={onClick}
|
|
34
|
+
style={{ height: '100%', cursor: 'pointer' }}
|
|
35
|
+
styles={{ body: { display: 'flex', flexDirection: 'column', height: '100%' } }}
|
|
36
|
+
>
|
|
37
|
+
<UISpace direction="vertical" size="small" style={{ width: '100%', flex: 1 }}>
|
|
38
|
+
{/* Header: Icon + Name */}
|
|
39
|
+
<UISpace align="center" style={{ width: '100%' }}>
|
|
40
|
+
<span style={{ fontSize: 18, lineHeight: 1, flexShrink: 0, opacity: display?.icon ? 1 : 0.4 }}>
|
|
41
|
+
{display?.icon || <UIIcon name="AppstoreOutlined" />}
|
|
42
|
+
</span>
|
|
43
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
44
|
+
<UITypographyText strong style={{ fontSize: 14 }}>
|
|
45
|
+
{display?.name || manifest.id}
|
|
46
|
+
</UITypographyText>
|
|
47
|
+
<br />
|
|
48
|
+
<UITypographyText type="secondary" style={{ fontSize: 12 }}>
|
|
49
|
+
v{manifest.version}
|
|
50
|
+
</UITypographyText>
|
|
51
|
+
</div>
|
|
52
|
+
</UISpace>
|
|
53
|
+
|
|
54
|
+
{/* Description */}
|
|
55
|
+
{display?.description && (
|
|
56
|
+
<UITypographyParagraph
|
|
57
|
+
type="secondary"
|
|
58
|
+
style={{
|
|
59
|
+
fontSize: 13,
|
|
60
|
+
marginBottom: 8,
|
|
61
|
+
lineHeight: 1.4,
|
|
62
|
+
}}
|
|
63
|
+
ellipsis={{ rows: 2, tooltip: display.description }}
|
|
64
|
+
>
|
|
65
|
+
{display.description}
|
|
66
|
+
</UITypographyParagraph>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{/* Tags */}
|
|
70
|
+
{display?.tags && display.tags.length > 0 && (
|
|
71
|
+
<div style={{ marginBottom: 8 }}>
|
|
72
|
+
{display.tags.slice(0, 3).map((tag) => (
|
|
73
|
+
<UITag key={tag} style={{ fontSize: 11, marginBottom: 4 }}>
|
|
74
|
+
{tag}
|
|
75
|
+
</UITag>
|
|
76
|
+
))}
|
|
77
|
+
{display.tags.length > 3 && (
|
|
78
|
+
<UITag style={{ fontSize: 11 }}>+{display.tags.length - 3}</UITag>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
{/* Capabilities */}
|
|
84
|
+
<UISpace wrap size={4}>
|
|
85
|
+
{cliCount > 0 && (
|
|
86
|
+
<UITooltip title={`${cliCount} CLI command${cliCount > 1 ? 's' : ''}`}>
|
|
87
|
+
<UITag icon={<UIIcon name="CodeOutlined" />} color="blue">
|
|
88
|
+
{cliCount}
|
|
89
|
+
</UITag>
|
|
90
|
+
</UITooltip>
|
|
91
|
+
)}
|
|
92
|
+
{restCount > 0 && (
|
|
93
|
+
<UITooltip title={`${restCount} REST route${restCount > 1 ? 's' : ''}`}>
|
|
94
|
+
<UITag icon={<UIIcon name="ApiOutlined" />} color="green">
|
|
95
|
+
{restCount}
|
|
96
|
+
</UITag>
|
|
97
|
+
</UITooltip>
|
|
98
|
+
)}
|
|
99
|
+
{workflowCount > 0 && (
|
|
100
|
+
<UITooltip title={`${workflowCount} workflow${workflowCount > 1 ? 's' : ''}`}>
|
|
101
|
+
<UITag icon={<UIIcon name="NodeIndexOutlined" />} color="purple">
|
|
102
|
+
{workflowCount}
|
|
103
|
+
</UITag>
|
|
104
|
+
</UITooltip>
|
|
105
|
+
)}
|
|
106
|
+
{jobCount > 0 && (
|
|
107
|
+
<UITooltip title={`${jobCount} scheduled job${jobCount > 1 ? 's' : ''}`}>
|
|
108
|
+
<UITag icon={<UIIcon name="ClockCircleOutlined" />} color="orange">
|
|
109
|
+
{jobCount}
|
|
110
|
+
</UITag>
|
|
111
|
+
</UITooltip>
|
|
112
|
+
)}
|
|
113
|
+
</UISpace>
|
|
114
|
+
|
|
115
|
+
{/* Indicators */}
|
|
116
|
+
<UISpace wrap size={4} style={{ marginTop: 'auto' }}>
|
|
117
|
+
{hasValidationErrors && (
|
|
118
|
+
<UITooltip title={`${plugin.validation!.errors.length} validation error${plugin.validation!.errors.length > 1 ? 's' : ''}`}>
|
|
119
|
+
<UITag icon={<UIIcon name="CloseCircleOutlined" />} color="red" style={{ fontSize: 11 }}>Invalid</UITag>
|
|
120
|
+
</UITooltip>
|
|
121
|
+
)}
|
|
122
|
+
{hasPermissions && (
|
|
123
|
+
<UITooltip title="Requires permissions">
|
|
124
|
+
<UITag icon={<UIIcon name="WarningOutlined" />} color="gold" style={{ fontSize: 11 }}>Permissions</UITag>
|
|
125
|
+
</UITooltip>
|
|
126
|
+
)}
|
|
127
|
+
{requiresPlatform && (
|
|
128
|
+
<UITooltip title={`Requires: ${manifest.platform!.requires!.join(', ')}`}>
|
|
129
|
+
<UITag icon={<UIIcon name="CheckCircleOutlined" />} color="cyan" style={{ fontSize: 11 }}>Platform</UITag>
|
|
130
|
+
</UITooltip>
|
|
131
|
+
)}
|
|
132
|
+
</UISpace>
|
|
133
|
+
</UISpace>
|
|
134
|
+
</UICard>
|
|
135
|
+
);
|
|
136
|
+
}
|