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:
Cursor Agent 2026-04-23 11:21:53 +00:00
parent 584b26c170
commit 30f6ef0d95
No known key found for this signature in database
6 changed files with 153 additions and 94 deletions

View file

@ -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>
</>
);

View file

@ -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>
</>
);
};

View file

@ -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>

View file

@ -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>
</>
);
};

View file

@ -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;

View file

@ -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;