mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): TopModelView + ModelSettingsModal + AddCredentialModal
- TopModelView: antd Segmented + tremor BarChart \u2192 shadcn ToggleGroup + preserved BarChart (phase-1 chart whitelist). View-mode pills use the shared blue categorical palette. - ModelSettingsModal: antd Modal/Button/Skeleton/Space/Typography \u2192 shadcn Dialog/Button/Skeleton. Form.Item still drives the store-in-DB toggle using the original antd Switch (as AntdFormSwitch) since Form.Item still provides the value binding. - AddCredentialModal: antd Modal/Button/Typography/Tooltip + @tremor TextInput \u2192 shadcn Dialog/Button/Input/Tooltip. antd Select is preserved inside Form.Item for now \u2014 full provider-logo rewrite is a larger section migration. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
6fa4d35d7d
commit
381cb8a614
3 changed files with 274 additions and 148 deletions
|
|
@ -1,6 +1,11 @@
|
|||
// eslint-disable-next-line litellm-ui/no-banned-ui-imports
|
||||
import { BarChart } from "@tremor/react";
|
||||
import { Segmented } from "antd";
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/components/ui/toggle-group";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { formatNumberWithCommas } from "../../../../utils/dataUtils";
|
||||
import { DataTable } from "../../../view_logs/table";
|
||||
|
||||
|
|
@ -18,18 +23,26 @@ interface TopModelViewProps {
|
|||
setTopModelsLimit: (limit: number) => void;
|
||||
}
|
||||
|
||||
export default function TopModelView({ topModels, topModelsLimit, setTopModelsLimit }: TopModelViewProps) {
|
||||
const [modelViewMode, setModelViewMode] = useState<"chart" | "table">("table");
|
||||
export default function TopModelView({
|
||||
topModels,
|
||||
topModelsLimit,
|
||||
setTopModelsLimit,
|
||||
}: TopModelViewProps) {
|
||||
const [modelViewMode, setModelViewMode] = useState<"chart" | "table">(
|
||||
"table",
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
header: "Model",
|
||||
accessorKey: "key",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cell: (info: any) => info.getValue() || "-",
|
||||
},
|
||||
{
|
||||
header: "Spend (USD)",
|
||||
accessorKey: "spend",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cell: (info: any) => {
|
||||
const value = info.getValue();
|
||||
return `$${formatNumberWithCommas(value, 2)}`;
|
||||
|
|
@ -38,16 +51,27 @@ export default function TopModelView({ topModels, topModelsLimit, setTopModelsLi
|
|||
{
|
||||
header: "Successful",
|
||||
accessorKey: "successful_requests",
|
||||
cell: (info: any) => <span className="text-green-600">{info.getValue()?.toLocaleString() || 0}</span>,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cell: (info: any) => (
|
||||
<span className="text-emerald-600 dark:text-emerald-400">
|
||||
{info.getValue()?.toLocaleString() || 0}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Failed",
|
||||
accessorKey: "failed_requests",
|
||||
cell: (info: any) => <span className="text-red-600">{info.getValue()?.toLocaleString() || 0}</span>,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cell: (info: any) => (
|
||||
<span className="text-destructive">
|
||||
{info.getValue()?.toLocaleString() || 0}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Tokens",
|
||||
accessorKey: "tokens",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cell: (info: any) => info.getValue()?.toLocaleString() || 0,
|
||||
},
|
||||
];
|
||||
|
|
@ -56,26 +80,42 @@ export default function TopModelView({ topModels, topModelsLimit, setTopModelsLi
|
|||
return (
|
||||
<>
|
||||
<div className="mb-4 flex justify-between items-center">
|
||||
<Segmented
|
||||
options={[
|
||||
{ label: "5", value: 5 },
|
||||
{ label: "10", value: 10 },
|
||||
{ label: "25", value: 25 },
|
||||
{ label: "50", value: 50 },
|
||||
]}
|
||||
value={topModelsLimit}
|
||||
onChange={(value) => setTopModelsLimit(value as number)}
|
||||
/>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={String(topModelsLimit)}
|
||||
onValueChange={(v) => {
|
||||
if (!v) return;
|
||||
setTopModelsLimit(parseInt(v));
|
||||
}}
|
||||
>
|
||||
{[5, 10, 25, 50].map((n) => (
|
||||
<ToggleGroupItem key={n} value={String(n)}>
|
||||
{n}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setModelViewMode("table")}
|
||||
className={`px-3 py-1 text-sm rounded-md ${modelViewMode === "table" ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-700"}`}
|
||||
className={cn(
|
||||
"px-3 py-1 text-sm rounded-md",
|
||||
modelViewMode === "table"
|
||||
? "bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300"
|
||||
: "bg-muted text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
Table View
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setModelViewMode("chart")}
|
||||
className={`px-3 py-1 text-sm rounded-md ${modelViewMode === "chart" ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-700"}`}
|
||||
className={cn(
|
||||
"px-3 py-1 text-sm rounded-md",
|
||||
modelViewMode === "chart"
|
||||
? "bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300"
|
||||
: "bg-muted text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
Chart View
|
||||
</button>
|
||||
|
|
@ -85,7 +125,10 @@ export default function TopModelView({ topModels, topModelsLimit, setTopModelsLi
|
|||
<div className="relative max-h-[600px] overflow-y-auto">
|
||||
<BarChart
|
||||
className="mt-4 cursor-pointer hover:opacity-90"
|
||||
style={{ height: Math.min(processedTopModels.length, topModelsLimit) * 52 }}
|
||||
style={{
|
||||
height:
|
||||
Math.min(processedTopModels.length, topModelsLimit) * 52,
|
||||
}}
|
||||
data={processedTopModels}
|
||||
index="key"
|
||||
categories={["spend"]}
|
||||
|
|
|
|||
|
|
@ -1,117 +1,154 @@
|
|||
import { TextInput } from "@tremor/react";
|
||||
import { Select as AntdSelect, Button, Form, Modal, Tooltip, Typography } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import {
|
||||
Select as AntdSelect,
|
||||
Form,
|
||||
} from "antd";
|
||||
import type { UploadProps } from "antd/es/upload";
|
||||
import React, { useState } from "react";
|
||||
import ProviderSpecificFields from "../add_model/provider_specific_fields";
|
||||
import { Providers, providerLogoMap } from "../provider_info_helpers";
|
||||
const { Link } = Typography;
|
||||
|
||||
interface AddCredentialsModalProps {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onAddCredential: (values: any) => void;
|
||||
uploadProps: UploadProps;
|
||||
}
|
||||
|
||||
const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({ open, onCancel, onAddCredential, uploadProps }) => {
|
||||
const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({
|
||||
open,
|
||||
onCancel,
|
||||
onAddCredential,
|
||||
uploadProps,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [selectedProvider, setSelectedProvider] = useState<Providers>(Providers.OpenAI);
|
||||
const [selectedProvider, setSelectedProvider] = useState<Providers>(
|
||||
Providers.OpenAI,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleSubmit = (values: any) => {
|
||||
const filteredValues = Object.entries(values).reduce((acc, [key, value]) => {
|
||||
if (value !== "" && value !== undefined && value !== null) {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as any);
|
||||
const filteredValues = Object.entries(values).reduce(
|
||||
(acc, [key, value]) => {
|
||||
if (value !== "" && value !== undefined && value !== null) {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
{} as any,
|
||||
);
|
||||
onAddCredential(filteredValues);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
onCancel();
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Add New Credential"
|
||||
open={open}
|
||||
onCancel={() => {
|
||||
onCancel();
|
||||
form.resetFields();
|
||||
}}
|
||||
footer={null}
|
||||
width={600}
|
||||
>
|
||||
<Form form={form} onFinish={handleSubmit} layout="vertical">
|
||||
{/* Credential Name */}
|
||||
<Form.Item
|
||||
label="Credential Name:"
|
||||
name="credential_name"
|
||||
rules={[{ required: true, message: "Credential name is required" }]}
|
||||
>
|
||||
<TextInput placeholder="Enter a friendly name for these credentials" />
|
||||
</Form.Item>
|
||||
|
||||
{/* Provider Selection */}
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="Provider:"
|
||||
name="custom_llm_provider"
|
||||
tooltip="Helper to auto-populate provider specific fields"
|
||||
>
|
||||
<AntdSelect
|
||||
showSearch
|
||||
onChange={(value) => {
|
||||
setSelectedProvider(value as Providers);
|
||||
form.setFieldValue("custom_llm_provider", value);
|
||||
}}
|
||||
<Dialog open={open} onOpenChange={(o) => (!o ? handleCancel() : undefined)}>
|
||||
<DialogContent className="max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add New Credential</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form form={form} onFinish={handleSubmit} layout="vertical">
|
||||
<Form.Item
|
||||
label="Credential Name:"
|
||||
name="credential_name"
|
||||
rules={[{ required: true, message: "Credential name is required" }]}
|
||||
>
|
||||
{Object.entries(Providers).map(([providerEnum, providerDisplayName]) => (
|
||||
<AntdSelect.Option key={providerEnum} value={providerEnum}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
src={providerLogoMap[providerDisplayName]}
|
||||
alt={`${providerEnum} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
const fallbackDiv = document.createElement("div");
|
||||
fallbackDiv.className =
|
||||
"w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs";
|
||||
fallbackDiv.textContent = providerDisplayName.charAt(0);
|
||||
parent.replaceChild(fallbackDiv, target);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span>{providerDisplayName}</span>
|
||||
</div>
|
||||
</AntdSelect.Option>
|
||||
))}
|
||||
</AntdSelect>
|
||||
</Form.Item>
|
||||
<Input placeholder="Enter a friendly name for these credentials" />
|
||||
</Form.Item>
|
||||
|
||||
<ProviderSpecificFields selectedProvider={selectedProvider} uploadProps={uploadProps} />
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="flex justify-between items-center">
|
||||
<Tooltip title="Get help on our github">
|
||||
<Link href="https://github.com/BerriAI/litellm/issues">Need Help?</Link>
|
||||
</Tooltip>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onCancel();
|
||||
form.resetFields();
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="Provider:"
|
||||
name="custom_llm_provider"
|
||||
tooltip="Helper to auto-populate provider specific fields"
|
||||
>
|
||||
<AntdSelect
|
||||
showSearch
|
||||
onChange={(value) => {
|
||||
setSelectedProvider(value as Providers);
|
||||
form.setFieldValue("custom_llm_provider", value);
|
||||
}}
|
||||
style={{ marginRight: 10 }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit">{"Add Credential"}</Button>
|
||||
{Object.entries(Providers).map(
|
||||
([providerEnum, providerDisplayName]) => (
|
||||
<AntdSelect.Option key={providerEnum} value={providerEnum}>
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={providerLogoMap[providerDisplayName]}
|
||||
alt={`${providerEnum} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
const fallbackDiv = document.createElement("div");
|
||||
fallbackDiv.className =
|
||||
"w-5 h-5 rounded-full bg-muted flex items-center justify-center text-xs";
|
||||
fallbackDiv.textContent =
|
||||
providerDisplayName.charAt(0);
|
||||
parent.replaceChild(fallbackDiv, target);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span>{providerDisplayName}</span>
|
||||
</div>
|
||||
</AntdSelect.Option>
|
||||
),
|
||||
)}
|
||||
</AntdSelect>
|
||||
</Form.Item>
|
||||
|
||||
<ProviderSpecificFields
|
||||
selectedProvider={selectedProvider}
|
||||
uploadProps={uploadProps}
|
||||
/>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href="https://github.com/BerriAI/litellm/issues"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Need Help?
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Get help on our github</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Add Credential</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,25 @@
|
|||
"use client";
|
||||
|
||||
import { ConfigType, useProxyConfig } from "@/app/(dashboard)/hooks/proxyConfig/useProxyConfig";
|
||||
import { StoreModelInDBParams, useStoreModelInDB } from "@/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB";
|
||||
import {
|
||||
ConfigType,
|
||||
useProxyConfig,
|
||||
} from "@/app/(dashboard)/hooks/proxyConfig/useProxyConfig";
|
||||
import {
|
||||
StoreModelInDBParams,
|
||||
useStoreModelInDB,
|
||||
} from "@/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB";
|
||||
import NotificationsManager from "@/components/molecules/notifications_manager";
|
||||
import { parseErrorMessage } from "@/components/shared/errorUtils";
|
||||
import { Button, Form, Modal, Skeleton, Space, Switch, Typography } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Form, Switch as AntdFormSwitch } from "antd";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
|
||||
interface ModelSettingsModalProps {
|
||||
|
|
@ -13,19 +28,25 @@ interface ModelSettingsModalProps {
|
|||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
const ModelSettingsModal: React.FC<ModelSettingsModalProps> = ({ isVisible, onCancel, onSuccess }) => {
|
||||
const ModelSettingsModal: React.FC<ModelSettingsModalProps> = ({
|
||||
isVisible,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const { mutateAsync, isPending } = useStoreModelInDB();
|
||||
const { data: proxyConfigData, isLoading: isLoadingConfig, refetch } = useProxyConfig(ConfigType.GENERAL_SETTINGS);
|
||||
const {
|
||||
data: proxyConfigData,
|
||||
isLoading: isLoadingConfig,
|
||||
refetch,
|
||||
} = useProxyConfig(ConfigType.GENERAL_SETTINGS);
|
||||
|
||||
// Refetch config when modal opens to ensure we have the latest values
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
refetch();
|
||||
}
|
||||
}, [isVisible, refetch]);
|
||||
|
||||
// Compute initial values from fetched config data
|
||||
const initialValues = useMemo(() => {
|
||||
if (!proxyConfigData) {
|
||||
return {
|
||||
|
|
@ -33,7 +54,9 @@ const ModelSettingsModal: React.FC<ModelSettingsModalProps> = ({ isVisible, onCa
|
|||
};
|
||||
}
|
||||
|
||||
const storeModelField = proxyConfigData.find(field => field.field_name === 'store_model_in_db');
|
||||
const storeModelField = proxyConfigData.find(
|
||||
(field) => field.field_name === "store_model_in_db",
|
||||
);
|
||||
|
||||
return {
|
||||
store_model_in_db: storeModelField?.field_value ?? false,
|
||||
|
|
@ -44,16 +67,22 @@ const ModelSettingsModal: React.FC<ModelSettingsModalProps> = ({ isVisible, onCa
|
|||
try {
|
||||
await mutateAsync(formValues, {
|
||||
onSuccess: () => {
|
||||
NotificationsManager.success("Model storage settings updated successfully");
|
||||
NotificationsManager.success(
|
||||
"Model storage settings updated successfully",
|
||||
);
|
||||
refetch();
|
||||
onSuccess?.();
|
||||
},
|
||||
onError: (error) => {
|
||||
NotificationsManager.fromBackend("Failed to save model storage settings: " + parseErrorMessage(error));
|
||||
NotificationsManager.fromBackend(
|
||||
"Failed to save model storage settings: " + parseErrorMessage(error),
|
||||
);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
NotificationsManager.fromBackend("Failed to save model storage settings: " + parseErrorMessage(error));
|
||||
NotificationsManager.fromBackend(
|
||||
"Failed to save model storage settings: " + parseErrorMessage(error),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -62,42 +91,59 @@ const ModelSettingsModal: React.FC<ModelSettingsModalProps> = ({ isVisible, onCa
|
|||
onCancel();
|
||||
};
|
||||
|
||||
const storeFieldDescription =
|
||||
proxyConfigData?.find((f) => f.field_name === "store_model_in_db")
|
||||
?.field_description ||
|
||||
"If enabled, models and config are stored in and loaded from the database.";
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<Typography.Title level={5}>Model Settings</Typography.Title>}
|
||||
<Dialog
|
||||
open={isVisible}
|
||||
footer={
|
||||
<Space>
|
||||
<Button onClick={handleCancel} disabled={isPending || isLoadingConfig}>
|
||||
onOpenChange={(o) => (!o ? handleCancel() : undefined)}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base font-semibold">
|
||||
Model Settings
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form
|
||||
key={proxyConfigData ? JSON.stringify(initialValues) : "loading"}
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
onFinish={handleFormSubmit}
|
||||
initialValues={initialValues}
|
||||
>
|
||||
<Form.Item
|
||||
label="Store Model in DB"
|
||||
name="store_model_in_db"
|
||||
tooltip={storeFieldDescription}
|
||||
valuePropName="checked"
|
||||
>
|
||||
{isLoadingConfig ? (
|
||||
<Skeleton className="h-6 w-full" />
|
||||
) : (
|
||||
<AntdFormSwitch />
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
disabled={isPending || isLoadingConfig}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" loading={isPending} disabled={isLoadingConfig} onClick={() => form.submit()}>
|
||||
<Button
|
||||
onClick={() => form.submit()}
|
||||
disabled={isPending || isLoadingConfig}
|
||||
>
|
||||
{isPending ? "Saving..." : "Save Settings"}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form
|
||||
key={proxyConfigData ? JSON.stringify(initialValues) : 'loading'}
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
onFinish={handleFormSubmit}
|
||||
initialValues={initialValues}
|
||||
>
|
||||
<Form.Item
|
||||
label="Store Model in DB"
|
||||
name="store_model_in_db"
|
||||
tooltip={
|
||||
proxyConfigData?.find(f => f.field_name === 'store_model_in_db')?.field_description ||
|
||||
"If enabled, models and config are stored in and loaded from the database."
|
||||
}
|
||||
valuePropName="checked"
|
||||
>
|
||||
{isLoadingConfig ? <Skeleton.Input active block /> : <Switch />}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue