feat(ui): ReasoningContent + compare ModelSelector + CreateProjectModal

- ReasoningContent: antd Button + @ant-design BulbOutlined/Down/Right
  \u2192 shadcn Button variant=ghost + lucide Lightbulb/Chevron. Code
  blocks use semantic bg-muted; Markdown renderer unchanged.
- compareUI/ModelSelector: antd Select + @tremor TextInput \u2192 shadcn
  Select + Input. 'Add custom model' option triggers inline input
  with Enter-to-commit and blur-to-commit.
- CreateProjectModal: antd Modal/Typography/Button + @ant-design
  FolderAddOutlined \u2192 shadcn Dialog + Button + lucide FolderPlus.
  Inner ProjectBaseForm (antd Form) is preserved.

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:52:38 +00:00
parent baefdd512c
commit 2d876bfb8f
No known key found for this signature in database
3 changed files with 90 additions and 56 deletions

View file

@ -1,5 +1,13 @@
import { Modal, Form, Button, Typography } from "antd";
import { FolderAddOutlined } from "@ant-design/icons";
import { FolderPlus } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Form } from "antd";
import MessageManager from "@/components/molecules/message_manager";
import {
useCreateProject,
@ -52,32 +60,30 @@ export function CreateProjectModal({
};
return (
<Modal
title={
<Typography.Text strong style={{ fontSize: 18 }}>
Create New Project
</Typography.Text>
}
<Dialog
open={isOpen}
onCancel={handleCancel}
width={720}
destroyOnHidden
footer={[
<Button key="cancel" onClick={handleCancel}>
Cancel
</Button>,
<Button
key="submit"
type="primary"
icon={<FolderAddOutlined />}
loading={createMutation.isPending}
onClick={handleSubmit}
>
Create Project
</Button>,
]}
onOpenChange={(o) => (!o ? handleCancel() : undefined)}
>
<ProjectBaseForm form={form} />
</Modal>
<DialogContent className="max-w-[720px]">
<DialogHeader>
<DialogTitle className="text-lg font-semibold">
Create New Project
</DialogTitle>
</DialogHeader>
<ProjectBaseForm form={form} />
<DialogFooter>
<Button variant="outline" onClick={handleCancel}>
Cancel
</Button>
<Button
onClick={handleSubmit}
disabled={createMutation.isPending}
>
<FolderPlus className="h-4 w-4" />
{createMutation.isPending ? "Creating…" : "Create Project"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View file

@ -1,15 +1,17 @@
import React, { useState } from "react";
import { Button } from "antd";
import { Button } from "@/components/ui/button";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { coy } from "react-syntax-highlighter/dist/esm/styles/prism";
import { DownOutlined, RightOutlined, BulbOutlined } from "@ant-design/icons";
import { ChevronDown, ChevronRight, Lightbulb } from "lucide-react";
interface ReasoningContentProps {
reasoningContent: string;
}
const ReasoningContent: React.FC<ReasoningContentProps> = ({ reasoningContent }) => {
const ReasoningContent: React.FC<ReasoningContentProps> = ({
reasoningContent,
}) => {
const [isExpanded, setIsExpanded] = useState(true);
if (!reasoningContent) return null;
@ -17,32 +19,38 @@ const ReasoningContent: React.FC<ReasoningContentProps> = ({ reasoningContent })
return (
<div className="reasoning-content mt-1 mb-2">
<Button
type="text"
className="flex items-center text-xs text-gray-500 hover:text-gray-700"
variant="ghost"
size="sm"
className="flex items-center text-xs text-muted-foreground hover:text-foreground"
onClick={() => setIsExpanded(!isExpanded)}
icon={<BulbOutlined />}
>
<Lightbulb className="h-3.5 w-3.5" />
{isExpanded ? "Hide reasoning" : "Show reasoning"}
{isExpanded ? <DownOutlined className="ml-1" /> : <RightOutlined className="ml-1" />}
{isExpanded ? (
<ChevronDown className="h-3 w-3 ml-1" />
) : (
<ChevronRight className="h-3 w-3 ml-1" />
)}
</Button>
{isExpanded && (
<div className="mt-2 p-3 bg-gray-50 border border-gray-200 rounded-md text-sm text-gray-700">
<div className="mt-2 p-3 bg-muted border border-border rounded-md text-sm text-foreground">
<ReactMarkdown
components={{
code({
node,
inline,
className,
children,
...props
}: React.ComponentPropsWithoutRef<"code"> & {
inline?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
node?: any;
}) {
const match = /language-(\w+)/.exec(className || "");
return !inline && match ? (
<SyntaxHighlighter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
style={coy as any}
language={match[1]}
PreTag="div"
@ -52,7 +60,10 @@ const ReasoningContent: React.FC<ReasoningContentProps> = ({ reasoningContent })
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code className={`${className} px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono`} {...props}>
<code
className={`${className} px-1.5 py-0.5 rounded bg-muted text-sm font-mono`}
{...props}
>
{children}
</code>
);

View file

@ -1,6 +1,13 @@
import React, { useMemo, useState } from "react";
import { Select } from "antd";
import { TextInput } from "@tremor/react";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
interface ModelSelectorProps {
value: string;
onChange: (value: string) => void;
@ -8,7 +15,14 @@ interface ModelSelectorProps {
loading?: boolean;
disabled?: boolean;
}
export function ModelSelector({ value, onChange, models, loading, disabled }: ModelSelectorProps) {
export function ModelSelector({
value,
onChange,
models,
loading,
disabled,
}: ModelSelectorProps) {
const [isAddingCustom, setIsAddingCustom] = useState(false);
const [customValue, setCustomValue] = useState("");
@ -48,31 +62,34 @@ export function ModelSelector({ value, onChange, models, loading, disabled }: Mo
setIsAddingCustom(false);
setCustomValue("");
};
return (
<div className="flex-1 min-w-0">
<Select<string>
<Select
value={selectValue}
onChange={handleSelectChange}
disabled={disabled}
loading={loading}
placeholder={loading ? "Loading models..." : "Select a model"}
className="w-full rounded-md"
showSearch
optionFilterProp="children"
onValueChange={handleSelectChange}
disabled={disabled || loading}
>
{displayOptions.map((model) => (
<Select.Option key={model} value={model}>
{model}
</Select.Option>
))}
<Select.Option value="__custom__">+ Add custom model</Select.Option>
<SelectTrigger className="w-full">
<SelectValue
placeholder={loading ? "Loading models..." : "Select a model"}
/>
</SelectTrigger>
<SelectContent>
{displayOptions.map((model) => (
<SelectItem key={model} value={model}>
{model}
</SelectItem>
))}
<SelectItem value="__custom__">+ Add custom model</SelectItem>
</SelectContent>
</Select>
{isAddingCustom && (
<TextInput
<Input
className="mt-2"
placeholder="Custom Model Name (Enter to add)"
value={customValue}
onValueChange={setCustomValue}
onChange={(e) => setCustomValue(e.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();