From 1f07471f81b4a266f51af106ed27c128586cfd04 Mon Sep 17 00:00:00 2001 From: fdc310 <2213070223@qq.com> Date: Tue, 25 Aug 2026 22:34:45 +0800 Subject: [PATCH] feat(wizard): label page bot test preview --- src/langbot/templates/embed/widget.js | 12 +++++++++ web/src/app/wizard/page.tsx | 8 ++++-- web/src/i18n/locales/en-US.ts | 2 ++ web/src/i18n/locales/ja-JP.ts | 2 ++ web/src/i18n/locales/zh-Hans.ts | 1 + web/tests/unit/wizard-page-bot.test.mjs | 33 +++++++++++++++++++++++++ 6 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 web/tests/unit/wizard-page-bot.test.mjs diff --git a/src/langbot/templates/embed/widget.js b/src/langbot/templates/embed/widget.js index 3e6f423b3..a62c54a4c 100644 --- a/src/langbot/templates/embed/widget.js +++ b/src/langbot/templates/embed/widget.js @@ -7,6 +7,9 @@ // Read config from script tag data attributes var scriptEl = document.currentScript; var scriptTitle = scriptEl ? scriptEl.getAttribute("data-title") : null; + var scriptTestNotice = scriptEl + ? scriptEl.getAttribute("data-test-notice") + : null; // ========== i18n ========== var I18N = { @@ -192,6 +195,7 @@ .lb-header-btn { background: none; border: none; color: #fff; cursor: pointer; padding: 4px; border-radius: 6px; display: flex; align-items: center; justify-content: center; opacity: 0.8; transition: opacity 0.15s; }\ .lb-header-btn:hover { opacity: 1; }\ .lb-header-btn svg { width: 18px; height: 18px; fill: currentColor; }\ + .lb-test-notice { padding: 8px 16px; border-bottom: 1px solid #fde68a; background: #fffbeb; color: #92400e; font-size: 12px; line-height: 1.5; text-align: center; flex-shrink: 0; }\ .lb-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 16px; scroll-behavior: smooth; }\ .lb-messages::-webkit-scrollbar { width: 6px; }\ .lb-messages::-webkit-scrollbar-track { background: transparent; }\ @@ -1336,6 +1340,14 @@ header.appendChild(headerActions); panel.appendChild(header); + if (scriptTestNotice) { + var testNotice = document.createElement("div"); + testNotice.className = "lb-test-notice"; + testNotice.setAttribute("role", "note"); + testNotice.textContent = scriptTestNotice; + panel.appendChild(testNotice); + } + // Messages area var messages = document.createElement("div"); messages.className = "lb-messages"; diff --git a/web/src/app/wizard/page.tsx b/web/src/app/wizard/page.tsx index d81da665c..1bf964b82 100644 --- a/web/src/app/wizard/page.tsx +++ b/web/src/app/wizard/page.tsx @@ -1128,14 +1128,17 @@ function StepPlatform({ function PageBotFloatingWidget({ botUuid, title, + testNotice, }: { botUuid: string; title?: string; + testNotice: string; }) { useEffect(() => { const script = document.createElement('script'); - script.src = `${window.location.origin}/api/v1/embed/${botUuid}/widget.js`; + script.src = `${window.location.origin}/api/v1/embed/${botUuid}/widget.js?preview=wizard&v=${Date.now()}`; script.dataset.title = title || 'LangBot'; + script.dataset.testNotice = testNotice; document.body.appendChild(script); return () => { @@ -1149,7 +1152,7 @@ function PageBotFloatingWidget({ root?.remove(); } }; - }, [botUuid, title]); + }, [botUuid, testNotice, title]); return null; } @@ -1242,6 +1245,7 @@ function StepBotConfig({ ? adapterConfigValues.title : undefined } + testNotice={t('wizard.botConfig.pageBotTestNotice')} /> )} diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index b0130d041..bf1d8d74c 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -1833,6 +1833,8 @@ const enUS = { 'The bot received an IM message. You can continue to the next step.', pageBotTestPrompt: 'Page Bot is enabled. Click the chat bubble in the lower-right corner and send a message to verify the full conversation flow.', + pageBotTestNotice: + 'For testing only. Embed the code on a real external webpage.', webhookTestPrompt: 'The callback URL is ready. Configure it on the external platform, then send the bot a real message.', httpTestPrompt: diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index f2f7ecbaf..c3e6a99a0 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -1750,6 +1750,8 @@ const jaJP = { 'ボットが IM メッセージを受信しました。次のステップに進めます。', pageBotTestPrompt: 'ページボットが有効になりました。右下のチャットバブルをクリックしてメッセージを送信し、会話フロー全体を確認してください。', + pageBotTestNotice: + 'テスト専用です。実際の外部 Web ページにコードを埋め込んでください。', webhookTestPrompt: 'コールバック URL の準備ができました。外部プラットフォームに設定し、ボットへ実際のメッセージを送信してください。', httpTestPrompt: diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index c3bbcab74..160ba9419 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -1753,6 +1753,7 @@ const zhHans = { messageReceived: '机器人已成功收到 IM 消息,可以进入下一步。', pageBotTestPrompt: '页面机器人已启用。点击右下角聊天气泡并发送一条消息,验证完整对话链路。', + pageBotTestNotice: '仅供测试使用,请嵌入代码到真实外部网页。', webhookTestPrompt: '回调地址已就绪。将它配置到外部平台,然后向机器人发送一条真实消息。', httpTestPrompt: 'HTTP Bot 已启用。可直接发送一条真实入站消息验证连接。', diff --git a/web/tests/unit/wizard-page-bot.test.mjs b/web/tests/unit/wizard-page-bot.test.mjs new file mode 100644 index 000000000..82548c83c --- /dev/null +++ b/web/tests/unit/wizard-page-bot.test.mjs @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +const currentDirectory = path.dirname(fileURLToPath(import.meta.url)); +const wizardSource = fs.readFileSync( + path.resolve(currentDirectory, '../../src/app/wizard/page.tsx'), + 'utf8', +); +const widgetSource = fs.readFileSync( + path.resolve( + currentDirectory, + '../../../src/langbot/templates/embed/widget.js', + ), + 'utf8', +); + +test('shows the test-only notice only when the wizard opts in', () => { + assert.match( + wizardSource, + /widget\.js\?preview=wizard&v=\$\{Date\.now\(\)\}/, + ); + assert.match(wizardSource, /script\.dataset\.testNotice = testNotice/); + assert.match( + wizardSource, + /testNotice=\{t\('wizard\.botConfig\.pageBotTestNotice'\)\}/, + ); + assert.match(widgetSource, /getAttribute\("data-test-notice"\)/); + assert.match(widgetSource, /if \(scriptTestNotice\)/); + assert.match(widgetSource, /testNotice\.textContent = scriptTestNotice/); +});