/** * Context Document Modal Component * * Modal for viewing and editing context documents with markdown rendering support */ import React, { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Edit, Save, Download, FileText, Trash2, X, Calendar, AlertCircle, } from "lucide-react"; import { ContextDocument, ContextDocumentType, CONTEXT_DOCUMENT_TYPES, } from "@/types/context"; import { shouldRenderAsMarkdown } from "@/lib/markdown-utils"; import Editor from "@monaco-editor/react"; import ReactMarkdown from "react-markdown"; interface ContextDocumentModalProps { document: ContextDocument; isOpen: boolean; onClose: () => void; onSave: (updates: { title?: string; content?: string; document_type?: ContextDocumentType; }) => Promise; onDelete: () => Promise; } export function ContextDocumentModal({ document, isOpen, onClose, onSave, onDelete, }: ContextDocumentModalProps) { const [isEditing, setIsEditing] = useState(false); const [editedTitle, setEditedTitle] = useState(document.title); const [editedContent, setEditedContent] = useState(document.content); const [editedType, setEditedType] = useState(document.document_type); const [isSaving, setIsSaving] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [isDirty, setIsDirty] = useState(false); const [error, setError] = useState(null); // Update local state when document changes useEffect(() => { setEditedTitle(document.title); setEditedContent(document.content); setEditedType(document.document_type); setIsDirty(false); setError(null); }, [document]); // Check if content has changed useEffect(() => { const hasChanges = editedTitle !== document.title || editedContent !== document.content || editedType !== document.document_type; setIsDirty(hasChanges); }, [editedTitle, editedContent, editedType, document]); const handleEdit = () => { setIsEditing(true); setError(null); }; const handleSave = async () => { if (!isDirty) return; setIsSaving(true); setError(null); try { const updates: any = {}; if (editedTitle !== document.title) updates.title = editedTitle; if (editedContent !== document.content) updates.content = editedContent; if (editedType !== document.document_type) updates.document_type = editedType; const success = await onSave(updates); if (success) { setIsEditing(false); setIsDirty(false); } } catch (err) { setError(err instanceof Error ? err.message : "Failed to save document"); } finally { setIsSaving(false); } }; const handleCancel = () => { setEditedTitle(document.title); setEditedContent(document.content); setEditedType(document.document_type); setIsEditing(false); setIsDirty(false); setError(null); }; const handleDownload = () => { const blob = new Blob([document.content], { type: "text/plain" }); const url = URL.createObjectURL(blob); const a = window.document.createElement("a"); a.href = url; a.download = `${document.title}.${ shouldRenderAsMarkdown(document.content, document.file_name) ? "md" : "txt" }`; window.document.body.appendChild(a); a.click(); window.document.body.removeChild(a); URL.revokeObjectURL(url); }; const handleDelete = async () => { if ( !window.confirm( "Are you sure you want to delete this context document? This action cannot be undone." ) ) { return; } setIsDeleting(true); setError(null); try { const success = await onDelete(); if (success) { onClose(); } } catch (err) { setError( err instanceof Error ? err.message : "Failed to delete document" ); } finally { setIsDeleting(false); } }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }; const formatDocumentType = (type: ContextDocumentType) => { const typeConfig = CONTEXT_DOCUMENT_TYPES.find((t) => t.value === type); return typeConfig?.label || type.replace(/_/g, " "); }; const isMarkdown = shouldRenderAsMarkdown( document.content, document.file_name ); return (
{isEditing ? ( setEditedTitle(e.target.value)} className="text-lg font-semibold" placeholder="Document title" /> ) : ( {document.title} )}
{!isEditing ? ( <> ) : ( <> )}
{/* Document metadata */}
{isEditing ? (
) : ( {formatDocumentType(document.document_type)} )} {formatDate(document.created_at)} {document.content.length.toLocaleString()} chars
{error && (
{error}
)}
{/* Single content area - conditional rendering based on editing state */}
{isEditing ? ( /* Edit Mode */
setEditedContent(value || "")} theme="vs" options={{ minimap: { enabled: false }, scrollBeyondLastLine: false, wordWrap: "on", lineNumbers: "on", folding: false, renderWhitespace: "selection", fontSize: 14, lineHeight: 20, fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace", padding: { top: 10, bottom: 10 }, automaticLayout: true, }} />
) : ( /* View Mode */
{isMarkdown ? (
{document.content}
) : (
                      {document.content}
                    
)}
)}
); }