Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Advanced Course Creator Tool</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.4.0/mammoth.browser.min.js"></script> | |
| <style> | |
| .file-drop-area { | |
| border: 2px dashed #3b82f6; | |
| transition: all 0.3s ease; | |
| } | |
| .file-drop-area.active { | |
| border-color: #10b981; | |
| background-color: rgba(16, 185, 129, 0.05); | |
| } | |
| .course-item { | |
| transition: all 0.2s ease; | |
| } | |
| .course-item:hover { | |
| transform: translateY(-2px); | |
| } | |
| /* Custom scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #3b82f6; | |
| border-radius: 4px; | |
| } | |
| /* Modal animation */ | |
| .modal-enter { | |
| opacity: 0; | |
| transform: translateY(-10px); | |
| } | |
| .modal-enter-active { | |
| opacity: 1; | |
| transform: translateY(0); | |
| transition: all 0.3s ease; | |
| } | |
| .loader { | |
| border-top-color: #3b82f6; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-12"> | |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Advanced Course Creator</h1> | |
| <p class="text-lg text-gray-600">Transform your documents into structured online courses</p> | |
| <div class="w-24 h-1 bg-blue-500 mx-auto mt-4 rounded-full"></div> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <!-- Upload Section --> | |
| <div class="lg:col-span-1 bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-6"> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-blue-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-file-upload text-blue-500 text-xl"></i> | |
| </div> | |
| <h2 class="text-xl font-semibold text-gray-800">Upload Documents</h2> | |
| </div> | |
| <div id="fileDropArea" class="file-drop-area rounded-lg p-8 text-center cursor-pointer mb-4"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-blue-400 mb-3"></i> | |
| <p class="font-medium text-gray-600 mb-2">Drag & drop your files here</p> | |
| <p class="text-sm text-gray-500 mb-4">or</p> | |
| <label for="fileInput" class="inline-block px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition cursor-pointer"> | |
| <input id="fileInput" type="file" accept=".txt,.pdf,.docx" multiple class="hidden"> | |
| <span>Browse Files</span> | |
| </label> | |
| <p class="text-xs text-gray-500 mt-3">Supported formats: TXT, PDF, DOCX</p> | |
| </div> | |
| <div class="mt-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">File Processing Options</h3> | |
| <div class="space-y-3"> | |
| <label class="flex items-center"> | |
| <input type="checkbox" id="autoSplitChapters" class="form-checkbox h-4 w-4 text-blue-500" checked> | |
| <span class="ml-2 text-gray-700">Auto-split into chapters</span> | |
| </label> | |
| <label class="flex items-center"> | |
| <input type="checkbox" id="detectHeadings" class="form-checkbox h-4 w-4 text-blue-500" checked> | |
| <span class="ml-2 text-gray-700">Detect headings as sections</span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Course Structure Section --> | |
| <div class="lg:col-span-2 bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-6"> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-purple-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-graduation-cap text-purple-500 text-xl"></i> | |
| </div> | |
| <h2 class="text-xl font-semibold text-gray-800">Course Structure</h2> | |
| </div> | |
| <div class="mb-6"> | |
| <input type="text" id="courseTitle" placeholder="Enter course title" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> | |
| </div> | |
| <div id="courseOutline" class="space-y-3"> | |
| <!-- Example content that will be replaced --> | |
| <div class="p-4 bg-gray-100 rounded-lg text-center text-gray-500"> | |
| <p>Upload documents to see the generated course outline here</p> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <div id="processingLoader" class="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-6 w-6 mr-2 hidden"></div> | |
| <span id="processingStatus" class="text-sm text-gray-500">Ready to process</span> | |
| </div> | |
| <button id="generateCourseBtn" class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition flex items-center disabled:opacity-50"> | |
| <i class="fas fa-magic mr-2"></i> | |
| Generate Course | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Preview Section --> | |
| <div id="previewSection" class="mt-12 bg-white rounded-xl shadow-md overflow-hidden hidden"> | |
| <div class="p-6"> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-green-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-eye text-green-500 text-xl"></i> | |
| </div> | |
| <h2 class="text-xl font-semibold text-gray-800">Course Preview</h2> | |
| </div> | |
| <div class="border border-gray-200 rounded-lg p-6"> | |
| <div id="coursePreview" class="prose max-w-none"> | |
| <!-- Preview content will be inserted here --> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-end space-x-3"> | |
| <button id="exportPdfBtn" class="px-4 py-2 border border-gray-300 hover:bg-gray-50 text-gray-700 font-medium rounded-lg transition flex items-center"> | |
| <i class="fas fa-file-pdf mr-2 text-red-500"></i> | |
| Export PDF | |
| </button> | |
| <button id="exportHtmlBtn" class="px-4 py-2 border border-gray-300 hover:bg-gray-50 text-gray-700 font-medium rounded-lg transition flex items-center"> | |
| <i class="fas fa-file-code mr-2 text-blue-500"></i> | |
| Export HTML | |
| </button> | |
| <button id="publishBtn" class="px-6 py-2 bg-green-500 hover:bg-green-600 text-white font-medium rounded-lg transition flex items-center"> | |
| <i class="fas fa-rocket mr-2"></i> | |
| Publish | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Modal --> | |
| <div id="successModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-xl p-6 max-w-md w-full transform transition-all modal-enter"> | |
| <div class="text-center"> | |
| <div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-4"> | |
| <i class="fas fa-check text-green-500 text-xl"></i> | |
| </div> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2" id="modalTitle">Success!</h3> | |
| <p class="text-sm text-gray-500" id="modalMessage">Your course has been successfully generated.</p> | |
| <div class="mt-5"> | |
| <button id="modalCloseBtn" type="button" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition"> | |
| Continue | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Set PDF.js worker path | |
| pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js'; | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Elements | |
| const fileDropArea = document.getElementById('fileDropArea'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const courseOutline = document.getElementById('courseOutline'); | |
| const generateCourseBtn = document.getElementById('generateCourseBtn'); | |
| const previewSection = document.getElementById('previewSection'); | |
| const processingLoader = document.getElementById('processingLoader'); | |
| const processingStatus = document.getElementById('processingStatus'); | |
| const coursePreview = document.getElementById('coursePreview'); | |
| const successModal = document.getElementById('successModal'); | |
| const modalCloseBtn = document.getElementById('modalCloseBtn'); | |
| const autoSplitChapters = document.getElementById('autoSplitChapters'); | |
| const detectHeadings = document.getElementById('detectHeadings'); | |
| const courseTitle = document.getElementById('courseTitle'); | |
| // File handling | |
| fileDropArea.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| fileDropArea.classList.add('active'); | |
| }); | |
| fileDropArea.addEventListener('dragleave', () => { | |
| fileDropArea.classList.remove('active'); | |
| }); | |
| fileDropArea.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| fileDropArea.classList.remove('active'); | |
| handleFiles(e.dataTransfer.files); | |
| }); | |
| fileInput.addEventListener('change', () => { | |
| if (fileInput.files.length > 0) { | |
| handleFiles(fileInput.files); | |
| } | |
| }); | |
| // Initialize empty course data | |
| let courseData = { | |
| title: '', | |
| chapters: [] | |
| }; | |
| // Process uploaded files | |
| async function handleFiles(files) { | |
| processingLoader.classList.remove('hidden'); | |
| processingStatus.textContent = 'Processing files...'; | |
| generateCourseBtn.disabled = true; | |
| courseData = { | |
| title: courseTitle.value || 'New Course', | |
| chapters: [] | |
| }; | |
| for (let file of files) { | |
| try { | |
| const content = await extractTextFromFile(file); | |
| const chapterTitle = file.name.replace(/\.[^/.]+$/, ''); // Remove extension | |
| courseData.chapters.push({ | |
| title: chapterTitle, | |
| content: content, | |
| sections: autoSplitChapters.checked && detectHeadings.checked ? | |
| splitIntoSections(content) : | |
| [{ title: 'Content', content: content }] | |
| }); | |
| } catch (error) { | |
| console.error('Error processing file:', error); | |
| } | |
| } | |
| renderCourseOutline(); | |
| processingLoader.classList.add('hidden'); | |
| processingStatus.textContent = `${files.length} file(s) processed`; | |
| generateCourseBtn.disabled = false; | |
| } | |
| // Extract text from different file types | |
| async function extractTextFromFile(file) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = async (e) => { | |
| try { | |
| if (file.type === 'application/pdf') { | |
| const pdf = await pdfjsLib.getDocument(e.target.result).promise; | |
| let text = ''; | |
| for (let i = 1; i <= pdf.numPages; i++) { | |
| const page = await pdf.getPage(i); | |
| const content = await page.getTextContent(); | |
| const strings = content.items.map(item => item.str); | |
| text += strings.join(' ') + '\n'; | |
| } | |
| resolve(text); | |
| } else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') { | |
| const arrayBuffer = e.target.result; | |
| const result = await mammoth.extractRawText({ arrayBuffer }); | |
| resolve(result.value); | |
| } else { // Plain text | |
| resolve(e.target.result); | |
| } | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }; | |
| if (file.type === 'application/pdf' || file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') { | |
| reader.readAsArrayBuffer(file); | |
| } else { | |
| reader.readAsText(file); | |
| } | |
| }); | |
| } | |
| // Split content into sections based on headings | |
| function splitIntoSections(content) { | |
| // Simple implementation - can be enhanced with NLP | |
| const lines = content.split('\n'); | |
| const sections = []; | |
| let currentSection = { title: 'Introduction', content: '' }; | |
| for (const line of lines) { | |
| if (line.match(/^(#+ )|([A-Z][^\.!?]+:\s*$)|(Chapter \d+:|Section \d+:)/)) { | |
| if (currentSection.content.trim()) { | |
| sections.push(currentSection); | |
| } | |
| currentSection = { title: line.trim(), content: '' }; | |
| } else { | |
| currentSection.content += line + '\n'; | |
| } | |
| } | |
| if (currentSection.content.trim()) { | |
| sections.push(currentSection); | |
| } | |
| return sections.length > 0 ? sections : [{ title: 'Content', content }]; | |
| } | |
| // Render the course outline | |
| function renderCourseOutline() { | |
| if (courseData.chapters.length === 0) { | |
| courseOutline.innerHTML = ` | |
| <div class="p-4 bg-gray-100 rounded-lg text-center text-gray-500"> | |
| <p>Upload documents to see the generated course outline here</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| let html = '<div class="space-y-4">'; | |
| courseData.chapters.forEach((chapter, chapterIndex) => { | |
| html += ` | |
| <div class="course-item bg-white border border-gray-200 rounded-lg overflow-hidden"> | |
| <div class="px-4 py-3 bg-gray-50 border-b border-gray-200 flex justify-between items-center"> | |
| <h3 class="font-medium text-gray-800 flex items-center"> | |
| <i class="far fa-folder-open text-blue-400 mr-2"></i> | |
| ${chapter.title} | |
| </h3> | |
| <button class="text-gray-400 hover:text-gray-600" onclick="toggleChapterSections(${chapterIndex})"> | |
| <i class="fas fa-chevron-down"></i> | |
| </button> | |
| </div> | |
| <div id="chapter-${chapterIndex}-sections" class="divide-y divide-gray-200"> | |
| `; | |
| chapter.sections.forEach((section, sectionIndex) => { | |
| html += ` | |
| <div class="px-4 py-3 flex items-start"> | |
| <div class="mr-3 mt-1"> | |
| <i class="far fa-file-alt text-gray-400"></i> | |
| </div> | |
| <div class="flex-1"> | |
| <h4 class="font-medium text-gray-700">${section.title}</h4> | |
| <p class="text-sm text-gray-500 line-clamp-2">${section.content.substring(0, 100)}...</p> | |
| </div> | |
| <div class="flex space-x-2 ml-2"> | |
| <button class="text-gray-400 hover:text-blue-500" title="Edit" onclick="editSection(${chapterIndex}, ${sectionIndex})"> | |
| <i class="fas fa-pencil-alt text-sm"></i> | |
| </button> | |
| <button class="text-gray-400 hover:text-red-500" title="Delete" onclick="deleteSection(${chapterIndex}, ${sectionIndex})"> | |
| <i class="fas fa-trash-alt text-sm"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| html += ` | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| html += ` | |
| <button onclick="addNewChapter()" class="w-full p-3 border-2 border-dashed border-gray-300 hover:border-blue-400 rounded-lg text-gray-500 hover:text-blue-500 transition flex items-center justify-center"> | |
| <i class="fas fa-plus mr-2"></i> | |
| Add New Chapter | |
| </button> | |
| </div> | |
| `; | |
| courseOutline.innerHTML = html; | |
| } | |
| // Generate the final course | |
| generateCourseBtn.addEventListener('click', function() { | |
| processingLoader.classList.remove('hidden'); | |
| processingStatus.textContent = 'Generating course...'; | |
| // Update course title if changed | |
| courseData.title = courseTitle.value || 'New Course'; | |
| // Generate preview | |
| setTimeout(() => { | |
| renderCoursePreview(); | |
| previewSection.classList.remove('hidden'); | |
| processingLoader.classList.add('hidden'); | |
| processingStatus.textContent = 'Course generated'; | |
| // Scroll to preview | |
| previewSection.scrollIntoView({ behavior: 'smooth' }); | |
| // Show success modal | |
| showModal('Course Generated', 'Your course has been successfully created from your documents.'); | |
| }, 1000); | |
| }); | |
| // Render the course preview | |
| function renderCoursePreview() { | |
| let html = ` | |
| <h1 class="text-3xl font-bold mb-6 text-center">${courseData.title}</h1> | |
| <div class="space-y-8"> | |
| `; | |
| courseData.chapters.forEach(chapter => { | |
| html += ` | |
| <div class="chapter"> | |
| <h2 class="text-2xl font-semibold mb-4 flex items-center"> | |
| <i class="far fa-folder-open text-blue-500 mr-3"></i> | |
| ${chapter.title} | |
| </h2> | |
| <div class="ml-8 space-y-6"> | |
| `; | |
| chapter.sections.forEach(section => { | |
| html += ` | |
| <div class="section"> | |
| <h3 class="text-xl font-medium mb-3">${section.title}</h3> | |
| <div class="prose prose-sm max-w-none"> | |
| <p>${section.content.split('\n').join('</p><p>')}</p> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| html += ` | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| html += `</div>`; | |
| coursePreview.innerHTML = html; | |
| } | |
| // Show modal | |
| function showModal(title, message) { | |
| document.getElementById('modalTitle').textContent = title; | |
| document.getElementById('modalMessage').textContent = message; | |
| successModal.classList.remove('hidden'); | |
| } | |
| // Close modal | |
| modalCloseBtn.addEventListener('click', function() { | |
| successModal.classList.add('hidden'); | |
| }); | |
| // Export functions | |
| document.getElementById('exportPdfBtn').addEventListener('click', function() { | |
| showModal('Export Feature', 'PDF export would be implemented here with a library like jsPDF.'); | |
| }); | |
| document.getElementById('exportHtmlBtn').addEventListener('click', function() { | |
| showModal('Export Feature', 'HTML export would be implemented here, generating a downloadable HTML file.'); | |
| }); | |
| document.getElementById('publishBtn').addEventListener('click', function() { | |
| showModal('Publish Feature', 'In a real application, this would publish your course to a learning management system.'); | |
| }); | |
| // Global functions accessible from inline handlers | |
| window.toggleChapterSections = function(chapterIndex) { | |
| const sectionsDiv = document.getElementById(`chapter-${chapterIndex}-sections`); | |
| const toggleBtn = sectionsDiv.previousElementSibling.querySelector('button'); | |
| if (sectionsDiv.classList.contains('hidden')) { | |
| sectionsDiv.classList.remove('hidden'); | |
| toggleBtn.innerHTML = '<i class="fas fa-chevron-down"></i>'; | |
| } else { | |
| sectionsDiv.classList.add('hidden'); | |
| toggleBtn.innerHTML = '<i class="fas fa-chevron-right"></i>'; | |
| } | |
| }; | |
| window.editSection = function(chapterIndex, sectionIndex) { | |
| const section = courseData.chapters[chapterIndex].sections[sectionIndex]; | |
| showModal('Edit Section', `Editing feature would be implemented here for: "${section.title}"`); | |
| }; | |
| window.deleteSection = function(chapterIndex, sectionIndex) { | |
| if (confirm('Are you sure you want to delete this section?')) { | |
| courseData.chapters[chapterIndex].sections.splice(sectionIndex, 1); | |
| renderCourseOutline(); | |
| } | |
| }; | |
| window.addNewChapter = function() { | |
| const chapterTitle = prompt('Enter chapter title:', 'New Chapter'); | |
| if (chapterTitle) { | |
| courseData.chapters.push({ | |
| title: chapterTitle, | |
| content: '', | |
| sections: [{ title: 'New Section', content: '' }] | |
| }); | |
| renderCourseOutline(); | |
| } | |
| }; | |
| }); | |
| </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=zoeboy/criador-de-cursos" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
| </html> |