import { JobData, StatusResponse, Model } from '../types'; import * as legacyApi from './legacy_api'; const TTS_BASE_URL = 'https://hamed744-ttsp3.hf.space'; const ENHANCE_BASE_URL = 'https://ezmarynoori-taqviat-sada.hf.space'; const safeBase64Encode = (str: string): string => { return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { return String.fromCharCode(parseInt('0x' + p1)); })); }; const uploadAudioToOrchestrator = async (file: Blob | File, runId: string, suffix: string): Promise => { const formData = new FormData(); const ext = (file as File).name?.split('.').pop()?.toLowerCase() || 'wav'; formData.append('run_id', `${runId}_${suffix}`); formData.append('ext', ext); formData.append('file', file); const response = await fetch('/api/webhook/upload', { method: 'POST', body: formData }); if (!response.ok) throw new Error("خطا در آپلود فایل صوتی به سرور پشتیبان."); return ext; }; export const uploadJob = async ( sourceFile: File | Blob, refFile: File | Blob, metadata: { type?: 'custom' | 'model', modelName?: string, modelImage?: string } ): Promise => { const currentRunId = "vev" + Math.random().toString(36).substring(2, 14); // ۱. آپلود صوتی‌های مبدا و مرجع به ارکستریتور const sourceExt = await uploadAudioToOrchestrator(sourceFile, currentRunId, 'vevo_source'); const refExt = await uploadAudioToOrchestrator(refFile, currentRunId, 'vevo_ref'); // ۲. رمزگذاری آدرس فضای پردازش و ساخت کلید تنظیمات Vevo const b64SpaceUrl = safeBase64Encode("https://opera8-sada.hf.space"); const vevoPayload = `VEVOCONFIG_userRunId_${currentRunId}_sourceExt_${sourceExt}_refExt_${refExt}_spaceUrl_${b64SpaceUrl}`; // ۳. ارسال دستور کار به سیستم دیسپچ گیت‌هاب اکشنز const response = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: vevoPayload, action_name: 'vevo-voice' }), }); if (!response.ok) { const errData = await response.json().catch(() => ({})); throw new Error(errData.message || `خطا در تخصیص صف گیت‌هاب: ${response.status}`); } const data = await response.json(); const finalRunId = data.run_id || currentRunId; return { job_id: finalRunId, total_chunks: 1, chunks: [], date: new Date().toLocaleString('fa-IR'), timestamp: Date.now(), progress: 0, status: 'started', type: metadata.type, modelName: metadata.modelName, modelImage: metadata.modelImage, backend: 'standard' }; }; export const uploadEnhancementJob = async (audioBlob: Blob): Promise => { const formData = new FormData(); formData.append('source_audio', audioBlob, 'input_to_enhance.wav'); formData.append('solver', 'Midpoint'); formData.append('nfe', '64'); formData.append('tau', '0.5'); formData.append('denoising', 'true'); formData.append('subscriptionStatus', 'paid'); formData.append('fingerprint', 'admin_bypass'); const response = await fetch(`${ENHANCE_BASE_URL}/upload`, { method: 'POST', body: formData, }); return await response.json(); }; export const checkEnhancementStatus = async (metadata: any): Promise => { const response = await fetch(`${ENHANCE_BASE_URL}/check_status`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job_id: metadata.job_id, total_chunks: metadata.total_chunks, chunks: metadata.chunks }), }); return await response.json(); }; export const getEnhancementDownloadUrl = (filename: string): string => { return `${ENHANCE_BASE_URL}/download/${filename}`; }; const generateBaseTTS = async (text: string, speakerId: string = "Algieba"): Promise => { const response = await fetch(`${TTS_BASE_URL}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, speaker: speakerId, temperature: 0.9, subscriptionStatus: 'paid', fingerprint: 'admin_bypass' }) }); if (!response.ok) throw new Error(`TTS Generation failed`); const data = await response.json(); const jobId = data.job_id; return new Promise((resolve, reject) => { const pollInterval = setInterval(async () => { try { const statusRes = await fetch(`${TTS_BASE_URL}/api/check_status`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job_id: jobId }) }); const statusData = await statusRes.json(); if (statusData.status === 'completed' && statusData.proxy_url) { clearInterval(pollInterval); const audioRes = await fetch(`${TTS_BASE_URL}${statusData.proxy_url}`); resolve(await audioRes.blob()); } else if (statusData.status === 'failed') { clearInterval(pollInterval); reject(new Error("TTS failed")); } } catch (e) { clearInterval(pollInterval); reject(e); } }, 3000); }); }; export const fetchRefAudioAsFile = async (url: string, filename: string): Promise => { const response = await fetch(url); const blob = await response.blob(); return new File([blob], filename, { type: 'audio/wav' }); }; export const checkJobStatus = async (job: JobData): Promise => { // Priority 1: Use the explicit backend flag if available if (job.backend === 'legacy') return await legacyApi.checkLegacyStatus(job.job_id); if (job.backend === 'standard') { try { const response = await fetch(`/api/status/${job.job_id}`); if (!response.ok) throw new Error(`بررسی وضعیت با خطا مواجه شد: ${response.status}`); const data = await response.json(); let status: 'processing' | 'completed' | 'failed' | 'error' = 'processing'; if (data.status === 'ready') status = 'completed'; else if (data.status === 'failed') status = 'failed'; else if (data.status === 'not_found' || data.status === 'error') status = 'error'; return { status, progress: data.status === 'ready' ? 100 : (data.status === 'processing' ? 50 : 10), filename: data.url ? data.url.split('/').pop() : undefined, detail: data.message || (data.status === 'ready' ? 'تکمیل شد' : 'در حال پردازش در سرور ابری...'), downloadUrl: data.url ? data.url : undefined }; } catch (e) { return { status: 'processing', progress: 10, detail: 'درحال تلاش برای برقراری ارتباط با سرور...' }; } } // Fallback for legacy models tracking const legacyNames = [ 'shadmehr', 'moein', 'billie', 'chavoshi', 'ghomayshi', 'yas', 'ferdosipour', 'khiabani', 'jalilvand', 'tahmasb', 'pourang', 'sponge', 'jenab', 'yousef', 'jomeong', 'musk', 'شادمهر عقیلی', 'معین', 'بیلی آیلیش', 'محسن چاوشی', 'سیاوش قمیشی', 'یاس', 'عادل فردوسی‌پور', 'جواد خیابانی', 'چنگیز جلیلوند', 'ناصر طهماسب', 'عمو پورنگ', 'باب اسفنجی', 'جناب خان', 'یوسف پیامبر', 'جومونگ', 'ایلان ماسک' ]; const isLegacy = (job.modelName && legacyNames.includes(job.modelName.toLowerCase())) || job.job_id.includes('-'); if (isLegacy) return await legacyApi.checkLegacyStatus(job.job_id); try { const response = await fetch(`/api/status/${job.job_id}`); if (!response.ok) throw new Error(`بررسی وضعیت با خطا مواجه شد: ${response.status}`); const data = await response.json(); let status: 'processing' | 'completed' | 'failed' | 'error' = 'processing'; if (data.status === 'ready') status = 'completed'; else if (data.status === 'failed') status = 'failed'; else if (data.status === 'not_found' || data.status === 'error') status = 'error'; return { status, progress: data.status === 'ready' ? 100 : (data.status === 'processing' ? 50 : 10), filename: data.url ? data.url.split('/').pop() : undefined, detail: data.message || (data.status === 'ready' ? 'تکمیل شد' : 'در حال پردازش در سرور ابری...'), downloadUrl: data.url ? data.url : undefined }; } catch (e) { return { status: 'processing', progress: 10, detail: 'درحال تلاش برای برقراری ارتباط با سرور...' }; } }; export const processUnifiedJob = async ( sourceFile: File | Blob | undefined, text: string | undefined, refFile: File | Blob, model: Model | null, mode: 'vc' | 'tts', pitch: number, onProgress: (msg: string, progress?: number) => void, jobType: 'custom' | 'model' ): Promise => { let actualSource: File | Blob; if (mode === 'vc') { if (!sourceFile) throw new Error("فایل صوتی یافت نشد"); actualSource = sourceFile; } else { if (!text) throw new Error("متن یافت نشد"); let baseSpeaker = (model && model.targetGender === 'female') ? "Despina" : "Algieba"; onProgress(`تولید صدای پایه...`, 5); actualSource = await generateBaseTTS(text, baseSpeaker); onProgress("صدای پایه تولید شد.", 10); } const metadata = { type: jobType, modelName: model?.name, modelImage: typeof model?.image === 'string' ? model.image : undefined }; if (model && model.type === 'legacy_rvc' && model.legacyConfig) { onProgress("ارسال به سرور لگاسی...", 15); return await legacyApi.uploadLegacyJob(actualSource, model.legacyConfig.modelUrl, pitch, { name: model.name, image: metadata.modelImage }); } else { onProgress("ارسال به سرور تغییر صدا...", 20); return await uploadJob(actualSource, refFile, metadata); } };