Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21d480a3b5 | |||
| 771c045844 | |||
| e6ce484c15 | |||
| 102a92f62d | |||
| 6c7ac70701 | |||
| 9d8372289f | |||
| 766f6a1ba2 | |||
| 193ff24f4c | |||
| c675017374 | |||
| 86cb852507 | |||
| 73494e0d7d | |||
| ec61aa1b6f | |||
| 6df0e78b22 | |||
| 63c604359b | |||
| 08212588a0 |
@@ -16,3 +16,5 @@ venv*/
|
||||
ENV/
|
||||
.conda/
|
||||
README*.md
|
||||
dashboard/
|
||||
data/
|
||||
@@ -1,8 +1,9 @@
|
||||
name: Docker Image CI/CD
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/astrbot:latest
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/astrbot:${{ github.event.release.tag_name }}
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/astrbot:${{ github.ref_name }}
|
||||
|
||||
- name: Post build notifications
|
||||
run: echo "Docker image has been built and pushed successfully"
|
||||
|
||||
@@ -16,4 +16,8 @@ addons/plugins
|
||||
|
||||
|
||||
tests/astrbot_plugin_openai
|
||||
chroma
|
||||
chroma
|
||||
node_modules/
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
package.json
|
||||
@@ -2,7 +2,7 @@ from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||
from astrbot import logger
|
||||
from astrbot.core.utils.personality import personalities
|
||||
from astrbot.core import html_renderer
|
||||
from astrbot.core.provider.register import register_llm_tool as llm_tool
|
||||
from astrbot.core.star.register import register_llm_tool as llm_tool
|
||||
|
||||
__all__ = [
|
||||
"AstrBotConfig",
|
||||
|
||||
@@ -3,7 +3,7 @@ from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||
from astrbot import logger
|
||||
from astrbot.core.utils.personality import personalities
|
||||
from astrbot.core import html_renderer
|
||||
from astrbot.core.provider.register import register_llm_tool as llm_tool
|
||||
from astrbot.core.star.register import register_llm_tool as llm_tool
|
||||
|
||||
# event
|
||||
from astrbot.core.message.message_event_result import (
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
from astrbot.core.message.message_event_result import (
|
||||
MessageEventResult, MessageChain, CommandResult, EventResultType
|
||||
)
|
||||
MessageEventResult,
|
||||
MessageChain,
|
||||
CommandResult,
|
||||
EventResultType,
|
||||
ResultContentType,
|
||||
)
|
||||
|
||||
from astrbot.core.platform import AstrMessageEvent
|
||||
|
||||
__all__ = [
|
||||
'MessageEventResult', 'MessageChain', 'CommandResult', 'EventResultType', 'AstrMessageEvent'
|
||||
]
|
||||
"MessageEventResult",
|
||||
"MessageChain",
|
||||
"CommandResult",
|
||||
"EventResultType",
|
||||
"AstrMessageEvent",
|
||||
"ResultContentType",
|
||||
]
|
||||
|
||||
@@ -4,7 +4,11 @@ from astrbot.core.star.register import (
|
||||
register_event_message_type as event_message_type,
|
||||
register_regex as regex,
|
||||
register_platform_adapter_type as platform_adapter_type,
|
||||
register_permission_type as permission_type
|
||||
register_permission_type as permission_type,
|
||||
register_on_llm_request as on_llm_request,
|
||||
register_llm_tool as llm_tool,
|
||||
register_on_decorating_result as on_decorating_result,
|
||||
register_after_message_sent as after_message_sent
|
||||
)
|
||||
|
||||
from astrbot.core.star.filter.event_message_type import EventMessageTypeFilter, EventMessageType
|
||||
@@ -24,4 +28,8 @@ __all__ = [
|
||||
'PlatformAdapterType',
|
||||
'PermissionTypeFilter',
|
||||
'PermissionType',
|
||||
'on_llm_request',
|
||||
'llm_tool',
|
||||
'on_decorating_result',
|
||||
'after_message_sent'
|
||||
]
|
||||
@@ -1 +1,2 @@
|
||||
from astrbot.core.provider import Provider, Personality, ProviderMetaData
|
||||
from astrbot.core.provider.entites import ProviderRequest
|
||||
@@ -2,7 +2,7 @@
|
||||
如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。
|
||||
"""
|
||||
|
||||
VERSION = "3.4.0"
|
||||
VERSION = "3.4.2"
|
||||
DB_PATH = "data/data_v3.db"
|
||||
|
||||
# 默认配置
|
||||
@@ -17,6 +17,7 @@ DEFAULT_CONFIG = {
|
||||
},
|
||||
"reply_prefix": "",
|
||||
"forward_threshold": 200,
|
||||
"enable_id_white_list": True,
|
||||
"id_whitelist": [],
|
||||
"id_whitelist_log": True,
|
||||
"wl_ignore_admin_on_group": True,
|
||||
@@ -162,6 +163,10 @@ CONFIG_METADATA_2 = {
|
||||
"type": "int",
|
||||
"hint": "超过一定字数后,机器人会将消息折叠成 QQ 群聊的 “转发消息”,以防止刷屏。目前仅 QQ 平台适配器适用。",
|
||||
},
|
||||
"enable_id_white_list": {
|
||||
"description": "启用 ID 白名单",
|
||||
"type": "bool"
|
||||
},
|
||||
"id_whitelist": {
|
||||
"description": "ID 白名单",
|
||||
"type": "list",
|
||||
|
||||
@@ -97,7 +97,14 @@ class EventResultType(enum.Enum):
|
||||
'''
|
||||
CONTINUE = enum.auto()
|
||||
STOP = enum.auto()
|
||||
|
||||
|
||||
class ResultContentType(enum.Enum):
|
||||
'''用于描述事件结果的内容的类型。
|
||||
'''
|
||||
LLM_RESULT = enum.auto()
|
||||
'''调用 LLM 产生的结果'''
|
||||
GENERAL_RESULT = enum.auto()
|
||||
'''普通的消息结果'''
|
||||
@dataclass
|
||||
class MessageEventResult(MessageChain):
|
||||
'''MessageEventResult 描述了一整条消息中带有的所有组件以及事件处理的结果。
|
||||
@@ -112,6 +119,8 @@ class MessageEventResult(MessageChain):
|
||||
|
||||
result_type: Optional[EventResultType] = field(default_factory=lambda: EventResultType.CONTINUE)
|
||||
|
||||
result_content_type: Optional[ResultContentType] = field(default_factory=lambda: ResultContentType.GENERAL_RESULT)
|
||||
|
||||
def stop_event(self) -> 'MessageEventResult':
|
||||
'''终止事件传播。
|
||||
'''
|
||||
@@ -130,5 +139,14 @@ class MessageEventResult(MessageChain):
|
||||
'''
|
||||
return self.result_type == EventResultType.STOP
|
||||
|
||||
def set_result_content_type(self, typ: EventResultType) -> 'MessageEventResult':
|
||||
'''设置事件处理的结果类型。
|
||||
|
||||
Args:
|
||||
result_type (EventResultType): 事件处理的结果类型。
|
||||
'''
|
||||
self.result_content_type = typ
|
||||
return self
|
||||
|
||||
|
||||
CommandResult = MessageEventResult
|
||||
@@ -1,6 +1,6 @@
|
||||
from . import ContentSafetyStrategy
|
||||
from typing import List, Tuple
|
||||
|
||||
from astrbot import logger
|
||||
|
||||
class StrategySelector:
|
||||
def __init__(self, config: dict) -> None:
|
||||
@@ -15,7 +15,8 @@ class StrategySelector:
|
||||
try:
|
||||
from .baidu_aip import BaiduAipStrategy
|
||||
except ImportError:
|
||||
raise ImportError("使用百度内容审核应该先 pip install baidu-aip")
|
||||
logger.warning("使用百度内容审核应该先 pip install baidu-aip")
|
||||
return
|
||||
self.enabled_strategies.append(
|
||||
BaiduAipStrategy(
|
||||
config["baidu_aip"]["app_id"],
|
||||
|
||||
@@ -1,113 +1,90 @@
|
||||
import traceback
|
||||
import inspect
|
||||
from typing import Union, AsyncGenerator
|
||||
from ...context import PipelineContext
|
||||
from ..stage import Stage
|
||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from astrbot.core.message.message_event_result import MessageEventResult, CommandResult
|
||||
from astrbot.core.message.message_event_result import MessageEventResult, ResultContentType
|
||||
from astrbot.core.message.components import Image
|
||||
from astrbot.core import logger
|
||||
from astrbot.core.utils.metrics import Metric
|
||||
from astrbot.core.star.star import star_map
|
||||
|
||||
from astrbot.core.provider.entites import ProviderRequest
|
||||
from astrbot.core.star.star_handler import star_handlers_registry, EventType
|
||||
|
||||
class LLMRequestSubStage(Stage):
|
||||
|
||||
async def initialize(self, ctx: PipelineContext) -> None:
|
||||
self.prompt_prefix = ctx.astrbot_config['provider_settings']['prompt_prefix']
|
||||
self.identifier = ctx.astrbot_config['provider_settings']['identifier']
|
||||
self.ctx = ctx
|
||||
|
||||
async def process(self, event: AstrMessageEvent) -> Union[None, AsyncGenerator[None, None]]:
|
||||
# Chat 唤醒前缀
|
||||
if self.ctx.astrbot_config['provider_settings']['wake_prefix']:
|
||||
if not event.message_str.startswith(self.ctx.astrbot_config['provider_settings']['wake_prefix']):
|
||||
return
|
||||
event.message_str = event.message_str[len(self.ctx.astrbot_config['provider_settings']['wake_prefix']):]
|
||||
|
||||
if self.prompt_prefix:
|
||||
event.message_str = self.prompt_prefix + event.message_str
|
||||
if self.identifier:
|
||||
user_id = event.message_obj.sender.user_id
|
||||
user_nickname = event.message_obj.sender.nickname
|
||||
user_info = f"[User ID: {user_id}, Nickname: {user_nickname}]\n"
|
||||
event.message_str = user_info + event.message_str
|
||||
|
||||
image_urls = []
|
||||
for comp in event.message_obj.message:
|
||||
if isinstance(comp, Image):
|
||||
image_url = comp.url if comp.url else comp.file
|
||||
image_urls.append(image_url)
|
||||
|
||||
tools = self.ctx.plugin_manager.context.get_llm_tool_manager()
|
||||
req: ProviderRequest = None
|
||||
|
||||
provider = self.ctx.plugin_manager.context.get_using_provider()
|
||||
try:
|
||||
llm_response = await provider.text_chat(
|
||||
prompt=event.message_str,
|
||||
session_id=event.session_id,
|
||||
image_urls=image_urls,
|
||||
func_tool=tools
|
||||
)
|
||||
await Metric.upload(llm_tick=1, model_name=provider.get_model(), provider_type=provider.meta().type)
|
||||
if event.get_extra("provider_request"):
|
||||
req = event.get_extra("provider_request")
|
||||
assert isinstance(req, ProviderRequest), "provider_request 必须是 ProviderRequest 类型。"
|
||||
else:
|
||||
req = ProviderRequest(prompt="", image_urls=[])
|
||||
if self.ctx.astrbot_config['provider_settings']['wake_prefix']:
|
||||
if not event.message_str.startswith(self.ctx.astrbot_config['provider_settings']['wake_prefix']):
|
||||
return
|
||||
req.prompt = event.message_str[len(self.ctx.astrbot_config['provider_settings']['wake_prefix']):]
|
||||
req.func_tool = self.ctx.plugin_manager.context.get_llm_tool_manager()
|
||||
for comp in event.message_obj.message:
|
||||
if isinstance(comp, Image):
|
||||
image_url = comp.url if comp.url else comp.file
|
||||
req.image_urls.append(image_url)
|
||||
req.session_id = event.session_id
|
||||
event.set_extra("provider_request", req)
|
||||
session_provider_context = provider.session_memory.get(event.session_id)
|
||||
req.contexts = session_provider_context if session_provider_context else []
|
||||
|
||||
# 执行请求 LLM 前事件。
|
||||
# 装饰 system_prompt 等功能
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(EventType.OnLLMRequestEvent)
|
||||
for handler in handlers:
|
||||
try:
|
||||
await handler.handler(event, req)
|
||||
except BaseException:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
try:
|
||||
logger.debug(f"请求 LLM:{req.__dict__}")
|
||||
llm_response = await provider.text_chat(**req.__dict__) # 请求 LLM
|
||||
await Metric.upload(llm_tick=1, model_name=provider.get_model(), provider_type=provider.meta().type)
|
||||
|
||||
if llm_response.role == 'assistant':
|
||||
# text completion
|
||||
event.set_result(MessageEventResult().message(llm_response.completion_text))
|
||||
event.set_result(MessageEventResult().message(llm_response.completion_text)
|
||||
.set_result_content_type(ResultContentType.LLM_RESULT))
|
||||
elif llm_response.role == 'tool':
|
||||
# function calling
|
||||
function_calling_result = {}
|
||||
for func_tool_name, func_tool_args in zip(llm_response.tools_call_name, llm_response.tools_call_args):
|
||||
func_tool = tools.get_func(func_tool_name)
|
||||
func_tool = req.func_tool.get_func(func_tool_name)
|
||||
logger.info(f"调用工具函数:{func_tool_name},参数:{func_tool_args}")
|
||||
try:
|
||||
# 尝试调用工具函数
|
||||
|
||||
star_cls_obj = star_map.get(func_tool.module_name).star_cls
|
||||
# 判断 handler 是否是类方法(通过装饰器注册的没有 __self__ 属性)
|
||||
ready_to_call = None
|
||||
if hasattr(func_tool.func_obj, '__self__'):
|
||||
# 猜测没有通过装饰器去注册
|
||||
try:
|
||||
ready_to_call = func_tool.func_obj(event, **func_tool_args)
|
||||
except TypeError:
|
||||
# 向下兼容
|
||||
ready_to_call = func_tool.func_obj(event, self.ctx.plugin_manager.context, **func_tool_args)
|
||||
else:
|
||||
ready_to_call = func_tool.func_obj(star_cls_obj, event, **func_tool_args)
|
||||
if isinstance(ready_to_call, AsyncGenerator):
|
||||
async for mer in ready_to_call:
|
||||
# 如果处理函数是生成器,返回值只能是 MessageEventResult 或者 None(无返回值)
|
||||
if mer:
|
||||
assert isinstance(mer, (MessageEventResult, CommandResult)), "如果有返回值,必须是 MessageEventResult 或 CommandResult 类型。"
|
||||
event.set_result(mer)
|
||||
yield
|
||||
else:
|
||||
if event.get_result():
|
||||
yield
|
||||
elif inspect.iscoroutine(ready_to_call):
|
||||
# 如果只是一个 coroutine
|
||||
ret = await ready_to_call
|
||||
if ret:
|
||||
# 如果有返回值
|
||||
assert isinstance(ret, (MessageEventResult, CommandResult)), "如果有返回值,必须是 MessageEventResult 或 CommandResult 类型。"
|
||||
event.set_result(ret)
|
||||
# 执行后续步骤来发送消息
|
||||
if event.is_stopped() and event.get_result():
|
||||
# 主动停止事件传播,并且有结果
|
||||
event.continue_event()
|
||||
yield
|
||||
event.clear_result()
|
||||
event.stop_event()
|
||||
yield
|
||||
elif not event.is_stopped and not event.get_result():
|
||||
continue
|
||||
wrapper = self._call_handler(self.ctx, event, func_tool.handler, **func_tool_args)
|
||||
async for resp in wrapper:
|
||||
if resp is not None:
|
||||
function_calling_result[func_tool_name] = resp
|
||||
else:
|
||||
yield
|
||||
event.clear_result() # 清除上一个 handler 的结果
|
||||
except BaseException as e:
|
||||
logger.warning(traceback.format_exc())
|
||||
function_calling_result[func_tool_name] = "When calling the function, an error occurred: " + str(e)
|
||||
if function_calling_result:
|
||||
# 工具返回 LLM 资源。比如 RAG、网页 得到的相关结果等。
|
||||
# 我们重新执行一遍这个 stage
|
||||
req.func_tool = None # 暂时不支持递归工具调用
|
||||
extra_prompt = "\n\nSystem executed some external tools for this task and here are the results:\n"
|
||||
for tool_name, tool_result in function_calling_result.items():
|
||||
extra_prompt += f"Tool: {tool_name}\nTool Result: {tool_result}\n"
|
||||
req.prompt += extra_prompt
|
||||
async for _ in self.process(event):
|
||||
yield
|
||||
|
||||
except BaseException:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
event.set_result(MessageEventResult().message("AstrBot 请求 LLM 资源失败:" + str(e)))
|
||||
|
||||
@@ -2,12 +2,12 @@ from ...context import PipelineContext
|
||||
from ..stage import Stage
|
||||
from typing import Dict, Any, List, AsyncGenerator, Union
|
||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from astrbot.core.message.message_event_result import MessageEventResult, CommandResult
|
||||
from astrbot.core.message.message_event_result import MessageEventResult
|
||||
from astrbot.core import logger
|
||||
from astrbot.core.star.star_handler import StarHandlerMetadata
|
||||
from astrbot.core.star.star import star_map
|
||||
import traceback
|
||||
import inspect
|
||||
|
||||
class StarRequestSubStage(Stage):
|
||||
|
||||
async def initialize(self, ctx: PipelineContext) -> None:
|
||||
@@ -27,50 +27,11 @@ class StarRequestSubStage(Stage):
|
||||
if handler.handler_module_str not in star_map:
|
||||
# 孤立无援的 star handler
|
||||
continue
|
||||
star_cls_obj = star_map.get(handler.handler_module_str).star_cls
|
||||
|
||||
logger.debug(f"执行 Star Handler {handler.handler_full_name}")
|
||||
# 判断 handler 是否是类方法(通过装饰器注册的没有 __self__ 属性)
|
||||
ready_to_call = None
|
||||
if hasattr(handler.handler, '__self__'):
|
||||
# 猜测没有通过装饰器去注册
|
||||
try:
|
||||
ready_to_call = handler.handler(event, **params)
|
||||
except TypeError:
|
||||
# 向下兼容
|
||||
ready_to_call = handler.handler(event, self.ctx.plugin_manager.context, **params)
|
||||
else:
|
||||
ready_to_call = handler.handler(star_cls_obj, event, **params)
|
||||
|
||||
if isinstance(ready_to_call, AsyncGenerator):
|
||||
async for mer in ready_to_call:
|
||||
# 如果处理函数是生成器,返回值只能是 MessageEventResult 或者 None(无返回值)
|
||||
if mer:
|
||||
assert isinstance(mer, (MessageEventResult, CommandResult)), "如果有返回值,必须是 MessageEventResult 或 CommandResult 类型。"
|
||||
event.set_result(mer)
|
||||
yield
|
||||
else:
|
||||
if event.get_result():
|
||||
yield
|
||||
elif inspect.iscoroutine(ready_to_call):
|
||||
# 如果只是一个 coroutine
|
||||
ret = await ready_to_call
|
||||
if ret:
|
||||
# 如果有返回值
|
||||
assert isinstance(ret, (MessageEventResult, CommandResult)), "如果有返回值,必须是 MessageEventResult 或 CommandResult 类型。"
|
||||
event.set_result(ret)
|
||||
# 执行后续步骤来发送消息
|
||||
if event.is_stopped() and event.get_result():
|
||||
# 插件主动停止事件传播,并且有结果
|
||||
event.continue_event()
|
||||
yield
|
||||
event.clear_result()
|
||||
event.stop_event()
|
||||
yield
|
||||
elif not event.is_stopped and not event.get_result():
|
||||
continue
|
||||
else:
|
||||
yield
|
||||
wrapper = self._call_handler(self.ctx, event, handler.handler, **params)
|
||||
async for ret in wrapper:
|
||||
yield ret
|
||||
event.clear_result() # 清除上一个 handler 的结果
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@@ -5,6 +5,8 @@ from .method.llm_request import LLMRequestSubStage
|
||||
from .method.star_request import StarRequestSubStage
|
||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from astrbot.core.star.star_handler import StarHandlerMetadata
|
||||
from astrbot.core.provider.entites import ProviderRequest
|
||||
from astrbot.core import logger
|
||||
|
||||
@register_stage
|
||||
class ProcessStage(Stage):
|
||||
@@ -23,10 +25,17 @@ class ProcessStage(Stage):
|
||||
'''处理事件
|
||||
'''
|
||||
activated_handlers: List[StarHandlerMetadata] = event.get_extra("activated_handlers")
|
||||
|
||||
if activated_handlers:
|
||||
async for _ in self.star_request_sub_stage.process(event):
|
||||
yield
|
||||
async for resp in self.star_request_sub_stage.process(event):
|
||||
# 生成器返回值处理
|
||||
if isinstance(resp, ProviderRequest):
|
||||
# Handler 的 LLM 请求
|
||||
logger.debug(f"llm request -> {resp.prompt}")
|
||||
event.set_extra("provider_request", resp)
|
||||
async for _ in self.llm_request_sub_stage.process(event):
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
if self.ctx.astrbot_config['provider_settings'].get('enable', True):
|
||||
if not event._has_send_oper:
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from typing import Union, AsyncGenerator
|
||||
from ..stage import register_stage
|
||||
from ..stage import register_stage, Stage
|
||||
from ..context import PipelineContext
|
||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from astrbot.core import logger
|
||||
from astrbot.core.star.star_handler import star_handlers_registry, EventType
|
||||
|
||||
@register_stage
|
||||
class RespondStage:
|
||||
class RespondStage(Stage):
|
||||
async def initialize(self, ctx: PipelineContext):
|
||||
self.ctx = ctx
|
||||
|
||||
@@ -13,8 +14,12 @@ class RespondStage:
|
||||
result = event.get_result()
|
||||
if result is None:
|
||||
return
|
||||
|
||||
|
||||
if len(result.chain) > 0:
|
||||
await event.send(result)
|
||||
logger.info(f"AstrBot -> {event.get_sender_name()}/{event.get_sender_id()}: {event._outline_chain(result.chain)}")
|
||||
|
||||
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(EventType.OnAfterMessageSentEvent)
|
||||
for handler in handlers:
|
||||
# TODO: 如何让这里的 handler 也能使用 LLM 能力。也许需要将 LLMRequestSubStage 提取出来。
|
||||
await handler.handler(event)
|
||||
@@ -6,6 +6,7 @@ from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from astrbot.core import logger
|
||||
from astrbot.core.message.components import Plain, Image
|
||||
from astrbot.core import html_renderer
|
||||
from astrbot.core.star.star_handler import star_handlers_registry, EventType
|
||||
|
||||
@register_stage
|
||||
class ResultDecorateStage:
|
||||
@@ -19,6 +20,11 @@ class ResultDecorateStage:
|
||||
if result is None:
|
||||
return
|
||||
|
||||
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:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from __future__ import annotations
|
||||
import abc
|
||||
from typing import List, AsyncGenerator, Union
|
||||
import inspect
|
||||
from typing import List, AsyncGenerator, Union, Awaitable
|
||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from .context import PipelineContext
|
||||
from astrbot.core.message.message_event_result import MessageEventResult, CommandResult
|
||||
|
||||
registered_stages: List[Stage] = []
|
||||
'''维护了所有已注册的 Stage 实现类'''
|
||||
@@ -29,4 +31,36 @@ class Stage(abc.ABC):
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
async def _call_handler(
|
||||
self,
|
||||
ctx: PipelineContext,
|
||||
event: AstrMessageEvent,
|
||||
handler: Awaitable,
|
||||
**params
|
||||
) -> AsyncGenerator[None, None]:
|
||||
'''调用 Handler。'''
|
||||
# 判断 handler 是否是类方法(通过装饰器注册的没有 __self__ 属性)
|
||||
ready_to_call = None
|
||||
try:
|
||||
ready_to_call = handler(event, **params)
|
||||
except TypeError as e:
|
||||
print(e)
|
||||
# 向下兼容
|
||||
ready_to_call = handler(event, ctx.plugin_manager.context, **params)
|
||||
|
||||
if isinstance(ready_to_call, AsyncGenerator):
|
||||
async for ret in ready_to_call:
|
||||
# 如果处理函数是生成器,返回值只能是 MessageEventResult 或者 None(无返回值)
|
||||
if isinstance(ret, (MessageEventResult, CommandResult)):
|
||||
event.set_result(ret)
|
||||
yield
|
||||
else:
|
||||
yield ret
|
||||
elif inspect.iscoroutine(ready_to_call):
|
||||
# 如果只是一个 coroutine
|
||||
ret = await ready_to_call
|
||||
if isinstance(ret, (MessageEventResult, CommandResult)):
|
||||
event.set_result(ret)
|
||||
yield
|
||||
else:
|
||||
yield ret
|
||||
@@ -4,7 +4,7 @@ from typing import Union, AsyncGenerator
|
||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from astrbot.core.message.message_event_result import MessageEventResult
|
||||
from astrbot.core.message.components import At
|
||||
from astrbot.core.star.star_handler import star_handlers_registry
|
||||
from astrbot.core.star.star_handler import star_handlers_registry, EventType
|
||||
from astrbot.core.star.filter.command_group import CommandGroupFilter
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class WakingCheckStage(Stage):
|
||||
# 检查插件的 handler filter
|
||||
activated_handlers = []
|
||||
handlers_parsed_params = {} # 注册了指令的 handler
|
||||
for handler in star_handlers_registry:
|
||||
for handler in star_handlers_registry.get_handlers_by_event_type(EventType.AdapterMessageEvent):
|
||||
# filter 需要满足 AND 的逻辑关系
|
||||
passed = True
|
||||
child_command_handler_md = None
|
||||
|
||||
@@ -10,12 +10,16 @@ class WhitelistCheckStage(Stage):
|
||||
'''检查是否在群聊/私聊白名单
|
||||
'''
|
||||
async def initialize(self, ctx: PipelineContext) -> None:
|
||||
self.enable_whitelist_check = ctx.astrbot_config['platform_settings']['enable_id_white_list']
|
||||
self.whitelist = ctx.astrbot_config['platform_settings']['id_whitelist']
|
||||
self.wl_ignore_admin_on_group = ctx.astrbot_config['platform_settings']['wl_ignore_admin_on_group']
|
||||
self.wl_ignore_admin_on_friend = ctx.astrbot_config['platform_settings']['wl_ignore_admin_on_friend']
|
||||
self.wl_log = ctx.astrbot_config['platform_settings']['id_whitelist_log']
|
||||
|
||||
async def process(self, event: AstrMessageEvent) -> Union[None, AsyncGenerator[None, None]]:
|
||||
if not self.enable_whitelist_check:
|
||||
return
|
||||
|
||||
# 检查是否在白名单
|
||||
if self.wl_ignore_admin_on_group:
|
||||
if event.role == 'admin' and event.get_message_type() == MessageType.GROUP_MESSAGE:
|
||||
|
||||
@@ -7,6 +7,8 @@ from astrbot.core.platform.message_type import MessageType
|
||||
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
|
||||
|
||||
|
||||
@dataclass
|
||||
class MessageSesion:
|
||||
@@ -236,7 +238,9 @@ class AstrMessageEvent(abc.ABC):
|
||||
清除消息事件的结果。
|
||||
'''
|
||||
self._result = None
|
||||
|
||||
|
||||
'''消息链相关'''
|
||||
|
||||
def make_result(self) -> MessageEventResult:
|
||||
'''
|
||||
创建一个空的消息事件结果。
|
||||
@@ -275,4 +279,33 @@ class AstrMessageEvent(abc.ABC):
|
||||
'''
|
||||
mer = MessageEventResult()
|
||||
mer.chain = chain
|
||||
return mer
|
||||
return mer
|
||||
|
||||
'''LLM 请求相关'''
|
||||
|
||||
def request_llm(
|
||||
self,
|
||||
prompt: str,
|
||||
session_id: str = None,
|
||||
image_urls: List[str] = None,
|
||||
contexts: List = None,
|
||||
system_prompt: str = ""
|
||||
) -> ProviderRequest:
|
||||
'''
|
||||
创建一个 LLM 请求。
|
||||
|
||||
Examples:
|
||||
```py
|
||||
yield event.request_llm(prompt="hi")
|
||||
```
|
||||
|
||||
image_urls: 可以是 base64:// 或者 http:// 开头的图片链接,也可以是本地图片路径。
|
||||
contexts: 当指定 contexts 时,将会**只**使用 contexts 作为上下文。
|
||||
'''
|
||||
return ProviderRequest(
|
||||
prompt = prompt,
|
||||
session_id = session_id,
|
||||
image_urls = image_urls,
|
||||
contexts = contexts,
|
||||
system_prompt = system_prompt
|
||||
)
|
||||
@@ -11,7 +11,7 @@ from botpy import Client
|
||||
from astrbot.api.platform import Platform, AstrBotMessage, MessageMember, MessageType, PlatformMetadata
|
||||
from astrbot.api.event import MessageChain
|
||||
from typing import Union, List
|
||||
from astrbot.api.message_components import Image, Plain
|
||||
from astrbot.api.message_components import Image, Plain, At
|
||||
from astrbot.core.platform.astr_message_event import MessageSesion
|
||||
from .qqofficial_message_event import QQOfficialMessageEvent
|
||||
from ...register import register_platform_adapter
|
||||
@@ -111,6 +111,7 @@ class QQOfficialPlatformAdapter(Platform):
|
||||
abm.message_id = message.id
|
||||
abm.tag = "qq_official"
|
||||
msg: List[BaseMessageComponent] = []
|
||||
|
||||
|
||||
if isinstance(message, botpy.message.GroupMessage) or isinstance(message, botpy.message.C2CMessage):
|
||||
if isinstance(message, botpy.message.GroupMessage):
|
||||
@@ -126,7 +127,7 @@ class QQOfficialPlatformAdapter(Platform):
|
||||
)
|
||||
abm.message_str = message.content.strip()
|
||||
abm.self_id = "unknown_selfid"
|
||||
|
||||
msg.append(At(qq="qq_official"))
|
||||
msg.append(Plain(abm.message_str))
|
||||
if message.attachments:
|
||||
for i in message.attachments:
|
||||
@@ -146,7 +147,7 @@ class QQOfficialPlatformAdapter(Platform):
|
||||
|
||||
plain_content = message.content.replace(
|
||||
"<@!"+str(abm.self_id)+">", "").strip()
|
||||
msg.append(Plain(plain_content))
|
||||
|
||||
if message.attachments:
|
||||
for i in message.attachments:
|
||||
if i.content_type.startswith("image"):
|
||||
@@ -161,11 +162,14 @@ class QQOfficialPlatformAdapter(Platform):
|
||||
str(message.author.id),
|
||||
str(message.author.username)
|
||||
)
|
||||
msg.append(At(qq="qq_official"))
|
||||
msg.append(Plain(plain_content))
|
||||
|
||||
if isinstance(message, botpy.message.Message):
|
||||
abm.group_id = message.channel_id
|
||||
else:
|
||||
raise ValueError(f"Unknown message type: {message_type}")
|
||||
abm.self_id = "qq_official"
|
||||
return abm
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from .provider import Provider, Personality
|
||||
|
||||
from .provider_metadata import ProviderMetaData
|
||||
from .entites import ProviderMetaData
|
||||
|
||||
__all__ = [
|
||||
"Provider",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict
|
||||
from .func_tool_manager import FuncCall
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProviderMetaData():
|
||||
type: str # 提供商适配器名称,如 openai, ollama
|
||||
desc: str = "" # 提供商适配器描述.
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProviderRequest():
|
||||
prompt: str
|
||||
'''提示词'''
|
||||
session_id: str = ""
|
||||
'''会话 ID'''
|
||||
image_urls: List[str] = None
|
||||
'''图片 URL 列表'''
|
||||
func_tool: FuncCall = None
|
||||
'''工具'''
|
||||
contexts: List = None
|
||||
'''上下文。格式与 openai 的上下文格式一致:
|
||||
参考 https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages
|
||||
'''
|
||||
|
||||
system_prompt: str = ""
|
||||
'''系统提示词'''
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMResponse:
|
||||
role: str
|
||||
'''角色'''
|
||||
completion_text: str = None
|
||||
'''LLM 返回的文本'''
|
||||
tools_call_args: List[Dict[str, any]] = None
|
||||
'''工具调用参数'''
|
||||
tools_call_name: List[str] = None
|
||||
'''工具调用名称'''
|
||||
@@ -1,25 +1,9 @@
|
||||
import json
|
||||
import textwrap
|
||||
from typing import Awaitable, Dict, List
|
||||
from typing import Dict, List, Awaitable
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class FuncCallJsonFormatError(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
|
||||
class FuncNotFoundError(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
|
||||
@dataclass
|
||||
class FuncTool:
|
||||
"""
|
||||
@@ -29,8 +13,7 @@ class FuncTool:
|
||||
name: str
|
||||
parameters: Dict
|
||||
description: str
|
||||
func_obj: Awaitable
|
||||
module_name: str = None
|
||||
handler: Awaitable
|
||||
|
||||
active: bool = True
|
||||
'''是否激活'''
|
||||
@@ -56,8 +39,7 @@ class FuncCall:
|
||||
name: str,
|
||||
func_args: list,
|
||||
desc: str,
|
||||
func_obj: Awaitable,
|
||||
module_name: str = None,
|
||||
handler: Awaitable,
|
||||
) -> None:
|
||||
"""
|
||||
为函数调用(function-calling / tools-use)添加工具。
|
||||
@@ -80,8 +62,7 @@ class FuncCall:
|
||||
name=name,
|
||||
parameters=params,
|
||||
description=desc,
|
||||
func_obj=func_obj,
|
||||
module_name=module_name,
|
||||
handler=handler,
|
||||
)
|
||||
self.func_list.append(_func)
|
||||
|
||||
@@ -179,11 +160,11 @@ class FuncCall:
|
||||
# 调用函数
|
||||
tool_callable = None
|
||||
for func in self.func_list:
|
||||
if func["name"] == func_name:
|
||||
tool_callable = func["func_obj"]
|
||||
if func.name == func_name:
|
||||
tool_callable = func.star_handler_metadata.handler
|
||||
break
|
||||
if not tool_callable:
|
||||
raise FuncNotFoundError(f"Request function {func_name} not found.")
|
||||
raise Exception(f"Request function {func_name} not found.")
|
||||
ret = await tool_callable(**args)
|
||||
if ret:
|
||||
tool_call_result.append(str(ret))
|
||||
@@ -1,13 +0,0 @@
|
||||
from typing import Dict, List
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class LLMResponse:
|
||||
role: str
|
||||
'''角色'''
|
||||
completion_text: str = None
|
||||
'''LLM 返回的文本'''
|
||||
tools_call_args: List[Dict[str, any]] = None
|
||||
'''工具调用参数'''
|
||||
tools_call_name: List[str] = None
|
||||
'''工具调用名称'''
|
||||
@@ -1,3 +1,4 @@
|
||||
import traceback
|
||||
from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||
from .provider import Provider
|
||||
from typing import List
|
||||
@@ -42,8 +43,12 @@ class ProviderManager():
|
||||
continue
|
||||
cls_type = provider_cls_map[provider_config['type']]
|
||||
logger.info(f"尝试实例化 {provider_config['type']}({provider_config['id']}) 大模型提供商适配器 ...")
|
||||
inst = cls_type(provider_config, self.provider_settings, self.db_helper, self.provider_settings.get('persistant_history', True))
|
||||
self.provider_insts.append(inst)
|
||||
try:
|
||||
inst = cls_type(provider_config, self.provider_settings, self.db_helper, self.provider_settings.get('persistant_history', True))
|
||||
self.provider_insts.append(inst)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
logger.error(f"实例化 {provider_config['type']}({provider_config['id']}) 大模型提供商适配器 失败:{e}")
|
||||
|
||||
if len(self.provider_insts) > 0:
|
||||
self.curr_provider_inst = self.provider_insts[0]
|
||||
|
||||
@@ -5,8 +5,8 @@ from typing import List
|
||||
from astrbot.core.db import BaseDatabase
|
||||
from astrbot.core import logger
|
||||
from typing import TypedDict
|
||||
from astrbot.core.provider.tool import FuncCall
|
||||
from astrbot.core.provider.llm_response import LLMResponse
|
||||
from astrbot.core.provider.func_tool_manager import FuncCall
|
||||
from astrbot.core.provider.entites import LLMResponse
|
||||
from dataclasses import dataclass
|
||||
class Personality(TypedDict):
|
||||
prompt: str = ""
|
||||
@@ -99,6 +99,7 @@ class Provider(abc.ABC):
|
||||
image_urls: List[str]=None,
|
||||
func_tool: FuncCall=None,
|
||||
contexts: List=None,
|
||||
system_prompt: str=None,
|
||||
**kwargs) -> LLMResponse:
|
||||
'''获得 LLM 的文本对话结果。会使用当前的模型进行对话。
|
||||
|
||||
@@ -111,13 +112,11 @@ class Provider(abc.ABC):
|
||||
kwargs: 其他参数
|
||||
|
||||
Notes:
|
||||
- 如果传入了 contexts,将会提前加上上下文。否则使用 session_memory 中的上下文。
|
||||
- 可以选择性地传入 session_id,如果传入了 session_id,将会使用 session_id 对应的上下文进行对话,
|
||||
并且也会记录相应的对话上下文,实现多轮对话。如果不传入则不会记录上下文。
|
||||
- 如果传入了 image_urls,将会在对话时附上图片。如果模型不支持图片输入,将会抛出错误。
|
||||
- 如果传入了 tools,将会使用 tools 进行 Function-calling。如果模型不支持 Function-calling,将会抛出错误。
|
||||
- 如果传入了 contexts,将会**直接**使用所提供的 contexts 进行对话。
|
||||
传入此值通常意味着你需要自己维护 context,AstrBot 将不会记录上下文,并且会忽略 prompt、session_id、image_urls、tools。
|
||||
|
||||
'''
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class ProviderMetaData():
|
||||
type: str # 提供商适配器名称,如 openai, ollama
|
||||
desc: str = "" # 提供商适配器描述.
|
||||
@@ -1,8 +1,7 @@
|
||||
import docstring_parser
|
||||
from typing import List, Dict, Type, Awaitable
|
||||
from .provider_metadata import ProviderMetaData
|
||||
from typing import List, Dict, Type
|
||||
from .entites import ProviderMetaData
|
||||
from astrbot.core import logger
|
||||
from .tool import FuncCall, SUPPORTED_TYPES
|
||||
from .func_tool_manager import FuncCall
|
||||
|
||||
provider_registry: List[ProviderMetaData] = []
|
||||
'''维护了通过装饰器注册的 Provider'''
|
||||
@@ -27,43 +26,3 @@ def register_provider_adapter(provider_type_name: str, desc: str):
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
def register_llm_tool(name: str = None):
|
||||
'''为函数调用(function-calling / tools-use)添加工具。
|
||||
|
||||
请务必按照以下格式编写一个工具(包括函数注释,AstrBot 会尝试解析该函数注释)
|
||||
|
||||
```
|
||||
@llm_tool(name="get_weather") # 如果 name 不填,将使用函数名
|
||||
async def get_weather(event: AstrMessageEvent, location: str) -> MessageEventResult:
|
||||
\'\'\'获取天气信息。
|
||||
|
||||
Args:
|
||||
location(string): 地点
|
||||
\'\'\'
|
||||
# 处理逻辑
|
||||
```
|
||||
|
||||
可接受的参数类型有:string, number, object, array, boolean。
|
||||
'''
|
||||
name_ = name
|
||||
|
||||
def decorator(func_obj: Awaitable):
|
||||
llm_tool_name = name_ if name_ else func_obj.__name__
|
||||
module_name = func_obj.__module__
|
||||
docstring = docstring_parser.parse(func_obj.__doc__)
|
||||
args = []
|
||||
for arg in docstring.params:
|
||||
if arg.type_name not in SUPPORTED_TYPES:
|
||||
raise ValueError(f"LLM 函数工具 {func_obj.__module__}_{llm_tool_name} 不支持的参数类型:{arg.type_name}")
|
||||
args.append({
|
||||
"type": arg.type_name,
|
||||
"name": arg.arg_name,
|
||||
"description": arg.description
|
||||
})
|
||||
llm_tools.add_func(llm_tool_name, args, docstring.short_description, func_obj, module_name)
|
||||
|
||||
logger.debug(f"LLM 函数工具 {llm_tool_name} 已注册")
|
||||
return func_obj
|
||||
|
||||
return decorator
|
||||
@@ -3,90 +3,119 @@ import os
|
||||
from llmtuner.chat import ChatModel
|
||||
from typing import List
|
||||
from .. import Provider
|
||||
from ..entites import LLMResponse
|
||||
from ..func_tool_manager import FuncCall
|
||||
from astrbot.core.db import BaseDatabase
|
||||
from astrbot import logger
|
||||
|
||||
from ..register import register_provider_adapter
|
||||
|
||||
@register_provider_adapter("llm_tuner", "LLMTuner 适配器, 用于装载使用 LlamaFactory 微调后的模型")
|
||||
|
||||
@register_provider_adapter(
|
||||
"llm_tuner", "LLMTuner 适配器, 用于装载使用 LlamaFactory 微调后的模型"
|
||||
)
|
||||
class LLMTunerModelLoader(Provider):
|
||||
def __init__(
|
||||
self,
|
||||
provider_config: dict,
|
||||
self,
|
||||
provider_config: dict,
|
||||
provider_settings: dict,
|
||||
db_helper: BaseDatabase,
|
||||
persistant_history = True
|
||||
db_helper: BaseDatabase,
|
||||
persistant_history=True,
|
||||
) -> None:
|
||||
super().__init__(provider_config, provider_settings, persistant_history, db_helper)
|
||||
self.base_model_path = provider_config['base_model_path']
|
||||
self.adapter_model_path = provider_config['adapter_model_path']
|
||||
self.model = ChatModel({
|
||||
"model_name_or_path": self.base_model_path,
|
||||
"adapter_name_or_path": self.adapter_model_path,
|
||||
"template": provider_config['llmtuner_template'],
|
||||
"finetuning_type": provider_config['finetuning_type'],
|
||||
"quantization_bit": provider_config['quantization_bit'],
|
||||
})
|
||||
self.set_model(os.path.basename(self.base_model_path) + "_" + os.path.basename(self.adapter_model_path))
|
||||
|
||||
super().__init__(
|
||||
provider_config, provider_settings, persistant_history, db_helper
|
||||
)
|
||||
if not os.path.exists(provider_config["base_model_path"]) or not os.path.exists(
|
||||
provider_config["adapter_model_path"]
|
||||
):
|
||||
raise FileNotFoundError("模型文件路径不存在。")
|
||||
self.base_model_path = provider_config["base_model_path"]
|
||||
self.adapter_model_path = provider_config["adapter_model_path"]
|
||||
self.model = ChatModel(
|
||||
{
|
||||
"model_name_or_path": self.base_model_path,
|
||||
"adapter_name_or_path": self.adapter_model_path,
|
||||
"template": provider_config["llmtuner_template"],
|
||||
"finetuning_type": provider_config["finetuning_type"],
|
||||
"quantization_bit": provider_config["quantization_bit"],
|
||||
}
|
||||
)
|
||||
self.set_model(
|
||||
os.path.basename(self.base_model_path)
|
||||
+ "_"
|
||||
+ os.path.basename(self.adapter_model_path)
|
||||
)
|
||||
|
||||
async def assemble_context(self, text: str, image_urls: List[str] = None):
|
||||
'''
|
||||
"""
|
||||
组装上下文。
|
||||
'''
|
||||
"""
|
||||
return {"role": "user", "content": text}
|
||||
|
||||
async def text_chat(self,
|
||||
prompt: str,
|
||||
session_id: str,
|
||||
image_urls: List[str] = None,
|
||||
tools = None,
|
||||
contexts: List=None,
|
||||
**kwargs) -> str:
|
||||
|
||||
|
||||
async def text_chat(
|
||||
self,
|
||||
prompt: str,
|
||||
session_id: str = None,
|
||||
image_urls: List[str] = None,
|
||||
func_tool: FuncCall = None,
|
||||
contexts: List = None,
|
||||
system_prompt: str = None,
|
||||
**kwargs,
|
||||
) -> LLMResponse:
|
||||
system_prompt = ""
|
||||
if not contexts:
|
||||
contexts = [*self.session_memory[session_id], {"role": "user", "content": prompt}]
|
||||
query_context = [
|
||||
*self.session_memory[session_id],
|
||||
{"role": "user", "content": prompt},
|
||||
]
|
||||
system_prompt = self.curr_personality["prompt"]
|
||||
else:
|
||||
# 提取出系统提示
|
||||
system_idxs = []
|
||||
for idx, context in enumerate(contexts):
|
||||
if context["role"] == "system":
|
||||
system_idxs.append(idx)
|
||||
for idx in reversed(system_idxs):
|
||||
system_prompt += " " + contexts.pop(idx)["content"]
|
||||
|
||||
logger.debug(f"请求上下文:{contexts}")
|
||||
logger.debug(f"请求 System Prompt:{system_prompt}")
|
||||
|
||||
query_context = [*contexts, {"role": "user", "content": prompt}]
|
||||
|
||||
# 提取出系统提示
|
||||
system_idxs = []
|
||||
for idx, context in enumerate(query_context):
|
||||
if context["role"] == "system":
|
||||
system_idxs.append(idx)
|
||||
for idx in reversed(system_idxs):
|
||||
system_prompt += " " + query_context.pop(idx)["content"]
|
||||
|
||||
conf = {
|
||||
"messages": contexts,
|
||||
"messages": query_context,
|
||||
"system": system_prompt,
|
||||
}
|
||||
if tools:
|
||||
conf['tools'] = tools
|
||||
|
||||
if func_tool:
|
||||
conf["tools"] = func_tool
|
||||
|
||||
responses = await self.model.achat(**conf)
|
||||
logger.debug(f"返回上下文:{responses}")
|
||||
self.db_helper.update_llm_history(session_id, json.dumps(self.session_memory[session_id]), self.meta().type)
|
||||
self.session_memory[session_id].append({"role": "user", "content": prompt})
|
||||
self.session_memory[session_id].append({"role": "assistant", "content": responses[-1].response_text})
|
||||
|
||||
if session_id:
|
||||
if not contexts:
|
||||
self.session_memory[session_id].append(
|
||||
{"role": "user", "content": prompt}
|
||||
)
|
||||
self.session_memory[session_id].append(
|
||||
{"role": "assistant", "content": responses[-1].response_text}
|
||||
)
|
||||
else:
|
||||
self.session_memory[session_id] = [
|
||||
*contexts,
|
||||
{"role": "user", "content": prompt},
|
||||
{"role": "assistant", "content": responses[-1].response_text},
|
||||
]
|
||||
self.db_helper.update_llm_history(session_id, json.dumps(self.session_memory[session_id]), self.meta().type)
|
||||
return responses[-1].response_text
|
||||
|
||||
async def forget(self, session_id):
|
||||
logger.info("llmtuner reset")
|
||||
self.session_memory[session_id] = []
|
||||
return True
|
||||
|
||||
|
||||
async def get_current_key(self):
|
||||
return "none"
|
||||
|
||||
|
||||
async def set_key(self, key):
|
||||
pass
|
||||
|
||||
|
||||
async def get_models(self):
|
||||
return [self.get_model()]
|
||||
|
||||
|
||||
async def get_human_readable_context(self, session_id, page, page_size):
|
||||
if session_id not in self.session_memory:
|
||||
@@ -94,9 +123,9 @@ class LLMTunerModelLoader(Provider):
|
||||
contexts = []
|
||||
temp_contexts = []
|
||||
for record in self.session_memory[session_id]:
|
||||
if record['role'] == "user":
|
||||
if record["role"] == "user":
|
||||
temp_contexts.append(f"User: {record['content']}")
|
||||
elif record['role'] == "assistant":
|
||||
elif record["role"] == "assistant":
|
||||
temp_contexts.append(f"Assistant: {record['content']}")
|
||||
contexts.insert(0, temp_contexts)
|
||||
temp_contexts = []
|
||||
@@ -105,9 +134,9 @@ class LLMTunerModelLoader(Provider):
|
||||
contexts = [item for sublist in contexts for item in sublist]
|
||||
|
||||
# 计算分页
|
||||
paged_contexts = contexts[(page-1)*page_size:page*page_size]
|
||||
paged_contexts = contexts[(page - 1) * page_size : page * page_size]
|
||||
total_pages = len(contexts) // page_size
|
||||
if len(contexts) % page_size != 0:
|
||||
total_pages += 1
|
||||
|
||||
return paged_contexts, total_pages
|
||||
|
||||
return paged_contexts, total_pages
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import traceback
|
||||
import base64
|
||||
import json
|
||||
import datetime
|
||||
|
||||
from openai import AsyncOpenAI, NOT_GIVEN
|
||||
from openai.types.chat.chat_completion import ChatCompletion
|
||||
@@ -11,10 +10,10 @@ from astrbot.core.utils.io import download_image_by_url
|
||||
from astrbot.core.db import BaseDatabase
|
||||
from astrbot.api.provider import Provider
|
||||
from astrbot import logger
|
||||
from astrbot.core.provider.tool import FuncCall
|
||||
from astrbot.core.provider.func_tool_manager import FuncCall
|
||||
from typing import List
|
||||
from ..register import register_provider_adapter
|
||||
from astrbot.core.provider.llm_response import LLMResponse
|
||||
from astrbot.core.provider.entites import LLMResponse
|
||||
|
||||
@register_provider_adapter("openai_chat_completion", "OpenAI API Chat Completion 提供商适配器")
|
||||
class ProviderOpenAIOfficial(Provider):
|
||||
@@ -29,7 +28,6 @@ class ProviderOpenAIOfficial(Provider):
|
||||
self.chosen_api_key = None
|
||||
self.api_keys: List = provider_config.get("key", [])
|
||||
self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None
|
||||
self.enable_datetime = provider_config.get("datetime_system_prompt", True)
|
||||
|
||||
self.client = AsyncOpenAI(
|
||||
api_key=self.chosen_api_key,
|
||||
@@ -37,16 +35,22 @@ class ProviderOpenAIOfficial(Provider):
|
||||
timeout=provider_config.get("timeout", NOT_GIVEN),
|
||||
)
|
||||
self.set_model(provider_config['model_config']['model'])
|
||||
|
||||
|
||||
async def get_human_readable_context(self, session_id, page, page_size):
|
||||
if session_id not in self.session_memory:
|
||||
raise Exception("会话 ID 不存在")
|
||||
contexts = []
|
||||
temp_contexts = []
|
||||
for record in self.session_memory[session_id]:
|
||||
if record['role'] == "user":
|
||||
contexts.append(f"User: {record['content']}")
|
||||
temp_contexts.append(f"User: {record['content']}")
|
||||
elif record['role'] == "assistant":
|
||||
contexts.append(f"Assistant: {record['content']}")
|
||||
temp_contexts.append(f"Assistant: {record['content']}")
|
||||
contexts.insert(0, temp_contexts)
|
||||
temp_contexts = []
|
||||
|
||||
# 展平 contexts 列表
|
||||
contexts = [item for sublist in contexts for item in sublist]
|
||||
|
||||
# 计算分页
|
||||
paged_contexts = contexts[(page-1)*page_size:page*page_size]
|
||||
@@ -127,36 +131,30 @@ class ProviderOpenAIOfficial(Provider):
|
||||
else:
|
||||
raise Exception("Internal Error")
|
||||
|
||||
async def text_chat(self,
|
||||
prompt: str,
|
||||
session_id: str,
|
||||
image_urls: List[str]=None,
|
||||
func_tool: FuncCall=None,
|
||||
contexts=None,
|
||||
**kwargs
|
||||
) -> LLMResponse:
|
||||
async def text_chat(
|
||||
self,
|
||||
prompt: str,
|
||||
session_id: str,
|
||||
image_urls: List[str]=None,
|
||||
func_tool: FuncCall=None,
|
||||
contexts=None,
|
||||
system_prompt=None,
|
||||
**kwargs
|
||||
) -> LLMResponse:
|
||||
new_record = await self.assemble_context(prompt, image_urls)
|
||||
|
||||
context_query = []
|
||||
if not contexts:
|
||||
context_query = [*self.session_memory[session_id], new_record]
|
||||
system_prompt = ""
|
||||
if self.curr_personality["prompt"]:
|
||||
system_prompt = self.curr_personality["prompt"]
|
||||
if self.enable_datetime:
|
||||
system_prompt += f"Current datetime: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}"
|
||||
if system_prompt:
|
||||
context_query.insert(0, {"role": "system", "content": system_prompt})
|
||||
else:
|
||||
context_query = contexts
|
||||
|
||||
logger.debug(f"请求上下文:{context_query}, {self.get_model()}")
|
||||
|
||||
context_query = [*contexts, new_record]
|
||||
if system_prompt:
|
||||
context_query.insert(0, {"role": "system", "content": system_prompt})
|
||||
|
||||
payloads = {
|
||||
"messages": context_query,
|
||||
**self.provider_config.get("model_config", {})
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
llm_response = await self._query(payloads, func_tool)
|
||||
except Exception as e:
|
||||
@@ -165,7 +163,7 @@ class ProviderOpenAIOfficial(Provider):
|
||||
self.pop_record(session_id)
|
||||
logger.warning(traceback.format_exc())
|
||||
|
||||
if llm_response.role == "assistant":
|
||||
if llm_response.role == "assistant" and session_id:
|
||||
# 文本回复
|
||||
if not contexts:
|
||||
# 添加用户 record
|
||||
@@ -175,7 +173,12 @@ class ProviderOpenAIOfficial(Provider):
|
||||
"role": "assistant",
|
||||
"content": llm_response.completion_text
|
||||
})
|
||||
self.db_helper.update_llm_history(session_id, json.dumps(self.session_memory[session_id]), self.provider_config['type'])
|
||||
else:
|
||||
self.session_memory[session_id] = [*contexts, new_record, {
|
||||
"role": "assistant",
|
||||
"content": llm_response.completion_text
|
||||
}]
|
||||
self.db_helper.update_llm_history(session_id, json.dumps(self.session_memory[session_id]), self.provider_config['type'])
|
||||
|
||||
return llm_response
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
from asyncio import Queue
|
||||
from typing import List, TypedDict, Union
|
||||
|
||||
from astrbot.core.provider import Provider
|
||||
from astrbot.core.provider.provider import Provider
|
||||
from astrbot.core.db import BaseDatabase
|
||||
from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||
from astrbot.core.provider.tool import FuncCall
|
||||
from astrbot.core.provider.func_tool_manager import FuncCall
|
||||
from astrbot.core.platform.astr_message_event import MessageSesion
|
||||
from astrbot.core.message.message_event_result import MessageChain
|
||||
from astrbot.core.provider.manager import ProviderManager
|
||||
from astrbot.core.platform.manager import PlatformManager
|
||||
from .star import star_registry, StarMetadata
|
||||
from .star_handler import star_handlers_registry, star_handlers_map, StarHandlerMetadata
|
||||
from .star_handler import star_handlers_registry, StarHandlerMetadata, EventType
|
||||
from .filter.command import CommandFilter
|
||||
from .filter.regex import RegexFilter
|
||||
from typing import Awaitable
|
||||
@@ -69,7 +69,17 @@ class Context:
|
||||
|
||||
异步处理函数会接收到额外的的关键词参数:event: AstrMessageEvent, context: Context。
|
||||
'''
|
||||
self.provider_manager.llm_tools.add_func(name, func_args, desc, func_obj, func_obj.__module__)
|
||||
md = StarHandlerMetadata(
|
||||
event_type=EventType.OnLLMRequestEvent,
|
||||
handler_full_name=func_obj.__module__ + "_" + func_obj.__name__,
|
||||
handler_name=func_obj.__name__,
|
||||
handler_module_str=func_obj.__module__,
|
||||
handler=func_obj,
|
||||
event_filters=[],
|
||||
desc=desc
|
||||
)
|
||||
star_handlers_registry.append(md)
|
||||
self.provider_manager.llm_tools.add_func(name, func_args, desc, func_obj, func_obj)
|
||||
|
||||
def unregister_llm_tool(self, name: str) -> None:
|
||||
'''删除一个函数调用工具。如果再要启用,需要重新注册。'''
|
||||
@@ -112,6 +122,7 @@ class Context:
|
||||
|
||||
'''
|
||||
md = StarHandlerMetadata(
|
||||
event_type=EventType.AdapterMessageEvent,
|
||||
handler_full_name=awaitable.__module__ + "_" + awaitable.__name__,
|
||||
handler_name=awaitable.__name__,
|
||||
handler_module_str=awaitable.__module__,
|
||||
@@ -129,7 +140,6 @@ class Context:
|
||||
handler_md=md
|
||||
))
|
||||
star_handlers_registry.append(md)
|
||||
star_handlers_map[md.handler_full_name] = md
|
||||
|
||||
def register_provider(self, provider: Provider):
|
||||
'''
|
||||
|
||||
@@ -6,8 +6,8 @@ from astrbot.core.config import AstrBotConfig
|
||||
class PermissionType(enum.Flag):
|
||||
'''权限类型。当选择 MEMBER,ADMIN 也可以通过。
|
||||
'''
|
||||
ADMIN = "admin"
|
||||
MEMBER = "member"
|
||||
ADMIN = enum.auto()
|
||||
MEMBER = enum.auto()
|
||||
|
||||
class PermissionTypeFilter(HandlerFilter):
|
||||
def __init__(self, permission_type: PermissionType, raise_error: bool = True):
|
||||
|
||||
@@ -5,7 +5,11 @@ from .star_handler import (
|
||||
register_event_message_type,
|
||||
register_platform_adapter_type,
|
||||
register_regex,
|
||||
register_permission_type
|
||||
register_permission_type,
|
||||
register_on_llm_request,
|
||||
register_llm_tool,
|
||||
register_on_decorating_result,
|
||||
register_after_message_sent
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
@@ -15,5 +19,9 @@ __all__ = [
|
||||
'register_event_message_type',
|
||||
'register_platform_adapter_type',
|
||||
'register_regex',
|
||||
'register_permission_type'
|
||||
'register_permission_type',
|
||||
'register_on_llm_request',
|
||||
'register_llm_tool',
|
||||
'register_on_decorating_result',
|
||||
'register_after_message_sent'
|
||||
]
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
import docstring_parser
|
||||
|
||||
from ..star_handler import star_handlers_registry, star_handlers_map, StarHandlerMetadata
|
||||
from ..star_handler import star_handlers_registry, StarHandlerMetadata, EventType
|
||||
from ..filter.command import CommandFilter
|
||||
from ..filter.command_group import CommandGroupFilter
|
||||
from ..filter.event_message_type import EventMessageTypeFilter, EventMessageType
|
||||
@@ -8,19 +9,23 @@ from ..filter.platform_adapter_type import PlatformAdapterTypeFilter, PlatformAd
|
||||
from ..filter.permission import PermissionTypeFilter, PermissionType
|
||||
from ..filter.regex import RegexFilter
|
||||
from typing import Awaitable
|
||||
from astrbot.core.provider.func_tool_manager import SUPPORTED_TYPES
|
||||
from astrbot.core.provider.register import llm_tools
|
||||
from astrbot.core import logger
|
||||
|
||||
|
||||
def get_handler_full_name(awatable: Awaitable) -> str:
|
||||
def get_handler_full_name(awaitable: Awaitable) -> str:
|
||||
'''获取 Handler 的全名'''
|
||||
return f"{awatable.__module__}_{awatable.__name__}"
|
||||
return f"{awaitable.__module__}_{awaitable.__name__}"
|
||||
|
||||
def get_handler_or_create(handler: Awaitable, dont_add = False) -> StarHandlerMetadata:
|
||||
def get_handler_or_create(handler: Awaitable, event_type: EventType, dont_add = False) -> StarHandlerMetadata:
|
||||
'''获取 Handler 或者创建一个新的 Handler'''
|
||||
handler_full_name = get_handler_full_name(handler)
|
||||
if handler_full_name in star_handlers_map:
|
||||
return star_handlers_map[handler_full_name]
|
||||
md = star_handlers_registry.get_handler_by_full_name(handler_full_name)
|
||||
if md:
|
||||
return md
|
||||
else:
|
||||
md = StarHandlerMetadata(
|
||||
event_type=event_type,
|
||||
handler_full_name=handler_full_name,
|
||||
handler_name=handler.__name__,
|
||||
handler_module_str=handler.__module__,
|
||||
@@ -29,7 +34,6 @@ def get_handler_or_create(handler: Awaitable, dont_add = False) -> StarHandlerMe
|
||||
)
|
||||
if not dont_add:
|
||||
star_handlers_registry.append(md)
|
||||
star_handlers_map[handler_full_name] = md
|
||||
return md
|
||||
|
||||
def register_command(command_name: str = None, *args):
|
||||
@@ -47,7 +51,7 @@ def register_command(command_name: str = None, *args):
|
||||
add_to_event_filters = True
|
||||
|
||||
def decorator(awaitable):
|
||||
handler_md = get_handler_or_create(awaitable)
|
||||
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent)
|
||||
new_command.init_handler_md(handler_md)
|
||||
if add_to_event_filters:
|
||||
# 裸指令
|
||||
@@ -74,7 +78,7 @@ def register_command_group(command_group_name: str = None, *args):
|
||||
def decorator(obj):
|
||||
if add_to_event_filters:
|
||||
# 根指令组
|
||||
handler_md = get_handler_or_create(obj)
|
||||
handler_md = get_handler_or_create(obj, EventType.AdapterMessageEvent)
|
||||
handler_md.event_filters.append(new_group)
|
||||
|
||||
return RegisteringCommandable(new_group)
|
||||
@@ -91,28 +95,28 @@ class RegisteringCommandable():
|
||||
|
||||
def register_event_message_type(event_message_type: EventMessageType):
|
||||
'''注册一个 EventMessageType'''
|
||||
def decorator(awatable):
|
||||
handler_md = get_handler_or_create(awatable)
|
||||
def decorator(awaitable):
|
||||
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent)
|
||||
handler_md.event_filters.append(EventMessageTypeFilter(event_message_type))
|
||||
return awatable
|
||||
return awaitable
|
||||
|
||||
return decorator
|
||||
|
||||
def register_platform_adapter_type(platform_adapter_type: PlatformAdapterType):
|
||||
'''注册一个 PlatformAdapterType'''
|
||||
def decorator(awatable):
|
||||
handler_md = get_handler_or_create(awatable)
|
||||
def decorator(awaitable):
|
||||
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent)
|
||||
handler_md.event_filters.append(PlatformAdapterTypeFilter(platform_adapter_type))
|
||||
return awatable
|
||||
return awaitable
|
||||
|
||||
return decorator
|
||||
|
||||
def register_regex(regex: str):
|
||||
'''注册一个 Regex'''
|
||||
def decorator(awatable):
|
||||
handler_md = get_handler_or_create(awatable)
|
||||
def decorator(awaitable):
|
||||
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent)
|
||||
handler_md.event_filters.append(RegexFilter(regex))
|
||||
return awatable
|
||||
return awaitable
|
||||
|
||||
return decorator
|
||||
|
||||
@@ -123,9 +127,83 @@ def register_permission_type(permission_type: PermissionType, raise_error: bool
|
||||
permission_type: PermissionType
|
||||
raise_error: 如果没有权限,是否抛出错误到消息平台,并且停止事件传播。默认为 True
|
||||
'''
|
||||
def decorator(awatable):
|
||||
handler_md = get_handler_or_create(awatable)
|
||||
def decorator(awaitable):
|
||||
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent)
|
||||
handler_md.event_filters.append(PermissionTypeFilter(permission_type, raise_error))
|
||||
return awatable
|
||||
return awaitable
|
||||
|
||||
return decorator
|
||||
|
||||
def register_on_llm_request():
|
||||
'''当有 LLM 请求时的事件
|
||||
|
||||
Examples:
|
||||
```py
|
||||
@on_llm_request()
|
||||
async def test(self, event: AstrMessageEvent, request: ProviderRequest) -> None:
|
||||
request.system_prompt += "你是一个猫娘..."
|
||||
```
|
||||
|
||||
请务必接收两个参数:event, request
|
||||
'''
|
||||
def decorator(awaitable):
|
||||
_ = get_handler_or_create(awaitable, EventType.OnLLMRequestEvent)
|
||||
return awaitable
|
||||
|
||||
return decorator
|
||||
|
||||
def register_llm_tool(name: str = None):
|
||||
'''为函数调用(function-calling / tools-use)添加工具。
|
||||
|
||||
请务必按照以下格式编写一个工具(包括函数注释,AstrBot 会尝试解析该函数注释)
|
||||
|
||||
```
|
||||
@llm_tool(name="get_weather") # 如果 name 不填,将使用函数名
|
||||
async def get_weather(event: AstrMessageEvent, location: str) -> MessageEventResult:
|
||||
\'\'\'获取天气信息。
|
||||
|
||||
Args:
|
||||
location(string): 地点
|
||||
\'\'\'
|
||||
# 处理逻辑
|
||||
```
|
||||
|
||||
可接受的参数类型有:string, number, object, array, boolean。
|
||||
'''
|
||||
name_ = name
|
||||
|
||||
def decorator(awaitable: Awaitable):
|
||||
llm_tool_name = name_ if name_ else awaitable.__name__
|
||||
docstring = docstring_parser.parse(awaitable.__doc__)
|
||||
args = []
|
||||
for arg in docstring.params:
|
||||
if arg.type_name not in SUPPORTED_TYPES:
|
||||
raise ValueError(f"LLM 函数工具 {awaitable.__module__}_{llm_tool_name} 不支持的参数类型:{arg.type_name}")
|
||||
args.append({
|
||||
"type": arg.type_name,
|
||||
"name": arg.arg_name,
|
||||
"description": arg.description
|
||||
})
|
||||
md = get_handler_or_create(awaitable, EventType.OnCallingFuncToolEvent)
|
||||
llm_tools.add_func(llm_tool_name, args, docstring.short_description, md.handler)
|
||||
|
||||
logger.debug(f"LLM 函数工具 {llm_tool_name} 已注册")
|
||||
return awaitable
|
||||
|
||||
return decorator
|
||||
|
||||
def register_on_decorating_result():
|
||||
'''在发送消息前的事件'''
|
||||
def decorator(awaitable):
|
||||
_ = get_handler_or_create(awaitable, EventType.OnDecoratingResultEvent)
|
||||
return awaitable
|
||||
|
||||
return decorator
|
||||
|
||||
def register_after_message_sent():
|
||||
'''在消息发送后的事件'''
|
||||
def decorator(awaitable):
|
||||
_ = get_handler_or_create(awaitable, EventType.OnAfterMessageSentEvent)
|
||||
return awaitable
|
||||
|
||||
return decorator
|
||||
@@ -1,17 +1,54 @@
|
||||
from __future__ import annotations
|
||||
import enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Awaitable, List, Dict
|
||||
from .filter import HandlerFilter
|
||||
|
||||
star_handlers_registry: List[StarHandlerMetadata] = []
|
||||
|
||||
star_handlers_map: Dict[str, StarHandlerMetadata] = {}
|
||||
'''用于快速查找。key 是 handler_full_name'''
|
||||
class StarHandlerRegistry(List):
|
||||
'''用于存储所有的 Star Handler'''
|
||||
|
||||
star_handlers_map: Dict[str, StarHandlerMetadata] = {}
|
||||
'''用于快速查找。key 是 handler_full_name'''
|
||||
|
||||
def append(self, handler: StarHandlerMetadata):
|
||||
'''添加一个 Handler'''
|
||||
super().append(handler)
|
||||
self.star_handlers_map[handler.handler_full_name] = handler
|
||||
|
||||
def get_handlers_by_event_type(self, event_type: EventType) -> List[StarHandlerMetadata]:
|
||||
'''通过事件类型获取 Handler'''
|
||||
return [handler for handler in self if handler.event_type == event_type]
|
||||
|
||||
def get_handler_by_full_name(self, full_name: str) -> StarHandlerMetadata:
|
||||
'''通过 Handler 的全名获取 Handler'''
|
||||
return self.star_handlers_map.get(full_name, None)
|
||||
|
||||
def get_handlers_by_module_name(self, module_name: str) -> List[StarHandlerMetadata]:
|
||||
'''通过模块名获取 Handler'''
|
||||
return [handler for handler in self if handler.handler_module_str == module_name]
|
||||
|
||||
|
||||
star_handlers_registry = StarHandlerRegistry()
|
||||
|
||||
class EventType(enum.Enum):
|
||||
'''表示一个 AstrBot 内部事件的类型。如适配器消息事件、LLM 请求事件、发送消息前的事件等
|
||||
|
||||
用于对 Handler 的职能分组。
|
||||
'''
|
||||
AdapterMessageEvent = enum.auto() # 收到适配器发来的消息
|
||||
OnLLMRequestEvent = enum.auto() # 收到 LLM 请求(可以是用户也可以是插件)
|
||||
OnDecoratingResultEvent = enum.auto() # 发送消息前
|
||||
OnCallingFuncToolEvent = enum.auto() # 调用函数工具
|
||||
OnAfterMessageSentEvent = enum.auto() # 发送消息后
|
||||
|
||||
@dataclass
|
||||
class StarHandlerMetadata():
|
||||
'''描述一个 Star 所注册的某一个 Handler。'''
|
||||
|
||||
event_type: EventType
|
||||
'''Handler 的事件类型'''
|
||||
|
||||
handler_full_name: str
|
||||
'''格式为 f"{handler.__module__}_{handler.__name__}"'''
|
||||
|
||||
@@ -25,7 +62,7 @@ class StarHandlerMetadata():
|
||||
'''Handler 的函数对象,应当是一个异步函数'''
|
||||
|
||||
event_filters: List[HandlerFilter]
|
||||
'''一个事件过滤器,用于描述这个 Handler 能够处理、应该处理的事件'''
|
||||
'''一个适配器消息事件过滤器,用于描述这个 Handler 能够处理、应该处理的适配器消息事件'''
|
||||
|
||||
desc: str = ""
|
||||
'''Handler 的描述信息'''
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import inspect
|
||||
import functools
|
||||
import os
|
||||
import traceback
|
||||
import yaml
|
||||
@@ -13,6 +14,7 @@ from . import StarMetadata
|
||||
from .updator import PluginUpdator
|
||||
from astrbot.core.utils.io import remove_dir
|
||||
from .star import star_registry, star_map
|
||||
from astrbot.core.provider.register import llm_tools
|
||||
|
||||
from .star_handler import star_handlers_registry
|
||||
|
||||
@@ -174,6 +176,17 @@ class PluginManager:
|
||||
star_metadata.module = module
|
||||
star_metadata.root_dir_name = root_dir_name
|
||||
star_metadata.reserved = reserved
|
||||
|
||||
related_handlers = star_handlers_registry.get_handlers_by_module_name(star_metadata.module_path)
|
||||
for handler in related_handlers:
|
||||
logger.debug(f"bind handler {handler.handler_name} to {star_metadata.name}")
|
||||
# handler.handler.__self__ = star_metadata.star_cls # 绑定 handler 的 self
|
||||
handler.handler = functools.partial(handler.handler, star_metadata.star_cls)
|
||||
# llm_tool
|
||||
for func_tool in llm_tools.func_list:
|
||||
if func_tool.handler.__module__ == star_metadata.module_path:
|
||||
func_tool.handler = functools.partial(func_tool.handler, star_metadata.star_cls)
|
||||
|
||||
else:
|
||||
# v3.4.0 以前的方式注册插件
|
||||
logger.debug(f"插件 {path} 未通过装饰器注册。尝试通过旧版本方式载入。")
|
||||
|
||||
@@ -39,7 +39,7 @@ class UpdateRoute(Route):
|
||||
latest = False
|
||||
try:
|
||||
await self.astrbot_updator.update(latest=latest, version=version)
|
||||
threading.Thread(target=self.astrbot_updator._reboot, args=(2, self.context)).start()
|
||||
threading.Thread(target=self.astrbot_updator._reboot, args=(2, )).start()
|
||||
return Response().ok(None, "更新成功,AstrBot 将在 2 秒内全量重启以应用新的代码。").__dict__
|
||||
except Exception as e:
|
||||
logger.error(f"/api/update_project: {traceback.format_exc()}")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
package.json
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 CodedThemes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,3 @@
|
||||
# AstrBot 管理面板
|
||||
|
||||
基于 CodedThemes/Berry 模板开发。
|
||||
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="keywords" content="AstrBot Soulter" />
|
||||
<meta name="description" content="AstrBot Dashboard" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap"
|
||||
/>
|
||||
<title>AstrBot - 仪表盘</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
/* /index.html 200
|
||||
@@ -0,0 +1 @@
|
||||
<svg t="1702013028016" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1541" width="200" height="200"><path d="M0 0m204.8 0l614.4 0q204.8 0 204.8 204.8l0 614.4q0 204.8-204.8 204.8l-614.4 0q-204.8 0-204.8-204.8l0-614.4q0-204.8 204.8-204.8Z" fill="#FFEC9C" p-id="1542"></path><path d="M819.2 0H534.272A756.48 756.48 0 0 0 0 483.584V819.2a204.8 204.8 0 0 0 204.8 204.8h614.4a204.8 204.8 0 0 0 204.8-204.8V204.8a204.8 204.8 0 0 0-204.8-204.8z" fill="#FFE98A" p-id="1543"></path><path d="M819.2 0h-3.84a755.2 755.2 0 0 0-539.392 1024H819.2a204.8 204.8 0 0 0 204.8-204.8V204.8a204.8 204.8 0 0 0-204.8-204.8z" fill="#FFE471" p-id="1544"></path><path d="M497.152 721.152A752.384 752.384 0 0 0 560.384 1024H819.2a204.8 204.8 0 0 0 204.8-204.8V204.8a204.8 204.8 0 0 0-89.088-168.96 755.2 755.2 0 0 0-437.76 685.312z" fill="#FFE161" p-id="1545"></path><path d="M526.08 140.032l98.304 199.168L844.8 371.2a15.616 15.616 0 0 1 8.704 25.6l-159.744 156.16 37.632 219.136a15.616 15.616 0 0 1-22.528 16.384l-196.608-102.4-196.608 102.4a15.616 15.616 0 0 1-22.528-16.384l37.12-219.136-159.232-155.136a15.616 15.616 0 0 1 8.704-25.6l219.904-32 98.304-199.168a15.616 15.616 0 0 1 28.16-1.024z" fill="#FFF5CC" p-id="1546"></path><path d="M665.6 409.6a444.16 444.16 0 0 0 25.6-61.44l-65.536-9.472-99.584-198.656a15.616 15.616 0 0 0-27.904 0l-98.304 199.168L179.2 371.2a15.616 15.616 0 0 0-8.704 25.6l159.744 156.16-15.104 87.04A407.808 407.808 0 0 0 665.6 409.6z" fill="#FFFFFF" p-id="1547"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<RouterView></RouterView>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.06129 13.2253L4.31871 15.9975L1.60458 16.0549C0.793457 14.5504 0.333374 12.8292 0.333374 11C0.333374 9.23119 0.763541 7.56319 1.52604 6.09448H1.52662L3.94296 6.53748L5.00146 8.93932C4.77992 9.58519 4.65917 10.2785 4.65917 11C4.65925 11.783 4.80108 12.5332 5.06129 13.2253Z" fill="#FBBB00"/>
|
||||
<path d="M21.4804 9.00732C21.6029 9.65257 21.6668 10.3189 21.6668 11C21.6668 11.7637 21.5865 12.5086 21.4335 13.2271C20.9143 15.6722 19.5575 17.8073 17.678 19.3182L17.6774 19.3177L14.6339 19.1624L14.2031 16.4734C15.4503 15.742 16.425 14.5974 16.9384 13.2271H11.2346V9.00732H17.0216H21.4804Z" fill="#518EF8"/>
|
||||
<path d="M17.6772 19.3176L17.6777 19.3182C15.8498 20.7875 13.5277 21.6666 11 21.6666C6.93783 21.6666 3.40612 19.3962 1.60449 16.0549L5.0612 13.2253C5.96199 15.6294 8.28112 17.3408 11 17.3408C12.1686 17.3408 13.2634 17.0249 14.2029 16.4734L17.6772 19.3176Z" fill="#28B446"/>
|
||||
<path d="M17.8085 2.78892L14.353 5.61792C13.3807 5.01017 12.2313 4.65908 11 4.65908C8.21963 4.65908 5.85713 6.44896 5.00146 8.93925L1.52658 6.09442H1.526C3.30125 2.67171 6.8775 0.333252 11 0.333252C13.5881 0.333252 15.9612 1.25517 17.8085 2.78892Z" fill="#F14336"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,18 @@
|
||||
<svg width="46" height="55" viewBox="0 0 46 55" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0)">
|
||||
<path d="M19.6667 55C8.82205 55 0 46.2504 0 35.4968C0 24.7431 8.82292 15.9935 19.6667 15.9935C30.5105 15.9935 39.3334 24.7431 39.3334 35.4968C39.3334 46.2504 30.5122 55 19.6667 55ZM19.6667 17.8563C9.8587 17.8563 1.87839 25.7686 1.87839 35.4959C1.87839 45.2233 9.8587 53.1355 19.6667 53.1355C29.4747 53.1355 37.4559 45.2215 37.4559 35.4942C37.4559 25.7668 29.4765 17.8563 19.6667 17.8563Z" fill="#2196F3"/>
|
||||
<path d="M33.9387 36.3618C33.3269 34.1133 27.7188 33.8706 24.3807 34.6949C22.6326 35.1283 20.846 35.6917 19.0034 36.0159C20.3521 37.2026 21.8005 38.3251 23.879 38.6042C29.0361 39.2942 32.2404 37.6898 33.9387 36.3618Z" fill="#2196F3"/>
|
||||
<path d="M23.8788 38.6042C21.7959 38.3251 20.3519 37.2026 19.0032 36.016C16.9159 34.1792 15.0594 32.189 11.4154 32.9379C5.62198 34.1289 4.85978 40.9247 9.3333 45.2917C11.254 47.2864 13.7197 48.6822 16.4284 49.3079C19.137 49.9336 21.9709 49.7621 24.5828 48.8144C27.1946 47.8667 29.4709 46.1839 31.1327 43.9724C32.7945 41.7608 33.7696 39.1165 33.9385 36.3635C32.2402 37.6898 29.0358 39.2942 23.8788 38.6042Z" fill="#673AB7"/>
|
||||
<path d="M26.9105 23.8962C26.1876 25.4331 32.6321 27.1381 33.4031 32.2419C33.7746 27.2178 27.8046 21.9962 26.9105 23.8962Z" fill="#2196F3"/>
|
||||
<path d="M13.3649 30.3107C14.5267 29.8335 15.0784 28.5126 14.5972 27.3604C14.116 26.2083 12.784 25.6611 11.6222 26.1384C10.4604 26.6156 9.90867 27.9365 10.3899 29.0887C10.8712 30.2408 12.2031 30.7879 13.3649 30.3107Z" fill="#673AB7"/>
|
||||
<path d="M18.5351 24.1103C19.0786 23.5714 19.0786 22.6977 18.5351 22.1587C17.9917 21.6198 17.1106 21.6198 16.5672 22.1587C16.0238 22.6977 16.0238 23.5714 16.5672 24.1103C17.1106 24.6492 17.9917 24.6492 18.5351 24.1103Z" fill="#2196F3"/>
|
||||
<path d="M23.4513 15.2376C25.4617 9.3485 24.1103 4.64345 19.9786 2.40881C17.1544 2.97831 15.4779 4.334 14.5444 6.20544C20.0843 5.76077 23.5999 9.1994 23.4513 15.2376Z" fill="#2196F3"/>
|
||||
<path d="M46.0001 10.0923C36.0487 6.55051 29.7685 7.76491 28.7808 15.8349C34.4841 21.6703 40.2286 18.8774 46.0001 10.0923Z" fill="#2196F3"/>
|
||||
<path d="M38.0851 6.89635C38.5466 4.94082 38.7861 2.6299 38.8219 0C28.5017 2.27885 23.8473 6.6337 27.3584 13.9782C27.5333 14.0198 27.7011 14.0536 27.8698 14.0883C28.6905 8.34132 32.3031 6.2133 38.0851 6.89635Z" fill="#2196F3"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="46" height="55" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 9H9C7.89543 9 7 9.89543 7 11V17C7 18.1046 7.89543 19 9 19H19C20.1046 19 21 18.1046 21 17V11C21 9.89543 20.1046 9 19 9Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 16C15.1046 16 16 15.1046 16 14C16 12.8954 15.1046 12 14 12C12.8954 12 12 12.8954 12 14C12 15.1046 12.8954 16 14 16Z" fill="#90CAF9"/>
|
||||
<path d="M17 9V7C17 6.46957 16.7893 5.96086 16.4142 5.58579C16.0391 5.21071 15.5304 5 15 5H5C4.46957 5 3.96086 5.21071 3.58579 5.58579C3.21071 5.96086 3 6.46957 3 7V13C3 13.5304 3.21071 14.0391 3.58579 14.4142C3.96086 14.7893 4.46957 15 5 15H7" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 794 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="92" height="32" viewBox="0 0 92 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M33.085 26.4841V12.3839H37.9541C39.6408 12.3839 40.9202 12.7131 41.7922 13.3717C42.6642 14.0237 43.1002 14.9825 43.1002 16.2478C43.1002 16.9387 42.9251 17.5488 42.5751 18.0782C42.225 18.6011 41.7381 18.9853 41.1143 19.2306C41.8272 19.4114 42.3873 19.7761 42.7947 20.3249C43.2084 20.8737 43.4152 21.5452 43.4152 22.3392C43.4152 23.695 42.9888 24.7215 42.1359 25.4188C41.283 26.1161 40.0673 26.4712 38.4888 26.4841H33.085ZM35.9492 20.3443V24.1502H38.4028C39.0775 24.1502 39.6026 23.9888 39.9781 23.666C40.36 23.3367 40.551 22.8848 40.551 22.3102C40.551 21.0189 39.8922 20.3637 38.5747 20.3443H35.9492ZM35.9492 18.2912H38.0687C39.5135 18.2654 40.236 17.6811 40.236 16.5384C40.236 15.8992 40.0514 15.4408 39.6822 15.1632C39.3194 14.8792 38.7434 14.7371 37.9541 14.7371H35.9492V18.2912ZM53.9365 20.3733H48.4371V24.1502H54.8913V26.4841H45.573V12.3839H54.8723V14.7371H48.4371V18.0976H53.9365V20.3733ZM61.7175 21.3224H59.436V26.4841H56.5717V12.3839H61.7365C63.379 12.3839 64.6455 12.7551 65.5365 13.4975C66.4276 14.24 66.8734 15.2891 66.8734 16.6449C66.8734 17.6069 66.6661 18.4107 66.2527 19.0563C65.8455 19.6954 65.2248 20.2055 64.3907 20.5864L67.3985 26.3485V26.4841H64.3242L61.7175 21.3224ZM59.436 18.9691H61.746C62.4656 18.9691 63.0226 18.7851 63.417 18.4172C63.8114 18.0427 64.0092 17.5294 64.0092 16.8773C64.0092 16.2124 63.8214 15.6894 63.4455 15.3085C63.0768 14.9276 62.5069 14.7371 61.7365 14.7371H59.436V18.9691ZM74.2058 21.3224H71.9237V26.4841H69.0594V12.3839H74.2248C75.8667 12.3839 77.1337 12.7551 78.0248 13.4975C78.9159 14.24 79.3611 15.2891 79.3611 16.6449C79.3611 17.6069 79.1544 18.4107 78.7404 19.0563C78.3332 19.6954 77.7125 20.2055 76.879 20.5864L79.8863 26.3485V26.4841H76.8119L74.2058 21.3224ZM71.9237 18.9691H74.2343C74.9533 18.9691 75.5103 18.7851 75.9052 18.4172C76.2997 18.0427 76.4969 17.5294 76.4969 16.8773C76.4969 16.2124 76.3092 15.6894 75.9337 15.3085C75.5645 14.9276 74.9946 14.7371 74.2248 14.7371H71.9237V18.9691ZM85.8823 18.7367L88.7751 12.3839H91.9064L87.3427 21.3708V26.4841H84.4309V21.3708L79.8673 12.3839H83.008L85.8823 18.7367Z" fill="#212121"/>
|
||||
<path d="M10.987 31.5841C4.92849 31.5841 0 26.626 0 20.5323C0 14.4385 4.92899 9.48041 10.987 9.48041C17.045 9.48041 21.974 14.4385 21.974 20.5323C21.974 26.626 17.0459 31.5841 10.987 31.5841ZM10.987 10.536C5.50765 10.536 1.04938 15.0196 1.04938 20.5318C1.04938 26.044 5.50765 30.5275 10.987 30.5275C16.4663 30.5275 20.9251 26.0429 20.9251 20.5308C20.9251 15.0186 16.4673 10.536 10.987 10.536Z" fill="#2196F3"/>
|
||||
<path d="M18.96 21.0225C18.6182 19.7483 15.4851 19.6108 13.6203 20.0779C12.6437 20.3235 11.6456 20.6428 10.6162 20.8265C11.3697 21.4989 12.1788 22.135 13.34 22.2932C16.2211 22.6842 18.0112 21.775 18.96 21.0225Z" fill="#2196F3"/>
|
||||
<path d="M13.34 22.2932C12.1764 22.135 11.3697 21.4989 10.6162 20.8265C9.45013 19.7857 8.41298 18.6579 6.37723 19.0823C3.14069 19.7572 2.71488 23.6081 5.21404 26.0828C6.28706 27.2131 7.66455 28.0041 9.17779 28.3586C10.691 28.7132 12.2742 28.616 13.7333 28.079C15.1924 27.5419 16.4641 26.5883 17.3925 25.3352C18.3209 24.0819 18.8656 22.5835 18.96 21.0235C18.0112 21.775 16.221 22.6842 13.34 22.2932Z" fill="#673AB7"/>
|
||||
<path d="M15.034 13.9586C14.6301 14.8295 18.2304 15.7957 18.6611 18.6879C18.8687 15.8409 15.5335 12.882 15.034 13.9586Z" fill="#2196F3"/>
|
||||
<path d="M7.46619 17.5935C8.11524 17.3231 8.42345 16.5746 8.15463 15.9217C7.8858 15.2688 7.14167 14.9587 6.49262 15.2292C5.84357 15.4996 5.53536 16.2481 5.80418 16.9011C6.07306 17.5539 6.81714 17.8639 7.46619 17.5935Z" fill="#673AB7"/>
|
||||
<path d="M10.3549 14.08C10.6585 13.7746 10.6585 13.2795 10.3549 12.9741C10.0513 12.6687 9.55909 12.6687 9.25551 12.9741C8.95194 13.2795 8.95194 13.7746 9.25551 14.08C9.55909 14.3854 10.0513 14.3854 10.3549 14.08Z" fill="#2196F3"/>
|
||||
<path d="M13.1014 9.05206C14.2245 5.7149 13.4696 3.04871 11.1614 1.78241C9.58359 2.10513 8.647 2.87335 8.12549 3.93383C11.2204 3.68185 13.1844 5.63041 13.1014 9.05206Z" fill="#2196F3"/>
|
||||
<path d="M25.6983 6.13641C20.1389 4.1294 16.6304 4.81756 16.0786 9.39055C19.2648 12.6973 22.474 11.1146 25.6983 6.13641Z" fill="#2196F3"/>
|
||||
<path d="M21.2765 4.32541C21.5343 3.21728 21.6681 1.90776 21.6881 0.41748C15.9226 1.70883 13.3224 4.17658 15.2839 8.33846C15.3816 8.36203 15.4754 8.38119 15.5696 8.40085C16.0281 5.14422 18.0463 3.93835 21.2765 4.32541Z" fill="#2196F3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg width="92" height="32" viewBox="0 0 92 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M33.085 26.4841V12.3839H37.9541C39.6408 12.3839 40.9202 12.7131 41.7922 13.3717C42.6642 14.0237 43.1002 14.9825 43.1002 16.2478C43.1002 16.9387 42.9251 17.5488 42.5751 18.0782C42.225 18.6011 41.7381 18.9853 41.1143 19.2306C41.8272 19.4114 42.3873 19.7761 42.7947 20.3249C43.2084 20.8737 43.4152 21.5452 43.4152 22.3392C43.4152 23.695 42.9888 24.7215 42.1359 25.4188C41.283 26.1161 40.0673 26.4712 38.4888 26.4841H33.085ZM35.9492 20.3443V24.1502H38.4028C39.0775 24.1502 39.6026 23.9888 39.9781 23.666C40.36 23.3367 40.551 22.8848 40.551 22.3102C40.551 21.0189 39.8922 20.3637 38.5747 20.3443H35.9492ZM35.9492 18.2912H38.0687C39.5135 18.2654 40.236 17.6811 40.236 16.5384C40.236 15.8992 40.0514 15.4408 39.6822 15.1632C39.3194 14.8792 38.7434 14.7371 37.9541 14.7371H35.9492V18.2912ZM53.9365 20.3733H48.4371V24.1502H54.8913V26.4841H45.573V12.3839H54.8723V14.7371H48.4371V18.0976H53.9365V20.3733ZM61.7175 21.3224H59.436V26.4841H56.5717V12.3839H61.7365C63.379 12.3839 64.6455 12.7551 65.5365 13.4975C66.4276 14.24 66.8734 15.2891 66.8734 16.6449C66.8734 17.6069 66.6661 18.4107 66.2527 19.0563C65.8455 19.6954 65.2248 20.2055 64.3907 20.5864L67.3985 26.3485V26.4841H64.3242L61.7175 21.3224ZM59.436 18.9691H61.746C62.4656 18.9691 63.0226 18.7851 63.417 18.4172C63.8114 18.0427 64.0092 17.5294 64.0092 16.8773C64.0092 16.2124 63.8214 15.6894 63.4455 15.3085C63.0768 14.9276 62.5069 14.7371 61.7365 14.7371H59.436V18.9691ZM74.2058 21.3224H71.9237V26.4841H69.0594V12.3839H74.2248C75.8667 12.3839 77.1337 12.7551 78.0248 13.4975C78.9159 14.24 79.3611 15.2891 79.3611 16.6449C79.3611 17.6069 79.1544 18.4107 78.7404 19.0563C78.3332 19.6954 77.7125 20.2055 76.879 20.5864L79.8863 26.3485V26.4841H76.8119L74.2058 21.3224ZM71.9237 18.9691H74.2343C74.9533 18.9691 75.5103 18.7851 75.9052 18.4172C76.2997 18.0427 76.4969 17.5294 76.4969 16.8773C76.4969 16.2124 76.3092 15.6894 75.9337 15.3085C75.5645 14.9276 74.9946 14.7371 74.2248 14.7371H71.9237V18.9691ZM85.8823 18.7367L88.7751 12.3839H91.9064L87.3427 21.3708V26.4841H84.4309V21.3708L79.8673 12.3839H83.008L85.8823 18.7367Z" fill="#ffffff"/>
|
||||
<path d="M10.987 31.5841C4.92849 31.5841 0 26.626 0 20.5323C0 14.4385 4.92899 9.48041 10.987 9.48041C17.045 9.48041 21.974 14.4385 21.974 20.5323C21.974 26.626 17.0459 31.5841 10.987 31.5841ZM10.987 10.536C5.50765 10.536 1.04938 15.0196 1.04938 20.5318C1.04938 26.044 5.50765 30.5275 10.987 30.5275C16.4663 30.5275 20.9251 26.0429 20.9251 20.5308C20.9251 15.0186 16.4673 10.536 10.987 10.536Z" fill="#2196F3"/>
|
||||
<path d="M18.96 21.0225C18.6182 19.7483 15.4851 19.6108 13.6203 20.0779C12.6437 20.3235 11.6456 20.6428 10.6162 20.8265C11.3697 21.4989 12.1788 22.135 13.34 22.2932C16.2211 22.6842 18.0112 21.775 18.96 21.0225Z" fill="#2196F3"/>
|
||||
<path d="M13.34 22.2932C12.1764 22.135 11.3697 21.4989 10.6162 20.8265C9.45013 19.7857 8.41298 18.6579 6.37723 19.0823C3.14069 19.7572 2.71488 23.6081 5.21404 26.0828C6.28706 27.2131 7.66455 28.0041 9.17779 28.3586C10.691 28.7132 12.2742 28.616 13.7333 28.079C15.1924 27.5419 16.4641 26.5883 17.3925 25.3352C18.3209 24.0819 18.8656 22.5835 18.96 21.0235C18.0112 21.775 16.221 22.6842 13.34 22.2932Z" fill="#673AB7"/>
|
||||
<path d="M15.034 13.9586C14.6301 14.8295 18.2304 15.7957 18.6611 18.6879C18.8687 15.8409 15.5335 12.882 15.034 13.9586Z" fill="#2196F3"/>
|
||||
<path d="M7.46619 17.5935C8.11524 17.3231 8.42345 16.5746 8.15463 15.9217C7.8858 15.2688 7.14167 14.9587 6.49262 15.2292C5.84357 15.4996 5.53536 16.2481 5.80418 16.9011C6.07306 17.5539 6.81714 17.8639 7.46619 17.5935Z" fill="#673AB7"/>
|
||||
<path d="M10.3549 14.08C10.6585 13.7746 10.6585 13.2795 10.3549 12.9741C10.0513 12.6687 9.55909 12.6687 9.25551 12.9741C8.95194 13.2795 8.95194 13.7746 9.25551 14.08C9.55909 14.3854 10.0513 14.3854 10.3549 14.08Z" fill="#2196F3"/>
|
||||
<path d="M13.1014 9.05206C14.2245 5.7149 13.4696 3.04871 11.1614 1.78241C9.58359 2.10513 8.647 2.87335 8.12549 3.93383C11.2204 3.68185 13.1844 5.63041 13.1014 9.05206Z" fill="#2196F3"/>
|
||||
<path d="M25.6983 6.13641C20.1389 4.1294 16.6304 4.81756 16.0786 9.39055C19.2648 12.6973 22.474 11.1146 25.6983 6.13641Z" fill="#2196F3"/>
|
||||
<path d="M21.2765 4.32541C21.5343 3.21728 21.6681 1.90776 21.6881 0.41748C15.9226 1.70883 13.3224 4.17658 15.2839 8.33846C15.3816 8.36203 15.4754 8.38119 15.5696 8.40085C16.0281 5.14422 18.0463 3.93835 21.2765 4.32541Z" fill="#2196F3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,34 @@
|
||||
<svg width="676" height="391" viewBox="0 0 676 391" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.09">
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 4.49127 197.53)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 342.315 387.578)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 28.0057 211.105)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 365.829 374.002)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 51.52 224.68)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 389.344 360.428)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 75.0345 238.255)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 412.858 346.852)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 98.5488 251.83)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 436.372 333.277)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 122.063 265.405)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 459.887 319.703)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 145.578 278.979)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 483.401 306.127)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 169.092 292.556)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 506.916 292.551)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 192.597 306.127)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 530.43 278.977)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 216.111 319.703)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 553.944 265.402)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 239.626 333.277)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 577.459 251.827)" stroke="black"/>
|
||||
<path d="M263.231 346.905L601.064 151.871" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 600.973 238.252)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 286.654 360.428)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 624.487 224.677)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 310.169 374.002)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 648.002 211.102)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(0.866041 -0.499972 -0.866041 -0.499972 333.683 387.578)" stroke="black"/>
|
||||
<line y1="-0.5" x2="390.089" y2="-0.5" transform="matrix(-0.866041 -0.499972 -0.866041 0.499972 671.516 197.527)" stroke="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,43 @@
|
||||
<svg width="676" height="395" viewBox="0 0 676 395" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="26.998" height="26.8293" transform="matrix(0.866041 -0.499972 0.866041 0.499972 361.873 290.126)" fill="#E3F2FD"/>
|
||||
<rect width="24.2748" height="24.1231" transform="matrix(0.866041 -0.499972 0.866041 0.499972 364.249 291.115)" fill="#90CAF9"/>
|
||||
<rect width="26.998" height="26.8293" transform="matrix(0.866041 -0.499972 0.866041 0.499972 291.67 86.4912)" fill="#E3F2FD"/>
|
||||
<rect width="24.2748" height="24.1231" transform="matrix(0.866041 -0.499972 0.866041 0.499972 294.046 87.48)" fill="#90CAF9"/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M370.694 211.828L365.394 208.768V215.835L365.404 215.829C365.459 216.281 365.785 216.724 366.383 217.069L417.03 246.308C418.347 247.068 420.481 247.068 421.798 246.308L468.671 219.248C469.374 218.842 469.702 218.301 469.654 217.77V210.861L464.282 213.962L418.024 187.257C416.708 186.497 414.573 186.497 413.257 187.257L370.694 211.828Z" fill="url(#paint0_linear)"/>
|
||||
</g>
|
||||
<rect width="59.6284" height="63.9858" rx="5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 364 208.812)" fill="#90CAF9"/>
|
||||
<rect width="59.6284" height="63.9858" rx="5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 364 208.812)" fill="url(#paint1_linear)"/>
|
||||
<rect width="56.6816" height="60.8238" rx="5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 366.645 208.761)" fill="url(#paint2_linear)"/>
|
||||
<path d="M421.238 206.161C421.238 206.434 421.62 206.655 422.092 206.655L432.159 206.656C435.164 206.656 437.6 208.063 437.601 209.798C437.602 211.533 435.166 212.939 432.162 212.938L422.09 212.937C421.62 212.937 421.24 213.157 421.24 213.428L421.241 215.814C421.241 216.087 421.624 216.308 422.096 216.308L432.689 216.309C438.917 216.31 443.967 213.395 443.965 209.799C443.964 206.202 438.914 203.286 432.684 203.286L422.086 203.284C421.617 203.284 421.236 203.504 421.237 203.775L421.238 206.161Z" fill="#1E88E5"/>
|
||||
<path d="M413.422 213.43C413.422 213.157 413.039 212.936 412.567 212.936L402.896 212.935C399.891 212.935 397.455 211.528 397.454 209.793C397.453 208.059 399.889 206.652 402.894 206.653L412.57 206.654C413.039 206.654 413.419 206.435 413.419 206.164L413.418 203.777C413.418 203.504 413.035 203.283 412.563 203.283L402.366 203.282C396.138 203.281 391.089 206.197 391.09 209.793C391.091 213.389 396.141 216.305 402.371 216.306L412.573 216.307C413.042 216.307 413.423 216.088 413.423 215.817L413.422 213.43Z" fill="#1E88E5"/>
|
||||
<path d="M407.999 198.145L411.211 201.235C411.266 201.288 411.332 201.336 411.405 201.379C411.813 201.614 412.461 201.669 412.979 201.49C413.59 201.278 413.787 200.821 413.421 200.469L410.209 197.379C409.843 197.027 409.051 196.913 408.441 197.124C407.831 197.335 407.633 197.793 407.999 198.145Z" fill="#1E88E5"/>
|
||||
<path d="M416.235 200.853C416.235 201.058 416.38 201.244 416.613 201.379C416.846 201.513 417.168 201.597 417.524 201.597C418.236 201.596 418.813 201.263 418.813 200.852L418.812 197.021C418.811 196.61 418.234 196.277 417.522 196.277C416.811 196.278 416.234 196.611 416.234 197.022L416.235 200.853Z" fill="#1E88E5"/>
|
||||
<path d="M421.627 200.47C421.317 200.769 421.412 201.143 421.82 201.379C421.893 201.421 421.977 201.459 422.069 201.491C422.68 201.703 423.472 201.588 423.838 201.236L427.047 198.147C427.413 197.794 427.215 197.337 426.605 197.126C425.994 196.915 425.203 197.029 424.836 197.381L421.627 200.47Z" fill="#1E88E5"/>
|
||||
<path d="M427.056 221.447L423.844 218.357C423.478 218.005 422.686 217.891 422.076 218.102C421.466 218.314 421.268 218.771 421.634 219.123L424.846 222.213C424.901 222.266 424.967 222.314 425.04 222.357C425.448 222.592 426.097 222.647 426.614 222.468C427.225 222.257 427.423 221.799 427.056 221.447Z" fill="#1E88E5"/>
|
||||
<path d="M418.82 218.739C418.82 218.328 418.243 217.995 417.531 217.995C416.819 217.995 416.242 218.329 416.242 218.74L416.243 222.57C416.244 222.776 416.388 222.962 416.621 223.096C416.854 223.231 417.177 223.314 417.533 223.314C418.245 223.314 418.822 222.981 418.821 222.57L418.82 218.739Z" fill="#1E88E5"/>
|
||||
<path d="M413.428 219.122C413.794 218.77 413.596 218.312 412.986 218.101C412.375 217.89 411.584 218.004 411.217 218.356L408.008 221.445C407.698 221.744 407.793 222.118 408.201 222.354C408.274 222.396 408.358 222.434 408.45 222.466C409.061 222.678 409.853 222.563 410.219 222.211L413.428 219.122Z" fill="#1E88E5"/>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="301.394" y="186.687" width="232.264" height="208.191" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="84"/>
|
||||
<feGaussianBlur stdDeviation="32"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.129412 0 0 0 0 0.588235 0 0 0 0 0.952941 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear" x1="417.526" y1="205.789" x2="365.394" y2="216.782" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2196F3"/>
|
||||
<stop offset="1" stop-color="#B1DCFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="0.503035" y1="2.68177" x2="20.3032" y2="42.2842" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAFAFA" stop-opacity="0.74"/>
|
||||
<stop offset="1" stop-color="#91CBFA"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="-18.5494" y1="-44.8799" x2="14.7845" y2="40.5766" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAFAFA" stop-opacity="0.74"/>
|
||||
<stop offset="1" stop-color="#91CBFA"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,42 @@
|
||||
<svg width="710" height="391" viewBox="0 0 710 391" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="26.9258" height="26.7576" transform="matrix(0.866041 -0.499972 0.866041 0.499972 161.088 154.333)" fill="#EDE7F6"/>
|
||||
<rect width="24.9267" height="24.7709" transform="matrix(0.866041 -0.499972 0.866041 0.499972 162.809 155.327)" fill="#B39DDB"/>
|
||||
<rect width="26.9258" height="26.7576" transform="matrix(0.866041 -0.499972 0.866041 0.499972 536.744 181.299)" fill="#EDE7F6"/>
|
||||
<rect width="24.9267" height="24.7709" transform="matrix(0.866041 -0.499972 0.866041 0.499972 538.465 182.292)" fill="#B39DDB"/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M67.7237 137.573V134.673H64.009V140.824L64.0177 140.829C64.0367 141.477 64.4743 142.121 65.3305 142.615L103.641 164.733C105.393 165.744 108.232 165.744 109.983 164.733L204.044 110.431C204.879 109.949 205.316 109.324 205.355 108.693L205.355 108.692V108.68C205.358 108.628 205.358 108.576 205.355 108.523L205.362 102.335L200.065 104.472L165.733 84.6523C163.982 83.6413 161.142 83.6413 159.391 84.6523L67.7237 137.573Z" fill="url(#paint0_linear)"/>
|
||||
</g>
|
||||
<rect width="115.933" height="51.5596" rx="5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 62.1588 134.683)" fill="#673AB7"/>
|
||||
<rect width="115.933" height="51.5596" rx="5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 62.1588 134.683)" fill="url(#paint1_linear)" fill-opacity="0.3"/>
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="64" y="78" width="141" height="81">
|
||||
<rect width="115.933" height="51.5596" rx="5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 62.1588 134.683)" fill="#673AB7"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
</g>
|
||||
<mask id="mask1" mask-type="alpha" maskUnits="userSpaceOnUse" x="64" y="78" width="141" height="81">
|
||||
<rect width="115.933" height="51.5596" rx="5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 62.1588 134.683)" fill="#673AB7"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1)">
|
||||
<rect width="64.3732" height="64.3732" rx="5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 111.303 81.6006)" fill="#5E35B1"/>
|
||||
<rect opacity="0.7" x="0.866041" width="63.3732" height="63.3732" rx="4.5" transform="matrix(0.866041 -0.499972 0.866041 0.499972 79.1848 87.8305)" stroke="#5E35B1"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="0.0090332" y="83.894" width="269.353" height="229.597" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="84"/>
|
||||
<feGaussianBlur stdDeviation="32"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.403922 0 0 0 0 0.227451 0 0 0 0 0.717647 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear" x1="200.346" y1="102.359" x2="71.0293" y2="158.071" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A491C8"/>
|
||||
<stop offset="1" stop-color="#D7C5F8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="8.1531" y1="-0.145767" x2="57.1962" y2="72.3003" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,27 @@
|
||||
<svg width="676" height="391" viewBox="0 0 676 391" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M267.744 237.142L279.699 230.24L300.636 242.329L288.682 249.231L313.566 263.598L286.344 279.314L261.46 264.947L215.984 291.203L197.779 282.558L169.334 211.758L169.092 211.618L196.313 195.902L267.744 237.142ZM219.359 265.077L240.523 252.859L204.445 232.029L205.487 234.589L219.359 265.077Z" fill="#FFAB91"/>
|
||||
<path d="M469.959 120.206L481.913 113.304L502.851 125.392L490.897 132.294L515.78 146.661L488.559 162.377L463.675 148.011L418.199 174.266L399.994 165.621L371.548 94.8211L371.307 94.6816L398.528 78.9654L469.959 120.206ZM421.574 148.141L442.737 135.922L406.66 115.093L407.701 117.653L421.574 148.141Z" fill="#FFAB91"/>
|
||||
<path d="M204.523 235.027V232.237L219.401 265.014L240.555 252.926V255.018L218.936 267.339L204.523 235.027Z" fill="#D84315"/>
|
||||
<path d="M406.738 118.09V115.301L421.616 148.078L442.77 135.99V138.082L421.151 150.402L406.738 118.09Z" fill="#D84315"/>
|
||||
<rect width="109.114" height="136.405" transform="matrix(0.866025 -0.5 0.866025 0.5 220.507 181.925)" fill="url(#paint0_linear)"/>
|
||||
<rect width="40.2357" height="70.0545" transform="matrix(0.866025 -0.5 0.866025 0.5 280.437 201.886)" fill="url(#paint1_linear)"/>
|
||||
<rect x="25.1147" width="80.1144" height="107.405" transform="matrix(0.866025 -0.5 0.866025 0.5 223.872 194.482)" stroke="#1565C0" stroke-width="29"/>
|
||||
<rect x="25.1147" width="80.1144" height="107.405" transform="matrix(0.866025 -0.5 0.866025 0.5 223.872 194.482)" stroke="url(#paint2_linear)" stroke-width="29"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M279.517 230.177L267.662 237.15L196.064 195.772L168.866 211.58L169.331 212.097L170.096 214.002L196.436 198.795L267.866 240.035L279.821 233.133L298.211 243.751L300.787 242.265L279.517 230.177ZM291.278 250.695L288.804 252.124L311.1 264.996L313.805 263.418L291.278 250.695Z" fill="#D84315"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M481.732 113.24L469.877 120.214L398.279 78.8359L371.081 94.6433L371.546 95.1603L372.311 97.0652L398.651 81.8581L470.081 123.099L482.036 116.196L500.426 126.814L503.002 125.328L481.732 113.24ZM493.493 133.759L491.019 135.187L513.315 148.06L516.02 146.482L493.493 133.759Z" fill="#D84315"/>
|
||||
<path d="M288.674 252.229V249.207L291.929 251.067L288.674 252.229Z" fill="#D84315"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="77.7511" y1="139.902" x2="-10.8629" y2="8.75671" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3076C8"/>
|
||||
<stop offset="0.992076" stop-color="#91CBFA"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="25.8162" y1="51.0447" x2="68.7073" y2="-5.41524" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2E75C7"/>
|
||||
<stop offset="1" stop-color="#4283CC"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="-16.1224" y1="-47.972" x2="123.494" y2="290.853" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 250 KiB |
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<h3 style="margin-bottom: 8px;" v-if="iterable && metadata[metadataKey]?.type === 'object'">
|
||||
{{metadata[metadataKey]?.description }}
|
||||
</h3>
|
||||
<v-card-text>
|
||||
<div v-for="(index, key) in iterable" :key="key" style="margin-bottom: 0.5px;" v-if="metadata[metadataKey]?.type === 'object' || metadata[metadataKey]?.config_template">
|
||||
<v-alert v-if="metadata[metadataKey].items[key]?.obvious_hint && metadata[metadataKey].items[key]?.hint && metadata[metadataKey].items[key]?.type !== 'object'" style="margin-bottom: 16px"
|
||||
:text="metadata[metadataKey].items[key]?.hint" :title="'💡 关于' + metadata[metadataKey].items[key]?.description"
|
||||
type="info" variant="tonal">
|
||||
</v-alert>
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: center; gap: 16px">
|
||||
<div style="width: 100%;">
|
||||
<v-select v-if="metadata[metadataKey].items[key]?.options && !metadata[metadataKey].items[key]?.invisible" v-model="iterable[key]"
|
||||
variant="outlined" :items="metadata[metadataKey].items[key]?.options"
|
||||
:label="metadata[metadataKey].items[key]?.description + '(' + key + ')'" dense :disabled="metadata[metadataKey].items[key]?.readonly"></v-select>
|
||||
<v-text-field v-else-if="metadata[metadataKey].items[key]?.type === 'string' && !metadata[metadataKey].items[key]?.invisible"
|
||||
v-model="iterable[key]" :label="metadata[metadataKey].items[key]?.description + '(' + key + ')'"
|
||||
variant="outlined" dense ></v-text-field>
|
||||
<v-text-field
|
||||
v-else-if="(metadata[metadataKey].items[key]?.type === 'int' || metadata[metadataKey].items[key]?.type === 'float') && !metadata[metadataKey].items[key]?.invisible"
|
||||
v-model="iterable[key]" :label="metadata[metadataKey].items[key]?.description + '(' + key + ')'"
|
||||
variant="outlined" dense></v-text-field>
|
||||
<v-textarea v-else-if="metadata[metadataKey].items[key]?.type === 'text' && !metadata[metadataKey].items[key]?.invisible" v-model="iterable[key]"
|
||||
:label="metadata[metadataKey].items[key]?.description + '(' + key + ')'" variant="outlined"
|
||||
dense></v-textarea>
|
||||
<v-switch v-else-if="metadata[metadataKey].items[key]?.type === 'bool' && !metadata[metadataKey].items[key]?.invisible" v-model="iterable[key]"
|
||||
:label="metadata[metadataKey].items[key]?.description + '(' + key + ')'" color="primary"
|
||||
inset></v-switch>
|
||||
<v-combobox variant="outlined" v-else-if="metadata[metadataKey].items[key]?.type === 'list' && !metadata[metadataKey].items[key]?.invisible"
|
||||
v-model="iterable[key]" chips clearable
|
||||
:label="metadata[metadataKey].items[key]?.description + '(' + key + ')'" multiple
|
||||
prepend-icon="mdi-tag-multiple-outline">
|
||||
<template v-slot:selection="{ attrs, item, select, selected }">
|
||||
<v-chip v-bind="attrs" :model-value="selected" closable @click="select"
|
||||
@click:close="remove(item)">
|
||||
<strong>{{ item }}</strong>
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-combobox>
|
||||
<div v-else-if="metadata[metadataKey].items[key]?.type === 'object' && !metadata[metadataKey].items[key]?.invisible"
|
||||
style="border: 1px solid #e0e0e0; padding: 8px; margin-bottom: 16px; border-radius: 10px;">
|
||||
<AstrBotConfig :metadata="metadata[metadataKey].items" :iterable="iterable[key]"
|
||||
:metadataKey=key>
|
||||
</AstrBotConfig>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!metadata[metadataKey].items[key]?.obvious_hint && metadata[metadataKey].items[key]?.hint && metadata[metadataKey].items[key]?.type !== 'object' && !metadata[metadataKey].items[key]?.invisible">
|
||||
<v-btn icon size="x-small" style="margin-bottom: 22px;">
|
||||
<v-icon size="x-small">mdi-help</v-icon>
|
||||
<v-tooltip activator="parent" location="start">{{ metadata[metadataKey].items[key]?.hint
|
||||
}}</v-tooltip>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-alert v-if="metadata[metadataKey]?.obvious_hint && metadata[metadataKey]?.hint && metadata[metadataKey]?.type !== 'object'" style="margin-bottom: 16px"
|
||||
:text="metadata[metadataKey]?.hint" :title="'💡 关于' + metadata[metadataKey]?.description"
|
||||
type="info" variant="tonal">
|
||||
</v-alert>
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: center; gap: 16px">
|
||||
<div style="width: 100%;">
|
||||
<v-select v-if="metadata[metadataKey]?.options && !metadata[metadataKey]?.invisible" v-model="iterable[metadataKey]"
|
||||
variant="outlined" :items="metadata[metadataKey]?.options"
|
||||
:label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'" dense :disabled="metadata[metadataKey]?.readonly"></v-select>
|
||||
<v-text-field v-else-if="metadata[metadataKey]?.type === 'string' && !metadata[metadataKey]?.invisible"
|
||||
v-model="iterable[metadataKey]" :label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'"
|
||||
variant="outlined" dense ></v-text-field>
|
||||
<v-text-field
|
||||
v-else-if="(metadata[metadataKey]?.type === 'int' || metadata[metadataKey]?.type === 'float') && !metadata[metadataKey]?.invisible"
|
||||
v-model="iterable[metadataKey]" :label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'"
|
||||
variant="outlined" dense></v-text-field>
|
||||
<v-textarea v-else-if="metadata[metadataKey]?.type === 'text' && !metadata[metadataKey]?.invisible" v-model="iterable[metadataKey]"
|
||||
:label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'" variant="outlined"
|
||||
dense></v-textarea>
|
||||
<v-switch v-else-if="metadata[metadataKey]?.type === 'bool' && !metadata[metadataKey]?.invisible" v-model="iterable[metadataKey]"
|
||||
:label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'" color="primary"
|
||||
inset></v-switch>
|
||||
<v-combobox variant="outlined" v-else-if="metadata[metadataKey]?.type === 'list' && !metadata[metadataKey]?.invisible"
|
||||
v-model="iterable[metadataKey]" chips clearable
|
||||
:label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'" multiple
|
||||
prepend-icon="mdi-tag-multiple-outline">
|
||||
<template v-slot:selection="{ attrs, item, select, selected }">
|
||||
<v-chip v-bind="attrs" :model-value="selected" closable @click="select"
|
||||
@click:close="remove(item)">
|
||||
<strong>{{ item }}</strong>
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-combobox>
|
||||
<div v-else-if="metadata[metadataKey]?.type === 'object' && !metadata[metadataKey]?.invisible"
|
||||
style="border: 1px solid #e0e0e0; padding: 8px; margin-bottom: 16px; border-radius: 10px;">
|
||||
<AstrBotConfig :metadata="metadata[metadataKey].items" :iterable="iterable[metadataKey]"
|
||||
:metadataKey=key>
|
||||
</AstrBotConfig>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!metadata[metadataKey]?.obvious_hint && metadata[metadataKey]?.hint && metadata[metadataKey]?.type !== 'object' && !metadata[metadataKey]?.invisible">
|
||||
<v-btn icon size="x-small" style="margin-bottom: 22px;">
|
||||
<v-icon size="x-small">mdi-help</v-icon>
|
||||
<v-tooltip activator="parent" location="start">{{ metadata[metadataKey]?.hint
|
||||
}}</v-tooltip>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { readonly } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
metadata: Object,
|
||||
iterable: Object,
|
||||
metadataKey: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import UiParentCard from '@/components/shared/UiParentCard.vue';
|
||||
|
||||
const props = defineProps({
|
||||
config: Array
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a v-show="config.length === 0">该插件没有配置</a>
|
||||
<UiParentCard v-for="group in config" :key="group.name" :title="group.name" style="margin-bottom: 16px;">
|
||||
<template v-for="item in group.body">
|
||||
<template v-if="item.config_type === 'item'">
|
||||
<template v-if="item.val_type === 'bool'">
|
||||
<v-switch v-model="item.value" :label="item.name" :hint="item.description" color="primary" inset></v-switch>
|
||||
</template>
|
||||
<template v-else-if="item.val_type === 'str'">
|
||||
<v-text-field v-model="item.value" :label="item.name" :hint="item.description" style="margin-bottom: 8px;"
|
||||
variant="outlined"></v-text-field>
|
||||
</template>
|
||||
<template v-else-if="item.val_type === 'int'">
|
||||
<v-text-field v-model="item.value" :label="item.name" :hint="item.description" style="margin-bottom: 8px;"
|
||||
variant="outlined"></v-text-field>
|
||||
</template>
|
||||
<template v-else-if="item.val_type === 'list'">
|
||||
<span>{{ item.name }}</span>
|
||||
<v-combobox v-model="item.value" chips clearable label="请添加" multiple prepend-icon="mdi-tag-multiple-outline">
|
||||
<template v-slot:selection="{ attrs, item, select, selected }">
|
||||
<v-chip v-bind="attrs" :model-value="selected" closable @click="select" @click:close="remove(item)">
|
||||
<strong>{{ item }}</strong>
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-combobox>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="item.config_type === 'divider'">
|
||||
<v-divider style="margin-top: 8px; margin-bottom: 8px;"></v-divider>
|
||||
</template>
|
||||
</template>
|
||||
</UiParentCard>
|
||||
</template>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup>
|
||||
import { useCommonStore } from '@/stores/common';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="term"
|
||||
style="background-color: #1e1e1e; padding: 16px; border-radius: 8px; overflow-y:scroll">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ConsoleDisplayer',
|
||||
data() {
|
||||
return {
|
||||
logColorAnsiMap: {
|
||||
'\u001b[1;34m': 'color: #0000FF; font-weight: bold;', // bold_blue
|
||||
'\u001b[1;36m': 'color: #00FFFF; font-weight: bold;', // bold_cyan
|
||||
'\u001b[1;33m': 'color: #FFFF00; font-weight: bold;', // bold_yellow
|
||||
'\u001b[31m': 'color: #FF0000;', // red
|
||||
'\u001b[1;31m': 'color: #FF0000; font-weight: bold;', // bold_red
|
||||
'\u001b[0m': 'color: inherit; font-weight: normal;', // reset
|
||||
'\u001b[32m': 'color: #00FF00;', // green
|
||||
'default': 'color: #FFFFFF;'
|
||||
},
|
||||
logCache: useCommonStore().getLogCache(),
|
||||
historyNum_: -1
|
||||
}
|
||||
},
|
||||
props: {
|
||||
historyNum: {
|
||||
type: String,
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
logCache: {
|
||||
handler(val) {
|
||||
this.printLog(val[this.logCache.length - 1])
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.historyNum_ = parseInt(this.historyNum)
|
||||
let i = 0
|
||||
for (let log of this.logCache) {
|
||||
if (this.historyNum_ != -1 && i >= this.logCache.length - this.historyNum_) {
|
||||
this.printLog(log)
|
||||
++i
|
||||
} else if (this.historyNum_ == -1) {
|
||||
this.printLog(log)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
printLog(log) {
|
||||
// append 一个 span 标签到 term,block 的方式
|
||||
let ele = document.getElementById('term')
|
||||
let span = document.createElement('pre')
|
||||
let style = this.logColorAnsiMap['default']
|
||||
for (let key in this.logColorAnsiMap) {
|
||||
if (log.startsWith(key)) {
|
||||
style = this.logColorAnsiMap[key]
|
||||
log = log.replace(key, '').replace('\u001b[0m', '')
|
||||
break
|
||||
}
|
||||
}
|
||||
span.style = style + 'display: block; font-size: 12px; font-family: Consolas, monospace;'
|
||||
span.classList.add('fade-in')
|
||||
span.innerText = log
|
||||
ele.appendChild(span)
|
||||
ele.scrollTop = ele.scrollHeight
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
link: String
|
||||
});
|
||||
|
||||
const open = (link: string | undefined) => {
|
||||
window.open(link, '_blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card variant="outlined" elevation="0" class="withbg">
|
||||
<v-card-item style="padding: 10px 14px">
|
||||
<div class="d-sm-flex align-center justify-space-between">
|
||||
<v-card-title style="font-size: 17px;">{{ props.title }}</v-card-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="plain" @click="open(props.link)">仓库</v-btn>
|
||||
</div>
|
||||
</v-card-item>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<slot />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
title: String
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card variant="outlined" elevation="0" class="withbg">
|
||||
<v-card-item>
|
||||
<div class="d-sm-flex align-center justify-space-between">
|
||||
<v-card-title>{{ props.title }}</v-card-title>
|
||||
<slot name="action"></slot>
|
||||
</div>
|
||||
</v-card-item>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<slot />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<v-dialog v-model="visible" persistent max-width="400">
|
||||
<v-card>
|
||||
<v-card-title>正在等待 AstrBot 重启...</v-card-title>
|
||||
<v-card-text>
|
||||
<v-progress-linear indeterminate color="primary"></v-progress-linear>
|
||||
<div style="margin-top: 16px;">
|
||||
<div class="py-12 text-center" v-if="newStartTime != -1">
|
||||
<v-icon class="mb-6" color="success" icon="mdi-check-circle-outline" size="128"></v-icon>
|
||||
<p>重启成功!</p>
|
||||
</div>
|
||||
<small v-if="startTime != -1" style="display: block;">当前实例标识:{{ startTime }}</small>
|
||||
<small v-if="newStartTime != -1" style="display: block;">检查到新实例:{{ newStartTime }},即将自动刷新页面</small>
|
||||
<small v-if="status" style="display: block;">{{ status }}</small>
|
||||
<small style="display: block;">尝试次数:{{ cnt }} / 60</small>
|
||||
</div>
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
import { useCommonStore } from '@/stores/common';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'WaitingForRestart',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
startTime: -1,
|
||||
newStartTime: -1,
|
||||
status: '',
|
||||
cnt: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async check() {
|
||||
this.newStartTime = -1
|
||||
this.startTime = useCommonStore().getStartTime()
|
||||
this.visible = true
|
||||
this.status = ""
|
||||
console.log('start wfr')
|
||||
setTimeout(() => {
|
||||
this.timeoutInternal()
|
||||
}, 1000)
|
||||
},
|
||||
timeoutInternal() {
|
||||
console.log('wfr: timeoutInternal', this.newStartTime, this.startTime)
|
||||
if (this.newStartTime === -1 && this.cnt < 60 && this.visible) {
|
||||
this.checkStartTime()
|
||||
this.cnt++
|
||||
setTimeout(() => {
|
||||
this.timeoutInternal()
|
||||
}, 1000)
|
||||
} else {
|
||||
if (this.cnt == 10) {
|
||||
this.status = '拉取状态达到最大次数,请手动检查。'
|
||||
}
|
||||
this.cnt = 0
|
||||
setTimeout(() => {
|
||||
this.visible = false
|
||||
}, 1000)
|
||||
}
|
||||
},
|
||||
async checkStartTime() {
|
||||
let res = await axios.get('/api/stat/start-time', { timeout: 3000 })
|
||||
let newStartTime = res.data.data.start_time
|
||||
console.log('wfr: checkStartTime', this.newStartTime, this.startTime)
|
||||
if (this.newStartTime !== this.startTime) {
|
||||
this.newStartTime = newStartTime
|
||||
console.log('wfr: restarted')
|
||||
setTimeout(() => {
|
||||
this.visible = false
|
||||
// reload
|
||||
window.location.reload()
|
||||
}, 2000)
|
||||
}
|
||||
return this.newStartTime
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,17 @@
|
||||
export type ConfigProps = {
|
||||
Sidebar_drawer: boolean;
|
||||
Customizer_drawer: boolean;
|
||||
mini_sidebar: boolean;
|
||||
fontTheme: string;
|
||||
inputBg: boolean;
|
||||
};
|
||||
|
||||
const config: ConfigProps = {
|
||||
Sidebar_drawer: true,
|
||||
Customizer_drawer: false,
|
||||
mini_sidebar: false,
|
||||
fontTheme: 'Roboto',
|
||||
inputBg: false
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<RouterView />
|
||||
</v-app>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
import VerticalSidebarVue from './vertical-sidebar/VerticalSidebar.vue';
|
||||
import VerticalHeaderVue from './vertical-header/VerticalHeader.vue';
|
||||
import { useCustomizerStore } from '../../stores/customizer';
|
||||
const customizer = useCustomizerStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-locale-provider>
|
||||
<v-app
|
||||
theme="PurpleTheme"
|
||||
:class="[customizer.fontTheme, customizer.mini_sidebar ? 'mini-sidebar' : '', customizer.inputBg ? 'inputWithbg' : '']"
|
||||
>
|
||||
<VerticalHeaderVue />
|
||||
<VerticalSidebarVue />
|
||||
<v-main>
|
||||
<v-container fluid class="page-wrapper" style="height: calc(100% - 8px)">
|
||||
<div style="height: 100%;">
|
||||
<RouterView />
|
||||
</div>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</v-locale-provider>
|
||||
</template>
|
||||
@@ -0,0 +1,223 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useCustomizerStore } from '../../../stores/customizer';
|
||||
import axios from 'axios';
|
||||
import { md5 } from 'js-md5';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { useCommonStore } from '@/stores/common';
|
||||
|
||||
const customizer = useCustomizerStore();
|
||||
let dialog = ref(false);
|
||||
let updateStatusDialog = ref(false);
|
||||
let password = ref('');
|
||||
let newPassword = ref('');
|
||||
let newUsername = ref('');
|
||||
let status = ref('');
|
||||
let updateStatus = ref('')
|
||||
let hasNewVersion = ref(false);
|
||||
let version = ref('');
|
||||
|
||||
const open = (link: string) => {
|
||||
window.open(link, '_blank');
|
||||
};
|
||||
|
||||
// 账户修改
|
||||
function accountEdit() {
|
||||
// md5加密
|
||||
// @ts-ignore
|
||||
if (password.value != '') {
|
||||
password.value = md5(password.value);
|
||||
}
|
||||
if (newPassword.value != '') {
|
||||
newPassword.value = md5(newPassword.value);
|
||||
}
|
||||
axios.post('/api/auth/account/edit', {
|
||||
password: password.value,
|
||||
new_password: newPassword.value,
|
||||
new_username: newUsername.value
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.status == 'error') {
|
||||
status.value = res.data.message;
|
||||
password.value = '';
|
||||
newPassword.value = '';
|
||||
return;
|
||||
}
|
||||
dialog.value = !dialog.value;
|
||||
status.value = res.data.message;
|
||||
setTimeout(() => {
|
||||
const authStore = useAuthStore();
|
||||
authStore.logout();
|
||||
}, 1000);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
status.value = err
|
||||
password.value = '';
|
||||
newPassword.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
function checkUpdate() {
|
||||
updateStatus.value = '正在检查更新...';
|
||||
axios.get('/api/update/check')
|
||||
.then((res) => {
|
||||
hasNewVersion.value = res.data.data.has_new_version;
|
||||
updateStatus.value = res.data.message;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response.status == 401) {
|
||||
console.log("401");
|
||||
const authStore = useAuthStore();
|
||||
authStore.logout();
|
||||
return;
|
||||
}
|
||||
console.log(err);
|
||||
updateStatus.value = err
|
||||
});
|
||||
}
|
||||
|
||||
function switchVersion(version: string) {
|
||||
updateStatus.value = '正在切换版本...';
|
||||
axios.post('/api/update/do', {
|
||||
version: version
|
||||
})
|
||||
.then((res) => {
|
||||
updateStatus.value = res.data.message;
|
||||
if (res.data.status == 'success') {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
updateStatus.value = err
|
||||
});
|
||||
}
|
||||
|
||||
checkUpdate();
|
||||
|
||||
const commonStore = useCommonStore();
|
||||
commonStore.createWebSocket();
|
||||
commonStore.getStartTime();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app-bar elevation="0" height="70">
|
||||
|
||||
<v-btn style="margin-left: 22px;" class="hidden-md-and-down text-secondary" color="lightsecondary" icon rounded="sm" variant="flat"
|
||||
@click.stop="customizer.SET_MINI_SIDEBAR(!customizer.mini_sidebar)" size="small">
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
<v-btn class="hidden-lg-and-up text-secondary ms-3" color="lightsecondary" icon rounded="sm" variant="flat"
|
||||
@click.stop="customizer.SET_SIDEBAR_DRAWER" size="small">
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<span style="margin-left: 16px; font-size: 24px; font-weight: 1000;">Astr<span
|
||||
style="font-weight: normal;">Bot</span></span>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<div class="mr-4">
|
||||
<small v-if="hasNewVersion">
|
||||
有新版本!
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
<v-dialog v-model="updateStatusDialog" width="700">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn @click="checkUpdate" class="text-primary mr-4" color="lightprimary" variant="flat" rounded="sm"
|
||||
v-bind="props">
|
||||
更新 🔄
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">更新项目</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<h3 class="mb-4">升级到最新版本</h3>
|
||||
<p>{{ updateStatus }}</p>
|
||||
<v-btn class="mt-4 mb-4" @click="switchVersion('latest')" color="primary" style="border-radius: 10px;"
|
||||
:disabled="!hasNewVersion">
|
||||
更新到最新版本
|
||||
</v-btn>
|
||||
<v-divider></v-divider>
|
||||
<div style="margin-top: 16px;">
|
||||
<h3 class="mb-4">切换到指定版本或指定提交</h3>
|
||||
<v-text-field label="输入版本号或 master 分支下的 commit hash。" v-model="version" required
|
||||
variant="outlined"></v-text-field>
|
||||
<div class="mb-4">
|
||||
<small>如 v3.3.16 (不带 SHA) 或 42e5ec5d80b93b6bfe8b566754d45ffac4c3fe0b</small>
|
||||
<br>
|
||||
<a href="https://github.com/Soulter/AstrBot/commits/master"><small>查看 master 分支提交记录(点击右边的 copy
|
||||
即可复制)</small></a>
|
||||
</div>
|
||||
<v-btn color="error" style="border-radius: 10px;" @click="switchVersion(version)">
|
||||
确定切换
|
||||
</v-btn>
|
||||
|
||||
</div>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="updateStatusDialog = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="dialog" persistent width="700">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn class="text-primary mr-4" color="lightprimary" variant="flat" rounded="sm" v-bind="props">
|
||||
账户 📰
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">账户</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field label="原密码*" type="password" v-model="password" required
|
||||
variant="outlined"></v-text-field>
|
||||
|
||||
<v-text-field label="新用户名" v-model="newUsername" required
|
||||
variant="outlined"></v-text-field>
|
||||
|
||||
<v-text-field label="新密码" type="password" v-model="newPassword" required
|
||||
variant="outlined"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<small>默认用户名和密码是 astrbot。</small>
|
||||
<br>
|
||||
<small>{{ status }}</small>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="dialog = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="accountEdit">
|
||||
提交
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
|
||||
<v-btn class="text-primary mr-4" @click="open('https://github.com/Soulter/AstrBot')" color="lightprimary"
|
||||
variant="flat" rounded="sm">
|
||||
GitHub Star! 🌟
|
||||
</v-btn>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
|
||||
const props = defineProps({ item: Object, level: Number });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list-item
|
||||
:to="item.type === 'external' ? '' : item.to"
|
||||
:href="item.type === 'external' ? item.to : ''"
|
||||
rounded
|
||||
class="mb-1"
|
||||
color="secondary"
|
||||
:disabled="item.disabled"
|
||||
:target="item.type === 'external' ? '_blank' : ''"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon v-if="item.icon" :size="item.iconSize" class="hide-menu" :icon="item.icon"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="item.subCaption" class="text-caption mt-n1 hide-menu">
|
||||
{{ item.subCaption }}
|
||||
</v-list-item-subtitle>
|
||||
<template v-slot:append v-if="item.chip">
|
||||
<v-chip
|
||||
:color="item.chipColor"
|
||||
class="sidebarchip hide-menu"
|
||||
:size="item.chipIcon ? 'small' : 'default'"
|
||||
:variant="item.chipVariant"
|
||||
:prepend-icon="item.chipIcon"
|
||||
>
|
||||
{{ item.chip }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { shallowRef } from 'vue';
|
||||
import { useCustomizerStore } from '../../../stores/customizer';
|
||||
import sidebarItems from './sidebarItem';
|
||||
import NavItem from './NavItem.vue';
|
||||
|
||||
const customizer = useCustomizerStore();
|
||||
const sidebarMenu = shallowRef(sidebarItems);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-navigation-drawer left v-model="customizer.Sidebar_drawer" elevation="0" rail-width="80" mobile-breakpoint="960"
|
||||
app class="leftSidebar" :rail="customizer.mini_sidebar">
|
||||
<v-list class="pa-4 listitem" style="height: auto">
|
||||
<template v-for="(item, i) in sidebarMenu" :key="i">
|
||||
<NavItem :item="item" class="leftPadding" />
|
||||
</template>
|
||||
</v-list>
|
||||
<div class="text-center">
|
||||
<v-chip color="inputBorder" size="small"> v{{ version }} </v-chip>
|
||||
</div>
|
||||
|
||||
<div style="position: absolute; bottom: 32px; width: 100%" class="text-center">
|
||||
<v-list-item v-if="!customizer.mini_sidebar" href="https://astrbot.soulter.top/">
|
||||
<v-btn variant="plain" size="small">
|
||||
🤔 初次使用?点击查看文档!
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
<small style="display: block;" v-if="buildVer">构建: {{ buildVer }}</small>
|
||||
<small style="display: block;" v-else="buildVer">构建: embedded</small>
|
||||
<small style="display: block; margin-top: 8px;">© 2024 AstrBot</small>
|
||||
</div>
|
||||
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
export default {
|
||||
name: 'VerticalSidebar',
|
||||
components: {
|
||||
NavItem,
|
||||
},
|
||||
data: () => ({
|
||||
version: "",
|
||||
buildVer: ""
|
||||
}),
|
||||
mounted() {
|
||||
this.get_version()
|
||||
fetch('/assets/version').then((res) => {
|
||||
return res.text()
|
||||
}).then((res) => {
|
||||
if (res.length > 10) {
|
||||
// 不是版本,不显示 😎
|
||||
return
|
||||
}
|
||||
this.buildVer = res
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
get_version() {
|
||||
axios.get('/api/stat/version')
|
||||
.then((res) => {
|
||||
this.version = res.data.data.version;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
export interface menu {
|
||||
header?: string;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
to?: string;
|
||||
divider?: boolean;
|
||||
chip?: string;
|
||||
chipColor?: string;
|
||||
chipVariant?: string;
|
||||
chipIcon?: string;
|
||||
children?: menu[];
|
||||
disabled?: boolean;
|
||||
type?: string;
|
||||
subCaption?: string;
|
||||
}
|
||||
|
||||
const sidebarItem: menu[] = [
|
||||
{
|
||||
title: '面板',
|
||||
icon: 'mdi-view-dashboard',
|
||||
to: '/dashboard/default'
|
||||
},
|
||||
{
|
||||
title: '配置',
|
||||
icon: 'mdi-cog',
|
||||
to: '/config',
|
||||
},
|
||||
{
|
||||
title: '插件',
|
||||
icon: 'mdi-puzzle',
|
||||
to: '/extension'
|
||||
},
|
||||
{
|
||||
title: '控制台',
|
||||
icon: 'mdi-console',
|
||||
to: '/console'
|
||||
},
|
||||
// {
|
||||
// title: 'Project ATRI',
|
||||
// icon: 'mdi-grain',
|
||||
// to: '/project-atri'
|
||||
// },
|
||||
];
|
||||
|
||||
export default sidebarItem;
|
||||
@@ -0,0 +1,32 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import App from './App.vue';
|
||||
import { router } from './router';
|
||||
import vuetify from './plugins/vuetify';
|
||||
import '@/scss/style.scss';
|
||||
import VueApexCharts from 'vue3-apexcharts';
|
||||
|
||||
import print from 'vue3-print-nb';
|
||||
import { loader } from '@guolao/vue-monaco-editor'
|
||||
import axios from 'axios';
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(createPinia());
|
||||
app.use(print);
|
||||
app.use(VueApexCharts);
|
||||
app.use(vuetify).mount('#app');
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
loader.config({
|
||||
paths: {
|
||||
vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
import { createVuetify } from 'vuetify';
|
||||
import '@mdi/font/css/materialdesignicons.css';
|
||||
import * as components from 'vuetify/components';
|
||||
import * as directives from 'vuetify/directives';
|
||||
import { PurpleTheme } from '@/theme/LightTheme';
|
||||
|
||||
export default createVuetify({
|
||||
components,
|
||||
directives,
|
||||
|
||||
theme: {
|
||||
defaultTheme: 'PurpleTheme',
|
||||
themes: {
|
||||
PurpleTheme
|
||||
}
|
||||
},
|
||||
defaults: {
|
||||
VBtn: {},
|
||||
VCard: {
|
||||
rounded: 'md'
|
||||
},
|
||||
VTextField: {
|
||||
rounded: 'lg'
|
||||
},
|
||||
VTooltip: {
|
||||
// set v-tooltip default location to top
|
||||
location: 'top'
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
const AuthRoutes = {
|
||||
path: '/auth',
|
||||
component: () => import('@/layouts/blank/BlankLayout.vue'),
|
||||
meta: {
|
||||
requiresAuth: false
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'Login',
|
||||
path: '/auth/login',
|
||||
component: () => import('@/views/authentication/auth/LoginPage.vue')
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default AuthRoutes;
|
||||
@@ -0,0 +1,43 @@
|
||||
const MainRoutes = {
|
||||
path: '/main',
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
},
|
||||
redirect: '/main/dashboard/default',
|
||||
component: () => import('@/layouts/full/FullLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
name: 'Dashboard',
|
||||
path: '/',
|
||||
component: () => import('@/views/dashboards/default/DefaultDashboard.vue')
|
||||
},
|
||||
{
|
||||
name: 'Extensions',
|
||||
path: '/extension',
|
||||
component: () => import('@/views/ExtensionPage.vue')
|
||||
},
|
||||
{
|
||||
name: 'Configs',
|
||||
path: '/config',
|
||||
component: () => import('@/views/ConfigPage.vue')
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Default',
|
||||
path: '/dashboard/default',
|
||||
component: () => import('@/views/dashboards/default/DefaultDashboard.vue')
|
||||
},
|
||||
{
|
||||
name: 'Console',
|
||||
path: '/console',
|
||||
component: () => import('@/views/ConsolePage.vue')
|
||||
},
|
||||
{
|
||||
name: 'Project ATRI',
|
||||
path: '/project-atri',
|
||||
component: () => import('@/views/ATRIProject.vue')
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default MainRoutes;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import MainRoutes from './MainRoutes';
|
||||
import AuthRoutes from './AuthRoutes';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
MainRoutes,
|
||||
AuthRoutes
|
||||
]
|
||||
});
|
||||
|
||||
interface AuthStore {
|
||||
username: string;
|
||||
returnUrl: string | null;
|
||||
login(username: string, password: string): Promise<void>;
|
||||
logout(): void;
|
||||
has_token(): boolean;
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const publicPages = ['/auth/login'];
|
||||
const authRequired = !publicPages.includes(to.path);
|
||||
const auth: AuthStore = useAuthStore();
|
||||
|
||||
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
||||
if (authRequired && !auth.has_token()) {
|
||||
auth.returnUrl = to.fullPath;
|
||||
return next('/auth/login');
|
||||
} else next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
html {
|
||||
.bg-success {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.v-row + .v-row {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
opacity: 1;
|
||||
border-color: rgba(var(--v-theme-borderLight), 0.36);
|
||||
}
|
||||
|
||||
.v-selection-control {
|
||||
flex: unset;
|
||||
}
|
||||
|
||||
.customizer-btn .icon {
|
||||
animation: progress-circular-rotate 1.4s linear infinite;
|
||||
transform-origin: center center;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.no-spacer {
|
||||
.v-list-item__spacer {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes progress-circular-rotate {
|
||||
100% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
@use 'sass:math';
|
||||
@use 'sass:map';
|
||||
@use 'sass:meta';
|
||||
@use 'vuetify/lib/styles/tools/functions' as *;
|
||||
|
||||
// This will false all colors which is not necessory for theme
|
||||
$color-pack: false;
|
||||
|
||||
// Global font size and border radius
|
||||
$font-size-root: 1rem;
|
||||
$border-radius-root: 8px;
|
||||
$body-font-family: 'Roboto', sans-serif !default;
|
||||
$heading-font-family: $body-font-family !default;
|
||||
$btn-font-weight: 400 !default;
|
||||
$btn-letter-spacing: 0 !default;
|
||||
|
||||
// Global Radius as per breakeven point
|
||||
$rounded: () !default;
|
||||
$rounded: map-deep-merge(
|
||||
(
|
||||
0: 0,
|
||||
'sm': $border-radius-root * 0.5,
|
||||
null: $border-radius-root,
|
||||
'md': $border-radius-root * 1,
|
||||
'lg': $border-radius-root * 2,
|
||||
'xl': $border-radius-root * 6,
|
||||
'pill': 9999px,
|
||||
'circle': 50%,
|
||||
'shaped': $border-radius-root * 6 0
|
||||
),
|
||||
$rounded
|
||||
);
|
||||
// Global Typography
|
||||
$typography: () !default;
|
||||
$typography: map-deep-merge(
|
||||
(
|
||||
'h1': (
|
||||
'size': 2.125rem,
|
||||
'weight': 700,
|
||||
'line-height': 3.5rem,
|
||||
'font-family': inherit
|
||||
),
|
||||
'h2': (
|
||||
'size': 1.5rem,
|
||||
'weight': 700,
|
||||
'line-height': 2.5rem,
|
||||
'font-family': inherit
|
||||
),
|
||||
'h3': (
|
||||
'size': 1.25rem,
|
||||
'weight': 600,
|
||||
'line-height': 2rem,
|
||||
'font-family': inherit
|
||||
),
|
||||
'h4': (
|
||||
'size': 1rem,
|
||||
'weight': 600,
|
||||
'line-height': 1.5rem,
|
||||
'font-family': inherit
|
||||
),
|
||||
'h5': (
|
||||
'size': 0.875rem,
|
||||
'weight': 500,
|
||||
'line-height': 1.2rem,
|
||||
'font-family': inherit
|
||||
),
|
||||
'h6': (
|
||||
'size': 0.75rem,
|
||||
'weight': 500,
|
||||
'font-family': inherit
|
||||
),
|
||||
'subtitle-1': (
|
||||
'size': 0.875rem,
|
||||
'weight': 500,
|
||||
'line-height': 1rem,
|
||||
'font-family': inherit
|
||||
),
|
||||
'subtitle-2': (
|
||||
'size': 0.75rem,
|
||||
'weight': 400,
|
||||
'line-height': 1rem,
|
||||
'font-family': inherit
|
||||
),
|
||||
'body-1': (
|
||||
'size': 0.875rem,
|
||||
'weight': 400,
|
||||
'font-family': inherit
|
||||
),
|
||||
'body-2': (
|
||||
'size': 0.75rem,
|
||||
'weight': 400,
|
||||
'font-family': inherit
|
||||
),
|
||||
'button': (
|
||||
'size': 0.875rem,
|
||||
'weight': 500,
|
||||
'font-family': inherit,
|
||||
'text-transform': uppercase
|
||||
),
|
||||
'caption': (
|
||||
'size': 0.75rem,
|
||||
'weight': 400,
|
||||
'font-family': inherit
|
||||
),
|
||||
'overline': (
|
||||
'size': 0.75rem,
|
||||
'weight': 500,
|
||||
'font-family': inherit,
|
||||
'text-transform': uppercase
|
||||
)
|
||||
),
|
||||
$typography
|
||||
);
|
||||
|
||||
// Custom Variables
|
||||
// colors
|
||||
$white: #fff !default;
|
||||
|
||||
// cards
|
||||
$card-item-spacer-xy: 20px 24px !default;
|
||||
$card-text-spacer: 24px !default;
|
||||
$card-title-size: 18px !default;
|
||||
// Global Shadow
|
||||
$box-shadow: 1px 0 20px rgb(0 0 0 / 8%);
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Light Buttons
|
||||
//
|
||||
|
||||
.v-btn {
|
||||
&.bg-lightsecondary {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background-color: rgb(var(--v-theme-secondary)) !important;
|
||||
color: $white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
text-transform: capitalize;
|
||||
letter-spacing: $btn-letter-spacing;
|
||||
}
|
||||
.v-btn--icon.v-btn--density-default {
|
||||
width: calc(var(--v-btn-height) + 6px);
|
||||
height: calc(var(--v-btn-height) + 6px);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Outline Card
|
||||
.v-card--variant-outlined {
|
||||
border-color: rgba(var(--v-theme-borderLight), 0.36);
|
||||
.v-divider {
|
||||
border-color: rgba(var(--v-theme-borderLight), 0.36);
|
||||
}
|
||||
}
|
||||
|
||||
.v-card-text {
|
||||
padding: $card-text-spacer;
|
||||
}
|
||||
|
||||
.v-card {
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
&.withbg {
|
||||
background-color: rgb(var(--v-theme-background));
|
||||
}
|
||||
&.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.v-card-item {
|
||||
padding: $card-item-spacer-xy;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.v-field--variant-outlined .v-field__outline__start.v-locale--is-ltr,
|
||||
.v-locale--is-ltr .v-field--variant-outlined .v-field__outline__start {
|
||||
border-radius: $border-radius-root 0 0 $border-radius-root;
|
||||
}
|
||||
|
||||
.v-field--variant-outlined .v-field__outline__end.v-locale--is-ltr,
|
||||
.v-locale--is-ltr .v-field--variant-outlined .v-field__outline__end {
|
||||
border-radius: 0 $border-radius-root $border-radius-root 0;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.v-input--density-default,
|
||||
.v-field--variant-solo,
|
||||
.v-field--variant-filled {
|
||||
--v-input-control-height: 51px;
|
||||
--v-input-padding-top: 14px;
|
||||
}
|
||||
.v-input--density-comfortable {
|
||||
--v-input-control-height: 56px;
|
||||
--v-input-padding-top: 17px;
|
||||
}
|
||||
.v-label {
|
||||
font-size: 0.975rem;
|
||||
}
|
||||
.v-switch .v-label,
|
||||
.v-checkbox .v-label {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.v-navigation-drawer__scrim.fade-transition-leave-to {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.elevation-10 {
|
||||
box-shadow: $box-shadow !important;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.theme-tab {
|
||||
&.v-tabs {
|
||||
.v-tab {
|
||||
border-radius: $border-radius-root !important;
|
||||
min-width: auto !important;
|
||||
&.v-slide-group-item--active {
|
||||
background: rgb(var(--v-theme-primary));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.v-text-field input {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.v-input--density-default {
|
||||
.v-field__input {
|
||||
min-height: 51px;
|
||||
}
|
||||
}
|
||||
|
||||
.v-field__outline {
|
||||
color: rgb(var(--v-theme-inputBorder));
|
||||
}
|
||||
.inputWithbg {
|
||||
.v-field--variant-outlined {
|
||||
background-color: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
html {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.v-main {
|
||||
margin-right: 20px;
|
||||
}
|
||||
@media (max-width: 1279px) {
|
||||
.v-main {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
.spacer {
|
||||
padding: 100px 0;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.spacer {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
min-height: calc(100vh - 100px);
|
||||
padding: 15px;
|
||||
border-radius: $border-radius-root;
|
||||
background: rgb(var(--v-theme-containerBg));
|
||||
}
|
||||
$sizes: (
|
||||
'display-1': 44px,
|
||||
'display-2': 40px,
|
||||
'display-3': 30px,
|
||||
'h1': 36px,
|
||||
'h2': 30px,
|
||||
'h3': 21px,
|
||||
'h4': 18px,
|
||||
'h5': 16px,
|
||||
'h6': 14px,
|
||||
'text-8': 8px,
|
||||
'text-10': 10px,
|
||||
'text-13': 13px,
|
||||
'text-18': 18px,
|
||||
'text-20': 20px,
|
||||
'text-24': 24px,
|
||||
'body-text-1': 10px
|
||||
);
|
||||
|
||||
@each $pixel, $size in $sizes {
|
||||
.#{$pixel} {
|
||||
font-size: $size;
|
||||
line-height: $size + 10;
|
||||
}
|
||||
}
|
||||
|
||||
.customizer-btn {
|
||||
position: fixed;
|
||||
top: 25%;
|
||||
right: 10px;
|
||||
border-radius: 50% 50% 4px;
|
||||
.icon {
|
||||
animation: progress-circular-rotate 1.4s linear infinite;
|
||||
transform-origin: center center;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-100vh {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
color: rgb(255, 255, 255) !important;
|
||||
}
|
||||
|
||||
// font family
|
||||
|
||||
body {
|
||||
.Poppins {
|
||||
font-family: 'Poppins', sans-serif !important;
|
||||
}
|
||||
|
||||
.Inter {
|
||||
font-family: 'Inter', sans-serif !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
20%,
|
||||
53%,
|
||||
to {
|
||||
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
40%,
|
||||
43% {
|
||||
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||
transform: translate3d(0, -5px, 0);
|
||||
}
|
||||
70% {
|
||||
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||
transform: translate3d(0, -7px, 0);
|
||||
}
|
||||
80% {
|
||||
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
90% {
|
||||
transform: translate3d(0, -2px, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*This is for the logo*/
|
||||
.leftSidebar {
|
||||
border: 0px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.listitem {
|
||||
height: calc(100vh - 100px);
|
||||
.v-list {
|
||||
color: rgb(var(--v-theme-lightText));
|
||||
}
|
||||
.v-list-group__items .v-list-item,
|
||||
.v-list-item {
|
||||
border-radius: $border-radius-root;
|
||||
padding-inline-start: calc(12px + var(--indent-padding) / 2) !important;
|
||||
&:hover {
|
||||
color: rgb(var(--v-theme-secondary));
|
||||
}
|
||||
}
|
||||
.leftPadding {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
.v-navigation-drawer--rail {
|
||||
.scrollnavbar .v-list .v-list-group__items,
|
||||
.hide-menu {
|
||||
opacity: 1;
|
||||
}
|
||||
.leftPadding {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 1170px) {
|
||||
.mini-sidebar {
|
||||
.logo {
|
||||
width: 90px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.leftSidebar:hover {
|
||||
box-shadow: $box-shadow !important;
|
||||
}
|
||||
.v-navigation-drawer--expand-on-hover:hover {
|
||||
.logo {
|
||||
width: 100%;
|
||||
}
|
||||
.v-list .v-list-group__items,
|
||||
.hide-menu {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
.bubble-shape {
|
||||
position: relative;
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
border-radius: 50%;
|
||||
top: -125px;
|
||||
right: -15px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
border-radius: 50%;
|
||||
top: -85px;
|
||||
right: -95px;
|
||||
}
|
||||
|
||||
// &.bubble-primary-shape {
|
||||
// &::before {
|
||||
// background: rgb(var(--v-theme-darkprimary));
|
||||
// }
|
||||
// &::after {
|
||||
// background: rgb(var(--v-theme-darkprimary));
|
||||
// }
|
||||
// }
|
||||
|
||||
// &.bubble-secondary-shape {
|
||||
// &::before {
|
||||
// background: rgb(var(--v-theme-darksecondary));
|
||||
// }
|
||||
// &::after {
|
||||
// background: rgb(var(--v-theme-darksecondary));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.z-1 {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
.bubble-shape-sm {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
border-radius: 50%;
|
||||
top: -160px;
|
||||
right: -130px;
|
||||
}
|
||||
// &.bubble-primary {
|
||||
// &::before {
|
||||
// background: linear-gradient(140.9deg, rgb(var(--v-theme-lightprimary)) -14.02%, rgba(var(--v-theme-darkprimary), 0) 77.58%);
|
||||
// }
|
||||
// }
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
border-radius: 50%;
|
||||
top: -30px;
|
||||
right: -180px;
|
||||
}
|
||||
// &.bubble-primary {
|
||||
// &::after {
|
||||
// background: linear-gradient(210.04deg, rgb(var(--v-theme-lightprimary)) -50.94%, rgba(var(--v-theme-darkprimary), 0) 83.49%);
|
||||
// }
|
||||
// }
|
||||
|
||||
// &.bubble-warning {
|
||||
// &::before {
|
||||
// background: linear-gradient(140.9deg, rgb(var(--v-theme-warning)) -14.02%, rgba(144, 202, 249, 0) 70.5%);
|
||||
// }
|
||||
// }
|
||||
|
||||
// &.bubble-warning {
|
||||
// &::after {
|
||||
// background: linear-gradient(210.04deg, rgb(var(--v-theme-warning)) -50.94%, rgba(144, 202, 249, 0) 83.49%);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.rounded-square {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
@import './variables';
|
||||
@import 'vuetify/styles/main.sass';
|
||||
@import './override';
|
||||
@import './layout/container';
|
||||
@import './layout/sidebar';
|
||||
|
||||
@import './components/VButtons';
|
||||
@import './components/VCard';
|
||||
@import './components/VField';
|
||||
@import './components/VInput';
|
||||
@import './components/VNavigationDrawer';
|
||||
@import './components/VShadow';
|
||||
@import './components/VTextField';
|
||||
@import './components/VTabs';
|
||||
|
||||
@import './pages/dashboards';
|
||||
@@ -0,0 +1,42 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { router } from '@/router';
|
||||
import axios from 'axios';
|
||||
|
||||
export const useAuthStore = defineStore({
|
||||
id: 'auth',
|
||||
state: () => ({
|
||||
// @ts-ignore
|
||||
username: '',
|
||||
returnUrl: null
|
||||
}),
|
||||
actions: {
|
||||
async login(username: string, password: string): Promise<void> {
|
||||
try {
|
||||
const res = await axios.post('/api/auth/login', {
|
||||
username: username,
|
||||
password: password
|
||||
});
|
||||
|
||||
if (res.data.status === 'error') {
|
||||
return Promise.reject(res.data.message);
|
||||
}
|
||||
|
||||
this.username = res.data.data.username
|
||||
localStorage.setItem('user', this.username);
|
||||
localStorage.setItem('token', res.data.data.token);
|
||||
router.push(this.returnUrl || '/dashboard/default');
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
this.username = '';
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem('token');
|
||||
router.push('/auth/login');
|
||||
},
|
||||
has_token(): boolean {
|
||||
return !!localStorage.getItem('token');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import axios from 'axios';
|
||||
|
||||
export const useCommonStore = defineStore({
|
||||
id: 'common',
|
||||
state: () => ({
|
||||
// @ts-ignore
|
||||
websocket: null,
|
||||
log_cache: [],
|
||||
log_cache_max_len: 1000,
|
||||
startTime: -1,
|
||||
}),
|
||||
actions: {
|
||||
createWebSocket() {
|
||||
if (this.websocket) {
|
||||
return
|
||||
}
|
||||
let protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
let route = '/api/live-log'
|
||||
let port = window.location.port
|
||||
let url = `${protocol}://${window.location.hostname}:${port}${route}`
|
||||
console.log('websocket url:', url)
|
||||
this.websocket = new WebSocket(url)
|
||||
this.websocket.onmessage = (evt) => {
|
||||
this.log_cache.push(evt.data)
|
||||
if (this.log_cache.length > this.log_cache_max_len) {
|
||||
this.log_cache.shift()
|
||||
}
|
||||
}
|
||||
},
|
||||
getLogCache() {
|
||||
return this.log_cache
|
||||
},
|
||||
getStartTime() {
|
||||
if (this.startTime !== -1) {
|
||||
return this.startTime
|
||||
}
|
||||
axios.get('/api/stat/start-time').then((res) => {
|
||||
this.startTime = res.data.data.start_time
|
||||
})
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import config from '@/config';
|
||||
|
||||
export const useCustomizerStore = defineStore({
|
||||
id: 'customizer',
|
||||
state: () => ({
|
||||
Sidebar_drawer: config.Sidebar_drawer,
|
||||
Customizer_drawer: config.Customizer_drawer,
|
||||
mini_sidebar: config.mini_sidebar,
|
||||
fontTheme: "Poppins",
|
||||
inputBg: config.inputBg
|
||||
}),
|
||||
|
||||
getters: {},
|
||||
actions: {
|
||||
SET_SIDEBAR_DRAWER() {
|
||||
this.Sidebar_drawer = !this.Sidebar_drawer;
|
||||
},
|
||||
SET_MINI_SIDEBAR(payload: boolean) {
|
||||
this.mini_sidebar = payload;
|
||||
},
|
||||
SET_FONT(payload: string) {
|
||||
this.fontTheme = payload;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { ThemeTypes } from '@/types/themeTypes/ThemeType';
|
||||
|
||||
const PurpleTheme: ThemeTypes = {
|
||||
name: 'PurpleTheme',
|
||||
dark: false,
|
||||
variables: {
|
||||
'border-color': '#1e88e5',
|
||||
'carousel-control-size': 10
|
||||
},
|
||||
colors: {
|
||||
primary: '#1e88e5',
|
||||
secondary: '#5e35b1',
|
||||
info: '#03c9d7',
|
||||
success: '#00c853',
|
||||
accent: '#FFAB91',
|
||||
warning: '#ffc107',
|
||||
error: '#f44336',
|
||||
lightprimary: '#eef2f6',
|
||||
lightsecondary: '#ede7f6',
|
||||
lightsuccess: '#b9f6ca',
|
||||
lighterror: '#f9d8d8',
|
||||
lightwarning: '#fff8e1',
|
||||
darkText: '#212121',
|
||||
lightText: '#616161',
|
||||
darkprimary: '#1565c0',
|
||||
darksecondary: '#4527a0',
|
||||
borderLight: '#d0d0d0',
|
||||
inputBorder: '#787878',
|
||||
containerBg: '#eef2f6',
|
||||
surface: '#fff',
|
||||
'on-surface-variant': '#fff',
|
||||
facebook: '#4267b2',
|
||||
twitter: '#1da1f2',
|
||||
linkedin: '#0e76a8',
|
||||
gray100: '#fafafa',
|
||||
primary200: '#90caf9',
|
||||
secondary200: '#b39ddb'
|
||||
}
|
||||
};
|
||||
|
||||
export { PurpleTheme };
|
||||
@@ -0,0 +1,35 @@
|
||||
export type ThemeTypes = {
|
||||
name: string;
|
||||
dark: boolean;
|
||||
variables?: object;
|
||||
colors: {
|
||||
primary?: string;
|
||||
secondary?: string;
|
||||
info?: string;
|
||||
success?: string;
|
||||
accent?: string;
|
||||
warning?: string;
|
||||
error?: string;
|
||||
lightprimary?: string;
|
||||
lightsecondary?: string;
|
||||
lightsuccess?: string;
|
||||
lighterror?: string;
|
||||
lightwarning?: string;
|
||||
darkprimary?: string;
|
||||
darksecondary?: string;
|
||||
darkText?: string;
|
||||
lightText?: string;
|
||||
borderLight?: string;
|
||||
inputBorder?: string;
|
||||
containerBg?: string;
|
||||
surface?: string;
|
||||
background?: string;
|
||||
'on-surface-variant'?: string;
|
||||
facebook?: string;
|
||||
twitter?: string;
|
||||
linkedin?: string;
|
||||
gray100?: string;
|
||||
primary200?: string;
|
||||
secondary200?: string;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
declare module 'vue3-print-nb';
|
||||
@@ -0,0 +1,10 @@
|
||||
import { VNodeChild } from 'vue';
|
||||
declare module '@vue/runtime-dom' {
|
||||
export interface HTMLAttributes {
|
||||
$children?: VNodeChild;
|
||||
}
|
||||
export interface SVGAttributes {
|
||||
$children?: VNodeChild;
|
||||
strokeWidth?: string | number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<v-alert style="margin-bottom: 16px"
|
||||
text="这是一个长期实验性功能,目标是实现更具人类机能的 LLM 对话。推荐使用 gpt-4o-mini 作为文本生成和视觉理解模型,成本很低。推荐使用 text-embedding-3-small 作为 Embedding 模型,成本忽略不计。"
|
||||
title="💡实验性功能" type="info" variant="tonal">
|
||||
</v-alert>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<v-container fluid>
|
||||
<AstrBotConfig :metadata="project_atri_config_metadata" :iterable="project_atri_config?.project_atri"
|
||||
metadataKey="project_atri">
|
||||
</AstrBotConfig>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-btn icon="mdi-content-save" size="x-large" style="position: fixed; right: 52px; bottom: 52px;" color="darkprimary"
|
||||
@click="updateConfig">
|
||||
</v-btn>
|
||||
<v-snackbar :timeout="3000" elevation="24" :color="save_message_success" v-model="save_message_snack">
|
||||
{{ save_message }}
|
||||
</v-snackbar>
|
||||
<WaitingForRestart ref="wfr"></WaitingForRestart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import AstrBotConfig from '@/components/shared/AstrBotConfig.vue';
|
||||
import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
|
||||
export default {
|
||||
name: 'AtriProject',
|
||||
components: {
|
||||
AstrBotConfig,
|
||||
WaitingForRestart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
project_atri_config: {},
|
||||
fetched: false,
|
||||
project_atri_config_metadata: {},
|
||||
save_message_snack: false,
|
||||
save_message: "",
|
||||
save_message_success: "",
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getConfig();
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
// 获取配置
|
||||
axios.get('/api/config/get').then((res) => {
|
||||
this.project_atri_config = res.data.data.config;
|
||||
this.fetched = true
|
||||
this.project_atri_config_metadata = res.data.data.metadata;
|
||||
}).catch((err) => {
|
||||
save_message = err;
|
||||
save_message_snack = true;
|
||||
save_message_success = "error";
|
||||
});
|
||||
},
|
||||
updateConfig() {
|
||||
if (!this.fetched) return;
|
||||
axios.post('/api/config/astrbot/update', this.project_atri_config).then((res) => {
|
||||
if (res.data.status === "ok") {
|
||||
this.save_message = res.data.message;
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "success";
|
||||
this.$refs.wfr.check();
|
||||
} else {
|
||||
this.save_message = res.data.message;
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.save_message = err;
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,220 @@
|
||||
<script setup>
|
||||
import axios from 'axios';
|
||||
import AstrBotConfig from '@/components/shared/AstrBotConfig.vue';
|
||||
import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
|
||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card style="margin-bottom: 16px;">
|
||||
<v-card-text style="padding: 0;">
|
||||
<div>
|
||||
<v-btn-group variant="outlined" divided>
|
||||
<v-btn icon="mdi-text-box-edit-outline" style="width: 80px;" :color="editorTab === 0 ? 'primary' : ''"
|
||||
@click="editorTab = 0">
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-code-json" style="width: 80px;" :color="editorTab === 1 ? 'primary' : ''"
|
||||
@click="configToString(); editorTab = 1;"></v-btn>
|
||||
</v-btn-group>
|
||||
<v-btn v-if="editorTab === 1" style="margin-left: 16px;" size="small" @click="configToString()">回到更改前的代码</v-btn>
|
||||
<v-btn v-if="editorTab === 1 && config_data_has_changed" style="margin-left: 16px;" size="small"
|
||||
@click="applyStrConfig()">应用此配置</v-btn>
|
||||
<small v-if="editorTab === 1" style="margin-left: 16px;">💡 `应用此配置` 将配置暂存并应用到可视化。如要保存,需<span
|
||||
style="font-weight: 1000;">再</span>点击右下角保存按钮。</small>
|
||||
</div>
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 可视化编辑 -->
|
||||
<v-card v-if="editorTab === 0">
|
||||
<v-tabs v-model="tab" align-tabs="left" color="deep-purple-accent-4">
|
||||
<v-tab v-for="(val, key, index) in metadata" :key="index" :value="index" style="font-weight: 1000; font-size: 15px">
|
||||
{{ metadata[key]['name'] }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-tabs-window v-model="tab">
|
||||
<v-tabs-window-item v-for="(val, key, index) in metadata" v-show="index == tab" :key="index">
|
||||
<v-container fluid>
|
||||
<v-expansion-panels variant="accordion">
|
||||
<v-expansion-panel v-for="(val2, key2, index2) in metadata[key]['metadata']">
|
||||
<v-expansion-panel-title>
|
||||
<h3>{{metadata[key]['metadata'][key2]['description']}}</h3>
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text v-if="metadata[key]['metadata'][key2]?.config_template">
|
||||
<!-- 带有 config_template 的配置项 -->
|
||||
<v-tabs style="margin-top: 16px;" align-tabs="left" color="deep-purple-accent-4" v-model="config_template_tab">
|
||||
<v-tab v-for="(item, index) in config_data[key2]" :key="index" :value="index">
|
||||
{{ item.id }}({{ item.type }})
|
||||
</v-tab>
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="plain" size="large" v-bind="props">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list @update:selected="addFromDefaultConfigTmpl($event, key, key2)">
|
||||
<v-list-item v-for="(item, index) in metadata[key]['metadata'][key2]?.config_template" :key="index" :value="index">
|
||||
<v-list-item-title>{{ index }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-tabs>
|
||||
<v-tabs-window v-model="config_template_tab">
|
||||
<v-tabs-window-item v-for="(config_item, index) in config_data[key2]" v-show="config_template_tab === index"
|
||||
:key="index" :value="index">
|
||||
<v-container>
|
||||
<AstrBotConfig :metadata="metadata[key]['metadata']" :iterable="config_item" :metadataKey="key2"></AstrBotConfig>
|
||||
</v-container>
|
||||
</v-tabs-window-item>
|
||||
</v-tabs-window>
|
||||
</v-expansion-panel-text>
|
||||
<v-expansion-panel-text v-else>
|
||||
<!-- 如果配置项是一个 object,那么 iterable 需要取到这个 object 的值,否则取到整个 config_data -->
|
||||
<AstrBotConfig v-if="metadata[key]['metadata'][key2]['type'] == 'object'" :metadata="metadata[key]['metadata']" :iterable="config_data[key2]" :metadataKey="key2"></AstrBotConfig>
|
||||
<AstrBotConfig v-else :metadata="metadata[key]['metadata']" :iterable="config_data" :metadataKey="key2"></AstrBotConfig>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
|
||||
</v-expansion-panels>
|
||||
</v-container>
|
||||
</v-tabs-window-item>
|
||||
|
||||
|
||||
<div style="margin-left: 16px; padding-bottom: 16px">
|
||||
<small>不了解配置?请见 <a
|
||||
href="https://astrbot.soulter.top/docs/%E5%BC%80%E5%A7%8B%E4%B8%8A%E6%89%8B/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6">官方文档</a>
|
||||
或 <a
|
||||
href="https://qm.qq.com/cgi-bin/qm/qr?k=EYGsuUTfe00_iOu9JTXS7_TEpMkXOvwv&jump_from=webapi&authKey=uUEMKCROfsseS+8IzqPjzV3y1tzy4AkykwTib2jNkOFdzezF9s9XknqnIaf3CDft">加群询问</a>。</small>
|
||||
</div>
|
||||
|
||||
</v-tabs-window>
|
||||
</v-card>
|
||||
|
||||
<!-- 代码编辑 -->
|
||||
<v-card v-else style="background-color: #1e1e1e;">
|
||||
<VueMonacoEditor theme="vs-dark" language="json" height="80vh" style="padding-top: 16px; padding-bottom: 16px;"
|
||||
v-model:value="config_data_str">
|
||||
</VueMonacoEditor>
|
||||
</v-card>
|
||||
|
||||
<v-btn icon="mdi-content-save" size="x-large" style="position: fixed; right: 52px; bottom: 52px;" color="darkprimary"
|
||||
@click="updateConfig">
|
||||
</v-btn>
|
||||
|
||||
<v-snackbar :timeout="3000" elevation="24" :color="save_message_success" v-model="save_message_snack">
|
||||
{{ save_message }}
|
||||
</v-snackbar>
|
||||
|
||||
<WaitingForRestart ref="wfr"></WaitingForRestart>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ConfigPage',
|
||||
components: {
|
||||
AstrBotConfig,
|
||||
VueMonacoEditor,
|
||||
WaitingForRestart
|
||||
},
|
||||
watch: {
|
||||
config_data_str: function (val) {
|
||||
this.config_data_has_changed = true;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
config_data_has_changed: false,
|
||||
config_data_str: "",
|
||||
config_data: {
|
||||
config: {}
|
||||
},
|
||||
fetched: false,
|
||||
metadata: {},
|
||||
provider_config_tmpl: {},
|
||||
adapter_config_tmpl: {}, // 平台适配器
|
||||
save_message_snack: false,
|
||||
save_message: "",
|
||||
save_message_success: "",
|
||||
namespace: "",
|
||||
tab: 0,
|
||||
editorTab: 0, // 0: visual, 1: code
|
||||
|
||||
config_template_tab: 0,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getConfig();
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
// 获取配置
|
||||
axios.get('/api/config/get').then((res) => {
|
||||
this.config_data = res.data.data.config;
|
||||
this.fetched = true
|
||||
this.metadata = res.data.data.metadata;
|
||||
this.provider_config_tmpl = res.data.data.provider_config_tmpl;
|
||||
this.adapter_config_tmpl = res.data.data.adapter_config_tmpl;
|
||||
}).catch((err) => {
|
||||
save_message = err;
|
||||
save_message_snack = true;
|
||||
save_message_success = "error";
|
||||
});
|
||||
},
|
||||
updateConfig() {
|
||||
if (!this.fetched) return;
|
||||
axios.post('/api/config/astrbot/update', this.config_data).then((res) => {
|
||||
if (res.data.status === "ok") {
|
||||
this.save_message = res.data.message;
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "success";
|
||||
this.$refs.wfr.check();
|
||||
} else {
|
||||
this.save_message = res.data.message;
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.save_message = err;
|
||||
this.save_message_snack = true;
|
||||
this.save_message_success = "error";
|
||||
});
|
||||
},
|
||||
configToString() {
|
||||
this.config_data_str = JSON.stringify(this.config_data, null, 2);
|
||||
this.config_data_has_changed = false;
|
||||
},
|
||||
applyStrConfig() {
|
||||
try {
|
||||
this.config_data = JSON.parse(this.config_data_str);
|
||||
this.config_data_has_changed = false;
|
||||
this.save_message_success = "success";
|
||||
this.save_message = "配置成功应用。如要保存,需再点击右下角保存按钮。";
|
||||
this.save_message_snack = true;
|
||||
} catch (e) {
|
||||
this.save_message_success = "error";
|
||||
this.save_message = "配置未应用,Json 格式错误。";
|
||||
this.save_message_snack = true;
|
||||
}
|
||||
},
|
||||
addFromDefaultConfigTmpl(val, group_name, config_item_name) {
|
||||
console.log(val);
|
||||
|
||||
let tmpl = this.metadata[group_name]['metadata'][config_item_name]['config_template'][val];
|
||||
let new_tmpl_cfg = JSON.parse(JSON.stringify(tmpl));
|
||||
new_tmpl_cfg.id = "new_" + val + "_" + this.config_data[config_item_name].length;
|
||||
this.config_data[config_item_name].push(new_tmpl_cfg);
|
||||
this.config_template_tab = this.config_data[config_item_name].length - 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.v-tab {
|
||||
text-transform: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="height: 100%;">
|
||||
<div
|
||||
style="background-color: white; padding: 8px; padding-left: 16px; border-radius: 8px; margin-bottom: 16px; display: flex; flex-direction: row; align-items: center; justify-content: space-between;">
|
||||
<h4>控制台</h4>
|
||||
</div>
|
||||
<ConsoleDisplayer style="height: calc(100vh - 160px); "/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'ConsolePage',
|
||||
components: {
|
||||
ConsoleDisplayer
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.fade-in {
|
||||
animation: fadeIn 0.2s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,391 @@
|
||||
<script setup>
|
||||
import ExtensionCard from '@/components/shared/ExtensionCard.vue';
|
||||
import ConfigDetailCard from '@/components/shared/ConfigDetailCard.vue';
|
||||
import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
|
||||
import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue';
|
||||
import axios from 'axios';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-alert style="margin: 16px" text="1. 如果因为网络问题安装失败,可以前往 配置->其他配置->插件仓库镜像 修改安装镜像源。2. 如需插件帮助请点击 `仓库` 查看 README"
|
||||
title="💡小提示" type="info" variant="tonal">
|
||||
</v-alert>
|
||||
<v-col cols="12" md="12">
|
||||
<div style="background-color: white; width: 100%; padding: 16px; border-radius: 10px;">
|
||||
<h3>🧩 已安装的插件</h3>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" lg="4" v-for="extension in extension_data.data">
|
||||
<ExtensionCard :key="extension.name" :title="extension.name" :link="extension.repo" style="margin-bottom: 4px;">
|
||||
<p style="min-height: 130px; max-height: 130px; overflow: none;">{{ extension.desc }}</p>
|
||||
<div class="d-flex align-center gap-2">
|
||||
<v-icon>mdi-account</v-icon>
|
||||
<span>{{ extension.author }}</span>
|
||||
<v-spacer></v-spacer>
|
||||
<div v-if="!extension.reserved">
|
||||
<v-btn variant="plain" @click="openExtensionConfig(extension.name)">配置</v-btn>
|
||||
<v-btn variant="plain" @click="updateExtension(extension.name)">更新</v-btn>
|
||||
<v-btn variant="plain" @click="uninstallExtension(extension.name)">卸载</v-btn>
|
||||
</div>
|
||||
<span v-else>保留插件</span>
|
||||
</div>
|
||||
</ExtensionCard>
|
||||
</v-col>
|
||||
<v-col cols="12" md="12">
|
||||
<div style="background-color: white; width: 100%; padding: 16px; border-radius: 10px;">
|
||||
<h3>🧩 插件市场</h3>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" lg="4" v-for="plugin in pluginMarketData">
|
||||
<ExtensionCard :key="plugin.name" :title="plugin.name" :link="plugin.repo" style="margin-bottom: 4px;">
|
||||
<p style="min-height: 130px; max-height: 130px; overflow: hidden;">{{ plugin.desc }}</p>
|
||||
<div class="d-flex align-center gap-2">
|
||||
<v-icon>mdi-account</v-icon>
|
||||
<span>{{ plugin.author }}</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn v-if="!plugin.installed" variant="plain"
|
||||
@click="extension_url = plugin.repo; newExtension()">安装</v-btn>
|
||||
<v-btn v-else variant="plain" disabled>已安装</v-btn>
|
||||
</div>
|
||||
</ExtensionCard>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
|
||||
<v-dialog v-model="configDialog" width="750">
|
||||
<template v-slot:activator="{ props }">
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">插件配置</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<ConfigDetailCard :config="extension_config"></ConfigDetailCard>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="updateConfig">
|
||||
保存并关闭
|
||||
</v-btn>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="configDialog = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="dialog" persistent width="700">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon="mdi-plus" size="x-large" style="position: fixed; right: 52px; bottom: 52px;"
|
||||
color="darkprimary">
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">安装插件</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<h3>从 GitHub 上在线下载</h3>
|
||||
<v-col cols="12">
|
||||
<small>请输入合法的 GitHub 仓库链接,当前仅支持 GitHub。如:https://github.com/Soulter/astrbot_plugin_aiocqhttp</small>
|
||||
<v-text-field label="仓库链接" v-model="extension_url" variant="outlined" required></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<h3>从本机上传 .zip 压缩包</h3>
|
||||
<v-col cols="12">
|
||||
<small>请保证插件文件存在压缩包根目录中的第一个文件夹中(即类似于从 GitHub 仓库页上下载的 Zip 压缩包的格式)。</small>
|
||||
<v-file-input label="选择文件" v-model="upload_file" accept=".zip" outlined required></v-file-input>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
<br>
|
||||
<small>{{ status }}</small>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="dialog = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-btn color="blue-darken-1" variant="text" :loading="loading_" @click="newExtension()">
|
||||
安装
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="loadingDialog.show" width="700" persistent>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">{{ loadingDialog.title }}</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-progress-linear indeterminate color="primary"
|
||||
v-if="loadingDialog.statusCode === 0"></v-progress-linear>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<div class="py-12 text-center" v-if="loadingDialog.statusCode !== 0">
|
||||
<v-icon class="mb-6" color="success" icon="mdi-check-circle-outline" size="128"
|
||||
v-if="loadingDialog.statusCode === 1"></v-icon>
|
||||
<v-icon class="mb-6" color="error" icon="mdi-alert-circle-outline" size="128"
|
||||
v-if="loadingDialog.statusCode === 2"></v-icon>
|
||||
<div class="text-h4 font-weight-bold">{{ loadingDialog.result }}</div>
|
||||
</div>
|
||||
<div style="margin-top: 32px;">
|
||||
<h3>日志</h3>
|
||||
<ConsoleDisplayer historyNum="10" style="height: 200px; margin-top: 16px;"></ConsoleDisplayer>
|
||||
</div>
|
||||
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="resetLoadingDialog()">
|
||||
关闭
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar :timeout="2000" elevation="24" :color="snack_success" v-model="snack_show">
|
||||
{{ snack_message }}
|
||||
</v-snackbar>
|
||||
|
||||
<WaitingForRestart ref="wfr"></WaitingForRestart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ExtensionPage',
|
||||
components: {
|
||||
ExtensionCard,
|
||||
ConfigDetailCard,
|
||||
WaitingForRestart,
|
||||
ConsoleDisplayer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
extension_data: {
|
||||
"data": []
|
||||
},
|
||||
extension_url: "",
|
||||
status: "",
|
||||
dialog: false,
|
||||
snack_message: "",
|
||||
snack_show: false,
|
||||
snack_success: "success",
|
||||
loading_: false,
|
||||
configDialog: false,
|
||||
extension_config: {},
|
||||
upload_file: null,
|
||||
pluginMarketData: {},
|
||||
loadingDialog: {
|
||||
show: false,
|
||||
title: "加载中...",
|
||||
statusCode: 0, // 0: loading, 1: success, 2: error,
|
||||
result: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getExtensions();
|
||||
this.fetchPluginCollection();
|
||||
},
|
||||
methods: {
|
||||
toast(message, success) {
|
||||
this.snack_message = message;
|
||||
this.snack_show = true;
|
||||
this.snack_success = success;
|
||||
},
|
||||
resetLoadingDialog() {
|
||||
this.loadingDialog = {
|
||||
show: false,
|
||||
title: "加载中...",
|
||||
statusCode: 0,
|
||||
result: ""
|
||||
}
|
||||
},
|
||||
onLoadingDialogResult(statusCode, result, timeToClose = 2000) {
|
||||
this.loadingDialog.statusCode = statusCode;
|
||||
this.loadingDialog.result = result;
|
||||
if (timeToClose === -1) {
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.resetLoadingDialog()
|
||||
}, timeToClose);
|
||||
},
|
||||
getExtensions() {
|
||||
axios.get('/api/plugin/get').then((res) => {
|
||||
this.extension_data.data = res.data.data;
|
||||
this.checkAlreadyInstalled();
|
||||
});
|
||||
},
|
||||
newExtension() {
|
||||
if (this.extension_url === "" && this.upload_file === null) {
|
||||
this.toast("请填写插件链接或上传插件文件", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.extension_url !== "" && this.upload_file !== null) {
|
||||
this.toast("请不要同时填写插件链接和上传插件文件", "error");
|
||||
return;
|
||||
}
|
||||
this.loading_ = true;
|
||||
this.loadingDialog.show = true;
|
||||
if (this.upload_file !== null) {
|
||||
this.toast("正在从文件安装插件", "primary");
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.upload_file[0]);
|
||||
axios.post('/api/plugin/install-upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}).then((res) => {
|
||||
this.loading_ = false;
|
||||
if (res.data.status === "error") {
|
||||
this.onLoadingDialogResult(2, res.data.message, -1);
|
||||
return;
|
||||
}
|
||||
this.extension_data.data = res.data.data;
|
||||
this.upload_file = "";
|
||||
this.onLoadingDialogResult(1, res.data.message);
|
||||
this.dialog = false;
|
||||
this.$refs.wfr.check();
|
||||
}).catch((err) => {
|
||||
this.loading_ = false;
|
||||
this.onLoadingDialogResult(2, err, -1);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.toast("正在从链接 " + this.extension_url + " 安装插件...", "primary");
|
||||
axios.post('/api/plugin/install',
|
||||
{
|
||||
url: this.extension_url
|
||||
}).then((res) => {
|
||||
this.loading_ = false;
|
||||
if (res.data.status === "error") {
|
||||
this.onLoadingDialogResult(2, res.data.message, -1);
|
||||
return;
|
||||
}
|
||||
this.extension_data.data = res.data.data;
|
||||
console.log(this.extension_data);
|
||||
this.extension_url = "";
|
||||
this.onLoadingDialogResult(1, res.data.message);
|
||||
this.dialog = false;
|
||||
this.$refs.wfr.check();
|
||||
}).catch((err) => {
|
||||
this.loading_ = false;
|
||||
this.onLoadingDialogResult(2, err, -1);
|
||||
});
|
||||
|
||||
}
|
||||
},
|
||||
uninstallExtension(extension_name) {
|
||||
this.toast("正在卸载" + extension_name, "primary");
|
||||
axios.post('/api/plugin/uninstall',
|
||||
{
|
||||
name: extension_name
|
||||
}).then((res) => {
|
||||
if (res.data.status === "error") {
|
||||
this.toast(res.data.message, "error");
|
||||
return;
|
||||
}
|
||||
this.extension_data.data = res.data.data;
|
||||
this.toast(res.data.message, "success");
|
||||
this.dialog = false;
|
||||
this.getExtensions();
|
||||
}).catch((err) => {
|
||||
this.toast(err, "error");
|
||||
});
|
||||
},
|
||||
updateExtension(extension_name) {
|
||||
this.loadingDialog.show = true;
|
||||
axios.post('/api/plugin/update',
|
||||
{
|
||||
name: extension_name
|
||||
}).then((res) => {
|
||||
if (res.data.status === "error") {
|
||||
this.onLoadingDialogResult(2, res.data.message, -1);
|
||||
return;
|
||||
}
|
||||
this.extension_data.data = res.data.data;
|
||||
console.log(this.extension_data);
|
||||
this.onLoadingDialogResult(1, res.data.message);
|
||||
this.dialog = false;
|
||||
this.$refs.wfr.check();
|
||||
}).catch((err) => {
|
||||
this.toast(err, "error");
|
||||
});
|
||||
},
|
||||
openExtensionConfig(extension_name) {
|
||||
this.curr_namespace = extension_name;
|
||||
this.configDialog = true;
|
||||
axios.get('/api/config/get?namespace=' + extension_name).then((res) => {
|
||||
this.extension_config = res.data.data;
|
||||
console.log(this.extension_config);
|
||||
}).catch((err) => {
|
||||
this.toast(err, "error");
|
||||
});
|
||||
},
|
||||
updateConfig() {
|
||||
axios.post('/api/config/plugin/update', {
|
||||
"config": this.extension_config,
|
||||
"namespace": this.curr_namespace
|
||||
}).then((res) => {
|
||||
if (res.data.status === "ok") {
|
||||
this.toast(res.data.message, "success");
|
||||
this.$refs.wfr.check();
|
||||
} else {
|
||||
this.toast(res.data.message, "error");
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.toast(err, "error");
|
||||
});
|
||||
},
|
||||
fetchPluginCollection() {
|
||||
axios.get('/api/plugin/market_list').then((res) => {
|
||||
let data = []
|
||||
this.pluginMarketDataOrigin = res.data.data;
|
||||
for (let key in res.data.data) {
|
||||
data.push({
|
||||
"name": key,
|
||||
"desc": res.data.data[key].desc,
|
||||
"author": res.data.data[key].author,
|
||||
"repo": res.data.data[key].repo,
|
||||
"installed": false
|
||||
})
|
||||
}
|
||||
this.pluginMarketData = data;
|
||||
this.checkAlreadyInstalled();
|
||||
}).catch((err) => {
|
||||
this.toast("获取插件市场数据失败: " + err, "error");
|
||||
});
|
||||
},
|
||||
checkAlreadyInstalled() {
|
||||
for (let i = 0; i < this.pluginMarketData.length; i++) {
|
||||
this.pluginMarketData[i].installed = false;
|
||||
}
|
||||
for (let i = 0; i < this.pluginMarketData.length; i++) {
|
||||
for (let j = 0; j < this.extension_data.data.length; j++) {
|
||||
if (this.pluginMarketData[i].repo === this.extension_data.data[j].repo) {
|
||||
this.pluginMarketData[i].installed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import AuthLogin from '../authForms/AuthLogin.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="display: flex; justify-content: center; flex-direction: column; align-items: center; height: 100vh; background-color: aliceblue;">
|
||||
<v-card variant="outlined" style="max-width: 500px; box-shadow: 0 0 3px rgba(0, 0, 0, 0.1);">
|
||||
<v-card-text class="pa-9">
|
||||
<div class="text-center">
|
||||
<Logo />
|
||||
<h2 class="text-secondary text-h2 mt-4">AstrBot 仪表盘</h2>
|
||||
<h4 class="text-disabled text-h4 mt-3">登录以继续</h4>
|
||||
</div>
|
||||
<AuthLogin />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.loginBox {
|
||||
max-width: 475px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||