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