sunatest / frontend /src /components /agents /agent-version-switcher.tsx
llama1's picture
Upload 781 files
5da4770 verified
'use client';
import React, { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { GitBranch, ChevronDown, Clock, RotateCcw, Check, AlertCircle, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { useAgentVersions, useActivateAgentVersion, useCreateAgentVersion } from '@/lib/versioning';
import { formatDistanceToNow } from 'date-fns';
import { toast } from 'sonner';
import type { AgentVersion } from '@/lib/versioning';
import { VersionInlineEditor } from './version-inline-editor';
interface AgentVersionSwitcherProps {
agentId: string;
currentVersionId?: string | null;
currentFormData: {
system_prompt: string;
configured_mcps: any[];
custom_mcps: any[];
agentpress_tools: Record<string, any>;
};
}
export function AgentVersionSwitcher({
agentId,
currentVersionId,
currentFormData
}: AgentVersionSwitcherProps) {
const router = useRouter();
const searchParams = useSearchParams();
const versionParam = searchParams.get('version');
const { data: versions, isLoading } = useAgentVersions(agentId);
const activateVersionMutation = useActivateAgentVersion();
const createVersionMutation = useCreateAgentVersion();
const [showRollbackDialog, setShowRollbackDialog] = useState(false);
const [selectedVersion, setSelectedVersion] = useState<AgentVersion | null>(null);
const [isRollingBack, setIsRollingBack] = useState(false);
const viewingVersionId = versionParam || currentVersionId;
const viewingVersion = versions?.find(v => v.versionId.value === viewingVersionId) || versions?.[0];
const canRollback = viewingVersion && viewingVersion.versionNumber.value > 1;
const handleVersionSelect = async (version: AgentVersion) => {
if (version.versionId.value === viewingVersionId) return;
const params = new URLSearchParams(searchParams.toString());
if (version.versionId.value === currentVersionId) {
params.delete('version');
} else {
params.set('version', version.versionId.value);
}
const newUrl = params.toString() ? `?${params.toString()}` : window.location.pathname;
router.push(newUrl);
if (version.versionId.value !== currentVersionId) {
toast.success(`Viewing ${version.versionName} (read-only)`);
}
};
const handleRollback = async () => {
if (!selectedVersion || !viewingVersion) return;
setIsRollingBack(true);
try {
const newVersion = await createVersionMutation.mutateAsync({
agentId,
data: {
system_prompt: selectedVersion.systemPrompt,
configured_mcps: selectedVersion.configuredMcps,
custom_mcps: selectedVersion.customMcps,
agentpress_tools: selectedVersion.toolConfiguration.tools,
description: `Rolled back from ${viewingVersion.versionName} to ${selectedVersion.versionName}`
}
});
await activateVersionMutation.mutateAsync({
agentId,
versionId: newVersion.versionId.value
});
const params = new URLSearchParams(searchParams.toString());
params.delete('version');
const newUrl = params.toString() ? `?${params.toString()}` : window.location.pathname;
router.push(newUrl);
setShowRollbackDialog(false);
toast.success(`Rolled back to ${selectedVersion.versionName} configuration`);
} catch (error) {
console.error('Failed to rollback:', error);
toast.error('Failed to rollback version');
} finally {
setIsRollingBack(false);
}
};
const openRollbackDialog = (version: AgentVersion) => {
setSelectedVersion(version);
setShowRollbackDialog(true);
};
if (isLoading) {
return (
<div className="flex items-center gap-2 px-3 py-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="text-sm text-muted-foreground">Loading versions...</span>
</div>
);
}
if (!versions || versions.length === 0) {
return null;
}
return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="gap-2">
<GitBranch className="h-4 w-4" />
{viewingVersion ? (
<>
{viewingVersion.versionName}
{viewingVersionId === currentVersionId && (
<div className="h-2 w-2 rounded-full bg-green-500" />
)}
</>
) : (
'Select Version'
)}
<ChevronDown className="h-4 w-4 ml-1" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-80">
<DropdownMenuLabel>Version History</DropdownMenuLabel>
<DropdownMenuSeparator />
<div className="max-h-96 overflow-y-auto">
{versions.map((version) => {
const isViewing = version.versionId.value === viewingVersionId;
const isCurrent = version.versionId.value === currentVersionId;
return (
<div key={version.versionId.value} className="relative mb-1">
<div className={`p-2 hover:bg-accent rounded-sm ${isViewing ? 'bg-accent' : ''}`}>
<div className="flex items-start justify-between w-full">
<div className="flex-1">
<div className="flex items-center gap-2">
<div
className="flex-1 cursor-pointer"
onClick={() => handleVersionSelect(version)}
>
<VersionInlineEditor
agentId={agentId}
versionId={version.versionId.value}
versionName={version.versionName}
changeDescription={version.changeDescription}
isActive={isCurrent}
/>
</div>
{isCurrent && (
<Badge variant="default" className="text-xs">
Current
</Badge>
)}
{isViewing && !isCurrent && (
<Badge variant="outline" className="text-xs">
Viewing
</Badge>
)}
</div>
<div
className="flex items-center gap-2 mt-1 cursor-pointer"
onClick={() => handleVersionSelect(version)}
>
<Clock className="h-3 w-3 text-muted-foreground" />
<span className="text-xs text-muted-foreground">
{formatDistanceToNow(version.createdAt, { addSuffix: true })}
</span>
</div>
</div>
{isViewing && (
<Check className="h-4 w-4 text-primary ml-2" />
)}
</div>
</div>
</div>
);
})}
</div>
{versions.length === 1 && (
<div className="p-2">
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription>
This is the first version. Make changes to create a new version.
</AlertDescription>
</Alert>
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
</>
);
}