@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,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Shell Components — Registered at boot time.
|
|
3
|
+
*
|
|
4
|
+
* Only shells that DON'T depend on external workspace packages:
|
|
5
|
+
* - markdown: Renders markdown content
|
|
6
|
+
* - iframe: Renders HTML in a sandboxed iframe (with TW Editor support)
|
|
7
|
+
*
|
|
8
|
+
* shell-vscode and shell-canvas must be registered by the HOST app
|
|
9
|
+
* that has @decido/react-shell and @decido/node-engine as dependencies.
|
|
10
|
+
*/
|
|
11
|
+
import React, { useMemo, useRef, useEffect, useState, useCallback } from 'react';
|
|
12
|
+
import type { ShellProps } from '../../../store/useShellRegistry';
|
|
13
|
+
import {
|
|
14
|
+
Paintbrush, X, Plus, Copy, RotateCcw, Search, MousePointer,
|
|
15
|
+
ChevronDown, ChevronRight,
|
|
16
|
+
Layout, Type, Palette, Box, Maximize, Layers, Sparkles,
|
|
17
|
+
} from 'lucide-react';
|
|
18
|
+
|
|
19
|
+
// ═══════════════════════════════════════════
|
|
20
|
+
// MARKDOWN SHELL
|
|
21
|
+
// ═══════════════════════════════════════════
|
|
22
|
+
export const MarkdownShell: React.FC<ShellProps> = ({ data }) => {
|
|
23
|
+
const content = data?.content || data?.markdown || '';
|
|
24
|
+
return (
|
|
25
|
+
<div className="w-full h-full overflow-auto bg-surface-primary p-8 custom-scrollbar">
|
|
26
|
+
<div className="max-w-3xl mx-auto prose prose-invert prose-sm prose-zinc">
|
|
27
|
+
<div className="whitespace-pre-wrap text-text-primary font-sans leading-relaxed text-sm">
|
|
28
|
+
{content}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ═══════════════════════════════════════════
|
|
36
|
+
// TW INSPECTOR BRIDGE SCRIPT (injected into iframe blob HTML)
|
|
37
|
+
// ═══════════════════════════════════════════
|
|
38
|
+
const TW_BRIDGE_SCRIPT = `
|
|
39
|
+
<script>
|
|
40
|
+
(function() {
|
|
41
|
+
var inspectorActive = false;
|
|
42
|
+
var selectedEl = null;
|
|
43
|
+
var hoveredEl = null;
|
|
44
|
+
|
|
45
|
+
function generateSelector(el) {
|
|
46
|
+
var parts = [];
|
|
47
|
+
var cur = el;
|
|
48
|
+
while (cur && cur !== document.body && cur !== document.documentElement) {
|
|
49
|
+
var part = cur.tagName.toLowerCase();
|
|
50
|
+
if (cur.id) { parts.unshift('#' + cur.id); break; }
|
|
51
|
+
var parent = cur.parentElement;
|
|
52
|
+
if (parent) {
|
|
53
|
+
var sibs = Array.from(parent.children).filter(function(c) { return c.tagName === cur.tagName; });
|
|
54
|
+
if (sibs.length > 1) {
|
|
55
|
+
part += ':nth-of-type(' + (sibs.indexOf(cur) + 1) + ')';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
parts.unshift(part);
|
|
59
|
+
cur = cur.parentElement;
|
|
60
|
+
}
|
|
61
|
+
return parts.join(' > ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
window.addEventListener('message', function(e) {
|
|
65
|
+
if (e.data && e.data.type === 'tw-bridge:toggle') {
|
|
66
|
+
inspectorActive = !!e.data.enabled;
|
|
67
|
+
document.body.style.cursor = inspectorActive ? 'crosshair' : '';
|
|
68
|
+
if (!inspectorActive) {
|
|
69
|
+
if (hoveredEl) { hoveredEl.style.outline = ''; hoveredEl.style.outlineOffset = ''; hoveredEl = null; }
|
|
70
|
+
if (selectedEl) { selectedEl.style.outline = ''; selectedEl.style.outlineOffset = ''; selectedEl = null; }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (e.data && e.data.type === 'tw-bridge:apply') {
|
|
74
|
+
var el = document.querySelector(e.data.selector);
|
|
75
|
+
if (el) el.className = e.data.classes.join(' ');
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
document.addEventListener('mouseover', function(e) {
|
|
80
|
+
if (!inspectorActive) return;
|
|
81
|
+
var t = e.target;
|
|
82
|
+
if (hoveredEl && hoveredEl !== selectedEl) {
|
|
83
|
+
hoveredEl.style.outline = '';
|
|
84
|
+
hoveredEl.style.outlineOffset = '';
|
|
85
|
+
}
|
|
86
|
+
t.style.outline = '2px dashed rgba(139, 92, 246, 0.6)';
|
|
87
|
+
t.style.outlineOffset = '2px';
|
|
88
|
+
hoveredEl = t;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
document.addEventListener('mouseout', function(e) {
|
|
92
|
+
if (!inspectorActive) return;
|
|
93
|
+
if (e.target !== selectedEl) {
|
|
94
|
+
e.target.style.outline = '';
|
|
95
|
+
e.target.style.outlineOffset = '';
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
document.addEventListener('click', function(e) {
|
|
100
|
+
if (!inspectorActive) return;
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
e.stopPropagation();
|
|
103
|
+
var t = e.target;
|
|
104
|
+
if (selectedEl) { selectedEl.style.outline = ''; selectedEl.style.outlineOffset = ''; }
|
|
105
|
+
t.style.outline = '2px solid rgba(139, 92, 246, 0.9)';
|
|
106
|
+
t.style.outlineOffset = '2px';
|
|
107
|
+
selectedEl = t;
|
|
108
|
+
var classes = t.className ? t.className.split(/\\s+/).filter(Boolean) : [];
|
|
109
|
+
var rect = t.getBoundingClientRect();
|
|
110
|
+
window.parent.postMessage({
|
|
111
|
+
type: 'tw-bridge:select',
|
|
112
|
+
selector: generateSelector(t),
|
|
113
|
+
tagName: t.tagName,
|
|
114
|
+
classes: classes,
|
|
115
|
+
originalClasses: classes,
|
|
116
|
+
rect: { width: rect.width, height: rect.height }
|
|
117
|
+
}, '*');
|
|
118
|
+
}, true);
|
|
119
|
+
})();
|
|
120
|
+
</script>`;
|
|
121
|
+
|
|
122
|
+
// ═══════════════════════════════════════════
|
|
123
|
+
// INLINE TW EDITOR — Compact panel for IframeShell
|
|
124
|
+
// ═══════════════════════════════════════════
|
|
125
|
+
|
|
126
|
+
const TW_CATS = [
|
|
127
|
+
{ id: 'layout', label: 'Layout', classes: ['flex','inline-flex','grid','block','hidden','flex-row','flex-col','items-center','items-start','items-end','justify-center','justify-between','justify-start','justify-end','relative','absolute','fixed','overflow-hidden','overflow-auto'] },
|
|
128
|
+
{ id: 'spacing', label: 'Spacing', classes: ['p-0','p-1','p-2','p-3','p-4','p-6','p-8','px-2','px-4','px-6','py-1','py-2','py-3','py-4','m-0','m-1','m-2','m-4','mx-auto','my-2','my-4','gap-1','gap-2','gap-3','gap-4','gap-6'] },
|
|
129
|
+
{ id: 'sizing', label: 'Sizing', classes: ['w-full','w-auto','w-1/2','w-1/3','h-full','h-auto','h-screen','min-w-0','max-w-sm','max-w-md','max-w-lg'] },
|
|
130
|
+
{ id: 'typography', label: 'Type', classes: ['text-xs','text-sm','text-base','text-lg','text-xl','text-2xl','font-light','font-normal','font-medium','font-semibold','font-bold','text-left','text-center','text-right','truncate'] },
|
|
131
|
+
{ id: 'colors', label: 'Colors', classes: ['text-white','text-black','text-gray-500','bg-white','bg-black','bg-transparent','bg-gray-100','bg-gray-800','bg-gray-900','bg-blue-500','bg-green-500','bg-red-500','bg-purple-500','bg-cyan-500'] },
|
|
132
|
+
{ id: 'borders', label: 'Borders', classes: ['border','border-0','border-2','rounded-none','rounded-sm','rounded','rounded-md','rounded-lg','rounded-xl','rounded-2xl','rounded-full'] },
|
|
133
|
+
{ id: 'effects', label: 'Effects', classes: ['shadow-sm','shadow','shadow-md','shadow-lg','shadow-xl','opacity-0','opacity-50','opacity-100','transition-all','transition-colors','cursor-pointer'] },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
interface TwSelection {
|
|
137
|
+
selector: string;
|
|
138
|
+
tagName: string;
|
|
139
|
+
classes: string[];
|
|
140
|
+
originalClasses: string[];
|
|
141
|
+
rect: { width: number; height: number };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function InlineTwEditor({ iframeRef, isOpen, onClose }: {
|
|
145
|
+
iframeRef: React.RefObject<HTMLIFrameElement>;
|
|
146
|
+
isOpen: boolean;
|
|
147
|
+
onClose: () => void;
|
|
148
|
+
}) {
|
|
149
|
+
const [selected, setSelected] = useState<TwSelection | null>(null);
|
|
150
|
+
const [inspectorOn, setInspectorOn] = useState(true);
|
|
151
|
+
const [addInput, setAddInput] = useState('');
|
|
152
|
+
const [expandedCat, setExpandedCat] = useState<string | null>('layout');
|
|
153
|
+
const [filter, setFilter] = useState('');
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!isOpen) return;
|
|
157
|
+
const handler = (e: MessageEvent) => {
|
|
158
|
+
if (e.data?.type === 'tw-bridge:select') {
|
|
159
|
+
setSelected({
|
|
160
|
+
selector: e.data.selector,
|
|
161
|
+
tagName: e.data.tagName,
|
|
162
|
+
classes: e.data.classes || [],
|
|
163
|
+
originalClasses: e.data.originalClasses || e.data.classes || [],
|
|
164
|
+
rect: e.data.rect || { width: 0, height: 0 },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
window.addEventListener('message', handler);
|
|
169
|
+
return () => window.removeEventListener('message', handler);
|
|
170
|
+
}, [isOpen]);
|
|
171
|
+
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (!isOpen) return;
|
|
174
|
+
iframeRef.current?.contentWindow?.postMessage({ type: 'tw-bridge:toggle', enabled: inspectorOn }, '*');
|
|
175
|
+
}, [inspectorOn, isOpen, iframeRef]);
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (isOpen) {
|
|
179
|
+
setInspectorOn(true);
|
|
180
|
+
iframeRef.current?.contentWindow?.postMessage({ type: 'tw-bridge:toggle', enabled: true }, '*');
|
|
181
|
+
} else {
|
|
182
|
+
iframeRef.current?.contentWindow?.postMessage({ type: 'tw-bridge:toggle', enabled: false }, '*');
|
|
183
|
+
}
|
|
184
|
+
}, [isOpen, iframeRef]);
|
|
185
|
+
|
|
186
|
+
const applyClasses = useCallback((newClasses: string[]) => {
|
|
187
|
+
if (!selected) return;
|
|
188
|
+
setSelected(prev => prev ? { ...prev, classes: newClasses } : null);
|
|
189
|
+
iframeRef.current?.contentWindow?.postMessage({
|
|
190
|
+
type: 'tw-bridge:apply',
|
|
191
|
+
selector: selected.selector,
|
|
192
|
+
classes: newClasses,
|
|
193
|
+
}, '*');
|
|
194
|
+
}, [selected, iframeRef]);
|
|
195
|
+
|
|
196
|
+
const removeClass = useCallback((cls: string) => {
|
|
197
|
+
if (!selected) return;
|
|
198
|
+
applyClasses(selected.classes.filter(c => c !== cls));
|
|
199
|
+
}, [selected, applyClasses]);
|
|
200
|
+
|
|
201
|
+
const addClass = useCallback((cls: string) => {
|
|
202
|
+
if (!selected) return;
|
|
203
|
+
const t = cls.trim();
|
|
204
|
+
if (!t || selected.classes.includes(t)) return;
|
|
205
|
+
applyClasses([...selected.classes, t]);
|
|
206
|
+
setAddInput('');
|
|
207
|
+
}, [selected, applyClasses]);
|
|
208
|
+
|
|
209
|
+
const resetClasses = useCallback(() => {
|
|
210
|
+
if (!selected) return;
|
|
211
|
+
applyClasses([...selected.originalClasses]);
|
|
212
|
+
}, [selected, applyClasses]);
|
|
213
|
+
|
|
214
|
+
const copyClasses = useCallback(() => {
|
|
215
|
+
if (!selected) return;
|
|
216
|
+
navigator.clipboard.writeText(selected.classes.join(' '));
|
|
217
|
+
}, [selected]);
|
|
218
|
+
|
|
219
|
+
const hasChanges = selected && JSON.stringify(selected.classes) !== JSON.stringify(selected.originalClasses);
|
|
220
|
+
|
|
221
|
+
if (!isOpen) return null;
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<div className="w-[280px] shrink-0 flex flex-col bg-surface-primary border-l border-border-subtle h-full text-text-primary">
|
|
225
|
+
{/* Header */}
|
|
226
|
+
<div className="px-3 py-2 border-b border-border-subtle shrink-0">
|
|
227
|
+
<div className="flex items-center justify-between mb-1.5">
|
|
228
|
+
<div className="flex items-center gap-1.5">
|
|
229
|
+
<Paintbrush size={12} className="text-accent-purple" />
|
|
230
|
+
<span className="text-[11px] font-bold">TW Editor</span>
|
|
231
|
+
<span className="text-[7px] px-1 py-0.5 rounded-full bg-accent-purple/15 text-accent-purple font-mono font-bold">LIVE</span>
|
|
232
|
+
</div>
|
|
233
|
+
<button onClick={onClose} className="text-text-muted hover:text-text-primary transition-colors">
|
|
234
|
+
<X size={12} />
|
|
235
|
+
</button>
|
|
236
|
+
</div>
|
|
237
|
+
<button
|
|
238
|
+
onClick={() => setInspectorOn(!inspectorOn)}
|
|
239
|
+
className={`w-full flex items-center gap-1.5 px-2 py-1 rounded text-[10px] font-medium transition-all cursor-pointer ${
|
|
240
|
+
inspectorOn ? 'bg-accent-purple/15 text-accent-purple border border-accent-purple/30' : 'bg-surface-glass text-text-muted border border-border-subtle'
|
|
241
|
+
}`}
|
|
242
|
+
>
|
|
243
|
+
<MousePointer size={10} />
|
|
244
|
+
{inspectorOn ? 'Click un elemento' : 'Inspector off'}
|
|
245
|
+
<span className={`ml-auto w-1.5 h-1.5 rounded-full ${inspectorOn ? 'bg-accent-green animate-pulse' : 'bg-text-muted/30'}`} />
|
|
246
|
+
</button>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
{selected ? (
|
|
250
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
251
|
+
{/* Element info */}
|
|
252
|
+
<div className="px-3 py-1.5 border-b border-border-subtle/50 shrink-0">
|
|
253
|
+
<div className="flex items-center gap-1.5">
|
|
254
|
+
<span className="text-[9px] font-mono px-1 py-0.5 rounded bg-surface-glass text-text-muted"><{selected.tagName.toLowerCase()}></span>
|
|
255
|
+
<span className="text-[8px] font-mono text-text-muted/40 ml-auto">{Math.round(selected.rect.width)}×{Math.round(selected.rect.height)}</span>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
{/* Current classes */}
|
|
260
|
+
<div className="px-3 py-1.5 border-b border-border-subtle/50 shrink-0">
|
|
261
|
+
<div className="flex items-center justify-between mb-1">
|
|
262
|
+
<span className="text-[8px] font-mono text-text-muted/60 uppercase tracking-wider">Clases ({selected.classes.length})</span>
|
|
263
|
+
<div className="flex gap-1">
|
|
264
|
+
{hasChanges && (
|
|
265
|
+
<button onClick={resetClasses} className="text-[7px] text-accent-amber flex items-center gap-0.5 cursor-pointer"><RotateCcw size={7} /> Undo</button>
|
|
266
|
+
)}
|
|
267
|
+
<button onClick={copyClasses} className="text-[7px] text-text-muted/40 hover:text-accent-cyan flex items-center gap-0.5 cursor-pointer"><Copy size={7} /> Copy</button>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
<div className="flex flex-wrap gap-0.5 max-h-[100px] overflow-y-auto custom-scrollbar">
|
|
271
|
+
{selected.classes.map(cls => {
|
|
272
|
+
const isNew = !selected.originalClasses.includes(cls);
|
|
273
|
+
return (
|
|
274
|
+
<span key={cls} className={`group inline-flex items-center gap-0.5 px-1 py-0.5 rounded text-[8px] font-mono transition-all ${
|
|
275
|
+
isNew ? 'bg-accent-green/15 text-accent-green border border-accent-green/30' : 'bg-surface-glass text-text-secondary border border-border-subtle'
|
|
276
|
+
}`}>
|
|
277
|
+
{cls}
|
|
278
|
+
<button onClick={() => removeClass(cls)} className="opacity-0 group-hover:opacity-100 text-red-400 cursor-pointer"><X size={7} /></button>
|
|
279
|
+
</span>
|
|
280
|
+
);
|
|
281
|
+
})}
|
|
282
|
+
</div>
|
|
283
|
+
{hasChanges && (
|
|
284
|
+
<div className="mt-1 flex flex-wrap gap-0.5">
|
|
285
|
+
{selected.originalClasses.filter(c => !selected.classes.includes(c)).map(cls => (
|
|
286
|
+
<button key={`rm-${cls}`} onClick={() => addClass(cls)} className="px-1 py-0.5 rounded text-[8px] font-mono bg-red-500/10 text-red-400/60 border border-red-500/20 line-through hover:no-underline cursor-pointer">{cls}</button>
|
|
287
|
+
))}
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Add input */}
|
|
293
|
+
<div className="px-3 py-1.5 border-b border-border-subtle/50 shrink-0 flex gap-1">
|
|
294
|
+
<div className="flex-1 relative">
|
|
295
|
+
<Plus size={9} className="absolute left-1.5 top-1/2 -translate-y-1/2 text-text-muted/40" />
|
|
296
|
+
<input
|
|
297
|
+
type="text" value={addInput}
|
|
298
|
+
onChange={e => setAddInput(e.target.value)}
|
|
299
|
+
onKeyDown={e => { if (e.key === 'Enter' && addInput.trim()) addClass(addInput); }}
|
|
300
|
+
placeholder="Agregar clase..."
|
|
301
|
+
className="w-full pl-5 pr-1 py-1 text-[10px] font-mono bg-surface-glass border border-border-subtle rounded text-text-primary placeholder:text-text-muted/30 focus:outline-none focus:border-accent-purple/40"
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
<button onClick={() => addClass(addInput)} disabled={!addInput.trim()} className="px-2 py-1 rounded bg-accent-purple/15 text-accent-purple text-[9px] font-bold disabled:opacity-30 cursor-pointer">+</button>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
{/* Quick-add */}
|
|
308
|
+
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
|
309
|
+
<div className="px-3 py-1 border-b border-border-subtle/30 sticky top-0 bg-surface-primary z-10">
|
|
310
|
+
<div className="relative">
|
|
311
|
+
<Search size={9} className="absolute left-1.5 top-1/2 -translate-y-1/2 text-text-muted/30" />
|
|
312
|
+
<input type="text" value={filter} onChange={e => setFilter(e.target.value)} placeholder="Filtrar..."
|
|
313
|
+
className="w-full pl-5 pr-1 py-0.5 text-[9px] font-mono bg-surface-glass border border-border-subtle/50 rounded text-text-primary placeholder:text-text-muted/20 focus:outline-none" />
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
{TW_CATS.map(cat => {
|
|
317
|
+
const filtered = filter ? cat.classes.filter(c => c.includes(filter.toLowerCase())) : cat.classes;
|
|
318
|
+
if (filtered.length === 0) return null;
|
|
319
|
+
const isOpen = expandedCat === cat.id;
|
|
320
|
+
return (
|
|
321
|
+
<div key={cat.id} className="border-b border-border-subtle/20">
|
|
322
|
+
<button onClick={() => setExpandedCat(isOpen ? null : cat.id)} className="w-full flex items-center gap-1.5 px-3 py-1 hover:bg-surface-glass text-left cursor-pointer">
|
|
323
|
+
{isOpen ? <ChevronDown size={8} /> : <ChevronRight size={8} />}
|
|
324
|
+
<span className="text-[9px] font-semibold text-text-primary flex-1">{cat.label}</span>
|
|
325
|
+
<span className="text-[7px] font-mono text-text-muted/40">{filtered.length}</span>
|
|
326
|
+
</button>
|
|
327
|
+
{isOpen && (
|
|
328
|
+
<div className="px-3 pb-1.5 flex flex-wrap gap-0.5">
|
|
329
|
+
{filtered.map(cls => {
|
|
330
|
+
const active = selected.classes.includes(cls);
|
|
331
|
+
return (
|
|
332
|
+
<button key={cls} onClick={() => active ? removeClass(cls) : addClass(cls)}
|
|
333
|
+
className={`px-1 py-0.5 rounded text-[8px] font-mono cursor-pointer transition-all ${
|
|
334
|
+
active ? 'bg-accent-green/15 text-accent-green border border-accent-green/30' : 'bg-surface-glass text-text-muted border border-border-subtle/50 hover:border-accent-purple/30'
|
|
335
|
+
}`}>
|
|
336
|
+
{active ? '✓ ' : ''}{cls}
|
|
337
|
+
</button>
|
|
338
|
+
);
|
|
339
|
+
})}
|
|
340
|
+
</div>
|
|
341
|
+
)}
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
})}
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
{/* Footer */}
|
|
348
|
+
{hasChanges && (
|
|
349
|
+
<div className="px-3 py-1.5 border-t border-border-subtle bg-surface-secondary/50 shrink-0">
|
|
350
|
+
<div className="flex items-center justify-between mb-0.5">
|
|
351
|
+
<span className="text-[7px] font-mono text-accent-green/60 uppercase">className</span>
|
|
352
|
+
<button onClick={copyClasses} className="text-[7px] text-text-muted/40 hover:text-accent-cyan flex items-center gap-0.5 cursor-pointer"><Copy size={7} /> Copy</button>
|
|
353
|
+
</div>
|
|
354
|
+
<div className="text-[8px] font-mono text-text-secondary bg-surface-primary p-1.5 rounded border border-border-subtle max-h-[48px] overflow-y-auto break-all">
|
|
355
|
+
{selected.classes.join(' ')}
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
)}
|
|
359
|
+
</div>
|
|
360
|
+
) : (
|
|
361
|
+
<div className="flex-1 flex flex-col items-center justify-center p-4 text-center">
|
|
362
|
+
<MousePointer size={20} className="text-accent-purple/30 mb-2" />
|
|
363
|
+
<p className="text-[10px] text-text-muted">Click un elemento del preview</p>
|
|
364
|
+
<p className="text-[8px] text-text-muted/40 mt-0.5">para editar sus clases Tailwind</p>
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
</div>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ═══════════════════════════════════════════
|
|
372
|
+
// IFRAME SHELL (with TW Editor support)
|
|
373
|
+
// ═══════════════════════════════════════════
|
|
374
|
+
export const IframeShell: React.FC<ShellProps> = ({ data, instanceId }) => {
|
|
375
|
+
const iframeRef = useRef<HTMLIFrameElement>(null!);
|
|
376
|
+
const content = data?.content || data?.html || '';
|
|
377
|
+
const url = data?.url || '';
|
|
378
|
+
const [twEditorOpen, setTwEditorOpen] = useState(false);
|
|
379
|
+
|
|
380
|
+
// Inject TW bridge script + Tailwind CDN into HTML content
|
|
381
|
+
const blobUrl = useMemo(() => {
|
|
382
|
+
if (url) return url;
|
|
383
|
+
if (!content) return 'about:blank';
|
|
384
|
+
|
|
385
|
+
// Inject Tailwind CDN + bridge script at the end of the HTML
|
|
386
|
+
const twCdn = '<script src="https://cdn.tailwindcss.com"><\/script>';
|
|
387
|
+
let enrichedContent = content;
|
|
388
|
+
|
|
389
|
+
// Insert before </body> or </html> or at the end
|
|
390
|
+
if (enrichedContent.includes('</body>')) {
|
|
391
|
+
enrichedContent = enrichedContent.replace('</body>', `${twCdn}${TW_BRIDGE_SCRIPT}</body>`);
|
|
392
|
+
} else if (enrichedContent.includes('</html>')) {
|
|
393
|
+
enrichedContent = enrichedContent.replace('</html>', `${twCdn}${TW_BRIDGE_SCRIPT}</html>`);
|
|
394
|
+
} else {
|
|
395
|
+
enrichedContent += `${twCdn}${TW_BRIDGE_SCRIPT}`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const blob = new Blob([enrichedContent], { type: 'text/html' });
|
|
399
|
+
return URL.createObjectURL(blob);
|
|
400
|
+
}, [content, url]);
|
|
401
|
+
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
return () => {
|
|
404
|
+
if (!url && blobUrl !== 'about:blank') {
|
|
405
|
+
URL.revokeObjectURL(blobUrl);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}, [blobUrl, url]);
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<div className="w-full h-full bg-surface-secondary rounded-lg overflow-hidden flex">
|
|
412
|
+
{/* Iframe preview */}
|
|
413
|
+
<div className="flex-1 relative">
|
|
414
|
+
<iframe
|
|
415
|
+
ref={iframeRef}
|
|
416
|
+
src={blobUrl}
|
|
417
|
+
title={`sandbox-${instanceId}`}
|
|
418
|
+
sandbox="allow-scripts allow-same-origin allow-forms"
|
|
419
|
+
className="w-full h-full border-none"
|
|
420
|
+
/>
|
|
421
|
+
{/* TW Editor toggle button */}
|
|
422
|
+
<button
|
|
423
|
+
onClick={() => setTwEditorOpen(!twEditorOpen)}
|
|
424
|
+
className={`absolute top-2 right-2 z-10 flex items-center gap-1 px-2 py-1 rounded-lg text-[10px] font-semibold transition-all shadow-lg cursor-pointer ${
|
|
425
|
+
twEditorOpen
|
|
426
|
+
? 'bg-accent-purple text-white'
|
|
427
|
+
: 'bg-black/60 text-white/80 hover:bg-accent-purple/80 hover:text-white backdrop-blur-sm'
|
|
428
|
+
}`}
|
|
429
|
+
title="Tailwind Editor"
|
|
430
|
+
>
|
|
431
|
+
<Paintbrush size={11} /> TW
|
|
432
|
+
</button>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
{/* Inline TW Editor Panel */}
|
|
436
|
+
<InlineTwEditor
|
|
437
|
+
iframeRef={iframeRef}
|
|
438
|
+
isOpen={twEditorOpen}
|
|
439
|
+
onClose={() => setTwEditorOpen(false)}
|
|
440
|
+
/>
|
|
441
|
+
</div>
|
|
442
|
+
);
|
|
443
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { Database } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
export const DatawayChatShell: React.FC<any> = ({ artifacts, activeArtifactIndex }) => {
|
|
5
|
+
const artifact = artifacts?.[activeArtifactIndex];
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!artifact) return;
|
|
9
|
+
|
|
10
|
+
let script = '';
|
|
11
|
+
if (typeof artifact.content === 'string') {
|
|
12
|
+
script = artifact.content;
|
|
13
|
+
} else if (artifact.data && typeof artifact.data.content === 'string') {
|
|
14
|
+
script = artifact.data.content;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (script) {
|
|
18
|
+
// Limpiar tags de markdown si el LLM los incluye
|
|
19
|
+
script = script.replace(/```[a-zA-Z-]*\n/gi, '').replace(/```/g, '').trim();
|
|
20
|
+
window.dispatchEvent(new CustomEvent('intent:dataway-generate-script', { detail: { script } }));
|
|
21
|
+
}
|
|
22
|
+
}, [artifact]);
|
|
23
|
+
|
|
24
|
+
if (!artifact) return null;
|
|
25
|
+
|
|
26
|
+
let displayScript = typeof artifact.content === 'string' ? artifact.content : (artifact.data?.content || '');
|
|
27
|
+
displayScript = displayScript.replace(/```[a-zA-Z-]*\n/gi, '').replace(/```/g, '').trim();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="w-full flex tracking-tight flex-col border border-teal-500/20 bg-teal-500/5 rounded-xl overflow-hidden mt-2">
|
|
31
|
+
<div className="bg-teal-500/10 px-3 py-2 flex items-center gap-2 border-b border-teal-500/10">
|
|
32
|
+
<Database className="w-4 h-4 text-teal-600 dark:text-teal-400" />
|
|
33
|
+
<span className="text-[11px] font-bold text-teal-800 dark:text-teal-300 uppercase tracking-widest">M-Script Aplicado</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div className="p-3">
|
|
36
|
+
<pre className="text-[13px] font-mono text-teal-900/80 dark:text-teal-100/80 whitespace-pre-wrap break-all">
|
|
37
|
+
{displayScript}
|
|
38
|
+
</pre>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|