mirror of
https://github.com/langbot-app/LangBot.git
synced 2025-11-25 03:15:06 +08:00
Merge branch 'master' into refactor/new-plugin-system
This commit is contained in:
@@ -107,9 +107,9 @@ docker compose up -d
|
||||
| [Anthropic](https://www.anthropic.com/) | ✅ | |
|
||||
| [xAI](https://x.ai/) | ✅ | |
|
||||
| [智谱AI](https://open.bigmodel.cn/) | ✅ | |
|
||||
| [胜算云](https://www.shengsuanyun.com/?from=CH_KYIPP758) | ✅ | 全球大模型都可调用(友情推荐) |
|
||||
| [优云智算](https://www.compshare.cn/?ytag=GPU_YY-gh_langbot) | ✅ | 大模型和 GPU 资源平台 |
|
||||
| [PPIO](https://ppinfra.com/user/register?invited_by=QJKFYD&utm_source=github_langbot) | ✅ | 大模型和 GPU 资源平台 |
|
||||
| [胜算云](https://www.shengsuanyun.com/?from=CH_KYIPP758) | ✅ | 大模型和 GPU 资源平台 |
|
||||
| [302.AI](https://share.302.ai/SuTG99) | ✅ | 大模型聚合平台 |
|
||||
| [Google Gemini](https://aistudio.google.com/prompts/new_chat) | ✅ | |
|
||||
| [Dify](https://dify.ai) | ✅ | LLMOps 平台 |
|
||||
|
||||
@@ -212,6 +212,7 @@ class DBMigrateV3Config(migration.DBMigration):
|
||||
self.ap.instance_config.data['api']['port'] = self.ap.system_cfg.data['http-api']['port']
|
||||
self.ap.instance_config.data['command'] = {
|
||||
'prefix': self.ap.command_cfg.data['command-prefix'],
|
||||
'enable': self.ap.command_cfg.data['command-enable'],
|
||||
'privilege': self.ap.command_cfg.data['privilege'],
|
||||
}
|
||||
self.ap.instance_config.data['concurrency']['pipeline'] = self.ap.system_cfg.data['pipeline-concurrency']
|
||||
|
||||
@@ -30,6 +30,10 @@ class BanSessionCheckStage(stage.PipelineStage):
|
||||
if sess == f'{query.launcher_type.value}_{query.launcher_id}':
|
||||
found = True
|
||||
break
|
||||
# 使用 *_id 来表示加白/拉黑某用户的私聊和群聊场景
|
||||
if sess.startswith('*_') and (sess[2:] == query.launcher_id or sess[2:] == query.sender_id):
|
||||
found = True
|
||||
break
|
||||
|
||||
ctn = False
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ class ChatMessageHandler(handler.MessageHandler):
|
||||
|
||||
event_ctx = await self.ap.plugin_connector.emit_event(event)
|
||||
|
||||
is_create_card = False # 判断下是否需要创建流式卡片
|
||||
if event_ctx.is_prevented_default():
|
||||
if event_ctx.event.reply is not None:
|
||||
mc = platform_message.MessageChain(event_ctx.event.reply)
|
||||
@@ -74,14 +75,17 @@ class ChatMessageHandler(handler.MessageHandler):
|
||||
raise ValueError(f'未找到请求运行器: {query.pipeline_config["ai"]["runner"]["runner"]}')
|
||||
if is_stream:
|
||||
resp_message_id = uuid.uuid4()
|
||||
await query.adapter.create_message_card(str(resp_message_id), query.message_event)
|
||||
|
||||
async for result in runner.run(query):
|
||||
result.resp_message_id = str(resp_message_id)
|
||||
if query.resp_messages:
|
||||
query.resp_messages.pop()
|
||||
if query.resp_message_chain:
|
||||
query.resp_message_chain.pop()
|
||||
|
||||
# 此时连接外部 AI 服务正常,创建卡片
|
||||
if not is_create_card: # 只有不是第一次才创建卡片
|
||||
await query.adapter.create_message_card(str(resp_message_id), query.message_event)
|
||||
is_create_card = True
|
||||
query.resp_messages.append(result)
|
||||
self.ap.logger.info(f'对话({query.query_id})流式响应: {self.cut_str(result.readable_str())}')
|
||||
|
||||
|
||||
@@ -42,12 +42,14 @@ class Processor(stage.PipelineStage):
|
||||
|
||||
async def generator():
|
||||
cmd_prefix = self.ap.instance_config.data['command']['prefix']
|
||||
cmd_enable = self.ap.instance_config.data['command'].get('enable', True)
|
||||
|
||||
if any(message_text.startswith(prefix) for prefix in cmd_prefix):
|
||||
async for result in self.cmd_handler.handle(query):
|
||||
yield result
|
||||
if cmd_enable and any(message_text.startswith(prefix) for prefix in cmd_prefix):
|
||||
handler_to_use = self.cmd_handler
|
||||
else:
|
||||
async for result in self.chat_handler.handle(query):
|
||||
yield result
|
||||
handler_to_use = self.chat_handler
|
||||
|
||||
async for result in handler_to_use.handle(query):
|
||||
yield result
|
||||
|
||||
return generator()
|
||||
|
||||
@@ -20,6 +20,9 @@ class DingTalkMessageConverter(abstract_platform_adapter.AbstractMessageConverte
|
||||
at = True
|
||||
if type(msg) is platform_message.Plain:
|
||||
content += msg.text
|
||||
if type(msg) is platform_message.Forward:
|
||||
for node in msg.node_list:
|
||||
content += (await DingTalkMessageConverter.yiri2target(node.message_chain))[0]
|
||||
return content, at
|
||||
|
||||
@staticmethod
|
||||
@@ -58,7 +61,7 @@ class DingTalkEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
if event.conversation == 'FriendMessage':
|
||||
return platform_events.FriendMessage(
|
||||
sender=platform_entities.Friend(
|
||||
id=event.incoming_message.sender_id,
|
||||
id=event.incoming_message.sender_staff_id,
|
||||
nickname=event.incoming_message.sender_nick,
|
||||
remark='',
|
||||
),
|
||||
@@ -68,7 +71,7 @@ class DingTalkEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
)
|
||||
elif event.conversation == 'GroupMessage':
|
||||
sender = platform_entities.GroupMember(
|
||||
id=event.incoming_message.sender_id,
|
||||
id=event.incoming_message.sender_staff_id,
|
||||
member_name=event.incoming_message.sender_nick,
|
||||
permission='MEMBER',
|
||||
group=platform_entities.Group(
|
||||
@@ -162,8 +165,11 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
content, at = await DingTalkMessageConverter.yiri2target(message)
|
||||
|
||||
card_instance, card_instance_id = self.card_instance_id_dict[message_id]
|
||||
if not content and bot_message.content:
|
||||
content = bot_message.content # 兼容直接传入content的情况
|
||||
# print(card_instance_id)
|
||||
await self.bot.send_card_message(card_instance, card_instance_id, content, is_final)
|
||||
if content:
|
||||
await self.bot.send_card_message(card_instance, card_instance_id, content, is_final)
|
||||
if is_final and bot_message.tool_calls is None:
|
||||
# self.seq = 1 # 消息回复结束之后重置seq
|
||||
self.card_instance_id_dict.pop(message_id) # 消息回复结束之后删除卡片实例id
|
||||
|
||||
@@ -45,17 +45,23 @@ class AnnouncementManager:
|
||||
|
||||
async def fetch_all(self) -> list[Announcement]:
|
||||
"""获取所有公告"""
|
||||
resp = requests.get(
|
||||
url='https://api.github.com/repos/langbot-app/LangBot/contents/res/announcement.json',
|
||||
proxies=self.ap.proxy_mgr.get_forward_proxies(),
|
||||
timeout=5,
|
||||
)
|
||||
obj_json = resp.json()
|
||||
b64_content = obj_json['content']
|
||||
# 解码
|
||||
content = base64.b64decode(b64_content).decode('utf-8')
|
||||
try:
|
||||
resp = requests.get(
|
||||
url='https://api.github.com/repos/langbot-app/LangBot/contents/res/announcement.json',
|
||||
proxies=self.ap.proxy_mgr.get_forward_proxies(),
|
||||
timeout=5,
|
||||
)
|
||||
resp.raise_for_status() # 检查请求是否成功
|
||||
obj_json = resp.json()
|
||||
b64_content = obj_json['content']
|
||||
# 解码
|
||||
content = base64.b64decode(b64_content).decode('utf-8')
|
||||
|
||||
return [Announcement(**item) for item in json.loads(content)]
|
||||
return [Announcement(**item) for item in json.loads(content)]
|
||||
except (requests.RequestException, json.JSONDecodeError, KeyError) as e:
|
||||
self.ap.logger.warning(f'获取公告失败: {e}')
|
||||
pass
|
||||
return [] # 请求失败时返回空列表
|
||||
|
||||
async def fetch_saved(self) -> list[Announcement]:
|
||||
if not os.path.exists('data/labels/announcement_saved.json'):
|
||||
|
||||
@@ -28,15 +28,19 @@ class VersionManager:
|
||||
|
||||
async def get_release_list(self) -> list:
|
||||
"""获取发行列表"""
|
||||
rls_list_resp = requests.get(
|
||||
url='https://api.github.com/repos/langbot-app/LangBot/releases',
|
||||
proxies=self.ap.proxy_mgr.get_forward_proxies(),
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
rls_list = rls_list_resp.json()
|
||||
|
||||
return rls_list
|
||||
try:
|
||||
rls_list_resp = requests.get(
|
||||
url='https://api.github.com/repos/langbot-app/LangBot/releases',
|
||||
proxies=self.ap.proxy_mgr.get_forward_proxies(),
|
||||
timeout=5,
|
||||
)
|
||||
rls_list_resp.raise_for_status() # 检查请求是否成功
|
||||
rls_list = rls_list_resp.json()
|
||||
return rls_list
|
||||
except Exception as e:
|
||||
self.ap.logger.warning(f"获取发行列表失败: {e}")
|
||||
pass
|
||||
return []
|
||||
|
||||
async def update_all(self):
|
||||
"""检查更新并下载源码"""
|
||||
|
||||
@@ -2,6 +2,7 @@ admins: []
|
||||
api:
|
||||
port: 5300
|
||||
command:
|
||||
enable: true
|
||||
prefix:
|
||||
- '!'
|
||||
- !
|
||||
|
||||
@@ -79,6 +79,9 @@ stages:
|
||||
label:
|
||||
en_US: Blacklist
|
||||
zh_Hans: 黑名单
|
||||
description:
|
||||
en_US: Sessions in the blacklist will be ignored, the format is `{launcher_type}_{launcher_id}`(remove quotes), for example `person_123` matches private chat, `group_456` matches group chat, `person_*` matches all private chats, `group_*` matches all group chats, `*_123` matches private and group chats with user ID 123
|
||||
zh_Hans: 黑名单中的会话将被忽略;会话格式:`{launcher_type}_{launcher_id}`(删除引号),例如 `person_123` 匹配私聊会话,`group_456` 匹配群聊会话;`person_*` 匹配所有私聊会话,`group_*` 匹配所有群聊会话;`*_123` 匹配用户 ID 为 123 的私聊和群聊消息
|
||||
type: array[string]
|
||||
required: true
|
||||
default: []
|
||||
@@ -86,6 +89,9 @@ stages:
|
||||
label:
|
||||
en_US: Whitelist
|
||||
zh_Hans: 白名单
|
||||
description:
|
||||
en_US: Only respond to sessions in the whitelist, the format is `{launcher_type}_{launcher_id}`(remove quotes), for example `person_123` matches private chat, `group_456` matches group chat, `person_*` matches all private chats, `group_*` matches all group chats, `*_123` matches private and group chats with user ID 123
|
||||
zh_Hans: 仅响应白名单中的会话;会话格式:`{launcher_type}_{launcher_id}`(删除引号),例如 `person_123` 匹配私聊会话,`group_456` 匹配群聊会话;`person_*` 匹配所有私聊会话,`group_*` 匹配所有群聊会话;`*_123` 匹配用户 ID 为 123 的私聊和群聊消息
|
||||
type: array[string]
|
||||
required: true
|
||||
default: []
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { useRouter, usePathname } from 'next/navigation';
|
||||
import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList';
|
||||
import langbotIcon from '@/app/assets/langbot-logo.webp';
|
||||
import { systemInfo } from '@/app/infra/http/HttpClient';
|
||||
import { systemInfo, spaceClient } from '@/app/infra/http/HttpClient';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Moon, Sun, Monitor } from 'lucide-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||
import { LanguageSelector } from '@/components/ui/language-selector';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import PasswordChangeDialog from '@/app/home/components/password-change-dialog/PasswordChangeDialog';
|
||||
|
||||
// TODO 侧边导航栏要加动画
|
||||
@@ -44,6 +45,7 @@ export default function HomeSidebar({
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const [passwordChangeOpen, setPasswordChangeOpen] = useState(false);
|
||||
const [languageSelectorOpen, setLanguageSelectorOpen] = useState(false);
|
||||
const [starCount, setStarCount] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
initSelect();
|
||||
@@ -51,6 +53,16 @@ export default function HomeSidebar({
|
||||
localStorage.setItem('token', 'test-token');
|
||||
localStorage.setItem('userEmail', 'test@example.com');
|
||||
}
|
||||
|
||||
spaceClient
|
||||
.get('/api/v1/dist/info/repo')
|
||||
.then((response) => {
|
||||
const data = response as { repo: { stargazers_count: number } };
|
||||
setStarCount(data.repo.stargazers_count);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch GitHub star count:', error);
|
||||
});
|
||||
return () => console.log('sidebar.unmounted');
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -150,6 +162,30 @@ export default function HomeSidebar({
|
||||
</div>
|
||||
|
||||
<div className={`${styles.sidebarBottomContainer}`}>
|
||||
{starCount !== null && (
|
||||
<div
|
||||
onClick={() => {
|
||||
window.open('https://github.com/langbot-app/LangBot', '_blank');
|
||||
}}
|
||||
className="flex justify-center cursor-pointer p-2 rounded-lg hover:bg-accent/30 transition-colors"
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="hover:bg-secondary/50 px-3 py-1.5 text-sm font-medium transition-colors border-border relative overflow-hidden group"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.464-1.11-1.464-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z" />
|
||||
</svg>
|
||||
<div className="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/20 to-transparent group-hover:translate-x-full transition-transform duration-1000 ease-out"></div>
|
||||
{starCount.toLocaleString()}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SidebarChild
|
||||
onClick={() => {
|
||||
// open docs.langbot.app
|
||||
|
||||
Reference in New Issue
Block a user