fix: replace Hyperscript with plain JS for chat interactions
The Hyperscript trigger/call commands couldn't reliably trigger HTMX form submissions or call global JS functions. Moved all chat interactions to plain JavaScript: - toggleChatPanel(): open/close panel + icon swap - sendChatQuestion(q): set input + htmx.trigger(form, 'submit') - closeChatHelpAndAsk(q): close modal + open chat + send question - htmx:afterRequest listener clears input after submit Hyperscript kept only for site-wide patterns (closeOnBackdrop) that work reliably. Also: better error message for rate-limited API responses (429).
This commit is contained in:
@@ -144,8 +144,11 @@ func (h *Handler) HandleChat(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
log.Printf("Chat agent error: %v", err)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = fmt.Fprint(w, `<div class="chat-message chat-error">Something went wrong. Please try again.</div>`)
|
||||
errMsg := "Something went wrong. Please try again in a moment."
|
||||
if strings.Contains(err.Error(), "429") || strings.Contains(err.Error(), "RESOURCE_EXHAUSTED") {
|
||||
errMsg = "The AI service is temporarily busy. Please try again in a few seconds."
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, `<div class="chat-message chat-error">%s</div>`, errMsg)
|
||||
return
|
||||
}
|
||||
if event.IsFinalResponse() {
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
{{if eq .Lang "es"}}Experiencia{{else}}Experience{{end}}
|
||||
</summary>
|
||||
<div class="chat-help-questions">
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Cuántos años de experiencia tiene Juan?{{else}}How many years of experience does Juan have?{{end}}')">{{if eq .Lang "es"}}¿Cuántos años de experiencia tiene?{{else}}How many years of experience?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿En qué empresas ha trabajado?{{else}}What companies has he worked at?{{end}}')">{{if eq .Lang "es"}}¿En qué empresas ha trabajado?{{else}}What companies has he worked at?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}Cuéntame sobre Olympic Broadcasting{{else}}Tell me about Olympic Broadcasting{{end}}')">{{if eq .Lang "es"}}Cuéntame sobre Olympic Broadcasting{{else}}Tell me about Olympic Broadcasting{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué hacía en SAP?{{else}}What did he do at SAP?{{end}}')">{{if eq .Lang "es"}}¿Qué hacía en SAP?{{else}}What did he do at SAP?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Cuántos años de experiencia tiene Juan?{{else}}How many years of experience does Juan have?{{end}}')">{{if eq .Lang "es"}}¿Cuántos años de experiencia tiene?{{else}}How many years of experience?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿En qué empresas ha trabajado?{{else}}What companies has he worked at?{{end}}')">{{if eq .Lang "es"}}¿En qué empresas ha trabajado?{{else}}What companies has he worked at?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}Cuéntame sobre Olympic Broadcasting{{else}}Tell me about Olympic Broadcasting{{end}}')">{{if eq .Lang "es"}}Cuéntame sobre Olympic Broadcasting{{else}}Tell me about Olympic Broadcasting{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué hacía en SAP?{{else}}What did he do at SAP?{{end}}')">{{if eq .Lang "es"}}¿Qué hacía en SAP?{{else}}What did he do at SAP?{{end}}</button>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -45,11 +45,11 @@
|
||||
{{if eq .Lang "es"}}Tecnologías{{else}}Technologies{{end}}
|
||||
</summary>
|
||||
<div class="chat-help-questions">
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué lenguajes de programación conoce?{{else}}What programming languages does he know?{{end}}')">{{if eq .Lang "es"}}¿Qué lenguajes conoce?{{else}}What languages does he know?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Ha trabajado con React? ¿Dónde?{{else}}Has he worked with React? Where?{{end}}')">{{if eq .Lang "es"}}¿Ha trabajado con React?{{else}}Has he worked with React?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Cuál es su experiencia con Go?{{else}}What is his Go experience?{{end}}')">{{if eq .Lang "es"}}¿Experiencia con Go?{{else}}Go experience?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Conoce Node.js?{{else}}Does he know Node.js?{{end}}')">{{if eq .Lang "es"}}¿Conoce Node.js?{{else}}Does he know Node.js?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Tiene experiencia con Docker?{{else}}Does he have Docker experience?{{end}}')">{{if eq .Lang "es"}}¿Experiencia con Docker?{{else}}Docker experience?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué lenguajes de programación conoce?{{else}}What programming languages does he know?{{end}}')">{{if eq .Lang "es"}}¿Qué lenguajes conoce?{{else}}What languages does he know?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Ha trabajado con React? ¿Dónde?{{else}}Has he worked with React? Where?{{end}}')">{{if eq .Lang "es"}}¿Ha trabajado con React?{{else}}Has he worked with React?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Cuál es su experiencia con Go?{{else}}What is his Go experience?{{end}}')">{{if eq .Lang "es"}}¿Experiencia con Go?{{else}}Go experience?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Conoce Node.js?{{else}}Does he know Node.js?{{end}}')">{{if eq .Lang "es"}}¿Conoce Node.js?{{else}}Does he know Node.js?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Tiene experiencia con Docker?{{else}}Does he have Docker experience?{{end}}')">{{if eq .Lang "es"}}¿Experiencia con Docker?{{else}}Docker experience?{{end}}</button>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
{{if eq .Lang "es"}}Proyectos{{else}}Projects{{end}}
|
||||
</summary>
|
||||
<div class="chat-help-questions">
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué proyectos personales ha creado?{{else}}What personal projects has he built?{{end}}')">{{if eq .Lang "es"}}¿Qué proyectos ha creado?{{else}}What projects has he built?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}Cuéntame sobre Immich Photo Manager{{else}}Tell me about Immich Photo Manager{{end}}')">{{if eq .Lang "es"}}Sobre Immich Photo Manager{{else}}About Immich Photo Manager{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué proyectos open-source mantiene?{{else}}What open-source projects does he maintain?{{end}}')">{{if eq .Lang "es"}}¿Proyectos open-source?{{else}}Open-source projects?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué proyectos personales ha creado?{{else}}What personal projects has he built?{{end}}')">{{if eq .Lang "es"}}¿Qué proyectos ha creado?{{else}}What projects has he built?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}Cuéntame sobre Immich Photo Manager{{else}}Tell me about Immich Photo Manager{{end}}')">{{if eq .Lang "es"}}Sobre Immich Photo Manager{{else}}About Immich Photo Manager{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué proyectos open-source mantiene?{{else}}What open-source projects does he maintain?{{end}}')">{{if eq .Lang "es"}}¿Proyectos open-source?{{else}}Open-source projects?{{end}}</button>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
{{if eq .Lang "es"}}Formación{{else}}Education{{end}}
|
||||
</summary>
|
||||
<div class="chat-help-questions">
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué certificaciones tiene?{{else}}What certifications does he have?{{end}}')">{{if eq .Lang "es"}}¿Certificaciones?{{else}}Certifications?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Dónde estudió?{{else}}Where did he study?{{end}}')">{{if eq .Lang "es"}}¿Dónde estudió?{{else}}Where did he study?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué cursos ha completado?{{else}}What courses has he completed?{{end}}')">{{if eq .Lang "es"}}¿Cursos completados?{{else}}Courses completed?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué certificaciones tiene?{{else}}What certifications does he have?{{end}}')">{{if eq .Lang "es"}}¿Certificaciones?{{else}}Certifications?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Dónde estudió?{{else}}Where did he study?{{end}}')">{{if eq .Lang "es"}}¿Dónde estudió?{{else}}Where did he study?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué cursos ha completado?{{else}}What courses has he completed?{{end}}')">{{if eq .Lang "es"}}¿Cursos completados?{{else}}Courses completed?{{end}}</button>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -86,9 +86,9 @@
|
||||
{{if eq .Lang "es"}}Habilidades{{else}}Skills{{end}}
|
||||
</summary>
|
||||
<div class="chat-help-questions">
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Cuáles son sus principales habilidades técnicas?{{else}}What are his main technical skills?{{end}}')">{{if eq .Lang "es"}}¿Habilidades técnicas principales?{{else}}Main technical skills?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué hay de CI/CD?{{else}}What about CI/CD?{{end}}')">{{if eq .Lang "es"}}¿Experiencia con CI/CD?{{else}}CI/CD experience?{{end}}</button>
|
||||
<button class="chat-help-q" _="on click call closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué idiomas habla?{{else}}What languages does he speak?{{end}}')">{{if eq .Lang "es"}}¿Qué idiomas habla?{{else}}Languages spoken?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Cuáles son sus principales habilidades técnicas?{{else}}What are his main technical skills?{{end}}')">{{if eq .Lang "es"}}¿Habilidades técnicas principales?{{else}}Main technical skills?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué hay de CI/CD?{{else}}What about CI/CD?{{end}}')">{{if eq .Lang "es"}}¿Experiencia con CI/CD?{{else}}CI/CD experience?{{end}}</button>
|
||||
<button class="chat-help-q" onclick="closeChatHelpAndAsk('{{if eq .Lang "es"}}¿Qué idiomas habla?{{else}}What languages does he speak?{{end}}')">{{if eq .Lang "es"}}¿Qué idiomas habla?{{else}}Languages spoken?{{end}}</button>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
@@ -6,14 +6,7 @@
|
||||
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}}"
|
||||
_="on click toggle .chat-open on #chat-panel
|
||||
then if #chat-panel matches .chat-open
|
||||
then add .mascot-active to me
|
||||
then set #chat-input.focus to true
|
||||
then call #chat-input.focus()
|
||||
else
|
||||
remove .mascot-active from me
|
||||
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>
|
||||
@@ -46,27 +39,17 @@
|
||||
<!-- Suggested Questions -->
|
||||
<div class="chat-suggestions">
|
||||
{{if eq .Lang "es"}}
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to '¿Qué proyectos en Go ha hecho?' then trigger submit on #chat-form">¿Proyectos en Go?</button>
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to '¿Cuántos años de experiencia tiene?' then trigger submit on #chat-form">¿Años de experiencia?</button>
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to '¿En qué empresas ha trabajado?' then trigger submit on #chat-form">¿Empresas?</button>
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to '¿Conoce React?' then trigger submit on #chat-form">¿Conoce React?</button>
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to '¿Qué certificaciones tiene?' then trigger submit on #chat-form">¿Certificaciones?</button>
|
||||
<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"
|
||||
_="on click set #chat-input.value to 'What Go projects has he built?' then trigger submit on #chat-form">Go projects?</button>
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to 'How many years of experience?' then trigger submit on #chat-form">Years of experience?</button>
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to 'What companies has he worked at?' then trigger submit on #chat-form">Companies?</button>
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to 'Does he know React?' then trigger submit on #chat-form">Knows React?</button>
|
||||
<button type="button" class="chat-chip"
|
||||
_="on click set #chat-input.value to 'What certifications?' then trigger submit on #chat-form">Certifications?</button>
|
||||
<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>
|
||||
|
||||
@@ -74,8 +57,7 @@
|
||||
hx-post="/api/chat"
|
||||
hx-target="#chat-messages"
|
||||
hx-swap="beforeend scroll:#chat-messages:bottom"
|
||||
hx-indicator="#chat-typing"
|
||||
_="on htmx:afterRequest set #chat-input.value to ''">
|
||||
hx-indicator="#chat-typing">
|
||||
<input type="hidden" id="chat-session-id" name="session_id" value="">
|
||||
<input type="hidden" name="lang" value="{{.Lang}}">
|
||||
<input
|
||||
@@ -90,5 +72,46 @@
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Chat JavaScript — all interactions in plain JS, no Hyperscript -->
|
||||
<script>
|
||||
// Toggle chat panel open/close
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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}}
|
||||
|
||||
Reference in New Issue
Block a user