mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
feat(ui): SearchResultsDisplay + PromptMessagesCard + LoggingCallbacksTable
- SearchResultsDisplay: antd Button + @ant-design Database/FileText/ Chevrons \u2192 shadcn Button/Badge + lucide equivalents. Blue score chip added to categorical overrides. - PromptMessagesCard: @tremor Card/Text + antd Select \u2192 shadcn Card + Select. Drag-and-drop reorder preserved (pure HTML5 drag handlers, no antd dependency). - LoggingCallbacksTable: @tremor Button + antd Table/Title \u2192 shadcn Button + shadcn Table + plain h3. Categorical success/failure/info badges added to overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
db992fba74
commit
fa9d5ae6d9
4 changed files with 234 additions and 151 deletions
|
|
@ -62,6 +62,8 @@
|
|||
"src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx",
|
||||
"src/components/prompts/prompt_editor_view/conversation_panel/MessageBubble.tsx",
|
||||
"src/components/playground/chat_ui/SessionManagement.tsx",
|
||||
"src/components/playground/chat_ui/SearchResultsDisplay.tsx",
|
||||
"src/components/Settings/LoggingAndAlerts/LoggingCallbacks/LoggingCallbacksTable.tsx",
|
||||
"src/components/policies/impact_preview_alert.tsx",
|
||||
"src/components/policies/impact_popover.tsx",
|
||||
"src/components/DeletedKeysPage/DeletedKeysPage.tsx",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { Button } from "@tremor/react";
|
||||
import type { TableProps } from "antd";
|
||||
import { Table } from "antd";
|
||||
import Title from "antd/es/typography/Title";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import React from "react";
|
||||
import TableIconActionButton from "../../../common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
|
||||
import { AlertingObject } from "./types";
|
||||
|
|
@ -41,81 +47,96 @@ export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
|
|||
onDelete = () => {},
|
||||
onAdd = () => {},
|
||||
}) => {
|
||||
const columns: TableProps<CallbackRow>["columns"] = [
|
||||
{
|
||||
title: <span className="font-medium text-gray-700">Callback Name</span>,
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
render: (_: string, record: CallbackRow) => {
|
||||
const id = record.name;
|
||||
console.log("availableCallbacks", availableCallbacks);
|
||||
const displayName = availableCallbacks[id]?.ui_callback_name || id;
|
||||
return <div className="font-medium text-gray-800">{displayName}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: <span className="font-medium text-gray-700">Mode</span>,
|
||||
key: "mode",
|
||||
render: (_: unknown, record: CallbackRow) => {
|
||||
const mode = record.mode || "success";
|
||||
const label = CALLBACK_MODES.find((m) => m.value === mode)?.label || mode;
|
||||
const badgeClass =
|
||||
mode === "success"
|
||||
? "bg-green-100 text-green-800"
|
||||
: mode === "failure"
|
||||
? "bg-red-100 text-red-800"
|
||||
: "bg-blue-100 text-blue-800";
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${badgeClass}`}>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
title: <span className="font-medium text-gray-700 text-right w-full block">Actions</span>,
|
||||
key: "actions",
|
||||
align: "right",
|
||||
render: (_: unknown, record: CallbackRow) => (
|
||||
<div className="flex justify-end gap-2">
|
||||
<TableIconActionButton variant="Test" tooltipText="Test Callback" onClick={() => onTest(record)} />
|
||||
<TableIconActionButton variant="Edit" tooltipText="Edit Callback" onClick={() => onEdit(record)} />
|
||||
<TableIconActionButton variant="Delete" tooltipText="Delete Callback" onClick={() => onDelete(record)} />
|
||||
</div>
|
||||
),
|
||||
width: 240,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<div className="w-full mt-4">
|
||||
<Button onClick={onAdd} className="mx-auto">
|
||||
+ Add Callback
|
||||
</Button>
|
||||
<div className="flex justify-between items-center my-2">
|
||||
<Title level={4}>Active Logging Callbacks</Title>
|
||||
</div>
|
||||
{/* Empty state */}
|
||||
{callbacks.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center p-8 bg-gray-50 border border-gray-200 rounded-lg">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-medium text-gray-700 mb-2">No callbacks configured</h3>
|
||||
<p className="text-gray-500">Add your first callback to start logging data to external services.</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={callbacks as CallbackRow[]}
|
||||
rowKey={(record) => record.name}
|
||||
pagination={false}
|
||||
rowClassName={() => "hover:bg-gray-50"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mt-4">
|
||||
<Button onClick={onAdd} className="mx-auto">
|
||||
+ Add Callback
|
||||
</Button>
|
||||
<div className="flex justify-between items-center my-2">
|
||||
<h3 className="text-lg font-semibold">Active Logging Callbacks</h3>
|
||||
</div>
|
||||
</>
|
||||
{callbacks.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center p-8 bg-muted border border-border rounded-lg">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-medium text-foreground mb-2">
|
||||
No callbacks configured
|
||||
</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Add your first callback to start logging data to external
|
||||
services.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-background border border-border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="font-medium">Callback Name</TableHead>
|
||||
<TableHead className="font-medium w-[240px]">Mode</TableHead>
|
||||
<TableHead className="font-medium text-right w-[240px]">
|
||||
Actions
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{callbacks.map((cb) => {
|
||||
const record = cb as CallbackRow;
|
||||
const id = record.name;
|
||||
const displayName =
|
||||
availableCallbacks[id]?.ui_callback_name || id;
|
||||
const mode = record.mode || "success";
|
||||
const label =
|
||||
CALLBACK_MODES.find((m) => m.value === mode)?.label || mode;
|
||||
const badgeClass =
|
||||
mode === "success"
|
||||
? "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300"
|
||||
: mode === "failure"
|
||||
? "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-300"
|
||||
: "bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300";
|
||||
return (
|
||||
<TableRow key={record.name} className="hover:bg-muted">
|
||||
<TableCell>
|
||||
<div className="font-medium text-foreground">
|
||||
{displayName}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
||||
badgeClass,
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<TableIconActionButton
|
||||
variant="Test"
|
||||
tooltipText="Test Callback"
|
||||
onClick={() => onTest(record)}
|
||||
/>
|
||||
<TableIconActionButton
|
||||
variant="Edit"
|
||||
tooltipText="Edit Callback"
|
||||
onClick={() => onEdit(record)}
|
||||
/>
|
||||
<TableIconActionButton
|
||||
variant="Delete"
|
||||
tooltipText="Delete Callback"
|
||||
onClick={() => onDelete(record)}
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,26 @@
|
|||
import React, { useState } from "react";
|
||||
import { Button } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Database,
|
||||
FileText,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { VectorStoreSearchResponse } from "./types";
|
||||
import { DatabaseOutlined, FileTextOutlined, DownOutlined, RightOutlined } from "@ant-design/icons";
|
||||
|
||||
interface SearchResultsDisplayProps {
|
||||
searchResults: VectorStoreSearchResponse[];
|
||||
}
|
||||
|
||||
export function SearchResultsDisplay({ searchResults }: SearchResultsDisplayProps) {
|
||||
export function SearchResultsDisplay({
|
||||
searchResults,
|
||||
}: SearchResultsDisplayProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const [expandedResults, setExpandedResults] = useState<Record<string, boolean>>({});
|
||||
const [expandedResults, setExpandedResults] = useState<
|
||||
Record<string, boolean>
|
||||
>({});
|
||||
|
||||
if (!searchResults || searchResults.length === 0) {
|
||||
return null;
|
||||
|
|
@ -23,85 +34,114 @@ export function SearchResultsDisplay({ searchResults }: SearchResultsDisplayProp
|
|||
}));
|
||||
};
|
||||
|
||||
const totalResults = searchResults.reduce((sum, page) => sum + page.data.length, 0);
|
||||
const totalResults = searchResults.reduce(
|
||||
(sum, page) => sum + page.data.length,
|
||||
0,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="search-results-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={<DatabaseOutlined />}
|
||||
>
|
||||
<Database className="h-3.5 w-3.5" />
|
||||
{isExpanded ? "Hide sources" : `Show sources (${totalResults})`}
|
||||
{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">
|
||||
<div className="mt-2 p-3 bg-muted border border-border rounded-md text-sm">
|
||||
<div className="space-y-3">
|
||||
{searchResults.map((resultPage, pageIndex) => (
|
||||
<div key={pageIndex}>
|
||||
<div className="text-xs text-gray-600 mb-2 flex items-center gap-2">
|
||||
<div className="text-xs text-muted-foreground mb-2 flex items-center gap-2">
|
||||
<span className="font-medium">Query:</span>
|
||||
<span className="italic">"{resultPage.search_query}"</span>
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="text-gray-500">{resultPage.data.length} result{resultPage.data.length !== 1 ? 's' : ''}</span>
|
||||
<span className="italic">
|
||||
"{resultPage.search_query}"
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{resultPage.data.length} result
|
||||
{resultPage.data.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{resultPage.data.map((result, resultIndex) => {
|
||||
const isResultExpanded = expandedResults[`${pageIndex}-${resultIndex}`] || false;
|
||||
const isResultExpanded =
|
||||
expandedResults[`${pageIndex}-${resultIndex}`] || false;
|
||||
|
||||
return (
|
||||
<div key={resultIndex} className="border border-gray-200 rounded-md overflow-hidden bg-white">
|
||||
<div
|
||||
key={resultIndex}
|
||||
className="border border-border rounded-md overflow-hidden bg-background"
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-between p-2 cursor-pointer hover:bg-gray-50 transition-colors"
|
||||
className="flex items-center justify-between p-2 cursor-pointer hover:bg-muted transition-colors"
|
||||
onClick={() => toggleResult(pageIndex, resultIndex)}
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<svg
|
||||
className={`w-4 h-4 text-gray-400 transition-transform flex-shrink-0 ${isResultExpanded ? "transform rotate-90" : ""}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<FileTextOutlined className="text-gray-400 flex-shrink-0" style={{ fontSize: "12px" }} />
|
||||
<span className="text-xs font-medium text-gray-700 truncate">
|
||||
{result.filename || result.file_id || `Result ${resultIndex + 1}`}
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
"w-4 h-4 text-muted-foreground transition-transform flex-shrink-0",
|
||||
isResultExpanded && "rotate-90",
|
||||
)}
|
||||
/>
|
||||
<FileText className="w-3 h-3 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-xs font-medium text-foreground truncate">
|
||||
{result.filename ||
|
||||
result.file_id ||
|
||||
`Result ${resultIndex + 1}`}
|
||||
</span>
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-blue-100 text-blue-700 font-mono flex-shrink-0">
|
||||
<Badge className="bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300 text-xs font-mono flex-shrink-0">
|
||||
{result.score.toFixed(3)}
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isResultExpanded && (
|
||||
<div className="border-t border-gray-200 bg-white">
|
||||
<div className="border-t border-border bg-background">
|
||||
<div className="p-3 space-y-2">
|
||||
{result.content.map((content, contentIndex) => (
|
||||
<div key={contentIndex}>
|
||||
<div className="text-xs font-mono bg-gray-50 p-2 rounded text-gray-800 whitespace-pre-wrap break-words">
|
||||
<div className="text-xs font-mono bg-muted p-2 rounded text-foreground whitespace-pre-wrap break-words">
|
||||
{content.text}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{result.attributes && Object.keys(result.attributes).length > 0 && (
|
||||
<div className="mt-2 pt-2 border-t border-gray-100">
|
||||
<div className="text-xs text-gray-500 mb-1 font-medium">Metadata:</div>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(result.attributes).map(([key, value]) => (
|
||||
<div key={key} className="text-xs flex gap-2">
|
||||
<span className="text-gray-500 font-medium">{key}:</span>
|
||||
<span className="text-gray-700 font-mono break-all">{String(value)}</span>
|
||||
</div>
|
||||
))}
|
||||
{result.attributes &&
|
||||
Object.keys(result.attributes).length > 0 && (
|
||||
<div className="mt-2 pt-2 border-t border-border">
|
||||
<div className="text-xs text-muted-foreground mb-1 font-medium">
|
||||
Metadata:
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(result.attributes).map(
|
||||
([key, value]) => (
|
||||
<div
|
||||
key={key}
|
||||
className="text-xs flex gap-2"
|
||||
>
|
||||
<span className="text-muted-foreground font-medium">
|
||||
{key}:
|
||||
</span>
|
||||
<span className="text-foreground font-mono break-all">
|
||||
{String(value)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -117,4 +157,3 @@ export function SearchResultsDisplay({ searchResults }: SearchResultsDisplayProp
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,25 @@
|
|||
import React, { useState } from "react";
|
||||
import { Card, Text } from "@tremor/react";
|
||||
import { Select } from "antd";
|
||||
import { PlusIcon, TrashIcon, GripVerticalIcon } from "lucide-react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { GripVertical, Plus, Trash } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import VariableTextArea from "../variable_textarea";
|
||||
import { Message } from "./types";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
interface PromptMessagesCardProps {
|
||||
messages: Message[];
|
||||
onAddMessage: () => void;
|
||||
onUpdateMessage: (index: number, field: "role" | "content", value: string) => void;
|
||||
onUpdateMessage: (
|
||||
index: number,
|
||||
field: "role" | "content",
|
||||
value: string,
|
||||
) => void;
|
||||
onRemoveMessage: (index: number) => void;
|
||||
onMoveMessage: (fromIndex: number, toIndex: number) => void;
|
||||
}
|
||||
|
|
@ -51,10 +60,14 @@ const PromptMessagesCard: React.FC<PromptMessagesCardProps> = ({
|
|||
return (
|
||||
<Card className="p-3">
|
||||
<div className="mb-2">
|
||||
<Text className="text-sm font-medium">Prompt messages</Text>
|
||||
<Text className="text-gray-500 text-xs mt-1">
|
||||
Use <code className="bg-gray-100 px-1 rounded text-xs">{'{{variable}}'}</code> syntax for template variables
|
||||
</Text>
|
||||
<div className="text-sm font-medium">Prompt messages</div>
|
||||
<div className="text-muted-foreground text-xs mt-1">
|
||||
Use{" "}
|
||||
<code className="bg-muted px-1 rounded text-xs">
|
||||
{"{{variable}}"}
|
||||
</code>{" "}
|
||||
syntax for template variables
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{messages.map((message, index) => (
|
||||
|
|
@ -65,33 +78,41 @@ const PromptMessagesCard: React.FC<PromptMessagesCardProps> = ({
|
|||
onDragOver={(e) => handleDragOver(e, index)}
|
||||
onDrop={(e) => handleDrop(e, index)}
|
||||
onDragEnd={handleDragEnd}
|
||||
className={`border border-gray-300 rounded overflow-hidden bg-white transition-all ${
|
||||
draggedIndex === index ? "opacity-50" : ""
|
||||
} ${dragOverIndex === index && draggedIndex !== index ? "border-blue-500 border-2" : ""}`}
|
||||
className={cn(
|
||||
"border border-border rounded overflow-hidden bg-background transition-all",
|
||||
draggedIndex === index && "opacity-50",
|
||||
dragOverIndex === index &&
|
||||
draggedIndex !== index &&
|
||||
"border-primary border-2",
|
||||
)}
|
||||
>
|
||||
<div className="bg-gray-50 px-2 py-1.5 border-b border-gray-300 flex items-center justify-between">
|
||||
<div className="bg-muted px-2 py-1.5 border-b border-border flex items-center justify-between">
|
||||
<Select
|
||||
value={message.role}
|
||||
onChange={(value) => onUpdateMessage(index, "role", value)}
|
||||
style={{ width: 100 }}
|
||||
size="small"
|
||||
bordered={false}
|
||||
onValueChange={(v) => onUpdateMessage(index, "role", v)}
|
||||
>
|
||||
<Option value="user">User</Option>
|
||||
<Option value="assistant">Assistant</Option>
|
||||
<Option value="system">System</Option>
|
||||
<SelectTrigger className="w-[100px] h-7 border-0 bg-transparent shadow-none">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="user">User</SelectItem>
|
||||
<SelectItem value="assistant">Assistant</SelectItem>
|
||||
<SelectItem value="system">System</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex items-center gap-1">
|
||||
{messages.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRemoveMessage(index)}
|
||||
className="text-gray-400 hover:text-red-500"
|
||||
className="text-muted-foreground hover:text-destructive"
|
||||
aria-label="Remove message"
|
||||
>
|
||||
<TrashIcon size={14} />
|
||||
<Trash size={14} />
|
||||
</button>
|
||||
)}
|
||||
<div className="cursor-grab active:cursor-grabbing text-gray-400 hover:text-gray-600">
|
||||
<GripVerticalIcon size={16} />
|
||||
<div className="cursor-grab active:cursor-grabbing text-muted-foreground hover:text-foreground">
|
||||
<GripVertical size={16} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -107,10 +128,11 @@ const PromptMessagesCard: React.FC<PromptMessagesCardProps> = ({
|
|||
))}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onAddMessage}
|
||||
className="mt-2 text-xs text-blue-600 hover:text-blue-700 flex items-center"
|
||||
className="mt-2 text-xs text-primary hover:underline flex items-center"
|
||||
>
|
||||
<PlusIcon size={14} className="mr-1" />
|
||||
<Plus size={14} className="mr-1" />
|
||||
Add message
|
||||
</button>
|
||||
</Card>
|
||||
|
|
@ -118,4 +140,3 @@ const PromptMessagesCard: React.FC<PromptMessagesCardProps> = ({
|
|||
};
|
||||
|
||||
export default PromptMessagesCard;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue