Skip to content

Luco1421/Chatbot

Repository files navigation

TEC Math Chatbot Widget

An embeddable web widget that answers frequently asked questions about the Examen Diagnóstico de Matemática (Math Diagnostic Exam) at Tecnológico de Costa Rica.

The project builds a native Web Component — no React, no backend, no database required. The final bundle lands in chatbot/ and can be copied into any other site.

How it answers

Each user message goes through a small pipeline before falling back to search:

  1. Rule-based intent detection — greetings, thanks, farewells, and guardrails around academic-integrity questions (e.g. "give me the exam answers") or platform-security questions are matched against fixed rules. Score-outcome questions ("I got an 85") are resolved with hardcoded thresholds (e.g. ≥80 → eligible for the placement exam, <60 → recommended for the support workshop).
  2. Date-aware answers — "how many days until X" is computed live from meta.schedule.
  3. Fuzzy search fallback — anything else is matched against the knowledge base with Fuse.js and classified as high/medium/low/ambiguous confidence. High/medium confidence shows the stored answer; ambiguous asks a clarifying question; low confidence falls back to a "contact us" message. The bot never invents an answer.

There is no generative AI/LLM involved anywhere in the widget.

What gets built

Running the build produces:

chatbot/
├── README.txt
├── tec-math-chatbot.es.js
├── tec-math-chatbot.iife.js
└── data/
    └── knowledge.json

To embed that folder in a page:

<script type="module" src="./chatbot/tec-math-chatbot.es.js"></script>

<tec-math-chatbot
  data-source="./chatbot/data/knowledge.json"
  contact-email="diagnosticomate@tec.ac.cr">
</tec-math-chatbot>

The chatbot/ folder must sit next to the HTML if using those relative paths; adjust src and data-source otherwise.

Stack

  • TypeScript, built with Vite in library mode (ES module + IIFE bundles)
  • Native Web Component with Shadow DOM, no UI framework
  • Fuse.js for fuzzy search — the only runtime dependency
  • Vitest for unit tests, colocated with the source
  • An optional, standalone Node.js http server (analytics-endpoint/) as a reference backend for anonymized usage analytics — not required for the widget to work

Commands

npm install
npm run dev
npm run build
npm run edit:knowledge
npm run test
  • npm run dev — applies schedule dates, validates the knowledge JSON, builds chatbot/, and starts Vite to try the widget locally.
  • npm run build — same validation/build, without starting Vite; produces chatbot/ for delivery to another system.
  • npm run edit:knowledge — interactive CLI to update dates or add topics.
  • npm run test — runs the test suite.

npm run validate:knowledge and npm run apply:schedule exist as standalone diagnostic commands, but dev/build already run what they need.

Workflows

Edit manually and try it

  1. Edit public/data/knowledge.json.
  2. Run npm run dev (applies meta.schedule, validates the JSON, rebuilds chatbot/, and starts Vite).
  3. Open http://localhost:5173.

Edit with the assistant and try it

npm run edit:knowledge
npm run dev

Useful for people who don't want to hand-edit JSON.

Prepare a delivery build

npm run build

(optionally preceded by npm run edit:knowledge), then copy the generated chatbot/ folder.

Knowledge base

The main editable source is public/data/knowledge.json, with two important sections:

  • meta — general config, contact email, and schedule dates.
  • items — the answers the bot can find and show.

Dates live in meta.schedule for quick editing. Date-dependent items store generic placeholder text to avoid visual contradictions; at load time, the widget's JavaScript generates the real answer text from meta.schedule.

Updating dates

The simplest way:

npm run edit:knowledge

Choose "Actualizar fechas principales." The assistant only asks for dates in YYYY-MM-DD format and generates display strings automatically (e.g. "1 al 3 de diciembre de 2026").

You can also edit meta.schedule directly at the top of public/data/knowledge.json:

"schedule": {
  "inscription": {
    "label": "1 al 3 de diciembre de 2026",
    "shortLabel": "1 al 3 dic 2026",
    "targetDate": "2026-12-01",
    "expiresAt": "2026-12-03"
  }
}

After a manual edit, just run npm run dev or npm run build. There's no need to hand-update the matricula, practica, aplicacion, resultados, or fechas-importantes answers — those stay generic in the file, and the widget generates the real text in memory at load time.

Adding new topics

Via the assistant:

npm run edit:knowledge

Choose "Agregar tema nuevo" and answer the prompts (category, title, questions, keywords, semantic tags, answer).

Or edit public/data/knowledge.json directly, appending to items:

{
  "id": "tema-nuevo",
  "category": "Categoría visible",
  "title": "Título interno del tema",
  "questions": [
    "¿Pregunta común 1?",
    "¿Pregunta común 2?"
  ],
  "keywords": ["palabra clave", "sinónimo"],
  "semanticTags": ["intención del usuario", "frase parecida"],
  "answer": "Respuesta breve y clara que mostrará el chatbot.",
  "lastUpdated": "2026-06-01"
}

Fields: id (unique, lowercase, hyphenated), category (visible group, used for stats), title (short internal name), questions (real phrasings a person would use), keywords (search terms), semanticTags (synonyms / equivalent intents), answer, lastUpdated (YYYY-MM-DD), and the optional targetDate / expiresAt for date-dependent topics.

Then run npm run dev or npm run build.

Analytics

Analytics are not collected client-side by default. If the host site wants to track usage, it must expose its own endpoint and pass it to the widget:

<tec-math-chatbot
  data-source="./chatbot/data/knowledge.json"
  contact-email="diagnosticomate@tec.ac.cr"
  analytics-endpoint="/api/tec-chatbot">
</tec-math-chatbot>

If analytics-endpoint isn't set, nothing is sent. Recommended in production: use a same-domain path (e.g. /api/tec-chatbot) rather than an external URL, to avoid CORS and reduce the odds of ad-blockers filtering the request — also avoid path names like /analytics, /events, or /tracking, which some blockers filter by pattern.

The widget POSTs minimal JSON:

{
  "type": "match",
  "ts": "2026-06-01T12:00:00.000Z",
  "topic": "Matrícula",
  "itemId": "matricula"
}

When it can't find an answer or needs to ask a clarifying question, it also sends the query text so the knowledge base can be improved:

{
  "type": "fallback",
  "ts": "2026-06-01T12:00:00.000Z",
  "query": "cuando dan beca por el examen"
}

Event types: match (confident match — topic/id only), fallback (no match — the query is sent), clarification (ambiguous — the query is sent), policy (a fixed policy/safety response — no query is sent). No PII is collected; queries are sanitized before sending.

The server can store each event as one line in an .ndjson file, or process it however the host site's real backend does — that part lives outside the widget on purpose.

Local demo with analytics

npm run dev                        # widget demo at http://localhost:5173
node analytics-endpoint/server.mjs # reference analytics server, in another terminal

index.html already points at /api/tec-chatbot; in dev, Vite proxies that path to http://127.0.0.1:8080 (see vite.config.ts), so no IP needs to be hardcoded in the HTML.

Events land in analytics/chatbot-events.ndjson. A live summary is served at http://127.0.0.1:8080/api/tec-chatbot/summary; to write it to a file:

curl -X POST http://127.0.0.1:8080/api/tec-chatbot/report

which writes analytics/chatbot-summary.txt. To reset stats:

curl -X POST http://127.0.0.1:8080/api/tec-chatbot/clear

In production the endpoint must live on the real host site's server — the widget only sends events; it can't write files on its own once deployed as static HTML/JS.

Reference endpoint

analytics-endpoint/server.mjs is not required by the widget — it's a reference implementation for whoever integrates the chatbot into a real server. If that server can run Node.js, it can be run as-is:

node analytics-endpoint/server.mjs

It exposes:

POST /api/tec-chatbot
GET  /api/tec-chatbot/summary
POST /api/tec-chatbot/report
POST /api/tec-chatbot/clear

storing events in analytics/chatbot-events.ndjson and reports in analytics/chatbot-summary.txt. Same-domain paths (analytics-endpoint="/api/tec-chatbot") are preferred in production over cross-origin URLs.

CORS

If the endpoint is same-domain, there's no CORS issue. If it's on another domain/port, the endpoint server must explicitly allow the host page's origin, e.g.:

Access-Control-Allow-Origin: https://www.tec.ac.cr
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

The widget sends analytics with credentials: 'omit' (no cookies/session), so Access-Control-Allow-Credentials isn't needed. Don't combine Access-Control-Allow-Origin: * with credentials — browsers block it.

In this project, the allowed-origins list is hardcoded in analytics-endpoint/server.mjs: currently http://localhost:5173 and https://www.tec.ac.cr. Requests from any other origin get a 403.

Troubleshooting: blocked by CORS policy means the endpoint responded but CORS isn't configured; ERR_CONNECTION_REFUSED means the endpoint isn't running or the port is wrong; ERR_ADDRESS_UNREACHABLE/timeout points to network/firewall issues; ERR_BLOCKED_BY_CLIENT means a browser extension blocked it (use a neutral path like /api/tec-chatbot, avoid /analytics//events//tracking); if curl works but the browser doesn't, it's CORS or an extension — curl doesn't enforce CORS.

Key principle

The chatbot must never invent answers. If it can't find a sufficiently confident match in the knowledge base, it defers to the contact email.

About

Rule-based FAQ chatbot widget for TEC's Math Diagnostic Exam, built as an embeddable Web Component.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages