mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
feat(ui): another small batch to shadcn
- Navbar/CommunityEngagementButtons: antd Button + @ant-design icons
\u2192 shadcn Button asChild + lucide Slack/Github. Indigo shadow
preserved.
- playground/{Chat,Responses}ImageUpload: antd Upload.Dragger + antd
Tooltip + @ant-design PaperClipOutlined \u2192 hidden <input type=file>
+ shadcn Tooltip + lucide Paperclip. Keeps beforeUpload contract.
- playground/FilePreviewCard: @ant-design DeleteOutlined/FilePdfOutlined
\u2192 lucide Trash2/FileText; semantic tokens throughout.
- router_settings/TagFilteringToggle + RoutingStrategySelector: antd
Switch/Select \u2192 shadcn Switch/Select. onChange \u2192 onValueChange /
onCheckedChange. Strategy option now renders a two-line label via
flex-col inside the SelectItem.
Gates: TS \u2713 | Build \u2713.
Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
584b26c170
commit
30f6ef0d95
6 changed files with 153 additions and 94 deletions
|
|
@ -1,12 +1,11 @@
|
|||
import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts";
|
||||
import { GithubOutlined, SlackOutlined } from "@ant-design/icons";
|
||||
import { Button } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Github, Slack } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
export const CommunityEngagementButtons: React.FC = () => {
|
||||
const disableShowPrompts = useDisableShowPrompts();
|
||||
|
||||
// Hide buttons if prompts are disabled
|
||||
if (disableShowPrompts) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -14,22 +13,32 @@ export const CommunityEngagementButtons: React.FC = () => {
|
|||
return (
|
||||
<>
|
||||
<Button
|
||||
href="https://www.litellm.ai/support"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
icon={<SlackOutlined />}
|
||||
asChild
|
||||
variant="outline"
|
||||
className="shadow-md shadow-indigo-500/20 hover:shadow-indigo-500/50 transition-shadow"
|
||||
>
|
||||
Join Slack
|
||||
<a
|
||||
href="https://www.litellm.ai/support"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Slack className="h-4 w-4" />
|
||||
Join Slack
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
href="https://github.com/BerriAI/litellm"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
asChild
|
||||
variant="outline"
|
||||
className="shadow-md shadow-indigo-500/20 hover:shadow-indigo-500/50 transition-shadow"
|
||||
icon={<GithubOutlined />}
|
||||
>
|
||||
Star us on GitHub
|
||||
<a
|
||||
href="https://github.com/BerriAI/litellm"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Github className="h-4 w-4" />
|
||||
Star us on GitHub
|
||||
</a>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,43 +1,61 @@
|
|||
import React from "react";
|
||||
import { Upload, Tooltip } from "antd";
|
||||
import { PaperClipOutlined } from "@ant-design/icons";
|
||||
|
||||
const { Dragger } = Upload;
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
interface ChatImageUploadProps {
|
||||
chatUploadedImage: File | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
chatImagePreviewUrl: string | null;
|
||||
onImageUpload: (file: File) => false;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
onRemoveImage: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin hidden-file-input wrapper around the old Ant Design Dragger. The
|
||||
* previous implementation used antd Upload's `beforeUpload` hook which
|
||||
* returns `false` to intercept the file \u2014 we preserve that contract.
|
||||
*/
|
||||
const ChatImageUpload: React.FC<ChatImageUploadProps> = ({
|
||||
chatUploadedImage,
|
||||
chatImagePreviewUrl,
|
||||
onImageUpload,
|
||||
onRemoveImage,
|
||||
}) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
if (chatUploadedImage) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Subtle upload button - only show when no image */}
|
||||
{!chatUploadedImage && (
|
||||
<Dragger
|
||||
beforeUpload={onImageUpload}
|
||||
accept="image/*,.pdf"
|
||||
showUploadList={false}
|
||||
className="inline-block"
|
||||
style={{ padding: 0, border: "none", background: "none" }}
|
||||
>
|
||||
<Tooltip title="Attach image or PDF">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept="image/*,.pdf"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) onImageUpload(file);
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center w-8 h-8 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
|
||||
onClick={() => inputRef.current?.click()}
|
||||
className="flex items-center justify-center w-8 h-8 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors"
|
||||
>
|
||||
<PaperClipOutlined style={{ fontSize: "16px" }} />
|
||||
<Paperclip className="h-4 w-4" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</Dragger>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Attach image or PDF</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { DeleteOutlined, FilePdfOutlined } from "@ant-design/icons";
|
||||
import { FileText, Trash2 } from "lucide-react";
|
||||
|
||||
interface FilePreviewCardProps {
|
||||
file: File;
|
||||
|
|
@ -11,31 +11,36 @@ function FilePreviewCard({ file, previewUrl, onRemove }: FilePreviewCardProps) {
|
|||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<div className="flex items-center gap-3 p-3 bg-muted rounded-lg border border-border">
|
||||
<div className="relative inline-block">
|
||||
{isPdf ? (
|
||||
<div className="w-10 h-10 rounded-md bg-red-500 flex items-center justify-center">
|
||||
<FilePdfOutlined style={{ fontSize: "16px", color: "white" }} />
|
||||
<div className="w-10 h-10 rounded-md bg-destructive flex items-center justify-center">
|
||||
<FileText className="h-4 w-4 text-destructive-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={previewUrl || ""}
|
||||
alt="Upload preview"
|
||||
className="w-10 h-10 rounded-md border border-gray-200 object-cover"
|
||||
className="w-10 h-10 rounded-md border border-border object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-gray-900 truncate">{file.name}</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
<div className="text-sm font-medium text-foreground truncate">
|
||||
{file.name}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{isPdf ? "PDF" : "Image"}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center justify-center w-6 h-6 text-gray-400 hover:text-gray-600 hover:bg-gray-200 rounded-full transition-colors"
|
||||
type="button"
|
||||
className="flex items-center justify-center w-6 h-6 text-muted-foreground hover:text-foreground hover:bg-accent rounded-full transition-colors"
|
||||
onClick={onRemove}
|
||||
aria-label="Remove attachment"
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: "12px" }} />
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,43 +1,56 @@
|
|||
import React from "react";
|
||||
import { Upload, Tooltip } from "antd";
|
||||
import { PaperClipOutlined } from "@ant-design/icons";
|
||||
|
||||
const { Dragger } = Upload;
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
interface ResponsesImageUploadProps {
|
||||
responsesUploadedImage: File | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
responsesImagePreviewUrl: string | null;
|
||||
onImageUpload: (file: File) => false;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
onRemoveImage: () => void;
|
||||
}
|
||||
|
||||
const ResponsesImageUpload: React.FC<ResponsesImageUploadProps> = ({
|
||||
responsesUploadedImage,
|
||||
responsesImagePreviewUrl,
|
||||
onImageUpload,
|
||||
onRemoveImage,
|
||||
}) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
if (responsesUploadedImage) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Subtle upload button - only show when no image */}
|
||||
{!responsesUploadedImage && (
|
||||
<Dragger
|
||||
beforeUpload={onImageUpload}
|
||||
accept="image/*,.pdf"
|
||||
showUploadList={false}
|
||||
className="inline-block"
|
||||
style={{ padding: 0, border: "none", background: "none" }}
|
||||
>
|
||||
<Tooltip title="Attach image or PDF">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept="image/*,.pdf"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) onImageUpload(file);
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center w-8 h-8 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
|
||||
onClick={() => inputRef.current?.click()}
|
||||
className="flex items-center justify-center w-8 h-8 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors"
|
||||
>
|
||||
<PaperClipOutlined style={{ fontSize: "16px" }} />
|
||||
<Paperclip className="h-4 w-4" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</Dragger>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Attach image or PDF</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
import React from "react";
|
||||
import { Select } from "antd";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
interface RoutingStrategySelectorProps {
|
||||
selectedStrategy: string | null;
|
||||
availableStrategies: string[];
|
||||
routingStrategyDescriptions: { [key: string]: string };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
routerFieldsMetadata: { [key: string]: any };
|
||||
onStrategyChange: (strategy: string) => void;
|
||||
}
|
||||
|
|
@ -19,32 +26,38 @@ const RoutingStrategySelector: React.FC<RoutingStrategySelectorProps> = ({
|
|||
return (
|
||||
<div className="space-y-2 max-w-3xl">
|
||||
<div>
|
||||
<label className="text-xs font-medium text-gray-700 uppercase tracking-wide">
|
||||
{routerFieldsMetadata["routing_strategy"]?.ui_field_name || "Routing Strategy"}
|
||||
<label className="text-xs font-medium text-foreground uppercase tracking-wide">
|
||||
{routerFieldsMetadata["routing_strategy"]?.ui_field_name ||
|
||||
"Routing Strategy"}
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 mt-0.5 mb-2">
|
||||
<p className="text-xs text-muted-foreground mt-0.5 mb-2">
|
||||
{routerFieldsMetadata["routing_strategy"]?.field_description || ""}
|
||||
</p>
|
||||
</div>
|
||||
<div className="routing-strategy-select max-w-3xl">
|
||||
<Select
|
||||
value={selectedStrategy}
|
||||
onChange={onStrategyChange}
|
||||
style={{ width: "100%" }}
|
||||
size="large"
|
||||
value={selectedStrategy ?? undefined}
|
||||
onValueChange={onStrategyChange}
|
||||
>
|
||||
{availableStrategies.map((strategy) => (
|
||||
<Select.Option key={strategy} value={strategy} label={strategy}>
|
||||
<div className="flex flex-col gap-0.5 py-1">
|
||||
<span className="font-mono text-sm font-medium">{strategy}</span>
|
||||
{routingStrategyDescriptions[strategy] && (
|
||||
<span className="text-xs text-gray-500 font-normal">
|
||||
{routingStrategyDescriptions[strategy]}
|
||||
<SelectTrigger className="w-full h-10">
|
||||
<SelectValue placeholder="Select strategy" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableStrategies.map((strategy) => (
|
||||
<SelectItem key={strategy} value={strategy}>
|
||||
<div className="flex flex-col gap-0.5 py-1">
|
||||
<span className="font-mono text-sm font-medium">
|
||||
{strategy}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
{routingStrategyDescriptions[strategy] && (
|
||||
<span className="text-xs text-muted-foreground font-normal">
|
||||
{routingStrategyDescriptions[strategy]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -52,4 +65,3 @@ const RoutingStrategySelector: React.FC<RoutingStrategySelectorProps> = ({
|
|||
};
|
||||
|
||||
export default RoutingStrategySelector;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import React from "react";
|
||||
import { Switch } from "antd";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
interface TagFilteringToggleProps {
|
||||
enabled: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
routerFieldsMetadata: { [key: string]: any };
|
||||
onToggle: (enabled: boolean) => void;
|
||||
}
|
||||
|
|
@ -16,11 +17,13 @@ const TagFilteringToggle: React.FC<TagFilteringToggleProps> = ({
|
|||
<div className="space-y-3 max-w-3xl">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<label className="text-xs font-medium text-gray-700 uppercase tracking-wide">
|
||||
{routerFieldsMetadata["enable_tag_filtering"]?.ui_field_name || "Enable Tag Filtering"}
|
||||
<label className="text-xs font-medium text-foreground uppercase tracking-wide">
|
||||
{routerFieldsMetadata["enable_tag_filtering"]?.ui_field_name ||
|
||||
"Enable Tag Filtering"}
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
{routerFieldsMetadata["enable_tag_filtering"]?.field_description || ""}
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{routerFieldsMetadata["enable_tag_filtering"]?.field_description ||
|
||||
""}
|
||||
{routerFieldsMetadata["enable_tag_filtering"]?.link && (
|
||||
<>
|
||||
{" "}
|
||||
|
|
@ -28,7 +31,7 @@ const TagFilteringToggle: React.FC<TagFilteringToggleProps> = ({
|
|||
href={routerFieldsMetadata["enable_tag_filtering"].link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:text-blue-800 underline"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
|
|
@ -38,7 +41,7 @@ const TagFilteringToggle: React.FC<TagFilteringToggleProps> = ({
|
|||
</div>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={onToggle}
|
||||
onCheckedChange={onToggle}
|
||||
className="ml-4"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -47,4 +50,3 @@ const TagFilteringToggle: React.FC<TagFilteringToggleProps> = ({
|
|||
};
|
||||
|
||||
export default TagFilteringToggle;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue