Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Resume Optimizer</title> | |
| <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| <style> | |
| body { | |
| background-color: #f0f2f5; | |
| font-family: 'Arial', sans-serif; | |
| } | |
| :root { | |
| --primary-color: #003366; | |
| --secondary-color: #f0f2f5; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| line-height: 1.6; | |
| margin: 0; | |
| padding: 20px; | |
| background-color: var(--secondary-color); | |
| color: #333; | |
| } | |
| h1, h2 { | |
| color: var(--primary-color); | |
| } | |
| h1 { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| } | |
| .container { | |
| max-width: 800px; | |
| margin: 30px auto; | |
| background-color: #ffffff; | |
| padding: 30px; | |
| border-radius: 10px; | |
| box-shadow: 0 0 20px rgba(0,0,0,0.1); | |
| } | |
| textarea { | |
| width: 100%; | |
| min-height: 150px; | |
| border: 1px solid #ced4da; | |
| border-radius: 5px; | |
| padding: 10px; | |
| } | |
| .spinner { | |
| border: 4px solid #f3f3f3; | |
| border-top: 4px solid #3498db; | |
| border-radius: 50%; | |
| width: 40px; | |
| height: 40px; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .btn-primary, .btn-success, .btn-info { | |
| transition: all 0.3s ease; | |
| } | |
| .btn-primary:hover, .btn-success:hover, .btn-info:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| } | |
| .fade-enter-active, .fade-leave-active { | |
| transition: opacity 0.5s; | |
| } | |
| .fade-enter-from, .fade-leave-to { | |
| opacity: 0; | |
| } | |
| .optimized-content { | |
| background-color: #f8f9fa; | |
| border-radius: 5px; | |
| padding: 20px; | |
| margin-top: 20px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="app" class="container"> | |
| <h1 class="text-center mb-4">Resume Optimizer</h1> | |
| <transition name="fade"> | |
| <form v-if="!optimizedResume" @submit.prevent="optimizeResume"> | |
| <div class="mb-3"> | |
| <label for="resumeText" class="form-label">Resume:</label> | |
| <textarea id="resumeText" v-model="resumeText" class="form-control" placeholder="Paste your resume here"></textarea> | |
| <input type="file" @change="handleResumeFile" class="form-control mt-2" accept=".txt,.pdf,.doc,.docx"> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="jobDescriptionText" class="form-label">Job Description:</label> | |
| <textarea id="jobDescriptionText" v-model="jobDescriptionText" class="form-control" placeholder="Paste the job description here"></textarea> | |
| <input type="text" v-model="jobDescriptionUrl" class="form-control mt-2" placeholder="Or enter job description URL"> | |
| </div> | |
| <button type="submit" class="btn btn-primary w-100" :disabled="isLoading">Optimize Resume</button> | |
| </form> | |
| </transition> | |
| <div v-if="isLoading" class="text-center mt-4"> | |
| <div class="spinner"></div> | |
| <p>Optimizing your resume...</p> | |
| </div> | |
| <transition name="fade"> | |
| <div v-if="optimizedResume" class="mt-4"> | |
| <h3 class="mb-3">Optimized Resume</h2> | |
| <div class="optimized-content" v-html="markedOptimizedResume"></div> | |
| <div class="mt-3"> | |
| <button @click="downloadPdf" class="btn btn-success me-2">Download as PDF</button> | |
| <button @click="downloadDocx" class="btn btn-info">Download as DOCX</button> | |
| <button @click="resetForm" class="btn btn-outline-secondary ms-2">Start Over</button> | |
| </div> | |
| </div> | |
| </transition> | |
| <transition name="fade"> | |
| <div v-if="changesMade" class="mt-4"> | |
| <h3 class="mb-3">Changes Made</h2> | |
| <div class="optimized-content" v-html="markedChangesMade"></div> | |
| </div> | |
| </transition> | |
| <div v-if="errorMessage" class="alert alert-danger mt-4">{{ errorMessage }}</div> | |
| </div> | |
| <script> | |
| const { createApp } = Vue; | |
| createApp({ | |
| data() { | |
| return { | |
| resumeText: '', | |
| resumeFile: null, | |
| jobDescriptionText: '', | |
| jobDescriptionUrl: '', | |
| optimizedResume: '', | |
| changesMade: '', | |
| isLoading: false, | |
| errorMessage: '', | |
| AUTH_TOKEN: "44d5c2ac18ced6fc25c1e57dcd06fc0b31fb4ad97bf56e67540671a647465df4" | |
| } | |
| }, | |
| computed: { | |
| markedOptimizedResume() { | |
| return marked.parse(this.optimizedResume); | |
| }, | |
| markedChangesMade() { | |
| return marked.parse(this.changesMade); | |
| } | |
| }, | |
| methods: { | |
| async optimizeResume() { | |
| this.isLoading = true; | |
| this.errorMessage = ''; | |
| const formData = new FormData(); | |
| if (this.resumeFile) { | |
| formData.append('resume', this.resumeFile); | |
| } else if (this.resumeText) { | |
| formData.append('resumeText', this.resumeText); | |
| } else { | |
| this.errorMessage = 'Please provide a resume (text or file).'; | |
| this.isLoading = false; | |
| return; | |
| } | |
| if (this.jobDescriptionText) { | |
| formData.append('jobDescription', this.jobDescriptionText); | |
| } else if (this.jobDescriptionUrl) { | |
| formData.append('jobDescriptionUrl', this.jobDescriptionUrl); | |
| } else { | |
| this.errorMessage = 'Please provide a job description (text or URL).'; | |
| this.isLoading = false; | |
| return; | |
| } | |
| try { | |
| const response = await fetch('https://pvanand-specialized-agents.hf.space/api/v1/optimize-resume', { | |
| method: 'POST', | |
| headers: { 'Authorization': `Bearer ${this.AUTH_TOKEN}` }, | |
| body: formData | |
| }); | |
| if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); | |
| const data = await response.json(); | |
| this.optimizedResume = data.optimized_resume || 'Optimized resume not found.'; | |
| this.changesMade = data.changes_made || 'Changes not found.'; | |
| } catch (error) { | |
| this.errorMessage = `Error: ${error.message}`; | |
| } finally { | |
| this.isLoading = false; | |
| } | |
| }, | |
| handleResumeFile(event) { | |
| const file = event.target.files[0]; | |
| if (file) { | |
| this.resumeFile = file; | |
| if (file.type === 'application/pdf') { | |
| this.resumeText = `[PDF File: ${file.name}]`; | |
| } else { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| this.resumeText = e.target.result; | |
| }; | |
| reader.readAsText(file); | |
| } | |
| } | |
| }, | |
| async downloadPdf() { | |
| await this.downloadFile('https://pvanand-web-scraping.hf.space/html_to_pdf', 'html_content', 'optimized_resume.pdf'); | |
| }, | |
| async downloadDocx() { | |
| await this.downloadFile('https://pvanand-web-scraping.hf.space/convert', 'html', 'optimized_resume.docx'); | |
| }, | |
| async downloadFile(url, paramName, filename) { | |
| try { | |
| const response = await fetch(url, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ [paramName]: this.markedOptimizedResume }) | |
| }); | |
| if (!response.ok) throw new Error('Conversion failed'); | |
| const blob = await response.blob(); | |
| const downloadUrl = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.style.display = 'none'; | |
| a.href = downloadUrl; | |
| a.download = filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(downloadUrl); | |
| } catch (error) { | |
| this.errorMessage = `Failed to download ${filename}. Please try again.`; | |
| } | |
| }, | |
| resetForm() { | |
| this.resumeText = ''; | |
| this.resumeFile = null; | |
| this.jobDescriptionText = ''; | |
| this.jobDescriptionUrl = ''; | |
| this.optimizedResume = ''; | |
| this.changesMade = ''; | |
| this.errorMessage = ''; | |
| } | |
| } | |
| }).mount('#app'); | |
| </script> | |
| </body> | |
| </html> |