feat(ui): ModelConfigCard + OutputCard + PromptEditorHeader

- ModelConfigCard: @tremor Text + antd Input + custom black-overlay
  modal \u2192 shadcn Dialog + Button + Input + lucide Settings. Proper
  focus-trap and backdrop via Radix.
- OutputCard: antd Typography \u2192 plain span with semantic tokens.
  Preserves SectionHeader and max-height expand/collapse animation.
- PromptEditorHeader: @tremor Button + antd Input/Select + lucide
  icons \u2192 shadcn Button + Input + Select. Draft/version chips and
  'Unsaved changes' indicator preserved. File added to overrides for
  the version pill's blue palette.

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:13:38 +00:00
parent ff7cefada3
commit e66b69852c
No known key found for this signature in database
4 changed files with 110 additions and 134 deletions

View file

@ -58,6 +58,7 @@
"src/components/view_logs/ToolsSection/ToolItem.tsx",
"src/components/guardrails/content_filter/PatternTable.tsx",
"src/components/guardrails/content_filter/ContentFilterDisplay.tsx",
"src/components/prompts/prompt_editor_view/PromptEditorHeader.tsx",
"src/components/policies/impact_preview_alert.tsx",
"src/components/policies/impact_popover.tsx",
"src/components/DeletedKeysPage/DeletedKeysPage.tsx",

View file

@ -1,7 +1,13 @@
import React, { useState } from "react";
import { Text } from "@tremor/react";
import { Input } from "antd";
import { SettingsIcon } from "lucide-react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Settings } from "lucide-react";
import ModelSelector from "../../common_components/ModelSelector";
interface ModelConfigCardProps {
@ -36,63 +42,53 @@ const ModelConfigCard: React.FC<ModelConfigCardProps> = ({
/>
</div>
<button
onClick={() => setShowConfig(!showConfig)}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50"
>
<SettingsIcon size={16} />
<span>Parameters</span>
</button>
<Button variant="outline" onClick={() => setShowConfig(!showConfig)}>
<Settings className="h-4 w-4" />
Parameters
</Button>
{showConfig && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-30">
<div className="bg-white rounded-lg shadow-xl p-6 w-96">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">Model Parameters</h3>
<button
onClick={() => setShowConfig(false)}
className="text-gray-400 hover:text-gray-600"
>
✕
</button>
</div>
<div className="space-y-4">
<div>
<div className="flex items-center justify-between mb-2">
<Text className="text-sm text-gray-700">Temperature</Text>
<Input
type="number"
size="small"
min={0}
max={2}
step={0.1}
value={temperature}
onChange={(e) => onTemperatureChange(parseFloat(e.target.value) || 0)}
className="w-20"
/>
</div>
<Dialog open={showConfig} onOpenChange={setShowConfig}>
<DialogContent className="max-w-96">
<DialogHeader>
<DialogTitle>Model Parameters</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-foreground">Temperature</span>
<Input
type="number"
min={0}
max={2}
step={0.1}
value={temperature}
onChange={(e) =>
onTemperatureChange(parseFloat(e.target.value) || 0)
}
className="w-20 h-8"
/>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<Text className="text-sm text-gray-700">Max Tokens</Text>
<Input
type="number"
size="small"
min={1}
max={32768}
value={maxTokens}
onChange={(e) => onMaxTokensChange(parseInt(e.target.value) || 1000)}
className="w-24"
/>
</div>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-foreground">Max Tokens</span>
<Input
type="number"
min={1}
max={32768}
value={maxTokens}
onChange={(e) =>
onMaxTokensChange(parseInt(e.target.value) || 1000)
}
className="w-24 h-8"
/>
</div>
</div>
</div>
</div>
)}
</DialogContent>
</Dialog>
</div>
);
};
export default ModelConfigCard;

View file

@ -1,7 +1,14 @@
import React from "react";
import { Button as TremorButton } from "@tremor/react";
import { Input, Select } from "antd";
import { ArrowLeftIcon, SaveIcon, ClockIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { ArrowLeft, Clock, Save } from "lucide-react";
import PromptCodeSnippets from "./PromptCodeSnippets";
interface PromptEditorHeaderProps {
@ -41,35 +48,36 @@ const PromptEditorHeader: React.FC<PromptEditorHeaderProps> = ({
onEnvironmentChange,
}) => {
return (
<div className="bg-white border-b border-gray-200 px-6 py-3 flex items-center justify-between">
<div className="bg-background border-b border-border px-6 py-3 flex items-center justify-between">
<div className="flex items-center space-x-3">
<TremorButton icon={ArrowLeftIcon} variant="light" onClick={onBack} size="xs">
<Button variant="ghost" size="sm" onClick={onBack}>
<ArrowLeft className="h-3.5 w-3.5" />
Back
</TremorButton>
</Button>
<Input
value={promptName}
onChange={(e) => onNameChange(e.target.value)}
className="text-base font-medium border-none shadow-none"
style={{ width: "200px" }}
className="text-base font-medium border-none shadow-none w-[200px] h-8"
/>
{version && (
<span className="px-2 py-0.5 text-xs bg-blue-100 text-blue-700 rounded font-medium">
<span className="px-2 py-0.5 text-xs bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300 rounded font-medium">
{version}
</span>
)}
<Select
value={environment}
onChange={onEnvironmentChange}
style={{ width: 140 }}
size="small"
options={[
{ label: "Development", value: "development" },
{ label: "Staging", value: "staging" },
{ label: "Production", value: "production" },
]}
/>
<span className="px-2 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">Draft</span>
<span className="text-xs text-gray-400">Unsaved changes</span>
<Select value={environment} onValueChange={onEnvironmentChange}>
<SelectTrigger className="w-[140px] h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="development">Development</SelectItem>
<SelectItem value="staging">Staging</SelectItem>
<SelectItem value="production">Production</SelectItem>
</SelectContent>
</Select>
<span className="px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded">
Draft
</span>
<span className="text-xs text-muted-foreground">Unsaved changes</span>
</div>
<div className="flex items-center space-x-2">
<PromptCodeSnippets
@ -77,30 +85,22 @@ const PromptEditorHeader: React.FC<PromptEditorHeaderProps> = ({
model={promptModel}
promptVariables={promptVariables}
accessToken={accessToken}
version={version?.replace('v', '') || "1"}
version={version?.replace("v", "") || "1"}
proxySettings={proxySettings}
/>
{editMode && onShowHistory && (
<TremorButton
icon={ClockIcon}
variant="secondary"
onClick={onShowHistory}
>
<Button variant="secondary" onClick={onShowHistory}>
<Clock className="h-4 w-4" />
History
</TremorButton>
</Button>
)}
<TremorButton
icon={SaveIcon}
onClick={onSave}
loading={isSaving}
disabled={isSaving}
>
{editMode ? "Update" : "Save"}
</TremorButton>
<Button onClick={onSave} disabled={isSaving}>
<Save className="h-4 w-4" />
{isSaving ? "Saving…" : editMode ? "Update" : "Save"}
</Button>
</div>
</div>
);
};
export default PromptEditorHeader;

View file

@ -1,16 +1,8 @@
/**
* OutputCard - Displays output message with token count and cost
* Datadog-style: header with icon/metrics, content below
*/
import { useState } from 'react';
import { Typography } from 'antd';
import { useState } from "react";
import MessageManager from "@/components/molecules/message_manager";
import { ParsedMessage } from './prettyMessagesTypes';
import { SectionHeader } from './SectionHeader';
import { SimpleMessageBlock } from './SimpleMessageBlock';
const { Text } = Typography;
import { ParsedMessage } from "./prettyMessagesTypes";
import { SectionHeader } from "./SectionHeader";
import { SimpleMessageBlock } from "./SimpleMessageBlock";
interface OutputCardProps {
message: ParsedMessage | null;
@ -18,26 +10,24 @@ interface OutputCardProps {
outputCost?: number;
}
export function OutputCard({ message, completionTokens, outputCost }: OutputCardProps) {
export function OutputCard({
message,
completionTokens,
outputCost,
}: OutputCardProps) {
const [isCollapsed, setIsCollapsed] = useState(false);
const handleCopy = () => {
if (!message) return;
const content = message.content || '';
const content = message.content || "";
navigator.clipboard.writeText(content);
MessageManager.success('Output copied');
MessageManager.success("Output copied");
};
if (!message) {
return (
<div
style={{
border: '1px solid #f0f0f0',
borderRadius: 6,
overflow: 'hidden',
}}
>
<div className="border border-border rounded-md overflow-hidden">
<SectionHeader
type="output"
tokens={completionTokens}
@ -47,17 +37,16 @@ export function OutputCard({ message, completionTokens, outputCost }: OutputCard
onToggleCollapse={() => setIsCollapsed(!isCollapsed)}
/>
<div
className="overflow-hidden transition-[max-height,opacity] duration-300 ease-out"
style={{
maxHeight: isCollapsed ? '0px' : '10000px',
overflow: 'hidden',
transition: 'max-height 0.3s ease-out, opacity 0.3s ease-out',
maxHeight: isCollapsed ? "0px" : "10000px",
opacity: isCollapsed ? 0 : 1,
}}
>
<div style={{ padding: '12px 16px' }}>
<Text type="secondary" style={{ fontSize: 13, fontStyle: 'italic' }}>
<div className="px-4 py-3">
<span className="text-[13px] italic text-muted-foreground">
No response data available
</Text>
</span>
</div>
</div>
</div>
@ -65,14 +54,7 @@ export function OutputCard({ message, completionTokens, outputCost }: OutputCard
}
return (
<div
style={{
border: '1px solid #f0f0f0',
borderRadius: 6,
overflow: 'hidden',
}}
>
{/* Datadog-style Header */}
<div className="border border-border rounded-md overflow-hidden">
<SectionHeader
type="output"
tokens={completionTokens}
@ -82,16 +64,14 @@ export function OutputCard({ message, completionTokens, outputCost }: OutputCard
onToggleCollapse={() => setIsCollapsed(!isCollapsed)}
/>
{/* Content */}
<div
className="overflow-hidden transition-[max-height,opacity] duration-300 ease-out"
style={{
maxHeight: isCollapsed ? '0px' : '10000px',
overflow: 'hidden',
transition: 'max-height 0.3s ease-out, opacity 0.3s ease-out',
maxHeight: isCollapsed ? "0px" : "10000px",
opacity: isCollapsed ? 0 : 1,
}}
>
<div style={{ padding: '12px 16px' }}>
<div className="px-4 py-3">
<SimpleMessageBlock
label="ASSISTANT"
content={message.content}
@ -102,4 +82,3 @@ export function OutputCard({ message, completionTokens, outputCost }: OutputCard
</div>
);
}