Raju2024's picture
Upload 1072 files
e3278e4 verified
import React, { useState, useEffect } from "react";
import {
Card,
Subtitle,
Table,
TableHead,
TableRow,
Badge,
TableHeaderCell,
TableCell,
TableBody,
Metric,
Text,
Grid,
Button,
TextInput,
Switch,
Col,
TabPanel,
TabPanels,
TabGroup,
TabList,
Tab,
Callout,
SelectItem,
Icon,
} from "@tremor/react";
import {
PencilAltIcon
} from "@heroicons/react/outline";
import { Modal, Typography, Form, Input, Select, Button as Button2, message } from "antd";
const { Title, Paragraph } = Typography;
const isLocal = process.env.NODE_ENV === "development";
const proxyBaseUrl = isLocal ? "http://localhost:4000" : null;
if (isLocal != true) {
console.log = function() {};
}
import {
getCallbacksCall,
setCallbacksCall,
serviceHealthCheck,
} from "./networking";
import AlertingSettings from "./alerting/alerting_settings";
import FormItem from "antd/es/form/FormItem";
interface SettingsPageProps {
accessToken: string | null;
userRole: string | null;
userID: string | null;
premiumUser: boolean;
}
interface genericCallbackParams {
litellm_callback_name: string // what to send in request
ui_callback_name: string // what to show on UI
litellm_callback_params: string[] | null // known required params for this callback
}
interface AlertingVariables {
SLACK_WEBHOOK_URL: string | null;
LANGFUSE_PUBLIC_KEY: string | null;
LANGFUSE_SECRET_KEY: string | null;
LANGFUSE_HOST: string | null;
OPENMETER_API_KEY: string | null;
}
interface AlertingObject {
name: string;
variables: AlertingVariables;
}
const defaultLoggingObject: AlertingObject[] = [
{
name: "slack",
variables: {
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: null,
LANGFUSE_SECRET_KEY: null,
OPENMETER_API_KEY: null,
SLACK_WEBHOOK_URL: null,
},
},
{
name: "langfuse",
variables: {
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: null,
LANGFUSE_SECRET_KEY: null,
OPENMETER_API_KEY: null,
SLACK_WEBHOOK_URL: null,
},
},
{
name: "openmeter",
variables: {
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: null,
LANGFUSE_SECRET_KEY: null,
OPENMETER_API_KEY: null,
SLACK_WEBHOOK_URL: null,
},
},
];
const Settings: React.FC<SettingsPageProps> = ({
accessToken,
userRole,
userID,
premiumUser,
}) => {
const [callbacks, setCallbacks] =
useState<AlertingObject[]>([]);
const [alerts, setAlerts] = useState<any[]>([]);
const [isModalVisible, setIsModalVisible] = useState(false);
const [form] = Form.useForm();
const [selectedCallback, setSelectedCallback] = useState<string | null>(null);
const [selectedAlertValues, setSelectedAlertValues] = useState([]);
const [catchAllWebhookURL, setCatchAllWebhookURL] = useState<string>("");
const [alertToWebhooks, setAlertToWebhooks] = useState<
Record<string, string>
>({});
const [activeAlerts, setActiveAlerts] = useState<string[]>([]);
const [showAddCallbacksModal, setShowAddCallbacksModal] = useState(false);
const [allCallbacks, setAllCallbacks] = useState<genericCallbackParams[]>([]);
const [selectedCallbacktoAdd, setSelectedCallbacktoAdd] = useState<string | null>(null);
const [selectedCallbackParams, setSelectedCallbackParams] = useState<string[]>([]);
const [showEditCallback, setShowEditCallback] = useState(false);
const [selectedEditCallback, setSelectedEditCallback] = useState<any | null>(null);
const handleSwitchChange = (alertName: string) => {
if (activeAlerts.includes(alertName)) {
setActiveAlerts(activeAlerts.filter((alert) => alert !== alertName));
} else {
setActiveAlerts([...activeAlerts, alertName]);
}
};
const alerts_to_UI_NAME: Record<string, string> = {
llm_exceptions: "LLM Exceptions",
llm_too_slow: "LLM Responses Too Slow",
llm_requests_hanging: "LLM Requests Hanging",
budget_alerts: "Budget Alerts (API Keys, Users)",
db_exceptions: "Database Exceptions (Read/Write)",
daily_reports: "Weekly/Monthly Spend Reports",
outage_alerts: "Outage Alerts",
region_outage_alerts: "Region Outage Alerts",
};
useEffect(() => {
if (!accessToken || !userRole || !userID) {
return;
}
getCallbacksCall(accessToken, userID, userRole).then((data) => {
console.log("callbacks", data);
setCallbacks(data.callbacks);
setAllCallbacks(data.available_callbacks);
// setCallbacks(callbacks_data);
let alerts_data = data.alerts;
console.log("alerts_data", alerts_data);
if (alerts_data) {
if (alerts_data.length > 0) {
let _alert_info = alerts_data[0];
console.log("_alert_info", _alert_info);
let catch_all_webhook = _alert_info.variables.SLACK_WEBHOOK_URL;
console.log("catch_all_webhook", catch_all_webhook);
let active_alerts = _alert_info.active_alerts;
setActiveAlerts(active_alerts);
setCatchAllWebhookURL(catch_all_webhook);
setAlertToWebhooks(_alert_info.alerts_to_webhook);
}
}
setAlerts(alerts_data);
});
}, [accessToken, userRole, userID]);
const isAlertOn = (alertName: string) => {
return activeAlerts && activeAlerts.includes(alertName);
};
const handleAddCallback = () => {
console.log("Add callback clicked");
setIsModalVisible(true);
};
const handleCancel = () => {
setIsModalVisible(false);
form.resetFields();
setSelectedCallback(null);
};
const handleChange = (values: any) => {
setSelectedAlertValues(values);
// Here, you can perform any additional logic with the selected values
console.log("Selected values:", values);
};
const handleSaveEmailSettings = () => {
if (!accessToken) {
return;
}
let updatedVariables: Record<string, string> = {};
alerts
.filter((alert) => alert.name === "email")
.forEach((alert) => {
Object.entries(alert.variables ?? {}).forEach(([key, value]) => {
const inputElement = document.querySelector(`input[name="${key}"]`) as HTMLInputElement;
if (inputElement && inputElement.value) {
updatedVariables[key] = inputElement?.value;
}
});
});
console.log("updatedVariables", updatedVariables);
//filter out null / undefined values for updatedVariables
const payload = {
general_settings: {
alerting: ["email"],
},
environment_variables: updatedVariables,
};
try {
setCallbacksCall(accessToken, payload);
} catch (error) {
message.error("Failed to update alerts: " + error, 20);
}
message.success("Email settings updated successfully");
}
const updateCallbackCall = async (formValues: Record<string, any>) => {
if (!accessToken) {
return;
}
let env_vars: Record<string, string> = {};
// add all other variables
Object.entries(formValues).forEach(([key, value]) => {
if (key !== "callback") {
env_vars[key] = value
}
});
let payload = {
environment_variables: env_vars,
}
try {
let newCallback = await setCallbacksCall(accessToken, payload);
message.success(`Callback added successfully`);
setIsModalVisible(false);
form.resetFields();
setSelectedCallback(null);
} catch (error) {
message.error("Failed to add callback: " + error, 20);
}
}
const addNewCallbackCall = async (formValues: Record<string, any>) => {
if (!accessToken) {
return;
}
let new_callback = formValues?.callback
let env_vars: Record<string, string> = {};
// add all other variables
Object.entries(formValues).forEach(([key, value]) => {
if (key !== "callback") {
env_vars[key] = value
}
});
let payload = {
environment_variables: env_vars,
litellm_settings: {
success_callback: [new_callback],
},
}
try {
let newCallback = await setCallbacksCall(accessToken, payload);
message.success(`Callback ${new_callback} added successfully`);
setIsModalVisible(false);
form.resetFields();
setSelectedCallback(null);
} catch (error) {
message.error("Failed to add callback: " + error, 20);
}
}
const handleSelectedCallbackChange = (callbackObject: genericCallbackParams) => {
console.log("inside handleSelectedCallbackChange", callbackObject);
setSelectedCallback(callbackObject.litellm_callback_name);
console.log("all callbacks", allCallbacks);
if (callbackObject && callbackObject.litellm_callback_params) {
setSelectedCallbackParams(callbackObject.litellm_callback_params);
console.log("selectedCallbackParams", selectedCallbackParams);
} else {
setSelectedCallbackParams([]);
}
};
const handleSaveAlerts = () => {
if (!accessToken) {
return;
}
const updatedAlertToWebhooks: Record<string, string> = {};
Object.entries(alerts_to_UI_NAME).forEach(([key, value]) => {
const webhookInput = document.querySelector(
`input[name="${key}"]`
) as HTMLInputElement;
console.log("key", key);
console.log("webhookInput", webhookInput);
const newWebhookValue = webhookInput?.value || "";
console.log("newWebhookValue", newWebhookValue);
updatedAlertToWebhooks[key] = newWebhookValue;
});
console.log("updatedAlertToWebhooks", updatedAlertToWebhooks);
const payload = {
general_settings: {
alert_to_webhook_url: updatedAlertToWebhooks,
alert_types: activeAlerts,
},
};
console.log("payload", payload);
try {
setCallbacksCall(accessToken, payload);
} catch (error) {
message.error("Failed to update alerts: " + error, 20);
}
message.success("Alerts updated successfully");
};
const handleSaveChanges = (callback: any) => {
if (!accessToken) {
return;
}
const updatedVariables = Object.fromEntries(
Object.entries(callback.variables).map(([key, value]) => [
key,
(document.querySelector(`input[name="${key}"]`) as HTMLInputElement)
?.value || value,
])
);
console.log("updatedVariables", updatedVariables);
console.log("updateAlertTypes", selectedAlertValues);
const payload = {
environment_variables: updatedVariables,
litellm_settings: {
success_callback: [callback.name],
},
};
try {
setCallbacksCall(accessToken, payload);
} catch (error) {
message.error("Failed to update callback: " + error, 20);
}
message.success("Callback updated successfully");
};
const handleOk = () => {
if (!accessToken) {
return;
}
// Handle form submission
form.validateFields().then((values) => {
// Call API to add the callback
console.log("Form values:", values);
let payload;
if (values.callback === "langfuse") {
payload = {
environment_variables: {
LANGFUSE_PUBLIC_KEY: values.langfusePublicKey,
LANGFUSE_SECRET_KEY: values.langfusePrivateKey,
},
litellm_settings: {
success_callback: [values.callback],
},
};
setCallbacksCall(accessToken, payload);
let newCallback: AlertingObject = {
name: values.callback,
variables: {
SLACK_WEBHOOK_URL: null,
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: values.langfusePublicKey,
LANGFUSE_SECRET_KEY: values.langfusePrivateKey,
OPENMETER_API_KEY: null,
},
};
// add langfuse to callbacks
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
} else if (values.callback === "slack") {
console.log(`values.slackWebhookUrl: ${values.slackWebhookUrl}`);
payload = {
general_settings: {
alerting: ["slack"],
alerting_threshold: 300,
},
environment_variables: {
SLACK_WEBHOOK_URL: values.slackWebhookUrl,
},
};
setCallbacksCall(accessToken, payload);
// add slack to callbacks
console.log(`values.callback: ${values.callback}`);
let newCallback: AlertingObject = {
name: values.callback,
variables: {
SLACK_WEBHOOK_URL: values.slackWebhookUrl,
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: null,
LANGFUSE_SECRET_KEY: null,
OPENMETER_API_KEY: null,
},
};
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
} else if (values.callback == "openmeter") {
console.log(`values.openMeterApiKey: ${values.openMeterApiKey}`);
payload = {
environment_variables: {
OPENMETER_API_KEY: values.openMeterApiKey,
},
litellm_settings: {
success_callback: [values.callback],
},
};
setCallbacksCall(accessToken, payload);
let newCallback: AlertingObject = {
name: values.callback,
variables: {
SLACK_WEBHOOK_URL: null,
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: null,
LANGFUSE_SECRET_KEY: null,
OPENMETER_API_KEY: values.openMeterAPIKey,
},
};
// add langfuse to callbacks
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
} else {
payload = {
error: "Invalid callback value",
};
}
setIsModalVisible(false);
form.resetFields();
setSelectedCallback(null);
});
};
const handleCallbackChange = (value: string) => {
setSelectedCallback(value);
};
if (!accessToken) {
return null;
}
console.log(`callbacks: ${callbacks}`);
return (
<div className="w-full mx-4">
<Grid numItems={1} className="gap-2 p-8 w-full mt-2">
<TabGroup>
<TabList variant="line" defaultValue="1">
<Tab value="1">Logging Callbacks</Tab>
<Tab value="2">Alerting Types</Tab>
<Tab value="3">Alerting Settings</Tab>
<Tab value="4">Email Alerts</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Title level={4}>Active Logging Callbacks</Title>
<Grid numItems={2}>
<Card className="max-h-[50vh]">
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Callback Name</TableHeaderCell>
{/* <TableHeaderCell>Callback Env Vars</TableHeaderCell> */}
</TableRow>
</TableHead>
<TableBody>
{callbacks
.map((callback, index) => (
<TableRow key={index} className="flex justify-between">
<TableCell>
<Text>{callback.name}</Text>
</TableCell>
<TableCell>
<Grid numItems={2} className="flex justify-between">
<Icon
icon={PencilAltIcon}
size="sm"
onClick={() => {
setSelectedEditCallback(callback);
setShowEditCallback(true);
}}
/>
<Button
onClick={() =>
serviceHealthCheck(accessToken, callback.name)
}
className="ml-2"
variant="secondary"
>
Test Callback
</Button>
</Grid>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</Grid>
<Button
className="mt-2"
onClick={() => setShowAddCallbacksModal(true)}>
Add Callback
</Button>
</TabPanel>
<TabPanel>
<Card>
<Text className="my-2">
Alerts are only supported for Slack Webhook URLs. Get your
webhook urls from{" "}
<a
href="https://api.slack.com/messaging/webhooks"
target="_blank"
style={{ color: "blue" }}
>
here
</a>
</Text>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell></TableHeaderCell>
<TableHeaderCell></TableHeaderCell>
<TableHeaderCell>Slack Webhook URL</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{Object.entries(alerts_to_UI_NAME).map(
([key, value], index) => (
<TableRow key={index}>
<TableCell>
{key == "region_outage_alerts" ? (
premiumUser ? (
<Switch
id="switch"
name="switch"
checked={isAlertOn(key)}
onChange={() => handleSwitchChange(key)}
/>
) : (
<Button className="flex items-center justify-center">
<a
href="https://forms.gle/W3U4PZpJGFHWtHyA9"
target="_blank"
>
✨ Enterprise Feature
</a>
</Button>
)
) : (
<Switch
id="switch"
name="switch"
checked={isAlertOn(key)}
onChange={() => handleSwitchChange(key)}
/>
)}
</TableCell>
<TableCell>
<Text>{value}</Text>
</TableCell>
<TableCell>
<TextInput
name={key}
type="password"
defaultValue={
alertToWebhooks && alertToWebhooks[key]
? alertToWebhooks[key]
: (catchAllWebhookURL as string)
}
></TextInput>
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
<Button size="xs" className="mt-2" onClick={handleSaveAlerts}>
Save Changes
</Button>
<Button
onClick={() => serviceHealthCheck(accessToken, "slack")}
className="mx-2"
>
Test Alerts
</Button>
</Card>
</TabPanel>
<TabPanel>
<AlertingSettings
accessToken={accessToken}
premiumUser={premiumUser}
/>
</TabPanel>
<TabPanel>
<Card>
<Title level={4}>Email Settings</Title>
<Text>
<a href="https://docs.litellm.ai/docs/proxy/email" target="_blank" style={{ color: "blue" }}> LiteLLM Docs: email alerts</a> <br/>
</Text>
<div className="flex w-full">
{alerts
.filter((alert) => alert.name === "email")
.map((alert, index) => (
<TableCell key={index}>
<ul>
<Grid numItems={2}>
{Object.entries(alert.variables ?? {}).map(([key, value]) => (
<li key={key} className="mx-2 my-2">
{ premiumUser!= true && (key === "EMAIL_LOGO_URL" || key === "EMAIL_SUPPORT_CONTACT") ? (
<div>
<a
href="https://forms.gle/W3U4PZpJGFHWtHyA9"
target="_blank"
>
<Text className="mt-2">
{" "}
✨ {key}
</Text>
</a>
<TextInput
name={key}
defaultValue={value as string}
type="password"
disabled={true}
style={{ width: "400px" }}
/>
</div>
) : (
<div>
<Text className="mt-2">{key}</Text>
<TextInput
name={key}
defaultValue={value as string}
type="password"
style={{ width: "400px" }}
/>
</div>
)}
{/* Added descriptions for input fields */}
<p style={{ fontSize: "small", fontStyle: "italic" }}>
{key === "SMTP_HOST" && (
<div style={{ color: "gray" }}>
Enter the SMTP host address, e.g. `smtp.resend.com`
<span style={{ color: "red" }}> Required * </span>
</div>
)}
{key === "SMTP_PORT" && (
<div style={{ color: "gray" }}>
Enter the SMTP port number, e.g. `587`
<span style={{ color: "red" }}> Required * </span>
</div>
)}
{key === "SMTP_USERNAME" && (
<div style={{ color: "gray" }}>
Enter the SMTP username, e.g. `username`
<span style={{ color: "red" }}> Required * </span>
</div>
)}
{key === "SMTP_PASSWORD" && (
<span style={{ color: "red" }}> Required * </span>
)}
{key === "SMTP_SENDER_EMAIL" && (
<div style={{ color: "gray" }}>
Enter the sender email address, e.g. `[email protected]`
<span style={{ color: "red" }}> Required * </span>
</div>
)}
{key === "TEST_EMAIL_ADDRESS" && (
<div style={{ color: "gray" }}>
Email Address to send `Test Email Alert` to. example: `[email protected]`
<span style={{ color: "red" }}> Required * </span>
</div>
)
}
{key === "EMAIL_LOGO_URL" && (
<div style={{ color: "gray" }}>
(Optional) Customize the Logo that appears in the email, pass a url to your logo
</div>
)
}
{key === "EMAIL_SUPPORT_CONTACT" && (
<div style={{ color: "gray" }}>
(Optional) Customize the support email address that appears in the email. Default is [email protected]
</div>
)
}
</p>
</li>
))}
</Grid>
</ul>
</TableCell>
))}
</div>
<Button
className="mt-2"
onClick={() => handleSaveEmailSettings()}
>
Save Changes
</Button>
<Button
onClick={() =>
serviceHealthCheck(accessToken, "email")
}
className="mx-2"
>
Test Email Alerts
</Button>
</Card>
</TabPanel>
</TabPanels>
</TabGroup>
</Grid>
<Modal
title="Add Logging Callback"
visible={showAddCallbacksModal}
width={800}
onCancel= {() => setShowAddCallbacksModal(false)}
footer={null}
>
<a href="https://docs.litellm.ai/docs/proxy/logging" className="mb-8 mt-4" target="_blank" style={{ color: "blue" }}> LiteLLM Docs: Logging</a>
<Form
form={form}
onFinish={addNewCallbackCall}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
labelAlign="left"
>
<>
<FormItem
label="Callback"
name="callback"
rules={[{ required: true, message: "Please select a callback" }]}
>
<Select
onChange={(value) => {
const selectedCallback = allCallbacks[value];
if (selectedCallback) {
console.log(selectedCallback.ui_callback_name);
handleSelectedCallbackChange(selectedCallback);
}
}}
>
{allCallbacks &&
Object.values(allCallbacks).map((callback) => (
<SelectItem
key={callback.litellm_callback_name}
value={callback.litellm_callback_name}
>
{callback.ui_callback_name}
</SelectItem>
))}
</Select>
</FormItem>
{
selectedCallbackParams && selectedCallbackParams.map((param) => (
<FormItem
label={param}
name={param}
key={param}
rules={[{ required: true, message: "Please enter the value for " + param}]}
>
<TextInput type="password" />
</FormItem>
))
}
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Save</Button2>
</div>
</>
</Form>
</Modal>
<Modal
visible={showEditCallback}
width={800}
title={`Edit ${selectedEditCallback?.name } Settings`}
onCancel= {() => setShowEditCallback(false)}
footer={null}
>
<Form
form={form}
onFinish={updateCallbackCall}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
labelAlign="left"
>
<>
{
selectedEditCallback && selectedEditCallback.variables && Object.entries(selectedEditCallback.variables).map(([param, value]) => (
<FormItem
label={param}
name={param}
key={param}
>
<TextInput type="password" defaultValue={value as string}/>
</FormItem>
))
}
</>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Save</Button2>
</div>
</Form>
</Modal>
</div>
);
};
export default Settings;