import { useEffect, useState } from "react"; import { Anchor, Button, Card, Group, Table, Text, TextInput, Title, } from "@mantine/core"; import { useForm } from "@mantine/form"; import { notifications } from "@mantine/notifications"; import { IconExternalLink, IconRefresh, IconUpload, } from "@tabler/icons-react"; import { z } from "zod"; import { LANGFLOW_URL, api, type FlowDefinition, type FlowVersion, } from "../api"; import { formatApiError } from "../utils"; import FlowHelp from "./FlowHelp"; import { ErrorAlert } from "./ErrorAlert"; import StatusBadge from "./StatusBadge"; const importSchema = z.object({ langflowId: z.string().min(1, "Flow-ID erforderlich"), importName: z.string(), }); export default function AdminTab() { const [flows, setFlows] = useState([]); const [versions, setVersions] = useState>({}); const importForm = useForm({ initialValues: { langflowId: "", importName: "" }, validate: (values) => { const parsed = importSchema.safeParse(values); if (parsed.success) return {}; return { langflowId: parsed.error.flatten().fieldErrors.langflowId?.[0] ?? null, }; }, }); const [error, setError] = useState(""); function notifyInfo(message: string) { notifications.show({ message, color: "green" }); } useEffect(() => { void (async () => { try { const list = await api.listFlows(); setFlows(list); const entries = await Promise.all( list.map(async (f) => [f.id, await api.listVersions(f.id)] as const), ); setVersions(Object.fromEntries(entries)); } catch (e) { setError(formatApiError(e)); } })(); }, []); async function load() { try { const list = await api.listFlows(); setFlows(list); const entries = await Promise.all( list.map(async (f) => [f.id, await api.listVersions(f.id)] as const), ); setVersions(Object.fromEntries(entries)); } catch (e) { setError(formatApiError(e)); } } async function onImport() { setError(""); const validation = importForm.validate(); if (validation.hasErrors) return; const { langflowId, importName } = importForm.values; try { const result = await api.importFlow( langflowId, importName.trim() ? importName.trim() : undefined, ); notifyInfo( `Importiert: ${result.definition.name} (Version ${result.version.version_no})`, ); importForm.reset(); await load(); } catch (e) { setError(formatApiError(e)); } } async function onDeploy(flowId: string, versionNo: number) { setError(""); try { const version = await api.deployVersion(flowId, versionNo); notifyInfo(`Deployed: Version ${version.version_no} (${version.lfx_flow_id})`); await load(); } catch (e) { setError(formatApiError(e)); } } async function onSync() { setError(""); try { const r = await api.syncFlows(); const parts = [ `Langflow: ${r.pulled.length}`, `+${r.added.length}`, `~${r.updated.length}`, `-${r.deleted.length}`, ].join(" "); const errs = Object.entries(r.errors) .map(([f, e]) => `${f}: ${e}`) .join("; "); notifyInfo(`Sync ${parts}${errs ? ` — Fehler: ${errs}` : ""}`); await load(); } catch (e) { setError(formatApiError(e)); } } return (
Flows & Versionen Flows werden visuell in Langflow entworfen:{" "} Langflow öffnen Holt alle Flows aus Langflow nach backend/flows und deployed sie danach automatisch. {flows.length === 0 ? ( Keine Flows vorhanden. ) : ( Flow Version Status Autor {flows.map((f) => { const list = versions[f.id] ?? []; const latest = list[list.length - 1]; if (!latest) { return ( {f.name}{" "} {f.source_path ? `(${f.source_path})` : "(API)"} – – ); } const live = list .filter((v) => v.deployed_at && v.id !== latest.id) .at(-1); return ( {f.name}{" "} {f.source_path ? `(${f.source_path})` : "(API)"} v{latest.version_no} {live && ( {" "} (live: v{live.version_no}) )} {latest.deployed_at ? ( ) : ( )} {latest.author || "–"} ); })}
)}
); }