Spaces:
Sleeping
Sleeping
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>HanSoo Office Editor</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; min-height: 100vh; } | |
| .container { max-width: 1200px; margin: 0 auto; padding: 20px; } | |
| header { background: #fff; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } | |
| h1 { color: #333; font-size: 24px; margin-bottom: 8px; } | |
| .subtitle { color: #666; font-size: 14px; } | |
| .upload-area { background: #fff; padding: 40px; border-radius: 12px; border: 2px dashed #ddd; text-align: center; cursor: pointer; transition: all 0.3s; } | |
| .upload-area:hover { border-color: #4CAF50; background: #f9fff9; } | |
| .upload-area.dragover { border-color: #4CAF50; background: #e8f5e9; } | |
| .upload-icon { font-size: 48px; margin-bottom: 16px; } | |
| .upload-text { color: #666; font-size: 16px; margin-bottom: 8px; } | |
| .upload-hint { color: #999; font-size: 12px; } | |
| .file-list { background: #fff; padding: 20px; border-radius: 12px; margin-top: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } | |
| .file-item { display: flex; align-items: center; padding: 12px; border-bottom: 1px solid #eee; } | |
| .file-item:last-child { border-bottom: none; } | |
| .file-name { flex: 1; color: #333; } | |
| .file-size { color: #999; font-size: 12px; margin-right: 16px; } | |
| .file-actions { display: flex; gap: 8px; } | |
| .btn { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; } | |
| .btn-primary { background: #4CAF50; color: #fff; } | |
| .btn-primary:hover { background: #45a049; } | |
| .btn-secondary { background: #e0e0e0; color: #333; } | |
| .btn-secondary:hover { background: #d0d0d0; } | |
| .btn-danger { background: #f44336; color: #fff; } | |
| .btn-danger:hover { background: #da190b; } | |
| .status { padding: 12px; border-radius: 6px; margin-top: 16px; display: none; } | |
| .status.success { background: #e8f5e9; color: #2e7d32; display: block; } | |
| .status.error { background: #ffebee; color: #c62828; display: block; } | |
| .supported-formats { margin-top: 20px; padding: 16px; background: #fff3e0; border-radius: 8px; } | |
| .supported-formats h3 { font-size: 14px; color: #e65100; margin-bottom: 8px; } | |
| .supported-formats p { font-size: 12px; color: #666; } | |
| #fileInput { display: none; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>HanSoo Office Editor</h1> | |
| <p class="subtitle">HWP Β· DOCX Β· XLSX Β· PPTX Β· PDF νμΌ μ λ‘λ λ° μ²λ¦¬</p> | |
| </header> | |
| <div class="upload-area" id="uploadArea"> | |
| <div class="upload-icon">π</div> | |
| <div class="upload-text">νμΌμ λλκ·Ένκ±°λ ν΄λ¦νμ¬ μ λ‘λ</div> | |
| <div class="upload-hint">μ΅λ 50MB μ§μ</div> | |
| <input type="file" id="fileInput" multiple> | |
| </div> | |
| <div class="supported-formats"> | |
| <h3>μ§μνλ νμΌ νμ</h3> | |
| <p>HWP, HWPX, DOCX, XLSX, PPTX, PDF, TXT, CSV, JSON</p> | |
| </div> | |
| <div class="file-list" id="fileList" style="display: none;"> | |
| <h2 style="margin-bottom: 16px; font-size: 18px;">μ λ‘λλ νμΌ</h2> | |
| <div id="fileItems"></div> | |
| </div> | |
| <div class="status" id="status"></div> | |
| </div> | |
| <script> | |
| const uploadArea = document.getElementById('uploadArea'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileList = document.getElementById('fileList'); | |
| const fileItems = document.getElementById('fileItems'); | |
| const status = document.getElementById('status'); | |
| let uploadedFiles = []; | |
| // λλκ·Έ μ€ λλ‘ | |
| uploadArea.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.add('dragover'); | |
| }); | |
| uploadArea.addEventListener('dragleave', () => { | |
| uploadArea.classList.remove('dragover'); | |
| }); | |
| uploadArea.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.remove('dragover'); | |
| handleFiles(e.dataTransfer.files); | |
| }); | |
| // ν΄λ¦ μ λ‘λ | |
| uploadArea.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', (e) => handleFiles(e.target.files)); | |
| async function handleFiles(files) { | |
| for (const file of files) { | |
| await uploadFile(file); | |
| } | |
| } | |
| async function uploadFile(file) { | |
| showStatus('μ λ‘λ μ€...', 'info'); | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch('/api/upload', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| uploadedFiles.push({ | |
| file_id: data.file_id, | |
| filename: data.filename, | |
| size: data.size | |
| }); | |
| renderFileList(); | |
| showStatus(`"${data.filename}" μ λ‘λ μλ£`, 'success'); | |
| } else { | |
| showStatus('μ λ‘λ μ€ν¨', 'error'); | |
| } | |
| } catch (error) { | |
| showStatus(`μ€λ₯: ${error.message}`, 'error'); | |
| } | |
| } | |
| function renderFileList() { | |
| if (uploadedFiles.length > 0) { | |
| fileList.style.display = 'block'; | |
| fileItems.innerHTML = uploadedFiles.map(f => ` | |
| <div class="file-item"> | |
| <span class="file-name">${escapeHtml(f.filename)}</span> | |
| <span class="file-size">${formatSize(f.size)}</span> | |
| <div class="file-actions"> | |
| <button class="btn btn-primary" onclick="processFile('${f.file_id}', 'extract')">μΆμΆ</button> | |
| <button class="btn btn-danger" onclick="deleteFile('${f.file_id}')">μμ </button> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| } | |
| async function processFile(fileId, operation) { | |
| showStatus('μ²λ¦¬ μ€...', 'info'); | |
| const formData = new FormData(); | |
| formData.append('file_id', fileId); | |
| formData.append('operation', operation); | |
| try { | |
| const response = await fetch('/api/process', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| showStatus('μ²λ¦¬ μλ£', 'success'); | |
| console.log(data); | |
| } else { | |
| showStatus('μ²λ¦¬ μ€ν¨', 'error'); | |
| } | |
| } catch (error) { | |
| showStatus(`μ€λ₯: ${error.message}`, 'error'); | |
| } | |
| } | |
| async function deleteFile(fileId) { | |
| uploadedFiles = uploadedFiles.filter(f => f.file_id !== fileId); | |
| renderFileList(); | |
| if (uploadedFiles.length > 0) { | |
| showStatus('νμΌ μμ μλ£', 'success'); | |
| } | |
| } | |
| function showStatus(message, type) { | |
| status.textContent = message; | |
| status.className = `status ${type}`; | |
| setTimeout(() => { status.className = 'status'; }, 3000); | |
| } | |
| function formatSize(bytes) { | |
| if (bytes < 1024) return bytes + ' B'; | |
| if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; | |
| return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| </script> | |
| </body> | |
| </html> | |