michael-agent 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,482 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Career Portal - SmartRecruitAgent</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
9
+ <link rel="stylesheet" href="/static/styles.css">
10
+ <style>
11
+ .job-card {
12
+ transition: transform 0.2s;
13
+ cursor: pointer;
14
+ }
15
+ .job-card:hover {
16
+ transform: translateY(-5px);
17
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
18
+ }
19
+ .job-tag {
20
+ font-size: 0.8rem;
21
+ padding: 0.25rem 0.5rem;
22
+ }
23
+ </style>
24
+ </head>
25
+ <body>
26
+ <header class="bg-dark text-white py-4">
27
+ <div class="container">
28
+ <div class="row align-items-center">
29
+ <div class="col-md-6">
30
+ <h1 class="mb-0">Career Opportunities</h1>
31
+ <p class="lead mb-0">Find your next opportunity with us</p>
32
+ </div>
33
+ <div class="col-md-6 d-flex justify-content-end">
34
+ <a href="/" class="btn btn-outline-light">Dashboard</a>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </header>
39
+
40
+ <div class="container my-5">
41
+ <!-- Search and Filters Section -->
42
+ <div class="card mb-4 shadow-sm">
43
+ <div class="card-body">
44
+ <form id="search-form">
45
+ <div class="row g-3">
46
+ <div class="col-md-6">
47
+ <div class="input-group">
48
+ <span class="input-group-text bg-white"><i class="bi bi-search"></i></span>
49
+ <input type="text" class="form-control" id="search-input" placeholder="Search for jobs...">
50
+ </div>
51
+ </div>
52
+ <div class="col-md-3">
53
+ <select class="form-select" id="location-filter">
54
+ <option value="">All Locations</option>
55
+ <!-- Locations will be populated dynamically -->
56
+ </select>
57
+ </div>
58
+ <div class="col-md-3">
59
+ <button type="submit" class="btn btn-primary w-100">Search Jobs</button>
60
+ </div>
61
+ </div>
62
+ </form>
63
+ </div>
64
+ </div>
65
+
66
+ <!-- Job Listings Container -->
67
+ <div class="row mb-4" id="job-listings-container">
68
+ <!-- Job listings will be loaded dynamically -->
69
+ <div class="col-12 text-center py-5">
70
+ <div class="spinner-border text-primary" role="status">
71
+ <span class="visually-hidden">Loading...</span>
72
+ </div>
73
+ <p class="mt-2">Loading job listings...</p>
74
+ </div>
75
+ </div>
76
+
77
+ <!-- No Results Message (hidden by default) -->
78
+ <div class="text-center py-5 d-none" id="no-results">
79
+ <i class="bi bi-search display-1 text-muted"></i>
80
+ <h3 class="mt-3">No job listings found</h3>
81
+ <p>Please try different search criteria.</p>
82
+ </div>
83
+ </div>
84
+
85
+ <!-- Job Details Modal -->
86
+ <div class="modal fade" id="job-details-modal" tabindex="-1" aria-hidden="true">
87
+ <div class="modal-dialog modal-lg modal-dialog-scrollable">
88
+ <div class="modal-content">
89
+ <div class="modal-header bg-primary text-white">
90
+ <h5 class="modal-title" id="modal-job-title">Job Title</h5>
91
+ <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
92
+ </div>
93
+ <div class="modal-body">
94
+ <div class="mb-4">
95
+ <div class="d-flex justify-content-between align-items-start">
96
+ <div>
97
+ <p class="mb-1" id="modal-job-location"><i class="bi bi-geo-alt"></i> Location</p>
98
+ <div class="d-flex gap-2 mb-3">
99
+ <span class="badge bg-info" id="modal-job-type">Full-time</span>
100
+ <span class="badge bg-secondary" id="modal-job-level">Mid-level</span>
101
+ </div>
102
+ </div>
103
+ <div>
104
+ <span class="badge bg-success" id="modal-job-id">Job ID: JD123456</span>
105
+ </div>
106
+ </div>
107
+
108
+ <hr>
109
+ <div id="modal-job-description">
110
+ <!-- Job description will be loaded here -->
111
+ </div>
112
+
113
+ <div class="mt-4">
114
+ <h6>Required Skills:</h6>
115
+ <div class="d-flex flex-wrap gap-2 mb-3" id="modal-required-skills">
116
+ <!-- Skills will be loaded here -->
117
+ </div>
118
+
119
+ <h6>Preferred Skills:</h6>
120
+ <div class="d-flex flex-wrap gap-2" id="modal-preferred-skills">
121
+ <!-- Skills will be loaded here -->
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ <div class="modal-footer bg-light">
127
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
128
+ <button type="button" class="btn btn-primary" id="apply-button">Apply Now</button>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+
134
+ <!-- Application Modal -->
135
+ <div class="modal fade" id="application-modal" tabindex="-1" aria-hidden="true">
136
+ <div class="modal-dialog">
137
+ <div class="modal-content">
138
+ <div class="modal-header bg-primary text-white">
139
+ <h5 class="modal-title">Apply for <span id="application-job-title"></span></h5>
140
+ <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
141
+ </div>
142
+ <div class="modal-body">
143
+ <form id="application-form">
144
+ <input type="hidden" id="application-job-id" name="job_id">
145
+
146
+ <div class="mb-3">
147
+ <label for="applicant-name" class="form-label">Full Name*</label>
148
+ <input type="text" class="form-control" id="applicant-name" name="name" required>
149
+ </div>
150
+
151
+ <div class="mb-3">
152
+ <label for="applicant-email" class="form-label">Email Address*</label>
153
+ <input type="email" class="form-control" id="applicant-email" name="email" required>
154
+ </div>
155
+
156
+ <div class="mb-3">
157
+ <label for="applicant-phone" class="form-label">Phone Number</label>
158
+ <input type="tel" class="form-control" id="applicant-phone" name="phone">
159
+ </div>
160
+
161
+ <div class="mb-3">
162
+ <label for="resume-file" class="form-label">Upload Resume*</label>
163
+ <input type="file" class="form-control" id="resume-file" name="resume" required accept=".pdf,.doc,.docx">
164
+ <div class="form-text">Accepted formats: PDF, DOC, DOCX</div>
165
+ </div>
166
+
167
+ <div class="mb-3">
168
+ <label for="cover-letter" class="form-label">Cover Letter</label>
169
+ <textarea class="form-control" id="cover-letter" name="cover_letter" rows="4"></textarea>
170
+ </div>
171
+ </form>
172
+ </div>
173
+ <div class="modal-footer">
174
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
175
+ <button type="button" class="btn btn-primary" id="submit-application">
176
+ <span class="spinner-border spinner-border-sm d-none" id="submit-spinner" role="status" aria-hidden="true"></span>
177
+ Submit Application
178
+ </button>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- Success Modal -->
185
+ <div class="modal fade" id="success-modal" tabindex="-1" aria-hidden="true">
186
+ <div class="modal-dialog">
187
+ <div class="modal-content">
188
+ <div class="modal-header bg-success text-white">
189
+ <h5 class="modal-title">Application Submitted!</h5>
190
+ <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
191
+ </div>
192
+ <div class="modal-body text-center py-4">
193
+ <i class="bi bi-check-circle-fill text-success display-1"></i>
194
+ <h4 class="mt-3">Thank You for Applying</h4>
195
+ <p>Your application has been successfully submitted. Our team will review your resume and get back to you soon.</p>
196
+ </div>
197
+ <div class="modal-footer">
198
+ <button type="button" class="btn btn-primary" data-bs-dismiss="modal">Close</button>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ </div>
203
+
204
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
205
+ <script>
206
+ document.addEventListener('DOMContentLoaded', function() {
207
+ // Initialize global variables
208
+ let allJobs = [];
209
+ let locations = new Set();
210
+
211
+ // Load job listings
212
+ fetchJobs();
213
+
214
+ // Event listeners
215
+ document.getElementById('search-form').addEventListener('submit', function(e) {
216
+ e.preventDefault();
217
+ filterJobs();
218
+ });
219
+
220
+ document.getElementById('location-filter').addEventListener('change', filterJobs);
221
+ document.getElementById('search-input').addEventListener('input', filterJobs);
222
+
223
+ // Apply button in job modal
224
+ document.getElementById('apply-button').addEventListener('click', function() {
225
+ // Get job details from the current modal
226
+ const jobId = this.getAttribute('data-job-id');
227
+ const jobTitle = document.getElementById('modal-job-title').textContent;
228
+
229
+ // Set values in application modal
230
+ document.getElementById('application-job-id').value = jobId;
231
+ document.getElementById('application-job-title').textContent = jobTitle;
232
+
233
+ // Hide job details modal and show application modal
234
+ const jobModal = bootstrap.Modal.getInstance(document.getElementById('job-details-modal'));
235
+ jobModal.hide();
236
+
237
+ const applicationModal = new bootstrap.Modal(document.getElementById('application-modal'));
238
+ applicationModal.show();
239
+ });
240
+
241
+ // Submit application
242
+ document.getElementById('submit-application').addEventListener('click', function() {
243
+ const form = document.getElementById('application-form');
244
+
245
+ // Check form validity
246
+ if (!form.checkValidity()) {
247
+ form.reportValidity();
248
+ return;
249
+ }
250
+
251
+ // Get form data
252
+ const formData = new FormData(form);
253
+
254
+ // Add resume file
255
+ const resumeFile = document.getElementById('resume-file').files[0];
256
+ if (!resumeFile) {
257
+ alert('Please select a resume file');
258
+ return;
259
+ }
260
+ formData.append('resume_file', resumeFile);
261
+
262
+ // Show spinner
263
+ this.disabled = true;
264
+ document.getElementById('submit-spinner').classList.remove('d-none');
265
+
266
+ // Submit application
267
+ fetch('/api/apply', {
268
+ method: 'POST',
269
+ body: formData
270
+ })
271
+ .then(response => response.json())
272
+ .then(data => {
273
+ if (data.success) {
274
+ // Hide application modal
275
+ const applicationModal = bootstrap.Modal.getInstance(document.getElementById('application-modal'));
276
+ applicationModal.hide();
277
+
278
+ // Show success modal
279
+ const successModal = new bootstrap.Modal(document.getElementById('success-modal'));
280
+ successModal.show();
281
+
282
+ // Reset form
283
+ form.reset();
284
+ } else {
285
+ alert('Error submitting application: ' + data.error);
286
+ }
287
+ })
288
+ .catch(error => {
289
+ console.error('Error:', error);
290
+ alert('Error submitting application. Please try again.');
291
+ })
292
+ .finally(() => {
293
+ // Hide spinner
294
+ this.disabled = false;
295
+ document.getElementById('submit-spinner').classList.add('d-none');
296
+ });
297
+ });
298
+
299
+ // Functions
300
+ function fetchJobs() {
301
+ fetch('/api/career-jobs')
302
+ .then(response => response.json())
303
+ .then(data => {
304
+ allJobs = data.jobs;
305
+
306
+ // Collect unique locations
307
+ allJobs.forEach(job => {
308
+ if (job.location) {
309
+ locations.add(job.location);
310
+ }
311
+ });
312
+
313
+ // Populate location filter
314
+ const locationFilter = document.getElementById('location-filter');
315
+ locations.forEach(location => {
316
+ const option = document.createElement('option');
317
+ option.value = location;
318
+ option.textContent = location;
319
+ locationFilter.appendChild(option);
320
+ });
321
+
322
+ // Display jobs
323
+ displayJobs(allJobs);
324
+ })
325
+ .catch(error => {
326
+ console.error('Error fetching jobs:', error);
327
+ document.getElementById('job-listings-container').innerHTML = `
328
+ <div class="col-12 text-center py-5">
329
+ <i class="bi bi-exclamation-triangle text-warning display-4"></i>
330
+ <h3 class="mt-3">Error Loading Jobs</h3>
331
+ <p>Please try refreshing the page.</p>
332
+ </div>
333
+ `;
334
+ });
335
+ }
336
+
337
+ function displayJobs(jobs) {
338
+ const container = document.getElementById('job-listings-container');
339
+
340
+ // Show/hide no results message
341
+ if (jobs.length === 0) {
342
+ container.innerHTML = '';
343
+ document.getElementById('no-results').classList.remove('d-none');
344
+ return;
345
+ }
346
+
347
+ document.getElementById('no-results').classList.add('d-none');
348
+
349
+ // Generate HTML for job cards
350
+ container.innerHTML = jobs.map(job => `
351
+ <div class="col-md-6 col-lg-4 mb-4">
352
+ <div class="card h-100 shadow-sm job-card" data-job-id="${job.id}">
353
+ <div class="card-body">
354
+ <div class="d-flex justify-content-between">
355
+ <h5 class="card-title">${job.title}</h5>
356
+ </div>
357
+ <p class="text-muted mb-2"><i class="bi bi-geo-alt"></i> ${job.location || 'Remote'}</p>
358
+ <div class="mb-3">
359
+ <span class="badge bg-info me-1">${job.employment_type || 'Full-time'}</span>
360
+ <span class="badge bg-secondary">${job.experience_level || 'Not specified'}</span>
361
+ </div>
362
+ <h6 class="card-subtitle mb-2">Required Skills:</h6>
363
+ <div class="d-flex flex-wrap gap-1 mb-3">
364
+ ${job.required_skills && job.required_skills.length > 0 ?
365
+ job.required_skills.slice(0, 3).map(skill =>
366
+ `<span class="badge bg-light text-dark job-tag">${skill}</span>`
367
+ ).join('') + (job.required_skills.length > 3 ?
368
+ `<span class="badge bg-light text-dark job-tag">+${job.required_skills.length - 3} more</span>` : '') :
369
+ '<span class="text-muted small">No specific skills required</span>'
370
+ }
371
+ </div>
372
+ <div class="d-grid gap-2 mt-3">
373
+ <button class="btn btn-outline-primary view-job-btn" data-job-id="${job.id}">View Details</button>
374
+ </div>
375
+ </div>
376
+ <div class="card-footer text-muted">
377
+ <small>Posted: ${formatDate(job.created_at)}</small>
378
+ </div>
379
+ </div>
380
+ </div>
381
+ `).join('');
382
+
383
+ // Add click event to each job card
384
+ document.querySelectorAll('.job-card').forEach(card => {
385
+ card.addEventListener('click', function() {
386
+ const jobId = this.getAttribute('data-job-id');
387
+ openJobModal(jobId);
388
+ });
389
+ });
390
+
391
+ // Add click event to view details buttons
392
+ document.querySelectorAll('.view-job-btn').forEach(btn => {
393
+ btn.addEventListener('click', function(e) {
394
+ e.stopPropagation(); // Prevent the card click event from firing
395
+ const jobId = this.getAttribute('data-job-id');
396
+ openJobModal(jobId);
397
+ });
398
+ });
399
+ }
400
+
401
+ function filterJobs() {
402
+ const searchTerm = document.getElementById('search-input').value.toLowerCase();
403
+ const selectedLocation = document.getElementById('location-filter').value.toLowerCase();
404
+
405
+ const filteredJobs = allJobs.filter(job => {
406
+ // Search in title and description
407
+ const matchesSearch = !searchTerm ||
408
+ job.title.toLowerCase().includes(searchTerm) ||
409
+ job.content.toLowerCase().includes(searchTerm) ||
410
+ job.required_skills.some(skill => skill.toLowerCase().includes(searchTerm));
411
+
412
+ // Filter by location
413
+ const matchesLocation = !selectedLocation ||
414
+ (job.location && job.location.toLowerCase() === selectedLocation);
415
+
416
+ return matchesSearch && matchesLocation;
417
+ });
418
+
419
+ displayJobs(filteredJobs);
420
+ }
421
+
422
+ function openJobModal(jobId) {
423
+ // Find the job
424
+ const job = allJobs.find(j => j.id === jobId);
425
+ if (!job) return;
426
+
427
+ // Set modal content
428
+ document.getElementById('modal-job-title').textContent = job.title;
429
+ document.getElementById('modal-job-location').innerHTML = `<i class="bi bi-geo-alt"></i> ${job.location || 'Remote'}`;
430
+ document.getElementById('modal-job-type').textContent = job.employment_type || 'Full-time';
431
+ document.getElementById('modal-job-level').textContent = job.experience_level || 'Not specified';
432
+ document.getElementById('modal-job-id').textContent = `Job ID: ${job.id}`;
433
+
434
+ // Format and set job description with line breaks
435
+ document.getElementById('modal-job-description').innerHTML = job.content.replace(/\n/g, '<br>');
436
+
437
+ // Set required skills
438
+ const requiredSkillsContainer = document.getElementById('modal-required-skills');
439
+ requiredSkillsContainer.innerHTML = '';
440
+ if (job.required_skills && job.required_skills.length > 0) {
441
+ job.required_skills.forEach(skill => {
442
+ const badge = document.createElement('span');
443
+ badge.className = 'badge bg-light text-dark';
444
+ badge.textContent = skill;
445
+ requiredSkillsContainer.appendChild(badge);
446
+ });
447
+ } else {
448
+ requiredSkillsContainer.innerHTML = '<span class="text-muted">No specific skills required</span>';
449
+ }
450
+
451
+ // Set preferred skills
452
+ const preferredSkillsContainer = document.getElementById('modal-preferred-skills');
453
+ preferredSkillsContainer.innerHTML = '';
454
+ if (job.preferred_skills && job.preferred_skills.length > 0) {
455
+ job.preferred_skills.forEach(skill => {
456
+ const badge = document.createElement('span');
457
+ badge.className = 'badge bg-light text-dark';
458
+ badge.textContent = skill;
459
+ preferredSkillsContainer.appendChild(badge);
460
+ });
461
+ } else {
462
+ preferredSkillsContainer.innerHTML = '<span class="text-muted">No preferred skills specified</span>';
463
+ }
464
+
465
+ // Set job ID for apply button
466
+ document.getElementById('apply-button').setAttribute('data-job-id', job.id);
467
+
468
+ // Show modal
469
+ const modal = new bootstrap.Modal(document.getElementById('job-details-modal'));
470
+ modal.show();
471
+ }
472
+
473
+ // Utility function to format dates
474
+ function formatDate(dateString) {
475
+ if (!dateString) return 'Unknown';
476
+ const date = new Date(dateString);
477
+ return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
478
+ }
479
+ });
480
+ </script>
481
+ </body>
482
+ </html>