|
import gradio as gr
|
|
import json
|
|
from pathlib import Path
|
|
import yaml
|
|
import re
|
|
import logging
|
|
import io
|
|
import sys
|
|
import os
|
|
import re
|
|
from datetime import datetime, timezone, timedelta
|
|
import requests
|
|
|
|
from tools import FileUploader, ResultExtractor, audio_to_str, image_to_str, azure_speech_to_text
|
|
import numpy as np
|
|
from scipy.io.wavfile import write as write_wav
|
|
from PIL import Image
|
|
|
|
|
|
SAVE_DIR = 'download'
|
|
os.makedirs(SAVE_DIR, exist_ok=True)
|
|
|
|
def save_audio(audio, filename):
|
|
"""保存音频为.wav文件"""
|
|
sample_rate, audio_data = audio
|
|
write_wav(filename, sample_rate, audio_data)
|
|
|
|
def save_image(image, filename):
|
|
"""保存图片为.jpg文件"""
|
|
img = Image.fromarray(image.astype('uint8'))
|
|
img.save(filename)
|
|
|
|
|
|
def get_client_ip(request: gr.Request, debug_mode=False):
|
|
"""获取客户端真实IP地址"""
|
|
if request:
|
|
|
|
x_forwarded_for = request.headers.get("x-forwarded-for", "")
|
|
if x_forwarded_for:
|
|
client_ip = x_forwarded_for.split(",")[0]
|
|
else:
|
|
client_ip = request.client.host
|
|
if debug_mode:
|
|
print(f"Debug: Client IP detected as {client_ip}")
|
|
return client_ip
|
|
return "unknown"
|
|
|
|
|
|
CONFIG = None
|
|
HF_CONFIG_PATH = Path(__file__).parent / "todogen_LLM_config.yaml"
|
|
|
|
def load_hf_config():
|
|
global CONFIG
|
|
if CONFIG is None:
|
|
try:
|
|
with open(HF_CONFIG_PATH, 'r', encoding='utf-8') as f:
|
|
CONFIG = yaml.safe_load(f)
|
|
print(f"✅ 配置已加载: {HF_CONFIG_PATH}")
|
|
except FileNotFoundError:
|
|
print(f"❌ 错误: 配置文件 {HF_CONFIG_PATH} 未找到。请确保它在 hf 目录下。")
|
|
CONFIG = {}
|
|
except Exception as e:
|
|
print(f"❌ 加载配置文件 {HF_CONFIG_PATH} 时出错: {e}")
|
|
CONFIG = {}
|
|
return CONFIG
|
|
|
|
def get_hf_openai_config():
|
|
config = load_hf_config()
|
|
return config.get('openai', {})
|
|
|
|
def get_hf_openai_filter_config():
|
|
config = load_hf_config()
|
|
return config.get('openai_filter', {})
|
|
|
|
def get_hf_xunfei_config():
|
|
config = load_hf_config()
|
|
return config.get('xunfei', {})
|
|
|
|
def get_hf_azure_speech_config():
|
|
config = load_hf_config()
|
|
return config.get('azure_speech', {})
|
|
|
|
def get_hf_paths_config():
|
|
config = load_hf_config()
|
|
|
|
base = Path(__file__).resolve().parent
|
|
paths_cfg = config.get('paths', {})
|
|
return {
|
|
'base_dir': base,
|
|
'prompt_template': base / paths_cfg.get('prompt_template', 'prompt_template.txt'),
|
|
'true_positive_examples': base / paths_cfg.get('true_positive_examples', 'TruePositive_few_shot.txt'),
|
|
'false_positive_examples': base / paths_cfg.get('false_positive_examples', 'FalsePositive_few_shot.txt'),
|
|
|
|
}
|
|
|
|
|
|
|
|
llm_config = get_hf_openai_config()
|
|
NVIDIA_API_BASE_URL = llm_config.get('base_url')
|
|
NVIDIA_API_KEY = llm_config.get('api_key')
|
|
NVIDIA_MODEL_NAME = llm_config.get('model')
|
|
|
|
|
|
filter_config = get_hf_openai_filter_config()
|
|
Filter_API_BASE_URL = filter_config.get('base_url_filter')
|
|
Filter_API_KEY = filter_config.get('api_key_filter')
|
|
Filter_MODEL_NAME = filter_config.get('model_filter')
|
|
|
|
|
|
if not NVIDIA_API_BASE_URL or not NVIDIA_API_KEY or not NVIDIA_MODEL_NAME:
|
|
print("❌ 错误: NVIDIA API 配置不完整。请检查 todogen_LLM_config.yaml 中的 openai 部分。")
|
|
|
|
NVIDIA_API_BASE_URL = ""
|
|
NVIDIA_API_KEY = ""
|
|
NVIDIA_MODEL_NAME = ""
|
|
|
|
if not Filter_API_BASE_URL or not Filter_API_KEY or not Filter_MODEL_NAME:
|
|
print("❌ 错误: Filter API 配置不完整。请检查 todogen_LLM_config.yaml 中的 openai_filter 部分。")
|
|
|
|
Filter_API_BASE_URL = ""
|
|
Filter_API_KEY = ""
|
|
Filter_MODEL_NAME = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def load_single_few_shot_file_hf(file_path: Path) -> str:
|
|
"""加载单个 few-shot 文件并转义 { 和 }"""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
escaped_content = content.replace('{', '{{').replace('}', '}}')
|
|
logger.info(f"✅ 成功加载并转义文件: {file_path}")
|
|
return escaped_content
|
|
except FileNotFoundError:
|
|
logger.warning(f"⚠️ 警告:找不到文件 {file_path}。")
|
|
return ""
|
|
except Exception as e:
|
|
logger.error(f"❌ 加载文件 {file_path} 时出错: {e}", exc_info=True)
|
|
return ""
|
|
|
|
PROMPT_TEMPLATE_CONTENT = ""
|
|
TRUE_POSITIVE_EXAMPLES_CONTENT = ""
|
|
FALSE_POSITIVE_EXAMPLES_CONTENT = ""
|
|
|
|
def load_prompt_data_hf():
|
|
global PROMPT_TEMPLATE_CONTENT, TRUE_POSITIVE_EXAMPLES_CONTENT, FALSE_POSITIVE_EXAMPLES_CONTENT
|
|
paths = get_hf_paths_config()
|
|
try:
|
|
with open(paths['prompt_template'], 'r', encoding='utf-8') as f:
|
|
PROMPT_TEMPLATE_CONTENT = f.read()
|
|
logger.info(f"✅ 成功加载 Prompt 模板文件: {paths['prompt_template']}")
|
|
except FileNotFoundError:
|
|
logger.error(f"❌ 错误:找不到 Prompt 模板文件:{paths['prompt_template']}")
|
|
PROMPT_TEMPLATE_CONTENT = "Error: Prompt template not found."
|
|
|
|
TRUE_POSITIVE_EXAMPLES_CONTENT = load_single_few_shot_file_hf(paths['true_positive_examples'])
|
|
FALSE_POSITIVE_EXAMPLES_CONTENT = load_single_few_shot_file_hf(paths['false_positive_examples'])
|
|
|
|
|
|
load_prompt_data_hf()
|
|
|
|
|
|
def json_parser(text: str) -> dict:
|
|
|
|
logger.info(f"Attempting to parse: {text[:200]}...")
|
|
try:
|
|
|
|
try:
|
|
parsed_data = json.loads(text)
|
|
|
|
return _process_parsed_json(parsed_data)
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
|
|
match = re.search(r'```(?:json)?\n(.*?)```', text, re.DOTALL)
|
|
if match:
|
|
json_str = match.group(1).strip()
|
|
|
|
json_str = re.sub(r',\s*]', ']', json_str)
|
|
json_str = re.sub(r',\s*}', '}', json_str)
|
|
try:
|
|
parsed_data = json.loads(json_str)
|
|
|
|
return _process_parsed_json(parsed_data)
|
|
except json.JSONDecodeError as e_block:
|
|
logger.warning(f"JSONDecodeError from code block: {e_block} while parsing: {json_str[:200]}")
|
|
|
|
|
|
|
|
|
|
array_match = re.search(r'\[\s*\{.*?\}\s*(?:,\s*\{.*?\}\s*)*\]', text, re.DOTALL)
|
|
if array_match:
|
|
potential_json = array_match.group(0).strip()
|
|
try:
|
|
parsed_data = json.loads(potential_json)
|
|
|
|
return _process_parsed_json(parsed_data)
|
|
except json.JSONDecodeError:
|
|
logger.warning(f"Could not parse potential JSON array: {potential_json[:200]}")
|
|
pass
|
|
|
|
|
|
object_match = re.search(r'\{.*?\}', text, re.DOTALL)
|
|
if object_match:
|
|
potential_json = object_match.group(0).strip()
|
|
try:
|
|
parsed_data = json.loads(potential_json)
|
|
|
|
return _process_parsed_json(parsed_data)
|
|
except json.JSONDecodeError:
|
|
logger.warning(f"Could not parse potential JSON object: {potential_json[:200]}")
|
|
pass
|
|
|
|
|
|
logger.error(f"Failed to find or parse JSON block in text: {text[:500]}")
|
|
return {"error": "No valid JSON block found or failed to parse", "raw_text": text}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error in json_parser: {e} for text: {text[:200]}", exc_info=True)
|
|
return {"error": f"Unexpected error in json_parser: {e}", "raw_text": text}
|
|
|
|
def _process_parsed_json(parsed_data):
|
|
"""处理解析后的JSON数据,确保返回有效的数据结构"""
|
|
try:
|
|
|
|
if isinstance(parsed_data, list):
|
|
if not parsed_data:
|
|
logger.warning("JSON解析结果为空列表,返回包含空字典的列表")
|
|
return [{}]
|
|
|
|
|
|
processed_list = []
|
|
for item in parsed_data:
|
|
if isinstance(item, dict):
|
|
processed_list.append(item)
|
|
else:
|
|
|
|
try:
|
|
processed_list.append({"content": str(item)})
|
|
except:
|
|
processed_list.append({"content": "无法转换的项目"})
|
|
|
|
|
|
if not processed_list:
|
|
logger.warning("处理后的JSON列表为空,返回包含空字典的列表")
|
|
return [{}]
|
|
|
|
return processed_list
|
|
|
|
|
|
elif isinstance(parsed_data, dict):
|
|
return parsed_data
|
|
|
|
|
|
else:
|
|
logger.warning(f"JSON解析结果不是列表或字典,而是{type(parsed_data)},转换为字典")
|
|
return {"content": str(parsed_data)}
|
|
|
|
except Exception as e:
|
|
logger.error(f"处理解析后的JSON数据时出错: {e}")
|
|
return {"error": f"Error processing parsed JSON: {e}"}
|
|
|
|
|
|
FILTER_SYSTEM_PROMPT = """
|
|
# 角色
|
|
你是一个专业的短信内容分析助手,根据输入判断内容的类型及可信度,为用户使用信息提供依据和便利。
|
|
|
|
# 任务
|
|
对于输入的多条数据,分析每一条数据内容(主键:`message_id`)属于【物流取件、缴费充值、待付(还)款、会议邀约、其他】的可能性百分比。
|
|
主要对于聊天、问候、回执、结果通知、上月账单等信息不需要收件人进行下一步处理的信息,直接归到其他类进行忽略
|
|
|
|
# 要求
|
|
1. 以json格式输出
|
|
2. content简洁提炼关键词,字符数<20以内
|
|
3. 输入条数和输出条数完全一样
|
|
|
|
# 输出示例
|
|
```
|
|
[
|
|
{"message_id":"1111111","content":"账单805.57元待还","物流取件":0,"欠费缴纳":99,"待付(还)款":1,"会议邀约":0,"其他":0, "分类":"欠费缴纳"},
|
|
{"message_id":"222222","content":"邀请你加入飞书视频会议","物流取件":0,"欠费缴纳":0,"待付(还)款":1,"会议邀约":100,"其他":0, "分类":"会议"}
|
|
]
|
|
|
|
```
|
|
"""
|
|
|
|
|
|
def filter_message_with_llm(text_input: str, message_id: str = "user_input_001"):
|
|
logger.info(f"调用 filter_message_with_llm 处理输入: {text_input} (msg_id: {message_id})")
|
|
|
|
|
|
|
|
|
|
mock_data = [(text_input, message_id)]
|
|
|
|
|
|
system_prompt = """
|
|
# 角色
|
|
你是一个专业的短信内容分析助手,根据输入判断内容的类型及可信度,为用户使用信息提供依据和便利。
|
|
|
|
# 任务
|
|
对于输入的多条数据,分析每一条数据内容(主键:`message_id`)属于【物流取件、缴费充值、待付(还)款、会议邀约、其他】的可能性百分比。
|
|
主要对于聊天、问候、回执、结果通知、上月账单等信息不需要收件人进行下一步处理的信息,直接归到其他类进行忽略
|
|
|
|
# 要求
|
|
1. 以json格式输出
|
|
2. content简洁提炼关键词,字符数<20以内
|
|
3. 输入条数和输出条数完全一样
|
|
|
|
# 输出示例
|
|
```
|
|
[
|
|
{"message_id":"1111111","content":"账单805.57元待还","物流取件":0,"欠费缴纳":99,"待付(还)款":1,"会议邀约":0,"其他":0, "分类":"欠费缴纳"},
|
|
{"message_id":"222222","content":"邀请你加入飞书视频会议","物流取件":0,"欠费缴纳":0,"待付(还)款":1,"会议邀约":100,"其他":0, "分类":"会议邀约"}
|
|
]
|
|
```
|
|
"""
|
|
|
|
llm_messages = [
|
|
{"role": "system", "content": system_prompt},
|
|
{"role": "user", "content": str(mock_data)}
|
|
]
|
|
|
|
try:
|
|
if not Filter_API_BASE_URL or not Filter_API_KEY or not Filter_MODEL_NAME:
|
|
logger.error("Filter API 配置不完整,无法调用 Filter LLM。")
|
|
return [{"error": "Filter API configuration incomplete", "-": "-"}]
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {Filter_API_KEY}",
|
|
"Accept": "application/json"
|
|
}
|
|
payload = {
|
|
"model": Filter_MODEL_NAME,
|
|
"messages": llm_messages,
|
|
"temperature": 0.0,
|
|
"top_p": 0.95,
|
|
"max_tokens": 1024,
|
|
"stream": False
|
|
}
|
|
|
|
api_url = f"{Filter_API_BASE_URL}/chat/completions"
|
|
|
|
try:
|
|
response = requests.post(api_url, headers=headers, json=payload)
|
|
response.raise_for_status()
|
|
raw_llm_response = response.json()["choices"][0]["message"]["content"]
|
|
logger.info(f"LLM 原始回复 (部分): {raw_llm_response[:200]}...")
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"调用 Filter API 失败: {e}")
|
|
return [{"error": f"Filter API call failed: {e}", "-": "-"}]
|
|
logger.info(f"Filter LLM 原始回复 (部分): {raw_llm_response[:200]}...")
|
|
|
|
|
|
|
|
raw_llm_response = raw_llm_response.replace("```json", "").replace("```", "")
|
|
parsed_filter_data = json_parser(raw_llm_response)
|
|
|
|
if "error" in parsed_filter_data:
|
|
logger.error(f"解析 Filter LLM 响应失败: {parsed_filter_data['error']}")
|
|
return [{"error": f"Filter LLM response parsing error: {parsed_filter_data['error']}"}]
|
|
|
|
|
|
if isinstance(parsed_filter_data, list) and parsed_filter_data:
|
|
|
|
for item in parsed_filter_data:
|
|
if isinstance(item, dict) and item.get("分类") == "欠费缴纳" and "缴费支出" in item.get("content", ""):
|
|
item["分类"] = "其他"
|
|
|
|
|
|
request_id_list = {message_id}
|
|
response_id_list = {item.get('message_id') for item in parsed_filter_data if isinstance(item, dict)}
|
|
diff = request_id_list - response_id_list
|
|
|
|
if diff:
|
|
logger.warning(f"Filter LLM 响应中有遗漏的消息ID: {diff}")
|
|
|
|
for missed_id in diff:
|
|
parsed_filter_data.append({
|
|
"message_id": missed_id,
|
|
"content": text_input[:20],
|
|
"物流取件": 0,
|
|
"欠费缴纳": 0,
|
|
"待付(还)款": 0,
|
|
"会议邀约": 0,
|
|
"其他": 100,
|
|
"分类": "其他"
|
|
})
|
|
|
|
return parsed_filter_data
|
|
else:
|
|
logger.warning(f"Filter LLM 返回空列表或非预期格式: {parsed_filter_data}")
|
|
|
|
return [{
|
|
"message_id": message_id,
|
|
"content": text_input[:20],
|
|
"物流取件": 0,
|
|
"欠费缴纳": 0,
|
|
"待付(还)款": 0,
|
|
"会议邀约": 0,
|
|
"其他": 100,
|
|
"分类": "其他",
|
|
"error": "Filter LLM returned empty or unexpected format"
|
|
}]
|
|
|
|
except Exception as e:
|
|
logger.exception(f"调用 Filter LLM 或解析时发生错误 (filter_message_with_llm)")
|
|
return [{
|
|
"message_id": message_id,
|
|
"content": text_input[:20],
|
|
"物流取件": 0,
|
|
"欠费缴纳": 0,
|
|
"待付(还)款": 0,
|
|
"会议邀约": 0,
|
|
"其他": 100,
|
|
"分类": "其他",
|
|
"error": f"Filter LLM call/parse error: {str(e)}"
|
|
}]
|
|
|
|
|
|
def generate_todolist_from_text(text_input: str, message_id: str = "user_input_001"):
|
|
"""根据输入文本生成 ToDoList (使用迁移的逻辑)"""
|
|
logger.info(f"调用 generate_todolist_from_text 处理输入: {text_input} (msg_id: {message_id})")
|
|
|
|
if not PROMPT_TEMPLATE_CONTENT or "Error:" in PROMPT_TEMPLATE_CONTENT:
|
|
logger.error("Prompt 模板未正确加载,无法生成 ToDoList。")
|
|
return [["error", "Prompt template not loaded", "-"]]
|
|
|
|
current_time_iso = datetime.now(timezone.utc).isoformat()
|
|
|
|
content_escaped = text_input.replace('{', '{{').replace('}', '}}')
|
|
|
|
|
|
formatted_prompt = PROMPT_TEMPLATE_CONTENT.format(
|
|
true_positive_examples=TRUE_POSITIVE_EXAMPLES_CONTENT,
|
|
false_positive_examples=FALSE_POSITIVE_EXAMPLES_CONTENT,
|
|
current_time=current_time_iso,
|
|
message_id=message_id,
|
|
content_escaped=content_escaped
|
|
)
|
|
|
|
|
|
enhanced_prompt = formatted_prompt + """
|
|
|
|
# 重要提示
|
|
请确保你的回复是有效的JSON格式,并且只包含JSON内容。不要添加任何额外的解释或文本。
|
|
你的回复应该严格按照上面的输出示例格式,只包含JSON对象,不要有任何其他文本。
|
|
"""
|
|
|
|
|
|
llm_messages = [
|
|
{"role": "user", "content": enhanced_prompt}
|
|
]
|
|
|
|
logger.info(f"发送给 LLM 的消息 (部分): {str(llm_messages)[:300]}...")
|
|
|
|
try:
|
|
|
|
|
|
if ("充值" in text_input or "缴费" in text_input) and ("移动" in text_input or "话费" in text_input or "余额" in text_input):
|
|
|
|
todo_item = {
|
|
message_id: {
|
|
"is_todo": True,
|
|
"end_time": (datetime.now(timezone.utc) + timedelta(days=3)).isoformat(),
|
|
"location": "线上:中国移动APP",
|
|
"todo_content": "缴纳话费",
|
|
"urgency": "important"
|
|
}
|
|
}
|
|
|
|
|
|
todo_content = "缴纳话费"
|
|
end_time = todo_item[message_id]["end_time"].split("T")[0]
|
|
location = todo_item[message_id]["location"]
|
|
|
|
|
|
combined_content = f"{todo_content} (截止时间: {end_time}, 地点: {location})"
|
|
|
|
output_for_df = []
|
|
output_for_df.append([1, combined_content, "重要"])
|
|
|
|
return output_for_df
|
|
|
|
|
|
elif "会议" in text_input and ("邀请" in text_input or "参加" in text_input):
|
|
|
|
meeting_time = None
|
|
meeting_pattern = r'(\d{1,2}[月/-]\d{1,2}[日号]?\s*\d{1,2}[点:]\d{0,2}|\d{4}[年/-]\d{1,2}[月/-]\d{1,2}[日号]?\s*\d{1,2}[点:]\d{0,2})'
|
|
meeting_match = re.search(meeting_pattern, text_input)
|
|
|
|
if meeting_match:
|
|
|
|
meeting_time = (datetime.now(timezone.utc) + timedelta(days=1, hours=2)).isoformat()
|
|
else:
|
|
meeting_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()
|
|
|
|
todo_item = {
|
|
message_id: {
|
|
"is_todo": True,
|
|
"end_time": meeting_time,
|
|
"location": "线上:会议软件",
|
|
"todo_content": "参加会议",
|
|
"urgency": "important"
|
|
}
|
|
}
|
|
|
|
|
|
todo_content = "参加会议"
|
|
end_time = todo_item[message_id]["end_time"].split("T")[0]
|
|
location = todo_item[message_id]["location"]
|
|
|
|
|
|
combined_content = f"{todo_content} (截止时间: {end_time}, 地点: {location})"
|
|
|
|
output_for_df = []
|
|
output_for_df.append([1, combined_content, "重要"])
|
|
|
|
return output_for_df
|
|
|
|
|
|
elif ("快递" in text_input or "物流" in text_input or "取件" in text_input) and ("到达" in text_input or "取件码" in text_input or "柜" in text_input):
|
|
|
|
pickup_code = None
|
|
code_pattern = r'取件码[是为:]?\s*(\d{4,6})'
|
|
code_match = re.search(code_pattern, text_input)
|
|
|
|
todo_content = "取快递"
|
|
if code_match:
|
|
pickup_code = code_match.group(1)
|
|
todo_content = f"取快递(取件码:{pickup_code})"
|
|
|
|
todo_item = {
|
|
message_id: {
|
|
"is_todo": True,
|
|
"end_time": (datetime.now(timezone.utc) + timedelta(days=2)).isoformat(),
|
|
"location": "线下:快递柜",
|
|
"todo_content": todo_content,
|
|
"urgency": "important"
|
|
}
|
|
}
|
|
|
|
|
|
end_time = todo_item[message_id]["end_time"].split("T")[0]
|
|
location = todo_item[message_id]["location"]
|
|
|
|
|
|
combined_content = f"{todo_content} (截止时间: {end_time}, 地点: {location})"
|
|
|
|
output_for_df = []
|
|
output_for_df.append([1, combined_content, "重要"])
|
|
|
|
return output_for_df
|
|
|
|
|
|
if not Filter_API_BASE_URL or not Filter_API_KEY or not Filter_MODEL_NAME:
|
|
logger.error("Filter API 配置不完整,无法调用 Filter LLM。")
|
|
return [["error", "Filter API configuration incomplete", "-"]]
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {Filter_API_KEY}",
|
|
"Accept": "application/json"
|
|
}
|
|
payload = {
|
|
"model": Filter_MODEL_NAME,
|
|
"messages": llm_messages,
|
|
"temperature": 0.2,
|
|
"top_p": 0.95,
|
|
"max_tokens": 1024,
|
|
"stream": False
|
|
}
|
|
|
|
api_url = f"{Filter_API_BASE_URL}/chat/completions"
|
|
|
|
try:
|
|
response = requests.post(api_url, headers=headers, json=payload)
|
|
response.raise_for_status()
|
|
raw_llm_response = response.json()['choices'][0]['message']['content']
|
|
logger.info(f"LLM 原始回复 (部分): {raw_llm_response[:200]}...")
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"调用 Filter API 失败: {e}")
|
|
return [["error", f"Filter API call failed: {e}", "-"]]
|
|
|
|
|
|
parsed_todos_data = json_parser(raw_llm_response)
|
|
|
|
if "error" in parsed_todos_data:
|
|
logger.error(f"解析 LLM 响应失败: {parsed_todos_data['error']}")
|
|
return [["error", f"LLM response parsing error: {parsed_todos_data['error']}", parsed_todos_data.get('raw_text', '')[:50] + "..."]]
|
|
|
|
|
|
output_for_df = []
|
|
|
|
|
|
if isinstance(parsed_todos_data, dict):
|
|
|
|
todo_info = None
|
|
for key, value in parsed_todos_data.items():
|
|
if key == message_id or key == str(message_id):
|
|
todo_info = value
|
|
break
|
|
|
|
if todo_info and isinstance(todo_info, dict) and todo_info.get("is_todo", False):
|
|
|
|
todo_content = todo_info.get("todo_content", "未指定待办内容")
|
|
end_time = todo_info.get("end_time")
|
|
location = todo_info.get("location")
|
|
urgency = todo_info.get("urgency", "unimportant")
|
|
|
|
|
|
combined_content = todo_content
|
|
|
|
|
|
if end_time and end_time != "null":
|
|
try:
|
|
date_part = end_time.split("T")[0] if "T" in end_time else end_time
|
|
combined_content += f" (截止时间: {date_part}"
|
|
except:
|
|
combined_content += f" (截止时间: {end_time}"
|
|
else:
|
|
combined_content += " ("
|
|
|
|
|
|
if location and location != "null":
|
|
combined_content += f", 地点: {location})"
|
|
else:
|
|
combined_content += ")"
|
|
|
|
|
|
urgency_display = "一般"
|
|
if urgency == "urgent":
|
|
urgency_display = "紧急"
|
|
elif urgency == "important":
|
|
urgency_display = "重要"
|
|
|
|
|
|
output_for_df = []
|
|
output_for_df.append([1, combined_content, urgency_display])
|
|
else:
|
|
|
|
output_for_df = []
|
|
output_for_df.append([1, "此消息不包含待办事项", "-"])
|
|
|
|
|
|
elif isinstance(parsed_todos_data, list):
|
|
output_for_df = []
|
|
|
|
|
|
if not parsed_todos_data:
|
|
logger.warning("LLM 返回了空列表,无法生成 ToDo 项目")
|
|
return [[1, "未能生成待办事项", "-"]]
|
|
|
|
for i, item in enumerate(parsed_todos_data):
|
|
if isinstance(item, dict):
|
|
todo_content = item.get('todo_content', item.get('content', 'N/A'))
|
|
status = item.get('status', '未完成')
|
|
urgency = item.get('urgency', 'normal')
|
|
|
|
|
|
combined_content = todo_content
|
|
|
|
|
|
if 'end_time' in item and item['end_time']:
|
|
try:
|
|
if isinstance(item['end_time'], str):
|
|
date_part = item['end_time'].split("T")[0] if "T" in item['end_time'] else item['end_time']
|
|
combined_content += f" (截止时间: {date_part}"
|
|
else:
|
|
combined_content += f" (截止时间: {str(item['end_time'])}"
|
|
except Exception as e:
|
|
logger.warning(f"处理end_time时出错: {e}")
|
|
combined_content += " ("
|
|
else:
|
|
combined_content += " ("
|
|
|
|
|
|
if 'location' in item and item['location']:
|
|
combined_content += f", 地点: {item['location']})"
|
|
else:
|
|
combined_content += ")"
|
|
|
|
|
|
importance = "一般"
|
|
if urgency == "urgent":
|
|
importance = "紧急"
|
|
elif urgency == "important":
|
|
importance = "重要"
|
|
|
|
output_for_df.append([i + 1, combined_content, importance])
|
|
else:
|
|
|
|
try:
|
|
item_str = str(item) if item is not None else "未知项目"
|
|
output_for_df.append([i + 1, item_str, "一般"])
|
|
except Exception as e:
|
|
logger.warning(f"处理非字典项目时出错: {e}")
|
|
output_for_df.append([i + 1, "处理错误的项目", "一般"])
|
|
|
|
if not output_for_df:
|
|
logger.info("LLM 解析结果为空或无法转换为DataFrame格式。")
|
|
return [["info", "未发现待办事项", "-"]]
|
|
|
|
return output_for_df
|
|
|
|
except Exception as e:
|
|
logger.exception(f"调用 LLM 或解析时发生错误 (generate_todolist_from_text)")
|
|
return [["error", f"LLM call/parse error: {str(e)}", "-"]]
|
|
|
|
|
|
def process(audio, image, request: gr.Request):
|
|
"""处理语音和图片的示例函数"""
|
|
|
|
client_ip = get_client_ip(request, True)
|
|
print(f"Processing audio/image request from IP: {client_ip}")
|
|
|
|
if audio is not None:
|
|
sample_rate, audio_data = audio
|
|
audio_info = f"音频采样率: {sample_rate}Hz, 数据长度: {len(audio_data)}"
|
|
else:
|
|
audio_info = "未收到音频"
|
|
|
|
if image is not None:
|
|
image_info = f"图片尺寸: {image.shape}"
|
|
else:
|
|
image_info = "未收到图片"
|
|
|
|
return audio_info, image_info
|
|
|
|
def respond(
|
|
message,
|
|
history: list[tuple[str, str]],
|
|
system_message,
|
|
max_tokens,
|
|
temperature,
|
|
top_p,
|
|
audio,
|
|
image
|
|
):
|
|
|
|
|
|
|
|
|
|
multimodal_content = ""
|
|
|
|
|
|
|
|
if audio is not None:
|
|
try:
|
|
audio_sample_rate, audio_data = audio
|
|
multimodal_content += f"\n[音频信息: 采样率 {audio_sample_rate}Hz, 时长 {len(audio_data)/audio_sample_rate:.2f}秒]"
|
|
|
|
|
|
azure_speech_config = get_hf_azure_speech_config()
|
|
azure_speech_key = azure_speech_config.get('key')
|
|
azure_speech_region = azure_speech_config.get('region')
|
|
|
|
if azure_speech_key and azure_speech_region:
|
|
import tempfile
|
|
import soundfile as sf
|
|
import os
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
|
|
sf.write(temp_audio.name, audio_data, audio_sample_rate)
|
|
temp_audio_path = temp_audio.name
|
|
|
|
audio_text = azure_speech_to_text(azure_speech_key, azure_speech_region, temp_audio_path)
|
|
if audio_text:
|
|
multimodal_content += f"\n[语音识别结果: {audio_text}]"
|
|
else:
|
|
multimodal_content += "\n[语音识别失败]"
|
|
|
|
os.unlink(temp_audio_path)
|
|
else:
|
|
multimodal_content += "\n[Azure Speech API配置不完整,无法进行语音识别]"
|
|
|
|
except Exception as e:
|
|
multimodal_content += f"\n[音频处理错误: {str(e)}]"
|
|
|
|
if image is not None:
|
|
try:
|
|
multimodal_content += f"\n[图片信息: 尺寸 {image.shape}]"
|
|
|
|
|
|
if xunfei_appid and xunfei_apikey and xunfei_apisecret:
|
|
import tempfile
|
|
from PIL import Image
|
|
import os
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as temp_image:
|
|
if len(image.shape) == 3:
|
|
pil_image = Image.fromarray(image.astype('uint8'), 'RGB')
|
|
else:
|
|
pil_image = Image.fromarray(image.astype('uint8'), 'L')
|
|
|
|
pil_image.save(temp_image.name, 'JPEG')
|
|
temp_image_path = temp_image.name
|
|
|
|
image_text = image_to_str(endpoint="https://ai-siyuwang5414995ai361208251338.cognitiveservices.azure.com/", key="45PYY2Av9CdMCveAjVG43MGKrnHzSxdiFTK9mWBgrOsMAHavxKj0JQQJ99BDACHYHv6XJ3w3AAAAACOGeVpQ", unused_param=None, file_path=temp_image_path)
|
|
if image_text:
|
|
multimodal_content += f"\n[图像识别结果: {image_text}]"
|
|
else:
|
|
multimodal_content += "\n[图像识别失败]"
|
|
|
|
os.unlink(temp_image_path)
|
|
else:
|
|
multimodal_content += "\n[讯飞API配置不完整,无法进行图像识别]"
|
|
|
|
except Exception as e:
|
|
multimodal_content += f"\n[图像处理错误: {str(e)}]"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chat_messages = [{"role": "system", "content": system_message}]
|
|
for val in history:
|
|
if val[0]:
|
|
chat_messages.append({"role": "user", "content": val[0]})
|
|
if val[1]:
|
|
chat_messages.append({"role": "assistant", "content": val[1]})
|
|
chat_messages.append({"role": "user", "content": message})
|
|
|
|
chat_response_stream = ""
|
|
if not Filter_API_BASE_URL or not Filter_API_KEY or not Filter_MODEL_NAME:
|
|
logger.error("Filter API 配置不完整,无法调用 LLM333。")
|
|
yield "Filter API 配置不完整,无法提供聊天回复。", []
|
|
return
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {Filter_API_KEY}",
|
|
"Accept": "application/json"
|
|
}
|
|
payload = {
|
|
"model": Filter_MODEL_NAME,
|
|
"messages": chat_messages,
|
|
"temperature": temperature,
|
|
"top_p": top_p,
|
|
"max_tokens": max_tokens,
|
|
"stream": True
|
|
}
|
|
api_url = f"{Filter_API_BASE_URL}/chat/completions"
|
|
|
|
try:
|
|
response = requests.post(api_url, headers=headers, json=payload, stream=True)
|
|
response.raise_for_status()
|
|
|
|
for chunk in response.iter_content(chunk_size=None):
|
|
if chunk:
|
|
try:
|
|
|
|
|
|
for line in chunk.decode('utf-8').splitlines():
|
|
if line.startswith('data: '):
|
|
json_data = line[len('data: '):]
|
|
if json_data.strip() == '[DONE]':
|
|
break
|
|
data = json.loads(json_data)
|
|
|
|
if 'choices' in data and len(data['choices']) > 0:
|
|
token = data['choices'][0]['delta'].get('content', '')
|
|
if token:
|
|
chat_response_stream += token
|
|
yield chat_response_stream, []
|
|
except json.JSONDecodeError:
|
|
logger.warning(f"无法解析流式响应块: {chunk.decode('utf-8')}")
|
|
except Exception as e:
|
|
logger.error(f"处理流式响应时发生错误: {e}")
|
|
yield chat_response_stream + f"\n\n错误: {e}", []
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"调用 NVIDIA API 失败: {e}")
|
|
yield f"调用 NVIDIA API 失败: {e}", []
|
|
|
|
|
|
all_todos_global = []
|
|
|
|
|
|
with gr.Blocks() as app:
|
|
gr.Markdown("# ToDoAgent Multi-Modal Interface with ToDo List")
|
|
|
|
with gr.Row():
|
|
with gr.Column(scale=2):
|
|
gr.Markdown("## Chat Interface")
|
|
chatbot = gr.Chatbot(height=450, label="聊天记录", type="messages")
|
|
msg = gr.Textbox(label="输入消息", placeholder="输入您的问题或待办事项...")
|
|
|
|
with gr.Row():
|
|
audio_input = gr.Audio(label="上传语音", type="numpy", sources=["upload", "microphone"])
|
|
image_input = gr.Image(label="上传图片", type="numpy")
|
|
|
|
with gr.Accordion("高级设置", open=False):
|
|
system_msg = gr.Textbox(value="You are a friendly Chatbot.", label="系统提示")
|
|
max_tokens_slider = gr.Slider(minimum=1, maximum=4096, value=1024, step=1, label="最大生成长度(聊天)")
|
|
temperature_slider = gr.Slider(minimum=0.1, maximum=2.0, value=0.7, step=0.1, label="温度(聊天)")
|
|
top_p_slider = gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p(聊天)")
|
|
|
|
with gr.Row():
|
|
submit_btn = gr.Button("发送", variant="primary")
|
|
clear_btn = gr.Button("清除聊天和ToDo")
|
|
|
|
with gr.Column(scale=1):
|
|
gr.Markdown("## Generated ToDo List")
|
|
todolist_df = gr.DataFrame(headers=["ID", "任务内容", "状态"],
|
|
datatype=["number", "str", "str"],
|
|
row_count=(0, "dynamic"),
|
|
col_count=(3, "fixed"),
|
|
label="待办事项列表")
|
|
|
|
def user(user_message, chat_history):
|
|
|
|
if not chat_history: chat_history = []
|
|
chat_history.append({"role": "user", "content": user_message})
|
|
return "", chat_history
|
|
|
|
def bot_interaction(chat_history, system_message, max_tokens, temperature, top_p, audio, image):
|
|
user_message_for_chat = ""
|
|
if chat_history and chat_history[-1]["role"] == "user":
|
|
user_message_for_chat = chat_history[-1]["content"]
|
|
|
|
|
|
text_for_todolist = user_message_for_chat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
formatted_history_for_respond = []
|
|
temp_user_msg = None
|
|
for item in chat_history[:-1]:
|
|
if item["role"] == "user":
|
|
temp_user_msg = item["content"]
|
|
elif item["role"] == "assistant" and temp_user_msg is not None:
|
|
formatted_history_for_respond.append((temp_user_msg, item["content"]))
|
|
temp_user_msg = None
|
|
elif item["role"] == "assistant" and temp_user_msg is None:
|
|
formatted_history_for_respond.append(("", item["content"]))
|
|
|
|
chat_stream_generator = respond(
|
|
user_message_for_chat,
|
|
formatted_history_for_respond,
|
|
system_message,
|
|
max_tokens,
|
|
temperature,
|
|
top_p,
|
|
audio,
|
|
image
|
|
)
|
|
|
|
full_chat_response = ""
|
|
current_todos = []
|
|
|
|
for chat_response_part, _ in chat_stream_generator:
|
|
full_chat_response = chat_response_part
|
|
|
|
if chat_history and chat_history[-1]["role"] == "user":
|
|
|
|
|
|
|
|
|
|
pass
|
|
yield chat_history + [[None, full_chat_response]], current_todos
|
|
|
|
|
|
if chat_history and full_chat_response:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
if text_for_todolist:
|
|
|
|
message_id_for_todo = f"hf_app_{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
|
|
new_todo_items = generate_todolist_from_text(text_for_todolist, message_id_for_todo)
|
|
current_todos = new_todo_items
|
|
|
|
|
|
|
|
final_chat_history = list(chat_history)
|
|
if full_chat_response:
|
|
final_chat_history.append({"role": "assistant", "content": full_chat_response})
|
|
|
|
yield final_chat_history, current_todos
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process_filtered_result_for_todo(filtered_result, content, source_type):
|
|
"""处理过滤结果并生成todolist的辅助函数"""
|
|
todos = []
|
|
|
|
if isinstance(filtered_result, dict) and "error" in filtered_result:
|
|
logger.error(f"{source_type} Filter 模块处理失败: {filtered_result['error']}")
|
|
todos = [["Error", f"{source_type}: {filtered_result['error']}", "Filter Failed"]]
|
|
elif isinstance(filtered_result, dict) and filtered_result.get("分类") == "其他":
|
|
logger.info(f"{source_type}消息被 Filter 模块归类为 '其他',不生成 ToDo List。")
|
|
todos = [["Info", f"{source_type}: 消息被归类为 '其他',无需生成 ToDo。", "Filtered"]]
|
|
elif isinstance(filtered_result, list):
|
|
|
|
category = None
|
|
if filtered_result:
|
|
for item in filtered_result:
|
|
if isinstance(item, dict) and "分类" in item:
|
|
category = item["分类"]
|
|
break
|
|
|
|
if category == "其他":
|
|
logger.info(f"{source_type}消息被 Filter 模块归类为 '其他',不生成 ToDo List。")
|
|
todos = [["Info", f"{source_type}: 消息被归类为 '其他',无需生成 ToDo。", "Filtered"]]
|
|
else:
|
|
logger.info(f"{source_type}消息被 Filter 模块归类为 '{category if category else '未知'}',继续生成 ToDo List。")
|
|
if content:
|
|
msg_id_todo = f"hf_app_todo_{source_type}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
|
|
todos = generate_todolist_from_text(content, msg_id_todo)
|
|
|
|
for todo in todos:
|
|
if len(todo) > 1:
|
|
todo[1] = f"[{source_type}] {todo[1]}"
|
|
else:
|
|
|
|
logger.info(f"{source_type}消息被 Filter 模块归类为 '{filtered_result.get('分类') if isinstance(filtered_result, dict) else '未知'}',继续生成 ToDo List。")
|
|
if content:
|
|
msg_id_todo = f"hf_app_todo_{source_type}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
|
|
todos = generate_todolist_from_text(content, msg_id_todo)
|
|
|
|
for todo in todos:
|
|
if len(todo) > 1:
|
|
todo[1] = f"[{source_type}] {todo[1]}"
|
|
|
|
return todos
|
|
|
|
def handle_submit(user_msg_content, ch_history, sys_msg, max_t, temp, t_p, audio_f, image_f, request: gr.Request):
|
|
global all_todos_global
|
|
|
|
|
|
client_ip = get_client_ip(request, True)
|
|
print(f"Processing request from IP: {client_ip}")
|
|
|
|
|
|
multimodal_text_content = ""
|
|
|
|
logger.info(f"开始多模态处理 - 音频: {audio_f is not None}, 图像: {image_f is not None}")
|
|
|
|
|
|
azure_speech_config = get_hf_azure_speech_config()
|
|
azure_speech_key = azure_speech_config.get('key')
|
|
azure_speech_region = azure_speech_config.get('region')
|
|
|
|
|
|
logger.info(f"Azure Speech配置状态 - key: {bool(azure_speech_key)}, region: {bool(azure_speech_region)}")
|
|
|
|
|
|
if audio_f is not None and azure_speech_key and azure_speech_region:
|
|
logger.info("开始处理音频输入...")
|
|
try:
|
|
audio_sample_rate, audio_data = audio_f
|
|
logger.info(f"音频信息: 采样率 {audio_sample_rate}Hz, 数据长度 {len(audio_data)}")
|
|
|
|
|
|
audio_filename = os.path.join(SAVE_DIR, f"audio_{client_ip}.wav")
|
|
save_audio(audio_f, audio_filename)
|
|
logger.info(f"音频已保存: {audio_filename}")
|
|
|
|
|
|
audio_text = azure_speech_to_text(azure_speech_key, azure_speech_region, audio_filename)
|
|
logger.info(f"音频识别结果: {audio_text}")
|
|
if audio_text:
|
|
multimodal_text_content += f"音频内容: {audio_text}"
|
|
logger.info("音频处理完成")
|
|
else:
|
|
logger.warning("音频处理失败")
|
|
except Exception as e:
|
|
logger.error(f"音频处理错误: {str(e)}")
|
|
elif audio_f is not None:
|
|
logger.warning("音频文件存在但Azure Speech配置不完整,跳过音频处理")
|
|
|
|
|
|
if image_f is not None:
|
|
logger.info("开始处理图像输入...")
|
|
try:
|
|
logger.info(f"图像信息: 形状 {image_f.shape}, 数据类型 {image_f.dtype}")
|
|
|
|
|
|
image_filename = os.path.join(SAVE_DIR, f"image_{client_ip}.jpg")
|
|
save_image(image_f, image_filename)
|
|
logger.info(f"图像已保存: {image_filename}")
|
|
|
|
|
|
image_text = image_to_str(endpoint="https://ai-siyuwang5414995ai361208251338.cognitiveservices.azure.com/", key="45PYY2Av9CdMCveAjVG43MGKrnHzSxdiFTK9mWBgrOsMAHavxKj0JQQJ99BDACHYHv6XJ3w3AAAAACOGeVpQ", unused_param=None, file_path=image_filename)
|
|
logger.info(f"图像识别结果: {image_text}")
|
|
if image_text:
|
|
if multimodal_text_content:
|
|
multimodal_text_content += "\n"
|
|
multimodal_text_content += f"图像内容: {image_text}"
|
|
logger.info("图像处理完成")
|
|
else:
|
|
logger.warning("图像处理失败")
|
|
except Exception as e:
|
|
logger.error(f"图像处理错误: {str(e)}")
|
|
elif image_f is not None:
|
|
logger.warning("图像文件存在但处理失败,跳过图像处理")
|
|
|
|
|
|
final_user_content = user_msg_content.strip() if user_msg_content else ""
|
|
if not final_user_content and multimodal_text_content:
|
|
final_user_content = multimodal_text_content
|
|
logger.info(f"用户无文本输入,使用多模态内容作为用户输入: {final_user_content}")
|
|
elif final_user_content and multimodal_text_content:
|
|
|
|
final_user_content = f"{final_user_content}\n{multimodal_text_content}"
|
|
logger.info(f"用户有文本输入,多模态内容作为补充")
|
|
|
|
|
|
if not final_user_content:
|
|
final_user_content = "[无输入内容]"
|
|
logger.warning("用户没有提供任何输入内容(文本、音频或图像)")
|
|
|
|
logger.info(f"最终用户输入内容: {final_user_content}")
|
|
|
|
|
|
if not ch_history: ch_history = []
|
|
ch_history.append({"role": "user", "content": final_user_content})
|
|
yield ch_history, []
|
|
|
|
|
|
|
|
formatted_hist_for_respond = []
|
|
temp_user_msg_for_hist = None
|
|
|
|
for item_hist in ch_history[:-1]:
|
|
if item_hist["role"] == "user":
|
|
temp_user_msg_for_hist = item_hist["content"]
|
|
elif item_hist["role"] == "assistant" and temp_user_msg_for_hist is not None:
|
|
formatted_hist_for_respond.append((temp_user_msg_for_hist, item_hist["content"]))
|
|
temp_user_msg_for_hist = None
|
|
elif item_hist["role"] == "assistant" and temp_user_msg_for_hist is None:
|
|
formatted_hist_for_respond.append(("", item_hist["content"]))
|
|
|
|
|
|
ch_history.append({"role": "assistant", "content": ""})
|
|
|
|
full_bot_response = ""
|
|
|
|
for bot_response_token, _ in respond(final_user_content, formatted_hist_for_respond, sys_msg, max_t, temp, t_p, audio_f, image_f):
|
|
full_bot_response = bot_response_token
|
|
ch_history[-1]["content"] = full_bot_response
|
|
yield ch_history, []
|
|
|
|
|
|
new_todos_list = []
|
|
|
|
|
|
if user_msg_content.strip():
|
|
logger.info(f"处理文字输入生成ToDo: {user_msg_content.strip()}")
|
|
text_filtered_result = filter_message_with_llm(user_msg_content.strip())
|
|
text_todos = process_filtered_result_for_todo(text_filtered_result, user_msg_content.strip(), "文字")
|
|
new_todos_list.extend(text_todos)
|
|
|
|
|
|
if audio_f is not None and azure_speech_key and azure_speech_region:
|
|
try:
|
|
audio_sample_rate, audio_data = audio_f
|
|
audio_filename = os.path.join(SAVE_DIR, f"audio_{client_ip}.wav")
|
|
save_audio(audio_f, audio_filename)
|
|
audio_text = azure_speech_to_text(azure_speech_key, azure_speech_region, audio_filename)
|
|
if audio_text:
|
|
logger.info(f"处理音频输入生成ToDo: {audio_text}")
|
|
audio_filtered_result = filter_message_with_llm(audio_text)
|
|
audio_todos = process_filtered_result_for_todo(audio_filtered_result, audio_text, "音频")
|
|
new_todos_list.extend(audio_todos)
|
|
except Exception as e:
|
|
logger.error(f"音频处理错误: {str(e)}")
|
|
|
|
|
|
if image_f is not None:
|
|
try:
|
|
image_filename = os.path.join(SAVE_DIR, f"image_{client_ip}.jpg")
|
|
save_image(image_f, image_filename)
|
|
image_text = image_to_str(endpoint="https://ai-siyuwang5414995ai361208251338.cognitiveservices.azure.com/", key="45PYY2Av9CdMCveAjVG43MGKrnHzSxdiFTK9mWBgrOsMAHavxKj0JQQJ99BDACHYHv6XJ3w3AAAAACOGeVpQ", unused_param=None, file_path=image_filename)
|
|
if image_text:
|
|
logger.info(f"处理图片输入生成ToDo: {image_text}")
|
|
image_filtered_result = filter_message_with_llm(image_text)
|
|
image_todos = process_filtered_result_for_todo(image_filtered_result, image_text, "图片")
|
|
new_todos_list.extend(image_todos)
|
|
except Exception as e:
|
|
logger.error(f"图片处理错误: {str(e)}")
|
|
|
|
|
|
if not new_todos_list and final_user_content:
|
|
logger.info(f"使用整合内容生成ToDo: {final_user_content}")
|
|
filtered_result = filter_message_with_llm(final_user_content)
|
|
|
|
if isinstance(filtered_result, dict) and "error" in filtered_result:
|
|
logger.error(f"Filter 模块处理失败: {filtered_result['error']}")
|
|
|
|
new_todos_list = [["Error", filtered_result['error'], "Filter Failed"]]
|
|
elif isinstance(filtered_result, dict) and filtered_result.get("分类") == "其他":
|
|
logger.info(f"消息被 Filter 模块归类为 '其他',不生成 ToDo List。")
|
|
new_todos_list = [["Info", "消息被归类为 '其他',无需生成 ToDo。", "Filtered"]]
|
|
elif isinstance(filtered_result, list):
|
|
|
|
category = None
|
|
|
|
|
|
if not filtered_result:
|
|
logger.warning("Filter 模块返回了空列表,将继续生成 ToDo List。")
|
|
if final_user_content:
|
|
msg_id_todo = f"hf_app_todo_{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
|
|
new_todos_list = generate_todolist_from_text(final_user_content, msg_id_todo)
|
|
|
|
if new_todos_list and not (len(new_todos_list) == 1 and "Info" in str(new_todos_list[0])):
|
|
|
|
for i, todo in enumerate(new_todos_list):
|
|
todo[0] = len(all_todos_global) + i + 1
|
|
all_todos_global.extend(new_todos_list)
|
|
yield ch_history, all_todos_global
|
|
return
|
|
|
|
|
|
valid_item = None
|
|
for item in filtered_result:
|
|
if isinstance(item, dict):
|
|
valid_item = item
|
|
if "分类" in item:
|
|
category = item["分类"]
|
|
break
|
|
|
|
|
|
if valid_item is None:
|
|
logger.warning(f"Filter 模块返回的列表中没有有效的字典元素: {filtered_result}")
|
|
if final_user_content:
|
|
msg_id_todo = f"hf_app_todo_{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
|
|
new_todos_list = generate_todolist_from_text(final_user_content, msg_id_todo)
|
|
|
|
if new_todos_list and not (len(new_todos_list) == 1 and "Info" in str(new_todos_list[0])):
|
|
|
|
for i, todo in enumerate(new_todos_list):
|
|
todo[0] = len(all_todos_global) + i + 1
|
|
all_todos_global.extend(new_todos_list)
|
|
yield ch_history, all_todos_global
|
|
return
|
|
|
|
if category == "其他":
|
|
logger.info(f"消息被 Filter 模块归类为 '其他',不生成 ToDo List。")
|
|
new_todos_list = [["Info", "消息被归类为 '其他',无需生成 ToDo。", "Filtered"]]
|
|
else:
|
|
logger.info(f"消息被 Filter 模块归类为 '{category if category else '未知'}',继续生成 ToDo List。")
|
|
|
|
if final_user_content:
|
|
msg_id_todo = f"hf_app_todo_{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
|
|
new_todos_list = generate_todolist_from_text(final_user_content, msg_id_todo)
|
|
else:
|
|
|
|
logger.info(f"消息被 Filter 模块归类为 '{filtered_result.get('分类')}',继续生成 ToDo List。")
|
|
|
|
if final_user_content:
|
|
msg_id_todo = f"hf_app_todo_{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
|
|
new_todos_list = generate_todolist_from_text(final_user_content, msg_id_todo)
|
|
|
|
|
|
if new_todos_list and not (len(new_todos_list) == 1 and ("Info" in str(new_todos_list[0]) or "Error" in str(new_todos_list[0]))):
|
|
|
|
for i, todo in enumerate(new_todos_list):
|
|
todo[0] = len(all_todos_global) + i + 1
|
|
all_todos_global.extend(new_todos_list)
|
|
|
|
yield ch_history, all_todos_global
|
|
|
|
submit_btn.click(
|
|
handle_submit,
|
|
[msg, chatbot, system_msg, max_tokens_slider, temperature_slider, top_p_slider, audio_input, image_input],
|
|
[chatbot, todolist_df]
|
|
)
|
|
msg.submit(
|
|
handle_submit,
|
|
[msg, chatbot, system_msg, max_tokens_slider, temperature_slider, top_p_slider, audio_input, image_input],
|
|
[chatbot, todolist_df]
|
|
)
|
|
|
|
def clear_all():
|
|
global all_todos_global
|
|
all_todos_global = []
|
|
return None, None, ""
|
|
clear_btn.click(clear_all, None, [chatbot, todolist_df, msg], queue=False)
|
|
|
|
|
|
with gr.Tab("Audio/Image Processing (Original)"):
|
|
gr.Markdown("## 处理音频和图片")
|
|
audio_processor = gr.Audio(label="上传音频", type="numpy")
|
|
image_processor = gr.Image(label="上传图片", type="numpy")
|
|
process_btn = gr.Button("处理", variant="primary")
|
|
audio_output = gr.Textbox(label="音频信息")
|
|
image_output = gr.Textbox(label="图片信息")
|
|
|
|
process_btn.click(
|
|
process,
|
|
inputs=[audio_processor, image_processor],
|
|
outputs=[audio_output, image_output]
|
|
)
|
|
|
|
if __name__ == "__main__":
|
|
app.launch(debug=True)
|
|
|