33< head >
44< meta charset ="UTF-8 ">
55< meta name ="viewport " content ="width=device-width,initial-scale=1.0 ">
6- < title > AI Interview – SimpaticoHR</ title >
6+ < title > AI Interview � SimpaticoHR</ title >
77< link href ="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap " rel ="stylesheet ">
88< link rel ="stylesheet " href ="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css ">
99< script src ="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js "> </ script >
@@ -237,11 +237,11 @@ <h1>Simpatico<span>HR</span></h1>
237237 < p class ="setup-sub "> Enterprise AI Proctored Interview</ p >
238238
239239 < div class ="job-card " id ="jobCard ">
240- < div class ="jc-title " id ="jcTitle "> Loading position… </ div >
240+ < div class ="jc-title " id ="jcTitle "> Loading position� </ div >
241241 < div class ="jc-meta ">
242- < span id ="jcDept "> < i class ="fas fa-building "> </ i > — </ span >
243- < span id ="jcExp "> < i class ="fas fa-briefcase "> </ i > — </ span >
244- < span id ="jcLoc "> < i class ="fas fa-map-marker-alt "> </ i > — </ span >
242+ < span id ="jcDept "> < i class ="fas fa-building "> </ i > � </ span >
243+ < span id ="jcExp "> < i class ="fas fa-briefcase "> </ i > � </ span >
244+ < span id ="jcLoc "> < i class ="fas fa-map-marker-alt "> </ i > � </ span >
245245 </ div >
246246 < div class ="jc-skills " id ="jcSkills "> </ div >
247247 </ div >
@@ -256,7 +256,7 @@ <h1>Simpatico<span>HR</span></h1>
256256 < input type ="file " id ="resumeInput " accept =".pdf,.txt " style ="display:none " onchange ="handleResumeUpload(event) ">
257257 </ div >
258258 < div style ="font-size:11px;color:var(--muted);margin:10px 0;font-weight:700 "> OR MANUALLY ENTER SKILLS / PROJECTS</ div >
259- < textarea id ="manualBackground " class ="manual-bg-input " placeholder ="E.g., 5 years in React, built scalable microservices, strong in AWS, led team of 6… "> </ textarea >
259+ < textarea id ="manualBackground " class ="manual-bg-input " placeholder ="E.g., 5 years in React, built scalable microservices, strong in AWS, led team of 6� "> </ textarea >
260260 </ div >
261261
262262 < div class ="lang-label "> < i class ="fas fa-globe "> </ i > Select Interview Language</ div >
@@ -266,6 +266,19 @@ <h1>Simpatico<span>HR</span></h1>
266266 < button class ="lang-btn " data-lang ="ml " onclick ="pickLang('ml',this) "> ???? ??????</ button >
267267 < button class ="lang-btn " data-lang ="ta " onclick ="pickLang('ta',this) "> ???? ?????</ button >
268268 < button class ="lang-btn " data-lang ="ar " onclick ="pickLang('ar',this) "> ???? ?????</ button >
269+ < button class ="lang-btn " data-lang ="fr " onclick ="pickLang('fr',this) "> 🇫🇷 Français</ button >
270+ < button class ="lang-btn " data-lang ="de " onclick ="pickLang('de',this) "> 🇩🇪 Deutsch</ button >
271+ < button class ="lang-btn " data-lang ="es " onclick ="pickLang('es',this) "> 🇪🇸 Español</ button >
272+ < button class ="lang-btn " data-lang ="pt " onclick ="pickLang('pt',this) "> 🇵🇹 Português</ button >
273+ < button class ="lang-btn " data-lang ="it " onclick ="pickLang('it',this) "> 🇮🇹 Italiano</ button >
274+ < button class ="lang-btn " data-lang ="nl " onclick ="pickLang('nl',this) "> 🇳🇱 Nederlands</ button >
275+ < button class ="lang-btn " data-lang ="pl " onclick ="pickLang('pl',this) "> 🇵🇱 Polski</ button >
276+ < button class ="lang-btn " data-lang ="sv " onclick ="pickLang('sv',this) "> 🇸🇪 Svenska</ button >
277+ < button class ="lang-btn " data-lang ="da " onclick ="pickLang('da',this) "> 🇩🇰 Dansk</ button >
278+ < button class ="lang-btn " data-lang ="fi " onclick ="pickLang('fi',this) "> 🇫🇮 Suomi</ button >
279+ < button class ="lang-btn " data-lang ="no " onclick ="pickLang('no',this) "> 🇳🇴 Norsk</ button >
280+ < button class ="lang-btn " data-lang ="ro " onclick ="pickLang('ro',this) "> 🇷🇴 Română</ button >
281+ < button class ="lang-btn " data-lang ="el " onclick ="pickLang('el',this) "> 🇬🇷 Ελληνικά</ button >
269282 </ div >
270283
271284 < div class ="setup-cam ">
@@ -306,7 +319,7 @@ <h2>Screen Sharing Required</h2>
306319 < div style ="width:80px;height:80px;font-size:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(99,102,241,0.1);color:var(--primary);margin-bottom:24px;border:2px solid rgba(99,102,241,0.3);animation:pulse 1s infinite ">
307320 < i class ="fas fa-circle-notch fa-spin "> </ i >
308321 </ div >
309- < h2 style ="color:#fff;font-weight:900;font-size:28px "> Finalizing Assessment… </ h2 >
322+ < h2 style ="color:#fff;font-weight:900;font-size:28px "> Finalizing Assessment� </ h2 >
310323 < p style ="color:var(--dim);margin-top:12px;font-size:15px;font-weight:500 "> AI is evaluating your responses, technical depth, and proctoring logs.</ p >
311324</ div >
312325
@@ -355,15 +368,15 @@ <h2>Screen Sharing Required</h2>
355368 < div class ="score-ticker ">
356369 < div class ="q-label " id ="qOverlay "> Q 0</ div >
357370 < div class ="mini-scores ">
358- < div class ="ms ms-t " id ="tScore "> T— </ div >
359- < div class ="ms ms-b " id ="bScore "> B— </ div >
360- < div class ="ms ms-p " id ="pScore "> P— </ div >
371+ < div class ="ms ms-t " id ="tScore "> T� </ div >
372+ < div class ="ms ms-b " id ="bScore "> B� </ div >
373+ < div class ="ms ms-p " id ="pScore "> P� </ div >
361374 </ div >
362375 </ div >
363376 < div class ="viz-wrap " id ="vizWrap "> </ div >
364377 < div class ="orb-wrap ">
365378 < div class ="orb " id ="orb "> ??</ div >
366- < div class ="status-text " id ="statusTxt "> CONNECTING… </ div >
379+ < div class ="status-text " id ="statusTxt "> CONNECTING� </ div >
367380 < div class ="subtitle " id ="subtitle "> </ div >
368381 </ div >
369382 < div class ="self-cam ">
@@ -403,10 +416,23 @@ <h2>Screen Sharing Required</h2>
403416 ml :{ s :'ml-IN' , t :'ml-IN' , n :'Malayalam' } ,
404417 ta :{ s :'ta-IN' , t :'ta-IN' , n :'Tamil' } ,
405418 ar :{ s :'ar-SA' , t :'ar-SA' , n :'Arabic' } ,
419+ fr :{ s :'fr-FR' , t :'fr-FR' , n :'French' } ,
420+ de :{ s :'de-DE' , t :'de-DE' , n :'German' } ,
421+ es :{ s :'es-ES' , t :'es-ES' , n :'Spanish' } ,
422+ pt :{ s :'pt-PT' , t :'pt-PT' , n :'Portuguese' } ,
423+ it :{ s :'it-IT' , t :'it-IT' , n :'Italian' } ,
424+ nl :{ s :'nl-NL' , t :'nl-NL' , n :'Dutch' } ,
425+ pl :{ s :'pl-PL' , t :'pl-PL' , n :'Polish' } ,
426+ sv :{ s :'sv-SE' , t :'sv-SE' , n :'Swedish' } ,
427+ da :{ s :'da-DK' , t :'da-DK' , n :'Danish' } ,
428+ fi :{ s :'fi-FI' , t :'fi-FI' , n :'Finnish' } ,
429+ no :{ s :'nb-NO' , t :'nb-NO' , n :'Norwegian' } ,
430+ ro :{ s :'ro-RO' , t :'ro-RO' , n :'Romanian' } ,
431+ el :{ s :'el-GR' , t :'el-GR' , n :'Greek' } ,
406432} ;
407433
408434// ----------------------------------------------------------------
409- // GUARD — No Token
435+ // GUARD � No Token
410436// ----------------------------------------------------------------
411437if ( ! urlToken ) {
412438 document . addEventListener ( 'DOMContentLoaded' , ( ) => {
@@ -506,14 +532,18 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:10px;font-weight:800">Acce
506532// AI CALL
507533// ----------------------------------------------------------------
508534async function aiCall ( messages , maxTokens = 600 ) {
509- const r = await fetch ( W + '/api/interview' , {
535+ const tenantId = interviewMeta ?. company_id || 'default' ;
536+ const r = await fetch ( W + '/ai/interview-question' , {
510537 method :'POST' ,
511- headers :{ 'Content-Type' :'application/json' } ,
512- body :JSON . stringify ( { messages, model :'llama-3.3-70b-versatile' , max_tokens :maxTokens } )
538+ headers :{
539+ 'Content-Type' :'application/json' ,
540+ 'X-Tenant-ID' : tenantId
541+ } ,
542+ body :JSON . stringify ( { messages, token : curToken , max_tokens :maxTokens } )
513543 } ) ;
514544 if ( ! r . ok ) throw new Error ( 'AI ' + r . status ) ;
515545 const d = await r . json ( ) ;
516- return d . choices ?. [ 0 ] ?. message ?. content || d . content || d . response || '' ;
546+ return d . data ?. response || d . response || d . choices ?. [ 0 ] ?. message ?. content || d . content || '' ;
517547}
518548
519549// ----------------------------------------------------------------
@@ -523,7 +553,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:10px;font-weight:800">Acce
523553 const file = e . target . files [ 0 ] ; if ( ! file ) return ;
524554 const zone = document . getElementById ( 'resumeDropZone' ) ;
525555 const lbl = document . getElementById ( 'resumeLabel' ) ;
526- lbl . innerHTML = '<i class="fas fa-spinner fa-spin"></i> Reading ' + file . name + '… ' ;
556+ lbl . innerHTML = '<i class="fas fa-spinner fa-spin"></i> Reading ' + file . name + '� ' ;
527557 try {
528558 if ( file . type === 'application/pdf' || file . name . endsWith ( '.pdf' ) ) {
529559 if ( window . pdfjsLib ) {
@@ -551,9 +581,9 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:10px;font-weight:800">Acce
551581 resumeText = ( await file . text ( ) ) . substring ( 0 , 4000 ) ;
552582 }
553583 zone . classList . add ( 'loaded' ) ;
554- lbl . innerHTML = '<i class="fas fa-check-circle"></i> Resume loaded — ' + file . name ;
584+ lbl . innerHTML = '<i class="fas fa-check-circle"></i> Resume loaded � ' + file . name ;
555585 } catch ( err ) {
556- lbl . innerHTML = '?? Could not read file — enter background manually' ;
586+ lbl . innerHTML = '?? Could not read file � enter background manually' ;
557587 }
558588}
559589
@@ -790,7 +820,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
790820 return ;
791821 }
792822
793- // Browser STT — continuous, hot mic
823+ // Browser STT � continuous, hot mic
794824 const SR = window . SpeechRecognition || window . webkitSpeechRecognition ;
795825 if ( SR ) {
796826 recognition = new SR ( ) ;
@@ -818,7 +848,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
818848 const totalWords = liveTranscript . trim ( ) . split ( / \s + / ) . filter ( Boolean ) . length ;
819849 baseSilenceMS = totalWords > 20 ? 4000 : 2600 ;
820850 const disp = interim || liveTranscript . trim ( ) . split ( ' ' ) . slice ( - 5 ) . join ( ' ' ) ;
821- setStatus ( `LISTENING · "${ disp } … "` ) ;
851+ setStatus ( `LISTENING � "${ disp } � "` ) ;
822852 }
823853 } ;
824854
@@ -850,9 +880,9 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
850880 liveTranscript = '' ;
851881 audioChunks = [ ] ;
852882 setOrb ( 'interrupted' ) ;
853- setStatus ( 'INTERRUPTED · LISTENING' ) ;
883+ setStatus ( 'INTERRUPTED � LISTENING' ) ;
854884 document . getElementById ( 'subtitle' ) . classList . remove ( 'show' ) ;
855- toast ( '<i class="fas fa-microphone-lines"></i> Interruption detected — go ahead!' ) ;
885+ toast ( '<i class="fas fa-microphone-lines"></i> Interruption detected � go ahead!' ) ;
856886 setTimeout ( ( ) => { if ( ! isSpeaking ) setOrb ( 'listening' ) ; } , 800 ) ;
857887}
858888
@@ -958,11 +988,11 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
958988 ? `No background provided. Ask general ${ S . jobLevel } -level questions for "${ S . jobTitle } ".`
959989 : `
960990CANDIDATE BACKGROUND:
961- • Technologies: ${ candidateProfile . technologies . join ( ', ' ) || 'not specified' }
962- • Experience: ${ candidateProfile . yearsExperience ? candidateProfile . yearsExperience + ' years' : 'not stated' }
963- • Seniority: ${ candidateProfile . seniorityHints . join ( ', ' ) || 'not stated' }
964- • Projects built: ${ candidateProfile . projects . join ( ' | ' ) || 'none extracted' }
965- • Raw context: ${ candidateProfile . rawText . substring ( 0 , 900 ) }
991+ � Technologies: ${ candidateProfile . technologies . join ( ', ' ) || 'not specified' }
992+ � Experience: ${ candidateProfile . yearsExperience ? candidateProfile . yearsExperience + ' years' : 'not stated' }
993+ � Seniority: ${ candidateProfile . seniorityHints . join ( ', ' ) || 'not stated' }
994+ � Projects built: ${ candidateProfile . projects . join ( ' | ' ) || 'none extracted' }
995+ � Raw context: ${ candidateProfile . rawText . substring ( 0 , 900 ) }
966996` . trim ( ) ;
967997
968998 const uncovered = candidateProfile . technologies . filter ( t => ! S . skillsCovered . has ( t ) ) . slice ( 0 , 5 ) ;
@@ -971,7 +1001,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
9711001 warmup :`
9721002ASK a warm personalised opener referencing ONE specific technology or project from their background.
9731003BAD: "Tell me about yourself."
974- GOOD: "I see you've worked with ${ candidateProfile . technologies [ 0 ] || 'various technologies' } — what first drew you to that ecosystem?"
1004+ GOOD: "I see you've worked with ${ candidateProfile . technologies [ 0 ] || 'various technologies' } � what first drew you to that ecosystem?"
9751005` ,
9761006 technical :`
9771007ASK a deep technical question targeting one of: [${ uncovered . join ( ', ' ) || 'core technical fundamentals' } ].
@@ -999,10 +1029,10 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
9991029Last answer: "${ S . history . filter ( h => h . role === 'candidate' ) . slice ( - 1 ) [ 0 ] ?. content ?. substring ( 0 , 400 ) || '' } "
10001030
10011031Probe ONE of:
1002- • Quantify: "What was the measurable impact of that?"
1003- • Specifics: "Walk me through the exact implementation of [X they mentioned]."
1004- • Deeper: "How would you approach that differently today?"
1005- • Catch vague: "You mentioned [X] — what specifically did that involve?"
1032+ � Quantify: "What was the measurable impact of that?"
1033+ � Specifics: "Walk me through the exact implementation of [X they mentioned]."
1034+ � Deeper: "How would you approach that differently today?"
1035+ � Catch vague: "You mentioned [X] � what specifically did that involve?"
10061036Be conversational, not interrogative.
10071037` : '' ;
10081038
@@ -1011,8 +1041,8 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
10111041 . join ( '\n' ) ;
10121042
10131043 const sys = `You are an elite AI technical interviewer for "${ S . jobTitle || 'Professional Role' } " at ${ S . jobLevel } level.
1014- Language: ${ LANG [ selectedLang ] ?. n || 'English' } — respond ENTIRELY in this language.
1015- Phase: ${ phase . toUpperCase ( ) } — Q${ S . questionCount + 1 } of ${ S . maxQuestions }
1044+ Language: ${ LANG [ selectedLang ] ?. n || 'English' } � respond ENTIRELY in this language.
1045+ Phase: ${ phase . toUpperCase ( ) } � Q${ S . questionCount + 1 } of ${ S . maxQuestions }
10161046
10171047${ profileCtx }
10181048
@@ -1023,7 +1053,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
102310531. Output EXACTLY ONE question. Max 2 sentences.
102410542. NEVER repeat a topic already covered: [${ [ ...S . skillsCovered ] . join ( ', ' ) || 'none' } ]
102510553. NEVER offer feedback, praise, or advice ("Great answer!" is forbidden).
1026- 4. Make it personal — reference their ACTUAL background.
1056+ 4. Make it personal � reference their ACTUAL background.
102710575. Sound like a thoughtful senior engineer / hiring manager.
102810586. No labels, no prefixes, no quotes around output.
10291059
@@ -1147,7 +1177,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
11471177}
11481178
11491179// ----------------------------------------------------------------
1150- // TTS — INTERRUPTIBLE CHUNKED SPEECH
1180+ // TTS � INTERRUPTIBLE CHUNKED SPEECH
11511181// ----------------------------------------------------------------
11521182function makeUtt ( text , cb ) {
11531183 const u = new SpeechSynthesisUtterance ( text ) ;
@@ -1225,7 +1255,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
12251255 submitAnswer ( ) ;
12261256 } else if ( words >= MIN_WORDS && elapsed > 1000 ) {
12271257 const remaining = Math . max ( 0 , baseSilenceMS - elapsed ) ;
1228- setStatus ( `LISTENING · ${ words } words · submitting in ${ ( remaining / 1000 ) . toFixed ( 1 ) } s` ) ;
1258+ setStatus ( `LISTENING � ${ words } words � submitting in ${ ( remaining / 1000 ) . toFixed ( 1 ) } s` ) ;
12291259 }
12301260 }
12311261 setTimeout ( loop , 200 ) ;
@@ -1502,7 +1532,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
15021532 let summary = 'Interview completed.' , strengths = [ ] , improvements = [ ] ;
15031533
15041534 try {
1505- // AI evaluates answers — stored server-side only
1535+ // AI evaluates answers � stored server-side only
15061536 const answerTexts = S . history
15071537 . filter ( h => h . role === 'candidate' )
15081538 . map ( ( h , i ) => `A${ i + 1 } : ${ h . content . substring ( 0 , 350 ) } ` )
@@ -1532,7 +1562,7 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
15321562 summary,
15331563 strengths : JSON . stringify ( strengths ) ,
15341564 improvements : JSON . stringify ( improvements ) ,
1535- // Transcripts saved for HR eyes only — never shown in candidate UI
1565+ // Transcripts saved for HR eyes only � never shown in candidate UI
15361566 answers : JSON . stringify ( S . history . filter ( h => h . role === 'candidate' ) . map ( h => h . content ) ) ,
15371567 full_transcript : JSON . stringify ( S . history ) ,
15381568 violation_log : JSON . stringify ( violations ) ,
@@ -1564,16 +1594,16 @@ <h2 style="color:#f8fafc;font-size:22px;margin-bottom:12px;font-weight:800">${ms
15641594
15651595 <div style="font-size:52px;margin-bottom:12px">${ finalScore >= 65 ?'??' :'??' } </div>
15661596 <h2 style="font-size:26px;font-weight:900;margin-bottom:6px;letter-spacing:-0.5px">Assessment Complete</h2>
1567- <p style="color:var(--dim);font-size:13px;margin-bottom:28px;font-weight:500">AI-Powered Evaluation · SimpaticoHR Enterprise</p>
1597+ <p style="color:var(--dim);font-size:13px;margin-bottom:28px;font-weight:500">AI-Powered Evaluation � SimpaticoHR Enterprise</p>
15681598
15691599 <!-- Score -->
15701600 <div style="background:rgba(255,255,255,0.02);border-radius:16px;padding:28px;margin-bottom:20px;border:1px solid rgba(255,255,255,0.05)">
15711601 <div style="font-size:72px;font-weight:900;color:${ col } ;line-height:1;text-shadow:0 0 40px ${ col } ;">${ finalScore } <span style="font-size:32px">%</span></div>
15721602 <div style="font-size:15px;font-weight:800;color:${ col } ;margin:12px 0;letter-spacing:0.5px;text-transform:uppercase">${ rec } </div>
15731603 <div style="font-size:12px;color:var(--dim);font-weight:600">
15741604 Grade: <strong style="color:#fff">${ grade } </strong>
1575- · Trust: <strong style="color:#fff">${ trust } %</strong>
1576- · Violations: <strong style="color:${ violations . length > 0 ?'#fca5a5' :'#6ee7b7' } ">${ violations . length } </strong>
1605+ � Trust: <strong style="color:#fff">${ trust } %</strong>
1606+ � Violations: <strong style="color:${ violations . length > 0 ?'#fca5a5' :'#6ee7b7' } ">${ violations . length } </strong>
15771607 ${ vPenalty > 0 ?`<span style="color:#fca5a5;font-size:11px"> (-${ vPenalty } pts)</span>` :'' }
15781608 </div>
15791609 </div>
0 commit comments