Skip to content

YourGPT/chatbot-widget-angular-demo

Repository files navigation

YourGPT Chatbot — Angular Example

Official example of integrating the YourGPT chatbot widget into an Angular application, including AI Actions (custom tools the bot can call to fetch data from your backend) and HMAC-signed user identification (linking the visitor to a real user in your system).

YourGPT widget calling a custom AI Action inside an Angular app

In the screenshot above the user asks "show my employee profile". The LLM has no way to answer from the page DOM, so it calls our registered get_my_employee_details AI Action. The widget renders a Fetching your profile status pill while our Angular handler hits the backend API; the bot then replies with the real record.

🎥 Video walkthrough

If your viewer can't play the inline video, download public/demo.mp4 and play it locally.


Stack

  • Angular 18 LTS — standalone components, routing, signals
  • YourGPT widget loaded via the official script — no npm SDK required
  • A tiny Node HMAC + mock-backend server (server/dev-server.mjs)

Running the example

You need Node 18+ and npm installed.

1 · Install

npm install

2 · Configure your widget

Open src/app/widget-config.ts and replace YOUR_WIDGET_UID with your widget UID from the YourGPT dashboard:

// src/app/widget-config.ts
export const WIDGET_UID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; // ← yours here

3 · Add the widget secret (for HMAC user identification)

The example signs user-identification payloads server-side. Grab your Secret Key from YourGPT Dashboard → Widget → Settings, then:

cp .env.example .env
# edit .env and paste your secret as YOURGPT_WIDGET_SECRET

.env is gitignored — it never gets committed.

4 · Register the two sample AI Actions in your dashboard

Without these, the LLM won't know to call your handlers. Under Dashboard → Widget → AI Copilot Actions, click Add New Action twice and use the values from Dashboard config — step by step below.

5 · Run two terminals

# terminal 1 — backend dev server (HMAC + mock API)
npm run server      # http://localhost:3001

# terminal 2 — Angular dev server
npm start           # http://localhost:4200

The Angular dev server proxies /api/* to the backend, so the AI Action handlers can call the mock employee / purchase-orders endpoints during development.

6 · Try both routes

Route What it shows
http://localhost:4200/ Floating mode — classic chat bubble in the bottom-right
http://localhost:4200/panel Embedded mode — chat lives in a collapsible right-side panel

Click Identify as demo-user@yourcompany.com, then ask the bot "show my employee profile" or "what purchase orders do I have?" — the loading pill renders inside the chat, your Angular handler hits the mock backend, and the bot replies with the result.


Two demos in one app — floating vs embedded render modes

The example ships with two routes, each demonstrating a different render mode of the same widget:

Route Mode What you see
/ floating (default) The classic chat bubble in the bottom-right corner
/panel embedded The widget rendered inline inside a collapsible right-side panel

Switching between them in the top nav triggers a full page reload because the widget's render mode is set on a window flag before its script is loaded — it can't be changed at runtime.

How embedded mode works

Three things differ from the floating setup:

  1. Set window.YGC_WIDGET_RENDER_MODE = 'embedded' before the widget script loads. (In this example a service injects the script tag dynamically with the right mode per route.)
  2. Render your own <div id="yourgpt_root"> somewhere in the DOM — that's where the widget mounts. Size and style it however you want (right sidebar, bottom drawer, in-page panel, full screen).
  3. After the script is ready, call window.YGC_WIDGET.renderEmbedded(rootEl) to trigger the actual render. (Floating mode does this automatically; embedded leaves it to you so you control timing.)

WidgetLoaderService in src/app/widget-loader.service.ts handles all three steps. See src/app/panel-demo.component.html for the panel layout and CSS for the slide-in/out animation.


How the integration works

1. Load the widget dynamically — WidgetLoaderService

window.YGC_WIDGET_ID = widgetUid;
window.YGC_WIDGET_RENDER_MODE = mode;          // 'floating' or 'embedded'
const script = document.createElement('script');
script.id = 'yourgpt-chatbot';
script.src = 'https://widget.yourgpt.ai/script.js';
document.body.appendChild(script);

if (mode === 'embedded') {
  // Wait for window.YGC_WIDGET.renderEmbedded then call it with your root div
  // (see the service for the polling logic)
}

The loader script attaches a global window.$yourgptChatbot object exposing { execute, on, off, set }.

2. Type the global — src/app/chatbot-integration.service.ts

declare global {
  interface Window {
    $yourgptChatbot?: {
      execute: (command: string, ...args: unknown[]) => void;
      on: (event: string, callback: (...args: unknown[]) => void) => void;
      off?: (event: string, callback?: (...args: unknown[]) => void) => void;
      set?: (key: string, value: unknown) => void;
    };
  }
}

3. Register AI Action handlers

const actions = {
  get_my_employee_details: async (_data, helpers) => {
    const res = await fetch(`/api/employee/${this.currentExternalUserId ?? 'default'}`);
    const { employee } = await res.json();
    helpers.respond(`Employee: ${employee.name}\nID: ${employee.employee_id}\nDept: ${employee.department}`);
  },
};

for (const [name, handler] of Object.entries(actions)) {
  window.$yourgptChatbot.on(`ai:action:${name}`, handler);
}

The handler receives (data, helpers). Call helpers.respond(text) to send a result back to the LLM, or await helpers.confirm({...}) to ask the user for approval first.

Because the widget script may not be ready when Angular boots, the example polls window.$yourgptChatbot every 100 ms until it's available before attaching.


AI Actions: the two-sided contract

An AI Action only fires if both sides are wired up.

Side Where What you provide
Client This Angular app A handler that runs and calls helpers.respond()
Server YourGPT dashboard The action's name, description, parameters schema, and loading label

The widget transmits the names of your registered handlers to the server on every message (in the ai_widget_register_action field of the chatbot:message:send socket frame). The LLM then chooses whether to call an action based on the description you set in the dashboard. Without a dashboard entry the LLM doesn't know what the action does and will refuse to call it.

Add the actions in the YourGPT dashboard

For each action handler in this project, create a matching entry under Dashboard → AI Actions for your widget. See the API-based AI Actions section below for the full description and loading-label values to use.

Once configured, ask the bot questions like "show my profile" — the widget will render the loading pill, run your Angular handler, and post the response back into the chat.


How a tool call flows over the wire

[client] chatbot:message:send  + ai_widget_register_action: [...]
              ↓
       LLM decides to call a tool
              ↓
[server → client] chatbot:widget:aiAction { name, data, loading_label }
              ↓
       widget renders "<loading_label>…" pill
       widget fires local  ai:action:<name>  event
              ↓
       your Angular handler runs → helpers.respond("…")
              ↓
[client → server] chatbot:widget:aiActionToolResponse { response }
              ↓
       LLM uses the result, generates the final reply
              ↓
[server → client] chatbot:message:compose (streamed reply)

Tip — the widget also auto-sends extra_content: { page_url, page_title, page_content } with every message. If the answer to the user's question is in there, the LLM will short-circuit and skip the tool call. Make your custom actions return information that isn't already in the DOM, otherwise the bot will answer from page context and your handler won't fire.


Link the visitor to a user in your system (opt-in, HMAC verified)

By default every visitor chats as an anonymous YourGPT visitor — no identification required.

If your app already has its own user accounts, you can opt-in to link the YourGPT visitor record to a known user in your system by passing their external_user_id (plus optional email / name / phone) to the widget. Once linked, that conversation shows up against that user in your YourGPT dashboard — useful for:

  • Analytics & audit — "which of our customers chatted with the bot today?"
  • Segmenting conversations by your own user attributes.

The payload is HMAC-signed so a malicious visitor can't claim to be someone else by guessing their user id — the server only accepts the link if the hash matches.

⚠️ This identification is not about making the bot personally aware of who's chatting inside the conversation. The LLM's reply text won't auto-include the user's name unless you template {{contact.name}} in the bot's system prompt in the dashboard, or register an AI Action that returns identity info. The link is about the YourGPT side of things — who the visitor is in your product.

Full reference: YourGPT Identity Verification docs

⚠️ Never compute the HMAC in the browser. Your widget Secret Key must stay on the server. This example ships a tiny Node server (server/dev-server.mjs) that does the signing. Setup steps are in Running the example at the top.

How it works

ChatbotIntegrationService in src/app/chatbot-integration.service.ts exposes an identifyUser() method bound to the Identify button on the page. In a real app you'd call this right after your own auth flow completes (after sign-in / SSO). The two-step flow:

// 1. Ask your backend to sign the user's external id with your widget secret.
//    Your backend already knows who's logged in via your normal session/auth,
//    so it can sign safely without trusting anything from the browser.
const res = await fetch('/api/hmac', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ external_user_id: 'demo-user-xxxx' }),
});
const { user_hash } = await res.json();

// 2. Pass the signed payload to the widget so YourGPT can link this visitor
//    to that user in your system.
window.$yourgptChatbot.set('contact:data', {
  email: 'demo-user@yourcompany.com',
  name: 'Demo User',
  ext_user_id: 'demo-user-xxxx',
  user_hash,
});

The HMAC verification priority on the server is external_user_id → email → phone (highest first). Sign whichever identifier you pass at the top of that list.

You can also watch for the result in the widget's event stream — chatbot:visitor:identityStatus fires with { status: true, message: "Success" } on a valid hash, or { status: false, message: "Invalid hash" } if the signature doesn't match. In production you'd want to surface that to the user / log it.

Anonymous mode (the default)

Don't call set('contact:data', …) at all. The widget treats every visitor as anonymous until you link them — analytics still record the conversation, just against an anonymous visitor instead of one of your known users.

To unlink an already-linked visitor (e.g. user logged out of your app), call:

window.$yourgptChatbot.set('contact:data', null);

The example wires this to the Sign out button.


API-based AI Actions

Most production AI Actions don't return a local string — they go fetch data from a backend (HRMS, CRM, ERP, an internal microservice, anything you'd otherwise expose to an authenticated user) and feed the result back to the bot. The example includes two such actions, hitting a mock backend that ships in server/dev-server.mjs:

Action name What the handler does Try asking
get_my_employee_details GET /api/employee/<ext_user_id> → formats the employee profile "show my employee profile"
get_my_purchase_orders GET /api/purchase-orders/<ext_user_id> → formats the PO list "what purchase orders do I have?"

Identify yourself first (HMAC section above). Once identified, the handler keys off your ext_user_id so the backend returns your personalized record. Without identification, both actions fall back to a "Guest User" record.

The handler pattern

get_my_employee_details: async (_data, helpers) => {
  const userId = this.currentExternalUserId ?? 'default';
  try {
    const res = await fetch(`/api/employee/${encodeURIComponent(userId)}`);
    if (!res.ok) throw new Error(`Backend returned ${res.status}`);
    const { employee } = await res.json();
    helpers.respond(
      `Employee: ${employee.name}\nID: ${employee.employee_id}\nDept: ${employee.department}`,
    );
  } catch (err) {
    helpers.respond(`Could not fetch employee profile: ${err.message}`);
  }
},

Replacing the mock with your real backend

Swap the routes in server/dev-server.mjs for real API calls. For example, against an SAP OData service:

// Your backend authenticates upstream with a service account / OAuth / SAML
const upstream = await fetch(
  `${API_BASE}/employees/${userId}`,
  { headers: { Authorization: `Bearer ${SERVICE_TOKEN}` } },
);
const employee = await upstream.json();
return send(res, 200, { external_user_id: userId, employee });

⚠️ Never put upstream credentials in the browser. The Angular client only talks to your backend at /api/.... Your backend authenticates to the upstream system (SAP, Salesforce, Workday, internal service, etc.) with a service account / OAuth client credentials / SAML — whatever it requires. Map the YourGPT ext_user_id you trust (HMAC-verified) to the upstream user record server-side.

Dashboard config — step by step

Both actions also need to be registered in your YourGPT dashboard so the LLM knows when to call them. Here's the walkthrough.

1. From your widget's settings, open AI Copilot Actions.

Dashboard → Widget settings → AI Copilot Actions

2. You'll see the built-in actions. Click Add New Action in the top-right.

AI Copilot Actions list with Add New Action highlighted

3. Fill in the form for each action. The Name must match exactly what your client registered with on('ai:action:<name>', …). The Description is the prompt the LLM reads to decide when to call the action — keep it explicit. Start with "Use this when the user…" and list every intent that should trigger it.

Add Widget Function form filled with get_my_employee_details

Use these exact values for the two actions in this example:

get_my_employee_details

  • Description:

    Use this when the user asks about their own employee profile — job role, designation, department, team, manager, reporting line, employee ID, hire date / date of joining, work location, office, or any HR record about themselves. Do not call this for other people's records. Returns the user's employee profile.

  • Loading label: Fetching your profile
  • Parameters: none

get_my_purchase_orders

  • Description:

    Use this when the user asks about their own purchase orders or procurement records — POs, recent orders, pending approvals, vendor purchases, what they've ordered, PO status, or how much they've spent on a vendor. Returns the list of the user's purchase orders with PO number, vendor, amount, status, and creation date.

  • Loading label: Looking up your purchase orders
  • Parameters: none

Project layout

src/
├── index.html                          # bare host page; widget loaded dynamically per route
└── app/
    ├── app.component.ts                # shell: top nav + <router-outlet>
    ├── app.routes.ts                   # / → floating, /panel → embedded
    ├── app.config.ts
    ├── widget-config.ts                # YOUR_WIDGET_UID — replace with yours
    ├── widget-loader.service.ts        # injects widget script with the requested mode
    ├── chatbot-integration.service.ts  # AI Action handlers + identify state (shared)
    ├── floating-demo.component.{ts,html,css}    # / — floating mode demo
    └── panel-demo.component.{ts,html,css}       # /panel — embedded mode demo
server/
└── dev-server.mjs                      # HMAC signing + mock backend endpoints
proxy.conf.json                         # /api → http://localhost:3001 in dev
.env.example                            # copy to .env, fill YOURGPT_WIDGET_SECRET
public/
├── demo.png                            # hero screenshot
├── demo.mp4                            # video walkthrough (embedded on the page)
├── dashboard-1-find-ai-actions.png
├── dashboard-2-actions-list.png
└── dashboard-3-add-action.png

Scripts

Command What it does
npm start Run Angular dev server at http://localhost:4200/
npm run server Run backend dev server (HMAC signing + mock API) at http://localhost:3001/ (reads .env)
npm run build Production build into dist/chatbot-widget-angular-demo/
npm test Run unit tests

Resources

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors