Changes
Build and Push Docker Images / build (push) Successful in 13s

This commit is contained in:
2026-05-04 14:34:38 +03:00
parent ba57695e01
commit 86993fbc66
15 changed files with 1766 additions and 444 deletions
File diff suppressed because one or more lines are too long
+141 -41
View File
@@ -11,6 +11,8 @@
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://myai.ro/cv-matcher/" /> <meta property="og:url" content="https://myai.ro/cv-matcher/" />
<meta name="theme-color" content="#071326" /> <meta name="theme-color" content="#071326" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" type="image/png" href="/img/favicon-256.png" />
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
@@ -21,77 +23,175 @@
<div class="site-shell"> <div class="site-shell">
<header class="header" id="top"> <header class="header" id="top">
<div class="container nav-wrap"> <div class="container nav-wrap">
<a class="brand" href="/" aria-label="MyAi.ro home"><span class="brand-mark ai-mark">AI</span><span><span class="brand-text">MyAi.ro</span><small>CV Matcher</small></span></a> <a class="brand" href="/" aria-label="MyAi.ro home">
<nav class="nav" id="mainNav" aria-label="Primary navigation"><a href="/">Navigator</a><a href="#matcher">Matcher</a><a href="#contact">Contact</a></nav> <span class="brand-mark">
<button class="menu-toggle" id="menuToggle" aria-expanded="false" aria-controls="mainNav"><span></span><span></span><span></span></button> <img src="/img/myai-logo.svg" alt="MyAi.ro">
</span>
<span>
<span class="brand-text">MyAi.ro</span>
<small data-i18n="cv.brand">CV Matcher</small>
</span>
</a>
<nav class="nav" id="mainNav" aria-label="Primary navigation">
<a href="/" data-i18n="nav.navigator">Navigator</a>
<a href="#matcher" data-i18n="nav.matcher">Matcher</a>
<a href="#contact" data-i18n="nav.contact">Contact</a>
</nav>
<div class="nav-actions">
<div class="lang-switch" aria-label="Language selector">
<button class="lang-flag" data-lang="ro" aria-label="Română">
<img src="/img/flags/ro.svg" alt="RO">
</button>
<button class="lang-flag" data-lang="en" aria-label="English">
<img src="/img/flags/en.svg" alt="EN">
</button>
</div>
</div>
<button class="menu-toggle" id="menuToggle" aria-expanded="false" aria-controls="mainNav">
<span></span>
<span></span>
<span></span>
</button>
</div> </div>
</header> </header>
<main> <main>
<section class="hero matcher-hero"> <section class="hero matcher-hero">
<div class="container hero-grid"> <div class="container hero-grid">
<div class="hero-copy"> <div class="hero-copy">
<span class="eyebrow">AI CV Matcher</span> <span class="eyebrow" data-i18n="cv.eyebrow">AI CV Matcher</span>
<h1>Upload your CV, add a job link, and see how well they match.</h1> <h1 data-i18n="cv.title">Upload your CV, add a job link, and see how well they match.</h1>
<p class="hero-text">The backend should extract text from the PDF, create RAG context from your CV, retrieve relevant experience for the job, then score strengths, gaps and next actions.</p> <p class="hero-text" data-i18n="cv.text">The backend should extract text from the PDF, create RAG context from your CV, retrieve relevant experience for the job, then score strengths, gaps and next actions.</p>
<div class="hero-actions"><a class="btn btn-primary" href="#matcher">Try matcher</a><a class="btn btn-secondary" href="/">Back to navigator</a></div> <div class="hero-actions">
<a class="btn btn-primary" href="#matcher" data-i18n="cv.try">Try matcher</a>
<a class="btn btn-secondary" href="/" data-i18n="cv.back">Back to navigator</a>
</div>
</div>
<div class="hero-card ai-console-card banner-card">
<img class="showcase-banner" src="/img/myai-banner.svg" alt="MyAi.ro AI engineering banner">
<div class="console-line">
<span>1</span>
<b data-i18n="cv.step1">PDF text extraction</b>
</div>
<div class="console-line">
<span>2</span>
<b data-i18n="cv.step2">CV chunking + embeddings</b>
</div>
<div class="console-line">
<span>3</span>
<b data-i18n="cv.step3">Job URL/description parsing</b>
</div>
<div class="console-line">
<span>4</span>
<b data-i18n="cv.step4">Match score + evidence</b>
</div> </div>
<div class="hero-card ai-console-card">
<div class="console-line"><span>1</span> PDF text extraction</div>
<div class="console-line"><span>2</span> CV chunking + embeddings</div>
<div class="console-line"><span>3</span> Job URL/description parsing</div>
<div class="console-line"><span>4</span> Match score + evidence</div>
</div> </div>
</div> </div>
</section> </section>
<section class="section" id="matcher"> <section class="section" id="matcher">
<div class="container matcher-grid"> <div class="container matcher-grid">
<form class="ai-panel" id="cvMatcherForm"> <form class="ai-panel" id="cvMatcherForm">
<span class="eyebrow">Input</span> <span class="eyebrow" data-i18n="cv.input">Input</span>
<h2>CV and job details</h2> <h2 data-i18n="cv.details">CV and job details</h2>
<label class="file-drop" for="cvFile"><strong>Upload CV PDF</strong><span id="cvFileName">PDF only, max size handled by backend</span><input type="file" id="cvFile" accept="application/pdf" required /></label> <label class="file-drop" for="cvFile">
<label><span>Job link</span><input type="url" id="jobUrl" placeholder="https://company.com/careers/job" /></label> <strong data-i18n="cv.upload">Upload CV PDF</strong>
<label><span>Or paste job description</span><textarea id="jobDescription" rows="8" placeholder="Paste the job description if the page cannot be crawled."></textarea></label> <span id="cvFileName" data-i18n="cv.fileHint">PDF only, max size handled by backend</span>
<div class="consent-inline"><input type="checkbox" id="gdprConsent" required /><label for="gdprConsent">I agree that my CV is processed for this matching demo. Do not upload sensitive documents unless you trust the deployment.</label></div> <input type="file" id="cvFile" accept="application/pdf" required />
<button id="matchSubmit" type="submit" class="btn btn-primary">Extract CV and match job</button> </label>
<label>
<span data-i18n="cv.jobLink">Job link</span>
<input type="url" id="jobUrl" placeholder="https://company.com/careers/job" />
</label>
<label>
<span data-i18n="cv.jobDescription">Or paste job description</span>
<textarea id="jobDescription" rows="8" data-i18n-placeholder="cv.jobPlaceholder" placeholder="Paste the job description if the page cannot be crawled."></textarea>
</label>
<div class="consent-inline">
<input type="checkbox" id="gdprConsent" required />
<label for="gdprConsent" data-i18n="cv.gdpr">I agree that my CV is processed for this matching demo. Do not upload sensitive documents unless you trust the deployment.</label>
</div>
<button id="matchSubmit" type="submit" class="btn btn-primary" data-i18n="cv.submit">Extract CV and match job</button>
<strong id="matcherMsg" class="form-message"></strong> <strong id="matcherMsg" class="form-message"></strong>
</form> </form>
<aside class="ai-panel result-panel"> <aside class="ai-panel result-panel">
<span class="eyebrow">Result</span> <span class="eyebrow" data-i18n="cv.result">Result</span>
<h2>Match analysis</h2> <h2 data-i18n="cv.analysis">Match analysis</h2>
<div id="matchResult" class="empty-result">Upload a CV and provide a job link or description to generate a result.</div> <div id="matchResult" class="empty-result" data-i18n="cv.empty">Upload a CV and provide a job link or description to generate a result.</div>
</aside> </aside>
</div> </div>
</section> </section>
<section class="section contact" id="contact"> <section class="section contact" id="contact">
<div class="container contact-grid"> <div class="container contact-grid">
<div> <div>
<span class="eyebrow">Contact</span> <span class="eyebrow" data-i18n="nav.contact">Contact</span>
<h2>Want this adapted for your workflow?</h2> <h2 data-i18n="cv.contactTitle">Want this adapted for your workflow?</h2>
<p>This form uses the existing template contact API endpoint.</p> <p data-i18n="cv.contactText">This form uses the existing template contact API endpoint.</p>
<div class="contact-list"><div><span>Contact person</span><strong>Mihes Gelu</strong></div><div><span>Phone</span><strong><a href="tel:+40722523764">+40 722-523-764</a></strong></div></div> <div class="contact-list">
<div>
<span data-i18n="contact.person">Contact person</span>
<strong>Mihes Gelu</strong>
</div>
<div>
<span data-i18n="contact.phone">Phone</span>
<strong>
<a href="tel:+40722523764">+40 722-523-764</a>
</strong>
</div>
</div>
</div> </div>
<form class="contact-form" id="contactForm"> <form class="contact-form" id="contactForm">
<label><span>Name</span><input type="text" id="name" placeholder="Your name" required /></label> <label>
<label><span>Email</span><input type="email" id="email" placeholder="name@company.com" required /></label> <span data-i18n="form.name">Name</span>
<label><span>Message</span><textarea id="message" rows="6" placeholder="Tell me what you want to build." required></textarea></label> <input type="text" id="name" data-i18n-placeholder="form.namePlaceholder" placeholder="Your name" required />
<button id="submit" type="submit" class="btn btn-primary">Send message</button> </label>
<label>
<span data-i18n="form.email">Email</span>
<input type="email" id="email" data-i18n-placeholder="form.emailPlaceholder" placeholder="name@company.com" required />
</label>
<label>
<span data-i18n="form.message">Message</span>
<textarea id="message" rows="6" data-i18n-placeholder="form.messagePlaceholder" placeholder="Tell me what you want to build." required></textarea>
</label>
<button id="submit" type="submit" class="btn btn-primary" data-i18n="form.send">Send message</button>
<strong id="msgSubmit" class="form-message"></strong> <strong id="msgSubmit" class="form-message"></strong>
</form> </form>
</div> </div>
</section> </section>
</main> </main>
<footer class="footer">
<footer class="footer"><div class="container footer-wrap"><p>© <span id="year"></span> MyAi.ro · All rights reserved</p><div class="footer-links footer-legal"><a href="/legal/terms-en.html" target="_blank">Terms</a><a href="/legal/privacy-en.html" target="_blank">Privacy</a><a href="/legal/cookies-en.html" target="_blank">Cookies</a></div><a href="#top" class="back-to-top btn btn-dark btn-sm shadow">Back to top</a></div></footer> <div class="container footer-wrap">
<p>
©
<span id="year"></span> MyAi.ro ·
<span data-i18n="footer.rights">All rights reserved</span>
</p>
<div class="footer-links footer-legal">
<a data-legal="terms" href="/legal/terms-en.html" target="_blank" data-i18n="legal.terms">Terms</a>
<a data-legal="privacy" href="/legal/privacy-en.html" target="_blank" data-i18n="legal.privacy">Privacy</a>
<a data-legal="cookies" href="/legal/cookies-en.html" target="_blank" data-i18n="legal.cookies">Cookies</a>
</div> </div>
<a href="#top" class="back-to-top btn btn-dark btn-sm shadow" data-i18n="footer.top">Back to top</a>
<div id="contactLoader" class="loader-overlay" style="display:none;"><div class="loader-box">Sending...</div></div> </div>
<div id="cookieBanner" class="cookie-overlay" style="display:none;"><div class="cookie-box"><div class="cookie-text"><strong>Cookies</strong><br>We use necessary cookies and, with your consent, analytics through Google Tag Manager. <a href="/legal/privacy-en.html" target="_blank">Privacy policy</a>.</div><div class="cookie-actions"><button id="cookieReject" class="btn btn-warning btn-sm">Reject</button><button id="cookieNecessary" class="btn btn-warning btn-sm">Necessary only</button><button id="cookieAccept" class="btn btn-primary btn-sm">Accept analytics</button></div></div></div> </footer>
<a href="#" id="cookieManage" class="cookie-manage btn btn-dark btn-sm shadow" style="display:none;">Cookie settings</a> </div>
<div id="contactLoader" class="loader-overlay" style="display:none;">
<div class="loader-box" data-i18n="status.sending">Sending...</div>
</div>
<div id="cookieBanner" class="cookie-overlay" style="display:none;">
<div class="cookie-box">
<div class="cookie-text">
<strong data-i18n="cookies.title">Cookies</strong>
<br>
<span data-i18n="cookies.text">We use necessary cookies and, with your consent, analytics through Google Tag Manager.</span>
<a data-legal="privacy" href="/legal/privacy-en.html" target="_blank" data-i18n="cookies.policy">Privacy policy</a>.
</div>
<div class="cookie-actions">
<button id="cookieReject" class="btn btn-warning btn-sm" data-i18n="cookies.reject">Reject</button>
<button id="cookieNecessary" class="btn btn-warning btn-sm" data-i18n="cookies.necessary">Necessary only</button>
<button id="cookieAccept" class="btn btn-primary btn-sm" data-i18n="cookies.accept">Accept analytics</button>
</div>
</div>
</div>
<a href="#" id="cookieManage" class="cookie-manage btn btn-dark btn-sm shadow" style="display:none;" data-i18n="cookies.settings">Cookie settings</a>
<script src="/js/vendor/jquery-1.12.4.min.js"></script> <script src="/js/vendor/jquery-1.12.4.min.js"></script>
<script src="/js/bootstrap.min.js"></script> <script src="/js/bootstrap.min.js"></script>
<script src="/js/myai.js"></script> <script src="/js/myai.js"></script>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 360" role="img" aria-label="MyAi AI engineering banner">
<defs>
<linearGradient id="bg" x1="0" x2="960" y1="0" y2="360" gradientUnits="userSpaceOnUse">
<stop stop-color="#06152B"/><stop offset=".5" stop-color="#0A2B54"/><stop offset="1" stop-color="#2F1F74"/>
</linearGradient>
<linearGradient id="line" x1="120" x2="850" y1="40" y2="320" gradientUnits="userSpaceOnUse">
<stop stop-color="#20E3B2"/><stop offset=".5" stop-color="#5FA0FF"/><stop offset="1" stop-color="#8B6CFF"/>
</linearGradient>
</defs>
<rect width="960" height="360" rx="42" fill="url(#bg)"/>
<g opacity=".18" stroke="#fff"><path d="M0 70h960M0 140h960M0 210h960M0 280h960M140 0v360M280 0v360M420 0v360M560 0v360M700 0v360M840 0v360"/></g>
<path d="M110 250 C240 100, 360 280, 500 142 S720 84, 850 205" fill="none" stroke="url(#line)" stroke-width="18" stroke-linecap="round"/>
<g fill="#fff"><circle cx="110" cy="250" r="18"/><circle cx="500" cy="142" r="18"/><circle cx="850" cy="205" r="18"/></g>
<text x="84" y="138" font-family="Inter, Arial, sans-serif" font-size="58" font-weight="800" fill="#fff">MyAi.ro</text>
<text x="86" y="188" font-family="Inter, Arial, sans-serif" font-size="26" font-weight="600" fill="#BFD1F0">Applied AI engineering showcase</text>
<g transform="translate(700 76)"><rect width="150" height="64" rx="22" fill="rgba(255,255,255,.08)" stroke="rgba(255,255,255,.18)"/><text x="30" y="42" font-family="Inter, Arial" font-size="25" font-weight="800" fill="#fff">RAG</text></g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+18
View File
@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="MyAi logo">
<defs>
<linearGradient id="g" x1="80" x2="420" y1="80" y2="420" gradientUnits="userSpaceOnUse">
<stop stop-color="#5FA0FF"/>
<stop offset="0.55" stop-color="#8B6CFF"/>
<stop offset="1" stop-color="#20E3B2"/>
</linearGradient>
<filter id="s" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="18" stdDeviation="22" flood-color="#2b6bff" flood-opacity=".32"/>
</filter>
</defs>
<rect x="44" y="44" width="424" height="424" rx="116" fill="#06152b"/>
<path d="M126 327V185h31l55 80 55-80h31v142h-34v-88l-42 61h-21l-42-61v88h-33Z" fill="#fff"/>
<path d="M318 327V217h34v110h-34Zm17-124c-11 0-20-8-20-19s9-19 20-19 20 8 20 19-9 19-20 19Z" fill="#fff" opacity=".92"/>
<path d="M92 113c70-55 177-62 250-13 74 50 102 145 68 226" fill="none" stroke="url(#g)" stroke-width="24" stroke-linecap="round" filter="url(#s)"/>
<circle cx="94" cy="113" r="18" fill="#20E3B2"/>
<circle cx="410" cy="326" r="18" fill="#5FA0FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+116 -61
View File
@@ -11,134 +11,189 @@
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://myai.ro/" /> <meta property="og:url" content="https://myai.ro/" />
<meta name="theme-color" content="#071326" /> <meta name="theme-color" content="#071326" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" type="image/png" href="/img/favicon-256.png" />
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/bootstrap.min.css" /> <link rel="stylesheet" href="/css/bootstrap.min.css" />
<link rel="stylesheet" href="css/myai.css" /> <link rel="stylesheet" href="/css/myai.css" />
</head> </head>
<body> <body>
<div class="site-shell"> <div class="site-shell">
<header class="header" id="top"> <header class="header" id="top">
<div class="container nav-wrap"> <div class="container nav-wrap">
<a class="brand" href="/" aria-label="MyAi.ro home"> <a class="brand" href="/" aria-label="MyAi.ro home">
<span class="brand-mark ai-mark">AI</span> <span class="brand-mark">
<img src="/img/myai-logo.svg" alt="MyAi.ro">
</span>
<span> <span>
<span class="brand-text">MyAi.ro</span> <span class="brand-text">MyAi.ro</span>
<small>AI engineering showcase</small> <small data-i18n="brand.subtitle">AI engineering showcase</small>
</span> </span>
</a> </a>
<nav class="nav" id="mainNav" aria-label="Primary navigation"> <nav class="nav" id="mainNav" aria-label="Primary navigation">
<a href="#demos">Demos</a> <a href="#demos" data-i18n="nav.demos">Demos</a>
<a href="#contact">Contact</a> <a href="#contact" data-i18n="nav.contact">Contact</a>
</nav> </nav>
<div class="nav-actions">
<div class="lang-switch" aria-label="Language selector">
<button class="lang-flag" data-lang="ro" aria-label="Română">
<img src="/img/flags/ro.svg" alt="RO">
</button>
<button class="lang-flag" data-lang="en" aria-label="English">
<img src="/img/flags/en.svg" alt="EN">
</button>
</div>
</div>
<button class="menu-toggle" id="menuToggle" aria-expanded="false" aria-controls="mainNav"> <button class="menu-toggle" id="menuToggle" aria-expanded="false" aria-controls="mainNav">
<span></span><span></span><span></span> <span></span>
<span></span>
<span></span>
</button> </button>
</div> </div>
</header> </header>
<main> <main>
<section class="hero navigator-hero"> <section class="hero navigator-hero">
<div class="container hero-grid"> <div class="container hero-grid">
<div class="hero-copy"> <div class="hero-copy">
<span class="eyebrow">Applied AI lab</span> <span class="eyebrow" data-i18n="home.eyebrow">Applied AI lab</span>
<h1>Production-minded AI demos, not generic chatbot wrappers.</h1> <h1 data-i18n="home.title">Production-minded AI demos, not generic chatbot wrappers.</h1>
<p class="hero-text">MyAi.ro is a technical showcase for practical AI systems: document understanding, retrieval, matching, automation and decision support.</p> <p class="hero-text" data-i18n="home.text">MyAi.ro is a technical showcase for practical AI systems: document understanding, retrieval, matching, automation and decision support.</p>
<div class="hero-actions"> <div class="hero-actions">
<a class="btn btn-primary" href="/cv-matcher/">Open CV Matcher</a> <a class="btn btn-primary" href="/cv-matcher/" data-i18n="home.openCv">Open CV Matcher</a>
<a class="btn btn-secondary" href="#contact">Contact</a> <a class="btn btn-secondary" href="#contact" data-i18n="nav.contact">Contact</a>
</div> </div>
</div> </div>
<div class="hero-card ai-console-card"> <div class="hero-card ai-console-card banner-card">
<div class="console-line"><span>upload</span> CV.pdf</div> <img class="showcase-banner" src="/img/myai-banner.svg" alt="MyAi.ro AI engineering banner">
<div class="console-line"><span>extract</span> skills, projects, experience</div> <div class="console-line">
<div class="console-line"><span>retrieve</span> relevant CV context</div> <span>upload</span>
<div class="console-line"><span>score</span> job match + gaps</div> <b data-i18n="home.step1">CV.pdf</b>
</div>
<div class="console-line">
<span>extract</span>
<b data-i18n="home.step2">skills, projects, experience</b>
</div>
<div class="console-line">
<span>retrieve</span>
<b data-i18n="home.step3">relevant CV context</b>
</div>
<div class="console-line">
<span>score</span>
<b data-i18n="home.step4">job match + gaps</b>
</div>
</div> </div>
</div> </div>
</section> </section>
<section class="section" id="demos"> <section class="section" id="demos">
<div class="container"> <div class="container">
<div class="section-heading"> <div class="section-heading">
<span class="eyebrow">Navigator</span> <span class="eyebrow" data-i18n="home.navigator">Navigator</span>
<h2>Select an AI demo</h2> <h2 data-i18n="home.selectDemo">Select an AI demo</h2>
<p>Start with the CV Matcher. More demos can be added here without changing the site structure.</p> <p data-i18n="home.selectText">Start with the CV Matcher. More demos can be added here without changing the site structure.</p>
</div> </div>
<div class="demo-grid"> <div class="demo-grid">
<a class="demo-card active" href="/cv-matcher/"> <a class="demo-card active" href="/cv-matcher/">
<span class="product-tag">Available</span> <span class="product-tag" data-i18n="tag.available">Available</span>
<h3>CV Matcher</h3> <h3 data-i18n="demo.cv.title">CV Matcher</h3>
<p>Upload a CV PDF, provide a job link or description, extract RAG context and generate a match score with strengths and gaps.</p> <p data-i18n="demo.cv.text">Upload a CV PDF, provide a job link or description, extract RAG context and generate a match score with strengths and gaps.</p>
<strong>Open demo →</strong> <strong data-i18n="demo.open">Open demo →</strong>
</a> </a>
<article class="demo-card muted-card"> <article class="demo-card muted-card">
<span class="product-tag">Next</span> <span class="product-tag" data-i18n="tag.next">Next</span>
<h3>RAG Playground</h3> <h3 data-i18n="demo.rag.title">RAG Playground</h3>
<p>Experiment with chunk size, retrieval count and source context transparency.</p> <p data-i18n="demo.rag.text">Experiment with chunk size, retrieval count and source context transparency.</p>
</article> </article>
<article class="demo-card muted-card"> <article class="demo-card muted-card">
<span class="product-tag">Next</span> <span class="product-tag" data-i18n="tag.next">Next</span>
<h3>Agent Automation</h3> <h3 data-i18n="demo.agent.title">Agent Automation</h3>
<p>Job discovery, filtering, ranking and notification workflows.</p> <p data-i18n="demo.agent.text">Job discovery, filtering, ranking and notification workflows.</p>
</article> </article>
</div> </div>
</div> </div>
</section> </section>
<section class="section contact" id="contact"> <section class="section contact" id="contact">
<div class="container contact-grid"> <div class="container contact-grid">
<div> <div>
<span class="eyebrow">Contact</span> <span class="eyebrow" data-i18n="nav.contact">Contact</span>
<h2>Discuss an AI integration or custom software project</h2> <h2 data-i18n="contact.title">Discuss an AI integration or custom software project</h2>
<p>Use the form and it will submit through the existing contact API endpoint from the template.</p> <p data-i18n="contact.text">Use the form and it will submit through the existing contact API endpoint from the template.</p>
<div class="contact-list"> <div class="contact-list">
<div><span>Contact person</span><strong>Mihes Gelu</strong></div> <div>
<div><span>Phone</span><strong><a href="tel:+40722523764">+40 722-523-764</a></strong></div> <span data-i18n="contact.person">Contact person</span>
<div><span>WhatsApp</span><strong><a href="https://wa.me/40744564177" target="_blank" rel="noreferrer">+40 744-564-177</a></strong></div> <strong>Mihes Gelu</strong>
</div>
<div>
<span data-i18n="contact.phone">Phone</span>
<strong>
<a href="tel:+40722523764">+40 722-523-764</a>
</strong>
</div>
<div>
<span>WhatsApp</span>
<strong>
<a href="https://wa.me/40744564177" target="_blank" rel="noreferrer">+40 744-564-177</a>
</strong>
</div>
</div> </div>
</div> </div>
<form class="contact-form" id="contactForm"> <form class="contact-form" id="contactForm">
<label><span>Name</span><input type="text" id="name" placeholder="Your name" required /></label> <label>
<label><span>Email</span><input type="email" id="email" placeholder="name@company.com" required /></label> <span data-i18n="form.name">Name</span>
<label><span>Message</span><textarea id="message" rows="6" placeholder="Tell me what you want to build." required></textarea></label> <input type="text" id="name" data-i18n-placeholder="form.namePlaceholder" placeholder="Your name" required />
<button id="submit" type="submit" class="btn btn-primary">Send message</button> </label>
<label>
<span data-i18n="form.email">Email</span>
<input type="email" id="email" data-i18n-placeholder="form.emailPlaceholder" placeholder="name@company.com" required />
</label>
<label>
<span data-i18n="form.message">Message</span>
<textarea id="message" rows="6" data-i18n-placeholder="form.messagePlaceholder" placeholder="Tell me what you want to build." required></textarea>
</label>
<button id="submit" type="submit" class="btn btn-primary" data-i18n="form.send">Send message</button>
<strong id="msgSubmit" class="form-message"></strong> <strong id="msgSubmit" class="form-message"></strong>
</form> </form>
</div> </div>
</section> </section>
</main> </main>
<footer class="footer"> <footer class="footer">
<div class="container footer-wrap"> <div class="container footer-wrap">
<p>© <span id="year"></span> MyAi.ro · All rights reserved</p> <p>
©
<span id="year"></span> MyAi.ro ·
<span data-i18n="footer.rights">All rights reserved</span>
</p>
<div class="footer-links footer-legal"> <div class="footer-links footer-legal">
<a href="/legal/terms-en.html" target="_blank">Terms</a> <a data-legal="terms" href="/legal/terms-en.html" target="_blank" data-i18n="legal.terms">Terms</a>
<a href="/legal/privacy-en.html" target="_blank">Privacy</a> <a data-legal="privacy" href="/legal/privacy-en.html" target="_blank" data-i18n="legal.privacy">Privacy</a>
<a href="/legal/cookies-en.html" target="_blank">Cookies</a> <a data-legal="cookies" href="/legal/cookies-en.html" target="_blank" data-i18n="legal.cookies">Cookies</a>
</div> </div>
<a href="#top" class="back-to-top btn btn-dark btn-sm shadow">Back to top</a> <a href="#top" class="back-to-top btn btn-dark btn-sm shadow" data-i18n="footer.top">Back to top</a>
</div> </div>
</footer> </footer>
</div> </div>
<div id="contactLoader" class="loader-overlay" style="display:none;">
<div id="contactLoader" class="loader-overlay" style="display:none;"><div class="loader-box">Sending...</div></div> <div class="loader-box" data-i18n="status.sending">Sending...</div>
</div>
<div id="cookieBanner" class="cookie-overlay" style="display:none;"> <div id="cookieBanner" class="cookie-overlay" style="display:none;">
<div class="cookie-box"> <div class="cookie-box">
<div class="cookie-text"><strong>Cookies</strong><br>We use necessary cookies and, with your consent, analytics through Google Tag Manager. <a href="/legal/privacy-en.html" target="_blank">Privacy policy</a>.</div> <div class="cookie-text">
<strong data-i18n="cookies.title">Cookies</strong>
<br>
<span data-i18n="cookies.text">We use necessary cookies and, with your consent, analytics through Google Tag Manager.</span>
<a data-legal="privacy" href="/legal/privacy-en.html" target="_blank" data-i18n="cookies.policy">Privacy policy</a>.
</div>
<div class="cookie-actions"> <div class="cookie-actions">
<button id="cookieReject" class="btn btn-warning btn-sm">Reject</button> <button id="cookieReject" class="btn btn-warning btn-sm" data-i18n="cookies.reject">Reject</button>
<button id="cookieNecessary" class="btn btn-warning btn-sm">Necessary only</button> <button id="cookieNecessary" class="btn btn-warning btn-sm" data-i18n="cookies.necessary">Necessary only</button>
<button id="cookieAccept" class="btn btn-primary btn-sm">Accept analytics</button> <button id="cookieAccept" class="btn btn-primary btn-sm" data-i18n="cookies.accept">Accept analytics</button>
</div> </div>
</div> </div>
</div> </div>
<a href="#" id="cookieManage" class="cookie-manage btn btn-dark btn-sm shadow" style="display:none;">Cookie settings</a> <a href="#" id="cookieManage" class="cookie-manage btn btn-dark btn-sm shadow" style="display:none;" data-i18n="cookies.settings">Cookie settings</a>
<script src="/js/vendor/jquery-1.12.4.min.js"></script>
<script src="js/vendor/jquery-1.12.4.min.js"></script> <script src="/js/bootstrap.min.js"></script>
<script src="js/bootstrap.min.js"></script> <script src="/js/myai.js"></script>
<script src="js/myai.js"></script>
</body> </body>
</html> </html>
+312 -56
View File
@@ -4,8 +4,236 @@
var reCaptchaSiteKey = null; var reCaptchaSiteKey = null;
var gTagManagerId = null; var gTagManagerId = null;
var CONSENT_KEY = "myai_cookie_consent"; var CONSENT_KEY = "myai_cookie_consent";
var LANG_KEY = "myai_lang";
var i18n = {
en: {
"brand.subtitle": "AI engineering showcase",
"nav.demos": "Demos",
"nav.contact": "Contact",
"nav.navigator": "Navigator",
"nav.matcher": "Matcher",
"home.eyebrow": "Applied AI lab",
"home.title": "Production-minded AI demos, not generic chatbot wrappers.",
"home.text": "MyAi.ro is a technical showcase for practical AI systems: document understanding, retrieval, matching, automation and decision support.",
"home.openCv": "Open CV Matcher",
"home.step1": "CV.pdf",
"home.step2": "skills, projects, experience",
"home.step3": "relevant CV context",
"home.step4": "job match + gaps",
"home.navigator": "Navigator",
"home.selectDemo": "Select an AI demo",
"home.selectText": "Our first steps in AI integrations.",
"tag.available": "Available",
"tag.next": "Next",
"demo.cv.title": "CV Matcher",
"demo.cv.text": "Upload a CV PDF, provide a job link or description, extract RAG context and generate a match score with strengths and gaps.",
"demo.open": "Open demo →",
"demo.rag.title": "RAG Playground",
"demo.rag.text": "Experiment with chunk size, retrieval count and source context transparency.",
"demo.agent.title": "Agent Automation",
"demo.agent.text": "Job discovery, filtering, ranking and notification workflows.",
"contact.title": "Discuss an AI integration or custom software project",
"contact.text": "Fill in the form fields and will contact you.",
"contact.person": "Contact person",
"contact.phone": "Phone",
"form.name": "Name",
"form.namePlaceholder": "Your name",
"form.email": "Email",
"form.emailPlaceholder": "name@company.com",
"form.message": "Message",
"form.messagePlaceholder": "Tell me what you want to build.",
"form.send": "Send message",
"form.thanks": "Thank you for your message.",
"form.captchaFailed": "Captcha verification failed.",
"form.failed": "Failed to send the message.",
"footer.rights": "All rights reserved",
"footer.top": "Back to top",
"legal.terms": "Terms",
"legal.privacy": "Privacy",
"legal.cookies": "Cookies",
"status.sending": "Sending...",
"cookies.title": "Cookies",
"cookies.text": "We use necessary cookies and, with your consent, analytics through Google Tag Manager.",
"cookies.policy": "Privacy policy",
"cookies.reject": "Reject",
"cookies.necessary": "Necessary only",
"cookies.accept": "Accept analytics",
"cookies.settings": "Cookie settings",
"cv.brand": "CV Matcher",
"cv.eyebrow": "AI CV Matcher",
"cv.title": "Upload your CV, add a job link, and see how well they match.",
"cv.text": "The backend should extract text from the PDF, create RAG context from your CV, retrieve relevant experience for the job, then score strengths, gaps and next actions.",
"cv.try": "Try matcher",
"cv.back": "Back to navigator",
"cv.step1": "PDF text extraction",
"cv.step2": "CV chunking + embeddings",
"cv.step3": "Job URL/description parsing",
"cv.step4": "Match score + evidence",
"cv.input": "Input",
"cv.details": "CV and job details",
"cv.upload": "Upload CV PDF",
"cv.fileHint": "PDF only, max size handled by backend",
"cv.jobLink": "Job link",
"cv.jobDescription": "Or paste job description",
"cv.jobPlaceholder": "Paste the job description if the page cannot be crawled.",
"cv.gdpr": "I agree that my CV is processed for this matching demo. Do not upload sensitive documents unless you trust the deployment.",
"cv.submit": "Extract CV and match job",
"cv.result": "Result",
"cv.analysis": "Match analysis",
"cv.empty": "Upload a CV and provide a job link or description to generate a result.",
"cv.contactTitle": "Want this adapted for your workflow?",
"cv.contactText": "This form uses the existing template contact API endpoint.",
"cv.noFile": "Please upload a CV PDF.",
"cv.noJob": "Add a job link or paste a job description.",
"cv.noConsent": "GDPR consent is required.",
"cv.processing": "Processing...",
"cv.extracting": "Extracting CV and matching job...",
"cv.processingLong": "Processing CV PDF and job input. Backend endpoints must be available.",
"cv.cvFailed": "CV extraction failed",
"cv.matchFailed": "Job matching failed",
"cv.completed": "Match completed.",
"cv.backendMissing": "The frontend is ready, but the backend endpoints /api/rag/cv and /api/rag/match-job must be implemented.",
"cv.noSummary": "No summary returned.",
"cv.noItems": "No items returned.",
"cv.strengths": "Strengths",
"cv.gaps": "Gaps",
"cv.evidence": "Retrieved CV evidence"
},
ro: {
"brand.subtitle": "prezentare inginerie AI",
"nav.demos": "Demo-uri",
"nav.contact": "Contact",
"nav.navigator": "Navigator",
"nav.matcher": "Matcher",
"home.eyebrow": "Laborator AI aplicat",
"home.title": "Demo-uri AI orientate spre producție, nu simple wrapper-e de chatbot.",
"home.text": "MyAi.ro este o prezentare tehnică pentru sisteme AI practice: înțelegere documente, retrieval, potrivire, automatizare și suport decizional.",
"home.openCv": "Deschide CV Matcher",
"home.step1": "CV.pdf",
"home.step2": "competențe, proiecte, experiență",
"home.step3": "context relevant din CV",
"home.step4": "potrivire job + lipsuri",
"home.navigator": "Navigator",
"home.selectDemo": "Alege un demo AI",
"home.selectText": "Primele noastre projecte",
"tag.available": "Disponibil",
"tag.next": "Urmează",
"demo.cv.title": "CV Matcher",
"demo.cv.text": "Încarcă un CV PDF, adaugă un link sau o descriere de job, extrage context RAG și generează un scor de potrivire cu puncte forte și lipsuri.",
"demo.open": "Deschide demo →",
"demo.rag.title": "RAG Playground",
"demo.rag.text": "Experimentează cu dimensiunea chunk-urilor, numărul de rezultate și transparența surselor.",
"demo.agent.title": "Automatizare cu agenți",
"demo.agent.text": "Fluxuri pentru descoperire joburi, filtrare, ranking și notificări.",
"contact.title": "Discută o integrare AI sau un proiect software custom",
"contact.text": "Complecteaza formularul si te vom contacta.",
"contact.person": "Persoană de contact",
"contact.phone": "Telefon",
"form.name": "Nume",
"form.namePlaceholder": "Numele tău",
"form.email": "Email",
"form.emailPlaceholder": "nume@companie.ro",
"form.message": "Mesaj",
"form.messagePlaceholder": "Spune-mi ce vrei să construiești.",
"form.send": "Trimite mesajul",
"form.thanks": "Mulțumesc pentru mesaj.",
"form.captchaFailed": "Verificarea Captcha a eșuat.",
"form.failed": "Mesajul nu a putut fi trimis.",
"footer.rights": "Toate drepturile rezervate",
"footer.top": "Înapoi sus",
"legal.terms": "Termeni",
"legal.privacy": "Confidențialitate",
"legal.cookies": "Cookies",
"status.sending": "Se trimite...",
"cookies.title": "Cookies",
"cookies.text": "Folosim cookies necesare și, cu acordul tău, analytics prin Google Tag Manager.",
"cookies.policy": "Politica de confidențialitate",
"cookies.reject": "Respinge",
"cookies.necessary": "Doar necesare",
"cookies.accept": "Accept analytics",
"cookies.settings": "Setări cookies",
"cv.brand": "CV Matcher",
"cv.eyebrow": "AI CV Matcher",
"cv.title": "Încarcă CV-ul, adaugă un link de job și vezi cât de bine se potrivesc.",
"cv.text": "Backend-ul ar trebui să extragă textul din PDF, să creeze context RAG din CV, să recupereze experiența relevantă pentru job, apoi să calculeze punctele forte, lipsurile și pașii următori.",
"cv.try": "Încearcă matcherul",
"cv.back": "Înapoi la navigator",
"cv.step1": "Extragere text PDF",
"cv.step2": "Chunking CV + embeddings",
"cv.step3": "Parsare URL/descriere job",
"cv.step4": "Scor potrivire + dovezi",
"cv.input": "Date de intrare",
"cv.details": "CV și detalii job",
"cv.upload": "Încarcă CV PDF",
"cv.fileHint": "Doar PDF, limita de mărime este gestionată de backend",
"cv.jobLink": "Link job",
"cv.jobDescription": "Sau lipește descrierea jobului",
"cv.jobPlaceholder": "Lipește descrierea jobului dacă pagina nu poate fi citită automat.",
"cv.gdpr": "Sunt de acord ca CV-ul meu să fie procesat pentru acest demo de matching. Nu încărca documente sensibile dacă nu ai încredere în deployment.",
"cv.submit": "Extrage CV și compară jobul",
"cv.result": "Rezultat",
"cv.analysis": "Analiză potrivire",
"cv.empty": "Încarcă un CV și adaugă un link sau o descriere de job pentru a genera rezultatul.",
"cv.contactTitle": "Vrei să adaptezi asta pentru workflow-ul tău?",
"cv.contactText": "Formularul folosește endpoint-ul existent de contact din template.",
"cv.noFile": "Te rog încarcă un CV PDF.",
"cv.noJob": "Adaugă un link de job sau lipește descrierea jobului.",
"cv.noConsent": "Consimțământul GDPR este obligatoriu.",
"cv.processing": "Se procesează...",
"cv.extracting": "Se extrage CV-ul și se compară jobul...",
"cv.processingLong": "Se procesează PDF-ul și informațiile despre job. Endpoint-urile backend trebuie să fie disponibile.",
"cv.cvFailed": "Extragerea CV-ului a eșuat",
"cv.matchFailed": "Matching-ul jobului a eșuat",
"cv.completed": "Matching finalizat.",
"cv.backendMissing": "Frontend-ul este pregătit, dar endpoint-urile backend /api/rag/cv și /api/rag/match-job trebuie implementate.",
"cv.noSummary": "Nu a fost returnat niciun sumar.",
"cv.noItems": "Nu au fost returnate elemente.",
"cv.strengths": "Puncte forte",
"cv.gaps": "Lipsuri",
"cv.evidence": "Dovezi recuperate din CV"
}
};
function browserLang() {
return ((navigator.language || navigator.userLanguage || 'en').toLowerCase().indexOf('ro') === 0) ? 'ro' : 'en';
}
function currentLang() {
return localStorage.getItem(LANG_KEY) || browserLang();
}
function t(key) {
var lang = currentLang();
return (i18n[lang] && i18n[lang][key]) || i18n.en[key] || key;
}
function updateLegalLinks(lang) {
$('[data-legal]').each(function () {
var page = $(this).data('legal');
$(this).attr('href', '/legal/' + page + '-' + lang + '.html');
});
}
function applyLanguage(lang) {
localStorage.setItem(LANG_KEY, lang);
document.documentElement.lang = lang;
updateLegalLinks(lang);
$('[data-i18n]').each(function () {
$(this).text(t($(this).data('i18n')));
});
$('[data-i18n-placeholder]').each(function () {
$(this).attr('placeholder', t($(this).data('i18n-placeholder')));
});
$('.lang-flag').attr('aria-pressed', 'false');
$('.lang-flag[data-lang="' + lang + '"]').attr('aria-pressed', 'true');
}
$('#year').text(new Date().getFullYear()); $('#year').text(new Date().getFullYear());
applyLanguage(currentLang());
$('.lang-flag').on('click', function () {
applyLanguage($(this).data('lang'));
});
$('#menuToggle').on('click', function () { $('#menuToggle').on('click', function () {
var $nav = $('#mainNav'); var $nav = $('#mainNav');
@@ -13,7 +241,6 @@
$nav.toggleClass('is-open', open); $nav.toggleClass('is-open', open);
$(this).attr('aria-expanded', open ? 'true' : 'false'); $(this).attr('aria-expanded', open ? 'true' : 'false');
}); });
$('.nav a').on('click', function () { $('.nav a').on('click', function () {
$('#mainNav').removeClass('is-open'); $('#mainNav').removeClass('is-open');
$('#menuToggle').attr('aria-expanded', 'false'); $('#menuToggle').attr('aria-expanded', 'false');
@@ -45,7 +272,10 @@
if (window.__gtm_loaded || !gTagManagerId) return; if (window.__gtm_loaded || !gTagManagerId) return;
window.__gtm_loaded = true; window.__gtm_loaded = true;
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); window.dataLayer.push({
'gtm.start': new Date().getTime(),
event: 'gtm.js'
});
var script = document.createElement('script'); var script = document.createElement('script');
script.async = true; script.async = true;
script.src = 'https://www.googletagmanager.com/gtm/js?id=' + gTagManagerId; script.src = 'https://www.googletagmanager.com/gtm/js?id=' + gTagManagerId;
@@ -53,7 +283,11 @@
} }
function getConsent() { function getConsent() {
try { return JSON.parse(localStorage.getItem(CONSENT_KEY)); } catch { return null; } try {
return JSON.parse(localStorage.getItem(CONSENT_KEY));
} catch (e) {
return null;
}
} }
function setConsent(consent) { function setConsent(consent) {
@@ -64,24 +298,37 @@
if (consent && consent.analytics === true) loadGoogleTagManager(); if (consent && consent.analytics === true) loadGoogleTagManager();
} }
function showBanner() { $('#cookieBanner').fadeIn(200); } function showBanner() {
function hideBanner() { $('#cookieBanner').fadeOut(200); } $('#cookieBanner').fadeIn(200);
function showManage() { $('#cookieManage').show(); } }
function hideBanner() {
$('#cookieBanner').fadeOut(200);
}
function showManage() {
$('#cookieManage').show();
}
$('#cookieReject, #cookieNecessary').on('click', function () { $('#cookieReject, #cookieNecessary').on('click', function () {
setConsent({ necessary: true, analytics: false, ts: new Date().toISOString() }); setConsent({
necessary: true,
analytics: false,
ts: new Date().toISOString()
});
hideBanner(); hideBanner();
showManage(); showManage();
}); });
$('#cookieAccept').on('click', function () { $('#cookieAccept').on('click', function () {
var consent = { necessary: true, analytics: true, ts: new Date().toISOString() }; var consent = {
necessary: true,
analytics: true,
ts: new Date().toISOString()
};
setConsent(consent); setConsent(consent);
applyConsent(consent); applyConsent(consent);
hideBanner(); hideBanner();
showManage(); showManage();
}); });
$('#cookieManage').on('click', function (e) { $('#cookieManage').on('click', function (e) {
e.preventDefault(); e.preventDefault();
showBanner(); showBanner();
@@ -90,17 +337,19 @@
function initConsent() { function initConsent() {
var consent = getConsent(); var consent = getConsent();
if (!consent) showBanner(); if (!consent) showBanner();
else { applyConsent(consent); showManage(); } else {
applyConsent(consent);
showManage();
}
} }
function submitMSG(valid, msg) { function submitMSG(valid, msg) {
var msgClasses = valid ? 'form-message text-success' : 'form-message text-danger'; $('#msgSubmit').removeClass().addClass(valid ? 'form-message text-success' : 'form-message text-danger').text(msg);
$('#msgSubmit').removeClass().addClass(msgClasses).text(msg);
} }
function formSuccess() { function formSuccess() {
$('#contactForm')[0].reset(); $('#contactForm')[0].reset();
submitMSG(true, 'Thank you for your message.'); submitMSG(true, t('form.thanks'));
} }
function formError() { function formError() {
@@ -111,8 +360,8 @@
$('#contactForm').on('submit', function (event) { $('#contactForm').on('submit', function (event) {
event.preventDefault(); event.preventDefault();
var loader = $('#contactLoader'); var loader = $('#contactLoader'),
var button = $('#submit'); button = $('#submit');
loader.css('display', 'flex'); loader.css('display', 'flex');
button.prop('disabled', true); button.prop('disabled', true);
$('#msgSubmit').text(''); $('#msgSubmit').text('');
@@ -133,19 +382,20 @@
dataType: 'json' dataType: 'json'
}).done(function (resp) { }).done(function (resp) {
if (resp && resp.ok === true) formSuccess(); if (resp && resp.ok === true) formSuccess();
else submitMSG(false, 'Captcha verification failed.'); else submitMSG(false, t('form.captchaFailed'));
}).fail(function () { }).fail(function () {
submitMSG(false, 'Failed to send the message.'); submitMSG(false, t('form.failed'));
formError(); formError();
}).always(function () { }).always(function () {
loader.hide(); loader.hide();
button.prop('disabled', false); button.prop('disabled', false);
}); });
} }
if (window.grecaptcha && reCaptchaSiteKey) { if (window.grecaptcha && reCaptchaSiteKey) {
grecaptcha.ready(function () { grecaptcha.ready(function () {
grecaptcha.execute(reCaptchaSiteKey, { action: 'contact' }).then(postContact); grecaptcha.execute(reCaptchaSiteKey, {
action: 'contact'
}).then(postContact);
}); });
} else { } else {
postContact(''); postContact('');
@@ -154,39 +404,47 @@
$('#cvFile').on('change', function () { $('#cvFile').on('change', function () {
var file = this.files && this.files[0]; var file = this.files && this.files[0];
$('#cvFileName').text(file ? file.name : 'PDF only, max size handled by backend'); $('#cvFileName').text(file ? file.name : t('cv.fileHint'));
}); });
$('#cvMatcherForm').on('submit', async function (event) { $('#cvMatcherForm').on('submit', async function (event) {
event.preventDefault(); event.preventDefault();
var file = $('#cvFile')[0] && $('#cvFile')[0].files[0]; var file = $('#cvFile')[0] && $('#cvFile')[0].files[0];
var jobUrl = $('#jobUrl').val(); var jobUrl = $('#jobUrl').val();
var jobDescription = $('#jobDescription').val(); var jobDescription = $('#jobDescription').val();
var consent = $('#gdprConsent').is(':checked'); var consent = $('#gdprConsent').is(':checked');
var $msg = $('#matcherMsg'); var $msg = $('#matcherMsg'),
var $button = $('#matchSubmit'); $button = $('#matchSubmit'),
var $result = $('#matchResult'); $result = $('#matchResult');
if (!file) {
if (!file) { $msg.removeClass().addClass('form-message text-danger').text('Please upload a CV PDF.'); return; } $msg.removeClass().addClass('form-message text-danger').text(t('cv.noFile'));
if (!jobUrl && !jobDescription) { $msg.removeClass().addClass('form-message text-danger').text('Add a job link or paste a job description.'); return; } return;
if (!consent) { $msg.removeClass().addClass('form-message text-danger').text('GDPR consent is required.'); return; } }
if (!jobUrl && !jobDescription) {
$button.prop('disabled', true).text('Processing...'); $msg.removeClass().addClass('form-message text-danger').text(t('cv.noJob'));
$msg.removeClass().addClass('form-message').text('Extracting CV and matching job...'); return;
$result.html('<div class="empty-result">Processing CV PDF and job input. Backend endpoints must be available.</div>'); }
if (!consent) {
$msg.removeClass().addClass('form-message text-danger').text(t('cv.noConsent'));
return;
}
$button.prop('disabled', true).text(t('cv.processing'));
$msg.removeClass().addClass('form-message').text(t('cv.extracting'));
$result.html('<div class="empty-result">' + escapeHtml(t('cv.processingLong')) + '</div>');
try { try {
var formData = new FormData(); var formData = new FormData();
formData.append('cv', file); formData.append('cv', file);
formData.append('gdprConsent', String(consent)); formData.append('gdprConsent', String(consent));
var cvResponse = await fetch('/api/rag/cv', {
var cvResponse = await fetch('/api/rag/cv', { method: 'POST', body: formData }); method: 'POST',
if (!cvResponse.ok) throw new Error('CV extraction failed'); body: formData
});
if (!cvResponse.ok) throw new Error(t('cv.cvFailed'));
var cvData = await cvResponse.json(); var cvData = await cvResponse.json();
var matchResponse = await fetch('/api/rag/match-job', { var matchResponse = await fetch('/api/rag/match-job', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ body: JSON.stringify({
cvDocumentId: cvData.documentId || cvData.cvDocumentId, cvDocumentId: cvData.documentId || cvData.cvDocumentId,
jobUrl: jobUrl, jobUrl: jobUrl,
@@ -194,49 +452,47 @@
gdprConsent: consent gdprConsent: consent
}) })
}); });
if (!matchResponse.ok) throw new Error('Job matching failed'); if (!matchResponse.ok) throw new Error(t('cv.matchFailed'));
var match = await matchResponse.json(); var match = await matchResponse.json();
renderMatchResult(match); renderMatchResult(match);
$msg.removeClass().addClass('form-message text-success').text('Match completed.'); $msg.removeClass().addClass('form-message text-success').text(t('cv.completed'));
} catch (err) { } catch (err) {
console.error(err); console.error(err);
$msg.removeClass().addClass('form-message text-danger').text(err.message || 'Failed to run the matcher.'); $msg.removeClass().addClass('form-message text-danger').text(err.message || t('cv.matchFailed'));
$result.html('<div class="empty-result">The frontend is ready, but the backend endpoints /api/rag/cv and /api/rag/match-job must be implemented.</div>'); $result.html('<div class="empty-result">' + escapeHtml(t('cv.backendMissing')) + '</div>');
} finally { } finally {
$button.prop('disabled', false).text('Extract CV and match job'); $button.prop('disabled', false).text(t('cv.submit'));
} }
}); });
function renderMatchResult(match) { function renderMatchResult(match) {
var score = match.score || match.matchScore || 0; var score = match.score || match.matchScore || 0;
var summary = match.summary || 'No summary returned.'; var summary = match.summary || t('cv.noSummary');
var strengths = match.strengths || []; var strengths = match.strengths || [];
var gaps = match.gaps || match.missingSkills || []; var gaps = match.gaps || match.missingSkills || [];
var evidence = match.evidence || match.retrievedChunks || []; var evidence = match.evidence || match.retrievedChunks || [];
function list(items) { function list(items) {
if (!items || !items.length) return '<p class="empty-result">No items returned.</p>'; if (!items || !items.length) return '<p class="empty-result">' + escapeHtml(t('cv.noItems')) + '</p>';
return '<ul class="result-list">' + items.map(function (x) { return '<ul class="result-list">' + items.map(function (x) {
var text = typeof x === 'string' ? x : (x.text || x.title || JSON.stringify(x)); var text = typeof x === 'string' ? x : (x.text || x.title || JSON.stringify(x));
return '<li>' + escapeHtml(text) + '</li>'; return '<li>' + escapeHtml(text) + '</li>';
}).join('') + '</ul>'; }).join('') + '</ul>';
} }
$('#matchResult').html('<div class="score-badge">' + Number(score).toFixed(0) + '%</div><p>' + escapeHtml(summary) + '</p><h3>' + t('cv.strengths') + '</h3>' + list(strengths) + '<h3>' + t('cv.gaps') + '</h3>' + list(gaps) + '<h3>' + t('cv.evidence') + '</h3>' + list(evidence));
$('#matchResult').html(
'<div class="score-badge">' + Number(score).toFixed(0) + '%</div>' +
'<p>' + escapeHtml(summary) + '</p>' +
'<h3>Strengths</h3>' + list(strengths) +
'<h3>Gaps</h3>' + list(gaps) +
'<h3>Retrieved CV evidence</h3>' + list(evidence)
);
} }
function escapeHtml(value) { function escapeHtml(value) {
return String(value).replace(/[&<>'"]/g, function (char) { return String(value).replace(/[&<>'"]/g, function (char) {
return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' })[char]; return ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
})[char];
}); });
} }
$(window).on('load', function () { $(window).on('load', function () {
$.when(getRecaptchaWebKey(), getGoogleTagManagerId()).always(initConsent); $.when(getRecaptchaWebKey(), getGoogleTagManagerId()).always(initConsent);
}); });
+7 -5
View File
@@ -10,11 +10,13 @@
<body> <body>
<div class="wrap"> <div class="wrap">
<div class="topbar"> <div class="topbar">
<a class="brand" href="\" aria-label="myAi home"> <a class="brand" href="/" aria-label="Back">
<div class="brand-badge"> <span class="brand-mark">
<img src="\logo.png" alt="myAi"> <img src="/img/myai-logo.svg" alt="MyAi.ro">
</div> </span>
<div class="brand-copy">myAi<small>Legal pages</small></div> <span>
<span class="brand-text">Back</span>
</span>
</a> </a>
<div class="switcher"> <div class="switcher">
<a href="cookies-ro.html" class="lang-link " data-lang="ro" aria-label="Română"> <a href="cookies-ro.html" class="lang-link " data-lang="ro" aria-label="Română">
+7 -5
View File
@@ -10,11 +10,13 @@
<body> <body>
<div class="wrap"> <div class="wrap">
<div class="topbar"> <div class="topbar">
<a class="brand" href="\" aria-label="myAi home"> <a class="brand" href="/" aria-label="Inapoi">
<div class="brand-badge"> <span class="brand-mark">
<img src="\logo.png" alt="myAi"> <img src="/img/myai-logo.svg" alt="MyAi.ro">
</div> </span>
<div class="brand-copy">myAi<small>Pagini legale</small></div> <span>
<span class="brand-text">Inapoi</span>
</span>
</a> </a>
<div class="switcher"> <div class="switcher">
<a href="cookies-ro.html" class="lang-link active" data-lang="ro" aria-label="Română"> <a href="cookies-ro.html" class="lang-link active" data-lang="ro" aria-label="Română">
+179 -49
View File
@@ -8,79 +8,209 @@
--line: rgba(255,255,255,.10); --line: rgba(255,255,255,.10);
--accent: #7eb7ff; --accent: #7eb7ff;
} }
*{box-sizing:border-box}
* {
box-sizing: border-box
}
body { body {
margin: 0; margin: 0;
font-family: Arial,Helvetica,sans-serif; font-family: Arial,Helvetica,sans-serif;
background: background: radial-gradient(circle at top left, rgba(79,140,255,.18), transparent 28%), linear-gradient(180deg, var(--bg) 0%, #05111f 100%);
radial-gradient(circle at top left, rgba(79,140,255,.18), transparent 28%),
linear-gradient(180deg, var(--bg) 0%, #05111f 100%);
color: var(--text); color: var(--text);
line-height: 1.75; line-height: 1.75;
} }
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:none} a {
.wrap{max-width:1100px;margin:0 auto;padding:28px 20px 60px} color: var(--accent);
text-decoration: none
}
a:hover {
text-decoration: none
}
.wrap {
max-width: 1100px;
margin: 0 auto;
padding: 28px 20px 60px
}
.topbar { .topbar {
display:flex;justify-content:space-between;align-items:center;gap:16px; display: flex;
padding:10px 0 22px;margin-bottom:24px;border-bottom:1px solid var(--line); justify-content: space-between;
} align-items: center;
.brand{display:flex;align-items:center;gap:14px;} gap: 16px;
.brand-badge { padding: 10px 0 22px;
width: 48px; margin-bottom: 24px;
height: 48px; border-bottom: 1px solid var(--line);
background: none;
} }
.brand-badge img { .brand {
width: 100%; display: flex;
height: 100%; align-items: center;
object-fit: contain; gap: 14px;
}
.brand-text {
font-size: 1.35rem
}
.brand small {
display: none
}
.brand-mark {
width: 42px;
height: 42px
}
.switcher {
display: flex;
align-items: center;
gap: 10px
} }
.brand-copy small{display:block;color:var(--muted);font-weight:400}
.switcher{display:flex;align-items:center;gap:10px}
.switcher a { .switcher a {
display:inline-flex;align-items:center;justify-content:center; display: inline-flex;
width:44px;height:44px;border-radius:999px;border:1px solid var(--line); align-items: center;
background:rgba(255,255,255,.04);transition:.2s ease; justify-content: center;
width: 44px;
height: 44px;
border-radius: 999px;
border: 1px solid var(--line);
background: rgba(255,255,255,.04);
transition: .2s ease;
} }
.switcher a:hover{text-decoration:none;transform:translateY(-1px)}
.switcher a.active{border-color:rgba(126,183,255,.65);box-shadow:0 0 0 3px rgba(126,183,255,.13)} .switcher a:hover {
.switcher img{width:24px;height:24px;display:block} text-decoration: none;
transform: translateY(-1px)
}
.switcher a.active {
border-color: rgba(126,183,255,.65);
box-shadow: 0 0 0 3px rgba(126,183,255,.13)
}
.switcher img {
width: 24px;
height: 24px;
display: block
}
.hero, .content, .footer { .hero, .content, .footer {
background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.02)); background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.02));
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 22px; border-radius: 22px;
box-shadow: 0 25px 60px rgba(0,0,0,.18); box-shadow: 0 25px 60px rgba(0,0,0,.18);
} }
.hero{padding:28px 30px;margin-bottom:18px}
.hero {
padding: 28px 30px;
margin-bottom: 18px
}
.kicker { .kicker {
display:inline-block;padding:8px 12px;border-radius:999px; display: inline-block;
background:rgba(255,255,255,.04);border:1px solid var(--line);color:var(--muted); padding: 8px 12px;
font-size:13px;margin-bottom:12px border-radius: 999px;
background: rgba(255,255,255,.04);
border: 1px solid var(--line);
color: var(--muted);
font-size: 13px;
margin-bottom: 12px
} }
.hero h1{margin:0 0 8px;font-size:40px;line-height:1.08}
.hero p{margin:0;color:var(--muted);max-width:780px} .hero h1 {
.content{padding:30px} margin: 0 0 8px;
.content h2{font-size:28px;line-height:1.15;margin:28px 0 10px} font-size: 40px;
.content h2:first-child{margin-top:0} line-height: 1.08
.content p{margin:0 0 14px;color:#deebff} }
.content ul{margin:0 0 18px 22px;padding:0}
.content li{margin-bottom:8px;color:#deebff} .hero p {
margin: 0;
color: var(--muted);
max-width: 780px
}
.content {
padding: 30px
}
.content h2 {
font-size: 28px;
line-height: 1.15;
margin: 28px 0 10px
}
.content h2:first-child {
margin-top: 0
}
.content p {
margin: 0 0 14px;
color: #deebff
}
.content ul {
margin: 0 0 18px 22px;
padding: 0
}
.content li {
margin-bottom: 8px;
color: #deebff
}
.notice { .notice {
margin:16px 0 18px;padding:16px 18px;border-radius:18px; margin: 16px 0 18px;
background:rgba(126,183,255,.08);border:1px solid rgba(126,183,255,.18) padding: 16px 18px;
border-radius: 18px;
background: rgba(126,183,255,.08);
border: 1px solid rgba(126,183,255,.18)
} }
.footer { .footer {
margin-top:18px;padding:22px 26px;display:flex;justify-content:space-between;gap:16px;align-items:center;flex-wrap:wrap margin-top: 18px;
padding: 22px 26px;
display: flex;
justify-content: space-between;
gap: 16px;
align-items: center;
flex-wrap: wrap
} }
.footer-links{display:flex;gap:18px;flex-wrap:wrap}
.footer small{color:var(--muted)} .footer-links {
.meta{color:var(--muted);font-size:14px} display: flex;
gap: 18px;
flex-wrap: wrap
}
.footer small {
color: var(--muted)
}
.meta {
color: var(--muted);
font-size: 14px
}
@media (max-width:768px) { @media (max-width:768px) {
.hero h1{font-size:32px} .hero h1 {
.content{padding:22px} font-size: 32px
.content h2{font-size:24px} }
.topbar{align-items:flex-start;flex-direction:column}
.content {
padding: 22px
}
.content h2 {
font-size: 24px
}
.topbar {
align-items: flex-start;
flex-direction: column
}
} }
+7 -5
View File
@@ -10,11 +10,13 @@
<body> <body>
<div class="wrap"> <div class="wrap">
<div class="topbar"> <div class="topbar">
<a class="brand" href="\" aria-label="myAi home"> <a class="brand" href="/" aria-label="Back">
<div class="brand-badge"> <span class="brand-mark">
<img src="\logo.png" alt="myAi"> <img src="/img/myai-logo.svg" alt="MyAi.ro">
</div> </span>
<div class="brand-copy">myAi<small>Legal pages</small></div> <span>
<span class="brand-text">Back</span>
</span>
</a> </a>
<div class="switcher"> <div class="switcher">
<a href="privacy-ro.html" class="lang-link " data-lang="ro" aria-label="Română"> <a href="privacy-ro.html" class="lang-link " data-lang="ro" aria-label="Română">
+7 -5
View File
@@ -10,11 +10,13 @@
<body> <body>
<div class="wrap"> <div class="wrap">
<div class="topbar"> <div class="topbar">
<a class="brand" href="\" aria-label="myAi home"> <a class="brand" href="/" aria-label="Inapoi">
<div class="brand-badge"> <span class="brand-mark">
<img src="\logo.png" alt="myAi"> <img src="/img/myai-logo.svg" alt="MyAi.ro">
</div> </span>
<div class="brand-copy">myAi<small>Pagini legale</small></div> <span>
<span class="brand-text">Inapoi</span>
</span>
</a> </a>
<div class="switcher"> <div class="switcher">
<a href="privacy-ro.html" class="lang-link active" data-lang="ro" aria-label="Română"> <a href="privacy-ro.html" class="lang-link active" data-lang="ro" aria-label="Română">
+7 -5
View File
@@ -10,11 +10,13 @@
<body> <body>
<div class="wrap"> <div class="wrap">
<div class="topbar"> <div class="topbar">
<a class="brand" href="\" aria-label="myAi home"> <a class="brand" href="/" aria-label="Back">
<div class="brand-badge"> <span class="brand-mark">
<img src="\logo.png" alt="myAi"> <img src="/img/myai-logo.svg" alt="MyAi.ro">
</div> </span>
<div class="brand-copy">myAi<small>Legal pages</small></div> <span>
<span class="brand-text">Back</span>
</span>
</a> </a>
<div class="switcher"> <div class="switcher">
<a href="terms-ro.html" class="lang-link " data-lang="ro" aria-label="Română"> <a href="terms-ro.html" class="lang-link " data-lang="ro" aria-label="Română">
+7 -5
View File
@@ -10,11 +10,13 @@
<body> <body>
<div class="wrap"> <div class="wrap">
<div class="topbar"> <div class="topbar">
<a class="brand" href="\" aria-label="myAi home"> <a class="brand" href="/" aria-label="Inapoi">
<div class="brand-badge"> <span class="brand-mark">
<img src="\logo.png" alt="myAi"> <img src="/img/myai-logo.svg" alt="MyAi.ro">
</div> </span>
<div class="brand-copy">myAi<small>Pagini legale</small></div> <span>
<span class="brand-text">Inapoi</span>
</span>
</a> </a>
<div class="switcher"> <div class="switcher">
<a href="terms-ro.html" class="lang-link active" data-lang="ro" aria-label="Română"> <a href="terms-ro.html" class="lang-link active" data-lang="ro" aria-label="Română">