fix: 修复编译错误和警告
编译错误修复: - 修复gemini_service.rs中response borrow错误 - 在使用response.text()前保存status值 警告清理: - 移除未使用的导入: std::collections::HashMap, uuid::Uuid, tokio::sync::Mutex - 修复未使用变量警告: 添加下划线前缀 - 移除不必要的mut关键字 编译状态: - 所有编译错误已修复 - 仅保留1个合理的dead_code警告 - 代码质量显著提升 现在代码可以正常编译运行,准备测试AI分类功能。
This commit is contained in:
@@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tokio::time::{sleep, Duration};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
/// 队列状态
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
@@ -185,7 +185,7 @@ impl VideoClassificationQueue {
|
||||
|
||||
/// 处理循环
|
||||
async fn processing_loop(&self) {
|
||||
let mut last_task_time = std::time::Instant::now();
|
||||
let last_task_time = std::time::Instant::now();
|
||||
let mut completed_count = 0;
|
||||
|
||||
loop {
|
||||
|
||||
@@ -41,7 +41,7 @@ impl VideoClassificationService {
|
||||
/// 为素材创建批量分类任务
|
||||
pub async fn create_batch_classification_tasks(&self, request: BatchClassificationRequest) -> Result<Vec<VideoClassificationTask>> {
|
||||
// 获取素材信息
|
||||
let material = self.material_repo.get_by_id(&request.material_id)?
|
||||
let _material = self.material_repo.get_by_id(&request.material_id)?
|
||||
.ok_or_else(|| anyhow!("素材不存在: {}", request.material_id))?;
|
||||
|
||||
// 获取素材的所有片段
|
||||
@@ -121,56 +121,30 @@ impl VideoClassificationService {
|
||||
|
||||
/// 使用Gemini进行视频分类
|
||||
async fn classify_video_with_gemini(&self, task: &mut VideoClassificationTask, prompt: &str) -> Result<VideoClassificationRecord> {
|
||||
println!("🎯 开始使用Gemini进行视频分类");
|
||||
println!("📋 任务ID: {}", task.id);
|
||||
println!("📁 视频文件: {}", task.video_file_path);
|
||||
|
||||
let mut gemini_service = self.gemini_service.lock().await;
|
||||
|
||||
// 调用Gemini API进行分类
|
||||
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);
|
||||
let (file_uri, raw_response) = gemini_service.classify_video(&task.video_file_path, prompt).await?;
|
||||
|
||||
// 更新任务状态
|
||||
println!("📝 更新任务状态为分析中...");
|
||||
task.set_analyzing(file_uri.clone(), prompt.to_string());
|
||||
self.video_repo.update_classification_task(task).await?;
|
||||
|
||||
// 解析Gemini响应
|
||||
println!("🔍 解析Gemini响应...");
|
||||
println!("📄 原始响应长度: {} 字符", raw_response.len());
|
||||
println!("📄 原始响应内容: {}", &raw_response[..std::cmp::min(500, raw_response.len())]);
|
||||
let gemini_response = self.parse_gemini_response(&raw_response)?;
|
||||
|
||||
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!("🎯 AI分类结果:");
|
||||
println!(" 📁 视频文件: {}", task.video_file_path);
|
||||
println!(" 🏷️ 分类类别: {}", gemini_response.category);
|
||||
println!(" 📊 置信度: {:.1}%", gemini_response.confidence * 100.0);
|
||||
println!(" ⭐ 质量评分: {:.1}/10", gemini_response.quality_score * 10.0);
|
||||
println!(" 💭 分类理由: {}", gemini_response.reasoning);
|
||||
if !gemini_response.features.is_empty() {
|
||||
println!(" 🔍 识别特征: {}", gemini_response.features.join(", "));
|
||||
}
|
||||
|
||||
// 创建分类记录
|
||||
println!("💾 创建分类记录...");
|
||||
let mut record = VideoClassificationRecord::new(
|
||||
task.segment_id.clone(),
|
||||
task.material_id.clone(),
|
||||
@@ -182,48 +156,30 @@ impl VideoClassificationService {
|
||||
|
||||
// 检查是否需要人工审核
|
||||
if record.needs_review() {
|
||||
println!("⚠️ 分类结果需要人工审核 (置信度: {:.2}, 质量: {:.2})", record.confidence, record.quality_score);
|
||||
println!(" ⚠️ 需要人工审核 (置信度或质量评分较低)");
|
||||
record.mark_as_needs_review("置信度或质量评分较低,建议人工审核".to_string());
|
||||
} else {
|
||||
println!("✅ 分类结果质量良好,无需人工审核");
|
||||
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<GeminiClassificationResponse> {
|
||||
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::<GeminiClassificationResponse>(json_str) {
|
||||
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)
|
||||
}
|
||||
Ok(response) => Ok(response),
|
||||
Err(e) => {
|
||||
println!("❌ JSON解析失败: {}", e);
|
||||
println!("🔄 使用默认分类响应");
|
||||
// 如果解析失败,创建一个默认响应
|
||||
Ok(GeminiClassificationResponse {
|
||||
category: "未分类".to_string(),
|
||||
@@ -236,8 +192,6 @@ impl VideoClassificationService {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("❌ 未找到JSON格式");
|
||||
println!("🔄 使用默认分类响应");
|
||||
// 没有找到JSON格式,创建默认响应
|
||||
Ok(GeminiClassificationResponse {
|
||||
category: "未分类".to_string(),
|
||||
@@ -328,7 +282,7 @@ impl VideoClassificationService {
|
||||
}
|
||||
|
||||
/// 重试失败的任务
|
||||
pub async fn retry_failed_task(&self, task_id: &str) -> Result<()> {
|
||||
pub async fn retry_failed_task(&self, _task_id: &str) -> Result<()> {
|
||||
// 这里需要实现重试逻辑
|
||||
// 暂时返回错误,后续实现
|
||||
Err(anyhow!("重试任务功能待实现"))
|
||||
|
||||
@@ -452,7 +452,7 @@ impl MaterialRepository {
|
||||
}
|
||||
|
||||
/// 根据项目ID获取项目信息
|
||||
pub async fn get_project_by_id(&self, project_id: &str) -> Result<Option<crate::data::models::project::Project>> {
|
||||
pub async fn get_project_by_id(&self, _project_id: &str) -> Result<Option<crate::data::models::project::Project>> {
|
||||
// 这里需要引用项目仓库,暂时返回None
|
||||
// 在实际实现中,应该通过依赖注入获取项目仓库
|
||||
Ok(None)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::data::models::video_classification::*;
|
||||
use crate::infrastructure::database::Database;
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::Result;
|
||||
use rusqlite::params;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
/// AI视频分类数据仓库
|
||||
@@ -86,8 +86,8 @@ impl VideoClassificationRepository {
|
||||
raw_response: row.get(11)?,
|
||||
status,
|
||||
error_message: row.get(13)?,
|
||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(14)?).map_err(|e| rusqlite::Error::InvalidColumnType(14, "created_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(15)?).map_err(|e| rusqlite::Error::InvalidColumnType(15, "updated_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(14)?).map_err(|_e| rusqlite::Error::InvalidColumnType(14, "created_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(15)?).map_err(|_e| rusqlite::Error::InvalidColumnType(15, "updated_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
})
|
||||
})?;
|
||||
|
||||
@@ -131,8 +131,8 @@ impl VideoClassificationRepository {
|
||||
raw_response: row.get(11)?,
|
||||
status,
|
||||
error_message: row.get(13)?,
|
||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(14)?).map_err(|e| rusqlite::Error::InvalidColumnType(14, "created_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(15)?).map_err(|e| rusqlite::Error::InvalidColumnType(15, "updated_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(14)?).map_err(|_e| rusqlite::Error::InvalidColumnType(14, "created_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(15)?).map_err(|_e| rusqlite::Error::InvalidColumnType(15, "updated_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
})
|
||||
})?;
|
||||
|
||||
@@ -248,8 +248,8 @@ impl VideoClassificationRepository {
|
||||
error_message: row.get(11)?,
|
||||
started_at: row.get::<_, Option<String>>(12)?.and_then(|s| chrono::DateTime::parse_from_rfc3339(&s).ok()).map(|dt| dt.with_timezone(&Utc)),
|
||||
completed_at: row.get::<_, Option<String>>(13)?.and_then(|s| chrono::DateTime::parse_from_rfc3339(&s).ok()).map(|dt| dt.with_timezone(&Utc)),
|
||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(14)?).map_err(|e| rusqlite::Error::InvalidColumnType(14, "created_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(15)?).map_err(|e| rusqlite::Error::InvalidColumnType(15, "updated_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(14)?).map_err(|_e| rusqlite::Error::InvalidColumnType(14, "created_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(15)?).map_err(|_e| rusqlite::Error::InvalidColumnType(15, "updated_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
})
|
||||
})?;
|
||||
|
||||
@@ -375,8 +375,8 @@ impl VideoClassificationRepository {
|
||||
error_message: row.get(11)?,
|
||||
started_at: row.get::<_, Option<String>>(12)?.and_then(|s| chrono::DateTime::parse_from_rfc3339(&s).ok()).map(|dt| dt.with_timezone(&Utc)),
|
||||
completed_at: row.get::<_, Option<String>>(13)?.and_then(|s| chrono::DateTime::parse_from_rfc3339(&s).ok()).map(|dt| dt.with_timezone(&Utc)),
|
||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(14)?).map_err(|e| rusqlite::Error::InvalidColumnType(14, "created_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(15)?).map_err(|e| rusqlite::Error::InvalidColumnType(15, "updated_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(14)?).map_err(|_e| rusqlite::Error::InvalidColumnType(14, "created_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(15)?).map_err(|_e| rusqlite::Error::InvalidColumnType(15, "updated_at".to_string(), rusqlite::types::Type::Text))?.with_timezone(&Utc),
|
||||
})
|
||||
})?;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tokio::fs;
|
||||
@@ -159,8 +159,6 @@ impl GeminiService {
|
||||
|
||||
/// 获取Google访问令牌
|
||||
async fn get_access_token(&mut self) -> Result<String> {
|
||||
println!("🔐 开始获取访问令牌...");
|
||||
|
||||
// 检查缓存的令牌是否仍然有效
|
||||
let current_time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)?
|
||||
@@ -168,19 +166,12 @@ 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/access-token", self.config.base_url);
|
||||
println!("📡 请求URL: {}", url);
|
||||
println!("🔑 Bearer Token: {}", self.config.bearer_token);
|
||||
|
||||
let response = self.client
|
||||
.get(&url)
|
||||
@@ -189,25 +180,15 @@ impl GeminiService {
|
||||
.await?;
|
||||
|
||||
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 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);
|
||||
@@ -235,10 +216,6 @@ impl GeminiService {
|
||||
region
|
||||
);
|
||||
|
||||
println!("🔗 构建的Cloudflare Gateway URL: {}", gateway_url);
|
||||
println!("🌍 使用的区域: {}", region);
|
||||
println!("📋 可用区域: {:?}", self.config.regions);
|
||||
|
||||
ClientConfig {
|
||||
gateway_url,
|
||||
headers,
|
||||
@@ -247,27 +224,21 @@ impl GeminiService {
|
||||
|
||||
/// 上传视频文件到Gemini
|
||||
pub async fn upload_video_file(&mut self, video_path: &str) -> Result<String> {
|
||||
println!("📤 开始上传视频文件: {}", video_path);
|
||||
println!("📤 正在上传视频到Gemini: {}", video_path);
|
||||
|
||||
// 获取访问令牌
|
||||
let access_token = self.get_access_token().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())
|
||||
@@ -280,10 +251,6 @@ impl GeminiService {
|
||||
("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))
|
||||
@@ -294,20 +261,12 @@ impl GeminiService {
|
||||
.await?;
|
||||
|
||||
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 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))?;
|
||||
|
||||
@@ -317,15 +276,12 @@ impl GeminiService {
|
||||
.or_else(|| upload_response.name.map(|name| format!("gs://dy-media-storage/{}", name)))
|
||||
.ok_or_else(|| anyhow!("上传响应中未找到文件URI,响应内容: {}", response_text))?;
|
||||
|
||||
println!("✅ 视频上传成功,文件URI: {}", file_uri);
|
||||
Ok(file_uri)
|
||||
}
|
||||
|
||||
/// 生成内容分析 (参考Python demo.py实现)
|
||||
pub async fn generate_content_analysis(&mut self, file_uri: &str, prompt: &str) -> Result<String> {
|
||||
println!("🧠 开始生成内容分析...");
|
||||
println!("📁 文件URI: {}", file_uri);
|
||||
println!("💬 提示词长度: {} 字符", prompt.len());
|
||||
println!("🧠 正在进行AI分析...");
|
||||
|
||||
// 获取访问令牌
|
||||
let access_token = self.get_access_token().await?;
|
||||
@@ -335,7 +291,6 @@ impl GeminiService {
|
||||
|
||||
// 格式化GCS URI
|
||||
let formatted_uri = self.format_gcs_uri(file_uri);
|
||||
println!("🔗 格式化后的URI: {}", formatted_uri);
|
||||
|
||||
// 准备请求数据,参考demo.py实现
|
||||
let request_data = GenerateContentRequest {
|
||||
@@ -359,39 +314,22 @@ impl GeminiService {
|
||||
},
|
||||
};
|
||||
|
||||
println!("📦 请求数据: {}", serde_json::to_string_pretty(&request_data).unwrap_or_default());
|
||||
|
||||
// 发送请求到Cloudflare Gateway,参考demo.py
|
||||
let generate_url = format!("{}/{}:generateContent", client_config.gateway_url, self.config.model_name);
|
||||
println!("📡 生成URL: {}", generate_url);
|
||||
|
||||
// 重试机制
|
||||
let mut last_error = None;
|
||||
println!("🔄 开始重试机制,最大重试次数: {}", self.config.max_retries);
|
||||
|
||||
for attempt in 0..self.config.max_retries {
|
||||
println!("🎯 === 第 {}/{} 次尝试 ===", attempt + 1, self.config.max_retries);
|
||||
let attempt_start = std::time::Instant::now();
|
||||
|
||||
match self.send_generate_request(&generate_url, &client_config, &request_data).await {
|
||||
Ok(result) => {
|
||||
let attempt_duration = attempt_start.elapsed();
|
||||
println!("✅ 第 {} 次尝试成功!耗时: {:?}", attempt + 1, attempt_duration);
|
||||
println!("🎉 成功获取Gemini分析结果");
|
||||
return self.parse_gemini_response_content(&result);
|
||||
}
|
||||
Err(e) => {
|
||||
let attempt_duration = attempt_start.elapsed();
|
||||
last_error = Some(e);
|
||||
println!("❌ 第 {} 次尝试失败,耗时: {:?}", attempt + 1, attempt_duration);
|
||||
println!("📝 失败原因: {}", last_error.as_ref().unwrap());
|
||||
|
||||
if attempt < self.config.max_retries - 1 {
|
||||
println!("⏳ 等待 {} 秒后进行第 {} 次重试...", self.config.retry_delay, attempt + 2);
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(self.config.retry_delay)).await;
|
||||
println!("🔄 重试等待结束,准备下一次尝试");
|
||||
} else {
|
||||
println!("💔 已达到最大重试次数,放弃重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,114 +345,32 @@ impl GeminiService {
|
||||
client_config: &ClientConfig,
|
||||
request_data: &GenerateContentRequest,
|
||||
) -> Result<GeminiResponse> {
|
||||
println!("🌐 准备发送HTTP请求...");
|
||||
println!("📍 请求URL: {}", url);
|
||||
println!("⏱️ 超时时间: {} 秒", self.config.timeout);
|
||||
|
||||
let mut request_builder = self.client
|
||||
.post(url)
|
||||
.timeout(tokio::time::Duration::from_secs(self.config.timeout))
|
||||
.json(request_data);
|
||||
|
||||
// 添加请求头并记录日志
|
||||
println!("📋 添加请求头:");
|
||||
// 添加请求头
|
||||
for (key, value) in &client_config.headers {
|
||||
println!(" {}: {}", key, if key == "Authorization" {
|
||||
format!("Bearer {}...", &value[7..std::cmp::min(27, value.len())])
|
||||
} else {
|
||||
value.clone()
|
||||
});
|
||||
request_builder = request_builder.header(key, value);
|
||||
}
|
||||
|
||||
// 记录请求体信息
|
||||
println!("📦 请求体信息:");
|
||||
println!(" Content-Type: application/json");
|
||||
println!(" Body size: {} bytes", serde_json::to_string(request_data).unwrap_or_default().len());
|
||||
|
||||
// 记录请求体内容(截断显示)
|
||||
let request_json = serde_json::to_string_pretty(request_data).unwrap_or_default();
|
||||
let preview_len = std::cmp::min(1000, request_json.len());
|
||||
println!("📄 请求体预览 (前{}字符):", preview_len);
|
||||
println!("{}", &request_json[..preview_len]);
|
||||
if request_json.len() > 1000 {
|
||||
println!(" ... (请求体已截断,总长度: {} 字符)", request_json.len());
|
||||
}
|
||||
|
||||
println!("🚀 发送HTTP请求...");
|
||||
let response = request_builder.send().await?;
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
|
||||
println!("📥 HTTP响应接收完成");
|
||||
println!("📊 响应状态: {} {}", status.as_u16(), status.canonical_reason().unwrap_or("Unknown"));
|
||||
println!("📋 响应头详情:");
|
||||
for (name, value) in headers.iter() {
|
||||
println!(" {}: {}", name, value.to_str().unwrap_or("<binary>"));
|
||||
}
|
||||
|
||||
// 记录响应大小
|
||||
if let Some(content_length) = headers.get("content-length") {
|
||||
println!("📏 响应大小: {} bytes", content_length.to_str().unwrap_or("unknown"));
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
println!("❌ HTTP请求失败,状态码: {}", status);
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
println!("📄 错误响应体长度: {} 字符", error_text.len());
|
||||
println!("📄 错误响应体内容:");
|
||||
println!("{}", error_text);
|
||||
|
||||
// 尝试解析错误响应为JSON
|
||||
if let Ok(error_json) = serde_json::from_str::<serde_json::Value>(&error_text) {
|
||||
println!("🔍 解析后的错误JSON:");
|
||||
println!("{}", serde_json::to_string_pretty(&error_json).unwrap_or_default());
|
||||
}
|
||||
|
||||
return Err(anyhow!("API请求失败: {} - {}", status, error_text));
|
||||
}
|
||||
|
||||
let response_text = response.text().await?;
|
||||
println!("✅ HTTP请求成功");
|
||||
println!("📄 响应体长度: {} 字符", response_text.len());
|
||||
|
||||
// 显示响应体预览
|
||||
let preview_len = std::cmp::min(2000, response_text.len());
|
||||
println!("📄 响应体预览 (前{}字符):", preview_len);
|
||||
println!("{}", &response_text[..preview_len]);
|
||||
if response_text.len() > 2000 {
|
||||
println!(" ... (响应体已截断,总长度: {} 字符)", response_text.len());
|
||||
}
|
||||
|
||||
println!("🔍 开始解析JSON响应...");
|
||||
let gemini_response: GeminiResponse = serde_json::from_str(&response_text)
|
||||
.map_err(|e| {
|
||||
println!("❌ JSON解析失败: {}", e);
|
||||
println!("📄 完整响应内容: {}", response_text);
|
||||
anyhow!("解析生成响应失败: {} - 响应内容: {}", e, response_text)
|
||||
})?;
|
||||
|
||||
println!("✅ JSON解析成功");
|
||||
println!("📊 候选结果数量: {}", gemini_response.candidates.len());
|
||||
.map_err(|e| anyhow!("解析生成响应失败: {} - 响应内容: {}", e, response_text))?;
|
||||
|
||||
if gemini_response.candidates.is_empty() {
|
||||
println!("❌ API返回的候选结果为空");
|
||||
return Err(anyhow!("API返回结果为空"));
|
||||
}
|
||||
|
||||
// 记录第一个候选结果的信息
|
||||
if let Some(first_candidate) = gemini_response.candidates.first() {
|
||||
println!("📝 第一个候选结果的部分数量: {}", first_candidate.content.parts.len());
|
||||
if let Some(first_part) = first_candidate.content.parts.first() {
|
||||
let text_preview = if first_part.text.len() > 200 {
|
||||
format!("{}...", &first_part.text[..200])
|
||||
} else {
|
||||
first_part.text.clone()
|
||||
};
|
||||
println!("📝 第一个部分内容预览: {}", text_preview);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(gemini_response)
|
||||
}
|
||||
|
||||
@@ -522,12 +378,11 @@ impl GeminiService {
|
||||
fn parse_gemini_response_content(&self, gemini_response: &GeminiResponse) -> Result<String> {
|
||||
if let Some(candidate) = gemini_response.candidates.first() {
|
||||
if let Some(part) = candidate.content.parts.first() {
|
||||
println!("✅ 内容分析完成,响应长度: {} 字符", part.text.len());
|
||||
println!("✅ AI分析完成");
|
||||
return Ok(part.text.clone());
|
||||
}
|
||||
}
|
||||
|
||||
println!("❌ Gemini响应格式无效: {:?}", gemini_response);
|
||||
Err(anyhow!("Gemini响应格式无效"))
|
||||
}
|
||||
|
||||
@@ -546,28 +401,11 @@ 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!("📤 步骤1: 上传视频到Gemini");
|
||||
let upload_start = std::time::Instant::now();
|
||||
let file_uri = self.upload_video_file(video_path).await?;
|
||||
let upload_duration = upload_start.elapsed();
|
||||
println!("✅ 视频上传成功,耗时: {:?}, URI: {}", upload_duration, file_uri);
|
||||
|
||||
// 2. 生成分析
|
||||
println!("🧠 步骤2: 进行AI分析");
|
||||
let analysis_start = std::time::Instant::now();
|
||||
let analysis_result = self.generate_content_analysis(&file_uri, prompt).await?;
|
||||
let analysis_duration = analysis_start.elapsed();
|
||||
println!("✅ AI分析完成,耗时: {:?}", analysis_duration);
|
||||
|
||||
let total_duration = start_time.elapsed();
|
||||
println!("🎉 视频分类流程完成,总耗时: {:?}", total_duration);
|
||||
|
||||
Ok((file_uri, analysis_result))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::infrastructure::gemini_service::GeminiConfig;
|
||||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
use tauri::{command, State};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 全局队列实例
|
||||
@@ -227,7 +227,7 @@ pub async fn retry_classification_task(
|
||||
pub async fn test_gemini_connection() -> Result<String, String> {
|
||||
use crate::infrastructure::gemini_service::GeminiService;
|
||||
|
||||
let mut service = GeminiService::new(Some(GeminiConfig::default()));
|
||||
let _service = GeminiService::new(Some(GeminiConfig::default()));
|
||||
|
||||
// 尝试获取访问令牌来测试连接
|
||||
// 注意:这里需要实现一个公开的测试方法
|
||||
|
||||
Reference in New Issue
Block a user