mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-27 03:34:10 +00:00
feat(credentials/): working e2e flow for add / update models on LiteLLM UI
This commit is contained in:
parent
f7c033bc1b
commit
8fa56227d0
5 changed files with 132 additions and 27 deletions
|
@ -148,12 +148,13 @@ const AddModelTab: React.FC<AddModelTabProps> = ({
|
|||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.credential_name !== currentValues.credential_name ||
|
||||
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 (
|
||||
|
|
|
@ -15,33 +15,46 @@ 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 AddCredentialsModalProps {
|
||||
isVisible: boolean;
|
||||
onCancel: () => void;
|
||||
onAddCredential: (values: any) => void;
|
||||
onUpdateCredential: (values: any) => void;
|
||||
uploadProps: UploadProps;
|
||||
addOrEdit: "add" | "edit";
|
||||
existingCredential: CredentialItem | null;
|
||||
}
|
||||
|
||||
const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({
|
||||
isVisible,
|
||||
onCancel,
|
||||
onAddCredential,
|
||||
uploadProps
|
||||
onUpdateCredential,
|
||||
uploadProps,
|
||||
addOrEdit,
|
||||
existingCredential
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [selectedProvider, setSelectedProvider] = useState<Providers>(Providers.OpenAI);
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
|
||||
console.log(`existingCredential in add credentials tab: ${JSON.stringify(existingCredential)}`);
|
||||
|
||||
const handleSubmit = (values: any) => {
|
||||
if (addOrEdit === "add") {
|
||||
onAddCredential(values);
|
||||
} else {
|
||||
onUpdateCredential(values);
|
||||
}
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Add New Credential"
|
||||
title={addOrEdit === "add" ? "Add New Credential" : "Edit Credential"}
|
||||
visible={isVisible}
|
||||
onCancel={() => {
|
||||
onCancel();
|
||||
|
@ -55,18 +68,31 @@ const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({
|
|||
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"
|
||||
disabled={existingCredential?.credential_name ? true : false}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* Provider Selection */}
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="Provider:"
|
||||
name="custom_llm_provider"
|
||||
tooltip="Select the credential provider"
|
||||
tooltip="Helper to auto-populate provider specific fields"
|
||||
>
|
||||
<AntdSelect
|
||||
showSearch={true}
|
||||
value={selectedProvider}
|
||||
value={existingCredential?.credential_info.custom_llm_provider || selectedProvider}
|
||||
onChange={(value) => {
|
||||
setSelectedProvider(value);
|
||||
setSelectedProvider(value as Providers);
|
||||
}}
|
||||
>
|
||||
{Object.entries(Providers).map(([providerEnum, providerDisplayName]) => (
|
||||
|
@ -97,14 +123,7 @@ const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({
|
|||
</AntdSelect>
|
||||
</Form.Item>
|
||||
|
||||
{/* 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>
|
||||
|
||||
|
||||
<ProviderSpecificFields
|
||||
selectedProvider={selectedProvider}
|
||||
|
@ -132,7 +151,7 @@ const AddCredentialsModal: React.FC<AddCredentialsModalProps> = ({
|
|||
<Button
|
||||
htmlType="submit"
|
||||
>
|
||||
Add Credential
|
||||
{addOrEdit === "add" ? "Add Credential" : "Update Credential"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from "@heroicons/react/outline";
|
||||
import { UploadProps } from "antd/es/upload";
|
||||
import { PlusIcon } from "@heroicons/react/solid";
|
||||
import { credentialListCall, credentialCreateCall, credentialDeleteCall, CredentialItem, CredentialsResponse } from "@/components/networking"; // Assume this is your networking function
|
||||
import { credentialListCall, credentialCreateCall, credentialDeleteCall, credentialUpdateCall, CredentialItem, CredentialsResponse } from "@/components/networking"; // Assume this is your networking function
|
||||
import AddCredentialsTab from "./add_credentials_tab";
|
||||
import { Form, message } from "antd";
|
||||
interface CredentialsPanelProps {
|
||||
|
@ -35,7 +35,36 @@ interface CredentialsPanelProps {
|
|||
|
||||
const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ accessToken, uploadProps, credentialList, fetchCredentials }) => {
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false);
|
||||
const [selectedCredential, setSelectedCredential] = useState<CredentialItem | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
console.log(`selectedCredential in credentials panel: ${JSON.stringify(selectedCredential)}`);
|
||||
|
||||
const restrictedFields = ['credential_name', 'custom_llm_provider'];
|
||||
const handleUpdateCredential = async (values: any) => {
|
||||
if (!accessToken) {
|
||||
console.error('No access token found');
|
||||
return;
|
||||
}
|
||||
|
||||
const filter_credential_values = Object.entries(values)
|
||||
.filter(([key]) => !restrictedFields.includes(key))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
|
||||
// Transform form values into credential structure
|
||||
const newCredential = {
|
||||
credential_name: values.credential_name,
|
||||
credential_values: filter_credential_values,
|
||||
credential_info: {
|
||||
custom_llm_provider: values.custom_llm_provider,
|
||||
}
|
||||
};
|
||||
|
||||
const response = await credentialUpdateCall(accessToken, values.credential_name, newCredential);
|
||||
message.success('Credential updated successfully');
|
||||
console.log(`response: ${JSON.stringify(response)}`);
|
||||
setIsUpdateModalOpen(false);
|
||||
fetchCredentials(accessToken);
|
||||
}
|
||||
|
||||
const handleAddCredential = async (values: any) => {
|
||||
if (!accessToken) {
|
||||
|
@ -44,7 +73,7 @@ const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ accessToken, upload
|
|||
}
|
||||
|
||||
const filter_credential_values = Object.entries(values)
|
||||
.filter(([key]) => !['credential_name', 'custom_llm_provider'].includes(key))
|
||||
.filter(([key]) => !restrictedFields.includes(key))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
|
||||
// Transform form values into credential structure
|
||||
const newCredential = {
|
||||
|
@ -145,6 +174,11 @@ const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ accessToken, upload
|
|||
icon={PencilAltIcon}
|
||||
variant="light"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
console.log(`credential being set: ${JSON.stringify(credential)}`);
|
||||
setSelectedCredential(credential);
|
||||
setIsUpdateModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={TrashIcon}
|
||||
|
@ -175,6 +209,19 @@ const CredentialsPanel: React.FC<CredentialsPanelProps> = ({ accessToken, upload
|
|||
isVisible={isAddModalOpen}
|
||||
onCancel={() => setIsAddModalOpen(false)}
|
||||
uploadProps={uploadProps}
|
||||
addOrEdit="add"
|
||||
/>
|
||||
)}
|
||||
{isUpdateModalOpen && (
|
||||
<AddCredentialsTab
|
||||
form={form}
|
||||
onAddCredential={handleAddCredential}
|
||||
isVisible={isUpdateModalOpen}
|
||||
existingCredential={selectedCredential}
|
||||
onUpdateCredential={handleUpdateCredential}
|
||||
uploadProps={uploadProps}
|
||||
onCancel={() => setIsUpdateModalOpen(false)}
|
||||
addOrEdit="edit"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -196,14 +196,7 @@ export const columns = (
|
|||
return model.litellm_params.litellm_credential_name ? (
|
||||
<div className="overflow-hidden">
|
||||
<Tooltip title={model.litellm_params.litellm_credential_name}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
|
||||
onClick={() => setSelectedTeamId(model.model_info.team_id)}
|
||||
>
|
||||
{model.litellm_params.litellm_credential_name.slice(0, 7)}...
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
@ -2648,6 +2648,51 @@ export const credentialDeleteCall = async (accessToken: String, credentialName:
|
|||
}
|
||||
};
|
||||
|
||||
export const credentialUpdateCall = async (
|
||||
accessToken: string,
|
||||
credentialName: string,
|
||||
formValues: Record<string, any> // Assuming formValues is an object
|
||||
) => {
|
||||
try {
|
||||
console.log("Form Values in credentialUpdateCall:", formValues); // Log the form values before making the API call
|
||||
if (formValues.metadata) {
|
||||
console.log("formValues.metadata:", formValues.metadata);
|
||||
// if there's an exception JSON.parse, show it in the message
|
||||
try {
|
||||
formValues.metadata = JSON.parse(formValues.metadata);
|
||||
} catch (error) {
|
||||
throw new Error("Failed to parse metadata: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/credentials/${credentialName}` : `/credentials/${credentialName}`;
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...formValues, // Include formValues in the request body
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
handleError(errorData);
|
||||
console.error("Error response from the server:", errorData);
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("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 keyUpdateCall = async (
|
||||
accessToken: string,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue