File size: 11,322 Bytes
a8df197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1467cef
 
 
a8df197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239

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;