mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-22 13:34:24 +00:00
docs(examples): add web-page-bot embed demo, drop stray test-embed.html
Move the Page Bot (web_page_bot) embed test page out of the repo root into examples/web-page-bot/ as a proper, LangBot-styled demo: a self-contained index.html that loads the live widget.js against a running instance, plus bilingual READMEs mirroring examples/http-bot/.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# Page Bot Adapter — Embed Demo
|
||||
|
||||
> English | [中文](./README.zh.md)
|
||||
|
||||
A single self-contained HTML page that demos the LangBot **Page Bot**
|
||||
(`web_page_bot`) embeddable chat widget — the one you drop onto any website with
|
||||
a single `<script>` tag.
|
||||
|
||||
Full guide: [docs.langbot.app — Page Bot](https://docs.langbot.app/en/usage/platforms/webpage).
|
||||
|
||||
## Files
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `index.html` | **Browser demo** — open it, point it at a running LangBot instance + a Page Bot you created, and it loads the live embed widget so you can chat with the bot exactly as a site visitor would. Zero deps, no build step. |
|
||||
|
||||
## How to use
|
||||
|
||||
1. In the LangBot WebUI, create a bot with the **Page Bot** (`页面机器人`)
|
||||
adapter and bind it to a working pipeline. Copy its **bot UUID** from the
|
||||
generated embed code.
|
||||
2. Open `index.html` in a browser. Any of these work:
|
||||
- double-click the file, or
|
||||
- serve the folder: `python3 -m http.server 8930` then open
|
||||
`http://localhost:8930/examples/web-page-bot/`.
|
||||
3. Fill in:
|
||||
- **LangBot base URL** — where your instance is reachable from the browser
|
||||
(e.g. `http://localhost:5300`, or your public address).
|
||||
- **Page Bot UUID** — from step 1.
|
||||
- **Widget title** — optional, sets the `data-title` attribute.
|
||||
4. Click **Load widget**. A floating chat bubble appears in the bottom-right
|
||||
corner — click it and chat.
|
||||
|
||||
The page also renders the exact `<script>` snippet you'd paste into your own
|
||||
site (before `</body>`), and updates it live as you edit the fields.
|
||||
|
||||
## What it demonstrates
|
||||
|
||||
- The embed contract: `<script data-title="…" src="<base>/api/v1/embed/<uuid>/widget.js"></script>`.
|
||||
- `widget.js` is served by LangBot pre-configured for that bot UUID — title,
|
||||
bubble icon, language and optional Cloudflare Turnstile protection all come
|
||||
from the bot's config, no page changes needed.
|
||||
- Messages travel over a WebSocket to the bot's bound pipeline; replies stream
|
||||
back into the bubble.
|
||||
|
||||
> The widget loads `widget.js` from your LangBot instance, so the **base URL
|
||||
> must be reachable from the browser** you open this page in. If LangBot runs on
|
||||
> a server, use its public address instead of `localhost`.
|
||||
@@ -0,0 +1,44 @@
|
||||
# 页面机器人适配器 —— 嵌入演示
|
||||
|
||||
> [English](./README.md) | 中文
|
||||
|
||||
一个自包含的单文件 HTML 页面,用于演示 LangBot **页面机器人**
|
||||
(`web_page_bot`) 的可嵌入聊天组件 —— 也就是你用一行 `<script>` 标签就能放到任意
|
||||
网站上的那个组件。
|
||||
|
||||
完整指南:[docs.langbot.app —— 页面机器人](https://docs.langbot.app/zh/usage/platforms/webpage)。
|
||||
|
||||
## 文件清单
|
||||
|
||||
| 文件 | 是什么 |
|
||||
|---|---|
|
||||
| `index.html` | **浏览器演示页** —— 打开它,填上一个运行中的 LangBot 实例地址 + 你创建的页面机器人,它就会加载真实的嵌入组件,让你像网站访客一样和机器人对话。零依赖,无需构建。 |
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 在 LangBot WebUI 中,用 **页面机器人**(`web_page_bot`)适配器创建一个机器人,
|
||||
并绑定一个可用的流水线。从生成的嵌入代码里复制它的 **机器人 UUID**。
|
||||
2. 在浏览器中打开 `index.html`,以下任一方式皆可:
|
||||
- 直接双击该文件;或
|
||||
- 起一个静态服务:`python3 -m http.server 8930`,然后打开
|
||||
`http://localhost:8930/examples/web-page-bot/`。
|
||||
3. 填写:
|
||||
- **LangBot base URL** —— 你的实例在该浏览器中可访问的地址
|
||||
(例如 `http://localhost:5300`,或你的公网地址)。
|
||||
- **页面机器人 UUID** —— 第 1 步里复制的。
|
||||
- **组件标题** —— 可选,对应 `data-title` 属性。
|
||||
4. 点击 **Load widget**。页面右下角会出现一个浮动聊天气泡 —— 点开即可对话。
|
||||
|
||||
页面还会实时渲染出你需要粘贴到自己网站(放在 `</body>` 前)的那段 `<script>`
|
||||
代码,并随着你编辑输入框同步更新。
|
||||
|
||||
## 它演示了什么
|
||||
|
||||
- 嵌入契约:`<script data-title="…" src="<base>/api/v1/embed/<uuid>/widget.js"></script>`。
|
||||
- `widget.js` 由 LangBot 针对该机器人 UUID 预配置后下发 —— 标题、气泡图标、语言
|
||||
以及可选的 Cloudflare Turnstile 防护,全部来自机器人配置,无需改动页面。
|
||||
- 消息通过 WebSocket 发往机器人绑定的流水线,回复流式回到气泡中。
|
||||
|
||||
> 组件会从你的 LangBot 实例加载 `widget.js`,因此 **base URL 必须能从你打开本页
|
||||
> 的浏览器访问到**。如果 LangBot 部署在服务器上,请用它的公网地址而非
|
||||
> `localhost`。
|
||||
@@ -0,0 +1,205 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>LangBot Page Bot · Embed Demo</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f7f8fa; --panel: #ffffff; --line: #e8eaed; --ink: #1f2329;
|
||||
--mut: #8a909a; --brand: #2563eb; --brand-soft: #eef3ff;
|
||||
--ok: #16a34a; --bad: #dc2626; --code: #f3f4f6;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
margin: 0; background: var(--bg); color: var(--ink);
|
||||
font: 14px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
.top {
|
||||
height: 52px; background: var(--panel); border-bottom: 1px solid var(--line);
|
||||
display: flex; align-items: center; gap: 10px; padding: 0 18px;
|
||||
}
|
||||
.logo {
|
||||
width: 26px; height: 26px; border-radius: 7px; background: var(--brand);
|
||||
display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 14px;
|
||||
}
|
||||
.top b { font-size: 15px; }
|
||||
.top .ver { font-size: 12px; color: var(--mut); }
|
||||
.wrap { max-width: 760px; margin: 0 auto; padding: 28px 18px 80px; }
|
||||
.hero h1 { margin: 8px 0 6px; font-size: 22px; }
|
||||
.hero p { margin: 0 0 4px; color: var(--mut); }
|
||||
.card {
|
||||
background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
|
||||
padding: 20px; margin-top: 20px;
|
||||
}
|
||||
.card h3 {
|
||||
margin: 0 0 14px; font-size: 14px; font-weight: 600; color: #4b5563;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.card h3 .num {
|
||||
width: 20px; height: 20px; border-radius: 50%; background: var(--brand-soft);
|
||||
color: var(--brand); display: grid; place-items: center; font-size: 12px; font-weight: 700;
|
||||
}
|
||||
.field { margin-bottom: 14px; }
|
||||
.field:last-child { margin-bottom: 0; }
|
||||
.field label { display: block; font-size: 12px; color: var(--mut); margin-bottom: 5px; }
|
||||
.field input {
|
||||
width: 100%; border: 1px solid var(--line); border-radius: 9px;
|
||||
padding: 10px 12px; font-size: 14px; outline: none; font-family: inherit;
|
||||
}
|
||||
.field input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }
|
||||
.hint { font-size: 12px; color: var(--mut); margin-top: 5px; }
|
||||
.hint code { background: var(--code); border-radius: 5px; padding: 1px 5px; font-size: 11px; }
|
||||
.actions { display: flex; gap: 10px; margin-top: 18px; align-items: center; }
|
||||
button {
|
||||
border: 0; border-radius: 9px; padding: 10px 18px; font-size: 14px;
|
||||
font-weight: 500; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.btn-primary { background: var(--brand); color: #fff; }
|
||||
.btn-primary:disabled { opacity: .5; cursor: default; }
|
||||
.btn-ghost { background: #fff; border: 1px solid var(--line); color: #4b5563; }
|
||||
.status { font-size: 13px; color: var(--mut); }
|
||||
.status .ok { color: var(--ok); }
|
||||
.status .bad { color: var(--bad); }
|
||||
pre {
|
||||
background: #0f172a; color: #e2e8f0; border-radius: 10px; padding: 14px 16px;
|
||||
overflow: auto; font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
margin: 0;
|
||||
}
|
||||
.snippet-row { position: relative; }
|
||||
.snippet-row .copy {
|
||||
position: absolute; top: 10px; right: 10px; background: rgba(255,255,255,.12);
|
||||
color: #fff; border: 0; border-radius: 7px; padding: 5px 10px; font-size: 12px; cursor: pointer;
|
||||
}
|
||||
ul.steps { margin: 0; padding-left: 18px; color: #4b5563; }
|
||||
ul.steps li { margin-bottom: 6px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top">
|
||||
<div class="logo">L</div>
|
||||
<b>Page Bot · Embed Demo</b>
|
||||
<span class="ver">examples/web-page-bot</span>
|
||||
</div>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="hero">
|
||||
<h1>Try the LangBot Page Bot widget</h1>
|
||||
<p>Point this page at a running LangBot instance and a <strong>Page Bot</strong> you created,</p>
|
||||
<p>then load the live embed widget below to chat with it — exactly as your site visitors would.</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="num">1</span> Connect your Page Bot</h3>
|
||||
<div class="field">
|
||||
<label for="base">LangBot base URL</label>
|
||||
<input id="base" placeholder="http://localhost:5300" value="http://localhost:5300" />
|
||||
<div class="hint">The address where your LangBot instance is reachable from this browser. No trailing slash.</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="uuid">Page Bot UUID</label>
|
||||
<input id="uuid" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
|
||||
<div class="hint">Create a bot with the <code>Page Bot</code> adapter in the WebUI, then copy its UUID from the embed code.</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="title">Widget title (optional)</label>
|
||||
<input id="title" placeholder="LangBot" value="LangBot" />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="load" class="btn-primary">Load widget</button>
|
||||
<button id="unload" class="btn-ghost">Remove widget</button>
|
||||
<span class="status" id="status">Not loaded.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="num">2</span> The embed snippet</h3>
|
||||
<p style="margin:0 0 12px;color:var(--mut)">This is exactly what you paste into your own site (before <code></body></code>). It updates as you edit the fields above.</p>
|
||||
<div class="snippet-row">
|
||||
<button class="copy" id="copy">Copy</button>
|
||||
<pre id="snippet"><script data-title="LangBot" src="http://localhost:5300/api/v1/embed/<bot-uuid>/widget.js"></script></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="num">3</span> How it works</h3>
|
||||
<ul class="steps">
|
||||
<li>The <code><script></code> tag pulls <code>widget.js</code> from your LangBot instance, pre-configured for that bot UUID.</li>
|
||||
<li>A floating chat bubble appears in the bottom-right corner of the page.</li>
|
||||
<li>Messages travel over a WebSocket to the bot's bound pipeline; replies stream back into the bubble.</li>
|
||||
<li>Title, bubble icon, language and optional Cloudflare Turnstile protection are all set in the bot's config — no page changes needed.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var $ = function (s) { return document.querySelector(s); };
|
||||
var baseEl = $("#base"), uuidEl = $("#uuid"), titleEl = $("#title"),
|
||||
statusEl = $("#status"), snippetEl = $("#snippet");
|
||||
var WIDGET_ID = "langbot-embed-demo-script";
|
||||
|
||||
function clean(v) { return (v || "").trim().replace(/\/+$/, ""); }
|
||||
|
||||
function buildSrc() {
|
||||
var base = clean(baseEl.value) || "http://localhost:5300";
|
||||
var uuid = uuidEl.value.trim() || "<bot-uuid>";
|
||||
return base + "/api/v1/embed/" + uuid + "/widget.js";
|
||||
}
|
||||
|
||||
function refreshSnippet() {
|
||||
var title = titleEl.value.trim() || "LangBot";
|
||||
var src = buildSrc();
|
||||
snippetEl.textContent =
|
||||
'<script data-title="' + title + '" src="' + src + '"><\/script>';
|
||||
}
|
||||
|
||||
function setStatus(html) { statusEl.innerHTML = html; }
|
||||
|
||||
function removeWidget() {
|
||||
var old = document.getElementById(WIDGET_ID);
|
||||
if (old) old.remove();
|
||||
// The widget injects its own DOM (bubble + panel). Clear the common containers it creates.
|
||||
document.querySelectorAll('[id^="langbot-"]').forEach(function (n) {
|
||||
if (n.id !== WIDGET_ID) n.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function loadWidget() {
|
||||
var uuid = uuidEl.value.trim();
|
||||
var uuidRe = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
||||
if (!uuidRe.test(uuid)) {
|
||||
setStatus('<span class="bad">Enter a valid bot UUID first.</span>');
|
||||
return;
|
||||
}
|
||||
removeWidget();
|
||||
var s = document.createElement("script");
|
||||
s.id = WIDGET_ID;
|
||||
s.setAttribute("data-title", titleEl.value.trim() || "LangBot");
|
||||
s.src = buildSrc();
|
||||
s.onload = function () {
|
||||
setStatus('<span class="ok">Widget loaded — look bottom-right.</span>');
|
||||
};
|
||||
s.onerror = function () {
|
||||
setStatus('<span class="bad">Failed to load widget.js — check the base URL and that the bot is enabled.</span>');
|
||||
};
|
||||
document.body.appendChild(s);
|
||||
setStatus("Loading…");
|
||||
}
|
||||
|
||||
$("#load").onclick = loadWidget;
|
||||
$("#unload").onclick = function () {
|
||||
removeWidget();
|
||||
setStatus("Widget removed.");
|
||||
};
|
||||
$("#copy").onclick = function () {
|
||||
navigator.clipboard.writeText(snippetEl.textContent).then(function () {
|
||||
var b = $("#copy"); b.textContent = "Copied"; setTimeout(function () { b.textContent = "Copy"; }, 1200);
|
||||
});
|
||||
};
|
||||
[baseEl, uuidEl, titleEl].forEach(function (el) { el.addEventListener("input", refreshSnippet); });
|
||||
refreshSnippet();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>LangBot Embed Widget Test</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; padding: 40px; background: #f5f5f5; }
|
||||
h1 { margin-bottom: 10px; }
|
||||
p { color: #666; }
|
||||
code { background: #e0e0e0; padding: 2px 6px; border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>LangBot Embed Widget Test Page</h1>
|
||||
<p>If the widget loaded correctly, you should see a blue chat bubble in the bottom-right corner.</p>
|
||||
<p>Replace the <code>BOT_UUID</code> below with your actual bot UUID.</p>
|
||||
|
||||
<!-- Replace BOT_UUID with your real bot UUID -->
|
||||
<script data-title="LangBot" src="http://localhost:5300/api/v1/embed/a0ab80e7-742a-445f-bd0e-7d9758f1cfa7/widget.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user