@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,133 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Mic2, Monitor, Zap, Image, PenSquare } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
interface TrackEditorProps {
|
|
5
|
+
keyframe: any;
|
|
6
|
+
onUpdate: (patch: Record<string, any>) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const TRACK_CONFIG: Record<string, { icon: React.ComponentType<any>; label: string; color: string; bg: string; border: string }> = {
|
|
10
|
+
dialogue: { icon: Mic2, label: 'Diálogo', color: 'text-purple-400', bg: 'bg-purple-500/10', border: 'border-purple-500/20' },
|
|
11
|
+
ui: { icon: Monitor, label: 'Estado UI', color: 'text-cyan-400', bg: 'bg-cyan-500/10', border: 'border-cyan-500/20' },
|
|
12
|
+
logic: { icon: Zap, label: 'Lógica MCP', color: 'text-orange-400', bg: 'bg-orange-500/10', border: 'border-orange-500/20' },
|
|
13
|
+
render: { icon: Image, label: 'UI Render', color: 'text-pink-400', bg: 'bg-pink-500/10', border: 'border-pink-500/20' },
|
|
14
|
+
trigger: { icon: Zap, label: 'Trigger', color: 'text-yellow-400', bg: 'bg-yellow-500/10', border: 'border-yellow-500/20' },
|
|
15
|
+
set: { icon: PenSquare, label: 'Set Variable', color: 'text-violet-400', bg: 'bg-violet-500/10', border: 'border-violet-500/20' },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function getTrackConfig(track: string) {
|
|
19
|
+
return TRACK_CONFIG[track] || { icon: Zap, label: track, color: 'text-orange-400', bg: 'bg-orange-500/10', border: 'border-orange-500/20' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Dialogue track — actor select + TTS speech textarea. */
|
|
23
|
+
export const DialogueEditor: React.FC<TrackEditorProps> = ({ keyframe, onUpdate }) => (
|
|
24
|
+
<div className="flex flex-col gap-4">
|
|
25
|
+
<h4 className="text-[10px] font-bold text-purple-400/40 uppercase tracking-widest border-b border-purple-500/20 pb-1">Configuración Diálogo</h4>
|
|
26
|
+
<div className="flex flex-col gap-1">
|
|
27
|
+
<label className="text-[10px] text-text-muted">Actor ID</label>
|
|
28
|
+
<select value={keyframe.actorId || 'system'} onChange={e => onUpdate({ actorId: e.target.value })}
|
|
29
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-1.5 text-xs text-purple-300 focus:outline-hidden focus:border-purple-500/50">
|
|
30
|
+
<option value="system">🚀 System</option><option value="user">👤 User</option><option value="client">💼 Client</option><option value="inventory_agent">📦 Inventory Agent</option>
|
|
31
|
+
</select>
|
|
32
|
+
</div>
|
|
33
|
+
<div className="flex flex-col gap-1 flex-1">
|
|
34
|
+
<label className="text-[10px] text-text-muted flex justify-between"><span>Texto (TTS)</span><span className="text-purple-400/50">{keyframe.speech?.length || 0} chars</span></label>
|
|
35
|
+
<textarea value={keyframe.speech || ''} onChange={e => onUpdate({ speech: e.target.value })}
|
|
36
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-2 text-xs text-text-primary focus:outline-hidden focus:border-purple-500/50 min-h-[120px] resize-y" placeholder="Escribe el diálogo aquí..." />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/** UI State track — state trigger + 3D canvas toggle. */
|
|
42
|
+
export const UIStateEditor: React.FC<TrackEditorProps> = ({ keyframe, onUpdate }) => (
|
|
43
|
+
<div className="flex flex-col gap-4">
|
|
44
|
+
<h4 className="text-[10px] font-bold text-cyan-400/40 uppercase tracking-widest border-b border-cyan-500/20 pb-1">Eventos Interfaz</h4>
|
|
45
|
+
<div className="flex flex-col gap-1">
|
|
46
|
+
<label className="text-[10px] text-text-muted">App State Trigger</label>
|
|
47
|
+
<input type="text" value={keyframe.state || ''} onChange={e => onUpdate({ state: e.target.value })}
|
|
48
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-1.5 text-xs text-cyan-300 font-mono focus:outline-hidden focus:border-cyan-500/50" placeholder="e.g., loading_screen" />
|
|
49
|
+
</div>
|
|
50
|
+
<div className="mt-1 bg-surface-glass rounded-lg border border-border-default p-3">
|
|
51
|
+
<label className="flex items-start gap-3 cursor-pointer">
|
|
52
|
+
<input type="checkbox" checked={keyframe.canvas || false} onChange={e => onUpdate({ canvas: e.target.checked })} className="mt-0.5 rounded border-border-strong bg-surface-glass text-cyan-500 w-4 h-4 cursor-pointer" />
|
|
53
|
+
<div className="flex flex-col"><span className="text-xs font-bold text-cyan-300">Embeber Lienzo 3D</span><span className="text-[10px] text-text-muted mt-1">Al pasar por este nodo, mostrará el motor SpatialCanvas3D.</span></div>
|
|
54
|
+
</label>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
/** Render track — component name + autoComplete + uiSchema JSON. */
|
|
60
|
+
export const RenderEditor: React.FC<TrackEditorProps> = ({ keyframe, onUpdate }) => (
|
|
61
|
+
<div className="flex flex-col gap-4">
|
|
62
|
+
<h4 className="text-[10px] font-bold text-pink-400/40 uppercase tracking-widest border-b border-pink-500/20 pb-1">UI Component</h4>
|
|
63
|
+
<div className="flex flex-col gap-1">
|
|
64
|
+
<label className="text-[10px] text-text-muted">Nombre</label>
|
|
65
|
+
<input type="text" value={keyframe.label || keyframe.state || ''} onChange={e => onUpdate({ label: e.target.value, state: e.target.value })}
|
|
66
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-1.5 text-xs text-pink-300 font-mono focus:outline-hidden focus:border-pink-500/50" placeholder="e.g., Formulario Contacto" />
|
|
67
|
+
</div>
|
|
68
|
+
<div className="bg-surface-glass rounded-lg border border-border-default p-3">
|
|
69
|
+
<label className="flex items-start gap-3 cursor-pointer">
|
|
70
|
+
<input type="checkbox" checked={keyframe.autoComplete ?? true} onChange={e => onUpdate({ autoComplete: e.target.checked })} className="mt-0.5 rounded border-border-strong bg-surface-glass text-pink-500 w-4 h-4 cursor-pointer" />
|
|
71
|
+
<div className="flex flex-col"><span className="text-xs font-bold text-pink-300">Auto-Complete</span><span className="text-[10px] text-text-muted mt-1">Si activo, el nodo se completa al mostrarse. Si no, espera interacción.</span></div>
|
|
72
|
+
</label>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="flex flex-col gap-1">
|
|
75
|
+
<label className="text-[10px] text-text-muted">UI Schema (JSON)</label>
|
|
76
|
+
<textarea value={typeof keyframe.uiSchema === 'object' ? JSON.stringify(keyframe.uiSchema, null, 2) : (keyframe.uiSchema || '')}
|
|
77
|
+
onChange={e => { try { onUpdate({ uiSchema: JSON.parse(e.target.value) }); } catch {} }}
|
|
78
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-2 text-[10px] text-pink-200 font-mono focus:outline-hidden focus:border-pink-500/50 min-h-[80px] resize-y"
|
|
79
|
+
placeholder='{"type":"div","children":...}' />
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
/** Logic track — intent field. */
|
|
85
|
+
export const LogicEditor: React.FC<TrackEditorProps> = ({ keyframe, onUpdate }) => (
|
|
86
|
+
<div className="flex flex-col gap-4">
|
|
87
|
+
<h4 className="text-[10px] font-bold text-orange-400/40 uppercase tracking-widest border-b border-orange-500/20 pb-1">Motor de Lógica</h4>
|
|
88
|
+
<div className="flex flex-col gap-1">
|
|
89
|
+
<label className="text-[10px] text-text-muted">Tópico / Intención</label>
|
|
90
|
+
<input type="text" value={keyframe.intent || ''} onChange={e => onUpdate({ intent: e.target.value })}
|
|
91
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-1.5 text-xs text-orange-300 font-mono focus:outline-hidden focus:border-orange-500/50" placeholder="e.g., process_payment" />
|
|
92
|
+
</div>
|
|
93
|
+
<div className="p-3 bg-orange-500/10 border border-orange-500/20 rounded"><p className="text-[10px] text-orange-300/80 leading-relaxed">Este nodo disparará eventos hacia el Event Mesh para orquestar servicios remotos.</p></div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
/** Trigger track — guard expression + priority. */
|
|
98
|
+
export const TriggerEditor: React.FC<TrackEditorProps> = ({ keyframe, onUpdate }) => (
|
|
99
|
+
<div className="flex flex-col gap-4">
|
|
100
|
+
<h4 className="text-[10px] font-bold text-yellow-400/40 uppercase tracking-widest border-b border-yellow-500/20 pb-1">Activación Automática</h4>
|
|
101
|
+
<div className="flex flex-col gap-1">
|
|
102
|
+
<label className="text-[10px] text-text-muted">Guard Expression</label>
|
|
103
|
+
<input type="text" value={keyframe.guardExpression || ''} onChange={e => onUpdate({ guardExpression: e.target.value })}
|
|
104
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-1.5 text-xs text-yellow-300 font-mono focus:outline-hidden focus:border-yellow-500/50" placeholder="!user.onboardingComplete" />
|
|
105
|
+
<span className="text-[9px] text-text-muted">Si true, el flujo se auto-activa al cargar la app.</span>
|
|
106
|
+
</div>
|
|
107
|
+
<div className="flex flex-col gap-1">
|
|
108
|
+
<label className="text-[10px] text-text-muted">Prioridad</label>
|
|
109
|
+
<input type="number" value={keyframe.priority || 0} onChange={e => onUpdate({ priority: parseInt(e.target.value) || 0 })}
|
|
110
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-1.5 text-xs text-yellow-300 font-mono focus:outline-hidden focus:border-yellow-500/50 w-20" placeholder="0" />
|
|
111
|
+
<span className="text-[9px] text-text-muted">Mayor = se evalúa primero.</span>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
/** Set Variable track — target + value fields. */
|
|
117
|
+
export const SetVariableEditor: React.FC<TrackEditorProps> = ({ keyframe, onUpdate }) => (
|
|
118
|
+
<div className="flex flex-col gap-4">
|
|
119
|
+
<h4 className="text-[10px] font-bold text-violet-400/40 uppercase tracking-widest border-b border-violet-500/20 pb-1">Escritura de Variable</h4>
|
|
120
|
+
<div className="flex flex-col gap-1">
|
|
121
|
+
<label className="text-[10px] text-text-muted">Variable Target</label>
|
|
122
|
+
<input type="text" value={keyframe.variableTarget || ''} onChange={e => onUpdate({ variableTarget: e.target.value })}
|
|
123
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-1.5 text-xs text-violet-300 font-mono focus:outline-hidden focus:border-violet-500/50" placeholder="e.g., user.onboardingComplete" />
|
|
124
|
+
<span className="text-[9px] text-text-muted">Prefijo "user." = persistente. Sin prefijo = sesión.</span>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="flex flex-col gap-1">
|
|
127
|
+
<label className="text-[10px] text-text-muted">Valor</label>
|
|
128
|
+
<input type="text" value={keyframe.variableValue ?? ''} onChange={e => onUpdate({ variableValue: e.target.value })}
|
|
129
|
+
className="bg-surface-overlay border border-border-default rounded px-2 py-1.5 text-xs text-emerald-300 font-mono focus:outline-hidden focus:border-violet-500/50" placeholder="e.g., true, 50, user.xp + 10" />
|
|
130
|
+
<span className="text-[9px] text-text-muted">Puede ser literal o expresión JS.</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TreeNodeItem — recursive node renderer for the DependencyTreePanel.
|
|
3
|
+
* Extracted to reduce DependencyTreePanel line count.
|
|
4
|
+
*/
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
|
+
import {
|
|
7
|
+
ChevronRight, ChevronDown, FileText, Network,
|
|
8
|
+
Zap, AlertTriangle
|
|
9
|
+
} from 'lucide-react';
|
|
10
|
+
|
|
11
|
+
export interface TreeNode {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
type: 'blueprint' | 'subflow' | 'trigger' | 'orphan';
|
|
15
|
+
children: TreeNode[];
|
|
16
|
+
isCyclic?: boolean;
|
|
17
|
+
depth: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const TreeNodeItem: React.FC<{
|
|
21
|
+
node: TreeNode;
|
|
22
|
+
activeId: string;
|
|
23
|
+
onNavigate: (id: string) => void;
|
|
24
|
+
}> = ({ node, activeId, onNavigate }) => {
|
|
25
|
+
const [isExpanded, setIsExpanded] = useState(node.depth < 2);
|
|
26
|
+
const hasChildren = node.children.length > 0;
|
|
27
|
+
const isActive = node.id === activeId;
|
|
28
|
+
|
|
29
|
+
const icon = node.type === 'trigger'
|
|
30
|
+
? <Zap size={13} className="text-amber-400" />
|
|
31
|
+
: node.type === 'subflow'
|
|
32
|
+
? <Network size={13} className={node.isCyclic ? 'text-red-400' : 'text-violet-400'} />
|
|
33
|
+
: node.type === 'orphan'
|
|
34
|
+
? <AlertTriangle size={13} className="text-text-muted" />
|
|
35
|
+
: <FileText size={13} className="text-cyan-400" />;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
<div
|
|
40
|
+
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-lg cursor-pointer transition-all mb-0.5 ${
|
|
41
|
+
isActive
|
|
42
|
+
? 'bg-cyan-500/15 border border-cyan-500/30'
|
|
43
|
+
: node.isCyclic
|
|
44
|
+
? 'bg-red-500/5 border border-red-500/20 hover:bg-red-500/10'
|
|
45
|
+
: 'hover:bg-surface-glass border border-transparent'
|
|
46
|
+
}`}
|
|
47
|
+
style={{ paddingLeft: `${8 + node.depth * 16}px` }}
|
|
48
|
+
onClick={() => onNavigate(node.id)}
|
|
49
|
+
>
|
|
50
|
+
{hasChildren ? (
|
|
51
|
+
<button
|
|
52
|
+
onClick={(e) => { e.stopPropagation(); setIsExpanded(!isExpanded); }}
|
|
53
|
+
className="p-0.5 rounded hover:bg-surface-glass text-text-muted shrink-0"
|
|
54
|
+
>
|
|
55
|
+
{isExpanded ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
|
56
|
+
</button>
|
|
57
|
+
) : (
|
|
58
|
+
<span className="w-5 shrink-0" />
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{icon}
|
|
62
|
+
|
|
63
|
+
<span className={`text-xs truncate flex-1 ${
|
|
64
|
+
isActive ? 'text-text-primary font-medium' : node.isCyclic ? 'text-red-300' : 'text-text-secondary'
|
|
65
|
+
}`}>
|
|
66
|
+
{node.name}
|
|
67
|
+
</span>
|
|
68
|
+
|
|
69
|
+
{node.isCyclic && (
|
|
70
|
+
<span className="text-[8px] bg-red-500/20 text-red-400 px-1 py-0.5 rounded font-mono">CICLO</span>
|
|
71
|
+
)}
|
|
72
|
+
{node.type === 'trigger' && (
|
|
73
|
+
<span className="text-[8px] bg-amber-500/10 text-amber-400 px-1 py-0.5 rounded font-mono">⚡</span>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{hasChildren && isExpanded && (
|
|
78
|
+
<div>
|
|
79
|
+
{node.children.map(child => (
|
|
80
|
+
<TreeNodeItem
|
|
81
|
+
key={child.id}
|
|
82
|
+
node={child}
|
|
83
|
+
activeId={activeId}
|
|
84
|
+
onNavigate={onNavigate}
|
|
85
|
+
/>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Edge, MarkerType } from '@xyflow/react';
|
|
2
|
+
|
|
3
|
+
// ─── Known source handle IDs for edge coloring ───
|
|
4
|
+
const VALID_HANDLES = new Set(['success', 'error', 'true', 'false', 'fallback', 'cancel']);
|
|
5
|
+
|
|
6
|
+
const HANDLE_COLORS: Record<string, string> = {
|
|
7
|
+
success: '#22c55e', error: '#ef4444', true: '#22c55e',
|
|
8
|
+
false: '#ef4444', fallback: '#f97316', cancel: '#a855f7',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build ReactFlow edges from timeline edges with active-node highlighting.
|
|
13
|
+
*/
|
|
14
|
+
export function buildStyledEdges(
|
|
15
|
+
timelineEdges: any[],
|
|
16
|
+
activeNodeIds: string[],
|
|
17
|
+
activeEdgeIds: string[]
|
|
18
|
+
): Edge[] {
|
|
19
|
+
return timelineEdges.map(e => {
|
|
20
|
+
const rawHandle = e.sourceHandle || 'success';
|
|
21
|
+
const sourceHandle = VALID_HANDLES.has(rawHandle) ? rawHandle : 'success';
|
|
22
|
+
const color = HANDLE_COLORS[sourceHandle] || '#a855f7';
|
|
23
|
+
|
|
24
|
+
const isSourceActive = activeNodeIds.includes(e.source);
|
|
25
|
+
const isTraversed = activeEdgeIds.includes(e.id);
|
|
26
|
+
|
|
27
|
+
const activeColor = isTraversed ? '#22d3ee' : color;
|
|
28
|
+
const glowFilter = isTraversed
|
|
29
|
+
? `drop-shadow(0 0 10px ${activeColor})`
|
|
30
|
+
: isSourceActive ? 'drop-shadow(0 0 5px rgba(255,255,255,0.8))' : 'none';
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
id: e.id,
|
|
34
|
+
source: e.source,
|
|
35
|
+
target: e.target,
|
|
36
|
+
sourceHandle,
|
|
37
|
+
targetHandle: e.targetHandle,
|
|
38
|
+
animated: isSourceActive || isTraversed,
|
|
39
|
+
style: { stroke: activeColor, strokeWidth: isTraversed ? 5 : isSourceActive ? 4 : 3, filter: glowFilter },
|
|
40
|
+
markerEnd: { type: MarkerType.ArrowClosed, color: activeColor },
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Edge, Node } from '@xyflow/react';
|
|
2
|
+
|
|
3
|
+
interface ClipboardState {
|
|
4
|
+
nodes: Node[];
|
|
5
|
+
edges: Edge[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a keydown handler for the ReactFlow editor canvas.
|
|
10
|
+
* Handles: Select All (Cmd+A), Copy (Cmd+C), Paste (Cmd+V), Delete/Backspace.
|
|
11
|
+
*/
|
|
12
|
+
export function createEditorKeyHandler(opts: {
|
|
13
|
+
getNodes: () => Node[];
|
|
14
|
+
getEdges: () => Edge[];
|
|
15
|
+
setNodes: (fn: (nodes: Node[]) => Node[]) => void;
|
|
16
|
+
setEdges: (fn: (edges: Edge[]) => Edge[]) => void;
|
|
17
|
+
clipboard: React.MutableRefObject<ClipboardState>;
|
|
18
|
+
activeTimelineId: string;
|
|
19
|
+
currentBlueprintId: string | null;
|
|
20
|
+
addKeyframe: (tid: string, kf: any) => void;
|
|
21
|
+
addTimelineEdge: (tid: string, edge: any) => void;
|
|
22
|
+
removeKeyframe: (tid: string, id: string) => void;
|
|
23
|
+
removeTimelineEdge: (tid: string, id: string) => void;
|
|
24
|
+
setSelectedNodeId: (id: string | null) => void;
|
|
25
|
+
NODE_TYPE_TO_TRACK: Record<string, string>;
|
|
26
|
+
}) {
|
|
27
|
+
return (e: KeyboardEvent) => {
|
|
28
|
+
const meta = e.metaKey || e.ctrlKey;
|
|
29
|
+
const t = e.target as HTMLElement;
|
|
30
|
+
if (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT') return;
|
|
31
|
+
|
|
32
|
+
const nodes = opts.getNodes();
|
|
33
|
+
const edges = opts.getEdges();
|
|
34
|
+
const tid = opts.currentBlueprintId || opts.activeTimelineId;
|
|
35
|
+
|
|
36
|
+
// Select All
|
|
37
|
+
if (meta && e.key === 'a') {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
opts.setNodes(n => n.map(x => ({ ...x, selected: true })));
|
|
40
|
+
opts.setEdges(ed => ed.map(x => ({ ...x, selected: true })));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Copy
|
|
44
|
+
if (meta && e.key === 'c') {
|
|
45
|
+
const sel = nodes.filter(n => n.selected);
|
|
46
|
+
if (!sel.length) return;
|
|
47
|
+
const ids = new Set(sel.map(n => n.id));
|
|
48
|
+
opts.clipboard.current = { nodes: sel, edges: edges.filter(e => ids.has(e.source) && ids.has(e.target)) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Paste
|
|
52
|
+
if (meta && e.key === 'v') {
|
|
53
|
+
const { nodes: cn, edges: ce } = opts.clipboard.current;
|
|
54
|
+
if (!cn.length) return;
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
const idMap: Record<string, string> = {};
|
|
57
|
+
opts.setNodes(n => n.map(x => ({ ...x, selected: false })));
|
|
58
|
+
cn.forEach(node => {
|
|
59
|
+
const newId = `${node.id}_copy_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
60
|
+
idMap[node.id] = newId;
|
|
61
|
+
opts.addKeyframe(tid, {
|
|
62
|
+
id: newId, t: Date.now(),
|
|
63
|
+
track: opts.NODE_TYPE_TO_TRACK[node.type || ''] || node.type || 'dialogue',
|
|
64
|
+
label: `${node.data.label || node.data.state || 'Copy'} (copia)`,
|
|
65
|
+
state: `${node.data.state || node.data.label || 'Copy'} (copia)`,
|
|
66
|
+
position: { x: node.position.x + 50, y: node.position.y + 50 },
|
|
67
|
+
speech: node.data.speech, actorId: node.data.actorId, intent: node.data.intent,
|
|
68
|
+
uiSchema: node.data.uiSchema || node.data.schema, autoComplete: node.data.autoComplete,
|
|
69
|
+
triggerType: node.data.triggerType, guardExpression: node.data.guardExpression,
|
|
70
|
+
priority: node.data.priority, conditionExpression: node.data.conditionExpression,
|
|
71
|
+
variableTarget: node.data.variableTarget, variableValue: node.data.variableValue,
|
|
72
|
+
targetBlueprintId: node.data.targetBlueprintId, handle: node.data.handle,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
ce.forEach(edge => {
|
|
76
|
+
const ns = idMap[edge.source], nt = idMap[edge.target];
|
|
77
|
+
if (ns && nt) opts.addTimelineEdge(tid, {
|
|
78
|
+
id: `edge-${ns}-${nt}-${Date.now()}`, source: ns, target: nt,
|
|
79
|
+
sourceHandle: edge.sourceHandle || null, targetHandle: edge.targetHandle || null,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Delete
|
|
85
|
+
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
86
|
+
const sn = nodes.filter(n => n.selected);
|
|
87
|
+
const se = edges.filter(e => e.selected);
|
|
88
|
+
if (!sn.length && !se.length) return;
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
sn.forEach(n => opts.removeKeyframe(tid, n.id));
|
|
91
|
+
se.forEach(e => opts.removeTimelineEdge(tid, e.id));
|
|
92
|
+
opts.setSelectedNodeId(null);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Node } from '@xyflow/react';
|
|
2
|
+
import {
|
|
3
|
+
DialogueNode, LogicNode, StateNode, SubflowNode,
|
|
4
|
+
EntryNode, ReturnNode, RenderNode, TriggerNode,
|
|
5
|
+
ConditionalNode, SetVariableNode, MorphNode
|
|
6
|
+
} from '@decido/canvas-core';
|
|
7
|
+
import {
|
|
8
|
+
RegisterButtonNode, RegisterTabNode, RegisterPanelNode, RegisterSidebarNode,
|
|
9
|
+
RegisterStatusBarNode, RegisterTopBarNode, RegisterActionNode,
|
|
10
|
+
ToggleSidebarNode, TogglePanelNode, SplitPaneNode, FocusTabNode, CloseTabNode,
|
|
11
|
+
SetThemeNode, ApplyPaletteNode, UpdateTokenNode,
|
|
12
|
+
ShowNotificationNode, ShowModalNode, ExecuteCommandNode, RegisterShortcutNode, EmitEventNode,
|
|
13
|
+
ShellConfigNode
|
|
14
|
+
} from '../nodes/shell';
|
|
15
|
+
|
|
16
|
+
// ─── Node Type Registry ───
|
|
17
|
+
export const nodeTypes: Record<string, any> = {
|
|
18
|
+
dialogue: DialogueNode,
|
|
19
|
+
'ai:dialogue': DialogueNode,
|
|
20
|
+
logic: LogicNode,
|
|
21
|
+
'mcp:execute': LogicNode,
|
|
22
|
+
ui: StateNode,
|
|
23
|
+
'ui:state': StateNode,
|
|
24
|
+
'core:subflow': SubflowNode,
|
|
25
|
+
'core:entry': EntryNode,
|
|
26
|
+
'core:return': ReturnNode,
|
|
27
|
+
'ui:render': RenderNode,
|
|
28
|
+
'render': RenderNode,
|
|
29
|
+
'trigger': TriggerNode,
|
|
30
|
+
'event:trigger': TriggerNode,
|
|
31
|
+
'condition': ConditionalNode,
|
|
32
|
+
'logic:condition': ConditionalNode,
|
|
33
|
+
'set': SetVariableNode,
|
|
34
|
+
'state:set': SetVariableNode,
|
|
35
|
+
'morph': MorphNode,
|
|
36
|
+
'ui:morph': MorphNode,
|
|
37
|
+
// Shell Control Nodes
|
|
38
|
+
'shell:registerButton': RegisterButtonNode,
|
|
39
|
+
'shell:registerTab': RegisterTabNode,
|
|
40
|
+
'shell:registerPanel': RegisterPanelNode,
|
|
41
|
+
'shell:registerSidebar': RegisterSidebarNode,
|
|
42
|
+
'shell:registerStatusBar': RegisterStatusBarNode,
|
|
43
|
+
'shell:registerTopBar': RegisterTopBarNode,
|
|
44
|
+
'shell:registerAction': RegisterActionNode,
|
|
45
|
+
'shell:toggleSidebar': ToggleSidebarNode,
|
|
46
|
+
'shell:togglePanel': TogglePanelNode,
|
|
47
|
+
'shell:splitPane': SplitPaneNode,
|
|
48
|
+
'shell:focusTab': FocusTabNode,
|
|
49
|
+
'shell:closeTab': CloseTabNode,
|
|
50
|
+
'shell:setTheme': SetThemeNode,
|
|
51
|
+
'shell:applyPalette': ApplyPaletteNode,
|
|
52
|
+
'shell:updateToken': UpdateTokenNode,
|
|
53
|
+
'shell:notification': ShowNotificationNode,
|
|
54
|
+
'shell:modal': ShowModalNode,
|
|
55
|
+
'shell:execCommand': ExecuteCommandNode,
|
|
56
|
+
'shell:shortcut': RegisterShortcutNode,
|
|
57
|
+
'shell:emitEvent': EmitEventNode,
|
|
58
|
+
'shell:config': ShellConfigNode,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ─── Track X Position Map (default horizontal placement) ───
|
|
62
|
+
export const TRACK_X_MAP: Record<string, number> = {
|
|
63
|
+
'ai:dialogue': 100, 'ui:state': 450, 'mcp:execute': 800,
|
|
64
|
+
'core:subflow': 1100, 'core:entry': 300, 'core:return': 300,
|
|
65
|
+
'ui:render': 1400, 'render': 1400, 'event:trigger': -200,
|
|
66
|
+
'logic:condition': 600, 'state:set': 1000, 'set': 1000,
|
|
67
|
+
'morph': 1400, 'ui:morph': 1400,
|
|
68
|
+
'shell:registerButton': 1700, 'shell:registerTab': 1700, 'shell:registerPanel': 1700,
|
|
69
|
+
'shell:registerSidebar': 1700, 'shell:registerStatusBar': 1700, 'shell:registerTopBar': 1700,
|
|
70
|
+
'shell:registerAction': 1700, 'shell:toggleSidebar': 2000, 'shell:togglePanel': 2000,
|
|
71
|
+
'shell:splitPane': 2000, 'shell:focusTab': 2000, 'shell:closeTab': 2000,
|
|
72
|
+
'shell:setTheme': 2300, 'shell:applyPalette': 2300, 'shell:updateToken': 2300,
|
|
73
|
+
'shell:notification': 2600, 'shell:modal': 2600, 'shell:execCommand': 2600,
|
|
74
|
+
'shell:shortcut': 2600, 'shell:emitEvent': 2600, 'shell:config': 2900,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// ─── Node Type ↔ Track mapping (for pasting/copy) ───
|
|
78
|
+
export const NODE_TYPE_TO_TRACK: Record<string, string> = {
|
|
79
|
+
'dialogue': 'dialogue', 'ai:dialogue': 'dialogue',
|
|
80
|
+
'logic': 'logic', 'mcp:execute': 'logic',
|
|
81
|
+
'ui': 'ui', 'ui:state': 'ui',
|
|
82
|
+
'core:subflow': 'subflow', 'core:entry': 'entry', 'core:return': 'return',
|
|
83
|
+
'ui:render': 'render', 'render': 'render',
|
|
84
|
+
'trigger': 'trigger', 'event:trigger': 'trigger',
|
|
85
|
+
'condition': 'condition', 'logic:condition': 'condition',
|
|
86
|
+
'set': 'set', 'state:set': 'set',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// ─── Map DecidoNode payload → compatible React Flow data ───
|
|
90
|
+
export function mapNodePayload(decidoNode: any): Record<string, any> {
|
|
91
|
+
const data = { ...decidoNode.payload };
|
|
92
|
+
const t = decidoNode.type;
|
|
93
|
+
if (t === 'core:subflow') data.label = decidoNode.payload.label;
|
|
94
|
+
if (t === 'ui:render' || t === 'render') {
|
|
95
|
+
data.label = decidoNode.payload.label || decidoNode.payload.state || 'UI Component';
|
|
96
|
+
data.state = decidoNode.payload.state || decidoNode.payload.label || 'UI Component';
|
|
97
|
+
data.uiSchema = decidoNode.payload.uiSchema || decidoNode.payload.schema;
|
|
98
|
+
}
|
|
99
|
+
if (t === 'event:trigger') {
|
|
100
|
+
Object.assign(data, {
|
|
101
|
+
triggerType: decidoNode.payload.triggerType || 'SYSTEM_START',
|
|
102
|
+
cronExpression: decidoNode.payload.cronExpression,
|
|
103
|
+
webhookPath: decidoNode.payload.webhookPath,
|
|
104
|
+
eventChannel: decidoNode.payload.eventChannel,
|
|
105
|
+
guardExpression: decidoNode.payload.guardExpression,
|
|
106
|
+
priority: decidoNode.payload.priority,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (t === 'logic:condition') data.conditionExpression = decidoNode.payload.conditionExpression || '';
|
|
110
|
+
if (t === 'morph' || t === 'ui:morph') {
|
|
111
|
+
Object.assign(data, {
|
|
112
|
+
morphType: decidoNode.payload.morphType || 'workbench',
|
|
113
|
+
morphData: decidoNode.payload.morphData || null,
|
|
114
|
+
componentId: decidoNode.payload.componentId || '',
|
|
115
|
+
suspendOnMorph: decidoNode.payload.suspendOnMorph || false,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (t.startsWith('shell:')) {
|
|
119
|
+
Object.keys(decidoNode.payload).forEach(key => {
|
|
120
|
+
if (key.startsWith('shell')) data[key] = decidoNode.payload[key];
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return data;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── Build default keyframe from track type (for context menu / drag-drop insertion) ───
|
|
127
|
+
export function buildKeyframeDefaults(track: string, label: string, position: { x: number; y: number }, extra?: any): any {
|
|
128
|
+
const kf: any = { t: Date.now(), track, position, label, state: label };
|
|
129
|
+
if (track === 'dialogue') { kf.speech = 'Nuevo diálogo...'; kf.actorId = 'assistant'; }
|
|
130
|
+
else if (track === 'render' && extra?.uiSchema) { kf.uiSchema = extra.uiSchema; kf.autoComplete = false; }
|
|
131
|
+
else if (track === 'subflow') kf.targetBlueprintId = '';
|
|
132
|
+
else if (track === 'morph') kf.morphType = 'workbench';
|
|
133
|
+
else if (track === 'set') { kf.target = ''; kf.value = ''; }
|
|
134
|
+
else if (track === 'condition') kf.conditionExpression = 'true';
|
|
135
|
+
if (track.startsWith('shell:')) { kf.track = 'shell'; kf.shellAction = extra?.shellAction || track.replace('shell:', ''); }
|
|
136
|
+
return kf;
|
|
137
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
MessageCircle, Cpu, Monitor, Image, Network, LogIn, LogOut,
|
|
4
|
+
Zap, Layout, GitBranch, PenSquare, Terminal, PanelBottom,
|
|
5
|
+
Sidebar, Minus, ArrowUpFromDot, FileText, Columns2, Focus, X,
|
|
6
|
+
Palette, Paintbrush, Pipette, Bell, SquareStack, Play, Keyboard, Radio, Settings2, PanelLeft
|
|
7
|
+
} from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
// ─── Palette types ───
|
|
10
|
+
export interface PaletteItem {
|
|
11
|
+
type: string;
|
|
12
|
+
label: string;
|
|
13
|
+
icon: React.ReactNode;
|
|
14
|
+
color: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
meta?: Record<string, any>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PaletteCategory {
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
icon: React.ReactNode;
|
|
23
|
+
items: PaletteItem[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const DRAG_DATA_KEY = 'application/decido-node';
|
|
27
|
+
|
|
28
|
+
// ─── Static palette categories ───
|
|
29
|
+
export const STATIC_CATEGORIES: PaletteCategory[] = [
|
|
30
|
+
{ id: 'dialogue', label: 'Diálogo', icon: <MessageCircle size={14} />, items: [
|
|
31
|
+
{ type: 'dialogue', label: 'Dialogue', icon: <MessageCircle size={16} />, color: 'border-blue-500/40', description: 'Nodo de diálogo AI' },
|
|
32
|
+
]},
|
|
33
|
+
{ id: 'logic', label: 'Lógica / MCP', icon: <Cpu size={14} />, items: [
|
|
34
|
+
{ type: 'logic', label: 'Logic Gate', icon: <Cpu size={16} />, color: 'border-amber-500/40', description: 'Evaluación condicional' },
|
|
35
|
+
]},
|
|
36
|
+
{ id: 'ui', label: 'Estado UI', icon: <Monitor size={14} />, items: [
|
|
37
|
+
{ type: 'ui', label: 'State Change', icon: <Monitor size={16} />, color: 'border-purple-500/40', description: 'Cambio de estado visual' },
|
|
38
|
+
]},
|
|
39
|
+
{ id: 'hsm', label: 'HSM / Subflujos', icon: <Network size={14} />, items: [
|
|
40
|
+
{ type: 'core:subflow', label: 'Subflow', icon: <Network size={16} />, color: 'border-violet-500/40', description: 'Subflujo jerárquico' },
|
|
41
|
+
{ type: 'core:entry', label: 'Entry', icon: <LogIn size={16} />, color: 'border-emerald-500/40', description: 'Punto de entrada' },
|
|
42
|
+
{ type: 'core:return', label: 'Return', icon: <LogOut size={16} />, color: 'border-rose-500/40', description: 'Punto de retorno' },
|
|
43
|
+
]},
|
|
44
|
+
{ id: 'triggers', label: 'Triggers / Branching', icon: <Zap size={14} />, items: [
|
|
45
|
+
{ type: 'trigger', label: 'Event Trigger', icon: <Zap size={16} />, color: 'border-yellow-500/40', description: 'Disparador de evento' },
|
|
46
|
+
{ type: 'condition', label: 'Condición', icon: <GitBranch size={16} />, color: 'border-amber-500/40', description: 'Bifurcación condicional' },
|
|
47
|
+
{ type: 'set', label: 'Set Variable', icon: <PenSquare size={16} />, color: 'border-violet-500/40', description: 'Escribir variable (UserState o sesión)' },
|
|
48
|
+
]},
|
|
49
|
+
{ id: 'morphology', label: 'Morfología', icon: <Layout size={14} />, items: [
|
|
50
|
+
{ type: 'morph', label: 'Morph Stage', icon: <Monitor size={16} />, color: 'border-purple-500/40', description: 'Controlar área de renderizado' },
|
|
51
|
+
]},
|
|
52
|
+
// Shell Control
|
|
53
|
+
{ id: 'shell-register', label: '🖥️ Shell: Registro', icon: <Terminal size={14} />, items: [
|
|
54
|
+
{ type: 'shell:registerButton', label: 'Activity Bar', icon: <PanelLeft size={16} />, color: 'border-teal-500/40', description: 'Registrar botón en Activity Bar', meta: { shellAction: 'registerActivityBarItem' } },
|
|
55
|
+
{ type: 'shell:registerTab', label: 'Open Tab', icon: <FileText size={16} />, color: 'border-cyan-500/40', description: 'Abrir tab en el editor', meta: { shellAction: 'openTab' } },
|
|
56
|
+
{ type: 'shell:registerPanel', label: 'Panel View', icon: <PanelBottom size={16} />, color: 'border-indigo-500/40', description: 'Registrar vista en panel inferior', meta: { shellAction: 'registerPanelView' } },
|
|
57
|
+
{ type: 'shell:registerSidebar', label: 'Sidebar View', icon: <Sidebar size={16} />, color: 'border-sky-500/40', description: 'Registrar vista en sidebar', meta: { shellAction: 'registerSidebarView' } },
|
|
58
|
+
{ type: 'shell:registerStatusBar', label: 'Status Bar', icon: <Minus size={16} />, color: 'border-emerald-500/40', description: 'Añadir item al status bar', meta: { shellAction: 'registerStatusBarItem' } },
|
|
59
|
+
{ type: 'shell:registerTopBar', label: 'Top Bar', icon: <ArrowUpFromDot size={16} />, color: 'border-violet-500/40', description: 'Añadir botón al top bar', meta: { shellAction: 'registerTopBarItem' } },
|
|
60
|
+
{ type: 'shell:registerAction', label: 'Register Cmd', icon: <Terminal size={16} />, color: 'border-amber-500/40', description: 'Registrar un comando', meta: { shellAction: 'registerCommand' } },
|
|
61
|
+
]},
|
|
62
|
+
{ id: 'shell-layout', label: '🖥️ Shell: Layout', icon: <Columns2 size={14} />, items: [
|
|
63
|
+
{ type: 'shell:toggleSidebar', label: 'Toggle Sidebar', icon: <PanelLeft size={16} />, color: 'border-sky-500/40', description: 'Mostrar/ocultar sidebar', meta: { shellAction: 'toggleSidebar' } },
|
|
64
|
+
{ type: 'shell:togglePanel', label: 'Toggle Panel', icon: <PanelBottom size={16} />, color: 'border-indigo-500/40', description: 'Mostrar/ocultar panel', meta: { shellAction: 'togglePanel' } },
|
|
65
|
+
{ type: 'shell:splitPane', label: 'Split Pane', icon: <Columns2 size={16} />, color: 'border-violet-500/40', description: 'Dividir editor', meta: { shellAction: 'splitPane' } },
|
|
66
|
+
{ type: 'shell:focusTab', label: 'Focus Tab', icon: <Focus size={16} />, color: 'border-cyan-500/40', description: 'Enfocar un tab', meta: { shellAction: 'focusTab' } },
|
|
67
|
+
{ type: 'shell:closeTab', label: 'Close Tab', icon: <X size={16} />, color: 'border-rose-500/40', description: 'Cerrar un tab', meta: { shellAction: 'closeTab' } },
|
|
68
|
+
]},
|
|
69
|
+
{ id: 'shell-theme', label: '🖥️ Shell: Tema', icon: <Palette size={14} />, items: [
|
|
70
|
+
{ type: 'shell:setTheme', label: 'Set Theme', icon: <Palette size={16} />, color: 'border-amber-500/40', description: 'Cambiar dark/light', meta: { shellAction: 'setTheme' } },
|
|
71
|
+
{ type: 'shell:applyPalette', label: 'Apply Palette', icon: <Paintbrush size={16} />, color: 'border-pink-500/40', description: 'Aplicar paleta de colores', meta: { shellAction: 'applyPalette' } },
|
|
72
|
+
{ type: 'shell:updateToken', label: 'CSS Token', icon: <Pipette size={16} />, color: 'border-orange-500/40', description: 'Modificar token CSS', meta: { shellAction: 'updateToken' } },
|
|
73
|
+
]},
|
|
74
|
+
{ id: 'shell-interaction', label: '🖥️ Shell: Interacción', icon: <Bell size={14} />, items: [
|
|
75
|
+
{ type: 'shell:notification', label: 'Notification', icon: <Bell size={16} />, color: 'border-emerald-500/40', description: 'Mostrar toast', meta: { shellAction: 'showNotification' } },
|
|
76
|
+
{ type: 'shell:modal', label: 'Show Modal', icon: <SquareStack size={16} />, color: 'border-violet-500/40', description: 'Abrir modal', meta: { shellAction: 'showModal' } },
|
|
77
|
+
{ type: 'shell:execCommand', label: 'Exec Command', icon: <Play size={16} />, color: 'border-teal-500/40', description: 'Ejecutar comando', meta: { shellAction: 'executeCommand' } },
|
|
78
|
+
{ type: 'shell:shortcut', label: 'Shortcut', icon: <Keyboard size={16} />, color: 'border-amber-500/40', description: 'Registrar atajo', meta: { shellAction: 'registerShortcut' } },
|
|
79
|
+
{ type: 'shell:emitEvent', label: 'Emit Event', icon: <Radio size={16} />, color: 'border-rose-500/40', description: 'Emitir evento', meta: { shellAction: 'emitEvent' } },
|
|
80
|
+
]},
|
|
81
|
+
{ id: 'shell-config', label: '🖥️ Shell: Config', icon: <Settings2 size={14} />, items: [
|
|
82
|
+
{ type: 'shell:config', label: 'Shell Config', icon: <Settings2 size={16} />, color: 'border-teal-500/40', description: 'Configuración completa del shell', meta: { shellAction: 'shellConfig' } },
|
|
83
|
+
]},
|
|
84
|
+
];
|