This commit is contained in:
HuangHai
2026-01-26 09:50:09 +08:00
parent 437df303c5
commit 4868198143
3 changed files with 71 additions and 55 deletions

View File

@@ -56,17 +56,17 @@ async def get_history(target_name="对方"):
# 调用 WxUtil 中的分析函数
dialogue_log, input_box = await analyze_chat_image(save_path, analyzed_path, device=d, target_name=target_name)
# 检查是否正在转换
if isinstance(dialogue_log, list) and any("[正在转换语音...]" in str(msg) for msg in dialogue_log):
logger.info("检测到语音正在转文字T2 任务暂停。")
return
logger.info("✅ T2 执行完成。历史对话如下:")
logger.info("✅ T2 识别结果:")
if dialogue_log:
for log in dialogue_log:
print(log) # 打印到控制台
else:
logger.info("未提取到对话内容或当前屏幕无对话气泡。")
# 检查是否触发了转换
if isinstance(dialogue_log, list) and any("[正在转换语音...]" in str(msg) for msg in dialogue_log):
logger.info("检测到语音正在转文字,建议等待转换完成后重新运行 T2 以获取完整内容。")
return
except Exception as e:
logger.error(f"❌ T2 执行失败: {e}")

View File

@@ -124,42 +124,56 @@ async def get_vlm_analysis(image_path):
# 构造 Prompt
prompt = """
请分析这张微信聊天截图。
请分析这张微信聊天截图,提取所有对话消息
【核心任务
识别图中的【语音消息气泡】和【文本消息气泡】,并区分【发送者】
⚠️ **特别注意**:必须识别屏幕上**所有**的消息,特别是位于**屏幕最底部**的消息,哪怕只有一部分,也要识别!
【重要判别规则】
1. 👤 **发送者 (Sender)**
- **对方 (Other)**:气泡在屏幕**左侧**,通常为白色或灰色,头像在左边。
- **我 (Me)**:气泡在屏幕**右侧**,通常为绿色,头像在右边。
2. 🔊 **语音消息 (Voice)**
- **视觉特征**
- **高度**:固定(单行)。
- **宽度**随时长1"~60")变化。
- **极短 (1"-2")**:气泡非常短,形状接近一个小正方形。
- **极长 (60")**:气泡很长,宽度接近屏幕的一半。
- **内容**:气泡内**只有一个**表示时长的数字(如 `8"`)和一个声波图标。
- **辅助文字**:语音气泡右侧可能会有灰色的“转文字”或“取消转文字”字样,**请忽略这些文字**,依然将该气泡识别为语音消息!
- **绝对排除**:凡是包含汉字、长句子的气泡,**统统不是**语音消息。
3. 📝 **文本消息 (Text)**
- **视觉特征**:气泡内包含汉字、标点符号、表情等文本内容。
4. 🔴 **未读状态 (Unread) - 极度重要!**
- **特征**:语音气泡的右上角(或紧邻右侧)有一个明显的**红色圆形小点**。
- **判别**
- 只要看到红色小点,`is_unread` 必须为 **true**。
- 如果没有红色小点,`is_unread` 为 false。
- **注意**:红点可能很小,请仔细观察!这是判断是否处理的关键依据。即使红点旁边有灰色的“转文字”字样,只要有红点,就是未读!
【核心规则 - 优先级最高
1. 🚀 **从下往上扫描**:必须确保屏幕最底部的消息被识别。很多时候最底部的消息是最重要的
2. 🔴 **未读红点 (Unread)**:极度关注语音气泡右上角的红点。如果有红点,`is_unread` 必须为 true。
3. 📦 **完整性**:识别图中【所有】可见的消息气泡,包括文本消息、语音消息、系统提示(如“昨天 10:36”、“你撤回了一条消息”
【消息类型判别】
- **发送者 (Sender)**:左侧头像为“对方”(Other),右侧头像为“我”(Me)。
- **语音 (Voice)**
- 气泡内只有时长(如 5")和声波图标。
- **重点**:如果语音气泡右侧有灰色的“转文字”字样或红点,且下方没有对应的文本翻译气泡,说明它【尚未转换】。
- `status` 判断:只有当语音气泡【正下方】紧跟着一个相同发送者的文本气泡(内容是翻译结果),`status` 才为 "converted"。否则为 "unconverted"
- **文本 (Text)**:气泡内包含具体的文字内容。
【坐标系统】
**必须使用 [0-1000] 归一化坐标系。**
- 左上角为 [0, 0],右下角为 [1000, 1000]
- 请返回气泡的**几何中心点**的归一化坐标。
- 使用 [0-1000] 归一化坐标。返回气泡的几何中心点 `center`。
- 识别底部输入框的位置 `input_box`
【输出格式】
请返回纯 JSON 格式:
{
"is_chat_interface": true,
"input_box": [x, y],
"messages": [
{
"type": "voice" | "text" | "system",
"sender": "对方" | "" | "系统",
"status": "converted" | "unconverted",
"is_unread": true | false,
"center": [x, y],
"content": "消息内容或时长"
},
...
]
}
"""
2. <EFBFBD> **红点 (Unread)**极度关注语音气泡右上角的红点如果有红点`is_unread` 必须为 true
3. 📦 **完整性**识别图中所有可见的消息气泡不要遗漏任何一个特别是连续的语音消息
消息类型判别
- **发送者 (Sender)**左侧头像为对方(Other)右侧头像为(Me)
- **语音 (Voice)**气泡内只有时长 5")和声波图标。
- 语音气泡右侧可能有转文字取消等灰色小字请忽略这些文字气泡依然是 Voice
- `status` 判断如果语音气泡下方紧接着有一个属于同一人的文本气泡且内容看起来像翻译结果 `status` "converted"否则为 "unconverted"
- **文本 (Text)**气泡内包含具体的文字内容
坐标系统
- 使用 [0-1000] 归一化坐标返回气泡的几何中心点 `center`
- 识别底部输入框的位置 `input_box`
输出格式
请返回纯 JSON 格式
@@ -175,24 +189,15 @@ async def get_vlm_analysis(image_path):
"center": [x, y],
"content": "8\""
},
{
"type": "text",
"sender": "对方" | "",
"center": [x, y],
"content": "这里是文本内容"
}
...
]
}
注意:
1. 坐标 `center` 和 `input_box` 必须是 [0-1000] 的归一化坐标。
2. `status` 判断:如果语音气泡的正下方紧挨着一条文本消息(通常是转换出的文字),则为 `converted`,否则为 `unconverted`。
3. `is_unread` 判断:务必准确识别红点!如果有红点则为 true。
4. 请按从上到下的顺序输出所有消息。
"""
try:
# 调用 VLM
response = await vlm_kit.analyze_image(image_path, prompt)
logger.info(f"VLM Raw Response: {response}") # 打印原始响应以便调试
json_str = vlm_kit.extract_json(response)
result_data = json.loads(json_str)
@@ -272,27 +277,38 @@ async def analyze_chat_image(image_path, output_path, device=None, target_name="
content = msg.get('content', '')
coords = msg.get('center', [0, 0]) # center
status = msg.get('status', 'unconverted')
is_unread = msg.get('is_unread', False)
is_converted = (status == "converted")
unread_mark = "[未读]" if is_unread else ""
# 记录对话日志
if msg_type == 'voice':
if is_converted:
dialogue_log.append(f"{sender}: [语音] {content} (已转换)")
dialogue_log.append(f"{sender}: {unread_mark}[语音] {content} (已转换)")
else:
dialogue_log.append(f"{sender}: [语音] (待转换)")
dialogue_log.append(f"{sender}: {unread_mark}[语音] (待转换)")
# 将 center 转换为 coordinates 供后续使用
msg['coordinates'] = coords
unconverted_voices.append(msg)
elif msg_type == 'text':
dialogue_log.append(f"{sender}: {content}")
logger.info(f"VLM 识别: {sender} [{msg_type}] {content} (Converted: {is_converted})")
logger.info(f"VLM 识别: {sender} [{msg_type}] {content} (Converted: {is_converted}, Unread: {is_unread})")
# 处理未转换的语音消息
if unconverted_voices:
logger.info(f"发现 {len(unconverted_voices)} 条未转换的语音消息,将仅处理最后一条...")
# 仅保留最后一条语音消息进行处理
unconverted_voices = [unconverted_voices[-1]]
# 优先级1. 有红点的最后一条 2. 没红点的最后一条
unread_voices = [v for v in unconverted_voices if v.get('is_unread')]
if unread_voices:
logger.info(f"发现 {len(unread_voices)} 条未读语音消息,优先处理最后一条...")
voice_to_process = unread_voices[-1]
else:
logger.info(f"发现 {len(unconverted_voices)} 条未转换语音消息,处理最后一条...")
voice_to_process = unconverted_voices[-1]
# 仅保留选中的一条进行处理
unconverted_voices = [voice_to_process]
# 使用传入的 device 或创建新连接
d = device if device else connect_device()