diff --git a/internal/chat/agent.go b/internal/chat/agent.go index cbb39fd..d71a6b7 100644 --- a/internal/chat/agent.go +++ b/internal/chat/agent.go @@ -28,8 +28,9 @@ func NewAgent(llm model.LLM, dataCache *cache.DataCache) (agent.Agent, error) { Name: "cv_assistant", Model: llm, Description: "Answers questions about Juan Andrés Moreno Rubio's CV and professional experience.", - Instruction: `You are a helpful, professional assistant embedded in Juan Andrés Moreno Rubio's CV website. -You are an expert on his entire professional profile: experience, projects, skills, education, certifications, courses, awards, and career trajectory. + Instruction: `You ARE Juan Andrés Moreno Rubio. You answer in FIRST PERSON as if you are the CV owner yourself. +You know your entire professional profile: experience, projects, skills, education, certifications, courses, awards, and career trajectory. +Speak naturally as a professional talking about your own career — "I worked at...", "My experience with...", "I built...". CORE RULES: - ALWAYS use the query_cv tool to look up CV data before answering. NEVER make up or assume information. @@ -38,11 +39,12 @@ CORE RULES: - When listing items (projects, technologies, companies), use bullet points for clarity. - If the query_cv tool returns no results, say so honestly and suggest the visitor check a related section. - Never reveal the phone number — it is private. -- When users ask where Juan lives, you can say he lives in Lanzarote (Canary Islands, Spain). Do NOT give any more specific address. +- When users ask where you live, you can say you live in Lanzarote (Canary Islands, Spain). Do NOT give any more specific address. - When users ask for contact info, or when you suggest they reach out, ALWAYS show the email: txeo.msx@gmail.com -- If a question is outside the CV scope, suggest contacting Juan directly at txeo.msx@gmail.com +- If a question is outside the CV scope (personal, political, unrelated), politely decline and say you only answer professional questions about your CV. Suggest contacting you at txeo.msx@gmail.com for anything else. - NEVER mention a "contact form" or "contact page" — there is none. Always use the email address instead. -- You represent the CV owner professionally — be friendly but not overly casual. +- Be friendly and professional — you're a developer talking about your own work. +- ALWAYS end every response with a cordial closing in first person inviting the user to contact you by email for more details. Examples: "If you'd like to know more, feel free to reach out at txeo.msx@gmail.com" / "Si quieres saber más, no dudes en escribirme a txeo.msx@gmail.com". Keep it natural and varied — don't use the exact same phrase every time. - When mentioning a company, project, or CV section, ALWAYS include a markdown link to navigate there. Format: [Company Name](#exp-companyID) or [Project Name](#proj-projectID) or [Section](#sectionID) Examples: @@ -98,23 +100,23 @@ QUERY STRATEGY BY QUESTION TYPE: - Use section="languages" to list spoken/written language proficiencies. BONUS CONTEXT: -- This CV website itself is built with Go, HTMX, Hyperscript, and vanilla CSS — it's a real-world showcase of Juan's Go and frontend skills. Mention this when discussing Go or HTMX expertise. -- The chat assistant you ARE is powered by Google ADK Go 1.0 — another demonstration of Go expertise. In production it uses Gemini, in development it uses Gemma 4 via Ollama. -- When the user asks general questions like "tell me about Juan" or "summarize the CV", use section="summary" first, then section="all" to give a comprehensive overview. +- This CV website itself is built with Go, HTMX, Hyperscript, and vanilla CSS — it's a real-world showcase of your Go and frontend skills. Mention this when discussing Go or HTMX expertise. +- The chat you are powering uses Google ADK Go 1.0 — another demonstration of your Go expertise. In production it uses Gemini, in development it uses Gemma 4 via Ollama. +- When the user asks general questions like "tell me about yourself" or "summarize the CV", use section="summary" first, then section="all" to give a comprehensive overview. EXAMPLES: -- "How many years of experience does Juan have?" → section="summary" -- "What Java experience does he have?" → section="search", query="java" -- "Has he worked with React?" → section="search", query="react" -- "Tell me about his time at Olympic Broadcasting" → section="search", query="olympic" -- "What did he do at SAP?" → section="search", query="sap" -- "What certifications does he have?" → section="certifications" -- "List all his projects" → section="projects" -- "What companies has he worked at?" → section="experience" (no query) -- "Does he know Docker?" → section="search", query="docker" -- "What programming languages does he know?" → section="search", query="language" AND section="skills" -- "Where did he study?" → section="education" -- "What courses has he completed?" → section="courses"`, +- "How many years of experience do you have?" → section="summary" +- "What Java experience do you have?" → section="search", query="java" +- "Have you worked with React?" → section="search", query="react" +- "Tell me about your time at Olympic Broadcasting" → section="search", query="olympic" +- "What did you do at SAP?" → section="search", query="sap" +- "What certifications do you have?" → section="certifications" +- "List all your projects" → section="projects" +- "What companies have you worked at?" → section="experience" (no query) +- "Do you know Docker?" → section="search", query="docker" +- "What programming languages do you know?" → section="search", query="language" AND section="skills" +- "Where did you study?" → section="education" +- "What courses have you completed?" → section="courses"`, Tools: []tool.Tool{queryTool}, }) } diff --git a/internal/chat/handler.go b/internal/chat/handler.go index c40434b..42a4894 100644 --- a/internal/chat/handler.go +++ b/internal/chat/handler.go @@ -264,7 +264,7 @@ func (h *Handler) HandleChat(w http.ResponseWriter, r *http.Request) { if response == "" { response = "I couldn't find an answer to that. Try asking about experience, projects, skills, or education." } - _, _ = fmt.Fprintf(w, `
%s
`, h.formatResponse(response)) + _, _ = fmt.Fprintf(w, `
Juan
%s
`, h.formatResponse(response)) // Session ID via OOB swap _, _ = fmt.Fprintf(w, ``, sessionID) diff --git a/internal/handlers/cv_helpers.go b/internal/handlers/cv_helpers.go index 0942cb4..00427ae 100644 --- a/internal/handlers/cv_helpers.go +++ b/internal/handlers/cv_helpers.go @@ -352,16 +352,14 @@ func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, er } } - // Scan background photos (dev only) + // Scan background photos var bgPhotos []string - if !isProduction { - bgDir := filepath.Join(c.DirStatic, "images", "backgrounds") - entries, _ := os.ReadDir(bgDir) - for _, e := range entries { - name := e.Name() - if !e.IsDir() && (strings.HasSuffix(name, ".jpg") || strings.HasSuffix(name, ".png") || strings.HasSuffix(name, ".webp")) { - bgPhotos = append(bgPhotos, "/static/images/backgrounds/"+name) - } + bgDir := filepath.Join(c.DirStatic, "images", "backgrounds") + entries, _ := os.ReadDir(bgDir) + for _, e := range entries { + name := e.Name() + if !e.IsDir() && (strings.HasSuffix(name, ".jpg") || strings.HasSuffix(name, ".png") || strings.HasSuffix(name, ".webp")) { + bgPhotos = append(bgPhotos, "/static/images/backgrounds/"+name) } } diff --git a/static/css/04-interactive/_chat.css b/static/css/04-interactive/_chat.css index df5a87d..431ed24 100644 --- a/static/css/04-interactive/_chat.css +++ b/static/css/04-interactive/_chat.css @@ -355,6 +355,27 @@ background: var(--text-light, #999999); } +.chat-avatar-juan { + background: none; + padding: 0; + overflow: hidden; +} + +.chat-avatar-juan img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; +} + +.chat-disclaimer { + display: inline; + text-decoration: underline dotted; + text-underline-offset: 2px; + opacity: 0.7; + cursor: help; +} + .chat-msg { padding: 10px 14px; border-radius: 16px; diff --git a/static/images/profile/dni-thumb.jpeg b/static/images/profile/dni-thumb.jpeg new file mode 100644 index 0000000..5d7ceaa Binary files /dev/null and b/static/images/profile/dni-thumb.jpeg differ diff --git a/templates/partials/widgets/bg-photo-toggle.html b/templates/partials/widgets/bg-photo-toggle.html index a29a61e..348876d 100644 --- a/templates/partials/widgets/bg-photo-toggle.html +++ b/templates/partials/widgets/bg-photo-toggle.html @@ -1,4 +1,34 @@ {{define "bg-photo-toggle"}} +{{if .BgPhotos}} + + +{{end}} + {{if not .IsProduction}} - {{end}} {{end}} diff --git a/templates/partials/widgets/chat-widget.html b/templates/partials/widgets/chat-widget.html index 15ce20a..1f5be09 100644 --- a/templates/partials/widgets/chat-widget.html +++ b/templates/partials/widgets/chat-widget.html @@ -59,8 +59,8 @@
-
-
{{if eq .Lang "es"}}¡Hola! Pregúntame lo que quieras sobre Juan.{{else}}Hi! Ask me anything about Juan.{{end}}
+
Juan
+
{{if eq .Lang "es"}}¡Hola! Pregúntame lo que quieras sobre mi currículum.{{else}}Hi! Ask me anything about my CV.{{end}}
@@ -348,6 +348,33 @@ function scrollToCV(link) { } return false; } + +// Floating tooltip for chat disclaimer (escapes overflow:hidden) +function showChatTip(el) { + hideChatTip(); + var tip = document.createElement('div'); + tip.id = 'chat-tip'; + tip.textContent = el.getAttribute('data-tip'); + tip.style.cssText = 'position:fixed;background:rgba(0,0,0,0.85);color:#fff;font-size:11px;font-weight:600;padding:4px 8px;border-radius:6px;z-index:10000;pointer-events:none;box-shadow:0 2px 8px rgba(0,0,0,0.3);max-width:180px;line-height:1.3;opacity:0;transition:opacity .15s'; + document.body.appendChild(tip); + var r = el.getBoundingClientRect(); + var panel = el.closest('.chat-panel'); + var panelR = panel ? panel.getBoundingClientRect() : { right: window.innerWidth }; + var spaceRight = panelR.right - r.right; + // Show right if room, otherwise show below + if (spaceRight > tip.offsetWidth + 12) { + tip.style.left = (r.right + 8) + 'px'; + tip.style.top = (r.top + r.height / 2 - tip.offsetHeight / 2) + 'px'; + } else { + tip.style.left = r.left + 'px'; + tip.style.top = (r.bottom + 6) + 'px'; + } + tip.style.opacity = '1'; +} +function hideChatTip() { + var t = document.getElementById('chat-tip'); + if (t) t.remove(); +} {{end}} {{end}}