"use client"; /** * TASK-0124 — "Generate database from a prompt" dialog. * * Flow: the user types a description → the dialog streams the generation * (SSE — `sendDbGenerate`) → on success the `done` frame carries the new * `databaseId`; the dialog toasts + (when embedded on a database page) offers * nothing to navigate to (the new database is a sibling — the sidebar * page-tree is invalidated so it appears there), then closes. * * Hidden for roles lacking EDIT (the server still enforces — 403). * * Pinned testids (tests/e2e/specs/ai-database.spec.ts — do not rename): * db-ai-generate the dialog * db-ai-generate-prompt the prompt textarea * db-ai-generate-btn the Generate button * db-ai-generate-done the success summary (database name + counts) * db-ai-error-banner / db-ai-loading (shared chrome) */ import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { CheckCheck, Loader2, Sparkles } from "lucide-react"; import { useDbAiStream } from "@/hooks/use-db-ai-stream"; import { DbAiDialog, DbAiErrorBanner, DbAiLoading } from "@/components/ai/db-ai-dialog"; export interface AiGenerateDialogProps { open: boolean; workspaceId: string; /** Navigate to the new database after success (the page-tree route). */ workspaceSlug?: string; onClose: () => void; } export function AiGenerateDialog({ open, workspaceId, workspaceSlug, onClose }: AiGenerateDialogProps) { const qc = useQueryClient(); const router = useRouter(); const [prompt, setPrompt] = useState(""); const { phase, result, error, runGenerate } = useDbAiStream({ workspaceId }); const lastPrompt = useRef(""); // Reset the form when the dialog is (re)opened. useEffect(() => { if (open) setPrompt(""); }, [open]); const invalid = prompt.trim().length === 0; const generate = useCallback(() => { lastPrompt.current = prompt.trim(); void runGenerate(prompt.trim()); }, [prompt, runGenerate]); const retry = useCallback(() => { void runGenerate(lastPrompt.current); }, [runGenerate]); const close = useCallback(() => { onClose(); }, [onClose]); const done = phase === "done" && result?.done; const databaseId = done ? (result!.done as { databaseId?: string }).databaseId : undefined; const propCount = done ? (result!.done as { propertyIds?: string[] }).propertyIds?.length ?? 0 : 0; const recordCount = done ? (result!.done as { recordIds?: string[] }).recordIds?.length ?? 0 : 0; const invalidateTree = useCallback(() => { void qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "page-tree"] }); }, [qc, workspaceId]); // Refresh the sidebar page-tree once the generation succeeds. useEffect(() => { if (phase === "done" && databaseId) invalidateTree(); }, [phase, databaseId, invalidateTree]); const budget = error?.budget; const live = phase === "streaming" ? "AI is generating a database" : done ? `Database generated: ${propCount} properties, ${recordCount} sample records` : error ? `Error: ${error.message}` : ""; return ( Creates the database with AI-suggested properties + sample records. } >