UNPKG

morphbox

Version:

Docker-based AI sandbox for development with Claude integration

836 lines (824 loc) 25.7 kB
import { g as get_store_value, h as set_current_component, j as current_component, r as run_all } from './ssr-Bi8A3Ffq.js'; import { d as derived, w as writable } from './index-6zo8caE3.js'; const dirty_components = []; const binding_callbacks = []; let render_callbacks = []; const flush_callbacks = []; const resolved_promise = /* @__PURE__ */ Promise.resolve(); let update_scheduled = false; function schedule_update() { if (!update_scheduled) { update_scheduled = true; resolved_promise.then(flush); } } function tick() { schedule_update(); return resolved_promise; } function add_render_callback(fn) { render_callbacks.push(fn); } const seen_callbacks = /* @__PURE__ */ new Set(); let flushidx = 0; function flush() { if (flushidx !== 0) { return; } const saved_component = current_component; do { try { while (flushidx < dirty_components.length) { const component = dirty_components[flushidx]; flushidx++; set_current_component(component); update(component.$$); } } catch (e) { dirty_components.length = 0; flushidx = 0; throw e; } set_current_component(null); dirty_components.length = 0; flushidx = 0; while (binding_callbacks.length) binding_callbacks.pop()(); for (let i = 0; i < render_callbacks.length; i += 1) { const callback = render_callbacks[i]; if (!seen_callbacks.has(callback)) { seen_callbacks.add(callback); callback(); } } render_callbacks.length = 0; } while (dirty_components.length); while (flush_callbacks.length) { flush_callbacks.pop()(); } update_scheduled = false; seen_callbacks.clear(); set_current_component(saved_component); } function update($$) { if ($$.fragment !== null) { $$.update(); run_all($$.before_update); const dirty = $$.dirty; $$.dirty = [-1]; $$.fragment && $$.fragment.p($$.ctx, dirty); $$.after_update.forEach(add_render_callback); } } const defaultSettings = { theme: "dark", customTheme: { background: "#1e1e1e", foreground: "#d4d4d4", accent: "#007acc" }, terminal: { fontSize: 12, fontFamily: '"Cascadia Code", "Fira Code", monospace', lineHeight: 1.1, cursorStyle: "block", cursorBlink: true }, panels: { defaultPositions: { terminal: { x: 0, y: 40, width: "100%", height: "calc(100% - 62px)" }, settings: { x: 50, y: 50, width: "600px", height: "500px" } }, snapToGrid: true, gridSize: 10 }, shortcuts: { "toggle-terminal": "Ctrl+`", "toggle-settings": "Ctrl+,", "clear-terminal": "Ctrl+L", "new-terminal": "Ctrl+Shift+`", "close-panel": "Escape", "save-settings": "Ctrl+S" }, editor: { fontSize: 14, fontFamily: '"Cascadia Code", "Fira Code", monospace', lineHeight: 1.5, wordWrap: false, minimap: true, theme: "vs-dark" }, customPanels: { systemPrompt: `Create a vanilla JavaScript panel for MorphBox with the following requirements: Panel Name: {name} Description: {description} Generate a complete HTML/CSS/JavaScript panel that: 1. Uses vanilla JavaScript (no frameworks) 2. Has access to these variables: panelId, data, websocketUrl 3. Can connect to WebSocket for real-time data: const ws = new WebSocket(websocketUrl) 4. Uses MorphBox CSS variables for theming (--bg-primary, --text-primary, --border-color, etc.) 5. Implements the functionality described above 6. Uses proper error handling and loading states where applicable 7. IMPORTANT: The JavaScript code will be automatically wrapped in an onMount() function, so you can safely access DOM elements directly without waiting for DOMContentLoaded 8. Is responsive and works well on mobile WebSocket Access: - Connect using: new WebSocket(websocketUrl) - Listen for 'OUTPUT' events (terminal output) - Listen for 'context_update' events (Claude Code context tracking) - See full API: https://github.com/instant-unicorn/morphbox/blob/main/docs/CUSTOM_PANELS.md IMPORTANT: Return ONLY the HTML code starting with <div> tags. Do not include any markdown formatting, code blocks, or explanations. Just the raw HTML/CSS/JavaScript code. The panel should follow this structure: <!-- @morphbox-panel id: (will be generated) name: {name} description: {description} version: 1.0.0 --> <div class="custom-panel"> <div class="panel-header"> <h2>{name}</h2> </div> <div class="panel-content"> <!-- Panel content here --> </div> </div> <style> /* Panel styles using CSS variables */ </style> <script> // Panel logic here // Available: panelId, data, websocketUrl // Use vanilla JS, no frameworks <\/script> Make it fully functional and production-ready. Use modern JavaScript features.` } }; function createSettingsStore() { const { subscribe, set, update: update2 } = writable(defaultSettings); return { subscribe, load: () => { if (typeof window !== "undefined") { const saved = localStorage.getItem("morphbox-settings"); if (saved) { try { const parsed = JSON.parse(saved); set({ ...defaultSettings, ...parsed }); } catch (e) { console.error("Failed to load settings:", e); set(defaultSettings); } } } }, save: () => { const current = get_store_value(settings); if (typeof window !== "undefined") { localStorage.setItem("morphbox-settings", JSON.stringify(current)); } }, reset: () => { set(defaultSettings); if (typeof window !== "undefined") { localStorage.removeItem("morphbox-settings"); } }, update: update2 }; } const settings = createSettingsStore(); const defaultPanelConfigs = { terminal: { type: "terminal", title: "Terminal", size: { width: 800, height: 400 }, persistent: false, terminalPersistent: true }, claude: { type: "claude", title: "Claude", size: { width: 800, height: 400 }, persistent: true, terminalPersistent: true }, fileExplorer: { type: "fileExplorer", title: "File Explorer", size: { width: 300, height: 600 }, persistent: false }, editor: { type: "editor", title: "Editor", size: { width: 800, height: 600 }, persistent: false }, codeEditor: { type: "codeEditor", title: "Code Editor", size: { width: 800, height: 600 }, persistent: false }, preview: { type: "preview", title: "Preview", size: { width: 600, height: 500 }, persistent: false }, settings: { type: "settings", title: "Settings", size: { width: 600, height: 400 }, persistent: false } }; function restoreWebSocketConnection(panelId, connectionId) { return null; } function createPanelSnapshot(state) { try { const snapshot = { panels: state.panels.map((panel) => ({ ...panel, websocketConnections: void 0 // Remove non-serializable data })), workspaces: state.workspaces, activeWorkspaceId: state.activeWorkspaceId, layout: state.layout, activePanel: state.activePanel, timestamp: Date.now() }; return JSON.stringify(snapshot); } catch (error) { console.error("Failed to create panel snapshot:", error); return ""; } } function restorePanelSnapshot(snapshotData) { try { const snapshot = JSON.parse(snapshotData); const defaultWorkspace = { id: "workspace-1", name: "Workspace 1", created: Date.now() }; return { panels: snapshot.panels.map((panel) => ({ ...panel, workspaceId: panel.workspaceId || defaultWorkspace.id, websocketConnections: /* @__PURE__ */ new Map() })), workspaces: snapshot.workspaces || [defaultWorkspace], activeWorkspaceId: snapshot.activeWorkspaceId || defaultWorkspace.id, layout: snapshot.layout || "floating", activePanel: snapshot.activePanel || null }; } catch (error) { console.error("Failed to restore panel snapshot:", error); return null; } } function createPanelStore() { const defaultWorkspace = { id: "workspace-1", name: "Workspace 1", created: Date.now() }; const initialState = { panels: [], workspaces: [defaultWorkspace], activeWorkspaceId: defaultWorkspace.id, layout: "grid", // Changed to grid for new layout activePanel: null }; const { subscribe, set, update: update2 } = writable(initialState); const generateId = () => `panel-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const getNextPosition = (panels2) => { const baseX = 50; const baseY = 50; const offset = 30; const count = panels2.length; return { x: baseX + count * offset % 300, y: baseY + count * offset % 200 }; }; const getHighestZIndex = (panels2) => { return Math.max(0, ...panels2.map((p) => p.zIndex || 0)); }; return { subscribe, // Add a new panel addPanel: (type, config) => { update2((state) => { const defaultConfig = defaultPanelConfigs[type] || {}; const isCustomPanel = type.match(/^[a-z]/) || type.includes("-"); const id = isCustomPanel ? type : generateId(); const position = config?.position || getNextPosition(state.panels); const zIndex = getHighestZIndex(state.panels) + 1; const { id: configId, ...configWithoutId } = config || {}; const currentSettings = get_store_value(settings); const defaultColors = currentSettings.panels?.defaultPanelColors || {}; const newPanel = { id, type, title: "Untitled", workspaceId: configWithoutId?.workspaceId || state.activeWorkspaceId, position, size: { width: 400, height: 300 }, persistent: false, zIndex, headerColor: defaultColors?.headerColor, backgroundColor: defaultColors?.backgroundColor, borderColor: defaultColors?.borderColor, ...defaultConfig, ...configWithoutId }; return { ...state, panels: [...state.panels, newPanel], activePanel: id }; }); }, // Remove a panel removePanel: (id) => { update2((state) => ({ ...state, panels: state.panels.filter((p) => p.id !== id), activePanel: state.activePanel === id ? null : state.activePanel })); }, // Update a panel updatePanel: (id, updates) => { update2((state) => ({ ...state, panels: state.panels.map( (p) => p.id === id ? { ...p, ...updates } : p ) })); }, // Set active panel (bring to front) setActivePanel: (id) => { update2((state) => { if (!id) return { ...state, activePanel: null }; const highestZ = getHighestZIndex(state.panels); const panels2 = state.panels.map( (p) => p.id === id ? { ...p, zIndex: highestZ + 1 } : p ); return { ...state, panels: panels2, activePanel: id }; }); }, // Toggle minimize toggleMinimize: (id) => { update2((state) => ({ ...state, panels: state.panels.map( (p) => p.id === id ? { ...p, minimized: !p.minimized, maximized: false } : p ) })); }, // Toggle maximize toggleMaximize: (id) => { update2((state) => ({ ...state, panels: state.panels.map( (p) => p.id === id ? { ...p, maximized: !p.maximized, minimized: false } : p ) })); }, // Set layout mode setLayout: (layout) => { update2((state) => ({ ...state, layout })); }, // Clear all panels clearPanels: () => { update2((state) => ({ ...state, panels: state.panels.filter((p) => p.persistent), activePanel: null })); }, // Save configuration to localStorage saveConfiguration: (name = "default") => { const state = get_store_value({ subscribe }); const config = { panels: state.panels, layout: state.layout }; localStorage.setItem(`panel-config-${name}`, JSON.stringify(config)); }, // Load configuration from localStorage loadConfiguration: (name = "default") => { const saved = localStorage.getItem(`panel-config-${name}`); if (saved) { try { const config = JSON.parse(saved); const defaultWorkspace2 = { id: "workspace-1", name: "Workspace 1", created: Date.now() }; set({ panels: (config.panels || []).map((panel) => ({ ...panel, workspaceId: panel.workspaceId || defaultWorkspace2.id })), workspaces: config.workspaces || [defaultWorkspace2], activeWorkspaceId: config.activeWorkspaceId || defaultWorkspace2.id, layout: config.layout || "floating", activePanel: null }); return true; } catch (e) { console.error("Failed to load panel configuration:", e); return false; } } return false; }, // Get all saved configurations getSavedConfigurations: () => { const configs = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key?.startsWith("panel-config-")) { configs.push(key.replace("panel-config-", "")); } } return configs; }, // Delete a saved configuration deleteConfiguration: (name) => { localStorage.removeItem(`panel-config-${name}`); }, // Initialize with default panels initializeDefaults: () => { update2((state) => { if (state.panels.length > 0) return state; const defaultPanels = [ { ...defaultPanelConfigs.claude, id: generateId(), position: { x: 340, y: 440 } } ]; return { ...state, panels: defaultPanels, activePanel: defaultPanels[0].id }; }); }, // Hot reload recovery methods recoverFromHotReload: () => { }, // Terminal persistence methods setTerminalPersistence: (id, persistent) => { update2((state) => ({ ...state, panels: state.panels.map( (p) => p.id === id ? { ...p, terminalPersistent: persistent } : p ) })); }, // WebSocket connection management preserveWebSocket: (panelId, connectionId, websocket) => { update2((state) => ({ ...state, panels: state.panels.map((p) => { if (p.id === panelId) { const connections = p.websocketConnections || /* @__PURE__ */ new Map(); connections.set(connectionId, websocket); return { ...p, websocketConnections: connections }; } return p; }) })); }, restoreWebSocket: (panelId, connectionId) => { return restoreWebSocketConnection(); }, // State snapshot methods createSnapshot: () => { const state = get_store_value({ subscribe }); return createPanelSnapshot(state); }, restoreSnapshot: (snapshotData) => { const restoredState2 = restorePanelSnapshot(snapshotData); if (restoredState2) { set(restoredState2); return true; } return false; }, // Manual state preservation saveState: () => { get_store_value({ subscribe }); }, // Clear all state clearState: () => { }, // Workspace management addWorkspace: (name) => { update2((state) => { const id = `workspace-${Date.now()}`; const workspaceName = name || `Workspace ${state.workspaces.length + 1}`; const newWorkspace = { id, name: workspaceName, created: Date.now() }; return { ...state, workspaces: [...state.workspaces, newWorkspace], activeWorkspaceId: id }; }); }, removeWorkspace: (workspaceId) => { update2((state) => { if (state.workspaces.length <= 1) return state; const newWorkspaces = state.workspaces.filter((w) => w.id !== workspaceId); const newPanels = state.panels.filter((p) => p.workspaceId !== workspaceId); const newActiveWorkspaceId = workspaceId === state.activeWorkspaceId ? newWorkspaces[0].id : state.activeWorkspaceId; return { ...state, workspaces: newWorkspaces, panels: newPanels, activeWorkspaceId: newActiveWorkspaceId, activePanel: null }; }); }, switchWorkspace: (workspaceId) => { update2((state) => ({ ...state, activeWorkspaceId: workspaceId, activePanel: null })); }, renameWorkspace: (workspaceId, newName) => { update2((state) => ({ ...state, workspaces: state.workspaces.map( (w) => w.id === workspaceId ? { ...w, name: newName } : w ) })); }, // Clear all panels (for grid layout) clear: () => { update2((state) => ({ ...state, panels: state.panels.filter((p) => p.workspaceId !== state.activeWorkspaceId), activePanel: null })); }, // Check if recovering from hot reload isRecoveringFromHotReload: () => { const state = get_store_value({ subscribe }); return state.hotReloadRecovery || false; } }; } const panelStore = createPanelStore(); const panels = derived( panelStore, ($store) => $store.panels.filter((p) => p.workspaceId === $store.activeWorkspaceId) ); derived(panelStore, ($store) => $store.panels); derived(panelStore, ($store) => $store.workspaces || []); derived(panelStore, ($store) => $store.activeWorkspaceId); derived( panelStore, ($store) => $store.workspaces.find((w) => w.id === $store.activeWorkspaceId) ?? null ); const activePanel = derived( panelStore, ($store) => $store.panels.find((p) => p.id === $store.activePanel) ?? null ); derived(panelStore, ($store) => { const byType = {}; $store.panels.filter((p) => p.workspaceId === $store.activeWorkspaceId).forEach((panel) => { if (!byType[panel.type]) byType[panel.type] = []; byType[panel.type].push(panel); }); return byType; }); derived( panelStore, ($store) => $store.panels.filter((p) => p.workspaceId === $store.activeWorkspaceId).filter((p) => !p.minimized) ); function createPanelRegistry() { const { subscribe, set, update: update2 } = writable({ panels: /* @__PURE__ */ new Map() }); return { subscribe, // Register a new panel register(definition) { update2((state) => { state.panels.set(definition.id, definition); return state; }); this.saveRegistry(); }, // Unregister a panel unregister(id) { update2((state) => { state.panels.delete(id); return state; }); this.saveRegistry(); }, // Get a panel definition get(id) { const state = get_store_value({ subscribe }); return state.panels.get(id); }, // Get all panels getAll() { const state = get_store_value({ subscribe }); return Array.from(state.panels.values()); }, // Get panels by feature getByFeature(feature) { const state = get_store_value({ subscribe }); return Array.from(state.panels.values()).filter((panel) => panel.features.includes(feature)); }, // Load a panel component dynamically async loadComponent(id) { const panel = this.get(id); if (!panel) return null; try { const module = await import( /* @vite-ignore */ panel.path ); return module.default; } catch (error) { console.error(`Failed to load panel component: ${id}`, error); return null; } }, // Save registry to localStorage saveRegistry() { const state = get_store_value({ subscribe }); const customPanelsOnly = Array.from(state.panels.entries()).filter(([id, panel]) => panel.isCustom).map(([id, panel]) => ({ ...panel, component: null, // Don't serialize components createdAt: panel.createdAt.toISOString() })); localStorage.setItem("panel-registry", JSON.stringify(customPanelsOnly)); }, // Load registry from localStorage loadRegistry() { const saved = localStorage.getItem("panel-registry"); if (!saved) return; try { const parsed = JSON.parse(saved); const panels2 = /* @__PURE__ */ new Map(); for (const panel of parsed) { if (panel.isCustom) { panels2.set(panel.id, { ...panel, createdAt: new Date(panel.createdAt), component: null // Will be loaded on demand }); } } update2((state) => { for (const [id, panel] of panels2) { state.panels.set(id, panel); } return state; }); } catch (error) { console.error("Failed to load panel registry:", error); localStorage.removeItem("panel-registry"); } }, // Initialize with built-in panels initializeBuiltins() { const builtinPanels2 = [ { id: "terminal", name: "Terminal", description: "Interactive terminal emulator", path: "$lib/Terminal.svelte", features: ["terminal", "websocket"], createdAt: /* @__PURE__ */ new Date(), isCustom: false }, { id: "claude", name: "Claude", description: "Claude AI assistant", path: "$lib/Claude.svelte", features: ["terminal", "websocket", "ai"], createdAt: /* @__PURE__ */ new Date(), isCustom: false }, { id: "fileExplorer", name: "File Explorer", description: "Browse and manage files", path: "$lib/panels/FileExplorer/FileExplorer.svelte", features: ["fileSystem", "stateManagement"], createdAt: /* @__PURE__ */ new Date(), isCustom: false }, { id: "codeEditor", name: "Code Editor", description: "Edit code with syntax highlighting", path: "$lib/panels/CodeEditor/CodeEditor.svelte", features: ["fileSystem", "stateManagement"], createdAt: /* @__PURE__ */ new Date(), isCustom: false }, { id: "settings", name: "Settings", description: "Application settings and preferences", path: "$lib/panels/Settings/Settings.svelte", features: ["formHandling", "stateManagement"], createdAt: /* @__PURE__ */ new Date(), isCustom: false }, { id: "promptQueue", name: "Prompt Queue", description: "Queue and manage prompts for Claude", path: "$lib/panels/PromptQueue/PromptQueue.svelte", features: ["ai", "queue", "automation"], createdAt: /* @__PURE__ */ new Date(), isCustom: false }, { id: "webBrowser", name: "Web Browser", description: "Preview web pages and local servers", path: "$lib/panels/WebBrowser/WebBrowser.svelte", features: ["preview", "browser", "development"], createdAt: /* @__PURE__ */ new Date(), isCustom: false }, { id: "gitPanel", name: "Git", description: "Git version control management", path: "$lib/panels/GitPanel/GitPanel.svelte", features: ["git", "vcs", "development"], createdAt: /* @__PURE__ */ new Date(), isCustom: false }, { id: "taskRunner", name: "Task Runner", description: "Run npm scripts and custom commands", path: "$lib/panels/TaskRunner/TaskRunner.svelte", features: ["tasks", "terminal", "development"], createdAt: /* @__PURE__ */ new Date(), isCustom: false } ]; for (const panel of builtinPanels2) { this.register({ ...panel, component: null }); } } }; } const panelRegistry = createPanelRegistry(); const customPanels = derived( panelRegistry, ($registry) => Array.from($registry.panels.values()).filter((p) => p.isCustom) ); const builtinPanels = derived( panelRegistry, ($registry) => Array.from($registry.panels.values()).filter((p) => !p.isCustom) ); if (typeof window !== "undefined") { console.log("[PanelRegistry] Initializing..."); panelRegistry.loadRegistry(); console.log("[PanelRegistry] Initializing built-in panels..."); panelRegistry.initializeBuiltins(); const registry = get_store_value(panelRegistry); console.log("[PanelRegistry] Total panels:", registry.panels.size); console.log("[PanelRegistry] All panels:", Array.from(registry.panels.keys())); const builtins = get_store_value(builtinPanels); const customs = get_store_value(customPanels); console.log("[PanelRegistry] Built-in panels:", builtins.map((p) => p.id)); console.log("[PanelRegistry] Custom panels:", customs.map((p) => p.id)); } export { panels as a, activePanel as b, customPanels as c, builtinPanels as d, panelRegistry as e, panelStore as p, settings as s, tick as t }; //# sourceMappingURL=registry-kTtxA5oL.js.map