Spaces:
Running
Running
| import React, { useState } from 'react'; | |
| import { JobData } from '../types'; | |
| import * as api from '../services/api'; | |
| interface ProcessingPanelProps { | |
| job: JobData | null; | |
| embedded?: boolean; | |
| onEnhance?: (job: JobData) => void; | |
| } | |
| const ProcessingPanel: React.FC<ProcessingPanelProps> = ({ job, embedded = false, onEnhance }) => { | |
| const [downloadingType, setDownloadingType] = useState<'main' | 'enhanced' | null>(null); | |
| const [copySuccess, setCopySuccess] = useState(false); | |
| if (!job) return null; | |
| const isCompleted = job.status === 'completed'; | |
| const isFailed = job.status === 'failed' || job.status === 'error'; | |
| const VC_BASE_URL = 'https://ezmarynoori-sada.hf.space'; | |
| let downloadUrl = '#'; | |
| if (isCompleted) { | |
| if (job.downloadUrl) { | |
| downloadUrl = job.downloadUrl; | |
| } else if (job.filename) { | |
| downloadUrl = `${VC_BASE_URL}/download/${job.filename}`; | |
| } | |
| } | |
| const enhanceInfo = job.enhancement; | |
| const isEnhancing = enhanceInfo?.status === 'processing' || enhanceInfo?.status === 'started'; | |
| const isEnhanceCompleted = enhanceInfo?.status === 'completed'; | |
| // استفاده اولویتدار از لینک مستقیم بله که در مرورگر ذخیره شده است | |
| const enhanceDownloadUrl = isEnhanceCompleted | |
| ? (enhanceInfo?.downloadUrl || (enhanceInfo?.filename ? api.getEnhancementDownloadUrl(enhanceInfo.filename) : '#')) | |
| : '#'; | |
| const handleDownload = async (e: React.MouseEvent, url: string, type: 'main' | 'enhanced') => { | |
| e.preventDefault(); | |
| if (downloadingType) return; | |
| setDownloadingType(type); | |
| try { | |
| let blobUrl = url; | |
| if (!url.startsWith('blob:')) { | |
| const resp = await fetch(url); | |
| if (!resp.ok) throw new Error(`Network response was not ok: ${resp.statusText}`); | |
| const blob = await resp.blob(); | |
| blobUrl = URL.createObjectURL(blob); | |
| } | |
| window.parent.postMessage({ | |
| type: 'INITIATE_DOWNLOAD_FROM_URL', | |
| payload: { audioUrl: blobUrl } | |
| }, '*'); | |
| } catch (err) { | |
| console.error("Download preparation error:", err); | |
| alert("خطا در آمادهسازی دانلود. لطفاً اتصال اینترنت خود را بررسی کنید."); | |
| } finally { | |
| setDownloadingType(null); | |
| } | |
| }; | |
| const handleCopyErrorLog = () => { | |
| const logText = job.statusMessage || 'Unknown Error'; | |
| navigator.clipboard.writeText(logText).then(() => { | |
| setCopySuccess(true); | |
| setTimeout(() => setCopySuccess(false), 2000); | |
| }).catch(err => { | |
| console.error('Failed to copy error log', err); | |
| }); | |
| }; | |
| const containerClasses = embedded | |
| ? "bg-gray-50/80 rounded-lg p-3 mt-2 border border-gray-100 animate-[fadeIn_0.3s_ease-out]" | |
| : "bg-white rounded-3xl shadow-lg border border-gray-100 p-6 mt-6 animate-[slideUp_0.5s_ease-out]"; | |
| return ( | |
| <div className={containerClasses}> | |
| <div className="flex items-center justify-between mb-2"> | |
| <div> | |
| <h4 className={`font-bold text-gray-800 ${embedded ? 'text-xs' : 'text-lg'}`}> | |
| {isCompleted ? 'فایل اصلی آماده' : isFailed ? 'خطا در پردازش' : 'در حال تغییر صدا...'} | |
| </h4> | |
| {!embedded && ( | |
| <p className="text-xs text-gray-500"> | |
| {job.type === 'model' ? `مدل: ${job.modelName}` : 'اختصاصی'} | |
| </p> | |
| )} | |
| </div> | |
| {!embedded && ( | |
| <div className={`w-10 h-10 rounded-full flex items-center justify-center text-white shadow-md | |
| ${isCompleted ? 'bg-green-500' : isFailed ? 'bg-red-500' : 'bg-amber-400'}`}> | |
| <i className={`fas ${isCompleted ? 'fa-check' : isFailed ? 'fa-exclamation' : 'fa-cog fa-spin'}`}></i> | |
| </div> | |
| )} | |
| </div> | |
| {!isCompleted && !isFailed && ( | |
| <div className="mb-2"> | |
| {!embedded && ( | |
| <div className="flex justify-between text-xs text-gray-500 mb-1"> | |
| <span>پیشرفت کلی</span> | |
| <span>{job.progress || 0}%</span> | |
| </div> | |
| )} | |
| <div className={`w-full bg-gray-200 rounded-full overflow-hidden ${embedded ? 'h-1.5' : 'h-2'}`}> | |
| <div | |
| className="h-full bg-gradient-to-r from-amber-400 to-orange-500 rounded-full transition-all duration-500 relative" | |
| style={{ width: `${job.progress || 0}%` }} | |
| > | |
| <div className="absolute inset-0 bg-white/20 w-full h-full animate-[shimmer_2s_infinite]"></div> | |
| </div> | |
| </div> | |
| <p className="text-center text-[10px] text-gray-500 mt-2 font-medium truncate px-1"> | |
| {job.statusMessage || 'در حال انجام عملیات...'} | |
| </p> | |
| </div> | |
| )} | |
| {isCompleted && ( | |
| <div className={`${embedded ? 'mt-1' : 'mt-4 pt-4 border-t border-gray-100'}`}> | |
| <audio controls className={`w-full ${embedded ? 'mb-2 h-7' : 'mb-3 h-8'}`} src={downloadUrl} /> | |
| <a | |
| href="#" | |
| onClick={(e) => handleDownload(e, downloadUrl, 'main')} | |
| className={`block w-full text-center text-white rounded-xl font-bold transition-all shadow-sm flex items-center justify-center gap-2 | |
| ${embedded ? 'py-2 text-xs bg-gray-600 hover:bg-gray-700' : 'py-3 bg-gray-600 shadow-gray-200'} | |
| ${downloadingType === 'main' ? 'opacity-70 cursor-wait' : ''}`} | |
| > | |
| {downloadingType === 'main' ? ( | |
| <><i className="fas fa-circle-notch fa-spin"></i> آمادهسازی...</> | |
| ) : ( | |
| <><i className="fas fa-download"></i> دانلود صدای اصلی (معمولی)</> | |
| )} | |
| </a> | |
| </div> | |
| )} | |
| {isFailed && ( | |
| <div | |
| onClick={handleCopyErrorLog} | |
| className="mt-1 p-2 bg-red-50 text-red-600 rounded-lg text-[10px] text-center border border-red-100 cursor-pointer hover:bg-red-100 transition-colors relative group" | |
| > | |
| {copySuccess ? ( | |
| <span className="font-bold text-green-600 animate-pulse"> | |
| <i className="fas fa-check mr-1"></i> متن خطا کپی شد | |
| </span> | |
| ) : ( | |
| <span className="flex items-center justify-center gap-1"> | |
| خطا در پردازش. | |
| </span> | |
| )} | |
| </div> | |
| )} | |
| {isCompleted && onEnhance && ( | |
| <div className={`mt-4 pt-4 border-t-2 border-dashed ${isEnhanceCompleted ? 'border-amber-200 bg-amber-50/50 -mx-3 px-3 pb-3 rounded-b-xl' : 'border-gray-100'}`}> | |
| {!enhanceInfo && ( | |
| <button | |
| onClick={() => onEnhance(job)} | |
| className="group relative w-full overflow-hidden rounded-xl bg-gradient-to-r from-violet-600 to-indigo-600 p-[2px] transition-all hover:shadow-lg hover:shadow-indigo-500/30 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2 active:scale-95" | |
| > | |
| <div className="relative flex items-center justify-center gap-2 rounded-[10px] bg-white px-4 py-2.5 transition-all group-hover:bg-transparent group-hover:text-white"> | |
| <i className="fas fa-wand-magic-sparkles text-lg text-indigo-600 transition-colors group-hover:text-white animate-pulse"></i> | |
| <span className="font-black text-sm text-gray-800 transition-colors group-hover:text-white"> | |
| افزایش کیفیت صدا و حذف نویز (AI) | |
| </span> | |
| </div> | |
| </button> | |
| )} | |
| {enhanceInfo && isEnhancing && ( | |
| <div className="animate-[fadeIn_0.5s_ease-out]"> | |
| <div className="flex items-center justify-between mb-2"> | |
| <span className="text-xs font-bold text-indigo-700 flex items-center gap-2"> | |
| <i className="fas fa-sparkles fa-spin text-amber-500"></i> | |
| در حال حذف نویز و تقویت صدا... | |
| </span> | |
| <span className="text-[11px] font-black text-indigo-600">{enhanceInfo.progress || 0}%</span> | |
| </div> | |
| <div className="h-3 w-full rounded-full bg-indigo-100 overflow-hidden shadow-inner"> | |
| <div | |
| className="h-full rounded-full bg-gradient-to-r from-violet-500 via-fuchsia-500 to-indigo-500 transition-all duration-500 ease-out relative" | |
| style={{ width: `${Math.max(5, enhanceInfo.progress || 0)}%` }} | |
| > | |
| <div className="absolute inset-0 bg-white/30 w-full h-full animate-[shimmer_1.5s_infinite]"></div> | |
| </div> | |
| </div> | |
| <p className="text-[9px] text-center text-indigo-400 mt-1 font-bold">در حال انجام کار ممکن است زمانبر باشد</p> | |
| </div> | |
| )} | |
| {enhanceInfo && isEnhanceCompleted && ( | |
| <div className="animate-[scaleIn_0.4s_cubic-bezier(0.175,0.885,0.32,1.275)]"> | |
| <div className="flex items-center gap-2 mb-2 justify-center"> | |
| <i className="fas fa-star text-amber-400 text-sm animate-[spin_3s_linear_infinite]"></i> | |
| <h5 className="text-xs font-black text-amber-700">نسخه تقویت شده (High Quality)</h5> | |
| <i className="fas fa-star text-amber-400 text-sm animate-[spin_3s_linear_infinite_reverse]"></i> | |
| </div> | |
| <audio controls className="w-full h-7 mb-2 border border-amber-200 rounded-full bg-amber-100" src={enhanceDownloadUrl} /> | |
| <a | |
| href="#" | |
| onClick={(e) => handleDownload(e, enhanceDownloadUrl, 'enhanced')} | |
| className={`block w-full text-center text-white rounded-xl font-bold bg-gradient-to-r from-amber-500 to-orange-500 py-2.5 text-xs shadow-lg shadow-amber-500/30 hover:shadow-amber-500/50 transition-all transform hover:-translate-y-0.5 active:translate-y-0 | |
| ${downloadingType === 'enhanced' ? 'opacity-70 cursor-wait' : ''}`} | |
| > | |
| {downloadingType === 'enhanced' ? ( | |
| <><i className="fas fa-circle-notch fa-spin mr-1"></i> در حال آمادهسازی...</> | |
| ) : ( | |
| <><i className="fas fa-download mr-1"></i> دانلود صدای با کیفیت عالی</> | |
| )} | |
| </a> | |
| </div> | |
| )} | |
| {enhanceInfo?.status === 'failed' && ( | |
| <div className="text-xs text-red-500 bg-red-50 p-2 rounded border border-red-100 text-center font-bold"> | |
| خطا در تقویت صدا. لطفا دوباره تلاش کنید. | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default ProcessingPanel; | |