mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 03:55:55 +00:00
perf: add notification toasts
This commit is contained in:
22
web/package-lock.json
generated
22
web/package-lock.json
generated
@@ -27,10 +27,12 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.507.0",
|
||||
"next": "15.2.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"postcss": "^8.5.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.56.3",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^4.1.5",
|
||||
"uuidjs": "^5.1.0",
|
||||
@@ -5834,6 +5836,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-themes": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
@@ -7443,6 +7455,16 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sonner": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
|
||||
"integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.507.0",
|
||||
"next": "15.2.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"postcss": "^8.5.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.56.3",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^4.1.5",
|
||||
"uuidjs": "^5.1.0",
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Bot } from '@/app/infra/entities/api';
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
@@ -44,8 +45,8 @@ const formSchema = z.object({
|
||||
description: z.string().min(1, { message: '机器人描述不能为空' }),
|
||||
adapter: z.string().min(1, { message: '适配器不能为空' }),
|
||||
adapter_config: z.record(z.string(), z.any()),
|
||||
enable: z.boolean(),
|
||||
use_pipeline_uuid: z.string().min(1, { message: '流水线不能为空' }),
|
||||
enable: z.boolean().optional(),
|
||||
use_pipeline_uuid: z.string().optional(),
|
||||
});
|
||||
|
||||
export default function BotForm({
|
||||
@@ -114,6 +115,8 @@ export default function BotForm({
|
||||
console.log('form', form.getValues());
|
||||
handleAdapterSelect(val.adapter);
|
||||
// dynamicForm.setFieldsValue(val.adapter_config);
|
||||
}).catch((err) => {
|
||||
toast.error("获取机器人配置失败:" + err.message);
|
||||
});
|
||||
|
||||
} else {
|
||||
@@ -182,25 +185,30 @@ export default function BotForm({
|
||||
});
|
||||
setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap);
|
||||
}
|
||||
|
||||
async function getBotConfig(botId: string): Promise<z.infer<typeof formSchema>> {
|
||||
const bot = (await httpClient.getBot(botId)).bot;
|
||||
return {
|
||||
adapter: bot.adapter,
|
||||
description: bot.description,
|
||||
name: bot.name,
|
||||
adapter_config: bot.adapter_config,
|
||||
enable: bot.enable ?? true,
|
||||
use_pipeline_uuid: bot.use_pipeline_uuid ?? '',
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
httpClient.getBot(botId)
|
||||
.then(res => {
|
||||
const bot = res.bot;
|
||||
resolve({
|
||||
adapter: bot.adapter,
|
||||
description: bot.description,
|
||||
name: bot.name,
|
||||
adapter_config: bot.adapter_config,
|
||||
enable: bot.enable ?? true,
|
||||
use_pipeline_uuid: bot.use_pipeline_uuid ?? '',
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleAdapterSelect(adapterName: string) {
|
||||
console.log('Select adapter: ', adapterName);
|
||||
if (adapterName) {
|
||||
const dynamicFormConfigList =
|
||||
adapterNameToDynamicConfigMap.get(adapterName);
|
||||
console.log(dynamicFormConfigList);
|
||||
if (dynamicFormConfigList) {
|
||||
setDynamicFormConfigList(dynamicFormConfigList);
|
||||
if (!initBotId) {
|
||||
@@ -240,20 +248,12 @@ export default function BotForm({
|
||||
httpClient
|
||||
.updateBot(initBotId, updateBot)
|
||||
.then((res) => {
|
||||
// TODO success toast
|
||||
console.log('update bot success', res);
|
||||
onFormSubmit(form.getValues());
|
||||
// notification.success({
|
||||
// message: '更新成功',
|
||||
// description: '机器人更新成功',
|
||||
// });
|
||||
toast.success("保存成功");
|
||||
})
|
||||
.catch(() => {
|
||||
// TODO error toast
|
||||
// notification.error({
|
||||
// message: '更新失败',
|
||||
// description: '机器人更新失败',
|
||||
// });
|
||||
.catch((err) => {
|
||||
toast.error("保存失败:" + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
@@ -272,20 +272,12 @@ export default function BotForm({
|
||||
httpClient
|
||||
.createBot(newBot)
|
||||
.then((res) => {
|
||||
// TODO success toast
|
||||
// notification.success({
|
||||
// message: '创建成功',
|
||||
// description: '机器人创建成功',
|
||||
// });
|
||||
console.log(res);
|
||||
onFormSubmit(form.getValues());
|
||||
toast.success("创建成功");
|
||||
})
|
||||
.catch(() => {
|
||||
// TODO error toast
|
||||
// notification.error({
|
||||
// message: '创建失败',
|
||||
// description: '机器人创建失败',
|
||||
// });
|
||||
.catch((err) => {
|
||||
toast.error("创建失败:" + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
@@ -295,8 +287,6 @@ export default function BotForm({
|
||||
}
|
||||
setShowDynamicForm(false);
|
||||
console.log('set loading', false);
|
||||
// TODO 刷新bot列表
|
||||
// TODO 关闭当前弹窗 Already closed @setShowDynamicForm(false)?
|
||||
}
|
||||
|
||||
function handleSaveButton() {
|
||||
@@ -307,6 +297,8 @@ export default function BotForm({
|
||||
if (initBotId) {
|
||||
httpClient.deleteBot(initBotId).then(() => {
|
||||
onBotDeleted();
|
||||
}).catch((err) => {
|
||||
toast.error("删除失败:" + err.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
import { toast } from "sonner";
|
||||
export default function BotConfigPage() {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const [botList, setBotList] = useState<BotCardVO[]>([]);
|
||||
@@ -56,12 +56,7 @@ export default function BotConfigPage() {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('get bot list error', err);
|
||||
// TODO HACK: need refactor to hook mode Notification, but it's not working under render
|
||||
// notification.error({
|
||||
// message: '获取机器人列表失败',
|
||||
// description: err.message,
|
||||
// placement: 'bottomRight',
|
||||
// });
|
||||
toast.error("获取机器人列表失败:" + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
// setIsLoading(false);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { useEffect, useState } from "react";
|
||||
import { httpClient } from "@/app/infra/http/HttpClient";
|
||||
import { LLMModel } from "@/app/infra/entities/api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function DynamicFormItemComponent({
|
||||
config,
|
||||
@@ -25,7 +26,7 @@ export default function DynamicFormItemComponent({
|
||||
httpClient.getProviderLLMModels().then((resp) => {
|
||||
setLlmModels(resp.models);
|
||||
}).catch((err) => {
|
||||
console.error('获取 LLM 模型列表失败:', err);
|
||||
toast.error("获取 LLM 模型列表失败:" + err.message);
|
||||
});
|
||||
}
|
||||
}, [config.type]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GetMetaDataResponse } from '@/app/infra/api/api-types/pipelines/GetMetaDataResponse';
|
||||
import { ApiResponse } from '@/app/infra/api/api-types';
|
||||
import { ApiResponse } from '@/app/infra/entities/api';
|
||||
|
||||
export async function fetchPipelineMetaData(): Promise<
|
||||
ApiResponse<GetMetaDataResponse>
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
|
||||
import { toast } from "sonner"
|
||||
const extraArgSchema = z.object({
|
||||
key: z.string().min(1, { message: '键名不能为空' }),
|
||||
type: z.enum(['string', 'number', 'boolean']),
|
||||
@@ -242,6 +242,9 @@ export default function LLMForm({
|
||||
};
|
||||
httpClient.createProviderLLMModel(requestParam).then(() => {
|
||||
onFormSubmit(value);
|
||||
toast.success("创建成功");
|
||||
}).catch((err) => {
|
||||
toast.error("创建失败:" + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -251,6 +254,9 @@ export default function LLMForm({
|
||||
if (initLLMId) {
|
||||
httpClient.deleteProviderLLMModel(initLLMId).then(() => {
|
||||
onLLMDeleted();
|
||||
toast.success("删除成功");
|
||||
}).catch((err) => {
|
||||
toast.error("删除失败:" + err.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function LLMConfigPage() {
|
||||
const [cardList, setCardList] = useState<LLMCardVO[]>([]);
|
||||
@@ -56,8 +56,8 @@ export default function LLMConfigPage() {
|
||||
setCardList(llmModelList);
|
||||
})
|
||||
.catch((err) => {
|
||||
// TODO error toast
|
||||
console.error('get LLM model list error', err);
|
||||
toast.error("获取模型列表失败:" + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
|
||||
import { toast } from "sonner"
|
||||
|
||||
export default function PipelineFormComponent({
|
||||
initValues,
|
||||
@@ -142,6 +142,9 @@ export default function PipelineFormComponent({
|
||||
httpClient.createPipeline(pipeline).then((resp) => {
|
||||
onFinish();
|
||||
onNewPipelineCreated(resp.uuid);
|
||||
toast.success("创建成功 请编辑流水线详细参数");
|
||||
}).catch((err) => {
|
||||
toast.error("创建失败:" + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,7 +168,12 @@ export default function PipelineFormComponent({
|
||||
// uuid: pipelineId || '',
|
||||
// is_default: false,
|
||||
};
|
||||
httpClient.updatePipeline(pipelineId || '', pipeline).then(() => onFinish());
|
||||
httpClient.updatePipeline(pipelineId || '', pipeline).then(() => {
|
||||
onFinish();
|
||||
toast.success("保存成功");
|
||||
}).catch((err) => {
|
||||
toast.error("保存失败:" + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function renderDynamicForms(stage: PipelineConfigStage, formName: keyof FormValues) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
import { toast } from "sonner"
|
||||
export default function PluginConfigPage() {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const [isEditForm, setIsEditForm] = useState(false);
|
||||
@@ -57,8 +57,8 @@ export default function PluginConfigPage() {
|
||||
setPipelineList(pipelineList);
|
||||
})
|
||||
.catch((error) => {
|
||||
// TODO toast
|
||||
console.log(error);
|
||||
toast.error("获取流水线列表失败:" + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { toast } from "sonner"
|
||||
|
||||
export default function PluginCardComponent({
|
||||
cardVO,
|
||||
@@ -24,7 +25,7 @@ export default function PluginCardComponent({
|
||||
setEnabled(!enabled);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('error: ', err);
|
||||
toast.error("修改失败:" + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setSwitchEnable(true);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { toast } from "sonner";
|
||||
|
||||
enum PluginRemoveStatus {
|
||||
WAIT_INPUT = 'WAIT_INPUT',
|
||||
@@ -51,14 +52,14 @@ export default function PluginForm({
|
||||
|
||||
const handleSubmit = async (values: object) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await httpClient.updatePluginConfig(pluginAuthor, pluginName, values);
|
||||
onFormSubmit();
|
||||
} catch (error) {
|
||||
console.error('更新插件配置失败:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
httpClient.updatePluginConfig(pluginAuthor, pluginName, values).then(() => {
|
||||
onFormSubmit();
|
||||
toast.success("保存成功");
|
||||
}).catch((error) => {
|
||||
toast.error("保存失败:" + error.message);
|
||||
}).finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
if (!pluginInfo || !pluginConfig) {
|
||||
|
||||
@@ -30,7 +30,8 @@ import {
|
||||
GetPipelineMetadataResponseData,
|
||||
AsyncTask
|
||||
} from '@/app/infra/entities/api';
|
||||
import { notification } from 'antd';
|
||||
import { toast } from "sonner"
|
||||
|
||||
|
||||
type JSONValue = string | number | boolean | JSONObject | JSONArray | null;
|
||||
interface JSONObject {
|
||||
@@ -141,13 +142,8 @@ class HttpClient {
|
||||
console.error('Permission denied:', errMessage);
|
||||
break;
|
||||
case 500:
|
||||
// TODO 弹Toast窗
|
||||
// NOTE: move to component layer for customized message?
|
||||
notification.error({
|
||||
message: '服务器错误',
|
||||
description: errMessage,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
// toast.error(errMessage);
|
||||
console.error('Server error:', errMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import './global.css';
|
||||
import type { Metadata } from 'next';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'LangBot',
|
||||
@@ -13,7 +14,10 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={``}>{children}</body>
|
||||
<body className={``}>
|
||||
{children}
|
||||
<Toaster />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Mail, Lock } from "lucide-react";
|
||||
import langbotIcon from '@/app/assets/langbot-logo.webp';
|
||||
import { toast } from "sonner"
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email("请输入有效的邮箱地址"),
|
||||
@@ -78,9 +79,12 @@ export default function Login() {
|
||||
localStorage.setItem('token', res.token);
|
||||
console.log('login success: ', res);
|
||||
router.push('/home');
|
||||
toast.success("登录成功");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('login error: ', err);
|
||||
|
||||
toast.error("登录失败,请检查邮箱和密码是否正确");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Mail, Lock } from "lucide-react";
|
||||
import langbotIcon from '@/app/assets/langbot-logo.webp';
|
||||
import { toast } from "sonner";
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email("请输入有效的邮箱地址"),
|
||||
@@ -64,10 +65,12 @@ export default function Register() {
|
||||
.initUser(username, password)
|
||||
.then((res) => {
|
||||
console.log('init user success: ', res);
|
||||
toast.success("初始化成功 请登录");
|
||||
router.push('/login');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('init user error: ', err);
|
||||
toast.error("初始化失败:" + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
25
web/src/components/ui/sonner.tsx
Normal file
25
web/src/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
"use client"
|
||||
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, ToasterProps } from "sonner"
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
style={
|
||||
{
|
||||
"--normal-bg": "var(--popover)",
|
||||
"--normal-text": "var(--popover-foreground)",
|
||||
"--normal-border": "var(--border)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Toaster }
|
||||
Reference in New Issue
Block a user