Refactor agent details page
Move AI model select above system prompt and add version history modal.
This commit is contained in:
@@ -0,0 +1,122 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Copy } from "lucide-react";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
|
||||||
|
interface VersionHistoryDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
agentName: string;
|
||||||
|
versions: Array<{
|
||||||
|
id: number;
|
||||||
|
version: string;
|
||||||
|
type: "major" | "minor" | "patch";
|
||||||
|
description: string;
|
||||||
|
prompt: string;
|
||||||
|
createdAt: string;
|
||||||
|
author: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VersionHistoryDialog({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
agentName,
|
||||||
|
versions,
|
||||||
|
}: VersionHistoryDialogProps) {
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const handleCopyPrompt = async (prompt: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(prompt);
|
||||||
|
toast({
|
||||||
|
title: "Copiado!",
|
||||||
|
description: "Prompt copiado para a área de transferência.",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: "Erro",
|
||||||
|
description: "Não foi possível copiar o prompt.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVersionTypeColor = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case "major":
|
||||||
|
return "bg-primary text-primary-foreground";
|
||||||
|
case "minor":
|
||||||
|
return "bg-secondary text-secondary-foreground";
|
||||||
|
case "patch":
|
||||||
|
return "bg-muted text-muted-foreground";
|
||||||
|
default:
|
||||||
|
return "bg-muted text-muted-foreground";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-3xl max-h-[85vh] w-[95vw] sm:w-full">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Histórico de Versões</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Todas as alterações salvas do prompt de {agentName}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<ScrollArea className="h-[60vh] pr-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{versions.map((version) => (
|
||||||
|
<div
|
||||||
|
key={version.id}
|
||||||
|
className="p-4 rounded-lg border bg-card hover:bg-accent/50 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-2">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-semibold">v{version.version}</span>
|
||||||
|
<Badge className={getVersionTypeColor(version.type)}>
|
||||||
|
{version.type}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
{version.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => handleCopyPrompt(version.prompt)}
|
||||||
|
>
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 p-3 rounded bg-muted/50">
|
||||||
|
<p className="text-xs font-mono text-muted-foreground line-clamp-3">
|
||||||
|
{version.prompt}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 text-xs text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
{version.author} • {version.createdAt}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
+69
-144
@@ -1,20 +1,19 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ArrowLeft, Bot, Save, History } from "lucide-react";
|
||||||
import { ArrowLeft, Bot, Save, Clock, Copy } from "lucide-react";
|
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
import { VersionHistoryDialog } from "@/components/agents/VersionHistoryDialog";
|
||||||
|
|
||||||
export default function AgentDetails() {
|
export default function AgentDetails() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const [isHistoryOpen, setIsHistoryOpen] = useState(false);
|
||||||
|
|
||||||
// Mock data - substituir por dados reais do backend
|
// Mock data - substituir por dados reais do backend
|
||||||
const [agent, setAgent] = useState({
|
const [agent, setAgent] = useState({
|
||||||
@@ -73,35 +72,6 @@ export default function AgentDetails() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyPrompt = async (prompt: string) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(prompt);
|
|
||||||
toast({
|
|
||||||
title: "Copiado!",
|
|
||||||
description: "Prompt copiado para a área de transferência.",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
toast({
|
|
||||||
title: "Erro",
|
|
||||||
description: "Não foi possível copiar o prompt.",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getVersionTypeColor = (type: string) => {
|
|
||||||
switch (type) {
|
|
||||||
case "major":
|
|
||||||
return "bg-primary text-primary-foreground";
|
|
||||||
case "minor":
|
|
||||||
return "bg-secondary text-secondary-foreground";
|
|
||||||
case "patch":
|
|
||||||
return "bg-muted text-muted-foreground";
|
|
||||||
default:
|
|
||||||
return "bg-muted text-muted-foreground";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="animate-fade-in pb-8">
|
<div className="animate-fade-in pb-8">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -115,7 +85,7 @@ export default function AgentDetails() {
|
|||||||
Voltar para Agentes
|
Voltar para Agentes
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="flex flex-col md:flex-row md:items-start justify-between gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="h-16 w-16 rounded-xl bg-primary/10 flex items-center justify-center">
|
<div className="h-16 w-16 rounded-xl bg-primary/10 flex items-center justify-center">
|
||||||
<Bot className="h-8 w-8 text-primary" />
|
<Bot className="h-8 w-8 text-primary" />
|
||||||
@@ -136,121 +106,76 @@ export default function AgentDetails() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button className="gap-2 w-full md:w-auto" onClick={handleSave}>
|
<div className="flex flex-col sm:flex-row gap-2">
|
||||||
<Save className="h-4 w-4" />
|
<Button className="gap-2 flex-1 sm:flex-initial" onClick={handleSave}>
|
||||||
Salvar Alterações
|
<Save className="h-4 w-4" />
|
||||||
</Button>
|
Salvar Alterações
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2 flex-1 sm:flex-initial"
|
||||||
|
onClick={() => setIsHistoryOpen(true)}
|
||||||
|
>
|
||||||
|
<History className="h-4 w-4" />
|
||||||
|
Histórico de Versões
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="space-y-6 max-w-4xl">
|
||||||
{/* Left Column - Prompt & Model */}
|
{/* AI Model */}
|
||||||
<div className="lg:col-span-2 space-y-6">
|
<Card>
|
||||||
{/* System Prompt */}
|
<CardHeader>
|
||||||
<Card>
|
<CardTitle>Modelo de IA</CardTitle>
|
||||||
<CardHeader>
|
<CardDescription>
|
||||||
<CardTitle>System Prompt</CardTitle>
|
Selecione o modelo de inteligência artificial
|
||||||
<CardDescription>
|
</CardDescription>
|
||||||
Define o comportamento e personalidade do agente
|
</CardHeader>
|
||||||
</CardDescription>
|
<CardContent>
|
||||||
</CardHeader>
|
<Select
|
||||||
<CardContent>
|
value={agent.model}
|
||||||
<Textarea
|
onValueChange={(value) => setAgent({ ...agent, model: value })}
|
||||||
value={agent.systemPrompt}
|
>
|
||||||
onChange={(e) => setAgent({ ...agent, systemPrompt: e.target.value })}
|
<SelectTrigger>
|
||||||
className="min-h-[200px] font-mono text-sm"
|
<SelectValue />
|
||||||
placeholder="Digite o prompt do sistema..."
|
</SelectTrigger>
|
||||||
/>
|
<SelectContent>
|
||||||
</CardContent>
|
{aiModels.map((model) => (
|
||||||
</Card>
|
<SelectItem key={model.value} value={model.value}>
|
||||||
|
{model.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* AI Model */}
|
{/* System Prompt */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Modelo de IA</CardTitle>
|
<CardTitle>System Prompt</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Selecione o modelo de inteligência artificial
|
Define o comportamento e personalidade do agente
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Select
|
<Textarea
|
||||||
value={agent.model}
|
value={agent.systemPrompt}
|
||||||
onValueChange={(value) => setAgent({ ...agent, model: value })}
|
onChange={(e) => setAgent({ ...agent, systemPrompt: e.target.value })}
|
||||||
>
|
className="min-h-[300px] font-mono text-sm"
|
||||||
<SelectTrigger>
|
placeholder="Digite o prompt do sistema..."
|
||||||
<SelectValue />
|
/>
|
||||||
</SelectTrigger>
|
</CardContent>
|
||||||
<SelectContent>
|
</Card>
|
||||||
{aiModels.map((model) => (
|
|
||||||
<SelectItem key={model.value} value={model.value}>
|
|
||||||
{model.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Column - Version History */}
|
|
||||||
<div className="lg:col-span-1">
|
|
||||||
<Card className="h-full">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Clock className="h-5 w-5" />
|
|
||||||
Histórico de Versões
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Todas as alterações salvas do prompt
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<ScrollArea className="h-[600px] pr-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
{versions.map((version) => (
|
|
||||||
<div
|
|
||||||
key={version.id}
|
|
||||||
className="p-4 rounded-lg border bg-card hover:bg-accent/50 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="flex items-start justify-between mb-2">
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="font-semibold">v{version.version}</span>
|
|
||||||
<Badge className={getVersionTypeColor(version.type)}>
|
|
||||||
{version.type}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
|
||||||
{version.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-8 w-8"
|
|
||||||
onClick={() => handleCopyPrompt(version.prompt)}
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-3 p-3 rounded bg-muted/50">
|
|
||||||
<p className="text-xs font-mono text-muted-foreground line-clamp-3">
|
|
||||||
{version.prompt}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-3 text-xs text-muted-foreground">
|
|
||||||
<p>{version.author} • {version.createdAt}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<VersionHistoryDialog
|
||||||
|
open={isHistoryOpen}
|
||||||
|
onOpenChange={setIsHistoryOpen}
|
||||||
|
agentName={agent.name}
|
||||||
|
versions={versions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user