Spaces:
Paused
Paused
| import React, { useState } from "react"; | |
| import { Card, Form, Button, Tooltip, Typography, Select as AntdSelect, Modal } from "antd"; | |
| import type { FormInstance } from "antd"; | |
| import type { UploadProps } from "antd/es/upload"; | |
| import LiteLLMModelNameField from "./litellm_model_name"; | |
| import ConditionalPublicModelName from "./conditional_public_model_name"; | |
| import ProviderSpecificFields from "./provider_specific_fields"; | |
| import AdvancedSettings from "./advanced_settings"; | |
| import { Providers, providerLogoMap, getPlaceholder } from "../provider_info_helpers"; | |
| import type { Team } from "../key_team_helpers/key_list"; | |
| import { CredentialItem } from "../networking"; | |
| import ConnectionErrorDisplay from "./model_connection_test"; | |
| import { TEST_MODES } from "./add_model_modes"; | |
| import { Row, Col } from "antd"; | |
| import { Text, TextInput } from "@tremor/react"; | |
| import TeamDropdown from "../common_components/team_dropdown"; | |
| import { all_admin_roles } from "@/utils/roles"; | |
| interface AddModelTabProps { | |
| form: FormInstance; | |
| handleOk: () => void; | |
| selectedProvider: Providers; | |
| setSelectedProvider: (provider: Providers) => void; | |
| providerModels: string[]; | |
| setProviderModelsFn: (provider: Providers) => void; | |
| getPlaceholder: (provider: Providers) => string; | |
| uploadProps: UploadProps; | |
| showAdvancedSettings: boolean; | |
| setShowAdvancedSettings: (show: boolean) => void; | |
| teams: Team[] | null; | |
| credentials: CredentialItem[]; | |
| accessToken: string; | |
| userRole: string; | |
| } | |
| const { Title, Link } = Typography; | |
| const AddModelTab: React.FC<AddModelTabProps> = ({ | |
| form, | |
| handleOk, | |
| selectedProvider, | |
| setSelectedProvider, | |
| providerModels, | |
| setProviderModelsFn, | |
| getPlaceholder, | |
| uploadProps, | |
| showAdvancedSettings, | |
| setShowAdvancedSettings, | |
| teams, | |
| credentials, | |
| accessToken, | |
| userRole, | |
| }) => { | |
| // State for test mode and connection testing | |
| const [testMode, setTestMode] = useState<string>("chat"); | |
| const [isResultModalVisible, setIsResultModalVisible] = useState<boolean>(false); | |
| const [isTestingConnection, setIsTestingConnection] = useState<boolean>(false); | |
| // Using a unique ID to force the ConnectionErrorDisplay to remount and run a fresh test | |
| const [connectionTestId, setConnectionTestId] = useState<string>(""); | |
| // Test connection when button is clicked | |
| const handleTestConnection = async () => { | |
| setIsTestingConnection(true); | |
| // Generate a new test ID (using timestamp for uniqueness) | |
| // This forces React to create a new instance of ConnectionErrorDisplay | |
| setConnectionTestId(`test-${Date.now()}`); | |
| // Show the modal with the fresh test | |
| setIsResultModalVisible(true); | |
| }; | |
| const isAdmin = all_admin_roles.includes(userRole); | |
| return ( | |
| <> | |
| <Title level={2}>Add new model</Title> | |
| <Card> | |
| <Form | |
| form={form} | |
| onFinish={handleOk} | |
| labelCol={{ span: 10 }} | |
| wrapperCol={{ span: 16 }} | |
| labelAlign="left" | |
| > | |
| <> | |
| {/* Provider Selection */} | |
| <Form.Item | |
| rules={[{ required: true, message: "Required" }]} | |
| label="Provider:" | |
| name="custom_llm_provider" | |
| tooltip="E.g. OpenAI, Azure OpenAI, Anthropic, Bedrock, etc." | |
| labelCol={{ span: 10 }} | |
| labelAlign="left" | |
| > | |
| <AntdSelect | |
| showSearch={true} | |
| value={selectedProvider} | |
| onChange={(value) => { | |
| setSelectedProvider(value); | |
| setProviderModelsFn(value); | |
| form.setFieldsValue({ | |
| model: [], | |
| model_name: undefined | |
| }); | |
| }} | |
| > | |
| {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) => { | |
| // Create a div with provider initial as fallback | |
| 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> | |
| <LiteLLMModelNameField | |
| selectedProvider={selectedProvider} | |
| providerModels={providerModels} | |
| getPlaceholder={getPlaceholder} | |
| /> | |
| {/* Conditionally Render "Public Model Name" */} | |
| <ConditionalPublicModelName /> | |
| {/* Select Mode */} | |
| <Form.Item | |
| label="Mode" | |
| name="mode" | |
| className="mb-1" | |
| > | |
| <AntdSelect | |
| style={{ width: '100%' }} | |
| value={testMode} | |
| onChange={(value) => setTestMode(value)} | |
| options={TEST_MODES} | |
| /> | |
| </Form.Item> | |
| <Row> | |
| <Col span={10}></Col> | |
| <Col span={10}> | |
| <Text className="mb-5 mt-1"> | |
| <strong>Optional</strong> - LiteLLM endpoint to use when health checking this model <Link href="https://docs.litellm.ai/docs/proxy/health#health" target="_blank">Learn more</Link> | |
| </Text> | |
| </Col> | |
| </Row> | |
| {/* Credentials */} | |
| <div className="mb-4"> | |
| <Typography.Text className="text-sm text-gray-500 mb-2"> | |
| Either select existing credentials OR enter new provider credentials below | |
| </Typography.Text> | |
| </div> | |
| <Form.Item | |
| label="Existing Credentials" | |
| name="litellm_credential_name" | |
| > | |
| <AntdSelect | |
| showSearch | |
| placeholder="Select or search for existing credentials" | |
| optionFilterProp="children" | |
| filterOption={(input, option) => | |
| (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) | |
| } | |
| options={[ | |
| { value: null, label: 'None' }, | |
| ...credentials.map((credential) => ({ | |
| value: credential.credential_name, | |
| label: credential.credential_name | |
| })) | |
| ]} | |
| allowClear | |
| /> | |
| </Form.Item> | |
| <div className="flex items-center my-4"> | |
| <div className="flex-grow border-t border-gray-200"></div> | |
| <span className="px-4 text-gray-500 text-sm">OR</span> | |
| <div className="flex-grow border-t border-gray-200"></div> | |
| </div> | |
| <Form.Item | |
| noStyle | |
| shouldUpdate={(prevValues, currentValues) => | |
| prevValues.litellm_credential_name !== currentValues.litellm_credential_name || | |
| prevValues.provider !== currentValues.provider | |
| } | |
| > | |
| {({ getFieldValue }) => { | |
| const credentialName = getFieldValue('litellm_credential_name'); | |
| console.log("π Credential Name Changed:", credentialName); | |
| // Only show provider specific fields if no credentials selected | |
| if (!credentialName) { | |
| return ( | |
| <ProviderSpecificFields | |
| selectedProvider={selectedProvider} | |
| uploadProps={uploadProps} | |
| /> | |
| ); | |
| } | |
| return ( | |
| <div className="text-gray-500 text-sm text-center"> | |
| Using existing credentials - no additional provider fields needed | |
| </div> | |
| ); | |
| }} | |
| </Form.Item> | |
| <div className="flex items-center my-4"> | |
| <div className="flex-grow border-t border-gray-200"></div> | |
| <span className="px-4 text-gray-500 text-sm">Team Settings</span> | |
| <div className="flex-grow border-t border-gray-200"></div> | |
| </div> | |
| <Form.Item | |
| label="Team" | |
| name="team_id" | |
| className="mb-4" | |
| tooltip="Only keys for this team, will be able to call this model." | |
| rules={[ | |
| { | |
| required: !isAdmin, // Required if not admin | |
| message: 'Please select a team.' | |
| } | |
| ]} | |
| > | |
| <TeamDropdown teams={teams} /> | |
| </Form.Item> | |
| <AdvancedSettings | |
| showAdvancedSettings={showAdvancedSettings} | |
| setShowAdvancedSettings={setShowAdvancedSettings} | |
| teams={teams} | |
| /> | |
| <div className="flex justify-between items-center mb-4"> | |
| <Tooltip title="Get help on our github"> | |
| <Typography.Link href="https://github.com/BerriAI/litellm/issues"> | |
| Need Help? | |
| </Typography.Link> | |
| </Tooltip> | |
| <div className="space-x-2"> | |
| <Button onClick={handleTestConnection} loading={isTestingConnection}>Test Connect</Button> | |
| <Button htmlType="submit">Add Model</Button> | |
| </div> | |
| </div> | |
| </> | |
| </Form> | |
| </Card> | |
| {/* Test Connection Results Modal */} | |
| <Modal | |
| title="Connection Test Results" | |
| open={isResultModalVisible} | |
| onCancel={() => { | |
| setIsResultModalVisible(false); | |
| setIsTestingConnection(false); | |
| }} | |
| footer={[ | |
| <Button key="close" onClick={() => { | |
| setIsResultModalVisible(false); | |
| setIsTestingConnection(false); | |
| }}> | |
| Close | |
| </Button> | |
| ]} | |
| width={700} | |
| > | |
| {/* Only render the ConnectionErrorDisplay when modal is visible and we have a test ID */} | |
| {isResultModalVisible && ( | |
| <ConnectionErrorDisplay | |
| // The key prop tells React to create a fresh component instance when it changes | |
| key={connectionTestId} | |
| formValues={form.getFieldsValue()} | |
| accessToken={accessToken} | |
| testMode={testMode} | |
| modelName={form.getFieldValue('model_name') || form.getFieldValue('model')} | |
| onClose={() => { | |
| setIsResultModalVisible(false); | |
| setIsTestingConnection(false); | |
| }} | |
| onTestComplete={() => setIsTestingConnection(false)} | |
| /> | |
| )} | |
| </Modal> | |
| </> | |
| ); | |
| }; | |
| export default AddModelTab; |