feat: add AI catalog description generation
This commit is contained in:
@@ -8,14 +8,17 @@ import { toast } from "sonner";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { ApiError, apiErrorMessage } from "../../api/client";
|
||||
import {
|
||||
consolidateCatalogDescriptions,
|
||||
deleteCatalogTableMetadata,
|
||||
listCatalogTables,
|
||||
startCatalogSync,
|
||||
startDescriptionGenerationRun,
|
||||
updateCatalogTableMetadata,
|
||||
type CatalogDatabase,
|
||||
type CatalogSyncRun,
|
||||
type CatalogTable,
|
||||
type CatalogTableMetadataDeleteTarget,
|
||||
type DescriptionGenerationRun,
|
||||
} from "../../api/catalog-databases";
|
||||
import type { DatabaseNavigationState } from "./model";
|
||||
import { DatabaseColumns } from "./DatabaseColumns";
|
||||
@@ -24,12 +27,15 @@ interface Props {
|
||||
database: CatalogDatabase;
|
||||
canManage: boolean;
|
||||
activeRun?: CatalogSyncRun;
|
||||
selectedMetadataModel: string | null;
|
||||
descriptionGenerationActive: boolean;
|
||||
onBackToDatabases: () => void;
|
||||
onOpenOverview: () => void;
|
||||
onOpenRelationships: () => void;
|
||||
onNavigationStateChange: (state: DatabaseNavigationState) => void;
|
||||
onRunStarted: (run: CatalogSyncRun) => void;
|
||||
onOpenSync: () => void;
|
||||
onDescriptionGenerationRunStarted: (run: DescriptionGenerationRun) => void;
|
||||
}
|
||||
|
||||
interface TableGridContext {
|
||||
@@ -55,12 +61,15 @@ export function DatabaseTables({
|
||||
database,
|
||||
canManage,
|
||||
activeRun,
|
||||
selectedMetadataModel,
|
||||
descriptionGenerationActive,
|
||||
onBackToDatabases,
|
||||
onOpenOverview,
|
||||
onOpenRelationships,
|
||||
onNavigationStateChange,
|
||||
onRunStarted,
|
||||
onOpenSync,
|
||||
onDescriptionGenerationRunStarted,
|
||||
}: Props) {
|
||||
const databaseId = database.id!;
|
||||
const queryClient = useQueryClient();
|
||||
@@ -77,7 +86,7 @@ export function DatabaseTables({
|
||||
const [editorVersion, setEditorVersion] = useState<number | null>(null);
|
||||
const [stale, setStale] = useState(false);
|
||||
const [staleBannerOpen, setStaleBannerOpen] = useState(true);
|
||||
const [busy, setBusy] = useState<"sync" | "save" | "delete" | null>(null);
|
||||
const [busy, setBusy] = useState<"sync" | "save" | "delete" | "consolidate" | "generate" | null>(null);
|
||||
const [pendingDelete, setPendingDelete] = useState<CatalogTableMetadataDeleteTarget | null>(null);
|
||||
const [columnNavigation, setColumnNavigation] = useState<DatabaseNavigationState>({ dirty: false, busy: false });
|
||||
const gridRef = useRef<AgGridReact<CatalogTable>>(null);
|
||||
@@ -203,6 +212,34 @@ export function DatabaseTables({
|
||||
toast.error(apiErrorMessage(error));
|
||||
} finally { setBusy(null); }
|
||||
};
|
||||
const consolidateDescriptions = async () => {
|
||||
setBusy("consolidate");
|
||||
try {
|
||||
const counts = await consolidateCatalogDescriptions(databaseId, "tables", selectedIds);
|
||||
await queryClient.invalidateQueries({ queryKey, exact: true });
|
||||
gridRef.current?.api.deselectAll();
|
||||
setSelectedIds([]);
|
||||
toast.success(`Copied ${counts.copied} description${counts.copied === 1 ? "" : "s"}; skipped ${counts.skipped}`);
|
||||
} catch (error) {
|
||||
toast.error(apiErrorMessage(error));
|
||||
} finally { setBusy(null); }
|
||||
};
|
||||
const generateDescriptions = async () => {
|
||||
if (selectedIds.length === 0 || !selectedMetadataModel) return;
|
||||
setBusy("generate");
|
||||
try {
|
||||
const run = await startDescriptionGenerationRun(
|
||||
databaseId,
|
||||
selectedMetadataModel,
|
||||
"selected_tables",
|
||||
selectedIds,
|
||||
);
|
||||
onDescriptionGenerationRunStarted(run);
|
||||
toast.success(`Description generation started for ${selectedIds.length} table${selectedIds.length === 1 ? "" : "s"}`);
|
||||
} catch (error) {
|
||||
toast.error(apiErrorMessage(error));
|
||||
} finally { setBusy(null); }
|
||||
};
|
||||
|
||||
const columns = useMemo<ColDef<CatalogTable>[]>(() => [
|
||||
{ field: "name", headerName: "Name", minWidth: 250, flex: 1, cellClass: "font-mono text-xs" },
|
||||
@@ -257,7 +294,16 @@ export function DatabaseTables({
|
||||
</nav>
|
||||
</div>
|
||||
{tableSection === "columns" ? (
|
||||
<DatabaseColumns databaseId={databaseId} table={activeTable} canManage={canManage} onNavigationStateChange={setColumnNavigation} onSync={() => void synchronize("columns", [activeTable.id])} />
|
||||
<DatabaseColumns
|
||||
databaseId={databaseId}
|
||||
table={activeTable}
|
||||
canManage={canManage}
|
||||
selectedMetadataModel={selectedMetadataModel}
|
||||
descriptionGenerationActive={descriptionGenerationActive}
|
||||
onDescriptionGenerationRunStarted={onDescriptionGenerationRunStarted}
|
||||
onNavigationStateChange={setColumnNavigation}
|
||||
onSync={() => void synchronize("columns", [activeTable.id])}
|
||||
/>
|
||||
) : (
|
||||
<div className="min-h-0 overflow-y-auto px-4 py-5">
|
||||
<p className="text-sm text-muted-foreground">Only review metadata can be changed.</p>
|
||||
@@ -334,6 +380,13 @@ export function DatabaseTables({
|
||||
<Menu.Portal>
|
||||
<Menu.Positioner side="bottom" align="start" sideOffset={4}>
|
||||
<Menu.Popup className="z-50 min-w-64 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-none">
|
||||
<Menu.Item
|
||||
className="rounded-md px-3 py-2 text-sm outline-none data-[highlighted]:bg-muted data-[disabled]:opacity-45"
|
||||
disabled={!canManage || !selectedMetadataModel || descriptionGenerationActive || busy !== null || Boolean(currentRun)}
|
||||
onClick={() => void generateDescriptions()}
|
||||
>
|
||||
Generate {selectedIds.length === 1 ? "description" : "descriptions"}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
className="rounded-md px-3 py-2 text-sm outline-none data-[highlighted]:bg-muted data-[disabled]:opacity-45"
|
||||
disabled={!canManage || !bindingReady || busy !== null || Boolean(currentRun)}
|
||||
@@ -341,6 +394,13 @@ export function DatabaseTables({
|
||||
>
|
||||
Synchronize columns
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
className="rounded-md px-3 py-2 text-sm outline-none data-[highlighted]:bg-muted data-[disabled]:opacity-45"
|
||||
disabled={!canManage || busy !== null || Boolean(currentRun)}
|
||||
onClick={() => void consolidateDescriptions()}
|
||||
>
|
||||
Move generated description to Description
|
||||
</Menu.Item>
|
||||
<Menu.Separator className="my-1 h-px bg-border" />
|
||||
<Menu.Item
|
||||
className="rounded-md px-3 py-2 text-sm text-destructive outline-none data-[highlighted]:bg-destructive/10 data-[disabled]:opacity-45"
|
||||
|
||||
Reference in New Issue
Block a user