ruff, typing, api, bug fix
This commit is contained in:
@@ -16,27 +16,27 @@ logger = get_logger("context_web")
|
||||
|
||||
class ContextMessage:
|
||||
"""上下文消息类"""
|
||||
|
||||
|
||||
def __init__(self, message: MessageRecv):
|
||||
self.user_name = message.message_info.user_info.user_nickname
|
||||
self.user_id = message.message_info.user_info.user_id
|
||||
self.content = message.processed_plain_text
|
||||
self.timestamp = datetime.now()
|
||||
self.group_name = message.message_info.group_info.group_name if message.message_info.group_info else "私聊"
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"user_name": self.user_name,
|
||||
"user_id": self.user_id,
|
||||
"content": self.content,
|
||||
"timestamp": self.timestamp.strftime("%m-%d %H:%M:%S"),
|
||||
"group_name": self.group_name
|
||||
"group_name": self.group_name,
|
||||
}
|
||||
|
||||
|
||||
class ContextWebManager:
|
||||
"""上下文网页管理器"""
|
||||
|
||||
|
||||
def __init__(self, max_messages: int = 10, port: int = 8765):
|
||||
self.max_messages = max_messages
|
||||
self.port = port
|
||||
@@ -46,53 +46,53 @@ class ContextWebManager:
|
||||
self.runner = None
|
||||
self.site = None
|
||||
self._server_starting = False # 添加启动标志防止并发
|
||||
|
||||
|
||||
async def start_server(self):
|
||||
"""启动web服务器"""
|
||||
if self.site is not None:
|
||||
logger.debug("Web服务器已经启动,跳过重复启动")
|
||||
return
|
||||
|
||||
|
||||
if self._server_starting:
|
||||
logger.debug("Web服务器正在启动中,等待启动完成...")
|
||||
# 等待启动完成
|
||||
while self._server_starting and self.site is None:
|
||||
await asyncio.sleep(0.1)
|
||||
return
|
||||
|
||||
|
||||
self._server_starting = True
|
||||
|
||||
|
||||
try:
|
||||
self.app = web.Application()
|
||||
|
||||
|
||||
# 设置CORS
|
||||
cors = aiohttp_cors.setup(self.app, defaults={
|
||||
"*": aiohttp_cors.ResourceOptions(
|
||||
allow_credentials=True,
|
||||
expose_headers="*",
|
||||
allow_headers="*",
|
||||
allow_methods="*"
|
||||
)
|
||||
})
|
||||
|
||||
cors = aiohttp_cors.setup(
|
||||
self.app,
|
||||
defaults={
|
||||
"*": aiohttp_cors.ResourceOptions(
|
||||
allow_credentials=True, expose_headers="*", allow_headers="*", allow_methods="*"
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
# 添加路由
|
||||
self.app.router.add_get('/', self.index_handler)
|
||||
self.app.router.add_get('/ws', self.websocket_handler)
|
||||
self.app.router.add_get('/api/contexts', self.get_contexts_handler)
|
||||
self.app.router.add_get('/debug', self.debug_handler)
|
||||
|
||||
self.app.router.add_get("/", self.index_handler)
|
||||
self.app.router.add_get("/ws", self.websocket_handler)
|
||||
self.app.router.add_get("/api/contexts", self.get_contexts_handler)
|
||||
self.app.router.add_get("/debug", self.debug_handler)
|
||||
|
||||
# 为所有路由添加CORS
|
||||
for route in list(self.app.router.routes()):
|
||||
cors.add(route)
|
||||
|
||||
|
||||
self.runner = web.AppRunner(self.app)
|
||||
await self.runner.setup()
|
||||
|
||||
self.site = web.TCPSite(self.runner, 'localhost', self.port)
|
||||
|
||||
self.site = web.TCPSite(self.runner, "localhost", self.port)
|
||||
await self.site.start()
|
||||
|
||||
|
||||
logger.info(f"🌐 上下文网页服务器启动成功在 http://localhost:{self.port}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 启动Web服务器失败: {e}")
|
||||
# 清理部分启动的资源
|
||||
@@ -104,7 +104,7 @@ class ContextWebManager:
|
||||
raise
|
||||
finally:
|
||||
self._server_starting = False
|
||||
|
||||
|
||||
async def stop_server(self):
|
||||
"""停止web服务器"""
|
||||
if self.site:
|
||||
@@ -115,10 +115,11 @@ class ContextWebManager:
|
||||
self.runner = None
|
||||
self.site = None
|
||||
self._server_starting = False
|
||||
|
||||
|
||||
async def index_handler(self, request):
|
||||
"""主页处理器"""
|
||||
html_content = '''
|
||||
html_content = (
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -231,7 +232,9 @@ class ContextWebManager:
|
||||
|
||||
function connectWebSocket() {
|
||||
console.log('正在连接WebSocket...');
|
||||
ws = new WebSocket('ws://localhost:''' + str(self.port) + '''/ws');
|
||||
ws = new WebSocket('ws://localhost:"""
|
||||
+ str(self.port)
|
||||
+ """/ws');
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('WebSocket连接已建立');
|
||||
@@ -402,47 +405,48 @@ class ContextWebManager:
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
return web.Response(text=html_content, content_type='text/html')
|
||||
|
||||
"""
|
||||
)
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
async def websocket_handler(self, request):
|
||||
"""WebSocket处理器"""
|
||||
ws = web.WebSocketResponse()
|
||||
await ws.prepare(request)
|
||||
|
||||
|
||||
self.websockets.append(ws)
|
||||
logger.debug(f"WebSocket连接建立,当前连接数: {len(self.websockets)}")
|
||||
|
||||
|
||||
# 发送初始数据
|
||||
await self.send_contexts_to_websocket(ws)
|
||||
|
||||
|
||||
async for msg in ws:
|
||||
if msg.type == WSMsgType.ERROR:
|
||||
logger.error(f'WebSocket错误: {ws.exception()}')
|
||||
logger.error(f"WebSocket错误: {ws.exception()}")
|
||||
break
|
||||
|
||||
|
||||
# 清理断开的连接
|
||||
if ws in self.websockets:
|
||||
self.websockets.remove(ws)
|
||||
logger.debug(f"WebSocket连接断开,当前连接数: {len(self.websockets)}")
|
||||
|
||||
|
||||
return ws
|
||||
|
||||
|
||||
async def get_contexts_handler(self, request):
|
||||
"""获取上下文API"""
|
||||
all_context_msgs = []
|
||||
for chat_id, contexts in self.contexts.items():
|
||||
all_context_msgs.extend(list(contexts))
|
||||
|
||||
|
||||
# 按时间排序,最新的在最后
|
||||
all_context_msgs.sort(key=lambda x: x.timestamp)
|
||||
|
||||
|
||||
# 转换为字典格式
|
||||
contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages:]]
|
||||
|
||||
contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages :]]
|
||||
|
||||
logger.debug(f"返回上下文数据,共 {len(contexts_data)} 条消息")
|
||||
return web.json_response({"contexts": contexts_data})
|
||||
|
||||
|
||||
async def debug_handler(self, request):
|
||||
"""调试信息处理器"""
|
||||
debug_info = {
|
||||
@@ -451,7 +455,7 @@ class ContextWebManager:
|
||||
"total_chats": len(self.contexts),
|
||||
"total_messages": sum(len(contexts) for contexts in self.contexts.values()),
|
||||
}
|
||||
|
||||
|
||||
# 构建聊天详情HTML
|
||||
chats_html = ""
|
||||
for chat_id, contexts in self.contexts.items():
|
||||
@@ -460,15 +464,15 @@ class ContextWebManager:
|
||||
timestamp = msg.timestamp.strftime("%H:%M:%S")
|
||||
content = msg.content[:50] + "..." if len(msg.content) > 50 else msg.content
|
||||
messages_html += f'<div class="message">[{timestamp}] {msg.user_name}: {content}</div>'
|
||||
|
||||
chats_html += f'''
|
||||
|
||||
chats_html += f"""
|
||||
<div class="chat">
|
||||
<h3>聊天 {chat_id} ({len(contexts)} 条消息)</h3>
|
||||
{messages_html}
|
||||
</div>
|
||||
'''
|
||||
|
||||
html_content = f'''
|
||||
"""
|
||||
|
||||
html_content = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -510,74 +514,78 @@ class ContextWebManager:
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
return web.Response(text=html_content, content_type='text/html')
|
||||
|
||||
"""
|
||||
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
async def add_message(self, chat_id: str, message: MessageRecv):
|
||||
"""添加新消息到上下文"""
|
||||
if chat_id not in self.contexts:
|
||||
self.contexts[chat_id] = deque(maxlen=self.max_messages)
|
||||
logger.debug(f"为聊天 {chat_id} 创建新的上下文队列")
|
||||
|
||||
|
||||
context_msg = ContextMessage(message)
|
||||
self.contexts[chat_id].append(context_msg)
|
||||
|
||||
|
||||
# 统计当前总消息数
|
||||
total_messages = sum(len(contexts) for contexts in self.contexts.values())
|
||||
|
||||
logger.info(f"✅ 添加消息到上下文 [总数: {total_messages}]: [{context_msg.group_name}] {context_msg.user_name}: {context_msg.content}")
|
||||
|
||||
|
||||
logger.info(
|
||||
f"✅ 添加消息到上下文 [总数: {total_messages}]: [{context_msg.group_name}] {context_msg.user_name}: {context_msg.content}"
|
||||
)
|
||||
|
||||
# 调试:打印当前所有消息
|
||||
logger.info(f"📝 当前上下文中的所有消息:")
|
||||
for cid, contexts in self.contexts.items():
|
||||
logger.info(f" 聊天 {cid}: {len(contexts)} 条消息")
|
||||
for i, msg in enumerate(contexts):
|
||||
logger.info(f" {i+1}. [{msg.timestamp.strftime('%H:%M:%S')}] {msg.user_name}: {msg.content[:30]}...")
|
||||
|
||||
logger.info(
|
||||
f" {i + 1}. [{msg.timestamp.strftime('%H:%M:%S')}] {msg.user_name}: {msg.content[:30]}..."
|
||||
)
|
||||
|
||||
# 广播更新给所有WebSocket连接
|
||||
await self.broadcast_contexts()
|
||||
|
||||
|
||||
async def send_contexts_to_websocket(self, ws: web.WebSocketResponse):
|
||||
"""向单个WebSocket发送上下文数据"""
|
||||
all_context_msgs = []
|
||||
for chat_id, contexts in self.contexts.items():
|
||||
all_context_msgs.extend(list(contexts))
|
||||
|
||||
|
||||
# 按时间排序,最新的在最后
|
||||
all_context_msgs.sort(key=lambda x: x.timestamp)
|
||||
|
||||
|
||||
# 转换为字典格式
|
||||
contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages:]]
|
||||
|
||||
contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages :]]
|
||||
|
||||
data = {"contexts": contexts_data}
|
||||
await ws.send_str(json.dumps(data, ensure_ascii=False))
|
||||
|
||||
|
||||
async def broadcast_contexts(self):
|
||||
"""向所有WebSocket连接广播上下文更新"""
|
||||
if not self.websockets:
|
||||
logger.debug("没有WebSocket连接,跳过广播")
|
||||
return
|
||||
|
||||
|
||||
all_context_msgs = []
|
||||
for chat_id, contexts in self.contexts.items():
|
||||
all_context_msgs.extend(list(contexts))
|
||||
|
||||
|
||||
# 按时间排序,最新的在最后
|
||||
all_context_msgs.sort(key=lambda x: x.timestamp)
|
||||
|
||||
|
||||
# 转换为字典格式
|
||||
contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages:]]
|
||||
|
||||
contexts_data = [msg.to_dict() for msg in all_context_msgs[-self.max_messages :]]
|
||||
|
||||
data = {"contexts": contexts_data}
|
||||
message = json.dumps(data, ensure_ascii=False)
|
||||
|
||||
|
||||
logger.info(f"广播 {len(contexts_data)} 条消息到 {len(self.websockets)} 个WebSocket连接")
|
||||
|
||||
|
||||
# 创建WebSocket列表的副本,避免在遍历时修改
|
||||
websockets_copy = self.websockets.copy()
|
||||
removed_count = 0
|
||||
|
||||
|
||||
for ws in websockets_copy:
|
||||
if ws.closed:
|
||||
if ws in self.websockets:
|
||||
@@ -592,7 +600,7 @@ class ContextWebManager:
|
||||
if ws in self.websockets:
|
||||
self.websockets.remove(ws)
|
||||
removed_count += 1
|
||||
|
||||
|
||||
if removed_count > 0:
|
||||
logger.debug(f"清理了 {removed_count} 个断开的WebSocket连接")
|
||||
|
||||
@@ -613,5 +621,4 @@ async def init_context_web_manager():
|
||||
"""初始化上下文网页管理器"""
|
||||
manager = get_context_web_manager()
|
||||
await manager.start_server()
|
||||
return manager
|
||||
|
||||
return manager
|
||||
|
||||
@@ -11,6 +11,7 @@ from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
||||
from src.manager.async_task_manager import AsyncTask, async_task_manager
|
||||
from src.plugin_system.apis import send_api
|
||||
|
||||
|
||||
async def send_loading(chat_id: str, content: str):
|
||||
await send_api.custom_to_stream(
|
||||
message_type="loading",
|
||||
@@ -19,7 +20,8 @@ async def send_loading(chat_id: str, content: str):
|
||||
storage_message=False,
|
||||
show_log=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
async def send_unloading(chat_id: str):
|
||||
await send_api.custom_to_stream(
|
||||
message_type="loading",
|
||||
@@ -28,4 +30,3 @@ async def send_unloading(chat_id: str):
|
||||
storage_message=False,
|
||||
show_log=True,
|
||||
)
|
||||
|
||||
@@ -29,7 +29,6 @@ class MessageSenderContainer:
|
||||
self._task: Optional[asyncio.Task] = None
|
||||
self._paused_event = asyncio.Event()
|
||||
self._paused_event.set() # 默认设置为非暂停状态
|
||||
|
||||
|
||||
async def add_message(self, chunk: str):
|
||||
"""向队列中添加一个消息块。"""
|
||||
@@ -201,10 +200,10 @@ class S4UChat:
|
||||
score = 0.0
|
||||
# 如果消息 @ 了机器人,则增加一个很大的分数
|
||||
# if f"@{global_config.bot.nickname}" in message.processed_plain_text or any(
|
||||
# f"@{alias}" in message.processed_plain_text for alias in global_config.bot.alias_names
|
||||
# f"@{alias}" in message.processed_plain_text for alias in global_config.bot.alias_names
|
||||
# ):
|
||||
# score += self.at_bot_priority_bonus
|
||||
|
||||
# score += self.at_bot_priority_bonus
|
||||
|
||||
# 加上消息自带的优先级
|
||||
score += priority_info.get("message_priority", 0.0)
|
||||
|
||||
@@ -214,9 +213,9 @@ class S4UChat:
|
||||
|
||||
async def add_message(self, message: MessageRecv) -> None:
|
||||
"""根据VIP状态和中断逻辑将消息放入相应队列。"""
|
||||
|
||||
|
||||
await self.relationship_builder.build_relation()
|
||||
|
||||
|
||||
priority_info = self._get_priority_info(message)
|
||||
is_vip = self._is_vip(priority_info)
|
||||
new_priority_score = self._calculate_base_priority_score(message, priority_info)
|
||||
@@ -273,36 +272,38 @@ class S4UChat:
|
||||
"""清理普通队列中不在最近N条消息范围内的消息"""
|
||||
if self._normal_queue.empty():
|
||||
return
|
||||
|
||||
|
||||
# 计算阈值:保留最近 recent_message_keep_count 条消息
|
||||
cutoff_counter = max(0, self._entry_counter - self.recent_message_keep_count)
|
||||
|
||||
|
||||
# 临时存储需要保留的消息
|
||||
temp_messages = []
|
||||
removed_count = 0
|
||||
|
||||
|
||||
# 取出所有普通队列中的消息
|
||||
while not self._normal_queue.empty():
|
||||
try:
|
||||
item = self._normal_queue.get_nowait()
|
||||
neg_priority, entry_count, timestamp, message = item
|
||||
|
||||
|
||||
# 如果消息在最近N条消息范围内,保留它
|
||||
if entry_count >= cutoff_counter:
|
||||
temp_messages.append(item)
|
||||
else:
|
||||
removed_count += 1
|
||||
self._normal_queue.task_done() # 标记被移除的任务为完成
|
||||
|
||||
|
||||
except asyncio.QueueEmpty:
|
||||
break
|
||||
|
||||
|
||||
# 将保留的消息重新放入队列
|
||||
for item in temp_messages:
|
||||
self._normal_queue.put_nowait(item)
|
||||
|
||||
|
||||
if removed_count > 0:
|
||||
logger.info(f"[{self.stream_name}] Cleaned up {removed_count} old normal messages outside recent {self.recent_message_keep_count} range.")
|
||||
logger.info(
|
||||
f"[{self.stream_name}] Cleaned up {removed_count} old normal messages outside recent {self.recent_message_keep_count} range."
|
||||
)
|
||||
|
||||
async def _message_processor(self):
|
||||
"""调度器:优先处理VIP队列,然后处理普通队列。"""
|
||||
@@ -311,7 +312,7 @@ class S4UChat:
|
||||
# 等待有新消息的信号,避免空转
|
||||
await self._new_message_event.wait()
|
||||
self._new_message_event.clear()
|
||||
|
||||
|
||||
# 清理普通队列中的过旧消息
|
||||
self._cleanup_old_normal_messages()
|
||||
|
||||
@@ -372,16 +373,16 @@ class S4UChat:
|
||||
async def _generate_and_send(self, message: MessageRecv):
|
||||
"""为单个消息生成文本回复。整个过程可以被中断。"""
|
||||
self._is_replying = True
|
||||
|
||||
|
||||
await send_loading(self.stream_id, "......")
|
||||
|
||||
|
||||
# 视线管理:开始生成回复时切换视线状态
|
||||
chat_watching = watching_manager.get_watching_by_chat_id(self.stream_id)
|
||||
await chat_watching.on_reply_start()
|
||||
|
||||
|
||||
# 回复生成实时展示:开始生成
|
||||
user_name = message.message_info.user_info.user_nickname
|
||||
|
||||
|
||||
sender_container = MessageSenderContainer(self.chat_stream, message)
|
||||
sender_container.start()
|
||||
|
||||
@@ -395,13 +396,11 @@ class S4UChat:
|
||||
|
||||
# a. 发送文本块
|
||||
await sender_container.add_message(chunk)
|
||||
|
||||
|
||||
# 等待所有文本消息发送完成
|
||||
await sender_container.close()
|
||||
await sender_container.join()
|
||||
|
||||
|
||||
|
||||
logger.info(f"[{self.stream_name}] 所有文本块处理完毕。")
|
||||
|
||||
except asyncio.CancelledError:
|
||||
@@ -412,13 +411,13 @@ class S4UChat:
|
||||
# 回复生成实时展示:清空内容(出错时)
|
||||
finally:
|
||||
self._is_replying = False
|
||||
|
||||
|
||||
await send_unloading(self.stream_id)
|
||||
|
||||
|
||||
# 视线管理:回复结束时切换视线状态
|
||||
chat_watching = watching_manager.get_watching_by_chat_id(self.stream_id)
|
||||
await chat_watching.on_reply_finished()
|
||||
|
||||
|
||||
# 确保发送器被妥善关闭(即使已关闭,再次调用也是安全的)
|
||||
sender_container.resume()
|
||||
if not sender_container._task.done():
|
||||
|
||||
@@ -125,7 +125,7 @@ class ChatMood:
|
||||
)
|
||||
|
||||
self.last_change_time = 0
|
||||
|
||||
|
||||
# 发送初始情绪状态到ws端
|
||||
asyncio.create_task(self.send_emotion_update(self.mood_values))
|
||||
|
||||
@@ -231,10 +231,10 @@ class ChatMood:
|
||||
if numerical_mood_response:
|
||||
_old_mood_values = self.mood_values.copy()
|
||||
self.mood_values = numerical_mood_response
|
||||
|
||||
|
||||
# 发送情绪更新到ws端
|
||||
await self.send_emotion_update(self.mood_values)
|
||||
|
||||
|
||||
logger.info(f"[{self.chat_id}] 情绪变化: {_old_mood_values} -> {self.mood_values}")
|
||||
|
||||
self.last_change_time = message_time
|
||||
@@ -308,10 +308,10 @@ class ChatMood:
|
||||
if numerical_mood_response:
|
||||
_old_mood_values = self.mood_values.copy()
|
||||
self.mood_values = numerical_mood_response
|
||||
|
||||
|
||||
# 发送情绪更新到ws端
|
||||
await self.send_emotion_update(self.mood_values)
|
||||
|
||||
|
||||
logger.info(f"[{self.chat_id}] 情绪回归: {_old_mood_values} -> {self.mood_values}")
|
||||
|
||||
self.regression_count += 1
|
||||
@@ -322,9 +322,9 @@ class ChatMood:
|
||||
"joy": mood_values.get("joy", 5),
|
||||
"anger": mood_values.get("anger", 1),
|
||||
"sorrow": mood_values.get("sorrow", 1),
|
||||
"fear": mood_values.get("fear", 1)
|
||||
"fear": mood_values.get("fear", 1),
|
||||
}
|
||||
|
||||
|
||||
await send_api.custom_to_stream(
|
||||
message_type="emotion",
|
||||
content=emotion_data,
|
||||
@@ -332,7 +332,7 @@ class ChatMood:
|
||||
storage_message=False,
|
||||
show_log=True,
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"[{self.chat_id}] 发送情绪更新: {emotion_data}")
|
||||
|
||||
|
||||
@@ -345,27 +345,27 @@ class MoodRegressionTask(AsyncTask):
|
||||
async def run(self):
|
||||
self.run_count += 1
|
||||
logger.info(f"[回归任务] 第{self.run_count}次检查,当前管理{len(self.mood_manager.mood_list)}个聊天的情绪状态")
|
||||
|
||||
|
||||
now = time.time()
|
||||
regression_executed = 0
|
||||
|
||||
|
||||
for mood in self.mood_manager.mood_list:
|
||||
chat_info = f"chat {mood.chat_id}"
|
||||
|
||||
|
||||
if mood.last_change_time == 0:
|
||||
logger.debug(f"[回归任务] {chat_info} 尚未有情绪变化,跳过回归")
|
||||
continue
|
||||
|
||||
time_since_last_change = now - mood.last_change_time
|
||||
|
||||
|
||||
# 检查是否有极端情绪需要快速回归
|
||||
high_emotions = {k: v for k, v in mood.mood_values.items() if v >= 8}
|
||||
has_extreme_emotion = len(high_emotions) > 0
|
||||
|
||||
|
||||
# 回归条件:1. 正常时间间隔(120s) 或 2. 有极端情绪且距上次变化>=30s
|
||||
should_regress = False
|
||||
regress_reason = ""
|
||||
|
||||
|
||||
if time_since_last_change > 120:
|
||||
should_regress = True
|
||||
regress_reason = f"常规回归(距上次变化{int(time_since_last_change)}秒)"
|
||||
@@ -373,24 +373,28 @@ class MoodRegressionTask(AsyncTask):
|
||||
should_regress = True
|
||||
high_emotion_str = ", ".join([f"{k}={v}" for k, v in high_emotions.items()])
|
||||
regress_reason = f"极端情绪快速回归({high_emotion_str}, 距上次变化{int(time_since_last_change)}秒)"
|
||||
|
||||
|
||||
if should_regress:
|
||||
if mood.regression_count >= 3:
|
||||
logger.debug(f"[回归任务] {chat_info} 已达到最大回归次数(3次),停止回归")
|
||||
continue
|
||||
|
||||
logger.info(f"[回归任务] {chat_info} 开始情绪回归 ({regress_reason},第{mood.regression_count + 1}次回归)")
|
||||
logger.info(
|
||||
f"[回归任务] {chat_info} 开始情绪回归 ({regress_reason},第{mood.regression_count + 1}次回归)"
|
||||
)
|
||||
await mood.regress_mood()
|
||||
regression_executed += 1
|
||||
else:
|
||||
if has_extreme_emotion:
|
||||
remaining_time = 5 - time_since_last_change
|
||||
high_emotion_str = ", ".join([f"{k}={v}" for k, v in high_emotions.items()])
|
||||
logger.debug(f"[回归任务] {chat_info} 存在极端情绪({high_emotion_str}),距离快速回归还需等待{int(remaining_time)}秒")
|
||||
logger.debug(
|
||||
f"[回归任务] {chat_info} 存在极端情绪({high_emotion_str}),距离快速回归还需等待{int(remaining_time)}秒"
|
||||
)
|
||||
else:
|
||||
remaining_time = 120 - time_since_last_change
|
||||
logger.debug(f"[回归任务] {chat_info} 距离回归还需等待{int(remaining_time)}秒")
|
||||
|
||||
|
||||
if regression_executed > 0:
|
||||
logger.info(f"[回归任务] 本次执行了{regression_executed}个聊天的情绪回归")
|
||||
else:
|
||||
@@ -409,11 +413,11 @@ class MoodManager:
|
||||
return
|
||||
|
||||
logger.info("启动情绪管理任务...")
|
||||
|
||||
|
||||
# 启动情绪回归任务
|
||||
regression_task = MoodRegressionTask(self)
|
||||
await async_task_manager.add_task(regression_task)
|
||||
|
||||
|
||||
self.task_started = True
|
||||
logger.info("情绪管理任务已启动(情绪回归)")
|
||||
|
||||
@@ -435,7 +439,7 @@ class MoodManager:
|
||||
# 发送重置后的情绪状态到ws端
|
||||
asyncio.create_task(mood.send_emotion_update(mood.mood_values))
|
||||
return
|
||||
|
||||
|
||||
# 如果没有找到现有的mood,创建新的
|
||||
new_mood = ChatMood(chat_id)
|
||||
self.mood_list.append(new_mood)
|
||||
|
||||
@@ -103,7 +103,7 @@ class S4UMessageProcessor:
|
||||
await s4u_chat.add_message(message)
|
||||
|
||||
interested_rate, _ = await _calculate_interest(message)
|
||||
|
||||
|
||||
await mood_manager.start()
|
||||
|
||||
chat_mood = mood_manager.get_mood_by_chat_id(chat.stream_id)
|
||||
@@ -111,7 +111,7 @@ class S4UMessageProcessor:
|
||||
chat_action = action_manager.get_action_state_by_chat_id(chat.stream_id)
|
||||
asyncio.create_task(chat_action.update_action_by_message(message))
|
||||
# asyncio.create_task(chat_action.update_facial_expression_by_message(message, interested_rate))
|
||||
|
||||
|
||||
# 视线管理:收到消息时切换视线状态
|
||||
chat_watching = watching_manager.get_watching_by_chat_id(chat.stream_id)
|
||||
asyncio.create_task(chat_watching.on_message_received())
|
||||
@@ -124,25 +124,25 @@ class S4UMessageProcessor:
|
||||
|
||||
async def _handle_context_web_update(self, chat_id: str, message: MessageRecv):
|
||||
"""处理上下文网页更新的独立task
|
||||
|
||||
|
||||
Args:
|
||||
chat_id: 聊天ID
|
||||
message: 消息对象
|
||||
"""
|
||||
try:
|
||||
logger.debug(f"🔄 开始处理上下文网页更新: {message.message_info.user_info.user_nickname}")
|
||||
|
||||
|
||||
context_manager = get_context_web_manager()
|
||||
|
||||
|
||||
# 只在服务器未启动时启动(避免重复启动)
|
||||
if context_manager.site is None:
|
||||
logger.info("🚀 首次启动上下文网页服务器...")
|
||||
await context_manager.start_server()
|
||||
|
||||
|
||||
# 添加消息到上下文并更新网页
|
||||
await context_manager.add_message(chat_id, message)
|
||||
|
||||
|
||||
logger.debug(f"✅ 上下文网页更新完成: {message.message_info.user_info.user_nickname}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 处理上下文网页更新失败: {e}", exc_info=True)
|
||||
|
||||
@@ -107,7 +107,6 @@ class S4UStreamGenerator:
|
||||
model_name: str,
|
||||
**kwargs,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
|
||||
buffer = ""
|
||||
delimiters = ",。!?,.!?\n\r" # For final trimming
|
||||
punctuation_buffer = ""
|
||||
|
||||
@@ -43,23 +43,24 @@ logger = get_logger("watching")
|
||||
|
||||
class WatchingState(Enum):
|
||||
"""视线状态枚举"""
|
||||
|
||||
WANDERING = "wandering" # 随意看
|
||||
DANMU = "danmu" # 看弹幕
|
||||
LENS = "lens" # 看镜头
|
||||
DANMU = "danmu" # 看弹幕
|
||||
LENS = "lens" # 看镜头
|
||||
|
||||
|
||||
class ChatWatching:
|
||||
def __init__(self, chat_id: str):
|
||||
self.chat_id: str = chat_id
|
||||
self.current_state: WatchingState = WatchingState.LENS # 默认看镜头
|
||||
self.last_sent_state: Optional[WatchingState] = None # 上次发送的状态
|
||||
self.state_needs_update: bool = True # 是否需要更新状态
|
||||
|
||||
self.last_sent_state: Optional[WatchingState] = None # 上次发送的状态
|
||||
self.state_needs_update: bool = True # 是否需要更新状态
|
||||
|
||||
# 状态切换相关
|
||||
self.is_replying: bool = False # 是否正在生成回复
|
||||
self.reply_finished_time: Optional[float] = None # 回复完成时间
|
||||
self.danmu_viewing_duration: float = 1.0 # 看弹幕持续时间(秒)
|
||||
|
||||
self.is_replying: bool = False # 是否正在生成回复
|
||||
self.reply_finished_time: Optional[float] = None # 回复完成时间
|
||||
self.danmu_viewing_duration: float = 1.0 # 看弹幕持续时间(秒)
|
||||
|
||||
logger.info(f"[{self.chat_id}] 视线管理器初始化,默认状态: {self.current_state.value}")
|
||||
|
||||
async def _change_state(self, new_state: WatchingState, reason: str = ""):
|
||||
@@ -69,7 +70,7 @@ class ChatWatching:
|
||||
self.current_state = new_state
|
||||
self.state_needs_update = True
|
||||
logger.info(f"[{self.chat_id}] 视线状态切换: {old_state.value} → {new_state.value} ({reason})")
|
||||
|
||||
|
||||
# 立即发送视线状态更新
|
||||
await self._send_watching_update()
|
||||
else:
|
||||
@@ -86,7 +87,7 @@ class ChatWatching:
|
||||
"""开始生成回复时调用"""
|
||||
self.is_replying = True
|
||||
self.reply_finished_time = None
|
||||
|
||||
|
||||
if look_at_lens:
|
||||
await self._change_state(WatchingState.LENS, "开始生成回复-看镜头")
|
||||
else:
|
||||
@@ -96,35 +97,29 @@ class ChatWatching:
|
||||
"""生成回复完毕时调用"""
|
||||
self.is_replying = False
|
||||
self.reply_finished_time = time.time()
|
||||
|
||||
|
||||
# 先看弹幕1秒
|
||||
await self._change_state(WatchingState.DANMU, "回复完毕-看弹幕")
|
||||
logger.info(f"[{self.chat_id}] 回复完毕,将看弹幕{self.danmu_viewing_duration}秒后转为看镜头")
|
||||
|
||||
|
||||
# 设置定时器,1秒后自动切换到看镜头
|
||||
asyncio.create_task(self._auto_switch_to_lens())
|
||||
|
||||
async def _auto_switch_to_lens(self):
|
||||
"""自动切换到看镜头(延迟执行)"""
|
||||
await asyncio.sleep(self.danmu_viewing_duration)
|
||||
|
||||
|
||||
# 检查是否仍需要切换(可能状态已经被其他事件改变)
|
||||
if (self.reply_finished_time is not None and
|
||||
self.current_state == WatchingState.DANMU and
|
||||
not self.is_replying):
|
||||
|
||||
if self.reply_finished_time is not None and self.current_state == WatchingState.DANMU and not self.is_replying:
|
||||
await self._change_state(WatchingState.LENS, "看弹幕时间结束")
|
||||
self.reply_finished_time = None # 重置完成时间
|
||||
|
||||
async def _send_watching_update(self):
|
||||
"""立即发送视线状态更新"""
|
||||
await send_api.custom_to_stream(
|
||||
message_type="watching",
|
||||
content=self.current_state.value,
|
||||
stream_id=self.chat_id,
|
||||
storage_message=False
|
||||
message_type="watching", content=self.current_state.value, stream_id=self.chat_id, storage_message=False
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"[{self.chat_id}] 发送视线状态更新: {self.current_state.value}")
|
||||
self.last_sent_state = self.current_state
|
||||
self.state_needs_update = False
|
||||
@@ -139,11 +134,10 @@ class ChatWatching:
|
||||
"current_state": self.current_state.value,
|
||||
"is_replying": self.is_replying,
|
||||
"reply_finished_time": self.reply_finished_time,
|
||||
"state_needs_update": self.state_needs_update
|
||||
"state_needs_update": self.state_needs_update,
|
||||
}
|
||||
|
||||
|
||||
|
||||
class WatchingManager:
|
||||
def __init__(self):
|
||||
self.watching_list: list[ChatWatching] = []
|
||||
@@ -156,7 +150,7 @@ class WatchingManager:
|
||||
return
|
||||
|
||||
logger.info("启动视线管理系统...")
|
||||
|
||||
|
||||
self.task_started = True
|
||||
logger.info("视线管理系统已启动(状态变化时立即发送)")
|
||||
|
||||
@@ -169,10 +163,10 @@ class WatchingManager:
|
||||
new_watching = ChatWatching(chat_id)
|
||||
self.watching_list.append(new_watching)
|
||||
logger.info(f"为chat {chat_id}创建新的视线管理器")
|
||||
|
||||
|
||||
# 发送初始状态
|
||||
asyncio.create_task(new_watching._send_watching_update())
|
||||
|
||||
|
||||
return new_watching
|
||||
|
||||
def reset_watching_by_chat_id(self, chat_id: str):
|
||||
@@ -185,27 +179,24 @@ class WatchingManager:
|
||||
watching.is_replying = False
|
||||
watching.reply_finished_time = None
|
||||
logger.info(f"[{chat_id}] 视线状态已重置为默认状态")
|
||||
|
||||
|
||||
# 发送重置后的状态
|
||||
asyncio.create_task(watching._send_watching_update())
|
||||
return
|
||||
|
||||
|
||||
# 如果没有找到现有的watching,创建新的
|
||||
new_watching = ChatWatching(chat_id)
|
||||
self.watching_list.append(new_watching)
|
||||
logger.info(f"为chat {chat_id}创建并重置视线管理器")
|
||||
|
||||
|
||||
# 发送初始状态
|
||||
asyncio.create_task(new_watching._send_watching_update())
|
||||
|
||||
def get_all_watching_info(self) -> dict:
|
||||
"""获取所有聊天的视线状态信息(用于调试)"""
|
||||
return {
|
||||
watching.chat_id: watching.get_state_info()
|
||||
for watching in self.watching_list
|
||||
}
|
||||
return {watching.chat_id: watching.get_state_info() for watching in self.watching_list}
|
||||
|
||||
|
||||
# 全局视线管理器实例
|
||||
watching_manager = WatchingManager()
|
||||
"""全局视线管理器"""
|
||||
"""全局视线管理器"""
|
||||
|
||||
Reference in New Issue
Block a user