mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-26 19:24:27 +00:00
feat(ui/): allow admin to reuse existing model credentials
Prevents need to go to backend llm provider for getting credentials
This commit is contained in:
parent
b75cd3b887
commit
70c44741f3
4 changed files with 598 additions and 159 deletions
|
@ -3,132 +3,392 @@ import { Form, Select } from "antd";
|
||||||
import { TextInput, Text } from "@tremor/react";
|
import { TextInput, Text } from "@tremor/react";
|
||||||
import { Row, Col, Typography, Button as Button2, Upload, UploadProps } from "antd";
|
import { Row, Col, Typography, Button as Button2, Upload, UploadProps } from "antd";
|
||||||
import { UploadOutlined } from "@ant-design/icons";
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
import { Providers } from "../provider_info_helpers";
|
import { provider_map, Providers } from "../provider_info_helpers";
|
||||||
|
import { CredentialItem } from "../networking";
|
||||||
const { Link } = Typography;
|
const { Link } = Typography;
|
||||||
|
|
||||||
|
|
||||||
interface ProviderSpecificFieldsProps {
|
interface ProviderSpecificFieldsProps {
|
||||||
selectedProvider: Providers;
|
selectedProvider: Providers;
|
||||||
uploadProps?: UploadProps;
|
uploadProps?: UploadProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProviderCredentialField {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
tooltip?: string;
|
||||||
|
required?: boolean;
|
||||||
|
type?: "text" | "password" | "select" | "upload";
|
||||||
|
options?: string[];
|
||||||
|
defaultValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CredentialValues {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const createCredentialFromModel = (provider: string, modelData: any): CredentialItem => {
|
||||||
|
console.log("provider", provider);
|
||||||
|
console.log("modelData", modelData);
|
||||||
|
const enumKey = Object.keys(provider_map).find(
|
||||||
|
key => provider_map[key].toLowerCase() === provider.toLowerCase()
|
||||||
|
);
|
||||||
|
if (!enumKey) {
|
||||||
|
throw new Error(`Provider ${provider} not found in provider_map`);
|
||||||
|
}
|
||||||
|
const providerEnum = Providers[enumKey as keyof typeof Providers];
|
||||||
|
const providerFields = PROVIDER_CREDENTIAL_FIELDS[providerEnum] || [];
|
||||||
|
const credentialValues: object = {};
|
||||||
|
|
||||||
|
console.log("providerFields", providerFields);
|
||||||
|
|
||||||
|
// Go through each field defined for this provider
|
||||||
|
providerFields.forEach(field => {
|
||||||
|
const value = modelData.litellm_params[field.key];
|
||||||
|
console.log("field", field);
|
||||||
|
console.log("value", value);
|
||||||
|
if (value !== undefined) {
|
||||||
|
(credentialValues as Record<string, string>)[field.key] = value.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const credential: CredentialItem = {
|
||||||
|
credential_name: `${provider}-credential-${Math.floor(Math.random() * 1000000)}`,
|
||||||
|
credential_values: credentialValues,
|
||||||
|
credential_info: {
|
||||||
|
custom_llm_provider: provider,
|
||||||
|
description: `Credential for ${provider}. Created from model ${modelData.model_name}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return credential;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROVIDER_CREDENTIAL_FIELDS: Record<Providers, ProviderCredentialField[]> = {
|
||||||
|
[Providers.OpenAI]: [
|
||||||
|
{
|
||||||
|
key: "api_base",
|
||||||
|
label: "API Base",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
"https://api.openai.com/v1",
|
||||||
|
"https://eu.api.openai.com"
|
||||||
|
],
|
||||||
|
defaultValue: "https://api.openai.com/v1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "organization",
|
||||||
|
label: "OpenAI Organization ID",
|
||||||
|
placeholder: "[OPTIONAL] my-unique-org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "api_key",
|
||||||
|
label: "OpenAI API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.OpenAI_Text]: [
|
||||||
|
{
|
||||||
|
key: "api_base",
|
||||||
|
label: "API Base",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
"https://api.openai.com/v1",
|
||||||
|
"https://eu.api.openai.com"
|
||||||
|
],
|
||||||
|
defaultValue: "https://api.openai.com/v1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "organization",
|
||||||
|
label: "OpenAI Organization ID",
|
||||||
|
placeholder: "[OPTIONAL] my-unique-org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "api_key",
|
||||||
|
label: "OpenAI API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.Vertex_AI]: [
|
||||||
|
{
|
||||||
|
key: "vertex_project",
|
||||||
|
label: "Vertex Project",
|
||||||
|
placeholder: "adroit-cadet-1234..",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "vertex_location",
|
||||||
|
label: "Vertex Location",
|
||||||
|
placeholder: "us-east-1",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "vertex_credentials",
|
||||||
|
label: "Vertex Credentials",
|
||||||
|
required: true,
|
||||||
|
type: "upload"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.AssemblyAI]: [
|
||||||
|
{
|
||||||
|
key: "api_base",
|
||||||
|
label: "API Base",
|
||||||
|
type: "select",
|
||||||
|
required: true,
|
||||||
|
options: [
|
||||||
|
"https://api.assemblyai.com",
|
||||||
|
"https://api.eu.assemblyai.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "api_key",
|
||||||
|
label: "AssemblyAI API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.Azure]: [
|
||||||
|
{
|
||||||
|
key: "api_base",
|
||||||
|
label: "API Base",
|
||||||
|
placeholder: "https://...",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "api_version",
|
||||||
|
label: "API Version",
|
||||||
|
placeholder: "2023-07-01-preview",
|
||||||
|
tooltip: "By default litellm will use the latest version. If you want to use a different version, you can specify it here"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "base_model",
|
||||||
|
label: "Base Model",
|
||||||
|
placeholder: "azure/gpt-3.5-turbo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "api_key",
|
||||||
|
label: "Azure API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.Azure_AI_Studio]: [
|
||||||
|
{
|
||||||
|
key: "api_base",
|
||||||
|
label: "API Base",
|
||||||
|
placeholder: "https://...",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "api_key",
|
||||||
|
label: "Azure API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.OpenAI_Compatible]: [
|
||||||
|
{
|
||||||
|
key: "api_base",
|
||||||
|
label: "API Base",
|
||||||
|
placeholder: "https://...",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "api_key",
|
||||||
|
label: "OpenAI API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.OpenAI_Text_Compatible]: [
|
||||||
|
{
|
||||||
|
key: "api_base",
|
||||||
|
label: "API Base",
|
||||||
|
placeholder: "https://...",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "api_key",
|
||||||
|
label: "OpenAI API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.Bedrock]: [
|
||||||
|
{
|
||||||
|
key: "aws_access_key_id",
|
||||||
|
label: "AWS Access Key ID",
|
||||||
|
required: true,
|
||||||
|
tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "aws_secret_access_key",
|
||||||
|
label: "AWS Secret Access Key",
|
||||||
|
required: true,
|
||||||
|
tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "aws_region_name",
|
||||||
|
label: "AWS Region Name",
|
||||||
|
placeholder: "us-east-1",
|
||||||
|
required: true,
|
||||||
|
tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[Providers.Ollama]: [], // No specific fields needed
|
||||||
|
[Providers.Anthropic]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
placeholder: "sk-",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Google_AI_Studio]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
placeholder: "aig-",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Groq]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.MistralAI]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Deepseek]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Cohere]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Databricks]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.xAI]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Cerebras]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Sambanova]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Perplexity]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.TogetherAI]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.Openrouter]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
[Providers.FireworksAI]: [{
|
||||||
|
key: "api_key",
|
||||||
|
label: "API Key",
|
||||||
|
type: "password",
|
||||||
|
required: true
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
|
const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
|
||||||
selectedProvider,
|
selectedProvider,
|
||||||
uploadProps
|
uploadProps
|
||||||
}) => {
|
}) => {
|
||||||
console.log(`Selected provider: ${selectedProvider}`);
|
|
||||||
console.log(`type of selectedProvider: ${typeof selectedProvider}`);
|
|
||||||
// cast selectedProvider to Providers
|
|
||||||
const selectedProviderEnum = Providers[selectedProvider as keyof typeof Providers] as Providers;
|
const selectedProviderEnum = Providers[selectedProvider as keyof typeof Providers] as Providers;
|
||||||
console.log(`selectedProviderEnum: ${selectedProviderEnum}`);
|
|
||||||
console.log(`type of selectedProviderEnum: ${typeof selectedProviderEnum}`);
|
// Simply use the fields as defined in PROVIDER_CREDENTIAL_FIELDS
|
||||||
|
const allFields = React.useMemo(() => {
|
||||||
|
return PROVIDER_CREDENTIAL_FIELDS[selectedProviderEnum] || [];
|
||||||
|
}, [selectedProviderEnum]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{selectedProviderEnum === Providers.OpenAI || selectedProviderEnum === Providers.OpenAI_Text && (
|
{allFields.map((field) => (
|
||||||
<>
|
<React.Fragment key={field.key}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="API Base"
|
label={field.label}
|
||||||
name="api_base"
|
name={field.key}
|
||||||
|
rules={field.required ? [{ required: true, message: "Required" }] : undefined}
|
||||||
|
tooltip={field.tooltip}
|
||||||
|
className={field.key === "vertex_credentials" ? "mb-0" : undefined}
|
||||||
>
|
>
|
||||||
<Select placeholder="Select API Base" defaultValue="https://api.openai.com/v1">
|
{field.type === "select" ? (
|
||||||
<Select.Option value="https://api.openai.com/v1">https://api.openai.com/v1</Select.Option>
|
<Select
|
||||||
<Select.Option value="https://eu.api.openai.com">https://eu.api.openai.com</Select.Option>
|
placeholder={field.placeholder}
|
||||||
</Select>
|
defaultValue={field.defaultValue}
|
||||||
</Form.Item>
|
>
|
||||||
|
{field.options?.map((option) => (
|
||||||
<Form.Item label="OpenAI Organization ID" name="organization">
|
<Select.Option key={option} value={option}>
|
||||||
<TextInput placeholder="[OPTIONAL] my-unique-org" />
|
{option}
|
||||||
</Form.Item>
|
</Select.Option>
|
||||||
</>
|
))}
|
||||||
)}
|
</Select>
|
||||||
|
) : field.type === "upload" ? (
|
||||||
{selectedProviderEnum === Providers.Vertex_AI && (
|
<Upload {...uploadProps}>
|
||||||
<>
|
<Button2 icon={<UploadOutlined />}>Click to Upload</Button2>
|
||||||
<Form.Item
|
</Upload>
|
||||||
rules={[{ required: true, message: "Required" }]}
|
) : (
|
||||||
label="Vertex Project"
|
<TextInput
|
||||||
name="vertex_project"
|
placeholder={field.placeholder}
|
||||||
>
|
type={field.type === "password" ? "password" : "text"}
|
||||||
<TextInput placeholder="adroit-cadet-1234.." />
|
/>
|
||||||
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
{/* Special case for Vertex Credentials help text */}
|
||||||
rules={[{ required: true, message: "Required" }]}
|
{field.key === "vertex_credentials" && (
|
||||||
label="Vertex Location"
|
<Row>
|
||||||
name="vertex_location"
|
<Col span={10}></Col>
|
||||||
>
|
<Col span={10}>
|
||||||
<TextInput placeholder="us-east-1" />
|
<Text className="mb-3 mt-1">
|
||||||
</Form.Item>
|
Give litellm a gcp service account(.json file), so it
|
||||||
|
can make the relevant calls
|
||||||
|
</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
|
||||||
<Form.Item
|
{/* Special case for Azure Base Model help text */}
|
||||||
rules={[{ required: true, message: "Required" }]}
|
{field.key === "base_model" && (
|
||||||
label="Vertex Credentials"
|
|
||||||
name="vertex_credentials"
|
|
||||||
className="mb-0"
|
|
||||||
>
|
|
||||||
<Upload {...uploadProps}>
|
|
||||||
<Button2 icon={<UploadOutlined />}>
|
|
||||||
Click to Upload
|
|
||||||
</Button2>
|
|
||||||
</Upload>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Row>
|
|
||||||
<Col span={10}></Col>
|
|
||||||
<Col span={10}>
|
|
||||||
<Text className="mb-3 mt-1">
|
|
||||||
Give litellm a gcp service account(.json file), so it
|
|
||||||
can make the relevant calls
|
|
||||||
</Text>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedProviderEnum === Providers.AssemblyAI && (
|
|
||||||
<Form.Item
|
|
||||||
rules={[{ required: true, message: "Required" }]}
|
|
||||||
label="API Base"
|
|
||||||
name="api_base"
|
|
||||||
>
|
|
||||||
<Select placeholder="Select API Base">
|
|
||||||
<Select.Option value="https://api.assemblyai.com">https://api.assemblyai.com</Select.Option>
|
|
||||||
<Select.Option value="https://api.eu.assemblyai.com">https://api.eu.assemblyai.com</Select.Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(selectedProviderEnum === Providers.Azure ||
|
|
||||||
selectedProviderEnum === Providers.Azure_AI_Studio ||
|
|
||||||
selectedProviderEnum === Providers.OpenAI_Compatible ||
|
|
||||||
selectedProviderEnum === Providers.OpenAI_Text_Compatible
|
|
||||||
) && (
|
|
||||||
<Form.Item
|
|
||||||
rules={[{ required: true, message: "Required" }]}
|
|
||||||
label="API Base"
|
|
||||||
name="api_base"
|
|
||||||
>
|
|
||||||
<TextInput placeholder="https://..." />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedProviderEnum === Providers.Azure && (
|
|
||||||
<>
|
|
||||||
<Form.Item
|
|
||||||
label="API Version"
|
|
||||||
name="api_version"
|
|
||||||
tooltip="By default litellm will use the latest version. If you want to use a different version, you can specify it here"
|
|
||||||
>
|
|
||||||
<TextInput placeholder="2023-07-01-preview" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Form.Item
|
|
||||||
label="Base Model"
|
|
||||||
name="base_model"
|
|
||||||
className="mb-0"
|
|
||||||
>
|
|
||||||
<TextInput placeholder="azure/gpt-3.5-turbo" />
|
|
||||||
</Form.Item>
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={10}></Col>
|
<Col span={10}></Col>
|
||||||
<Col span={10}>
|
<Col span={10}>
|
||||||
|
@ -144,54 +404,9 @@ const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
|
||||||
</Text>
|
</Text>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
)}
|
||||||
</>
|
</React.Fragment>
|
||||||
)}
|
))}
|
||||||
|
|
||||||
{selectedProviderEnum === Providers.Bedrock && (
|
|
||||||
<>
|
|
||||||
<Form.Item
|
|
||||||
rules={[{ required: true, message: "Required" }]}
|
|
||||||
label="AWS Access Key ID"
|
|
||||||
name="aws_access_key_id"
|
|
||||||
tooltip="You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
|
||||||
>
|
|
||||||
<TextInput placeholder="" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
rules={[{ required: true, message: "Required" }]}
|
|
||||||
label="AWS Secret Access Key"
|
|
||||||
name="aws_secret_access_key"
|
|
||||||
tooltip="You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
|
||||||
>
|
|
||||||
<TextInput placeholder="" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
rules={[{ required: true, message: "Required" }]}
|
|
||||||
label="AWS Region Name"
|
|
||||||
name="aws_region_name"
|
|
||||||
tooltip="You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
|
||||||
>
|
|
||||||
<TextInput placeholder="us-east-1" />
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedProviderEnum != Providers.Bedrock &&
|
|
||||||
selectedProviderEnum != Providers.Vertex_AI &&
|
|
||||||
selectedProviderEnum != Providers.Ollama &&
|
|
||||||
(
|
|
||||||
<Form.Item
|
|
||||||
rules={[{ required: true, message: "Required" }]}
|
|
||||||
label="API Key"
|
|
||||||
name="api_key"
|
|
||||||
tooltip="LLM API Credentials"
|
|
||||||
>
|
|
||||||
<TextInput placeholder="sk-" type="password" />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Form,
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
Select as AntdSelect,
|
||||||
|
Input,
|
||||||
|
Switch,
|
||||||
|
Modal
|
||||||
|
} from "antd";
|
||||||
|
import type { UploadProps } from "antd/es/upload";
|
||||||
|
import { Providers, providerLogoMap } from "../provider_info_helpers";
|
||||||
|
import type { FormInstance } from "antd";
|
||||||
|
import ProviderSpecificFields from "../add_model/provider_specific_fields";
|
||||||
|
import { TextInput } from "@tremor/react";
|
||||||
|
import { CredentialItem } from "../networking";
|
||||||
|
const { Title, Link } = Typography;
|
||||||
|
|
||||||
|
interface ReuseCredentialsModalProps {
|
||||||
|
isVisible: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onAddCredential: (values: any) => void;
|
||||||
|
existingCredential: CredentialItem | null;
|
||||||
|
setIsCredentialModalOpen: (isVisible: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReuseCredentialsModal: React.FC<ReuseCredentialsModalProps> = ({
|
||||||
|
isVisible,
|
||||||
|
onCancel,
|
||||||
|
onAddCredential,
|
||||||
|
existingCredential,
|
||||||
|
setIsCredentialModalOpen
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
console.log(`existingCredential in add credentials tab: ${JSON.stringify(existingCredential)}`);
|
||||||
|
|
||||||
|
const handleSubmit = (values: any) => {
|
||||||
|
onAddCredential(values);
|
||||||
|
form.resetFields();
|
||||||
|
setIsCredentialModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Reuse Credentials"
|
||||||
|
visible={isVisible}
|
||||||
|
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" }]}
|
||||||
|
initialValue={existingCredential?.credential_name}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Enter a friendly name for these credentials"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{/* Display Credential Values of existingCredential, don't allow user to edit. Credential values is a dictionary */}
|
||||||
|
{Object.entries(existingCredential?.credential_values || {}).map(([key, value]) => (
|
||||||
|
<Form.Item
|
||||||
|
key={key}
|
||||||
|
label={key}
|
||||||
|
name={key}
|
||||||
|
initialValue={value}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
placeholder={`Enter ${key}`}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 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();
|
||||||
|
}}
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
htmlType="submit"
|
||||||
|
>
|
||||||
|
Reuse Credentials
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReuseCredentialsModal;
|
|
@ -14,13 +14,15 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
} from "@tremor/react";
|
} from "@tremor/react";
|
||||||
import { ArrowLeftIcon, TrashIcon } from "@heroicons/react/outline";
|
import { ArrowLeftIcon, TrashIcon, KeyIcon } from "@heroicons/react/outline";
|
||||||
import { modelDeleteCall, modelUpdateCall } from "./networking";
|
import { modelDeleteCall, modelUpdateCall, CredentialItem, credentialGetCall, credentialCreateCall } from "./networking";
|
||||||
import { Button, Form, Input, InputNumber, message, Select } from "antd";
|
import { Button, Form, Input, InputNumber, message, Select, Modal } from "antd";
|
||||||
import EditModelModal from "./edit_model/edit_model_modal";
|
import EditModelModal from "./edit_model/edit_model_modal";
|
||||||
import { handleEditModelSubmit } from "./edit_model/edit_model_modal";
|
import { handleEditModelSubmit } from "./edit_model/edit_model_modal";
|
||||||
import { getProviderLogoAndName } from "./provider_info_helpers";
|
import { getProviderLogoAndName } from "./provider_info_helpers";
|
||||||
import { getDisplayModelName } from "./view_model/model_name_display";
|
import { getDisplayModelName } from "./view_model/model_name_display";
|
||||||
|
import AddCredentialsModal from "./model_add/add_credentials_tab";
|
||||||
|
import ReuseCredentialsModal from "./model_add/reuse_credentials";
|
||||||
|
|
||||||
interface ModelInfoViewProps {
|
interface ModelInfoViewProps {
|
||||||
modelId: string;
|
modelId: string;
|
||||||
|
@ -48,11 +50,51 @@ export default function ModelInfoView({
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [localModelData, setLocalModelData] = useState(modelData);
|
const [localModelData, setLocalModelData] = useState(modelData);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [isCredentialModalOpen, setIsCredentialModalOpen] = useState(false);
|
||||||
const [isDirty, setIsDirty] = useState(false);
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [existingCredential, setExistingCredential] = useState<CredentialItem | null>(null);
|
||||||
|
|
||||||
const canEditModel = userRole === "Admin";
|
const canEditModel = userRole === "Admin";
|
||||||
|
const isAdmin = userRole === "Admin";
|
||||||
|
|
||||||
|
const usingExistingCredential = modelData.litellm_params?.litellm_credential_name != null && modelData.litellm_params?.litellm_credential_name != undefined;
|
||||||
|
console.log("usingExistingCredential, ", usingExistingCredential);
|
||||||
|
console.log("modelData.litellm_params.litellm_credential_name, ", modelData.litellm_params.litellm_credential_name);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getExistingCredential = async () => {
|
||||||
|
console.log("accessToken, ", accessToken);
|
||||||
|
if (!accessToken) return;
|
||||||
|
if (usingExistingCredential) return;
|
||||||
|
let existingCredentialResponse = await credentialGetCall(accessToken, null, modelId);
|
||||||
|
console.log("existingCredentialResponse, ", existingCredentialResponse);
|
||||||
|
setExistingCredential({
|
||||||
|
credential_name: existingCredentialResponse["credential_name"],
|
||||||
|
credential_values: existingCredentialResponse["credential_values"],
|
||||||
|
credential_info: existingCredentialResponse["credential_info"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getExistingCredential();
|
||||||
|
}, [accessToken, modelId]);
|
||||||
|
|
||||||
|
const handleReuseCredential = async (values: any) => {
|
||||||
|
console.log("values, ", values);
|
||||||
|
if (!accessToken) return;
|
||||||
|
let credentialItem = {
|
||||||
|
credential_name: values.credential_name,
|
||||||
|
model_id: modelId,
|
||||||
|
credential_info: {
|
||||||
|
"custom_llm_provider": localModelData.litellm_params?.custom_llm_provider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.info("Storing credential..");
|
||||||
|
let credentialResponse = await credentialCreateCall(accessToken, credentialItem);
|
||||||
|
console.log("credentialResponse, ", credentialResponse);
|
||||||
|
message.success("Credential stored successfully");
|
||||||
|
}
|
||||||
|
|
||||||
const handleModelUpdate = async (values: any) => {
|
const handleModelUpdate = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
|
@ -143,8 +185,16 @@ export default function ModelInfoView({
|
||||||
<Title>Public Model Name: {getDisplayModelName(modelData)}</Title>
|
<Title>Public Model Name: {getDisplayModelName(modelData)}</Title>
|
||||||
<Text className="text-gray-500 font-mono">{modelData.model_info.id}</Text>
|
<Text className="text-gray-500 font-mono">{modelData.model_info.id}</Text>
|
||||||
</div>
|
</div>
|
||||||
{canEditModel && (
|
{isAdmin && (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
<TremorButton
|
||||||
|
icon={KeyIcon}
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => setIsCredentialModalOpen(true)}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
Re-use Credentials
|
||||||
|
</TremorButton>
|
||||||
<TremorButton
|
<TremorButton
|
||||||
icon={TrashIcon}
|
icon={TrashIcon}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
@ -507,6 +557,25 @@ export default function ModelInfoView({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isCredentialModalOpen &&
|
||||||
|
!usingExistingCredential ? (
|
||||||
|
<ReuseCredentialsModal
|
||||||
|
isVisible={isCredentialModalOpen}
|
||||||
|
onCancel={() => setIsCredentialModalOpen(false)}
|
||||||
|
onAddCredential={handleReuseCredential}
|
||||||
|
existingCredential={existingCredential}
|
||||||
|
setIsCredentialModalOpen={setIsCredentialModalOpen}
|
||||||
|
/>
|
||||||
|
): (
|
||||||
|
<Modal
|
||||||
|
open={isCredentialModalOpen}
|
||||||
|
onCancel={() => setIsCredentialModalOpen(false)}
|
||||||
|
title="Using Existing Credential"
|
||||||
|
>
|
||||||
|
<Text>{modelData.litellm_params.litellm_credential_name}</Text>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -2652,6 +2652,42 @@ export const credentialListCall = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const credentialGetCall = async (accessToken: String, credentialName: String | null, modelId: String | null) => {
|
||||||
|
try {
|
||||||
|
let url = proxyBaseUrl ? `${proxyBaseUrl}/credentials` : `/credentials`;
|
||||||
|
|
||||||
|
if (credentialName) {
|
||||||
|
url += `/by_name/${credentialName}`;
|
||||||
|
} else if (modelId) {
|
||||||
|
url += `/by_model/${modelId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("in credentialListCall");
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.text();
|
||||||
|
handleError(errorData);
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("/credentials API Response:", data);
|
||||||
|
return data;
|
||||||
|
// Handle success - you might want to update some state or UI based on the created key
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create key:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const credentialDeleteCall = async (accessToken: String, credentialName: String) => {
|
export const credentialDeleteCall = async (accessToken: String, credentialName: String) => {
|
||||||
try {
|
try {
|
||||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/credentials/${credentialName}` : `/credentials/${credentialName}`;
|
const url = proxyBaseUrl ? `${proxyBaseUrl}/credentials/${credentialName}` : `/credentials/${credentialName}`;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue