(function ($) { "use strict"; var reCaptchaSiteKey = null; var gTagManagerId = null; var CONSENT_KEY = "myai_cookie_consent"; $('#year').text(new Date().getFullYear()); $('#menuToggle').on('click', function () { var $nav = $('#mainNav'); var open = !$nav.hasClass('is-open'); $nav.toggleClass('is-open', open); $(this).attr('aria-expanded', open ? 'true' : 'false'); }); $('.nav a').on('click', function () { $('#mainNav').removeClass('is-open'); $('#menuToggle').attr('aria-expanded', 'false'); }); function getRecaptchaWebKey() { return $.get('/api/contact').done(function (res) { reCaptchaSiteKey = res; if (reCaptchaSiteKey && !window.__recaptcha_loaded) { window.__recaptcha_loaded = true; var script = document.createElement('script'); script.setAttribute('src', 'https://www.google.com/recaptcha/api.js?render=' + reCaptchaSiteKey); document.head.appendChild(script); } }).fail(function () { console.warn('Could not load reCaptcha site key from /api/contact'); }); } function getGoogleTagManagerId() { return $.get('/api/google/tagmanager').done(function (res) { gTagManagerId = res; }).fail(function () { console.warn('Could not load Google Tag Manager id from /api/google/tagmanager'); }); } function loadGoogleTagManager() { if (window.__gtm_loaded || !gTagManagerId) return; window.__gtm_loaded = true; window.dataLayer = window.dataLayer || []; window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); var script = document.createElement('script'); script.async = true; script.src = 'https://www.googletagmanager.com/gtm/js?id=' + gTagManagerId; document.head.appendChild(script); } function getConsent() { try { return JSON.parse(localStorage.getItem(CONSENT_KEY)); } catch { return null; } } function setConsent(consent) { localStorage.setItem(CONSENT_KEY, JSON.stringify(consent)); } function applyConsent(consent) { if (consent && consent.analytics === true) loadGoogleTagManager(); } function showBanner() { $('#cookieBanner').fadeIn(200); } function hideBanner() { $('#cookieBanner').fadeOut(200); } function showManage() { $('#cookieManage').show(); } $('#cookieReject, #cookieNecessary').on('click', function () { setConsent({ necessary: true, analytics: false, ts: new Date().toISOString() }); hideBanner(); showManage(); }); $('#cookieAccept').on('click', function () { var consent = { necessary: true, analytics: true, ts: new Date().toISOString() }; setConsent(consent); applyConsent(consent); hideBanner(); showManage(); }); $('#cookieManage').on('click', function (e) { e.preventDefault(); showBanner(); }); function initConsent() { var consent = getConsent(); if (!consent) showBanner(); else { applyConsent(consent); showManage(); } } function submitMSG(valid, msg) { var msgClasses = valid ? 'form-message text-success' : 'form-message text-danger'; $('#msgSubmit').removeClass().addClass(msgClasses).text(msg); } function formSuccess() { $('#contactForm')[0].reset(); submitMSG(true, 'Thank you for your message.'); } function formError() { $('#contactForm').removeClass().addClass('contact-form shake').one('animationend', function () { $(this).removeClass('shake'); }); } $('#contactForm').on('submit', function (event) { event.preventDefault(); var loader = $('#contactLoader'); var button = $('#submit'); loader.css('display', 'flex'); button.prop('disabled', true); $('#msgSubmit').text(''); function postContact(token) { var message = { Name: $('#name').val(), Email: $('#email').val(), Subject: '[MyAi.ro contact request]', Message: $('#message').val(), CaptchaToken: token || '' }; $.ajax({ type: 'POST', url: '/api/contact', data: JSON.stringify(message), contentType: 'application/json; charset=utf-8', dataType: 'json' }).done(function (resp) { if (resp && resp.ok === true) formSuccess(); else submitMSG(false, 'Captcha verification failed.'); }).fail(function () { submitMSG(false, 'Failed to send the message.'); formError(); }).always(function () { loader.hide(); button.prop('disabled', false); }); } if (window.grecaptcha && reCaptchaSiteKey) { grecaptcha.ready(function () { grecaptcha.execute(reCaptchaSiteKey, { action: 'contact' }).then(postContact); }); } else { postContact(''); } }); $('#cvFile').on('change', function () { var file = this.files && this.files[0]; $('#cvFileName').text(file ? file.name : 'PDF only, max size handled by backend'); }); $('#cvMatcherForm').on('submit', async function (event) { event.preventDefault(); var file = $('#cvFile')[0] && $('#cvFile')[0].files[0]; var jobUrl = $('#jobUrl').val(); var jobDescription = $('#jobDescription').val(); var consent = $('#gdprConsent').is(':checked'); var $msg = $('#matcherMsg'); var $button = $('#matchSubmit'); var $result = $('#matchResult'); if (!file) { $msg.removeClass().addClass('form-message text-danger').text('Please upload a CV PDF.'); return; } if (!jobUrl && !jobDescription) { $msg.removeClass().addClass('form-message text-danger').text('Add a job link or paste a job description.'); return; } if (!consent) { $msg.removeClass().addClass('form-message text-danger').text('GDPR consent is required.'); return; } $button.prop('disabled', true).text('Processing...'); $msg.removeClass().addClass('form-message').text('Extracting CV and matching job...'); $result.html('
Processing CV PDF and job input. Backend endpoints must be available.
'); try { var formData = new FormData(); formData.append('cv', file); formData.append('gdprConsent', String(consent)); var cvResponse = await fetch('/api/rag/cv', { method: 'POST', body: formData }); if (!cvResponse.ok) throw new Error('CV extraction failed'); var cvData = await cvResponse.json(); var matchResponse = await fetch('/api/rag/match-job', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cvDocumentId: cvData.documentId || cvData.cvDocumentId, jobUrl: jobUrl, jobDescription: jobDescription, gdprConsent: consent }) }); if (!matchResponse.ok) throw new Error('Job matching failed'); var match = await matchResponse.json(); renderMatchResult(match); $msg.removeClass().addClass('form-message text-success').text('Match completed.'); } catch (err) { console.error(err); $msg.removeClass().addClass('form-message text-danger').text(err.message || 'Failed to run the matcher.'); $result.html('
The frontend is ready, but the backend endpoints /api/rag/cv and /api/rag/match-job must be implemented.
'); } finally { $button.prop('disabled', false).text('Extract CV and match job'); } }); function renderMatchResult(match) { var score = match.score || match.matchScore || 0; var summary = match.summary || 'No summary returned.'; var strengths = match.strengths || []; var gaps = match.gaps || match.missingSkills || []; var evidence = match.evidence || match.retrievedChunks || []; function list(items) { if (!items || !items.length) return '

No items returned.

'; return ''; } $('#matchResult').html( '
' + Number(score).toFixed(0) + '%
' + '

' + escapeHtml(summary) + '

' + '

Strengths

' + list(strengths) + '

Gaps

' + list(gaps) + '

Retrieved CV evidence

' + list(evidence) ); } function escapeHtml(value) { return String(value).replace(/[&<>'"]/g, function (char) { return ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' })[char]; }); } $(window).on('load', function () { $.when(getRecaptchaWebKey(), getGoogleTagManagerId()).always(initConsent); }); })(jQuery);