feat: chat module portability guide + fix mobile wave position
- doc/30-CHAT-MODULE-PORTABILITY.md: step-by-step guide to port the chat agent to other Go apps (files, dependencies, customization) - Fix wave emoji position on mobile (follows button to bottom:5rem right:1rem)
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
# Chat Module Portability Guide
|
||||
|
||||
**Project:** CV Interactive Website
|
||||
**Last Updated:** 2026-04-09
|
||||
**Tag Reference:** `v1.2.0`
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The AI chat agent is a self-contained module that can be ported to other Go web applications. It provides an embeddable chat widget powered by Google Gemini (production) with Ollama fallback (local development).
|
||||
|
||||
---
|
||||
|
||||
## Module Files
|
||||
|
||||
### Backend (Go)
|
||||
|
||||
| File | Purpose | Dependencies |
|
||||
|------|---------|-------------|
|
||||
| `internal/chat/agent.go` | ADK agent definition, tools, prompt | `google.golang.org/adk`, your data model |
|
||||
| `internal/chat/handler.go` | HTTP handlers, provider init, warmup, icon injection | `internal/chat/agent.go`, `internal/cache` |
|
||||
| `internal/chat/ollama.go` | Ollama/OpenAI-compatible LLM adapter | None (standalone) |
|
||||
|
||||
### Frontend (HTML + CSS + JS)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `templates/partials/widgets/chat-widget.html` | Complete widget: panel, header, messages, input, JS |
|
||||
| `templates/partials/modals/chat-help-modal.html` | Help accordion with suggested questions |
|
||||
| `static/css/04-interactive/_chat.css` | All styling: layout modes, responsive, dark mode, animations |
|
||||
|
||||
### Configuration
|
||||
|
||||
| File | Keys |
|
||||
|------|------|
|
||||
| `.env` | `GOOGLE_API_KEY`, `OLLAMA_MODEL`, `OLLAMA_HOST` |
|
||||
| `config/systemd/cv.service` | `EnvironmentFile` for production secrets |
|
||||
|
||||
---
|
||||
|
||||
## Go Dependencies
|
||||
|
||||
Add to your `go.mod`:
|
||||
|
||||
```
|
||||
google.golang.org/adk v1.0.0
|
||||
google.golang.org/genai v1.x.x
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Steps
|
||||
|
||||
### 1. Copy the backend files
|
||||
|
||||
```bash
|
||||
mkdir -p internal/chat
|
||||
cp internal/chat/agent.go internal/chat/handler.go internal/chat/ollama.go YOUR_PROJECT/internal/chat/
|
||||
```
|
||||
|
||||
### 2. Adapt `agent.go`
|
||||
|
||||
This is the only file that needs significant changes per project:
|
||||
|
||||
- **`NewAgent()`**: Change the `Instruction` prompt to describe YOUR data, not a CV
|
||||
- **`QueryCVArgs` / `QueryCVResult`**: Rename and adapt the tool to query YOUR data source
|
||||
- **`newQueryCVTool()`**: Replace with a tool that queries your application's data
|
||||
- **Filter functions**: Adapt `filterExperience()`, `filterProjects()`, etc. to your data model
|
||||
|
||||
### 3. Adapt `handler.go`
|
||||
|
||||
Minimal changes needed:
|
||||
|
||||
- **`buildIconMap()`**: Remove or adapt for your icon system (sprite sheets, image files)
|
||||
- **`formatResponse()`**: The markdown→HTML converter works generically. Icon injection is optional.
|
||||
- **`NewHandler(dataCache)`**: Change the parameter type to your data source
|
||||
|
||||
### 4. Keep `ollama.go` as-is
|
||||
|
||||
This is a generic Ollama/OpenAI-compatible adapter. No changes needed — it implements `model.LLM` interface for any ADK agent.
|
||||
|
||||
### 5. Copy frontend files
|
||||
|
||||
```bash
|
||||
cp templates/partials/widgets/chat-widget.html YOUR_PROJECT/templates/partials/widgets/
|
||||
cp templates/partials/modals/chat-help-modal.html YOUR_PROJECT/templates/partials/modals/
|
||||
cp static/css/04-interactive/_chat.css YOUR_PROJECT/static/css/
|
||||
```
|
||||
|
||||
### 6. Adapt the widget
|
||||
|
||||
- Change suggested questions (chips) to match your domain
|
||||
- Change help modal questions
|
||||
- Update welcome message
|
||||
- Adjust CSS variables to match your theme
|
||||
|
||||
### 7. Register routes
|
||||
|
||||
```go
|
||||
// In your routes setup:
|
||||
chatHandler := chat.NewHandler(yourDataSource)
|
||||
mux.Handle("/api/chat", rateLimiter.Middleware(http.HandlerFunc(chatHandler.HandleChat)))
|
||||
mux.HandleFunc("/api/chat/warmup", chatHandler.HandleWarmup)
|
||||
mux.HandleFunc("/api/chat/status", chatHandler.HandleStatus)
|
||||
```
|
||||
|
||||
### 8. Include in templates
|
||||
|
||||
```html
|
||||
{{template "chat-widget" .}}
|
||||
{{template "chat-help-modal" .}}
|
||||
```
|
||||
|
||||
Conditionally load CSS:
|
||||
```html
|
||||
{{if .ChatEnabled}}
|
||||
<link rel="stylesheet" href="/static/css/_chat.css">
|
||||
{{end}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
User clicks chat → HTMX POST /api/chat
|
||||
↓
|
||||
handler.HandleChat()
|
||||
↓
|
||||
runAgent(primary) ← Gemini or Ollama
|
||||
↓
|
||||
ADK Runner loop:
|
||||
1. LLM sees prompt + user message
|
||||
2. LLM calls query tool (function calling)
|
||||
3. Tool queries your data source
|
||||
4. LLM generates response from tool results
|
||||
↓
|
||||
formatResponse() → HTML with icons + links
|
||||
↓
|
||||
HTMX appends to #chat-messages
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features Included
|
||||
|
||||
| Feature | What it does |
|
||||
|---------|-------------|
|
||||
| **Dual provider** | Gemini (prod) + Ollama (dev) with auto-fallback |
|
||||
| **Auto-warmup** | Local model pre-loaded on startup in dev mode |
|
||||
| **Status polling** | `/api/chat/status` → "Initializing AI model..." indicator |
|
||||
| **4 layout modes** | Compact, Side Panel, Floating (draggable), Full Screen |
|
||||
| **Mobile responsive** | Split mode on phones, desktop modes hidden |
|
||||
| **User + bot avatars** | Teams-style bubble layout |
|
||||
| **Inline icons** | Sprite + image fallback next to navigation links |
|
||||
| **External links** | `[text](https://...)` rendered as clickable links |
|
||||
| **Wave greeting** | 👋 animation to attract visitors |
|
||||
| **Help modal** | Accordion with suggested questions |
|
||||
| **Chip questions** | One-click with instant bubble rendering |
|
||||
| **Rate limiting** | 30 req/hour per IP (configurable) |
|
||||
| **Dark mode** | Lighter panel to contrast with dark backgrounds |
|
||||
| **HTMX timeout** | 120s for slow local models |
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
| Test file | Assertions | Covers |
|
||||
|-----------|-----------|--------|
|
||||
| `tests/mjs/84-chat-layout-modes.test.mjs` | 38 | Desktop layout modes, drag, switching, avatars |
|
||||
| `tests/mjs/85-chat-mobile.test.mjs` | 79 | Mobile on 3 iPhone viewports + desktop sanity |
|
||||
| `tests/mjs/83-chat-mascot.test.mjs` | 39 | Chat UX, chips, responses, navigation |
|
||||
|
||||
---
|
||||
|
||||
## What to Customize Per Project
|
||||
|
||||
| Component | What to change |
|
||||
|-----------|---------------|
|
||||
| Agent prompt | `agent.go` — describe YOUR domain, not a CV |
|
||||
| Query tool | `agent.go` — query YOUR data source |
|
||||
| Suggested questions | `chat-widget.html` — chips and help modal |
|
||||
| Welcome message | `chat-widget.html` — greeting text |
|
||||
| Icons/sprites | `handler.go` — `buildIconMap()` and CSS |
|
||||
| CSS theme | `_chat.css` — colors, `--accent-green`, fonts |
|
||||
| Rate limits | `routes.go` — requests per hour |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Google ADK Go Documentation](https://google.github.io/adk-docs/)
|
||||
- [Ollama API](https://github.com/ollama/ollama/blob/main/docs/api.md)
|
||||
- [HTMX Documentation](https://htmx.org/docs/)
|
||||
- [doc/28-AI-CHAT-AGENT.md](./28-AI-CHAT-AGENT.md) — Full technical documentation
|
||||
- [doc/29-AI-CHAT-SHOWCASE.md](./29-AI-CHAT-SHOWCASE.md) — Public showcase writeup
|
||||
@@ -848,6 +848,12 @@
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/* Wave position follows mobile button */
|
||||
.chat-wave {
|
||||
bottom: calc(5rem + 38px);
|
||||
right: calc(1rem + 38px);
|
||||
}
|
||||
|
||||
/* Tooltips: prevent overflow on mobile */
|
||||
.chat-mode-btn[title]:hover::after {
|
||||
display: none;
|
||||
|
||||
Reference in New Issue
Block a user