import { JobData, StatusResponse } from '../types'; 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('https://opera8-action.hf.space/api/webhook/upload', { method: 'POST', body: formData }); if (!response.ok) throw new Error("خطا در آپلود فایل صوتی به سرور پشتیبان."); return ext; }; /** * Uploads a job to the FastAPI RVC Legacy backend via the load-balanced GitHub Actions orchestrator. */ export const uploadLegacyJob = async ( sourceFile: Blob | File, modelUrl: string, pitch: number, modelMetadata: { name?: string, image?: string }, algo: string = "rmvpe+", indexInf: number = 0.75 ): Promise => { const currentRunId = "rvc" + Math.random().toString(36).substring(2, 14); // ۱. آپلود فایل صوتی ورودی به ارکستریتور ابری const audioExt = await uploadAudioToOrchestrator(sourceFile, currentRunId, 'rvc_audio'); // ۲. رمزگذاری آدرس‌ها و آماده‌سازی کلید پیکربندی آر‌وی‌سی const b64ModelUrl = safeBase64Encode(modelUrl); const b64SpaceUrl = safeBase64Encode("https://r3gm-rvc-zero.hf.space"); const rvcPayload = `RVCCONFIG_userRunId_${currentRunId}_audioExt_${audioExt}_pitchAlgo_${algo}_pitchLvl_${pitch}_indexInf_${indexInf}_denoise_false_reverb_false_modelUrl_${b64ModelUrl}_spaceUrl_${b64SpaceUrl}`; // ۳. ارسال درخواست به بک‌اند ابری جهت توزیع در صف گیت‌هاب const response = await fetch('https://opera8-action.hf.space/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: rvcPayload, action_name: 'rvc-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, status: 'started', progress: 10, statusMessage: 'در صف انتظار سرورهای موازی ...', total_chunks: 1, chunks: [], date: new Date().toLocaleString('fa-IR'), timestamp: Date.now(), type: 'model', modelName: modelMetadata.name, modelImage: modelMetadata.image, retryCount: 0, backend: 'legacy' }; }; /** * Checks the status of a legacy job on the orchestrator. */ export const checkLegacyStatus = async (jobId: string): Promise => { try { const response = await fetch(`/api/status/${jobId}`); 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: 'درحال تلاش برای برقراری ارتباط با سرور...' }; } };