mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
feat(ui): conversation MessageInput + CodeInterpreterTool + tool_modal + OnboardingFormBody
- conversation_panel/MessageInput: antd Input.TextArea + @tremor Button + @ant-design ArrowUpOutlined \u2192 shadcn Textarea + Button (icon round) + lucide ArrowUp. Loading-state Cancel button uses destructive-tinted outline. - CodeInterpreterTool: antd Switch/Tooltip + @tremor Text + @ant-design Code/InfoCircle/Exclamation \u2192 shadcn Switch/Tooltip + lucide Code2/Info/AlertTriangle. Preserves the blue\u2192purple gradient background for the active tool row. - prompts/tool_modal: antd Modal/Button + raw textarea \u2192 shadcn Dialog + Button + Textarea. JSON validation logic unchanged. - OnboardingFormBody: antd Alert/Card/Button/Typography \u2192 shadcn Card/Button + semantic alert tiles (info/destructive) + lucide Info. Inner antd Form is preserved (parent-controlled). Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
2980e65157
commit
ca3226bc42
4 changed files with 141 additions and 109 deletions
|
|
@ -1,5 +1,8 @@
|
|||
import React from "react";
|
||||
import { Alert, Button, Card, Form, Input, Typography } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Info } from "lucide-react";
|
||||
import { Form, Input } from "antd";
|
||||
|
||||
type OnboardingFormBodyProps = {
|
||||
variant: "signup" | "reset_password";
|
||||
|
|
@ -24,42 +27,46 @@ export function OnboardingFormBody({
|
|||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md mt-10">
|
||||
<Card>
|
||||
<Typography.Title level={5} className="text-center mb-5">
|
||||
<Card className="p-6">
|
||||
<h5 className="text-center mb-5 text-base font-semibold">
|
||||
🚅 LiteLLM
|
||||
</Typography.Title>
|
||||
<Typography.Title level={3}>
|
||||
</h5>
|
||||
<h3 className="text-xl font-semibold">
|
||||
{variant === "reset_password" ? "Reset Password" : "Sign Up"}
|
||||
</Typography.Title>
|
||||
<Typography.Text>
|
||||
</h3>
|
||||
<p className="text-sm mt-1">
|
||||
{variant === "reset_password"
|
||||
? "Reset your password to access Admin UI."
|
||||
: "Claim your user account to login to Admin UI."}
|
||||
</Typography.Text>
|
||||
</p>
|
||||
|
||||
{variant === "signup" && (
|
||||
<Alert
|
||||
className="mt-4"
|
||||
type="info"
|
||||
message="SSO"
|
||||
description={
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="mt-4 flex gap-2 items-start p-3 rounded-md bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-900 text-blue-800 dark:text-blue-200">
|
||||
<Info className="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<div className="flex-1">
|
||||
<div className="font-semibold">SSO</div>
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span>SSO is under the Enterprise Tier.</span>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
href="https://forms.gle/W3U4PZpJGFHWtHyA9"
|
||||
target="_blank"
|
||||
>
|
||||
Get Free Trial
|
||||
<Button asChild size="sm">
|
||||
<a
|
||||
href="https://forms.gle/W3U4PZpJGFHWtHyA9"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Get Free Trial
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
showIcon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form className="mt-10 mb-5" layout="vertical" form={form} onFinish={(values) => onSubmit({ password: values.password })}>
|
||||
<Form
|
||||
className="mt-10 mb-5"
|
||||
layout="vertical"
|
||||
form={form}
|
||||
onFinish={(values) => onSubmit({ password: values.password })}
|
||||
>
|
||||
<Form.Item label="Email Address" name="user_email">
|
||||
<Input type="email" disabled />
|
||||
</Form.Item>
|
||||
|
|
@ -67,7 +74,9 @@ export function OnboardingFormBody({
|
|||
<Form.Item
|
||||
label="Password"
|
||||
name="password"
|
||||
rules={[{ required: true, message: "password required to sign up" }]}
|
||||
rules={[
|
||||
{ required: true, message: "password required to sign up" },
|
||||
]}
|
||||
help={
|
||||
variant === "reset_password"
|
||||
? "Enter your new password"
|
||||
|
|
@ -78,12 +87,19 @@ export function OnboardingFormBody({
|
|||
</Form.Item>
|
||||
|
||||
{claimError && (
|
||||
<Alert type="error" message={claimError} showIcon className="mb-4" />
|
||||
<div className="flex gap-2 items-start p-3 rounded-md bg-destructive/10 border border-destructive/30 text-destructive mb-4">
|
||||
<Info className="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<div className="text-sm">{claimError}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-10">
|
||||
<Button htmlType="submit" loading={isPending}>
|
||||
{variant === "reset_password" ? "Reset Password" : "Sign Up"}
|
||||
<Button type="submit" disabled={isPending}>
|
||||
{isPending
|
||||
? "…"
|
||||
: variant === "reset_password"
|
||||
? "Reset Password"
|
||||
: "Sign Up"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import React from "react";
|
||||
import { Switch, Tooltip } from "antd";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
import { CodeOutlined, InfoCircleOutlined, ExclamationCircleOutlined } from "@ant-design/icons";
|
||||
import { Text } from "@tremor/react";
|
||||
import { AlertTriangle, Code2, Info } from "lucide-react";
|
||||
|
||||
interface CodeInterpreterToolProps {
|
||||
accessToken: string;
|
||||
|
|
@ -14,7 +19,8 @@ interface CodeInterpreterToolProps {
|
|||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const GITHUB_FEATURE_REQUEST_URL = "https://github.com/BerriAI/litellm/issues/new?template=feature_request.yml";
|
||||
const GITHUB_FEATURE_REQUEST_URL =
|
||||
"https://github.com/BerriAI/litellm/issues/new?template=feature_request.yml";
|
||||
|
||||
const isOpenAIModel = (model: string): boolean => {
|
||||
if (!model) return false;
|
||||
|
|
@ -39,42 +45,52 @@ const CodeInterpreterTool: React.FC<CodeInterpreterToolProps> = ({
|
|||
|
||||
const handleToggle = (checked: boolean) => {
|
||||
if (checked && !isOpenAI) {
|
||||
MessageManager.warning("Code Interpreter is only available for OpenAI models");
|
||||
MessageManager.warning(
|
||||
"Code Interpreter is only available for OpenAI models",
|
||||
);
|
||||
return;
|
||||
}
|
||||
onEnabledChange(checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border border-gray-200 rounded-lg p-3 bg-gradient-to-r from-blue-50 to-purple-50">
|
||||
<div className="border border-border rounded-lg p-3 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-950/30 dark:to-purple-950/30">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<CodeOutlined className="text-blue-500" />
|
||||
<Text className="font-medium text-gray-700">Code Interpreter</Text>
|
||||
<Tooltip title="Run Python code to generate files, charts, and analyze data. Container is created automatically.">
|
||||
<InfoCircleOutlined className="text-gray-400 text-xs" />
|
||||
</Tooltip>
|
||||
<Code2 className="h-4 w-4 text-blue-500 dark:text-blue-400" />
|
||||
<span className="font-medium text-foreground">Code Interpreter</span>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3 w-3 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
Run Python code to generate files, charts, and analyze data.
|
||||
Container is created automatically.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Switch
|
||||
checked={enabled && isOpenAI}
|
||||
onChange={handleToggle}
|
||||
onCheckedChange={handleToggle}
|
||||
disabled={isDisabled}
|
||||
size="small"
|
||||
className={enabled && isOpenAI ? "bg-blue-500" : ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{!isOpenAI && (
|
||||
<div className="mt-2 pt-2 border-t border-gray-200">
|
||||
<div className="mt-2 pt-2 border-t border-border">
|
||||
<div className="flex items-start gap-2">
|
||||
<ExclamationCircleOutlined className="text-amber-500 mt-0.5" />
|
||||
<div className="text-xs text-gray-600">
|
||||
<span>Code Interpreter is currently only supported for OpenAI models. </span>
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-amber-500 mt-0.5" />
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<span>
|
||||
Code Interpreter is currently only supported for OpenAI models.{" "}
|
||||
</span>
|
||||
<a
|
||||
href={GITHUB_FEATURE_REQUEST_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:text-blue-800 underline"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Request support for other providers
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import React from "react";
|
||||
import { ArrowUpOutlined } from "@ant-design/icons";
|
||||
import { Button as TremorButton } from "@tremor/react";
|
||||
import { Input } from "antd";
|
||||
|
||||
const { TextArea } = Input;
|
||||
import { ArrowUp } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
interface MessageInputProps {
|
||||
inputMessage: string;
|
||||
|
|
@ -26,46 +24,38 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||
}) => {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center flex-1 bg-white border border-gray-300 rounded-xl px-3 py-1 min-h-[44px]">
|
||||
<TextArea
|
||||
<div className="flex items-center flex-1 bg-background border border-border rounded-xl px-3 py-1 min-h-[44px]">
|
||||
<Textarea
|
||||
value={inputMessage}
|
||||
onChange={(e) => onInputChange(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder="Type your message... (Shift+Enter for new line)"
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
style={{
|
||||
resize: "none",
|
||||
border: "none",
|
||||
boxShadow: "none",
|
||||
background: "transparent",
|
||||
padding: "4px 0",
|
||||
fontSize: "14px",
|
||||
lineHeight: "20px",
|
||||
}}
|
||||
rows={1}
|
||||
className="flex-1 resize-none border-0 shadow-none bg-transparent p-0 py-1 text-sm leading-5 min-h-[20px]"
|
||||
/>
|
||||
|
||||
<TremorButton
|
||||
<Button
|
||||
onClick={onSend}
|
||||
disabled={isDisabled}
|
||||
className="flex-shrink-0 ml-2 !w-8 !h-8 !min-w-8 !p-0 !rounded-full !bg-blue-600 hover:!bg-blue-700 disabled:!bg-gray-300 !border-none !text-white disabled:!text-gray-500 !flex !items-center !justify-center"
|
||||
size="icon"
|
||||
className="flex-shrink-0 ml-2 h-8 w-8 rounded-full"
|
||||
>
|
||||
<ArrowUpOutlined style={{ fontSize: "14px" }} />
|
||||
</TremorButton>
|
||||
<ArrowUp className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isLoading && (
|
||||
<TremorButton
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
className="bg-red-50 hover:bg-red-100 text-red-600 border-red-200"
|
||||
className="bg-destructive/10 hover:bg-destructive/20 text-destructive border-destructive/20"
|
||||
>
|
||||
Cancel
|
||||
</TremorButton>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageInput;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import React, { useState } from "react";
|
||||
import { Modal, Button } from "antd";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
interface ToolModalProps {
|
||||
visible: boolean;
|
||||
|
|
@ -30,7 +38,12 @@ const defaultToolJson = `{
|
|||
}
|
||||
}`;
|
||||
|
||||
const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onClose }) => {
|
||||
const ToolModal: React.FC<ToolModalProps> = ({
|
||||
visible,
|
||||
initialJson,
|
||||
onSave,
|
||||
onClose,
|
||||
}) => {
|
||||
const [json, setJson] = useState(initialJson || defaultToolJson);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -39,7 +52,8 @@ const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onC
|
|||
JSON.parse(json);
|
||||
setError(null);
|
||||
onSave(json);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (_e) {
|
||||
setError("Invalid JSON format. Please check your syntax.");
|
||||
}
|
||||
};
|
||||
|
|
@ -50,40 +64,36 @@ const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onC
|
|||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-lg font-medium">Add Tool</span>
|
||||
</div>
|
||||
}
|
||||
<Dialog
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
width={800}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button key="save" type="primary" onClick={handleSave}>
|
||||
Add
|
||||
</Button>,
|
||||
]}
|
||||
onOpenChange={(o) => (!o ? handleClose() : undefined)}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded text-red-600 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<textarea
|
||||
value={json}
|
||||
onChange={(e) => setJson(e.target.value)}
|
||||
className="w-full min-h-[400px] px-4 py-3 border border-gray-300 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
placeholder="Paste your tool JSON here..."
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
<DialogContent className="max-w-[800px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-medium">Add Tool</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
{error && (
|
||||
<div className="p-3 bg-destructive/10 border border-destructive/30 rounded text-destructive text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<Textarea
|
||||
value={json}
|
||||
onChange={(e) => setJson(e.target.value)}
|
||||
className="w-full min-h-[400px] font-mono text-sm resize-none"
|
||||
placeholder="Paste your tool JSON here..."
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave}>Add</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToolModal;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue