style: introduce ruff as linter and formatter (#1356)

* style: remove necessary imports

* style: fix F841

* style: fix F401

* style: fix F811

* style: fix E402

* style: fix E721

* style: fix E722

* style: fix E722

* style: fix F541

* style: ruff format

* style: all passed

* style: add ruff in deps

* style: more ignores in ruff.toml

* style: add pre-commit
This commit is contained in:
Junyan Qin (Chin)
2025-04-29 17:24:07 +08:00
committed by GitHub
parent 09e70d70e9
commit 209f16af76
240 changed files with 5307 additions and 4689 deletions

View File

@@ -3,17 +3,17 @@ from __future__ import annotations
import typing
from ..core import app, entities as core_entities
from ..provider import entities as llm_entities
from . import entities, operator, errors
from ..config import manager as cfg_mgr
from ..utils import importutil
# 引入所有算子以便注册
from .operators import func, plugin, reset, list as list_cmd, last, next, delc, resend, prompt, cmd, help, version, update, ollama, model
from . import operators
importutil.import_modules_in_pkg(operators)
class CommandManager:
"""命令管理器
"""
"""命令管理器"""
ap: app.Application
@@ -26,14 +26,13 @@ class CommandManager:
self.ap = ap
async def initialize(self):
# 设置各个类的路径
def set_path(cls: operator.CommandOperator, ancestors: list[str]):
cls.path = '.'.join(ancestors + [cls.name])
for op in operator.preregistered_operators:
if op.parent_class == cls:
set_path(op, ancestors + [cls.name])
for cls in operator.preregistered_operators:
if cls.parent_class is None:
set_path(cls, [])
@@ -41,14 +40,18 @@ class CommandManager:
# 应用命令权限配置
for cls in operator.preregistered_operators:
if cls.path in self.ap.instance_config.data['command']['privilege']:
cls.lowest_privilege = self.ap.instance_config.data['command']['privilege'][cls.path]
cls.lowest_privilege = self.ap.instance_config.data['command'][
'privilege'
][cls.path]
# 实例化所有类
self.cmd_list = [cls(self.ap) for cls in operator.preregistered_operators]
# 设置所有类的子节点
for cmd in self.cmd_list:
cmd.children = [child for child in self.cmd_list if child.parent_class == cmd.__class__]
cmd.children = [
child for child in self.cmd_list if child.parent_class == cmd.__class__
]
# 初始化所有类
for cmd in self.cmd_list:
@@ -58,27 +61,25 @@ class CommandManager:
self,
context: entities.ExecuteContext,
operator_list: list[operator.CommandOperator],
operator: operator.CommandOperator = None
operator: operator.CommandOperator = None,
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行命令
"""
"""执行命令"""
found = False
if len(context.crt_params) > 0: # 查找下一个参数是否对应此节点的某个子节点名
for oper in operator_list:
if (context.crt_params[0] == oper.name \
or context.crt_params[0] in oper.alias) \
and (oper.parent_class is None or oper.parent_class == operator.__class__):
if (
context.crt_params[0] == oper.name
or context.crt_params[0] in oper.alias
) and (
oper.parent_class is None or oper.parent_class == operator.__class__
):
found = True
context.crt_command = context.crt_params[0]
context.crt_params = context.crt_params[1:]
async for ret in self._execute(
context,
oper.children,
oper
):
async for ret in self._execute(context, oper.children, oper):
yield ret
break
@@ -96,19 +97,20 @@ class CommandManager:
async for ret in operator.execute(context):
yield ret
async def execute(
self,
command_text: str,
query: core_entities.Query,
session: core_entities.Session
session: core_entities.Session,
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行命令
"""
"""执行命令"""
privilege = 1
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.instance_config.data['admins']:
if (
f'{query.launcher_type.value}_{query.launcher_id}'
in self.ap.instance_config.data['admins']
):
privilege = 2
ctx = entities.ExecuteContext(
@@ -119,11 +121,8 @@ class CommandManager:
crt_command='',
params=command_text.split(' '),
crt_params=command_text.split(' '),
privilege=privilege
privilege=privilege,
)
async for ret in self._execute(
ctx,
self.cmd_list
):
async for ret in self._execute(ctx, self.cmd_list):
yield ret

View File

@@ -4,14 +4,13 @@ import typing
import pydantic.v1 as pydantic
from ..core import app, entities as core_entities
from . import errors, operator
from ..core import entities as core_entities
from . import errors
from ..platform.types import message as platform_message
class CommandReturn(pydantic.BaseModel):
"""命令返回值
"""
"""命令返回值"""
text: typing.Optional[str] = None
"""文本
@@ -24,7 +23,7 @@ class CommandReturn(pydantic.BaseModel):
"""图片链接
"""
error: typing.Optional[errors.CommandError]= None
error: typing.Optional[errors.CommandError] = None
"""错误
"""
@@ -33,8 +32,7 @@ class CommandReturn(pydantic.BaseModel):
class ExecuteContext(pydantic.BaseModel):
"""单次命令执行上下文
"""
"""单次命令执行上下文"""
query: core_entities.Query
"""本次消息的请求对象"""

View File

@@ -1,33 +1,26 @@
class CommandError(Exception):
def __init__(self, message: str = None):
self.message = message
def __str__(self):
return self.message
class CommandNotFoundError(CommandError):
def __init__(self, message: str = None):
super().__init__("未知命令: "+message)
super().__init__('未知命令: ' + message)
class CommandPrivilegeError(CommandError):
def __init__(self, message: str = None):
super().__init__("权限不足: "+message)
super().__init__('权限不足: ' + message)
class ParamNotEnoughError(CommandError):
def __init__(self, message: str = None):
super().__init__("参数不足: "+message)
super().__init__('参数不足: ' + message)
class CommandOperationError(CommandError):
def __init__(self, message: str = None):
super().__init__("操作失败: "+message)
super().__init__('操作失败: ' + message)

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import typing
import abc
from ..core import app, entities as core_entities
from ..core import app
from . import entities
@@ -13,14 +13,14 @@ preregistered_operators: list[typing.Type[CommandOperator]] = []
def operator_class(
name: str,
help: str = "",
help: str = '',
usage: str = None,
alias: list[str] = [],
privilege: int=1, # 1为普通用户2为管理员
parent_class: typing.Type[CommandOperator] = None
privilege: int = 1, # 1为普通用户2为管理员
parent_class: typing.Type[CommandOperator] = None,
) -> typing.Callable[[typing.Type[CommandOperator]], typing.Type[CommandOperator]]:
"""命令类装饰器
Args:
name (str): 名称
help (str, optional): 帮助信息. Defaults to "".
@@ -35,7 +35,7 @@ def operator_class(
def decorator(cls: typing.Type[CommandOperator]) -> typing.Type[CommandOperator]:
assert issubclass(cls, CommandOperator)
cls.name = name
cls.alias = alias
cls.help = help
@@ -96,14 +96,13 @@ class CommandOperator(metaclass=abc.ABCMeta):
@abc.abstractmethod
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""实现此方法以执行命令
支持多次yield以返回多个结果。
例如:一个安装插件的命令,可能会有下载、解压、安装等多个步骤,每个步骤都可以返回一个结果。
Args:
context (entities.ExecuteContext): 命令执行上下文

View File

@@ -2,49 +2,46 @@ from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="cmd",
help='显示命令列表',
usage='!cmd\n!cmd <命令名称>'
)
@operator.operator_class(name='cmd', help='显示命令列表', usage='!cmd\n!cmd <命令名称>')
class CmdOperator(operator.CommandOperator):
"""命令列表
"""
"""命令列表"""
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行
"""
"""执行"""
if len(context.crt_params) == 0:
reply_str = "当前所有命令: \n\n"
reply_str = '当前所有命令: \n\n'
for cmd in self.ap.cmd_mgr.cmd_list:
if cmd.parent_class is None:
reply_str += f"{cmd.name}: {cmd.help}\n"
reply_str += "\n使用 !cmd <命令名称> 查看命令的详细帮助"
reply_str += f'{cmd.name}: {cmd.help}\n'
reply_str += '\n使用 !cmd <命令名称> 查看命令的详细帮助'
yield entities.CommandReturn(text=reply_str.strip())
else:
cmd_name = context.crt_params[0]
cmd = None
for _cmd in self.ap.cmd_mgr.cmd_list:
if (cmd_name == _cmd.name or cmd_name in _cmd.alias) and (_cmd.parent_class is None):
if (cmd_name == _cmd.name or cmd_name in _cmd.alias) and (
_cmd.parent_class is None
):
cmd = _cmd
break
if cmd is None:
yield entities.CommandReturn(error=errors.CommandNotFoundError(cmd_name))
yield entities.CommandReturn(
error=errors.CommandNotFoundError(cmd_name)
)
else:
reply_str = f"{cmd.name}: {cmd.help}\n\n"
reply_str += f"使用方法: \n{cmd.usage}"
reply_str = f'{cmd.name}: {cmd.help}\n\n'
reply_str += f'使用方法: \n{cmd.usage}'
yield entities.CommandReturn(text=reply_str.strip())

View File

@@ -1,62 +1,60 @@
from __future__ import annotations
import typing
import datetime
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="del",
help="删除当前会话的历史记录",
usage='!del <序号>\n!del all'
name='del', help='删除当前会话的历史记录', usage='!del <序号>\n!del all'
)
class DelOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if context.session.conversations:
delete_index = 0
if len(context.crt_params) > 0:
try:
delete_index = int(context.crt_params[0])
except:
yield entities.CommandReturn(error=errors.CommandOperationError('索引必须是整数'))
except Exception:
yield entities.CommandReturn(
error=errors.CommandOperationError('索引必须是整数')
)
return
if delete_index < 0 or delete_index >= len(context.session.conversations):
yield entities.CommandReturn(error=errors.CommandOperationError('索引超出范围'))
return
# 倒序
to_delete_index = len(context.session.conversations)-1-delete_index
if context.session.conversations[to_delete_index] == context.session.using_conversation:
if delete_index < 0 or delete_index >= len(context.session.conversations):
yield entities.CommandReturn(
error=errors.CommandOperationError('索引超出范围')
)
return
# 倒序
to_delete_index = len(context.session.conversations) - 1 - delete_index
if (
context.session.conversations[to_delete_index]
== context.session.using_conversation
):
context.session.using_conversation = None
del context.session.conversations[to_delete_index]
yield entities.CommandReturn(text=f"已删除对话: {delete_index}")
yield entities.CommandReturn(text=f'已删除对话: {delete_index}')
else:
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
yield entities.CommandReturn(
error=errors.CommandOperationError('当前没有对话')
)
@operator.operator_class(
name="all",
help="删除此会话的所有历史记录",
parent_class=DelOperator
name='all', help='删除此会话的所有历史记录', parent_class=DelOperator
)
class DelAllOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
context.session.conversations = []
context.session.using_conversation = None
yield entities.CommandReturn(text="已删除所有对话")
yield entities.CommandReturn(text='已删除所有对话')

View File

@@ -1,16 +1,15 @@
from __future__ import annotations
from typing import AsyncGenerator
from .. import operator, entities, cmdmgr
from ...plugin import context as plugin_context
from .. import operator, entities
@operator.operator_class(name="func", help="查看所有已注册的内容函数", usage='!func')
@operator.operator_class(name='func', help='查看所有已注册的内容函数', usage='!func')
class FuncOperator(operator.CommandOperator):
async def execute(
self, context: entities.ExecuteContext
) -> AsyncGenerator[entities.CommandReturn, None]:
reply_str = "当前已启用的内容函数: \n\n"
reply_str = '当前已启用的内容函数: \n\n'
index = 1
@@ -19,7 +18,7 @@ class FuncOperator(operator.CommandOperator):
)
for func in all_functions:
reply_str += "{}. {}:\n{}\n\n".format(
reply_str += '{}. {}:\n{}\n\n'.format(
index,
func.name,
func.description,

View File

@@ -2,19 +2,13 @@ from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities
@operator.operator_class(
name='help',
help='显示帮助',
usage='!help\n!help <命令名称>'
)
@operator.operator_class(name='help', help='显示帮助', usage='!help\n!help <命令名称>')
class HelpOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
help = 'LangBot - 大语言模型原生即时通信机器人平台\n链接https://langbot.app'

View File

@@ -1,36 +1,43 @@
from __future__ import annotations
import typing
import datetime
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="last",
help="切换到前一个对话",
usage='!last'
)
@operator.operator_class(name='last', help='切换到前一个对话', usage='!last')
class LastOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if context.session.conversations:
# 找到当前会话的上一个会话
for index in range(len(context.session.conversations)-1, -1, -1):
if context.session.conversations[index] == context.session.using_conversation:
for index in range(len(context.session.conversations) - 1, -1, -1):
if (
context.session.conversations[index]
== context.session.using_conversation
):
if index == 0:
yield entities.CommandReturn(error=errors.CommandOperationError('已经是第一个对话了'))
yield entities.CommandReturn(
error=errors.CommandOperationError('已经是第一个对话了')
)
return
else:
context.session.using_conversation = context.session.conversations[index-1]
time_str = context.session.using_conversation.create_time.strftime("%Y-%m-%d %H:%M:%S")
context.session.using_conversation = (
context.session.conversations[index - 1]
)
time_str = (
context.session.using_conversation.create_time.strftime(
'%Y-%m-%d %H:%M:%S'
)
)
yield entities.CommandReturn(text=f"已切换到上一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].readable_str()}")
yield entities.CommandReturn(
text=f'已切换到上一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].readable_str()}'
)
return
else:
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
yield entities.CommandReturn(
error=errors.CommandOperationError('当前没有对话')
)

View File

@@ -1,30 +1,26 @@
from __future__ import annotations
import typing
import datetime
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="list",
help="列出此会话中的所有历史对话",
usage='!list\n!list <页码>'
name='list', help='列出此会话中的所有历史对话', usage='!list\n!list <页码>'
)
class ListOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
page = 0
if len(context.crt_params) > 0:
try:
page = int(context.crt_params[0]-1)
except:
yield entities.CommandReturn(error=errors.CommandOperationError('页码应为整数'))
page = int(context.crt_params[0] - 1)
except Exception:
yield entities.CommandReturn(
error=errors.CommandOperationError('页码应为整数')
)
return
record_per_page = 10
@@ -36,21 +32,21 @@ class ListOperator(operator.CommandOperator):
using_conv_index = 0
for conv in context.session.conversations[::-1]:
time_str = conv.create_time.strftime("%Y-%m-%d %H:%M:%S")
time_str = conv.create_time.strftime('%Y-%m-%d %H:%M:%S')
if conv == context.session.using_conversation:
using_conv_index = index
if index >= page * record_per_page and index < (page + 1) * record_per_page:
content += f"{index} {time_str}: {conv.messages[0].readable_str() if len(conv.messages) > 0 else '无内容'}\n"
content += f'{index} {time_str}: {conv.messages[0].readable_str() if len(conv.messages) > 0 else "无内容"}\n'
index += 1
if content == '':
content = ''
else:
if context.session.using_conversation is None:
content += "\n当前处于新会话"
content += '\n当前处于新会话'
else:
content += f"\n当前会话: {using_conv_index} {context.session.using_conversation.create_time.strftime('%Y-%m-%d %H:%M:%S')}: {context.session.using_conversation.messages[0].readable_str() if len(context.session.using_conversation.messages) > 0 else '无内容'}"
yield entities.CommandReturn(text=f"{page + 1} 页 (时间倒序):\n{content}")
content += f'\n当前会话: {using_conv_index} {context.session.using_conversation.create_time.strftime("%Y-%m-%d %H:%M:%S")}: {context.session.using_conversation.messages[0].readable_str() if len(context.session.using_conversation.messages) > 0 else "无内容"}'
yield entities.CommandReturn(text=f'{page + 1} 页 (时间倒序):\n{content}')

View File

@@ -2,42 +2,44 @@ from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="model",
name='model',
help='显示和切换模型列表',
usage='!model\n!model show <模型名>\n!model set <模型名>',
privilege=2
privilege=2,
)
class ModelOperator(operator.CommandOperator):
"""Model命令"""
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
async def execute(
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
content = '模型列表:\n'
model_list = self.ap.model_mgr.model_list
for model in model_list:
content += f"\n名称: {model.name}\n"
content += f"请求器: {model.requester.name}\n"
content += f'\n名称: {model.name}\n'
content += f'请求器: {model.requester.name}\n'
content += f"\n当前对话使用模型: {context.query.use_model.name}\n"
content += f"新对话默认使用模型: {self.ap.provider_cfg.data.get('model')}\n"
content += f'\n当前对话使用模型: {context.query.use_model.name}\n'
content += f'新对话默认使用模型: {self.ap.provider_cfg.data.get("model")}\n'
yield entities.CommandReturn(text=content.strip())
@operator.operator_class(
name="show",
help='显示模型详情',
privilege=2,
parent_class=ModelOperator
name='show', help='显示模型详情', privilege=2, parent_class=ModelOperator
)
class ModelShowOperator(operator.CommandOperator):
"""Model Show命令"""
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
async def execute(
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
model_name = context.crt_params[0]
model = None
@@ -47,29 +49,31 @@ class ModelShowOperator(operator.CommandOperator):
break
if model is None:
yield entities.CommandReturn(error=errors.CommandError(f"未找到模型 {model_name}"))
yield entities.CommandReturn(
error=errors.CommandError(f'未找到模型 {model_name}')
)
else:
content = f"模型详情\n"
content += f"名称: {model.name}\n"
content = '模型详情\n'
content += f'名称: {model.name}\n'
if model.model_name is not None:
content += f"请求模型名称: {model.model_name}\n"
content += f"请求器: {model.requester.name}\n"
content += f"密钥组: {model.token_mgr.name}\n"
content += f"支持视觉: {model.vision_supported}\n"
content += f"支持工具: {model.tool_call_supported}\n"
content += f'请求模型名称: {model.model_name}\n'
content += f'请求器: {model.requester.name}\n'
content += f'密钥组: {model.token_mgr.name}\n'
content += f'支持视觉: {model.vision_supported}\n'
content += f'支持工具: {model.tool_call_supported}\n'
yield entities.CommandReturn(text=content.strip())
@operator.operator_class(
name="set",
help='设置默认使用模型',
privilege=2,
parent_class=ModelOperator
name='set', help='设置默认使用模型', privilege=2, parent_class=ModelOperator
)
class ModelSetOperator(operator.CommandOperator):
"""Model Set命令"""
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
async def execute(
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
model_name = context.crt_params[0]
model = None
@@ -79,8 +83,12 @@ class ModelSetOperator(operator.CommandOperator):
break
if model is None:
yield entities.CommandReturn(error=errors.CommandError(f"未找到模型 {model_name}"))
yield entities.CommandReturn(
error=errors.CommandError(f'未找到模型 {model_name}')
)
else:
self.ap.provider_cfg.data['model'] = model_name
await self.ap.provider_cfg.dump_config()
yield entities.CommandReturn(text=f"已设置当前使用模型为 {model_name},重置会话以生效")
yield entities.CommandReturn(
text=f'已设置当前使用模型为 {model_name},重置会话以生效'
)

View File

@@ -1,35 +1,42 @@
from __future__ import annotations
import typing
import datetime
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="next",
help="切换到后一个对话",
usage='!next'
)
@operator.operator_class(name='next', help='切换到后一个对话', usage='!next')
class NextOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if context.session.conversations:
# 找到当前会话的下一个会话
for index in range(len(context.session.conversations)):
if context.session.conversations[index] == context.session.using_conversation:
if index == len(context.session.conversations)-1:
yield entities.CommandReturn(error=errors.CommandOperationError('已经是最后一个对话了'))
if (
context.session.conversations[index]
== context.session.using_conversation
):
if index == len(context.session.conversations) - 1:
yield entities.CommandReturn(
error=errors.CommandOperationError('已经是最后一个对话了')
)
return
else:
context.session.using_conversation = context.session.conversations[index+1]
time_str = context.session.using_conversation.create_time.strftime("%Y-%m-%d %H:%M:%S")
context.session.using_conversation = (
context.session.conversations[index + 1]
)
time_str = (
context.session.using_conversation.create_time.strftime(
'%Y-%m-%d %H:%M:%S'
)
)
yield entities.CommandReturn(text=f"已切换到后一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}")
yield entities.CommandReturn(
text=f'已切换到后一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}'
)
return
else:
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
yield entities.CommandReturn(
error=errors.CommandOperationError('当前没有对话')
)

View File

@@ -2,31 +2,32 @@ from __future__ import annotations
import json
import typing
import traceback
import ollama
from .. import operator, entities, errors
@operator.operator_class(
name="ollama",
help="ollama平台操作",
usage="!ollama\n!ollama show <模型名>\n!ollama pull <模型名>\n!ollama del <模型名>"
name='ollama',
help='ollama平台操作',
usage='!ollama\n!ollama show <模型名>\n!ollama pull <模型名>\n!ollama del <模型名>',
)
class OllamaOperator(operator.CommandOperator):
async def execute(
self, context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
try:
content: str = '模型列表:\n'
model_list: list = ollama.list().get('models', [])
for model in model_list:
content += f"名称: {model['name']}\n"
content += f"修改时间: {model['modified_at']}\n"
content += f"大小: {bytes_to_mb(model['size'])}MB\n\n"
yield entities.CommandReturn(text=f"{content.strip()}")
except ollama.ResponseError as e:
yield entities.CommandReturn(error=errors.CommandError(f"无法获取模型列表,请确认 Ollama 服务正常"))
content += f'名称: {model["name"]}\n'
content += f'修改时间: {model["modified_at"]}\n'
content += f'大小: {bytes_to_mb(model["size"])}MB\n\n'
yield entities.CommandReturn(text=f'{content.strip()}')
except ollama.ResponseError:
yield entities.CommandReturn(
error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常')
)
def bytes_to_mb(num_bytes):
@@ -35,14 +36,11 @@ def bytes_to_mb(num_bytes):
@operator.operator_class(
name="show",
help="ollama模型详情",
privilege=2,
parent_class=OllamaOperator
name='show', help='ollama模型详情', privilege=2, parent_class=OllamaOperator
)
class OllamaShowOperator(operator.CommandOperator):
async def execute(
self, context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
content: str = '模型详情:\n'
try:
@@ -53,31 +51,36 @@ class OllamaShowOperator(operator.CommandOperator):
for key in ['license', 'modelfile']:
show[key] = ignore_show
for key in ['tokenizer.chat_template.rag', 'tokenizer.chat_template.tool_use']:
for key in [
'tokenizer.chat_template.rag',
'tokenizer.chat_template.tool_use',
]:
model_info[key] = ignore_show
content += json.dumps(show, indent=4)
yield entities.CommandReturn(text=content.strip())
except ollama.ResponseError as e:
yield entities.CommandReturn(error=errors.CommandError(f"无法获取模型详情,请确认 Ollama 服务正常"))
except ollama.ResponseError:
yield entities.CommandReturn(
error=errors.CommandError('无法获取模型详情,请确认 Ollama 服务正常')
)
@operator.operator_class(
name="pull",
help="ollama模型拉取",
privilege=2,
parent_class=OllamaOperator
name='pull', help='ollama模型拉取', privilege=2, parent_class=OllamaOperator
)
class OllamaPullOperator(operator.CommandOperator):
async def execute(
self, context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
try:
model_list: list = ollama.list().get('models', [])
if context.crt_params[0] in [model['name'] for model in model_list]:
yield entities.CommandReturn(text="模型已存在")
yield entities.CommandReturn(text='模型已存在')
return
except ollama.ResponseError as e:
yield entities.CommandReturn(error=errors.CommandError(f"无法获取模型列表,请确认 Ollama 服务正常"))
except ollama.ResponseError:
yield entities.CommandReturn(
error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常')
)
return
on_progress: bool = False
@@ -99,23 +102,21 @@ class OllamaPullOperator(operator.CommandOperator):
if percentage_completed > progress_count:
progress_count += 10
yield entities.CommandReturn(
text=f"下载进度: {completed}/{total} ({percentage_completed:.2f}%)")
text=f'下载进度: {completed}/{total} ({percentage_completed:.2f}%)'
)
except ollama.ResponseError as e:
yield entities.CommandReturn(text=f"拉取失败: {e.error}")
yield entities.CommandReturn(text=f'拉取失败: {e.error}')
@operator.operator_class(
name="del",
help="ollama模型删除",
privilege=2,
parent_class=OllamaOperator
name='del', help='ollama模型删除', privilege=2, parent_class=OllamaOperator
)
class OllamaDelOperator(operator.CommandOperator):
async def execute(
self, context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
try:
ret: str = ollama.delete(model=context.crt_params[0])['status']
except ollama.ResponseError as e:
ret = f"{e.error}"
ret = f'{e.error}'
yield entities.CommandReturn(text=ret)

View File

@@ -2,31 +2,30 @@ from __future__ import annotations
import typing
import traceback
from .. import operator, entities, cmdmgr, errors
from ...core import app
from .. import operator, entities, errors
@operator.operator_class(
name="plugin",
help="插件操作",
usage="!plugin\n!plugin get <插件仓库地址>\n!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>"
name='plugin',
help='插件操作',
usage='!plugin\n!plugin get <插件仓库地址>\n!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>',
)
class PluginOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
plugin_list = self.ap.plugin_mgr.plugins()
reply_str = "所有插件({}):\n".format(len(plugin_list))
reply_str = '所有插件({}):\n'.format(len(plugin_list))
idx = 0
for plugin in plugin_list:
reply_str += "\n#{} {} {}\n{}\nv{}\n作者: {}\n"\
.format((idx+1), plugin.plugin_name,
"[已禁用]" if not plugin.enabled else "",
plugin.plugin_description,
plugin.plugin_version, plugin.plugin_author)
reply_str += '\n#{} {} {}\n{}\nv{}\n作者: {}\n'.format(
(idx + 1),
plugin.plugin_name,
'[已禁用]' if not plugin.enabled else '',
plugin.plugin_description,
plugin.plugin_version,
plugin.plugin_author,
)
idx += 1
@@ -34,48 +33,42 @@ class PluginOperator(operator.CommandOperator):
@operator.operator_class(
name="get",
help="安装插件",
privilege=2,
parent_class=PluginOperator
name='get', help='安装插件', privilege=2, parent_class=PluginOperator
)
class PluginGetOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件仓库地址'))
yield entities.CommandReturn(
error=errors.ParamNotEnoughError('请提供插件仓库地址')
)
else:
repo = context.crt_params[0]
yield entities.CommandReturn(text="正在安装插件...")
yield entities.CommandReturn(text='正在安装插件...')
try:
await self.ap.plugin_mgr.install_plugin(repo)
yield entities.CommandReturn(text="插件安装成功,请重启程序以加载插件")
yield entities.CommandReturn(text='插件安装成功,请重启程序以加载插件')
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件安装失败: "+str(e)))
yield entities.CommandReturn(
error=errors.CommandError('插件安装失败: ' + str(e))
)
@operator.operator_class(
name="update",
help="更新插件",
privilege=2,
parent_class=PluginOperator
name='update', help='更新插件', privilege=2, parent_class=PluginOperator
)
class PluginUpdateOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
yield entities.CommandReturn(
error=errors.ParamNotEnoughError('请提供插件名称')
)
else:
plugin_name = context.crt_params[0]
@@ -83,36 +76,34 @@ class PluginUpdateOperator(operator.CommandOperator):
plugin_container = self.ap.plugin_mgr.get_plugin_by_name(plugin_name)
if plugin_container is not None:
yield entities.CommandReturn(text="正在更新插件...")
yield entities.CommandReturn(text='正在更新插件...')
await self.ap.plugin_mgr.update_plugin(plugin_name)
yield entities.CommandReturn(text="插件更新成功,请重启程序以加载插件")
yield entities.CommandReturn(
text='插件更新成功,请重启程序以加载插件'
)
else:
yield entities.CommandReturn(error=errors.CommandError("插件更新失败: 未找到插件"))
yield entities.CommandReturn(
error=errors.CommandError('插件更新失败: 未找到插件')
)
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e)))
yield entities.CommandReturn(
error=errors.CommandError('插件更新失败: ' + str(e))
)
@operator.operator_class(
name="all",
help="更新所有插件",
privilege=2,
parent_class=PluginUpdateOperator
name='all', help='更新所有插件', privilege=2, parent_class=PluginUpdateOperator
)
class PluginUpdateAllOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
try:
plugins = [
p.plugin_name
for p in self.ap.plugin_mgr.plugins()
]
plugins = [p.plugin_name for p in self.ap.plugin_mgr.plugins()]
if plugins:
yield entities.CommandReturn(text="正在更新插件...")
yield entities.CommandReturn(text='正在更新插件...')
updated = []
try:
for plugin_name in plugins:
@@ -120,30 +111,32 @@ class PluginUpdateAllOperator(operator.CommandOperator):
updated.append(plugin_name)
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e)))
yield entities.CommandReturn(text="已更新插件: {}".format(", ".join(updated)))
yield entities.CommandReturn(
error=errors.CommandError('插件更新失败: ' + str(e))
)
yield entities.CommandReturn(
text='已更新插件: {}'.format(', '.join(updated))
)
else:
yield entities.CommandReturn(text="没有可更新的插件")
yield entities.CommandReturn(text='没有可更新的插件')
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e)))
yield entities.CommandReturn(
error=errors.CommandError('插件更新失败: ' + str(e))
)
@operator.operator_class(
name="del",
help="删除插件",
privilege=2,
parent_class=PluginOperator
name='del', help='删除插件', privilege=2, parent_class=PluginOperator
)
class PluginDelOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
yield entities.CommandReturn(
error=errors.ParamNotEnoughError('请提供插件名称')
)
else:
plugin_name = context.crt_params[0]
@@ -151,67 +144,81 @@ class PluginDelOperator(operator.CommandOperator):
plugin_container = self.ap.plugin_mgr.get_plugin_by_name(plugin_name)
if plugin_container is not None:
yield entities.CommandReturn(text="正在删除插件...")
yield entities.CommandReturn(text='正在删除插件...')
await self.ap.plugin_mgr.uninstall_plugin(plugin_name)
yield entities.CommandReturn(text="插件删除成功,请重启程序以加载插件")
yield entities.CommandReturn(
text='插件删除成功,请重启程序以加载插件'
)
else:
yield entities.CommandReturn(error=errors.CommandError("插件删除失败: 未找到插件"))
yield entities.CommandReturn(
error=errors.CommandError('插件删除失败: 未找到插件')
)
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件删除失败: "+str(e)))
yield entities.CommandReturn(
error=errors.CommandError('插件删除失败: ' + str(e))
)
@operator.operator_class(
name="on",
help="启用插件",
privilege=2,
parent_class=PluginOperator
name='on', help='启用插件', privilege=2, parent_class=PluginOperator
)
class PluginEnableOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
yield entities.CommandReturn(
error=errors.ParamNotEnoughError('请提供插件名称')
)
else:
plugin_name = context.crt_params[0]
try:
if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, True):
yield entities.CommandReturn(text="已启用插件: {}".format(plugin_name))
yield entities.CommandReturn(
text='已启用插件: {}'.format(plugin_name)
)
else:
yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: 未找到插件 {}".format(plugin_name)))
yield entities.CommandReturn(
error=errors.CommandError(
'插件状态修改失败: 未找到插件 {}'.format(plugin_name)
)
)
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: "+str(e)))
yield entities.CommandReturn(
error=errors.CommandError('插件状态修改失败: ' + str(e))
)
@operator.operator_class(
name="off",
help="禁用插件",
privilege=2,
parent_class=PluginOperator
name='off', help='禁用插件', privilege=2, parent_class=PluginOperator
)
class PluginDisableOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
if len(context.crt_params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
yield entities.CommandReturn(
error=errors.ParamNotEnoughError('请提供插件名称')
)
else:
plugin_name = context.crt_params[0]
try:
if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, False):
yield entities.CommandReturn(text="已禁用插件: {}".format(plugin_name))
yield entities.CommandReturn(
text='已禁用插件: {}'.format(plugin_name)
)
else:
yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: 未找到插件 {}".format(plugin_name)))
yield entities.CommandReturn(
error=errors.CommandError(
'插件状态修改失败: 未找到插件 {}'.format(plugin_name)
)
)
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: "+str(e)))
yield entities.CommandReturn(
error=errors.CommandError('插件状态修改失败: ' + str(e))
)

View File

@@ -2,28 +2,23 @@ from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="prompt",
help="查看当前对话的前文",
usage='!prompt'
)
@operator.operator_class(name='prompt', help='查看当前对话的前文', usage='!prompt')
class PromptOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行
"""
"""执行"""
if context.session.using_conversation is None:
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
yield entities.CommandReturn(
error=errors.CommandOperationError('当前没有对话')
)
else:
reply_str = '当前对话所有内容:\n\n'
for msg in context.session.using_conversation.messages:
reply_str += f"{msg.role}: {msg.content}\n"
reply_str += f'{msg.role}: {msg.content}\n'
yield entities.CommandReturn(text=reply_str)
yield entities.CommandReturn(text=reply_str)

View File

@@ -2,26 +2,22 @@ from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="resend",
help="重发当前会话的最后一条消息",
usage='!resend'
name='resend', help='重发当前会话的最后一条消息', usage='!resend'
)
class ResendOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
# 回滚到最后一条用户message前
if context.session.using_conversation is None:
yield entities.CommandReturn(error=errors.CommandError("当前没有对话"))
yield entities.CommandReturn(error=errors.CommandError('当前没有对话'))
else:
conv_msg = context.session.using_conversation.messages
# 倒序一直删到最后一条用户message
while len(conv_msg) > 0 and conv_msg[-1].role != 'user':
conv_msg.pop()
@@ -31,4 +27,4 @@ class ResendOperator(operator.CommandOperator):
conv_msg.pop()
# 不重发了,提示用户已删除就行了
yield entities.CommandReturn(text="已删除最后一次请求记录")
yield entities.CommandReturn(text='已删除最后一次请求记录')

View File

@@ -2,22 +2,15 @@ from __future__ import annotations
import typing
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities
@operator.operator_class(
name="reset",
help="重置当前会话",
usage='!reset'
)
@operator.operator_class(name='reset', help='重置当前会话', usage='!reset')
class ResetOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行
"""
"""执行"""
context.session.using_conversation = None
yield entities.CommandReturn(text="已重置当前会话")
yield entities.CommandReturn(text='已重置当前会话')

View File

@@ -3,28 +3,22 @@ from __future__ import annotations
import typing
import traceback
from .. import operator, entities, cmdmgr, errors
from .. import operator, entities, errors
@operator.operator_class(
name="update",
help="更新程序",
usage='!update',
privilege=2
)
@operator.operator_class(name='update', help='更新程序', usage='!update', privilege=2)
class UpdateCommand(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
try:
yield entities.CommandReturn(text="正在进行更新...")
yield entities.CommandReturn(text='正在进行更新...')
if await self.ap.ver_mgr.update_all():
yield entities.CommandReturn(text="更新完成,请重启程序以应用更新")
yield entities.CommandReturn(text='更新完成,请重启程序以应用更新')
else:
yield entities.CommandReturn(text="当前已是最新版本")
yield entities.CommandReturn(text='当前已是最新版本')
except Exception as e:
traceback.print_exc()
yield entities.CommandReturn(error=errors.CommandError("更新失败: "+str(e)))
yield entities.CommandReturn(
error=errors.CommandError('更新失败: ' + str(e))
)

View File

@@ -2,26 +2,20 @@ from __future__ import annotations
import typing
from .. import operator, cmdmgr, entities, errors
from .. import operator, entities
@operator.operator_class(
name="version",
help="显示版本信息",
usage='!version'
)
@operator.operator_class(name='version', help='显示版本信息', usage='!version')
class VersionCommand(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
self, context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
reply_str = f"当前版本: \n{self.ap.ver_mgr.get_current_version()}"
reply_str = f'当前版本: \n{self.ap.ver_mgr.get_current_version()}'
try:
if await self.ap.ver_mgr.is_new_version_available():
reply_str += "\n\n有新版本可用。"
except:
reply_str += '\n\n有新版本可用。'
except Exception:
pass
yield entities.CommandReturn(text=reply_str.strip())
yield entities.CommandReturn(text=reply_str.strip())