diff --git a/README.md b/README.md index c9341668..6e677369 100644 --- a/README.md +++ b/README.md @@ -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 平台 | diff --git a/pkg/persistence/migrations/dbm001_migrate_v3_config.py b/pkg/persistence/migrations/dbm001_migrate_v3_config.py index 58f05e04..6d53718d 100644 --- a/pkg/persistence/migrations/dbm001_migrate_v3_config.py +++ b/pkg/persistence/migrations/dbm001_migrate_v3_config.py @@ -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'] diff --git a/pkg/pipeline/bansess/bansess.py b/pkg/pipeline/bansess/bansess.py index 15b68b08..a1cad2b1 100644 --- a/pkg/pipeline/bansess/bansess.py +++ b/pkg/pipeline/bansess/bansess.py @@ -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 diff --git a/pkg/pipeline/process/handlers/chat.py b/pkg/pipeline/process/handlers/chat.py index ed5380f6..b6da5fa6 100644 --- a/pkg/pipeline/process/handlers/chat.py +++ b/pkg/pipeline/process/handlers/chat.py @@ -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())}') diff --git a/pkg/pipeline/process/process.py b/pkg/pipeline/process/process.py index c1f258d8..27632f28 100644 --- a/pkg/pipeline/process/process.py +++ b/pkg/pipeline/process/process.py @@ -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() diff --git a/pkg/platform/sources/dingtalk.py b/pkg/platform/sources/dingtalk.py index 892808cd..f3d3d9f5 100644 --- a/pkg/platform/sources/dingtalk.py +++ b/pkg/platform/sources/dingtalk.py @@ -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 diff --git a/pkg/utils/announce.py b/pkg/utils/announce.py index 64e4ba88..8778a04f 100644 --- a/pkg/utils/announce.py +++ b/pkg/utils/announce.py @@ -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'): diff --git a/pkg/utils/version.py b/pkg/utils/version.py index b26b1e33..3a2748fc 100644 --- a/pkg/utils/version.py +++ b/pkg/utils/version.py @@ -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): """检查更新并下载源码""" diff --git a/templates/config.yaml b/templates/config.yaml index 942ace3f..a0c22c8a 100644 --- a/templates/config.yaml +++ b/templates/config.yaml @@ -2,6 +2,7 @@ admins: [] api: port: 5300 command: + enable: true prefix: - '!' - ! diff --git a/templates/metadata/pipeline/trigger.yaml b/templates/metadata/pipeline/trigger.yaml index 949b2698..cecfc59d 100644 --- a/templates/metadata/pipeline/trigger.yaml +++ b/templates/metadata/pipeline/trigger.yaml @@ -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: [] diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 25b3b58f..4009c77a 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -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(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({
+ {starCount !== null && ( +
{ + 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" + > + + + + +
+ {starCount.toLocaleString()} +
+
+ )} + { // open docs.langbot.app