| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>PDF Editor with AI Assistant</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .pdf-container { |
| height: calc(100vh - 180px); |
| overflow-y: auto; |
| border: 1px solid #e5e7eb; |
| } |
| .chat-container { |
| height: calc(100vh - 180px); |
| display: flex; |
| flex-direction: column; |
| } |
| .chat-messages { |
| flex-grow: 1; |
| overflow-y: auto; |
| } |
| .pdf-page { |
| margin-bottom: 20px; |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
| } |
| .canvas-container { |
| position: relative; |
| } |
| .annotation-tools { |
| position: absolute; |
| top: 10px; |
| left: 10px; |
| background: white; |
| padding: 5px; |
| border-radius: 5px; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| display: none; |
| } |
| .active-tool { |
| background-color: #3b82f6; |
| color: white; |
| } |
| .loading-spinner { |
| border: 4px solid rgba(0, 0, 0, 0.1); |
| border-radius: 50%; |
| border-top: 4px solid #3b82f6; |
| width: 40px; |
| height: 40px; |
| animation: spin 1s linear infinite; |
| } |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| .drawing-canvas { |
| position: absolute; |
| top: 0; |
| left: 0; |
| pointer-events: none; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50"> |
| <div class="container mx-auto px-4 py-6"> |
| <header class="mb-8"> |
| <h1 class="text-3xl font-bold text-blue-600 flex items-center"> |
| <i class="fas fa-file-pdf mr-3"></i> PDF Editor with AI Assistant |
| </h1> |
| <p class="text-gray-600 mt-2">Upload, edit, and chat with your PDF documents</p> |
| </header> |
|
|
| |
| <div class="bg-white rounded-lg shadow-md p-6 mb-8" id="upload-section"> |
| <div class="flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-lg p-12 bg-gray-50"> |
| <i class="fas fa-file-upload text-5xl text-blue-400 mb-4"></i> |
| <h3 class="text-xl font-semibold text-gray-700 mb-2">Upload your PDF file</h3> |
| <p class="text-gray-500 mb-6">Drag & drop your file here or click to browse</p> |
| <input type="file" id="pdf-upload" accept=".pdf" class="hidden"> |
| <button id="upload-btn" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition duration-200 flex items-center"> |
| <i class="fas fa-folder-open mr-2"></i> Select PDF File |
| </button> |
| <p class="text-sm text-gray-500 mt-4">Supports PDF files up to 50MB</p> |
| </div> |
| </div> |
|
|
| |
| <div id="editor-section" class="hidden"> |
| <div class="flex justify-between items-center mb-4"> |
| <h2 class="text-xl font-semibold text-gray-800 flex items-center"> |
| <i class="fas fa-edit mr-2"></i> PDF Editor |
| </h2> |
| <div class="flex space-x-2"> |
| <button id="download-btn" class="bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-lg transition duration-200 flex items-center"> |
| <i class="fas fa-download mr-2"></i> Download |
| </button> |
| <button id="new-file-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-lg transition duration-200 flex items-center"> |
| <i class="fas fa-plus mr-2"></i> New File |
| </button> |
| </div> |
| </div> |
|
|
| <div class="flex flex-col lg:flex-row gap-6"> |
| |
| <div class="w-full lg:w-2/3 bg-white rounded-lg shadow-md p-4"> |
| <div class="flex items-center justify-between mb-4"> |
| <div class="flex space-x-2"> |
| <button id="highlight-btn" class="p-2 rounded-md hover:bg-gray-100" title="Highlight"> |
| <i class="fas fa-highlighter text-yellow-500"></i> |
| </button> |
| <button id="underline-btn" class="p-2 rounded-md hover:bg-gray-100" title="Underline"> |
| <i class="fas fa-underline text-blue-500"></i> |
| </button> |
| <button id="strike-btn" class="p-2 rounded-md hover:bg-gray-100" title="Strikethrough"> |
| <i class="fas fa-strikethrough text-red-500"></i> |
| </button> |
| <button id="draw-btn" class="p-2 rounded-md hover:bg-gray-100" title="Draw"> |
| <i class="fas fa-pen text-purple-500"></i> |
| </button> |
| <button id="text-btn" class="p-2 rounded-md hover:bg-gray-100" title="Add Text"> |
| <i class="fas fa-font text-green-500"></i> |
| </button> |
| <button id="eraser-btn" class="p-2 rounded-md hover:bg-gray-100" title="Eraser"> |
| <i class="fas fa-eraser text-gray-500"></i> |
| </button> |
| </div> |
| <div class="flex items-center space-x-2"> |
| <button id="zoom-in" class="p-2 rounded-md hover:bg-gray-100"> |
| <i class="fas fa-search-plus"></i> |
| </button> |
| <span id="zoom-level" class="text-sm font-medium">100%</span> |
| <button id="zoom-out" class="p-2 rounded-md hover:bg-gray-100"> |
| <i class="fas fa-search-minus"></i> |
| </button> |
| </div> |
| </div> |
|
|
| <div class="pdf-container" id="pdf-viewer"> |
| <div class="flex items-center justify-center h-full" id="pdf-loading"> |
| <div class="text-center"> |
| <div class="loading-spinner mx-auto mb-4"></div> |
| <p class="text-gray-600">Loading PDF document...</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="w-full lg:w-1/3 bg-white rounded-lg shadow-md p-4"> |
| <div class="flex items-center mb-4"> |
| <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center mr-3"> |
| <i class="fas fa-robot text-blue-500"></i> |
| </div> |
| <h3 class="text-lg font-semibold text-gray-800">PDF Assistant</h3> |
| </div> |
|
|
| <div class="chat-container"> |
| <div class="chat-messages" id="chat-messages"> |
| <div class="bg-blue-50 rounded-lg p-4 mb-4"> |
| <p class="text-gray-800">Hello! I'm your PDF assistant. Ask me anything about the document or request help with editing.</p> |
| </div> |
| </div> |
|
|
| <div class="mt-4"> |
| <div class="flex items-center space-x-2 mb-2"> |
| <button class="bg-gray-100 hover:bg-gray-200 text-gray-800 text-xs py-1 px-2 rounded"> |
| Summarize |
| </button> |
| <button class="bg-gray-100 hover:bg-gray-200 text-gray-800 text-xs py-1 px-2 rounded"> |
| Find key points |
| </button> |
| <button class="bg-gray-100 hover:bg-gray-200 text-gray-800 text-xs py-1 px-2 rounded"> |
| Explain section |
| </button> |
| </div> |
| <div class="relative"> |
| <textarea id="chat-input" rows="2" class="w-full border border-gray-300 rounded-lg p-3 pr-12 focus:ring-blue-500 focus:border-blue-500" placeholder="Ask about the document..."></textarea> |
| <button id="send-chat" class="absolute right-3 bottom-3 bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-lg"> |
| <i class="fas fa-paper-plane"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js'; |
| |
| |
| const uploadSection = document.getElementById('upload-section'); |
| const editorSection = document.getElementById('editor-section'); |
| const pdfUpload = document.getElementById('pdf-upload'); |
| const uploadBtn = document.getElementById('upload-btn'); |
| const pdfViewer = document.getElementById('pdf-viewer'); |
| const pdfLoading = document.getElementById('pdf-loading'); |
| const zoomInBtn = document.getElementById('zoom-in'); |
| const zoomOutBtn = document.getElementById('zoom-out'); |
| const zoomLevel = document.getElementById('zoom-level'); |
| const downloadBtn = document.getElementById('download-btn'); |
| const newFileBtn = document.getElementById('new-file-btn'); |
| const chatMessages = document.getElementById('chat-messages'); |
| const chatInput = document.getElementById('chat-input'); |
| const sendChatBtn = document.getElementById('send-chat'); |
| const highlightBtn = document.getElementById('highlight-btn'); |
| const underlineBtn = document.getElementById('underline-btn'); |
| const strikeBtn = document.getElementById('strike-btn'); |
| const drawBtn = document.getElementById('draw-btn'); |
| const textBtn = document.getElementById('text-btn'); |
| const eraserBtn = document.getElementById('eraser-btn'); |
| |
| |
| let pdfDoc = null; |
| let currentPage = 1; |
| let pageRendering = false; |
| let pageNumPending = null; |
| let scale = 1.0; |
| let canvas, ctx; |
| let isDrawing = false; |
| let activeTool = null; |
| let lastX = 0; |
| let lastY = 0; |
| let drawingCanvases = []; |
| |
| |
| uploadBtn.addEventListener('click', () => pdfUpload.click()); |
| pdfUpload.addEventListener('change', handleFileUpload); |
| zoomInBtn.addEventListener('click', zoomIn); |
| zoomOutBtn.addEventListener('click', zoomOut); |
| downloadBtn.addEventListener('click', downloadPDF); |
| newFileBtn.addEventListener('click', resetApp); |
| sendChatBtn.addEventListener('click', sendChatMessage); |
| chatInput.addEventListener('keypress', (e) => { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| sendChatMessage(); |
| } |
| }); |
| |
| |
| highlightBtn.addEventListener('click', () => setActiveTool('highlight')); |
| underlineBtn.addEventListener('click', () => setActiveTool('underline')); |
| strikeBtn.addEventListener('click', () => setActiveTool('strike')); |
| drawBtn.addEventListener('click', () => setActiveTool('draw')); |
| textBtn.addEventListener('click', () => setActiveTool('text')); |
| eraserBtn.addEventListener('click', () => setActiveTool('eraser')); |
| |
| |
| function handleFileUpload(e) { |
| const file = e.target.files[0]; |
| if (file.type !== 'application/pdf') { |
| alert('Please select a PDF file.'); |
| return; |
| } |
| |
| uploadSection.classList.add('hidden'); |
| editorSection.classList.remove('hidden'); |
| |
| const fileReader = new FileReader(); |
| fileReader.onload = function() { |
| const typedarray = new Uint8Array(this.result); |
| loadPDF(typedarray); |
| }; |
| fileReader.readAsArrayBuffer(file); |
| } |
| |
| |
| function loadPDF(data) { |
| pdfLoading.style.display = 'flex'; |
| pdfViewer.innerHTML = ''; |
| pdfViewer.appendChild(pdfLoading); |
| |
| pdfjsLib.getDocument(data).promise.then(function(pdf) { |
| pdfDoc = pdf; |
| renderPDF(); |
| }).catch(function(error) { |
| console.error('Error loading PDF:', error); |
| pdfLoading.querySelector('p').textContent = 'Error loading PDF. Please try another file.'; |
| }); |
| } |
| |
| |
| function renderPDF() { |
| pdfLoading.style.display = 'none'; |
| |
| for (let i = 1; i <= pdfDoc.numPages; i++) { |
| const pageContainer = document.createElement('div'); |
| pageContainer.className = 'pdf-page'; |
| pageContainer.id = `page-${i}`; |
| |
| const canvasContainer = document.createElement('div'); |
| canvasContainer.className = 'canvas-container relative'; |
| |
| const annotationTools = document.createElement('div'); |
| annotationTools.className = 'annotation-tools'; |
| annotationTools.id = `tools-${i}`; |
| annotationTools.innerHTML = ` |
| <button class="p-1 hover:bg-gray-100 rounded" data-tool="highlight"><i class="fas fa-highlighter text-yellow-500"></i></button> |
| <button class="p-1 hover:bg-gray-100 rounded" data-tool="underline"><i class="fas fa-underline text-blue-500"></i></button> |
| <button class="p-1 hover:bg-gray-100 rounded" data-tool="strike"><i class="fas fa-strikethrough text-red-500"></i></button> |
| `; |
| |
| canvas = document.createElement('canvas'); |
| canvas.className = 'pdf-canvas border border-gray-200'; |
| canvas.id = `canvas-${i}`; |
| |
| const drawingCanvas = document.createElement('canvas'); |
| drawingCanvas.className = 'drawing-canvas'; |
| drawingCanvas.id = `drawing-${i}`; |
| drawingCanvases.push(drawingCanvas); |
| |
| canvasContainer.appendChild(canvas); |
| canvasContainer.appendChild(drawingCanvas); |
| canvasContainer.appendChild(annotationTools); |
| pageContainer.appendChild(canvasContainer); |
| pdfViewer.appendChild(pageContainer); |
| |
| renderPage(i); |
| } |
| |
| |
| setupDrawingCanvases(); |
| } |
| |
| |
| function renderPage(num) { |
| pageRendering = true; |
| pdfDoc.getPage(num).then(function(page) { |
| const viewport = page.getViewport({ scale: scale }); |
| const canvas = document.getElementById(`canvas-${num}`); |
| const context = canvas.getContext('2d'); |
| |
| canvas.height = viewport.height; |
| canvas.width = viewport.width; |
| |
| |
| const drawingCanvas = document.getElementById(`drawing-${num}`); |
| drawingCanvas.height = viewport.height; |
| drawingCanvas.width = viewport.width; |
| |
| const renderContext = { |
| canvasContext: context, |
| viewport: viewport |
| }; |
| |
| const renderTask = page.render(renderContext); |
| |
| renderTask.promise.then(function() { |
| pageRendering = false; |
| if (pageNumPending !== null) { |
| renderPage(pageNumPending); |
| pageNumPending = null; |
| } |
| }); |
| }); |
| |
| currentPage = num; |
| } |
| |
| |
| function zoomIn() { |
| if (scale >= 3.0) return; |
| scale += 0.25; |
| zoomLevel.textContent = `${Math.round(scale * 100)}%`; |
| rerenderPDF(); |
| } |
| |
| function zoomOut() { |
| if (scale <= 0.5) return; |
| scale -= 0.25; |
| zoomLevel.textContent = `${Math.round(scale * 100)}%`; |
| rerenderPDF(); |
| } |
| |
| function rerenderPDF() { |
| if (pageRendering) { |
| pageNumPending = currentPage; |
| } else { |
| for (let i = 1; i <= pdfDoc.numPages; i++) { |
| renderPage(i); |
| } |
| } |
| } |
| |
| |
| function setupDrawingCanvases() { |
| drawingCanvases.forEach(drawingCanvas => { |
| const ctx = drawingCanvas.getContext('2d'); |
| |
| drawingCanvas.addEventListener('mousedown', startDrawing); |
| drawingCanvas.addEventListener('mousemove', draw); |
| drawingCanvas.addEventListener('mouseup', stopDrawing); |
| drawingCanvas.addEventListener('mouseout', stopDrawing); |
| |
| function startDrawing(e) { |
| if (activeTool !== 'draw') return; |
| |
| isDrawing = true; |
| const rect = drawingCanvas.getBoundingClientRect(); |
| lastX = e.clientX - rect.left; |
| lastY = e.clientY - rect.top; |
| |
| ctx.beginPath(); |
| ctx.moveTo(lastX, lastY); |
| ctx.strokeStyle = '#3b82f6'; |
| ctx.lineWidth = 2; |
| ctx.lineCap = 'round'; |
| } |
| |
| function draw(e) { |
| if (!isDrawing || activeTool !== 'draw') return; |
| |
| const rect = drawingCanvas.getBoundingClientRect(); |
| const currentX = e.clientX - rect.left; |
| const currentY = e.clientY - rect.top; |
| |
| ctx.lineTo(currentX, currentY); |
| ctx.stroke(); |
| |
| lastX = currentX; |
| lastY = currentY; |
| } |
| |
| function stopDrawing() { |
| isDrawing = false; |
| } |
| }); |
| } |
| |
| |
| function setActiveTool(tool) { |
| activeTool = tool; |
| |
| |
| [highlightBtn, underlineBtn, strikeBtn, drawBtn, textBtn, eraserBtn].forEach(btn => { |
| btn.classList.remove('active-tool'); |
| }); |
| |
| |
| switch(tool) { |
| case 'highlight': |
| highlightBtn.classList.add('active-tool'); |
| break; |
| case 'underline': |
| underlineBtn.classList.add('active-tool'); |
| break; |
| case 'strike': |
| strikeBtn.classList.add('active-tool'); |
| break; |
| case 'draw': |
| drawBtn.classList.add('active-tool'); |
| break; |
| case 'text': |
| textBtn.classList.add('active-tool'); |
| break; |
| case 'eraser': |
| eraserBtn.classList.add('active-tool'); |
| break; |
| } |
| } |
| |
| |
| function sendChatMessage() { |
| const message = chatInput.value.trim(); |
| if (!message) return; |
| |
| |
| addChatMessage(message, 'user'); |
| |
| |
| setTimeout(() => { |
| const responses = [ |
| "Based on the document, the key points are...", |
| "The summary of section 3 is...", |
| "This document primarily discusses...", |
| "I've analyzed the content and found that...", |
| "The author's main argument appears to be..." |
| ]; |
| const randomResponse = responses[Math.floor(Math.random() * responses.length)]; |
| addChatMessage(randomResponse, 'ai'); |
| }, 1000); |
| |
| chatInput.value = ''; |
| } |
| |
| function addChatMessage(message, sender) { |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `mb-4 ${sender === 'user' ? 'text-right' : 'text-left'}`; |
| |
| const bubble = document.createElement('div'); |
| bubble.className = `inline-block rounded-lg p-3 max-w-xs lg:max-w-md ${sender === 'user' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-800'}`; |
| bubble.textContent = message; |
| |
| messageDiv.appendChild(bubble); |
| chatMessages.appendChild(messageDiv); |
| |
| |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| } |
| |
| |
| function downloadPDF() { |
| alert('In a real application, this would download the edited PDF with all annotations.'); |
| } |
| |
| |
| function resetApp() { |
| pdfUpload.value = ''; |
| pdfViewer.innerHTML = ''; |
| pdfViewer.appendChild(pdfLoading); |
| pdfLoading.style.display = 'flex'; |
| pdfLoading.querySelector('p').textContent = 'Loading PDF document...'; |
| chatMessages.innerHTML = ` |
| <div class="bg-blue-50 rounded-lg p-4 mb-4"> |
| <p class="text-gray-800">Hello! I'm your PDF assistant. Ask me anything about the document or request help with editing.</p> |
| </div> |
| `; |
| editorSection.classList.add('hidden'); |
| uploadSection.classList.remove('hidden'); |
| scale = 1.0; |
| zoomLevel.textContent = '100%'; |
| activeTool = null; |
| drawingCanvases = []; |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=luanngo/chatpdf" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |