Dual-provider architecture:
- Both Gemini and Ollama initialize at startup (if configured)
- Primary (Gemini) tried first for every request
- On any error (429, 503, timeout), automatically falls back to Ollama
- No manual switching needed — completely transparent to the user
- Log shows: "Primary failed (gemini: ...), falling back to ollama: ..."
Warmup:
- POST /api/chat/warmup called silently when chat panel opens
- Pre-loads Ollama model in background (10-15s) while user reads welcome
- By the time user types, model is ready for instant response
- Warms up fallback provider specifically (Gemini doesn't need it)
Timeout:
- Agent context increased to 60s (Ollama first response can be slow)
- Each request creates a fresh session (stateless for fallback compat)
Ollama adapter (internal/chat/ollama.go):
- Implements model.LLM interface for ADK Go
- Talks to Ollama's OpenAI-compatible API (/v1/chat/completions)
- Full tool/function calling support (tested with Mistral Small 3.2)
- Converts ADK types to OpenAI format (messages, tools, tool_calls)
- Configurable via OLLAMA_HOST and OLLAMA_MODEL env vars
Multi-provider handler:
- MODEL_PROVIDER env: "gemini" (default) or "ollama"
- Gemini: requires GOOGLE_API_KEY (pay-as-you-go recommended)
- Ollama: connects to local or Tailscale-remote instance
Rate limiter:
- 30 requests/hour per IP on /api/chat endpoint
- Uses existing middleware.NewRateLimiter pattern
Tested: Ollama + Mistral Small 3.2 on M4 Pro 64GB — correct answers
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).
- Replace flat list with <details>/<summary> accordion (5 categories)
- Questions are clickable: close modal → open chat → send question
- closeChatHelpAndAsk() helper bridges modal and chat panel
- Green accent on category icons and question hover
- Chevron arrows for expand/collapse state
- Dark theme support for all accordion elements
- Compact layout with no wasted space
- Move chat-widget outside the floating buttons section in index.html
- Remove fixed-btn class from toggle button (was inheriting left-side
positioning from the button stack's mobile flex layout)
- Chat widget now renders independently just before body-scripts
Position fix:
- Remove _chat.css @import from main.css (was overriding with old
left:2rem cached version). Chat CSS now loaded only via head-styles.
- Button confirmed at right:2rem, bottom:6rem (above back-to-top)
Help modal:
- New chat-help-modal.html using same <dialog> pattern as shortcuts
- 6 organized categories: Experience, Technologies, Projects,
Education, Skills, How it works
- Bilingual EN/ES with example questions per category
- ? button in header opens modal via commandfor/show-modal
- Removed inline help card (modal replaces it)
Intelligence:
- Comprehensive query strategy for 8 question types
- Technology queries always use cross-section search
- Company queries use experience without filter for full listing
- Agent knows CV site is built with Go/HTMX (bonus context)
- Skills report proficiency levels when technology found
CSS:
- Button moved to right: 2rem, above back-to-top (bottom: 6rem)
- Uses CV design tokens: --black-bar, --accent-blue, --paper-bg
- Fonts: Quicksand (header), Source Sans Pro (body)
- Tooltip on the left side (tooltip-left class)
- Dark theme uses CV-consistent grays
Intelligence:
- Agent instruction emphasizes exhaustive reporting of ALL matches
- Cross-section search results must not be truncated
- Mentions CV site itself is built with Go when relevant
Tests:
- Updated positioning assertions (right side, x > viewport/2)
- Added 5 intelligence tests: Go cross-section, company count,
years of experience, React cross-section, Spanish response
- Resilient to API errors (waits for any message, not just user)
- 42 total test assertions
- Mascot identity: robot-happy-outline icon, "CV Assistant" branding
- Help popup: onboarding card explaining what the mascot can do (EN/ES)
- Suggested questions: 5 clickable chips that auto-submit (bilingual)
- Typing indicator: three bouncing dots during agent response
- Icon swap: mascot icon ↔ close icon via Hyperscript class toggle
- Dark theme support for all new elements
- Modular CSS loading in development, chat CSS always loaded separately
Chat button and panel now anchor from left: 2rem to match the
zoom, shortcuts, and other fixed buttons. Panel opens rightward
so content is always visible.
- agent.go: add section="search" that queries experience, projects,
skills, and courses simultaneously — fixes missing results when
a technology spans multiple CV sections (e.g. Java at Insa)
- head-styles.html: use modular CSS in development mode and load
chat CSS separately — fixes unstyled page when bundle is stale
- doc/28-AI-CHAT-AGENT.md: comprehensive technical documentation covering
architecture, agent design, query_cv tool, HTMX integration, graceful
degradation, security, and example conversations
- README.md: add AI Chat Agent section with examples, ADK Go badge,
updated tech stack and documentation index
- doc/00-GO-DOCUMENTATION-INDEX.md: add chat agent to doc index
Visitors can ask questions about the CV via a floating chat panel.
The agent uses Gemini to answer questions about experience, projects,
skills, and education by querying the cached CV JSON data.
- internal/chat/agent.go: LLM agent with query_cv tool that searches
CV data by section (experience, projects, skills, etc.) with keyword filtering
- internal/chat/handler.go: POST /api/chat endpoint with session management,
graceful degradation when GOOGLE_API_KEY is not set
- chat-widget.html: HTMX-powered floating chat panel with Hyperscript toggle
- _chat.css: Responsive chat UI with dark theme support
- Wired into existing architecture via dependency injection (CVHandler,
routes, main.go) — zero breaking changes, all existing tests pass
Main project URLs now point to drolosoft.com product pages with
language-aware links. GitHub repos are shown via a new badge next
to the existing LIVE badge.
- Merge lang package into constants (add IsValidLang, ValidateLang, AllLangs)
- Rename internal/services to internal/email for consistency with pdf package
- Rename types to avoid redundancy: EmailService→Service, EmailConfig→Config
- Update all imports and references across codebase
- Delete internal/lang directory (functions moved to constants)
- Update all imports from 'constants' to 'c' for brevity
- Replace all 'constants.' references with 'c.'
- Fix remaining hardcoded content-type headers in httputil
- Fix remaining hardcoded User-Agent and Accept headers
- Rename CSRF receiver from 'c' to 'csrf' to avoid conflict
- Add ContentTypePlainSimple constant for Accept header matching
- Fix JSONCached to use proper integer formatting
- Create internal/constants package with all hardcoded values
(environment, cookies, themes, headers, routes, cache)
- Create internal/httputil package for HTTP helper functions
- Update all handlers and middleware to use centralized constants
- Reorganize documentation with numbered prefixes (00-26)
- Remove duplicate docs from validation folder and docs/
- Delete handlers/constants.go (moved to internal/constants)
Eliminate per-request file I/O by loading CV and UI data once at startup.
## Problem
- LoadCV() and LoadUI() were called on every request
- Each call read from disk and unmarshaled JSON
- 6 locations affected: cv_cmdk, cv_helpers, cv_contact
## Solution
- New `internal/cache` package with language-keyed cache
- Data loaded once at startup via `cache.New(["en", "es"])`
- Handlers use `h.dataCache.GetCV(lang)` / `GetUI(lang)`
- Thread-safe concurrent reads via sync.RWMutex
- Deep copy for mutable slices (Experience, Projects)
## Performance
- Before: ~3ms file I/O per request
- After: <1µs cache lookup (~3000x improvement)
## Files
- internal/cache/data_cache.go (new)
- internal/cache/data_cache_test.go (new)
- internal/cache/README.md (new)
- internal/handlers/cv.go (added dataCache field)
- internal/handlers/cv_*.go (use cache)
- main.go (initialize cache at startup)
- Add test-local target for running all tests from project root
- Add -short flag to test-unit for CI-safe execution
- Expand tests/README.md with Go backend test documentation
- Rename css-bundling test to follow numbered convention (81-)
Sprite icons (.icon-sprite.icon-section) had fixed 80px dimensions
while mobile breakpoint set container to 60px, causing overflow.
Added mobile-specific rules to scale sprites to 60px with proper
background-size and positioning calculations.
- Add media query for screens ≤400px (iPhone 13 mini = 375px)
- Reduce button size to 34px and icon size to 16px
- Recalculate 6-button positions with 6px gaps (234px total width)
- Ensures buttons stay centered within narrow viewports
Replace simple search button with search bar design in action bar:
- Semi-transparent styling integrated with dark action bar
- Keyboard shortcut indicators (⌘ K) shown as individual kbd elements
- Search icon and "Search" text for better discoverability
- Responsive: kbd keys hidden on mobile (<900px)
- Remove unused cmd-k-button.html partial template
Update test to verify new search bar structure (styling, kbd elements, icon).
Reduces HTTP requests from 44+ individual images to 3 sprite sheets
(~93% reduction). Includes Go sprite generator tool, CSS classes,
template integration, and E2E tests.
- Add cmd/sprites/main.go for sprite generation (60x60px + 120x120px @2x)
- Add _sprites.css with responsive sizing and retina support
- Update templates to use sprites with logoIndex fallback
- Add Makefile targets: sprites, sprites-clean
- Add 9-test E2E suite for sprite functionality
- Add doc/22-SPRITES.md with usage documentation
Remove 557-line server-design.md from _go-learning/architecture - content is now covered in updated architecture documentation with real implementation examples and test coverage.