Files
aiData/WeiXin/T5_AutoChatMonitor.py
HuangHai a662c33ecf 'commit'
2026-01-26 10:50:11 +08:00

163 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# coding=utf-8
import asyncio
import logging
import os
import sys
import time
from datetime import datetime
import hashlib
# 添加项目根目录到 sys.path
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
sys.path.append(project_root)
from Util import Win32Patch
from WeiXin import WxUtil
from WeiXin.WxUtil import perform_input_action
from Util.LlmUtil import get_llm_response
# 配置日志
log_dir = WxUtil.LOG_DIR
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# 设置 logger
logger = logging.getLogger("T5_AutoChatMonitor")
logger.setLevel(logging.INFO)
if logger.hasHandlers():
logger.handlers.clear()
log_file_path = os.path.join(log_dir, "T5_AutoChatMonitor.log")
file_handler = logging.FileHandler(log_file_path, encoding='utf-8', mode='w')
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(file_handler)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(stream_handler)
logger.propagate = False
logger.info(f"日志文件路径: {log_file_path}")
# 配置参数
CHECK_INTERVAL = 5 # 检查频率 (秒)
class ChatBot:
def __init__(self):
# 运行前清理 Logs 和 Output
WxUtil.setup_script_environment()
self.d = WxUtil.connect_device()
if not self.d:
raise Exception("无法连接到设备,任务终止")
self.last_processed_msg_hash = None # 记录最后一条已处理消息的哈希值
self.screenshot_dir = WxUtil.OUTPUT_DIR
self.persona = (
"你是一名1999年毕业、拥有27年一线教学经验的小学高级女教师名叫大张老师。你目前在长春市少惠林作文素养培养中心工作。"
"你不仅是一位作文教学专家,更是一位心思细腻、能与家长共情的教育智者。"
"你的回复风格应该是:温柔、知性、亲切,就像一位邻家大姐姐在聊天。"
"【严格约束】:\n"
"1. 绝对禁止发散!绝对禁止幻觉!\n"
"2. 知道什么就说什么,不要乱讲话,不要自己编造内容!\n"
"3. 仅针对家长明确表达的内容进行回复。\n"
"4. 严禁使用列表格式。严禁使用‘首先、其次’等逻辑词。\n"
"5. 回复必须简练,字数严格控制在 50 字以内!\n"
"如果涉及到校区信息,必须且只能使用以下真实数据:\n"
"- 单位:长春市少惠林作文素养培养中心\n"
"- 地址南环城路与临河街交汇TOUCH12街3楼325号\n"
"- 联系人小张老师电话18686619970\n"
"- 每学期开学招收小学三年级至六年级,初中七年级的学生入学,其它年段不招生。\n"
)
async def get_reply(self, last_message_text):
prompt = (
f"【教师人设】:{self.persona}\n\n"
f"【最后一条消息】:\n{last_message_text}\n\n"
"【任务要求】:\n"
"请作为大张老师回复家长。**必须且只能针对最后一条消息进行回复!**\n"
"严禁发散,严禁编造家长没说过的情况。如果不清楚家长的意图,就温柔询问。\n"
"字数严格控制在 50 字以内。直接输出回复正文。"
)
full_response = ""
async for chunk in get_llm_response(prompt, stream=False):
full_response += chunk
return full_response.strip().strip('"').strip('').strip('')
async def run(self):
logger.info("🚀 大张老师自动巡课系统启动 (CV版)...")
while True:
try:
# 1. 截图并分析
image_path = os.path.join(self.screenshot_dir, "current_screen.jpg")
self.d.screenshot(image_path)
# 使用 WxUtil 的集中式分析逻辑
# 它会自动处理语音转文字,并返回对话列表和输入框坐标
dialogue_log, input_pos = await WxUtil.analyze_chat_image(image_path, self.screenshot_dir, device=self.d)
if not dialogue_log:
logger.info("😴 未发现有效消息,等待下一次轮询。")
await asyncio.sleep(CHECK_INTERVAL)
continue
# 2. 只关注最后一条消息
last_msg = dialogue_log[-1]
logger.info(f"最后一条消息: {last_msg}")
# 计算最后一条消息的哈希值,用于去重
current_msg_hash = hashlib.md5(last_msg.encode('utf-8')).hexdigest()
# 3. 判断是否需要回复
# 规则:最后一条消息由“对方”发送,且不是上一次处理过的消息
if "对方:" in last_msg:
if current_msg_hash != self.last_processed_msg_hash:
logger.info(f"💡 发现新消息,准备生成回复: {last_msg}")
# 生成回复
reply = await self.get_reply(last_msg)
if reply:
logger.info(f"🤖 LLM 回复: {reply}")
# 执行输入和发送
if input_pos:
perform_input_action(self.d, input_pos, reply)
logger.info("✅ 回复已发送")
# 成功发送后更新最后处理的消息哈希
self.last_processed_msg_hash = current_msg_hash
else:
logger.warning("❌ 未找到输入框位置,无法发送回复")
else:
logger.warning("⚠️ LLM 未生成有效回复")
else:
# 消息已处理过,不重复回复
pass
else:
# 最后一条是我发送的或者是系统消息,更新哈希以防之后重复处理(如果之后又变成对方发)
# 或者简单地跳过
if current_msg_hash != self.last_processed_msg_hash:
logger.info(f"⚪ 最后一条消息非对方发送,无需回复: {last_msg}")
self.last_processed_msg_hash = current_msg_hash
# 4. 休眠
await asyncio.sleep(CHECK_INTERVAL)
except Exception as e:
logger.error(f"❌ 主循环发生错误: {e}", exc_info=True)
await asyncio.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
Win32Patch.patch()
bot = ChatBot()
try:
asyncio.run(bot.run())
except KeyboardInterrupt:
logger.info("🛑 用户手动停止程序。")
except Exception as e:
logger.error(f"❌ 程序异常退出: {e}", exc_info=True)