"use client"; import { useState, useRef, useEffect } from "react"; import api from "@/lib/axios"; interface ScriptExtractionModalProps { isOpen: boolean; onClose: () => void; onApply?: (text: string) => void; } export default function ScriptExtractionModal({ isOpen, onClose, onApply }: ScriptExtractionModalProps) { const [isLoading, setIsLoading] = useState(false); const [script, setScript] = useState(""); const [rewrittenScript, setRewrittenScript] = useState(""); const [error, setError] = useState(null); const [doRewrite, setDoRewrite] = useState(true); const [step, setStep] = useState<'config' | 'processing' | 'result'>('config'); const [dragActive, setDragActive] = useState(false); const [selectedFile, setSelectedFile] = useState(null); // New state for URL mode const [activeTab, setActiveTab] = useState<'file' | 'url'>('url'); const [inputUrl, setInputUrl] = useState(""); // Reset state when modal opens useEffect(() => { if (isOpen) { setStep('config'); setScript(""); setRewrittenScript(""); setError(null); setIsLoading(false); setSelectedFile(null); setInputUrl(""); setActiveTab('url'); } }, [isOpen]); const handleDrag = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.type === "dragenter" || e.type === "dragover") { setDragActive(true); } else if (e.type === "dragleave") { setDragActive(false); } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFile(e.dataTransfer.files[0]); } }; const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { handleFile(e.target.files[0]); } }; const handleFile = (file: File) => { const validTypes = ['.mp4', '.mov', '.avi', '.mp3', '.wav', '.m4a']; const ext = file.name.toLowerCase().slice(file.name.lastIndexOf('.')); if (!validTypes.includes(ext)) { setError(`不支持的文件格式 ${ext},请上传视频或音频文件`); return; } setSelectedFile(file); setError(null); }; const handleExtract = async () => { if (activeTab === 'file' && !selectedFile) { setError("请先上传文件"); return; } if (activeTab === 'url' && !inputUrl.trim()) { setError("请先输入视频链接"); return; } setIsLoading(true); setStep('processing'); setError(null); try { const formData = new FormData(); if (activeTab === 'file' && selectedFile) { formData.append('file', selectedFile); } else if (activeTab === 'url') { formData.append('url', inputUrl.trim()); } formData.append('rewrite', doRewrite ? 'true' : 'false'); const { data } = await api.post('/api/tools/extract-script', formData, { headers: { 'Content-Type': 'multipart/form-data' }, timeout: 180000 // 3 minutes timeout }); if (data.success) { setScript(data.original_script); setRewrittenScript(data.rewritten_script || ""); setStep('result'); } else { setError("提取失败:未知错误"); setStep('config'); } } catch (err: any) { console.error(err); const msg = err.response?.data?.detail || err.message || "请求失败"; setError(msg); setStep('config'); } finally { setIsLoading(false); } }; const copyToClipboard = (text: string) => { if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(() => { alert("已复制到剪贴板"); }).catch(err => { console.error('Async: Could not copy text: ', err); fallbackCopyTextToClipboard(text); }); } else { fallbackCopyTextToClipboard(text); } }; const fallbackCopyTextToClipboard = (text: string) => { var textArea = document.createElement("textarea"); textArea.value = text; // Avoid scrolling to bottom textArea.style.top = "0"; textArea.style.left = "0"; textArea.style.position = "fixed"; textArea.style.opacity = "0"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { var successful = document.execCommand('copy'); var msg = successful ? 'successful' : 'unsuccessful'; if (successful) { alert("已复制到剪贴板"); } else { alert("复制失败,请手动复制"); } } catch (err) { console.error('Fallback: Oops, unable to copy', err); alert("复制失败,请手动复制"); } document.body.removeChild(textArea); }; // Close when clicking outside - DISABLED as per user request // const modalRef = useRef(null); // const handleBackdropClick = (e: React.MouseEvent) => { // if (modalRef.current && !modalRef.current.contains(e.target as Node)) { // onClose(); // } // }; if (!isOpen) return null; return (
{/* Header */}

📜 文案提取助手

{/* Content */}
{step === 'config' && (
{/* Tabs */}
{/* URL Input Area */} {activeTab === 'url' && (
setInputUrl(e.target.value)} placeholder="请粘贴抖音、B站等主流平台视频链接..." className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-4 text-white placeholder-gray-500 focus:outline-none focus:border-purple-500 transition-colors" /> {inputUrl && ( )}

支持抖音、B站等主流平台分享链接,自动解析下载并提取文案。

)} {/* File Upload Area */} {activeTab === 'file' && (
{selectedFile ? (
📄
{selectedFile.name}
{(selectedFile.size / (1024 * 1024)).toFixed(1)} MB
点击更换文件
) : (
📤
点击上传或拖拽文件到此处
支持 MP4, MOV, MP3, WAV 等音视频格式
)}
)} {/* Options */}
{error && (
❌ {error}
)}
)} {step === 'processing' && (

正在处理中...

{activeTab === 'url' && "正在下载视频..."}
{doRewrite ? "正在进行语音识别和 AI 智能改写..." : "正在进行语音识别..."}
大文件可能需要几分钟,请不要关闭窗口

)} {step === 'result' && (
{rewrittenScript && (

✨ AI 洗稿结果 (推荐)

{onApply && ( )}

{rewrittenScript}

)}

🎙️ 原始识别结果

{onApply && ( )}

{script}

)}
); }