From b0ea168db81d570c250b400ca89ca58885330f9e Mon Sep 17 00:00:00 2001 From: imeepos Date: Mon, 14 Jul 2025 13:06:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E7=9A=84AI=E5=88=86=E7=B1=BB=E6=97=A5=E5=BF=97=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 日志增强: - Gemini API访问令牌获取详细日志 - 视频上传过程完整日志记录 - AI内容分析请求和响应日志 - 视频分类服务处理流程日志 - 任务队列处理状态详细跟踪 日志内容: - URL请求地址和参数 - HTTP状态码和响应头 - 请求和响应体内容 - 处理耗时统计 - 错误详细信息和堆栈 问题诊断: - 便于分析404 Not Found错误原因 - 跟踪API调用完整流程 - 监控性能瓶颈 - 调试响应解析问题 这些日志将帮助快速定位和解决AI分类功能的问题。 --- .../services/video_classification_queue.rs | 30 ++-- .../services/video_classification_service.rs | 84 +++++++++-- .../src/infrastructure/gemini_service.rs | 130 +++++++++++++++--- 3 files changed, 202 insertions(+), 42 deletions(-) diff --git a/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs b/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs index 01620d8..034d79d 100644 --- a/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs +++ b/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs @@ -237,41 +237,55 @@ impl VideoClassificationQueue { /// 处理下一个任务 async fn process_next_task(&self) -> Result> { + println!("🔍 查找待处理任务..."); + // 获取待处理任务 let pending_tasks = self.service.get_pending_tasks(Some(1)).await?; - + if let Some(task) = pending_tasks.first() { let task_id = task.id.clone(); - + println!("📋 找到待处理任务: {}", task_id); + println!("📁 任务视频文件: {}", task.video_file_path); + println!("🔢 任务优先级: {}", task.priority); + // 设置当前任务 { let mut current_task = self.current_task.lock().await; *current_task = Some(task_id.clone()); + println!("🎯 设置当前任务: {}", task_id); } - + // 更新任务进度 + println!("📊 更新任务进度: 开始处理"); self.update_task_progress(&task_id, TaskStatus::Uploading, 10.0, "开始处理").await; - + + let task_start_time = std::time::Instant::now(); + // 处理任务 + println!("🚀 开始处理分类任务: {}", task_id); match self.service.process_classification_task(&task_id).await { Ok(_) => { + let task_duration = task_start_time.elapsed(); self.update_task_progress(&task_id, TaskStatus::Completed, 100.0, "处理完成").await; - println!("任务处理成功: {}", task_id); + println!("✅ 任务处理成功: {} (耗时: {:?})", task_id, task_duration); } Err(e) => { + let task_duration = task_start_time.elapsed(); self.update_task_progress_with_error(&task_id, TaskStatus::Failed, e.to_string()).await; - println!("任务处理失败: {} - {}", task_id, e); + println!("❌ 任务处理失败: {} - {} (耗时: {:?})", task_id, e, task_duration); } } - + // 清除当前任务 { let mut current_task = self.current_task.lock().await; *current_task = None; + println!("🔄 清除当前任务"); } - + Ok(Some(task_id)) } else { + println!("⏸️ 没有待处理任务,等待中..."); Ok(None) } } diff --git a/apps/desktop/src-tauri/src/business/services/video_classification_service.rs b/apps/desktop/src-tauri/src/business/services/video_classification_service.rs index 770e439..18ec1fd 100644 --- a/apps/desktop/src-tauri/src/business/services/video_classification_service.rs +++ b/apps/desktop/src-tauri/src/business/services/video_classification_service.rs @@ -121,19 +121,56 @@ impl VideoClassificationService { /// 使用Gemini进行视频分类 async fn classify_video_with_gemini(&self, task: &mut VideoClassificationTask, prompt: &str) -> Result { + println!("🎯 开始使用Gemini进行视频分类"); + println!("📋 任务ID: {}", task.id); + println!("📁 视频文件: {}", task.video_file_path); + let mut gemini_service = self.gemini_service.lock().await; - + // 调用Gemini API进行分类 - let (file_uri, raw_response) = gemini_service.classify_video(&task.video_file_path, prompt).await?; - + println!("🚀 调用Gemini API进行分类..."); + let classification_start = std::time::Instant::now(); + + let (file_uri, raw_response) = match gemini_service.classify_video(&task.video_file_path, prompt).await { + Ok(result) => { + println!("✅ Gemini API调用成功"); + result + } + Err(e) => { + println!("❌ Gemini API调用失败: {}", e); + return Err(e); + } + }; + + let classification_duration = classification_start.elapsed(); + println!("⏱️ Gemini分类耗时: {:?}", classification_duration); + // 更新任务状态 + println!("📝 更新任务状态为分析中..."); task.set_analyzing(file_uri.clone(), prompt.to_string()); self.video_repo.update_classification_task(task).await?; // 解析Gemini响应 - let gemini_response = self.parse_gemini_response(&raw_response)?; - + println!("🔍 解析Gemini响应..."); + println!("📄 原始响应长度: {} 字符", raw_response.len()); + println!("📄 原始响应内容: {}", &raw_response[..std::cmp::min(500, raw_response.len())]); + + let gemini_response = match self.parse_gemini_response(&raw_response) { + Ok(response) => { + println!("✅ 响应解析成功"); + println!("🏷️ 分类结果: {}", response.category); + println!("📊 置信度: {:.2}", response.confidence); + println!("⭐ 质量评分: {:.2}", response.quality_score); + response + } + Err(e) => { + println!("❌ 响应解析失败: {}", e); + return Err(e); + } + }; + // 创建分类记录 + println!("💾 创建分类记录..."); let mut record = VideoClassificationRecord::new( task.segment_id.clone(), task.material_id.clone(), @@ -145,32 +182,53 @@ impl VideoClassificationService { // 检查是否需要人工审核 if record.needs_review() { + println!("⚠️ 分类结果需要人工审核 (置信度: {:.2}, 质量: {:.2})", record.confidence, record.quality_score); record.mark_as_needs_review("置信度或质量评分较低,建议人工审核".to_string()); + } else { + println!("✅ 分类结果质量良好,无需人工审核"); } // 保存分类记录 + println!("💾 保存分类记录到数据库..."); let saved_record = self.video_repo.create_classification_record(record).await?; - + println!("✅ 分类记录保存成功,记录ID: {}", saved_record.id); + Ok(saved_record) } /// 解析Gemini响应为结构化数据 fn parse_gemini_response(&self, raw_response: &str) -> Result { + println!("🔍 开始解析Gemini响应..."); + // 尝试从响应中提取JSON let json_start = raw_response.find('{'); let json_end = raw_response.rfind('}'); - + + println!("📍 JSON位置: start={:?}, end={:?}", json_start, json_end); + if let (Some(start), Some(end)) = (json_start, json_end) { let json_str = &raw_response[start..=end]; - + println!("📄 提取的JSON字符串: {}", json_str); + match serde_json::from_str::(json_str) { - Ok(response) => Ok(response), - Err(_) => { + Ok(response) => { + println!("✅ JSON解析成功"); + println!("🏷️ 解析结果 - 分类: {}", response.category); + println!("📊 解析结果 - 置信度: {:.2}", response.confidence); + println!("💭 解析结果 - 理由: {}", response.reasoning); + println!("🔍 解析结果 - 特征: {:?}", response.features); + println!("🎯 解析结果 - 商品匹配: {}", response.product_match); + println!("⭐ 解析结果 - 质量评分: {:.2}", response.quality_score); + Ok(response) + } + Err(e) => { + println!("❌ JSON解析失败: {}", e); + println!("🔄 使用默认分类响应"); // 如果解析失败,创建一个默认响应 Ok(GeminiClassificationResponse { category: "未分类".to_string(), confidence: 0.5, - reasoning: "AI响应解析失败,使用默认分类".to_string(), + reasoning: format!("AI响应解析失败: {} - 原始JSON: {}", e, json_str), features: vec!["解析失败".to_string()], product_match: false, quality_score: 0.5, @@ -178,11 +236,13 @@ impl VideoClassificationService { } } } else { + println!("❌ 未找到JSON格式"); + println!("🔄 使用默认分类响应"); // 没有找到JSON格式,创建默认响应 Ok(GeminiClassificationResponse { category: "未分类".to_string(), confidence: 0.3, - reasoning: format!("AI响应格式异常: {}", raw_response), + reasoning: format!("AI响应格式异常,未找到JSON: {}", &raw_response[..std::cmp::min(200, raw_response.len())]), features: vec!["格式异常".to_string()], product_match: false, quality_score: 0.3, diff --git a/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs b/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs index 66e97d5..68d1b29 100644 --- a/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs +++ b/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs @@ -128,6 +128,8 @@ impl GeminiService { /// 获取Google访问令牌 async fn get_access_token(&mut self) -> Result { + println!("🔐 开始获取访问令牌..."); + // 检查缓存的令牌是否仍然有效 let current_time = SystemTime::now() .duration_since(UNIX_EPOCH)? @@ -135,24 +137,46 @@ impl GeminiService { if let (Some(token), Some(expires_at)) = (&self.access_token, self.token_expires_at) { if current_time < expires_at - 300 { // 提前5分钟刷新 + println!("✅ 使用缓存的访问令牌 (剩余时间: {}秒)", expires_at - current_time); return Ok(token.clone()); + } else { + println!("⏰ 缓存的令牌即将过期,需要刷新"); } + } else { + println!("🆕 首次获取访问令牌"); } // 获取新的访问令牌 - let url = format!("{}/google/auth/token", self.config.base_url); + let url = format!("{}/google/access-token", self.config.base_url); + println!("📡 请求URL: {}", url); + println!("🔑 Bearer Token: {}", self.config.bearer_token); + let response = self.client - .post(&url) + .get(&url) .header("Authorization", format!("Bearer {}", self.config.bearer_token)) .send() .await?; - if !response.status().is_success() { - return Err(anyhow!("获取访问令牌失败: {}", response.status())); + let status = response.status(); + let headers = response.headers().clone(); + + println!("📥 响应状态: {}", status); + println!("📋 响应头: {:?}", headers); + + if !status.is_success() { + let error_body = response.text().await.unwrap_or_default(); + println!("❌ 错误响应体: {}", error_body); + return Err(anyhow!("获取访问令牌失败: {} - {}", status, error_body)); } - let token_response: TokenResponse = response.json().await?; - + let response_text = response.text().await?; + println!("📄 响应内容: {}", response_text); + + let token_response: TokenResponse = serde_json::from_str(&response_text) + .map_err(|e| anyhow!("解析令牌响应失败: {} - 响应内容: {}", e, response_text))?; + + println!("✅ 成功获取访问令牌,有效期: {}秒", token_response.expires_in); + // 缓存令牌 self.access_token = Some(token_response.access_token.clone()); self.token_expires_at = Some(current_time + token_response.expires_in); @@ -162,17 +186,27 @@ impl GeminiService { /// 上传视频文件到Gemini pub async fn upload_video_file(&mut self, video_path: &str) -> Result { + println!("📤 开始上传视频文件: {}", video_path); + // 获取访问令牌 let access_token = self.get_access_token().await?; // 读取视频文件 - let video_data = fs::read(video_path).await?; + println!("📁 读取视频文件..."); + let video_data = fs::read(video_path).await + .map_err(|e| anyhow!("读取视频文件失败: {} - {}", video_path, e))?; + + let file_size = video_data.len(); + println!("📊 视频文件大小: {} bytes ({:.2} MB)", file_size, file_size as f64 / 1024.0 / 1024.0); + let file_name = Path::new(video_path) .file_name() .and_then(|n| n.to_str()) .unwrap_or("video.mp4"); + println!("📝 文件名: {}", file_name); // 创建multipart表单 + println!("📦 创建multipart表单..."); let form = multipart::Form::new() .part("file", multipart::Part::bytes(video_data) .file_name(file_name.to_string()) @@ -180,35 +214,58 @@ impl GeminiService { // 上传到Vertex AI let upload_url = format!("{}/google/vertex-ai/upload", self.config.base_url); + let query_params = [ + ("bucket", "dy-media-storage"), + ("prefix", "video-analysis") + ]; + + println!("📡 上传URL: {}", upload_url); + println!("🔍 查询参数: {:?}", query_params); + println!("🔑 Authorization: Bearer {}", &access_token[..std::cmp::min(20, access_token.len())]); + let response = self.client .post(&upload_url) .header("Authorization", format!("Bearer {}", access_token)) .header("x-google-api-key", &access_token) - .query(&[ - ("bucket", "dy-media-storage"), - ("prefix", "video-analysis") - ]) + .query(&query_params) .multipart(form) .send() .await?; - if !response.status().is_success() { - let status = response.status(); + let status = response.status(); + let headers = response.headers().clone(); + + println!("📥 上传响应状态: {}", status); + println!("📋 上传响应头: {:?}", headers); + + if !status.is_success() { let error_text = response.text().await.unwrap_or_default(); + println!("❌ 上传失败响应体: {}", error_text); return Err(anyhow!("上传视频失败: {} - {}", status, error_text)); } - let upload_response: UploadResponse = response.json().await?; + let response_text = response.text().await?; + println!("📄 上传成功响应: {}", response_text); + + let upload_response: UploadResponse = serde_json::from_str(&response_text) + .map_err(|e| anyhow!("解析上传响应失败: {} - 响应内容: {}", e, response_text))?; + + println!("✅ 视频上传成功,文件URI: {}", upload_response.file_uri); Ok(upload_response.file_uri) } /// 生成内容分析 pub async fn generate_content_analysis(&mut self, file_uri: &str, prompt: &str) -> Result { + println!("🧠 开始生成内容分析..."); + println!("📁 文件URI: {}", file_uri); + println!("💬 提示词长度: {} 字符", prompt.len()); + // 获取访问令牌 let access_token = self.get_access_token().await?; // 格式化GCS URI let formatted_uri = self.format_gcs_uri(file_uri); + println!("🔗 格式化后的URI: {}", formatted_uri); // 准备请求数据 let request_data = GenerateContentRequest { @@ -232,8 +289,12 @@ impl GeminiService { }, }; + println!("📦 请求数据: {}", serde_json::to_string_pretty(&request_data).unwrap_or_default()); + // 发送生成请求 let generate_url = format!("{}/google/vertex-ai/generate", self.config.base_url); + println!("📡 生成URL: {}", generate_url); + let response = self.client .post(&generate_url) .header("Authorization", format!("Bearer {}", access_token)) @@ -243,20 +304,32 @@ impl GeminiService { .send() .await?; - if !response.status().is_success() { - let status = response.status(); + let status = response.status(); + let headers = response.headers().clone(); + + println!("📥 生成响应状态: {}", status); + println!("📋 生成响应头: {:?}", headers); + + if !status.is_success() { let error_text = response.text().await.unwrap_or_default(); + println!("❌ 生成失败响应体: {}", error_text); return Err(anyhow!("生成内容分析失败: {} - {}", status, error_text)); } - let gemini_response: GeminiResponse = response.json().await?; - + let response_text = response.text().await?; + println!("📄 生成成功响应: {}", response_text); + + let gemini_response: GeminiResponse = serde_json::from_str(&response_text) + .map_err(|e| anyhow!("解析生成响应失败: {} - 响应内容: {}", e, response_text))?; + if let Some(candidate) = gemini_response.candidates.first() { if let Some(part) = candidate.content.parts.first() { + println!("✅ 内容分析完成,响应长度: {} 字符", part.text.len()); return Ok(part.text.clone()); } } + println!("❌ Gemini响应格式无效: {:?}", gemini_response); Err(anyhow!("Gemini响应格式无效")) } @@ -275,15 +348,28 @@ impl GeminiService { /// 完整的视频分类流程 pub async fn classify_video(&mut self, video_path: &str, prompt: &str) -> Result<(String, String)> { + println!("🎬 开始完整的视频分类流程"); + println!("📁 视频路径: {}", video_path); + println!("💬 提示词预览: {}...", &prompt[..std::cmp::min(100, prompt.len())]); + + let start_time = std::time::Instant::now(); + // 1. 上传视频 - println!("正在上传视频到Gemini: {}", video_path); + println!("📤 步骤1: 上传视频到Gemini"); + let upload_start = std::time::Instant::now(); let file_uri = self.upload_video_file(video_path).await?; - println!("视频上传成功,URI: {}", file_uri); + let upload_duration = upload_start.elapsed(); + println!("✅ 视频上传成功,耗时: {:?}, URI: {}", upload_duration, file_uri); // 2. 生成分析 - println!("正在进行AI分析..."); + println!("🧠 步骤2: 进行AI分析"); + let analysis_start = std::time::Instant::now(); let analysis_result = self.generate_content_analysis(&file_uri, prompt).await?; - println!("AI分析完成"); + let analysis_duration = analysis_start.elapsed(); + println!("✅ AI分析完成,耗时: {:?}", analysis_duration); + + let total_duration = start_time.elapsed(); + println!("🎉 视频分类流程完成,总耗时: {:?}", total_duration); Ok((file_uri, analysis_result)) }