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:
Cursor Agent 2026-04-23 12:00:14 +00:00
parent 2980e65157
commit ca3226bc42
No known key found for this signature in database
4 changed files with 141 additions and 109 deletions

View file

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

View file

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

View file

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

View file

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