"use client"; import { useMemo } from "react"; import { DashboardCrudPage, createDashboardStatusColumn, type DashboardCrudColumn, type DashboardCrudFilter, } from "@/components/dashboard/crud"; import { Badge } from "@/components/ui/badge"; import { createAIConfig, deleteAIConfig, fetchAIConfigs, updateAIConfig, updateAIConfigSort, updateAIConfigStatus, type AIConfig, type CreateAIConfigPayload, } from "@/lib/api/admin"; import { AIModelType, AIProvider, Status } from "@/lib/generated/enums"; import { useI18n } from "@/i18n/provider"; import { EditDialog } from "./_components/edit"; type TFunction = (key: string, values?: Record) => string; function getStatusOptions(t: TFunction) { return [ { value: "all", label: t("aiConfig.allStatuses") }, { value: String(Status.Ok), label: t("aiConfig.enabled") }, { value: String(Status.Disabled), label: t("aiConfig.disabled") }, { value: String(Status.Deleted), label: t("aiConfig.deletedStatus") }, ]; } function getProviderOptions(t: TFunction, includeAll = true) { const options = [ { value: String(AIProvider.OpenAI), label: t("aiConfig.providerOpenAI") }, ]; return includeAll ? [{ value: "all", label: t("aiConfig.allProviders") }, ...options] : options; } function getModelTypeOptions(t: TFunction, includeAll = true) { const options = [ { value: String(AIModelType.LLM), label: t("aiConfig.modelTypeLlm") }, { value: String(AIModelType.Embedding), label: t("aiConfig.modelTypeEmbedding"), }, { value: String(AIModelType.Rerank), label: t("aiConfig.modelTypeRerank") }, ]; return includeAll ? [{ value: "all", label: t("aiConfig.allTypes") }, ...options] : options; } function getStatusLabel(value: Status, t: TFunction) { return ( getStatusOptions(t).find((item) => item.value === String(value))?.label ?? String(value) ); } function getProviderLabel(value: AIProvider, t: TFunction) { return ( getProviderOptions(t, false).find((item) => item.value === String(value)) ?.label ?? String(value) ); } function getModelTypeLabel(value: AIModelType, t: TFunction) { return ( getModelTypeOptions(t, false).find((item) => item.value === String(value)) ?.label ?? String(value) ); } function maskAPIKey(value: string) { const text = value.trim(); if (!text) return "-"; if (text.length <= 8) return "****"; return `${text.slice(0, 4)}****${text.slice(-4)}`; } function getNextStatus(item: AIConfig) { return item.status === Status.Ok ? Status.Disabled : Status.Ok; } export default function DashboardAIConfigsPage() { const t = useI18n(); const listStatusOptions = useMemo(() => getStatusOptions(t), [t]); const providerFilterOptions = useMemo(() => getProviderOptions(t), [t]); const modelTypeFilterOptions = useMemo(() => getModelTypeOptions(t), [t]); const filters = useMemo( () => [ { name: "name", label: t("aiConfig.filterName"), placeholder: t("aiConfig.filterName"), defaultValue: "", trim: true, className: "w-full sm:w-72", }, { name: "modelType", label: t("aiConfig.allTypes"), type: "select", defaultValue: "all", allValue: "all", options: modelTypeFilterOptions, className: "w-full sm:w-40", }, { name: "provider", label: t("aiConfig.allProviders"), type: "select", defaultValue: "all", allValue: "all", options: providerFilterOptions, className: "w-full sm:w-40", }, { name: "status", label: t("aiConfig.allStatuses"), type: "select", defaultValue: "all", allValue: "all", options: listStatusOptions, className: "w-full sm:w-32", }, ], [listStatusOptions, modelTypeFilterOptions, providerFilterOptions, t], ); const columns = useMemo[]>( () => [ { key: "config", label: t("aiConfig.columnConfig"), render: (item) => (
{item.name}
), }, { key: "provider", label: t("aiConfig.columnProvider"), render: (item) => ( {getProviderLabel(item.provider as AIProvider, t)} ), }, { key: "model", label: t("aiConfig.columnModel"), render: (item) => (
{getModelTypeLabel(item.modelType as AIModelType, t)}
{item.modelName}
{item.dimension > 0 ? (
{t("aiConfig.dimension", { count: item.dimension })}
) : null}
), }, { key: "access", label: t("aiConfig.columnAccess"), render: (item) => (
{item.baseUrl}
{t("aiConfig.apiKey", { key: maskAPIKey(item.apiKey) })}
), }, { key: "limits", label: t("aiConfig.columnLimits"), render: (item) => (
{t("aiConfig.contextTokens", { count: item.maxContextTokens || 0, })}
{t("aiConfig.outputTokens", { count: item.maxOutputTokens || 0, })}
{t("aiConfig.timeoutRetry", { timeout: item.timeoutMs, retries: item.maxRetryCount, })}
RPM {item.rpmLimit || 0} / TPM {item.tpmLimit || 0}
), }, createDashboardStatusColumn({ label: t("aiConfig.columnStatus"), getStatus: (item) => item.status, getLabel: (status) => getStatusLabel(status as Status, t), getBadgeVariant: (status) => status === Status.Ok ? "default" : "outline", isEnabled: (status) => status === Status.Ok, toggle: { getNextStatus, updateStatus: (item, nextStatus) => updateAIConfigStatus(item.id, nextStatus), successMessage: (item, nextStatus) => t("aiConfig.statusChanged", { name: item.name, status: nextStatus === Status.Ok ? t("aiConfig.enabled") : t("aiConfig.disabled"), }), errorMessage: t("aiConfig.statusUpdateFailed"), ariaLabel: (item) => t("aiConfig.toggleStatus", { name: item.name }), }, }), ], [t], ); return ( filters={filters} columns={columns} fetchList={(query) => fetchAIConfigs({ name: typeof query.name === "string" ? query.name : undefined, status: typeof query.status === "string" ? query.status : undefined, provider: typeof query.provider === "string" ? query.provider : undefined, modelType: typeof query.modelType === "string" ? query.modelType : undefined, page: Number(query.page), limit: Number(query.limit), }) } getItemId={(item) => item.id} createItem={createAIConfig} updateItem={(item, payload) => updateAIConfig({ id: item.id, ...payload })} deleteItem={(item) => deleteAIConfig(item.id)} canDelete={(item) => item.status !== Status.Ok} deleteConfirm={(item) => ({ title: t("aiConfig.confirmDeleteTitle"), description: t("aiConfig.confirmDeleteDescription", { name: item.name, }), confirmText: t("aiConfig.confirmDelete"), cancelText: t("aiConfig.cancel"), variant: "destructive", })} sort={{ enabled: true, onReorder: (items) => updateAIConfigSort(items.map((item) => item.id)), successMessage: t("aiConfig.sortUpdated"), errorMessage: t("aiConfig.sortUpdateFailed"), handleLabel: t("aiConfig.dragSort", { name: "" }), }} renderEditDialog={({ open, saving, itemId, onOpenChange, onSubmit }) => ( )} labels={{ refresh: t("aiConfig.refresh"), create: t("aiConfig.new"), query: t("aiConfig.query"), loading: t("aiConfig.loadingRows"), empty: t("aiConfig.emptyRows"), actions: t("aiConfig.columnActions"), edit: t("aiConfig.edit"), delete: t("aiConfig.delete"), processing: t("aiConfig.deleting"), moreActions: (item) => t("aiConfig.moreActions", { name: item.name }), loadFailed: t("aiConfig.loadFailed"), saveFailed: t("aiConfig.saveFailed"), deleteFailed: t("aiConfig.deleteFailed"), created: (payload) => t("aiConfig.created", { name: payload.name }), updated: (item) => t("aiConfig.updated", { name: item.name }), deleted: (item) => t("aiConfig.deleted", { name: item.name }), }} /> ); }