{
  "name": "chatbot",
  "title": "Chatbot kit (drop-in assistant)",
  "description": "Drop-in chatbot for any app: persona, scope guardrails with refusal examples, 20k-token static knowledge, suggested-prompt chips, streaming responses. Bubble widget or headless engine, powered by Gipity's LLM - no API keys.",
  "guid": "sk_kit_chat",
  "category": "Kits",
  "requiredTools": [
    "add",
    "file_write",
    "project_deploy"
  ],
  "content": "# Chatbot kit\n\n`chatbot` is a Gipity kit that drops a configurable AI assistant into any web app: a help bubble in the corner, a support bot, or an in-game NPC dialog. You give it a **persona**, optional **scope guardrails** (what it will and won't answer), and optional **static knowledge**; it streams answers from Gipity's LLM service. No API keys, no backend to write.\n\n## Install\n\n```bash\ngipity add chatbot\n```\n\nThe installer drops the kit into `src/packages/chatbot/`, wires the import map (`import { mount } from '@gipity/chatbot'`), and declares `llm: owner_pays` in your `gipity.yaml` so logged-out visitors can use the bot. Frontend-only: no migrations, no functions. Run `gipity deploy dev` to ship.\n\n## Two ways to use it\n\n**1) Bubble widget (default)** - a `<chatbot-widget>` custom element, floating launcher in the corner. The launcher shows a 💬 emoji by default; set `ui.launcherIcon` to an image URL to replace it. Assistant replies render minimal inline markdown (**bold**, *italic*, `code`, fenced blocks) with everything else HTML-escaped, so markdown in persona/knowledge answers displays properly:\n\n```html\n<chatbot-widget id=\"bot\"></chatbot-widget>\n<script type=\"module\">\n  import { mount } from '@gipity/chatbot';\n  import config from './js/chatbot.config.js';\n  mount('#bot', config);\n</script>\n```\n\n**2) Headless engine** - bring your own UI (an in-game NPC dialog box, a custom panel):\n\n```js\nimport { createChatbot } from '@gipity/chatbot';\nimport config from './js/chatbot.config.js';\n\nconst bot = createChatbot(config);\nbot.on('delta', (text) => myUi.append(text));   // streaming chunks\nbot.on('complete', () => myUi.stopThinking());\nawait bot.send('how do I fly the ship?');\n```\n\n## Config\n\nInstall scaffolds `src/js/chatbot.config.js` with **every key present, commented, and pre-filled** - persona, scope guardrails (with a worked refusal example), a knowledge placeholder, ui, model. Edit it in place; it is the config reference, so don't go looking for the shape elsewhere. Only `persona.name` and `persona.instructions` are required.\n\nOne thing worth knowing before you edit it: `scope` is what keeps the bot on-topic (\"if someone asks something off-topic it should politely decline\"). Fill `allowed` / `refused` / `onRefusal`, and keep at least one `refusalExamples` pair - the worked example is what makes the guardrail hold *in character*. (A stricter pre-classification step is on the roadmap.)\n\n## Knowledge - 20k token budget\n\nGive the bot facts to answer from. Two source types today:\n\n- `{ type: 'text', content: '...' }` - inline text, validated at config-load.\n- `{ type: 'url', url: 'https://...' }` - fetched once when the engine starts.\n\nIf the combined sources exceed `maxTokens` (default 20,000; estimate is `chars / 4`), the kit **throws** rather than silently truncating - trim the sources or raise the budget. File-based knowledge and RAG embeddings are on the roadmap; for now inline the content as a `text` source.\n\n## Theming\n\nColors come from CSS variables. `theme: 'match-app'` (the default) reads `--primary` from the host page, so the bot matches your app automatically (the templates' `gipity-theme.css` defines `--primary`). Override per-instance with `ui.primaryColor`. The widget uses Shadow DOM, so host CSS won't leak in.\n\n## Headless engine events\n\nWhen you drive the bot yourself with `createChatbot(config)`:\n\n```js\nbot.on('start',            () => {});            // a send is starting\nbot.on('delta',            (text) => {});        // streaming text chunk\nbot.on('message',          (msg)  => {});        // a full message landed in history\nbot.on('complete',         (msg)  => {});        // assistant response finished\nbot.on('usage',            (u)    => {});        // tokens / credits, when reported\nbot.on('reset',            ()     => {});        // history cleared\nbot.on('error',            (err)  => {});        // something threw\nbot.on('knowledge_loaded', ({ tokens }) => {});  // a url source finished loading\n```\n\n## What's not in v1\n\nTool-calling (an explicit `tools` allowlist exists in the config but project-function calling lands in a later PR), voice (`app-tts` + `app-audio`), vision, image generation, and persisted per-user history (`storage`) are wired in later PRs - the config keys are present but inert today. Build against persona + scope + knowledge for now.\n\n## Verifying\n\nAfter wiring it up, `gipity deploy dev` and open the page - the launcher should appear in the corner; the greeting renders on first open and the starter chips show before the first message.\n\n## Related skills\n- `app-llm` - the LLM service the chatbot calls under the hood\n- `web-app-basics` / `web-ui-patterns` - building and styling the host page\n- `deploy` - the deploy pipeline\n"
}
