Skip to content

argon-analytik/chat-widget

Repository files navigation

ChatWidget — Self‑Hosted OpenAI Chat Widget

Docker Compose OpenAI Responses API Agentic Builder License: MIT

Self‑hosted Docker stack (API + Admin Dashboard + embeddable widget) that streams chat responses via the OpenAI Responses API. Designed for a single bot per deployment (one customer/website per stack).

It supports OpenAI Agentic Builder workflow exports (SDK): paste the exported JS/TS into the dashboard and the server will compile and run it. If no workflow is enabled, the API falls back to direct Responses API calls with model + instructions (+ optional File Search via vector store IDs).

ChatWidget Admin Dashboard

Features

  • Self‑hosted: API key stays server‑side; widget is just static assets.
  • Agentic Builder (Workflow SDK) + automatic fallback to direct Responses API.
  • Streaming (SSE) from API → widget.
  • Admin Dashboard to configure model/workflow, origins, and widget branding.
  • Origin allow‑list + simple per‑IP rate limiting.
  • No ChatKit dependency.

Why not ChatKit?

This project does not use ChatKit. It is fully self‑hosted and ships a small widget script you serve yourself. That usually plays nicer with CSP, corporate firewalls, and ad blockers, and avoids third‑party widget loaders.

  • No cookies / tracking pixels by default.
  • The widget stores chat history in localStorage by default (client‑side only).

Architecture

  • API / Gateway
    • POST /api/chat/stream streams responses (Workflow SDK or direct Responses API).
    • Stores config in data/config.json (bind mounted).
    • Enforces ALLOWED_ORIGINS + rate limit.
  • Admin Dashboard
    • Configure OpenAI key, workflow/model, vector stores, allowed origins, UI/branding.
    • Generates the embed snippet.
  • Widget Host
    • Serves widget.js (and static assets).
    • Fetches /api/config/public and streams chat via /api/chat/stream.

Quickstart

cp .env.example .env
# Set at least: ADMIN_TOKEN, PUBLIC_WIDGET_ORIGIN, API_BASE_URL
# Optional: cp docker-compose.override.yml.example docker-compose.override.yml

docker compose up -d --build

Local URLs

  • Dashboard: http://localhost:3000 (admin token: ADMIN_TOKEN)
  • Widget script: http://localhost:8081/widget.js
  • API health: http://localhost:8080/healthz

GHCR images (optional)

This repository can publish Docker images to GitHub Container Registry (GHCR):

  • ghcr.io/<owner>/<repo>/api
  • ghcr.io/<owner>/<repo>/widget
  • ghcr.io/<owner>/<repo>/dashboard

Run without building locally:

cp .env.example .env
GHCR_REPO=OWNER/REPO IMAGE_TAG=latest docker compose -f docker-compose.ghcr.yml up -d

Versioning & releases

This project uses SemVer. To publish a new version:

  1. Update VERSION and CHANGELOG.md.
  2. Create and push a git tag, e.g. v1.0.0.

The GHCR workflow will publish images tagged like 1.0.0, 1.0, 1 (and also v1.0.0), plus latest on the default branch.

OpenAI setup

OpenAI Agentic Builder workflow (SDK) (recommended)

  1. Build a workflow in the OpenAI Agentic Builder.
  2. Export the workflow code (JS/TS).
  3. Paste it into the Admin Dashboard under Workflow SDK.
  4. Click Save Workflow.

The server expects an export like:

export const runWorkflow = async (workflow) => {
  // ...
};

The code runs server‑side. The widget never receives your API key.

Direct Responses API (fallback, no workflow)

If the workflow is empty/disabled, the API will call the Responses API directly using:

  • model (e.g. gpt-4.1-mini)
  • optional instructions
  • optional vector_store_ids for File Search

Embedding

<script src="https://YOUR-WIDGET-HOST/widget.js" data-api="https://YOUR-API-HOST"></script>

data-api can include a path (e.g. https://api.example.com/customer-1) if you run multiple stacks behind one host.

Next.js (Client Component)

In React/Next.js, <script> tags in JSX won’t execute automatically. Load the widget via useEffect:

"use client";

import { useEffect } from "react";

export default function ChatWidget() {
  useEffect(() => {
    if (document.getElementById("chatwidget-loader")) return;
    const script = document.createElement("script");
    script.id = "chatwidget-loader";
    script.src = "https://YOUR-WIDGET-HOST/widget.js";
    script.async = true;
    script.dataset.api = "https://YOUR-API-HOST";
    document.body.appendChild(script);
  }, []);

  return null;
}

Configuration

The dashboard persists configuration to data/config.json (and workflow code to data/workflow.source.ts). Make sure data/ is mounted as a persistent volume.

Required environment variables

  • ADMIN_TOKEN (protects /api/config and /api/workflow)
  • PUBLIC_WIDGET_ORIGIN (used to generate the embed snippet in the dashboard)
  • API_BASE_URL (used to generate the embed snippet in the dashboard)

Common optional variables

  • DATA_DIR (default: ./data) host directory mounted to /app/data
  • ALLOWED_ORIGINS (comma‑separated or newline‑separated)
  • RATE_LIMIT_PER_MINUTE, BASE_PATH, ALLOW_NO_ORIGIN, OPENAI_BASE_URL
  • Defaults used only when config.json is empty: OPENAI_API_KEY, OPENAI_MODEL, OPENAI_INSTRUCTIONS, VECTOR_STORE_IDS, OPENAI_TEMPERATURE, OPENAI_MAX_OUTPUT_TOKENS, OPENAI_BETA, OPENAI_REQUEST_OVERRIDES

See .env.example for a full list.

Security notes

  • Keep your ADMIN_TOKEN long and random.
  • Only add customer website origins to ALLOWED_ORIGINS.
  • The OpenAI API key is stored server‑side in data/config.json and never shipped to the widget.

Deployment patterns (multiple customers)

Option A: subdomain per stack

  • api-customer1.example.com
  • widget-customer1.example.com

Option B: one host, path per customer

  • https://api.example.com/customer-1
  • https://widget.example.com/customer-1

If your reverse proxy does not rewrite paths, set BASE_PATH=/customer-1 in the API container.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors