Compare commits

...

18 Commits

Author SHA1 Message Date
Soulter 386a312e96 fix: 修复一些typo 2025-02-08 22:52:24 +08:00
Soulter 2759d347e6 update: add socksio, echatpy, cryptography to dockerfile 2025-02-08 22:10:17 +08:00
Soulter b6ec327b49 perf:完善主动会话 2025-02-08 22:04:36 +08:00
Soulter ee02d622ba v3.4.23 2025-02-08 21:42:37 +08:00
Soulter 5c4a6083f5 Merge pull request #433 from Cvandia/master
支持 fishaudio tts 文字转语音
2025-02-08 21:20:03 +08:00
Soulter 49e63a3d3d perf: 优化报错显示 2025-02-08 21:19:25 +08:00
Soulter 6bae9dc9ed 👌 perf: 当响应头不为audio/wav时抛出报错 2025-02-08 21:16:09 +08:00
Cvandia 5fa1979a46 🐛 fix: 移除调试过程的不必要的文件写入操作 2025-02-08 20:49:37 +08:00
Cvandia b40d4fa315 Merge remote-tracking branch 'upstream/master' 2025-02-08 20:45:49 +08:00
Soulter 4d2ff7cd5b fix: 修复 qq 回复别人的时候也会触发机器人, Onebot at 使用 string #330 2025-02-08 20:35:10 +08:00
Cvandia d8ec0e64d0 Merge remote-tracking branch 'upstream/master' 2025-02-08 19:40:56 +08:00
Cvandia 82e979cc07 feat: 添加 FishAudio TTS API 支持,更新配置和依赖项 2025-02-08 19:37:43 +08:00
Soulter 8c132a51f5 fix: 修复子指令设置permission之后会导致其一定会被执行 #427 2025-02-08 18:51:30 +08:00
Soulter 40bd372cc1 fix: 重启gewe的时候机器人会疯狂发消息 #421 2025-02-08 18:02:42 +08:00
Soulter 212e114270 perf: 优化了一些提示 2025-02-08 15:55:46 +08:00
Soulter b0e9de6951 perf: 增加DIFY超时时间 #422 2025-02-08 12:58:54 +08:00
Soulter 3489522bbb feat: 支持展示插件是否有更新 2025-02-08 12:22:36 +08:00
Soulter 96237abc03 fix: 当群聊自动回复时,不会带上人格的Prompt #419 2025-02-08 10:17:43 +08:00
20 changed files with 260 additions and 54 deletions
+3 -1
View File
@@ -12,7 +12,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN python -m pip install -r requirements.txt
RUN python -m pip install -r requirements.txt --no-cache-dir
RUN python -m pip install socksio wechatpy cryptography --no-cache-dir
EXPOSE 6185
EXPOSE 6186
+23 -5
View File
@@ -2,7 +2,7 @@
如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。
"""
VERSION = "3.4.22"
VERSION = "3.4.23"
DB_PATH = "data/data_v3.db"
# 默认配置
@@ -69,7 +69,9 @@ DEFAULT_CONFIG = {
"internal_keywords": {"enable": True, "extra_keywords": []},
"baidu_aip": {"enable": False, "app_id": "", "api_key": "", "secret_key": ""},
},
"admins_id": [],
"admins_id": [
"astrbot"
],
"t2i": False,
"t2i_word_threshold": 150,
"http_proxy": "",
@@ -276,7 +278,7 @@ CONFIG_METADATA_2 = {
"items": {"type": "string"},
"obvious_hint": True,
"hint": "此功能解决由于文件系统不一致导致路径不存在的问题。格式为 <原路径>:<映射路径>。如 `/app/.config/QQ:/var/lib/docker/volumes/xxxx/_data`。这样,当消息平台下发的事件中图片和语音路径以 `/app/.config/QQ` 开头时,开头被替换为 `/var/lib/docker/volumes/xxxx/_data`。这在 AstrBot 或者平台协议端使用 Docker 部署时特别有用。",
}
},
},
},
"content_safety": {
@@ -433,6 +435,7 @@ CONFIG_METADATA_2 = {
"dify_api_key": "",
"dify_api_base": "https://api.dify.ai/v1",
"dify_workflow_output_key": "",
"timeout": 60,
},
"whisper(API)": {
"id": "whisper",
@@ -459,6 +462,15 @@ CONFIG_METADATA_2 = {
"openai-tts-voice": "alloy",
"timeout": "20",
},
"fishaudio_tts(API)": {
"id": "fishaudio_tts",
"type": "fishaudio_tts_api",
"enable": False,
"api_key": "",
"api_base": "https://api.fish-audio.cn/v1",
"fishaudio-tts-character": "可莉",
"timeout": "20",
},
},
"items": {
"timeout": {
@@ -472,6 +484,12 @@ CONFIG_METADATA_2 = {
"obvious_hint": True,
"hint": "OpenAI TTS 的声音。OpenAI 默认支持:'alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'",
},
"fishaudio-tts-character": {
"description": "character",
"type": "string",
"obvious_hint": True,
"hint": "fishaudio TTS 的角色。默认为可莉。更多角色请访问:https://fish.audio/zh-CN/discovery",
},
"whisper_hint": {
"description": "本地部署 Whisper 模型须知",
"type": "string",
@@ -725,7 +743,7 @@ CONFIG_METADATA_2 = {
},
"image_caption_prompt": {
"description": "图像转述提示词",
"type": "string"
"type": "string",
},
"active_reply": {
"description": "主动回复",
@@ -756,7 +774,7 @@ CONFIG_METADATA_2 = {
"hint": "提示词。当提示词为空时,如果触发回复,则向 LLM 请求的是触发的消息的内容;否则是提示词。此项可以和定时回复(暂未实现)配合使用。",
},
},
}
},
},
},
},
@@ -39,9 +39,8 @@ class ResultDecorateStage:
handlers = star_handlers_registry.get_handlers_by_event_type(EventType.OnDecoratingResultEvent)
for handler in handlers:
# TODO: 如何让这里的 handler 也能使用 LLM 能力。也许需要将 LLMRequestSubStage 提取出来。
await handler.handler(event)
if len(result.chain) > 0:
# 回复前缀
if self.reply_prefix:
@@ -84,7 +83,7 @@ class ResultDecorateStage:
logger.error(f"由于 TTS 音频文件没找到,消息段转语音失败: {comp.text}")
new_chain.append(comp)
except BaseException:
traceback.print_exc()
logger.error(traceback.format_exc())
logger.error("TTS 失败,使用文本发送。")
new_chain.append(comp)
else:
+6 -2
View File
@@ -3,7 +3,7 @@ from ..context import PipelineContext
from typing import Union, AsyncGenerator
from astrbot.core.platform.astr_message_event import AstrMessageEvent
from astrbot.core.message.message_event_result import MessageEventResult, MessageChain
from astrbot.core.message.components import At
from astrbot.core.message.components import At, Reply
from astrbot.core.star.star_handler import star_handlers_registry, EventType
from astrbot.core.star.filter.command_group import CommandGroupFilter
from astrbot.core.star.filter.permission import PermissionTypeFilter
@@ -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)
and (isinstance(messages[0], At) or isinstance(messages[0], Reply))
and str(messages[0].qq) != str(event.get_self_id())
and str(messages[0].qq) != "all"
):
@@ -86,6 +86,10 @@ class WakingCheckStage(Stage):
if len(handler.event_filters) == 0:
# 不可能有这种情况, 也不允许有这种情况
continue
if 'sub_command' in handler.extras_configs:
# 如果是子指令
continue
for filter in handler.event_filters:
try:
+11 -7
View File
@@ -8,7 +8,7 @@ from typing import List, Union
from astrbot.core.message.components import Plain, Image, BaseMessageComponent, Face, At, AtAll, Forward
from astrbot.core.utils.metrics import Metric
from astrbot.core.provider.entites import ProviderRequest
from astrbot.core.db.po import Conversation
@dataclass
class MessageSesion:
@@ -305,9 +305,10 @@ class AstrMessageEvent(abc.ABC):
prompt: str,
func_tool_manager = None,
session_id: str = None,
image_urls: List[str] = None,
contexts: List = None,
system_prompt: str = ""
image_urls: List[str] = [],
contexts: List = [],
system_prompt: str = "",
conversation: Conversation = None
) -> ProviderRequest:
'''
创建一个 LLM 请求。
@@ -316,10 +317,12 @@ class AstrMessageEvent(abc.ABC):
```py
yield event.request_llm(prompt="hi")
```
prompt: 提示词
session_id: 已经过时,留空即可
image_urls: 可以是 base64:// 或者 http:// 开头的图片链接,也可以是本地图片路径。
contexts: 当指定 contexts 时,将会**只**使用 contexts 作为上下文。
contexts: 当指定 contexts 时,将会使用 contexts 作为上下文。
func_tool_manager: 函数工具管理器,用于调用函数工具。用 self.context.get_llm_tool_manager() 获取。
conversation: 可选。如果指定,将在指定的对话中进行 LLM 请求。对话的人格会被用于 LLM 请求,并且结果将会被记录到对话中。
'''
return ProviderRequest(
prompt = prompt,
@@ -327,5 +330,6 @@ class AstrMessageEvent(abc.ABC):
image_urls = image_urls,
func_tool = func_tool_manager,
contexts = contexts,
system_prompt = system_prompt
system_prompt = system_prompt,
conversation=conversation
)
@@ -1,9 +1,7 @@
import os
import random
import asyncio
from astrbot.api.event import AstrMessageEvent, MessageChain
from astrbot.api.message_components import Plain, Image, Record
from astrbot.api.message_components import Plain, Image, Record, At
from aiocqhttp import CQHttp
from astrbot.core.utils.io import file_to_base64, download_image_by_url
@@ -33,6 +31,10 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
d['data'] = {
'file': bs64_data,
}
if isinstance(segment, At):
d['data'] = {
'qq': str(segment.qq) # 转换为字符串
}
ret.append(d)
return ret
@@ -3,6 +3,7 @@ import asyncio
import aiohttp
import quart
import base64
import datetime
from astrbot.api.platform import AstrBotMessage, MessageMember, MessageType
from astrbot.api.message_components import Plain, Image, At, Record
@@ -67,6 +68,17 @@ class SimpleGewechatClient():
logger.critical("收到 gewechat 下线通知。")
return
if 'Data' in data and 'CreateTime' in data['Data']:
# 得到系统 UTF+8 的 ts
tz_offset = datetime.timedelta(hours=8)
tz = datetime.timezone(tz_offset)
ts = datetime.datetime.now(tz).timestamp()
create_time = data['Data']['CreateTime']
if create_time < ts - 30:
logger.warning(f"消息时间戳过旧: {create_time},当前时间戳: {ts}")
return
abm = AstrBotMessage()
d = data['Data']
@@ -143,7 +155,7 @@ class SimpleGewechatClient():
abm.message.append(Record(file=file_path, url=file_path))
case _:
logger.error(f"未实现的消息类型: {d['MsgType']}")
logger.info(f"未实现的消息类型: {d['MsgType']}")
return
logger.info(f"abm: {abm}")
+10 -8
View File
@@ -114,22 +114,24 @@ class ProviderManager():
try:
match provider_cfg['type']:
case "openai_chat_completion":
from .sources.openai_source import ProviderOpenAIOfficial # noqa: F401
from .sources.openai_source import ProviderOpenAIOfficial as ProviderOpenAIOfficial
case "zhipu_chat_completion":
from .sources.zhipu_source import ProviderZhipu # noqa: F401
from .sources.zhipu_source import ProviderZhipu as ProviderZhipu
case "llm_tuner":
logger.info("加载 LLM Tuner 工具 ...")
from .sources.llmtuner_source import LLMTunerModelLoader # noqa: F401
from .sources.llmtuner_source import LLMTunerModelLoader as LLMTunerModelLoader
case "dify":
from .sources.dify_source import ProviderDify # noqa: F401
from .sources.dify_source import ProviderDify as ProviderDify
case "googlegenai_chat_completion":
from .sources.gemini_source import ProviderGoogleGenAI # noqa: F401
from .sources.gemini_source import ProviderGoogleGenAI as ProviderGoogleGenAI
case "openai_whisper_api":
from .sources.whisper_api_source import ProviderOpenAIWhisperAPI # noqa: F401
from .sources.whisper_api_source import ProviderOpenAIWhisperAPI as ProviderOpenAIWhisperAPI
case "openai_whisper_selfhost":
from .sources.whisper_selfhosted_source import ProviderOpenAIWhisperSelfHost # noqa: F401
from .sources.whisper_selfhosted_source import ProviderOpenAIWhisperSelfHost as ProviderOpenAIWhisperSelfHost
case "openai_tts_api":
from .sources.openai_tts_api_source import ProviderOpenAITTSAPI # noqa: F401
from .sources.openai_tts_api_source import ProviderOpenAITTSAPI as ProviderOpenAITTSAPI
case "fishaudio_tts_api":
from .sources.fishaudio_tts_api_source import ProviderFishAudioTTSAPI as ProviderFishAudioTTSAPI
except (ImportError, ModuleNotFoundError) as e:
logger.critical(f"加载 {provider_cfg['type']}({provider_cfg['id']}) 提供商适配器失败:{e}。可能是因为有未安装的依赖。")
continue
+7 -3
View File
@@ -31,7 +31,9 @@ class ProviderDify(Provider):
raise Exception("Dify API 类型不能为空。")
self.model_name = "dify"
self.workflow_output_key = provider_config.get("dify_workflow_output_key", "astrbot_wf_output")
self.timeout = provider_config.get("timeout", 120)
if isinstance(self.timeout, str):
self.timeout = int(self.timeout)
self.conversation_ids = {}
@@ -78,7 +80,8 @@ class ProviderDify(Provider):
query=prompt,
user=session_id,
conversation_id=conversation_id,
files=files_payload
files=files_payload,
timeout=self.timeout
):
logger.debug(f"dify resp chunk: {chunk}")
if chunk['event'] == "message" or \
@@ -96,7 +99,8 @@ class ProviderDify(Provider):
**session_var
},
user=session_id,
files=files_payload
files=files_payload,
timeout=self.timeout
):
match chunk['event']:
case "workflow_started":
@@ -0,0 +1,105 @@
import uuid
import ormsgpack
from pydantic import BaseModel, conint
from httpx import AsyncClient
from typing import Annotated, Literal
from ..provider import TTSProvider
from ..entites import ProviderType
from ..register import register_provider_adapter
class ServeReferenceAudio(BaseModel):
audio: bytes
text: str
class ServeTTSRequest(BaseModel):
text: str
chunk_length: Annotated[int, conint(ge=100, le=300, strict=True)] = 200
# 音频格式
format: Literal["wav", "pcm", "mp3"] = "mp3"
mp3_bitrate: Literal[64, 128, 192] = 128
# 参考音频
references: list[ServeReferenceAudio] = []
# 参考模型 ID
# 例如 https://fish.audio/m/7f92f8afb8ec43bf81429cc1c9199cb1/
# 其中reference_id为 7f92f8afb8ec43bf81429cc1c9199cb1
reference_id: str | None = None
# 对中英文文本进行标准化,这可以提高数字的稳定性
normalize: bool = True
# 平衡模式将延迟减少到300毫秒,但可能会降低稳定性
latency: Literal["normal", "balanced"] = "normal"
@register_provider_adapter(
"fishaudio_tts_api", "FishAudio TTS API", provider_type=ProviderType.TEXT_TO_SPEECH
)
class ProviderFishAudioTTSAPI(TTSProvider):
def __init__(
self,
provider_config: dict,
provider_settings: dict,
) -> None:
super().__init__(provider_config, provider_settings)
self.chosen_api_key: str = provider_config.get("api_key", "")
self.character: str = provider_config.get("fishaudio-tts-character", "可莉")
self.api_base: str = provider_config.get(
"api_base", "https://api.fish-audio.cn/v1"
)
self.headers = {
"Authorization": f"Bearer {self.chosen_api_key}",
}
self.set_model(provider_config.get("model", None))
async def _get_reference_id_by_character(self, character: str) -> str:
"""
获取角色的reference_id
Args:
character: 角色名称
Returns:
reference_id: 角色的reference_id
exception:
APIException: 获取语音角色列表为空
"""
sort_options = ["score", "task_count", "created_at"]
async with AsyncClient(base_url=self.api_base.replace("/v1", "")) as client:
for sort_by in sort_options:
params = {"title": character, "sort_by": sort_by}
response = await client.get(
"/model", params=params, headers=self.headers
)
resp_data = response.json()
if resp_data["total"] == 0:
continue
for item in resp_data["items"]:
if character in item["title"]:
return item["_id"]
return None
async def _generate_request(self, text: str) -> dict:
return ServeTTSRequest(
text=text,
format="wav",
reference_id=await self._get_reference_id_by_character(self.character),
)
async def get_audio(self, text: str) -> str:
path = f"data/temp/fishaudio_tts_api_{uuid.uuid4()}.wav"
self.headers["content-type"] = "application/msgpack"
request = await self._generate_request(text)
async with AsyncClient(base_url=self.api_base).stream(
"POST",
"/tts",
headers=self.headers,
content=ormsgpack.packb(request, option=ormsgpack.OPT_SERIALIZE_PYDANTIC),
) as response:
if response.headers["content-type"] == "audio/wav":
with open(path, "wb") as f:
async for chunk in response.aiter_bytes():
f.write(chunk)
return path
text = await response.aread()
raise Exception(f"Fish Audio API请求失败: {text}")
@@ -48,7 +48,12 @@ 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:
response = await resp.json()
try:
response = await resp.json()
except Exception as e:
text = await resp.text()
logger.error(f"gemini 返回了非 json 数据: {text}")
raise e
return response
@@ -111,7 +111,7 @@ class ProviderOpenAIOfficial(Provider):
self,
prompt: str,
session_id: str=None,
image_urls: List[str]=None,
image_urls: List[str]=[],
func_tool: FuncCall=None,
contexts=[],
system_prompt=None,
+4 -2
View File
@@ -68,12 +68,14 @@ def register_command(command_name: str = None, *args, **kwargs):
add_to_event_filters = True
def decorator(awaitable):
if not add_to_event_filters:
kwargs['sub_command'] = True # 打一个标记,表示这是一个子指令,再 wakingstage 阶段这个 handler 将会直接被跳过(其父指令会接管)
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent, **kwargs)
new_command.init_handler_md(handler_md)
if add_to_event_filters:
# 裸指令
handler_md.event_filters.append(new_command)
return awaitable
return decorator
@@ -116,7 +118,7 @@ class RegisteringCommandable():
def register_event_message_type(event_message_type: EventMessageType, **kwargs):
'''注册一个 EventMessageType'''
def decorator(awaitable):
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent, kwargs)
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent, **kwargs)
handler_md.event_filters.append(EventMessageTypeFilter(event_message_type))
return awaitable
+1 -1
View File
@@ -110,7 +110,7 @@ class RepoZipUpdator():
releases = await self.fetch_release_info(url=release_url)
if not releases:
# download from the default branch directly.
logger.info(f"未在仓库 {author}/{repo} 中找到任何发布版本,正在从默认分支下载。")
logger.info(f"正在从默认分支下载 {author}/{repo} ")
release_url = f"https://github.com/{author}/{repo}/archive/refs/heads/master.zip"
else:
release_url = releases[0]['zipball_url']
+5 -6
View File
@@ -1,6 +1,5 @@
import traceback
import aiohttp
import uuid
from .route import Route, Response, RouteContext
from astrbot.core import logger
from quart import request
@@ -49,7 +48,7 @@ class PluginRoute(Route):
return Response().error(message).__dict__
return Response().ok(None, "重载成功。").__dict__
except Exception as e:
logger.error(f"/api/extensions/reload: {traceback.format_exc()}")
logger.error(f"/api/plugin/reload: {traceback.format_exc()}")
return Response().error(str(e)).__dict__
async def get_online_plugins(self):
@@ -59,7 +58,6 @@ class PluginRoute(Route):
urls = [custom]
else:
urls = [
"https://soulter.github.io/AstrBot_Plugins_Collection/plugins.json",
"https://api.soulter.top/astrbot/plugins"
]
@@ -88,6 +86,7 @@ class PluginRoute(Route):
"version": plugin.version,
"reserved": plugin.reserved,
"activated": plugin.activated,
"online_vesion": "",
"handlers": await self.get_plugin_handlers_info(plugin.star_handler_full_names),
}
_plugin_resp.append(_t)
@@ -190,7 +189,7 @@ class PluginRoute(Route):
logger.info(f"更新插件 {plugin_name} 成功。")
return Response().ok(None, "更新成功。").__dict__
except Exception as e:
logger.error(f"/api/extensions/update: {traceback.format_exc()}")
logger.error(f"/api/plugin/update: {traceback.format_exc()}")
return Response().error(str(e)).__dict__
async def off_plugin(self):
@@ -201,7 +200,7 @@ class PluginRoute(Route):
logger.info(f"停用插件 {plugin_name}")
return Response().ok(None, "停用成功。").__dict__
except Exception as e:
logger.error(f"/api/extensions/off: {traceback.format_exc()}")
logger.error(f"/api/plugin/off: {traceback.format_exc()}")
return Response().error(str(e)).__dict__
async def on_plugin(self):
@@ -212,5 +211,5 @@ class PluginRoute(Route):
logger.info(f"启用插件 {plugin_name}")
return Response().ok(None, "启用成功。").__dict__
except Exception as e:
logger.error(f"/api/extensions/on: {traceback.format_exc()}")
logger.error(f"/api/plugin/on: {traceback.format_exc()}")
return Response().error(str(e)).__dict__
+11
View File
@@ -0,0 +1,11 @@
# What's Changed
0. ✨ 新增: 支持 海豚 AIFishAudio TTS API #433 by @Cvandia
1. 🐛 修复: 当群聊主动回复时,不会带上人格的Prompt #419
2. ✨ 新增: 支持展示插件是否有更新
3. 👌 优化: 增加DIFY超时时间 #422
4. 🐛 修复: 自部署文转图不生效 #352
5. 🐛 修复: 修复 qq 回复别人的时候也会触发机器人, Onebot at 使用 string #330
6. 👌 优化: 增加DIFY超时时间 #422
7. 🐛 修复: 重启gewe的时候机器人会疯狂发消息 #421
8. 🐛 修复: 修复子指令设置permission之后会导致其一定会被执行 #427
@@ -2,7 +2,8 @@
const props = defineProps({
title: String,
link: String,
logo: String
logo: String,
has_update: Boolean,
});
const open = (link: string | undefined) => {
@@ -17,6 +18,7 @@ const open = (link: string | undefined) => {
<img v-if="logo" :src="logo" alt="logo" style="width: 40px; height: 40px; margin-right: 8px;">
<v-card-title style="font-size: 16px;">{{ props.title }}</v-card-title>
<v-spacer></v-spacer>
<v-icon color="success" v-if="has_update">mdi-arrow-up-bold</v-icon>
<v-btn size="small" text="Read" variant="flat" border @click="open(props.link)">帮助</v-btn>
</div>
</v-card-item>
+38 -6
View File
@@ -46,11 +46,20 @@ import { max } from 'date-fns';
</div>
</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"
<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: 135px; max-height: 135px; overflow: none;">
<span style="font-weight: bold;">By @{{ extension.author }}</span>
<span> | 插件有 {{ extension.handlers.length }} 个行为</span>
<div style="min-height: 140px; max-height: 140px; overflow: none;">
<div>
<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>
<span v-if="extension.online_version">
| 最新: <v-chip size="small" color="primary">{{ extension.online_version }}</v-chip>
</span>
<span v-if="extension.has_update" style="font-weight: bold;">有更新
</span>
</span>
<p style="margin-top: 8px;">{{ extension.desc }}</p>
<a style="font-size: 12px; cursor: pointer; text-decoration: underline; color: #555;"
@click="reloadPlugin(extension.name)">重载插件</a>
@@ -329,6 +338,8 @@ export default {
{ title: '作者', value: 'author' },
{ title: '操作', value: 'actions', sortable: false }
],
alreadyCheckUpdate: false
}
},
mounted() {
@@ -367,10 +378,29 @@ export default {
getExtensions() {
axios.get('/api/plugin/get').then((res) => {
this.extension_data = res.data;
this.checkAlreadyInstalled();
this.checkUpdate()
});
},
checkUpdate() {
// 遍历 extension_data 和 pluginMarketData,检查是否有更新\
for (let i = 0; i < this.extension_data.data.length; i++) {
for (let j = 0; j < this.pluginMarketData.length; j++) {
console.log(this.extension_data.data[i].repo, this.pluginMarketData[j].repo);
if (this.extension_data.data[i].repo === this.pluginMarketData[j].repo ||
this.extension_data.data[i].name === this.pluginMarketData[j].name) {
this.extension_data.data[i].online_version = this.pluginMarketData[j].version;
if (this.extension_data.data[i].version !== this.pluginMarketData[j].version && this.pluginMarketData[j].version !== "未知") {
this.extension_data.data[i].has_update = true;
} else {
this.extension_data.data[i].has_update = false;
}
}
}
}
},
newExtension() {
if (this.extension_url === "" && this.upload_file === null) {
this.toast("请填写插件链接或上传插件文件", "error");
@@ -529,11 +559,13 @@ export default {
"desc": res.data.data[key].desc,
"author": res.data.data[key].author,
"repo": res.data.data[key].repo,
"installed": false
"installed": false,
"version": res.data.data[key]?.version ? res.data.data[key].version : "未知",
})
}
this.pluginMarketData = data;
this.checkAlreadyInstalled();
this.checkUpdate();
}).catch((err) => {
this.toast("获取插件市场数据失败: " + err, "error");
});
+4 -2
View File
@@ -6,6 +6,7 @@ import astrbot.api.star as star
import astrbot.api.event.filter as filter
from astrbot.api.event import AstrMessageEvent, MessageEventResult
from astrbot.api import sp
from astrbot.api.platform import MessageType
from astrbot.api.provider import Personality, ProviderRequest, LLMResponse
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
from astrbot.core.star.star_handler import star_handlers_registry, StarHandlerMetadata
@@ -632,7 +633,7 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
session_curr_cid = await self.context.conversation_manager.get_curr_conversation_id(event.unified_msg_origin)
if not session_curr_cid:
logger.error("当前未处于对话状态,无法主动回复,请使用 /switch 切换或者 /new 创建。")
logger.error("当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /switch 切换或者 /new 创建一个会话")
return
conv = await self.context.conversation_manager.get_conversation(
@@ -649,7 +650,8 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
prompt=prompt,
func_tool_manager=self.context.get_llm_tool_manager(),
session_id=event.session_id,
contexts=history if history else []
contexts=history if history else [],
conversation=conv,
)
except BaseException as e:
logger.error(f"主动回复失败: {e}")
+2 -1
View File
@@ -16,4 +16,5 @@ pyjwt
apscheduler
docstring_parser
aiodocker
silk-python
silk-python
ormsgpack