UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

1,055 lines (890 loc) 26.2 kB
/** * ComponentLibrary - Reusable UI components for Claude Flow Web UI * Provides consistent, themeable components across all views */ export class ComponentLibrary { constructor() { this.components = new Map(); this.theme = 'dark'; this.isInitialized = false; } /** * Initialize component library */ initialize() { this.registerComponents(); this.addComponentStyles(); this.isInitialized = true; console.log('🎨 Component Library initialized'); } /** * Register all reusable components */ registerComponents() { this.components.set('ToolPanel', this.createToolPanel); this.components.set('MetricsChart', this.createMetricsChart); this.components.set('CommandPalette', this.createCommandPalette); this.components.set('ProgressBar', this.createProgressBar); this.components.set('StatusBadge', this.createStatusBadge); this.components.set('LoadingSpinner', this.createLoadingSpinner); this.components.set('ErrorMessage', this.createErrorMessage); this.components.set('SuccessMessage', this.createSuccessMessage); this.components.set('InfoPanel', this.createInfoPanel); this.components.set('ActionButton', this.createActionButton); this.components.set('ToolGrid', this.createToolGrid); this.components.set('StatsCard', this.createStatsCard); this.components.set('LogViewer', this.createLogViewer); this.components.set('FormBuilder', this.createFormBuilder); this.components.set('TabContainer', this.createTabContainer); } /** * Get component by name */ getComponent(name) { const component = this.components.get(name); if (!component) { throw new Error(`Component not found: ${name}`); } return component; } /** * Create tool panel component */ createToolPanel(config) { const panel = document.createElement('div'); panel.className = 'claude-tool-panel'; const header = document.createElement('div'); header.className = 'claude-tool-panel-header'; const title = document.createElement('h3'); title.textContent = config.title; title.className = 'claude-tool-panel-title'; const subtitle = document.createElement('p'); subtitle.textContent = config.description; subtitle.className = 'claude-tool-panel-subtitle'; header.appendChild(title); header.appendChild(subtitle); const content = document.createElement('div'); content.className = 'claude-tool-panel-content'; panel.appendChild(header); panel.appendChild(content); return { element: panel, content, setTitle: (newTitle) => { title.textContent = newTitle; }, setDescription: (newDesc) => { subtitle.textContent = newDesc; }, clear: () => { content.innerHTML = ''; }, append: (element) => { content.appendChild(element); }, }; } /** * Create metrics chart component */ createMetricsChart(config) { const container = document.createElement('div'); container.className = 'claude-metrics-chart'; const title = document.createElement('h4'); title.textContent = config.title; title.className = 'claude-chart-title'; const canvas = document.createElement('canvas'); canvas.width = config.width || 400; canvas.height = config.height || 200; canvas.className = 'claude-chart-canvas'; const legend = document.createElement('div'); legend.className = 'claude-chart-legend'; container.appendChild(title); container.appendChild(canvas); container.appendChild(legend); return { element: container, canvas, updateData: (data) => this.updateChart(canvas, data, config), setTitle: (newTitle) => { title.textContent = newTitle; }, addLegendItem: (label, color) => this.addLegendItem(legend, label, color), }; } /** * Create command palette component */ createCommandPalette(config) { const overlay = document.createElement('div'); overlay.className = 'claude-command-palette-overlay'; overlay.style.display = 'none'; const palette = document.createElement('div'); palette.className = 'claude-command-palette'; const input = document.createElement('input'); input.type = 'text'; input.placeholder = 'Type a command...'; input.className = 'claude-command-input'; const results = document.createElement('div'); results.className = 'claude-command-results'; palette.appendChild(input); palette.appendChild(results); overlay.appendChild(palette); return { element: overlay, input, results, show: () => { overlay.style.display = 'flex'; input.focus(); }, hide: () => { overlay.style.display = 'none'; }, updateResults: (commands) => this.updateCommandResults(results, commands), onCommand: null, // Set by user }; } /** * Create progress bar component */ createProgressBar(config) { const container = document.createElement('div'); container.className = 'claude-progress-container'; const label = document.createElement('div'); label.className = 'claude-progress-label'; label.textContent = config.label || 'Progress'; const bar = document.createElement('div'); bar.className = 'claude-progress-bar'; const fill = document.createElement('div'); fill.className = 'claude-progress-fill'; fill.style.width = '0%'; const text = document.createElement('div'); text.className = 'claude-progress-text'; text.textContent = '0%'; bar.appendChild(fill); bar.appendChild(text); container.appendChild(label); container.appendChild(bar); return { element: container, setProgress: (percent) => { fill.style.width = `${percent}%`; text.textContent = `${Math.round(percent)}%`; }, setLabel: (newLabel) => { label.textContent = newLabel; }, }; } /** * Create status badge component */ createStatusBadge(status, text) { const badge = document.createElement('span'); badge.className = `claude-status-badge claude-status-${status}`; badge.textContent = text || status; return { element: badge, setStatus: (newStatus) => { badge.className = `claude-status-badge claude-status-${newStatus}`; }, setText: (newText) => { badge.textContent = newText; }, }; } /** * Create loading spinner component */ createLoadingSpinner(config = {}) { const container = document.createElement('div'); container.className = 'claude-loading-container'; const spinner = document.createElement('div'); spinner.className = 'claude-loading-spinner'; const message = document.createElement('div'); message.className = 'claude-loading-message'; message.textContent = config.message || 'Loading...'; container.appendChild(spinner); container.appendChild(message); return { element: container, setMessage: (newMessage) => { message.textContent = newMessage; }, show: () => { container.style.display = 'flex'; }, hide: () => { container.style.display = 'none'; }, }; } /** * Create error message component */ createErrorMessage(message, details = null) { const container = document.createElement('div'); container.className = 'claude-error-message'; const icon = document.createElement('span'); icon.textContent = '❌'; icon.className = 'claude-error-icon'; const text = document.createElement('div'); text.className = 'claude-error-text'; text.textContent = message; container.appendChild(icon); container.appendChild(text); if (details) { const detailsEl = document.createElement('div'); detailsEl.className = 'claude-error-details'; detailsEl.textContent = details; container.appendChild(detailsEl); } return { element: container, setMessage: (newMessage) => { text.textContent = newMessage; }, setDetails: (newDetails) => { if (newDetails) { if (!container.querySelector('.claude-error-details')) { const detailsEl = document.createElement('div'); detailsEl.className = 'claude-error-details'; container.appendChild(detailsEl); } container.querySelector('.claude-error-details').textContent = newDetails; } }, }; } /** * Create success message component */ createSuccessMessage(message) { const container = document.createElement('div'); container.className = 'claude-success-message'; const icon = document.createElement('span'); icon.textContent = '✅'; icon.className = 'claude-success-icon'; const text = document.createElement('div'); text.className = 'claude-success-text'; text.textContent = message; container.appendChild(icon); container.appendChild(text); return { element: container, setMessage: (newMessage) => { text.textContent = newMessage; }, }; } /** * Create info panel component */ createInfoPanel(config) { const panel = document.createElement('div'); panel.className = 'claude-info-panel'; const header = document.createElement('div'); header.className = 'claude-info-header'; header.textContent = config.title; const content = document.createElement('div'); content.className = 'claude-info-content'; panel.appendChild(header); panel.appendChild(content); return { element: panel, content, setTitle: (title) => { header.textContent = title; }, setContent: (html) => { content.innerHTML = html; }, append: (element) => { content.appendChild(element); }, clear: () => { content.innerHTML = ''; }, }; } /** * Create action button component */ createActionButton(config) { const button = document.createElement('button'); button.className = `claude-action-button claude-button-${config.type || 'primary'}`; button.textContent = config.text; if (config.icon) { const icon = document.createElement('span'); icon.textContent = config.icon; icon.className = 'claude-button-icon'; button.insertBefore(icon, button.firstChild); } if (config.onclick) { button.addEventListener('click', config.onclick); } return { element: button, setText: (text) => { button.textContent = text; if (config.icon) { const icon = document.createElement('span'); icon.textContent = config.icon; icon.className = 'claude-button-icon'; button.insertBefore(icon, button.firstChild); } }, setDisabled: (disabled) => { button.disabled = disabled; }, setLoading: (loading) => { if (loading) { button.classList.add('claude-button-loading'); button.disabled = true; } else { button.classList.remove('claude-button-loading'); button.disabled = false; } }, }; } /** * Create tool grid component */ createToolGrid(tools, onToolClick) { const grid = document.createElement('div'); grid.className = 'claude-tool-grid'; tools.forEach((tool) => { const card = document.createElement('div'); card.className = 'claude-tool-card'; const icon = document.createElement('div'); icon.className = 'claude-tool-icon'; icon.textContent = tool.icon || '🔧'; const name = document.createElement('div'); name.className = 'claude-tool-name'; name.textContent = tool.name; const desc = document.createElement('div'); desc.className = 'claude-tool-description'; desc.textContent = tool.description; card.appendChild(icon); card.appendChild(name); card.appendChild(desc); card.addEventListener('click', () => onToolClick(tool)); grid.appendChild(card); }); return { element: grid, updateTools: (newTools) => { grid.innerHTML = ''; newTools.forEach((tool) => { // Recreate cards with new tools const card = this.createToolCard(tool, onToolClick); grid.appendChild(card); }); }, }; } /** * Create stats card component */ createStatsCard(config) { const card = document.createElement('div'); card.className = 'claude-stats-card'; const icon = document.createElement('div'); icon.className = 'claude-stats-icon'; icon.textContent = config.icon; const content = document.createElement('div'); content.className = 'claude-stats-content'; const value = document.createElement('div'); value.className = 'claude-stats-value'; value.textContent = config.value; const label = document.createElement('div'); label.className = 'claude-stats-label'; label.textContent = config.label; content.appendChild(value); content.appendChild(label); card.appendChild(icon); card.appendChild(content); return { element: card, setValue: (newValue) => { value.textContent = newValue; }, setLabel: (newLabel) => { label.textContent = newLabel; }, setIcon: (newIcon) => { icon.textContent = newIcon; }, }; } /** * Create tab container component */ createTabContainer(tabs) { const container = document.createElement('div'); container.className = 'claude-tab-container'; const tabList = document.createElement('div'); tabList.className = 'claude-tab-list'; const tabContent = document.createElement('div'); tabContent.className = 'claude-tab-content'; let activeTab = 0; tabs.forEach((tab, index) => { const tabButton = document.createElement('button'); tabButton.className = `claude-tab-button ${index === 0 ? 'active' : ''}`; tabButton.textContent = tab.label; const tabPane = document.createElement('div'); tabPane.className = `claude-tab-pane ${index === 0 ? 'active' : ''}`; tabPane.innerHTML = tab.content; tabButton.addEventListener('click', () => { // Remove active class from all tabs tabList .querySelectorAll('.claude-tab-button') .forEach((btn) => btn.classList.remove('active')); tabContent .querySelectorAll('.claude-tab-pane') .forEach((pane) => pane.classList.remove('active')); // Add active class to clicked tab tabButton.classList.add('active'); tabPane.classList.add('active'); activeTab = index; }); tabList.appendChild(tabButton); tabContent.appendChild(tabPane); }); container.appendChild(tabList); container.appendChild(tabContent); return { element: container, setActiveTab: (index) => { if (index >= 0 && index < tabs.length) { tabList.children[index].click(); } }, getActiveTab: () => activeTab, addTab: (tab) => { // Implementation for adding new tabs dynamically }, }; } /** * Add component styles to document */ addComponentStyles() { if (document.getElementById('claude-component-styles')) return; const styles = document.createElement('style'); styles.id = 'claude-component-styles'; styles.textContent = ` /* Tool Panel Styles */ .claude-tool-panel { background: #2a2a2a; border: 1px solid #444; border-radius: 8px; padding: 16px; margin: 8px 0; } .claude-tool-panel-header { margin-bottom: 12px; } .claude-tool-panel-title { margin: 0 0 4px 0; color: #00d4ff; font-size: 18px; } .claude-tool-panel-subtitle { margin: 0; color: #888; font-size: 14px; } .claude-tool-panel-content { color: #fff; } /* Metrics Chart Styles */ .claude-metrics-chart { background: #2a2a2a; border: 1px solid #444; border-radius: 8px; padding: 16px; margin: 8px 0; } .claude-chart-title { margin: 0 0 12px 0; color: #00d4ff; font-size: 16px; } .claude-chart-canvas { display: block; margin: 0 auto; background: #1a1a1a; border-radius: 4px; } .claude-chart-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; justify-content: center; } /* Command Palette Styles */ .claude-command-palette-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: flex-start; padding-top: 20vh; z-index: 9999; } .claude-command-palette { background: #2a2a2a; border: 1px solid #444; border-radius: 8px; width: 600px; max-width: 90vw; max-height: 60vh; overflow: hidden; } .claude-command-input { width: 100%; padding: 16px; background: transparent; border: none; color: #fff; font-size: 18px; outline: none; border-bottom: 1px solid #444; } .claude-command-results { max-height: 400px; overflow-y: auto; } /* Progress Bar Styles */ .claude-progress-container { margin: 8px 0; } .claude-progress-label { color: #fff; margin-bottom: 4px; font-size: 14px; } .claude-progress-bar { position: relative; background: #1a1a1a; border: 1px solid #444; border-radius: 4px; height: 24px; overflow: hidden; } .claude-progress-fill { height: 100%; background: linear-gradient(90deg, #00d4ff, #0099cc); transition: width 0.3s ease; } .claude-progress-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff; font-size: 12px; font-weight: bold; } /* Status Badge Styles */ .claude-status-badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; text-transform: uppercase; } .claude-status-success { background: #22c55e; color: white; } .claude-status-error { background: #ef4444; color: white; } .claude-status-warning { background: #f59e0b; color: white; } .claude-status-info { background: #3b82f6; color: white; } .claude-status-idle { background: #6b7280; color: white; } /* Loading Spinner Styles */ .claude-loading-container { display: flex; flex-direction: column; align-items: center; padding: 32px; } .claude-loading-spinner { width: 32px; height: 32px; border: 3px solid #444; border-top: 3px solid #00d4ff; border-radius: 50%; animation: spin 1s linear infinite; } .claude-loading-message { margin-top: 12px; color: #888; font-size: 14px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Message Styles */ .claude-error-message, .claude-success-message { display: flex; align-items: center; padding: 12px 16px; border-radius: 6px; margin: 8px 0; } .claude-error-message { background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; } .claude-success-message { background: #f0fdf4; border: 1px solid #bbf7d0; color: #166534; } .claude-error-icon, .claude-success-icon { margin-right: 8px; font-size: 16px; } /* Tool Grid Styles */ .claude-tool-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 16px; margin: 16px 0; } .claude-tool-card { background: #2a2a2a; border: 1px solid #444; border-radius: 8px; padding: 16px; cursor: pointer; transition: all 0.2s ease; } .claude-tool-card:hover { border-color: #00d4ff; transform: translateY(-2px); } .claude-tool-icon { font-size: 32px; margin-bottom: 8px; } .claude-tool-name { font-weight: bold; color: #fff; margin-bottom: 4px; } .claude-tool-description { color: #888; font-size: 14px; } /* Stats Card Styles */ .claude-stats-card { display: flex; align-items: center; background: #2a2a2a; border: 1px solid #444; border-radius: 8px; padding: 16px; margin: 8px 0; } .claude-stats-icon { font-size: 24px; margin-right: 12px; } .claude-stats-value { font-size: 24px; font-weight: bold; color: #00d4ff; } .claude-stats-label { color: #888; font-size: 14px; } /* Tab Styles */ .claude-tab-container { background: #2a2a2a; border: 1px solid #444; border-radius: 8px; overflow: hidden; } .claude-tab-list { display: flex; background: #1a1a1a; border-bottom: 1px solid #444; } .claude-tab-button { background: transparent; border: none; color: #888; padding: 12px 16px; cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s ease; } .claude-tab-button:hover { color: #fff; } .claude-tab-button.active { color: #00d4ff; border-bottom-color: #00d4ff; } .claude-tab-content { position: relative; } .claude-tab-pane { display: none; padding: 16px; } .claude-tab-pane.active { display: block; } /* Button Styles */ .claude-action-button { display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s ease; } .claude-button-primary { background: #00d4ff; color: #000; } .claude-button-primary:hover { background: #00b8e6; } .claude-button-secondary { background: #444; color: #fff; } .claude-button-secondary:hover { background: #555; } .claude-button-danger { background: #ef4444; color: #fff; } .claude-button-danger:hover { background: #dc2626; } .claude-button-loading { opacity: 0.7; pointer-events: none; } /* Info Panel Styles */ .claude-info-panel { background: #2a2a2a; border: 1px solid #444; border-radius: 8px; margin: 8px 0; overflow: hidden; } .claude-info-header { background: #1a1a1a; padding: 12px 16px; font-weight: bold; color: #00d4ff; border-bottom: 1px solid #444; } .claude-info-content { padding: 16px; color: #fff; } `; document.head.appendChild(styles); } /** * Update chart with new data */ updateChart(canvas, data, config) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); // Simple chart implementation // In a real implementation, you'd use Chart.js or similar const padding = 40; const chartWidth = canvas.width - padding * 2; const chartHeight = canvas.height - padding * 2; if (config.type === 'line') { this.drawLineChart(ctx, data, padding, chartWidth, chartHeight); } else if (config.type === 'bar') { this.drawBarChart(ctx, data, padding, chartWidth, chartHeight); } } /** * Draw line chart */ drawLineChart(ctx, data, padding, width, height) { if (!data || data.length === 0) return; const maxValue = Math.max(...data.map((d) => d.value)); const stepX = width / (data.length - 1); ctx.strokeStyle = '#00d4ff'; ctx.lineWidth = 2; ctx.beginPath(); data.forEach((point, index) => { const x = padding + index * stepX; const y = padding + height - (point.value / maxValue) * height; if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); } /** * Draw bar chart */ drawBarChart(ctx, data, padding, width, height) { if (!data || data.length === 0) return; const maxValue = Math.max(...data.map((d) => d.value)); const barWidth = (width / data.length) * 0.8; const barSpacing = (width / data.length) * 0.2; ctx.fillStyle = '#00d4ff'; data.forEach((point, index) => { const x = padding + index * (barWidth + barSpacing); const barHeight = (point.value / maxValue) * height; const y = padding + height - barHeight; ctx.fillRect(x, y, barWidth, barHeight); }); } /** * Set theme */ setTheme(theme) { this.theme = theme; // Update component styles based on theme // This would be more comprehensive in a real implementation } } export default ComponentLibrary;