@decido/plugin-dev-console 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.

Potentially problematic release.


This version of @decido/plugin-dev-console might be problematic. Click here for more details.

@@ -0,0 +1,34 @@
1
+ import { kernel } from '@decido/kernel-bridge';
2
+
3
+ /**
4
+ * Creates a secure and limited version of the kernel for AI-generated components.
5
+ */
6
+ export const createSanitizedKernel = (missionTitle: string) => {
7
+ return new Proxy(kernel, {
8
+ get(target, prop) {
9
+ // TOTAL BLOCK of dangerous commands
10
+ if (prop === 'execute') {
11
+ return async (command: string, args: any) => {
12
+ const forbidden = ['run_command', 'write_file', 'kill_agent', 'initiate_singularity'];
13
+ const essential = ['notify', 'execute', 'send_message', 'stop_live_monitor'];
14
+
15
+ if (forbidden.includes(command) || !essential.includes(command)) {
16
+ console.error(`[Security Shield] 🛑 Bloqueado intento de ejecutar '${command}' desde UI Generativa: ${missionTitle}`);
17
+ kernel.notify('⚠️ Alerta de Seguridad', `El componente '${missionTitle}' intentó ejecutar un comando no autorizado: ${command}`);
18
+ throw new Error(`Permiso denegado para: ${command}. En Sandbox restrictivo.`);
19
+ }
20
+
21
+ // Allow read data or safe notifications
22
+ return target.execute(command, args);
23
+ };
24
+ }
25
+
26
+ // Block access to bridge internal config
27
+ if (prop === 'config' || prop === 'internals') {
28
+ return undefined;
29
+ }
30
+
31
+ return (target as any)[prop];
32
+ }
33
+ });
34
+ };
package/src/index.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { definePlugin } from '@decido/sdk';
2
+ import type { DecidoApp } from '@decido/sdk';
3
+ import { DeveloperPlaygroundWidget } from './widgets/DeveloperPlaygroundWidget';
4
+ import { ForgeTerminalWidget } from './widgets/ForgeTerminalWidget';
5
+ import { SDKShowcaseWidget } from './widgets/SDKShowcaseWidget';
6
+ import { PlaygroundEngine } from './engine/PlaygroundEngine';
7
+
8
+ // Re-export core utilities for consumers (App.tsx, etc.)
9
+ export { PlaygroundEngine, ForgeTerminalWidget, SDKShowcaseWidget };
10
+
11
+ // ── Developer Playground ──
12
+
13
+ export const DeveloperPlaygroundPlugin = definePlugin({
14
+ manifest: {
15
+ id: 'decido-developer-playground',
16
+ name: 'Developer Playground',
17
+ version: '2.0.0',
18
+ description: 'Entorno de pruebas y tutorial interactivo para desarrolladores de Decido OS',
19
+ author: 'Decido',
20
+ icon: 'code',
21
+ permissions: [],
22
+ intents: ['open-developer-playground'],
23
+ widgets: [
24
+ {
25
+ id: 'developer-playground',
26
+ name: 'Developer Playground',
27
+ defaultZone: 'ide-editor',
28
+ component: DeveloperPlaygroundWidget,
29
+ },
30
+ {
31
+ id: 'sdk-showcase',
32
+ name: 'SDK Hooks Showcase',
33
+ defaultZone: 'ide-editor',
34
+ component: SDKShowcaseWidget,
35
+ },
36
+ ],
37
+ blueprints: [],
38
+ },
39
+
40
+ onMount: (app: DecidoApp) => {
41
+ app.logger.log('Developer Playground ready');
42
+ },
43
+
44
+ onUnmount: () => {
45
+ console.log('[DevPlayground] Cleaned up.');
46
+ },
47
+ });
48
+
49
+ // ── The Forge (Terminal Lab) ──
50
+
51
+ let forgeUnsubscribers: (() => void)[] = [];
52
+
53
+ export const PlaygroundPlugin = definePlugin({
54
+ manifest: {
55
+ id: 'macia-playground',
56
+ name: 'The Forge',
57
+ version: '2.0.0',
58
+ description: 'Laboratorio de pruebas para Inferencia MLX y Enigo',
59
+ author: 'Decido',
60
+ icon: 'terminal',
61
+ permissions: [],
62
+ intents: ['open-forge', 'quick-dispatch'],
63
+ widgets: [
64
+ {
65
+ id: 'forge-terminal',
66
+ name: 'Terminal de Misiones',
67
+ defaultZone: 'ide-panel',
68
+ component: ForgeTerminalWidget,
69
+ },
70
+ ],
71
+ blueprints: [],
72
+ },
73
+
74
+ onMount: (app: DecidoApp) => {
75
+ app.logger.log('The Forge ready');
76
+
77
+ const unsub = app.subscribeToEvent('intent:quick-dispatch', (payload: any) => {
78
+ const intentText = payload?.text || payload?.matches?.[0] || '';
79
+ PlaygroundEngine.dispatchMission(intentText);
80
+ });
81
+
82
+ forgeUnsubscribers = [unsub];
83
+ },
84
+
85
+ onUnmount: () => {
86
+ forgeUnsubscribers.forEach(u => u());
87
+ forgeUnsubscribers = [];
88
+ console.log('[Forge] Cleaned up.');
89
+ },
90
+ });
91
+
92
+ // ── Node DevOps Bridge ──
93
+
94
+ let devOpsUnsubscribers: (() => void)[] = [];
95
+
96
+ export const NodeDevOpsPlugin = definePlugin({
97
+ manifest: {
98
+ id: 'macia-node-devops',
99
+ name: 'Node DevOps Bridge',
100
+ version: '2.0.0',
101
+ description: 'Enruta comandos de consola y archivos al Agente Node especializado vĂ­a Redis.',
102
+ author: 'Decido',
103
+ icon: 'server',
104
+ permissions: ['shell:execute'],
105
+ intents: ['node-filesystem'],
106
+ widgets: [],
107
+ blueprints: [],
108
+ },
109
+
110
+ onMount: (app: DecidoApp) => {
111
+ app.logger.log('Node DevOps Bridge ready');
112
+
113
+ const unsub = app.subscribeToEvent('intent:node-filesystem', async (payload: any) => {
114
+ const query = payload?.text || payload?.input || '';
115
+ app.logger.log(`Delegando a Node Agent: ${query}`);
116
+
117
+ await app.notify('Delegando a Experto Node', 'Enviando directiva a través del enjambre Redis...');
118
+
119
+ await app.callKernel('send_message', {
120
+ from: 'System_UI',
121
+ to: 'devops-expert',
122
+ intent: 'execute_task',
123
+ payload: query,
124
+ priority: 'high',
125
+ });
126
+ });
127
+
128
+ devOpsUnsubscribers = [unsub];
129
+ },
130
+
131
+ onUnmount: () => {
132
+ devOpsUnsubscribers.forEach(u => u());
133
+ devOpsUnsubscribers = [];
134
+ console.log('[DevOps Bridge] Cleaned up.');
135
+ },
136
+
137
+ onError: (error, phase) => {
138
+ console.error(`[DevOps Bridge] Error during ${phase}:`, error);
139
+ },
140
+ });
141
+
142
+ export default DeveloperPlaygroundPlugin;
@@ -0,0 +1,211 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { Play, Terminal, CheckCircle2, RefreshCw, ArrowRight } from 'lucide-react';
4
+ import {
5
+ ReactFlow,
6
+ Background,
7
+ MarkerType,
8
+ useNodesState,
9
+ useEdgesState,
10
+ } from '@xyflow/react';
11
+ import '@xyflow/react/dist/style.css';
12
+
13
+ // Import extracted components and data
14
+ import { OSNode } from '../components/ui/OSNode';
15
+ import { CodeSnippet } from '../components/ui/CodeSnippet';
16
+ import { TUTORIAL_STEPS, Sparkles } from '../data/tutorialSteps';
17
+
18
+ const nodeTypes = { osNode: OSNode };
19
+
20
+ export const DeveloperPlaygroundWidget = () => {
21
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
22
+ const [isExecuting, setIsExecuting] = useState(false);
23
+ const [executionFinished, setExecutionFinished] = useState(false);
24
+
25
+ const step = TUTORIAL_STEPS[currentStepIndex];
26
+
27
+ const [nodes, setNodes, onNodesChange] = useNodesState(step.nodes);
28
+ const [edges, setEdges, onEdgesChange] = useEdgesState(step.edges);
29
+
30
+ useEffect(() => {
31
+ // Reset flow when step changes
32
+ setNodes(step.nodes);
33
+ setEdges(step.edges);
34
+ setIsExecuting(false);
35
+ setExecutionFinished(false);
36
+ }, [currentStepIndex, step.nodes, step.edges, setNodes, setEdges]);
37
+
38
+ const handleExecute = () => {
39
+ if (isExecuting || executionFinished) return;
40
+ setIsExecuting(true);
41
+
42
+ // Timeline Animation Simulation
43
+ setTimeout(() => {
44
+ // Modify ReactFlow elements to show "active" state based on declarative data
45
+ setNodes((nds) =>
46
+ nds.map((n) => {
47
+ const update = step.nodeUpdatesOnExecute?.find(u => u.id === n.id);
48
+ if (update) {
49
+ return { ...n, data: { ...n.data, isActive: update.isActive, status: update.status } };
50
+ }
51
+ return n;
52
+ })
53
+ );
54
+
55
+ setEdges((eds) =>
56
+ eds.map((e) => ({
57
+ ...e,
58
+ animated: true,
59
+ style: { stroke: '#06b6d4', strokeWidth: 3 },
60
+ markerEnd: { type: MarkerType.ArrowClosed, color: '#06b6d4' }
61
+ }))
62
+ );
63
+
64
+ setIsExecuting(false);
65
+ setExecutionFinished(true);
66
+ }, 1500);
67
+ };
68
+
69
+ const nextStep = () => {
70
+ if (currentStepIndex < TUTORIAL_STEPS.length - 1) {
71
+ setCurrentStepIndex(i => i + 1);
72
+ }
73
+ };
74
+
75
+ return (
76
+ <div className="w-full h-screen bg-black flex flex-col font-sans !overflow-hidden">
77
+ {/* Navbar Minimalist */}
78
+ <nav className="h-16 border-b border-white/10 flex items-center px-6 justify-between bg-zinc-950/50 backdrop-blur-md relative z-10 shrink-0">
79
+ <div className="flex items-center gap-3">
80
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-orange-500 to-red-600 flex items-center justify-center shadow-[0_0_15px_rgba(249,115,22,0.4)]">
81
+ <Terminal className="w-4 h-4 text-white" />
82
+ </div>
83
+ <div>
84
+ <h1 className="font-bold text-white leading-tight">Decido Developer Playground</h1>
85
+ <p className="text-[10px] text-zinc-400 font-mono">onboarding_mode: active</p>
86
+ </div>
87
+ </div>
88
+
89
+ {/* Progress Dots */}
90
+ <div className="flex items-center gap-2">
91
+ {TUTORIAL_STEPS.map((_, i) => (
92
+ <div key={i} className={`w-2 h-2 rounded-full transition-all duration-500 ${i === currentStepIndex ? 'bg-orange-500 w-6' : i < currentStepIndex ? 'bg-zinc-500' : 'bg-zinc-800'}`} />
93
+ ))}
94
+ </div>
95
+ </nav>
96
+
97
+ {/* Main Split Interface */}
98
+ <div className="flex-1 flex flex-col md:flex-row min-h-0 bg-[#0a0a0a]">
99
+
100
+ {/* Left Side: Code & Content */}
101
+ <div className="w-full md:w-[45%] lg:w-[40%] flex flex-col border-r border-white/5 bg-zinc-950/30 overflow-y-auto">
102
+ <AnimatePresence mode="wait">
103
+ <motion.div
104
+ key={currentStepIndex}
105
+ initial={{ opacity: 0, x: -20 }}
106
+ animate={{ opacity: 1, x: 0 }}
107
+ exit={{ opacity: 0, x: 20 }}
108
+ transition={{ duration: 0.4 }}
109
+ className="p-8 flex-1 flex flex-col"
110
+ >
111
+ <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-orange-500/10 border border-orange-500/20 text-orange-400 text-xs font-bold mb-6 w-max">
112
+ Paso 0{currentStepIndex + 1} // {TUTORIAL_STEPS.length}
113
+ </div>
114
+
115
+ <h2 className="text-3xl font-black text-white tracking-tight mb-4">
116
+ {step.title}
117
+ </h2>
118
+
119
+ <p className="text-zinc-400 mb-8 leading-relaxed">
120
+ {step.description}
121
+ </p>
122
+
123
+ {/* Code Snippet Box */}
124
+ <div className="relative mb-8 flex-1 flex flex-col">
125
+ <div className="absolute -inset-1 bg-gradient-to-r from-orange-500/20 to-purple-500/20 rounded-xl blur-lg opacity-50 z-0"></div>
126
+ <div className="relative z-10 flex-1">
127
+ <CodeSnippet code={step.code} />
128
+ </div>
129
+ </div>
130
+
131
+ {/* Action Area */}
132
+ <div className="mt-auto pt-6 border-t border-white/5 flex items-center justify-between">
133
+ <button
134
+ onClick={handleExecute}
135
+ disabled={isExecuting || executionFinished}
136
+ className={`px-6 py-3 rounded-lg font-bold flex items-center gap-2 transition-all ${executionFinished
137
+ ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/30'
138
+ : isExecuting
139
+ ? 'bg-orange-500/50 text-white border border-transparent cursor-not-allowed'
140
+ : 'bg-orange-600 hover:bg-orange-500 text-white shadow-[0_0_20px_rgba(234,88,12,0.4)] active:scale-95'
141
+ }`}
142
+ >
143
+ {executionFinished ? (
144
+ <><CheckCircle2 className="w-5 h-5" /> Ejecutado exitosamente</>
145
+ ) : isExecuting ? (
146
+ <><RefreshCw className="w-5 h-5 animate-spin" /> Compilando y ejecutando...</>
147
+ ) : (
148
+ <><Play className="w-5 h-5 fill-current" /> {step.actionLabel}</>
149
+ )}
150
+ </button>
151
+
152
+ {executionFinished && currentStepIndex < TUTORIAL_STEPS.length - 1 && (
153
+ <motion.button
154
+ initial={{ opacity: 0, scale: 0.9 }}
155
+ animate={{ opacity: 1, scale: 1 }}
156
+ onClick={nextStep}
157
+ className="flex items-center gap-2 text-sm font-bold text-white bg-white/10 hover:bg-white/20 px-4 py-3 rounded-lg border border-white/10 transition-colors"
158
+ >
159
+ Siguiente LecciĂłn <ArrowRight className="w-4 h-4" />
160
+ </motion.button>
161
+ )}
162
+
163
+ {executionFinished && currentStepIndex === TUTORIAL_STEPS.length - 1 && (
164
+ <motion.button
165
+ initial={{ opacity: 0, scale: 0.9 }}
166
+ animate={{ opacity: 1, scale: 1 }}
167
+ onClick={() => window.location.reload()}
168
+ className="flex items-center gap-2 text-sm font-bold text-orange-400 bg-orange-500/10 border border-orange-500/30 hover:bg-orange-500/20 px-4 py-3 rounded-lg transition-colors"
169
+ >
170
+ Entrar el Sistema <Sparkles className="w-4 h-4" />
171
+ </motion.button>
172
+ )}
173
+ </div>
174
+ </motion.div>
175
+ </AnimatePresence>
176
+ </div>
177
+
178
+ {/* Right Side: Visual Data Flow Simulator */}
179
+ <div className="w-full md:w-[55%] lg:w-[60%] flex-1 relative bg-[#050505] overflow-hidden">
180
+ {/* Grid Background Effect */}
181
+ <div className="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E')] opacity-10 mix-blend-overlay z-10 pointer-events-none"></div>
182
+ <div className="absolute inset-0 bg-gradient-to-t from-black via-transparent to-transparent z-10 pointer-events-none"></div>
183
+
184
+ <div className="w-full h-full relative z-0">
185
+ <ReactFlow
186
+ nodes={nodes}
187
+ edges={edges}
188
+ onNodesChange={onNodesChange}
189
+ onEdgesChange={onEdgesChange}
190
+ nodeTypes={nodeTypes}
191
+ fitView
192
+ fitViewOptions={{ padding: 0.5 }}
193
+ proOptions={{ hideAttribution: true }}
194
+ >
195
+ <Background color="#27272a" gap={24} size={2} />
196
+ </ReactFlow>
197
+ </div>
198
+
199
+ {/* Overlays */}
200
+ <div className="absolute top-4 right-4 z-20 flex gap-2">
201
+ <div className="px-3 py-1.5 rounded bg-black/50 border border-white/10 backdrop-blur-md text-xs font-mono text-zinc-400 flex items-center gap-2">
202
+ <div className={`w-2 h-2 rounded-full ${isExecuting ? 'bg-orange-500 animate-pulse' : 'bg-emerald-500'}`}></div>
203
+ Tauri / Rust Bridge: ACTIVO
204
+ </div>
205
+ </div>
206
+ </div>
207
+
208
+ </div>
209
+ </div>
210
+ );
211
+ };
@@ -0,0 +1,202 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { useStore } from 'zustand';
3
+ import { useShellStore } from '@decido/shell';
4
+ import { playgroundStore, PlaygroundEngine } from '../engine/PlaygroundEngine';
5
+ import { Terminal, Play, Loader, Command, X, Cpu, Zap, Search, LayoutDashboard } from 'lucide-react';
6
+
7
+ export const ForgeTerminalWidget: React.FC<{
8
+ isGlobalOverlay?: boolean;
9
+ }> = ({ isGlobalOverlay = false }) => {
10
+ const { logs, isExecuting } = useStore(playgroundStore);
11
+ const [objective, setObjective] = useState('');
12
+ const logsEndRef = useRef<HTMLDivElement>(null);
13
+
14
+ // Global toggle integration
15
+ const { isForgeTerminalOpen, setForgeTerminalOpen } = useShellStore();
16
+
17
+ // Auto-scroll on new logs
18
+ useEffect(() => {
19
+ logsEndRef.current?.scrollIntoView({ behavior: 'smooth' });
20
+ }, [logs]);
21
+
22
+ const handleExecute = (cmd?: string) => {
23
+ const targetCommand = cmd || objective;
24
+ if (!targetCommand.trim() || isExecuting) return;
25
+ PlaygroundEngine.dispatchMission(targetCommand);
26
+ setObjective('');
27
+ };
28
+
29
+ const isVisible = isGlobalOverlay ? isForgeTerminalOpen : true;
30
+
31
+ if (!isVisible) return null;
32
+
33
+ const quickMissions = [
34
+ { icon: <LayoutDashboard size={14} />, label: "Generar UI (Lucide)", cmd: "Construye un Sales Dashboard moderno usando Tailwind. AsegĂşrate de incluir 3 iconos de lucide-react (ej. Activity, DollarSign, Users). Devuelve un intent ui:spawn_dynamic_widget" },
35
+ { icon: <Search size={14} />, label: "Read Cargo.toml", cmd: "Lee el archivo Cargo.toml de la raíz y dime qué dependencias de Rust tienes." },
36
+ { icon: <Zap size={14} />, label: "List Root", cmd: "Lista los archivos del directorio actual." },
37
+ { icon: <Cpu size={14} />, label: "System Info", cmd: "Dime quién eres, qué herramientas tienes y cuál es tu propósito." },
38
+ ];
39
+
40
+ const content = (
41
+ <div style={{
42
+ display: 'flex', flexDirection: 'column', height: '100%',
43
+ background: isGlobalOverlay ? 'rgba(15, 17, 21, 0.95)' : '#0f1115',
44
+ color: '#c1c2c5', fontFamily: 'var(--font-mono, monospace)',
45
+ backdropFilter: isGlobalOverlay ? 'blur(12px)' : 'none',
46
+ border: isGlobalOverlay ? '1px solid rgba(0, 242, 224, 0.2)' : 'none',
47
+ borderRadius: isGlobalOverlay ? '12px' : '0px',
48
+ boxShadow: isGlobalOverlay ? '0 20px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05)' : 'none',
49
+ overflow: 'hidden'
50
+ }}>
51
+ {/* Header */}
52
+ <div style={{
53
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
54
+ padding: '16px 20px', background: 'rgba(0,0,0,0.2)',
55
+ borderBottom: '1px solid rgba(255,255,255,0.05)'
56
+ }}>
57
+ <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
58
+ <div style={{
59
+ background: 'linear-gradient(135deg, #00f2e0 0%, #0099ff 100%)',
60
+ padding: '6px', borderRadius: '8px', display: 'flex'
61
+ }}>
62
+ <Terminal size={18} color="#000" />
63
+ </div>
64
+ <div>
65
+ <h3 style={{ margin: 0, fontSize: '13px', fontWeight: 600, color: '#fff', letterSpacing: '0.5px' }}>
66
+ THE FORGE LAB
67
+ </h3>
68
+ <div style={{ fontSize: '10px', color: '#00f2e0', opacity: 0.8, display: 'flex', alignItems: 'center', gap: '4px', marginTop: '2px' }}>
69
+ <div style={{ width: 6, height: 6, borderRadius: '50%', background: isExecuting ? '#f5a623' : '#00f2e0', boxShadow: '0 0 8px currentColor' }} />
70
+ {isExecuting ? 'AWAITING RESPONSE...' : 'SYSTEM SECURE & READY'}
71
+ </div>
72
+ </div>
73
+ </div>
74
+
75
+ {isGlobalOverlay && (
76
+ <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
77
+ <div style={{ fontSize: '10px', color: '#666', background: 'rgba(255,255,255,0.05)', padding: '4px 8px', borderRadius: '4px', display: 'flex', alignItems: 'center', gap: '4px' }}>
78
+ <Command size={10} /> J
79
+ </div>
80
+ <button
81
+ onClick={() => setForgeTerminalOpen(false)}
82
+ style={{ background: 'transparent', border: 'none', color: '#888', cursor: 'pointer', padding: '4px', borderRadius: '4px' }}
83
+ onMouseOver={e => e.currentTarget.style.color = '#fff'}
84
+ onMouseOut={e => e.currentTarget.style.color = '#888'}
85
+ >
86
+ <X size={18} />
87
+ </button>
88
+ </div>
89
+ )}
90
+ </div>
91
+
92
+ {/* Quick Missions */}
93
+ <div style={{ display: 'flex', gap: '8px', padding: '12px 20px', background: 'rgba(255,255,255,0.02)', overflowX: 'auto', borderBottom: '1px solid rgba(255,255,255,0.02)' }}>
94
+ {quickMissions.map((q, i) => (
95
+ <button
96
+ key={i}
97
+ onClick={() => handleExecute(q.cmd)}
98
+ disabled={isExecuting}
99
+ style={{
100
+ background: 'rgba(0, 242, 224, 0.05)', border: '1px solid rgba(0, 242, 224, 0.15)',
101
+ color: '#00f2e0', padding: '6px 12px', borderRadius: '6px', fontSize: '11px',
102
+ cursor: isExecuting ? 'not-allowed' : 'pointer', display: 'flex', alignItems: 'center', gap: '6px',
103
+ whiteSpace: 'nowrap', transition: 'all 0.2s', opacity: isExecuting ? 0.5 : 1
104
+ }}
105
+ onMouseOver={e => { if (!isExecuting) e.currentTarget.style.background = 'rgba(0, 242, 224, 0.1)'; }}
106
+ onMouseOut={e => { if (!isExecuting) e.currentTarget.style.background = 'rgba(0, 242, 224, 0.05)'; }}
107
+ >
108
+ {q.icon} {q.label}
109
+ </button>
110
+ ))}
111
+ </div>
112
+
113
+ {/* Terminal Output */}
114
+ <div style={{
115
+ flex: 1, overflowY: 'auto', padding: '20px', fontSize: '12px',
116
+ lineHeight: '1.6', color: '#a0aabc'
117
+ }}>
118
+ {logs.length === 0 && (
119
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', opacity: 0.3, gap: '10px' }}>
120
+ <Terminal size={48} />
121
+ <span style={{ fontFamily: 'inherit' }}>Initialization Complete. Awaiting Directives.</span>
122
+ </div>
123
+ )}
124
+ {logs.map((log, i) => {
125
+ // Simple highlighting for common patterns
126
+ const isThought = log.includes('Pensamiento');
127
+ const isAction = log.includes('AcciĂłn');
128
+ const isSystem = log.includes('Sistema en lĂ­nea') || log.includes('MisiĂłn enviada');
129
+
130
+ let color = '#a0aabc';
131
+ if (isThought) color = '#b48ead';
132
+ if (isAction) color = '#ebcb8b';
133
+ if (isSystem) color = '#a3be8c';
134
+
135
+ return (
136
+ <div key={i} style={{ marginBottom: '8px', color, wordBreak: 'break-word' }}>
137
+ {log}
138
+ </div>
139
+ );
140
+ })}
141
+ <div ref={logsEndRef} />
142
+ </div>
143
+
144
+ {/* Input Area */}
145
+ <div style={{ padding: '16px 20px', background: 'rgba(0,0,0,0.3)', borderTop: '1px solid rgba(255,255,255,0.05)' }}>
146
+ <div style={{ display: 'flex', gap: '10px', position: 'relative' }}>
147
+ <div style={{ position: 'absolute', left: '12px', top: '12px', color: '#00f2e0' }}>
148
+ ❯
149
+ </div>
150
+ <input
151
+ type="text"
152
+ value={objective}
153
+ onChange={e => setObjective(e.target.value)}
154
+ placeholder="Assign an objective to the swarm..."
155
+ style={{
156
+ flex: 1, padding: '12px 12px 12px 32px', background: 'rgba(255,255,255,0.03)',
157
+ border: '1px solid rgba(255,255,255,0.1)', color: 'white', borderRadius: '8px',
158
+ outline: 'none', fontSize: '13px', transition: 'border-color 0.2s', fontFamily: 'inherit'
159
+ }}
160
+ onFocus={e => e.currentTarget.style.borderColor = 'rgba(0, 242, 224, 0.4)'}
161
+ onBlur={e => e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)'}
162
+ onKeyDown={e => e.key === 'Enter' && handleExecute()}
163
+ autoFocus={isGlobalOverlay}
164
+ />
165
+ <button
166
+ onClick={() => handleExecute()}
167
+ disabled={isExecuting || !objective.trim()}
168
+ style={{
169
+ background: isExecuting ? 'rgba(255,255,255,0.05)' : 'linear-gradient(135deg, #00f2e0 0%, #0099ff 100%)',
170
+ color: isExecuting ? '#666' : '#000',
171
+ border: 'none', padding: '0 20px', borderRadius: '8px', cursor: isExecuting ? 'not-allowed' : 'pointer',
172
+ display: 'flex', alignItems: 'center', gap: '8px', fontWeight: 600, fontSize: '13px',
173
+ transition: 'opacity 0.2s, transform 0.1s'
174
+ }}
175
+ onMouseDown={e => { if (!isExecuting) e.currentTarget.style.transform = 'scale(0.96)'; }}
176
+ onMouseUp={e => { if (!isExecuting) e.currentTarget.style.transform = 'scale(1)'; }}
177
+ onMouseLeave={e => { if (!isExecuting) e.currentTarget.style.transform = 'scale(1)'; }}
178
+ >
179
+ {isExecuting ? <Loader size={16} className="animate-spin" /> : <Play size={16} />}
180
+ {isExecuting ? 'EXECUTING' : 'DISPATCH'}
181
+ </button>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ );
186
+
187
+ if (isGlobalOverlay) {
188
+ return (
189
+ <div style={{
190
+ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, zIndex: 9999,
191
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
192
+ padding: '40px', background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(4px)'
193
+ }}>
194
+ <div style={{ width: '100%', maxWidth: '800px', height: '80vh', display: 'flex', flexDirection: 'column' }}>
195
+ {content}
196
+ </div>
197
+ </div>
198
+ );
199
+ }
200
+
201
+ return <>{content}</>;
202
+ };