feat: 实现AI视频分类功能

新功能:
- 集成Google Gemini API进行视频智能分类
- 实现任务队列系统支持批量处理
- 添加实时进度显示和状态管理
- 自动文件整理到分类文件夹

 架构改进:
- 遵循Tauri开发规范的分层架构设计
- 完整的数据模型和仓库层实现
- 异步任务处理和错误处理机制
- 类型安全的前后端通信接口

 用户界面:
- MaterialCard组件添加AI分类按钮
- VideoClassificationProgress进度显示组件
- 优美的动画效果和响应式设计
- 符合前端开发规范的UI/UX优化

 数据库扩展:
- 新增video_classification_records表
- 新增video_classification_tasks表
- 完整的索引优化和外键约束

 技术实现:
- Rust后端服务层完整实现
- React/TypeScript前端状态管理
- Zustand状态存储和API封装
- 完善的错误处理和用户提示

 文档:
- 完整的功能文档和API说明
- 架构设计和使用流程说明
- 开发规范遵循情况说明

Closes #AI视频分类功能开发
This commit is contained in:
imeepos
2025-07-14 12:52:30 +08:00
parent c9882aad18
commit b8dfaf8af8
22 changed files with 3485 additions and 45 deletions

View File

@@ -0,0 +1,307 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
/// AI视频分类记录数据模型
/// 遵循 Tauri 开发规范的数据模型设计原则
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoClassificationRecord {
/// 分类记录唯一标识符
pub id: String,
/// 素材片段ID
pub segment_id: String,
/// 素材ID
pub material_id: String,
/// 项目ID
pub project_id: String,
/// 分类结果
pub category: String,
/// 置信度 (0.0 - 1.0)
pub confidence: f64,
/// 分类理由
pub reasoning: String,
/// 关键特征
pub features: Vec<String>,
/// 商品匹配度
pub product_match: bool,
/// 质量评分 (0.0 - 1.0)
pub quality_score: f64,
/// Gemini文件URI
pub gemini_file_uri: Option<String>,
/// 原始AI响应
pub raw_response: Option<String>,
/// 分类状态
pub status: ClassificationStatus,
/// 错误信息
pub error_message: Option<String>,
/// 创建时间
pub created_at: DateTime<Utc>,
/// 更新时间
pub updated_at: DateTime<Utc>,
}
/// AI视频分类任务数据模型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoClassificationTask {
/// 任务唯一标识符
pub id: String,
/// 素材片段ID
pub segment_id: String,
/// 素材ID
pub material_id: String,
/// 项目ID
pub project_id: String,
/// 视频文件路径
pub video_file_path: String,
/// 任务状态
pub status: TaskStatus,
/// 任务优先级
pub priority: i32,
/// 重试次数
pub retry_count: i32,
/// 最大重试次数
pub max_retries: i32,
/// Gemini文件URI上传后获得
pub gemini_file_uri: Option<String>,
/// 使用的提示词
pub prompt_text: Option<String>,
/// 错误信息
pub error_message: Option<String>,
/// 开始处理时间
pub started_at: Option<DateTime<Utc>>,
/// 完成时间
pub completed_at: Option<DateTime<Utc>>,
/// 创建时间
pub created_at: DateTime<Utc>,
/// 更新时间
pub updated_at: DateTime<Utc>,
}
/// 分类状态枚举
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ClassificationStatus {
/// 已分类
Classified,
/// 分类失败
Failed,
/// 需要人工审核
NeedsReview,
}
/// 任务状态枚举
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TaskStatus {
/// 等待处理
Pending,
/// 上传中
Uploading,
/// 分析中
Analyzing,
/// 已完成
Completed,
/// 失败
Failed,
/// 已取消
Cancelled,
}
/// 批量分类请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchClassificationRequest {
/// 素材ID
pub material_id: String,
/// 项目ID
pub project_id: String,
/// 是否覆盖已有分类
pub overwrite_existing: bool,
/// 任务优先级
pub priority: Option<i32>,
}
/// 分类任务查询参数
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ClassificationTaskQuery {
/// 项目ID过滤
pub project_id: Option<String>,
/// 素材ID过滤
pub material_id: Option<String>,
/// 状态过滤
pub status: Option<TaskStatus>,
/// 分页大小
pub limit: Option<i32>,
/// 分页偏移
pub offset: Option<i32>,
}
/// 分类统计信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClassificationStats {
/// 总任务数
pub total_tasks: i32,
/// 等待中任务数
pub pending_tasks: i32,
/// 处理中任务数
pub processing_tasks: i32,
/// 已完成任务数
pub completed_tasks: i32,
/// 失败任务数
pub failed_tasks: i32,
/// 总分类记录数
pub total_classifications: i32,
/// 平均置信度
pub average_confidence: f64,
/// 平均质量评分
pub average_quality_score: f64,
}
/// Gemini API响应结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeminiClassificationResponse {
/// 分类结果
pub category: String,
/// 置信度
pub confidence: f64,
/// 分类理由
pub reasoning: String,
/// 关键特征
pub features: Vec<String>,
/// 商品匹配度
pub product_match: bool,
/// 质量评分
pub quality_score: f64,
}
impl VideoClassificationRecord {
/// 创建新的分类记录
pub fn new(
segment_id: String,
material_id: String,
project_id: String,
gemini_response: GeminiClassificationResponse,
gemini_file_uri: Option<String>,
raw_response: Option<String>,
) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
segment_id,
material_id,
project_id,
category: gemini_response.category,
confidence: gemini_response.confidence,
reasoning: gemini_response.reasoning,
features: gemini_response.features,
product_match: gemini_response.product_match,
quality_score: gemini_response.quality_score,
gemini_file_uri,
raw_response,
status: ClassificationStatus::Classified,
error_message: None,
created_at: Utc::now(),
updated_at: Utc::now(),
}
}
/// 标记为失败
pub fn mark_as_failed(&mut self, error_message: String) {
self.status = ClassificationStatus::Failed;
self.error_message = Some(error_message);
self.updated_at = Utc::now();
}
/// 标记为需要审核
pub fn mark_as_needs_review(&mut self, reason: String) {
self.status = ClassificationStatus::NeedsReview;
self.error_message = Some(reason);
self.updated_at = Utc::now();
}
/// 是否需要人工审核(低置信度或低质量)
pub fn needs_review(&self) -> bool {
self.confidence < 0.7 || self.quality_score < 0.6
}
}
impl VideoClassificationTask {
/// 创建新的分类任务
pub fn new(
segment_id: String,
material_id: String,
project_id: String,
video_file_path: String,
priority: Option<i32>,
) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
segment_id,
material_id,
project_id,
video_file_path,
status: TaskStatus::Pending,
priority: priority.unwrap_or(0),
retry_count: 0,
max_retries: 3,
gemini_file_uri: None,
prompt_text: None,
error_message: None,
started_at: None,
completed_at: None,
created_at: Utc::now(),
updated_at: Utc::now(),
}
}
/// 开始处理任务
pub fn start_processing(&mut self) {
self.status = TaskStatus::Uploading;
self.started_at = Some(Utc::now());
self.updated_at = Utc::now();
}
/// 设置为分析状态
pub fn set_analyzing(&mut self, gemini_file_uri: String, prompt_text: String) {
self.status = TaskStatus::Analyzing;
self.gemini_file_uri = Some(gemini_file_uri);
self.prompt_text = Some(prompt_text);
self.updated_at = Utc::now();
}
/// 完成任务
pub fn complete(&mut self) {
self.status = TaskStatus::Completed;
self.completed_at = Some(Utc::now());
self.updated_at = Utc::now();
}
/// 任务失败
pub fn fail(&mut self, error_message: String) {
self.status = TaskStatus::Failed;
self.error_message = Some(error_message);
self.retry_count += 1;
self.updated_at = Utc::now();
}
/// 是否可以重试
pub fn can_retry(&self) -> bool {
self.retry_count < self.max_retries && self.status == TaskStatus::Failed
}
/// 重置为等待状态(用于重试)
pub fn reset_for_retry(&mut self) {
self.status = TaskStatus::Pending;
self.error_message = None;
self.started_at = None;
self.completed_at = None;
self.updated_at = Utc::now();
}
}
impl Default for ClassificationStatus {
fn default() -> Self {
ClassificationStatus::Classified
}
}
impl Default for TaskStatus {
fn default() -> Self {
TaskStatus::Pending
}
}