mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 12:05:54 +00:00
chore: adjust dir structure
This commit is contained in:
0
src/langbot/pkg/command/__init__.py
Normal file
0
src/langbot/pkg/command/__init__.py
Normal file
110
src/langbot/pkg/command/cmdmgr.py
Normal file
110
src/langbot/pkg/command/cmdmgr.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from ..core import app
|
||||
from . import operator
|
||||
from ..utils import importutil
|
||||
import langbot_plugin.api.entities.builtin.provider.session as provider_session
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
from langbot_plugin.api.entities.builtin.command import context as command_context, errors as command_errors
|
||||
|
||||
# 引入所有算子以便注册
|
||||
from . import operators
|
||||
|
||||
importutil.import_modules_in_pkg(operators)
|
||||
|
||||
|
||||
class CommandManager:
|
||||
ap: app.Application
|
||||
|
||||
cmd_list: list[operator.CommandOperator]
|
||||
"""
|
||||
Runtime command list, flat storage, each object contains a reference to the corresponding child node
|
||||
"""
|
||||
|
||||
def __init__(self, ap: app.Application):
|
||||
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, [])
|
||||
|
||||
# 应用命令权限配置
|
||||
# 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]
|
||||
|
||||
# 实例化所有类
|
||||
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__]
|
||||
|
||||
# 初始化所有类
|
||||
for cmd in self.cmd_list:
|
||||
await cmd.initialize()
|
||||
|
||||
async def _execute(
|
||||
self,
|
||||
context: command_context.ExecuteContext,
|
||||
operator_list: list[operator.CommandOperator],
|
||||
operator: operator.CommandOperator = None,
|
||||
bound_plugins: list[str] | None = None,
|
||||
) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
|
||||
"""执行命令"""
|
||||
|
||||
command_list = await self.ap.plugin_connector.list_commands(bound_plugins)
|
||||
|
||||
for command in command_list:
|
||||
if command.metadata.name == context.command:
|
||||
async for ret in self.ap.plugin_connector.execute_command(context, bound_plugins):
|
||||
yield ret
|
||||
break
|
||||
else:
|
||||
yield command_context.CommandReturn(error=command_errors.CommandNotFoundError(context.command))
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
command_text: str,
|
||||
full_command_text: str,
|
||||
query: pipeline_query.Query,
|
||||
session: provider_session.Session,
|
||||
) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
|
||||
"""执行命令"""
|
||||
|
||||
privilege = 1
|
||||
|
||||
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.instance_config.data['admins']:
|
||||
privilege = 2
|
||||
|
||||
ctx = command_context.ExecuteContext(
|
||||
query_id=query.query_id,
|
||||
session=session,
|
||||
command_text=command_text,
|
||||
full_command_text=full_command_text,
|
||||
command='',
|
||||
crt_command='',
|
||||
params=command_text.split(' '),
|
||||
crt_params=command_text.split(' '),
|
||||
privilege=privilege,
|
||||
)
|
||||
|
||||
ctx.command = ctx.params[0]
|
||||
|
||||
ctx.shift()
|
||||
|
||||
# Get bound plugins from query
|
||||
bound_plugins = query.variables.get('_pipeline_bound_plugins', None)
|
||||
|
||||
async for ret in self._execute(ctx, self.cmd_list, bound_plugins=bound_plugins):
|
||||
yield ret
|
||||
112
src/langbot/pkg/command/operator.py
Normal file
112
src/langbot/pkg/command/operator.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
import abc
|
||||
|
||||
from ..core import app
|
||||
from langbot_plugin.api.entities.builtin.command import context as command_context
|
||||
|
||||
|
||||
preregistered_operators: list[typing.Type[CommandOperator]] = []
|
||||
"""预注册命令算子列表。在初始化时,所有算子类会被注册到此列表中。"""
|
||||
|
||||
|
||||
def operator_class(
|
||||
name: str,
|
||||
help: str = '',
|
||||
usage: str = None,
|
||||
alias: list[str] = [],
|
||||
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 "".
|
||||
usage (str, optional): 使用说明. Defaults to None.
|
||||
alias (list[str], optional): 别名. Defaults to [].
|
||||
privilege (int, optional): 权限,1为普通用户可用,2为仅管理员可用. Defaults to 1.
|
||||
parent_class (typing.Type[CommandOperator], optional): 父节点,若为None则为顶级命令. Defaults to None.
|
||||
|
||||
Returns:
|
||||
typing.Callable[[typing.Type[CommandOperator]], typing.Type[CommandOperator]]: 装饰器
|
||||
"""
|
||||
|
||||
def decorator(cls: typing.Type[CommandOperator]) -> typing.Type[CommandOperator]:
|
||||
assert issubclass(cls, CommandOperator)
|
||||
|
||||
cls.name = name
|
||||
cls.alias = alias
|
||||
cls.help = help
|
||||
cls.usage = usage
|
||||
cls.parent_class = parent_class
|
||||
cls.lowest_privilege = privilege
|
||||
|
||||
preregistered_operators.append(cls)
|
||||
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class CommandOperator(metaclass=abc.ABCMeta):
|
||||
"""命令算子抽象类
|
||||
|
||||
以下的参数均不需要在子类中设置,只需要在使用装饰器注册类时作为参数传递即可。
|
||||
命令支持级联,即一个命令可以有多个子命令,子命令可以有子命令,以此类推。
|
||||
处理命令时,若有子命令,会以当前参数列表的第一个参数去匹配子命令,若匹配成功,则转移到子命令中执行。
|
||||
若没有匹配成功或没有子命令,则执行当前命令。
|
||||
"""
|
||||
|
||||
ap: app.Application
|
||||
|
||||
name: str
|
||||
"""名称,搜索到时若符合则使用"""
|
||||
|
||||
path: str
|
||||
"""路径,所有父节点的name的连接,用于定义命令权限,由管理器在初始化时自动设置。
|
||||
"""
|
||||
|
||||
alias: list[str]
|
||||
"""同name"""
|
||||
|
||||
help: str
|
||||
"""此节点的帮助信息"""
|
||||
|
||||
usage: str = None
|
||||
"""用法"""
|
||||
|
||||
parent_class: typing.Union[typing.Type[CommandOperator], None] = None
|
||||
"""父节点类。标记以供管理器在初始化时编织父子关系。"""
|
||||
|
||||
lowest_privilege: int = 0
|
||||
"""最低权限。若权限低于此值,则不予执行。"""
|
||||
|
||||
children: list[CommandOperator]
|
||||
"""子节点。解析命令时,若节点有子节点,则以下一个参数去匹配子节点,
|
||||
若有匹配中的,转移到子节点中执行,若没有匹配中的或没有子节点,执行此节点。"""
|
||||
|
||||
def __init__(self, ap: app.Application):
|
||||
self.ap = ap
|
||||
self.children = []
|
||||
|
||||
async def initialize(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
async def execute(
|
||||
self, context: command_context.ExecuteContext
|
||||
) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
|
||||
"""实现此方法以执行命令
|
||||
|
||||
支持多次yield以返回多个结果。
|
||||
例如:一个安装插件的命令,可能会有下载、解压、安装等多个步骤,每个步骤都可以返回一个结果。
|
||||
|
||||
Args:
|
||||
context (command_context.ExecuteContext): 命令执行上下文
|
||||
|
||||
Yields:
|
||||
command_context.CommandReturn: 命令返回封装
|
||||
"""
|
||||
pass
|
||||
0
src/langbot/pkg/command/operators/__init__.py
Normal file
0
src/langbot/pkg/command/operators/__init__.py
Normal file
48
src/langbot/pkg/command/operators/delc.py
Normal file
48
src/langbot/pkg/command/operators/delc.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# from __future__ import annotations
|
||||
|
||||
# import typing
|
||||
|
||||
# from .. import operator
|
||||
# from langbot_plugin.api.entities.builtin.command import context as command_context, errors as command_errors
|
||||
|
||||
|
||||
# @operator.operator_class(name='del', help='删除当前会话的历史记录', usage='!del <序号>\n!del all')
|
||||
# class DelOperator(operator.CommandOperator):
|
||||
# async def execute(
|
||||
# self, context: command_context.ExecuteContext
|
||||
# ) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
|
||||
# if context.session.conversations:
|
||||
# delete_index = 0
|
||||
# if len(context.crt_params) > 0:
|
||||
# try:
|
||||
# delete_index = int(context.crt_params[0])
|
||||
# except Exception:
|
||||
# yield command_context.CommandReturn(error=command_errors.CommandOperationError('索引必须是整数'))
|
||||
# return
|
||||
|
||||
# if delete_index < 0 or delete_index >= len(context.session.conversations):
|
||||
# yield command_context.CommandReturn(error=command_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 command_context.CommandReturn(text=f'已删除对话: {delete_index}')
|
||||
# else:
|
||||
# yield command_context.CommandReturn(error=command_errors.CommandOperationError('当前没有对话'))
|
||||
|
||||
|
||||
# @operator.operator_class(name='all', help='删除此会话的所有历史记录', parent_class=DelOperator)
|
||||
# class DelAllOperator(operator.CommandOperator):
|
||||
# async def execute(
|
||||
# self, context: command_context.ExecuteContext
|
||||
# ) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
|
||||
# context.session.conversations = []
|
||||
# context.session.using_conversation = None
|
||||
|
||||
# yield command_context.CommandReturn(text='已删除所有对话')
|
||||
33
src/langbot/pkg/command/operators/last.py
Normal file
33
src/langbot/pkg/command/operators/last.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# from __future__ import annotations
|
||||
|
||||
# import typing
|
||||
|
||||
|
||||
# from .. import operator
|
||||
# from langbot_plugin.api.entities.builtin.command import context as command_context, errors as command_errors
|
||||
|
||||
|
||||
# @operator.operator_class(name='last', help='切换到前一个对话', usage='!last')
|
||||
# class LastOperator(operator.CommandOperator):
|
||||
# async def execute(
|
||||
# self, context: command_context.ExecuteContext
|
||||
# ) -> typing.AsyncGenerator[command_context.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:
|
||||
# if index == 0:
|
||||
# yield command_context.CommandReturn(
|
||||
# error=command_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')
|
||||
|
||||
# yield command_context.CommandReturn(
|
||||
# text=f'已切换到上一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].readable_str()}'
|
||||
# )
|
||||
# return
|
||||
# else:
|
||||
# yield command_context.CommandReturn(error=command_errors.CommandOperationError('当前没有对话'))
|
||||
51
src/langbot/pkg/command/operators/list.py
Normal file
51
src/langbot/pkg/command/operators/list.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# from __future__ import annotations
|
||||
|
||||
# import typing
|
||||
|
||||
# from .. import operator
|
||||
# from langbot_plugin.api.entities.builtin.command import context as command_context, errors as command_errors
|
||||
|
||||
|
||||
# @operator.operator_class(name='list', help='列出此会话中的所有历史对话', usage='!list\n!list <页码>')
|
||||
# class ListOperator(operator.CommandOperator):
|
||||
# async def execute(
|
||||
# self, context: command_context.ExecuteContext
|
||||
# ) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
|
||||
# page = 0
|
||||
|
||||
# if len(context.crt_params) > 0:
|
||||
# try:
|
||||
# page = int(context.crt_params[0] - 1)
|
||||
# except Exception:
|
||||
# yield command_context.CommandReturn(error=command_errors.CommandOperationError('页码应为整数'))
|
||||
# return
|
||||
|
||||
# record_per_page = 10
|
||||
|
||||
# content = ''
|
||||
|
||||
# index = 0
|
||||
|
||||
# using_conv_index = 0
|
||||
|
||||
# for conv in context.session.conversations[::-1]:
|
||||
# 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'
|
||||
# )
|
||||
# index += 1
|
||||
|
||||
# if content == '':
|
||||
# content = '无'
|
||||
# else:
|
||||
# if context.session.using_conversation is None:
|
||||
# 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 command_context.CommandReturn(text=f'第 {page + 1} 页 (时间倒序):\n{content}')
|
||||
32
src/langbot/pkg/command/operators/next.py
Normal file
32
src/langbot/pkg/command/operators/next.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# from __future__ import annotations
|
||||
|
||||
# import typing
|
||||
|
||||
# from .. import operator
|
||||
# from langbot_plugin.api.entities.builtin.command import context as command_context, errors as command_errors
|
||||
|
||||
|
||||
# @operator.operator_class(name='next', help='切换到后一个对话', usage='!next')
|
||||
# class NextOperator(operator.CommandOperator):
|
||||
# async def execute(
|
||||
# self, context: command_context.ExecuteContext
|
||||
# ) -> typing.AsyncGenerator[command_context.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 command_context.CommandReturn(
|
||||
# error=command_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')
|
||||
|
||||
# yield command_context.CommandReturn(
|
||||
# text=f'已切换到后一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}'
|
||||
# )
|
||||
# return
|
||||
# else:
|
||||
# yield command_context.CommandReturn(error=command_errors.CommandOperationError('当前没有对话'))
|
||||
23
src/langbot/pkg/command/operators/prompt.py
Normal file
23
src/langbot/pkg/command/operators/prompt.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# from __future__ import annotations
|
||||
|
||||
# import typing
|
||||
|
||||
# from .. import operator
|
||||
# from langbot_plugin.api.entities.builtin.command import context as command_context, errors as command_errors
|
||||
|
||||
|
||||
# @operator.operator_class(name='prompt', help='查看当前对话的前文', usage='!prompt')
|
||||
# class PromptOperator(operator.CommandOperator):
|
||||
# async def execute(
|
||||
# self, context: command_context.ExecuteContext
|
||||
# ) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
|
||||
# """执行"""
|
||||
# if context.session.using_conversation is None:
|
||||
# yield command_context.CommandReturn(error=command_errors.CommandOperationError('当前没有对话'))
|
||||
# else:
|
||||
# reply_str = '当前对话所有内容:\n\n'
|
||||
|
||||
# for msg in context.session.using_conversation.messages:
|
||||
# reply_str += f'{msg.role}: {msg.content}\n'
|
||||
|
||||
# yield command_context.CommandReturn(text=reply_str)
|
||||
29
src/langbot/pkg/command/operators/resend.py
Normal file
29
src/langbot/pkg/command/operators/resend.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# from __future__ import annotations
|
||||
|
||||
# import typing
|
||||
|
||||
# from .. import operator
|
||||
# from langbot_plugin.api.entities.builtin.command import context as command_context, errors as command_errors
|
||||
|
||||
|
||||
# @operator.operator_class(name='resend', help='重发当前会话的最后一条消息', usage='!resend')
|
||||
# class ResendOperator(operator.CommandOperator):
|
||||
# async def execute(
|
||||
# self, context: command_context.ExecuteContext
|
||||
# ) -> typing.AsyncGenerator[command_context.CommandReturn, None]:
|
||||
# # 回滚到最后一条用户message前
|
||||
# if context.session.using_conversation is None:
|
||||
# yield command_context.CommandReturn(error=command_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()
|
||||
|
||||
# if len(conv_msg) > 0:
|
||||
# # 删除最后一条用户message
|
||||
# conv_msg.pop()
|
||||
|
||||
# # 不重发了,提示用户已删除就行了
|
||||
# yield command_context.CommandReturn(text='已删除最后一次请求记录')
|
||||
Reference in New Issue
Block a user