Foundry Chatbot · Embeddable widget

Add an AI chat widget with one line

A self-contained script that renders a floating launcher and chat panel on any website. Fully themeable through data-* attributes — no build step required on the host page.

Docker Hub License: MIT .NET 10 TypeScript widget Bundle size ~26 kB Zero dependencies

Prefer to run the backend yourself? The ready-to-use container image is on Docker Hub (registry docker.io) — deploy it to Azure App Service, Container Apps, or AKS. Source on GitHub.

Quick start Options reference Deploy Docker Hub image GitHub repo
Screenshot of the chat panel with a sample conversation
The widget: a floating launcher and a themeable chat panel.

Quick start

Add this single tag to your page, right before the closing </body>:

<script
    src="https://your-app.example.com/chatbot-widget.js"
    data-title="Support"
    data-accent="#2563eb"
    defer></script>

Replace the src with the URL where your ChatApp deployment serves chatbot-widget.js. By default the widget calls back to that same origin for the chat API; override it with data-api-base when the backend lives elsewhere.

Theming examples

Brand colours

<script
    src=".../chatbot-widget.js"
    data-title="Acme Help"
    data-accent="#7c3aed"
    data-user-bubble="#7c3aed"
    data-bot-bubble="#312e81"
    defer></script>

Light panel

<script
    src=".../chatbot-widget.js"
    data-panel="#f8fafc"
    data-bot-bubble="#e2e8f0"
    data-text="#0f172a"
    defer></script>

Custom icon · left side

<script
    src=".../chatbot-widget.js"
    data-icon="https://cdn.acme.com/bot.png"
    data-position="bottom-left"
    data-greeting="Hi! How can I help?"
    defer></script>

Options reference

All configuration is read from data-* attributes on the script tag. Every attribute is optional; omit it to keep the default.

Visitors can also switch the panel between light, dark and system themes using the theme button in the header (or the selector in the settings panel); their choice is remembered for the session.

Attribute Default Description
data-api-base script origin Backend base URL the widget calls for chat requests.
data-title Chatbot Header title text shown at the top of the panel.
data-accent #2563eb Accent colour used for the launcher and primary actions.
data-icon none URL of a custom launcher icon image. Allowed schemes: https:, data:image/, or a root-relative /path.
data-panel #1e293b Background colour of the chat panel.
data-user-bubble accent Background colour of user (outgoing) message bubbles.
data-bot-bubble #334155 Background colour of bot (incoming) message bubbles.
data-text #e2e8f0 Primary text colour inside the panel.
data-position bottom-right Anchor corner: bottom-right or bottom-left.
data-allow-settings true Set to false to hide the in-panel settings UI.
data-greeting Ask me anything to get started. Opening assistant message shown in an empty conversation.
data-storage-key derived LocalStorage key used to persist the conversation.
data-debug false Set to true for verbose console logging.

Colour values accept hex (#0f172a), rgb()/rgba(), hsl()/hsla() or CSS named colours. Invalid values are ignored and fall back to the defaults, so the widget can never be styled into an unreadable state by a malformed attribute.

Pointing the widget at a backend

When the widget is embedded on a different domain than the ChatApp deployment, set data-api-base to the deployment URL:

<script
    src="https://cdn.acme.com/chatbot-widget.js"
    data-api-base="https://chat.acme.com"
    data-title="Acme Support"
    defer></script>

Make sure the backend allows cross-origin requests from the embedding site (CORS) so the browser can reach the streaming endpoint.

Deploying the backend

The widget is served by the .NET 10 ChatApp backend, which also exposes the streaming chat API. The easiest way to run it is the published container image; you can also build and ship your own.

Run the published image

No build required. The image is published on Docker Hub (registry docker.io):

docker run -p 8080:8080 \
    -e AI_MODE=model \
    -e AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" \
    -e AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" \
    -e AZURE_OPENAI_API_KEY="<your-api-key>" \
    congiuluc/foundry-test-chatbot:latest

Use the image name congiuluc/foundry-test-chatbot:latest — do not prefix it with hub.docker.com/. That is the website, not the registry, and causes an “image not found” pull error.

Authentication: the API key is optional on Azure, where the app uses managed identity by default. When the image runs outside Azure (local Docker, another cloud, on-prem) managed identity is unavailable, so you must supply AZURE_OPENAI_API_KEY.

Azure App Service

Run the image directly as a Linux Web App for Containers:

az group create --name rg-chat --location westeurope

az appservice plan create --name plan-chat --resource-group rg-chat --is-linux --sku B1

az webapp create --name <app-name> --resource-group rg-chat --plan plan-chat \
    --container-image-name docker.io/congiuluc/foundry-test-chatbot:latest

az webapp config appsettings set --name <app-name> --resource-group rg-chat --settings \
    WEBSITES_PORT=8080 \
    AI_MODE=model \
    AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" \
    AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"

The container listens on port 8080, so set WEBSITES_PORT=8080.

Azure Container Apps (from Docker Hub)

az containerapp create \
    --name chatapp --resource-group rg-chat --environment cae-chat \
    --image docker.io/congiuluc/foundry-test-chatbot:latest \
    --target-port 8080 --ingress external \
    --env-vars \
        AI_MODE=model \
        AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" \
        AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"

Azure Kubernetes Service (AKS)

Save the manifest as chatapp.yaml and apply it:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: chatapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: chatapp
  template:
    metadata:
      labels:
        app: chatapp
    spec:
      containers:
        - name: chatapp
          image: congiuluc/foundry-test-chatbot:latest
          ports:
            - containerPort: 8080
          env:
            - name: AI_MODE
              value: "model"
            - name: AZURE_OPENAI_ENDPOINT
              value: "https://<your-resource>.openai.azure.com/"
            - name: AZURE_OPENAI_DEPLOYMENT_NAME
              value: "gpt-4o-mini"
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: chatapp
spec:
  type: LoadBalancer
  selector:
    app: chatapp
  ports:
    - port: 80
      targetPort: 8080
az aks get-credentials --resource-group rg-chat --name <aks-cluster>
kubectl apply -f chatapp.yaml
kubectl get service chatapp   # note the EXTERNAL-IP once provisioned

Build your own image

1. Build the widget bundle

cd src/ChatApp/widget
npm install
npm run build   # outputs ../wwwroot/chatbot-widget.js

2. Build & push the container image

Uses the included script to build the image and push it to Azure Container Registry:

./scripts/build-and-push.ps1 -RegistryName <acrName> -Tag v1

3. Deploy to Azure Container Apps

./scripts/deploy-containerapp.ps1 `
    -ResourceGroup rg-chat `
    -EnvironmentName cae-chat `
    -AppName chatapp `
    -RegistryName <acrName> `
    -Tag v1 `
    -EnvVars @{
        AI_MODE = "model"
        AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com/"
        AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4o-mini"
    }

The deploy script enables a system-assigned managed identity and grants it AcrPull. Remember to also grant that identity access to your Foundry resource (e.g. Azure AI User or Cognitive Services OpenAI User).

Run locally

$env:AI_MODE = "model"
$env:AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4o-mini"
dotnet run --project src/ChatApp/ChatApp.csproj

Once deployed, the widget script lives at https://<your-app>/chatbot-widget.js — point the embedding <script src> there, and set data-api-base if the page is hosted on a different domain.

Hosting this documentation

This site lives in the docs/ folder of the repository and is published with GitHub Pages. To enable it:

  1. Open the repository Settings → Pages.
  2. Under Build and deployment, set Source to Deploy from a branch.
  3. Choose the main branch and the /docs folder, then Save.

After saving, GitHub publishes the site at https://<org>.github.io/<repo>/ within a minute or two.