mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
refactor(ui): migrate budgets, skills and ui-theme to shadcn
Replaces antd and Tremor with the installed shadcn primitives on the three route-exclusive panels: Tremor tabs, buttons and text on budgets; the antd delete Modal and Tremor button on skills; the Tremor card, inputs and buttons on ui-theme. Markup only, no behaviour change. The characterisation tests added in the previous commit are untouched and stay green, and the ui-theme inputs now carry real label associations. Shared components stay on antd; they are reached by other routes and are migrated separately. The form-bearing files on these routes are left alone.
This commit is contained in:
parent
212421207e
commit
39f0b56502
4 changed files with 141 additions and 115 deletions
|
|
@ -144,9 +144,6 @@
|
|||
"src/app/(dashboard)/budgets/_components/budget_panel.tsx": {
|
||||
"local/filename-pascal-case": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/budgets/_components/edit_budget_modal.tsx": {
|
||||
|
|
@ -1840,9 +1837,6 @@
|
|||
}
|
||||
},
|
||||
"src/app/(dashboard)/skills/_components/ClaudeCodePluginsPanel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
|
|
@ -1888,9 +1882,6 @@
|
|||
}
|
||||
},
|
||||
"src/app/(dashboard)/ui-theme/UIThemeSettings.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-syntax": {
|
||||
"count": 3
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { Button, Tab, TabGroup, TabList, TabPanel, TabPanels, Text } from "@tremor/react";
|
||||
import React, { useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
|
||||
import NotificationsManager from "@/components/molecules/notifications_manager";
|
||||
import { useBudgets, useDeleteBudget, budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets";
|
||||
|
|
@ -73,76 +74,82 @@ const BudgetPanel: React.FC<BudgetSettingsPageProps> = ({ accessToken }) => {
|
|||
return (
|
||||
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
|
||||
{canModify && (
|
||||
<Button size="sm" variant="primary" className="mb-2" onClick={() => setIsCreateModelVisible(true)}>
|
||||
<Button size="sm" className="mb-2" onClick={() => setIsCreateModelVisible(true)}>
|
||||
+ Create Budget
|
||||
</Button>
|
||||
)}
|
||||
<TabGroup>
|
||||
<TabList>
|
||||
<Tab>Budgets</Tab>
|
||||
<Tab>Examples</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<div className="mt-6">
|
||||
<BudgetModal isModalVisible={isCreateModelVisible} setIsModalVisible={setIsCreateModelVisible} />
|
||||
{selectedBudget && (
|
||||
<EditBudgetModal
|
||||
isModalVisible={isEditModalVisible}
|
||||
setIsModalVisible={setIsEditModalVisible}
|
||||
existingBudget={selectedBudget}
|
||||
/>
|
||||
)}
|
||||
<Text className="mb-4">Create a budget to assign to customers.</Text>
|
||||
<BudgetTable
|
||||
budgets={budgetList}
|
||||
isLoading={isLoading}
|
||||
canModify={canModify}
|
||||
onEditClick={handleEditCall}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
<Tabs defaultValue="budgets">
|
||||
<TabsList variant="line" className="h-auto w-full justify-start rounded-none border-b p-0">
|
||||
<TabsTrigger value="budgets" className="flex-none rounded-none px-4 py-2">
|
||||
Budgets
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="examples" className="flex-none rounded-none px-4 py-2">
|
||||
Examples
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="budgets">
|
||||
<div className="mt-6">
|
||||
<BudgetModal isModalVisible={isCreateModelVisible} setIsModalVisible={setIsCreateModelVisible} />
|
||||
{selectedBudget && (
|
||||
<EditBudgetModal
|
||||
isModalVisible={isEditModalVisible}
|
||||
setIsModalVisible={setIsEditModalVisible}
|
||||
existingBudget={selectedBudget}
|
||||
/>
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteModalVisible}
|
||||
title="Delete Budget?"
|
||||
message="Are you sure you want to delete this budget? This action cannot be undone."
|
||||
resourceInformationTitle="Budget Information"
|
||||
resourceInformation={[
|
||||
{ label: "Budget ID", value: selectedBudget?.budget_id, code: true },
|
||||
{ label: "Max Budget", value: selectedBudget?.max_budget },
|
||||
{ label: "TPM", value: selectedBudget?.tpm_limit },
|
||||
{ label: "RPM", value: selectedBudget?.rpm_limit },
|
||||
]}
|
||||
onCancel={handleDeleteCancel}
|
||||
onOk={handleDeleteConfirm}
|
||||
confirmLoading={deleteBudget.isPending}
|
||||
/>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<div className="mt-6">
|
||||
<Text className="text-base">How to use budget id</Text>
|
||||
<TabGroup>
|
||||
<TabList>
|
||||
<Tab>Assign Budget to Customer</Tab>
|
||||
<Tab>Test it (Curl)</Tab>
|
||||
<Tab>Test it (OpenAI SDK)</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<SyntaxHighlighter language="bash">{CREATE_END_USER_CURL_COMMAND}</SyntaxHighlighter>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<SyntaxHighlighter language="bash">{CHAT_COMPLETIONS_CURL_COMMAND}</SyntaxHighlighter>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<SyntaxHighlighter language="python">{OPENAI_SDK_PYTHON_CODE}</SyntaxHighlighter>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
)}
|
||||
<p className="mb-4 text-sm text-muted-foreground">Create a budget to assign to customers.</p>
|
||||
<BudgetTable
|
||||
budgets={budgetList}
|
||||
isLoading={isLoading}
|
||||
canModify={canModify}
|
||||
onEditClick={handleEditCall}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
/>
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteModalVisible}
|
||||
title="Delete Budget?"
|
||||
message="Are you sure you want to delete this budget? This action cannot be undone."
|
||||
resourceInformationTitle="Budget Information"
|
||||
resourceInformation={[
|
||||
{ label: "Budget ID", value: selectedBudget?.budget_id, code: true },
|
||||
{ label: "Max Budget", value: selectedBudget?.max_budget },
|
||||
{ label: "TPM", value: selectedBudget?.tpm_limit },
|
||||
{ label: "RPM", value: selectedBudget?.rpm_limit },
|
||||
]}
|
||||
onCancel={handleDeleteCancel}
|
||||
onOk={handleDeleteConfirm}
|
||||
confirmLoading={deleteBudget.isPending}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="examples">
|
||||
<div className="mt-6">
|
||||
<p className="text-base text-muted-foreground">How to use budget id</p>
|
||||
<Tabs defaultValue="assign-budget">
|
||||
<TabsList variant="line" className="h-auto w-full justify-start rounded-none border-b p-0">
|
||||
<TabsTrigger value="assign-budget" className="flex-none rounded-none px-4 py-2">
|
||||
Assign Budget to Customer
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="curl" className="flex-none rounded-none px-4 py-2">
|
||||
Test it (Curl)
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="openai-sdk" className="flex-none rounded-none px-4 py-2">
|
||||
Test it (OpenAI SDK)
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="assign-budget">
|
||||
<SyntaxHighlighter language="bash">{CREATE_END_USER_CURL_COMMAND}</SyntaxHighlighter>
|
||||
</TabsContent>
|
||||
<TabsContent value="curl">
|
||||
<SyntaxHighlighter language="bash">{CHAT_COMPLETIONS_CURL_COMMAND}</SyntaxHighlighter>
|
||||
</TabsContent>
|
||||
<TabsContent value="openai-sdk">
|
||||
<SyntaxHighlighter language="python">{OPENAI_SDK_PYTHON_CODE}</SyntaxHighlighter>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Button } from "@tremor/react";
|
||||
import { Modal } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { getClaudeCodePluginsList, deleteClaudeCodePlugin } from "@/components/networking";
|
||||
import AddPluginForm from "./add_plugin_form";
|
||||
import PluginTable from "./PluginTable";
|
||||
|
|
@ -115,20 +123,28 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({ accessT
|
|||
/>
|
||||
|
||||
{pluginToDelete && (
|
||||
<Modal
|
||||
title="Delete Skill"
|
||||
open={pluginToDelete !== null}
|
||||
onOk={handleDeleteConfirm}
|
||||
onCancel={() => setPluginToDelete(null)}
|
||||
confirmLoading={isDeleting}
|
||||
okText="Delete"
|
||||
okButtonProps={{ danger: true }}
|
||||
<AlertDialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setPluginToDelete(null);
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Are you sure you want to delete skill: <strong>{pluginToDelete.displayName}</strong>?
|
||||
</p>
|
||||
<p>This action cannot be undone.</p>
|
||||
</Modal>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Skill</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete skill: <strong>{pluginToDelete.displayName}</strong>?
|
||||
</AlertDialogDescription>
|
||||
<p className="text-sm text-muted-foreground">This action cannot be undone.</p>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<Button variant="destructive" onClick={handleDeleteConfirm} disabled={isDeleting}>
|
||||
Delete
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Card, Title, Text, TextInput, Button } from "@tremor/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
|
||||
import NotificationsManager from "@/components/molecules/notifications_manager";
|
||||
|
|
@ -113,50 +117,58 @@ const UIThemeSettings: React.FC<UIThemeSettingsProps> = ({ userID, userRole, acc
|
|||
return (
|
||||
<div className="w-full mx-auto max-w-4xl px-6 py-8">
|
||||
<div className="mb-8">
|
||||
<Title className="text-2xl font-bold mb-2">UI Theme Customization</Title>
|
||||
<Text className="text-gray-600">Customize your LiteLLM admin dashboard with a custom logo and favicon.</Text>
|
||||
<h1 className="mb-2 text-2xl font-bold">UI Theme Customization</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Customize your LiteLLM admin dashboard with a custom logo and favicon.
|
||||
</p>
|
||||
</div>
|
||||
<Card className="shadow-xs p-6">
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<Text className="text-sm font-medium text-gray-700 mb-2 block">Custom Logo URL</Text>
|
||||
<TextInput
|
||||
<Label htmlFor="ui-theme-logo-url" className="mb-2">
|
||||
Custom Logo URL
|
||||
</Label>
|
||||
<Input
|
||||
id="ui-theme-logo-url"
|
||||
placeholder="https://example.com/logo.png"
|
||||
value={logoUrlInput}
|
||||
onValueChange={(v) => {
|
||||
setLogoUrlInput(v);
|
||||
setLogoUrl(v || null);
|
||||
onChange={(event) => {
|
||||
setLogoUrlInput(event.target.value);
|
||||
setLogoUrl(event.target.value || null);
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
<Text className="text-xs text-gray-500 mt-1">
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Enter a URL for your custom logo or leave empty for default
|
||||
</Text>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="text-sm font-medium text-gray-700 mb-2 block">Custom Favicon URL</Text>
|
||||
<TextInput
|
||||
<Label htmlFor="ui-theme-favicon-url" className="mb-2">
|
||||
Custom Favicon URL
|
||||
</Label>
|
||||
<Input
|
||||
id="ui-theme-favicon-url"
|
||||
placeholder="https://example.com/favicon.ico"
|
||||
value={faviconUrlInput}
|
||||
onValueChange={(v) => {
|
||||
setFaviconUrlInput(v);
|
||||
setFaviconUrl(v || null);
|
||||
onChange={(event) => {
|
||||
setFaviconUrlInput(event.target.value);
|
||||
setFaviconUrl(event.target.value || null);
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
<Text className="text-xs text-gray-500 mt-1">
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Enter a URL for your custom favicon (.ico, .png, or .svg) or leave empty for default
|
||||
</Text>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3 pt-4">
|
||||
<Button onClick={handleSave} loading={loading} disabled={loading} color="indigo">
|
||||
<Button onClick={handleSave} disabled={loading}>
|
||||
{loading && <UiLoadingSpinner className="size-4" />}
|
||||
Save Changes
|
||||
</Button>
|
||||
<Button onClick={handleReset} loading={loading} disabled={loading} variant="secondary" color="gray">
|
||||
<Button variant="outline" onClick={handleReset} disabled={loading}>
|
||||
{loading && <UiLoadingSpinner className="size-4" />}
|
||||
Reset to Default
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue