mixvideo-v2/TVAI_HOOKS_FIX_AND_FILE_SEL...

7.0 KiB
Raw Blame History

TVAI Hooks 错误修复和文件选择功能改进总结

🐛 问题诊断

React Hooks 错误

Uncaught Error: Rendered fewer hooks than expected. 
This may be caused by an accidental early return statement.

根本原因: 在React组件中Hooks必须在每次渲染时以相同的顺序调用。如果在早期返回语句之后调用Hooks会导致Hooks数量不一致的错误。

问题代码结构

export function TvaiExample() {
  // ✅ 正确Hooks在早期返回之前
  const [state1] = useState();
  const [state2] = useState();
  
  // ❌ 错误:早期返回
  if (loading) {
    return <div>Loading...</div>;
  }
  
  // ❌ 错误Hooks在早期返回之后
  const callback = useCallback(() => {}, []);
}

修复方案

1. Hooks 重新排序

将所有Hooks调用移到早期返回语句之前

export function TvaiExample() {
  // ✅ 所有Hooks必须在早期返回之前
  const { systemInfo, isLoading: systemLoading, initializeTvai } = useTvaiSystemInfo();
  const { tasks, isLoading: tasksLoading, cancelTask, cleanupCompletedTasks } = useTvai();
  
  // 状态Hooks
  const [processingType, setProcessingType] = useState<'video' | 'image' | 'interpolation'>('video');
  const [showAdvanced, setShowAdvanced] = useState(false);
  const [inputPath, setInputPath] = useState('');
  const [outputPath, setOutputPath] = useState('');
  const [scaleFactor, setScaleFactor] = useState(2.0);
  const [selectedPreset, setSelectedPreset] = useState('GENERAL');
  const [selectedModel, setSelectedModel] = useState<UpscaleModel>('iris-3');
  const [compression, setCompression] = useState(0.0);
  const [blend, setBlend] = useState(0.0);
  const [interpolationMultiplier, setInterpolationMultiplier] = useState(2.0);

  // useCallback Hooks
  const handleSelectInputFile = useCallback(async () => { /* ... */ }, [processingType]);
  const handleSelectOutputFile = useCallback(async () => { /* ... */ }, [processingType]);

  // 普通函数
  const applyPreset = (presetName: string) => { /* ... */ };
  const handleQuickProcess = async () => { /* ... */ };

  // ✅ 早期返回在所有Hooks之后
  if (systemLoading) {
    return <div>Loading...</div>;
  }

  // 正常渲染
  return <div>...</div>;
}

2. 清理未使用的变量

移除了以下未使用的状态变量:

  • currentStepsetCurrentStep
  • qualityPresetsetQualityPreset
  • outputFormatsetOutputFormat
  • targetFpssetTargetFps
  • isDraggingsetIsDragging

3. 移除重复的函数定义

删除了重复定义的函数:

  • 重复的 applyPreset 函数
  • 重复的 handleQuickProcess 函数
  • 重复的早期返回语句

🎯 文件选择功能改进

参考其他页面的实现

通过分析 ImageEditingTool.tsx 和其他工具页面,采用了统一的文件选择模式:

import { open } from '@tauri-apps/plugin-dialog';

const handleSelectFile = useCallback(async () => {
  try {
    const selected = await open({
      multiple: false,
      filters: [{ name: '视频文件', extensions: ['mp4', 'avi', 'mov'] }],
      title: '选择视频文件',
    });

    if (selected && typeof selected === 'string') {
      setInputPath(selected);
      // 自动生成输出路径
      generateOutputPath(selected);
    }
  } catch (error) {
    console.error('选择文件失败:', error);
    alert('选择文件失败: ' + error.message);
  }
}, [processingType]);

文件格式配置

const FILE_FORMATS = {
  video: {
    name: '视频文件',
    extensions: ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'm4v', 'webm', '3gp', 'ts']
  },
  image: {
    name: '图片文件', 
    extensions: ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'webp', 'gif', 'svg']
  }
};

UI 改进

从简单的文本输入框改为专业的文件选择按钮:

原来的设计:

<input
  type="text"
  value={inputPath}
  onChange={(e) => setInputPath(e.target.value)}
  placeholder="选择视频文件..."
/>

新的设计:

<button
  onClick={handleSelectInputFile}
  className="w-full flex items-center gap-3 px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors text-left"
>
  <FolderOpen className="w-5 h-5 text-gray-400 flex-shrink-0" />
  <div className="flex-1 min-w-0">
    {inputPath ? (
      <div>
        <div className="text-sm font-medium text-gray-900 truncate">
          {inputPath.split('/').pop() || inputPath.split('\\').pop()}
        </div>
        <div className="text-xs text-gray-500 truncate">
          {inputPath}
        </div>
      </div>
    ) : (
      <span className="text-gray-500">
        选择{processingType === 'image' ? '图片' : '视频'}文件...
      </span>
    )}
  </div>
</button>

智能功能

  1. 自动路径生成: 根据输入文件自动生成输出路径
  2. 动态文件类型: 根据处理类型显示对应的文件格式
  3. 文件名显示: 显示文件名和完整路径
  4. 一键生成: 提供"自动生成输出路径"按钮

🔧 技术细节

Hooks 调用顺序规则

  1. 自定义Hooks: useTvaiSystemInfo(), useTvai()
  2. 状态Hooks: useState()
  3. 效果Hooks: useCallback(), useMemo(), useEffect()
  4. 早期返回: 在所有Hooks之后
  5. 事件处理函数: 普通函数不是Hooks

文件选择最佳实践

  1. 统一API: 使用 @tauri-apps/plugin-dialogopen() 函数
  2. 类型安全: TypeScript 类型检查
  3. 错误处理: try-catch 包装,用户友好的错误提示
  4. 用户体验: 加载状态、进度反馈、自动完成

性能优化

  1. useCallback: 防止不必要的重新渲染
  2. 依赖数组: 正确设置依赖项
  3. 条件渲染: 避免不必要的计算

📊 修复前后对比

方面 修复前 修复后
Hooks错误 运行时崩溃 正常运行
文件选择 手动输入路径 原生文件对话框
用户体验 复杂操作 一键选择
错误处理 基础提示 详细错误信息
路径生成 手动输入 自动生成
文件格式 无限制 类型过滤

🚀 用户体验提升

操作流程简化

  1. 选择处理类型 → 自动设置文件格式过滤
  2. 点击选择文件 → 打开原生文件对话框
  3. 选择文件 → 自动生成输出路径
  4. 开始处理 → 一键启动

错误预防

  1. 文件格式验证: 只允许选择支持的格式
  2. 路径验证: 确保文件存在且可读
  3. 权限检查: 确保输出路径可写
  4. 友好提示: 清晰的错误信息和解决建议

总结

通过修复React Hooks的调用顺序问题和改进文件选择功能TVAI工具现在具有

稳定性 - 无运行时错误符合React规范 易用性 - 原生文件对话框,操作简单 智能化 - 自动路径生成,类型过滤 专业性 - 统一的UI设计完善的错误处理

这些改进大大提升了用户体验使TVAI工具更加稳定和易用。