@decido/shell 1.0.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/.turbo/turbo-build.log +13 -0
- package/package.json +65 -0
- package/src/AgentPlayer.tsx +105 -0
- package/src/DecidoPlayer.tsx +117 -0
- package/src/bridge/BridgeAgent.ts +443 -0
- package/src/components/DecidoIcon.tsx +56 -0
- package/src/components/JsonTreeEditor.tsx +117 -0
- package/src/components/PanelSplitter.tsx +71 -0
- package/src/components/PluginErrorBoundary.tsx +69 -0
- package/src/components/SafeLiquidUI.tsx +114 -0
- package/src/components/TransientLayer.tsx +92 -0
- package/src/components/agent/AgentChat.tsx +134 -0
- package/src/components/chat-extensions/IntentCatalogPanel.tsx +81 -0
- package/src/components/chat-extensions/chatSlashCommands.ts +101 -0
- package/src/components/controls/CreatorInputBar.tsx +144 -0
- package/src/components/controls/OSToolbar.tsx +90 -0
- package/src/components/controls/TimelineTape.tsx +43 -0
- package/src/components/debug/ActionTimelineTab.tsx +111 -0
- package/src/components/debug/CSSInspectorTab.tsx +436 -0
- package/src/components/debug/ExportTab.tsx +192 -0
- package/src/components/debug/FlowHealthTab.tsx +86 -0
- package/src/components/debug/LogsTab.tsx +110 -0
- package/src/components/debug/MorphStackTab.tsx +241 -0
- package/src/components/debug/NetworkTab.tsx +173 -0
- package/src/components/debug/PerformanceTab.tsx +171 -0
- package/src/components/debug/ProfilesTab.tsx +238 -0
- package/src/components/debug/ReplayTab.tsx +70 -0
- package/src/components/debug/StoresTab.tsx +255 -0
- package/src/components/debug/TopologyTab.tsx +59 -0
- package/src/components/debug/debugConfig.tsx +66 -0
- package/src/components/playground/DebugPanel.tsx +112 -0
- package/src/components/playground/HeaderCenterControls.tsx +92 -0
- package/src/components/playground/KeyframeListItem.tsx +70 -0
- package/src/components/playground/PlaygroundAppSidebar.tsx +171 -0
- package/src/components/playground/PlaygroundBottomControls.tsx +132 -0
- package/src/components/playground/PlaygroundCanvas.tsx +87 -0
- package/src/components/playground/PlaygroundChat.tsx +236 -0
- package/src/components/playground/PlaygroundErrorBoundary.tsx +63 -0
- package/src/components/playground/PlaygroundFloatingInput.tsx +352 -0
- package/src/components/playground/PlaygroundHeader.tsx +222 -0
- package/src/components/playground/PlaygroundSidebar.tsx +136 -0
- package/src/components/playground/PlaygroundTerminal.tsx +44 -0
- package/src/components/playground/SuggestionCards.tsx +29 -0
- package/src/components/playground/demos/ClinicaAINode.tsx +221 -0
- package/src/components/playground/demos/FinanceAINode.tsx +226 -0
- package/src/components/playground/demos/KiaAcademyNode.tsx +250 -0
- package/src/components/playground/demos/KiaBotNode.tsx +207 -0
- package/src/components/playground/demos/KiaCampaignNode.tsx +191 -0
- package/src/components/playground/demos/KiaComplianceNode.tsx +140 -0
- package/src/components/playground/demos/KiaCustomerJourneyNode.tsx +220 -0
- package/src/components/playground/demos/KiaCyberNode.tsx +203 -0
- package/src/components/playground/demos/KiaDashboardNode.tsx +399 -0
- package/src/components/playground/demos/KiaEmbudoOverviewNode.tsx +168 -0
- package/src/components/playground/demos/KiaExecutiveNode.tsx +169 -0
- package/src/components/playground/demos/KiaGamificationNode.tsx +229 -0
- package/src/components/playground/demos/KiaIntelligenceHubNode.tsx +165 -0
- package/src/components/playground/demos/KiaInventoryNode.tsx +183 -0
- package/src/components/playground/demos/KiaLeadScoringNode.tsx +226 -0
- package/src/components/playground/demos/KiaLiveSimulationNode.tsx +177 -0
- package/src/components/playground/demos/KiaMultiDealerNode.tsx +223 -0
- package/src/components/playground/demos/KiaNPSVoiceNode.tsx +214 -0
- package/src/components/playground/demos/KiaOmnichannelNode.tsx +162 -0
- package/src/components/playground/demos/KiaPBIBudgetNode.tsx +152 -0
- package/src/components/playground/demos/KiaPBIConversionNode.tsx +206 -0
- package/src/components/playground/demos/KiaPBIFunnelNode.tsx +184 -0
- package/src/components/playground/demos/KiaPBIOwnershipNode.tsx +113 -0
- package/src/components/playground/demos/KiaPBIPartnerNode.tsx +143 -0
- package/src/components/playground/demos/KiaPBIPreciosNode.tsx +120 -0
- package/src/components/playground/demos/KiaPBIRuntNode.tsx +205 -0
- package/src/components/playground/demos/KiaPartnerScoreNode.tsx +206 -0
- package/src/components/playground/demos/KiaPredictiveNode.tsx +226 -0
- package/src/components/playground/demos/KiaShowroomNode.tsx +194 -0
- package/src/components/playground/demos/KiaStoreNode.tsx +215 -0
- package/src/components/playground/demos/KiaSustainabilityNode.tsx +173 -0
- package/src/components/playground/demos/KiaUsedVehiclesNode.tsx +163 -0
- package/src/components/playground/demos/KiaWorkshopNode.tsx +221 -0
- package/src/components/playground/demos/SmartCityNode.tsx +205 -0
- package/src/components/playground/demos/kia_campaign_manifest.json +112 -0
- package/src/components/playground/input-parts/AIModelSelector.tsx +156 -0
- package/src/components/playground/input-parts/InputActions.tsx +80 -0
- package/src/components/playground/input-parts/InputToolbar.tsx +245 -0
- package/src/components/playground/input-parts/ResourceLibraryPanel.tsx +287 -0
- package/src/components/playground/sidebarDsdIO.ts +82 -0
- package/src/components/settings/SettingsPanel.tsx +267 -0
- package/src/components/shell/AppHeader.tsx +9 -0
- package/src/components/shell/AppShell.tsx +139 -0
- package/src/components/shell/ArtifactBar.tsx +97 -0
- package/src/components/shell/BootScreen.tsx +19 -0
- package/src/components/shell/CenterComposite.tsx +87 -0
- package/src/components/shell/CodeEditorPanel.tsx +88 -0
- package/src/components/shell/GlobalOverlays.tsx +228 -0
- package/src/components/shell/LayoutConfigurator.tsx +209 -0
- package/src/components/shell/LayoutGrid.tsx +178 -0
- package/src/components/shell/MorphShell.tsx +368 -0
- package/src/components/shell/PluginViewer.tsx +147 -0
- package/src/components/shell/ShellNexusPreview.tsx +458 -0
- package/src/components/shell/SlotRenderer.tsx +115 -0
- package/src/components/shell/TabBar.tsx +94 -0
- package/src/components/shell/TemplateLibrary.tsx +195 -0
- package/src/components/shell/layoutConstants.ts +35 -0
- package/src/components/shell/morphStageMeta.ts +15 -0
- package/src/components/shell/shells/BuiltInShells.tsx +443 -0
- package/src/components/shell/shells/DatawayChatShell.tsx +42 -0
- package/src/components/shell/shells/TokenPreview.tsx +339 -0
- package/src/components/shell/shells/bootShells.ts +31 -0
- package/src/components/shells/CreatorShell.tsx +37 -0
- package/src/components/shells/DecidoShell.tsx +447 -0
- package/src/components/shells/ExperimentalChatShell.tsx +245 -0
- package/src/components/shells/UserCanvas.tsx +44 -0
- package/src/components/studio/BlueprintManagerPanel.tsx +137 -0
- package/src/components/studio/DependencyTreePanel.tsx +192 -0
- package/src/components/studio/NodePalette.tsx +92 -0
- package/src/components/studio/NodePropertiesPanel.tsx +81 -0
- package/src/components/studio/ReactFlowEditor.tsx +242 -0
- package/src/components/studio/TimelineEditor.tsx +122 -0
- package/src/components/studio/TimelineKeyframeCard.tsx +99 -0
- package/src/components/studio/VariablePanel.tsx +181 -0
- package/src/components/studio/blueprint/BlueprintCard.tsx +82 -0
- package/src/components/studio/editor/CanvasContextMenu.tsx +107 -0
- package/src/components/studio/editor/EditorToolbar.tsx +80 -0
- package/src/components/studio/editor/StageContentRenderer.tsx +134 -0
- package/src/components/studio/editor/TrackPropertyEditors.tsx +133 -0
- package/src/components/studio/editor/TreeNodeItem.tsx +91 -0
- package/src/components/studio/editor/edgeStyles.ts +43 -0
- package/src/components/studio/editor/editorKeyHandler.ts +95 -0
- package/src/components/studio/editor/nodeTypeRegistry.ts +137 -0
- package/src/components/studio/editor/paletteCatalog.tsx +84 -0
- package/src/components/studio/nodes/shell/InteractionNodes.tsx +82 -0
- package/src/components/studio/nodes/shell/LayoutControlNodes.tsx +69 -0
- package/src/components/studio/nodes/shell/RegisterActionNode.tsx +20 -0
- package/src/components/studio/nodes/shell/RegisterButtonNode.tsx +22 -0
- package/src/components/studio/nodes/shell/RegisterPanelNode.tsx +19 -0
- package/src/components/studio/nodes/shell/RegisterSidebarNode.tsx +19 -0
- package/src/components/studio/nodes/shell/RegisterStatusBarNode.tsx +22 -0
- package/src/components/studio/nodes/shell/RegisterTabNode.tsx +21 -0
- package/src/components/studio/nodes/shell/RegisterTopBarNode.tsx +22 -0
- package/src/components/studio/nodes/shell/ShellConfigNode.tsx +51 -0
- package/src/components/studio/nodes/shell/ShellNodeBase.tsx +100 -0
- package/src/components/studio/nodes/shell/ThemeNodes.tsx +51 -0
- package/src/components/studio/nodes/shell/index.ts +12 -0
- package/src/components/widgets/BroadcastWidget.tsx +93 -0
- package/src/components/widgets/MarketplaceWidget.tsx +298 -0
- package/src/components/widgets/McpToolsWidget.tsx +231 -0
- package/src/components/widgets/OpsDashboard.tsx +59 -0
- package/src/components/widgets/QuickActionsWidget.tsx +60 -0
- package/src/components/widgets/UsageWidget.tsx +112 -0
- package/src/components/widgets/WidgetRenderer.tsx +892 -0
- package/src/components/widgets/WidgetSlotPanel.tsx +213 -0
- package/src/config/IconRegistry.ts +126 -0
- package/src/contexts/NetworkProvider.tsx +162 -0
- package/src/core/AIDirector.ts +71 -0
- package/src/core/EventBus.ts +37 -0
- package/src/core/PluginContext.tsx +141 -0
- package/src/hooks/listeners/useUIStateListener.ts +59 -0
- package/src/hooks/listeners/useWhatsAppListener.ts +110 -0
- package/src/hooks/morphBridge.ts +82 -0
- package/src/hooks/useAIModelSelector.ts +144 -0
- package/src/hooks/useAgentStream.ts +220 -0
- package/src/hooks/useAutoUpdater.ts +89 -0
- package/src/hooks/useBootSequence.ts +20 -0
- package/src/hooks/useExportDSD.ts +53 -0
- package/src/hooks/useFullscreen.ts +35 -0
- package/src/hooks/useGeminiStream.ts +282 -0
- package/src/hooks/useIntentLens.ts +224 -0
- package/src/hooks/useKeyboardShortcuts.ts +69 -0
- package/src/hooks/useLoggerBridge.ts +32 -0
- package/src/hooks/useMcpClient.ts +112 -0
- package/src/hooks/useNexusaiDeploy.ts +118 -0
- package/src/hooks/usePlaybackEngine.ts +21 -0
- package/src/hooks/usePlaygroundCommander.ts +475 -0
- package/src/hooks/usePluginEngine.ts +165 -0
- package/src/hooks/useScreenRecorder.ts +73 -0
- package/src/hooks/useShellKeyboard.ts +40 -0
- package/src/hooks/useShellShortcuts.ts +118 -0
- package/src/hooks/useSoundEffects.ts +35 -0
- package/src/hooks/useStudioConfig.ts +72 -0
- package/src/hooks/useSystemBoot.ts +84 -0
- package/src/hooks/useSystemTelemetry.ts +62 -0
- package/src/index.ts +97 -0
- package/src/lib/debugLogger.ts +80 -0
- package/src/lib/networkInterceptor.ts +100 -0
- package/src/mocks/decido.tsx +41 -0
- package/src/plugins/pluginAPI.ts +190 -0
- package/src/store/McpStore.ts +69 -0
- package/src/store/UpdaterStore.ts +60 -0
- package/src/store/engine.ts +392 -0
- package/src/store/index.ts +4 -0
- package/src/store/layoutPresets.ts +66 -0
- package/src/store/playgroundTypes.ts +98 -0
- package/src/store/useActionTimelineStore.ts +48 -0
- package/src/store/useDebugPanelStore.ts +98 -0
- package/src/store/useDebugProfileStore.ts +130 -0
- package/src/store/useLayoutStore.ts +205 -0
- package/src/store/useMorphInstanceStore.ts +289 -0
- package/src/store/useMorphologyStore.ts +103 -0
- package/src/store/usePlaygroundStore.ts +236 -0
- package/src/store/useShellRegistry.ts +123 -0
- package/src/store/useSuggestionsStore.ts +57 -0
- package/src/store/useThemeStore.ts +399 -0
- package/src/store/useUIComponentStore.ts +179 -0
- package/src/types/DecidoStoryDefinition.ts +43 -0
- package/src/utils/ai/ai-architect.ts +92 -0
- package/src/utils/ai/ai-code.ts +187 -0
- package/src/utils/ai/ai-core.ts +50 -0
- package/src/utils/ai/ai-media.ts +292 -0
- package/src/utils/layoutGraph.ts +67 -0
- package/tsconfig.json +17 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { usePluginStore } from '@decido/plugin-engine';
|
|
3
|
+
import { Trash2, Power, Puzzle, Code, RefreshCcw, DownloadCloud, AlertCircle, LayoutDashboard, ChevronLeft, Info, Box } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export const MarketplaceWidget: React.FC = () => {
|
|
6
|
+
const registry = usePluginStore(state => state.registry);
|
|
7
|
+
const availablePlugins = usePluginStore(state => state.availablePlugins);
|
|
8
|
+
const addRegistryEntry = usePluginStore(state => state.addRegistryEntry);
|
|
9
|
+
const removeRegistryEntry = usePluginStore(state => state.removeRegistryEntry);
|
|
10
|
+
const toggleRegistryEntry = usePluginStore(state => state.toggleRegistryEntry);
|
|
11
|
+
|
|
12
|
+
const [catalog, setCatalog] = useState<any[]>([]);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
14
|
+
const [error, setError] = useState<string | null>(null);
|
|
15
|
+
const [isCatalogView, setIsCatalogView] = useState(false);
|
|
16
|
+
const [selectedPlugin, setSelectedPlugin] = useState<any | null>(null);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const fetchCatalog = async () => {
|
|
20
|
+
try {
|
|
21
|
+
setIsLoading(true);
|
|
22
|
+
const baseUrl = (import.meta as any).env?.VITE_MARKETPLACE_URL || 'http://localhost:5010/api/marketplace';
|
|
23
|
+
const res = await fetch(`${baseUrl}/catalog`);
|
|
24
|
+
if (!res.ok) throw new Error('Catalog request failed');
|
|
25
|
+
const data = await res.json();
|
|
26
|
+
setCatalog(data);
|
|
27
|
+
setError(null);
|
|
28
|
+
} catch (err: any) {
|
|
29
|
+
setError(err.message || 'Failed to connect to Decido Hub');
|
|
30
|
+
} finally {
|
|
31
|
+
setIsLoading(false);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
fetchCatalog();
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const handleInstall = (plugin: any) => {
|
|
38
|
+
addRegistryEntry({
|
|
39
|
+
name: plugin.name,
|
|
40
|
+
url: plugin.url,
|
|
41
|
+
exposedModule: plugin.exposedModule,
|
|
42
|
+
signature: plugin.signature
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const isInstalled = (name: string) => {
|
|
47
|
+
return registry.some(p => p.name === name);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="w-full h-full flex flex-col bg-surface-primary text-text-primary font-sans overflow-hidden border-r border-border-subtle">
|
|
52
|
+
{/* Header */}
|
|
53
|
+
<div className="p-4 border-b border-border-subtle bg-surface-secondary/50 flex justify-between items-center shrink-0">
|
|
54
|
+
<div className="flex items-center gap-2">
|
|
55
|
+
<Puzzle className="w-5 h-5 text-cyan-400" />
|
|
56
|
+
<h2 className="font-bold text-sm tracking-wide">Decido Hub</h2>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="flex bg-surface-primary p-1 rounded-lg border border-border-subtle">
|
|
59
|
+
<button
|
|
60
|
+
onClick={() => { setIsCatalogView(false); setSelectedPlugin(null); }}
|
|
61
|
+
className={`px-3 py-1 text-xs rounded-md transition-colors ${!isCatalogView ? 'bg-cyan-500/20 text-cyan-400' : 'text-text-muted hover:text-text-primary'}`}
|
|
62
|
+
>
|
|
63
|
+
Installed
|
|
64
|
+
</button>
|
|
65
|
+
<button
|
|
66
|
+
onClick={() => { setIsCatalogView(true); setSelectedPlugin(null); }}
|
|
67
|
+
className={`px-3 py-1 flex items-center gap-1 text-xs rounded-md transition-colors ${isCatalogView ? 'bg-cyan-500/20 text-cyan-400' : 'text-text-muted hover:text-text-primary'}`}
|
|
68
|
+
>
|
|
69
|
+
<DownloadCloud className="w-3 h-3" /> Catalog
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Content */}
|
|
75
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
76
|
+
|
|
77
|
+
{!selectedPlugin && !isCatalogView && (
|
|
78
|
+
<div className="bg-cyan-500/10 border border-cyan-500/20 p-3 rounded-lg flex items-start gap-3">
|
|
79
|
+
<RefreshCcw className="w-4 h-4 text-cyan-400 shrink-0 mt-0.5" />
|
|
80
|
+
<p className="text-xs text-cyan-200/70 leading-relaxed">
|
|
81
|
+
Changes to the Federation Registry require a hard reload to apply securely to the OS Kernel context. (cmd+R)
|
|
82
|
+
</p>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{/* DETAILS VIEW */}
|
|
87
|
+
{selectedPlugin && (
|
|
88
|
+
<div className="space-y-4 animate-in fade-in slide-in-from-right-4 duration-300">
|
|
89
|
+
<button
|
|
90
|
+
onClick={() => setSelectedPlugin(null)}
|
|
91
|
+
className="flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary transition-colors mb-2"
|
|
92
|
+
>
|
|
93
|
+
<ChevronLeft className="w-4 h-4" /> Volver
|
|
94
|
+
</button>
|
|
95
|
+
|
|
96
|
+
<div className="flex items-start gap-4 pb-4 border-b border-border-subtle">
|
|
97
|
+
<div className="w-16 h-16 rounded-2xl bg-linear-to-br from-cyan-500/20 to-blue-500/20 border border-cyan-500/30 flex items-center justify-center shrink-0">
|
|
98
|
+
<Box className="w-8 h-8 text-cyan-400" />
|
|
99
|
+
</div>
|
|
100
|
+
<div className="flex-1">
|
|
101
|
+
<h2 className="text-xl font-bold text-text-primary mb-1">{selectedPlugin.title || selectedPlugin.name}</h2>
|
|
102
|
+
<p className="text-sm text-cyan-400 font-mono mb-2">{selectedPlugin.author || 'Decido Publisher'} • v{selectedPlugin.version || '1.0.0'}</p>
|
|
103
|
+
|
|
104
|
+
{isInstalled(selectedPlugin.name) ? (
|
|
105
|
+
<div className="flex items-center gap-2">
|
|
106
|
+
<span className="bg-emerald-500/10 text-emerald-400 text-xs px-3 py-1.5 rounded-full font-medium flex items-center gap-1">
|
|
107
|
+
<div className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></div>
|
|
108
|
+
Installed
|
|
109
|
+
</span>
|
|
110
|
+
<button
|
|
111
|
+
onClick={() => removeRegistryEntry(selectedPlugin.name)}
|
|
112
|
+
className="bg-surface-tertiary hover:bg-red-500/20 hover:text-red-400 text-text-primary text-xs px-3 py-1.5 rounded transition-colors"
|
|
113
|
+
>
|
|
114
|
+
Uninstall
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
) : (
|
|
118
|
+
<button
|
|
119
|
+
onClick={() => handleInstall(selectedPlugin)}
|
|
120
|
+
className="bg-cyan-600 hover:bg-cyan-500 text-text-primary text-sm px-6 py-2 rounded-lg transition-colors shadow-lg shadow-cyan-500/20 font-medium"
|
|
121
|
+
>
|
|
122
|
+
Install Plugin
|
|
123
|
+
</button>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="space-y-6 pt-2">
|
|
129
|
+
<div>
|
|
130
|
+
<h3 className="text-sm font-semibold text-text-primary mb-2 flex items-center gap-2">
|
|
131
|
+
<Info className="w-4 h-4 text-text-muted" /> General Description
|
|
132
|
+
</h3>
|
|
133
|
+
<p className="text-sm text-text-secondary leading-relaxed bg-surface-glass p-4 rounded-xl border border-border-subtle/50">
|
|
134
|
+
{selectedPlugin.description || 'No README.md currently available for this plugin. This is a technical expansion module for Decido OS.'}
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{/* Renderizar CortexManifest Data si está instalado y en memoria */}
|
|
139
|
+
{(() => {
|
|
140
|
+
const manifest = availablePlugins.find(p => p.id === selectedPlugin.name || p.name === selectedPlugin.name);
|
|
141
|
+
if (!manifest) return (
|
|
142
|
+
<div className="text-xs text-text-muted italic bg-surface-glass p-4 rounded-xl border border-border-subtle/30 text-center">
|
|
143
|
+
Install and activate this plugin to view its provided Architecture bounds (Widgets & Intents).
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className="space-y-4">
|
|
149
|
+
{manifest.widgets && manifest.widgets.length > 0 && (
|
|
150
|
+
<div>
|
|
151
|
+
<h3 className="text-sm font-semibold text-text-primary mb-2 flex items-center gap-2">
|
|
152
|
+
<LayoutDashboard className="w-4 h-4 text-cyan-400" /> Provisioned Widgets
|
|
153
|
+
</h3>
|
|
154
|
+
<div className="grid gap-2">
|
|
155
|
+
{manifest.widgets.map((w: any) => (
|
|
156
|
+
<div key={w.id} className="bg-surface-secondary border border-border-subtle p-3 rounded-lg flex items-center justify-between">
|
|
157
|
+
<div className="flex flex-col">
|
|
158
|
+
<span className="text-sm font-medium text-text-primary">{w.name}</span>
|
|
159
|
+
<span className="text-xs text-text-muted font-mono">ID: {w.id}</span>
|
|
160
|
+
</div>
|
|
161
|
+
<span className="text-xs bg-surface-primary text-emerald-400/80 border border-emerald-500/20 px-2 py-1 rounded">
|
|
162
|
+
Zone: {w.defaultZone}
|
|
163
|
+
</span>
|
|
164
|
+
</div>
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{manifest.intents && manifest.intents.length > 0 && (
|
|
171
|
+
<div>
|
|
172
|
+
<h3 className="text-sm font-semibold text-text-primary mb-2 flex items-center gap-2">
|
|
173
|
+
<Code className="w-4 h-4 text-purple-400" /> Registered AI Intents
|
|
174
|
+
</h3>
|
|
175
|
+
<div className="bg-surface-secondary border border-border-subtle rounded-lg p-3 flex flex-wrap gap-2">
|
|
176
|
+
{manifest.intents.map((intent: string) => (
|
|
177
|
+
<span key={intent} className="bg-surface-primary text-purple-400/90 text-xs px-2 py-1 rounded border border-purple-500/20 font-mono">
|
|
178
|
+
"{intent}"
|
|
179
|
+
</span>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
})()}
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{/* CATALOG VIEW */}
|
|
192
|
+
{!selectedPlugin && isCatalogView && (
|
|
193
|
+
<div className="space-y-4">
|
|
194
|
+
{isLoading && <div className="text-xs text-text-muted text-center py-8 animate-pulse">Connecting to Decido Hub...</div>}
|
|
195
|
+
{error && (
|
|
196
|
+
<div className="bg-red-500/10 border border-red-500/20 p-4 rounded-xl flex items-center gap-3 text-red-400 text-xs">
|
|
197
|
+
<AlertCircle className="w-4 h-4" /> {error}
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
{!isLoading && !error && catalog.map(plugin => {
|
|
201
|
+
const installed = isInstalled(plugin.name);
|
|
202
|
+
return (
|
|
203
|
+
<div key={plugin.id} onClick={() => setSelectedPlugin(plugin)} className="bg-surface-secondary border border-border-subtle hover:border-cyan-500/30 focus:border-cyan-500/30 cursor-pointer p-4 rounded-xl flex flex-col gap-3 transition-colors group">
|
|
204
|
+
<div className="flex justify-between items-start">
|
|
205
|
+
<div className="flex gap-3 items-center">
|
|
206
|
+
<div className="w-10 h-10 rounded-lg bg-surface-primary border border-border-subtle flex items-center justify-center group-hover:border-cyan-500/30 transition-colors">
|
|
207
|
+
<Box className="w-5 h-5 text-text-muted group-hover:text-cyan-400 transition-colors" />
|
|
208
|
+
</div>
|
|
209
|
+
<div>
|
|
210
|
+
<h3 className="font-bold text-sm text-text-primary group-hover:text-cyan-300 transition-colors">{plugin.title}</h3>
|
|
211
|
+
<p className="text-xs text-text-muted font-mono mt-0.5">{plugin.author} • v{plugin.version}</p>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
{installed && (
|
|
215
|
+
<span className="bg-emerald-500/10 text-emerald-400 text-[10px] px-2 py-1 rounded-full uppercase font-bold tracking-wider">Installed</span>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
<p className="text-xs text-text-secondary leading-relaxed border-l-2 border-border-subtle pl-2 line-clamp-2">
|
|
219
|
+
{plugin.description}
|
|
220
|
+
</p>
|
|
221
|
+
<div className="flex gap-2 mt-1">
|
|
222
|
+
<button className="text-[10px] text-text-secondary hover:text-text-primary bg-surface-glass px-2 py-1 rounded uppercase font-bold tracking-wide">
|
|
223
|
+
Details
|
|
224
|
+
</button>
|
|
225
|
+
{plugin.tags?.map((tag: string) => (
|
|
226
|
+
<span key={tag} className="text-[10px] bg-surface-primary text-text-muted px-2 py-1 rounded-full border border-border-subtle">
|
|
227
|
+
{tag}
|
|
228
|
+
</span>
|
|
229
|
+
))}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
})}
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
|
|
237
|
+
{/* INSTALLED VIEW */}
|
|
238
|
+
{!selectedPlugin && !isCatalogView && (
|
|
239
|
+
<div className="space-y-3">
|
|
240
|
+
{registry.map(plugin => (
|
|
241
|
+
<div key={plugin.name} onClick={() => setSelectedPlugin(plugin)} className={`bg-surface-secondary border ${plugin.enabled ? 'border-border-default/50' : 'border-border-subtle opacity-60'} hover:border-cyan-500/30 cursor-pointer p-4 rounded-xl transition-all group`}>
|
|
242
|
+
<div className="flex justify-between items-start mb-2">
|
|
243
|
+
<div className="flex items-center gap-3">
|
|
244
|
+
<div className="w-10 h-10 rounded-lg bg-surface-primary border border-border-subtle flex items-center justify-center group-hover:border-cyan-500/30 transition-colors">
|
|
245
|
+
<Box className="w-5 h-5 text-text-muted group-hover:text-cyan-400 transition-colors" />
|
|
246
|
+
</div>
|
|
247
|
+
<div>
|
|
248
|
+
<div className="flex items-center gap-2">
|
|
249
|
+
<div className={`w-2 h-2 rounded-full ${plugin.enabled ? 'bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.5)]' : 'bg-surface-elevated'}`}></div>
|
|
250
|
+
<h3 className="font-bold text-sm tracking-wide text-text-primary group-hover:text-cyan-300 transition-colors">{plugin.name}</h3>
|
|
251
|
+
</div>
|
|
252
|
+
<div className="text-xs font-mono text-text-muted mt-1">
|
|
253
|
+
module: <span className="text-cyan-300/80">{plugin.exposedModule}</span>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
<div className="flex items-center gap-1" onClick={e => e.stopPropagation()}>
|
|
258
|
+
<button
|
|
259
|
+
onClick={() => toggleRegistryEntry(plugin.name)}
|
|
260
|
+
className={`w-7 h-7 flex items-center justify-center rounded transition-colors ${plugin.enabled ? 'text-emerald-400 hover:bg-emerald-400/10' : 'text-text-muted hover:bg-surface-tertiary'}`}
|
|
261
|
+
title={plugin.enabled ? 'Disable Plugin' : 'Enable Plugin'}
|
|
262
|
+
>
|
|
263
|
+
<Power className="w-4 h-4" />
|
|
264
|
+
</button>
|
|
265
|
+
<button
|
|
266
|
+
onClick={() => removeRegistryEntry(plugin.name)}
|
|
267
|
+
className="w-7 h-7 flex items-center justify-center rounded transition-colors text-text-muted hover:bg-red-500/20 hover:text-red-400"
|
|
268
|
+
title="Uninstall Plugin"
|
|
269
|
+
>
|
|
270
|
+
<Trash2 className="w-4 h-4" />
|
|
271
|
+
</button>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<div className="bg-surface-glass p-2 border border-border-subtle/50 rounded flex gap-2 items-center mt-3">
|
|
276
|
+
<Code className="w-3.5 h-3.5 text-text-muted shrink-0" />
|
|
277
|
+
<span className="font-mono text-[10px] text-text-secondary truncate" title={plugin.url}>
|
|
278
|
+
{plugin.url}
|
|
279
|
+
</span>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
))}
|
|
283
|
+
|
|
284
|
+
{registry.length === 0 && (
|
|
285
|
+
<div className="text-center py-12 text-text-muted text-sm">
|
|
286
|
+
<Puzzle className="w-8 h-8 mx-auto text-text-muted mb-3" />
|
|
287
|
+
No local modules installed.<br />
|
|
288
|
+
<button onClick={() => setIsCatalogView(true)} className="mt-3 text-cyan-400 hover:text-cyan-300 underline underline-offset-4 decoration-zinc-800">
|
|
289
|
+
Browse Decido Hub
|
|
290
|
+
</button>
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
</div>
|
|
294
|
+
)}
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { kernel } from '@decido/kernel-bridge';
|
|
3
|
+
import { useMcpStore } from '../../store/McpStore';
|
|
4
|
+
|
|
5
|
+
export const McpToolsWidget = () => {
|
|
6
|
+
const { mcpServers, setMcpServerStatus } = useMcpStore();
|
|
7
|
+
const [tools, setTools] = useState<any[]>([]);
|
|
8
|
+
const [isLoading, setIsLoading] = useState<string | null>(null);
|
|
9
|
+
|
|
10
|
+
// Playground state
|
|
11
|
+
const [activePlayground, setActivePlayground] = useState<string | null>(null);
|
|
12
|
+
const [playgroundArgs, setPlaygroundArgs] = useState<string>('{}');
|
|
13
|
+
const [playgroundOutput, setPlaygroundOutput] = useState<string | null>(null);
|
|
14
|
+
const [isPlaygroundLoading, setIsPlaygroundLoading] = useState(false);
|
|
15
|
+
|
|
16
|
+
const fetchTools = async () => {
|
|
17
|
+
try {
|
|
18
|
+
const data = await kernel.execute<any[]>('list_mcp_tools');
|
|
19
|
+
setTools(data || []);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error("Failed to fetch MCP tools", e);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
fetchTools();
|
|
27
|
+
// Option to refresh periodically or manually
|
|
28
|
+
const interval = setInterval(fetchTools, 10000);
|
|
29
|
+
return () => clearInterval(interval);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const handleConnect = async (serverName: string, serverCommand: string, serverArgs: string) => {
|
|
33
|
+
setIsLoading(serverName);
|
|
34
|
+
try {
|
|
35
|
+
const parsedArgs = serverArgs.split(' ').filter(a => a.trim() !== '');
|
|
36
|
+
await kernel.execute('connect_mcp_server', {
|
|
37
|
+
name: serverName,
|
|
38
|
+
command: serverCommand,
|
|
39
|
+
args: parsedArgs
|
|
40
|
+
});
|
|
41
|
+
kernel.notify('MCP Connected', `Server ${serverName} connected successfully.`);
|
|
42
|
+
setMcpServerStatus(serverName, true);
|
|
43
|
+
await fetchTools();
|
|
44
|
+
} catch (e: any) {
|
|
45
|
+
kernel.notify('MCP Connection Failed', e.toString());
|
|
46
|
+
} finally {
|
|
47
|
+
setIsLoading(null);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleDisconnect = async (serverName: string) => {
|
|
52
|
+
setIsLoading(serverName);
|
|
53
|
+
try {
|
|
54
|
+
await kernel.execute('disconnect_mcp_server', { name: serverName });
|
|
55
|
+
kernel.notify('MCP Disconnected', `Server ${serverName} disconnected.`);
|
|
56
|
+
setMcpServerStatus(serverName, false);
|
|
57
|
+
await fetchTools();
|
|
58
|
+
} catch (e: any) {
|
|
59
|
+
kernel.notify('Disconnect Failed', e.toString());
|
|
60
|
+
} finally {
|
|
61
|
+
setIsLoading(null);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleOpenPlayground = (tool: any) => {
|
|
66
|
+
if (activePlayground === tool.name) {
|
|
67
|
+
setActivePlayground(null);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
setActivePlayground(tool.name);
|
|
71
|
+
setPlaygroundOutput(null);
|
|
72
|
+
|
|
73
|
+
// Generate default args based on schema
|
|
74
|
+
let defaultArgs: any = {};
|
|
75
|
+
if (tool.inputSchema?.properties) {
|
|
76
|
+
Object.keys(tool.inputSchema.properties).forEach(key => {
|
|
77
|
+
const prop = tool.inputSchema.properties[key];
|
|
78
|
+
if (prop.type === 'string') defaultArgs[key] = "";
|
|
79
|
+
else if (prop.type === 'number') defaultArgs[key] = 0;
|
|
80
|
+
else if (prop.type === 'boolean') defaultArgs[key] = false;
|
|
81
|
+
else if (prop.type === 'object') defaultArgs[key] = {};
|
|
82
|
+
else if (prop.type === 'array') defaultArgs[key] = [];
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
setPlaygroundArgs(JSON.stringify(defaultArgs, null, 2));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleRunPlayground = async (toolName: string) => {
|
|
89
|
+
setIsPlaygroundLoading(true);
|
|
90
|
+
setPlaygroundOutput(null);
|
|
91
|
+
try {
|
|
92
|
+
let parsedArgs = {};
|
|
93
|
+
try {
|
|
94
|
+
// Sanitize macOS smart quotes
|
|
95
|
+
const sanitizedJson = playgroundArgs
|
|
96
|
+
.replace(/[\u2018\u2019]/g, "'")
|
|
97
|
+
.replace(/[\u201C\u201D]/g, '"');
|
|
98
|
+
parsedArgs = JSON.parse(sanitizedJson);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
setPlaygroundOutput("Invalid JSON format in arguments.");
|
|
101
|
+
setIsPlaygroundLoading(false);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const parts = toolName.split(':');
|
|
106
|
+
let srvName = '';
|
|
107
|
+
let tName = toolName;
|
|
108
|
+
|
|
109
|
+
if (parts.length >= 3 && parts[0] === 'mcp') {
|
|
110
|
+
srvName = parts[1];
|
|
111
|
+
tName = parts.slice(2).join(':');
|
|
112
|
+
} else if (parts.length === 2 && parts[0] !== 'mcp') {
|
|
113
|
+
srvName = parts[0];
|
|
114
|
+
tName = parts[1];
|
|
115
|
+
} else {
|
|
116
|
+
srvName = parts[0] || 'frubeala-mcp';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const res = await kernel.execute<any>('call_mcp_tool', {
|
|
120
|
+
serverName: srvName,
|
|
121
|
+
toolName: tName,
|
|
122
|
+
arguments: parsedArgs
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (res && res.content) {
|
|
126
|
+
const texts = res.content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('\\n');
|
|
127
|
+
setPlaygroundOutput(texts);
|
|
128
|
+
} else {
|
|
129
|
+
setPlaygroundOutput(JSON.stringify(res, null, 2));
|
|
130
|
+
}
|
|
131
|
+
} catch (e: any) {
|
|
132
|
+
setPlaygroundOutput(`Error: ${e.toString()}`);
|
|
133
|
+
} finally {
|
|
134
|
+
setIsPlaygroundLoading(false);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div className="space-y-4">
|
|
140
|
+
<div className="space-y-2">
|
|
141
|
+
<h4 className="text-xs font-bold text-text-secondary uppercase tracking-widest mb-3">Conexiones Activas</h4>
|
|
142
|
+
{mcpServers.length === 0 && (
|
|
143
|
+
<div className="text-text-muted text-xs text-center py-4 bg-surface-glass rounded-xl border border-border-subtle">
|
|
144
|
+
Sin servidores conectados.
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
{mcpServers.map((server, i) => (
|
|
148
|
+
<div key={i} className="flex flex-col p-3 bg-surface-glass rounded-xl border border-border-subtle">
|
|
149
|
+
<div className="flex items-center justify-between">
|
|
150
|
+
<span className="text-sm font-medium text-text-primary">{server.name}</span>
|
|
151
|
+
{server.isConnected ? (
|
|
152
|
+
<button
|
|
153
|
+
onClick={() => handleDisconnect(server.name)}
|
|
154
|
+
disabled={isLoading === server.name}
|
|
155
|
+
className="px-2 py-1 bg-red-900/30 hover:bg-red-900/60 text-red-400 border border-red-500/30 rounded text-xs transition-colors"
|
|
156
|
+
>
|
|
157
|
+
{isLoading === server.name ? 'Disconnecting...' : 'Disconnect'}
|
|
158
|
+
</button>
|
|
159
|
+
) : (
|
|
160
|
+
<button
|
|
161
|
+
onClick={() => handleConnect(server.name, server.command, server.args)}
|
|
162
|
+
disabled={isLoading === server.name}
|
|
163
|
+
className="px-2 py-1 bg-emerald-900/30 hover:bg-emerald-900/60 text-emerald-400 border border-emerald-500/30 rounded text-xs transition-colors"
|
|
164
|
+
>
|
|
165
|
+
{isLoading === server.name ? 'Connecting...' : 'Connect'}
|
|
166
|
+
</button>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
<div className="text-[10px] text-text-muted font-mono mt-1">{server.command} {server.args}</div>
|
|
170
|
+
</div>
|
|
171
|
+
))}
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div className="space-y-2 mt-6">
|
|
175
|
+
<h4 className="text-xs font-bold text-text-secondary uppercase tracking-widest mb-3 border-t border-border-subtle pt-4">Tools Playground</h4>
|
|
176
|
+
{tools.length === 0 && (
|
|
177
|
+
<div className="text-text-muted text-xs text-center py-4 bg-surface-glass rounded-xl border border-border-subtle">
|
|
178
|
+
No hay herramientas disponibles. Asegúrate de que un servidor MCP esté online.
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
<div className="space-y-2 pb-10">
|
|
182
|
+
{tools.map((tool: any) => (
|
|
183
|
+
<div key={tool.name} className="bg-surface-overlay border border-border-subtle rounded-xl p-3 overflow-hidden">
|
|
184
|
+
<div className="flex items-center justify-between">
|
|
185
|
+
<div>
|
|
186
|
+
<h4 className="text-cyan-400 font-bold text-sm">{tool.name.split(':').pop() || tool.name}</h4>
|
|
187
|
+
<p className="text-xs text-text-muted line-clamp-1">{tool.description}</p>
|
|
188
|
+
</div>
|
|
189
|
+
<button
|
|
190
|
+
onClick={() => handleOpenPlayground(tool)}
|
|
191
|
+
className="px-3 py-1 bg-cyan-900/30 hover:bg-cyan-900/60 text-cyan-400 border border-cyan-500/30 rounded text-xs transition-colors"
|
|
192
|
+
>
|
|
193
|
+
Test
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{activePlayground === tool.name && (
|
|
198
|
+
<div className="mt-4 border-t border-border-default pt-4 flex flex-col gap-3 animate-in slide-in-from-top-4 duration-200">
|
|
199
|
+
<div>
|
|
200
|
+
<label className="text-[10px] text-text-muted font-bold uppercase tracking-wider mb-1 block">Arguments (JSON)</label>
|
|
201
|
+
<textarea
|
|
202
|
+
value={playgroundArgs}
|
|
203
|
+
onChange={e => setPlaygroundArgs(e.target.value)}
|
|
204
|
+
className="w-full h-24 bg-surface-overlay border border-border-default rounded p-2 text-cyan-300 font-mono text-xs focus:border-cyan-400 outline-hidden resize-none"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
<button
|
|
208
|
+
onClick={() => handleRunPlayground(tool.name)}
|
|
209
|
+
disabled={isPlaygroundLoading}
|
|
210
|
+
className="w-full py-2 bg-linear-to-r from-cyan-600 to-indigo-600 text-text-primary rounded font-bold text-xs hover:from-cyan-500 hover:to-indigo-500 transition-all shadow-[0_0_15px_rgba(6,182,212,0.3)] disabled:opacity-50"
|
|
211
|
+
>
|
|
212
|
+
{isPlaygroundLoading ? 'Executing...' : 'Execute Tool'}
|
|
213
|
+
</button>
|
|
214
|
+
|
|
215
|
+
{playgroundOutput && (
|
|
216
|
+
<div className="mt-2">
|
|
217
|
+
<label className="text-[10px] text-text-muted font-bold uppercase tracking-wider mb-1 block">Output</label>
|
|
218
|
+
<div className="w-full max-h-48 overflow-y-auto bg-surface-primary border border-border-default rounded p-2 text-emerald-400 font-mono text-xs whitespace-pre-wrap">
|
|
219
|
+
{playgroundOutput}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BroadcastWidget } from './BroadcastWidget';
|
|
2
|
+
import { QuickActionsWidget } from './QuickActionsWidget';
|
|
3
|
+
import { UsageWidget } from './UsageWidget';
|
|
4
|
+
import { Activity, Server, Users } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export const OpsDashboard = () => {
|
|
7
|
+
return (
|
|
8
|
+
<div className="min-h-screen bg-surface-primary p-8 text-text-primary">
|
|
9
|
+
<header className="mb-8 flex items-center justify-between">
|
|
10
|
+
<div>
|
|
11
|
+
<h1 className="text-3xl font-black text-transparent bg-clip-text bg-linear-to-r from-purple-400 to-pink-600">
|
|
12
|
+
MISSION CONTROL
|
|
13
|
+
</h1>
|
|
14
|
+
<p className="text-text-muted font-mono text-sm mt-1">System Operations & Real-time Command</p>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="flex gap-4">
|
|
17
|
+
<div className="bg-surface-secondary px-4 py-2 rounded-lg border border-border-subtle flex items-center gap-3">
|
|
18
|
+
<Users size={16} className="text-blue-400" />
|
|
19
|
+
<span className="font-mono font-bold">12 Active</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div className="bg-surface-secondary px-4 py-2 rounded-lg border border-border-subtle flex items-center gap-3">
|
|
22
|
+
<Server size={16} className="text-emerald-400" />
|
|
23
|
+
<span className="font-mono font-bold">Healthy</span>
|
|
24
|
+
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</header>
|
|
28
|
+
|
|
29
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
30
|
+
{/* Column 1: Comms */}
|
|
31
|
+
<div className="space-y-6">
|
|
32
|
+
<BroadcastWidget />
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
{/* Column 2: Safety & Actions */}
|
|
36
|
+
<div className="space-y-6">
|
|
37
|
+
<QuickActionsWidget />
|
|
38
|
+
|
|
39
|
+
{/* Cost & Resource Usage */}
|
|
40
|
+
<UsageWidget />
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
{/* Column 3: Live Feed */}
|
|
44
|
+
<div className="bg-surface-secondary rounded-xl border border-border-subtle p-6 font-mono text-xs h-[500px] flex flex-col">
|
|
45
|
+
<h3 className="font-bold text-text-secondary mb-4 flex items-center gap-2">
|
|
46
|
+
<Activity size={16} /> Live Event Stream
|
|
47
|
+
</h3>
|
|
48
|
+
<div className="flex-1 overflow-y-auto space-y-2 text-text-muted">
|
|
49
|
+
<p><span className="text-emerald-500">[10:00:01]</span> System Init</p>
|
|
50
|
+
<p><span className="text-blue-500">[10:05:23]</span> User 'Julio' connected</p>
|
|
51
|
+
<p><span className="text-yellow-500">[10:12:45]</span> AI Graph Analysis triggered</p>
|
|
52
|
+
<p><span className="text-purple-500">[10:15:00]</span> Order #1234 staged to PRODUCTION</p>
|
|
53
|
+
<div className="opacity-30 italic mt-4">Listening for events...</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { RefreshCw, Zap, ShieldAlert } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const QuickActionsWidget = () => {
|
|
5
|
+
|
|
6
|
+
const triggerAction = async (action: string) => {
|
|
7
|
+
if (!confirm('Are you sure you want to execute this global action?')) return;
|
|
8
|
+
try {
|
|
9
|
+
const baseUrl = (import.meta as any).env?.VITE_API_URL || 'http://localhost:3001/api';
|
|
10
|
+
if (action === 'FORCE_RELOAD') {
|
|
11
|
+
await fetch(`${baseUrl}/admin/broadcast`, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: { 'Content-Type': 'application/json' },
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
message: 'System Integrity Check: Reloading...',
|
|
16
|
+
type: 'WARNING',
|
|
17
|
+
action: 'RELOAD'
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
} else if (action === 'CLEAR_ALERTS') {
|
|
21
|
+
await fetch(`${baseUrl}/admin/broadcast`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
message: 'System Normal',
|
|
26
|
+
type: 'SUCCESS'
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
alert('Action Failed');
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="bg-surface-tertiary rounded-xl border border-border-default p-6 shadow-lg">
|
|
37
|
+
<h3 className="text-lg font-bold text-text-primary mb-4 flex items-center gap-2">
|
|
38
|
+
<ShieldAlert size={20} className="text-red-400" /> Panic Room
|
|
39
|
+
</h3>
|
|
40
|
+
|
|
41
|
+
<div className="grid grid-cols-2 gap-3">
|
|
42
|
+
<button
|
|
43
|
+
onClick={() => triggerAction('FORCE_RELOAD')}
|
|
44
|
+
className="flex flex-col items-center justify-center p-4 bg-red-900/20 hover:bg-red-900/40 border border-red-800/50 rounded-lg transition-colors group"
|
|
45
|
+
>
|
|
46
|
+
<RefreshCw size={24} className="text-red-500 mb-2 group-hover:rotate-180 transition-transform duration-500" />
|
|
47
|
+
<span className="text-xs font-bold text-red-400">Force Reload All</span>
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
<button
|
|
51
|
+
onClick={() => triggerAction('CLEAR_ALERTS')}
|
|
52
|
+
className="flex flex-col items-center justify-center p-4 bg-emerald-900/20 hover:bg-emerald-900/40 border border-emerald-800/50 rounded-lg transition-colors"
|
|
53
|
+
>
|
|
54
|
+
<Zap size={24} className="text-emerald-500 mb-2" />
|
|
55
|
+
<span className="text-xs font-bold text-emerald-400">System Normal</span>
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|