Files
aiData/WeiXin/T5_AutoChatMonitor.py
HuangHai bf485d10f1 'commit'
2026-01-25 12:52:52 +08:00

198 lines
9.7 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 uiautomator2 as u2
import time
import logging
import sys
import os
import asyncio
from datetime import datetime
# 添加项目根目录到 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 WeiXin.WxUtil import find_input_box_center, perform_input_action, analyze_chat_image, clean_screenshots_dir, is_in_chat_interface
from Util.LlmUtil import get_llm_response
# 配置日志
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Logs")
if not os.path.exists(log_dir):
os.makedirs(log_dir)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(log_dir, "T5_AutoChatMonitor.log"), encoding='utf-8', mode='w'),
logging.StreamHandler()
]
)
logger = logging.getLogger("T5_Monitor")
# 配置参数
CHECK_INTERVAL = 10 # 检查频率 (秒)
SILENCE_THRESHOLD = 60 * 5 # 静默阈值 (5分钟) 主动询问
MAX_PROACTIVE_PROMPTS = 1 # 最大主动询问次数
class ChatBot:
def __init__(self):
self.d = u2.connect()
self.last_message_text = ""
self.last_interaction_time = time.time()
self.proactive_count = 0
self.screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots")
if not os.path.exists(self.screenshot_dir):
os.makedirs(self.screenshot_dir)
self.persona = (
"你是一名1999年毕业、拥有27年一线教学经验的小学高级女教师名叫大张老师。你目前在长春市少惠林作文素养培养中心工作。"
"你不仅是一位作文教学专家,更是一位心思细腻、能与家长共情的教育智者。"
"你的回复风格应该是:温柔、知性、亲切,就像一位邻家大姐姐在聊天。"
"【严格约束】:\n"
"1. 绝对禁止发散!绝对禁止幻觉!\n"
"2. 知道什么就说什么,不要乱讲话,不要自己编造内容!\n"
"3. 仅针对家长明确表达的内容进行回复。\n"
"4. 严禁使用列表格式。严禁使用‘首先、其次’等逻辑词。\n"
"5. 回复必须简练,字数严格控制在 50 字以内!\n"
"如果涉及到校区信息,必须且只能使用以下真实数据:\n"
"- 单位:长春市少惠林作文素养培养中心\n"
"- 地址南环城路与临河街交汇TOUCH12街3楼325号\n"
"- 联系人小张老师电话18686619970"
)
async def get_reply(self, history_text, is_proactive=False):
if is_proactive:
prompt = (
f"【教师人设】:{self.persona}\n\n"
f"【对话背景】家长已经超过5分钟没有回应了。\n"
f"【近期聊天记录】:\n{history_text}\n\n"
"【任务要求】:\n"
"请作为大张老师,给家长发一段简短的关怀消息。不要催促,语气温柔。"
"字数严格控制在 50 字以内。不要编造事实。"
)
else:
prompt = (
f"【教师人设】:{self.persona}\n\n"
f"【近期聊天记录】:\n{history_text}\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("🚀 大张老师自动巡课系统启动...")
# 0. 清除旧截图
clean_screenshots_dir()
while True:
try:
# 1. 检查是否在微信聊天界面 (改为通过 VLM 识别结果判断,不再使用 UI 检查)
# if not is_in_chat_interface(self.d):
# logger.warning("⚠️ 当前不在微信聊天界面,等待下一次扫描...")
# await asyncio.sleep(CHECK_INTERVAL)
# continue
logger.info("🔍 正在扫描当前界面内容...")
# 1. 截图并分析
tmp_shot = os.path.join(self.screenshot_dir, "t5_monitor_temp.jpg")
analyzed_shot = os.path.join(self.screenshot_dir, "t5_monitor_analyzed.jpg")
logger.info(f"📸 正在截取屏幕... ({datetime.now().strftime('%H:%M:%S')})")
self.d.screenshot(tmp_shot)
logger.info("🎨 正在分析聊天界面内容 (检测头像与对话)...")
# analyze_chat_image 现在会返回 None, None 如果不是聊天界面
dialogue_log, input_center = await analyze_chat_image(tmp_shot, analyzed_shot, device=self.d)
if dialogue_log is None:
logger.warning("⚠️ VLM 判断当前不在微信聊天界面,或无法识别。")
await asyncio.sleep(CHECK_INTERVAL)
continue
# 语音转文字处理
if dialogue_log == "VOICE_CONVERTING":
logger.info("🎙️ 检测到语音消息,已触发转文字,等待处理完成 (5秒)...")
await asyncio.sleep(5)
continue
if not dialogue_log:
logger.info("⏳ 界面分析完成,未发现有效对话内容,继续监控...")
await asyncio.sleep(CHECK_INTERVAL)
continue
logger.info(f"📑 界面扫描完成,当前对话历史共 {len(dialogue_log)}")
# 2. 检查是否有新消息
current_last_msg = dialogue_log[-1]
logger.info(f"💬 当前最后一条消息: {current_last_msg}")
history_text = "\n".join(dialogue_log)
# 判断逻辑:如果最后一条消息是“对方”发的,且与上次不同,则回复
if "对方:" in current_last_msg and current_last_msg != self.last_message_text:
# 关键检查:如果包含 "(待转换)",说明语音还没转文字,绝对不能回复
if "(待转换)" in current_last_msg:
logger.info(f"🚫 检测到未转换的语音消息,跳过回复生成,等待转文字... ({current_last_msg})")
await asyncio.sleep(2) # 稍作等待
continue
logger.info(f"📩 检测到新消息: {current_last_msg}")
reply = await self.get_reply(history_text)
logger.info(f"🤖 生成回复: {reply}")
# 执行输入发送
if input_center:
center_point = input_center
logger.info(f"📍 使用 VLM 识别的输入框坐标: {center_point}")
else:
center_point, _ = find_input_box_center(tmp_shot)
logger.info(f"📍 使用 CV 识别的输入框坐标: {center_point}")
# 即使 CV 没找到坐标,也尝试执行,因为 perform_input_action 内部有原生控件识别
perform_input_action(self.d, center_point, reply, auto_send=True)
self.last_message_text = f"我: {reply}" # 更新状态,避免重复回复自己
self.last_interaction_time = time.time()
self.proactive_count = 0 # 重置主动询问计数
# 3. 检查是否需要主动询问 (用户长时间不响应)
elif "我:" in current_last_msg:
# 如果最后一条是我发的,检查距离现在的时间
elapsed = time.time() - self.last_interaction_time
if elapsed > SILENCE_THRESHOLD and self.proactive_count < MAX_PROACTIVE_PROMPTS:
logger.info(f"⏳ 用户长时间未响应 ({int(elapsed)}s),准备主动询问...")
proactive_reply = await self.get_reply(history_text, is_proactive=True)
logger.info(f"🤖 发起主动询问: {proactive_reply}")
if input_center:
center_point = input_center
else:
center_point, _ = find_input_box_center(tmp_shot)
# 同上,解耦 CV 坐标
perform_input_action(self.d, center_point, proactive_reply, auto_send=True)
self.proactive_count += 1
self.last_interaction_time = time.time() # 更新时间,避免连续询问
self.last_message_text = f"我: {proactive_reply}"
# 更新最后一条消息记录(仅用于对比)
if "对方:" in current_last_msg:
self.last_message_text = current_last_msg
except Exception as e:
logger.error(f"❌ 监控循环出错: {e}", exc_info=True)
await asyncio.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
bot = ChatBot()
asyncio.run(bot.run())