Compare commits

...

10 Commits

Author SHA1 Message Date
Junyan Qin
a594cc07f6 chore: release v4.0.3.1 2025-05-19 10:31:11 +08:00
Junyan Qin
0a9714fbe7 perf: no cache for fronend page 2025-05-17 19:30:26 +08:00
Junyan Qin (Chin)
1992934dce fix: user_funcs typo in ollama chat requester (#1431) 2025-05-15 20:51:58 +08:00
zejiewang
bb930aec14 fix:lark adapter listeners init problem (#1426)
Co-authored-by: wangzejie <wangzejie@meicai.cn>
2025-05-15 11:25:38 +08:00
Junyan Qin
1d7f2ab701 fix: wrong ref in HomeTitleBar 2025-05-15 10:54:22 +08:00
Junyan Qin
347da6142e perf: multi language 2025-05-15 10:40:36 +08:00
Junyan Qin
a9f4dc517a perf: remove -q params in plugin deps precheking 2025-05-15 10:24:53 +08:00
Junyan Qin (Chin)
9d45f3f3a7 updatr README.md 2025-05-15 09:04:38 +08:00
Guanchao Wang
256d24718b fix: dingtalk & wecom problems (#1424) 2025-05-14 22:55:16 +08:00
Junyan Qin
1272b8ef16 ci: update Dockerfile python version 2025-05-14 22:22:17 +08:00
22 changed files with 125 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
name: 漏洞反馈
description: 报错或漏洞请使用这个模板创建不使用此模板创建的异常、漏洞相关issue将被直接关闭。由于自己操作不当/不甚了解所用技术栈引起的网络连接问题恕无法解决,请勿提 issue。容器间网络连接问题参考文档 https://docs.langbot.app/deploy/network-details.html
description: 报错或漏洞请使用这个模板创建不使用此模板创建的异常、漏洞相关issue将被直接关闭。由于自己操作不当/不甚了解所用技术栈引起的网络连接问题恕无法解决,请勿提 issue。容器间网络连接问题参考文档 https://docs.langbot.app/zh/workshop/network-details.html
title: "[Bug]: "
labels: ["bug?"]
body:

View File

@@ -6,7 +6,7 @@ COPY web ./web
RUN cd web && npm install && npm run build
FROM python:3.10.13-slim
FROM python:3.12.7-slim
WORKDIR /app

View File

@@ -32,6 +32,8 @@
</p>
> 近期 GeWeChat 项目归档,我们已经适配 WeChatPad 协议端,个微恢复正常使用,详情请查看文档。
## ✨ 特性
- 💬 大模型对话、Agent支持多种大模型适配群聊和私聊具有多轮对话、工具调用、多模态能力并深度适配 [Dify](https://dify.ai)。目前支持 QQ、QQ频道、企业微信、个人微信、飞书、Discord、Telegram 等平台。

View File

@@ -115,13 +115,20 @@ class DingTalkClient:
if event:
await self._handle_message(event)
async def send_message(self, content: str, incoming_message):
async def send_message(self, content: str, incoming_message,at:bool):
if self.markdown_card:
self.EchoTextHandler.reply_markdown(
title=self.robot_name + '的回答',
text=content,
incoming_message=incoming_message,
)
if at:
self.EchoTextHandler.reply_markdown(
title='@'+incoming_message.sender_nick+' '+content,
text='@'+incoming_message.sender_nick+' '+content,
incoming_message=incoming_message,
)
else:
self.EchoTextHandler.reply_markdown(
title=content,
text=content,
incoming_message=incoming_message,
)
else:
self.EchoTextHandler.reply_text(content, incoming_message)

View File

@@ -29,7 +29,6 @@ class WecomClient:
self.access_token = ''
self.secret_for_contacts = contacts_secret
self.app = Quart(__name__)
self.wxcpt = WXBizMsgCrypt(self.token, self.aes, self.corpid)
self.app.add_url_rule(
'/callback/command',
'handle_callback',
@@ -171,16 +170,17 @@ class WecomClient:
timestamp = request.args.get('timestamp')
nonce = request.args.get('nonce')
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.corpid)
if request.method == 'GET':
echostr = request.args.get('echostr')
ret, reply_echo_str = self.wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
ret, reply_echo_str = wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
if ret != 0:
raise Exception(f'验证失败,错误码: {ret}')
return reply_echo_str
elif request.method == 'POST':
encrypt_msg = await request.data
ret, xml_msg = self.wxcpt.DecryptMsg(encrypt_msg, msg_signature, timestamp, nonce)
ret, xml_msg = wxcpt.DecryptMsg(encrypt_msg, msg_signature, timestamp, nonce)
if ret != 0:
raise Exception(f'消息解密失败,错误码: {ret}')

14
main.py
View File

@@ -10,8 +10,8 @@ asciiart = r"""
|____\__,_|_||_\__, |___/\___/\__|
|___/
⭐️开源地址: https://github.com/RockChinQ/LangBot
📖文档地址: https://docs.langbot.app
⭐️ Open Source 开源地址: https://github.com/RockChinQ/LangBot
📖 Documentation 文档地址: https://docs.langbot.app
"""
@@ -28,10 +28,14 @@ async def main_entry(loop: asyncio.AbstractEventLoop):
if missing_deps:
print('以下依赖包未安装,将自动安装,请完成后重启程序:')
print(
'These dependencies are missing, they will be installed automatically, please restart the program after completion:'
)
for dep in missing_deps:
print('-', dep)
await deps.install_deps(missing_deps)
print('已自动安装缺失的依赖包,请重启程序。')
print('The missing dependencies have been installed automatically, please restart the program.')
sys.exit(0)
# check plugin deps
@@ -53,6 +57,7 @@ async def main_entry(loop: asyncio.AbstractEventLoop):
if generated_files:
print('以下文件不存在,已自动生成:')
print('Following files do not exist and have been automatically generated:')
for file in generated_files:
print('-', file)
@@ -69,9 +74,10 @@ if __name__ == '__main__':
if sys.version_info < (3, 10, 1):
print('需要 Python 3.10.1 及以上版本,当前 Python 版本为:', sys.version)
input('按任意键退出...')
print('Your Python version is not supported. Please exit the program by pressing any key.')
exit(1)
# 检查本目录是否有main.py且包含LangBot字符串
# Check if the current directory is the LangBot project root directory
invalid_pwd = False
if not os.path.exists('main.py'):
@@ -84,6 +90,8 @@ if __name__ == '__main__':
if invalid_pwd:
print('请在 LangBot 项目根目录下以命令形式运行此程序。')
input('按任意键退出...')
print('Please run this program in the LangBot project root directory in command form.')
print('Press any key to exit...')
exit(1)
loop = asyncio.new_event_loop()

View File

@@ -107,4 +107,8 @@ class HTTPController:
elif path.endswith('.txt'):
mimetype = 'text/plain'
return await quart.send_from_directory(frontend_path, path, mimetype=mimetype)
response = await quart.send_from_directory(frontend_path, path, mimetype=mimetype)
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response

View File

@@ -158,7 +158,10 @@ class Application:
"""打印访问 webui 的提示"""
if not os.path.exists(os.path.join('.', 'web/out')):
self.logger.warning('WebUI 文件缺失,请根据文档获取https://docs.langbot.app/webui/intro.html')
self.logger.warning('WebUI 文件缺失,请根据文档部署https://docs.langbot.app/zh')
self.logger.warning(
'WebUI files are missing, please deploy according to the documentation: https://docs.langbot.app/en'
)
return
host_ip = '127.0.0.1'

View File

@@ -74,5 +74,5 @@ async def precheck_plugin_deps():
if 'requirements.txt' in os.listdir(subdir):
pkgmgr.install_requirements(
os.path.join(subdir, 'requirements.txt'),
extra_params=['-q', '-q', '-q'],
extra_params=[],
)

View File

@@ -14,9 +14,14 @@ import datetime
class DingTalkMessageConverter(adapter.MessageConverter):
@staticmethod
async def yiri2target(message_chain: platform_message.MessageChain):
content = ''
at = False
for msg in message_chain:
if type(msg) is platform_message.At:
at = True
if type(msg) is platform_message.Plain:
return msg.text
content += msg.text
return content,at
@staticmethod
async def target2yiri(event: DingTalkEvent, bot_name: str):
@@ -128,8 +133,8 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
)
incoming_message = event.incoming_message
content = await DingTalkMessageConverter.yiri2target(message)
await self.bot.send_message(content, incoming_message)
content,at = await DingTalkMessageConverter.yiri2target(message)
await self.bot.send_message(content, incoming_message,at)
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
content = await DingTalkMessageConverter.yiri2target(message)

View File

@@ -332,7 +332,7 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
listeners: typing.Dict[
typing.Type[platform_events.Event],
typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
] = {}
]
config: dict
quart_app: quart.Quart
@@ -342,6 +342,7 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
self.config = config
self.ap = ap
self.quart_app = quart.Quart(__name__)
self.listeners = {}
@self.quart_app.route('/lark/callback', methods=['POST'])
async def lark_callback():

View File

@@ -42,7 +42,7 @@ class OllamaChatCompletions(requester.LLMAPIRequester):
query: core_entities.Query,
req_messages: list[dict],
use_model: requester.RuntimeLLMModel,
user_funcs: list[tools_entities.LLMFunction] = None,
use_funcs: list[tools_entities.LLMFunction] = None,
extra_args: dict[str, typing.Any] = {},
) -> llm_entities.Message:
args = extra_args.copy()
@@ -67,8 +67,8 @@ class OllamaChatCompletions(requester.LLMAPIRequester):
args['messages'] = messages
args['tools'] = []
if user_funcs:
tools = await self.ap.tool_mgr.generate_tools_for_openai(user_funcs)
if use_funcs:
tools = await self.ap.tool_mgr.generate_tools_for_openai(use_funcs)
if tools:
args['tools'] = tools

View File

@@ -1,4 +1,4 @@
semantic_version = 'v4.0.3'
semantic_version = 'v4.0.3.1'
required_database_version = 1
"""标记本版本所需要的数据库结构版本,用于判断数据库迁移"""

View File

@@ -199,9 +199,9 @@ class VersionManager:
try:
if await self.ap.ver_mgr.is_new_version_available():
return (
'有新版本可用,根据文档更新https://docs.langbot.app/deploy/update.html',
'New version available:\n有新版本可用,根据文档更新: \nhttps://docs.langbot.app/zh/deploy/update.html',
logging.INFO,
)
except Exception as e:
return f'检查版本更新时出错: {e}', logging.WARNING
return f'Error checking version update: {e}', logging.WARNING

View File

@@ -138,7 +138,18 @@ export default function HomeSidebar({
<SidebarChild
onClick={() => {
// open docs.langbot.app
window.open('https://docs.langbot.app', '_blank');
const language = localStorage.getItem('langbot_language');
if (language === 'zh-Hans') {
window.open(
'https://docs.langbot.app/zh/insight/guide.html',
'_blank',
);
} else {
window.open(
'https://docs.langbot.app/en/insight/guide.html',
'_blank',
);
}
}}
isSelected={false}
icon={

View File

@@ -1,4 +1,5 @@
import styles from './HomeSidebar.module.css';
import { I18nText } from '@/app/infra/entities/api';
export interface ISidebarChildVO {
id: string;
@@ -6,7 +7,7 @@ export interface ISidebarChildVO {
name: string;
route: string;
description: string;
helpLink: string;
helpLink: I18nText;
}
export class SidebarChildVO {
@@ -15,7 +16,7 @@ export class SidebarChildVO {
name: string;
route: string;
description: string;
helpLink: string;
helpLink: I18nText;
constructor(props: ISidebarChildVO) {
this.id = props.id;

View File

@@ -22,7 +22,10 @@ export const sidebarConfigList = [
),
route: '/home/bots',
description: t('bots.description'),
helpLink: 'https://docs.langbot.app/zh/deploy/platforms/readme.html',
helpLink: {
en_US: 'https://docs.langbot.app/en/deploy/platforms/readme.html',
zh_Hans: 'https://docs.langbot.app/zh/deploy/platforms/readme.html',
},
}),
new SidebarChildVO({
id: 'models',
@@ -39,7 +42,10 @@ export const sidebarConfigList = [
),
route: '/home/models',
description: t('models.description'),
helpLink: 'https://docs.langbot.app/zh/deploy/models/readme.html',
helpLink: {
en_US: 'https://docs.langbot.app/en/deploy/models/readme.html',
zh_Hans: 'https://docs.langbot.app/zh/deploy/models/readme.html',
},
}),
new SidebarChildVO({
id: 'pipelines',
@@ -56,7 +62,10 @@ export const sidebarConfigList = [
),
route: '/home/pipelines',
description: t('pipelines.description'),
helpLink: 'https://docs.langbot.app/zh/deploy/pipelines/readme.html',
helpLink: {
en_US: 'https://docs.langbot.app/en/deploy/pipelines/readme.html',
zh_Hans: 'https://docs.langbot.app/zh/deploy/pipelines/readme.html',
},
}),
new SidebarChildVO({
id: 'plugins',
@@ -73,6 +82,9 @@ export const sidebarConfigList = [
),
route: '/home/plugins',
description: t('plugins.description'),
helpLink: 'https://docs.langbot.app/zh/plugin/plugin-intro.html',
helpLink: {
en_US: 'https://docs.langbot.app/en/plugin/plugin-intro.html',
zh_Hans: 'https://docs.langbot.app/zh/plugin/plugin-intro.html',
},
}),
];

View File

@@ -1,4 +1,6 @@
import { i18nObj } from '@/i18n/I18nProvider';
import styles from './HomeTittleBar.module.css';
import { I18nText } from '@/app/infra/entities/api';
export default function HomeTitleBar({
title,
@@ -7,7 +9,7 @@ export default function HomeTitleBar({
}: {
title: string;
subtitle: string;
helpLink: string;
helpLink: I18nText;
}) {
return (
<div className={`${styles.titleBarContainer}`}>
@@ -15,7 +17,12 @@ export default function HomeTitleBar({
<div className={`${styles.subtitleText}`}>
{subtitle}
<span className={`${styles.helpLink}`}>
<a href={helpLink} target="_blank" rel="noopener noreferrer">
<div
onClick={() => {
window.open(i18nObj(helpLink), '_blank');
}}
className="cursor-pointer"
>
<svg
className="w-[1rem] h-[1rem]"
xmlns="http://www.w3.org/2000/svg"
@@ -24,7 +31,7 @@ export default function HomeTitleBar({
>
<path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM13 13.3551V14H11V12.5C11 11.9477 11.4477 11.5 12 11.5C12.8284 11.5 13.5 10.8284 13.5 10C13.5 9.17157 12.8284 8.5 12 8.5C11.2723 8.5 10.6656 9.01823 10.5288 9.70577L8.56731 9.31346C8.88637 7.70919 10.302 6.5 12 6.5C13.933 6.5 15.5 8.067 15.5 10C15.5 11.5855 14.4457 12.9248 13 13.3551Z"></path>
</svg>
</a>
</div>
</span>
</div>
</div>

View File

@@ -5,6 +5,7 @@ import HomeSidebar from '@/app/home/components/home-sidebar/HomeSidebar';
import HomeTitleBar from '@/app/home/components/home-titlebar/HomeTitleBar';
import React, { useState } from 'react';
import { SidebarChildVO } from '@/app/home/components/home-sidebar/HomeSidebarChild';
import { I18nText } from '@/app/infra/entities/api';
export default function HomeLayout({
children,
@@ -13,7 +14,10 @@ export default function HomeLayout({
}>) {
const [title, setTitle] = useState<string>('');
const [subtitle, setSubtitle] = useState<string>('');
const [helpLink, setHelpLink] = useState<string>('');
const [helpLink, setHelpLink] = useState<I18nText>({
en_US: '',
zh_Hans: '',
});
const onSelectedChangeAction = (child: SidebarChildVO) => {
setTitle(child.name);
setSubtitle(child.description);

View File

@@ -2,9 +2,11 @@
import { useRouter } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { useTranslation } from 'react-i18next';
export default function NotFound() {
const router = useRouter();
const { t } = useTranslation();
return (
<div className="min-h-screen bg-white flex items-center justify-center">
@@ -18,11 +20,10 @@ export default function NotFound() {
{/* 错误文本 */}
<div className="text-center mb-8">
<h1 className="text-2xl font-normal text-gray-800 mb-2">
{t('notFound.title')}
</h1>
<p className="text-base text-gray-600 max-w-[450px] mx-auto mb-8">
URL
{t('notFound.description')}
</p>
</div>
@@ -33,26 +34,25 @@ export default function NotFound() {
onClick={() => router.back()}
className="h-9 px-4 cursor-pointer"
>
{t('notFound.back')}
</Button>
<Button
variant="outline"
onClick={() => router.push('/home')}
className="h-9 px-4 cursor-pointer"
>
{t('notFound.home')}
</Button>
</div>
{/* 帮助文档链接 */}
<div className="text-center">
<p className="text-sm text-gray-600">
<a
href="https://docs.langbot.app"
className="text-black no-underline hover:underline"
>
{t('notFound.help')}
</a>
</p>
</div>

View File

@@ -38,6 +38,13 @@ const enUS = {
deleteError: 'Delete failed: ',
addRound: 'Add Round',
},
notFound: {
title: 'Page not found',
description: 'The page you are looking for does not exist.',
back: 'Back',
home: 'Home',
help: 'Get Help',
},
models: {
title: 'Models',
description: 'Configure and manage models that can be used in pipelines',

View File

@@ -38,6 +38,14 @@ const zhHans = {
deleteError: '删除失败:',
addRound: '添加回合',
},
notFound: {
title: '页面不存在',
description:
'您要查找的页面似乎不存在。请检查您输入的 URL 是否正确,或者返回首页。',
back: '上一级',
home: '返回主页',
help: '查看帮助文档',
},
models: {
title: '模型配置',
description: '配置和管理可在流水线中使用的模型',