From ebe4b67e6cc74169b62b0508ffaf1d22f70f8670 Mon Sep 17 00:00:00 2001
From: CMHopeSunshine <277073121@qq.com>
Date: Sat, 10 Jun 2023 17:22:43 +0800
Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20=E6=A0=BC=E5=BC=8F=E5=8C=96?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=8C=E9=80=82=E9=85=8D=E6=96=B0`amis`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../plugins/Learning_Chat/__init__.py | 89 +-
LittlePaimon/plugins/Learning_Chat/config.py | 43 +-
LittlePaimon/plugins/Learning_Chat/handler.py | 502 ++++++----
LittlePaimon/plugins/Learning_Chat/models.py | 39 +-
LittlePaimon/plugins/Learning_Chat/web_api.py | 345 ++++---
.../plugins/Learning_Chat/web_page.py | 859 ++++++++++++------
6 files changed, 1243 insertions(+), 634 deletions(-)
diff --git a/LittlePaimon/plugins/Learning_Chat/__init__.py b/LittlePaimon/plugins/Learning_Chat/__init__.py
index 2ff2059..97fde20 100644
--- a/LittlePaimon/plugins/Learning_Chat/__init__.py
+++ b/LittlePaimon/plugins/Learning_Chat/__init__.py
@@ -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}将向群{event.group_id}回复"{answer}"')
+ logger.info(
+ "群聊学习", f'{NICKNAME}将向群{event.group_id}回复"{answer}"'
+ )
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}向群{event.group_id}的回复"{answer}"发送失败,可能处于风控中')
+ logger.info(
+ "群聊学习",
+ f'{NICKNAME}向群{event.group_id}的回复"{answer}"发送失败,可能处于风控中',
+ )
-@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}向群{group_id}主动发言"{msg}"')
- 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}向群{group_id}主动发言"{msg}"')
+ 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}向群{group_id}主动发言"{msg}"发送失败,可能处于风控中')
+ logger.info(
+ "群聊学习",
+ f'{NICKNAME}向群{group_id}主动发言"{msg}"发送失败,可能处于风控中',
+ )
diff --git a/LittlePaimon/plugins/Learning_Chat/config.py b/LittlePaimon/plugins/Learning_Chat/config.py
index 8f4fd68..fa7b752 100644
--- a/LittlePaimon/plugins/Learning_Chat/config.py
+++ b/LittlePaimon/plugins/Learning_Chat/config.py
@@ -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():
diff --git a/LittlePaimon/plugins/Learning_Chat/handler.py b/LittlePaimon/plugins/Learning_Chat/handler.py
index 676f59b..9d66c8d 100644
--- a/LittlePaimon/plugins/Learning_Chat/handler.py
+++ b/LittlePaimon/plugins/Learning_Chat/handler.py
@@ -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'➤该群{self.data.group_id}未开启群聊学习,跳过')
+ logger.debug("群聊学习", f"➤该群{self.data.group_id}未开启群聊学习,跳过")
# 如果未开启群聊学习,跳过
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'➤发言人{self.data.user_id}在屏蔽列表中,跳过')
+ logger.debug("群聊学习", f"➤发言人{self.data.user_id}在屏蔽列表中,跳过")
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'群{self.data.group_id}{"开启" if result == Result.SetEnable else "关闭"}学习功能')
- return [random.choice(ENABLE_WORDS if result == Result.SetEnable else DISABLE_WORDS)]
+ logger.info(
+ "群聊学习",
+ f'群{self.data.group_id}{"开启" 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'➤➤达到复读阈值,复读{messages[0].message}')
+ logger.debug("群聊学习", f"➤➤达到复读阈值,复读{messages[0].message}")
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'➤➤本次回复阈值为{answer_count_threshold},跨群阈值为{cross_group_threshold}')
+ logger.debug(
+ "群聊学习",
+ f"➤➤本次回复阈值为{answer_count_threshold},跨群阈值为{cross_group_threshold}",
+ )
# 获取满足跨群条件的回复
- 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'➤➤候选回复有{"|".join([f"""{a.keywords}({round(p, 3)})""" for a, p in answer_dict])}|不回复({round(per_list[-1], 3)})')
+ logger.debug(
+ "群聊学习",
+ f'➤➤候选回复有{"|".join([f"""{a.keywords}({round(p, 3)})""" for a, p in answer_dict])}|不回复({round(per_list[-1], 3)})',
+ )
- 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'➤➤将回复{result_message}')
+ logger.debug("群聊学习", f"➤➤将回复{result_message}")
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'待禁用消息{message_id}尝试撤回失败')
- 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"待禁用消息{message_id}尝试撤回失败")
+ 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'待禁用消息{last_reply.message_id}尝试撤回失败')
+ logger.info("群聊学习", f"待禁用消息{last_reply.message_id}尝试撤回失败")
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'学习词{keywords}将被全局禁用')
+ logger.info("群聊学习", f"学习词{keywords}将被全局禁用")
await ChatAnswer.filter(keywords=keywords).delete()
else:
- logger.info('群聊学习', f'群{self.data.group_id}禁用了学习词{keywords}')
- await ChatAnswer.filter(keywords=keywords, group_id=self.data.group_id).delete()
+ logger.info(
+ "群聊学习", f"群{self.data.group_id}禁用了学习词{keywords}"
+ )
+ await ChatAnswer.filter(
+ keywords=keywords, group_id=self.data.group_id
+ ).delete()
else:
# 没有屏蔽记录,则新建
- logger.info('群聊学习', f'群{self.data.group_id}禁用了学习词{keywords}')
- 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"群{self.data.group_id}禁用了学习词{keywords}")
+ 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'学习词{data.keywords}将被全局禁用')
+ logger.info("群聊学习", f"学习词{data.keywords}将被全局禁用")
await ChatAnswer.filter(keywords=data.keywords).delete()
else:
- logger.info('群聊学习', f'群{data.group_id}禁用了学习词{data.keywords}')
- await ChatAnswer.filter(keywords=data.keywords, group_id=data.group_id).delete()
+ logger.info(
+ "群聊学习", f"群{data.group_id}禁用了学习词{data.keywords}"
+ )
+ await ChatAnswer.filter(
+ keywords=data.keywords, group_id=data.group_id
+ ).delete()
else:
ban_word.global_ban = True
- logger.info('群聊学习', f'学习词{data.keywords}将被全局禁用')
+ logger.info("群聊学习", f"学习词{data.keywords}将被全局禁用")
await ChatAnswer.filter(keywords=data.keywords).delete()
else:
# 没有屏蔽记录,则新建
if isinstance(data, ChatMessage):
- logger.info('群聊学习', f'群{data.group_id}禁用了学习词{data.keywords}')
- 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"群{data.group_id}禁用了学习词{data.keywords}"
+ )
+ 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'学习词{data.keywords}将被全局禁用')
+ logger.info("群聊学习", f"学习词{data.keywords}将被全局禁用")
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'主动发言:群热度排行{">>".join([str(g[0]) for g in popularity])}')
+ popularity: List[Tuple[int, List[ChatMessage]]] = sorted(
+ total_messages.items(), key=cmp_to_key(group_popularity_cmp), reverse=True
+ )
+ logger.debug(
+ "群聊学习", f'主动发言:群热度排行{">>".join([str(g[0]) for g in popularity])}'
+ )
for group_id, messages in popularity:
if len(messages) < 30:
- logger.debug('群聊学习', f'主动发言:群{group_id}消息小于30条,不发言')
+ logger.debug("群聊学习", f"主动发言:群{group_id}消息小于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'主动发言:群{group_id}未开启,不发言')
+ logger.debug("群聊学习", f"主动发言:群{group_id}未开启,不发言")
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'主动发言:群{group_id}最后一条消息是{NICKNAME}发的{last_reply.message},不发言')
+ logger.debug(
+ "群聊学习",
+ f"主动发言:群{group_id}最后一条消息是{NICKNAME}发的{last_reply.message},不发言",
+ )
continue
elif cur_time - last_reply.time < config.speak_min_interval:
- logger.debug('群聊学习', f'主动发言:群{group_id}上次主动发言时间小于主动发言最小间隔,不发言')
+ logger.debug(
+ "群聊学习", f"主动发言:群{group_id}上次主动发言时间小于主动发言最小间隔,不发言"
+ )
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'主动发言:群{group_id}已沉默时间({silent_time})小于阈值({int(threshold)}),不发言')
+ logger.debug(
+ "群聊学习",
+ f"主动发言:群{group_id}已沉默时间({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'主动发言:群{group_id}没有找到符合条件的发言,不发言')
- logger.debug('群聊学习', '主动发言:没有符合条件的群,不主动发言')
+ logger.debug("群聊学习", f"主动发言:群{group_id}没有找到符合条件的发言,不发言")
+ 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'➤将被学习为{message.message}的回答,已学次数为{answer.count}')
+ 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"➤将被学习为{message.message}的回答,已学次数为{answer.count}"
+ )
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:
diff --git a/LittlePaimon/plugins/Learning_Chat/models.py b/LittlePaimon/plugins/Learning_Chat/models.py
index 302b8fe..69adb74 100644
--- a/LittlePaimon/plugins/Learning_Chat/models.py
+++ b/LittlePaimon/plugins/Learning_Chat/models.py
@@ -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"
+)
diff --git a/LittlePaimon/plugins/Learning_Chat/web_api.py b/LittlePaimon/plugins/Learning_Chat/web_api.py
index deac058..6b4503d 100644
--- a/LittlePaimon/plugins/Learning_Chat/web_api.py
+++ b/LittlePaimon/plugins/Learning_Chat/web_api.py
@@ -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)
diff --git a/LittlePaimon/plugins/Learning_Chat/web_page.py b/LittlePaimon/plugins/Learning_Chat/web_page.py
index 4f4e61e..4a4d112 100644
--- a/LittlePaimon/plugins/Learning_Chat/web_page.py
+++ b/LittlePaimon/plugins/Learning_Chat/web_page.py
@@ -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)