Compare commits

...

11 Commits

Author SHA1 Message Date
Soulter 609e723322 v3.4.24 2025-02-10 00:34:02 +08:00
Soulter c564a1d53e fix: raw_completion 没有正确传递 #439 2025-02-10 00:26:53 +08:00
Soulter a7fe31f28b fix: 修复指令不经过唤醒前缀也能生效的问题。在引用消息的时候无法使用前缀唤醒机器人 #444 2025-02-09 22:35:52 +08:00
Soulter a84dc599d6 fix: 修复 /tts 指令 2025-02-09 22:14:10 +08:00
Soulter 8da029add9 feat: 支持 TTS, STT 提供商的显示和快捷切换 2025-02-09 22:08:51 +08:00
Soulter ba45a2d270 feat: 支持设置GitHub反向代理地址 2025-02-09 18:51:53 +08:00
Soulter cb56b22aea Update README.md 2025-02-09 16:49:00 +08:00
Soulter 23cc5b31ba perf: 从压缩包上传插件时,去除branch尾缀 2025-02-09 14:59:27 +08:00
Soulter e8d99f0460 fix: 修复戳一戳消息报错 2025-02-09 13:57:33 +08:00
Soulter 6bcd10cd5c fix: gemini 报错时显示 apikey 2025-02-09 13:56:55 +08:00
Soulter 619fb20c5f fix: drun 不支持函数调用的报错 2025-02-09 01:20:11 +08:00
20 changed files with 237 additions and 56 deletions
+1 -2
View File
@@ -1,7 +1,6 @@
<p align="center">
![logo](https://github.com/user-attachments/assets/07649e07-3b8e-4feb-9aa9-bf13af4f3476)
![6e1279651f16d7fdf4727558b72bbaf1](https://github.com/user-attachments/assets/ead4c551-fc3c-48f7-a6f7-afbfdb820512)
</p>
+1 -1
View File
@@ -2,7 +2,7 @@
如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。
"""
VERSION = "3.4.23"
VERSION = "3.4.24"
DB_PATH = "data/data_v3.db"
# 默认配置
+6 -4
View File
@@ -325,11 +325,13 @@ class RedBag(BaseMessageComponent):
class Poke(BaseMessageComponent):
type: ComponentType = "Poke"
qq: int
type: str = ""
id: T.Optional[int] = 0
qq: T.Optional[int] = 0
def __init__(self, **_):
super().__init__(**_)
def __init__(self, type: str, **_):
type = f"Poke:{type}"
super().__init__(type=type, **_)
class Forward(BaseMessageComponent):
@@ -31,7 +31,7 @@ class StarRequestSubStage(Stage):
# 孤立无援的 star handler
continue
logger.debug(f"执行 Star Handler {handler.handler_full_name}")
logger.debug(f"执行插件 handler {handler.handler_full_name}")
wrapper = self._call_handler(self.ctx, event, handler.handler, **params)
async for ret in wrapper:
yield ret
@@ -18,7 +18,6 @@ class ResultDecorateStage:
self.reply_prefix = ctx.astrbot_config['platform_settings']['reply_prefix']
self.reply_with_mention = ctx.astrbot_config['platform_settings']['reply_with_mention']
self.reply_with_quote = ctx.astrbot_config['platform_settings']['reply_with_quote']
self.use_tts = ctx.astrbot_config['provider_tts_settings']['enable']
self.t2i_word_threshold = ctx.astrbot_config['t2i_word_threshold']
try:
self.t2i_word_threshold = int(self.t2i_word_threshold)
@@ -68,7 +67,7 @@ class ResultDecorateStage:
result.chain = new_chain
# TTS
if self.use_tts and result.is_llm_result():
if self.ctx.astrbot_config['provider_tts_settings']['enable'] and result.is_llm_result():
tts_provider = self.ctx.plugin_manager.context.provider_manager.curr_tts_provider_inst
new_chain = []
for comp in result.chain:
+1 -1
View File
@@ -43,7 +43,7 @@ class WakingCheckStage(Stage):
if event.message_str.startswith(wake_prefix):
if (
not event.is_private_chat()
and (isinstance(messages[0], At) or isinstance(messages[0], Reply))
and isinstance(messages[0], At)
and str(messages[0].qq) != str(event.get_self_id())
and str(messages[0].qq) != "all"
):
+11 -6
View File
@@ -48,13 +48,18 @@ class SimpleGoogleGenAIClient():
logger.debug(f"payload: {payload}")
request_url = f"{self.api_base}/v1beta/models/{model}:generateContent?key={self.api_key}"
async with self.client.post(request_url, json=payload, timeout=self.timeout) as resp:
try:
response = await resp.json()
except Exception as e:
if "application/json" in resp.headers.get("Content-Type"):
try:
response = await resp.json()
except Exception as e:
text = await resp.text()
logger.error(f"Gemini 返回了非 json 数据: {text}")
raise e
return response
else:
text = await resp.text()
logger.error(f"gemini 返回了非 json 数据: {text}")
raise e
return response
logger.error(f"Gemini 返回了非 json 数据: {text}")
raise Exception("Gemini 返回了非 json 数据: ")
@register_provider_adapter("googlegenai_chat_completion", "Google Gemini Chat Completion 提供商适配器")
@@ -105,6 +105,8 @@ class ProviderOpenAIOfficial(Provider):
logger.error(f"API 返回的 completion 无法解析:{completion}")
raise Exception(f"API 返回的 completion 无法解析:{completion}")
llm_response.raw_completion = completion
return llm_response
async def text_chat(
@@ -173,7 +175,10 @@ class ProviderOpenAIOfficial(Provider):
or 'Function call is not supported' in str(e) \
or 'Function calling is not enabled' in str(e) \
or 'Tool calling is not supported' in str(e) \
or 'No endpoints found that support tool use' in str(e): # siliconcloud
or 'No endpoints found that support tool use' in str(e) \
or 'model does not support function calling' in str(e) \
or ('tool' in str(e) and 'support' in str(e).lower()) \
or ('function' in str(e) and 'support' in str(e).lower()):
logger.info(f"{self.get_model()} 不支持函数工具调用,已自动去除,不影响使用。")
if 'tools' in payloads:
del payloads['tools']
+22 -2
View File
@@ -1,8 +1,8 @@
from asyncio import Queue
from typing import List, TypedDict, Union
from typing import List, Union
from astrbot.core import sp
from astrbot.core.provider.provider import Provider
from astrbot.core.provider.provider import Provider, TTSProvider, STTProvider
from astrbot.core.db import BaseDatabase
from astrbot.core.config.astrbot_config import AstrBotConfig
from astrbot.core.provider.func_tool_manager import FuncCall
@@ -127,6 +127,14 @@ class Context:
'''获取所有用于文本生成任务的 LLM Provider(Chat_Completion 类型)。'''
return self.provider_manager.provider_insts
def get_all_tts_providers(self) -> List[TTSProvider]:
'''获取所有用于 TTS 任务的 Provider。'''
return self.provider_manager.tts_provider_insts
def get_all_stt_providers(self) -> List[STTProvider]:
'''获取所有用于 STT 任务的 Provider。'''
return self.provider_manager.stt_provider_insts
def get_using_provider(self) -> Provider:
'''
获取当前使用的用于文本生成任务的 LLM Provider(Chat_Completion 类型)。
@@ -135,6 +143,18 @@ class Context:
'''
return self.provider_manager.curr_provider_inst
def get_using_tts_provider(self) -> TTSProvider:
'''
获取当前使用的用于 TTS 任务的 Provider。
'''
return self.provider_manager.curr_tts_provider_inst
def get_using_stt_provider(self) -> STTProvider:
'''
获取当前使用的用于 STT 任务的 Provider。
'''
return self.provider_manager.curr_stt_provider_inst
def get_config(self) -> AstrBotConfig:
'''获取 AstrBot 的配置。'''
return self._config
+1 -1
View File
@@ -43,7 +43,7 @@ class CommandFilter(HandlerFilter, ParameterValidationMixin):
return self.handler_md
def filter(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool:
if not event.is_wake_up():
if not event.is_at_or_wake_command:
return False
if event.get_extra("parsing_command"):
+1 -1
View File
@@ -37,7 +37,7 @@ class CommandGroupFilter(HandlerFilter):
return result
def filter(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> Tuple[bool, StarHandlerMetadata]:
if not event.is_wake_up():
if not event.is_at_or_wake_command:
return False, None
if event.get_extra("parsing_command"):
+3 -2
View File
@@ -376,14 +376,14 @@ class PluginManager:
logger.debug(f"unbind handler {v.handler_name} from {plugin_name} (map)")
del star_handlers_registry.star_handlers_map[k]
async def update_plugin(self, plugin_name: str):
async def update_plugin(self, plugin_name: str, proxy = ""):
plugin = self.context.get_registered_star(plugin_name)
if not plugin:
raise Exception("插件不存在。")
if plugin.reserved:
raise Exception("该插件是 AstrBot 保留插件,无法更新。")
await self.updator.update(plugin)
await self.updator.update(plugin, proxy=proxy)
await self.reload()
async def turn_off_plugin(self, plugin_name: str):
@@ -428,6 +428,7 @@ class PluginManager:
async def install_plugin_from_file(self, zip_file_path: str):
dir_name = os.path.basename(zip_file_path).replace(".zip", "")
dir_name = dir_name.removesuffix("-master").removesuffix("-main").lower()
desti_dir = os.path.join(self.plugin_store_path, dir_name)
self.updator.unzip_file(zip_file_path, desti_dir)
+5 -1
View File
@@ -23,12 +23,16 @@ class PluginUpdator(RepoZipUpdator):
return plugin_path
async def update(self, plugin: StarMetadata) -> str:
async def update(self, plugin: StarMetadata, proxy="") -> str:
repo_url = plugin.repo
if not repo_url:
raise Exception(f"插件 {plugin.name} 没有指定仓库地址。")
if proxy:
proxy = proxy.removesuffix("/")
repo_url = f"{proxy}/{repo_url}"
plugin_path = os.path.join(self.plugin_store_path, plugin.root_dir_name)
logger.info(f"正在更新插件,路径: {plugin_path},仓库地址: {repo_url}")
+8 -1
View File
@@ -142,6 +142,12 @@ class PluginRoute(Route):
async def install_plugin(self):
post_data = await request.json
repo_url = post_data["url"]
proxy: str = post_data.get("proxy", None)
if proxy:
proxy = proxy.removesuffix("/")
repo_url = f"{proxy}/{repo_url}"
try:
logger.info(f"正在安装插件 {repo_url}")
await self.plugin_manager.install_plugin(repo_url)
@@ -182,9 +188,10 @@ class PluginRoute(Route):
async def update_plugin(self):
post_data = await request.json
plugin_name = post_data["name"]
proxy: str = post_data.get("proxy", None)
try:
logger.info(f"正在更新插件 {plugin_name}")
await self.plugin_manager.update_plugin(plugin_name)
await self.plugin_manager.update_plugin(plugin_name, proxy)
self.core_lifecycle.restart()
logger.info(f"更新插件 {plugin_name} 成功。")
return Response().ok(None, "更新成功。").__dict__
+11
View File
@@ -0,0 +1,11 @@
# What's Changed
0. ✨ 新增: 支持正则表达式匹配触发机器人,机器人在某一段时间内持续唤醒(不用输唤醒词)。(安装 astrbot_plugin_wake_enhance 插件)
2. ✨ 新增: 可以通过 /tts 开关TTS,通过 /provider 更换 TTS #436
3. ✨ 新增: 管理面板支持设置 GitHub 反向代理地址以优化中国大陆地区下载 AstrBot 插件的速度。(在管理面板-设置页)
4. 🐛 修复: 修复指令不经过唤醒前缀也能生效的问题。在引用消息的时候无法使用前缀唤醒机器人 #444
5. 🐛 修复: 修复 Napcat 下戳一戳消息报错
6. 👌 优化: 从压缩包上传插件时,去除仓库 -branch 尾缀
7. 🐛 修复: gemini 报错时显示 apikey
8. 🐛 修复: drun 不支持函数调用的报错
9. 🐛 修复: raw_completion 没有正确传递导致部分插件无法正常运作 #439
@@ -16,12 +16,12 @@ export interface menu {
const sidebarItem: menu[] = [
{
title: '面板',
title: '统计',
icon: 'mdi-view-dashboard',
to: '/dashboard/default'
},
{
title: '配置',
title: '配置文件',
icon: 'mdi-cog',
to: '/config',
},
@@ -40,6 +40,11 @@ const sidebarItem: menu[] = [
icon: 'mdi-console',
to: '/console'
},
{
title: '设置',
icon: 'mdi-wrench',
to: '/settings'
},
{
title: '关于',
icon: 'mdi-information',
+5
View File
@@ -42,6 +42,11 @@ const MainRoutes = {
path: '/chat',
component: () => import('@/views/ChatPage.vue')
},
{
name: 'Settings',
path: '/settings',
component: () => import('@/views/Settings.vue')
},
{
name: 'About',
path: '/about',
+11 -10
View File
@@ -10,8 +10,8 @@ import { max } from 'date-fns';
<template>
<v-row>
<v-alert style="margin: 16px" text="1. 如果因为网络问题安装失败,可以自行前往仓库下载压缩包然后本地上传。2. 如需插件帮助请点击 `仓库` 查看 README" title="💡提示"
type="info" variant="tonal">
<v-alert style="margin: 16px" text="1. 如果因为网络问题安装失败,点击设置页选择 GitHub 加速地址。或前往仓库下载压缩包然后本地上传。" title="💡提示"
type="info" color="primary" variant="tonal">
</v-alert>
<v-col cols="12" md="12">
<div style="background-color: white; width: 100%; padding: 16px; border-radius: 10px;">
@@ -44,13 +44,13 @@ import { max } from 'date-fns';
</v-dialog>
</div>
</div>
</v-col>
</v-col>
<v-col cols="12" md="6" lg="3" v-for="extension in extension_data.data">
<ExtensionCard :key="extension.name" :title="extension.name" :link="extension.repo" :logo="extension?.logo" :has_update="extension.has_update"
style="margin-bottom: 4px;">
<div style="min-height: 140px; max-height: 140px; overflow: none;">
<ExtensionCard :key="extension.name" :title="extension.name" :link="extension.repo" :logo="extension?.logo"
:has_update="extension.has_update" style="margin-bottom: 4px;">
<div style="min-height: 140px; max-height: 140px; overflow: auto;">
<div>
<span style="font-weight: bold;">By @{{ extension.author }}</span>
<span style="font-weight: bold ;">By @{{ extension.author }}</span>
<span> | 插件有 {{ extension.handlers.length }} 个行为</span>
</div>
<span> 当前: <v-chip size="small" color="primary">{{ extension.version }}</v-chip>
@@ -338,7 +338,6 @@ export default {
{ title: '作者', value: 'author' },
{ title: '操作', value: 'actions', sortable: false }
],
alreadyCheckUpdate: false
}
},
@@ -441,7 +440,8 @@ export default {
this.toast("正在从链接 " + this.extension_url + " 安装插件...", "primary");
axios.post('/api/plugin/install',
{
url: this.extension_url
url: this.extension_url,
proxy: localStorage.getItem('selectedGitHubProxy') || ""
}).then((res) => {
this.loading_ = false;
if (res.data.status === "error") {
@@ -482,7 +482,8 @@ export default {
this.loadingDialog.show = true;
axios.post('/api/plugin/update',
{
name: extension_name
name: extension_name,
proxy: localStorage.getItem('selectedGitHubProxy') || ""
}).then((res) => {
if (res.data.status === "error") {
this.onLoadingDialogResult(2, res.data.message, -1);
+52
View File
@@ -0,0 +1,52 @@
<template>
<div style="background-color: white; padding: 8px; padding-left: 16px; border-radius: 8px; margin-bottom: 16px;">
<v-list lines="two">
<v-list-subheader>网络</v-list-subheader>
<v-list-item subtitle="设置下载插件时所用的 GitHub 加速地址。这在中国大陆的网络环境有效。可以自定义,输入结果实时生效" title="GitHub 加速地址">
<v-combobox variant="outlined" style="width: 100%; margin-top: 16px;" v-model="selectedGitHubProxy" :items="githubProxies"
label="选择 GitHub 加速地址">
</v-combobox>
</v-list-item>
</v-list>
</div>
</template>
<script>
export default {
data() {
return {
githubProxies: [
"https://ghproxy.cn",
"https://gh.llkk.cc",
"https://ghproxy.net",
"https://gitproxy.click",
"https://github.tbedu.top"
],
selectedGitHubProxy: "",
}
},
methods: {
},
mounted() {
this.selectedGitHubProxy = localStorage.getItem('selectedGitHubProxy') || "";
},
watch: {
selectedGitHubProxy: function (newVal, oldVal) {
if (!newVal) {
newVal = ""
}
localStorage.setItem('selectedGitHubProxy', newVal);
}
}
}
</script>
+83 -18
View File
@@ -60,6 +60,7 @@ AstrBot 指令:
[System]
/plugin: 查看插件、插件帮助
/t2i: 开关文本转图片
/tts: 开关文本转语音
/sid: 获取会话 ID
/op <admin_id>: 授权管理员(op)
/deop <admin_id>: 取消管理员(op)
@@ -84,10 +85,7 @@ AstrBot 指令:
/websearch: 网页搜索
[其他]
/set <变量名> <值>: 为会话定义变量。适用于 Dify 工作流输入
/unset <变量名>: 删除会话的变量。
提示:如要查看插件指令,请输入 /plugin 查看具体信息。
/set 变量名: 为会话定义变量(Dify 工作流输入)
{notice}"""
event.set_result(MessageEventResult().message(msg).use_t2i(False))
@@ -126,7 +124,7 @@ AstrBot 指令:
tm = self.context.get_llm_tool_manager()
for tool in tm.func_list:
self.context.deactivate_llm_tool(tool.name)
event.set_result(MessageEventResult().message(f"停用所有工具成功。"))
event.set_result(MessageEventResult().message("停用所有工具成功。"))
@filter.command("plugin")
async def plugin(self, event: AstrMessageEvent, oper1: str = None, oper2: str = None):
@@ -201,6 +199,18 @@ AstrBot 指令:
config.save_config()
event.set_result(MessageEventResult().message("已开启文本转图片模式。"))
@filter.command("tts")
async def tts(self, event: AstrMessageEvent):
config = self.context.get_config()
if config['provider_tts_settings']['enable']:
config['provider_tts_settings']['enable'] = False
config.save_config()
event.set_result(MessageEventResult().message("已关闭文本转语音。"))
return
config['provider_tts_settings']['enable'] = True
config.save_config()
event.set_result(MessageEventResult().message("已开启文本转语音。"))
@filter.command("sid")
async def sid(self, event: AstrMessageEvent):
sid = event.unified_msg_origin
@@ -246,34 +256,89 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
event.set_result(MessageEventResult().message("此 SID 不在白名单内。"))
@filter.command("provider")
async def provider(self, event: AstrMessageEvent, idx: int = None):
async def provider(self, event: AstrMessageEvent, idx: Union[str, int] = None, idx2: int = None):
'''查看或者切换 LLM Provider'''
if not self.context.get_using_provider():
event.set_result(MessageEventResult().message("未找到任何 LLM 提供商。请先配置。"))
return
if idx is None:
ret = "## 当前载入的 LLM 提供商\n"
if idx is None:
ret = "## 载入的 LLM 提供商\n"
for idx, llm in enumerate(self.context.get_all_providers()):
id_ = llm.meta().id
ret += f"{idx + 1}. {id_} ({llm.meta().model})"
if self.context.get_using_provider().meta().id == id_:
ret += " (当前使用)"
ret += "\n"
tts_providers = self.context.get_all_tts_providers()
if tts_providers:
ret += "\n## 载入的 TTS 提供商\n"
for idx, tts in enumerate(tts_providers):
id_ = tts.meta().id
ret += f"{idx + 1}. {id_}"
tts_using = self.context.get_using_tts_provider()
if tts_using and tts_using.meta().id == id_:
ret += " (当前使用)"
ret += "\n"
stt_providers = self.context.get_all_stt_providers()
if stt_providers:
ret += "\n## 载入的 STT 提供商\n"
for idx, stt in enumerate(stt_providers):
id_ = stt.meta().id
ret += f"{idx + 1}. {id_}"
stt_using = self.context.get_using_stt_provider()
if stt_using and stt_using.meta().id == id_:
ret += " (当前使用)"
ret += "\n"
ret += "\n使用 /provider <序号> 切换提供商。"
ret += "\n使用 /provider <序号> 切换 LLM 提供商。"
if tts_providers:
ret += "\n使用 /provider tts <序号> 切换 TTS 提供商。"
if stt_providers:
ret += "\n使用 /provider stt <切换> STT 提供商。"
event.set_result(MessageEventResult().message(ret))
else:
if idx > len(self.context.get_all_providers()) or idx < 1:
event.set_result(MessageEventResult().message("无效的序号。"))
if idx == "tts":
if idx2 is None:
event.set_result(MessageEventResult().message("请输入序号。"))
return
else:
if idx2 > len(self.context.get_all_tts_providers()) or idx2 < 1:
event.set_result(MessageEventResult().message("无效的序号。"))
provider = self.context.get_all_tts_providers()[idx2 - 1]
id_ = provider.meta().id
self.context.provider_manager.curr_tts_provider_inst = provider
sp.put("curr_provider_tts", id_)
event.set_result(MessageEventResult().message(f"成功切换到 {id_}"))
elif idx == "stt":
if idx2 is None:
event.set_result(MessageEventResult().message("请输入序号。"))
return
else:
if idx2 > len(self.context.get_all_stt_providers()) or idx2 < 1:
event.set_result(MessageEventResult().message("无效的序号。"))
provider = self.context.get_all_stt_providers()[idx2 - 1]
id_ = provider.meta().id
self.context.provider_manager.curr_stt_provider_inst = provider
sp.put("curr_provider_stt", id_)
event.set_result(MessageEventResult().message(f"成功切换到 {id_}"))
elif isinstance(idx, int):
if idx > len(self.context.get_all_providers()) or idx < 1:
event.set_result(MessageEventResult().message("无效的序号。"))
provider = self.context.get_all_providers()[idx - 1]
id_ = provider.meta().id
self.context.provider_manager.curr_provider_inst = provider
sp.put("curr_provider", id_)
provider = self.context.get_all_providers()[idx - 1]
id_ = provider.meta().id
self.context.provider_manager.curr_provider_inst = provider
sp.put("curr_provider", id_)
event.set_result(MessageEventResult().message(f"成功切换到 {id_}"))
event.set_result(MessageEventResult().message(f"成功切换到 {id_}"))
else:
event.set_result(MessageEventResult().message("无效的参数。"))
@filter.permission_type(filter.PermissionType.ADMIN)
@filter.command("reset")
@@ -582,7 +647,7 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
sp.put("session_variables", session_vars)
yield event.plain_result(f"会话 {session_id} 变量 {key} 存储成功。")
yield event.plain_result(f"会话 {session_id} 变量 {key} 存储成功。使用 /unset 移除。")
@filter.command("unset")
async def unset_variable(self, event: AstrMessageEvent, key: str):
@@ -592,7 +657,7 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
session_var = session_vars.get(session_id, {})
if key not in session_var:
yield event.plain_result("没有那个变量名。")
yield event.plain_result("没有那个变量名。格式 /unset 变量名。")
else:
del session_var[key]
sp.put("session_variables", session_vars)