mirror of
https://github.com/xuthus83/LittlePaimon.git
synced 2025-04-12 23:29:37 +08:00
🎨 格式化代码,适配新amis
This commit is contained in:
parent
7dad2158d7
commit
ebe4b67e6c
@ -16,51 +16,63 @@ from .config import config_manager
|
||||
from . import web_api, web_page
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name='群聊学习',
|
||||
description='群聊学习',
|
||||
usage='群聊学习',
|
||||
name="群聊学习",
|
||||
description="群聊学习",
|
||||
usage="群聊学习",
|
||||
extra={
|
||||
'author': '惜月',
|
||||
'priority': 16,
|
||||
}
|
||||
"author": "惜月",
|
||||
"priority": 16,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def ChatRule(event: GroupMessageEvent, state: T_State) -> bool:
|
||||
if answers := await LearningChat(event).answer():
|
||||
state['answers'] = answers
|
||||
state["answers"] = answers
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
learning_chat = on_message(priority=99, block=False, rule=Rule(ChatRule), permission=GROUP, state={
|
||||
'pm_name': '群聊学习',
|
||||
'pm_description': '(被动技能)bot会学习群友们的发言',
|
||||
'pm_usage': '群聊学习',
|
||||
'pm_priority': 1
|
||||
})
|
||||
|
||||
learning_chat = on_message(
|
||||
priority=99,
|
||||
block=False,
|
||||
rule=Rule(ChatRule),
|
||||
permission=GROUP,
|
||||
state={
|
||||
"pm_name": "群聊学习",
|
||||
"pm_description": "(被动技能)bot会学习群友们的发言",
|
||||
"pm_usage": "群聊学习",
|
||||
"pm_priority": 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@learning_chat.handle()
|
||||
async def _(event: GroupMessageEvent, answers=Arg('answers')):
|
||||
async def _(event: GroupMessageEvent, answers=Arg("answers")):
|
||||
for answer in answers:
|
||||
try:
|
||||
logger.info('群聊学习', f'{NICKNAME}将向群<m>{event.group_id}</m>回复<m>"{answer}"</m>')
|
||||
logger.info(
|
||||
"群聊学习", f'{NICKNAME}将向群<m>{event.group_id}</m>回复<m>"{answer}"</m>'
|
||||
)
|
||||
msg = await learning_chat.send(Message(answer))
|
||||
await ChatMessage.create(group_id=event.group_id,
|
||||
user_id=event.self_id,
|
||||
message_id=msg['message_id'],
|
||||
message=answer,
|
||||
raw_message=answer,
|
||||
time=int(time.time()),
|
||||
plain_text=Message(answer).extract_plain_text())
|
||||
await ChatMessage.create(
|
||||
group_id=event.group_id,
|
||||
user_id=event.self_id,
|
||||
message_id=msg["message_id"],
|
||||
message=answer,
|
||||
raw_message=answer,
|
||||
time=int(time.time()),
|
||||
plain_text=Message(answer).extract_plain_text(),
|
||||
)
|
||||
await asyncio.sleep(random.random() + 0.5)
|
||||
except ActionFailed:
|
||||
logger.info('群聊学习', f'{NICKNAME}向群<m>{event.group_id}</m>的回复<m>"{answer}"</m>发送<r>失败,可能处于风控中</r>')
|
||||
logger.info(
|
||||
"群聊学习",
|
||||
f'{NICKNAME}向群<m>{event.group_id}</m>的回复<m>"{answer}"</m>发送<r>失败,可能处于风控中</r>',
|
||||
)
|
||||
|
||||
|
||||
@scheduler.scheduled_job('interval', minutes=3, misfire_grace_time=5)
|
||||
@scheduler.scheduled_job("interval", minutes=3, misfire_grace_time=5)
|
||||
async def speak_up():
|
||||
if not config_manager.config.total_enable:
|
||||
return
|
||||
@ -73,15 +85,22 @@ async def speak_up():
|
||||
group_id, messages = speak
|
||||
for msg in messages:
|
||||
try:
|
||||
logger.info('群聊学习', f'{NICKNAME}向群<m>{group_id}</m>主动发言<m>"{msg}"</m>')
|
||||
send_result = await bot.send_group_msg(group_id=group_id, message=Message(msg))
|
||||
await ChatMessage.create(group_id=group_id,
|
||||
user_id=int(bot.self_id),
|
||||
message_id=send_result['message_id'],
|
||||
message=msg,
|
||||
raw_message=msg,
|
||||
time=int(time.time()),
|
||||
plain_text=Message(msg).extract_plain_text())
|
||||
logger.info("群聊学习", f'{NICKNAME}向群<m>{group_id}</m>主动发言<m>"{msg}"</m>')
|
||||
send_result = await bot.send_group_msg(
|
||||
group_id=group_id, message=Message(msg)
|
||||
)
|
||||
await ChatMessage.create(
|
||||
group_id=group_id,
|
||||
user_id=int(bot.self_id),
|
||||
message_id=send_result["message_id"],
|
||||
message=msg,
|
||||
raw_message=msg,
|
||||
time=int(time.time()),
|
||||
plain_text=Message(msg).extract_plain_text(),
|
||||
)
|
||||
await asyncio.sleep(random.randint(2, 4))
|
||||
except ActionFailed:
|
||||
logger.info('群聊学习', f'{NICKNAME}向群<m>{group_id}</m>主动发言<m>"{msg}"</m><r>发送失败,可能处于风控中</r>')
|
||||
logger.info(
|
||||
"群聊学习",
|
||||
f'{NICKNAME}向群<m>{group_id}</m>主动发言<m>"{msg}"</m><r>发送失败,可能处于风控中</r>',
|
||||
)
|
||||
|
@ -7,19 +7,19 @@ from LittlePaimon.utils.files import load_yaml, save_yaml
|
||||
|
||||
|
||||
class ChatGroupConfig(BaseModel):
|
||||
enable: bool = Field(True, alias='群聊学习开关')
|
||||
ban_words: List[str] = Field([], alias='屏蔽词')
|
||||
ban_users: List[int] = Field([], alias='屏蔽用户')
|
||||
answer_threshold: int = Field(4, alias='回复阈值')
|
||||
answer_threshold_weights: List[int] = Field([10, 30, 60], alias='回复阈值权重')
|
||||
repeat_threshold: int = Field(3, alias='复读阈值')
|
||||
break_probability: float = Field(0.25, alias='打断复读概率')
|
||||
speak_enable: bool = Field(True, alias='主动发言开关')
|
||||
speak_threshold: int = Field(5, alias='主动发言阈值')
|
||||
speak_min_interval: int = Field(300, alias='主动发言最小间隔')
|
||||
speak_continuously_probability: float = Field(0.5, alias='连续主动发言概率')
|
||||
speak_continuously_max_len: int = Field(3, alias='最大连续主动发言句数')
|
||||
speak_poke_probability: float = Field(0.5, alias='主动发言附带戳一戳概率')
|
||||
enable: bool = Field(default=True, alias="群聊学习开关")
|
||||
ban_words: List[str] = Field(default_factory=list, alias="屏蔽词")
|
||||
ban_users: List[int] = Field(default_factory=list, alias="屏蔽用户")
|
||||
answer_threshold: int = Field(default=4, alias="回复阈值")
|
||||
answer_threshold_weights: List[int] = Field(default=[10, 30, 60], alias="回复阈值权重")
|
||||
repeat_threshold: int = Field(default=3, alias="复读阈值")
|
||||
break_probability: float = Field(default=0.25, alias="打断复读概率")
|
||||
speak_enable: bool = Field(default=True, alias="主动发言开关")
|
||||
speak_threshold: int = Field(default=5, alias="主动发言阈值")
|
||||
speak_min_interval: int = Field(default=300, alias="主动发言最小间隔")
|
||||
speak_continuously_probability: float = Field(default=0.5, alias="连续主动发言概率")
|
||||
speak_continuously_max_len: int = Field(default=3, alias="最大连续主动发言句数")
|
||||
speak_poke_probability: float = Field(default=0.5, alias="主动发言附带戳一戳概率")
|
||||
|
||||
def update(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
@ -28,14 +28,14 @@ class ChatGroupConfig(BaseModel):
|
||||
|
||||
|
||||
class ChatConfig(BaseModel):
|
||||
total_enable: bool = Field(True, alias='群聊学习总开关')
|
||||
ban_words: List[str] = Field([], alias='全局屏蔽词')
|
||||
ban_users: List[int] = Field([], alias='全局屏蔽用户')
|
||||
KEYWORDS_SIZE: int = Field(3, alias='单句关键词分词数量')
|
||||
cross_group_threshold: int = Field(3, alias='跨群回复阈值')
|
||||
learn_max_count: int = Field(6, alias='最高学习次数')
|
||||
dictionary: List[str] = Field([], alias='自定义词典')
|
||||
group_config: Dict[int, ChatGroupConfig] = Field({}, alias='分群配置')
|
||||
total_enable: bool = Field(default=True, alias="群聊学习总开关")
|
||||
ban_words: List[str] = Field(default_factory=list, alias="全局屏蔽词")
|
||||
ban_users: List[int] = Field(default_factory=list, alias="全局屏蔽用户")
|
||||
KEYWORDS_SIZE: int = Field(default=3, alias="单句关键词分词数量")
|
||||
cross_group_threshold: int = Field(default=3, alias="跨群回复阈值")
|
||||
learn_max_count: int = Field(default=6, alias="最高学习次数")
|
||||
dictionary: List[str] = Field(default_factory=list, alias="自定义词典")
|
||||
group_config: Dict[int, ChatGroupConfig] = Field(default_factory=dict, alias="分群配置")
|
||||
|
||||
def update(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
@ -44,7 +44,6 @@ class ChatConfig(BaseModel):
|
||||
|
||||
|
||||
class ChatConfigManager:
|
||||
|
||||
def __init__(self):
|
||||
self.file_path = LEARNING_CHAT_CONFIG
|
||||
if self.file_path.exists():
|
||||
|
@ -21,16 +21,27 @@ from .config import config_manager
|
||||
|
||||
chat_config = config_manager.config
|
||||
command_start_ = command_start.copy()
|
||||
if '' in command_start_:
|
||||
command_start_.remove('')
|
||||
if "" in command_start_:
|
||||
command_start_.remove("")
|
||||
|
||||
NO_PERMISSION_WORDS = [f'{NICKNAME}就喜欢说这个,哼!', f'你管得着{NICKNAME}吗!']
|
||||
ENABLE_WORDS = [f'{NICKNAME}会尝试学你们说怪话!', f'好的呢,让{NICKNAME}学学你们的说话方式~']
|
||||
DISABLE_WORDS = [f'好好好,{NICKNAME}不学说话就是了!', f'果面呐噻,{NICKNAME}以后不学了...']
|
||||
SORRY_WORDS = [f'{NICKNAME}知道错了...达咩!', f'{NICKNAME}不会再这么说了...', f'果面呐噻,{NICKNAME}说错话了...']
|
||||
DOUBT_WORDS = [f'{NICKNAME}有说什么奇怪的话吗?']
|
||||
BREAK_REPEAT_WORDS = ['打断复读', '打断!']
|
||||
ALL_WORDS = NO_PERMISSION_WORDS + SORRY_WORDS + DOUBT_WORDS + ENABLE_WORDS + DISABLE_WORDS + BREAK_REPEAT_WORDS
|
||||
NO_PERMISSION_WORDS = [f"{NICKNAME}就喜欢说这个,哼!", f"你管得着{NICKNAME}吗!"]
|
||||
ENABLE_WORDS = [f"{NICKNAME}会尝试学你们说怪话!", f"好的呢,让{NICKNAME}学学你们的说话方式~"]
|
||||
DISABLE_WORDS = [f"好好好,{NICKNAME}不学说话就是了!", f"果面呐噻,{NICKNAME}以后不学了..."]
|
||||
SORRY_WORDS = [
|
||||
f"{NICKNAME}知道错了...达咩!",
|
||||
f"{NICKNAME}不会再这么说了...",
|
||||
f"果面呐噻,{NICKNAME}说错话了...",
|
||||
]
|
||||
DOUBT_WORDS = [f"{NICKNAME}有说什么奇怪的话吗?"]
|
||||
BREAK_REPEAT_WORDS = ["打断复读", "打断!"]
|
||||
ALL_WORDS = (
|
||||
NO_PERMISSION_WORDS
|
||||
+ SORRY_WORDS
|
||||
+ DOUBT_WORDS
|
||||
+ ENABLE_WORDS
|
||||
+ DISABLE_WORDS
|
||||
+ BREAK_REPEAT_WORDS
|
||||
)
|
||||
|
||||
|
||||
class Result(IntEnum):
|
||||
@ -50,92 +61,106 @@ class LearningChat:
|
||||
group_id=event.group_id,
|
||||
user_id=event.user_id,
|
||||
message_id=event.message_id,
|
||||
message=re.sub(r'(\[CQ:at,qq=.+])|(\[CQ:reply,id=.+])', '',
|
||||
re.sub(r'(,subType=\d+,url=.+])', r']', event.raw_message)).strip(),
|
||||
message=re.sub(
|
||||
r"(\[CQ:at,qq=.+])|(\[CQ:reply,id=.+])",
|
||||
"",
|
||||
re.sub(r"(,subType=\d+,url=.+])", r"]", event.raw_message),
|
||||
).strip(),
|
||||
raw_message=event.raw_message,
|
||||
plain_text=event.get_plaintext(),
|
||||
time=event.time
|
||||
time=event.time,
|
||||
)
|
||||
else:
|
||||
self.data = ChatMessage(
|
||||
group_id=event.group_id,
|
||||
user_id=event.user_id,
|
||||
message_id=event.message_id,
|
||||
message=re.sub(r'(\[CQ:at,qq=.+])', '',
|
||||
re.sub(r'(,subType=\d+,url=.+])', r']', event.raw_message)).strip(),
|
||||
message=re.sub(
|
||||
r"(\[CQ:at,qq=.+])",
|
||||
"",
|
||||
re.sub(r"(,subType=\d+,url=.+])", r"]", event.raw_message),
|
||||
).strip(),
|
||||
raw_message=event.raw_message,
|
||||
plain_text=event.get_plaintext(),
|
||||
time=event.time
|
||||
time=event.time,
|
||||
)
|
||||
self.reply = None
|
||||
self.bot_id = event.self_id
|
||||
self.to_me = event.to_me or NICKNAME in self.data.message
|
||||
self.role = 'superuser' if event.user_id in SUPERUSERS else event.sender.role
|
||||
self.role = "superuser" if event.user_id in SUPERUSERS else event.sender.role
|
||||
self.config = config_manager.get_group_config(self.data.group_id)
|
||||
self.ban_users = set(chat_config.ban_users + self.config.ban_users)
|
||||
self.ban_words = set(chat_config.ban_words + self.config.ban_words)
|
||||
|
||||
async def _learn(self) -> Result:
|
||||
if self.to_me and any(
|
||||
w in self.data.message for w in {'学说话', '快学', '开启学习'}):
|
||||
if self.to_me and any(w in self.data.message for w in {"学说话", "快学", "开启学习"}):
|
||||
return Result.SetEnable
|
||||
elif self.to_me and any(
|
||||
w in self.data.message for w in {'闭嘴', '别学', '关闭学习'}):
|
||||
elif self.to_me and any(w in self.data.message for w in {"闭嘴", "别学", "关闭学习"}):
|
||||
return Result.SetDisable
|
||||
elif not chat_config.total_enable or not self.config.enable:
|
||||
logger.debug('群聊学习', f'➤该群<m>{self.data.group_id}</m>未开启群聊学习,跳过')
|
||||
logger.debug("群聊学习", f"➤该群<m>{self.data.group_id}</m>未开启群聊学习,跳过")
|
||||
# 如果未开启群聊学习,跳过
|
||||
return Result.Pass
|
||||
elif command_start_ and self.data.message.startswith(tuple(command_start_)):
|
||||
# 以命令前缀开头的消息,跳过
|
||||
logger.debug('群聊学习', '➤该消息以命令前缀开头,跳过')
|
||||
logger.debug("群聊学习", "➤该消息以命令前缀开头,跳过")
|
||||
return Result.Pass
|
||||
elif self.data.user_id in self.ban_users:
|
||||
# 发言人在屏蔽列表中,跳过
|
||||
logger.debug('群聊学习', f'➤发言人<m>{self.data.user_id}</m>在屏蔽列表中,跳过')
|
||||
logger.debug("群聊学习", f"➤发言人<m>{self.data.user_id}</m>在屏蔽列表中,跳过")
|
||||
return Result.Pass
|
||||
elif self.to_me and any(w in self.data.message for w in {'不可以', '达咩', '不能说这'}):
|
||||
elif self.to_me and any(w in self.data.message for w in {"不可以", "达咩", "不能说这"}):
|
||||
# 如果是对某句话进行禁言
|
||||
return Result.Ban
|
||||
elif not await self._check_allow(self.data):
|
||||
# 本消息不合法,跳过
|
||||
logger.debug('群聊学习', '➤消息未通过校验,跳过')
|
||||
logger.debug("群聊学习", "➤消息未通过校验,跳过")
|
||||
return Result.Pass
|
||||
elif self.reply:
|
||||
# 如果是回复消息
|
||||
if not (message := await ChatMessage.filter(message_id=self.reply.message_id).first()):
|
||||
if not (
|
||||
message := await ChatMessage.filter(
|
||||
message_id=self.reply.message_id
|
||||
).first()
|
||||
):
|
||||
# 回复的消息在数据库中有记录
|
||||
logger.debug('群聊学习', '➤回复的消息不在数据库中,跳过')
|
||||
logger.debug("群聊学习", "➤回复的消息不在数据库中,跳过")
|
||||
return Result.Pass
|
||||
if message.user_id in self.ban_users:
|
||||
# 且回复的人不在屏蔽列表中
|
||||
logger.debug('群聊学习', '➤回复的人在屏蔽列表中,跳过')
|
||||
logger.debug("群聊学习", "➤回复的人在屏蔽列表中,跳过")
|
||||
return Result.Pass
|
||||
if not await self._check_allow(message):
|
||||
# 且回复的内容通过校验
|
||||
logger.debug('群聊学习', '➤回复的消息未通过校验,跳过')
|
||||
logger.debug("群聊学习", "➤回复的消息未通过校验,跳过")
|
||||
return Result.Pass
|
||||
# 则将该回复作为该消息的答案
|
||||
await self._set_answer(message)
|
||||
return Result.Learn
|
||||
elif messages := await ChatMessage.filter(group_id=self.data.group_id, time__gte=self.data.time - 3600).limit(
|
||||
5):
|
||||
elif messages := await ChatMessage.filter(
|
||||
group_id=self.data.group_id, time__gte=self.data.time - 3600
|
||||
).limit(5):
|
||||
# 获取本群一个小时内的最后5条消息
|
||||
if messages[0].message == self.data.message:
|
||||
# 判断是否为复读中
|
||||
logger.debug('群聊学习', '➤复读中,跳过')
|
||||
logger.debug("群聊学习", "➤复读中,跳过")
|
||||
return Result.Repeat
|
||||
for message in messages:
|
||||
# 如果5条内有相关信息,就作为该消息的答案
|
||||
if message.user_id not in self.ban_users and set(self.data.keyword_list) & set(
|
||||
message.keyword_list) and self.data.keyword_list != message.keyword_list and await self._check_allow(
|
||||
message):
|
||||
if (
|
||||
message.user_id not in self.ban_users
|
||||
and set(self.data.keyword_list) & set(message.keyword_list)
|
||||
and self.data.keyword_list != message.keyword_list
|
||||
and await self._check_allow(message)
|
||||
):
|
||||
await self._set_answer(message)
|
||||
return Result.Learn
|
||||
# 如果没有相关信息
|
||||
if messages[0].user_id in self.ban_users or not await self._check_allow(messages[0]):
|
||||
if messages[0].user_id in self.ban_users or not await self._check_allow(
|
||||
messages[0]
|
||||
):
|
||||
# 且最后一条消息的发送者不在屏蔽列表中并通过校验
|
||||
logger.debug('群聊学习', '➤最后一条消息未通过校验,跳过')
|
||||
logger.debug("群聊学习", "➤最后一条消息未通过校验,跳过")
|
||||
return Result.Pass
|
||||
# 则作为最后一条消息的答案
|
||||
await self._set_answer(messages[0])
|
||||
@ -150,7 +175,7 @@ class LearningChat:
|
||||
await self.data.save()
|
||||
if result == Result.Ban:
|
||||
# 禁用某句话
|
||||
if self.role not in {'superuser', 'admin', 'owner'}:
|
||||
if self.role not in {"superuser", "admin", "owner"}:
|
||||
# 检查权限
|
||||
return [random.choice(NO_PERMISSION_WORDS)]
|
||||
if self.reply:
|
||||
@ -163,57 +188,79 @@ class LearningChat:
|
||||
return [random.choice(DOUBT_WORDS)]
|
||||
elif result in [Result.SetEnable, Result.SetDisable]:
|
||||
# 检查权限
|
||||
if self.role not in {'superuser', 'admin', 'owner'}:
|
||||
if self.role not in {"superuser", "admin", "owner"}:
|
||||
return [random.choice(NO_PERMISSION_WORDS)]
|
||||
self.config.update(enable=(result == Result.SetEnable))
|
||||
config_manager.config.group_config[self.data.group_id] = self.config
|
||||
config_manager.save()
|
||||
logger.info('群聊学习',
|
||||
f'群<m>{self.data.group_id}</m>{"开启" if result == Result.SetEnable else "关闭"}学习功能')
|
||||
return [random.choice(ENABLE_WORDS if result == Result.SetEnable else DISABLE_WORDS)]
|
||||
logger.info(
|
||||
"群聊学习",
|
||||
f'群<m>{self.data.group_id}</m>{"开启" if result == Result.SetEnable else "关闭"}学习功能',
|
||||
)
|
||||
return [
|
||||
random.choice(
|
||||
ENABLE_WORDS if result == Result.SetEnable else DISABLE_WORDS
|
||||
)
|
||||
]
|
||||
elif result == Result.Pass:
|
||||
# 跳过
|
||||
return None
|
||||
elif result == Result.Repeat:
|
||||
if await ChatMessage.filter(group_id=self.data.group_id,
|
||||
time__gte=self.data.time - 3600).limit(
|
||||
self.config.repeat_threshold + 5).filter(
|
||||
user_id=self.bot_id, message=self.data.message).exists():
|
||||
if (
|
||||
await ChatMessage.filter(
|
||||
group_id=self.data.group_id, time__gte=self.data.time - 3600
|
||||
)
|
||||
.limit(self.config.repeat_threshold + 5)
|
||||
.filter(user_id=self.bot_id, message=self.data.message)
|
||||
.exists()
|
||||
):
|
||||
# 如果在阈值+5条消息内,bot已经回复过这句话,则跳过
|
||||
logger.debug('群聊学习', '➤➤已经复读过了,跳过')
|
||||
logger.debug("群聊学习", "➤➤已经复读过了,跳过")
|
||||
return None
|
||||
if not (messages := await ChatMessage.filter(
|
||||
group_id=self.data.group_id,
|
||||
time__gte=self.data.time - 3600).limit(self.config.repeat_threshold)):
|
||||
if not (
|
||||
messages := await ChatMessage.filter(
|
||||
group_id=self.data.group_id, time__gte=self.data.time - 3600
|
||||
).limit(self.config.repeat_threshold)
|
||||
):
|
||||
return None
|
||||
# 如果达到阈值,且不是全都为同一个人在说,则进行复读
|
||||
if len(messages) >= self.config.repeat_threshold and all(
|
||||
message.message == self.data.message
|
||||
for message in messages) and any(message.user_id != self.data.user_id
|
||||
for message in messages):
|
||||
if (
|
||||
len(messages) >= self.config.repeat_threshold
|
||||
and all(message.message == self.data.message for message in messages)
|
||||
and any(message.user_id != self.data.user_id for message in messages)
|
||||
):
|
||||
if random.random() < self.config.break_probability:
|
||||
logger.debug('群聊学习', '➤➤达到复读阈值,打断复读!')
|
||||
logger.debug("群聊学习", "➤➤达到复读阈值,打断复读!")
|
||||
return [random.choice(BREAK_REPEAT_WORDS)]
|
||||
else:
|
||||
logger.debug('群聊学习', f'➤➤达到复读阈值,复读<m>{messages[0].message}</m>')
|
||||
logger.debug("群聊学习", f"➤➤达到复读阈值,复读<m>{messages[0].message}</m>")
|
||||
return [self.data.message]
|
||||
return None
|
||||
else:
|
||||
# 回复
|
||||
if self.data.is_plain_text and len(self.data.plain_text) <= 1:
|
||||
logger.debug('群聊学习', '➤➤消息过短,不回复')
|
||||
logger.debug("群聊学习", "➤➤消息过短,不回复")
|
||||
return None
|
||||
if not (context := await ChatContext.filter(keywords=self.data.keywords).first()):
|
||||
logger.debug('群聊学习', '➤➤尚未有已学习的回复,不回复')
|
||||
if not (
|
||||
context := await ChatContext.filter(keywords=self.data.keywords).first()
|
||||
):
|
||||
logger.debug("群聊学习", "➤➤尚未有已学习的回复,不回复")
|
||||
return None
|
||||
|
||||
# 获取回复阈值
|
||||
if not self.to_me:
|
||||
answer_choices = list(
|
||||
range(self.config.answer_threshold - len(self.config.answer_threshold_weights) + 1,
|
||||
self.config.answer_threshold + 1))
|
||||
range(
|
||||
self.config.answer_threshold
|
||||
- len(self.config.answer_threshold_weights)
|
||||
+ 1,
|
||||
self.config.answer_threshold + 1,
|
||||
)
|
||||
)
|
||||
|
||||
answer_count_threshold = random.choices(answer_choices, weights=self.config.answer_threshold_weights)[0]
|
||||
answer_count_threshold = random.choices(
|
||||
answer_choices, weights=self.config.answer_threshold_weights
|
||||
)[0]
|
||||
|
||||
if len(self.data.keyword_list) == chat_config.KEYWORDS_SIZE:
|
||||
answer_count_threshold -= 1
|
||||
@ -221,17 +268,25 @@ class LearningChat:
|
||||
else:
|
||||
answer_count_threshold = 1
|
||||
cross_group_threshold = 1
|
||||
logger.debug('群聊学习',
|
||||
f'➤➤本次回复阈值为<m>{answer_count_threshold}</m>,跨群阈值为<m>{cross_group_threshold}</m>')
|
||||
logger.debug(
|
||||
"群聊学习",
|
||||
f"➤➤本次回复阈值为<m>{answer_count_threshold}</m>,跨群阈值为<m>{cross_group_threshold}</m>",
|
||||
)
|
||||
# 获取满足跨群条件的回复
|
||||
answers_cross = await ChatAnswer.filter(context=context, count__gte=answer_count_threshold,
|
||||
keywords__in=await ChatAnswer.annotate(
|
||||
cross=Count('keywords')).group_by('keywords').filter(
|
||||
cross__gte=cross_group_threshold).values_list('keywords',
|
||||
flat=True))
|
||||
answers_cross = await ChatAnswer.filter(
|
||||
context=context,
|
||||
count__gte=answer_count_threshold,
|
||||
keywords__in=await ChatAnswer.annotate(cross=Count("keywords"))
|
||||
.group_by("keywords")
|
||||
.filter(cross__gte=cross_group_threshold)
|
||||
.values_list("keywords", flat=True),
|
||||
)
|
||||
|
||||
answer_same_group = await ChatAnswer.filter(context=context, count__gte=answer_count_threshold,
|
||||
group_id=self.data.group_id)
|
||||
answer_same_group = await ChatAnswer.filter(
|
||||
context=context,
|
||||
count__gte=answer_count_threshold,
|
||||
group_id=self.data.group_id,
|
||||
)
|
||||
|
||||
candidate_answers: List[Optional[ChatAnswer]] = []
|
||||
# 检查候选回复是否在屏蔽列表中
|
||||
@ -242,23 +297,32 @@ class LearningChat:
|
||||
# answer.count -= answer_count_threshold - 1
|
||||
candidate_answers.append(answer)
|
||||
if not candidate_answers:
|
||||
logger.debug('群聊学习', '➤➤没有符合条件的候选回复')
|
||||
logger.debug("群聊学习", "➤➤没有符合条件的候选回复")
|
||||
return None
|
||||
|
||||
# 从候选回复中进行选择
|
||||
sum_count = sum(answer.count for answer in candidate_answers)
|
||||
per_list = [answer.count / sum_count * (1 - 1 / answer.count) for answer in candidate_answers]
|
||||
per_list = [
|
||||
answer.count / sum_count * (1 - 1 / answer.count)
|
||||
for answer in candidate_answers
|
||||
]
|
||||
|
||||
per_list.append(1 - sum(per_list))
|
||||
answer_dict = tuple(zip(candidate_answers, per_list))
|
||||
logger.debug('群聊学习',
|
||||
f'➤➤候选回复有<m>{"|".join([f"""{a.keywords}({round(p, 3)})""" for a, p in answer_dict])}|不回复({round(per_list[-1], 3)})</m>')
|
||||
logger.debug(
|
||||
"群聊学习",
|
||||
f'➤➤候选回复有<m>{"|".join([f"""{a.keywords}({round(p, 3)})""" for a, p in answer_dict])}|不回复({round(per_list[-1], 3)})</m>',
|
||||
)
|
||||
|
||||
if (result := random.choices(candidate_answers + [None], weights=per_list)[0]) is None:
|
||||
logger.debug('群聊学习', '➤➤但不进行回复')
|
||||
if (
|
||||
result := random.choices(candidate_answers + [None], weights=per_list)[
|
||||
0
|
||||
]
|
||||
) is None:
|
||||
logger.debug("群聊学习", "➤➤但不进行回复")
|
||||
return None
|
||||
result_message = random.choice(result.messages)
|
||||
logger.debug('群聊学习', f'➤➤将回复<m>{result_message}</m>')
|
||||
logger.debug("群聊学习", f"➤➤将回复<m>{result_message}</m>")
|
||||
await asyncio.sleep(random.random() + 0.5)
|
||||
return [result_message]
|
||||
|
||||
@ -266,21 +330,27 @@ class LearningChat:
|
||||
"""屏蔽消息"""
|
||||
bot = get_bot()
|
||||
if message_id:
|
||||
if not (message := await ChatMessage.filter(message_id=message_id).first()) or message.message in ALL_WORDS:
|
||||
if (
|
||||
not (message := await ChatMessage.filter(message_id=message_id).first())
|
||||
or message.message in ALL_WORDS
|
||||
):
|
||||
return False
|
||||
keywords = message.keywords
|
||||
try:
|
||||
await bot.delete_msg(message_id=message_id)
|
||||
except ActionFailed:
|
||||
logger.info('群聊学习', f'待禁用消息<m>{message_id}</m>尝试撤回<r>失败</r>')
|
||||
elif (last_reply := await ChatMessage.filter(group_id=self.data.group_id, user_id=self.bot_id).first()) and (
|
||||
last_reply.message not in ALL_WORDS):
|
||||
logger.info("群聊学习", f"待禁用消息<m>{message_id}</m>尝试撤回<r>失败</r>")
|
||||
elif (
|
||||
last_reply := await ChatMessage.filter(
|
||||
group_id=self.data.group_id, user_id=self.bot_id
|
||||
).first()
|
||||
) and (last_reply.message not in ALL_WORDS):
|
||||
# 没有指定消息ID,则屏蔽最后一条回复
|
||||
keywords = last_reply.keywords
|
||||
try:
|
||||
await bot.delete_msg(message_id=last_reply.message_id)
|
||||
except ActionFailed:
|
||||
logger.info('群聊学习', f'待禁用消息<m>{last_reply.message_id}</m>尝试撤回<r>失败</r>')
|
||||
logger.info("群聊学习", f"待禁用消息<m>{last_reply.message_id}</m>尝试撤回<r>失败</r>")
|
||||
else:
|
||||
return False
|
||||
if ban_word := await ChatBlackList.filter(keywords=keywords).first():
|
||||
@ -291,16 +361,24 @@ class LearningChat:
|
||||
if len(ban_word.ban_group_id) >= 2:
|
||||
# 如果有超过2个群都屏蔽了该条消息,则全局屏蔽
|
||||
ban_word.global_ban = True
|
||||
logger.info('群聊学习', f'学习词<m>{keywords}</m>将被全局禁用')
|
||||
logger.info("群聊学习", f"学习词<m>{keywords}</m>将被全局禁用")
|
||||
await ChatAnswer.filter(keywords=keywords).delete()
|
||||
else:
|
||||
logger.info('群聊学习', f'群<m>{self.data.group_id}</m>禁用了学习词<m>{keywords}</m>')
|
||||
await ChatAnswer.filter(keywords=keywords, group_id=self.data.group_id).delete()
|
||||
logger.info(
|
||||
"群聊学习", f"群<m>{self.data.group_id}</m>禁用了学习词<m>{keywords}</m>"
|
||||
)
|
||||
await ChatAnswer.filter(
|
||||
keywords=keywords, group_id=self.data.group_id
|
||||
).delete()
|
||||
else:
|
||||
# 没有屏蔽记录,则新建
|
||||
logger.info('群聊学习', f'群<m>{self.data.group_id}</m>禁用了学习词<m>{keywords}</m>')
|
||||
ban_word = ChatBlackList(keywords=keywords, ban_group_id=[self.data.group_id])
|
||||
await ChatAnswer.filter(keywords=keywords, group_id=self.data.group_id).delete()
|
||||
logger.info("群聊学习", f"群<m>{self.data.group_id}</m>禁用了学习词<m>{keywords}</m>")
|
||||
ban_word = ChatBlackList(
|
||||
keywords=keywords, ban_group_id=[self.data.group_id]
|
||||
)
|
||||
await ChatAnswer.filter(
|
||||
keywords=keywords, group_id=self.data.group_id
|
||||
).delete()
|
||||
await ChatContext.filter(keywords=keywords).delete()
|
||||
await ban_word.save()
|
||||
return True
|
||||
@ -316,49 +394,70 @@ class LearningChat:
|
||||
if len(ban_word.ban_group_id) >= 2:
|
||||
# 如果有超过2个群都屏蔽了该条消息,则全局屏蔽
|
||||
ban_word.global_ban = True
|
||||
logger.info('群聊学习', f'学习词<m>{data.keywords}</m>将被全局禁用')
|
||||
logger.info("群聊学习", f"学习词<m>{data.keywords}</m>将被全局禁用")
|
||||
await ChatAnswer.filter(keywords=data.keywords).delete()
|
||||
else:
|
||||
logger.info('群聊学习', f'群<m>{data.group_id}</m>禁用了学习词<m>{data.keywords}</m>')
|
||||
await ChatAnswer.filter(keywords=data.keywords, group_id=data.group_id).delete()
|
||||
logger.info(
|
||||
"群聊学习", f"群<m>{data.group_id}</m>禁用了学习词<m>{data.keywords}</m>"
|
||||
)
|
||||
await ChatAnswer.filter(
|
||||
keywords=data.keywords, group_id=data.group_id
|
||||
).delete()
|
||||
else:
|
||||
ban_word.global_ban = True
|
||||
logger.info('群聊学习', f'学习词<m>{data.keywords}</m>将被全局禁用')
|
||||
logger.info("群聊学习", f"学习词<m>{data.keywords}</m>将被全局禁用")
|
||||
await ChatAnswer.filter(keywords=data.keywords).delete()
|
||||
else:
|
||||
# 没有屏蔽记录,则新建
|
||||
if isinstance(data, ChatMessage):
|
||||
logger.info('群聊学习', f'群<m>{data.group_id}</m>禁用了学习词<m>{data.keywords}</m>')
|
||||
ban_word = ChatBlackList(keywords=data.keywords, ban_group_id=[data.group_id])
|
||||
await ChatAnswer.filter(keywords=data.keywords, group_id=data.group_id).delete()
|
||||
logger.info(
|
||||
"群聊学习", f"群<m>{data.group_id}</m>禁用了学习词<m>{data.keywords}</m>"
|
||||
)
|
||||
ban_word = ChatBlackList(
|
||||
keywords=data.keywords, ban_group_id=[data.group_id]
|
||||
)
|
||||
await ChatAnswer.filter(
|
||||
keywords=data.keywords, group_id=data.group_id
|
||||
).delete()
|
||||
else:
|
||||
logger.info('群聊学习', f'学习词<m>{data.keywords}</m>将被全局禁用')
|
||||
logger.info("群聊学习", f"学习词<m>{data.keywords}</m>将被全局禁用")
|
||||
ban_word = ChatBlackList(keywords=data.keywords, global_ban=True)
|
||||
await ChatAnswer.filter(keywords=data.keywords).delete()
|
||||
await ChatContext.filter(keywords=data.keywords).delete()
|
||||
await ban_word.save()
|
||||
|
||||
@staticmethod
|
||||
async def speak(self_id: int) -> Optional[Tuple[int, List[Union[str, MessageSegment]]]]:
|
||||
async def speak(
|
||||
self_id: int,
|
||||
) -> Optional[Tuple[int, List[Union[str, MessageSegment]]]]:
|
||||
# 主动发言
|
||||
cur_time = int(time.time())
|
||||
today_time = time.mktime(datetime.date.today().timetuple())
|
||||
# 获取两小时内消息超过10条的群列表
|
||||
groups = await ChatMessage.filter(time__gte=today_time).annotate(count=Count('id')).group_by('group_id'). \
|
||||
filter(count__gte=10).values_list('group_id', flat=True)
|
||||
groups = (
|
||||
await ChatMessage.filter(time__gte=today_time)
|
||||
.annotate(count=Count("id"))
|
||||
.group_by("group_id")
|
||||
.filter(count__gte=10)
|
||||
.values_list("group_id", flat=True)
|
||||
)
|
||||
if not groups:
|
||||
return None
|
||||
total_messages = {}
|
||||
# 获取这些群的两小时内的所有消息
|
||||
for group_id in groups:
|
||||
if messages := await ChatMessage.filter(group_id=group_id, time__gte=today_time):
|
||||
if messages := await ChatMessage.filter(
|
||||
group_id=group_id, time__gte=today_time
|
||||
):
|
||||
total_messages[group_id] = messages
|
||||
if not total_messages:
|
||||
return None
|
||||
|
||||
# 根据消息平均间隔来对群进行排序
|
||||
def group_popularity_cmp(left_group: Tuple[int, List[ChatMessage]],
|
||||
right_group: Tuple[int, List[ChatMessage]]):
|
||||
def group_popularity_cmp(
|
||||
left_group: Tuple[int, List[ChatMessage]],
|
||||
right_group: Tuple[int, List[ChatMessage]],
|
||||
):
|
||||
def cmp(a, b):
|
||||
return (a > b) - (a < b)
|
||||
|
||||
@ -366,34 +465,54 @@ class LearningChat:
|
||||
right_group_id, right_messages = right_group
|
||||
left_duration = left_messages[0].time - left_messages[-1].time
|
||||
right_duration = right_messages[0].time - right_messages[-1].time
|
||||
return cmp(len(left_messages) / left_duration, len(right_messages) / right_duration)
|
||||
return cmp(
|
||||
len(left_messages) / left_duration, len(right_messages) / right_duration
|
||||
)
|
||||
|
||||
popularity: List[Tuple[int, List[ChatMessage]]] = sorted(total_messages.items(),
|
||||
key=cmp_to_key(group_popularity_cmp), reverse=True)
|
||||
logger.debug('群聊学习', f'主动发言:群热度排行<m>{">>".join([str(g[0]) for g in popularity])}</m>')
|
||||
popularity: List[Tuple[int, List[ChatMessage]]] = sorted(
|
||||
total_messages.items(), key=cmp_to_key(group_popularity_cmp), reverse=True
|
||||
)
|
||||
logger.debug(
|
||||
"群聊学习", f'主动发言:群热度排行<m>{">>".join([str(g[0]) for g in popularity])}</m>'
|
||||
)
|
||||
for group_id, messages in popularity:
|
||||
if len(messages) < 30:
|
||||
logger.debug('群聊学习', f'主动发言:群<m>{group_id}</m>消息小于30条,不发言')
|
||||
logger.debug("群聊学习", f"主动发言:群<m>{group_id}</m>消息小于30条,不发言")
|
||||
continue
|
||||
|
||||
config = config_manager.get_group_config(group_id)
|
||||
ban_words = set(
|
||||
chat_config.ban_words + config.ban_words + ['[CQ:xml', '[CQ:json', '[CQ:at', '[CQ:video', '[CQ:record',
|
||||
'[CQ:share'])
|
||||
chat_config.ban_words
|
||||
+ config.ban_words
|
||||
+ [
|
||||
"[CQ:xml",
|
||||
"[CQ:json",
|
||||
"[CQ:at",
|
||||
"[CQ:video",
|
||||
"[CQ:record",
|
||||
"[CQ:share",
|
||||
]
|
||||
)
|
||||
|
||||
# 是否开启了主动发言
|
||||
if not config.speak_enable or not config.enable:
|
||||
logger.debug('群聊学习', f'主动发言:群<m>{group_id}</m>未开启,不发言')
|
||||
logger.debug("群聊学习", f"主动发言:群<m>{group_id}</m>未开启,不发言")
|
||||
continue
|
||||
|
||||
# 如果最后一条消息是自己发的,则不主动发言
|
||||
if last_reply := await ChatMessage.filter(group_id=group_id, user_id=self_id).first():
|
||||
if last_reply := await ChatMessage.filter(
|
||||
group_id=group_id, user_id=self_id
|
||||
).first():
|
||||
if last_reply.time >= messages[0].time:
|
||||
logger.debug('群聊学习',
|
||||
f'主动发言:群<m>{group_id}</m>最后一条消息是{NICKNAME}发的{last_reply.message},不发言')
|
||||
logger.debug(
|
||||
"群聊学习",
|
||||
f"主动发言:群<m>{group_id}</m>最后一条消息是{NICKNAME}发的{last_reply.message},不发言",
|
||||
)
|
||||
continue
|
||||
elif cur_time - last_reply.time < config.speak_min_interval:
|
||||
logger.debug('群聊学习', f'主动发言:群<m>{group_id}</m>上次主动发言时间小于主动发言最小间隔,不发言')
|
||||
logger.debug(
|
||||
"群聊学习", f"主动发言:群<m>{group_id}</m>上次主动发言时间小于主动发言最小间隔,不发言"
|
||||
)
|
||||
continue
|
||||
|
||||
# 该群每多少秒发一条消息
|
||||
@ -402,50 +521,79 @@ class LearningChat:
|
||||
silent_time = cur_time - messages[0].time
|
||||
threshold = avg_interval * config.speak_threshold
|
||||
if silent_time < threshold:
|
||||
logger.debug('群聊学习',
|
||||
f'主动发言:群<m>{group_id}</m>已沉默时间({silent_time})小于阈值({int(threshold)}),不发言')
|
||||
logger.debug(
|
||||
"群聊学习",
|
||||
f"主动发言:群<m>{group_id}</m>已沉默时间({silent_time})小于阈值({int(threshold)}),不发言",
|
||||
)
|
||||
continue
|
||||
|
||||
if contexts := await ChatContext.filter(count__gte=config.answer_threshold).all():
|
||||
if contexts := await ChatContext.filter(
|
||||
count__gte=config.answer_threshold
|
||||
).all():
|
||||
speak_list = []
|
||||
# context = random.choices(contexts, weights=[context.count for context in contexts])[0]
|
||||
# contexts.sort(key=lambda x: x.count, reverse=True)
|
||||
random.shuffle(contexts)
|
||||
for context in contexts:
|
||||
if (not speak_list or random.random() < config.speak_continuously_probability) and len(
|
||||
speak_list) < config.speak_continuously_max_len:
|
||||
if answers := await ChatAnswer.filter(context=context,
|
||||
group_id=group_id,
|
||||
count__gte=config.answer_threshold):
|
||||
answer = random.choices(answers,
|
||||
weights=[
|
||||
answer.count + 1 if answer.time >= today_time else answer.count
|
||||
for answer in answers])[0]
|
||||
if (
|
||||
not speak_list
|
||||
or random.random() < config.speak_continuously_probability
|
||||
) and len(speak_list) < config.speak_continuously_max_len:
|
||||
if answers := await ChatAnswer.filter(
|
||||
context=context,
|
||||
group_id=group_id,
|
||||
count__gte=config.answer_threshold,
|
||||
):
|
||||
answer = random.choices(
|
||||
answers,
|
||||
weights=[
|
||||
answer.count + 1
|
||||
if answer.time >= today_time
|
||||
else answer.count
|
||||
for answer in answers
|
||||
],
|
||||
)[0]
|
||||
message = random.choice(answer.messages)
|
||||
if len(message) < 2:
|
||||
continue
|
||||
if message.startswith('[') and message.endswith(']'):
|
||||
if message.startswith("[") and message.endswith(
|
||||
"]"
|
||||
):
|
||||
continue
|
||||
if any(word in message for word in ban_words):
|
||||
continue
|
||||
speak_list.append(message)
|
||||
follow_answer = answer
|
||||
while random.random() < config.speak_continuously_probability and len(
|
||||
speak_list) < config.speak_continuously_max_len:
|
||||
if (follow_context := await ChatContext.filter(
|
||||
keywords=follow_answer.keywords).first()) and (
|
||||
follow_answers := await ChatAnswer.filter(
|
||||
group_id=group_id,
|
||||
context=follow_context,
|
||||
count__gte=config.answer_threshold)):
|
||||
follow_answer = random.choices(follow_answers,
|
||||
weights=[
|
||||
a.count + 1 if a.time >= today_time else a.count
|
||||
for a in follow_answers])[0]
|
||||
while (
|
||||
random.random() < config.speak_continuously_probability
|
||||
and len(speak_list) < config.speak_continuously_max_len
|
||||
):
|
||||
if (
|
||||
follow_context := await ChatContext.filter(
|
||||
keywords=follow_answer.keywords
|
||||
).first()
|
||||
) and (
|
||||
follow_answers := await ChatAnswer.filter(
|
||||
group_id=group_id,
|
||||
context=follow_context,
|
||||
count__gte=config.answer_threshold,
|
||||
)
|
||||
):
|
||||
follow_answer = random.choices(
|
||||
follow_answers,
|
||||
weights=[
|
||||
a.count + 1
|
||||
if a.time >= today_time
|
||||
else a.count
|
||||
for a in follow_answers
|
||||
],
|
||||
)[0]
|
||||
message = random.choice(follow_answer.messages)
|
||||
if len(message) < 2:
|
||||
continue
|
||||
if message.startswith('[') and message.endswith(']'):
|
||||
if message.startswith("[") and message.endswith(
|
||||
"]"
|
||||
):
|
||||
continue
|
||||
if all(word not in message for word in ban_words):
|
||||
speak_list.append(message)
|
||||
@ -455,13 +603,17 @@ class LearningChat:
|
||||
break
|
||||
if speak_list:
|
||||
if random.random() < config.speak_poke_probability:
|
||||
last_speak_users = {message.user_id for message in messages[:5] if message.user_id != self_id}
|
||||
last_speak_users = {
|
||||
message.user_id
|
||||
for message in messages[:5]
|
||||
if message.user_id != self_id
|
||||
}
|
||||
select_user = random.choice(list(last_speak_users))
|
||||
speak_list.append(MessageSegment('poke', {'qq': select_user}))
|
||||
speak_list.append(MessageSegment("poke", {"qq": select_user}))
|
||||
return group_id, speak_list
|
||||
else:
|
||||
logger.debug('群聊学习', f'主动发言:群<m>{group_id}</m>没有找到符合条件的发言,不发言')
|
||||
logger.debug('群聊学习', '主动发言:没有符合条件的群,不主动发言')
|
||||
logger.debug("群聊学习", f"主动发言:群<m>{group_id}</m>没有找到符合条件的发言,不发言")
|
||||
logger.debug("群聊学习", "主动发言:没有符合条件的群,不主动发言")
|
||||
return None
|
||||
|
||||
async def _set_answer(self, message: ChatMessage):
|
||||
@ -469,42 +621,62 @@ class LearningChat:
|
||||
if context.count < chat_config.learn_max_count:
|
||||
context.count += 1
|
||||
context.time = self.data.time
|
||||
if answer := await ChatAnswer.filter(keywords=self.data.keywords,
|
||||
group_id=self.data.group_id,
|
||||
context=context).first():
|
||||
if answer := await ChatAnswer.filter(
|
||||
keywords=self.data.keywords,
|
||||
group_id=self.data.group_id,
|
||||
context=context,
|
||||
).first():
|
||||
if answer.count < chat_config.learn_max_count:
|
||||
answer.count += 1
|
||||
answer.time = self.data.time
|
||||
if self.data.message not in answer.messages:
|
||||
answer.messages.append(self.data.message)
|
||||
else:
|
||||
answer = ChatAnswer(keywords=self.data.keywords,
|
||||
group_id=self.data.group_id,
|
||||
time=self.data.time,
|
||||
context=context,
|
||||
messages=[self.data.message])
|
||||
answer = ChatAnswer(
|
||||
keywords=self.data.keywords,
|
||||
group_id=self.data.group_id,
|
||||
time=self.data.time,
|
||||
context=context,
|
||||
messages=[self.data.message],
|
||||
)
|
||||
await answer.save()
|
||||
await context.save()
|
||||
else:
|
||||
context = await ChatContext.create(keywords=message.keywords,
|
||||
time=self.data.time)
|
||||
answer = await ChatAnswer.create(keywords=self.data.keywords,
|
||||
group_id=self.data.group_id,
|
||||
time=self.data.time,
|
||||
context=context,
|
||||
messages=[self.data.message])
|
||||
logger.debug('群聊学习', f'➤将被学习为<m>{message.message}</m>的回答,已学次数为<m>{answer.count}</m>')
|
||||
context = await ChatContext.create(
|
||||
keywords=message.keywords, time=self.data.time
|
||||
)
|
||||
answer = await ChatAnswer.create(
|
||||
keywords=self.data.keywords,
|
||||
group_id=self.data.group_id,
|
||||
time=self.data.time,
|
||||
context=context,
|
||||
messages=[self.data.message],
|
||||
)
|
||||
logger.debug(
|
||||
"群聊学习", f"➤将被学习为<m>{message.message}</m>的回答,已学次数为<m>{answer.count}</m>"
|
||||
)
|
||||
|
||||
async def _check_allow(self, message: Union[ChatMessage, ChatAnswer]) -> bool:
|
||||
raw_message = message.message if isinstance(message, ChatMessage) else message.messages[0]
|
||||
raw_message = (
|
||||
message.message if isinstance(message, ChatMessage) else message.messages[0]
|
||||
)
|
||||
# if len(raw_message) < 2:
|
||||
# return False
|
||||
if any(i in raw_message for i in
|
||||
{'[CQ:xml', '[CQ:json', '[CQ:at', '[CQ:video', '[CQ:record', '[CQ:share'}):
|
||||
if any(
|
||||
i in raw_message
|
||||
for i in {
|
||||
"[CQ:xml",
|
||||
"[CQ:json",
|
||||
"[CQ:at",
|
||||
"[CQ:video",
|
||||
"[CQ:record",
|
||||
"[CQ:share",
|
||||
}
|
||||
):
|
||||
return False
|
||||
if any(i in raw_message for i in self.ban_words):
|
||||
return False
|
||||
if raw_message.startswith('[') and raw_message.endswith(']'):
|
||||
if raw_message.startswith("[") and raw_message.endswith("]"):
|
||||
return False
|
||||
if ban_word := await ChatBlackList.filter(keywords=message.keywords).first():
|
||||
if ban_word.global_ban or message.group_id in ban_word.ban_group_id:
|
||||
|
@ -23,7 +23,7 @@ config = config_manager.config
|
||||
|
||||
JSON_DUMPS = functools.partial(json.dumps, ensure_ascii=False)
|
||||
jieba.setLogLevel(jieba.logging.INFO)
|
||||
jieba.load_userdict(str(Path(__file__).parent / 'genshin_word.txt')) # 加载原神词典
|
||||
jieba.load_userdict(str(Path(__file__).parent / "genshin_word.txt")) # 加载原神词典
|
||||
jieba.load_userdict(config.dictionary) # 加载用户自定义的词典
|
||||
|
||||
|
||||
@ -46,14 +46,14 @@ class ChatMessage(Model):
|
||||
"""时间戳"""
|
||||
|
||||
class Meta:
|
||||
table = 'message'
|
||||
indexes = ('group_id', 'time')
|
||||
ordering = ['-time']
|
||||
table = "message"
|
||||
indexes = ("group_id", "time")
|
||||
ordering = ["-time"]
|
||||
|
||||
@cached_property
|
||||
def is_plain_text(self) -> bool:
|
||||
"""是否纯文本"""
|
||||
return '[CQ:' not in self.message
|
||||
return "[CQ:" not in self.message
|
||||
|
||||
@cached_property
|
||||
def keyword_list(self) -> List[str]:
|
||||
@ -67,7 +67,9 @@ class ChatMessage(Model):
|
||||
"""获取纯文本部分的关键词结果"""
|
||||
if not self.is_plain_text and not len(self.plain_text):
|
||||
return self.message
|
||||
return self.message if len(self.keyword_list) < 2 else ' '.join(self.keyword_list)
|
||||
return (
|
||||
self.message if len(self.keyword_list) < 2 else " ".join(self.keyword_list)
|
||||
)
|
||||
|
||||
|
||||
class ChatContext(Model):
|
||||
@ -79,13 +81,13 @@ class ChatContext(Model):
|
||||
"""时间戳"""
|
||||
count: int = fields.IntField(default=1)
|
||||
"""次数"""
|
||||
answers: fields.ReverseRelation['ChatAnswer']
|
||||
answers: fields.ReverseRelation["ChatAnswer"]
|
||||
"""答案"""
|
||||
|
||||
class Meta:
|
||||
table = 'context'
|
||||
indexes = ('keywords', 'time')
|
||||
ordering = ['-time']
|
||||
table = "context"
|
||||
indexes = ("keywords", "time")
|
||||
ordering = ["-time"]
|
||||
|
||||
|
||||
class ChatAnswer(Model):
|
||||
@ -103,12 +105,13 @@ class ChatAnswer(Model):
|
||||
"""消息列表"""
|
||||
|
||||
context: fields.ForeignKeyNullableRelation[ChatContext] = fields.ForeignKeyField(
|
||||
'LearningChat.ChatContext', related_name='answers', null=True)
|
||||
"LearningChat.ChatContext", related_name="answers", null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
table = 'answer'
|
||||
indexes = ('keywords', 'time')
|
||||
ordering = ['-time']
|
||||
table = "answer"
|
||||
indexes = ("keywords", "time")
|
||||
ordering = ["-time"]
|
||||
|
||||
|
||||
class ChatBlackList(Model):
|
||||
@ -122,8 +125,10 @@ class ChatBlackList(Model):
|
||||
"""禁用的群id"""
|
||||
|
||||
class Meta:
|
||||
table = 'blacklist'
|
||||
indexes = ('keywords',)
|
||||
table = "blacklist"
|
||||
indexes = ("keywords",)
|
||||
|
||||
|
||||
register_database(db_name='LearningChat', models=__name__, db_path=DATABASE_PATH / 'LearningChat.db')
|
||||
register_database(
|
||||
db_name="LearningChat", models=__name__, db_path=DATABASE_PATH / "LearningChat.db"
|
||||
)
|
||||
|
@ -4,7 +4,12 @@ from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
from nonebot import get_bot
|
||||
|
||||
from LittlePaimon.plugins.Learning_Chat.models import ChatMessage, ChatContext, ChatAnswer, ChatBlackList
|
||||
from LittlePaimon.plugins.Learning_Chat.models import (
|
||||
ChatMessage,
|
||||
ChatContext,
|
||||
ChatAnswer,
|
||||
ChatBlackList,
|
||||
)
|
||||
from LittlePaimon.web.api import BaseApiRouter
|
||||
from LittlePaimon.web.api.utils import authentication
|
||||
|
||||
@ -19,232 +24,290 @@ from .config import config_manager
|
||||
route = APIRouter()
|
||||
|
||||
|
||||
@route.get('/chat_global_config', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@route.get(
|
||||
"/chat_global_config", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def get_chat_global_config():
|
||||
try:
|
||||
bot = get_bot()
|
||||
groups = await bot.get_group_list()
|
||||
member_list = []
|
||||
for group in groups:
|
||||
members = await bot.get_group_member_list(group_id=group['group_id'])
|
||||
members = await bot.get_group_member_list(group_id=group["group_id"])
|
||||
member_list.extend(
|
||||
[{'label': f'{member["nickname"] or member["card"]}({member["user_id"]})', 'value': member['user_id']} for
|
||||
member in members])
|
||||
config = config_manager.config.dict(exclude={'group_config'})
|
||||
config['member_list'] = member_list
|
||||
[
|
||||
{
|
||||
"label": f'{member["nickname"] or member["card"]}({member["user_id"]})',
|
||||
"value": member["user_id"],
|
||||
}
|
||||
for member in members
|
||||
]
|
||||
)
|
||||
config = config_manager.config.dict(exclude={"group_config"})
|
||||
config["member_list"] = member_list
|
||||
return config
|
||||
except ValueError:
|
||||
return {
|
||||
'status': -100,
|
||||
'msg': '获取群和好友列表失败,请确认已连接GOCQ'
|
||||
}
|
||||
return {"status": -100, "msg": "获取群和好友列表失败,请确认已连接GOCQ"}
|
||||
|
||||
|
||||
@route.post('/chat_global_config', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@route.post(
|
||||
"/chat_global_config", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def post_chat_global_config(data: dict):
|
||||
config_manager.config.update(**data)
|
||||
config_manager.save()
|
||||
await ChatContext.filter(count__gt=config_manager.config.learn_max_count).update(
|
||||
count=config_manager.config.learn_max_count)
|
||||
count=config_manager.config.learn_max_count
|
||||
)
|
||||
await ChatAnswer.filter(count__gt=config_manager.config.learn_max_count).update(
|
||||
count=config_manager.config.learn_max_count)
|
||||
count=config_manager.config.learn_max_count
|
||||
)
|
||||
jieba.load_userdict(config_manager.config.dictionary)
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '保存成功'
|
||||
}
|
||||
return {"status": 0, "msg": "保存成功"}
|
||||
|
||||
|
||||
@route.get('/chat_group_config', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@route.get(
|
||||
"/chat_group_config", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def get_chat_global_config(group_id: int):
|
||||
try:
|
||||
members = await get_bot().get_group_member_list(group_id=group_id)
|
||||
member_list = [{'label': f'{member["nickname"] or member["card"]}({member["user_id"]})', 'value': member['user_id']}
|
||||
for member in members]
|
||||
member_list = [
|
||||
{
|
||||
"label": f'{member["nickname"] or member["card"]}({member["user_id"]})',
|
||||
"value": member["user_id"],
|
||||
}
|
||||
for member in members
|
||||
]
|
||||
config = config_manager.get_group_config(group_id).dict()
|
||||
config['break_probability'] = config['break_probability'] * 100
|
||||
config['speak_continuously_probability'] = config['speak_continuously_probability'] * 100
|
||||
config['speak_poke_probability'] = config['speak_poke_probability'] * 100
|
||||
config['member_list'] = member_list
|
||||
config["break_probability"] = config["break_probability"] * 100
|
||||
config["speak_continuously_probability"] = (
|
||||
config["speak_continuously_probability"] * 100
|
||||
)
|
||||
config["speak_poke_probability"] = config["speak_poke_probability"] * 100
|
||||
config["member_list"] = member_list
|
||||
return config
|
||||
except ValueError:
|
||||
return {
|
||||
'status': -100,
|
||||
'msg': '获取群和好友列表失败,请确认已连接GOCQ'
|
||||
}
|
||||
return {"status": -100, "msg": "获取群和好友列表失败,请确认已连接GOCQ"}
|
||||
|
||||
|
||||
@route.post('/chat_group_config', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@route.post(
|
||||
"/chat_group_config", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def post_chat_global_config(group_id: Union[int, str], data: dict):
|
||||
if not data['answer_threshold_weights']:
|
||||
return {
|
||||
'status': 400,
|
||||
'msg': '回复阈值权重不能为空,必须至少有一个数值'
|
||||
}
|
||||
if not data["answer_threshold_weights"]:
|
||||
return {"status": 400, "msg": "回复阈值权重不能为空,必须至少有一个数值"}
|
||||
else:
|
||||
data['break_probability'] = data['break_probability'] / 100
|
||||
data['speak_continuously_probability'] = data['speak_continuously_probability'] / 100
|
||||
data['speak_poke_probability'] = data['speak_poke_probability'] / 100
|
||||
if group_id != 'all':
|
||||
groups = [{'group_id': group_id}]
|
||||
data["break_probability"] = data["break_probability"] / 100
|
||||
data["speak_continuously_probability"] = (
|
||||
data["speak_continuously_probability"] / 100
|
||||
)
|
||||
data["speak_poke_probability"] = data["speak_poke_probability"] / 100
|
||||
if group_id != "all":
|
||||
groups = [{"group_id": group_id}]
|
||||
else:
|
||||
groups = await get_bot().get_group_list()
|
||||
for group in groups:
|
||||
config = config_manager.get_group_config(group['group_id'])
|
||||
config = config_manager.get_group_config(group["group_id"])
|
||||
config.update(**data)
|
||||
config_manager.config.group_config[group['group_id']] = config
|
||||
config_manager.config.group_config[group["group_id"]] = config
|
||||
config_manager.save()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '保存成功'
|
||||
}
|
||||
return {"status": 0, "msg": "保存成功"}
|
||||
|
||||
|
||||
@route.get('/get_chat_messages', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_chat_messages(page: int = 1,
|
||||
perPage: int = 10,
|
||||
orderBy: str = 'time',
|
||||
orderDir: str = 'desc',
|
||||
group_id: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
message: Optional[str] = None):
|
||||
orderBy = (orderBy or 'time') if (orderDir or 'desc') == 'asc' else f'-{orderBy or "time"}'
|
||||
filter_args = {f'{k}__contains': v for k, v in
|
||||
{'group_id': group_id, 'user_id': user_id, 'raw_message': message}.items() if v}
|
||||
@route.get(
|
||||
"/get_chat_messages", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def get_chat_messages(
|
||||
page: int = 1,
|
||||
perPage: int = 10,
|
||||
orderBy: str = "time",
|
||||
orderDir: str = "desc",
|
||||
group_id: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
message: Optional[str] = None,
|
||||
):
|
||||
orderBy = (
|
||||
(orderBy or "time")
|
||||
if (orderDir or "desc") == "asc"
|
||||
else f'-{orderBy or "time"}'
|
||||
)
|
||||
filter_args = {
|
||||
f"{k}__contains": v
|
||||
for k, v in {
|
||||
"group_id": group_id,
|
||||
"user_id": user_id,
|
||||
"raw_message": message,
|
||||
}.items()
|
||||
if v
|
||||
}
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'items': await ChatMessage.filter(**filter_args).order_by(orderBy).offset((page - 1) * perPage).limit(
|
||||
perPage).values(),
|
||||
'total': await ChatMessage.filter(**filter_args).count()
|
||||
}
|
||||
"status": 0,
|
||||
"msg": "ok",
|
||||
"data": {
|
||||
"items": await ChatMessage.filter(**filter_args)
|
||||
.order_by(orderBy)
|
||||
.offset((page - 1) * perPage)
|
||||
.limit(perPage)
|
||||
.values(),
|
||||
"total": await ChatMessage.filter(**filter_args).count(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@route.get('/get_chat_contexts', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_chat_context(page: int = 1, perPage: int = 10, orderBy: str = 'time', orderDir: str = 'desc',
|
||||
keywords: Optional[str] = None):
|
||||
orderBy = (orderBy or 'time') if (orderDir or 'desc') == 'asc' else f'-{orderBy or "time"}'
|
||||
filter_arg = {'keywords__contains': keywords} if keywords else {}
|
||||
@route.get(
|
||||
"/get_chat_contexts", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def get_chat_context(
|
||||
page: int = 1,
|
||||
perPage: int = 10,
|
||||
orderBy: str = "time",
|
||||
orderDir: str = "desc",
|
||||
keywords: Optional[str] = None,
|
||||
):
|
||||
orderBy = (
|
||||
(orderBy or "time")
|
||||
if (orderDir or "desc") == "asc"
|
||||
else f'-{orderBy or "time"}'
|
||||
)
|
||||
filter_arg = {"keywords__contains": keywords} if keywords else {}
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'items': await ChatContext.filter(**filter_arg).order_by(orderBy).offset((page - 1) * perPage).limit(
|
||||
perPage).values(),
|
||||
'total': await ChatContext.filter(**filter_arg).count()
|
||||
}
|
||||
"status": 0,
|
||||
"msg": "ok",
|
||||
"data": {
|
||||
"items": await ChatContext.filter(**filter_arg)
|
||||
.order_by(orderBy)
|
||||
.offset((page - 1) * perPage)
|
||||
.limit(perPage)
|
||||
.values(),
|
||||
"total": await ChatContext.filter(**filter_arg).count(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@route.get('/get_chat_answers', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_chat_answers(context_id: Optional[int] = None, page: int = 1, perPage: int = 10, orderBy: str = 'count',
|
||||
orderDir: str = 'desc', keywords: Optional[str] = None):
|
||||
filter_arg = {'context_id': context_id} if context_id else {}
|
||||
@route.get(
|
||||
"/get_chat_answers", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def get_chat_answers(
|
||||
context_id: Optional[int] = None,
|
||||
page: int = 1,
|
||||
perPage: int = 10,
|
||||
orderBy: str = "count",
|
||||
orderDir: str = "desc",
|
||||
keywords: Optional[str] = None,
|
||||
):
|
||||
filter_arg = {"context_id": context_id} if context_id else {}
|
||||
if keywords:
|
||||
filter_arg['keywords__contains'] = keywords # type: ignore
|
||||
orderBy = (orderBy or 'count') if (orderDir or 'desc') == 'asc' else f'-{orderBy or "count"}'
|
||||
filter_arg["keywords__contains"] = keywords # type: ignore
|
||||
orderBy = (
|
||||
(orderBy or "count")
|
||||
if (orderDir or "desc") == "asc"
|
||||
else f'-{orderBy or "count"}'
|
||||
)
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'items': list(
|
||||
map(lambda x: x.update({'messages': [{'msg': m} for m in x['messages']]}) or x,
|
||||
await ChatAnswer.filter(**filter_arg).order_by(orderBy).offset((page - 1) * perPage).limit(
|
||||
perPage).values())),
|
||||
'total': await ChatAnswer.filter(**filter_arg).count()
|
||||
}
|
||||
"status": 0,
|
||||
"msg": "ok",
|
||||
"data": {
|
||||
"items": list(
|
||||
map(
|
||||
lambda x: x.update(
|
||||
{"messages": [{"msg": m} for m in x["messages"]]}
|
||||
)
|
||||
or x,
|
||||
await ChatAnswer.filter(**filter_arg)
|
||||
.order_by(orderBy)
|
||||
.offset((page - 1) * perPage)
|
||||
.limit(perPage)
|
||||
.values(),
|
||||
)
|
||||
),
|
||||
"total": await ChatAnswer.filter(**filter_arg).count(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@route.get('/get_chat_blacklist', response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def get_chat_blacklist(page: int = 1, perPage: int = 10, keywords: Optional[str] = None,
|
||||
bans: Optional[str] = None):
|
||||
filter_arg = {'keywords__contains': keywords} if keywords else {}
|
||||
items = await ChatBlackList.filter(**filter_arg).offset((page - 1) * perPage).limit(perPage).values()
|
||||
@route.get(
|
||||
"/get_chat_blacklist", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def get_chat_blacklist(
|
||||
page: int = 1,
|
||||
perPage: int = 10,
|
||||
keywords: Optional[str] = None,
|
||||
bans: Optional[str] = None,
|
||||
):
|
||||
filter_arg = {"keywords__contains": keywords} if keywords else {}
|
||||
items = (
|
||||
await ChatBlackList.filter(**filter_arg)
|
||||
.offset((page - 1) * perPage)
|
||||
.limit(perPage)
|
||||
.values()
|
||||
)
|
||||
for item in items:
|
||||
item['bans'] = '全局禁用' if item['global_ban'] else str(item['ban_group_id'][0])
|
||||
item["bans"] = "全局禁用" if item["global_ban"] else str(item["ban_group_id"][0])
|
||||
if bans:
|
||||
items = list(filter(lambda x: bans in x['bans'], items))
|
||||
items = list(filter(lambda x: bans in x["bans"], items))
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'items': items,
|
||||
'total': await ChatBlackList.filter(**filter_arg).count()
|
||||
}
|
||||
"status": 0,
|
||||
"msg": "ok",
|
||||
"data": {
|
||||
"items": items,
|
||||
"total": await ChatBlackList.filter(**filter_arg).count(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@route.delete('/delete_chat', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@route.delete(
|
||||
"/delete_chat", response_class=JSONResponse, dependencies=[authentication()]
|
||||
)
|
||||
async def delete_chat(id: int, type: str):
|
||||
try:
|
||||
if type == 'message':
|
||||
if type == "message":
|
||||
await ChatMessage.filter(id=id).delete()
|
||||
elif type == 'context':
|
||||
elif type == "context":
|
||||
c = await ChatContext.get(id=id)
|
||||
await ChatAnswer.filter(context=c).delete()
|
||||
await c.delete()
|
||||
elif type == 'answer':
|
||||
elif type == "answer":
|
||||
await ChatAnswer.filter(id=id).delete()
|
||||
elif type == 'blacklist':
|
||||
elif type == "blacklist":
|
||||
await ChatBlackList.filter(id=id).delete()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '删除成功'
|
||||
}
|
||||
return {"status": 0, "msg": "删除成功"}
|
||||
except Exception as e:
|
||||
return {
|
||||
'status': 500,
|
||||
'msg': f'删除失败,{e}'
|
||||
}
|
||||
return {"status": 500, "msg": f"删除失败,{e}"}
|
||||
|
||||
|
||||
@route.put('/ban_chat', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@route.put("/ban_chat", response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def ban_chat(id: int, type: str):
|
||||
try:
|
||||
if type == 'message':
|
||||
if type == "message":
|
||||
data = await ChatMessage.get(id=id)
|
||||
elif type == 'context':
|
||||
elif type == "context":
|
||||
data = await ChatContext.get(id=id)
|
||||
else:
|
||||
data = await ChatAnswer.get(id=id)
|
||||
await LearningChat.add_ban(data)
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '禁用成功'
|
||||
}
|
||||
return {"status": 0, "msg": "禁用成功"}
|
||||
except Exception as e:
|
||||
return {
|
||||
'status': 500,
|
||||
'msg': f'禁用失败: {e}'
|
||||
}
|
||||
return {"status": 500, "msg": f"禁用失败: {e}"}
|
||||
|
||||
|
||||
@route.put('/delete_all', response_class=JSONResponse, dependencies=[authentication()])
|
||||
@route.put("/delete_all", response_class=JSONResponse, dependencies=[authentication()])
|
||||
async def delete_all(type: str, id: Optional[int] = None):
|
||||
try:
|
||||
if type == 'message':
|
||||
if type == "message":
|
||||
await ChatMessage.all().delete()
|
||||
elif type == 'context':
|
||||
elif type == "context":
|
||||
await ChatContext.all().delete()
|
||||
elif type == 'answer':
|
||||
elif type == "answer":
|
||||
if id:
|
||||
await ChatAnswer.filter(context_id=id).delete()
|
||||
else:
|
||||
await ChatAnswer.all().delete()
|
||||
elif type == 'blacklist':
|
||||
elif type == "blacklist":
|
||||
await ChatBlackList.all().delete()
|
||||
return {
|
||||
'status': 0,
|
||||
'msg': '操作成功'
|
||||
}
|
||||
return {"status": 0, "msg": "操作成功"}
|
||||
except Exception as e:
|
||||
return {
|
||||
'status': 500,
|
||||
'msg': f'操作失败,{e}'
|
||||
}
|
||||
return {"status": 500, "msg": f"操作失败,{e}"}
|
||||
|
||||
|
||||
BaseApiRouter.include_router(route)
|
||||
|
@ -5,275 +5,626 @@ from LittlePaimon.utils import NICKNAME
|
||||
from LittlePaimon.web.pages import admin_app
|
||||
|
||||
global_config_form = Form(
|
||||
title='全局配置',
|
||||
name='global_config',
|
||||
initApi='/LittlePaimon/api/chat_global_config',
|
||||
api='post:/LittlePaimon/api/chat_global_config',
|
||||
title="全局配置",
|
||||
name="global_config",
|
||||
initApi="/LittlePaimon/api/chat_global_config",
|
||||
api="post:/LittlePaimon/api/chat_global_config",
|
||||
interval=None,
|
||||
body=[
|
||||
Switch(label='群聊学习总开关', name='total_enable', value='${total_enable}', onText='开启', offText='关闭',
|
||||
labelRemark=Remark(shape='circle', content='关闭后,全局都将不会再学习和回复(但是仍会对收到的消息进行记录)。')),
|
||||
InputNumber(label='单句关键词数量', name='KEYWORDS_SIZE', value='${KEYWORDS_SIZE}', visibleOn='${total_enable}',
|
||||
min=2,
|
||||
labelRemark=Remark(shape='circle', content='单句语句标签数量,影响对一句话的主题词提取效果,建议保持默认为3。')),
|
||||
InputNumber(label='跨群回复阈值', name='cross_group_threshold', value='${cross_group_threshold}',
|
||||
visibleOn='${total_enable}', min=1,
|
||||
labelRemark=Remark(shape='circle', content='当学习到的一种回复在N个群都有,那么这个回复就会变为全局回复。')),
|
||||
InputNumber(label='最高学习次数', name='learn_max_count', value='${learn_max_count}',
|
||||
visibleOn='${total_enable}', min=2, labelRemark=Remark(shape='circle',
|
||||
content='学习的回复最高能累计到的次数,值越高,这个回复就会学习得越深,越容易进行回复,如果不想每次都大概率固定回复某一句话,可以将该值设低点。')),
|
||||
InputTag(label='全局屏蔽词', name='ban_words', value='${ban_words}', enableBatchAdd=True,
|
||||
placeholder='添加全局屏蔽词', visibleOn='${total_enable}', joinValues=False, extractValue=True,
|
||||
labelRemark=Remark(shape='circle', content='全局屏蔽词,含有这些词的消息不会学习和回复,默认已屏蔽at、分享、语音、和视频等消息。(回车进行添加)')),
|
||||
InputTag(label='全局屏蔽用户', source='${member_list}', name='ban_users', value='${ban_users}',
|
||||
enableBatchAdd=True,
|
||||
placeholder='添加全局屏蔽用户', visibleOn='${total_enable}', joinValues=False, extractValue=True,
|
||||
labelRemark=Remark(shape='circle', content='全局屏蔽用户,和这些用户有关的消息不会学习和回复。(回车进行添加)')),
|
||||
InputTag(label='自定义词典', name='dictionary', value='${dictionary}',
|
||||
enableBatchAdd=True,
|
||||
placeholder='添加自定义词语', visibleOn='${total_enable}', joinValues=False, extractValue=True,
|
||||
labelRemark=Remark(shape='circle', content='添加自定义词语,让分词能够识别未收录的词汇,提高学习的准确性。你可以添加特殊名词,这样学习时就会将该词看作一个整体,目前词典中已默认添加部分原神相关词汇。(回车进行添加)')),
|
||||
Switch(
|
||||
label="群聊学习总开关",
|
||||
name="total_enable",
|
||||
value="${total_enable}",
|
||||
onText="开启",
|
||||
offText="关闭",
|
||||
labelRemark=Remark(
|
||||
shape="circle", content="关闭后,全局都将不会再学习和回复(但是仍会对收到的消息进行记录)。"
|
||||
),
|
||||
),
|
||||
InputNumber(
|
||||
label="单句关键词数量",
|
||||
name="KEYWORDS_SIZE",
|
||||
value="${KEYWORDS_SIZE}",
|
||||
visibleOn="${total_enable}",
|
||||
min=2,
|
||||
labelRemark=Remark(
|
||||
shape="circle", content="单句语句标签数量,影响对一句话的主题词提取效果,建议保持默认为3。"
|
||||
),
|
||||
),
|
||||
InputNumber(
|
||||
label="跨群回复阈值",
|
||||
name="cross_group_threshold",
|
||||
value="${cross_group_threshold}",
|
||||
visibleOn="${total_enable}",
|
||||
min=1,
|
||||
labelRemark=Remark(
|
||||
shape="circle", content="当学习到的一种回复在N个群都有,那么这个回复就会变为全局回复。"
|
||||
),
|
||||
),
|
||||
InputNumber(
|
||||
label="最高学习次数",
|
||||
name="learn_max_count",
|
||||
value="${learn_max_count}",
|
||||
visibleOn="${total_enable}",
|
||||
min=2,
|
||||
labelRemark=Remark(
|
||||
shape="circle",
|
||||
content="学习的回复最高能累计到的次数,值越高,这个回复就会学习得越深,越容易进行回复,如果不想每次都大概率固定回复某一句话,可以将该值设低点。",
|
||||
),
|
||||
),
|
||||
InputTag(
|
||||
label="全局屏蔽词",
|
||||
name="ban_words",
|
||||
value="${ban_words}",
|
||||
enableBatchAdd=True,
|
||||
placeholder="添加全局屏蔽词",
|
||||
visibleOn="${total_enable}",
|
||||
joinValues=False,
|
||||
extractValue=True,
|
||||
labelRemark=Remark(
|
||||
shape="circle",
|
||||
content="全局屏蔽词,含有这些词的消息不会学习和回复,默认已屏蔽at、分享、语音、和视频等消息。(回车进行添加)",
|
||||
),
|
||||
),
|
||||
InputTag(
|
||||
label="全局屏蔽用户",
|
||||
source="${member_list}",
|
||||
name="ban_users",
|
||||
value="${ban_users}",
|
||||
enableBatchAdd=True,
|
||||
placeholder="添加全局屏蔽用户",
|
||||
visibleOn="${total_enable}",
|
||||
joinValues=False,
|
||||
extractValue=True,
|
||||
labelRemark=Remark(
|
||||
shape="circle", content="全局屏蔽用户,和这些用户有关的消息不会学习和回复。(回车进行添加)"
|
||||
),
|
||||
),
|
||||
InputTag(
|
||||
label="自定义词典",
|
||||
name="dictionary",
|
||||
value="${dictionary}",
|
||||
enableBatchAdd=True,
|
||||
placeholder="添加自定义词语",
|
||||
visibleOn="${total_enable}",
|
||||
joinValues=False,
|
||||
extractValue=True,
|
||||
labelRemark=Remark(
|
||||
shape="circle",
|
||||
content="添加自定义词语,让分词能够识别未收录的词汇,提高学习的准确性。你可以添加特殊名词,这样学习时就会将该词看作一个整体,目前词典中已默认添加部分原神相关词汇。(回车进行添加)",
|
||||
),
|
||||
),
|
||||
],
|
||||
actions=[
|
||||
Action(label="保存", level=LevelEnum.success, type="submit"),
|
||||
Action(label="重置", level=LevelEnum.warning, type="reset"),
|
||||
],
|
||||
actions=[Action(label='保存', level=LevelEnum.success, type='submit'),
|
||||
Action(label='重置', level=LevelEnum.warning, type='reset')]
|
||||
)
|
||||
group_select = Select(label='分群配置', name='group_id', source='${group_list}',
|
||||
placeholder='选择群')
|
||||
group_select = Select(
|
||||
label="分群配置", name="group_id", source="${group_list}", placeholder="选择群"
|
||||
)
|
||||
group_config_form = Form(
|
||||
title='分群配置',
|
||||
visibleOn='group_id != null',
|
||||
initApi='/LittlePaimon/api/chat_group_config?group_id=${group_id}',
|
||||
api='post:/LittlePaimon/api/chat_group_config?group_id=${group_id}',
|
||||
title="分群配置",
|
||||
visibleOn="group_id != null",
|
||||
initApi="/LittlePaimon/api/chat_group_config?group_id=${group_id}",
|
||||
api="post:/LittlePaimon/api/chat_group_config?group_id=${group_id}",
|
||||
interval=None,
|
||||
body=[
|
||||
Switch(label='群聊学习开关', name='enable', value='${enable}', onText='开启', offText='关闭',
|
||||
labelRemark=Remark(shape='circle', content='针对该群的群聊学习开关,关闭后,仅该群不会学习和回复。')),
|
||||
InputNumber(label='回复阈值', name='answer_threshold', value='${answer_threshold}', visibleOn='${enable}',
|
||||
min=2, labelRemark=Remark(shape='circle', content='可以理解为学习成功所需要的次数,值越低学得越快。')),
|
||||
InputArray(label='回复阈值权重', name='answer_threshold_weights', value='${answer_threshold_weights}',
|
||||
items=InputNumber(min=1, max=100, value=25, suffix='%'), inline=True, visibleOn='${enable}',
|
||||
labelRemark=Remark(shape='circle',
|
||||
content='影响回复阈值的计算方式,以默认的回复阈值4、权重[10, 30, 60]为例,在计算阈值时,60%概率为4,30%概率为3,10%概率为2。')),
|
||||
InputNumber(label='复读阈值', name='repeat_threshold', value='${repeat_threshold}', visibleOn='${enable}',
|
||||
min=2,
|
||||
labelRemark=Remark(shape='circle', content=f'跟随复读所需要的阈值,有N个人复读后,{NICKNAME}就会跟着复读。')),
|
||||
InputNumber(label='打断复读概率', name='break_probability', value='${break_probability}',
|
||||
min=0, max=100, suffix='%', visibleOn='${AND(enable, speak_enable)}',
|
||||
labelRemark=Remark(shape='circle', content='达到复读阈值时,打断复读而不是跟随复读的概率。')),
|
||||
InputTag(label='屏蔽词', name='ban_words', value='${ban_words}', enableBatchAdd=True,
|
||||
placeholder='添加屏蔽词', visibleOn='${enable}', joinValues=False, extractValue=True,
|
||||
labelRemark=Remark(shape='circle', content='含有这些词的消息不会学习和回复。(回车进行添加)')),
|
||||
InputTag(label='屏蔽用户', source='${member_list}', name='ban_users', value='${ban_users}', enableBatchAdd=True,
|
||||
placeholder='添加屏蔽用户', visibleOn='${enable}', joinValues=False, extractValue=True,
|
||||
labelRemark=Remark(shape='circle', content='和该群中这些用户有关的消息不会学习和回复。(回车进行添加)')),
|
||||
Switch(label='主动发言开关', name='speak_enable', value='${speak_enable}', visibleOn='${enable}',
|
||||
labelRemark=Remark(shape='circle', content=f'是否允许{NICKNAME}在该群主动发言,主动发言是指每隔一段时间挑选一个热度较高的群,主动发一些学习过的内容。')),
|
||||
InputNumber(label='主动发言阈值', name='speak_threshold', value='${speak_threshold}',
|
||||
visibleOn='${AND(enable, speak_enable)}', min=0,
|
||||
labelRemark=Remark(shape='circle', content='值越低,主动发言的可能性越高。')),
|
||||
InputNumber(label='主动发言最小间隔', name='speak_min_interval', value='${speak_min_interval}', min=0,
|
||||
visibleOn='${AND(enable, speak_enable)}', suffix='秒',
|
||||
labelRemark=Remark(shape='circle', content='进行主动发言的最小时间间隔。')),
|
||||
InputNumber(label='连续主动发言概率', name='speak_continuously_probability',
|
||||
value='${speak_continuously_probability}', min=0, max=100, suffix='%',
|
||||
visibleOn='${AND(enable, speak_enable)}', labelRemark=Remark(shape='circle', content='触发主动发言时,连续进行发言的概率。')),
|
||||
InputNumber(label='最大连续主动发言句数', name='speak_continuously_max_len',
|
||||
value='${speak_continuously_max_len}', visibleOn='${AND(enable, speak_enable)}', min=1,
|
||||
labelRemark=Remark(shape='circle', content='连续主动发言的最大句数。')),
|
||||
InputNumber(label='主动发言附带戳一戳概率', name='speak_poke_probability', value='${speak_poke_probability}',
|
||||
min=0, max=100, suffix='%', visibleOn='${AND(enable, speak_enable)}',
|
||||
labelRemark=Remark(shape='circle', content='主动发言时附带戳一戳的概率,会在最近5个发言者中随机选一个戳。')),
|
||||
Switch(
|
||||
label="群聊学习开关",
|
||||
name="enable",
|
||||
value="${enable}",
|
||||
onText="开启",
|
||||
offText="关闭",
|
||||
labelRemark=Remark(shape="circle", content="针对该群的群聊学习开关,关闭后,仅该群不会学习和回复。"),
|
||||
),
|
||||
InputNumber(
|
||||
label="回复阈值",
|
||||
name="answer_threshold",
|
||||
value="${answer_threshold}",
|
||||
visibleOn="${enable}",
|
||||
min=2,
|
||||
labelRemark=Remark(shape="circle", content="可以理解为学习成功所需要的次数,值越低学得越快。"),
|
||||
),
|
||||
InputArray(
|
||||
label="回复阈值权重",
|
||||
name="answer_threshold_weights",
|
||||
value="${answer_threshold_weights}",
|
||||
items=InputNumber(min=1, max=100, value=25, suffix="%"),
|
||||
inline=True,
|
||||
visibleOn="${enable}",
|
||||
labelRemark=Remark(
|
||||
shape="circle",
|
||||
content="影响回复阈值的计算方式,以默认的回复阈值4、权重[10, 30, 60]为例,在计算阈值时,60%概率为4,30%概率为3,10%概率为2。",
|
||||
),
|
||||
),
|
||||
InputNumber(
|
||||
label="复读阈值",
|
||||
name="repeat_threshold",
|
||||
value="${repeat_threshold}",
|
||||
visibleOn="${enable}",
|
||||
min=2,
|
||||
labelRemark=Remark(
|
||||
shape="circle", content=f"跟随复读所需要的阈值,有N个人复读后,{NICKNAME}就会跟着复读。"
|
||||
),
|
||||
),
|
||||
InputNumber(
|
||||
label="打断复读概率",
|
||||
name="break_probability",
|
||||
value="${break_probability}",
|
||||
min=0,
|
||||
max=100,
|
||||
suffix="%",
|
||||
visibleOn="${AND(enable, speak_enable)}",
|
||||
labelRemark=Remark(shape="circle", content="达到复读阈值时,打断复读而不是跟随复读的概率。"),
|
||||
),
|
||||
InputTag(
|
||||
label="屏蔽词",
|
||||
name="ban_words",
|
||||
value="${ban_words}",
|
||||
enableBatchAdd=True,
|
||||
placeholder="添加屏蔽词",
|
||||
visibleOn="${enable}",
|
||||
joinValues=False,
|
||||
extractValue=True,
|
||||
labelRemark=Remark(shape="circle", content="含有这些词的消息不会学习和回复。(回车进行添加)"),
|
||||
),
|
||||
InputTag(
|
||||
label="屏蔽用户",
|
||||
source="${member_list}",
|
||||
name="ban_users",
|
||||
value="${ban_users}",
|
||||
enableBatchAdd=True,
|
||||
placeholder="添加屏蔽用户",
|
||||
visibleOn="${enable}",
|
||||
joinValues=False,
|
||||
extractValue=True,
|
||||
labelRemark=Remark(shape="circle", content="和该群中这些用户有关的消息不会学习和回复。(回车进行添加)"),
|
||||
),
|
||||
Switch(
|
||||
label="主动发言开关",
|
||||
name="speak_enable",
|
||||
value="${speak_enable}",
|
||||
visibleOn="${enable}",
|
||||
labelRemark=Remark(
|
||||
shape="circle",
|
||||
content=f"是否允许{NICKNAME}在该群主动发言,主动发言是指每隔一段时间挑选一个热度较高的群,主动发一些学习过的内容。",
|
||||
),
|
||||
),
|
||||
InputNumber(
|
||||
label="主动发言阈值",
|
||||
name="speak_threshold",
|
||||
value="${speak_threshold}",
|
||||
visibleOn="${AND(enable, speak_enable)}",
|
||||
min=0,
|
||||
labelRemark=Remark(shape="circle", content="值越低,主动发言的可能性越高。"),
|
||||
),
|
||||
InputNumber(
|
||||
label="主动发言最小间隔",
|
||||
name="speak_min_interval",
|
||||
value="${speak_min_interval}",
|
||||
min=0,
|
||||
visibleOn="${AND(enable, speak_enable)}",
|
||||
suffix="秒",
|
||||
labelRemark=Remark(shape="circle", content="进行主动发言的最小时间间隔。"),
|
||||
),
|
||||
InputNumber(
|
||||
label="连续主动发言概率",
|
||||
name="speak_continuously_probability",
|
||||
value="${speak_continuously_probability}",
|
||||
min=0,
|
||||
max=100,
|
||||
suffix="%",
|
||||
visibleOn="${AND(enable, speak_enable)}",
|
||||
labelRemark=Remark(shape="circle", content="触发主动发言时,连续进行发言的概率。"),
|
||||
),
|
||||
InputNumber(
|
||||
label="最大连续主动发言句数",
|
||||
name="speak_continuously_max_len",
|
||||
value="${speak_continuously_max_len}",
|
||||
visibleOn="${AND(enable, speak_enable)}",
|
||||
min=1,
|
||||
labelRemark=Remark(shape="circle", content="连续主动发言的最大句数。"),
|
||||
),
|
||||
InputNumber(
|
||||
label="主动发言附带戳一戳概率",
|
||||
name="speak_poke_probability",
|
||||
value="${speak_poke_probability}",
|
||||
min=0,
|
||||
max=100,
|
||||
suffix="%",
|
||||
visibleOn="${AND(enable, speak_enable)}",
|
||||
labelRemark=Remark(
|
||||
shape="circle", content="主动发言时附带戳一戳的概率,会在最近5个发言者中随机选一个戳。"
|
||||
),
|
||||
),
|
||||
],
|
||||
actions=[
|
||||
Action(label="保存", level=LevelEnum.success, type="submit"),
|
||||
ActionType.Ajax(
|
||||
label="保存至所有群",
|
||||
level=LevelEnum.primary,
|
||||
confirmText="确认将当前配置保存至所有群?",
|
||||
api="post:/LittlePaimon/api/chat_group_config?group_id=all",
|
||||
),
|
||||
Action(label="重置", level=LevelEnum.warning, type="reset"),
|
||||
],
|
||||
actions=[Action(label='保存', level=LevelEnum.success, type='submit'),
|
||||
ActionType.Ajax(
|
||||
label='保存至所有群',
|
||||
level=LevelEnum.primary,
|
||||
confirmText='确认将当前配置保存至所有群?',
|
||||
api='post:/LittlePaimon/api/chat_group_config?group_id=all'
|
||||
),
|
||||
Action(label='重置', level=LevelEnum.warning, type='reset')]
|
||||
)
|
||||
|
||||
blacklist_table = TableCRUD(mode='table',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/LittlePaimon/api/get_chat_blacklist',
|
||||
interval=15000,
|
||||
headerToolbar=[ActionType.Ajax(label='取消所有禁用',
|
||||
level=LevelEnum.warning,
|
||||
confirmText='确定要取消所有禁用吗?',
|
||||
api='put:/LittlePaimon/api/delete_all?type=blacklist')],
|
||||
itemActions=[ActionType.Ajax(tooltip='取消禁用',
|
||||
icon='fa fa-check-circle-o text-info',
|
||||
confirmText='取消该被禁用的内容/关键词,但是仍然需要重新学习哦!',
|
||||
api='delete:/LittlePaimon/api/delete_chat?type=blacklist&id=${id}')
|
||||
],
|
||||
footable=True,
|
||||
columns=[TableColumn(type='tpl', tpl='${keywords|truncate:20}', label='内容/关键词',
|
||||
name='keywords',
|
||||
searchable=True, popOver={'mode': 'dialog', 'title': '全文',
|
||||
'className': 'break-all',
|
||||
'body': {'type': 'tpl',
|
||||
'tpl': '${keywords}'}}),
|
||||
TableColumn(label='已禁用的群', name='bans', searchable=True),
|
||||
])
|
||||
message_table = TableCRUD(mode='table',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/LittlePaimon/api/get_chat_messages',
|
||||
interval=12000,
|
||||
headerToolbar=[ActionType.Ajax(label='删除所有聊天记录',
|
||||
level=LevelEnum.warning,
|
||||
confirmText='确定要删除所有聊天记录吗?',
|
||||
api='put:/LittlePaimon/api/delete_all?type=message')],
|
||||
itemActions=[ActionType.Ajax(tooltip='禁用',
|
||||
icon='fa fa-ban text-danger',
|
||||
confirmText='禁用该聊天记录相关的学习内容和回复',
|
||||
api='put:/LittlePaimon/api/ban_chat?type=message&id=${id}'),
|
||||
ActionType.Ajax(tooltip='删除',
|
||||
icon='fa fa-times text-danger',
|
||||
confirmText='删除该条聊天记录',
|
||||
api='delete:/LittlePaimon/api/delete_chat?type=message&id=${id}')
|
||||
],
|
||||
footable=True,
|
||||
columns=[TableColumn(label='消息ID', name='message_id'),
|
||||
TableColumn(label='群ID', name='group_id', searchable=True),
|
||||
TableColumn(label='用户ID', name='user_id', searchable=True),
|
||||
TableColumn(type='tpl', tpl='${raw_message|truncate:20}', label='消息', name='message',
|
||||
searchable=True, popOver={'mode': 'dialog', 'title': '消息全文',
|
||||
'className': 'break-all',
|
||||
'body': {'type': 'tpl',
|
||||
'tpl': '${raw_message}'}}),
|
||||
TableColumn(type='tpl', tpl='${time|date:YYYY-MM-DD HH\\:mm\\:ss}', label='时间',
|
||||
name='time', sortable=True)
|
||||
])
|
||||
blacklist_table = TableCRUD(
|
||||
mode="table",
|
||||
title="",
|
||||
syncLocation=False,
|
||||
api="/LittlePaimon/api/get_chat_blacklist",
|
||||
interval=15000,
|
||||
headerToolbar=[
|
||||
ActionType.Ajax(
|
||||
label="取消所有禁用",
|
||||
level=LevelEnum.warning,
|
||||
confirmText="确定要取消所有禁用吗?",
|
||||
api="put:/LittlePaimon/api/delete_all?type=blacklist",
|
||||
)
|
||||
],
|
||||
itemActions=[
|
||||
ActionType.Ajax(
|
||||
tooltip="取消禁用",
|
||||
icon="fa fa-check-circle-o text-info",
|
||||
confirmText="取消该被禁用的内容/关键词,但是仍然需要重新学习哦!",
|
||||
api="delete:/LittlePaimon/api/delete_chat?type=blacklist&id=${id}",
|
||||
)
|
||||
],
|
||||
footable=True,
|
||||
columns=[
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${keywords|truncate:20}",
|
||||
label="内容/关键词",
|
||||
name="keywords",
|
||||
searchable=True,
|
||||
popOver={
|
||||
"mode": "dialog",
|
||||
"title": "全文",
|
||||
"className": "break-all",
|
||||
"body": {"type": "tpl", "tpl": "${keywords}"},
|
||||
},
|
||||
),
|
||||
TableColumn(label="已禁用的群", name="bans", searchable=True),
|
||||
],
|
||||
)
|
||||
message_table = TableCRUD(
|
||||
mode="table",
|
||||
title="",
|
||||
syncLocation=False,
|
||||
api="/LittlePaimon/api/get_chat_messages",
|
||||
interval=12000,
|
||||
headerToolbar=[
|
||||
ActionType.Ajax(
|
||||
label="删除所有聊天记录",
|
||||
level=LevelEnum.warning,
|
||||
confirmText="确定要删除所有聊天记录吗?",
|
||||
api="put:/LittlePaimon/api/delete_all?type=message",
|
||||
)
|
||||
],
|
||||
itemActions=[
|
||||
ActionType.Ajax(
|
||||
tooltip="禁用",
|
||||
icon="fa fa-ban text-danger",
|
||||
confirmText="禁用该聊天记录相关的学习内容和回复",
|
||||
api="put:/LittlePaimon/api/ban_chat?type=message&id=${id}",
|
||||
),
|
||||
ActionType.Ajax(
|
||||
tooltip="删除",
|
||||
icon="fa fa-times text-danger",
|
||||
confirmText="删除该条聊天记录",
|
||||
api="delete:/LittlePaimon/api/delete_chat?type=message&id=${id}",
|
||||
),
|
||||
],
|
||||
footable=True,
|
||||
columns=[
|
||||
TableColumn(label="消息ID", name="message_id"),
|
||||
TableColumn(label="群ID", name="group_id", searchable=True),
|
||||
TableColumn(label="用户ID", name="user_id", searchable=True),
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${raw_message|truncate:20}",
|
||||
label="消息",
|
||||
name="message",
|
||||
searchable=True,
|
||||
popOver={
|
||||
"mode": "dialog",
|
||||
"title": "消息全文",
|
||||
"className": "break-all",
|
||||
"body": {"type": "tpl", "tpl": "${raw_message}"},
|
||||
},
|
||||
),
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${time|date:YYYY-MM-DD HH\\:mm\\:ss}",
|
||||
label="时间",
|
||||
name="time",
|
||||
sortable=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
answer_table = TableCRUD(
|
||||
mode='table',
|
||||
mode="table",
|
||||
syncLocation=False,
|
||||
footable=True,
|
||||
api='/LittlePaimon/api/get_chat_answers',
|
||||
api="/LittlePaimon/api/get_chat_answers",
|
||||
interval=12000,
|
||||
headerToolbar=[ActionType.Ajax(label='删除所有已学习的回复',
|
||||
level=LevelEnum.warning,
|
||||
confirmText='确定要删除所有已学习的回复吗?',
|
||||
api='put:/LittlePaimon/api/delete_all?type=answer')],
|
||||
itemActions=[ActionType.Ajax(tooltip='禁用',
|
||||
icon='fa fa-ban text-danger',
|
||||
confirmText='禁用并删除该已学回复',
|
||||
api='put:/LittlePaimon/api/ban_chat?type=answer&id=${id}'),
|
||||
ActionType.Ajax(tooltip='删除',
|
||||
icon='fa fa-times text-danger',
|
||||
confirmText='仅删除该已学回复,不会禁用,所以依然能继续学',
|
||||
api='delete:/LittlePaimon/api/delete_chat?type=answer&id=${id}')],
|
||||
columns=[TableColumn(label='ID', name='id', visible=False),
|
||||
TableColumn(label='群ID', name='group_id', searchable=True),
|
||||
TableColumn(type='tpl', tpl='${keywords|truncate:20}', label='内容/关键词', name='keywords',
|
||||
searchable=True, popOver={'mode': 'dialog', 'title': '内容全文', 'className': 'break-all',
|
||||
'body': {'type': 'tpl', 'tpl': '${keywords}'}}),
|
||||
TableColumn(type='tpl', tpl='${time|date:YYYY-MM-DD HH\\:mm\\:ss}', label='最后学习时间', name='time',
|
||||
sortable=True),
|
||||
TableColumn(label='次数', name='count', sortable=True),
|
||||
ColumnList(label='完整消息', name='messages', breakpoint='*', source='${messages}',
|
||||
listItem=AmisList.Item(body={'name': 'msg'}))
|
||||
])
|
||||
headerToolbar=[
|
||||
ActionType.Ajax(
|
||||
label="删除所有已学习的回复",
|
||||
level=LevelEnum.warning,
|
||||
confirmText="确定要删除所有已学习的回复吗?",
|
||||
api="put:/LittlePaimon/api/delete_all?type=answer",
|
||||
)
|
||||
],
|
||||
itemActions=[
|
||||
ActionType.Ajax(
|
||||
tooltip="禁用",
|
||||
icon="fa fa-ban text-danger",
|
||||
confirmText="禁用并删除该已学回复",
|
||||
api="put:/LittlePaimon/api/ban_chat?type=answer&id=${id}",
|
||||
),
|
||||
ActionType.Ajax(
|
||||
tooltip="删除",
|
||||
icon="fa fa-times text-danger",
|
||||
confirmText="仅删除该已学回复,不会禁用,所以依然能继续学",
|
||||
api="delete:/LittlePaimon/api/delete_chat?type=answer&id=${id}",
|
||||
),
|
||||
],
|
||||
columns=[
|
||||
TableColumn(label="ID", name="id", visible=False),
|
||||
TableColumn(label="群ID", name="group_id", searchable=True),
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${keywords|truncate:20}",
|
||||
label="内容/关键词",
|
||||
name="keywords",
|
||||
searchable=True,
|
||||
popOver={
|
||||
"mode": "dialog",
|
||||
"title": "内容全文",
|
||||
"className": "break-all",
|
||||
"body": {"type": "tpl", "tpl": "${keywords}"},
|
||||
},
|
||||
),
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${time|date:YYYY-MM-DD HH\\:mm\\:ss}",
|
||||
label="最后学习时间",
|
||||
name="time",
|
||||
sortable=True,
|
||||
),
|
||||
TableColumn(label="次数", name="count", sortable=True),
|
||||
ColumnList(
|
||||
label="完整消息",
|
||||
name="messages",
|
||||
breakpoint="*",
|
||||
source="${messages}",
|
||||
listItem=AmisList.Item(body=[AmisList.Item.ListBodyField(name="msg")]),
|
||||
),
|
||||
],
|
||||
)
|
||||
answer_table_on_context = TableCRUD(
|
||||
mode='table',
|
||||
mode="table",
|
||||
syncLocation=False,
|
||||
footable=True,
|
||||
api='/LittlePaimon/api/get_chat_answers?context_id=${id}&page=${page}&perPage=${perPage}&orderBy=${orderBy}&orderDir=${orderDir}',
|
||||
api="/LittlePaimon/api/get_chat_answers?context_id=${id}&page=${page}&perPage=${perPage}&orderBy=${orderBy}&orderDir=${orderDir}",
|
||||
interval=12000,
|
||||
headerToolbar=[ActionType.Ajax(label='删除该内容所有回复',
|
||||
level=LevelEnum.warning,
|
||||
confirmText='确定要删除该条内容已学习的回复吗?',
|
||||
api='put:/LittlePaimon/api/delete_all?type=answer&id=${id}')],
|
||||
itemActions=[ActionType.Ajax(tooltip='禁用',
|
||||
icon='fa fa-ban text-danger',
|
||||
confirmText='禁用并删除该已学回复',
|
||||
api='put:/LittlePaimon/api/ban_chat?type=answer&id=${id}'),
|
||||
ActionType.Ajax(tooltip='删除',
|
||||
icon='fa fa-times text-danger',
|
||||
confirmText='仅删除该已学回复,但不禁用,依然能继续学',
|
||||
api='delete:/LittlePaimon/api/delete_chat?type=answer&id=${id}')],
|
||||
columns=[TableColumn(label='ID', name='id', visible=False),
|
||||
TableColumn(label='群ID', name='group_id'),
|
||||
TableColumn(type='tpl', tpl='${keywords|truncate:20}', label='内容/关键词', name='keywords',
|
||||
searchable=True, popOver={'mode': 'dialog', 'title': '内容全文', 'className': 'break-all',
|
||||
'body': {'type': 'tpl', 'tpl': '${keywords}'}}),
|
||||
TableColumn(type='tpl', tpl='${time|date:YYYY-MM-DD HH\\:mm\\:ss}', label='最后学习时间', name='time',
|
||||
sortable=True),
|
||||
TableColumn(label='次数', name='count', sortable=True),
|
||||
ColumnList(label='完整消息', name='messages', breakpoint='*', source='${messages}',
|
||||
listItem=AmisList.Item(body={'name': 'msg'}))
|
||||
])
|
||||
context_table = TableCRUD(mode='table',
|
||||
title='',
|
||||
syncLocation=False,
|
||||
api='/LittlePaimon/api/get_chat_contexts',
|
||||
interval=12000,
|
||||
headerToolbar=[ActionType.Ajax(label='删除所有学习内容',
|
||||
level=LevelEnum.warning,
|
||||
confirmText='确定要删除所有已学习的内容吗?',
|
||||
api='put:/LittlePaimon/api/delete_all?type=context')],
|
||||
itemActions=[ActionType.Dialog(tooltip='回复列表',
|
||||
icon='fa fa-book text-info',
|
||||
dialog=Dialog(title='回复列表',
|
||||
size='lg',
|
||||
body=answer_table_on_context)),
|
||||
ActionType.Ajax(tooltip='禁用',
|
||||
icon='fa fa-ban text-danger',
|
||||
confirmText='禁用并删除该学习的内容及其所有回复',
|
||||
api='put:/LittlePaimon/api/ban_chat?type=context&id=${id}'),
|
||||
ActionType.Ajax(tooltip='删除',
|
||||
icon='fa fa-times text-danger',
|
||||
confirmText='仅删除该学习的内容及其所有回复,但不禁用,依然能继续学',
|
||||
api='delete:/LittlePaimon/api/delete_chat?type=context&id=${id}')
|
||||
],
|
||||
footable=True,
|
||||
columns=[TableColumn(label='ID', name='id', visible=False),
|
||||
TableColumn(type='tpl', tpl='${keywords|truncate:20}', label='内容/关键词',
|
||||
name='keywords', searchable=True,
|
||||
popOver={'mode': 'dialog', 'title': '内容全文', 'className': 'break-all',
|
||||
'body': {'type': 'tpl', 'tpl': '${keywords}'}}),
|
||||
TableColumn(type='tpl', tpl='${time|date:YYYY-MM-DD HH\\:mm\\:ss}',
|
||||
label='最后学习时间', name='time', sortable=True),
|
||||
TableColumn(label='已学次数', name='count', sortable=True),
|
||||
])
|
||||
headerToolbar=[
|
||||
ActionType.Ajax(
|
||||
label="删除该内容所有回复",
|
||||
level=LevelEnum.warning,
|
||||
confirmText="确定要删除该条内容已学习的回复吗?",
|
||||
api="put:/LittlePaimon/api/delete_all?type=answer&id=${id}",
|
||||
)
|
||||
],
|
||||
itemActions=[
|
||||
ActionType.Ajax(
|
||||
tooltip="禁用",
|
||||
icon="fa fa-ban text-danger",
|
||||
confirmText="禁用并删除该已学回复",
|
||||
api="put:/LittlePaimon/api/ban_chat?type=answer&id=${id}",
|
||||
),
|
||||
ActionType.Ajax(
|
||||
tooltip="删除",
|
||||
icon="fa fa-times text-danger",
|
||||
confirmText="仅删除该已学回复,但不禁用,依然能继续学",
|
||||
api="delete:/LittlePaimon/api/delete_chat?type=answer&id=${id}",
|
||||
),
|
||||
],
|
||||
columns=[
|
||||
TableColumn(label="ID", name="id", visible=False),
|
||||
TableColumn(label="群ID", name="group_id"),
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${keywords|truncate:20}",
|
||||
label="内容/关键词",
|
||||
name="keywords",
|
||||
searchable=True,
|
||||
popOver={
|
||||
"mode": "dialog",
|
||||
"title": "内容全文",
|
||||
"className": "break-all",
|
||||
"body": {"type": "tpl", "tpl": "${keywords}"},
|
||||
},
|
||||
),
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${time|date:YYYY-MM-DD HH\\:mm\\:ss}",
|
||||
label="最后学习时间",
|
||||
name="time",
|
||||
sortable=True,
|
||||
),
|
||||
TableColumn(label="次数", name="count", sortable=True),
|
||||
ColumnList(
|
||||
label="完整消息",
|
||||
name="messages",
|
||||
breakpoint="*",
|
||||
source="${messages}",
|
||||
listItem=AmisList.Item(body=[AmisList.Item.ListBodyField(name="msg")]),
|
||||
),
|
||||
],
|
||||
)
|
||||
context_table = TableCRUD(
|
||||
mode="table",
|
||||
title="",
|
||||
syncLocation=False,
|
||||
api="/LittlePaimon/api/get_chat_contexts",
|
||||
interval=12000,
|
||||
headerToolbar=[
|
||||
ActionType.Ajax(
|
||||
label="删除所有学习内容",
|
||||
level=LevelEnum.warning,
|
||||
confirmText="确定要删除所有已学习的内容吗?",
|
||||
api="put:/LittlePaimon/api/delete_all?type=context",
|
||||
)
|
||||
],
|
||||
itemActions=[
|
||||
ActionType.Dialog(
|
||||
tooltip="回复列表",
|
||||
icon="fa fa-book text-info",
|
||||
dialog=Dialog(title="回复列表", size="lg", body=answer_table_on_context),
|
||||
),
|
||||
ActionType.Ajax(
|
||||
tooltip="禁用",
|
||||
icon="fa fa-ban text-danger",
|
||||
confirmText="禁用并删除该学习的内容及其所有回复",
|
||||
api="put:/LittlePaimon/api/ban_chat?type=context&id=${id}",
|
||||
),
|
||||
ActionType.Ajax(
|
||||
tooltip="删除",
|
||||
icon="fa fa-times text-danger",
|
||||
confirmText="仅删除该学习的内容及其所有回复,但不禁用,依然能继续学",
|
||||
api="delete:/LittlePaimon/api/delete_chat?type=context&id=${id}",
|
||||
),
|
||||
],
|
||||
footable=True,
|
||||
columns=[
|
||||
TableColumn(label="ID", name="id", visible=False),
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${keywords|truncate:20}",
|
||||
label="内容/关键词",
|
||||
name="keywords",
|
||||
searchable=True,
|
||||
popOver={
|
||||
"mode": "dialog",
|
||||
"title": "内容全文",
|
||||
"className": "break-all",
|
||||
"body": {"type": "tpl", "tpl": "${keywords}"},
|
||||
},
|
||||
),
|
||||
TableColumn(
|
||||
type="tpl",
|
||||
tpl="${time|date:YYYY-MM-DD HH\\:mm\\:ss}",
|
||||
label="最后学习时间",
|
||||
name="time",
|
||||
sortable=True,
|
||||
),
|
||||
TableColumn(label="已学次数", name="count", sortable=True),
|
||||
],
|
||||
)
|
||||
|
||||
message_page = PageSchema(url='/chat/messages', icon='fa fa-comments', label='群聊消息',
|
||||
schema=Page(title='群聊消息', body=[
|
||||
Alert(level=LevelEnum.info,
|
||||
className='white-space-pre-wrap',
|
||||
body=(f'此数据库记录了{NICKNAME}收到的除指令外的聊天记录。\n'
|
||||
'· 点击"禁用"可以将某条聊天记录进行禁用,这样其相关的学习就会列入禁用列表。\n'
|
||||
'· 点击"删除"可以删除某条记录,但不会影响它的学习。\n'
|
||||
f'· 可以通过搜索{NICKNAME}的QQ号,来查看它的回复记录。')),
|
||||
message_table]))
|
||||
context_page = PageSchema(url='/chat/contexts', icon='fa fa-comment', label='学习内容',
|
||||
schema=Page(title='内容',
|
||||
body=[Alert(level=LevelEnum.info,
|
||||
className='white-space-pre-wrap',
|
||||
body=(f'此数据库记录了{NICKNAME}所学习的内容。\n'
|
||||
'· 点击"回复列表"可以查看该条内容已学习到的可能的回复。\n'
|
||||
'· 点击"禁用"可以将该学习进行禁用,以后不会再学。\n'
|
||||
'· 点击"删除"可以删除该学习,让它重新开始学习这句话。')),
|
||||
context_table]))
|
||||
answer_page = PageSchema(url='/chat/answers', icon='fa fa-commenting-o', label='内容回复',
|
||||
schema=Page(title='回复',
|
||||
body=[Alert(level=LevelEnum.info,
|
||||
className='white-space-pre-wrap',
|
||||
body=(f'此数据库记录了{NICKNAME}已学习到的所有回复,但看不到这些回复属于哪些内容,推荐到"学习内容"表进行操作。\n'
|
||||
'· 点击"禁用"可以将该回复进行禁用,以后不会再学。\n'
|
||||
'· 点击"删除"可以删除该回复,让它重新开始学习。')),
|
||||
answer_table]))
|
||||
blacklist_page = PageSchema(url='/chat/blacklist', icon='fa fa-ban', label='禁用列表',
|
||||
schema=Page(title='禁用列表',
|
||||
body=[Alert(level=LevelEnum.info,
|
||||
className='white-space-pre-wrap',
|
||||
body=f'此数据库记录了{NICKNAME}被禁用的内容/关键词。\n'
|
||||
'· 可以取消禁用,使其能够重新继续学习。\n'
|
||||
'· 不能在此添加禁用,只能在群中回复[不可以]或者在<配置>中添加屏蔽词来达到禁用效果。'),
|
||||
blacklist_table]))
|
||||
database_page = PageSchema(label='数据库', icon='fa fa-database',
|
||||
children=[message_page, context_page, answer_page, blacklist_page])
|
||||
config_page = PageSchema(url='/chat/configs', icon='fa fa-wrench', label='配置',
|
||||
schema=Page(title='配置', initApi='/LittlePaimon/api/get_group_list', body=[global_config_form, group_select, group_config_form]))
|
||||
chat_page = PageSchema(label='群聊学习', icon='fa fa-wechat (alias)', children=[config_page, database_page])
|
||||
message_page = PageSchema(
|
||||
url="/chat/messages",
|
||||
icon="fa fa-comments",
|
||||
label="群聊消息",
|
||||
schema=Page(
|
||||
title="群聊消息",
|
||||
body=[
|
||||
Alert(
|
||||
level=LevelEnum.info,
|
||||
className="white-space-pre-wrap",
|
||||
body=(
|
||||
f"此数据库记录了{NICKNAME}收到的除指令外的聊天记录。\n"
|
||||
'· 点击"禁用"可以将某条聊天记录进行禁用,这样其相关的学习就会列入禁用列表。\n'
|
||||
'· 点击"删除"可以删除某条记录,但不会影响它的学习。\n'
|
||||
f"· 可以通过搜索{NICKNAME}的QQ号,来查看它的回复记录。"
|
||||
),
|
||||
),
|
||||
message_table,
|
||||
],
|
||||
),
|
||||
)
|
||||
context_page = PageSchema(
|
||||
url="/chat/contexts",
|
||||
icon="fa fa-comment",
|
||||
label="学习内容",
|
||||
schema=Page(
|
||||
title="内容",
|
||||
body=[
|
||||
Alert(
|
||||
level=LevelEnum.info,
|
||||
className="white-space-pre-wrap",
|
||||
body=(
|
||||
f"此数据库记录了{NICKNAME}所学习的内容。\n"
|
||||
'· 点击"回复列表"可以查看该条内容已学习到的可能的回复。\n'
|
||||
'· 点击"禁用"可以将该学习进行禁用,以后不会再学。\n'
|
||||
'· 点击"删除"可以删除该学习,让它重新开始学习这句话。'
|
||||
),
|
||||
),
|
||||
context_table,
|
||||
],
|
||||
),
|
||||
)
|
||||
answer_page = PageSchema(
|
||||
url="/chat/answers",
|
||||
icon="fa fa-commenting-o",
|
||||
label="内容回复",
|
||||
schema=Page(
|
||||
title="回复",
|
||||
body=[
|
||||
Alert(
|
||||
level=LevelEnum.info,
|
||||
className="white-space-pre-wrap",
|
||||
body=(
|
||||
f'此数据库记录了{NICKNAME}已学习到的所有回复,但看不到这些回复属于哪些内容,推荐到"学习内容"表进行操作。\n'
|
||||
'· 点击"禁用"可以将该回复进行禁用,以后不会再学。\n'
|
||||
'· 点击"删除"可以删除该回复,让它重新开始学习。'
|
||||
),
|
||||
),
|
||||
answer_table,
|
||||
],
|
||||
),
|
||||
)
|
||||
blacklist_page = PageSchema(
|
||||
url="/chat/blacklist",
|
||||
icon="fa fa-ban",
|
||||
label="禁用列表",
|
||||
schema=Page(
|
||||
title="禁用列表",
|
||||
interval=60000,
|
||||
body=[
|
||||
Alert(
|
||||
level=LevelEnum.info,
|
||||
className="white-space-pre-wrap",
|
||||
body=f"此数据库记录了{NICKNAME}被禁用的内容/关键词。\n"
|
||||
"· 可以取消禁用,使其能够重新继续学习。\n"
|
||||
"· 不能在此添加禁用,只能在群中回复[不可以]或者在<配置>中添加屏蔽词来达到禁用效果。",
|
||||
),
|
||||
blacklist_table,
|
||||
],
|
||||
),
|
||||
)
|
||||
database_page = PageSchema(
|
||||
label="数据库",
|
||||
icon="fa fa-database",
|
||||
children=[message_page, context_page, answer_page, blacklist_page],
|
||||
)
|
||||
config_page = PageSchema(
|
||||
url="/chat/configs",
|
||||
icon="fa fa-wrench",
|
||||
label="配置",
|
||||
schema=Page(
|
||||
title="配置",
|
||||
initApi="/LittlePaimon/api/get_group_list",
|
||||
body=[global_config_form, group_select, group_config_form],
|
||||
),
|
||||
)
|
||||
chat_page = PageSchema(
|
||||
label="群聊学习", icon="fa fa-wechat (alias)", children=[config_page, database_page]
|
||||
)
|
||||
admin_app.pages[0].children.append(chat_page)
|
||||
|
Loading…
x
Reference in New Issue
Block a user