be5fdd03c4
Avatars: - Robot icon (green circle) before each agent message - Person icon (dark circle) before each user message - New .chat-bubble wrapper with flex layout for avatar + message Dark theme fixes: - Panel background: #1e1e1e (not pure black) - Agent bubbles: #2d2d2d with light text (not dark/invisible) - Input area: #2d2d2d (not black) - Header stays green (--accent-green) in both themes - Chips, suggestions consistent with panel background Text overflow: - overflow-wrap + word-break on messages - min-width: 0 prevents flex overflow - User bubble properly right-aligned with avatar
161 lines
7.2 KiB
HTML
161 lines
7.2 KiB
HTML
{{define "chat-widget"}}
|
|
{{if .ChatEnabled}}
|
|
<!-- AI Chat Widget — CV Assistant Mascot -->
|
|
<button
|
|
id="chat-toggle-btn"
|
|
class="chat-toggle-btn no-print has-tooltip tooltip-left"
|
|
aria-label="{{if eq .Lang "es"}}Asistente del CV{{else}}CV Assistant{{end}}"
|
|
data-tooltip="{{if eq .Lang "es"}}Asistente del CV{{else}}CV Assistant{{end}}"
|
|
onclick="toggleChatPanel()">
|
|
<iconify-icon icon="mdi:robot-happy-outline" class="chat-icon-open"></iconify-icon>
|
|
<iconify-icon icon="mdi:close" class="chat-icon-close"></iconify-icon>
|
|
</button>
|
|
|
|
<div id="chat-panel" class="chat-panel no-print">
|
|
<div class="chat-header">
|
|
<iconify-icon icon="mdi:robot-happy-outline"></iconify-icon>
|
|
<span>{{if eq .Lang "es"}}Asistente del CV{{else}}CV Assistant{{end}}</span>
|
|
<button class="chat-size-btn" onclick="cycleChatSize()"
|
|
aria-label="{{if eq .Lang "es"}}Cambiar tamaño{{else}}Resize{{end}}">
|
|
<iconify-icon icon="mdi:arrow-expand" id="chat-size-icon"></iconify-icon>
|
|
</button>
|
|
<button class="chat-help-btn"
|
|
aria-label="{{if eq .Lang "es"}}Ayuda{{else}}Help{{end}}"
|
|
commandfor="chat-help-modal"
|
|
command="show-modal">
|
|
<iconify-icon icon="mdi:help-circle-outline"></iconify-icon>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="chat-messages" class="chat-messages">
|
|
<div class="chat-bubble chat-bot">
|
|
<div class="chat-avatar chat-avatar-bot"><iconify-icon icon="mdi:robot-happy-outline"></iconify-icon></div>
|
|
<div class="chat-message">{{if eq .Lang "es"}}¡Hola! Pregúntame lo que quieras sobre este CV.{{else}}Hi! Ask me anything about this CV.{{end}}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Typing Indicator -->
|
|
<div id="chat-typing" class="chat-typing">
|
|
<span class="chat-typing-dot"></span>
|
|
<span class="chat-typing-dot"></span>
|
|
<span class="chat-typing-dot"></span>
|
|
</div>
|
|
|
|
<!-- Suggested Questions -->
|
|
<div class="chat-suggestions">
|
|
{{if eq .Lang "es"}}
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('¿Qué proyectos en Go ha hecho?')">¿Proyectos en Go?</button>
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('¿Cuántos años de experiencia tiene?')">¿Años de experiencia?</button>
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('¿En qué empresas ha trabajado?')">¿Empresas?</button>
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('¿Conoce React?')">¿Conoce React?</button>
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('¿Qué certificaciones tiene?')">¿Certificaciones?</button>
|
|
{{else}}
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('What Go projects has he built?')">Go projects?</button>
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('How many years of experience?')">Years of experience?</button>
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('What companies has he worked at?')">Companies?</button>
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('Does he know React?')">Knows React?</button>
|
|
<button type="button" class="chat-chip" onclick="sendChatQuestion('What certifications?')">Certifications?</button>
|
|
{{end}}
|
|
</div>
|
|
|
|
<form id="chat-form" class="chat-input-area"
|
|
hx-post="/api/chat"
|
|
hx-target="#chat-messages"
|
|
hx-swap="beforeend scroll:#chat-messages:bottom"
|
|
hx-indicator="#chat-typing">
|
|
<input type="hidden" id="chat-session-id" name="session_id" value="">
|
|
<input type="hidden" name="lang" value="{{.Lang}}">
|
|
<input
|
|
type="text"
|
|
id="chat-input"
|
|
name="message"
|
|
class="chat-input"
|
|
placeholder="{{if eq .Lang "es"}}Pregunta algo sobre el CV...{{else}}Ask something about the CV...{{end}}"
|
|
autocomplete="off">
|
|
<button type="submit" class="chat-send-btn" aria-label="Send">
|
|
<iconify-icon icon="mdi:send"></iconify-icon>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Chat JavaScript — all interactions in plain JS, no Hyperscript -->
|
|
<script>
|
|
// Toggle chat panel open/close
|
|
var chatWarmedUp = false;
|
|
function toggleChatPanel() {
|
|
var panel = document.getElementById('chat-panel');
|
|
var btn = document.getElementById('chat-toggle-btn');
|
|
panel.classList.toggle('chat-open');
|
|
btn.classList.toggle('mascot-active');
|
|
if (panel.classList.contains('chat-open')) {
|
|
document.getElementById('chat-input').focus();
|
|
// Warm up the model on first open (silent background ping)
|
|
if (!chatWarmedUp) {
|
|
chatWarmedUp = true;
|
|
fetch('/api/chat/warmup', { method: 'POST' }).catch(function() {});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send a question (from chip or help modal)
|
|
function sendChatQuestion(question) {
|
|
var input = document.getElementById('chat-input');
|
|
var form = document.getElementById('chat-form');
|
|
input.value = question;
|
|
htmx.trigger(form, 'submit');
|
|
}
|
|
|
|
// Close help modal, open chat, and send question
|
|
function closeChatHelpAndAsk(question) {
|
|
document.getElementById('chat-help-modal').close();
|
|
var panel = document.getElementById('chat-panel');
|
|
var btn = document.getElementById('chat-toggle-btn');
|
|
if (!panel.classList.contains('chat-open')) {
|
|
panel.classList.add('chat-open');
|
|
btn.classList.add('mascot-active');
|
|
}
|
|
sendChatQuestion(question);
|
|
}
|
|
|
|
// Cycle chat panel size: compact → half-right → half-left → full → compact
|
|
var chatSizes = ['', 'chat-half-right', 'chat-half-left', 'chat-full'];
|
|
var chatSizeIcons = ['mdi:arrow-expand', 'mdi:dock-right', 'mdi:dock-left', 'mdi:arrow-collapse'];
|
|
var chatSizeIndex = 0;
|
|
function cycleChatSize() {
|
|
var panel = document.getElementById('chat-panel');
|
|
var icon = document.getElementById('chat-size-icon');
|
|
// Remove current size class
|
|
chatSizes.forEach(function(cls) { if (cls) panel.classList.remove(cls); });
|
|
// Next size
|
|
chatSizeIndex = (chatSizeIndex + 1) % chatSizes.length;
|
|
if (chatSizes[chatSizeIndex]) panel.classList.add(chatSizes[chatSizeIndex]);
|
|
icon.setAttribute('icon', chatSizeIcons[chatSizeIndex]);
|
|
}
|
|
|
|
// Navigate from chat link to CV section, then highlight
|
|
function scrollToCV(link) {
|
|
var anchor = link.getAttribute('href');
|
|
var target = document.querySelector(anchor);
|
|
if (target) {
|
|
// Close chat panel
|
|
document.getElementById('chat-panel').classList.remove('chat-open');
|
|
document.getElementById('chat-toggle-btn').classList.remove('mascot-active');
|
|
// Scroll to target
|
|
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
// Highlight briefly
|
|
target.classList.add('chat-highlight');
|
|
setTimeout(function() { target.classList.remove('chat-highlight'); }, 2000);
|
|
}
|
|
return false; // prevent default anchor navigation
|
|
}
|
|
|
|
// Clear input after HTMX request completes
|
|
document.addEventListener('htmx:afterRequest', function(event) {
|
|
if (event.detail.elt && event.detail.elt.id === 'chat-form') {
|
|
document.getElementById('chat-input').value = '';
|
|
}
|
|
});
|
|
</script>
|
|
{{end}}
|
|
{{end}}
|