mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-22 02:07:13 +00:00
feat(wizard): add inbound bot verification
This commit is contained in:
@@ -461,6 +461,15 @@ export class BackendClient extends BaseHttpClient {
|
||||
return this.post(`/api/v1/platform/bots/${botId}/logs`, request);
|
||||
}
|
||||
|
||||
public testHttpBotInbound(
|
||||
botId: string,
|
||||
message: string,
|
||||
): Promise<{ session_id: string; accepted_message_id: string }> {
|
||||
return this.post(`/api/v1/platform/bots/${botId}/test-inbound`, {
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
public getBotSessions(
|
||||
botId: string,
|
||||
limit: number = 100,
|
||||
|
||||
+125
-14
@@ -14,6 +14,9 @@ import {
|
||||
Cable,
|
||||
Settings2,
|
||||
Blocks,
|
||||
Copy,
|
||||
Send,
|
||||
Webhook,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
@@ -45,6 +48,7 @@ import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs';
|
||||
import i18n from 'i18next';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -996,6 +1000,10 @@ function StepBotConfig({
|
||||
extraWebhookUrl: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [testMessage, setTestMessage] = useState(
|
||||
t('wizard.botConfig.httpTestDefaultMessage'),
|
||||
);
|
||||
const [isSendingTest, setIsSendingTest] = useState(false);
|
||||
|
||||
const adapterLabel = useMemo(() => {
|
||||
const a = adapters.find((ad) => ad.name === selectedAdapterName);
|
||||
@@ -1010,6 +1018,29 @@ function StepBotConfig({
|
||||
[],
|
||||
);
|
||||
|
||||
const copyWebhookUrl = useCallback(async () => {
|
||||
if (!webhookUrl) return;
|
||||
await navigator.clipboard.writeText(webhookUrl);
|
||||
toast.success(t('common.copySuccess'));
|
||||
}, [t, webhookUrl]);
|
||||
|
||||
const sendHttpBotTest = useCallback(async () => {
|
||||
if (!createdBotUuid || !testMessage.trim()) return;
|
||||
setIsSendingTest(true);
|
||||
try {
|
||||
await httpClient.testHttpBotInbound(createdBotUuid, testMessage.trim());
|
||||
toast.success(t('wizard.botConfig.httpTestAccepted'));
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
t('wizard.botConfig.httpTestFailed', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
setIsSendingTest(false);
|
||||
}
|
||||
}, [createdBotUuid, testMessage, t]);
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto space-y-6">
|
||||
<div className="text-center">
|
||||
@@ -1019,6 +1050,100 @@ function StepBotConfig({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{botSaved && (
|
||||
<div
|
||||
className={cn(
|
||||
'border px-4 py-3',
|
||||
messageReceived
|
||||
? 'border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/30'
|
||||
: 'border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/30',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div
|
||||
className={cn(
|
||||
'mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full',
|
||||
messageReceived ? 'bg-green-500' : 'bg-amber-500',
|
||||
)}
|
||||
>
|
||||
{messageReceived ? (
|
||||
<Check className="size-3 text-white" />
|
||||
) : selectedAdapterName === 'http_bot' ? (
|
||||
<Send className="size-3 text-white" />
|
||||
) : webhookUrl ? (
|
||||
<Webhook className="size-3 text-white" />
|
||||
) : (
|
||||
<Loader2 className="size-3 animate-spin text-white" />
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p
|
||||
className={cn(
|
||||
'text-sm font-medium',
|
||||
messageReceived
|
||||
? 'text-green-800 dark:text-green-200'
|
||||
: 'text-amber-800 dark:text-amber-200',
|
||||
)}
|
||||
>
|
||||
{messageReceived
|
||||
? t('wizard.botConfig.messageReceived')
|
||||
: selectedAdapterName === 'http_bot'
|
||||
? t('wizard.botConfig.httpTestPrompt')
|
||||
: webhookUrl
|
||||
? t('wizard.botConfig.webhookTestPrompt')
|
||||
: t('wizard.botConfig.waitingForMessage')}
|
||||
</p>
|
||||
|
||||
{!messageReceived && webhookUrl && (
|
||||
<div className="mt-3 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap border bg-background px-2.5 py-2 text-xs">
|
||||
{webhookUrl}
|
||||
</code>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-9 shrink-0"
|
||||
onClick={copyWebhookUrl}
|
||||
title={t('common.copy')}
|
||||
>
|
||||
<Copy className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{selectedAdapterName === 'http_bot' && (
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
<Input
|
||||
value={testMessage}
|
||||
onChange={(event) => setTestMessage(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') void sendHttpBotTest();
|
||||
}}
|
||||
className="bg-background"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => void sendHttpBotTest()}
|
||||
disabled={isSendingTest || !testMessage.trim()}
|
||||
className="shrink-0"
|
||||
>
|
||||
{isSendingTest ? (
|
||||
<Loader2 className="mr-1.5 size-4 animate-spin" />
|
||||
) : (
|
||||
<Send className="mr-1.5 size-4" />
|
||||
)}
|
||||
{t('wizard.botConfig.sendHttpTest')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-6 grid-cols-1 lg:grid-cols-2">
|
||||
{/* Left column: Adapter config form */}
|
||||
<div className="space-y-4">
|
||||
@@ -1082,20 +1207,6 @@ function StepBotConfig({
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Bot and inbound-message verification status */}
|
||||
{botSaved && (
|
||||
<div className="flex items-center gap-2 px-4 py-3 rounded-lg border border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/30">
|
||||
<div className="w-5 h-5 rounded-full bg-green-500 flex items-center justify-center shrink-0">
|
||||
<Check className="w-3 h-3 text-white" />
|
||||
</div>
|
||||
<span className="text-sm text-green-700 dark:text-green-300">
|
||||
{messageReceived
|
||||
? t('wizard.botConfig.messageReceived')
|
||||
: t('wizard.botConfig.waitingForMessage')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right column: Bot logs */}
|
||||
|
||||
@@ -1831,6 +1831,17 @@ const enUS = {
|
||||
'The bot is enabled. Send it a message from your IM platform to continue.',
|
||||
messageReceived:
|
||||
'The bot received an IM message. You can continue to the next step.',
|
||||
webhookTestPrompt:
|
||||
'The callback URL is ready. Configure it on the external platform, then send the bot a real message.',
|
||||
httpTestPrompt:
|
||||
'HTTP Bot is enabled. Send a real inbound message here to verify the connection.',
|
||||
httpTestDefaultMessage: 'Hello, this is a connection test message.',
|
||||
sendHttpTest: 'Send Test Message',
|
||||
httpTestAccepted:
|
||||
'The test message was accepted. It will appear in the log shortly.',
|
||||
httpTestMissingSecret:
|
||||
'Enter an inbound signing secret and save the configuration first.',
|
||||
httpTestFailed: 'Failed to send the test message: {{error}}',
|
||||
logsTitle: 'Bot Logs',
|
||||
logsDescription:
|
||||
'Monitor bot activity to verify the platform connection is working.',
|
||||
|
||||
@@ -1748,6 +1748,17 @@ const jaJP = {
|
||||
'ボットが有効になりました。続行するには IM からメッセージを送信してください。',
|
||||
messageReceived:
|
||||
'ボットが IM メッセージを受信しました。次のステップに進めます。',
|
||||
webhookTestPrompt:
|
||||
'コールバック URL の準備ができました。外部プラットフォームに設定し、ボットへ実際のメッセージを送信してください。',
|
||||
httpTestPrompt:
|
||||
'HTTP Bot が有効になりました。実際の受信メッセージを送信して接続を確認できます。',
|
||||
httpTestDefaultMessage: 'こんにちは。これは接続テストメッセージです。',
|
||||
sendHttpTest: 'テストメッセージを送信',
|
||||
httpTestAccepted:
|
||||
'テストメッセージを受け付けました。まもなくログに表示されます。',
|
||||
httpTestMissingSecret:
|
||||
'受信署名シークレットを入力し、先に設定を保存してください。',
|
||||
httpTestFailed: 'テストメッセージの送信に失敗しました:{{error}}',
|
||||
logsTitle: 'ボットログ',
|
||||
logsDescription:
|
||||
'ボットの活動を監視して、プラットフォーム接続が正常に動作していることを確認します。',
|
||||
|
||||
@@ -1751,6 +1751,14 @@ const zhHans = {
|
||||
botSaved: '机器人配置已保存并启用,请查看日志确认连接正常。',
|
||||
waitingForMessage: '机器人已启用。请在 IM 中向机器人发送一条消息以继续。',
|
||||
messageReceived: '机器人已成功收到 IM 消息,可以进入下一步。',
|
||||
webhookTestPrompt:
|
||||
'回调地址已就绪。将它配置到外部平台,然后向机器人发送一条真实消息。',
|
||||
httpTestPrompt: 'HTTP Bot 已启用。可直接发送一条真实入站消息验证连接。',
|
||||
httpTestDefaultMessage: '你好,这是一条连接测试消息。',
|
||||
sendHttpTest: '发送测试消息',
|
||||
httpTestAccepted: '测试消息已被机器人接收,请稍候查看日志。',
|
||||
httpTestMissingSecret: '请先填写入站签名密钥并重新保存。',
|
||||
httpTestFailed: '测试消息发送失败:{{error}}',
|
||||
logsTitle: '机器人日志',
|
||||
logsDescription: '监控机器人活动,确认平台连接是否正常工作。',
|
||||
},
|
||||
|
||||
@@ -26,6 +26,10 @@ export default defineConfig(({ mode }) => {
|
||||
target: apiProxyTarget,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/bots': {
|
||||
target: apiProxyTarget,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
||||
Reference in New Issue
Block a user