diff --git a/ui/web/src/pages/vault/vault-detail-panel.tsx b/ui/web/src/pages/vault/vault-detail-panel.tsx new file mode 100644 index 00000000..54c4567e --- /dev/null +++ b/ui/web/src/pages/vault/vault-detail-panel.tsx @@ -0,0 +1,180 @@ +import { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { X, Pencil, Plus, FileText, Link2 } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { MarkdownRenderer } from "@/components/shared/markdown-renderer"; +import { useVaultLinks, useVaultFileContent, useUpdateDocument, useDeleteDocument } from "./hooks/use-vault"; +import { VaultLinkDialog } from "./vault-link-dialog"; +import { VaultEditControls, DocTypeSelect, ScopeSelect, LinkBadge } from "./vault-detail-edit-section"; +import type { VaultDocument } from "@/types/vault"; + +interface Props { + doc: VaultDocument | null; + open: boolean; + onClose: () => void; + onDeleted?: () => void; +} + +export function VaultDetailPanel({ doc, open, onClose, onDeleted }: Props) { + const { t } = useTranslation("vault"); + const { outlinks, backlinks } = useVaultLinks(doc?.agent_id ?? "", doc?.id ?? null); + const { content, loading: contentLoading, error: contentError } = useVaultFileContent(open && doc ? doc.path : null); + const { update } = useUpdateDocument(doc?.agent_id ?? "", doc?.id ?? ""); + const { remove } = useDeleteDocument(doc?.agent_id ?? "", doc?.id ?? ""); + + const [editMode, setEditMode] = useState(false); + const [editTitle, setEditTitle] = useState(""); + const [editDocType, setEditDocType] = useState(""); + const [editScope, setEditScope] = useState(""); + const [saving, setSaving] = useState(false); + const [confirmDelete, setConfirmDelete] = useState(false); + const [linkDialogOpen, setLinkDialogOpen] = useState(false); + + // Reset edit state when doc changes + useEffect(() => { setEditMode(false); setConfirmDelete(false); }, [doc?.id]); + + // Escape to close + useEffect(() => { + if (!open) return; + const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; + document.addEventListener("keydown", handler); + return () => document.removeEventListener("keydown", handler); + }, [open, onClose]); + + const startEdit = () => { + if (!doc) return; + setEditTitle(doc.title); setEditDocType(doc.doc_type); setEditScope(doc.scope); + setEditMode(true); + }; + + const handleSave = async () => { + setSaving(true); + try { await update({ title: editTitle, doc_type: editDocType, scope: editScope }); setEditMode(false); } + catch { /* toasted */ } + finally { setSaving(false); } + }; + + const handleDelete = async () => { + setSaving(true); + try { await remove(); onDeleted?.(); onClose(); } + catch { /* toasted */ } + finally { setSaving(false); setConfirmDelete(false); } + }; + + if (!doc || !open) return null; + + const hash = doc.content_hash; + const hashDisplay = hash.length > 16 ? `${hash.slice(0, 8)}...${hash.slice(-8)}` : hash; + + return ( + <> +