rony-chat-bot/web/README.md
Victor Hugo Vargas f33708534a feat: bootstrap rony-chat-bot Go module
Initial implementation of the bot:

- cmd/chat-bot: CLI entrypoint (serve, reindex, ask, version)
- internal/agent: LLM provider client + agent runner with RAG injection
- internal/config: YAML config loader (providers, RAG, persona, server)
- internal/i18n: response-language detection (EN/ES)
- internal/persona: persona system prompt assembly from YAML
- internal/portfolio: heading-based chunker + SQLite FTS5 indexer
- internal/server: chi router with /api/chat (SSE), /api/health, /api/info,
  /api/reindex, middleware (RequestID, Logging, CORS, RateLimit)
- internal/streaming: SSE protocol helpers (start, chunk, sources, done, error)
- web/: drop-in vanilla-JS chat widget (no build, no deps) + demo + README
- bench/: reproducible driver benchmark (modernc vs mattn SQLite)
- configs/portfolio-bot.yaml: llama.cpp default provider, SQLite RAG, canine persona
- docs/architecture.md / .es.md: aligned with SQLite FTS5 + llama.cpp decisions
- data/projects/README*.md: project data documentation
- README.md / .es.md: updated for current implementation

All tests pass (go test ./...). Bot is functional end-to-end with the
configured LLM provider.
2026-07-17 00:56:06 -07:00

5.7 KiB
Raw Blame History

rony-chat-widget

A drop-in vanilla-JS chat widget that talks to the Rony Chat Bot backend over Server-Sent Events. No build step, no runtime dependencies, no global CSS pollution.

Files

File Purpose
chat-widget.js The widget. Self-contained ~12 KB.
chat-widget.css Scoped styles, themable via CSS custom properties.
example.html Standalone demo page (use with python3 -m http.server).

Quick start (any site)

<link rel="stylesheet" href="/path/to/chat-widget.css">
<script src="/path/to/chat-widget.js"
        data-api-url="https://your-chatbot.example.com"
        data-title="Ask me anything"
        data-greeting="Hi! Ask me about the projects."
        data-position="bottom-right"
        data-theme="auto"
        defer></script>

The bubble appears bottom-right (or bottom-left), opens a 380×560 panel, and talks to data-api-url/api/chat over SSE.

Configuration (all via data-* attributes on the <script> tag)

Attribute Default Notes
data-api-url (required) Base URL of the chat-bot, e.g. https://chat.example.com. No trailing slash.
data-title "Chat" Header text.
data-greeting "" First message shown when the panel opens (no greeting if empty).
data-position "bottom-right" "bottom-right" or "bottom-left".
data-theme "auto" "auto" (follows prefers-color-scheme), "light", or "dark".

Language

The widget UI is bilingual (English / Spanish) with a toggle in the header.

  • Initial language: localStorage["rony-chat-lang"] if set, else detected from navigator.language (anything starting with es → Spanish, else English).
  • Persisted across page reloads via localStorage.
  • Conversation language is independent: the bot auto-detects the language of each user message and replies in that language. The toggle only changes the interface (placeholder, status, errors, send button).
  • No build step: strings live in a STRINGS object at the top of chat-widget.js. Add a new language by adding an entry.

To override the initial language (e.g., force English on a Spanish site):

<script>
  localStorage.setItem("rony-chat-lang", "en");
</script>
<script src="chat-widget.js" data-api-url="..." defer></script>

Theming (override without forking)

All visual tokens are CSS custom properties on the root element. Set them in your site's stylesheet:

.rony-chat-widget-root {
  --rony-accent: #ff6b35;        /* bubble + send button + links */
  --rony-radius: 4px;            /* tighter corners */
  --rony-font: "Inter", sans-serif;
}

See the full list in chat-widget.css (search for --rony-).

Astro integration

The simplest path is the drop-in. Add this to your Layout.astro (or any shared layout):

---
// src/layouts/ChatLayout.astro
import "../path/to/chat-widget.css";
const apiUrl = import.meta.env.PUBLIC_CHAT_API_URL || "http://localhost:7331";
---
<html>
  <head>
    <head><slot name="head" /></head>
  </head>
  <body>
    <slot />
    <script src="/path/to/chat-widget.js"
            data-api-url={apiUrl}
            data-title="Ask me anything"
            data-position="bottom-right"
            data-theme="auto"
            defer is:inline></script>
  </body>
</html>

Notes:

  • is:inline keeps Astro from hashing/transforming the script tag, so the data-* attributes survive.
  • PUBLIC_CHAT_API_URL is an Astro env var; set it in .env per environment.
  • The bot's cors_origins in YAML must include your Astro dev origin (http://localhost:4321).

React/Next.js

Mount the same script tag in your root layout:

// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <link rel="stylesheet" href="/chat-widget.css" />
        <Script src="/chat-widget.js"
                data-api-url={process.env.NEXT_PUBLIC_CHAT_API_URL}
                data-title="Ask me anything"
                data-position="bottom-right"
                data-theme="auto"
                strategy="afterInteractive" />
      </head>
      <body>{children}</body>
    </html>
  );
}

Backend requirements

The widget expects the bot to:

  1. Expose POST /api/chat accepting { messages, stream } (see docs/architecture.md §3.1).
  2. Stream SSE events: start, chunk, sources, done, error (see docs/architecture.md §3.2).
  3. Allow the page's origin via cors_origins in the bot's config.

Running the example locally

# 1. Start the bot
./bin/chat-bot serve

# 2. Serve the widget (in another terminal)
cd web
python3 -m http.server 8000

# 3. Open http://localhost:8000/example.html in a browser

Note: localhost:8000 must be in the bot's cors_origins for the demo to work. The default config already includes it.

Browser support

Modern browsers (Chrome/Edge 90+, Firefox 90+, Safari 15+). Uses:

  • fetch + ReadableStream (for SSE)
  • AbortController
  • CSS custom properties + prefers-color-scheme

No polyfills, no transpilation.

What's not in the widget (yet)

  • Conversation persistence — each visit is a fresh conversation. The bot is stateless; add a conversation_id cookie + server-side history if you want continuity.
  • Markdown images / tables — the renderer handles paragraphs, lists, code, links, bold/italic. Tables and images render as raw text. For richer output, swap renderMarkdown for marked or markdown-it.
  • Typing indicators beyond the streaming caret — the caret at the end of the streaming response is the only indicator. Good enough for short answers.
  • Mobile sheet drag-to-dismiss — the panel goes full-screen on phones, but can't be swiped away. Add a swipe handler if it matters.