import React, { useEffect, useRef, useState, useImperativeHandle, forwardRef } from 'react'; import { Button, Nav, Modal, Form, Dropdown, Row, Col, Container, Card, ListGroup, Badge, Table, Alert } from 'react-bootstrap'; import { useHistory } from 'react-router-dom'; import './home.css' import { toast } from 'react-toastify'; import VideoDownload from '../../components/videoDownload'; import SubtitleUpload from '../../components/subtitleUpload'; import AudioUpload from '../../components/audioUpload'; import VideoUpload from '../../components/videoUpload'; import { bookService, bookInfoService, getDbPath } from '../../db'; import CozeApiSettings from '../../components/CozeApiSettings'; import { hasValidToken } from '../../utils/cozeConfig'; const Home = forwardRef((props, ref) => { const [showProject, setShowProject] = useState(false); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [showApiSettings, setShowApiSettings] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [projectToDelete, setProjectToDelete] = useState(null); const [showAudioPlayer, setShowAudioPlayer] = useState(false); const [currentAudio, setCurrentAudio] = useState(null); const [projectAudioFile, setProjectAudioFile] = useState(null); const [projectVideoFile, setProjectVideoFile] = useState(null); const history = useHistory(); const handleAddProject = () => { setShowProject(true); }; // 处理API设置 const handleOpenApiSettings = () => { setShowApiSettings(true); }; const handleCloseApiSettings = () => { setShowApiSettings(false); }; // 加载项目列表 const loadProjects = async () => { try { setLoading(true); const books = await bookService.getAllBooks(); setProjects(books); } catch (error) { console.error('加载项目列表失败:', error); toast.error('加载项目列表失败'); } finally { setLoading(false); } }; // 查看项目详情 const viewProjectDetail = (projectId) => { history.push(`/project/${projectId}`); }; // 下载SRT字幕 const downloadSubtitleSRT = async (projectId, projectTitle) => { try { setLoading(true); // 获取项目详情 const segments = await bookInfoService.getBookInfoByBookId(projectId); if (!segments || segments.length === 0) { toast.error('该项目没有字幕内容可下载'); return; } // 生成SRT格式内容 const srtContent = segments .sort((a, b) => a.segment_id - b.segment_id) .map((segment) => { return `${segment.segment_id}\n${segment.start_time} --> ${segment.end_time}\n${segment.text}\n`; }) .join('\n'); // 创建Blob对象 const blob = new Blob([srtContent], { type: 'text/plain;charset=utf-8' }); // 创建下载链接 const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${projectTitle || '项目'}.srt`; // 触发下载 document.body.appendChild(link); link.click(); // 清理 document.body.removeChild(link); URL.revokeObjectURL(url); toast.success('字幕下载成功'); } catch (error) { console.error('下载字幕失败:', error); toast.error(`下载字幕失败: ${error.message}`); } finally { setLoading(false); } }; // 下载纯文本文案 const downloadPlainText = async (projectId, projectTitle) => { try { setLoading(true); // 获取项目详情 const segments = await bookInfoService.getBookInfoByBookId(projectId); if (!segments || segments.length === 0) { toast.error('该项目没有文本内容可下载'); return; } // 提取所有纯文本内容(不包含时间信息) const textContent = segments .sort((a, b) => a.segment_id - b.segment_id) .map(segment => segment.text) .join('\n\n'); // 创建Blob对象 const blob = new Blob([textContent], { type: 'text/plain;charset=utf-8' }); // 创建下载链接 const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${projectTitle || '项目'}_文案.txt`; // 触发下载 document.body.appendChild(link); link.click(); // 清理 document.body.removeChild(link); URL.revokeObjectURL(url); toast.success('文案下载成功'); } catch (error) { console.error('下载文案失败:', error); toast.error(`下载文案失败: ${error.message}`); } finally { setLoading(false); } }; // 复制文案到剪贴板 const copyProjectText = async (projectId, projectTitle) => { try { setLoading(true); // 获取项目详情 const segments = await bookInfoService.getBookInfoByBookId(projectId); if (!segments || segments.length === 0) { toast.error('该项目没有文本内容可复制'); return; } // 提取所有纯文本内容(不包含时间信息) const textContent = segments .sort((a, b) => a.segment_id - b.segment_id) .map(segment => segment.text) .join('\n\n'); // 复制到剪贴板 await navigator.clipboard.writeText(textContent); toast.success('文案已复制到剪贴板'); } catch (error) { console.error('复制文案失败:', error); toast.error(`复制文案失败: ${error.message}`); } finally { setLoading(false); } }; // 返回主页 const backToHome = () => { setShowProject(false); loadProjects(); // 刷新项目列表 }; // 格式化时间 const formatTime = (timeString) => { return timeString.replace(',', '.'); }; // 计算持续时间(秒) const calculateDuration = (startTime, endTime) => { const parseTime = (time) => { const [hours, minutes, seconds] = time.replace(',', '.').split(':').map(parseFloat); return hours * 3600 + minutes * 60 + seconds; }; const startSeconds = parseTime(startTime); const endSeconds = parseTime(endTime); return (endSeconds - startSeconds).toFixed(2); }; // 打开数据库路径 const openDatabasePath = async () => { try { if (window.electron && window.electron.files) { // 获取数据库路径 const dbPath = await getDbPath(); if (!dbPath) { toast.error('无法获取数据库路径'); return; } console.log('数据库路径:', dbPath); // 获取数据库所在目录 let dbDir = dbPath; const lastSlashIndex = Math.max(dbPath.lastIndexOf('/'), dbPath.lastIndexOf('\\')); if (lastSlashIndex !== -1) { dbDir = dbPath.substring(0, lastSlashIndex); } console.log('数据库目录:', dbDir); // 打开文件夹 window.electron.files.showItemInFolder(dbDir); toast.success('已打开数据库所在文件夹'); } else { toast.info('此功能仅在桌面应用中可用'); } } catch (error) { console.error('打开数据库路径失败:', error); toast.error(`打开数据库路径失败: ${error.message}`); } }; // 打开资源文件夹 const openResourcesPath = async () => { try { if (window.electron && window.electron.ipcRenderer) { // 获取资源目录路径 const result = await window.electron.ipcRenderer.invoke('get-resources-path'); if (!result.success) { toast.error(`无法获取资源目录路径: ${result.error}`); return; } console.log('资源目录路径:', result.path); // 打开文件夹 window.electron.files.showItemInFolder(result.path); toast.success('已打开资源文件夹'); } else { toast.info('此功能仅在桌面应用中可用'); } } catch (error) { console.error('打开资源文件夹失败:', error); toast.error(`打开资源文件夹失败: ${error.message}`); } }; // 删除项目 const handleDeleteProject = async (project) => { setProjectToDelete(project); setShowDeleteConfirm(true); }; // 确认删除项目 const confirmDeleteProject = async () => { if (!projectToDelete) return; try { setLoading(true); await bookService.deleteBook(projectToDelete.id); toast.success(`项目 "${projectToDelete.title}" 已删除`); setShowDeleteConfirm(false); setProjectToDelete(null); await loadProjects(); // 刷新项目列表 } catch (error) { console.error('删除项目失败:', error); toast.error(`删除项目失败: ${error.message}`); } finally { setLoading(false); } }; // 取消删除 const cancelDeleteProject = () => { setShowDeleteConfirm(false); setProjectToDelete(null); }; // 播放音频 const playAudio = (projectId, audioFileName) => { // 由于实际开发中,音频文件可能存储在特定位置,这里仅为示例,使用音频文件名 // 实际应用中可能需要通过其他方式获取真实的音频文件路径 setCurrentAudio({ projectId, fileName: audioFileName }); setShowAudioPlayer(true); }; // 关闭音频播放器 const closeAudioPlayer = () => { setShowAudioPlayer(false); setCurrentAudio(null); }; // 组件挂载时加载项目列表 useEffect(() => { loadProjects(); }, []); return ( <>
{!showProject ? ( <>
{!hasValidToken() && ( 请配置Coze API Token

您尚未配置Coze API Token,文本转描述词功能将无法使用。 请点击右上角的"配置Coze API"按钮进行设置。

)}
{projects.length > 0 ? (

项目列表

{projects.map((project) => ( ))}
项目名称 创建时间 字幕文件 音频文件 视频文件 操作
{project.title} {new Date(project.created_at).toLocaleString()} {project.subtitle_path ? ( {project.subtitle_path} ) : ( )} {project.audio_path ? (
{project.audio_path.length > 15 ? project.audio_path.substring(0, 6) + '...' + project.audio_path.substring(project.audio_path.length - 6) : project.audio_path}
) : ( )}
{project.video_path ? (
{project.video_path.length > 15 ? project.video_path.substring(0, 6) + '...' + project.video_path.substring(project.video_path.length - 6) : project.video_path}
) : ( )}
{project.audio_path && ( )}
) : (

点击左上角"添加项目"按钮开始创建新项目

)}
) : ( <>
setProjectAudioFile(audioFile)} /> setProjectVideoFile(videoFile)} /> )}
{/* API设置对话框 */} Coze API设置 {/* 删除确认对话框 */} 确认删除 {projectToDelete && (

您确定要删除项目 "{projectToDelete.title}" 吗?此操作将同时删除所有相关的分镜数据,且不可恢复!

)}
{/* 音频播放器模态框 */} 音频播放 {currentAudio && (

项目音频: {currentAudio.fileName}

注意: 这里仅展示文件名,实际应用中需要处理真实的音频文件路径
)}
); }); export default Home;