CoolFace
Apppublic

Leon4gr45/builder

sourceHugging Facemitupdated 2d agoView on Hugging Face
0likes
schema-editor.tsx218 linesDownload Raw Back to project-backend
1'use client';2 3import React, { useState, useCallback, useEffect, useRef } from 'react';4import { Button } from '@/components/ui/button';5import { Play, Loader2 } from 'lucide-react';6import { toast } from 'sonner';7import { SchemaViewer } from '@/components/database-manager/schema-viewer';8import { SqlEditor } from '@/components/database-manager/sql-editor';9 10interface SchemaEditorProps {11  projectId: string;12  enabled: boolean;13  onSchemaChange?: (schema: string) => void;14  workspaceId?: string;15}16 17// Keep these exports — used by vfs/index.ts for transient file generation18export function getProjectSchema(projectId: string): string {19  if (typeof window === 'undefined') return '';20  return localStorage.getItem(`osw-db-schema-${projectId}`) || '';21}22 23export function setProjectSchema(projectId: string, schema: string): void {24  if (typeof window === 'undefined') return;25  if (schema) {26    localStorage.setItem(`osw-db-schema-${projectId}`, schema);27  } else {28    localStorage.removeItem(`osw-db-schema-${projectId}`);29  }30}31 32/**33 * Save schema to localStorage and apply DDL to the project database (Server Mode only).34 * Used by project-manager and template-manager during project creation.35 */36export async function applyProjectDatabaseSchema(projectId: string, ddl: string, workspaceId?: string): Promise<void> {37  setProjectSchema(projectId, ddl);38  if (process.env.NEXT_PUBLIC_SERVER_MODE === 'true') {39    try {40      const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api';41      const res = await fetch(`${apiBase}/projects/${projectId}/database/query`, {42        method: 'POST',43        headers: { 'Content-Type': 'application/json' },44        body: JSON.stringify({ sql: ddl }),45      });46      if (!res.ok) {47        console.warn('[Schema] DDL apply failed — will auto-heal on Schema tab open');48      }49    } catch {50      // Non-fatal — auto-apply on Schema tab open will recover51    }52  }53}54 55type SubTab = 'tables' | 'sql' | 'ddl';56 57export function SchemaEditor({ projectId, enabled, onSchemaChange, workspaceId }: SchemaEditorProps) {58  const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api';59  const [activeSubTab, setActiveSubTab] = useState<SubTab>('tables');60  const [ddl, setDdl] = useState('');61  const [applying, setApplying] = useState(false);62  const [schemaKey, setSchemaKey] = useState(0);63  const autoAppliedRef = useRef<string | null>(null);64 65  const schemaEndpoint = `${apiBase}/projects/${projectId}/database/schema`;66  const queryEndpoint = `${apiBase}/projects/${projectId}/database/query`;67 68  // Auto-apply: if localStorage has schema DDL but the project database has no tables,69  // apply the DDL automatically. This self-heals when the initial application during70  // project creation failed (e.g., project not yet synced to SQLite, server restart).71  useEffect(() => {72    if (!enabled) return;73    // Only auto-apply once per projectId74    if (autoAppliedRef.current === projectId) return;75 76    const storedSchema = getProjectSchema(projectId);77    if (!storedSchema) return;78 79    const tryAutoApply = async () => {80      try {81        // Check if database already has tables82        const schemaRes = await fetch(schemaEndpoint);83        if (!schemaRes.ok) return;84        const schemaData = await schemaRes.json();85        if (schemaData.tables && schemaData.tables.length > 0) {86          autoAppliedRef.current = projectId;87          return; // Already has tables, nothing to do88        }89 90        // Database is empty but localStorage has DDL — apply it91        const res = await fetch(queryEndpoint, {92          method: 'POST',93          headers: { 'Content-Type': 'application/json' },94          body: JSON.stringify({ sql: storedSchema }),95        });96 97        if (res.ok) {98          autoAppliedRef.current = projectId;99          setSchemaKey(prev => prev + 1);100        }101      } catch {102        // Non-fatal — user can manually apply via DDL tab103      }104    };105 106    tryAutoApply();107  }, [enabled, projectId, schemaEndpoint, queryEndpoint]);108 109  const applyDDL = useCallback(async () => {110    if (!ddl.trim()) return;111 112    setApplying(true);113    try {114      const res = await fetch(queryEndpoint, {115        method: 'POST',116        headers: { 'Content-Type': 'application/json' },117        body: JSON.stringify({ sql: ddl.trim() }),118      });119 120      const data = await res.json();121      if (!res.ok) {122        toast.error(data.error || 'Failed to apply DDL');123        return;124      }125 126      toast.success('DDL applied successfully');127 128      // Update localStorage schema (append DDL) so AI server context stays in sync129      const existing = getProjectSchema(projectId);130      const updated = existing ? `${existing}\n\n${ddl.trim()}` : ddl.trim();131      setProjectSchema(projectId, updated);132      onSchemaChange?.(updated);133 134      // Refresh SchemaViewer135      setSchemaKey(prev => prev + 1);136      setDdl('');137    } catch (err) {138      toast.error(err instanceof Error ? err.message : 'Failed to apply DDL');139    } finally {140      setApplying(false);141    }142  }, [ddl, queryEndpoint, projectId, onSchemaChange]);143 144  if (!enabled) {145    return null;146  }147 148  return (149    <div className="h-full flex flex-col">150      {/* Sub-tab buttons */}151      <div className="flex items-center gap-1 mb-3 border-b pb-2">152        {(['tables', 'sql', 'ddl'] as const).map(tab => (153          <button154            key={tab}155            onClick={() => setActiveSubTab(tab)}156            className={`px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${157              activeSubTab === tab158                ? 'bg-primary text-primary-foreground'159                : 'text-muted-foreground hover:text-foreground hover:bg-muted'160            }`}161          >162            {tab === 'tables' ? 'Tables' : tab === 'sql' ? 'SQL' : 'DDL'}163          </button>164        ))}165      </div>166 167      {/* Sub-tab content */}168      <div className="flex-1 min-h-0">169        {activeSubTab === 'tables' && (170          <SchemaViewer171            key={schemaKey}172            schemaEndpoint={schemaEndpoint}173            showSystemTablesToggle={false}174          />175        )}176 177        {activeSubTab === 'sql' && (178          <SqlEditor queryEndpoint={queryEndpoint} />179        )}180 181        {activeSubTab === 'ddl' && (182          <div className="h-full flex flex-col gap-3">183            <div className="flex items-center justify-between">184              <div>185                <h4 className="text-sm font-medium">Apply DDL</h4>186                <p className="text-xs text-muted-foreground mt-0.5">187                  CREATE TABLE, ALTER TABLE, and other DDL statements188                </p>189              </div>190              <Button191                size="sm"192                className="h-7 px-2 text-xs"193                onClick={applyDDL}194                disabled={applying || !ddl.trim()}195              >196                {applying ? (197                  <Loader2 className="h-3 w-3 mr-1 animate-spin" />198                ) : (199                  <Play className="h-3 w-3 mr-1" />200                )}201                Apply202              </Button>203            </div>204            <textarea205              data-schema-editor206              className="flex-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-mono resize-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring placeholder:text-muted-foreground"207              placeholder={`-- Create or modify tables\nCREATE TABLE IF NOT EXISTS example (\n  id INTEGER PRIMARY KEY AUTOINCREMENT,\n  name TEXT NOT NULL,\n  created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);`}208              value={ddl}209              onChange={(e) => setDdl(e.target.value)}210              spellCheck={false}211            />212          </div>213        )}214      </div>215    </div>216  );217}218