'use client'; import React, { useState, useCallback, useEffect, useRef } from 'react'; import { Button } from '@/components/ui/button'; import { Play, Loader2 } from 'lucide-react'; import { toast } from 'sonner'; import { SchemaViewer } from '@/components/database-manager/schema-viewer'; import { SqlEditor } from '@/components/database-manager/sql-editor'; interface SchemaEditorProps { projectId: string; enabled: boolean; onSchemaChange?: (schema: string) => void; workspaceId?: string; } // Keep these exports — used by vfs/index.ts for transient file generation export function getProjectSchema(projectId: string): string { if (typeof window === 'undefined') return ''; return localStorage.getItem(`osw-db-schema-${projectId}`) || ''; } export function setProjectSchema(projectId: string, schema: string): void { if (typeof window === 'undefined') return; if (schema) { localStorage.setItem(`osw-db-schema-${projectId}`, schema); } else { localStorage.removeItem(`osw-db-schema-${projectId}`); } } /** * Save schema to localStorage and apply DDL to the project database (Server Mode only). * Used by project-manager and template-manager during project creation. */ export async function applyProjectDatabaseSchema(projectId: string, ddl: string, workspaceId?: string): Promise { setProjectSchema(projectId, ddl); if (process.env.NEXT_PUBLIC_SERVER_MODE === 'true') { try { const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api'; const res = await fetch(`${apiBase}/projects/${projectId}/database/query`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sql: ddl }), }); if (!res.ok) { console.warn('[Schema] DDL apply failed — will auto-heal on Schema tab open'); } } catch { // Non-fatal — auto-apply on Schema tab open will recover } } } type SubTab = 'tables' | 'sql' | 'ddl'; export function SchemaEditor({ projectId, enabled, onSchemaChange, workspaceId }: SchemaEditorProps) { const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api'; const [activeSubTab, setActiveSubTab] = useState('tables'); const [ddl, setDdl] = useState(''); const [applying, setApplying] = useState(false); const [schemaKey, setSchemaKey] = useState(0); const autoAppliedRef = useRef(null); const schemaEndpoint = `${apiBase}/projects/${projectId}/database/schema`; const queryEndpoint = `${apiBase}/projects/${projectId}/database/query`; // Auto-apply: if localStorage has schema DDL but the project database has no tables, // apply the DDL automatically. This self-heals when the initial application during // project creation failed (e.g., project not yet synced to SQLite, server restart). useEffect(() => { if (!enabled) return; // Only auto-apply once per projectId if (autoAppliedRef.current === projectId) return; const storedSchema = getProjectSchema(projectId); if (!storedSchema) return; const tryAutoApply = async () => { try { // Check if database already has tables const schemaRes = await fetch(schemaEndpoint); if (!schemaRes.ok) return; const schemaData = await schemaRes.json(); if (schemaData.tables && schemaData.tables.length > 0) { autoAppliedRef.current = projectId; return; // Already has tables, nothing to do } // Database is empty but localStorage has DDL — apply it const res = await fetch(queryEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sql: storedSchema }), }); if (res.ok) { autoAppliedRef.current = projectId; setSchemaKey(prev => prev + 1); } } catch { // Non-fatal — user can manually apply via DDL tab } }; tryAutoApply(); }, [enabled, projectId, schemaEndpoint, queryEndpoint]); const applyDDL = useCallback(async () => { if (!ddl.trim()) return; setApplying(true); try { const res = await fetch(queryEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sql: ddl.trim() }), }); const data = await res.json(); if (!res.ok) { toast.error(data.error || 'Failed to apply DDL'); return; } toast.success('DDL applied successfully'); // Update localStorage schema (append DDL) so AI server context stays in sync const existing = getProjectSchema(projectId); const updated = existing ? `${existing}\n\n${ddl.trim()}` : ddl.trim(); setProjectSchema(projectId, updated); onSchemaChange?.(updated); // Refresh SchemaViewer setSchemaKey(prev => prev + 1); setDdl(''); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to apply DDL'); } finally { setApplying(false); } }, [ddl, queryEndpoint, projectId, onSchemaChange]); if (!enabled) { return null; } return (
{/* Sub-tab buttons */}
{(['tables', 'sql', 'ddl'] as const).map(tab => ( ))}
{/* Sub-tab content */}
{activeSubTab === 'tables' && ( )} {activeSubTab === 'sql' && ( )} {activeSubTab === 'ddl' && (

Apply DDL

CREATE TABLE, ALTER TABLE, and other DDL statements