Compare commits

...

383 Commits

Author SHA1 Message Date
Junyan Qin
aa7c08ee00 chore: release v4.2.1 2025-08-21 10:15:05 +08:00
Junyan Qin
b98de29b07 feat: add shengsuanyun requester 2025-08-20 23:33:35 +08:00
fdc310
c7c2eb4518 fix:in the gmini tool_calls no id The resulting call failure (#1622)
* fix:in the dify agent llm return message can not joint

* fix:in the gmini tool_calls no id The resulting call failure
2025-08-20 22:39:16 +08:00
Ljzd_PRO
37fa318258 fix: update invoke_embedding to return only embeddings from client.embed (#1619) 2025-08-20 10:25:33 +08:00
fdc310
ff7bebb782 fix:in the dify agent llm return message can not joint (#1617) 2025-08-19 23:23:00 +08:00
Junyan Qin
30bb26f898 doc(README): streaming output 2025-08-18 21:21:50 +08:00
Junyan Qin
9c1f4e1690 chore: release v4.2.0 2025-08-18 18:43:20 +08:00
dependabot[bot]
865ee2ca01 Merge pull request #1612 from langbot-app/dependabot/npm_and_yarn/web/form-data-4.0.4
chore(deps): bump form-data from 4.0.2 to 4.0.4 in /web
2025-08-18 16:10:56 +08:00
Junyan Qin (Chin)
c2264080bd Merge pull request #1442 from langbot-app/feat/streaming
feat: streaming output
2025-08-17 23:36:30 +08:00
Dong_master
67b622d5a6 fix:Some adjustments to the return types 2025-08-17 23:34:19 +08:00
Dong_master
a534c02d75 fix:remove print 2025-08-17 23:34:01 +08:00
Junyan Qin
da890d3074 chore: remove fix.MD 2025-08-17 21:20:32 +08:00
Junyan Qin
3049aa7a96 feat: add migration for pipeline remove-think 2025-08-17 21:18:41 +08:00
Junyan Qin (Chin)
e66f674968 Merge branch 'master' into feat/streaming 2025-08-17 14:30:22 +08:00
Junyan Qin (Chin)
dd0e0abdc4 Merge pull request #1571 from fdc310/streaming_feature
feat:add streaming output and pipeline stream
2025-08-17 14:27:39 +08:00
Junyan Qin (Chin)
13f6396eb4 Merge pull request #1610 from langbot-app/devin/1755399221-add-password-change-feature
feat: add password change functionality
2025-08-17 14:25:24 +08:00
Junyan Qin
7bbaa4fcad feat: perf ui & complete i18n 2025-08-17 14:09:28 +08:00
Junyan Qin
e931d5eb88 chore: remove print 2025-08-17 13:52:40 +08:00
Junyan Qin
4bbfa2f1d7 fix: telegram adapter gracefully stop 2025-08-17 13:52:02 +08:00
Devin AI
dd30d08c68 feat: add password change functionality
- Add password change button to sidebar account menu
- Create PasswordChangeDialog component with shadcn UI components
- Implement backend API endpoint /api/v1/user/change-password
- Add form validation with current password verification
- Include internationalization support for Chinese and English
- Add proper error handling and success notifications

Co-Authored-By: Rock <rockchinq@gmail.com>
2025-08-17 03:03:36 +00:00
Dong_master
8ccda10045 fix: in the dashscopeapi.py workflow stream bug 2025-08-16 12:11:00 +08:00
Dong_master
46fbfbefea fix: in the dashscopeapi.py stream and non-stream remove_think logic 2025-08-16 02:13:45 +08:00
Dong_master
8f863cf530 fix: remove_think bug 2025-08-15 00:55:39 +08:00
Dong_master
2351193c51 fix: in the difysvapi.py add stream , and remove_think on chunk 2025-08-15 00:50:32 +08:00
Dong_master
8c87a47f5a fix: in the ollamachat.py func _closure add remove_think agr 2025-08-14 22:35:30 +08:00
Dong_master
b8b9a37825 fix: in the dify non-stream remove_think lgic 2025-08-14 22:32:22 +08:00
Dong_master
13dd6fcee3 fix: in the webchat non-stream not save resp_message in message_lists 2025-08-14 22:29:42 +08:00
Junyan Qin
29f0075bd8 perf: zh-Hant specs 2025-08-13 17:49:54 +08:00
Junyan Qin
8a96ffbcc0 chore: complete zh-Hant specs for top_k 2025-08-13 17:33:47 +08:00
Junyan Qin (Chin)
67f68d8101 Merge pull request #1606 from langbot-app/feat/topk_splitter
Feat/topk splitter
2025-08-13 17:31:11 +08:00
Junyan Qin
ad59d92cef perf: i18n 2025-08-13 17:28:00 +08:00
Dong_master
85f97860c5 fix: Fixed the errors in modelscopechatcmpl.py when in pseudo-non-streaming mode, regarding the display of main content and tool calls. 2025-08-13 01:55:06 +08:00
Dong_master
8fd21e76f2 fix: Only when messagechunk is present, will msg_sequence be assigned to the subsequent tool calls. 2025-08-13 00:00:10 +08:00
Dong_master
cc83ddbe21 fix: del print 2025-08-12 23:29:32 +08:00
Dong_master
99fcde1586 fix: in the MessageChunk add msg_sequence ,And obtain the usage in the adapter. 2025-08-12 23:20:41 +08:00
WangCham
eab08dfbf3 fix: format the code 2025-08-12 23:13:00 +08:00
Dong_master
dbf0200cca feat:add More attractive card templates 2025-08-12 22:36:42 +08:00
Junyan Qin
ac44f35299 chore: remove comments 2025-08-12 21:07:23 +08:00
Junyan Qin
d6a5fdd911 perf: complete sidebar menu 2025-08-12 21:02:40 +08:00
Dong_master
4668db716a fix: fix command reply_message error bug,del some print 2025-08-12 20:54:47 +08:00
Junyan Qin
f7cd6b76f2 feat: refactor account menu 2025-08-12 20:13:18 +08:00
Junyan Qin
b6d47187f5 perf: prettier 2025-08-12 19:39:41 +08:00
Junyan Qin
051fffd41e fix: stash 2025-08-12 19:18:49 +08:00
Junyan Qin
c5480078b3 perf: make prompt editor textarea 2025-08-12 11:30:42 +08:00
Dong_master
e744e9c4ef fix: in the localagent.py yield MessageChunk add agr tool_calls,and After calling the "tool_calls", the first returned body data will be concatenated. 2025-08-12 11:25:37 +08:00
Dong_master
9f22b8b585 fix: be adapter.py func reply_message_chunk agr message_id alter bot_message,and in pipelinemgr.py respback.py agr alter 2025-08-12 11:21:08 +08:00
Dong_master
27cee0a4e1 fix: in the adapter.py func reply_message_chunk agr message_id alter bot_message,and in dingtalk.py lark.py telegram.py webchat.py agr alter 2025-08-12 11:19:27 +08:00
Dong_master
6d35fc408c fix: some time in the anthropicmsgs.py mesg_dcit["content"] is str can not append 2025-08-12 11:15:17 +08:00
Dong_master
0607a0fa5c fix: in the modelscopechatcmpl.py stream tool_calls arguments bug, 2025-08-12 00:04:21 +08:00
Dong_master
ed57d2fafa del localagent.py print 2025-08-11 23:49:19 +08:00
Junyan Qin
39ef92676b doc: add back wechat 2025-08-11 23:38:41 +08:00
Dong_master
7301476228 fix:Because the message_id was popped out, it caused the issue where the tool couldn't find the message_id after being invoked. 2025-08-11 23:36:01 +08:00
WangCham
457cc3eecd fix: wrong definition of topk 2025-08-11 23:22:36 +08:00
Dong_master
a381069bcc fix:fix tool_result argument bug 2025-08-11 23:05:47 +08:00
WangCham
146c38e64c fix: wrong positions 2025-08-11 22:58:48 +08:00
Junyan Qin (Chin)
763c41729e Merge pull request #1605 from TwperBody/master
feat: dark mode supports for webui
2025-08-11 20:51:58 +08:00
Junyan Qin
0021efebd7 perf: minor fix 2025-08-11 20:50:39 +08:00
Junyan Qin
5f18a1b13a chore: prettier 2025-08-11 20:46:08 +08:00
Junyan Qin
0124448479 perf: card shadowbox 2025-08-11 20:41:57 +08:00
WangCham
e76bc80e51 Merge branch 'feat/topk_splitter' of github.com:RockChinQ/LangBot into feat/topk_splitter 2025-08-11 00:20:13 +08:00
WangCham
a27560e804 fix: page bug 2025-08-11 00:12:06 +08:00
Dong_master
46452de7b5 fix:The handling of the streaming tool calls has been fixed, but there are still bugs in the model's reply messages with thoughtfulness. 2025-08-10 23:14:57 +08:00
TwperBody
2aef139577 dark mode 2025-08-10 22:17:06 +08:00
Dong_master
03b11481ed fix:fix remove_think logic, and end<think> fix </think> 2025-08-10 00:28:55 +08:00
Dong_master
8c5cb71812 fix:del the chatcmpl.py useless logic,and in the modelscopechatcmpl.py Non-streaming add and del <think> logic,and fix the ppiochatcmpl.py stream logic and the giteeaichatcmpl.py inherit ppiochatcmpl.py 2025-08-10 00:16:13 +08:00
Dong_master
7c59bc1ce5 feat:add anthropic stream ouput 2025-08-10 00:09:19 +08:00
Dong_master
eede354d3b fix:chatcmpl.py del content <think>,in the ppiochatcmpl.py and modelsopechatcmpl.py fun _closure_stream stream logic 2025-08-09 02:46:13 +08:00
Junyan Qin
eb7b5dcc25 chore: rename zh_Hans label of deepseek requester 2025-08-08 17:31:24 +08:00
WangCham
47e9ce96fc feat: add topk 2025-08-07 18:10:33 +08:00
WangCham
4e95bc542c fix: kb form 2025-08-07 18:10:33 +08:00
WangCham
e4f321ea7a feat: add description for topk 2025-08-07 18:10:33 +08:00
WangCham
246eb71b75 feat: add topk 2025-08-07 18:10:33 +08:00
Junyan Qin
261f50b8ec feat: refactor with cursor max mode claude 4.1 opus 2025-08-07 15:47:57 +08:00
Junyan Qin
9736d0708a fix: missing deps 2025-08-07 10:15:09 +08:00
Junyan Qin
02dbe80d2f perf: model testing 2025-08-07 10:01:04 +08:00
Dong_master
0f239ace17 Merge remote-tracking branch 'origin/streaming_feature' into streaming_feature 2025-08-06 23:02:35 +08:00
Dong_master
3a82ae8da5 fix: the bug in the "remove_think" function. 2025-08-06 23:00:57 +08:00
Junyan Qin
c33c9eaab0 chore: remove remove_think param in trigger.yaml 2025-08-06 15:45:35 +08:00
Junyan Qin
87f626f3cc doc(README): add HelloGitHub badge 2025-08-05 17:40:27 +08:00
Dong_master
e88302f1b4 fix:The handling logic of remove_think in the connector and Temporarily blocked the processing of streaming tool calls in the runner. 2025-08-05 04:24:03 +08:00
Dong_master
5597dffaeb Merge remote-tracking branch 'origin/streaming_feature' into streaming_feature
# Conflicts:
#	pkg/api/http/controller/groups/pipelines/webchat.py
#	pkg/pipeline/process/handlers/chat.py
#	pkg/platform/sources/aiocqhttp.py
#	pkg/platform/sources/dingtalk.py
#	pkg/platform/sources/discord.py
#	pkg/platform/sources/lark.py
#	pkg/platform/sources/telegram.py
#	pkg/platform/sources/wechatpad.py
#	pkg/provider/modelmgr/requester.py
#	pkg/provider/modelmgr/requesters/chatcmpl.py
#	pkg/provider/modelmgr/requesters/deepseekchatcmpl.py
#	pkg/provider/modelmgr/requesters/giteeaichatcmpl.py
#	pkg/provider/modelmgr/requesters/modelscopechatcmpl.py
#	pkg/provider/modelmgr/requesters/ppiochatcmpl.py
#	pkg/provider/runners/dashscopeapi.py
#	pkg/provider/runners/difysvapi.py
#	pkg/provider/runners/localagent.py
2025-08-04 23:17:36 +08:00
Junyan Qin
7f25d61531 fix: minor fix 2025-08-04 23:00:54 +08:00
Junyan Qin
15e524c6e6 perf: move remove-think to output tab 2025-08-04 19:26:19 +08:00
fdc
4a1d033ee9 fix: Reduce chunk returns in dify and Hundred Refining Runner to every 8 chunks 2025-08-04 19:26:19 +08:00
fdc
8adc88a8c0 fix:Modify the remove_think that directly retrieves the configuration file from the requester, retrieves it from the runner, and passes it to the required function 2025-08-04 19:26:18 +08:00
fdc
a62b38eda7 fix: In the reply_message_chunk of the adapter, the message is only streamed into the card or edited at the end of the 8th chunk return or streaming 2025-08-04 19:26:18 +08:00
Dong_master
fcef784180 fix: In the runner, every 8 tokens yield 2025-08-04 19:26:18 +08:00
Junyan Qin
c3ed4ef6a1 feat: no longer use typewriter in debug dialog 2025-08-04 19:26:18 +08:00
Junyan Qin
b9f768af25 perf: minor fixes 2025-08-04 19:26:18 +08:00
Junyan Qin
47ff883fc7 perf: ruff format & remove stream params in requester 2025-08-04 19:26:18 +08:00
Dong_master
68906c43ff feat: add webchat Word-by-word output
fix:webchat on message stream bug
2025-08-04 19:26:18 +08:00
Dong_master
c6deed4e6e feat: webchat stream is ok 2025-08-04 19:26:18 +08:00
Dong_master
b45cc59322 fix:webchat stream judge bug and frontend bug 2025-08-04 19:26:17 +08:00
fdc
c33a96823b fix: frontend bug 2025-08-04 19:26:17 +08:00
fdc
d3ab16761d fix:lsome bug 2025-08-04 19:26:17 +08:00
fdc
70f23f24b0 fix: is_stream_output_supperted in webchat return 2025-08-04 19:26:17 +08:00
fdc
00a8410c94 feat:webchat frontend stream 2025-08-04 19:26:17 +08:00
fdc
2a17e89a99 feat: add webchat stream but only some 2025-08-04 19:26:17 +08:00
fdc
8fe0992c15 fix:in chat judge create_message_card telegram reply_message_chunk no message 2025-08-04 19:26:17 +08:00
Dong_master
a9776b7b53 fix:del some print ,and amend respback on stream judge ,and del in dingtalk this is_stream_output_supported() use 2025-08-04 19:26:16 +08:00
Dong_master
074d359c8e feat:add dashscopeapi stream
fix:dify 64chunk yield
2025-08-04 19:26:16 +08:00
Dong_master
7728b4262b fix:lark message_id and dingtalk incoming_message 2025-08-04 19:26:16 +08:00
Dong_master
4905b5a738 feat:add dingtalk stream
fix:adapter is_stream_output_supported bug
fix:stream message reply chunk in message_id
2025-08-04 19:26:16 +08:00
Dong_master
43a259a1ae feat:add dingtalk stream 2025-08-04 19:26:16 +08:00
Dong_master
cffe493db0 feat:add telegram stream 2025-08-04 19:26:16 +08:00
Dong_master
0042629bf0 feat:add ppio and openrouter llm stream,and ppio think in content remove_think.
fix: giteeai stream no remove_think content add char"<think>"
2025-08-04 19:26:16 +08:00
Dong_master
a7d638cc9a feat:add deepseek and modelscope llm stream,and giteeai think in content remove_think 2025-08-04 19:26:16 +08:00
Dong_master
f84a79bf74 perf:del dify stream in ai.yaml config.and enbale stream in lark.yaml.
fix:localagent remove_think bug
2025-08-04 19:26:15 +08:00
Dong_master
f5a0cb9175 feat:add dify _agent_chat_message streaming 2025-08-04 19:26:15 +08:00
Dong_master
f9a5507029 fix:修复了因为迭代数据只推入resq_messages和resq_message_chain导致缓存到内存中的数据和写入log中的数据量庞大,以及有思考的think处理
feat:增加带有深度思考模型的think的去think操作
feat:dify中聊天机器人,chatflow对流式的支持
2025-08-04 19:26:15 +08:00
Dong_master
5ce32d2f04 fix:修复了因为迭代数据只推入resq_messages和resq_message_chain导致缓存到内存中的数据和写入log中的数据量庞大,以及带有深度思考模型的think增加 2025-08-04 19:26:15 +08:00
Dong_master
4908996cac 流式基本流程已通过修改了yield和return的冲突导致的问题 2025-08-04 19:26:15 +08:00
fdc
ee545a163f 增加了飞书中的流式但是好像还有问题 2025-08-04 19:26:15 +08:00
fdc
6e0e5802cc fix:修改手误message_id写进reply_message中 2025-08-04 19:26:15 +08:00
fdc
0d53843230 chat中的流式修改 2025-08-04 19:26:14 +08:00
fdc
b65670cd1a feat: 实现流式消息处理支持 2025-08-04 19:26:14 +08:00
zejiewang
ba4b5255a2 feat:support dify message streaming output (#1437)
* fix:lark adapter listeners init problem

* feat:support dify streaming mode

* feat:remove some log

* fix(bot form): field desc missing

* fix: not compatible with chatflow

---------

Co-authored-by: wangzejie <wangzejie@meicai.cn>
Co-authored-by: Junyan Qin <rockchinq@gmail.com>
2025-08-04 18:45:52 +08:00
Junyan Qin (Chin)
d60af2b451 fix(pipeline dialog): config reset between tabs switching (#1597) 2025-08-04 00:05:55 +08:00
Dong_master
44ac8b2b63 fix: In the runner, every 8 tokens yield 2025-08-03 23:23:51 +08:00
Junyan Qin
b70001c579 chore: release v4.1.2 2025-08-03 22:52:47 +08:00
Junyan Qin (Chin)
4a8f5516f6 feat: add new api requester (#1596) 2025-08-03 22:30:52 +08:00
Junyan Qin
48d11540ae feat: no longer use typewriter in debug dialog 2025-08-03 17:18:44 +08:00
Junyan Qin
84129e3339 perf: minor fixes 2025-08-03 15:30:11 +08:00
Junyan Qin
377d455ec1 perf: ruff format & remove stream params in requester 2025-08-03 13:08:51 +08:00
Dong_master
52280d7a05 feat: add webchat Word-by-word output
fix:webchat on message stream bug
2025-08-02 01:42:22 +08:00
Dong_master
0ce81a2df2 feat: webchat stream is ok 2025-08-01 11:33:16 +08:00
Dong_master
d9a2bb9a06 fix:webchat stream judge bug and frontend bug 2025-07-31 14:49:12 +08:00
fdc
cb88da7f02 fix: frontend bug 2025-07-31 10:34:36 +08:00
fdc
5560a4f52d fix:lsome bug 2025-07-31 10:28:43 +08:00
fdc
e4d951b174 fix: is_stream_output_supperted in webchat return 2025-07-31 10:01:47 +08:00
fdc
6e08bf71c9 feat:webchat frontend stream 2025-07-31 09:51:25 +08:00
fdc
daaf4b54ef feat: add webchat stream but only some 2025-07-30 17:06:14 +08:00
fdc
3291266f5d fix:in chat judge create_message_card telegram reply_message_chunk no message 2025-07-30 15:21:59 +08:00
Dong_master
307f6acd8c fix:del some print ,and amend respback on stream judge ,and del in dingtalk this is_stream_output_supported() use 2025-07-29 23:09:02 +08:00
Junyan Qin
f1ac9c77e6 doc: update README_TW 2025-07-28 15:50:00 +08:00
Junyan Qin
b434a4e3d7 doc: add README_TW 2025-07-28 15:47:50 +08:00
Junyan Qin
2f209cd59f chore(i18n): add zh-Hant 2025-07-28 15:11:41 +08:00
Junyan Qin
0f585fd5ef fix(moonshot): make api.moonshot.ai the default api base url 2025-07-26 22:23:33 +08:00
Junyan Qin
a152dece9a chore: switch to pnpm 2025-07-26 19:45:38 +08:00
Junyan Qin
d3b31f7027 chore: release v4.1.1 2025-07-26 19:28:34 +08:00
How-Sean Xin
c00f05fca4 Add GitHub link redirection for front-end plugin cards (#1579)
* Update package.json

* Update PluginMarketComponent.tsx

* Update PluginMarketComponent.tsx

* Update package.json

* Update PluginCardComponent.tsx

* perf: no display github button when plugin has no github url

---------

Co-authored-by: Junyan Qin <rockchinq@gmail.com>
2025-07-26 19:22:00 +08:00
Junyan Qin
92c3a86356 feat: add qhaigc 2025-07-24 22:42:26 +08:00
Junyan Qin
341fdc409d perf: embedding model ui 2025-07-24 22:29:25 +08:00
Junyan Qin
ebd542f592 feat: 302.AI embeddings 2025-07-24 22:05:15 +08:00
Junyan Qin
194b2d9814 feat: supports more embedding providers 2025-07-24 22:03:20 +08:00
Junyan Qin
7aed5cf1ed feat: ollama embeddings models 2025-07-24 10:36:32 +08:00
Junyan Qin
abc88c4979 doc: update README 2025-07-23 18:53:15 +08:00
WangCham
3fa38f71f1 feat: add topk 2025-07-23 17:29:36 +08:00
WangCham
d651d956d6 Merge branch 'master' into feat/topk_splitter 2025-07-23 16:37:27 +08:00
gaord
6754666845 feat(wechatpad): 添加对@所有人的支持并统一处理消息派发 (#1588)
在消息转换器中添加对AtAll组件的支持,将@所有人转换为特定格式。同时在消息派发时统一处理@所有人的情况,确保通知能正确发送。
2025-07-23 15:22:04 +08:00
Junyan Qin
08e6f46b19 fix(deps): react-focus-scope pkg bug 2025-07-22 11:05:16 +08:00
Dong_master
8f8c8ff367 feat:add dashscopeapi stream
fix:dify 64chunk yield
2025-07-21 18:45:45 +08:00
Dong_master
63ec2a8c34 fix:lark message_id and dingtalk incoming_message 2025-07-21 17:28:11 +08:00
Dong_master
f58c8497c3 feat:add dingtalk stream
fix:adapter is_stream_output_supported bug
fix:stream message reply chunk in message_id
2025-07-20 23:53:20 +08:00
Junyan Qin
1497fdae56 doc(README): adjust structure 2025-07-20 22:10:32 +08:00
Junyan Qin
10a3cb40e1 perf(retrieve): ui 2025-07-20 17:57:33 +08:00
devin-ai-integration[bot]
dd1ec15a39 feat: add knowledge base retrieve test tab with Card-based UI (#1583)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin>, u79E6u9A8Fu8A00 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
2025-07-20 17:56:46 +08:00
devin-ai-integration[bot]
ea51cec57e feat: add pipeline sorting functionality with three sort options (#1582)
* feat: add pipeline sorting functionality with three sort options

Co-Authored-By: Junyan Qin <Chin>, u79E6u9A8Fu8A00 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>

* perf: ui

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin>, u79E6u9A8Fu8A00 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
2025-07-20 17:23:30 +08:00
Junyan Qin
28ce986a8c chore: release v4.1.0 2025-07-20 12:32:06 +08:00
Junyan Qin
489b145606 doc: update README 2025-07-20 12:30:41 +08:00
Junyan Qin (Chin)
5e92bffaa6 Merge pull request #1581 from langbot-app/RockChinQ-patch-1
Update README.md
2025-07-19 23:09:53 +08:00
Junyan Qin (Chin)
277d1b0e30 feat: rag engine (#1492)
* feat: add embeddings model management (#1461)

* feat: add embeddings model management backend support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: add embeddings model management frontend support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* chore: revert HttpClient URL to production setting

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* refactor: integrate embeddings models into models page with tabs

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* perf: move files

* perf: remove `s`

* feat: allow requester to declare supported types in manifest

* feat(embedding): delete dimension and encoding format

* feat: add extra_args for embedding moels

* perf: i18n ref

* fix: linter err

* fix: lint err

* fix: linter err

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: add knowledge page

* feat: add api for uploading files

* kb

* delete ap

* feat: add functions

* fix: modify rag database

* feat: add embeddings model management (#1461)

* feat: add embeddings model management backend support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: add embeddings model management frontend support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* chore: revert HttpClient URL to production setting

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* refactor: integrate embeddings models into models page with tabs

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* perf: move files

* perf: remove `s`

* feat: allow requester to declare supported types in manifest

* feat(embedding): delete dimension and encoding format

* feat: add extra_args for embedding moels

* perf: i18n ref

* fix: linter err

* fix: lint err

* fix: linter err

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: add knowledge page

* feat: add api for uploading files

* feat: add sidebar for rag and related i18n

* feat: add knowledge base page

* feat: basic entities of kb

* feat: complete support_type for 302ai and compshare requester

* perf: format

* perf: ruff check --fix

* feat: basic definition

* feat: rag fe framework

* perf: en comments

* feat: modify the rag.py

* perf: definitions

* fix: success method bad params

* fix: bugs

* fix: bug

* feat: kb dialog action

* fix: create knwoledge base issue

* fix: kb get api format

* fix: kb get api not contains model uuid

* fix: api bug

* fix: the fucking logger

* feat(fe): component for available apis

* fix: embbeding and chunking

* fix: ensure File.status is set correctly after storing data to avoid null values

* fix: add functions for deleting files

* feat(fe): file uploading

* perf: adjust ui

* fix: file be deleted twice

* feat(fe): complete kb ui

* fix: ui bugs

* fix: no longer require Query for invoking embedding

* feat: add embedder

* fix: delete embedding models file

* chore: stash

* chore: stash

* feat(rag): make embedding and retrieving available

* feat(rag): all APIs ok

* fix: delete utils

* feat: rag pipeline backend

* feat: combine kb with pipeline

* fix: .md file parse failed

* perf: debug output

* feat: add functions for frontend of kb

* perf(rag): ui and related apis

* perf(rag): use badge show doc status

* perf: open kb detail dialog after creating

* fix: linter error

* deps: remove sentence-transformers

* perf: description of default pipeline

* feat: add html and epub

* chore: no longer supports epub

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: WangCham <651122857@qq.com>
2025-07-19 22:06:11 +08:00
Junyan Qin
13f4ed8d2c chore: no longer supports epub 2025-07-19 21:56:50 +08:00
WangCham
91cb5ca36c feat: add html and epub 2025-07-19 19:57:57 +08:00
TwperBody
c34d54a6cb Fixed a bug where some Windows systems failed to recognize spaces. (#1577)
* Update package.json

* Update PluginMarketComponent.tsx

* Update PluginMarketComponent.tsx
2025-07-19 16:48:15 +08:00
TwperBody
2d1737da1f Optimize plugin display (#1578)
* Update package.json

* Update PluginMarketComponent.tsx

* Update PluginMarketComponent.tsx

* Update PluginMarketComponent.tsx

* Update package.json
2025-07-19 16:47:34 +08:00
Dong_master
adb0bf2473 feat:add dingtalk stream 2025-07-19 01:05:44 +08:00
Junyan Qin
a1b8b9d47b perf: description of default pipeline 2025-07-18 18:57:42 +08:00
Junyan Qin
8df14bf9d9 deps: remove sentence-transformers 2025-07-18 18:46:07 +08:00
Junyan Qin
c98d265a1e fix: linter error 2025-07-18 17:52:24 +08:00
Junyan Qin
4e6782a6b7 perf: open kb detail dialog after creating 2025-07-18 16:52:54 +08:00
Junyan Qin
5541e9e6d0 perf(rag): use badge show doc status 2025-07-18 16:38:55 +08:00
gaord
878ab0ef6b fix(wechatpad): @所有人的情况下,修复@机器人消息未被正确解析的问题 (#1575) 2025-07-18 12:52:30 +08:00
Junyan Qin
b61bd36b14 perf(rag): ui and related apis 2025-07-18 00:45:38 +08:00
Junyan Qin (Chin)
bb672d8f46 Merge branch 'master' into feat/rag 2025-07-18 00:45:24 +08:00
WangCham
ba1a26543b Merge branch 'feat/rag' of github.com:RockChinQ/LangBot into feat/rag 2025-07-17 23:57:52 +08:00
WangCham
cb868ee7b2 feat: add functions for frontend of kb 2025-07-17 23:52:46 +08:00
Junyan Qin
5dd5cb12ad perf: debug output 2025-07-17 23:34:35 +08:00
Junyan Qin
2dfa83ff22 fix: .md file parse failed 2025-07-17 23:22:20 +08:00
Junyan Qin
27bb4e1253 feat: combine kb with pipeline 2025-07-17 23:15:13 +08:00
WangCham
45afdbdfbb feat: rag pipeline backend 2025-07-17 15:05:11 +08:00
Dong_master
11e52a3ade feat:add telegram stream 2025-07-17 14:29:30 +08:00
WangCham
4cbbe9e000 fix: delete utils 2025-07-16 23:25:12 +08:00
WangCham
e986a0acaf fix: kb form 2025-07-16 22:50:17 +08:00
Junyan Qin
333ec346ef feat(rag): all APIs ok 2025-07-16 22:15:03 +08:00
Junyan Qin
2f2db4d445 feat(rag): make embedding and retrieving available 2025-07-16 21:17:18 +08:00
WangCham
e31883547d feat: add description for topk 2025-07-16 18:15:27 +08:00
WangCham
88c0066b06 feat: add topk 2025-07-16 17:20:13 +08:00
Junyan Qin
fdc79b8d77 chore: release v4.0.9 2025-07-16 11:39:15 +08:00
Junyan Qin
f244795e57 fix: rename to '302.AI' 2025-07-16 11:36:57 +08:00
Junyan Qin
5a2aa19d0f feat(aiocqhttp): no longer download files for now 2025-07-16 11:36:01 +08:00
Junyan Qin
f731115805 chore: stash 2025-07-16 11:31:55 +08:00
Junyan Qin
67bc065ccd chore: stash 2025-07-15 22:09:10 +08:00
Dong_master
d15df3338f feat:add ppio and openrouter llm stream,and ppio think in content remove_think.
fix: giteeai stream no remove_think content add char"<think>"
2025-07-15 00:50:42 +08:00
Dong_master
c74cf38e9f feat:add deepseek and modelscope llm stream,and giteeai think in content remove_think 2025-07-14 23:53:55 +08:00
Junyan Qin
81eb92646f doc: perf README_JP 2025-07-14 11:22:59 +08:00
Junyan Qin
019a9317e9 doc: perf README 2025-07-14 11:17:58 +08:00
Dong_master
0e68a922bd perf:del dify stream in ai.yaml config.and enbale stream in lark.yaml.
fix:localagent remove_think bug
2025-07-14 01:42:42 +08:00
Dong_master
4e1d81c9f8 feat:add dify _agent_chat_message streaming 2025-07-14 00:40:02 +08:00
WangCham
199164fc4b fix: delete embedding models file 2025-07-13 23:12:08 +08:00
WangCham
c9c26213df Merge branch 'feat/rag' of github.com:RockChinQ/LangBot into feat/rag 2025-07-13 23:09:41 +08:00
WangCham
b7c57104c4 feat: add embedder 2025-07-13 23:04:03 +08:00
Dong_master
0be08d8882 fix:修复了因为迭代数据只推入resq_messages和resq_message_chain导致缓存到内存中的数据和写入log中的数据量庞大,以及有思考的think处理
feat:增加带有深度思考模型的think的去think操作
feat:dify中聊天机器人,chatflow对流式的支持
2025-07-13 22:41:39 +08:00
TwperBody
858cfd8d5a Update package.json (#1570)
Compatible with the creation of environment variables in the Windows environment
2025-07-12 22:31:30 +08:00
Junyan Qin
cbe297dc59 fix: no longer require Query for invoking embedding 2025-07-12 21:23:19 +08:00
Junyan Qin
de76fed25a fix: ui bugs 2025-07-12 18:12:53 +08:00
Dong_master
301509b1db fix:修复了因为迭代数据只推入resq_messages和resq_message_chain导致缓存到内存中的数据和写入log中的数据量庞大,以及带有深度思考模型的think增加 2025-07-12 18:09:24 +08:00
Junyan Qin
a10e61735d feat(fe): complete kb ui 2025-07-12 18:00:54 +08:00
Junyan Qin
1ef0193028 fix: file be deleted twice 2025-07-12 17:47:53 +08:00
Junyan Qin
1e85d02ae4 perf: adjust ui 2025-07-12 17:29:39 +08:00
Junyan Qin
d78a329aa9 feat(fe): file uploading 2025-07-12 17:15:07 +08:00
Junyan Qin
bfdf238db5 chore: use new social image 2025-07-12 11:44:08 +08:00
WangCham
234b61e2f8 fix: add functions for deleting files 2025-07-12 01:37:44 +08:00
WangCham
9f43097361 fix: ensure File.status is set correctly after storing data to avoid null values 2025-07-12 01:21:02 +08:00
WangCham
f395cac893 fix: embbeding and chunking 2025-07-12 01:07:49 +08:00
Junyan Qin
fe122281fd feat(fe): component for available apis 2025-07-11 21:40:42 +08:00
Junyan Qin
6d788cadbc fix: the fucking logger 2025-07-11 21:37:31 +08:00
Junyan Qin
a79a22a74d fix: api bug 2025-07-11 21:30:47 +08:00
Junyan Qin
2ed3b68790 fix: kb get api not contains model uuid 2025-07-11 20:58:51 +08:00
Junyan Qin
bd9331ce62 fix: kb get api format 2025-07-11 20:57:09 +08:00
WangCham
14c161b733 fix: create knwoledge base issue 2025-07-11 18:14:03 +08:00
Junyan Qin
815cdf8b4a feat: kb dialog action 2025-07-11 17:22:43 +08:00
Junyan Qin
7d5503dab2 fix: bug 2025-07-11 16:49:55 +08:00
Junyan Qin
9ba1ad5bd3 fix: bugs 2025-07-11 16:38:08 +08:00
Junyan Qin
367d04d0f0 fix: success method bad params 2025-07-11 11:28:43 +08:00
Junyan Qin
75c3ddde19 perf: definitions 2025-07-10 16:45:59 +08:00
Junyan Qin
c6e77e42be chore: switch some comments to en 2025-07-10 11:09:33 +08:00
Junyan Qin
4d0a39eb65 chore: switch comments to en 2025-07-10 11:01:16 +08:00
WangCham
ac03a2dceb feat: modify the rag.py 2025-07-09 22:09:46 +08:00
Junyan Qin
56248c350f chore: repo transferred 2025-07-07 19:00:55 +08:00
gaord
244aaf6e20 feat: 聊天的@用户id内容需要保留 (#1564)
* converters could use the application logger

* keep @targets in message for some plugins may need it to their functionality

* fix:form wxid in config

fix:传参问题,可以直接从config中拿到wxid

---------

Co-authored-by: fdc310 <82008029+fdc310@users.noreply.github.com>
2025-07-07 10:28:12 +08:00
Junyan Qin
cd25340826 perf: en comments 2025-07-06 16:08:02 +08:00
Junyan Qin
ebd8e014c6 feat: rag fe framework 2025-07-06 15:52:53 +08:00
Junyan Qin
a0b7d759ac chore: release v4.0.8.1 2025-07-06 10:46:32 +08:00
Junyan Qin
09884d3152 revert: 0203faa 2025-07-06 10:34:24 +08:00
Junyan Qin
bef0d73e83 feat: basic definition 2025-07-06 10:25:28 +08:00
Junyan Qin
8d28ace252 perf: ruff check --fix 2025-07-05 21:56:54 +08:00
Junyan Qin
39c062f73e perf: format 2025-07-05 21:56:17 +08:00
Junyan Qin
0e5c9e19e1 feat: complete support_type for 302ai and compshare requester 2025-07-05 21:03:14 +08:00
Matthew_Astral
01f2ef5694 feat: new discord adapter (#1563) 2025-07-05 20:51:04 +08:00
Junyan Qin
c5b62b6ba3 Merge remote-tracking branch 'wangcham/feat/rag' into feat/rag 2025-07-05 20:16:37 +08:00
Junyan Qin
bbf583ddb5 feat: basic entities of kb 2025-07-05 20:07:27 +08:00
Junyan Qin
22ef1a399e feat: add knowledge base page 2025-07-05 20:07:27 +08:00
Junyan Qin
0733f8878f feat: add sidebar for rag and related i18n 2025-07-05 20:07:27 +08:00
Junyan Qin
f36a61dbb2 feat: add api for uploading files 2025-07-05 20:07:15 +08:00
Junyan Qin
6d8936bd74 feat: add knowledge page 2025-07-05 20:07:15 +08:00
devin-ai-integration[bot]
d2b93b3296 feat: add embeddings model management (#1461)
* feat: add embeddings model management backend support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: add embeddings model management frontend support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* chore: revert HttpClient URL to production setting

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* refactor: integrate embeddings models into models page with tabs

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* perf: move files

* perf: remove `s`

* feat: allow requester to declare supported types in manifest

* feat(embedding): delete dimension and encoding format

* feat: add extra_args for embedding moels

* perf: i18n ref

* fix: linter err

* fix: lint err

* fix: linter err

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-07-05 20:07:15 +08:00
WangCham
552fee9bac fix: modify rag database 2025-07-05 18:58:17 +08:00
WangCham
34fe8b324d feat: add functions 2025-07-05 18:58:16 +08:00
WangCham
c4671fbf1c delete ap 2025-07-05 18:58:16 +08:00
WangCham
4bcc06c955 kb 2025-07-05 18:58:16 +08:00
Junyan Qin
348f6d9eaa feat: add api for uploading files 2025-07-05 18:57:24 +08:00
Junyan Qin
157ffdc34c feat: add knowledge page 2025-07-05 18:57:24 +08:00
devin-ai-integration[bot]
c81d5a1a49 feat: add embeddings model management (#1461)
* feat: add embeddings model management backend support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: add embeddings model management frontend support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* chore: revert HttpClient URL to production setting

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* refactor: integrate embeddings models into models page with tabs

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* perf: move files

* perf: remove `s`

* feat: allow requester to declare supported types in manifest

* feat(embedding): delete dimension and encoding format

* feat: add extra_args for embedding moels

* perf: i18n ref

* fix: linter err

* fix: lint err

* fix: linter err

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-07-05 18:57:23 +08:00
Junyan Qin (Chin)
a01706d163 Feat/reset password (#1566)
* feat: reset password with recovery key

* perf: formatting and multi language
2025-07-05 17:36:35 +08:00
Junyan Qin
a8d03c98dc doc: replace comshare link 2025-07-04 11:37:31 +08:00
Dong_master
68cdd163d3 流式基本流程已通过修改了yield和return的冲突导致的问题 2025-07-04 03:26:44 +08:00
fdc
4005a8a3e2 增加了飞书中的流式但是好像还有问题 2025-07-03 22:58:17 +08:00
Junyan Qin
3f0153ea4d doc: fix incorrect 302.AI name 2025-07-03 17:26:17 +08:00
Junyan Qin
60b50a35f1 chore: release v4.0.8 2025-07-03 15:07:19 +08:00
Junyan Qin (Chin)
abd02f04af Feat/compshare requester (#1561)
* feat: add compshare requester

* doc: add compshare to README
2025-07-03 15:04:02 +08:00
fdc
542409d48d Merge branch 'feat/streaming' of github.com:fdc310/LangBot into streaming_feature 2025-07-02 14:09:01 +08:00
zejiewang
3c6e858c35 feat:support dify message streaming output (#1437)
* fix:lark adapter listeners init problem

* feat:support dify streaming mode

* feat:remove some log

* fix(bot form): field desc missing

* fix: not compatible with chatflow

---------

Co-authored-by: wangzejie <wangzejie@meicai.cn>
Co-authored-by: Junyan Qin <rockchinq@gmail.com>
2025-07-02 11:07:31 +08:00
fdc
8670ae82a3 fix:修改手误message_id写进reply_message中 2025-07-02 10:49:50 +08:00
Matthew_Astral
14411a8af6 Add Discord platform adapter implementation (#1560)
- Implement DiscordMessageConverter for message conversion
- Support image handling from base64, URL, and file paths
- Add DiscordEventConverter for event conversion
- Implement DiscordAdapter for Discord bot integration
- Support DM and TextChannel message handling
2025-07-02 09:48:49 +08:00
fdc
48c9d66ab8 chat中的流式修改 2025-07-01 18:03:05 +08:00
Junyan Qin
896fef8cce perf: make launch notes show async 2025-06-30 21:34:02 +08:00
Junyan Qin
89c1972abe perf: skip broken models and bots in bootstrap 2025-06-30 21:29:38 +08:00
Junyan Qin
1627d04958 fix: bad import 2025-06-30 21:13:14 +08:00
Junyan Qin (Chin)
c959c99e45 Feat/302 ai (#1558)
* feat: add 302.AI requester

* doc: add 302.AI to README
2025-06-30 21:05:32 +08:00
fdc
0eac9135c0 feat: 实现流式消息处理支持 2025-06-30 17:58:18 +08:00
Junyan Qin
0203faa8c1 fix: dingtalk adapter initializer blocks boot (#1544) 2025-06-28 22:06:12 +08:00
Junyan Qin (Chin)
35f76cb7ae Perf/combine entity dialogs (#1555)
* feat: combine bot settings and bot log dialogs

* perf: dialog style when creating bot

* perf: bot creation dialog

* feat: combine pipeline dialogs

* perf: ui

* perf: move buttons

* perf: ui layout in pipeline detail dialog

* perf: remove debug button from pipeline card

* perf: open pipeline dialog after creating

* perf: placeholder in send input

* perf: no close dialog when save done

* fix: linter errors
2025-06-28 21:50:51 +08:00
fdc310
c34232a26c fix: add wechatpad image (#1551)
* add wechatpad image

* add wechatpad image

---------

Co-authored-by: fdc <you@example.com>
2025-06-27 15:41:21 +08:00
简律纯
b43dd95dc6 chore(python): Delete .python-version (#1549) 2025-06-25 22:47:02 +08:00
Junyan Qin
5331ba83d7 chore: update description of lark bot name field 2025-06-25 10:57:44 +08:00
fdc310
a2038b86f1 feat:add onebotv11 face send and accept but some face no name. (#1543)
* feat:add onebotv11 face send and accept but some face no name.

* add face annotation

* add face_code_dict

* add some face in image can't download,so pass on face

* fix:Pass the face_id to face
2025-06-19 10:38:02 +08:00
Junyan Qin
eb066f3485 revert: 3cbc823 2025-06-18 15:16:55 +08:00
Junyan Qin
bf98b82cf2 chore: release v4.0.7 2025-06-18 13:10:20 +08:00
Junyan Qin (Chin)
edd70b943d Update bug-report_en.yml 2025-06-18 09:48:42 +08:00
Junyan Qin
3cbc823085 doc: make en README as default 2025-06-17 22:51:51 +08:00
Sheldon.li
48becf2c51 refactor(ContentFilterStage): Add logic for handling empty messages (#1525)
-In the ContentFilterStage, logic for handling empty messages has been added to ensure that the pipeline continues to process even when the message is empty.
- This change enhances the robustness of content filtering, preventing potential issues caused by empty messages.
- This optimization was implemented to address the issue where, when someone is @ in a group chat and a message is sent without any content, the Source type messages in the message chain are lost.
2025-06-17 22:12:55 +08:00
devin-ai-integration[bot]
56c686cd5a feat: add Japanese (ja-JP) language support (#1537)
* feat: add Japanese (ja-JP) language support

- Add comprehensive Japanese translation file (ja-JP.ts)
- Update i18n configuration to include Japanese locale
- Add Japanese language option to login and register page dropdowns
- Implement Japanese language detection and switching logic
- Maintain fallback to en-US for missing translations in flexible components

Co-Authored-By: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>

* perf: ui for ja-JP

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
2025-06-16 21:30:57 +08:00
Junyan Qin (Chin)
208273c0dd Update README.md 2025-06-16 21:01:11 +08:00
fdc310
2ff7ca3025 feat:add file url and add onebotv11(napcat) send file and seve file in local. (#1533)
* feat:add file url and add onebotv11(napcat) send file and seve file in local.

* del print
2025-06-15 17:22:35 +08:00
fdc310
61a2361730 feat:add new messagetyps WeChatFile and add wechat file is accepted and transmitted in base64 format. (#1531) 2025-06-15 17:17:08 +08:00
Junyan Qin
f80f997a89 chore: update version field in pyproject.toml 2025-06-11 10:24:18 +08:00
Junyan Qin
18529a42c1 chore: release v4.0.6 2025-06-11 10:23:46 +08:00
Junyan Qin (Chin)
3e707b4b6e feat: reset all associated session after bot and pipeline modified (#1517) 2025-06-09 21:50:08 +08:00
Junyan Qin
62f0a938a8 chore: remove legacy test in fe 2025-06-09 17:56:37 +08:00
Junyan Qin
ad3a163d82 fix: ruff linter error in libs 2025-06-09 17:56:21 +08:00
Junyan Qin
f5a4503610 perf: add text comment on bot log button 2025-06-09 15:27:17 +08:00
Junyan Qin
ec012cf5ed doc: update README 2025-06-09 10:20:11 +08:00
Junyan Qin
d70eceb72c fix(DebugDialog): \n not supported 2025-06-08 21:41:44 +08:00
devin-ai-integration[bot]
f271608114 feat: add dynamic base URL configuration using environment variables (#1511)
- Replace hardcoded base URL in HttpClient.ts with environment variable support
- Add NEXT_PUBLIC_API_BASE_URL environment variable for dynamic configuration
- Add dev:local script for development with localhost:5300 backend
- Development: uses localhost:5300, Production: uses / (relative path)
- Eliminates need for manual code changes when switching environments

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
2025-06-08 17:44:40 +08:00
Junyan Qin
793f0a9c10 fix: base url 2025-06-08 17:34:32 +08:00
devin-ai-integration[bot]
4f2ec195fc feat: add WebChat adapter for pipeline debugging (#1510)
* feat: add WebChat adapter for pipeline debugging

- Create WebChatAdapter for handling debug messages in pipeline testing
- Add HTTP API endpoints for debug message sending and retrieval
- Implement frontend debug dialog with session switching (private/group chat)
- Add Chinese i18n translations for debug interface
- Auto-create default WebChat bot during database initialization
- Support fixed session IDs: webchatperson and webchatgroup for testing

Co-Authored-By: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>

* perf: ui for webchat

* feat: complete webchat backend

* feat: core chat apis

* perf: button style in pipeline card

* perf: log btn in bot card

* perf: webchat entities definition

* fix: bugs

* perf: web chat

* perf: dialog styles

* perf: styles

* perf: styles

* fix: group invalid in webchat

* perf: simulate real im message

* perf: group timeout toast

* feat(webchat): add supports for mentioning bot in group

* perf(webchat): at component styles

* perf: at badge display in message

* fix: linter errors

* fix: webchat was listed on adapter list

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
2025-06-08 15:34:26 +08:00
Junyan Qin (Chin)
e6bc009414 feat: add i18n support for initialization page and fix plugin loading text (#1505)
* feat: add i18n support for initialization page and fix plugin loading text

- Add language selector to register/initialization page with Chinese and English options
- Add register section translations to both zh-Hans.ts and en-US.ts
- Replace hardcoded Chinese texts in register page with i18n translation calls
- Fix hardcoded '加载中...' text in plugin configuration dialog to use t('plugins.loading')
- Follow existing login page pattern for language selector implementation
- Maintain consistent UI/UX design with proper language switching functionality

Co-Authored-By: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>

* perf: language selecting logic

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
2025-06-06 21:29:36 +08:00
Junyan Qin
20dc8fb5ab perf: language selecting logic 2025-06-06 21:27:08 +08:00
Devin AI
9a71edfeb0 feat: add i18n support for initialization page and fix plugin loading text
- Add language selector to register/initialization page with Chinese and English options
- Add register section translations to both zh-Hans.ts and en-US.ts
- Replace hardcoded Chinese texts in register page with i18n translation calls
- Fix hardcoded '加载中...' text in plugin configuration dialog to use t('plugins.loading')
- Follow existing login page pattern for language selector implementation
- Maintain consistent UI/UX design with proper language switching functionality

Co-Authored-By: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
2025-06-06 10:50:31 +00:00
Guanchao Wang
fe3fd664af Fix/slack image (#1501)
* fix: dingtalk adapters couldn't handle images

* fix: slack adapter couldn't put the image in logger
2025-06-06 10:04:00 +08:00
Guanchao Wang
6402755ac6 fix: dingtalk adapters couldn't handle images (#1500) 2025-06-05 23:37:58 +08:00
Junyan Qin
ac8fe049de fix: uv removes it self 2025-06-05 11:12:04 +08:00
Junyan Qin
955b391253 chore: release v4.0.5 2025-06-03 16:28:55 +08:00
Junyan Qin
08c6672841 feat: allow skip plugin deps checking 2025-06-02 21:43:27 +08:00
Junyan Qin
8917050fae chore: add ppio icon 2025-05-31 20:00:18 +08:00
Junyan Qin
21daef46f7 chore: remove gemini related deps 2025-05-31 19:27:08 +08:00
Junyan Qin (Chin)
8ad60b5b64 refactor: gemini requester (#1490)
* refactor: use openai compatible api for gemini

* chore: remove codes
2025-05-31 13:11:53 +08:00
Junyan Qin
7e17c96c30 fix: linter error 2025-05-30 22:29:16 +08:00
whw174660897
f17b06767e Feature add n8 n (#1468)
* feat(n8n): 添加n8n工作流API支持

添加n8n工作流API作为新的运行器类型,支持通过webhook调用n8n工作流,并提供多种认证方式(Basic、JWT、Header)。新增N8nAuthFormComponent用于处理n8n认证表单联动,并更新相关配置文件和测试用例。

* chore: remove pip mirror url

* perf: simplify ret def of pipeline metadata

* feat(n8n): raise exc instead of ret as normal msg

* perf: add var `user_message_text`

* chore(n8n): migration and default config

* chore: required database version

---------

Co-authored-by: hengwei.wang <@>
Co-authored-by: Junyan Qin <rockchinq@gmail.com>
2025-05-30 22:23:57 +08:00
Junyan Qin
70a29fc623 chore: f u if you dont provide enough info in issue 2025-05-29 16:51:47 +08:00
Junyan Qin
239223be3f chore: release v4.0.4 2025-05-28 12:55:15 +08:00
Junyan Qin
b112cb320c fix: bad ability name in preproc check 2025-05-28 12:54:30 +08:00
Junyan Qin
5aaf2ba3ef fix: base url 2025-05-27 22:58:31 +08:00
Junyan Qin (Chin)
f1e9f46af1 feat: event log of bots (#1441)
* feat: basic arch of event log

* feat: complete event log framework

* fix: bad struct in bot log api

* feat: add event logging to all platform adapters

Co-Authored-By: wangcham233@gmail.com <651122857@qq.com>

* feat: add event logging to client classes

Co-Authored-By: wangcham233@gmail.com <651122857@qq.com>

* refactor: bot log getting api

* perf: logger for aiocqhttp and gewechat

* fix: add ignored logger in dingtalk

* fix: seq id bug in log getting

* feat: add logger in dingtalk,QQ official,Slack, wxoa

* feat: add logger for wecom

* feat: add logger for wecomcs

* perf(event logger): image processing

* 完成机器人日志的前端部分 (#1479)

* feat: webui  bot log framework done

* feat: bot log complete

* perf(bot-log): style

* chore: fix incompleted i18n

* feat: support message session copy

* fix: filter and badge text

* perf: styles

* feat: add bot toggle switch in bot card

* fix: linter errors

---------

Co-authored-by: Junyan Qin <rockchinq@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: wangcham233@gmail.com <651122857@qq.com>
Co-authored-by: HYana <65863826+KaedeSAMA@users.noreply.github.com>
2025-05-27 22:36:50 +08:00
aberry
8dfef1d118 Bugfix (#1482)
* Update modelscopechatcmpl.py

tool_call 流式输出的最后一个参数是 None,需要判断一下

* Update mcp.py

问题:闭包(closure)对循环变量 tool 的捕获,导致最终注册到 self.functions 里的所有 func,都会引用 同一个(最后一个)tool

解决:在定义 func 时,通过函数参数的 默认值 把当下的 tool “冻结”住

* Update mcp.py
2025-05-27 15:09:09 +08:00
Junyan Qin (Chin)
919a621bf8 fix: lru bug in t2i (#1445) (#1481) 2025-05-27 09:58:22 +08:00
Junyan Qin
3ac96f464d perf: show description in bot form 2025-05-23 10:31:11 +08:00
Junyan Qin
f9f03b81d1 chore: release v4.0.3.3 2025-05-22 10:49:24 +08:00
Junyan Qin
42171a9c07 fix: combine quote message not in default pipeline config 2025-05-22 10:44:33 +08:00
Junyan Qin
f1f00115c9 chore: update issue template 2025-05-22 10:42:59 +08:00
Junyan Qin
59bff61409 chore: release v4.0.3.2 2025-05-21 19:46:42 +08:00
Junyan Qin
778693a804 perf: desc of random 2025-05-21 19:45:45 +08:00
Junyan Qin
e5b2da225c perf: no longer get host ip 2025-05-21 19:42:04 +08:00
Steven Lynn
4a988b89a2 fix: update auto-reply probability description in trigger.yaml (#1463) 2025-05-21 17:50:23 +08:00
Junyan Qin
e5e8807312 perf: no longer ask for apikeys for ollama and lm studio 2025-05-20 16:01:20 +08:00
Junyan Qin
1376530c2e fix: conversation is null 2025-05-20 15:32:04 +08:00
Junyan Qin
7d34a2154b perf: unify i18n text class in frontend 2025-05-20 11:32:55 +08:00
Junyan Qin
ff335130ae chore: update CONTRIBUTING 2025-05-20 09:39:46 +08:00
Junyan Qin
0afef0ac0f chore: update pr template 2025-05-20 09:21:59 +08:00
Junyan Qin (Chin)
6447f270ea Update bug-report_en.yml 2025-05-20 09:16:30 +08:00
Junyan Qin (Chin)
81be62e1a4 Update bug-report_en.yml 2025-05-20 09:15:52 +08:00
Junyan Qin (Chin)
409909ccb1 Update bug-report_en.yml (#1456) 2025-05-20 09:14:52 +08:00
Junyan Qin
b821b69dbb chore: perf issue templates 2025-05-20 09:13:13 +08:00
Junyan Qin
7e2448655e chore: add english issue templates 2025-05-20 09:11:47 +08:00
Junyan Qin (Chin)
a7d2a68639 feat: add supports for testing llm models (#1454)
* feat: add supports for testing llm models

* fix: linter error
2025-05-19 23:10:04 +08:00
fdc310
aba51409a7 feat:add qoute message process and add Whether to enable this function (#1446)
* 更新了wechatpad接口,以及适配器

* 更新了wechatpad接口,以及适配器

* 修复一些细节问题,比如at回复,以及启动登录和启动ws长连接的线程同步

* importutil中修复了在wi上启动替换斜杠问题,login中加上了一个login,暂时没啥用。wechatpad中做出了一些细节修改

* 更新了wechatpad接口,以及适配器

* 怎加了处理图片链接转换为image_base64发送

* feat(wechatpad): 调整日志+bugfix

* feat(wechatpad): fix typo

* 修正了发送语音api参数错误,添加了发送链接处理为base64数据(好像只有一部分链接可以)

* 修复了部分手抽的typo错误

* chore: remove manager.py

* feat:add qoute message process and add Whether to enable this function

* chore: add db migration for this change

---------

Co-authored-by: shinelin <shinelinxx@gmail.com>
Co-authored-by: Junyan Qin (Chin) <rockchinq@gmail.com>
2025-05-19 22:24:18 +08:00
sheetung
5e5d37cbf1 St/webui (#1452)
* 解决webUI模型配置页面卡片溢出问题

* fix: webUI卡片文本溢出问题
2025-05-19 18:11:50 +08:00
sheetung
e5a99a0fe4 解决webUI模型配置页面卡片溢出问题 (#1451) 2025-05-19 13:14:39 +08:00
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
Junyan Qin
696162ee52 chore: release v4.0.3 2025-05-14 22:05:03 +08:00
Junyan Qin
533f993e3a fix: bad Dockerfile CMD 2025-05-14 22:04:08 +08:00
Junyan Qin
738b0af5fb chore: release v4.0.2 2025-05-14 21:35:21 +08:00
Junyan Qin
5d9bac5e7b doc: remove gewechat 2025-05-14 21:32:05 +08:00
Junyan Qin (Chin)
f376c9703a feat: add supports for open router (#1422) 2025-05-14 21:28:33 +08:00
fdc310
20a62fcf69 feat: add wechatpad for personal wechat
* 更新了wechatpad接口,以及适配器

* 更新了wechatpad接口,以及适配器

* 修复一些细节问题,比如at回复,以及启动登录和启动ws长连接的线程同步

* importutil中修复了在wi上启动替换斜杠问题,login中加上了一个login,暂时没啥用。wechatpad中做出了一些细节修改

* 更新了wechatpad接口,以及适配器

* 怎加了处理图片链接转换为image_base64发送

* feat(wechatpad): 调整日志+bugfix

* feat(wechatpad): fix typo

* 修正了发送语音api参数错误,添加了发送链接处理为base64数据(好像只有一部分链接可以)

* 修复了部分手抽的typo错误

* chore: remove manager.py

---------

Co-authored-by: shinelin <shinelinxx@gmail.com>
Co-authored-by: Junyan Qin (Chin) <rockchinq@gmail.com>
2025-05-14 21:18:08 +08:00
devin-ai-integration[bot]
248d4beed1 fix: add super().__init__() call to EchoTextHandler to initialize logger attribute (#1421)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-05-14 20:52:27 +08:00
Junyan Qin
0e52aff363 chore: remove requirements.txt 2025-05-14 19:37:06 +08:00
Junyan Qin (Chin)
4ed854d7b8 ci: update Dockerfile (#1420)
* ci: update Dockerfile

* ci: update Dockerfile

* ci: no `--locked`
2025-05-14 19:29:44 +08:00
Junyan Qin
c6ff33c6ab chore: add google ai deps 2025-05-14 19:14:12 +08:00
简律纯
6c10cb7dca feat: support package manager(uv) (#1414)
* chore: set Python version to 3.10

* feat: add pyproject.toml for project configuration and dependencies

* style: streamline bot retrieval and update logic in PipelineService

* feat: update dependencies and configuration for ruff and pip

* chore: remove ruff configuration file

* style: change quote style from single to double in ruff configuration

* style: unify string quote style to double quotes across multiple files

* chore: update .gitignore to include .venv and uv.lock

* chore: remove unused configuration files and clean up project structure

* chore: revert quote-style to `single`

* chore: set default python version to 3.12

---------

Co-authored-by: Junyan Qin <rockchinq@gmail.com>
2025-05-14 19:09:52 +08:00
Junyan Qin
130495f519 perf: missing translation in zh-Hans 2025-05-14 17:02:40 +08:00
Junyan Qin
219d328342 perf: completion some english translation 2025-05-14 17:00:03 +08:00
Junyan Qin
c835555a59 chore: change zh_CN to zh_Hans 2025-05-14 16:44:48 +08:00
Junyan Qin
6652b57a0d doc: README 2025-05-14 16:08:34 +08:00
Junyan Qin
bf51afedf6 perf: async bug in llm form 2025-05-14 15:37:58 +08:00
Junyan Qin
39f9400de7 fix: modelscope no usable 2025-05-14 15:35:37 +08:00
devin-ai-integration[bot]
ac1d39580b feat: add Google Gemini API support (#1418)
* feat: add Google Gemini API support

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* fix: remove unused imports

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: add google-genai dependency

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* fix: update Gemini API implementation to use correct API methods

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* refactor: improve Gemini API implementation based on official documentation

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* fix: remove unsupported timeout parameter from Gemini API implementation

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* fix: correct Gemini API implementation based on official documentation

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: update geminichatcmpl

* deps: add google-generativeai

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-05-14 15:05:04 +08:00
Junyan Qin
9362b34858 doc: readme images 2025-05-14 12:34:49 +08:00
Junyan Qin
c6f6c715bd doc: add demo images 2025-05-14 12:33:59 +08:00
Junyan Qin
6a8106d9ac doc: remove usage badge in README 2025-05-14 12:22:45 +08:00
Junyan Qin (Chin)
5abbcb62a2 Fix/system info 404 (#1413)
* fix: system info 404

* fix: lint error
2025-05-13 23:14:06 +08:00
devin-ai-integration[bot]
2bf94539bd Add i18n support with language selector on login page (#1410)
* feat: add i18n support with language selector on login page

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: complete i18n implementation for all webui components

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: complete all hardcoded text

* feat: dynamic label i18n

* fix: lint errors

* fix: lint errors

* delete sh fils

* fix: edit model dialog title

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-05-13 22:39:19 +08:00
Junyan Qin (Chin)
91cd8cf380 chore: release v4.0.1 (#1409) 2025-05-13 19:37:47 +08:00
Guanchao Wang
c3de3fa275 fix: wrong status when creating a WecomCS bot (#1408) 2025-05-13 19:33:32 +08:00
devin-ai-integration[bot]
039752419b Add User Card and Logout Button to Sidebar (#1405)
* feat: add user card and logout button to sidebar

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* feat: add test code to set dummy values in localStorage

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* style: fix formatting issues in HomeSidebar.tsx

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* style: fix whitespace in HomeSidebar.tsx

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* perf: styles of logout button

* fix: lint errors

* fix: lint errors

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-05-13 19:33:04 +08:00
Junyan Qin (Chin)
18c708da58 fix: windows path bug in importutil (#1404) 2025-05-13 16:52:16 +08:00
Junyan Qin (Chin)
8c08b8ee8a perf: no delay for model selector hover card (#1402) 2025-05-13 16:15:35 +08:00
Junyan Qin (Chin)
015be6008d fix: bugs in requesters (#1401) 2025-05-13 16:09:23 +08:00
Junyan Qin
da86384e58 doc(README): add Ask DeepWiki badge 2025-05-13 14:36:29 +08:00
devin-ai-integration[bot]
86ff6f5eb6 feat: plugin reordering (#1398)
* Add @dnd-kit/core and @dnd-kit/sortable dependencies for plugin sorting

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Add PluginSortDialog component with drag-and-drop functionality

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Integrate sorting button and dialog into PluginInstalledComponent

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Update HttpClient to use local backend URL for development

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Fix reorderPlugins method to use PUT and correct request format

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Update hover-card component using shadcn CLI

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Fix formatting issues in plugin sorting components

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* refactor: move plugin sorting button and dialog to page component

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* refactor: move PluginSortDialog component to plugins directory

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* chore: remove old PluginSortDialog component file

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* fix: api bug

* perf: desciption in plugin sorting dialog

* fix: lint errors

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-05-13 14:10:18 +08:00
devin-ai-integration[bot]
ae6979151f Fix #1391: Update bot's pipeline name when pipeline is renamed (#1397)
* Fix #1391: Update bot's pipeline name when pipeline is renamed

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Refactor: Use bot_service to update bot pipeline names

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-05-13 12:44:00 +08:00
devin-ai-integration[bot]
fd1b5d494e Add hover card to LLM model selector (#1393)
* Add hover card to LLM model selector to display detailed model information

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Fix formatting issues to resolve lint errors

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* Fix remaining formatting issue in DynamicFormItemComponent.tsx

Co-Authored-By: Junyan Qin <Chin> <rockchinq@gmail.com>

* perf(model preview): hover card style

* fix: wrong base url

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin> <rockchinq@gmail.com>
2025-05-13 12:25:32 +08:00
Junyan Qin (Chin)
cd68760c75 Fix/runner bugs (#1388)
* fix: bugs in runners

* fix: model entity of exists conversation is None after changed runner
2025-05-12 18:21:08 +08:00
359 changed files with 21527 additions and 2377 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:
@@ -7,7 +7,7 @@ body:
attributes:
label: 运行环境
description: LangBot 版本、操作系统、系统架构、**Python版本**、**主机地理位置**
placeholder: 例如v3.3.0、CentOS x64 Python 3.10.3、Docker 的系统直接写 Docker 就行
placeholder: 例如v3.3.0、CentOS x64 Python 3.10.3、Docker
validations:
required: true
- type: textarea
@@ -19,12 +19,12 @@ body:
- type: textarea
attributes:
label: 复现步骤
description: 如何重现这个问题,越详细越好;请贴上所有相关的配置文件和元数据文件(注意隐去敏感信息)
description: 提供越多信息,我们会越快解决问题,建议多提供配置截图;**如果你不认真填写(只一两句话概括),我们会很生气并且立即关闭 issue 或两年后才回复你**
validations:
required: true
required: false
- type: textarea
attributes:
label: 启用的插件
description: 有些情况可能和插件功能有关,建议提供插件启用情况。可以使用`!plugin`命令查看已启用的插件
description: 有些情况可能和插件功能有关,建议提供插件启用情况。
validations:
required: false

View File

@@ -0,0 +1,30 @@
name: Bug report
description: Report bugs or vulnerabilities using this template. For container network connection issues, refer to the documentation https://docs.langbot.app/en/workshop/network-details.html
title: "[Bug]: "
labels: ["bug?"]
body:
- type: input
attributes:
label: Runtime environment
description: LangBot version, operating system, system architecture, **Python version**, **host location**
placeholder: "For example: v3.3.0, CentOS x64 Python 3.10.3, Docker"
validations:
required: true
- type: textarea
attributes:
label: Exception
description: Describe the exception in detail, what happened and when it happened. **Please include log information.**
validations:
required: true
- type: textarea
attributes:
label: Reproduction steps
description: How to reproduce this problem, the more detailed the better; the more information you provide, the faster we will solve the problem. 【注意】请务必认真填写此部分,若不提供完整信息(如只有一两句话的概括),我们将不会回复!
validations:
required: false
- type: textarea
attributes:
label: Enabled plugins
description: Some cases may be related to plugin functionality, so please provide the plugin enablement status.
validations:
required: false

View File

@@ -1,7 +1,7 @@
name: 需求建议
title: "[Feature]: "
labels: ["改进"]
description: "新功能或现有功能优化请使用这个模板不符合类别的issue将被直接关闭"
labels: []
description: "【供中文用户】新功能或现有功能优化请使用这个模板不符合类别的issue将被直接关闭"
body:
- type: dropdown
attributes:

View File

@@ -0,0 +1,21 @@
name: Feature request
title: "[Feature]: "
labels: []
description: "New features or existing feature improvements should use this template; issues that do not match will be closed directly"
body:
- type: dropdown
attributes:
label: This is a?
description: New feature request or existing feature improvement
options:
- New feature
- Existing feature improvement
validations:
required: true
- type: textarea
attributes:
label: Detailed description
description: Detailed description, the more detailed the better
validations:
required: true

View File

@@ -1,7 +1,7 @@
name: 提交新插件
title: "[Plugin]: 请求登记新插件"
labels: ["独立插件"]
description: "本模板供且仅供提交新插件使用"
description: "【供中文用户】本模板供且仅供提交新插件使用"
body:
- type: input
attributes:

View File

@@ -0,0 +1,24 @@
name: Submit a new plugin
title: "[Plugin]: Request to register a new plugin"
labels: ["Independent Plugin"]
description: "This template is only for submitting new plugins"
body:
- type: input
attributes:
label: Plugin name
description: Fill in the name of the plugin
validations:
required: true
- type: textarea
attributes:
label: Plugin code repository address
description: Only support Github
validations:
required: true
- type: textarea
attributes:
label: Plugin description
description: The description of the plugin
validations:
required: true

View File

@@ -1,20 +1,21 @@
## 概述
## 概述 / Overview
实现/解决/优化的内容:
> 请在此部分填写你实现/解决/优化的内容:
> Summary of what you implemented/solved/optimized:
## 检查清单
## 检查清单 / Checklist
### PR 作者完成
### PR 作者完成 / For PR author
*请在方括号间写`x`以打勾
*请在方括号间写`x`以打勾 / Please tick the box with `x`*
- [ ] 阅读仓库[贡献指引](https://github.com/RockChinQ/LangBot/blob/master/CONTRIBUTING.md)了吗?
- [ ] 与项目所有者沟通过了吗?
- [ ] 我确定已自行测试所作的更改,确保功能符合预期。
- [ ] 阅读仓库[贡献指引](https://github.com/langbot-app/LangBot/blob/master/CONTRIBUTING.md)了吗? / Have you read the [contribution guide](https://github.com/langbot-app/LangBot/blob/master/CONTRIBUTING.md)?
- [ ] 与项目所有者沟通过了吗? / Have you communicated with the project maintainer?
- [ ] 我确定已自行测试所作的更改,确保功能符合预期。 / I have tested the changes and ensured they work as expected.
### 项目所有者完成
### 项目维护者完成 / For project maintainer
- [ ] 相关 issues 链接了吗?
- [ ] 配置项写好了吗?迁移写好了吗?生效了吗?
- [ ] 依赖requirements.txt 和 core/bootutils/deps.py 了吗
- [ ] 文档编写了吗?
- [ ] 相关 issues 链接了吗? / Have you linked the related issues?
- [ ] 配置项写好了吗?迁移写好了吗?生效了吗? / Have you written the configuration items? Have you written the migration? Has it taken effect?
- [ ] 依赖pyproject.toml 和 core/bootutils/deps.py 了吗 / Have you added the dependencies to pyproject.toml and core/bootutils/deps.py?
- [ ] 文档编写了吗? / Have you written the documentation?

3
.gitignore vendored
View File

@@ -41,3 +41,6 @@ botpy.log*
/venv
test.py
/web_ui
.venv/
uv.lock
/test

View File

@@ -1,42 +0,0 @@
line-length = 120
[lint]
ignore = [
"E712", # Comparison to true should be 'if cond is true:' or 'if cond:' (E712)
"F402", # Import `loader` from line 8 shadowed by loop variable
"F403", # * used, unable to detect undefined names
"F405", # may be undefined, or defined from star imports
"E741", # Ambiguous variable name: `l`
"E722", # bare-except
"E721", # type-comparison
"F821", # undefined-all
"FURB113", # repeated-append
"FURB152", # math-constant
"UP007", # non-pep604-annotation
"UP032", # f-string
"UP045", # non-pep604-annotation-optional
"B005", # strip-with-multi-characters
"B006", # mutable-argument-default
"B007", # unused-loop-control-variable
"B026", # star-arg-unpacking-after-keyword-arg
"B903", # class-as-data-structure
"B904", # raise-without-from-inside-except
"B905", # zip-without-explicit-strict
"N806", # non-lowercase-variable-in-function
"N815", # mixed-case-variable-in-class-scope
"PT011", # pytest-raises-too-broad
"SIM102", # collapsible-if
"SIM103", # needless-bool
"SIM105", # suppressible-exception
"SIM107", # return-in-try-except-finally
"SIM108", # if-else-block-instead-of-if-exp
"SIM113", # enumerate-for-loop
"SIM117", # multiple-with-statements
"SIM210", # if-expr-with-true-false
]
[format]
# 5. Use single quotes in `ruff format`.
quote-style = "single"

View File

@@ -5,22 +5,27 @@
### 贡献形式
- 提交PR解决issues中提到的bug或期待的功能
- 提交PR实现您设想的功能请先提出issue与者沟通)
- 优化代码架构,使各个模块的组织更加整洁优雅
- 在issues中提出发现的bug或者期待的功能
- 提交PR实现您设想的功能请先提出issue与项目维护者沟通)
- 为本项目在其他社交平台撰写文章、制作视频等
- 为本项目的衍生项目作出贡献,或开发插件增加功能
### 如何开始
### 沟通语言规范
- 加入本项目交流群,一同探讨项目相关事务
- 解决本项目或衍生项目的issues中亟待解决的问题
- 阅读并完善本项目文档
- 在各个社交媒体撰写本项目教程等
- 在 PR 和 Commit Message 中请使用全英文
- 对于中文用户issue 中可以使用中文
### 代码规范
<hr/>
- 代码中的注解`务必`符合Google风格的规范
- 模块顶部的引入代码请遵循`系统模块``第三方库模块``自定义模块`的顺序进行引入
- `不要`直接引入模块的特定属性,而是引入这个模块,再通过`xxx.yyy`的形式使用属性
- 任何作用域的字段`必须`先声明后使用,并在声明处注明类型提示
## Guidelines
### Contribution
- Submit PRs to solve bugs or features in the issues
- Submit PRs to implement your ideas (Please create an issue first and communicate with the project maintainer)
- Write articles or make videos about this project on other social platforms
- Contribute to the development of derivative projects, or develop plugins to add features
### Spoken Language
- Use English in PRs and Commit Messages
- For English users, you can use English in issues

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
@@ -16,7 +16,8 @@ COPY --from=node /app/web/out ./web/out
RUN apt update \
&& apt install gcc -y \
&& python -m pip install -r requirements.txt \
&& python -m pip install --no-cache-dir uv \
&& uv sync \
&& touch /.dockerenv
CMD [ "python", "main.py" ]
CMD [ "uv", "run", "main.py" ]

View File

@@ -1,54 +1,47 @@
<p align="center">
<a href="https://langbot.app">
<img src="https://docs.langbot.app/social.png" alt="LangBot"/>
<img src="https://docs.langbot.app/social_zh.png" alt="LangBot"/>
</a>
<div align="center">
<a href="https://trendshift.io/repositories/12901" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12901" alt="RockChinQ%2FLangBot | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://hellogithub.com/repository/langbot-app/LangBot" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=5ce8ae2aa4f74316bf393b57b952433c&claim_uid=gtmc6YWjMZkT21R" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
[English](README_EN.md) / 简体中文 / [繁體中文](README_TW.md) / [日本語](README_JP.md) / (PR for your language)
[![Discord](https://img.shields.io/discord/1335141740050649118?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb)](https://discord.gg/wdNEHETs87)
[![QQ Group](https://img.shields.io/badge/%E7%A4%BE%E5%8C%BAQQ%E7%BE%A4-966235608-blue)](https://qm.qq.com/q/JLi38whHum)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/langbot-app/LangBot)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/langbot-app/LangBot)](https://github.com/langbot-app/LangBot/releases/latest)
<img src="https://img.shields.io/badge/python-3.10 ~ 3.13 -blue.svg" alt="python">
[![star](https://gitcode.com/RockChinQ/LangBot/star/badge.svg)](https://gitcode.com/RockChinQ/LangBot)
<a href="https://langbot.app">项目主页</a>
<a href="https://docs.langbot.app/zh/insight/guide.html">部署文档</a>
<a href="https://docs.langbot.app/zh/plugin/plugin-intro.html">插件介绍</a>
<a href="https://github.com/RockChinQ/LangBot/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">提交插件</a>
<a href="https://github.com/langbot-app/LangBot/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">提交插件</a>
<div align="center">
😎高稳定、🧩支持扩展、🦄多模态 - 大模型原生即时通信机器人平台🤖
</div>
<br/>
[![Discord](https://img.shields.io/discord/1335141740050649118?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb)](https://discord.gg/wdNEHETs87)
[![QQ Group](https://img.shields.io/badge/%E7%A4%BE%E5%8C%BAQQ%E7%BE%A4-966235608-blue)](https://qm.qq.com/q/JLi38whHum)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/LangBot)](https://github.com/RockChinQ/LangBot/releases/latest)
![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.qchatgpt.rockchin.top%2Fapi%2Fv2%2Fview%2Frealtime%2Fcount_query%3Fminute%3D10080&query=%24.data.count&label=%E4%BD%BF%E7%94%A8%E9%87%8F%EF%BC%887%E6%97%A5%EF%BC%89)
<img src="https://img.shields.io/badge/python-3.10 ~ 3.13 -blue.svg" alt="python">
[![star](https://gitcode.com/RockChinQ/LangBot/star/badge.svg)](https://gitcode.com/RockChinQ/LangBot)
[简体中文](README.md) / [English](README_EN.md) / [日本語](README_JP.md)
</div>
</p>
## ✨ 特性
- 💬 大模型对话、Agent支持多种大模型适配群聊和私聊具有多轮对话、工具调用、多模态能力并深度适配 [Dify](https://dify.ai)。目前支持 QQ、QQ频道、企业微信、个人微信、飞书、Discord、Telegram 等平台。
- 🛠️ 高稳定性、功能完备:原生支持访问控制、限速、敏感词过滤等机制;配置简单,支持多种部署方式。
- 🧩 插件扩展、活跃社区:支持事件驱动、组件扩展等插件机制;适配 Anthropic [MCP 协议](https://modelcontextprotocol.io/);目前已有数百个插件。
- 😻 Web 管理面板:支持通过浏览器管理 LangBot 实例,不再需要手动编写配置文件。
LangBot 是一个开源的大语言模型原生即时通信机器人开发平台,旨在提供开箱即用的 IM 机器人开发体验,具有 Agent、RAG、MCP 等多种 LLM 应用功能,适配全球主流即时通信平台,并提供丰富的 API 接口,支持自定义开发。
## 📦 开始使用
> [!IMPORTANT]
>
> 在您开始任何方式部署之前,请务必阅读[新手指引](https://docs.langbot.app/zh/insight/guide.html)。
#### Docker Compose 部署
适合熟悉 Docker 的用户,查看文档[Docker 部署](https://docs.langbot.app/zh/deploy/langbot/docker.html)。
```bash
git clone https://github.com/langbot-app/LangBot
cd LangBot
docker compose up -d
```
访问 http://localhost:5300 即可开始使用。
详细文档[Docker 部署](https://docs.langbot.app/zh/deploy/langbot/docker.html)。
#### 宝塔面板部署
@@ -68,15 +61,25 @@
直接使用发行版运行,查看文档[手动部署](https://docs.langbot.app/zh/deploy/langbot/manual.html)。
## 📸 效果展示
## 😎 保持更新
<img alt="回复效果(带有联网插件)" src="https://docs.langbot.app/QChatGPT-0516.png" width="500px"/>
点击仓库右上角 Star 和 Watch 按钮,获取最新动态。
- WebUI Demo: https://demo.langbot.dev/
![star gif](https://docs.langbot.app/star.gif)
## ✨ 特性
- 💬 大模型对话、Agent支持多种大模型适配群聊和私聊具有多轮对话、工具调用、多模态、流式输出能力自带 RAG知识库实现并深度适配 [Dify](https://dify.ai)。
- 🤖 多平台支持:目前支持 QQ、QQ频道、企业微信、个人微信、飞书、Discord、Telegram 等平台。
- 🛠️ 高稳定性、功能完备:原生支持访问控制、限速、敏感词过滤等机制;配置简单,支持多种部署方式。支持多流水线配置,不同机器人用于不同应用场景。
- 🧩 插件扩展、活跃社区:支持事件驱动、组件扩展等插件机制;适配 Anthropic [MCP 协议](https://modelcontextprotocol.io/);目前已有数百个插件。
- 😻 Web 管理面板:支持通过浏览器管理 LangBot 实例,不再需要手动编写配置文件。
详细规格特性请访问[文档](https://docs.langbot.app/zh/insight/features.html)。
或访问 demo 环境https://demo.langbot.dev/
- 登录信息:邮箱:`demo@langbot.app` 密码:`langbot123456`
- 注意:仅展示webui效果,公开环境,请不要在其中填入您的任何敏感信息。
## 🔌 组件兼容性
- 注意:仅展示 WebUI 效果,公开环境,请不要在其中填入您的任何敏感信息。
### 消息平台
@@ -86,17 +89,13 @@
| QQ 官方机器人 | ✅ | QQ 官方机器人,支持频道、私聊、群聊 |
| 企业微信 | ✅ | |
| 企微对外客服 | ✅ | |
| 个人微信 | ✅ | 使用 [Gewechat](https://github.com/Devo919/Gewechat) 接入 |
| 个人微信 | ✅ | |
| 微信公众号 | ✅ | |
| 飞书 | ✅ | |
| 钉钉 | ✅ | |
| Discord | ✅ | |
| Telegram | ✅ | |
| Slack | ✅ | |
| LINE | 🚧 | |
| WhatsApp | 🚧 | |
🚧: 正在开发中
### 大模型能力
@@ -108,7 +107,10 @@
| [Anthropic](https://www.anthropic.com/) | ✅ | |
| [xAI](https://x.ai/) | ✅ | |
| [智谱AI](https://open.bigmodel.cn/) | ✅ | |
| [优云智算](https://www.compshare.cn/?ytag=GPU_YY-gh_langbot) | ✅ | 大模型和 GPU 资源平台 |
| [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | ✅ | 大模型和 GPU 资源平台 |
| [302.AI](https://share.302.ai/SuTG99) | ✅ | 大模型聚合平台 |
| [Google Gemini](https://aistudio.google.com/prompts/new_chat) | ✅ | |
| [Dify](https://dify.ai) | ✅ | LLMOps 平台 |
| [Ollama](https://ollama.com/) | ✅ | 本地大模型运行平台 |
| [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型运行平台 |
@@ -135,8 +137,8 @@
## 😘 社区贡献
感谢以下[代码贡献者](https://github.com/RockChinQ/LangBot/graphs/contributors)和社区里其他成员对 LangBot 的贡献:
感谢以下[代码贡献者](https://github.com/langbot-app/LangBot/graphs/contributors)和社区里其他成员对 LangBot 的贡献:
<a href="https://github.com/RockChinQ/LangBot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=RockChinQ/LangBot" />
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
</a>

View File

@@ -1,63 +1,51 @@
<p align="center">
<a href="https://langbot.app">
<img src="https://docs.langbot.app/social.png" alt="LangBot"/>
<img src="https://docs.langbot.app/social_en.png" alt="LangBot"/>
</a>
<div align="center">
<a href="https://trendshift.io/repositories/12901" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12901" alt="RockChinQ%2FLangBot | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://langbot.app">Home</a>
<a href="https://docs.langbot.app/zh/insight/guide.html">Deployment</a>
<a href="https://docs.langbot.app/zh/plugin/plugin-intro.html">Plugin</a>
<a href="https://github.com/RockChinQ/LangBot/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">Submit Plugin</a>
<div align="center">
😎High Stability, 🧩Extension Supported, 🦄Multi-modal - LLM Native Instant Messaging Bot Platform🤖
</div>
<br/>
English / [简体中文](README.md) / [繁體中文](README_TW.md) / [日本語](README_JP.md) / (PR for your language)
[![Discord](https://img.shields.io/discord/1335141740050649118?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb)](https://discord.gg/wdNEHETs87)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/LangBot)](https://github.com/RockChinQ/LangBot/releases/latest)
![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.qchatgpt.rockchin.top%2Fapi%2Fv2%2Fview%2Frealtime%2Fcount_query%3Fminute%3D10080&query=%24.data.count&label=Usage(7days))
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/langbot-app/LangBot)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/langbot-app/LangBot)](https://github.com/langbot-app/LangBot/releases/latest)
<img src="https://img.shields.io/badge/python-3.10 ~ 3.13 -blue.svg" alt="python">
[简体中文](README.md) / [English](README_EN.md) / [日本語](README_JP.md)
<a href="https://langbot.app">Home</a>
<a href="https://docs.langbot.app/en/insight/guide.html">Deployment</a>
<a href="https://docs.langbot.app/en/plugin/plugin-intro.html">Plugin</a>
<a href="https://github.com/langbot-app/LangBot/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">Submit Plugin</a>
</div>
</p>
## ✨ Features
- 💬 Chat with LLM / Agent: Supports multiple LLMs, adapt to group chats and private chats; Supports multi-round conversations, tool calls, and multi-modal capabilities. Deeply integrates with [Dify](https://dify.ai). Currently supports QQ, QQ Channel, WeCom, personal WeChat, Lark, DingTalk, Discord, Telegram, etc.
- 🛠️ High Stability, Feature-rich: Native access control, rate limiting, sensitive word filtering, etc. mechanisms; Easy to use, supports multiple deployment methods.
- 🧩 Plugin Extension, Active Community: Support event-driven, component extension, etc. plugin mechanisms; Integrate Anthropic [MCP protocol](https://modelcontextprotocol.io/); Currently has hundreds of plugins.
- 😻 [New] Web UI: Support management LangBot instance through the browser. No need to manually write configuration files.
LangBot is an open-source LLM native instant messaging robot development platform, aiming to provide out-of-the-box IM robot development experience, with Agent, RAG, MCP and other LLM application functions, adapting to global instant messaging platforms, and providing rich API interfaces, supporting custom development.
## 📦 Getting Started
> [!IMPORTANT]
>
> - Before you start deploying in any way, please read the [New User Guide](https://docs.langbot.app/zh/insight/guide.html).
> - All documentation is in Chinese, we will provide i18n version in the near future.
> - Read [the auto-generated wiki on DeepWiki](https://deepwiki.com/RockChinQ/LangBot).
#### Docker Compose Deployment
Suitable for users familiar with Docker, see the [Docker Deployment](https://docs.langbot.app/zh/deploy/langbot/docker.html) documentation.
```bash
git clone https://github.com/langbot-app/LangBot
cd LangBot
docker compose up -d
```
Visit http://localhost:5300 to start using it.
Detailed documentation [Docker Deployment](https://docs.langbot.app/en/deploy/langbot/docker.html).
#### One-click Deployment on BTPanel
LangBot has been listed on the BTPanel, if you have installed the BTPanel, you can use the [document](https://docs.langbot.app/zh/deploy/langbot/one-click/bt.html) to use it.
LangBot has been listed on the BTPanel, if you have installed the BTPanel, you can use the [document](https://docs.langbot.app/en/deploy/langbot/one-click/bt.html) to use it.
#### Zeabur Cloud Deployment
Community contributed Zeabur template.
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/zh-CN/templates/ZKTBDH)
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
#### Railway Cloud Deployment
@@ -65,17 +53,27 @@ Community contributed Zeabur template.
#### Other Deployment Methods
Directly use the released version to run, see the [Manual Deployment](https://docs.langbot.app/zh/deploy/langbot/manual.html) documentation.
Directly use the released version to run, see the [Manual Deployment](https://docs.langbot.app/en/deploy/langbot/manual.html) documentation.
## 📸 Demo
## 😎 Stay Ahead
<img alt="Reply Effect (with Internet Plugin)" src="https://docs.langbot.app/QChatGPT-0516.png" width="500px"/>
Click the Star and Watch button in the upper right corner of the repository to get the latest updates.
- WebUI Demo: https://demo.langbot.dev/
![star gif](https://docs.langbot.app/star.gif)
## ✨ Features
- 💬 Chat with LLM / Agent: Supports multiple LLMs, adapt to group chats and private chats; Supports multi-round conversations, tool calls, multi-modal, and streaming output capabilities. Built-in RAG (knowledge base) implementation, and deeply integrates with [Dify](https://dify.ai).
- 🤖 Multi-platform Support: Currently supports QQ, QQ Channel, WeCom, personal WeChat, Lark, DingTalk, Discord, Telegram, etc.
- 🛠️ High Stability, Feature-rich: Native access control, rate limiting, sensitive word filtering, etc. mechanisms; Easy to use, supports multiple deployment methods. Supports multiple pipeline configurations, different bots can be used for different scenarios.
- 🧩 Plugin Extension, Active Community: Support event-driven, component extension, etc. plugin mechanisms; Integrate Anthropic [MCP protocol](https://modelcontextprotocol.io/); Currently has hundreds of plugins.
- 😻 Web UI: Support management LangBot instance through the browser. No need to manually write configuration files.
For more detailed specifications, please refer to the [documentation](https://docs.langbot.app/en/insight/features.html).
Or visit the demo environment: https://demo.langbot.dev/
- Login information: Email: `demo@langbot.app` Password: `langbot123456`
- Note: Only the WebUI effect is shown, please do not fill in any sensitive information in the public environment.
## 🔌 Component Compatibility
- Note: For WebUI demo only, please do not fill in any sensitive information in the public environment.
### Message Platform
@@ -85,16 +83,12 @@ Directly use the released version to run, see the [Manual Deployment](https://do
| QQ Official API | ✅ | |
| WeCom | ✅ | |
| WeComCS | ✅ | |
| Personal WeChat | ✅ | Use [Gewechat](https://github.com/Devo919/Gewechat) to access |
| Personal WeChat | ✅ | |
| Lark | ✅ | |
| DingTalk | ✅ | |
| Discord | ✅ | |
| Telegram | ✅ | |
| Slack | ✅ | |
| LINE | 🚧 | |
| WhatsApp | 🚧 | |
🚧: In development
### LLMs
@@ -106,8 +100,11 @@ Directly use the released version to run, see the [Manual Deployment](https://do
| [Anthropic](https://www.anthropic.com/) | ✅ | |
| [xAI](https://x.ai/) | ✅ | |
| [Zhipu AI](https://open.bigmodel.cn/) | ✅ | |
| [CompShare](https://www.compshare.cn/?ytag=GPU_YY-gh_langbot) | ✅ | LLM and GPU resource platform |
| [Dify](https://dify.ai) | ✅ | LLMOps platform |
| [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | ✅ | LLM and GPU resource platform |
| [302.AI](https://share.302.ai/SuTG99) | ✅ | LLM gateway(MaaS) |
| [Google Gemini](https://aistudio.google.com/prompts/new_chat) | ✅ | |
| [Ollama](https://ollama.com/) | ✅ | Local LLM running platform |
| [LMStudio](https://lmstudio.ai/) | ✅ | Local LLM running platform |
| [GiteeAI](https://ai.gitee.com/) | ✅ | LLM interface gateway(MaaS) |
@@ -119,8 +116,8 @@ Directly use the released version to run, see the [Manual Deployment](https://do
## 🤝 Community Contribution
Thank you for the following [code contributors](https://github.com/RockChinQ/LangBot/graphs/contributors) and other members in the community for their contributions to LangBot:
Thank you for the following [code contributors](https://github.com/langbot-app/LangBot/graphs/contributors) and other members in the community for their contributions to LangBot:
<a href="https://github.com/RockChinQ/LangBot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=RockChinQ/LangBot" />
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
</a>

View File

@@ -1,62 +1,51 @@
<p align="center">
<a href="https://langbot.app">
<img src="https://docs.langbot.app/social.png" alt="LangBot"/>
<img src="https://docs.langbot.app/social_en.png" alt="LangBot"/>
</a>
<div align="center">
<a href="https://trendshift.io/repositories/12901" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12901" alt="RockChinQ%2FLangBot | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://langbot.app">ホーム</a>
<a href="https://docs.langbot.app/zh/insight/guide.html">デプロイ</a>
<a href="https://docs.langbot.app/zh/plugin/plugin-intro.html">プラグイン</a>
<a href="https://github.com/RockChinQ/LangBot/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">プラグインの提出</a>
<div align="center">
😎高い安定性、🧩拡張サポート、🦄マルチモーダル - LLMネイティブインスタントメッセージングボットプラットフォーム🤖
</div>
<br/>
[English](README_EN.md) / [简体中文](README.md) / [繁體中文](README_TW.md) / 日本語 / (PR for your language)
[![Discord](https://img.shields.io/discord/1335141740050649118?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb)](https://discord.gg/wdNEHETs87)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/LangBot)](https://github.com/RockChinQ/LangBot/releases/latest)
![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.qchatgpt.rockchin.top%2Fapi%2Fv2%2Fview%2Frealtime%2Fcount_query%3Fminute%3D10080&query=%24.data.count&label=Usage(7days))
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/langbot-app/LangBot)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/langbot-app/LangBot)](https://github.com/langbot-app/LangBot/releases/latest)
<img src="https://img.shields.io/badge/python-3.10 ~ 3.13 -blue.svg" alt="python">
[简体中文](README.md) / [English](README_EN.md) / [日本語](README_JP.md)
<a href="https://langbot.app">ホーム</a>
<a href="https://docs.langbot.app/en/insight/guide.html">デプロイ</a>
<a href="https://docs.langbot.app/en/plugin/plugin-intro.html">プラグイン</a>
<a href="https://github.com/langbot-app/LangBot/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">プラグインの提出</a>
</div>
</p>
## ✨ 機能
- 💬 LLM / エージェントとのチャット: 複数のLLMをサポートし、グループチャットとプライベートチャットに対応。マルチラウンドの会話、ツールの呼び出し、マルチモーダル機能をサポート。 [Dify](https://dify.ai) と深く統合。現在、QQ、QQ チャンネル、WeChat、個人 WeChat、Lark、DingTalk、Discord、Telegram など、複数のプラットフォームをサポートしています。
- 🛠️ 高い安定性、豊富な機能: ネイティブのアクセス制御、レート制限、敏感な単語のフィルタリングなどのメカニズムをサポート。使いやすく、複数のデプロイ方法をサポート。
- 🧩 プラグイン拡張、活発なコミュニティ: イベント駆動、コンポーネント拡張などのプラグインメカニズムをサポート。適配 Anthropic [MCP プロトコル](https://modelcontextprotocol.io/);豊富なエコシステム、現在数百のプラグインが存在。
- 😻 Web UI: ブラウザを通じてLangBotインスタンスを管理することをサポート。
LangBot は、エージェント、RAG、MCP などの LLM アプリケーション機能を備えた、オープンソースの LLM ネイティブのインスタントメッセージングロボット開発プラットフォームです。世界中のインスタントメッセージングプラットフォームに適応し、豊富な API インターフェースを提供し、カスタム開発をサポートします。
## 📦 始め方
> [!IMPORTANT]
>
> - どのデプロイ方法を始める前に、必ず[新規ユーザーガイド](https://docs.langbot.app/zh/insight/guide.html)をお読みください。
> - すべてのドキュメントは中国語で提供されています。近い将来、i18nバージョンを提供する予定です。
> - Read [the auto-generated wiki on DeepWiki](https://deepwiki.com/RockChinQ/LangBot)。
#### Docker Compose デプロイ
Dockerに慣れているユーザーに適しています。[Dockerデプロイ](https://docs.langbot.app/zh/deploy/langbot/docker.html)のドキュメントを参照してください。
```bash
git clone https://github.com/langbot-app/LangBot
cd LangBot
docker compose up -d
```
#### BTPanelでのワンクリックデプロイ
http://localhost:5300 にアクセスして使用を開始します。
LangBotはBTPanelにリストされています。BTPanelをインストールしている場合は、[ドキュメント](https://docs.langbot.app/zh/deploy/langbot/one-click/bt.html)を使用して使用できます
詳細なドキュメントは[Dockerデプロイ](https://docs.langbot.app/en/deploy/langbot/docker.html)を参照してください
#### Panelでのワンクリックデプロイ
LangBotはBTPanelにリストされています。BTPanelをインストールしている場合は、[ドキュメント](https://docs.langbot.app/en/deploy/langbot/one-click/bt.html)を使用して使用できます。
#### Zeaburクラウドデプロイ
コミュニティが提供するZeaburテンプレート。
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/zh-CN/templates/ZKTBDH)
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/en-US/templates/ZKTBDH)
#### Railwayクラウドデプロイ
@@ -64,17 +53,27 @@ LangBotはBTPanelにリストされています。BTPanelをインストール
#### その他のデプロイ方法
リリースバージョンを直接使用して実行します。[手動デプロイ](https://docs.langbot.app/zh/deploy/langbot/manual.html)のドキュメントを参照してください。
リリースバージョンを直接使用して実行します。[手動デプロイ](https://docs.langbot.app/en/deploy/langbot/manual.html)のドキュメントを参照してください。
## 📸 デモ
## 😎 最新情報を入手
<img alt="返信効果(インターネットプラグイン付き)" src="https://docs.langbot.app/QChatGPT-0516.png" width="500px"/>
リポジトリの右上にある Star と Watch ボタンをクリックして、最新の更新を取得してください。
- WebUIデモ: https://demo.langbot.dev/
![star gif](https://docs.langbot.app/star.gif)
## ✨ 機能
- 💬 LLM / エージェントとのチャット: 複数のLLMをサポートし、グループチャットとプライベートチャットに対応。マルチラウンドの会話、ツールの呼び出し、マルチモーダル、ストリーミング出力機能をサポート、RAG知識ベースを組み込み、[Dify](https://dify.ai) と深く統合。
- 🤖 多プラットフォーム対応: 現在、QQ、QQ チャンネル、WeChat、個人 WeChat、Lark、DingTalk、Discord、Telegram など、複数のプラットフォームをサポートしています。
- 🛠️ 高い安定性、豊富な機能: ネイティブのアクセス制御、レート制限、敏感な単語のフィルタリングなどのメカニズムをサポート。使いやすく、複数のデプロイ方法をサポート。複数のパイプライン設定をサポートし、異なるボットを異なる用途に使用できます。
- 🧩 プラグイン拡張、活発なコミュニティ: イベント駆動、コンポーネント拡張などのプラグインメカニズムをサポート。適配 Anthropic [MCP プロトコル](https://modelcontextprotocol.io/);豊富なエコシステム、現在数百のプラグインが存在。
- 😻 Web UI: ブラウザを通じてLangBotインスタンスを管理することをサポート。
詳細な仕様については、[ドキュメント](https://docs.langbot.app/en/insight/features.html)を参照してください。
または、デモ環境にアクセスしてください: https://demo.langbot.dev/
- ログイン情報: メール: `demo@langbot.app` パスワード: `langbot123456`
- 注意: WebUIの効果のみを示しています。公開環境では機密情報を入力しないでください。
## 🔌 コンポーネントの互換性
- 注意: WebUI のデモンストレーションのみの場合、公開環境では機密情報を入力しないでください。
### メッセージプラットフォーム
@@ -84,16 +83,12 @@ LangBotはBTPanelにリストされています。BTPanelをインストール
| QQ公式API | ✅ | |
| WeCom | ✅ | |
| WeComCS | ✅ | |
| 個人WeChat | ✅ | [Gewechat](https://github.com/Devo919/Gewechat)を使用して接続 |
| 個人WeChat | ✅ | |
| Lark | ✅ | |
| DingTalk | ✅ | |
| Discord | ✅ | |
| Telegram | ✅ | |
| Slack | ✅ | |
| LINE | 🚧 | |
| WhatsApp | 🚧 | |
🚧: 開発中
### LLMs
@@ -105,7 +100,10 @@ LangBotはBTPanelにリストされています。BTPanelをインストール
| [Anthropic](https://www.anthropic.com/) | ✅ | |
| [xAI](https://x.ai/) | ✅ | |
| [Zhipu AI](https://open.bigmodel.cn/) | ✅ | |
| [CompShare](https://www.compshare.cn/?ytag=GPU_YY-gh_langbot) | ✅ | 大模型とGPUリソースプラットフォーム |
| [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | ✅ | 大模型とGPUリソースプラットフォーム |
| [302.AI](https://share.302.ai/SuTG99) | ✅ | LLMゲートウェイ(MaaS) |
| [Google Gemini](https://aistudio.google.com/prompts/new_chat) | ✅ | |
| [Dify](https://dify.ai) | ✅ | LLMOpsプラットフォーム |
| [Ollama](https://ollama.com/) | ✅ | ローカルLLM実行プラットフォーム |
| [LMStudio](https://lmstudio.ai/) | ✅ | ローカルLLM実行プラットフォーム |
@@ -118,8 +116,8 @@ LangBotはBTPanelにリストされています。BTPanelをインストール
## 🤝 コミュニティ貢献
LangBot への貢献に対して、以下の [コード貢献者](https://github.com/RockChinQ/LangBot/graphs/contributors) とコミュニティの他のメンバーに感謝します。
LangBot への貢献に対して、以下の [コード貢献者](https://github.com/langbot-app/LangBot/graphs/contributors) とコミュニティの他のメンバーに感謝します。
<a href="https://github.com/RockChinQ/LangBot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=RockChinQ/LangBot" />
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
</a>

139
README_TW.md Normal file
View File

@@ -0,0 +1,139 @@
<p align="center">
<a href="https://langbot.app">
<img src="https://docs.langbot.app/social_zh.png" alt="LangBot"/>
</a>
<div align="center"><a href="https://hellogithub.com/repository/langbot-app/LangBot" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=5ce8ae2aa4f74316bf393b57b952433c&claim_uid=gtmc6YWjMZkT21R" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
[English](README_EN.md) / [简体中文](README.md) / 繁體中文 / [日本語](README_JP.md) / (PR for your language)
[![Discord](https://img.shields.io/discord/1335141740050649118?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb)](https://discord.gg/wdNEHETs87)
[![QQ Group](https://img.shields.io/badge/%E7%A4%BE%E5%8C%BAQQ%E7%BE%A4-966235608-blue)](https://qm.qq.com/q/JLi38whHum)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/langbot-app/LangBot)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/langbot-app/LangBot)](https://github.com/langbot-app/LangBot/releases/latest)
<img src="https://img.shields.io/badge/python-3.10 ~ 3.13 -blue.svg" alt="python">
[![star](https://gitcode.com/RockChinQ/LangBot/star/badge.svg)](https://gitcode.com/RockChinQ/LangBot)
<a href="https://langbot.app">主頁</a>
<a href="https://docs.langbot.app/zh/insight/guide.html">部署文件</a>
<a href="https://docs.langbot.app/zh/plugin/plugin-intro.html">外掛介紹</a>
<a href="https://github.com/langbot-app/LangBot/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">提交外掛</a>
</div>
</p>
LangBot 是一個開源的大語言模型原生即時通訊機器人開發平台,旨在提供開箱即用的 IM 機器人開發體驗,具有 Agent、RAG、MCP 等多種 LLM 應用功能,適配全球主流即時通訊平台,並提供豐富的 API 介面,支援自定義開發。
## 📦 開始使用
#### Docker Compose 部署
```bash
git clone https://github.com/langbot-app/LangBot
cd LangBot
docker compose up -d
```
訪問 http://localhost:5300 即可開始使用。
詳細文件[Docker 部署](https://docs.langbot.app/zh/deploy/langbot/docker.html)。
#### 寶塔面板部署
已上架寶塔面板,若您已安裝寶塔面板,可以根據[文件](https://docs.langbot.app/zh/deploy/langbot/one-click/bt.html)使用。
#### Zeabur 雲端部署
社群貢獻的 Zeabur 模板。
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/zh-CN/templates/ZKTBDH)
#### Railway 雲端部署
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.app/template/yRrAyL?referralCode=vogKPF)
#### 手動部署
直接使用發行版運行,查看文件[手動部署](https://docs.langbot.app/zh/deploy/langbot/manual.html)。
## 😎 保持更新
點擊倉庫右上角 Star 和 Watch 按鈕,獲取最新動態。
![star gif](https://docs.langbot.app/star.gif)
## ✨ 特性
- 💬 大模型對話、Agent支援多種大模型適配群聊和私聊具有多輪對話、工具調用、多模態、流式輸出能力自帶 RAG知識庫實現並深度適配 [Dify](https://dify.ai)。
- 🤖 多平台支援:目前支援 QQ、QQ頻道、企業微信、個人微信、飛書、Discord、Telegram 等平台。
- 🛠️ 高穩定性、功能完備:原生支援訪問控制、限速、敏感詞過濾等機制;配置簡單,支援多種部署方式。支援多流水線配置,不同機器人用於不同應用場景。
- 🧩 外掛擴展、活躍社群:支援事件驅動、組件擴展等外掛機制;適配 Anthropic [MCP 協議](https://modelcontextprotocol.io/);目前已有數百個外掛。
- 😻 Web 管理面板:支援通過瀏覽器管理 LangBot 實例,不再需要手動編寫配置文件。
詳細規格特性請訪問[文件](https://docs.langbot.app/zh/insight/features.html)。
或訪問 demo 環境https://demo.langbot.dev/
- 登入資訊:郵箱:`demo@langbot.app` 密碼:`langbot123456`
- 注意:僅展示 WebUI 效果,公開環境,請不要在其中填入您的任何敏感資訊。
### 訊息平台
| 平台 | 狀態 | 備註 |
| --- | --- | --- |
| QQ 個人號 | ✅ | QQ 個人號私聊、群聊 |
| QQ 官方機器人 | ✅ | QQ 官方機器人,支援頻道、私聊、群聊 |
| 微信 | ✅ | |
| 企微對外客服 | ✅ | |
| 微信公眾號 | ✅ | |
| Lark | ✅ | |
| DingTalk | ✅ | |
| Discord | ✅ | |
| Telegram | ✅ | |
| Slack | ✅ | |
### 大模型能力
| 模型 | 狀態 | 備註 |
| --- | --- | --- |
| [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 介面格式模型 |
| [DeepSeek](https://www.deepseek.com/) | ✅ | |
| [Moonshot](https://www.moonshot.cn/) | ✅ | |
| [Anthropic](https://www.anthropic.com/) | ✅ | |
| [xAI](https://x.ai/) | ✅ | |
| [智譜AI](https://open.bigmodel.cn/) | ✅ | |
| [優雲智算](https://www.compshare.cn/?ytag=GPU_YY-gh_langbot) | ✅ | 大模型和 GPU 資源平台 |
| [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | ✅ | 大模型和 GPU 資源平台 |
| [302.AI](https://share.302.ai/SuTG99) | ✅ | 大模型聚合平台 |
| [Google Gemini](https://aistudio.google.com/prompts/new_chat) | ✅ | |
| [Dify](https://dify.ai) | ✅ | LLMOps 平台 |
| [Ollama](https://ollama.com/) | ✅ | 本地大模型運行平台 |
| [LMStudio](https://lmstudio.ai/) | ✅ | 本地大模型運行平台 |
| [GiteeAI](https://ai.gitee.com/) | ✅ | 大模型介面聚合平台 |
| [SiliconFlow](https://siliconflow.cn/) | ✅ | 大模型聚合平台 |
| [阿里雲百煉](https://bailian.console.aliyun.com/) | ✅ | 大模型聚合平台, LLMOps 平台 |
| [火山方舟](https://console.volcengine.com/ark/region:ark+cn-beijing/model?vendor=Bytedance&view=LIST_VIEW) | ✅ | 大模型聚合平台, LLMOps 平台 |
| [ModelScope](https://modelscope.cn/docs/model-service/API-Inference/intro) | ✅ | 大模型聚合平台 |
| [MCP](https://modelcontextprotocol.io/) | ✅ | 支援通過 MCP 協議獲取工具 |
### TTS
| 平台/模型 | 備註 |
| --- | --- |
| [FishAudio](https://fish.audio/zh-CN/discovery/) | [外掛](https://github.com/the-lazy-me/NewChatVoice) |
| [海豚 AI](https://www.ttson.cn/?source=thelazy) | [外掛](https://github.com/the-lazy-me/NewChatVoice) |
| [AzureTTS](https://portal.azure.com/) | [外掛](https://github.com/Ingnaryk/LangBot_AzureTTS) |
### 文生圖
| 平台/模型 | 備註 |
| --- | --- |
| 阿里雲百煉 | [外掛](https://github.com/Thetail001/LangBot_BailianTextToImagePlugin)
## 😘 社群貢獻
感謝以下[程式碼貢獻者](https://github.com/langbot-app/LangBot/graphs/contributors)和社群裡其他成員對 LangBot 的貢獻:
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
</a>

View File

@@ -4,7 +4,7 @@ metadata:
name: builtin-components
label:
en_US: Builtin Components
zh_CN: 内置组件
zh_Hans: 内置组件
spec:
components:
ComponentTemplate:

View File

@@ -1,4 +1,4 @@
from v1 import client
from v1 import client # type: ignore
import asyncio
@@ -8,19 +8,13 @@ import json
class TestDifyClient:
async def test_chat_messages(self):
cln = client.AsyncDifyServiceClient(
api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL')
)
cln = client.AsyncDifyServiceClient(api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL'))
async for chunk in cln.chat_messages(
inputs={}, query='调用工具查看现在几点?', user='test'
):
async for chunk in cln.chat_messages(inputs={}, query='调用工具查看现在几点?', user='test'):
print(json.dumps(chunk, ensure_ascii=False, indent=4))
async def test_upload_file(self):
cln = client.AsyncDifyServiceClient(
api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL')
)
cln = client.AsyncDifyServiceClient(api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL'))
file_bytes = open('img.png', 'rb').read()
@@ -32,9 +26,7 @@ class TestDifyClient:
print(json.dumps(resp, ensure_ascii=False, indent=4))
async def test_workflow_run(self):
cln = client.AsyncDifyServiceClient(
api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL')
)
cln = client.AsyncDifyServiceClient(api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL'))
# resp = await cln.workflow_run(inputs={}, user="test")
# # print(json.dumps(resp, ensure_ascii=False, indent=4))

View File

@@ -1,10 +1,11 @@
import asyncio
import dingtalk_stream
import dingtalk_stream # type: ignore
from dingtalk_stream import AckMessage
class EchoTextHandler(dingtalk_stream.ChatbotHandler):
def __init__(self, client):
super().__init__() # Call parent class initializer to set up logger
self.msg_id = ''
self.incoming_message = None
self.client = client # 用于更新 DingTalkClient 中的 incoming_message
@@ -26,9 +27,3 @@ class EchoTextHandler(dingtalk_stream.ChatbotHandler):
await asyncio.sleep(0.1) # 异步等待,避免阻塞
return self.incoming_message
async def get_dingtalk_client(client_id, client_secret):
from api import DingTalkClient # 延迟导入,避免循环导入
return DingTalkClient(client_id, client_secret)

View File

@@ -2,7 +2,7 @@ import base64
import json
import time
from typing import Callable
import dingtalk_stream
import dingtalk_stream # type: ignore
from .EchoHandler import EchoTextHandler
from .dingtalkevent import DingTalkEvent
import httpx
@@ -17,6 +17,7 @@ class DingTalkClient:
robot_name: str,
robot_code: str,
markdown_card: bool,
logger: None,
):
"""初始化 WebSocket 连接并自动启动"""
self.credential = dingtalk_stream.Credential(client_id, client_secret)
@@ -34,6 +35,7 @@ class DingTalkClient:
self.robot_code = robot_code
self.access_token_expiry_time = ''
self.markdown_card = markdown_card
self.logger = logger
async def get_access_token(self):
url = 'https://api.dingtalk.com/v1.0/oauth2/accessToken'
@@ -47,8 +49,8 @@ class DingTalkClient:
self.access_token = response_data.get('accessToken')
expires_in = int(response_data.get('expireIn', 7200))
self.access_token_expiry_time = time.time() + expires_in - 60
except Exception as e:
raise Exception(e)
except Exception:
await self.logger.error('failed to get access token in dingtalk')
async def is_token_expired(self):
"""检查token是否过期"""
@@ -73,7 +75,7 @@ class DingTalkClient:
result = response.json()
download_url = result.get('downloadUrl')
else:
raise Exception(f'Error: {response.status_code}, {response.text}')
await self.logger.error(f'failed to get download url: {response.json()}')
if download_url:
return await self.download_url_to_base64(download_url)
@@ -84,10 +86,11 @@ class DingTalkClient:
if response.status_code == 200:
file_bytes = response.content
base64_str = base64.b64encode(file_bytes).decode('utf-8') # 返回字符串格式
return base64_str
mime_type = response.headers.get('Content-Type', 'application/octet-stream')
base64_str = base64.b64encode(file_bytes).decode('utf-8')
return f'data:{mime_type};base64,{base64_str}'
else:
raise Exception('获取文件失败')
await self.logger.error(f'failed to get files: {response.json()}')
async def get_audio_url(self, download_code: str):
if not await self.check_access_token():
@@ -103,7 +106,7 @@ class DingTalkClient:
if download_url:
return await self.download_url_to_base64(download_url)
else:
raise Exception('获取音频失败')
await self.logger.error(f'failed to get audio: {response.json()}')
else:
raise Exception(f'Error: {response.status_code}, {response.text}')
@@ -115,10 +118,17 @@ 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:
if at:
self.EchoTextHandler.reply_markdown(
title=self.robot_name + '的回答',
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,
)
@@ -184,6 +194,9 @@ class DingTalkClient:
del copy_message_data['IncomingMessage']
# print("message_data:", json.dumps(copy_message_data, indent=4, ensure_ascii=False))
except Exception:
if self.logger:
await self.logger.error(f'Error in get_message: {traceback.format_exc()}')
else:
traceback.print_exc()
return message_data
@@ -207,9 +220,12 @@ class DingTalkClient:
}
try:
async with httpx.AsyncClient() as client:
await client.post(url, headers=headers, json=data)
response = await client.post(url, headers=headers, json=data)
if response.status_code == 200:
return
except Exception:
traceback.print_exc()
await self.logger.error(f'failed to send proactive massage to person: {traceback.format_exc()}')
raise Exception(f'failed to send proactive massage to person: {traceback.format_exc()}')
async def send_proactive_message_to_group(self, target_id: str, content: str):
if not await self.check_access_token():
@@ -230,9 +246,49 @@ class DingTalkClient:
}
try:
async with httpx.AsyncClient() as client:
await client.post(url, headers=headers, json=data)
response = await client.post(url, headers=headers, json=data)
if response.status_code == 200:
return
except Exception:
traceback.print_exc()
await self.logger.error(f'failed to send proactive massage to group: {traceback.format_exc()}')
raise Exception(f'failed to send proactive massage to group: {traceback.format_exc()}')
async def create_and_card(
self, temp_card_id: str, incoming_message: dingtalk_stream.ChatbotMessage, quote_origin: bool = False
):
content_key = 'content'
card_data = {content_key: ''}
card_instance = dingtalk_stream.AICardReplier(self.client, incoming_message)
# print(card_instance)
# 先投放卡片: https://open.dingtalk.com/document/orgapp/create-and-deliver-cards
card_instance_id = await card_instance.async_create_and_deliver_card(
temp_card_id,
card_data,
)
return card_instance, card_instance_id
async def send_card_message(self, card_instance, card_instance_id: str, content: str, is_final: bool):
content_key = 'content'
try:
await card_instance.async_streaming(
card_instance_id,
content_key=content_key,
content_value=content,
append=False,
finished=is_final,
failed=False,
)
except Exception as e:
self.logger.exception(e)
await card_instance.async_streaming(
card_instance_id,
content_key=content_key,
content_value='',
append=False,
finished=is_final,
failed=True,
)
async def start(self):
"""启动 WebSocket 连接,监听消息"""

View File

@@ -1,5 +1,5 @@
from typing import Dict, Any, Optional
import dingtalk_stream
import dingtalk_stream # type: ignore
class DingTalkEvent(dict):

View File

@@ -1,7 +1,7 @@
# 微信公众号的加解密算法与企业微信一样,所以直接使用企业微信的加解密算法文件
import time
import traceback
from ..wecom_api.WXBizMsgCrypt3 import WXBizMsgCrypt
from libs.wecom_api.WXBizMsgCrypt3 import WXBizMsgCrypt
import xml.etree.ElementTree as ET
from quart import Quart, request
import hashlib
@@ -23,7 +23,7 @@ xml_template = """
class OAClient:
def __init__(self, token: str, EncodingAESKey: str, AppID: str, Appsecret: str):
def __init__(self, token: str, EncodingAESKey: str, AppID: str, Appsecret: str, logger: None):
self.token = token
self.aes = EncodingAESKey
self.appid = AppID
@@ -43,6 +43,7 @@ class OAClient:
self.access_token_expiry_time = None
self.msg_id_map = {}
self.generated_content = {}
self.logger = logger
async def handle_callback_request(self):
try:
@@ -54,6 +55,7 @@ class OAClient:
echostr = request.args.get('echostr', '')
msg_signature = request.args.get('msg_signature', '')
if msg_signature is None:
await self.logger.error('msg_signature不在请求体中')
raise Exception('msg_signature不在请求体中')
if request.method == 'GET':
@@ -64,6 +66,7 @@ class OAClient:
if check_signature == signature:
return echostr # 验证成功返回echostr
else:
await self.logger.error('拒绝请求')
raise Exception('拒绝请求')
elif request.method == 'POST':
encryt_msg = await request.data
@@ -72,6 +75,7 @@ class OAClient:
xml_msg = xml_msg.decode('utf-8')
if ret != 0:
await self.logger.error('消息解密失败')
raise Exception('消息解密失败')
message_data = await self.get_message(xml_msg)
@@ -114,6 +118,7 @@ class OAClient:
return ''
except Exception:
await self.logger.error(f'handle_callback_request失败: {traceback.format_exc()}')
traceback.print_exc()
async def get_message(self, xml_msg: str):
@@ -176,6 +181,7 @@ class OAClientForLongerResponse:
AppID: str,
Appsecret: str,
LoadingMessage: str,
logger: None,
):
self.token = token
self.aes = EncodingAESKey
@@ -197,6 +203,7 @@ class OAClientForLongerResponse:
self.loading_message = LoadingMessage
self.msg_queue = {}
self.user_msg_queue = {}
self.logger = logger
async def handle_callback_request(self):
try:
@@ -207,6 +214,7 @@ class OAClientForLongerResponse:
msg_signature = request.args.get('msg_signature', '')
if msg_signature is None:
await self.logger.error('msg_signature不在请求体中')
raise Exception('msg_signature不在请求体中')
if request.method == 'GET':
@@ -221,6 +229,7 @@ class OAClientForLongerResponse:
xml_msg = xml_msg.decode('utf-8')
if ret != 0:
await self.logger.error('消息解密失败')
raise Exception('消息解密失败')
# 解析 XML
@@ -270,6 +279,7 @@ class OAClientForLongerResponse:
return response_xml
except Exception:
await self.logger.error(f'handle_callback_request失败: {traceback.format_exc()}')
traceback.print_exc()
async def get_message(self, xml_msg: str):

View File

@@ -34,7 +34,7 @@ def handle_validation(body: dict, bot_secret: str):
class QQOfficialClient:
def __init__(self, secret: str, token: str, app_id: str):
def __init__(self, secret: str, token: str, app_id: str, logger: None):
self.app = Quart(__name__)
self.app.add_url_rule(
'/callback/command',
@@ -49,6 +49,7 @@ class QQOfficialClient:
self.base_url = 'https://api.sgroup.qq.com'
self.access_token = ''
self.access_token_expiry_time = None
self.logger = logger
async def check_access_token(self):
"""检查access_token是否存在"""
@@ -77,6 +78,7 @@ class QQOfficialClient:
if access_token:
self.access_token = access_token
except Exception as e:
await self.logger.error(f'获取access_token失败: {response_data}')
raise Exception(f'获取access_token失败: {e}')
async def handle_callback_request(self):
@@ -102,7 +104,7 @@ class QQOfficialClient:
return {'code': 0, 'message': 'success'}
except Exception as e:
traceback.print_exc()
await self.logger.error(f'Error in handle_callback_request: {traceback.format_exc()}')
return {'error': str(e)}, 400
async def run_task(self, host: str, port: int, *args, **kwargs):
@@ -178,9 +180,11 @@ class QQOfficialClient:
'msg_id': msg_id,
}
response = await client.post(url, headers=headers, json=data)
response_data = response.json()
if response.status_code == 200:
return
else:
await self.logger.error(f'发送私聊消息失败: {response_data}')
raise ValueError(response)
async def send_group_text_msg(self, group_openid: str, content: str, msg_id: str):
@@ -203,6 +207,7 @@ class QQOfficialClient:
if response.status_code == 200:
return
else:
await self.logger.error(f'发送群聊消息失败:{response.json()}')
raise Exception(response.read().decode())
async def send_channle_group_text_msg(self, channel_id: str, content: str, msg_id: str):
@@ -225,6 +230,7 @@ class QQOfficialClient:
if response.status_code == 200:
return True
else:
await self.logger.error(f'发送频道群聊消息失败: {response.json()}')
raise Exception(response)
async def send_channle_private_text_msg(self, guild_id: str, content: str, msg_id: str):
@@ -247,6 +253,7 @@ class QQOfficialClient:
if response.status_code == 200:
return True
else:
await self.logger.error(f'发送频道私聊消息失败: {response.json()}')
raise Exception(response)
async def is_token_expired(self):

View File

@@ -1,4 +1,5 @@
import json
import traceback
from quart import Quart, jsonify, request
from slack_sdk.web.async_client import AsyncWebClient
from .slackevent import SlackEvent
@@ -7,7 +8,7 @@ from pkg.platform.types import events as platform_events
class SlackClient:
def __init__(self, bot_token: str, signing_secret: str):
def __init__(self, bot_token: str, signing_secret: str, logger: None):
self.bot_token = bot_token
self.signing_secret = signing_secret
self.app = Quart(__name__)
@@ -19,6 +20,7 @@ class SlackClient:
'example': [],
}
self.bot_user_id = None # 避免机器人回复自己的消息
self.logger = logger
async def handle_callback_request(self):
try:
@@ -49,6 +51,7 @@ class SlackClient:
return jsonify({'status': 'ok'})
except Exception as e:
await self.logger.error(f'Error in handle_callback_request: {traceback.format_exc()}')
raise (e)
async def _handle_message(self, event: SlackEvent):
@@ -78,6 +81,7 @@ class SlackClient:
self.bot_user_id = response['message']['bot_id']
return
except Exception as e:
await self.logger.error(f'Error in send_message: {e}')
raise e
async def send_message_to_one(self, text: str, user_id: str):
@@ -88,6 +92,7 @@ class SlackClient:
return
except Exception as e:
await self.logger.error(f'Error in send_message: {traceback.format_exc()}')
raise e
async def run_task(self, host: str, port: int, *args, **kwargs):

201
libs/wechatpad_api/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,38 @@
# wechatpad-python
## 此项目时准备对接wechatpadpro 的pythonsdk
## 未完工接口
* 关于好友的接口
* 关于群管理的接口
* 关于下载的接口
* 关于用户的部分接口
* 关于消息的部分接口
* 关于支付的
* 关于朋友圈的
* 关于标签的
* 关于收藏的
* 暂时只写了一部分接口
## 已完工接口
1. 获取普通token
2. 登录二维码(只是返回数据,暂时还未打印二维码)
3. 获取登录状态
4. 唤醒登录
5. 退出登录
6. 获取用户信息
7. 获取用户二维码
8. 上传用户头像
9. 获取设备信息
10. 发送文本消息
11. 发送图片消息
12. 发送语音消息
13. 发送app消息
14. 发送emoji消息
15. 发送名片消息
16. 撤回消息

View File

@@ -0,0 +1 @@
from .client import WeChatPadClient as WeChatPadClient

View File

@@ -0,0 +1,12 @@
from libs.wechatpad_api.util.http_util import post_json
class ChatRoomApi:
def __init__(self, base_url, token):
self.base_url = base_url
self.token = token
def get_chatroom_member_detail(self, chatroom_name):
params = {'ChatRoomName': chatroom_name}
url = self.base_url + '/group/GetChatroomMemberDetail'
return post_json(url, token=self.token, data=params)

View File

@@ -0,0 +1,30 @@
from libs.wechatpad_api.util.http_util import post_json
import httpx
import base64
class DownloadApi:
def __init__(self, base_url, token):
self.base_url = base_url
self.token = token
def send_download(self, aeskey, file_type, file_url):
json_data = {'AesKey': aeskey, 'FileType': file_type, 'FileURL': file_url}
url = self.base_url + '/message/SendCdnDownload'
return post_json(url, token=self.token, data=json_data)
def get_msg_voice(self, buf_id, length, new_msgid):
json_data = {'Bufid': buf_id, 'Length': length, 'NewMsgId': new_msgid, 'ToUserName': ''}
url = self.base_url + '/message/GetMsgVoice'
return post_json(url, token=self.token, data=json_data)
async def download_url_to_base64(self, download_url):
async with httpx.AsyncClient() as client:
response = await client.get(download_url)
if response.status_code == 200:
file_bytes = response.content
base64_str = base64.b64encode(file_bytes).decode('utf-8') # 返回字符串格式
return base64_str
else:
raise Exception('获取文件失败')

View File

@@ -0,0 +1,6 @@
class FriendApi:
"""联系人API类处理所有与联系人相关的操作"""
def __init__(self, base_url: str, token: str):
self.base_url = base_url
self.token = token

View File

@@ -0,0 +1,82 @@
from libs.wechatpad_api.util.http_util import post_json, get_json
class LoginApi:
def __init__(self, base_url: str, token: str = None, admin_key: str = None):
"""
Args:
base_url: 原始路径
token: token
admin_key: 管理员key
"""
self.base_url = base_url
self.token = token
# self.admin_key = admin_key
def get_token(self, admin_key, day: int = 365):
# 获取普通token
url = f'{self.base_url}/admin/GenAuthKey1'
json_data = {'Count': 1, 'Days': day}
return post_json(base_url=url, token=admin_key, data=json_data)
def get_login_qr(self, Proxy: str = ''):
"""
Args:
Proxy:异地使用时代理
Returns:json数据
"""
"""
{
"Code": 200,
"Data": {
"Key": "3141312",
"QrCodeUrl": "https://1231x/g6bMlv2dX8zwNbqE6-Zs",
"Txt": "建议返回data=之后内容自定义生成二维码",
"baseResp": {
"ret": 0,
"errMsg": {}
}
},
"Text": ""
}
"""
# 获取登录二维码
url = f'{self.base_url}/login/GetLoginQrCodeNew'
check = False
if Proxy != '':
check = True
json_data = {'Check': check, 'Proxy': Proxy}
return post_json(base_url=url, token=self.token, data=json_data)
def get_login_status(self):
# 获取登录状态
url = f'{self.base_url}/login/GetLoginStatus'
return get_json(base_url=url, token=self.token)
def logout(self):
# 退出登录
url = f'{self.base_url}/login/LogOut'
return post_json(base_url=url, token=self.token)
def wake_up_login(self, Proxy: str = ''):
# 唤醒登录
url = f'{self.base_url}/login/WakeUpLogin'
check = False
if Proxy != '':
check = True
json_data = {'Check': check, 'Proxy': ''}
return post_json(base_url=url, token=self.token, data=json_data)
def login(self, admin_key):
login_status = self.get_login_status()
if login_status['Code'] == 300 and login_status['Text'] == '你已退出微信':
print('token已经失效重新获取')
token_data = self.get_token(admin_key)
self.token = token_data['Data'][0]

View File

@@ -0,0 +1,80 @@
from libs.wechatpad_api.util.http_util import post_json
class MessageApi:
def __init__(self, base_url, token):
self.base_url = base_url
self.token = token
def post_text(self, to_wxid, content, ats: list = []):
"""
Args:
app_id: 微信id
to_wxid: 发送方的微信id
content: 内容
ats: at
Returns:
"""
url = self.base_url + '/message/SendTextMessage'
"""发送文字消息"""
json_data = {
'MsgItem': [
{'AtWxIDList': ats, 'ImageContent': '', 'MsgType': 0, 'TextContent': content, 'ToUserName': to_wxid}
]
}
return post_json(base_url=url, token=self.token, data=json_data)
def post_image(self, to_wxid, img_url, ats: list = []):
"""发送图片消息"""
# 这里好像可以尝试发送多个暂时未测试
json_data = {
'MsgItem': [
{'AtWxIDList': ats, 'ImageContent': img_url, 'MsgType': 0, 'TextContent': '', 'ToUserName': to_wxid}
]
}
url = self.base_url + '/message/SendImageMessage'
return post_json(base_url=url, token=self.token, data=json_data)
def post_voice(self, to_wxid, voice_data, voice_forma, voice_duration):
"""发送语音消息"""
json_data = {
'ToUserName': to_wxid,
'VoiceData': voice_data,
'VoiceFormat': voice_forma,
'VoiceSecond': voice_duration,
}
url = self.base_url + '/message/SendVoice'
return post_json(base_url=url, token=self.token, data=json_data)
def post_name_card(self, alias, to_wxid, nick_name, name_card_wxid, flag):
"""发送名片消息"""
param = {
'CardAlias': alias,
'CardFlag': flag,
'CardNickName': nick_name,
'CardWxId': name_card_wxid,
'ToUserName': to_wxid,
}
url = f'{self.base_url}/message/ShareCardMessage'
return post_json(base_url=url, token=self.token, data=param)
def post_emoji(self, to_wxid, emoji_md5, emoji_size: int = 0):
"""发送emoji消息"""
json_data = {'EmojiList': [{'EmojiMd5': emoji_md5, 'EmojiSize': emoji_size, 'ToUserName': to_wxid}]}
url = f'{self.base_url}/message/SendEmojiMessage'
return post_json(base_url=url, token=self.token, data=json_data)
def post_app_msg(self, to_wxid, xml_data, contenttype: int = 0):
"""发送appmsg消息"""
json_data = {'AppList': [{'ContentType': contenttype, 'ContentXML': xml_data, 'ToUserName': to_wxid}]}
url = f'{self.base_url}/message/SendAppMessage'
return post_json(base_url=url, token=self.token, data=json_data)
def revoke_msg(self, to_wxid, msg_id, new_msg_id, create_time):
"""撤回消息"""
param = {'ClientMsgId': msg_id, 'CreateTime': create_time, 'NewMsgId': new_msg_id, 'ToUserName': to_wxid}
url = f'{self.base_url}/message/RevokeMsg'
return post_json(base_url=url, token=self.token, data=param)

View File

@@ -0,0 +1,30 @@
from libs.wechatpad_api.util.http_util import post_json, async_request, get_json
class UserApi:
def __init__(self, base_url, token):
self.base_url = base_url
self.token = token
def get_profile(self):
"""获取个人资料"""
url = f'{self.base_url}/user/GetProfile'
return get_json(base_url=url, token=self.token)
def get_qr_code(self, recover: bool = True, style: int = 8):
"""获取自己的二维码"""
param = {'Recover': recover, 'Style': style}
url = f'{self.base_url}/user/GetMyQRCode'
return post_json(base_url=url, token=self.token, data=param)
def get_safety_info(self):
"""获取设备记录"""
url = f'{self.base_url}/equipment/GetSafetyInfo'
return post_json(base_url=url, token=self.token)
async def update_head_img(self, head_img_base64):
"""修改头像"""
param = {'Base64': head_img_base64}
url = f'{self.base_url}/user/UploadHeadImage'
return await async_request(base_url=url, token_key=self.token, json=param)

View File

@@ -0,0 +1,92 @@
from libs.wechatpad_api.api.login import LoginApi
from libs.wechatpad_api.api.friend import FriendApi
from libs.wechatpad_api.api.message import MessageApi
from libs.wechatpad_api.api.user import UserApi
from libs.wechatpad_api.api.downloadpai import DownloadApi
from libs.wechatpad_api.api.chatroom import ChatRoomApi
class WeChatPadClient:
def __init__(self, base_url, token, logger=None):
self._login_api = LoginApi(base_url, token)
self._friend_api = FriendApi(base_url, token)
self._message_api = MessageApi(base_url, token)
self._user_api = UserApi(base_url, token)
self._download_api = DownloadApi(base_url, token)
self._chatroom_api = ChatRoomApi(base_url, token)
self.logger = logger
def get_token(self, admin_key, day: int):
"""获取token"""
return self._login_api.get_token(admin_key, day)
def get_login_qr(self, Proxy: str = ''):
"""登录二维码"""
return self._login_api.get_login_qr(Proxy=Proxy)
def awaken_login(self, Proxy: str = ''):
"""唤醒登录"""
return self._login_api.wake_up_login(Proxy=Proxy)
def log_out(self):
"""退出登录"""
return self._login_api.logout()
def get_login_status(self):
"""获取登录状态"""
return self._login_api.get_login_status()
def send_text_message(self, to_wxid, message, ats: list = []):
"""发送文本消息"""
return self._message_api.post_text(to_wxid, message, ats)
def send_image_message(self, to_wxid, img_url, ats: list = []):
"""发送图片消息"""
return self._message_api.post_image(to_wxid, img_url, ats)
def send_voice_message(self, to_wxid, voice_data, voice_forma, voice_duration):
"""发送音频消息"""
return self._message_api.post_voice(to_wxid, voice_data, voice_forma, voice_duration)
def send_app_message(self, to_wxid, app_message, type):
"""发送app消息"""
return self._message_api.post_app_msg(to_wxid, app_message, type)
def send_emoji_message(self, to_wxid, emoji_md5, emoji_size):
"""发送emoji消息"""
return self._message_api.post_emoji(to_wxid, emoji_md5, emoji_size)
def revoke_msg(self, to_wxid, msg_id, new_msg_id, create_time):
"""撤回消息"""
return self._message_api.revoke_msg(to_wxid, msg_id, new_msg_id, create_time)
def get_profile(self):
"""获取用户信息"""
return self._user_api.get_profile()
def get_qr_code(self, recover: bool = True, style: int = 8):
"""获取用户二维码"""
return self._user_api.get_qr_code(recover=recover, style=style)
def get_safety_info(self):
"""获取设备信息"""
return self._user_api.get_safety_info()
def update_head_img(self, head_img_base64):
"""上传用户头像"""
return self._user_api.update_head_img(head_img_base64)
def cdn_download(self, aeskey, file_type, file_url):
"""cdn下载"""
return self._download_api.send_download(aeskey, file_type, file_url)
def get_msg_voice(self, buf_id, length, msgid):
"""下载语音"""
return self._download_api.get_msg_voice(buf_id, length, msgid)
async def download_base64(self, url):
return await self._download_api.download_url_to_base64(download_url=url)
def get_chatroom_member_detail(self, chatroom_name):
"""查看群成员详情"""
return self._chatroom_api.get_chatroom_member_detail(chatroom_name)

View File

View File

@@ -0,0 +1,78 @@
import requests
import aiohttp
def post_json(base_url, token, data=None):
headers = {'Content-Type': 'application/json'}
url = base_url + f'?key={token}'
try:
response = requests.post(url, json=data, headers=headers, timeout=60)
response.raise_for_status()
result = response.json()
if result:
return result
else:
raise RuntimeError(response.text)
except Exception as e:
print(f'http请求失败, url={url}, exception={e}')
raise RuntimeError(str(e))
def get_json(base_url, token):
headers = {'Content-Type': 'application/json'}
url = base_url + f'?key={token}'
try:
response = requests.get(url, headers=headers, timeout=60)
response.raise_for_status()
result = response.json()
if result:
return result
else:
raise RuntimeError(response.text)
except Exception as e:
print(f'http请求失败, url={url}, exception={e}')
raise RuntimeError(str(e))
async def async_request(
base_url: str,
token_key: str,
method: str = 'POST',
params: dict = None,
# headers: dict = None,
data: dict = None,
json: dict = None,
):
"""
通用异步请求函数
:param base_url: 请求URL
:param token_key: 请求token
:param method: HTTP方法 (GET, POST, PUT, DELETE等)
:param params: URL查询参数
# :param headers: 请求头
:param data: 表单数据
:param json: JSON数据
:return: 响应文本
"""
headers = {'Content-Type': 'application/json'}
url = f'{base_url}?key={token_key}'
async with aiohttp.ClientSession() as session:
async with session.request(
method=method, url=url, params=params, headers=headers, data=data, json=json
) as response:
response.raise_for_status() # 如果状态码不是200抛出异常
result = await response.json()
# print(result)
return result
# if result.get('Code') == 200:
#
# return await result
# else:
# raise RuntimeError("请求失败",response.text)

View File

@@ -0,0 +1,34 @@
import qrcode
def print_green(text):
print(f'\033[32m{text}\033[0m')
def print_yellow(text):
print(f'\033[33m{text}\033[0m')
def print_red(text):
print(f'\033[31m{text}\033[0m')
def make_and_print_qr(url):
"""生成并打印二维码
Args:
url: 需要生成二维码的URL字符串
Returns:
None
功能:
1. 在终端打印二维码的ASCII图形
2. 同时提供在线二维码生成链接作为备选
"""
print_green('请扫描下方二维码登录')
qr = qrcode.QRCode()
qr.add_data(url)
qr.make()
qr.print_ascii(invert=True)
print_green(f'也可以访问下方链接获取二维码:\nhttps://api.qrserver.com/v1/create-qr-code/?data={url}')

View File

@@ -3,6 +3,7 @@ from .WXBizMsgCrypt3 import WXBizMsgCrypt
import base64
import binascii
import httpx
import traceback
from quart import Quart
import xml.etree.ElementTree as ET
from typing import Callable, Dict, Any
@@ -19,6 +20,7 @@ class WecomClient:
token: str,
EncodingAESKey: str,
contacts_secret: str,
logger: None,
):
self.corpid = corpid
self.secret = secret
@@ -28,8 +30,8 @@ class WecomClient:
self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin'
self.access_token = ''
self.secret_for_contacts = contacts_secret
self.logger = logger
self.app = Quart(__name__)
self.wxcpt = WXBizMsgCrypt(self.token, self.aes, self.corpid)
self.app.add_url_rule(
'/callback/command',
'handle_callback',
@@ -55,6 +57,7 @@ class WecomClient:
if 'access_token' in data:
return data['access_token']
else:
await self.logger.error(f'获取accesstoken失败:{response.json()}')
raise Exception(f'未获取access token: {data}')
async def get_users(self):
@@ -126,6 +129,7 @@ class WecomClient:
response = await client.post(url, json=params)
data = response.json()
except Exception as e:
await self.logger.error(f'发送图片失败:{data}')
raise Exception('Failed to send image: ' + str(e))
# 企业微信错误码40014和42001代表accesstoken问题
@@ -160,6 +164,7 @@ class WecomClient:
self.access_token = await self.get_access_token(self.secret)
return await self.send_private_msg(user_id, agent_id, content)
if data['errcode'] != 0:
await self.logger.error(f'发送消息失败:{data}')
raise Exception('Failed to send message: ' + str(data))
async def handle_callback_request(self):
@@ -171,17 +176,20 @@ 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:
await self.logger.error('验证失败')
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:
await self.logger.error('消息解密失败')
raise Exception(f'消息解密失败,错误码: {ret}')
# 解析消息并处理
@@ -193,6 +201,7 @@ class WecomClient:
return 'success'
except Exception as e:
await self.logger.error(f'Error in handle_callback_request: {traceback.format_exc()}')
return f'Error processing request: {str(e)}', 400
async def run_task(self, host: str, port: int, *args, **kwargs):
@@ -291,6 +300,7 @@ class WecomClient:
except binascii.Error as e:
raise ValueError(f'Invalid base64 string: {str(e)}')
else:
await self.logger.error('Image对象出错')
raise ValueError('image对象出错')
# 设置 multipart/form-data 格式的文件
@@ -314,6 +324,7 @@ class WecomClient:
self.access_token = await self.get_access_token(self.secret)
media_id = await self.upload_to_work(image)
if data.get('errcode', 0) != 0:
await self.logger.error(f'上传图片失败:{data}')
raise Exception('failed to upload file')
media_id = data.get('media_id')

View File

@@ -13,7 +13,7 @@ import aiofiles
class WecomCSClient:
def __init__(self, corpid: str, secret: str, token: str, EncodingAESKey: str):
def __init__(self, corpid: str, secret: str, token: str, EncodingAESKey: str, logger: None):
self.corpid = corpid
self.secret = secret
self.access_token_for_contacts = ''
@@ -21,8 +21,8 @@ class WecomCSClient:
self.aes = EncodingAESKey
self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin'
self.access_token = ''
self.logger = logger
self.app = Quart(__name__)
self.wxcpt = WXBizMsgCrypt(self.token, self.aes, self.corpid)
self.app.add_url_rule(
'/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST']
)
@@ -187,6 +187,7 @@ class WecomCSClient:
self.access_token = await self.get_access_token(self.secret)
return await self.send_text_msg(open_kfid, external_userid, msgid, content)
if data['errcode'] != 0:
await self.logger.error(f'发送消息失败:{data}')
raise Exception('Failed to send message')
return data
@@ -198,17 +199,21 @@ class WecomCSClient:
msg_signature = request.args.get('msg_signature')
timestamp = request.args.get('timestamp')
nonce = request.args.get('nonce')
try:
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.corpid)
except Exception as e:
raise Exception(f'初始化失败,错误码: {e}')
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}')
@@ -221,6 +226,9 @@ class WecomCSClient:
return 'success'
except Exception as e:
if self.logger:
await self.logger.error(f'Error in handle_callback_request: {traceback.format_exc()}')
else:
traceback.print_exc()
return f'Error processing request: {str(e)}', 400

20
main.py
View File

@@ -1,4 +1,5 @@
import asyncio
import argparse
# LangBot 终端启动入口
# 在此层级解决依赖项检查。
# LangBot/main.py
@@ -10,12 +11,16 @@ asciiart = r"""
|____\__,_|_||_\__, |___/\___/\__|
|___/
⭐️开源地址: https://github.com/RockChinQ/LangBot
📖文档地址: https://docs.langbot.app
⭐️ Open Source 开源地址: https://github.com/langbot-app/LangBot
📖 Documentation 文档地址: https://docs.langbot.app
"""
async def main_entry(loop: asyncio.AbstractEventLoop):
parser = argparse.ArgumentParser(description='LangBot')
parser.add_argument('--skip-plugin-deps-check', action='store_true', help='跳过插件依赖项检查', default=False)
args = parser.parse_args()
print(asciiart)
import sys
@@ -28,13 +33,18 @@ 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
if not args.skip_plugin_deps_check:
await deps.precheck_plugin_deps()
# 检查pydantic版本如果没有 pydantic.v1则把 pydantic 映射为 v1
@@ -53,6 +63,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 +80,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 +96,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

@@ -11,10 +11,10 @@ from ....core import app
preregistered_groups: list[type[RouterGroup]] = []
"""RouterGroup 的预注册列表"""
"""Pre-registered list of RouterGroup"""
def group_class(name: str, path: str) -> None:
def group_class(name: str, path: str) -> typing.Callable[[typing.Type[RouterGroup]], typing.Type[RouterGroup]]:
"""注册一个 RouterGroup"""
def decorator(cls: typing.Type[RouterGroup]) -> typing.Type[RouterGroup]:
@@ -27,7 +27,7 @@ def group_class(name: str, path: str) -> None:
class AuthType(enum.Enum):
"""认证类型"""
"""Authentication type"""
NONE = 'none'
USER_TOKEN = 'user-token'
@@ -56,7 +56,7 @@ class RouterGroup(abc.ABC):
auth_type: AuthType = AuthType.USER_TOKEN,
**options: typing.Any,
) -> typing.Callable[[RouteCallable], RouteCallable]: # decorator
"""注册一个路由"""
"""Register a route"""
def decorator(f: RouteCallable) -> RouteCallable:
nonlocal rule
@@ -64,11 +64,11 @@ class RouterGroup(abc.ABC):
async def handler_error(*args, **kwargs):
if auth_type == AuthType.USER_TOKEN:
# Authorization头中获取token
# get token from Authorization header
token = quart.request.headers.get('Authorization', '').replace('Bearer ', '')
if not token:
return self.http_status(401, -1, '未提供有效的用户令牌')
return self.http_status(401, -1, 'No valid user token provided')
try:
user_email = await self.ap.user_service.verify_jwt_token(token)
@@ -76,9 +76,9 @@ class RouterGroup(abc.ABC):
# check if this account exists
user = await self.ap.user_service.get_user_by_email(user_email)
if not user:
return self.http_status(401, -1, '用户不存在')
return self.http_status(401, -1, 'User not found')
# 检查f是否接受user_email参数
# check if f accepts user_email parameter
if 'user_email' in f.__code__.co_varnames:
kwargs['user_email'] = user_email
except Exception as e:
@@ -86,10 +86,11 @@ class RouterGroup(abc.ABC):
try:
return await f(*args, **kwargs)
except Exception: # 自动 500
except Exception as e: # 自动 500
traceback.print_exc()
# return self.http_status(500, -2, str(e))
return self.http_status(500, -2, 'internal server error')
return self.http_status(500, -2, str(e))
new_f = handler_error
new_f.__name__ = (self.name + rule).replace('/', '__')
@@ -101,7 +102,7 @@ class RouterGroup(abc.ABC):
return decorator
def success(self, data: typing.Any = None) -> quart.Response:
"""返回一个 200 响应"""
"""Return a 200 response"""
return quart.jsonify(
{
'code': 0,
@@ -111,7 +112,7 @@ class RouterGroup(abc.ABC):
)
def fail(self, code: int, msg: str) -> quart.Response:
"""返回一个异常响应"""
"""Return an error response"""
return quart.jsonify(
{
@@ -120,6 +121,6 @@ class RouterGroup(abc.ABC):
}
)
def http_status(self, status: int, code: int, msg: str) -> quart.Response:
def http_status(self, status: int, code: int, msg: str) -> typing.Tuple[quart.Response, int]:
"""返回一个指定状态码的响应"""
return self.fail(code, msg), status
return (self.fail(code, msg), status)

View File

@@ -0,0 +1,46 @@
from __future__ import annotations
import quart
import mimetypes
import uuid
import asyncio
import quart.datastructures
from .. import group
@group.group_class('files', '/api/v1/files')
class FilesRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('/image/<image_key>', methods=['GET'], auth_type=group.AuthType.NONE)
async def _(image_key: str) -> quart.Response:
if not await self.ap.storage_mgr.storage_provider.exists(image_key):
return quart.Response(status=404)
image_bytes = await self.ap.storage_mgr.storage_provider.load(image_key)
mime_type = mimetypes.guess_type(image_key)[0]
if mime_type is None:
mime_type = 'image/jpeg'
return quart.Response(image_bytes, mimetype=mime_type)
@self.route('/documents', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _() -> quart.Response:
request = quart.request
# get file bytes from 'file'
file = (await request.files)['file']
assert isinstance(file, quart.datastructures.FileStorage)
file_bytes = await asyncio.to_thread(file.stream.read)
extension = file.filename.split('.')[-1]
file_name = file.filename.split('.')[0]
file_key = file_name + '_' + str(uuid.uuid4())[:8] + '.' + extension
# save file to storage
await self.ap.storage_mgr.storage_provider.save(file_key, file_bytes)
return self.success(
data={
'file_id': file_key,
}
)

View File

@@ -0,0 +1,90 @@
import quart
from ... import group
@group.group_class('knowledge_base', '/api/v1/knowledge/bases')
class KnowledgeBaseRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['POST', 'GET'])
async def handle_knowledge_bases() -> quart.Response:
if quart.request.method == 'GET':
knowledge_bases = await self.ap.knowledge_service.get_knowledge_bases()
return self.success(data={'bases': knowledge_bases})
elif quart.request.method == 'POST':
json_data = await quart.request.json
knowledge_base_uuid = await self.ap.knowledge_service.create_knowledge_base(json_data)
return self.success(data={'uuid': knowledge_base_uuid})
return self.http_status(405, -1, 'Method not allowed')
@self.route(
'/<knowledge_base_uuid>',
methods=['GET', 'DELETE', 'PUT'],
)
async def handle_specific_knowledge_base(knowledge_base_uuid: str) -> quart.Response:
if quart.request.method == 'GET':
knowledge_base = await self.ap.knowledge_service.get_knowledge_base(knowledge_base_uuid)
if knowledge_base is None:
return self.http_status(404, -1, 'knowledge base not found')
return self.success(
data={
'base': knowledge_base,
}
)
elif quart.request.method == 'PUT':
json_data = await quart.request.json
await self.ap.knowledge_service.update_knowledge_base(knowledge_base_uuid, json_data)
return self.success({})
elif quart.request.method == 'DELETE':
await self.ap.knowledge_service.delete_knowledge_base(knowledge_base_uuid)
return self.success({})
@self.route(
'/<knowledge_base_uuid>/files',
methods=['GET', 'POST'],
)
async def get_knowledge_base_files(knowledge_base_uuid: str) -> str:
if quart.request.method == 'GET':
files = await self.ap.knowledge_service.get_files_by_knowledge_base(knowledge_base_uuid)
return self.success(
data={
'files': files,
}
)
elif quart.request.method == 'POST':
json_data = await quart.request.json
file_id = json_data.get('file_id')
if not file_id:
return self.http_status(400, -1, 'File ID is required')
# 调用服务层方法将文件与知识库关联
task_id = await self.ap.knowledge_service.store_file(knowledge_base_uuid, file_id)
return self.success(
{
'task_id': task_id,
}
)
@self.route(
'/<knowledge_base_uuid>/files/<file_id>',
methods=['DELETE'],
)
async def delete_specific_file_in_kb(file_id: str, knowledge_base_uuid: str) -> str:
await self.ap.knowledge_service.delete_file(knowledge_base_uuid, file_id)
return self.success({})
@self.route(
'/<knowledge_base_uuid>/retrieve',
methods=['POST'],
)
async def retrieve_knowledge_base(knowledge_base_uuid: str) -> str:
json_data = await quart.request.json
query = json_data.get('query')
results = await self.ap.knowledge_service.retrieve_knowledge_base(knowledge_base_uuid, query)
return self.success(data={'results': results})

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import quart
from .. import group
from ... import group
@group.group_class('pipelines', '/api/v1/pipelines')
@@ -11,7 +11,11 @@ class PipelinesRouterGroup(group.RouterGroup):
@self.route('', methods=['GET', 'POST'])
async def _() -> str:
if quart.request.method == 'GET':
return self.success(data={'pipelines': await self.ap.pipeline_service.get_pipelines()})
sort_by = quart.request.args.get('sort_by', 'created_at')
sort_order = quart.request.args.get('sort_order', 'DESC')
return self.success(
data={'pipelines': await self.ap.pipeline_service.get_pipelines(sort_by, sort_order)}
)
elif quart.request.method == 'POST':
json_data = await quart.request.json

View File

@@ -0,0 +1,109 @@
import json
import quart
from ... import group
@group.group_class('webchat', '/api/v1/pipelines/<pipeline_uuid>/chat')
class WebChatDebugRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('/send', methods=['POST'])
async def send_message(pipeline_uuid: str) -> str:
"""Send a message to the pipeline for debugging"""
async def stream_generator(generator):
yield 'data: {"type": "start"}\n\n'
async for message in generator:
yield f'data: {json.dumps({"message": message})}\n\n'
yield 'data: {"type": "end"}\n\n'
try:
data = await quart.request.get_json()
session_type = data.get('session_type', 'person')
message_chain_obj = data.get('message', [])
is_stream = data.get('is_stream', False)
if not message_chain_obj:
return self.http_status(400, -1, 'message is required')
if session_type not in ['person', 'group']:
return self.http_status(400, -1, 'session_type must be person or group')
webchat_adapter = self.ap.platform_mgr.webchat_proxy_bot.adapter
if not webchat_adapter:
return self.http_status(404, -1, 'WebChat adapter not found')
if is_stream:
generator = webchat_adapter.send_webchat_message(
pipeline_uuid, session_type, message_chain_obj, is_stream
)
# 设置正确的响应头
headers = {
'Content-Type': 'text/event-stream',
'Transfer-Encoding': 'chunked',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
return quart.Response(stream_generator(generator), mimetype='text/event-stream',headers=headers)
else: # non-stream
result = None
async for message in webchat_adapter.send_webchat_message(
pipeline_uuid, session_type, message_chain_obj
):
result = message
if result is not None:
return self.success(
data={
'message': result,
}
)
else:
return self.http_status(400, -1, 'message is required')
except Exception as e:
return self.http_status(500, -1, f'Internal server error: {str(e)}')
@self.route('/messages/<session_type>', methods=['GET'])
async def get_messages(pipeline_uuid: str, session_type: str) -> str:
"""Get the message history of the pipeline for debugging"""
try:
if session_type not in ['person', 'group']:
return self.http_status(400, -1, 'session_type must be person or group')
webchat_adapter = self.ap.platform_mgr.webchat_proxy_bot.adapter
if not webchat_adapter:
return self.http_status(404, -1, 'WebChat adapter not found')
messages = webchat_adapter.get_webchat_messages(pipeline_uuid, session_type)
return self.success(data={'messages': messages})
except Exception as e:
return self.http_status(500, -1, f'Internal server error: {str(e)}')
@self.route('/reset/<session_type>', methods=['POST'])
async def reset_session(session_type: str) -> str:
"""Reset the debug session"""
try:
if session_type not in ['person', 'group']:
return self.http_status(400, -1, 'session_type must be person or group')
webchat_adapter = None
for bot in self.ap.platform_mgr.bots:
if hasattr(bot.adapter, '__class__') and bot.adapter.__class__.__name__ == 'WebChatAdapter':
webchat_adapter = bot.adapter
break
if not webchat_adapter:
return self.http_status(404, -1, 'WebChat adapter not found')
webchat_adapter.reset_debug_session(session_type)
return self.success(data={'message': 'Session reset successfully'})
except Exception as e:
return self.http_status(500, -1, f'Internal server error: {str(e)}')

View File

@@ -29,3 +29,16 @@ class BotsRouterGroup(group.RouterGroup):
elif quart.request.method == 'DELETE':
await self.ap.bot_service.delete_bot(bot_uuid)
return self.success()
@self.route('/<bot_uuid>/logs', methods=['POST'])
async def _(bot_uuid: str) -> str:
json_data = await quart.request.json
from_index = json_data.get('from_index', -1)
max_count = json_data.get('max_count', 10)
logs, total_count = await self.ap.bot_service.list_event_logs(bot_uuid, from_index, max_count)
return self.success(
data={
'logs': logs,
'total_count': total_count,
}
)

View File

@@ -40,7 +40,7 @@ class PluginsRouterGroup(group.RouterGroup):
self.ap.plugin_mgr.update_plugin(plugin_name, task_context=ctx),
kind='plugin-operation',
name=f'plugin-update-{plugin_name}',
label=f'更新插件 {plugin_name}',
label=f'Updating plugin {plugin_name}',
context=ctx,
)
return self.success(data={'task_id': wrapper.id})
@@ -62,7 +62,7 @@ class PluginsRouterGroup(group.RouterGroup):
self.ap.plugin_mgr.uninstall_plugin(plugin_name, task_context=ctx),
kind='plugin-operation',
name=f'plugin-remove-{plugin_name}',
label=f'删除插件 {plugin_name}',
label=f'Removing plugin {plugin_name}',
context=ctx,
)
@@ -102,7 +102,7 @@ class PluginsRouterGroup(group.RouterGroup):
self.ap.plugin_mgr.install_plugin(data['source'], task_context=ctx),
kind='plugin-operation',
name='plugin-install-github',
label=f'安装插件 ...{short_source_str}',
label=f'Installing plugin ...{short_source_str}',
context=ctx,
)

View File

@@ -9,18 +9,18 @@ class LLMModelsRouterGroup(group.RouterGroup):
@self.route('', methods=['GET', 'POST'])
async def _() -> str:
if quart.request.method == 'GET':
return self.success(data={'models': await self.ap.model_service.get_llm_models()})
return self.success(data={'models': await self.ap.llm_model_service.get_llm_models()})
elif quart.request.method == 'POST':
json_data = await quart.request.json
model_uuid = await self.ap.model_service.create_llm_model(json_data)
model_uuid = await self.ap.llm_model_service.create_llm_model(json_data)
return self.success(data={'uuid': model_uuid})
@self.route('/<model_uuid>', methods=['GET', 'PUT', 'DELETE'])
async def _(model_uuid: str) -> str:
if quart.request.method == 'GET':
model = await self.ap.model_service.get_llm_model(model_uuid)
model = await self.ap.llm_model_service.get_llm_model(model_uuid)
if model is None:
return self.http_status(404, -1, 'model not found')
@@ -29,10 +29,61 @@ class LLMModelsRouterGroup(group.RouterGroup):
elif quart.request.method == 'PUT':
json_data = await quart.request.json
await self.ap.model_service.update_llm_model(model_uuid, json_data)
await self.ap.llm_model_service.update_llm_model(model_uuid, json_data)
return self.success()
elif quart.request.method == 'DELETE':
await self.ap.model_service.delete_llm_model(model_uuid)
await self.ap.llm_model_service.delete_llm_model(model_uuid)
return self.success()
@self.route('/<model_uuid>/test', methods=['POST'])
async def _(model_uuid: str) -> str:
json_data = await quart.request.json
await self.ap.llm_model_service.test_llm_model(model_uuid, json_data)
return self.success()
@group.group_class('models/embedding', '/api/v1/provider/models/embedding')
class EmbeddingModelsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET', 'POST'])
async def _() -> str:
if quart.request.method == 'GET':
return self.success(data={'models': await self.ap.embedding_models_service.get_embedding_models()})
elif quart.request.method == 'POST':
json_data = await quart.request.json
model_uuid = await self.ap.embedding_models_service.create_embedding_model(json_data)
return self.success(data={'uuid': model_uuid})
@self.route('/<model_uuid>', methods=['GET', 'PUT', 'DELETE'])
async def _(model_uuid: str) -> str:
if quart.request.method == 'GET':
model = await self.ap.embedding_models_service.get_embedding_model(model_uuid)
if model is None:
return self.http_status(404, -1, 'model not found')
return self.success(data={'model': model})
elif quart.request.method == 'PUT':
json_data = await quart.request.json
await self.ap.embedding_models_service.update_embedding_model(model_uuid, json_data)
return self.success()
elif quart.request.method == 'DELETE':
await self.ap.embedding_models_service.delete_embedding_model(model_uuid)
return self.success()
@self.route('/<model_uuid>/test', methods=['POST'])
async def _(model_uuid: str) -> str:
json_data = await quart.request.json
await self.ap.embedding_models_service.test_embedding_model(model_uuid, json_data)
return self.success()

View File

@@ -8,7 +8,8 @@ class RequestersRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@self.route('', methods=['GET'])
async def _() -> quart.Response:
return self.success(data={'requesters': self.ap.model_mgr.get_available_requesters_info()})
model_type = quart.request.args.get('type', '')
return self.success(data={'requesters': self.ap.model_mgr.get_available_requesters_info(model_type)})
@self.route('/<requester_name>', methods=['GET'])
async def _(requester_name: str) -> quart.Response:

View File

@@ -1,5 +1,6 @@
import quart
import argon2
import asyncio
from .. import group
@@ -13,7 +14,7 @@ class UserRouterGroup(group.RouterGroup):
return self.success(data={'initialized': await self.ap.user_service.is_initialized()})
if await self.ap.user_service.is_initialized():
return self.fail(1, '系统已初始化')
return self.fail(1, 'System already initialized')
json_data = await quart.request.json
@@ -31,7 +32,7 @@ class UserRouterGroup(group.RouterGroup):
try:
token = await self.ap.user_service.authenticate(json_data['user'], json_data['password'])
except argon2.exceptions.VerifyMismatchError:
return self.fail(1, '用户名或密码错误')
return self.fail(1, 'Invalid username or password')
return self.success(data={'token': token})
@@ -40,3 +41,45 @@ class UserRouterGroup(group.RouterGroup):
token = await self.ap.user_service.generate_jwt_token(user_email)
return self.success(data={'token': token})
@self.route('/reset-password', methods=['POST'], auth_type=group.AuthType.NONE)
async def _() -> str:
json_data = await quart.request.json
user_email = json_data['user']
recovery_key = json_data['recovery_key']
new_password = json_data['new_password']
# hard sleep 3s for security
await asyncio.sleep(3)
if not await self.ap.user_service.is_initialized():
return self.http_status(400, -1, 'System not initialized')
user_obj = await self.ap.user_service.get_user_by_email(user_email)
if user_obj is None:
return self.http_status(400, -1, 'User not found')
if recovery_key != self.ap.instance_config.data['system']['recovery_key']:
return self.http_status(403, -1, 'Invalid recovery key')
await self.ap.user_service.reset_password(user_email, new_password)
return self.success(data={'user': user_email})
@self.route('/change-password', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
async def _(user_email: str) -> str:
json_data = await quart.request.json
current_password = json_data['current_password']
new_password = json_data['new_password']
try:
await self.ap.user_service.change_password(user_email, current_password, new_password)
except argon2.exceptions.VerifyMismatchError:
return self.http_status(400, -1, 'Current password is incorrect')
except ValueError as e:
return self.http_status(400, -1, str(e))
return self.success(data={'user': user_email})

View File

@@ -13,10 +13,14 @@ from . import groups
from . import group
from .groups import provider as groups_provider
from .groups import platform as groups_platform
from .groups import pipelines as groups_pipelines
from .groups import knowledge as groups_knowledge
importutil.import_modules_in_pkg(groups)
importutil.import_modules_in_pkg(groups_provider)
importutil.import_modules_in_pkg(groups_platform)
importutil.import_modules_in_pkg(groups_pipelines)
importutil.import_modules_in_pkg(groups_knowledge)
class HTTPController:
@@ -43,7 +47,7 @@ class HTTPController:
try:
await self.quart_app.run_task(*args, **kwargs)
except Exception as e:
self.ap.logger.error(f'启动 HTTP 服务失败: {e}')
self.ap.logger.error(f'Failed to start HTTP service: {e}')
self.ap.task_mgr.create_task(
exception_handler(
@@ -107,4 +111,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

@@ -2,6 +2,7 @@ from __future__ import annotations
import uuid
import sqlalchemy
import typing
from ....core import app
from ....entity.persistence import bot as persistence_bot
@@ -9,7 +10,7 @@ from ....entity.persistence import pipeline as persistence_pipeline
class BotService:
"""机器人服务"""
"""Bot service"""
ap: app.Application
@@ -17,7 +18,7 @@ class BotService:
self.ap = ap
async def get_bots(self) -> list[dict]:
"""获取所有机器人"""
"""Get all bots"""
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_bot.Bot))
bots = result.all()
@@ -25,7 +26,7 @@ class BotService:
return [self.ap.persistence_mgr.serialize_model(persistence_bot.Bot, bot) for bot in bots]
async def get_bot(self, bot_uuid: str) -> dict | None:
"""获取机器人"""
"""Get bot"""
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_bot.Bot).where(persistence_bot.Bot.uuid == bot_uuid)
)
@@ -38,7 +39,7 @@ class BotService:
return self.ap.persistence_mgr.serialize_model(persistence_bot.Bot, bot)
async def create_bot(self, bot_data: dict) -> str:
"""创建机器人"""
"""Create bot"""
# TODO: 检查配置信息格式
bot_data['uuid'] = str(uuid.uuid4())
@@ -62,7 +63,7 @@ class BotService:
return bot_data['uuid']
async def update_bot(self, bot_uuid: str, bot_data: dict) -> None:
"""更新机器人"""
"""Update bot"""
if 'uuid' in bot_data:
del bot_data['uuid']
@@ -92,9 +93,25 @@ class BotService:
if runtime_bot.enable:
await runtime_bot.run()
# update all conversation that use this bot
for session in self.ap.sess_mgr.session_list:
if session.using_conversation is not None and session.using_conversation.bot_uuid == bot_uuid:
session.using_conversation = None
async def delete_bot(self, bot_uuid: str) -> None:
"""删除机器人"""
"""Delete bot"""
await self.ap.platform_mgr.remove_bot(bot_uuid)
await self.ap.persistence_mgr.execute_async(
sqlalchemy.delete(persistence_bot.Bot).where(persistence_bot.Bot.uuid == bot_uuid)
)
async def list_event_logs(
self, bot_uuid: str, from_index: int, max_count: int
) -> typing.Tuple[list[dict], int, int, int]:
runtime_bot = await self.ap.platform_mgr.get_bot_by_uuid(bot_uuid)
if runtime_bot is None:
raise Exception('Bot not found')
logs, total_count = await runtime_bot.logger.get_logs(from_index, max_count)
return [log.to_json() for log in logs], total_count

View File

@@ -0,0 +1,120 @@
from __future__ import annotations
import uuid
import sqlalchemy
from ....core import app
from ....entity.persistence import rag as persistence_rag
class KnowledgeService:
"""知识库服务"""
ap: app.Application
def __init__(self, ap: app.Application) -> None:
self.ap = ap
async def get_knowledge_bases(self) -> list[dict]:
"""获取所有知识库"""
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_rag.KnowledgeBase))
knowledge_bases = result.all()
return [
self.ap.persistence_mgr.serialize_model(persistence_rag.KnowledgeBase, knowledge_base)
for knowledge_base in knowledge_bases
]
async def get_knowledge_base(self, kb_uuid: str) -> dict | None:
"""获取知识库"""
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_rag.KnowledgeBase).where(persistence_rag.KnowledgeBase.uuid == kb_uuid)
)
knowledge_base = result.first()
if knowledge_base is None:
return None
return self.ap.persistence_mgr.serialize_model(persistence_rag.KnowledgeBase, knowledge_base)
async def create_knowledge_base(self, kb_data: dict) -> str:
"""创建知识库"""
kb_data['uuid'] = str(uuid.uuid4())
await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_rag.KnowledgeBase).values(kb_data))
kb = await self.get_knowledge_base(kb_data['uuid'])
await self.ap.rag_mgr.load_knowledge_base(kb)
return kb_data['uuid']
async def update_knowledge_base(self, kb_uuid: str, kb_data: dict) -> None:
"""更新知识库"""
if 'uuid' in kb_data:
del kb_data['uuid']
if 'embedding_model_uuid' in kb_data:
del kb_data['embedding_model_uuid']
await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(persistence_rag.KnowledgeBase)
.values(kb_data)
.where(persistence_rag.KnowledgeBase.uuid == kb_uuid)
)
await self.ap.rag_mgr.remove_knowledge_base_from_runtime(kb_uuid)
kb = await self.get_knowledge_base(kb_uuid)
await self.ap.rag_mgr.load_knowledge_base(kb)
async def store_file(self, kb_uuid: str, file_id: str) -> int:
"""存储文件"""
# await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_rag.File).values(kb_id=kb_uuid, file_id=file_id))
# await self.ap.rag_mgr.store_file(file_id)
runtime_kb = await self.ap.rag_mgr.get_knowledge_base_by_uuid(kb_uuid)
if runtime_kb is None:
raise Exception('Knowledge base not found')
return await runtime_kb.store_file(file_id)
async def retrieve_knowledge_base(self, kb_uuid: str, query: str) -> list[dict]:
"""检索知识库"""
runtime_kb = await self.ap.rag_mgr.get_knowledge_base_by_uuid(kb_uuid)
if runtime_kb is None:
raise Exception('Knowledge base not found')
return [
result.model_dump() for result in await runtime_kb.retrieve(query, runtime_kb.knowledge_base_entity.top_k)
]
async def get_files_by_knowledge_base(self, kb_uuid: str) -> list[dict]:
"""获取知识库文件"""
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_rag.File).where(persistence_rag.File.kb_id == kb_uuid)
)
files = result.all()
return [self.ap.persistence_mgr.serialize_model(persistence_rag.File, file) for file in files]
async def delete_file(self, kb_uuid: str, file_id: str) -> None:
"""删除文件"""
runtime_kb = await self.ap.rag_mgr.get_knowledge_base_by_uuid(kb_uuid)
if runtime_kb is None:
raise Exception('Knowledge base not found')
await runtime_kb.delete_file(file_id)
async def delete_knowledge_base(self, kb_uuid: str) -> None:
"""删除知识库"""
await self.ap.rag_mgr.delete_knowledge_base(kb_uuid)
await self.ap.persistence_mgr.execute_async(
sqlalchemy.delete(persistence_rag.KnowledgeBase).where(persistence_rag.KnowledgeBase.uuid == kb_uuid)
)
# delete files
files = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_rag.File).where(persistence_rag.File.kb_id == kb_uuid)
)
for file in files:
# delete chunks
await self.ap.persistence_mgr.execute_async(
sqlalchemy.delete(persistence_rag.Chunk).where(persistence_rag.Chunk.file_id == file.uuid)
)
# delete file
await self.ap.persistence_mgr.execute_async(
sqlalchemy.delete(persistence_rag.File).where(persistence_rag.File.uuid == file.uuid)
)

View File

@@ -6,9 +6,11 @@ import sqlalchemy
from ....core import app
from ....entity.persistence import model as persistence_model
from ....entity.persistence import pipeline as persistence_pipeline
from ....provider.modelmgr import requester as model_requester
from ....provider import entities as llm_entities
class ModelsService:
class LLMModelsService:
ap: app.Application
def __init__(self, ap: app.Application) -> None:
@@ -78,3 +80,112 @@ class ModelsService:
)
await self.ap.model_mgr.remove_llm_model(model_uuid)
async def test_llm_model(self, model_uuid: str, model_data: dict) -> None:
runtime_llm_model: model_requester.RuntimeLLMModel | None = None
if model_uuid != '_':
for model in self.ap.model_mgr.llm_models:
if model.model_entity.uuid == model_uuid:
runtime_llm_model = model
break
if runtime_llm_model is None:
raise Exception('model not found')
else:
runtime_llm_model = await self.ap.model_mgr.init_runtime_llm_model(model_data)
await runtime_llm_model.requester.invoke_llm(
query=None,
model=runtime_llm_model,
messages=[llm_entities.Message(role='user', content='Hello, world!')],
funcs=[],
extra_args=model_data.get('extra_args', {}),
)
class EmbeddingModelsService:
ap: app.Application
def __init__(self, ap: app.Application) -> None:
self.ap = ap
async def get_embedding_models(self) -> list[dict]:
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_model.EmbeddingModel))
models = result.all()
return [self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, model) for model in models]
async def create_embedding_model(self, model_data: dict) -> str:
model_data['uuid'] = str(uuid.uuid4())
await self.ap.persistence_mgr.execute_async(
sqlalchemy.insert(persistence_model.EmbeddingModel).values(**model_data)
)
embedding_model = await self.get_embedding_model(model_data['uuid'])
await self.ap.model_mgr.load_embedding_model(embedding_model)
return model_data['uuid']
async def get_embedding_model(self, model_uuid: str) -> dict | None:
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_model.EmbeddingModel).where(
persistence_model.EmbeddingModel.uuid == model_uuid
)
)
model = result.first()
if model is None:
return None
return self.ap.persistence_mgr.serialize_model(persistence_model.EmbeddingModel, model)
async def update_embedding_model(self, model_uuid: str, model_data: dict) -> None:
if 'uuid' in model_data:
del model_data['uuid']
await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(persistence_model.EmbeddingModel)
.where(persistence_model.EmbeddingModel.uuid == model_uuid)
.values(**model_data)
)
await self.ap.model_mgr.remove_embedding_model(model_uuid)
embedding_model = await self.get_embedding_model(model_uuid)
await self.ap.model_mgr.load_embedding_model(embedding_model)
async def delete_embedding_model(self, model_uuid: str) -> None:
await self.ap.persistence_mgr.execute_async(
sqlalchemy.delete(persistence_model.EmbeddingModel).where(
persistence_model.EmbeddingModel.uuid == model_uuid
)
)
await self.ap.model_mgr.remove_embedding_model(model_uuid)
async def test_embedding_model(self, model_uuid: str, model_data: dict) -> None:
runtime_embedding_model: model_requester.RuntimeEmbeddingModel | None = None
if model_uuid != '_':
for model in self.ap.model_mgr.embedding_models:
if model.model_entity.uuid == model_uuid:
runtime_embedding_model = model
break
if runtime_embedding_model is None:
raise Exception('model not found')
else:
runtime_embedding_model = await self.ap.model_mgr.init_runtime_embedding_model(model_data)
await runtime_embedding_model.requester.invoke_embedding(
model=runtime_embedding_model,
input_text=['Hello, world!'],
extra_args={},
)

View File

@@ -38,9 +38,21 @@ class PipelineService:
self.ap.pipeline_config_meta_output.data,
]
async def get_pipelines(self) -> list[dict]:
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_pipeline.LegacyPipeline))
async def get_pipelines(self, sort_by: str = 'created_at', sort_order: str = 'DESC') -> list[dict]:
query = sqlalchemy.select(persistence_pipeline.LegacyPipeline)
if sort_by == 'created_at':
if sort_order == 'DESC':
query = query.order_by(persistence_pipeline.LegacyPipeline.created_at.desc())
else:
query = query.order_by(persistence_pipeline.LegacyPipeline.created_at.asc())
elif sort_by == 'updated_at':
if sort_order == 'DESC':
query = query.order_by(persistence_pipeline.LegacyPipeline.updated_at.desc())
else:
query = query.order_by(persistence_pipeline.LegacyPipeline.updated_at.asc())
result = await self.ap.persistence_mgr.execute_async(query)
pipelines = result.all()
return [
self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline)
@@ -94,12 +106,29 @@ class PipelineService:
.values(**pipeline_data)
)
await self.ap.pipeline_mgr.remove_pipeline(pipeline_uuid)
pipeline = await self.get_pipeline(pipeline_uuid)
if 'name' in pipeline_data:
from ....entity.persistence import bot as persistence_bot
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(persistence_bot.Bot).where(persistence_bot.Bot.use_pipeline_uuid == pipeline_uuid)
)
bots = result.all()
for bot in bots:
bot_data = {'use_pipeline_name': pipeline_data['name']}
await self.ap.bot_service.update_bot(bot.uuid, bot_data)
await self.ap.pipeline_mgr.remove_pipeline(pipeline_uuid)
await self.ap.pipeline_mgr.load_pipeline(pipeline)
# update all conversation that use this pipeline
for session in self.ap.sess_mgr.session_list:
if session.using_conversation is not None and session.using_conversation.pipeline_uuid == pipeline_uuid:
session.using_conversation = None
async def delete_pipeline(self, pipeline_uuid: str) -> None:
await self.ap.persistence_mgr.execute_async(
sqlalchemy.delete(persistence_pipeline.LegacyPipeline).where(

View File

@@ -73,3 +73,27 @@ class UserService:
jwt_secret = self.ap.instance_config.data['system']['jwt']['secret']
return jwt.decode(token, jwt_secret, algorithms=['HS256'])['user']
async def reset_password(self, user_email: str, new_password: str) -> None:
ph = argon2.PasswordHasher()
hashed_password = ph.hash(new_password)
await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(user.User).where(user.User.user == user_email).values(password=hashed_password)
)
async def change_password(self, user_email: str, current_password: str, new_password: str) -> None:
ph = argon2.PasswordHasher()
user_obj = await self.get_user_by_email(user_email)
if user_obj is None:
raise ValueError('User not found')
ph.verify(user_obj.password, current_password)
hashed_password = ph.hash(new_password)
await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(user.User).where(user.User.user == user_email).values(password=hashed_password)
)

View File

@@ -6,7 +6,7 @@ from .. import model as file_model
class JSONConfigFile(file_model.ConfigFile):
"""JSON配置文件"""
"""JSON config file"""
def __init__(
self,
@@ -42,7 +42,7 @@ class JSONConfigFile(file_model.ConfigFile):
try:
cfg = json.load(f)
except json.JSONDecodeError as e:
raise Exception(f'配置文件 {self.config_file_name} 语法错误: {e}')
raise Exception(f'Syntax error in config file {self.config_file_name}: {e}')
if completion:
for key in self.template_data:

View File

@@ -7,13 +7,13 @@ from .. import model as file_model
class PythonModuleConfigFile(file_model.ConfigFile):
"""Python模块配置文件"""
"""Python module config file"""
config_file_name: str = None
"""配置文件名"""
"""Config file name"""
template_file_name: str = None
"""模板文件名"""
"""Template file name"""
def __init__(self, config_file_name: str, template_file_name: str) -> None:
self.config_file_name = config_file_name
@@ -42,7 +42,7 @@ class PythonModuleConfigFile(file_model.ConfigFile):
cfg[key] = getattr(module, key)
# 从模板模块文件中进行补全
# complete from template module file
if completion:
module_name = os.path.splitext(os.path.basename(self.template_file_name))[0]
module = importlib.import_module(module_name)
@@ -60,7 +60,7 @@ class PythonModuleConfigFile(file_model.ConfigFile):
return cfg
async def save(self, data: dict):
logging.warning('Python模块配置文件不支持保存')
logging.warning('Python module config file does not support saving')
def save_sync(self, data: dict):
logging.warning('Python模块配置文件不支持保存')
logging.warning('Python module config file does not support saving')

View File

@@ -6,7 +6,7 @@ from .. import model as file_model
class YAMLConfigFile(file_model.ConfigFile):
"""YAML配置文件"""
"""YAML config file"""
def __init__(
self,
@@ -42,7 +42,7 @@ class YAMLConfigFile(file_model.ConfigFile):
try:
cfg = yaml.load(f, Loader=yaml.FullLoader)
except yaml.YAMLError as e:
raise Exception(f'配置文件 {self.config_file_name} 语法错误: {e}')
raise Exception(f'Syntax error in config file {self.config_file_name}: {e}')
if completion:
for key in self.template_data:

View File

@@ -5,27 +5,27 @@ from .impls import pymodule, json as json_file, yaml as yaml_file
class ConfigManager:
"""配置文件管理器"""
"""Config file manager"""
name: str = None
"""配置管理器名"""
"""Config manager name"""
description: str = None
"""配置管理器描述"""
"""Config manager description"""
schema: dict = None
"""配置文件 schema
需要符合 JSON Schema Draft 7 规范
"""Config file schema
Must conform to JSON Schema Draft 7 specification
"""
file: file_model.ConfigFile = None
"""配置文件实例"""
"""Config file instance"""
data: dict = None
"""配置数据"""
"""Config data"""
doc_link: str = None
"""配置文件文档链接"""
"""Config file documentation link"""
def __init__(self, cfg_file: file_model.ConfigFile) -> None:
self.file = cfg_file
@@ -42,15 +42,15 @@ class ConfigManager:
async def load_python_module_config(config_name: str, template_name: str, completion: bool = True) -> ConfigManager:
"""加载Python模块配置文件
"""Load Python module config file
Args:
config_name (str): 配置文件名
template_name (str): 模板文件名
completion (bool): 是否自动补全内存中的配置文件
config_name (str): Config file name
template_name (str): Template file name
completion (bool): Whether to automatically complete the config file in memory
Returns:
ConfigManager: 配置文件管理器
ConfigManager: Config file manager
"""
cfg_inst = pymodule.PythonModuleConfigFile(config_name, template_name)
@@ -66,13 +66,13 @@ async def load_json_config(
template_data: dict = None,
completion: bool = True,
) -> ConfigManager:
"""加载JSON配置文件
"""Load JSON config file
Args:
config_name (str): 配置文件名
template_name (str): 模板文件名
template_data (dict): 模板数据
completion (bool): 是否自动补全内存中的配置文件
config_name (str): Config file name
template_name (str): Template file name
template_data (dict): Template data
completion (bool): Whether to automatically complete the config file in memory
"""
cfg_inst = json_file.JSONConfigFile(config_name, template_name, template_data)
@@ -88,16 +88,16 @@ async def load_yaml_config(
template_data: dict = None,
completion: bool = True,
) -> ConfigManager:
"""加载YAML配置文件
"""Load YAML config file
Args:
config_name (str): 配置文件名
template_name (str): 模板文件名
template_data (dict): 模板数据
completion (bool): 是否自动补全内存中的配置文件
config_name (str): Config file name
template_name (str): Template file name
template_data (dict): Template data
completion (bool): Whether to automatically complete the config file in memory
Returns:
ConfigManager: 配置文件管理器
ConfigManager: Config file manager
"""
cfg_inst = yaml_file.YAMLConfigFile(config_name, template_name, template_data)

View File

@@ -2,16 +2,16 @@ import abc
class ConfigFile(metaclass=abc.ABCMeta):
"""配置文件抽象类"""
"""Config file abstract class"""
config_file_name: str = None
"""配置文件名"""
"""Config file name"""
template_file_name: str = None
"""模板文件名"""
"""Template file name"""
template_data: dict = None
"""模板数据"""
"""Template data"""
@abc.abstractmethod
def exists(self) -> bool:

View File

@@ -22,14 +22,18 @@ from ..api.http.service import user as user_service
from ..api.http.service import model as model_service
from ..api.http.service import pipeline as pipeline_service
from ..api.http.service import bot as bot_service
from ..api.http.service import knowledge as knowledge_service
from ..discover import engine as discover_engine
from ..utils import logcache, ip
from ..storage import mgr as storagemgr
from ..utils import logcache
from . import taskmgr
from . import entities as core_entities
from ..rag.knowledge import kbmgr as rag_mgr
from ..vector import mgr as vectordb_mgr
class Application:
"""运行时应用对象和上下文"""
"""Runtime application object and context"""
event_loop: asyncio.AbstractEventLoop = None
@@ -46,10 +50,12 @@ class Application:
model_mgr: llm_model_mgr.ModelManager = None
# TODO 移动到 pipeline 里
rag_mgr: rag_mgr.RAGManager = None
# TODO move to pipeline
tool_mgr: llm_tool_mgr.ToolManager = None
# ======= 配置管理器 =======
# ======= Config manager =======
command_cfg: config_mgr.ConfigManager = None # deprecated
@@ -63,7 +69,7 @@ class Application:
instance_config: config_mgr.ConfigManager = None
# ======= 元数据配置管理器 =======
# ======= Metadata config manager =======
sensitive_meta: config_mgr.ConfigManager = None
@@ -92,20 +98,28 @@ class Application:
persistence_mgr: persistencemgr.PersistenceManager = None
vector_db_mgr: vectordb_mgr.VectorDBManager = None
http_ctrl: http_controller.HTTPController = None
log_cache: logcache.LogCache = None
storage_mgr: storagemgr.StorageMgr = None
# ========= HTTP Services =========
user_service: user_service.UserService = None
model_service: model_service.ModelsService = None
llm_model_service: model_service.LLMModelsService = None
embedding_models_service: model_service.EmbeddingModelsService = None
pipeline_service: pipeline_service.PipelineService = None
bot_service: bot_service.BotService = None
knowledge_service: knowledge_service.KnowledgeService = None
def __init__(self):
pass
@@ -140,6 +154,7 @@ class Application:
name='http-api-controller',
scopes=[core_entities.LifecycleControlScope.APPLICATION],
)
self.task_mgr.create_task(
never_ending(),
name='never-ending-task',
@@ -151,35 +166,31 @@ class Application:
except asyncio.CancelledError:
pass
except Exception as e:
self.logger.error(f'应用运行致命异常: {e}')
self.logger.error(f'Application runtime fatal exception: {e}')
self.logger.debug(f'Traceback: {traceback.format_exc()}')
async def print_web_access_info(self):
"""打印访问 webui 的提示"""
"""Print access webui tips"""
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'
public_ip = await ip.get_myip()
port = self.instance_config.data['api']['port']
tips = f"""
=======================================
您可通过以下方式访问管理面板
Access WebUI / 访问管理面板
🏠 本地地址:http://{host_ip}:{port}/
🌐 公网地址http://{public_ip}:{port}/
🏠 Local Address: http://{host_ip}:{port}/
🌐 Public Address: http://<Your Public IP>:{port}/
📌 如果您在容器中运行此程序,请确保容器的 {port} 端口已对外暴露
🔗 若要使用公网地址访问,请阅读以下须知
1. 公网地址仅供参考,请以您的主机公网 IP 为准;
2. 要使用公网地址访问,请确保您的主机具有公网 IP并且系统防火墙已放行 {port} 端口;
🤯 WebUI 仍处于 Beta 测试阶段,如有问题或建议请反馈到 https://github.com/RockChinQ/LangBot/issues
📌 Running this program in a container? Please ensure that the {port} port is exposed
=======================================
""".strip()
for line in tips.split('\n'):
@@ -191,7 +202,7 @@ class Application:
):
match scope:
case core_entities.LifecycleControlScope.PLATFORM.value:
self.logger.info('执行热重载 scope=' + scope)
self.logger.info('Hot reload scope=' + scope)
await self.platform_mgr.shutdown()
self.platform_mgr = im_mgr.PlatformManager(self)
@@ -207,7 +218,7 @@ class Application:
],
)
case core_entities.LifecycleControlScope.PLUGIN.value:
self.logger.info('执行热重载 scope=' + scope)
self.logger.info('Hot reload scope=' + scope)
await self.plugin_mgr.destroy_plugins()
# 删除 sys.module 中所有的 plugins/* 下的模块
@@ -223,7 +234,7 @@ class Application:
await self.plugin_mgr.load_plugins()
await self.plugin_mgr.initialize_plugins()
case core_entities.LifecycleControlScope.PROVIDER.value:
self.logger.info('执行热重载 scope=' + scope)
self.logger.info('Hot reload scope=' + scope)
await self.tool_mgr.shutdown()

View File

@@ -1,4 +1,4 @@
from __future__ import print_function
from __future__ import annotations
import traceback
import asyncio
@@ -8,7 +8,7 @@ from . import app
from . import stage
from ..utils import constants, importutil
# 引入启动阶段实现以便注册
# Import startup stage implementation to register
from . import stages
importutil.import_modules_in_pkg(stages)
@@ -25,7 +25,7 @@ stage_order = [
async def make_app(loop: asyncio.AbstractEventLoop) -> app.Application:
# 确定是否为调试模式
# Determine if it is debug mode
if 'DEBUG' in os.environ and os.environ['DEBUG'] in ['true', '1']:
constants.debug_mode = True
@@ -33,7 +33,7 @@ async def make_app(loop: asyncio.AbstractEventLoop) -> app.Application:
ap.event_loop = loop
# 执行启动阶段
# Execute startup stage
for stage_name in stage_order:
stage_cls = stage.preregistered_stages[stage_name]
stage_inst = stage_cls()
@@ -47,11 +47,11 @@ async def make_app(loop: asyncio.AbstractEventLoop) -> app.Application:
async def main(loop: asyncio.AbstractEventLoop):
try:
# 挂系统信号处理
# Hang system signal processing
import signal
def signal_handler(sig, frame):
print('[Signal] 程序退出.')
print('[Signal] Program exit.')
# ap.shutdown()
os._exit(0)

View File

@@ -2,8 +2,8 @@ import pip
import os
from ...utils import pkgmgr
# 检查依赖,防止用户未安装
# 左边为引入名称,右边为依赖名称
# Check dependencies to prevent users from not installing
# Left is the import name, right is the dependency name
required_deps = {
'requests': 'requests',
'openai': 'openai',
@@ -65,7 +65,7 @@ async def install_deps(deps: list[str]):
async def precheck_plugin_deps():
print('[Startup] Prechecking plugin dependencies...')
# 只有在plugins目录存在时才执行插件依赖安装
# Only execute plugin dependency installation when the plugins directory exists
if os.path.exists('plugins'):
for dir in os.listdir('plugins'):
subdir = os.path.join('plugins', dir)
@@ -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

@@ -17,7 +17,7 @@ log_colors_config = {
async def init_logging(extra_handlers: list[logging.Handler] = None) -> logging.Logger:
# 删除所有现有的logger
# Remove all existing loggers
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
@@ -54,13 +54,13 @@ async def init_logging(extra_handlers: list[logging.Handler] = None) -> logging.
handler.setFormatter(color_formatter)
qcg_logger.addHandler(handler)
qcg_logger.debug('日志初始化完成,日志级别:%s' % level)
qcg_logger.debug('Logging initialized, log level: %s' % level)
logging.basicConfig(
level=logging.CRITICAL, # 设置日志输出格式
level=logging.CRITICAL, # Set log output format
format='[DEPR][%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n%(message)s',
# 日志输出的格式
# -8表示占位符让输出左对齐输出长度都为8位
datefmt='%Y-%m-%d %H:%M:%S', # 时间输出的格式
# Log output format
# -8 is a placeholder, left-align the output, and output length is 8
datefmt='%Y-%m-%d %H:%M:%S', # Time output format
handlers=[logging.NullHandler()],
)

View File

@@ -87,7 +87,9 @@ class Query(pydantic.BaseModel):
"""使用的函数,由前置处理器阶段设置"""
resp_messages: (
typing.Optional[list[llm_entities.Message]] | typing.Optional[list[platform_message.MessageChain]]
typing.Optional[list[llm_entities.Message]]
| typing.Optional[list[platform_message.MessageChain]]
| typing.Optional[list[llm_entities.MessageChunk]]
) = []
"""由Process阶段生成的回复消息对象列表"""
@@ -137,6 +139,12 @@ class Conversation(pydantic.BaseModel):
use_funcs: typing.Optional[list[tools_entities.LLMFunction]]
pipeline_uuid: str
"""流水线UUID。"""
bot_uuid: str
"""机器人UUID。"""
uuid: typing.Optional[str] = None
"""该对话的 uuid在创建时不会自动生成。而是当使用 Dify API 等由外部管理对话信息的服务时,用于绑定外部的会话。具体如何使用,取决于 Runner。"""

View File

@@ -7,11 +7,11 @@ from . import app
preregistered_migrations: list[typing.Type[Migration]] = []
"""当前阶段暂不支持扩展"""
"""Currently not supported for extension"""
def migration_class(name: str, number: int):
"""注册一个迁移"""
"""Register a migration"""
def decorator(cls: typing.Type[Migration]) -> typing.Type[Migration]:
cls.name = name
@@ -23,7 +23,7 @@ def migration_class(name: str, number: int):
class Migration(abc.ABC):
"""一个版本的迁移"""
"""A version migration"""
name: str
@@ -36,10 +36,10 @@ class Migration(abc.ABC):
@abc.abstractmethod
async def need_migrate(self) -> bool:
"""判断当前环境是否需要运行此迁移"""
"""Determine if the current environment needs to run this migration"""
pass
@abc.abstractmethod
async def run(self):
"""执行迁移"""
"""Run migration"""
pass

View File

@@ -9,7 +9,7 @@ preregistered_notes: list[typing.Type[LaunchNote]] = []
def note_class(name: str, number: int):
"""注册一个启动信息"""
"""Register a launch information"""
def decorator(cls: typing.Type[LaunchNote]) -> typing.Type[LaunchNote]:
cls.name = name
@@ -21,7 +21,7 @@ def note_class(name: str, number: int):
class LaunchNote(abc.ABC):
"""启动信息"""
"""Launch information"""
name: str
@@ -34,10 +34,10 @@ class LaunchNote(abc.ABC):
@abc.abstractmethod
async def need_show(self) -> bool:
"""判断当前环境是否需要显示此启动信息"""
"""Determine if the current environment needs to display this launch information"""
pass
@abc.abstractmethod
async def yield_note(self) -> typing.AsyncGenerator[typing.Tuple[str, int], None]:
"""生成启动信息"""
"""Generate launch information"""
pass

View File

@@ -7,7 +7,7 @@ from .. import note
@note.note_class('ClassicNotes', 1)
class ClassicNotes(note.LaunchNote):
"""经典启动信息"""
"""Classic launch information"""
async def need_show(self) -> bool:
return True

View File

@@ -9,7 +9,7 @@ from .. import note
@note.note_class('SelectionModeOnWindows', 2)
class SelectionModeOnWindows(note.LaunchNote):
"""Windows 上的选择模式提示信息"""
"""Selection mode prompt information on Windows"""
async def need_show(self) -> bool:
return os.name == 'nt'
@@ -19,3 +19,8 @@ class SelectionModeOnWindows(note.LaunchNote):
"""您正在使用 Windows 系统,若窗口左上角显示处于”选择“模式,程序将被暂停运行,此时请右键窗口中空白区域退出选择模式。""",
logging.INFO,
)
yield (
"""You are using Windows system, if the top left corner of the window displays "Selection" mode, the program will be paused running, please right-click on the blank area in the window to exit the selection mode.""",
logging.INFO,
)

View File

@@ -7,9 +7,9 @@ from . import app
preregistered_stages: dict[str, typing.Type[BootingStage]] = {}
"""预注册的请求处理阶段。在初始化时,所有请求处理阶段类会被注册到此字典中。
"""Pre-registered request processing stages. All request processing stage classes are registered in this dictionary during initialization.
当前阶段暂不支持扩展
Currently not supported for extension
"""
@@ -22,11 +22,11 @@ def stage_class(name: str):
class BootingStage(abc.ABC):
"""启动阶段"""
"""Booting stage"""
name: str = None
@abc.abstractmethod
async def run(self, ap: app.Application):
"""启动"""
"""Run"""
pass

View File

@@ -9,6 +9,7 @@ from ...command import cmdmgr
from ...provider.session import sessionmgr as llm_session_mgr
from ...provider.modelmgr import modelmgr as llm_model_mgr
from ...provider.tools import toolmgr as llm_tool_mgr
from ...rag.knowledge import kbmgr as rag_mgr
from ...platform import botmgr as im_mgr
from ...persistence import mgr as persistencemgr
from ...api.http.controller import main as http_controller
@@ -16,17 +17,20 @@ from ...api.http.service import user as user_service
from ...api.http.service import model as model_service
from ...api.http.service import pipeline as pipeline_service
from ...api.http.service import bot as bot_service
from ...api.http.service import knowledge as knowledge_service
from ...discover import engine as discover_engine
from ...storage import mgr as storagemgr
from ...utils import logcache
from ...vector import mgr as vectordb_mgr
from .. import taskmgr
@stage.stage_class('BuildAppStage')
class BuildAppStage(stage.BootingStage):
"""构建应用阶段"""
"""Build LangBot application"""
async def run(self, ap: app.Application):
"""构建app对象的各个组件对象并初始化"""
"""Build LangBot application"""
ap.task_mgr = taskmgr.AsyncTaskManager(ap)
discover = discover_engine.ComponentDiscoveryEngine(ap)
@@ -41,7 +45,7 @@ class BuildAppStage(stage.BootingStage):
await ver_mgr.initialize()
ap.ver_mgr = ver_mgr
# 发送公告
# Send announcement
ann_mgr = announce.AnnouncementManager(ap)
ap.ann_mgr = ann_mgr
@@ -50,6 +54,10 @@ class BuildAppStage(stage.BootingStage):
log_cache = logcache.LogCache()
ap.log_cache = log_cache
storage_mgr_inst = storagemgr.StorageMgr(ap)
await storage_mgr_inst.initialize()
ap.storage_mgr = storage_mgr_inst
persistence_mgr_inst = persistencemgr.PersistenceManager(ap)
ap.persistence_mgr = persistence_mgr_inst
await persistence_mgr_inst.initialize()
@@ -83,6 +91,15 @@ class BuildAppStage(stage.BootingStage):
await pipeline_mgr.initialize()
ap.pipeline_mgr = pipeline_mgr
rag_mgr_inst = rag_mgr.RAGManager(ap)
await rag_mgr_inst.initialize()
ap.rag_mgr = rag_mgr_inst
# 初始化向量数据库管理器
vectordb_mgr_inst = vectordb_mgr.VectorDBManager(ap)
await vectordb_mgr_inst.initialize()
ap.vector_db_mgr = vectordb_mgr_inst
http_ctrl = http_controller.HTTPController(ap)
await http_ctrl.initialize()
ap.http_ctrl = http_ctrl
@@ -90,8 +107,11 @@ class BuildAppStage(stage.BootingStage):
user_service_inst = user_service.UserService(ap)
ap.user_service = user_service_inst
model_service_inst = model_service.ModelsService(ap)
ap.model_service = model_service_inst
llm_model_service_inst = model_service.LLMModelsService(ap)
ap.llm_model_service = llm_model_service_inst
embedding_models_service_inst = model_service.EmbeddingModelsService(ap)
ap.embedding_models_service = embedding_models_service_inst
pipeline_service_inst = pipeline_service.PipelineService(ap)
ap.pipeline_service = pipeline_service_inst
@@ -99,5 +119,8 @@ class BuildAppStage(stage.BootingStage):
bot_service_inst = bot_service.BotService(ap)
ap.bot_service = bot_service_inst
knowledge_service_inst = knowledge_service.KnowledgeService(ap)
ap.knowledge_service = knowledge_service_inst
ctrl = controller.Controller(ap)
ap.ctrl = ctrl

View File

@@ -7,11 +7,18 @@ from .. import stage, app
@stage.stage_class('GenKeysStage')
class GenKeysStage(stage.BootingStage):
"""生成密钥阶段"""
"""Generate keys stage"""
async def run(self, ap: app.Application):
"""启动"""
"""Generate keys"""
if not ap.instance_config.data['system']['jwt']['secret']:
ap.instance_config.data['system']['jwt']['secret'] = secrets.token_hex(16)
await ap.instance_config.dump_config()
if 'recovery_key' not in ap.instance_config.data['system']:
ap.instance_config.data['system']['recovery_key'] = ''
if not ap.instance_config.data['system']['recovery_key']:
ap.instance_config.data['system']['recovery_key'] = secrets.token_hex(3).upper()
await ap.instance_config.dump_config()

View File

@@ -8,10 +8,10 @@ from ..bootutils import config
@stage.stage_class('LoadConfigStage')
class LoadConfigStage(stage.BootingStage):
"""加载配置文件阶段"""
"""Load config file stage"""
async def run(self, ap: app.Application):
"""启动"""
"""Load config file"""
# ======= deprecated =======
if os.path.exists('data/config/command.json'):

View File

@@ -11,10 +11,13 @@ importutil.import_modules_in_pkg(migrations)
@stage.stage_class('MigrationStage')
class MigrationStage(stage.BootingStage):
"""迁移阶段"""
"""Migration stage
These migrations are legacy, only performed in version 3.x
"""
async def run(self, ap: app.Application):
"""启动"""
"""Run migration"""
if any(
[
@@ -29,7 +32,7 @@ class MigrationStage(stage.BootingStage):
migrations = migration.preregistered_migrations
# 按照迁移号排序
# Sort by migration number
migrations.sort(key=lambda x: x.number)
for migration_cls in migrations:
@@ -37,4 +40,4 @@ class MigrationStage(stage.BootingStage):
if await migration_instance.need_migrate():
await migration_instance.run()
print(f'已执行迁移 {migration_instance.name}')
print(f'Migration {migration_instance.name} executed')

View File

@@ -8,7 +8,7 @@ from ..bootutils import log
class PersistenceHandler(logging.Handler, object):
"""
保存日志到数据库
Save logs to database
"""
ap: app.Application
@@ -19,9 +19,9 @@ class PersistenceHandler(logging.Handler, object):
def emit(self, record):
"""
emit函数为自定义handler类时必重写的函数这里可以根据需要对日志消息做一些处理比如发送日志到服务器
emit function is a required function for custom handler classes, here you can process the log messages as needed, such as sending logs to the server
发出记录(Emit a record)
Emit a record
"""
try:
msg = self.format(record)
@@ -34,10 +34,10 @@ class PersistenceHandler(logging.Handler, object):
@stage.stage_class('SetupLoggerStage')
class SetupLoggerStage(stage.BootingStage):
"""设置日志器阶段"""
"""Setup logger stage"""
async def run(self, ap: app.Application):
"""启动"""
"""Setup logger"""
persistence_handler = PersistenceHandler('LoggerHandler', ap)
extra_handlers = []

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
import asyncio
from .. import stage, app, note
from ...utils import importutil
@@ -10,21 +12,25 @@ importutil.import_modules_in_pkg(notes)
@stage.stage_class('ShowNotesStage')
class ShowNotesStage(stage.BootingStage):
"""显示启动信息阶段"""
"""Show notes stage"""
async def run(self, ap: app.Application):
# 排序
# Sort
note.preregistered_notes.sort(key=lambda x: x.number)
for note_cls in note.preregistered_notes:
try:
note_inst = note_cls(ap)
if await note_inst.need_show():
async def ayield_note(note_inst: note.LaunchNote):
async for ret in note_inst.yield_note():
if not ret:
continue
msg, level = ret
if msg:
ap.logger.log(level, msg)
asyncio.create_task(ayield_note(note_inst))
except Exception:
continue

View File

@@ -9,13 +9,13 @@ from . import entities as core_entities
class TaskContext:
"""任务跟踪上下文"""
"""Task tracking context"""
current_action: str
"""当前正在执行的动作"""
"""Current action being executed"""
log: str
"""记录日志"""
"""Log"""
def __init__(self):
self.current_action = 'default'
@@ -58,40 +58,40 @@ placeholder_context: TaskContext | None = None
class TaskWrapper:
"""任务包装器"""
"""Task wrapper"""
_id_index: int = 0
"""任务ID索引"""
"""Task ID index"""
id: int
"""任务ID"""
"""Task ID"""
task_type: str = 'system' # 任务类型: system user
"""任务类型"""
task_type: str = 'system' # Task type: system or user
"""Task type"""
kind: str = 'system_task' # 由发起者确定任务种类,通常同质化的任务种类相同
"""任务种类"""
kind: str = 'system_task' # Task type determined by the initiator, usually the same task type
"""Task type"""
name: str = ''
"""任务唯一名称"""
"""Task unique name"""
label: str = ''
"""任务显示名称"""
"""Task display name"""
task_context: TaskContext
"""任务上下文"""
"""Task context"""
task: asyncio.Task
"""任务"""
"""Task"""
task_stack: list = None
"""任务堆栈"""
"""Task stack"""
ap: app.Application
"""应用实例"""
"""Application instance"""
scopes: list[core_entities.LifecycleControlScope]
"""任务所属生命周期控制范围"""
"""Task scope"""
def __init__(
self,
@@ -165,13 +165,13 @@ class TaskWrapper:
class AsyncTaskManager:
"""保存app中的所有异步任务
包含系统级的和用户级(插件安装、更新等由用户直接发起的)的"""
"""Save all asynchronous tasks in the app
Include system-level and user-level (plugin installation, update, etc. initiated by users directly)"""
ap: app.Application
tasks: list[TaskWrapper]
"""所有任务"""
"""All tasks"""
def __init__(self, ap: app.Application):
self.ap = ap

View File

@@ -15,7 +15,7 @@ class I18nString(pydantic.BaseModel):
en_US: str
"""英文"""
zh_CN: typing.Optional[str] = None
zh_Hans: typing.Optional[str] = None
"""中文"""
ja_JP: typing.Optional[str] = None
@@ -26,8 +26,8 @@ class I18nString(pydantic.BaseModel):
dic = {}
if self.en_US is not None:
dic['en_US'] = self.en_US
if self.zh_CN is not None:
dic['zh_CN'] = self.zh_CN
if self.zh_Hans is not None:
dic['zh_Hans'] = self.zh_Hans
if self.ja_JP is not None:
dic['ja_JP'] = self.ja_JP
return dic

View File

View File

@@ -0,0 +1,9 @@
from __future__ import annotations
class AdapterNotFoundError(Exception):
def __init__(self, adapter_name: str):
self.adapter_name = adapter_name
def __str__(self):
return f'Adapter {self.adapter_name} not found'

View File

@@ -0,0 +1,9 @@
from __future__ import annotations
class RequesterNotFoundError(Exception):
def __init__(self, requester_name: str):
self.requester_name = requester_name
def __str__(self):
return f'Requester {self.requester_name} not found'

View File

@@ -4,7 +4,7 @@ from .base import Base
class Bot(Base):
"""机器人"""
"""Bot"""
__tablename__ = 'bots'

View File

@@ -12,7 +12,7 @@ initial_metadata = [
class Metadata(Base):
"""数据库元数据"""
"""Database metadata"""
__tablename__ = 'metadata'

View File

@@ -4,7 +4,7 @@ from .base import Base
class LLMModel(Base):
"""LLM 模型"""
"""LLM model"""
__tablename__ = 'llm_models'
@@ -23,3 +23,24 @@ class LLMModel(Base):
server_default=sqlalchemy.func.now(),
onupdate=sqlalchemy.func.now(),
)
class EmbeddingModel(Base):
"""Embedding 模型"""
__tablename__ = 'embedding_models'
uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
description = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
requester = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
requester_config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={})
api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False)
extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={})
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
updated_at = sqlalchemy.Column(
sqlalchemy.DateTime,
nullable=False,
server_default=sqlalchemy.func.now(),
onupdate=sqlalchemy.func.now(),
)

View File

@@ -4,7 +4,7 @@ from .base import Base
class LegacyPipeline(Base):
"""旧版流水线"""
"""Legacy pipeline"""
__tablename__ = 'legacy_pipelines'
@@ -20,13 +20,12 @@ class LegacyPipeline(Base):
)
for_version = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
is_default = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False)
stages = sqlalchemy.Column(sqlalchemy.JSON, nullable=False)
config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False)
class PipelineRunRecord(Base):
"""流水线运行记录"""
"""Pipeline run record"""
__tablename__ = 'pipeline_run_records'
@@ -43,3 +42,4 @@ class PipelineRunRecord(Base):
started_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False)
finished_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False)
result = sqlalchemy.Column(sqlalchemy.JSON, nullable=False)
knowledge_base_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=True)

View File

@@ -4,7 +4,7 @@ from .base import Base
class PluginSetting(Base):
"""插件配置"""
"""Plugin setting"""
__tablename__ = 'plugin_settings'

View File

@@ -0,0 +1,50 @@
import sqlalchemy
from .base import Base
# Base = declarative_base()
# DATABASE_URL = os.getenv('DATABASE_URL', 'sqlite:///./rag_knowledge.db')
# print("Using database URL:", DATABASE_URL)
# engine = create_engine(DATABASE_URL, connect_args={'check_same_thread': False})
# SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# def create_db_and_tables():
# """Creates all database tables defined in the Base."""
# Base.metadata.create_all(bind=engine)
# print('Database tables created or already exist.')
class KnowledgeBase(Base):
__tablename__ = 'knowledge_bases'
uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
name = sqlalchemy.Column(sqlalchemy.String, index=True)
description = sqlalchemy.Column(sqlalchemy.Text)
created_at = sqlalchemy.Column(sqlalchemy.DateTime, default=sqlalchemy.func.now())
embedding_model_uuid = sqlalchemy.Column(sqlalchemy.String, default='')
top_k = sqlalchemy.Column(sqlalchemy.Integer, default=5)
class File(Base):
__tablename__ = 'knowledge_base_files'
uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
kb_id = sqlalchemy.Column(sqlalchemy.String(255), nullable=True)
file_name = sqlalchemy.Column(sqlalchemy.String)
extension = sqlalchemy.Column(sqlalchemy.String)
created_at = sqlalchemy.Column(sqlalchemy.DateTime, default=sqlalchemy.func.now())
status = sqlalchemy.Column(sqlalchemy.String, default='pending') # pending, processing, completed, failed
class Chunk(Base):
__tablename__ = 'knowledge_base_chunks'
uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
file_id = sqlalchemy.Column(sqlalchemy.String(255), nullable=True)
text = sqlalchemy.Column(sqlalchemy.Text)
# class Vector(Base):
# __tablename__ = 'knowledge_base_vectors'
# uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
# chunk_id = sqlalchemy.Column(sqlalchemy.String, nullable=True)
# embedding = sqlalchemy.Column(sqlalchemy.LargeBinary)

View File

@@ -0,0 +1,13 @@
from sqlalchemy import Column, Integer, ForeignKey, LargeBinary
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class Vector(Base):
__tablename__ = 'vectors'
id = Column(Integer, primary_key=True, index=True)
chunk_id = Column(Integer, ForeignKey('chunks.id'), unique=True)
embedding = Column(LargeBinary) # Store embeddings as binary
chunk = relationship('Chunk', back_populates='vector')

View File

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
import pydantic
from typing import Any
class RetrieveResultEntry(pydantic.BaseModel):
id: str
metadata: dict[str, Any]
distance: float

View File

@@ -11,7 +11,7 @@ preregistered_managers: list[type[BaseDatabaseManager]] = []
def manager_class(name: str) -> None:
"""注册一个数据库管理类"""
"""Register a database manager class"""
def decorator(cls: type[BaseDatabaseManager]) -> type[BaseDatabaseManager]:
cls.name = name
@@ -22,7 +22,7 @@ def manager_class(name: str) -> None:
class BaseDatabaseManager(abc.ABC):
"""基础数据库管理类"""
"""Base database manager class"""
name: str

View File

@@ -7,7 +7,7 @@ from .. import database
@database.manager_class('sqlite')
class SQLiteDatabaseManager(database.BaseDatabaseManager):
"""SQLite 数据库管理类"""
"""SQLite database manager"""
async def initialize(self) -> None:
sqlite_path = 'data/langbot.db'

View File

@@ -22,12 +22,12 @@ importutil.import_modules_in_pkg(persistence)
class PersistenceManager:
"""持久化模块管理器"""
"""Persistence module manager"""
ap: app.Application
db: database.BaseDatabaseManager
"""数据库管理器"""
"""Database manager"""
meta: sqlalchemy.MetaData
@@ -66,22 +66,25 @@ class PersistenceManager:
# write default pipeline
result = await self.execute_async(sqlalchemy.select(pipeline.LegacyPipeline))
default_pipeline_uuid = None
if result.first() is None:
self.ap.logger.info('Creating default pipeline...')
pipeline_config = json.load(open('templates/default-pipeline-config.json', 'r', encoding='utf-8'))
default_pipeline_uuid = str(uuid.uuid4())
pipeline_data = {
'uuid': str(uuid.uuid4()),
'uuid': default_pipeline_uuid,
'for_version': self.ap.ver_mgr.get_current_version(),
'stages': pipeline_service.default_stage_order,
'is_default': True,
'name': 'ChatPipeline',
'description': '默认提供的流水线,您配置的机器人、第一个模型将自动绑定到此流水线',
'description': 'Default pipeline, new bots will be bound to this pipeline | 默认提供的流水线,您配置的机器人将自动绑定到此流水线',
'config': pipeline_config,
}
await self.execute_async(sqlalchemy.insert(pipeline.LegacyPipeline).values(pipeline_data))
# =================================
# run migrations

View File

@@ -10,7 +10,7 @@ preregistered_db_migrations: list[typing.Type[DBMigration]] = []
def migration_class(number: int):
"""迁移类装饰器"""
"""Migration class decorator"""
def wrapper(cls: typing.Type[DBMigration]) -> typing.Type[DBMigration]:
cls.number = number
@@ -21,20 +21,20 @@ def migration_class(number: int):
class DBMigration(abc.ABC):
"""数据库迁移"""
"""Database migration"""
number: int
"""迁移号"""
"""Migration number"""
def __init__(self, ap: app.Application):
self.ap = ap
@abc.abstractmethod
async def upgrade(self):
"""升级"""
"""Upgrade"""
pass
@abc.abstractmethod
async def downgrade(self):
"""降级"""
"""Downgrade"""
pass

View File

@@ -15,21 +15,21 @@ from ...entity.persistence import (
@migration.migration_class(1)
class DBMigrateV3Config(migration.DBMigration):
"""从 v3 的配置迁移到 v4 的数据库"""
"""Migrate v3 config to v4 database"""
async def upgrade(self):
"""升级"""
"""Upgrade"""
"""
将 data/config 下的所有配置文件进行迁移。
迁移后,之前的配置文件都保存到 data/legacy/config 下。
迁移后data/metadata/ 下的所有配置文件都保存到 data/legacy/metadata 下。
Migrate all config files under data/config.
After migration, all previous config files are saved under data/legacy/config.
After migration, all config files under data/metadata/ are saved under data/legacy/metadata.
"""
if self.ap.provider_cfg is None:
return
# ======= 迁移模型 =======
# 只迁移当前选中的模型
# ======= Migrate model =======
# Only migrate the currently selected model
model_name = self.ap.provider_cfg.data.get('model', 'gpt-4o')
model_requester = 'openai-chat-completions'
@@ -91,8 +91,8 @@ class DBMigrateV3Config(migration.DBMigration):
sqlalchemy.insert(persistence_model.LLMModel).values(**llm_model_data)
)
# ======= 迁移流水线配置 =======
# 修改到默认流水线
# ======= Migrate pipeline config =======
# Modify to default pipeline
default_pipeline = [
self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline)
for pipeline in (
@@ -184,8 +184,8 @@ class DBMigrateV3Config(migration.DBMigration):
.where(persistence_pipeline.LegacyPipeline.uuid == default_pipeline['uuid'])
)
# ======= 迁移机器人 =======
# 只迁移启用的机器人
# ======= Migrate bot =======
# Only migrate enabled bots
for adapter in self.ap.platform_cfg.data.get('platform-adapters', []):
if not adapter.get('enable'):
continue
@@ -207,7 +207,7 @@ class DBMigrateV3Config(migration.DBMigration):
await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_bot.Bot).values(**bot_data))
# ======= 迁移系统设置 =======
# ======= Migrate system settings =======
self.ap.instance_config.data['admins'] = self.ap.system_cfg.data['admin-sessions']
self.ap.instance_config.data['api']['port'] = self.ap.system_cfg.data['http-api']['port']
self.ap.instance_config.data['command'] = {
@@ -223,7 +223,7 @@ class DBMigrateV3Config(migration.DBMigration):
await self.ap.instance_config.dump_config()
# ======= move files =======
# 迁移 data/config 下的所有配置文件
# Migrate all config files under data/config
all_legacy_dir_name = [
'config',
# 'metadata',
@@ -246,4 +246,4 @@ class DBMigrateV3Config(migration.DBMigration):
move_legacy_files(dir_name)
async def downgrade(self):
"""降级"""
"""Downgrade"""

View File

@@ -0,0 +1,41 @@
from .. import migration
import sqlalchemy
from ...entity.persistence import pipeline as persistence_pipeline
@migration.migration_class(2)
class DBMigrateCombineQuoteMsgConfig(migration.DBMigration):
"""Combine quote message config"""
async def upgrade(self):
"""Upgrade"""
# read all pipelines
pipelines = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_pipeline.LegacyPipeline))
for pipeline in pipelines:
serialized_pipeline = self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline)
config = serialized_pipeline['config']
if 'misc' not in config['trigger']:
config['trigger']['misc'] = {}
if 'combine-quote-message' not in config['trigger']['misc']:
config['trigger']['misc']['combine-quote-message'] = False
await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(persistence_pipeline.LegacyPipeline)
.where(persistence_pipeline.LegacyPipeline.uuid == serialized_pipeline['uuid'])
.values(
{
'config': config,
'for_version': self.ap.ver_mgr.get_current_version(),
}
)
)
async def downgrade(self):
"""Downgrade"""
pass

View File

@@ -0,0 +1,49 @@
from .. import migration
import sqlalchemy
from ...entity.persistence import pipeline as persistence_pipeline
@migration.migration_class(3)
class DBMigrateN8nConfig(migration.DBMigration):
"""N8n config"""
async def upgrade(self):
"""Upgrade"""
# read all pipelines
pipelines = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_pipeline.LegacyPipeline))
for pipeline in pipelines:
serialized_pipeline = self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline)
config = serialized_pipeline['config']
if 'n8n-service-api' not in config['ai']:
config['ai']['n8n-service-api'] = {
'webhook-url': 'http://your-n8n-webhook-url',
'auth-type': 'none',
'basic-username': '',
'basic-password': '',
'jwt-secret': '',
'jwt-algorithm': 'HS256',
'header-name': '',
'header-value': '',
'timeout': 120,
'output-key': 'response',
}
await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(persistence_pipeline.LegacyPipeline)
.where(persistence_pipeline.LegacyPipeline.uuid == serialized_pipeline['uuid'])
.values(
{
'config': config,
'for_version': self.ap.ver_mgr.get_current_version(),
}
)
)
async def downgrade(self):
"""Downgrade"""
pass

Some files were not shown because too many files have changed in this diff Show More