"use client"; import { Check, ChevronRight, Clock, Copy, RotateCcw } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Separator } from "@/components/ui/separator"; import { Spinner } from "@/components/ui/spinner"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { cn } from "@/lib/utils"; interface DocumentVersionSummary { version_number: number; title: string; content_hash: string; created_at: string | null; } interface VersionHistoryProps { documentId: number; documentType: string; } const VERSION_DOCUMENT_TYPES = new Set(["LOCAL_FOLDER_FILE", "OBSIDIAN_CONNECTOR"]); export function isVersionableType(documentType: string) { return VERSION_DOCUMENT_TYPES.has(documentType); } const DIALOG_CLASSES = "select-none max-w-[900px] w-[95vw] md:w-[90vw] h-[90vh] md:h-[80vh] max-h-[640px] flex flex-col md:flex-row p-0 gap-0 overflow-hidden [--card:var(--background)] dark:[--card:oklch(0.205_0_0)] dark:[--background:oklch(0.205_0_0)]"; export function VersionHistoryButton({ documentId, documentType }: VersionHistoryProps) { if (!isVersionableType(documentType)) return null; return ( Versions Version History ); } export function VersionHistoryDialog({ open, onOpenChange, documentId, }: { open: boolean; onOpenChange: (open: boolean) => void; documentId: number; }) { return ( Version History {open && } ); } function formatRelativeTime(dateStr: string): string { const now = Date.now(); const then = new Date(dateStr).getTime(); const diffMs = now - then; const diffMin = Math.floor(diffMs / 60_000); if (diffMin < 1) return "Just now"; if (diffMin < 60) return `${diffMin} minute${diffMin !== 1 ? "s" : ""} ago`; const diffHr = Math.floor(diffMin / 60); if (diffHr < 24) return `${diffHr} hour${diffHr !== 1 ? "s" : ""} ago`; return new Date(dateStr).toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", }); } function VersionHistoryPanel({ documentId }: { documentId: number }) { const [versions, setVersions] = useState([]); const [loading, setLoading] = useState(true); const [selectedVersion, setSelectedVersion] = useState(null); const [versionContent, setVersionContent] = useState(""); const [contentLoading, setContentLoading] = useState(false); const [restoring, setRestoring] = useState(false); const [copied, setCopied] = useState(false); const loadVersions = useCallback(async () => { setLoading(true); try { const data = await documentsApiService.listDocumentVersions(documentId); setVersions(data as DocumentVersionSummary[]); } catch { toast.error("Failed to load version history"); } finally { setLoading(false); } }, [documentId]); useEffect(() => { loadVersions(); }, [loadVersions]); const handleSelectVersion = async (versionNumber: number) => { if (selectedVersion === versionNumber) return; setSelectedVersion(versionNumber); setContentLoading(true); try { const data = (await documentsApiService.getDocumentVersion(documentId, versionNumber)) as { source_markdown: string; }; setVersionContent(data.source_markdown || ""); } catch { toast.error("Failed to load version content"); } finally { setContentLoading(false); } }; const handleRestore = async (versionNumber: number) => { setRestoring(true); try { await documentsApiService.restoreDocumentVersion(documentId, versionNumber); toast.success(`Restored version ${versionNumber}`); await loadVersions(); } catch { toast.error("Failed to restore version"); } finally { setRestoring(false); } }; const handleCopy = () => { navigator.clipboard.writeText(versionContent); setCopied(true); setTimeout(() => setCopied(false), 2000); }; if (loading) { return ( ); } if (versions.length === 0) { return ( No version history available yet Versions are created when file content changes ); } const selectedVersionData = versions.find((v) => v.version_number === selectedVersion); return ( <> {/* Left panel — version list */} Version History {versions.map((v) => ( handleSelectVersion(v.version_number)} className={cn( "flex items-center gap-2 rounded-lg px-3 py-2.5 text-left transition-colors focus:outline-none focus-visible:outline-none w-full", selectedVersion === v.version_number ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent/50 hover:text-foreground" )} > {v.created_at ? formatRelativeTime(v.created_at) : `Version ${v.version_number}`} {v.title && {v.title}} ))} {/* Right panel — content preview */} {selectedVersion !== null && selectedVersionData ? ( <> {selectedVersionData.title || `Version ${selectedVersion}`} {copied ? : } {copied ? "Copied" : "Copy"} handleRestore(selectedVersion)} > {restoring ? : } Restore {contentLoading ? ( ) : ( {versionContent || "(empty)"} )} > ) : ( Select a version to preview )} > ); }
No version history available yet
Versions are created when file content changes
{v.created_at ? formatRelativeTime(v.created_at) : `Version ${v.version_number}`}
{v.title}
{versionContent || "(empty)"}
Select a version to preview