|
<!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> |