import { useState, useRef, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { useConfig } from "./ConfigProvider"; import { ProviderList } from "./ProviderList"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { X, Trash2, Plus } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Combobox } from "@/components/ui/combobox"; import { ComboInput } from "@/components/ui/combo-input"; import { api } from "@/lib/api"; export function Providers() { const { t } = useTranslation(); const { config, setConfig } = useConfig(); const [editingProviderIndex, setEditingProviderIndex] = useState(null); const [deletingProviderIndex, setDeletingProviderIndex] = useState(null); const [hasFetchedModels, setHasFetchedModels] = useState>({}); const [providerParamInputs, setProviderParamInputs] = useState>({}); const [modelParamInputs, setModelParamInputs] = useState>({}); const [availableTransformers, setAvailableTransformers] = useState<{name: string; endpoint: string | null;}[]>([]); const comboInputRef = useRef(null); // Fetch available transformers when component mounts useEffect(() => { const fetchTransformers = async () => { try { const response = await api.get<{transformers: {name: string; endpoint: string | null;}[]}>('/transformers'); setAvailableTransformers(response.transformers); } catch (error) { console.error('Failed to fetch transformers:', error); } }; fetchTransformers(); }, []); // Handle case where config is null or undefined if (!config) { return ( {t("providers.title")}
Loading providers configuration...
); } // Validate config.Providers to ensure it's an array const validProviders = Array.isArray(config.Providers) ? config.Providers : []; const handleAddProvider = () => { const newProviders = [...config.Providers, { name: "", api_base_url: "", api_key: "", models: [] }]; setConfig({ ...config, Providers: newProviders }); setEditingProviderIndex(newProviders.length - 1); }; const handleSaveProvider = () => { setEditingProviderIndex(null); }; const handleCancelAddProvider = () => { // If we're adding a new provider, remove it regardless of content if (editingProviderIndex !== null && editingProviderIndex === config.Providers.length - 1) { const newProviders = [...config.Providers]; newProviders.pop(); setConfig({ ...config, Providers: newProviders }); } // Reset fetched models state for this provider if (editingProviderIndex !== null) { setHasFetchedModels(prev => { const newState = { ...prev }; delete newState[editingProviderIndex]; return newState; }); } setEditingProviderIndex(null); }; const handleRemoveProvider = (index: number) => { const newProviders = [...config.Providers]; newProviders.splice(index, 1); setConfig({ ...config, Providers: newProviders }); setDeletingProviderIndex(null); }; const handleProviderChange = (index: number, field: string, value: string) => { const newProviders = [...config.Providers]; newProviders[index][field] = value; setConfig({ ...config, Providers: newProviders }); }; const handleProviderTransformerChange = (index: number, transformerPath: string) => { if (!transformerPath) return; // Don't add empty transformers const newProviders = [...config.Providers]; if (!newProviders[index].transformer) { newProviders[index].transformer = { use: [] }; } // Add transformer to the use array newProviders[index].transformer!.use = [...newProviders[index].transformer!.use, transformerPath]; setConfig({ ...config, Providers: newProviders }); }; const removeProviderTransformerAtIndex = (index: number, transformerIndex: number) => { const newProviders = [...config.Providers]; if (newProviders[index].transformer) { const newUseArray = [...newProviders[index].transformer!.use]; newUseArray.splice(transformerIndex, 1); newProviders[index].transformer!.use = newUseArray; // If use array is now empty and no other properties, remove transformer entirely if (newUseArray.length === 0 && Object.keys(newProviders[index].transformer!).length === 1) { delete newProviders[index].transformer; } } setConfig({ ...config, Providers: newProviders }); }; const handleModelTransformerChange = (providerIndex: number, model: string, transformerPath: string) => { if (!transformerPath) return; // Don't add empty transformers const newProviders = [...config.Providers]; if (!newProviders[providerIndex].transformer) { newProviders[providerIndex].transformer = { use: [] }; } // Initialize model transformer if it doesn't exist if (!newProviders[providerIndex].transformer![model]) { newProviders[providerIndex].transformer![model] = { use: [] }; } // Add transformer to the use array newProviders[providerIndex].transformer![model].use = [...newProviders[providerIndex].transformer![model].use, transformerPath]; setConfig({ ...config, Providers: newProviders }); }; const removeModelTransformerAtIndex = (providerIndex: number, model: string, transformerIndex: number) => { const newProviders = [...config.Providers]; if (newProviders[providerIndex].transformer && newProviders[providerIndex].transformer![model]) { const newUseArray = [...newProviders[providerIndex].transformer![model].use]; newUseArray.splice(transformerIndex, 1); newProviders[providerIndex].transformer![model].use = newUseArray; // If use array is now empty and no other properties, remove model transformer entirely if (newUseArray.length === 0 && Object.keys(newProviders[providerIndex].transformer![model]).length === 1) { delete newProviders[providerIndex].transformer![model]; } } setConfig({ ...config, Providers: newProviders }); }; const addProviderTransformerParameter = (providerIndex: number, transformerIndex: number, paramName: string, paramValue: string) => { const newProviders = [...config.Providers]; if (!newProviders[providerIndex].transformer) { newProviders[providerIndex].transformer = { use: [] }; } // Add parameter to the specified transformer in use array if (newProviders[providerIndex].transformer!.use && newProviders[providerIndex].transformer!.use.length > transformerIndex) { const targetTransformer = newProviders[providerIndex].transformer!.use[transformerIndex]; // If it's already an array with parameters, update it if (Array.isArray(targetTransformer)) { const transformerArray = [...targetTransformer]; // Check if the second element is an object (parameters object) if (transformerArray.length > 1 && typeof transformerArray[1] === 'object' && transformerArray[1] !== null) { // Update the existing parameters object const existingParams = transformerArray[1] as Record; const paramsObj: Record = { ...existingParams, [paramName]: paramValue }; transformerArray[1] = paramsObj; } else if (transformerArray.length > 1) { // If there are other elements, add the parameters object const paramsObj = { [paramName]: paramValue }; transformerArray.splice(1, transformerArray.length - 1, paramsObj); } else { // Add a new parameters object const paramsObj = { [paramName]: paramValue }; transformerArray.push(paramsObj); } newProviders[providerIndex].transformer!.use[transformerIndex] = transformerArray as string | (string | Record | { max_tokens: number })[]; } else { // Convert to array format with parameters const paramsObj = { [paramName]: paramValue }; newProviders[providerIndex].transformer!.use[transformerIndex] = [targetTransformer as string, paramsObj]; } } setConfig({ ...config, Providers: newProviders }); }; const removeProviderTransformerParameterAtIndex = (providerIndex: number, transformerIndex: number, paramName: string) => { const newProviders = [...config.Providers]; if (!newProviders[providerIndex].transformer?.use || newProviders[providerIndex].transformer.use.length <= transformerIndex) { return; } const targetTransformer = newProviders[providerIndex].transformer.use[transformerIndex]; if (Array.isArray(targetTransformer) && targetTransformer.length > 1) { const transformerArray = [...targetTransformer]; // Check if the second element is an object (parameters object) if (typeof transformerArray[1] === 'object' && transformerArray[1] !== null) { const paramsObj = { ...(transformerArray[1] as Record) }; delete paramsObj[paramName]; // If the parameters object is now empty, remove it if (Object.keys(paramsObj).length === 0) { transformerArray.splice(1, 1); } else { transformerArray[1] = paramsObj; } newProviders[providerIndex].transformer!.use[transformerIndex] = transformerArray; setConfig({ ...config, Providers: newProviders }); } } }; const addModelTransformerParameter = (providerIndex: number, model: string, transformerIndex: number, paramName: string, paramValue: string) => { const newProviders = [...config.Providers]; if (!newProviders[providerIndex].transformer) { newProviders[providerIndex].transformer = { use: [] }; } if (!newProviders[providerIndex].transformer![model]) { newProviders[providerIndex].transformer![model] = { use: [] }; } // Add parameter to the specified transformer in use array if (newProviders[providerIndex].transformer![model].use && newProviders[providerIndex].transformer![model].use.length > transformerIndex) { const targetTransformer = newProviders[providerIndex].transformer![model].use[transformerIndex]; // If it's already an array with parameters, update it if (Array.isArray(targetTransformer)) { const transformerArray = [...targetTransformer]; // Check if the second element is an object (parameters object) if (transformerArray.length > 1 && typeof transformerArray[1] === 'object' && transformerArray[1] !== null) { // Update the existing parameters object const existingParams = transformerArray[1] as Record; const paramsObj: Record = { ...existingParams, [paramName]: paramValue }; transformerArray[1] = paramsObj; } else if (transformerArray.length > 1) { // If there are other elements, add the parameters object const paramsObj = { [paramName]: paramValue }; transformerArray.splice(1, transformerArray.length - 1, paramsObj); } else { // Add a new parameters object const paramsObj = { [paramName]: paramValue }; transformerArray.push(paramsObj); } newProviders[providerIndex].transformer![model].use[transformerIndex] = transformerArray as string | (string | Record | { max_tokens: number })[]; } else { // Convert to array format with parameters const paramsObj = { [paramName]: paramValue }; newProviders[providerIndex].transformer![model].use[transformerIndex] = [targetTransformer as string, paramsObj]; } } setConfig({ ...config, Providers: newProviders }); }; const removeModelTransformerParameterAtIndex = (providerIndex: number, model: string, transformerIndex: number, paramName: string) => { const newProviders = [...config.Providers]; if (!newProviders[providerIndex].transformer?.[model]?.use || newProviders[providerIndex].transformer[model].use.length <= transformerIndex) { return; } const targetTransformer = newProviders[providerIndex].transformer[model].use[transformerIndex]; if (Array.isArray(targetTransformer) && targetTransformer.length > 1) { const transformerArray = [...targetTransformer]; // Check if the second element is an object (parameters object) if (typeof transformerArray[1] === 'object' && transformerArray[1] !== null) { const paramsObj = { ...(transformerArray[1] as Record) }; delete paramsObj[paramName]; // If the parameters object is now empty, remove it if (Object.keys(paramsObj).length === 0) { transformerArray.splice(1, 1); } else { transformerArray[1] = paramsObj; } newProviders[providerIndex].transformer![model].use[transformerIndex] = transformerArray; setConfig({ ...config, Providers: newProviders }); } } }; const handleAddModel = (index: number, model: string) => { if (!model.trim()) return; // Handle case where config.Providers might be null or undefined if (!config || !Array.isArray(config.Providers)) return; // Handle case where the provider at the given index might be null or undefined if (!config.Providers[index]) return; const newProviders = [...config.Providers]; // Handle case where provider.models might be null or undefined const models = Array.isArray(newProviders[index].models) ? [...newProviders[index].models] : []; // Check if model already exists if (!models.includes(model.trim())) { models.push(model.trim()); newProviders[index].models = models; setConfig({ ...config, Providers: newProviders }); } }; const handleRemoveModel = (providerIndex: number, modelIndex: number) => { // Handle case where config.Providers might be null or undefined if (!config || !Array.isArray(config.Providers)) return; // Handle case where the provider at the given index might be null or undefined if (!config.Providers[providerIndex]) return; const newProviders = [...config.Providers]; // Handle case where provider.models might be null or undefined const models = Array.isArray(newProviders[providerIndex].models) ? [...newProviders[providerIndex].models] : []; // Handle case where modelIndex might be out of bounds if (modelIndex >= 0 && modelIndex < models.length) { models.splice(modelIndex, 1); newProviders[providerIndex].models = models; setConfig({ ...config, Providers: newProviders }); } }; const editingProvider = editingProviderIndex !== null ? validProviders[editingProviderIndex] : null; return ( {t("providers.title")} ({validProviders.length}) {/* Edit Dialog */} { if (!open) { handleCancelAddProvider(); } }}> {t("providers.edit")} {editingProvider && editingProviderIndex !== null && (
handleProviderChange(editingProviderIndex, 'name', e.target.value)} />
handleProviderChange(editingProviderIndex, 'api_base_url', e.target.value)} />
handleProviderChange(editingProviderIndex, 'api_key', e.target.value)} />
{hasFetchedModels[editingProviderIndex] ? ( ({ label: model, value: model }))} value="" onChange={() => { // 只更新输入值,不添加模型 }} onEnter={(value) => { if (editingProviderIndex !== null) { handleAddModel(editingProviderIndex, value); } }} inputPlaceholder={t("providers.models_placeholder")} /> ) : ( { if (e.key === 'Enter' && e.currentTarget.value.trim() && editingProviderIndex !== null) { handleAddModel(editingProviderIndex, e.currentTarget.value); e.currentTarget.value = ''; } }} /> )}
{/* */}
{(editingProvider.models || []).map((model, modelIndex) => ( {model} ))}
{/* Provider Transformer Selection */}
{/* Add new transformer */}
({ label: t.name, value: t.name }))} value="" onChange={(value) => { if (editingProviderIndex !== null) { handleProviderTransformerChange(editingProviderIndex, value); } }} placeholder={t("providers.select_transformer")} emptyPlaceholder={t("providers.no_transformers")} />
{/* Display existing transformers */} {editingProvider.transformer?.use && editingProvider.transformer.use.length > 0 && (
{t("providers.selected_transformers")}
{editingProvider.transformer.use.map((transformer: string | (string | Record | { max_tokens: number })[], transformerIndex: number) => (
{typeof transformer === 'string' ? transformer : Array.isArray(transformer) ? String(transformer[0]) : String(transformer)}
{/* Transformer-specific Parameters */}
{ const key = `provider-${editingProviderIndex}-transformer-${transformerIndex}`; setProviderParamInputs(prev => ({ ...prev, [key]: { ...prev[key] || {name: "", value: ""}, name: e.target.value } })); }} /> { const key = `provider-${editingProviderIndex}-transformer-${transformerIndex}`; setProviderParamInputs(prev => ({ ...prev, [key]: { ...prev[key] || {name: "", value: ""}, value: e.target.value } })); }} />
{/* Display existing parameters for this transformer */} {(() => { // Get parameters for this specific transformer if (!editingProvider.transformer?.use || editingProvider.transformer.use.length <= transformerIndex) { return null; } const targetTransformer = editingProvider.transformer.use[transformerIndex]; let params = {}; if (Array.isArray(targetTransformer) && targetTransformer.length > 1) { // Check if the second element is an object (parameters object) if (typeof targetTransformer[1] === 'object' && targetTransformer[1] !== null) { params = targetTransformer[1] as Record; } } return Object.keys(params).length > 0 ? (
{Object.entries(params).map(([key, value]) => (
{key}: {String(value)}
))}
) : null; })()}
))}
)}
{/* Model-specific Transformers */} {editingProvider.models && editingProvider.models.length > 0 && (
{(editingProvider.models || []).map((model, modelIndex) => (
{model}
{/* Add new transformer */}
({ label: t.name, value: t.name }))} value="" onChange={(value) => { if (editingProviderIndex !== null) { handleModelTransformerChange(editingProviderIndex, model, value); } }} placeholder={t("providers.select_transformer")} emptyPlaceholder={t("providers.no_transformers")} />
{/* Display existing transformers */} {editingProvider.transformer?.[model]?.use && editingProvider.transformer[model].use.length > 0 && (
{t("providers.selected_transformers")}
{editingProvider.transformer[model].use.map((transformer: string | (string | Record | { max_tokens: number })[], transformerIndex: number) => (
{typeof transformer === 'string' ? transformer : Array.isArray(transformer) ? String(transformer[0]) : String(transformer)}
{/* Transformer-specific Parameters */}
{ const key = `model-${editingProviderIndex}-${model}-transformer-${transformerIndex}`; setModelParamInputs(prev => ({ ...prev, [key]: { ...prev[key] || {name: "", value: ""}, name: e.target.value } })); }} /> { const key = `model-${editingProviderIndex}-${model}-transformer-${transformerIndex}`; setModelParamInputs(prev => ({ ...prev, [key]: { ...prev[key] || {name: "", value: ""}, value: e.target.value } })); }} />
{/* Display existing parameters for this transformer */} {(() => { // Get parameters for this specific transformer if (!editingProvider.transformer?.[model]?.use || editingProvider.transformer[model].use.length <= transformerIndex) { return null; } const targetTransformer = editingProvider.transformer[model].use[transformerIndex]; let params = {}; if (Array.isArray(targetTransformer) && targetTransformer.length > 1) { // Check if the second element is an object (parameters object) if (typeof targetTransformer[1] === 'object' && targetTransformer[1] !== null) { params = targetTransformer[1] as Record; } } return Object.keys(params).length > 0 ? (
{Object.entries(params).map(([key, value]) => (
{key}: {String(value)}
))}
) : null; })()}
))}
)}
))}
)}
)}
{/* */}
{/* Delete Confirmation Dialog */} setDeletingProviderIndex(null)}> {t("providers.delete")} {t("providers.delete_provider_confirm")}
); }