Skip to content

Latest commit

 

History

History
330 lines (251 loc) · 10.1 KB

README.md

File metadata and controls

330 lines (251 loc) · 10.1 KB

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.