const { useState, useRef, useEffect } = React; // Lightweight icon shims to avoid external module imports in the browser-only build const Send = () => 📤; const Database = () => 🗄️; const Users = () => 👥; const Shield = () => 🛡️; const CheckCircle = () => ✅; const AlertCircle = () => ⚠️; const Loader = () => ⏳; const Settings = () => ⚙️; const Terminal = () => 🖥️; const Copy = () => 📋; const CheckCheck = () => ✔️; const UnityCatalogChatbot = () => { // Connection Setup State const [isConnected, setIsConnected] = useState(false); const [showSetup, setShowSetup] = useState(true); const [setupForm, setSetupForm] = useState({ host: '', token: '', workspaceId: '' }); const [setupLoading, setSetupLoading] = useState(false); const [setupError, setSetupError] = useState(''); // Chat State const [messages, setMessages] = useState([ { role: 'assistant', content: 'Hello! I\'m your Unity Catalog assistant. I can help you create catalogs, schemas, tables, set permissions, and manage your data governance. What would you like to do?', timestamp: new Date() } ]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const [actionLog, setActionLog] = useState([]); const [activeTab, setActiveTab] = useState('chat'); // 'chat' or 'logs' const [copiedId, setCopiedId] = useState(null); const [dbxStatus, setDbxStatus] = useState('disconnected'); // 'connected', 'disconnected', 'loading' const messagesEndRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); }, [messages]); // Check Databricks connection on mount useEffect(() => { checkDatabricksConnection(); }, []); const checkDatabricksConnection = async () => { try { setDbxStatus('loading'); const response = await fetch('/api/health'); if (response.ok) { setDbxStatus('connected'); } else { setDbxStatus('disconnected'); } } catch (error) { console.error('Connection check failed:', error); setDbxStatus('disconnected'); } }; // Parse user intent and extract parameters // Connection setup validation const handleTestConnection = async () => { if (!setupForm.host || !setupForm.token || !setupForm.workspaceId) { setSetupError('All fields are required'); return; } setSetupLoading(true); setSetupError(''); try { const response = await fetch('/api/validate-connection', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ host: setupForm.host, token: setupForm.token, workspaceId: setupForm.workspaceId }) }); const result = await response.json(); if (result.success) { setIsConnected(true); setShowSetup(false); setDbxStatus('connected'); sessionStorage.setItem('dbx_connection', JSON.stringify(setupForm)); } else { setSetupError(result.message || 'Connection failed. Please check your credentials.'); setDbxStatus('disconnected'); } } catch (error) { console.error('Connection test failed:', error); setSetupError('Connection error: ' + error.message); setDbxStatus('disconnected'); } finally { setSetupLoading(false); } }; const handleSetupInputChange = (field, value) => { setSetupForm(prev => ({ ...prev, [field]: value })); setSetupError(''); }; const parseIntent = async (userMessage) => { const lowerMsg = userMessage.toLowerCase(); // Define intent patterns const intents = { createCatalog: /create\s+(a\s+)?catalog\s+(?:named\s+)?["']?(\w+)["']?/i, createSchema: /create\s+(a\s+)?schema\s+(?:named\s+)?["']?(\w+\.?\w*)["']?/i, createTable: /create\s+(a\s+)?table\s+(?:named\s+)?["']?([\w.]+)["']?/i, grantPermission: /grant\s+(\w+)\s+(?:permission|access|privileges?)\s+(?:on\s+)?["']?([\w.]+)["']?\s+to\s+(?:user\s+)?["']?(\w+)["']?/i, revokePermission: /revoke\s+(\w+)\s+(?:permission|access|privileges?)\s+(?:on\s+)?["']?([\w.]+)["']?\s+from\s+(?:user\s+)?["']?(\w+)["']?/i, listCatalogs: /list\s+(all\s+)?catalogs?/i, listSchemas: /list\s+schemas?\s+(?:in\s+)?["']?(\w+)["']?/i, showPermissions: /show\s+permissions?\s+(?:for\s+)?["']?([\w.]+)["']?/i, setOwner: /set\s+owner\s+(?:of\s+)?["']?([\w.]+)["']?\s+to\s+["']?(\w+)["']?/i, }; // Match intent for (const [intent, pattern] of Object.entries(intents)) { const match = userMessage.match(pattern); if (match) { return { intent, params: match.slice(1) }; } } // Use Claude API for complex queries return await analyzeWithClaude(userMessage); }; // Simulate API call to Claude for intent analysis const analyzeWithClaude = async (message) => { try { const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 1000, messages: [{ role: 'user', content: `Analyze this Unity Catalog request and extract the intent and parameters as JSON: "${message}" Possible intents: createCatalog, createSchema, createTable, grantPermission, revokePermission, listCatalogs, listSchemas, showPermissions, setOwner, complex, help Return ONLY a JSON object with "intent" and "params" fields. For example: {"intent": "createCatalog", "params": {"name": "sales_catalog"}} {"intent": "grantPermission", "params": {"privilege": "SELECT", "object": "sales.customers", "principal": "data_analyst"}} {"intent": "help", "params": {}}` }] }) }); const data = await response.json(); const textResponse = data.content.find(c => c.type === 'text')?.text || ''; // Extract JSON from response const jsonMatch = textResponse.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } } catch (error) { console.error('Claude API error:', error); } return { intent: 'help', params: {} }; }; // Execute Unity Catalog operations const executeOperation = async (intent, params) => { const operations = { createCatalog: async (p) => { const catalogName = p[0] || p.name; return { sql: `CREATE CATALOG IF NOT EXISTS ${catalogName}`, message: `Created catalog '${catalogName}' successfully.`, action: { type: 'create', object: 'catalog', name: catalogName } }; }, createSchema: async (p) => { const schemaPath = p[0] || p.name; return { sql: `CREATE SCHEMA IF NOT EXISTS ${schemaPath}`, message: `Created schema '${schemaPath}' successfully.`, action: { type: 'create', object: 'schema', name: schemaPath } }; }, createTable: async (p) => { const tablePath = p[0] || p.name; return { sql: `CREATE TABLE IF NOT EXISTS ${tablePath} ( id BIGINT GENERATED ALWAYS AS IDENTITY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(), data STRING ) USING DELTA`, message: `Created table '${tablePath}' with default schema. You can modify the schema as needed.`, action: { type: 'create', object: 'table', name: tablePath } }; }, grantPermission: async (p) => { const privilege = p[0] || p.privilege; const object = p[1] || p.object; const principal = p[2] || p.principal; return { sql: `GRANT ${privilege.toUpperCase()} ON ${object} TO \`${principal}\``, message: `Granted ${privilege} permission on '${object}' to user '${principal}'.`, action: { type: 'grant', privilege, object, principal } }; }, revokePermission: async (p) => { const privilege = p[0] || p.privilege; const object = p[1] || p.object; const principal = p[2] || p.principal; return { sql: `REVOKE ${privilege.toUpperCase()} ON ${object} FROM \`${principal}\``, message: `Revoked ${privilege} permission on '${object}' from user '${principal}'.`, action: { type: 'revoke', privilege, object, principal } }; }, listCatalogs: async () => { return { sql: `SHOW CATALOGS`, message: `Here are the available catalogs. Run the SQL query to see the full list.`, action: { type: 'list', object: 'catalogs' } }; }, listSchemas: async (p) => { const catalog = p[0] || p.catalog; return { sql: `SHOW SCHEMAS IN ${catalog}`, message: `Here are the schemas in catalog '${catalog}'.`, action: { type: 'list', object: 'schemas', catalog } }; }, showPermissions: async (p) => { const object = p[0] || p.object; return { sql: `SHOW GRANTS ON ${object}`, message: `Here are the current permissions for '${object}'.`, action: { type: 'show', object: 'permissions', target: object } }; }, setOwner: async (p) => { const object = p[0] || p.object; const owner = p[1] || p.owner; return { sql: `ALTER ${object.includes('.') ? 'TABLE' : 'CATALOG'} ${object} OWNER TO \`${owner}\``, message: `Set owner of '${object}' to '${owner}'.`, action: { type: 'owner', object, owner } }; }, help: async () => { return { message: `I can help you with Unity Catalog operations: **Creating Objects:** • "Create a catalog named sales_catalog" • "Create a schema called sales.customers" • "Create a table sales.customers.orders" **Managing Permissions:** • "Grant SELECT permission on sales.customers to data_analyst" • "Revoke MODIFY on sales.orders from john_doe" • "Show permissions for sales.customers" • "Set owner of sales.customers to admin_user" **Listing Objects:** • "List all catalogs" • "List schemas in sales_catalog" Just tell me what you'd like to do in natural language!`, action: { type: 'help' } }; }, complex: async () => { return { message: `This looks like a complex request. Let me break it down into steps. Could you provide more details or rephrase the request?`, action: { type: 'clarification' } }; } }; const operation = operations[intent.intent || intent]; if (operation) { return await operation(intent.params || params || []); } return { message: `I'm not sure how to handle that request. Type "help" to see what I can do.`, action: { type: 'unknown' } }; }; const handleSend = async () => { if (!input.trim() || isLoading) return; const userMessage = { role: 'user', content: input, timestamp: new Date() }; setMessages(prev => [...prev, userMessage]); setInput(''); setIsLoading(true); try { // Send to backend API const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: input.trim() }) }); if (!response.ok) { throw new Error(`API error: ${response.statusText}`); } const result = await response.json(); // Log the action if SQL was generated if (result.sql) { const logEntry = { id: `action-${Date.now()}`, timestamp: new Date(), sql: result.sql, intent: result.intent || 'unknown', status: result.success ? 'success' : 'failed', message: result.message, explanation: result.explanation }; setActionLog(prev => [...prev, logEntry]); } // Add assistant response const assistantMessage = { role: 'assistant', content: result.message, sql: result.sql, intent: result.intent, timestamp: new Date(), isError: !result.success }; setMessages(prev => [...prev, assistantMessage]); } catch (error) { console.error('Error:', error); const errorMessage = { role: 'assistant', content: `Sorry, I encountered an error: ${error.message}. Make sure the backend server is running on /api/chat.`, timestamp: new Date(), isError: true }; setMessages(prev => [...prev, errorMessage]); } finally { setIsLoading(false); } }; const handleKeyPress = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; const quickActions = [ { label: 'Create Catalog', icon: Database, prompt: 'Create a catalog named ' }, { label: 'Grant Access', icon: Shield, prompt: 'Grant SELECT permission on ' }, { label: 'List Catalogs', icon: Terminal, prompt: 'List all catalogs' }, { label: 'Help', icon: Settings, prompt: 'help' } ]; // Setup Screen - shown before connection if (showSetup) { return (
Enter your workspace credentials
DATABRICKS GOVERNANCE AI
{msg.sql}
{log.sql}