import { useEffect, useMemo, useState, type ReactElement } from "react"; import { useNavigate } from "react-router-dom"; import { Layout } from "@/components/layout/Layout"; import { DataTable, type Column, SearchBox, FilterDropdown, ActionDropdown, PrimaryButton, DeleteConfirmationModal, StatusBadge, } from "@/components/shared"; import { Plus, Play, Loader2, Eye, Trash2 } from "lucide-react"; import { aiService } from "@/services/ai-service"; import type { TenantAIConfig } from "@/types/ai"; import { showToast } from "@/utils/toast"; import { formatDate } from "@/utils/format-date"; import { ViewAIProviderModal } from "@/components/tenant/ViewAIProviderModal"; import CodeBadge from "@/components/shared/CodeBadge"; import { TenantAIProviderCreate } from "./TenantAIProviderCreate"; interface TenantAIProvidersProps { customTenantId?: string; hideLayout?: boolean; } export const TenantAIProviders = ({ customTenantId, hideLayout = false, }: TenantAIProvidersProps = {}): ReactElement => { const navigate = useNavigate(); const [view, setView] = useState<"list" | "create">("list"); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [configs, setConfigs] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [statusFilter, setStatusFilter] = useState(""); const [testingProviders, setTestingProviders] = useState< Record >({}); const [selectedConfig, setSelectedConfig] = useState( null, ); const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [providerToDelete, setProviderToDelete] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const fetchConfigs = async () => { setIsLoading(true); setError(null); try { const data = await aiService.listConfigs(customTenantId); setConfigs(data || []); } catch (err: any) { const msg = err?.response?.data?.error?.message || "Failed to load configs"; setError(msg); showToast.error(msg); } finally { setIsLoading(false); } }; useEffect(() => { void fetchConfigs(); }, [customTenantId]); const handleTestConnection = async (provider: string) => { setTestingProviders((prev) => ({ ...prev, [provider]: true })); try { const resp = await aiService.testConfig(provider, customTenantId); if (resp && resp.healthy) { showToast.success( `Connection healthy for ${provider}! Latency: ${resp.latency_ms || "N/A"} ms`, ); } else { showToast.error(`Connection test failed for ${provider}.`); } } catch (err: any) { const msg = err?.response?.data?.error?.message || "Failed to test connection."; showToast.error(msg); } finally { setTestingProviders((prev) => ({ ...prev, [provider]: false })); } }; const handleViewConfig = async (provider: string) => { try { const cfg = await aiService.getConfig(provider, customTenantId); setSelectedConfig(cfg); setIsViewModalOpen(true); } catch (err: any) { const msg = err?.response?.data?.error?.message || "Failed to fetch AI Provider config details."; showToast.error(msg); } }; const handleDeleteConfig = (provider: string) => { setProviderToDelete(provider); setIsDeleteModalOpen(true); }; const onConfirmDelete = async () => { if (!providerToDelete) return; setIsDeleting(true); try { await aiService.deleteConfig(providerToDelete, customTenantId); showToast.success(`${providerToDelete} config removed successfully`); void fetchConfigs(); setIsDeleteModalOpen(false); } catch (err: any) { const msg = err?.response?.data?.error?.message || "Failed to delete AI Provider configuration."; showToast.error(msg); } finally { setIsDeleting(false); setProviderToDelete(null); } }; const clearFilters = () => { setSearchQuery(""); setStatusFilter(""); }; const filteredConfigs = useMemo(() => { return configs.filter((cfg) => { const searchMatches = !searchQuery.trim() || (cfg.provider || "") .toLowerCase() .includes(searchQuery.toLowerCase()) || (cfg.display_name || "") .toLowerCase() .includes(searchQuery.toLowerCase()) || (cfg.default_model || "") .toLowerCase() .includes(searchQuery.toLowerCase()); const statusMatches = !statusFilter || (statusFilter === "active" ? cfg.is_active : !cfg.is_active); return searchMatches && statusMatches; }); }, [configs, searchQuery, statusFilter]); const columns: Column[] = useMemo( () => [ { key: "provider", label: "Provider", render: (row) => (

{row.display_name || row.provider}

{row.endpoint && (

{row.endpoint}

)}
), }, { key: "config_type", label: "Config Type", render: (row) => { const type = row.config_type || "direct"; return ; }, }, { key: "default_model", label: "Default Model", render: (row) => ( {row.default_model || "—"} ), }, { key: "is_active", label: "Status", render: (row) => ( {row.is_active ? "Active" : "Disabled"} ), }, { key: "last_verified_at", label: "Last Verified", render: (row) => ( {row.last_verified_at ? formatDate(row.last_verified_at) : "Never"} ), }, { key: "actions", label: "Actions", align: "right", render: (row) => { const isTesting = testingProviders[row.provider] || false; return (
, label: "View Config", onClick: () => handleViewConfig(row.provider), }, { icon: ( ), label: "Delete Config", onClick: () => handleDeleteConfig(row.provider), }, ]} />
); }, }, ], [testingProviders], ); if (view === "create") { return ( setView("list")} onSuccess={() => { setView("list"); void fetchConfigs(); }} /> ); } const listContent = (
{/* Subhead Toolbar matching Screenshot filter design */}
setStatusFilter(typeof v === "string" ? v : "")} placeholder="All" options={[ { value: "active", label: "Active" }, { value: "disabled", label: "Disabled" }, ]} /> {(searchQuery || statusFilter) && ( )}
{hideLayout && ( setView("create")} className="h-10 px-4 flex items-center gap-1.5 shrink-0" > Create AI Provider )}
{/* Table list */} item.id || item.provider} isLoading={isLoading} error={error} emptyMessage="No tenant AI providers configured." /> setIsViewModalOpen(false)} config={selectedConfig} /> setIsDeleteModalOpen(false)} onConfirm={onConfirmDelete} title="Delete AI Provider" message="Are you sure you want to delete this AI provider configuration? This action cannot be undone." itemName={providerToDelete || ""} isLoading={isDeleting} />
); if (hideLayout) { return (
{listContent}
); } return ( navigate("/tenant/ai/providers/create")} className="h-10 px-4 flex items-center gap-1.5" > Create AI Provider ), }} > {listContent} ); }; export default TenantAIProviders;