@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,245 @@
|
|
|
1
|
+
import React, { useState, Suspense } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import { Menu, Command, Workflow, Camera, Paperclip, Mic, Sparkles, Send, X, MonitorPlay, Layers, Code2, Database } from 'lucide-react';
|
|
4
|
+
import { Omnibar } from '../Omnibar';
|
|
5
|
+
import { TransientLayer } from '../TransientLayer';
|
|
6
|
+
|
|
7
|
+
// Lazy load: VoiceWidget comes from @decido/chat
|
|
8
|
+
const DecidoVoiceWidget = React.lazy(() =>
|
|
9
|
+
import('@decido/chat').then(m => ({ default: m.DecidoVoiceWidget })).catch(() => ({ default: () => null }))
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
interface ExperimentalChatShellProps {
|
|
13
|
+
onExit: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ExperimentalChatShell: React.FC<ExperimentalChatShellProps> = ({ onExit }) => {
|
|
17
|
+
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
|
18
|
+
const [isVoiceActive, setIsVoiceActive] = useState(false);
|
|
19
|
+
const [isTranscribing, setIsTranscribing] = useState(false);
|
|
20
|
+
const [inputValue, setInputValue] = useState('');
|
|
21
|
+
const [suggestions, setSuggestions] = useState<any[]>([]);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="h-dvh w-full bg-surface-primary flex font-sans relative overflow-hidden text-text-primary">
|
|
25
|
+
{/* Background Texture Drop */}
|
|
26
|
+
<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-[0.2] mix-blend-overlay pointer-events-none z-0"></div>
|
|
27
|
+
|
|
28
|
+
{/* COLLAPSIBLE LEFT SIDEBAR */}
|
|
29
|
+
<div className={`flex flex-col bg-surface-secondary border-r border-border-subtle z-30 transition-all duration-300 ease-[cubic-bezier(0.32,_0.72,_0,_1)] shrink-0 ${isSidebarOpen ? 'w-[280px]' : 'w-0'} overflow-hidden`}>
|
|
30
|
+
<div className="w-[280px] p-4 md:p-6 flex flex-col h-full opacity-100 min-w-[280px]">
|
|
31
|
+
<div className="flex items-center gap-3 mb-8">
|
|
32
|
+
<button onClick={() => setIsSidebarOpen(false)} className="p-2 -ml-2 rounded-full hover:bg-surface-glass text-text-secondary hover:text-text-primary transition-colors">
|
|
33
|
+
<Menu size={22} />
|
|
34
|
+
</button>
|
|
35
|
+
<span className="font-bold text-text-primary tracking-wide">Menú Principal</span>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div className="flex-1 space-y-2">
|
|
39
|
+
<button className="flex items-center w-full gap-3 p-3 rounded-xl bg-surface-glass text-cyan-400 font-medium border border-border-subtle transition-all">
|
|
40
|
+
<Command size={18} />
|
|
41
|
+
<span>Centro de Comandos</span>
|
|
42
|
+
</button>
|
|
43
|
+
<button className="flex items-center w-full gap-3 p-3 rounded-xl text-text-secondary font-medium border border-transparent hover:bg-surface-glass transition-all">
|
|
44
|
+
<Workflow size={18} />
|
|
45
|
+
<span>Flujos de Trabajo</span>
|
|
46
|
+
</button>
|
|
47
|
+
|
|
48
|
+
<div className="my-6 border-t border-border-subtle" />
|
|
49
|
+
|
|
50
|
+
<button onClick={onExit} className="flex items-center w-full gap-3 p-3 rounded-xl text-amber-400 font-medium border border-transparent hover:bg-amber-400/10 transition-all">
|
|
51
|
+
<X size={18} />
|
|
52
|
+
<span>Salir de Chat Exp.</span>
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div className="mt-auto pb-4">
|
|
57
|
+
<p className="text-xs text-text-muted font-mono">Kernel v1.0.0 (Chat Shell)</p>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* MAIN CHAT LAYER */}
|
|
63
|
+
<div className="flex-1 flex flex-col relative z-20 bg-surface-secondary shadow-[0_0_50px_rgba(0,0,0,0.5)]">
|
|
64
|
+
|
|
65
|
+
{/* HEADER */}
|
|
66
|
+
<header className="flex-none p-4 md:p-6 z-60 flex justify-between items-center bg-linear-to-b from-surface-secondary to-transparent pointer-events-none">
|
|
67
|
+
<div className="flex items-center gap-3 pointer-events-auto">
|
|
68
|
+
{!isSidebarOpen && (
|
|
69
|
+
<button onClick={() => setIsSidebarOpen(true)} className="p-2 -ml-2 rounded-full hover:bg-surface-glass text-text-secondary hover:text-text-primary transition-colors">
|
|
70
|
+
<Menu size={22} />
|
|
71
|
+
</button>
|
|
72
|
+
)}
|
|
73
|
+
<span className="text-xl md:text-2xl font-black text-text-primary tracking-tighter drop-shadow-[0_0_10px_rgba(255,255,255,0.2)]">
|
|
74
|
+
yo.<span className="text-cyan-400">decido</span>
|
|
75
|
+
</span>
|
|
76
|
+
<span className="ml-2 text-[10px] font-bold tracking-widest text-amber-400 border border-amber-400/30 rounded px-2 py-0.5 opacity-80 uppercase">Experimental</span>
|
|
77
|
+
</div>
|
|
78
|
+
</header>
|
|
79
|
+
|
|
80
|
+
{/* MAIN CHAT BODY / CANVAS */}
|
|
81
|
+
<div className="flex-1 overflow-y-auto z-10 p-4 md:p-6 pb-48 flex flex-col justify-end">
|
|
82
|
+
<AnimatePresence mode="wait">
|
|
83
|
+
<motion.div
|
|
84
|
+
key="chat-greeting"
|
|
85
|
+
initial={{ opacity: 0, y: 20 }}
|
|
86
|
+
animate={{ opacity: 1, y: 0 }}
|
|
87
|
+
exit={{ opacity: 0 }}
|
|
88
|
+
className="flex flex-col max-w-4xl mx-auto w-full gap-8"
|
|
89
|
+
>
|
|
90
|
+
{/* Greeting Bubble */}
|
|
91
|
+
<div className="flex flex-col gap-2">
|
|
92
|
+
<h1 className="text-3xl md:text-5xl font-semibold leading-tight text-text-primary/50">
|
|
93
|
+
<span className="bg-linear-to-r from-cyan-400 via-purple-400 to-indigo-400 text-transparent bg-clip-text">Hola.</span><br />
|
|
94
|
+
¿En qué puedo ayudarte hoy?
|
|
95
|
+
</h1>
|
|
96
|
+
<p className="text-sm text-text-muted font-mono mt-2">NÚCLEO EXPERIMENTAL • ZONA DE PRUEBAS</p>
|
|
97
|
+
</div>
|
|
98
|
+
</motion.div>
|
|
99
|
+
</AnimatePresence>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* BOTTOM INPUT AREA / DOCK */}
|
|
103
|
+
<div className="absolute bottom-0 left-0 w-full p-4 pb-8 md:p-6 md:pb-10 bg-linear-to-t from-surface-secondary via-surface-secondary to-transparent z-70 flex justify-center">
|
|
104
|
+
<motion.div
|
|
105
|
+
layout
|
|
106
|
+
className={`w-full max-w-4xl relative ${isVoiceActive ? 'h-64' : ''}`}
|
|
107
|
+
>
|
|
108
|
+
<div className={`bg-surface-tertiary border border-border-default rounded-4xl flex flex-col p-2 shadow-2xl transition-all duration-300 ${isVoiceActive ? 'h-full border-cyan-500/30' : 'shadow-[#06b6d4]/5 focus-within:border-border-strong focus-within:shadow-[#06b6d4]/10'}`}>
|
|
109
|
+
|
|
110
|
+
<AnimatePresence mode="wait">
|
|
111
|
+
{!isVoiceActive ? (
|
|
112
|
+
<motion.div
|
|
113
|
+
key="text-mode"
|
|
114
|
+
initial={{ opacity: 0 }}
|
|
115
|
+
animate={{ opacity: 1 }}
|
|
116
|
+
exit={{ opacity: 0 }}
|
|
117
|
+
className="flex flex-col w-full h-full cursor-text"
|
|
118
|
+
onClick={(e) => {
|
|
119
|
+
const input = e.currentTarget.querySelector('input');
|
|
120
|
+
if (input && e.target === e.currentTarget) input.focus();
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
<div className="relative flex items-center px-4 py-3 min-h-[60px]">
|
|
124
|
+
<input
|
|
125
|
+
type="text"
|
|
126
|
+
placeholder={isTranscribing ? "Escuchando..." : "Pregúntale a Decido..."}
|
|
127
|
+
value={inputValue}
|
|
128
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
129
|
+
className="w-full bg-transparent text-text-primary placeholder-text-muted outline-hidden text-[15px] leading-relaxed resize-none h-full"
|
|
130
|
+
onKeyDown={(e) => {
|
|
131
|
+
if (e.key === 'Enter') {
|
|
132
|
+
console.log("Submit:", inputValue);
|
|
133
|
+
setInputValue('');
|
|
134
|
+
}
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div className="flex items-center justify-between px-2 pb-1 relative z-10 w-full">
|
|
140
|
+
{/* Archivos / Acciones Base */}
|
|
141
|
+
<div className="flex items-center gap-1">
|
|
142
|
+
<button className="w-10 h-10 rounded-full text-text-secondary hover:text-text-primary hover:bg-surface-glass flex items-center justify-center transition-colors">
|
|
143
|
+
<Camera size={20} />
|
|
144
|
+
</button>
|
|
145
|
+
<button className="w-10 h-10 rounded-full text-text-secondary hover:text-text-primary hover:bg-surface-glass flex items-center justify-center transition-colors">
|
|
146
|
+
<Paperclip size={20} />
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{/* DEV CONTROL TOOLBAR (Centro) */}
|
|
151
|
+
<div className="flex items-center gap-1 px-3 py-1.5 bg-surface-overlay border border-border-subtle rounded-2xl">
|
|
152
|
+
<button
|
|
153
|
+
onClick={() => console.log("[DevTools] Solicitando Workspace")}
|
|
154
|
+
className="p-2 rounded-xl text-text-muted hover:text-cyan-400 hover:bg-surface-glass transition-all" title="Vista de Lienzo (Canvas)">
|
|
155
|
+
<MonitorPlay size={18} />
|
|
156
|
+
</button>
|
|
157
|
+
<button
|
|
158
|
+
onClick={() => window.dispatchEvent(new Event('decido-toggle-debug'))}
|
|
159
|
+
className="p-2 rounded-xl text-text-muted hover:text-emerald-400 hover:bg-surface-glass transition-all font-bold" title="Gestor de Capas / Resaltar Componentes">
|
|
160
|
+
<Layers size={18} />
|
|
161
|
+
</button>
|
|
162
|
+
<button
|
|
163
|
+
onClick={() => alert("Editor de Nodos (Kernel) en fase de Pruebas")}
|
|
164
|
+
className="p-2 rounded-xl text-text-muted hover:text-amber-400 hover:bg-surface-glass transition-all" title="Editor de Nodos (Kernel)">
|
|
165
|
+
<Code2 size={18} />
|
|
166
|
+
</button>
|
|
167
|
+
<div className="w-[1px] h-4 bg-surface-glass mx-1"></div>
|
|
168
|
+
<button
|
|
169
|
+
onClick={() => console.log("[DevTools] Database Inspector activo")}
|
|
170
|
+
className="p-2 rounded-xl text-text-muted hover:text-purple-400 hover:bg-surface-glass transition-all" title="Inspector de Estado">
|
|
171
|
+
<Database size={18} />
|
|
172
|
+
</button>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{/* Acciones de Envío / Voz */}
|
|
176
|
+
<div className="flex items-center gap-2">
|
|
177
|
+
{!inputValue ? (
|
|
178
|
+
<>
|
|
179
|
+
<button
|
|
180
|
+
onClick={(e) => { e.stopPropagation(); setIsTranscribing(!isTranscribing); }}
|
|
181
|
+
className={`w-10 h-10 rounded-full flex items-center justify-center transition-all ${isTranscribing ? 'text-red-400 bg-red-400/10' : 'text-text-secondary hover:text-text-primary hover:bg-surface-glass'}`}
|
|
182
|
+
title="Reconocimiento de voz"
|
|
183
|
+
>
|
|
184
|
+
<Mic size={20} className={isTranscribing ? "animate-pulse" : ""} />
|
|
185
|
+
</button>
|
|
186
|
+
<button
|
|
187
|
+
onClick={(e) => { e.stopPropagation(); setIsVoiceActive(true); }}
|
|
188
|
+
className="w-12 h-12 rounded-full text-cyan-400 bg-cyan-500/10 hover:bg-cyan-500/20 flex items-center justify-center border border-cyan-500/20 transition-all shadow-[0_0_15px_rgba(6,182,212,0.15)] hover:shadow-[0_0_20px_rgba(6,182,212,0.3)] group relative overflow-hidden"
|
|
189
|
+
title="Hablar con la IA (Live)"
|
|
190
|
+
>
|
|
191
|
+
<Sparkles size={22} className="relative z-10 transition-transform group-hover:scale-110" />
|
|
192
|
+
<div className="absolute inset-0 bg-linear-to-r from-cyan-400 to-purple-500 rounded-full opacity-0 group-hover:opacity-20 transition-opacity"></div>
|
|
193
|
+
</button>
|
|
194
|
+
</>
|
|
195
|
+
) : (
|
|
196
|
+
<button
|
|
197
|
+
onClick={(e) => { e.stopPropagation(); console.log("Send:", inputValue); setInputValue(''); }}
|
|
198
|
+
className="w-12 h-12 rounded-full text-text-inverse bg-cyan-400 hover:bg-cyan-300 flex items-center justify-center transition-colors shadow-[0_0_15px_rgba(34,211,238,0.3)]"
|
|
199
|
+
>
|
|
200
|
+
<Send size={20} className="mr-1" />
|
|
201
|
+
</button>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</motion.div>
|
|
206
|
+
) : (
|
|
207
|
+
<motion.div
|
|
208
|
+
key="voice-mode"
|
|
209
|
+
initial={{ opacity: 0 }}
|
|
210
|
+
animate={{ opacity: 1 }}
|
|
211
|
+
exit={{ opacity: 0 }}
|
|
212
|
+
className="flex flex-col w-full h-full relative p-2"
|
|
213
|
+
>
|
|
214
|
+
<div className="flex w-full items-center justify-between mb-2 z-10">
|
|
215
|
+
<span className="text-cyan-400 font-medium px-4 bg-linear-to-r from-cyan-400 via-purple-400 to-indigo-400 text-transparent bg-clip-text animate-pulse">
|
|
216
|
+
Experimental Live Activo...
|
|
217
|
+
</span>
|
|
218
|
+
<button
|
|
219
|
+
onClick={(e) => { e.stopPropagation(); setIsVoiceActive(false); }}
|
|
220
|
+
className="w-8 h-8 rounded-full bg-surface-glass hover:bg-surface-glass flex items-center justify-center text-text-secondary hover:text-text-primary transition-colors"
|
|
221
|
+
>
|
|
222
|
+
<X size={16} />
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div className="flex-1 w-full rounded-2xl overflow-hidden relative">
|
|
227
|
+
<DecidoVoiceWidget
|
|
228
|
+
isCompact={true}
|
|
229
|
+
onSuggestionsUpdate={(newSuggestions) => setSuggestions(newSuggestions)}
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
</motion.div>
|
|
233
|
+
)}
|
|
234
|
+
</AnimatePresence>
|
|
235
|
+
</div>
|
|
236
|
+
</motion.div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{/* OMNIBAR AND TRANSIENT LAYER INTEGRATION TO KEEP PLUGINS FUNCTIONAL */}
|
|
240
|
+
<Omnibar />
|
|
241
|
+
<TransientLayer />
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ExperimentalChatShell } from './ExperimentalChatShell';
|
|
3
|
+
|
|
4
|
+
export const UserCanvas: React.FC = () => {
|
|
5
|
+
const [isExperimentalChat, setIsExperimentalChat] = useState(false);
|
|
6
|
+
|
|
7
|
+
if (isExperimentalChat) {
|
|
8
|
+
return <ExperimentalChatShell onExit={() => setIsExperimentalChat(false)} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="relative w-full h-full flex flex-col selection:bg-cyan-500/30">
|
|
13
|
+
{/* Efectos de fondo ambiental dinámicos */}
|
|
14
|
+
<div className="absolute inset-x-0 top-0 h-[500px] w-full bg-linear-to-b from-cyan-900/20 via-blue-900/10 to-transparent pointer-events-none -translate-y-1/2 opacity-50 blur-[100px]" />
|
|
15
|
+
<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-20 mix-blend-overlay pointer-events-none"></div>
|
|
16
|
+
|
|
17
|
+
<div className="w-full shrink-0 flex-none bg-surface-overlay backdrop-blur-md border-b border-border-subtle" style={{ height: 'max(env(safe-area-inset-top, 0px), 1.5rem)' }} />
|
|
18
|
+
|
|
19
|
+
<div className="flex-1 relative overflow-hidden w-full flex items-center justify-center border-t border-cyan-500/10 backdrop-blur-xs">
|
|
20
|
+
<div className="absolute inset-0 pattern-grid-lg text-text-primary/2" />
|
|
21
|
+
|
|
22
|
+
<div className="text-center z-10 max-w-md bg-surface-overlay p-8 rounded-4xl border border-border-default shadow-[0_0_50px_rgba(0,0,0,0.5)] backdrop-blur-xl">
|
|
23
|
+
<div className="w-16 h-16 bg-surface-glass rounded-full flex items-center justify-center mx-auto mb-6">
|
|
24
|
+
<i className="fas fa-project-diagram text-2xl text-cyan-400 drop-shadow-[0_0_10px_rgba(34,211,238,0.5)]"></i>
|
|
25
|
+
</div>
|
|
26
|
+
<h2 className="text-2xl font-light mb-2 tracking-tight">Espacio Decido</h2>
|
|
27
|
+
<p className="text-text-secondary text-sm leading-relaxed mb-8">
|
|
28
|
+
Bienvenido. Tu Agente IA está escuchando. Haz clic en el orbe inferior o usa <kbd className="bg-surface-glass px-1 rounded">Cmd+K</kbd> para ordenar la materialización de componentes.
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
<button
|
|
32
|
+
onClick={() => setIsExperimentalChat(true)}
|
|
33
|
+
className="px-6 py-3 bg-linear-to-r from-cyan-500/10 to-blue-500/10 hover:from-cyan-500/20 hover:to-blue-500/20 border border-cyan-500/30 hover:border-cyan-400 text-cyan-300 rounded-xl font-medium transition-all shadow-[0_0_15px_rgba(34,211,238,0.1)] flex items-center justify-center gap-2 mx-auto w-full"
|
|
34
|
+
>
|
|
35
|
+
<i className="fas fa-flask"></i>
|
|
36
|
+
Probar Modo Inmersivo
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="w-full shrink-0 flex-none" style={{ height: 'max(env(safe-area-inset-bottom, 0px), 1rem)' }} />
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
3
|
+
import { FolderOpen, Plus, Check, X, FileText, Network } from 'lucide-react';
|
|
4
|
+
import { useTimelineStore } from '@decido/engine';
|
|
5
|
+
import { usePlaygroundStore } from '../../store/usePlaygroundStore';
|
|
6
|
+
import { useBlueprintNavigation } from '@decido/engine';
|
|
7
|
+
import { BlueprintCard } from './blueprint/BlueprintCard';
|
|
8
|
+
|
|
9
|
+
interface BlueprintManagerPanelProps { isOpen: boolean; onClose: () => void; }
|
|
10
|
+
|
|
11
|
+
export const BlueprintManagerPanel: React.FC<BlueprintManagerPanelProps> = ({ isOpen, onClose }) => {
|
|
12
|
+
const timelines = useTimelineStore(s => s.timelines);
|
|
13
|
+
const blueprintLibrary = useTimelineStore(s => s.blueprintLibrary);
|
|
14
|
+
const activeTimelineId = usePlaygroundStore(s => s.prototypeBrand);
|
|
15
|
+
const setPrototypeBrand = usePlaygroundStore(s => s.setPrototypeBrand);
|
|
16
|
+
const resetNavigation = useBlueprintNavigation(s => s.resetNavigation);
|
|
17
|
+
|
|
18
|
+
const createTimeline = useTimelineStore(s => s.createTimeline);
|
|
19
|
+
const addKeyframe = useTimelineStore(s => s.addKeyframe);
|
|
20
|
+
const addBlueprint = useTimelineStore(s => s.addBlueprint);
|
|
21
|
+
const duplicateTimeline = useTimelineStore(s => s.duplicateTimeline);
|
|
22
|
+
const renameTimeline = useTimelineStore(s => s.renameTimeline);
|
|
23
|
+
const deleteTimeline = useTimelineStore(s => s.deleteTimeline);
|
|
24
|
+
|
|
25
|
+
const [isCreating, setIsCreating] = useState(false);
|
|
26
|
+
const [createType, setCreateType] = useState<'blueprint' | 'subflow'>('blueprint');
|
|
27
|
+
const [newName, setNewName] = useState('');
|
|
28
|
+
const [showCreateMenu, setShowCreateMenu] = useState(false);
|
|
29
|
+
const createMenuRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const h = (e: MouseEvent) => { if (createMenuRef.current && !createMenuRef.current.contains(e.target as Node)) setShowCreateMenu(false); };
|
|
33
|
+
document.addEventListener('mousedown', h);
|
|
34
|
+
return () => document.removeEventListener('mousedown', h);
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const handleCreate = useCallback(() => {
|
|
38
|
+
if (!newName.trim()) return;
|
|
39
|
+
if (createType === 'subflow') {
|
|
40
|
+
const subKey = `subflow_${newName.trim().toLowerCase().replace(/\s+/g, '_')}`;
|
|
41
|
+
const id = createTimeline(newName.trim(), subKey);
|
|
42
|
+
addKeyframe(id, { t: 0, track: 'entry', label: 'Entrada', position: { x: 300, y: 50 } } as any);
|
|
43
|
+
addKeyframe(id, { t: 5, track: 'return', label: 'Retorno', handle: 'success', position: { x: 300, y: 300 } } as any);
|
|
44
|
+
const bpId = `bp_${newName.trim().toLowerCase().replace(/\s+/g, '_')}`;
|
|
45
|
+
const tl = useTimelineStore.getState().timelines[id];
|
|
46
|
+
if (tl) addBlueprint({ ...tl, id: bpId, isSubflow: true, signature: { inputs: {}, outputs: {} }, metadata: { author: 'user', version: '1.0.0', tags: ['custom'], description: newName.trim(), isPublic: false, createdAt: Date.now(), updatedAt: Date.now() } } as any);
|
|
47
|
+
setPrototypeBrand(id);
|
|
48
|
+
} else { setPrototypeBrand(createTimeline(newName.trim())); }
|
|
49
|
+
setNewName(''); setIsCreating(false);
|
|
50
|
+
}, [newName, createType, createTimeline, addKeyframe, addBlueprint, setPrototypeBrand]);
|
|
51
|
+
|
|
52
|
+
const handleSelect = useCallback((id: string) => { resetNavigation(); setPrototypeBrand(id); }, [setPrototypeBrand, resetNavigation]);
|
|
53
|
+
const handleDelete = useCallback((id: string) => {
|
|
54
|
+
const del = deleteTimeline(id);
|
|
55
|
+
if (del && activeTimelineId === id) { const rem = Object.keys(timelines).filter(k => k !== id); if (rem.length > 0) setPrototypeBrand(rem[0]); }
|
|
56
|
+
}, [deleteTimeline, activeTimelineId, timelines, setPrototypeBrand]);
|
|
57
|
+
|
|
58
|
+
const allBlueprints = Object.entries(timelines);
|
|
59
|
+
const subflows = Object.entries(blueprintLibrary);
|
|
60
|
+
const bpToKey: Record<string, string> = {};
|
|
61
|
+
for (const [key] of Object.entries(timelines)) { if (key.startsWith('subflow_')) bpToKey[`bp_${key.replace('subflow_', '')}`] = key; }
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<AnimatePresence>
|
|
65
|
+
{isOpen && (
|
|
66
|
+
<>
|
|
67
|
+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="fixed inset-0 bg-surface-overlay z-40" onClick={onClose} />
|
|
68
|
+
<motion.div initial={{ opacity: 0, x: -20, scale: 0.95 }} animate={{ opacity: 1, x: 0, scale: 1 }} exit={{ opacity: 0, x: -20, scale: 0.95 }} transition={{ type: 'spring', damping: 25, stiffness: 300 }}
|
|
69
|
+
className="fixed left-4 bottom-20 z-50 w-80 max-h-[60vh] bg-surface-primary border border-border-default rounded-2xl shadow-2xl overflow-hidden flex flex-col" style={{ backdropFilter: 'blur(20px)' }} onClick={e => e.stopPropagation()}>
|
|
70
|
+
|
|
71
|
+
{/* Header */}
|
|
72
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-border-default bg-surface-glass">
|
|
73
|
+
<div className="flex items-center gap-2">
|
|
74
|
+
<FolderOpen size={16} className="text-cyan-400" /><span className="text-sm font-bold text-text-primary tracking-wide">Blueprints</span>
|
|
75
|
+
<span className="text-[10px] bg-surface-glass text-text-secondary px-1.5 py-0.5 rounded-full font-mono">{allBlueprints.length}</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="relative" ref={createMenuRef}>
|
|
78
|
+
<button onClick={() => setShowCreateMenu(!showCreateMenu)} className="p-1.5 rounded-lg hover:bg-cyan-500/20 text-cyan-400 transition-colors" title="Crear nuevo"><Plus size={16} /></button>
|
|
79
|
+
<AnimatePresence>
|
|
80
|
+
{showCreateMenu && (
|
|
81
|
+
<motion.div initial={{ opacity: 0, scale: 0.9, y: -5 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9, y: -5 }}
|
|
82
|
+
className="absolute right-0 top-full mt-1 z-50 bg-surface-tertiary border border-border-default rounded-xl shadow-2xl overflow-hidden min-w-[180px]">
|
|
83
|
+
<button onClick={() => { setCreateType('blueprint'); setIsCreating(true); setShowCreateMenu(false); }} className="flex items-center gap-2 w-full px-3 py-2.5 text-xs text-text-primary hover:bg-surface-glass transition-colors">
|
|
84
|
+
<FileText size={13} className="text-cyan-400" /><div className="text-left"><div className="font-medium">Nuevo Blueprint</div><div className="text-[10px] text-text-muted">Flujo principal</div></div>
|
|
85
|
+
</button>
|
|
86
|
+
<button onClick={() => { setCreateType('subflow'); setIsCreating(true); setShowCreateMenu(false); }} className="flex items-center gap-2 w-full px-3 py-2.5 text-xs text-text-primary hover:bg-surface-glass transition-colors border-t border-border-subtle">
|
|
87
|
+
<Network size={13} className="text-violet-400" /><div className="text-left"><div className="font-medium">Nuevo SubFlow</div><div className="text-[10px] text-text-muted">Con Entry + Return</div></div>
|
|
88
|
+
</button>
|
|
89
|
+
</motion.div>
|
|
90
|
+
)}
|
|
91
|
+
</AnimatePresence>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Create Input */}
|
|
96
|
+
<AnimatePresence>
|
|
97
|
+
{isCreating && (
|
|
98
|
+
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} className="overflow-hidden border-b border-border-default">
|
|
99
|
+
<div className="flex items-center gap-2 px-4 py-3">
|
|
100
|
+
{createType === 'subflow' ? <Network size={14} className="text-violet-400 shrink-0" /> : <FileText size={14} className="text-cyan-400 shrink-0" />}
|
|
101
|
+
<input autoFocus value={newName} onChange={e => setNewName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') handleCreate(); if (e.key === 'Escape') setIsCreating(false); }}
|
|
102
|
+
placeholder={createType === 'subflow' ? 'Nombre del subflow...' : 'Nombre del blueprint...'} className="flex-1 bg-surface-glass border border-border-default rounded-lg px-3 py-1.5 text-xs text-text-primary focus:outline-hidden focus:border-cyan-500/50 font-mono" />
|
|
103
|
+
<button onClick={handleCreate} className="p-1.5 rounded-lg bg-cyan-500/20 text-cyan-400 hover:bg-cyan-500/30"><Check size={14} /></button>
|
|
104
|
+
<button onClick={() => setIsCreating(false)} className="p-1.5 rounded-lg hover:bg-surface-glass text-text-muted"><X size={14} /></button>
|
|
105
|
+
</div>
|
|
106
|
+
</motion.div>
|
|
107
|
+
)}
|
|
108
|
+
</AnimatePresence>
|
|
109
|
+
|
|
110
|
+
{/* Lists */}
|
|
111
|
+
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
|
112
|
+
<div className="px-2 py-1">
|
|
113
|
+
<div className="px-2 py-1.5 text-[9px] font-bold text-text-muted uppercase tracking-widest">Blueprints Principales</div>
|
|
114
|
+
{allBlueprints.map(([id, tl]) => (
|
|
115
|
+
<BlueprintCard key={id} id={id} name={tl.name || id} nodeCount={tl.keyframes?.length || 0} isActive={id === activeTimelineId}
|
|
116
|
+
onSelect={() => handleSelect(id)} onRename={name => renameTimeline(id, name)} onDuplicate={() => { setPrototypeBrand(duplicateTimeline(id)); }}
|
|
117
|
+
onDelete={allBlueprints.length > 1 ? () => handleDelete(id) : undefined} />
|
|
118
|
+
))}
|
|
119
|
+
</div>
|
|
120
|
+
{subflows.length > 0 && (
|
|
121
|
+
<div className="px-2 py-1 border-t border-border-subtle">
|
|
122
|
+
<div className="px-2 py-1.5 text-[9px] font-bold text-text-muted uppercase tracking-widest">Subflujos</div>
|
|
123
|
+
{subflows.map(([id, bp]) => {
|
|
124
|
+
const tk = bpToKey[id] || '';
|
|
125
|
+
return <BlueprintCard key={id} id={id} name={bp.name || id} nodeCount={bp.keyframes?.length || 0} isActive={tk === activeTimelineId} isSubflow
|
|
126
|
+
onSelect={() => tk && handleSelect(tk)} onRename={name => tk && renameTimeline(tk, name)} onDuplicate={() => tk && setPrototypeBrand(duplicateTimeline(tk))}
|
|
127
|
+
onDelete={() => tk && handleDelete(tk)} />;
|
|
128
|
+
})}
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
</motion.div>
|
|
133
|
+
</>
|
|
134
|
+
)}
|
|
135
|
+
</AnimatePresence>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
3
|
+
import { AlertTriangle, X, TreePine } from 'lucide-react';
|
|
4
|
+
import { useTimelineStore } from '@decido/engine';
|
|
5
|
+
import { usePlaygroundStore } from '../../store/usePlaygroundStore';
|
|
6
|
+
import { useBlueprintNavigation } from '@decido/engine';
|
|
7
|
+
import { TreeNodeItem, type TreeNode } from './editor/TreeNodeItem';
|
|
8
|
+
|
|
9
|
+
interface DependencyTreePanelProps {
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DependencyTreePanel: React.FC<DependencyTreePanelProps> = ({ isOpen, onClose }) => {
|
|
15
|
+
const timelines = useTimelineStore(s => s.timelines);
|
|
16
|
+
const blueprintLibrary = useTimelineStore(s => s.blueprintLibrary);
|
|
17
|
+
const setPrototypeBrand = usePlaygroundStore(s => s.setPrototypeBrand);
|
|
18
|
+
const resetNavigation = useBlueprintNavigation(s => s.resetNavigation);
|
|
19
|
+
const activeTimelineId = usePlaygroundStore(s => s.prototypeBrand);
|
|
20
|
+
|
|
21
|
+
// Build dependency tree
|
|
22
|
+
const { roots, orphans, cycleWarnings } = useMemo(() => {
|
|
23
|
+
// 1. Scan all timelines for subflow references
|
|
24
|
+
const references: Record<string, string[]> = {}; // timelineKey -> [targetBpId, ...]
|
|
25
|
+
const referencedBpIds = new Set<string>();
|
|
26
|
+
|
|
27
|
+
for (const [key, timeline] of Object.entries(timelines)) {
|
|
28
|
+
const subflowNodes = (timeline.keyframes || []).filter(
|
|
29
|
+
(kf: any) => kf.track === 'subflow' && kf.targetBlueprintId
|
|
30
|
+
);
|
|
31
|
+
if (subflowNodes.length > 0) {
|
|
32
|
+
references[key] = subflowNodes.map((kf: any) => kf.targetBlueprintId);
|
|
33
|
+
subflowNodes.forEach((kf: any) => referencedBpIds.add(kf.targetBlueprintId));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Identify roots: timelines that have subflow children but are NOT themselves referenced
|
|
38
|
+
const bpToTimelineKey: Record<string, string> = {};
|
|
39
|
+
for (const key of Object.keys(timelines)) {
|
|
40
|
+
if (key.startsWith('subflow_')) {
|
|
41
|
+
const bpId = `bp_${key.replace('subflow_', '')}`;
|
|
42
|
+
bpToTimelineKey[bpId] = key;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const referencedTimelineKeys = new Set(
|
|
47
|
+
[...referencedBpIds].map(bpId => bpToTimelineKey[bpId]).filter(Boolean)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// 3. Build tree recursively
|
|
51
|
+
const cycleWarnings: string[] = [];
|
|
52
|
+
|
|
53
|
+
const buildNode = (tlKey: string, visited: Set<string>, depth: number): TreeNode => {
|
|
54
|
+
const timeline = timelines[tlKey];
|
|
55
|
+
const name = timeline?.name || tlKey;
|
|
56
|
+
const isSubflow = tlKey.startsWith('subflow_');
|
|
57
|
+
const hasTrigger = (timeline?.keyframes || []).some((kf: any) => kf.track === 'trigger');
|
|
58
|
+
|
|
59
|
+
if (visited.has(tlKey)) {
|
|
60
|
+
cycleWarnings.push(`Ciclo: ${[...visited].join(' → ')} → ${tlKey}`);
|
|
61
|
+
return {
|
|
62
|
+
id: tlKey, name, type: 'subflow',
|
|
63
|
+
children: [], isCyclic: true, depth
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const newVisited = new Set(visited);
|
|
68
|
+
newVisited.add(tlKey);
|
|
69
|
+
|
|
70
|
+
const childBpIds = references[tlKey] || [];
|
|
71
|
+
const children: TreeNode[] = childBpIds.map(bpId => {
|
|
72
|
+
const childKey = bpToTimelineKey[bpId];
|
|
73
|
+
if (childKey) {
|
|
74
|
+
return buildNode(childKey, newVisited, depth + 1);
|
|
75
|
+
}
|
|
76
|
+
// Referenced but no timeline found
|
|
77
|
+
return {
|
|
78
|
+
id: bpId, name: bpId, type: 'orphan' as const,
|
|
79
|
+
children: [], depth: depth + 1
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
id: tlKey,
|
|
85
|
+
name,
|
|
86
|
+
type: hasTrigger ? 'trigger' : (isSubflow ? 'subflow' : 'blueprint'),
|
|
87
|
+
children,
|
|
88
|
+
depth
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Roots = timelines that reference subflows but aren't themselves subflows
|
|
93
|
+
const rootKeys = Object.keys(references).filter(key => !referencedTimelineKeys.has(key));
|
|
94
|
+
const roots = rootKeys.map(key => buildNode(key, new Set(), 0));
|
|
95
|
+
|
|
96
|
+
// Orphan subflows: in blueprintLibrary but not referenced by anyone
|
|
97
|
+
const orphanBpIds = Object.keys(blueprintLibrary).filter(bpId => {
|
|
98
|
+
const tlKey = bpToTimelineKey[bpId];
|
|
99
|
+
if (!tlKey) return false;
|
|
100
|
+
return !referencedBpIds.has(bpId) && !rootKeys.includes(tlKey);
|
|
101
|
+
});
|
|
102
|
+
const orphans = orphanBpIds.map(bpId => ({
|
|
103
|
+
id: bpToTimelineKey[bpId] || bpId,
|
|
104
|
+
name: blueprintLibrary[bpId]?.name || bpId,
|
|
105
|
+
type: 'subflow' as const,
|
|
106
|
+
children: [] as TreeNode[],
|
|
107
|
+
depth: 0
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
return { roots, orphans, cycleWarnings };
|
|
111
|
+
}, [timelines, blueprintLibrary]);
|
|
112
|
+
|
|
113
|
+
const handleNavigate = (nodeId: string) => {
|
|
114
|
+
resetNavigation();
|
|
115
|
+
setPrototypeBrand(nodeId);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<AnimatePresence>
|
|
120
|
+
{isOpen && (
|
|
121
|
+
<>
|
|
122
|
+
<motion.div
|
|
123
|
+
initial={{ opacity: 0 }}
|
|
124
|
+
animate={{ opacity: 1 }}
|
|
125
|
+
exit={{ opacity: 0 }}
|
|
126
|
+
className="fixed inset-0 bg-surface-overlay z-40"
|
|
127
|
+
onClick={onClose}
|
|
128
|
+
/>
|
|
129
|
+
<motion.div
|
|
130
|
+
initial={{ opacity: 0, x: -20, scale: 0.95 }}
|
|
131
|
+
animate={{ opacity: 1, x: 0, scale: 1 }}
|
|
132
|
+
exit={{ opacity: 0, x: -20, scale: 0.95 }}
|
|
133
|
+
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
|
|
134
|
+
className="fixed left-4 bottom-20 z-50 w-96 max-h-[60vh] bg-surface-primary border border-border-default rounded-2xl shadow-2xl overflow-hidden flex flex-col"
|
|
135
|
+
style={{ backdropFilter: 'blur(20px)' }}
|
|
136
|
+
onClick={(e) => e.stopPropagation()}
|
|
137
|
+
>
|
|
138
|
+
{/* Header */}
|
|
139
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-border-default bg-surface-glass">
|
|
140
|
+
<div className="flex items-center gap-2">
|
|
141
|
+
<TreePine size={16} className="text-emerald-400" />
|
|
142
|
+
<span className="text-sm font-bold text-text-primary tracking-wide">Jerarquía</span>
|
|
143
|
+
{cycleWarnings.length > 0 && (
|
|
144
|
+
<span className="text-[10px] bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded-full font-mono flex items-center gap-1">
|
|
145
|
+
<AlertTriangle size={10} /> {cycleWarnings.length} ciclo(s)
|
|
146
|
+
</span>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
<button onClick={onClose} className="p-1.5 rounded-lg hover:bg-surface-glass text-text-muted transition-colors">
|
|
150
|
+
<X size={14} />
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Tree */}
|
|
155
|
+
<div className="flex-1 overflow-y-auto custom-scrollbar p-2">
|
|
156
|
+
{roots.length === 0 && orphans.length === 0 && (
|
|
157
|
+
<div className="text-center text-text-muted text-xs py-8">
|
|
158
|
+
No hay flujos con subflows referenciados
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{roots.map(root => (
|
|
163
|
+
<TreeNodeItem
|
|
164
|
+
key={root.id}
|
|
165
|
+
node={root}
|
|
166
|
+
activeId={activeTimelineId}
|
|
167
|
+
onNavigate={handleNavigate}
|
|
168
|
+
/>
|
|
169
|
+
))}
|
|
170
|
+
|
|
171
|
+
{orphans.length > 0 && (
|
|
172
|
+
<div className="mt-3 pt-3 border-t border-border-subtle">
|
|
173
|
+
<div className="px-2 py-1 text-[9px] font-bold text-text-muted uppercase tracking-widest">
|
|
174
|
+
Sin Referencia
|
|
175
|
+
</div>
|
|
176
|
+
{orphans.map(node => (
|
|
177
|
+
<TreeNodeItem
|
|
178
|
+
key={node.id}
|
|
179
|
+
node={node}
|
|
180
|
+
activeId={activeTimelineId}
|
|
181
|
+
onNavigate={handleNavigate}
|
|
182
|
+
/>
|
|
183
|
+
))}
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
</motion.div>
|
|
188
|
+
</>
|
|
189
|
+
)}
|
|
190
|
+
</AnimatePresence>
|
|
191
|
+
);
|
|
192
|
+
};
|