mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
refactor(ui): style the logging settings from semantic tokens (#37385)
team/LoggingSettings.tsx carried 34 hardcoded palette classes and common_components/PremiumLoggingSettings.tsx another 9, so both render light-only regardless of theme. Map the neutrals onto foreground, muted-foreground, muted and border, the red affordances onto destructive, and swap the hand-rolled chips for the shadcn Badge primitive. The three event-type options carried decorative green, red and blue dots. The design system has no success or info token, so the dots are dropped and the option labels, which already say "Success Only", "Failure Only" and "Success & Failure", carry the meaning on their own. This is groundwork, not a visible change: nothing in the dashboard ever applies the .dark class today, so the dark palette is unreachable. The files no longer hardcode colour and will follow the theme once one exists.
This commit is contained in:
parent
41de13aa0c
commit
5012d11a18
4 changed files with 115 additions and 62 deletions
|
|
@ -0,0 +1,39 @@
|
|||
import { readFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { renderWithProviders, screen } from "../../../tests/test-utils";
|
||||
import PremiumLoggingSettings from "./PremiumLoggingSettings";
|
||||
|
||||
const SOURCE_PATH = resolve(process.cwd(), "src/components/common_components/PremiumLoggingSettings.tsx");
|
||||
|
||||
const HARDCODED_PALETTE =
|
||||
/\b(?:text|bg|border|hover:bg|hover:text|hover:border|dark:bg|dark:text|dark:border|ring|divide|fill|stroke)-(?:gray|slate|zinc|neutral|stone|red|blue|green|yellow|amber|orange|indigo|purple|pink|rose|teal|cyan|sky|violet|fuchsia|lime|emerald)-\d+(?:\/\d+)?\b/g;
|
||||
|
||||
const SEMANTIC_TOKEN =
|
||||
/\b(?:text|bg|border|hover:bg|hover:text|ring|divide|fill|stroke)-(?:foreground|muted-foreground|muted|background|card|popover|primary|secondary|destructive|border|input|accent|ring)(?:-foreground)?(?:\/\d+)?\b/g;
|
||||
|
||||
describe("PremiumLoggingSettings", () => {
|
||||
it("styles itself from semantic tokens instead of hardcoded palette classes", () => {
|
||||
const source = readFileSync(SOURCE_PATH, "utf8");
|
||||
|
||||
expect(source).toContain("export function PremiumLoggingSettings");
|
||||
expect(source.match(SEMANTIC_TOKEN) ?? []).not.toHaveLength(0);
|
||||
expect(source.match(HARDCODED_PALETTE) ?? []).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("shows the enterprise notice and withholds the editor from a free user", () => {
|
||||
renderWithProviders(<PremiumLoggingSettings value={[]} onChange={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText(/LiteLLM Enterprise feature/)).toBeInTheDocument();
|
||||
expect(screen.getByText("✨ langfuse-logging")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Logging Integrations")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the editor for a premium user", () => {
|
||||
renderWithProviders(<PremiumLoggingSettings value={[]} onChange={vi.fn()} premiumUser />);
|
||||
|
||||
expect(screen.getByText("Logging Integrations")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/LiteLLM Enterprise feature/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import LoggingSettings from "../team/LoggingSettings";
|
||||
|
||||
interface PremiumLoggingSettingsProps {
|
||||
|
|
@ -20,15 +21,15 @@ export function PremiumLoggingSettings({
|
|||
return (
|
||||
<div>
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
<div className="inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium opacity-50">
|
||||
<Badge variant="secondary" className="opacity-50">
|
||||
✨ langfuse-logging
|
||||
</div>
|
||||
<div className="inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium opacity-50">
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="opacity-50">
|
||||
✨ datadog-logging
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p className="text-sm text-yellow-800">
|
||||
<div className="p-3 bg-muted border border-border rounded-lg">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Setting Key/Team logging settings is a LiteLLM Enterprise feature. Global Logging Settings are available for
|
||||
all free users. Get a trial key{" "}
|
||||
<a href="https://www.litellm.ai/#pricing" target="_blank" rel="noopener noreferrer" className="underline">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
import { readFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders, screen, fireEvent } from "../../../tests/test-utils";
|
||||
import LoggingSettings from "./LoggingSettings";
|
||||
|
||||
const SOURCE_PATH = resolve(process.cwd(), "src/components/team/LoggingSettings.tsx");
|
||||
|
||||
const HARDCODED_PALETTE =
|
||||
/\b(?:text|bg|border|hover:bg|hover:text|hover:border|dark:bg|dark:text|dark:border|ring|divide|fill|stroke)-(?:gray|slate|zinc|neutral|stone|red|blue|green|yellow|amber|orange|indigo|purple|pink|rose|teal|cyan|sky|violet|fuchsia|lime|emerald)-\d+(?:\/\d+)?\b/g;
|
||||
|
||||
const SEMANTIC_TOKEN =
|
||||
/\b(?:text|bg|border|hover:bg|hover:text|ring|divide|fill|stroke)-(?:foreground|muted-foreground|muted|background|card|popover|primary|secondary|destructive|border|input|accent|ring)(?:-foreground)?(?:\/\d+)?\b/g;
|
||||
|
||||
describe("LoggingSettings", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
|
@ -163,6 +173,33 @@ describe("LoggingSettings", () => {
|
|||
expect(screen.getByText("C")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("styles itself from semantic tokens instead of hardcoded palette classes", () => {
|
||||
const source = readFileSync(SOURCE_PATH, "utf8");
|
||||
|
||||
expect(source).toContain("const LoggingSettings");
|
||||
expect(source.match(SEMANTIC_TOKEN) ?? []).not.toHaveLength(0);
|
||||
expect(source.match(HARDCODED_PALETTE) ?? []).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("reports the chosen event type when a different option is picked", async () => {
|
||||
const user = userEvent.setup({ delay: null });
|
||||
const mockOnChange = vi.fn();
|
||||
const initialValue = [
|
||||
{
|
||||
callback_name: "langsmith",
|
||||
callback_type: "success",
|
||||
callback_vars: {},
|
||||
},
|
||||
];
|
||||
|
||||
renderWithProviders(<LoggingSettings value={initialValue} onChange={mockOnChange} />);
|
||||
|
||||
await user.click(screen.getByTitle("Success Only"));
|
||||
await user.click(await screen.findByTitle("Failure Only"));
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith([expect.objectContaining({ callback_type: "failure" })]);
|
||||
});
|
||||
|
||||
it("correctly handles numerical input with decimal values", () => {
|
||||
const mockOnChange = vi.fn();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import React from "react";
|
|||
import { Select, Tooltip, Divider } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
|
||||
|
|
@ -142,30 +143,24 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
if (Object.keys(dynamicParams).length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-6 pt-4 border-t border-gray-100">
|
||||
<div className="mt-6 pt-4 border-t border-border">
|
||||
<div className="flex items-center space-x-2 mb-4">
|
||||
<div className="w-3 h-3 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<div className="w-1.5 h-1.5 bg-blue-500 rounded-full"></div>
|
||||
<div className="w-3 h-3 bg-muted rounded-full flex items-center justify-center">
|
||||
<div className="w-1.5 h-1.5 bg-primary rounded-full"></div>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-700">Integration Parameters</span>
|
||||
<span className="text-sm font-medium text-foreground">Integration Parameters</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{Object.entries(dynamicParams).map(([paramName, paramType]) => (
|
||||
<div key={paramName} className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700 capitalize flex items-center space-x-1">
|
||||
<label className="text-sm font-medium text-foreground capitalize flex items-center space-x-1">
|
||||
<span>{paramName.replace(/_/g, " ")}</span>
|
||||
{paramType === "password" && (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||
Sensitive
|
||||
</span>
|
||||
)}
|
||||
{paramType === "number" && (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||
Number
|
||||
</span>
|
||||
)}
|
||||
{paramType === "password" && <Badge variant="secondary">Sensitive</Badge>}
|
||||
{paramType === "number" && <Badge variant="secondary">Number</Badge>}
|
||||
</label>
|
||||
{paramType === "number" && <span className="text-xs text-gray-500">Value must be between 0 and 1</span>}
|
||||
{paramType === "number" && (
|
||||
<span className="text-xs text-muted-foreground">Value must be between 0 and 1</span>
|
||||
)}
|
||||
{paramType === "number" ? (
|
||||
<NumericalInput
|
||||
step={0.01}
|
||||
|
|
@ -194,15 +189,15 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
{/* Disabled Callbacks Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<BanIcon className="w-5 h-5 text-red-500" />
|
||||
<span className="text-base font-semibold text-gray-800">Disabled Callbacks</span>
|
||||
<BanIcon className="w-5 h-5 text-destructive" />
|
||||
<span className="text-base font-semibold text-foreground">Disabled Callbacks</span>
|
||||
<Tooltip title="Select callbacks to disable for this key. Disabled callbacks will not receive any logging data.">
|
||||
<InfoCircleOutlined className="text-gray-400 cursor-help" />
|
||||
<InfoCircleOutlined className="text-muted-foreground cursor-help" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Disabled Callbacks</label>
|
||||
<label className="text-sm font-medium text-foreground">Disabled Callbacks</label>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Select callbacks to disable"
|
||||
|
|
@ -229,7 +224,7 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
);
|
||||
})}
|
||||
</Select>
|
||||
<div className="text-xs text-gray-500">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Select callbacks that should be disabled for this key. These callbacks will not receive any logging data.
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -240,19 +235,13 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
{/* Logging Integrations Section */}
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CogIcon className="w-5 h-5 text-blue-500" />
|
||||
<span className="text-base font-semibold text-gray-800">Logging Integrations</span>
|
||||
<CogIcon className="w-5 h-5 text-foreground" />
|
||||
<span className="text-base font-semibold text-foreground">Logging Integrations</span>
|
||||
<Tooltip title="Configure callback logging integrations for this team.">
|
||||
<InfoCircleOutlined className="text-gray-400 cursor-help" />
|
||||
<InfoCircleOutlined className="text-muted-foreground cursor-help" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={addLoggingConfig}
|
||||
size="sm"
|
||||
className="hover:border-blue-400 hover:text-blue-500"
|
||||
type="button"
|
||||
>
|
||||
<Button variant="secondary" onClick={addLoggingConfig} size="sm" type="button">
|
||||
<Plus />
|
||||
Add Integration
|
||||
</Button>
|
||||
|
|
@ -267,7 +256,7 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
return (
|
||||
<Card
|
||||
key={index}
|
||||
className="block p-6 border border-gray-200 shadow-xs hover:shadow-md transition-shadow duration-200"
|
||||
className="block p-6 border border-border shadow-xs hover:shadow-md transition-shadow duration-200"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -284,7 +273,7 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
variant="ghost"
|
||||
onClick={() => removeLoggingConfig(index)}
|
||||
size="sm"
|
||||
className="text-red-500 hover:bg-red-50 hover:text-red-500"
|
||||
className="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
type="button"
|
||||
>
|
||||
<Trash2 />
|
||||
|
|
@ -294,7 +283,7 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Integration Type</label>
|
||||
<label className="text-sm font-medium text-foreground">Integration Type</label>
|
||||
<Select
|
||||
value={callbackDisplayName}
|
||||
placeholder="Select integration"
|
||||
|
|
@ -323,30 +312,15 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">Event Type</label>
|
||||
<label className="text-sm font-medium text-foreground">Event Type</label>
|
||||
<Select
|
||||
value={config.callback_type}
|
||||
onChange={(value) => updateLoggingConfig(index, "callback_type", value)}
|
||||
className="w-full"
|
||||
>
|
||||
<Option value="success">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span>Success Only</span>
|
||||
</div>
|
||||
</Option>
|
||||
<Option value="failure">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-red-500 rounded-full"></div>
|
||||
<span>Failure Only</span>
|
||||
</div>
|
||||
</Option>
|
||||
<Option value="success_and_failure">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
<span>Success & Failure</span>
|
||||
</div>
|
||||
</Option>
|
||||
<Option value="success">Success Only</Option>
|
||||
<Option value="failure">Failure Only</Option>
|
||||
<Option value="success_and_failure">Success & Failure</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -359,10 +333,12 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
|||
</div>
|
||||
|
||||
{value.length === 0 && (
|
||||
<div className="text-center py-12 text-gray-500 border-2 border-dashed border-gray-200 rounded-lg bg-gray-50/50">
|
||||
<CogIcon className="w-12 h-12 text-gray-300 mb-3 mx-auto" />
|
||||
<div className="text-center py-12 text-muted-foreground border-2 border-dashed border-border rounded-lg bg-muted/30">
|
||||
<CogIcon className="w-12 h-12 text-muted-foreground mb-3 mx-auto" />
|
||||
<div className="text-base font-medium mb-1">No logging integrations configured</div>
|
||||
<div className="text-sm text-gray-400">Click "Add Integration" to configure logging for this team</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Click "Add Integration" to configure logging for this team
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue