mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 14:57:15 +00:00
fix(runtime): preserve explicit replies and report bot configuration errors
This commit is contained in:
@@ -24,6 +24,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Settings, FileText, Users, RefreshCw, Trash2 } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toast } from 'sonner';
|
||||
import { showBotError } from './bot-error';
|
||||
import { useCurrentWorkspace } from '@/app/infra/http';
|
||||
import { Bot } from '@/app/infra/entities/api';
|
||||
import EntityBasicInfoDialog, {
|
||||
@@ -91,9 +92,9 @@ export default function BotDetailContent({ id }: { id: string }) {
|
||||
current ? { ...current, enable: checked } : current,
|
||||
);
|
||||
refreshBots();
|
||||
} catch {
|
||||
} catch (error) {
|
||||
setBotEnabled(prev);
|
||||
toast.error(t('bots.setBotEnableError'));
|
||||
showBotError(error, t('bots.setBotEnableError'), t);
|
||||
}
|
||||
},
|
||||
[id, botEnabled, refreshBots, t],
|
||||
@@ -129,11 +130,7 @@ export default function BotDetailContent({ id }: { id: string }) {
|
||||
await refreshBots();
|
||||
toast.success(t('bots.saveSuccess'));
|
||||
} catch (error) {
|
||||
const message =
|
||||
typeof error === 'object' && error && 'msg' in error
|
||||
? String((error as { msg?: string }).msg || '')
|
||||
: '';
|
||||
toast.error(t('bots.saveError') + message);
|
||||
showBotError(error, t('bots.saveError'), t);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TFunction } from 'i18next';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function showBotError(error: unknown, title: string, t: TFunction) {
|
||||
const detail =
|
||||
error && typeof error === 'object'
|
||||
? (error as {
|
||||
code?: string;
|
||||
msg?: string;
|
||||
message?: string;
|
||||
request_id?: string;
|
||||
})
|
||||
: {};
|
||||
const message = detail.msg || detail.message || '';
|
||||
const description =
|
||||
detail.code === 'internal_error'
|
||||
? [
|
||||
t('bots.internalErrorHint'),
|
||||
detail.request_id &&
|
||||
t('bots.errorReference', { id: detail.request_id }),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
: message;
|
||||
toast.error(
|
||||
detail.code === 'bot_apply_failed'
|
||||
? t('bots.applyFailed')
|
||||
: title.replace(/[::]\s*$/, ''),
|
||||
{ description, duration: 10000 },
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { showBotError } from '../../bot-error';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
@@ -413,7 +414,7 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
toast.success(t('bots.saveSuccess'));
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(t('bots.saveError') + err.msg);
|
||||
showBotError(err, t('bots.saveError'), t);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
@@ -438,7 +439,10 @@ const BotForm = forwardRef<BotFormHandle, BotFormProps>(function BotForm(
|
||||
onNewBotCreated(res.uuid);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(t('bots.createError') + err.msg);
|
||||
showBotError(err, t('bots.createError'), t);
|
||||
if (err.code === 'bot_apply_failed' && err.data?.uuid) {
|
||||
onNewBotCreated(err.data.uuid);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -139,6 +139,9 @@ export abstract class BaseHttpClient {
|
||||
code: data?.code || status,
|
||||
msg: errMsg,
|
||||
data: data?.data || null,
|
||||
request_id:
|
||||
(data as { request_id?: string })?.request_id ||
|
||||
error.response.headers['x-request-id'],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -340,6 +340,11 @@ const enUS = {
|
||||
},
|
||||
},
|
||||
bots: {
|
||||
applyFailed: 'Configuration saved, but could not be applied',
|
||||
internalErrorHint:
|
||||
'An unexpected error occurred. Check the backend logs using the reference below.',
|
||||
errorReference: 'Error reference: {{id}}',
|
||||
|
||||
title: 'Bots',
|
||||
description:
|
||||
'Create and manage bots, which are the entry points for LangBot to connect with various platforms',
|
||||
|
||||
@@ -348,6 +348,11 @@ const esES = {
|
||||
"Uses the Workspace owner's LangBot Account billing and credits.",
|
||||
},
|
||||
bots: {
|
||||
applyFailed: 'Configuración guardada, pero no se pudo aplicar',
|
||||
internalErrorHint:
|
||||
'Se produjo un error interno. Consulta los registros del servidor con esta referencia.',
|
||||
errorReference: 'Referencia del error: {{id}}',
|
||||
|
||||
adapterEventDebugAction: 'Probar escucha',
|
||||
title: 'Bots',
|
||||
description:
|
||||
|
||||
@@ -346,6 +346,11 @@ const jaJP = {
|
||||
},
|
||||
},
|
||||
bots: {
|
||||
applyFailed: '設定を保存しましたが、適用に失敗しました',
|
||||
internalErrorHint:
|
||||
'内部エラーが発生しました。エラー番号でバックエンドのログを確認してください。',
|
||||
errorReference: 'エラー番号: {{id}}',
|
||||
|
||||
title: 'ボット',
|
||||
description:
|
||||
'ボットの作成と管理を行います。LangBotと各プラットフォームを接続するためのエントリーポイントです',
|
||||
|
||||
@@ -346,6 +346,11 @@ const ruRU = {
|
||||
"Uses the Workspace owner's LangBot Account billing and credits.",
|
||||
},
|
||||
bots: {
|
||||
applyFailed: 'Настройки сохранены, но не применены',
|
||||
internalErrorHint:
|
||||
'Внутренняя ошибка. Проверьте журналы сервера по указанному идентификатору.',
|
||||
errorReference: 'Идентификатор ошибки: {{id}}',
|
||||
|
||||
adapterEventDebugAction: 'Тест прослушивания',
|
||||
title: 'Боты',
|
||||
description:
|
||||
|
||||
@@ -333,6 +333,11 @@ const thTH = {
|
||||
"Uses the Workspace owner's LangBot Account billing and credits.",
|
||||
},
|
||||
bots: {
|
||||
applyFailed: 'บันทึกการตั้งค่าแล้ว แต่ไม่สามารถนำไปใช้ได้',
|
||||
internalErrorHint:
|
||||
'เกิดข้อผิดพลาดภายใน โปรดตรวจสอบบันทึกของเซิร์ฟเวอร์ด้วยหมายเลขอ้างอิง',
|
||||
errorReference: 'หมายเลขข้อผิดพลาด: {{id}}',
|
||||
|
||||
adapterEventDebugAction: 'ทดสอบการรับเหตุการณ์',
|
||||
title: 'บอท',
|
||||
description:
|
||||
|
||||
@@ -342,6 +342,11 @@ const viVN = {
|
||||
"Uses the Workspace owner's LangBot Account billing and credits.",
|
||||
},
|
||||
bots: {
|
||||
applyFailed: 'Đã lưu cấu hình nhưng không thể áp dụng',
|
||||
internalErrorHint:
|
||||
'Đã xảy ra lỗi nội bộ. Hãy kiểm tra nhật ký máy chủ bằng mã lỗi.',
|
||||
errorReference: 'Mã lỗi: {{id}}',
|
||||
|
||||
adapterEventDebugAction: 'Kiểm tra lắng nghe',
|
||||
title: 'Bot',
|
||||
description:
|
||||
|
||||
@@ -325,6 +325,10 @@ const zhHans = {
|
||||
},
|
||||
},
|
||||
bots: {
|
||||
applyFailed: '配置已保存,但应用失败',
|
||||
internalErrorHint: '发生内部错误,请通过错误编号查看后端日志。',
|
||||
errorReference: '错误编号:{{id}}',
|
||||
|
||||
title: '机器人',
|
||||
description: '创建和管理机器人,这是 LangBot 与各个平台连接的入口',
|
||||
createBot: '创建机器人',
|
||||
|
||||
@@ -322,6 +322,10 @@ const zhHant = {
|
||||
"Uses the Workspace owner's LangBot Account billing and credits.",
|
||||
},
|
||||
bots: {
|
||||
applyFailed: '設定已儲存,但套用失敗',
|
||||
internalErrorHint: '發生內部錯誤,請透過錯誤編號查看後端日誌。',
|
||||
errorReference: '錯誤編號:{{id}}',
|
||||
|
||||
adapterEventDebugAction: '測試監聽',
|
||||
title: '機器人',
|
||||
description: '建立和管理機器人,這是 LangBot 與各個平台連接的入口',
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { installLangBotApiMocks } from './fixtures/langbot-api';
|
||||
|
||||
for (const failure of [
|
||||
{
|
||||
status: 400,
|
||||
code: 'invalid_bot_config',
|
||||
msg: 'Lark missing required config: app_id, app_secret, bot_name',
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
code: 'bot_apply_failed',
|
||||
msg: 'Lark missing required config: app_id, app_secret, bot_name',
|
||||
},
|
||||
{
|
||||
status: 500,
|
||||
code: 'internal_error',
|
||||
msg: 'Internal server error',
|
||||
request_id: 'bot-save-test-reference',
|
||||
},
|
||||
]) {
|
||||
test(`bot save displays actionable ${failure.code}`, async ({ page }) => {
|
||||
await installLangBotApiMocks(page, { authenticated: true });
|
||||
await page.goto('/home/bots?id=new');
|
||||
await page.getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: 'Playwright Adapter' }).click();
|
||||
await page.locator('input[name="name"]').fill('Error Test Bot');
|
||||
await page.getByRole('button', { name: /^Submit$/ }).click();
|
||||
await expect(page).toHaveURL(/\/home\/bots\?id=bot-1$/);
|
||||
await page.route('**/api/v1/platform/bots/bot-1', async (route) => {
|
||||
if (route.request().method() !== 'PUT') return route.fallback();
|
||||
await route.fulfill({
|
||||
status: failure.status,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(failure),
|
||||
});
|
||||
});
|
||||
await page.getByRole('button', { name: 'Edit basic information' }).click();
|
||||
const dialog = page.getByRole('dialog');
|
||||
await dialog.getByLabel('Name', { exact: true }).fill('Edited Bot');
|
||||
await dialog.getByRole('button', { name: /^Save$/ }).click();
|
||||
if (failure.code === 'internal_error') {
|
||||
await expect(
|
||||
page.getByText('Error reference: bot-save-test-reference'),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
await expect(page.getByText(failure.msg, { exact: true })).toBeVisible();
|
||||
}
|
||||
if (failure.code === 'bot_apply_failed') {
|
||||
await expect(
|
||||
page.getByText('Configuration saved, but could not be applied'),
|
||||
).toBeVisible();
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user