taghirsado / services /api.ts
Opera10's picture
Update services/api.ts
cf07449 verified
Raw
History Blame Contribute Delete
11 kB
import { JobData, StatusResponse, Model } from './types';
import * as legacyApi from './legacy_api';
const TTS_BASE_URL = 'https://sada8888-tts2.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<string> => {
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;
};
export const uploadJob = async (
sourceFile: File | Blob,
refFile: File | Blob,
metadata: { type?: 'custom' | 'model', modelName?: string, modelImage?: string }
): Promise<JobData> => {
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('https://opera8-action.hf.space/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<any> => {
const currentRunId = "taq" + Math.random().toString(36).substring(2, 14);
const ext = "wav"; // فرمت پیش‌فرض برای انتقال فایل باینری صوتی
// ۱. آپلود محلی مستقیم فایل صوتی به سرور اصلی (پوشه موقت)
const formData = new FormData();
formData.append('run_id', `${currentRunId}_input`);
formData.append('ext', ext);
formData.append('file', audioBlob);
const uploadResponse = await fetch('https://opera8-action.hf.space/api/webhook/upload', {
method: 'POST',
body: formData
});
if (!uploadResponse.ok) {
throw new Error("خطا در انتقال فایل صوتی به سرور اصلی جهت تقویت کیفیت.");
}
// ۲. ایجاد ساختار دستور پیکربندی برای گیت‌هاب اکشنز
const taqviatPayload = `VOICECONFIG_TAQVIAT_userRunId_${currentRunId}_solver_Midpoint_nfe_64_tau_0.5_denoising_true_ext_${ext}`;
// ۳. ارسال دستور کار صوتی به سیستم مدیریت موازی
const response = await fetch('https://opera8-action.hf.space/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: taqviatPayload,
action_name: 'taqviat-sada'
})
});
if (!response.ok) {
const errText = await response.text();
throw new Error(`پاسخ خطا از سرور مدل: ${response.status} - ${errText}`);
}
const result = await response.json();
const finalJobId = result.run_id || currentRunId;
return {
job_id: finalJobId,
total_chunks: 1,
chunks: []
};
};
export const checkEnhancementStatus = async (metadata: any): Promise<any> => {
const jobId = metadata.job_id;
// بررسی وضعیت از طریق پروکسی بک‌اند داخلی جهت تبدیل خودکار به بله
const response = await fetch(`/api/status/${jobId}`);
if (!response.ok) throw new Error("بررسی وضعیت تقویت صدا با خطا مواجه شد: " + response.status);
const data = await response.json();
let status = 'processing';
if (data.status === 'ready') status = 'completed';
else if (data.status === 'failed' || data.status === 'error') status = 'failed';
return {
status,
filename: jobId + ".wav", // ارجاع به آدرس فایل خروجی تقویت‌شده نهایی
downloadUrl: data.url // ذخیره لینک دائمی بله یا لوکال موقت
};
};
export const getEnhancementDownloadUrl = (filename: string): string => {
// این متد برای سازگاری در سوابق از downloadUrl مستقیم فیلد استفاده خواهد کرد.
return `/static/audio/${filename}`;
};
const generateBaseTTS = async (text: string, speakerId: string = "Algieba"): Promise<Blob> => {
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 })
});
if (!response.ok) throw new Error(`TTS Generation failed`);
// دریافت مستقیم فایل صوتی به صورت باینری (Blob)
return await response.blob();
};
export const fetchRefAudioAsFile = async (url: string, filename: string): Promise<File> => {
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<StatusResponse> => {
// هدایت بررسی فرآیندها به سمت اندپوینت پراکسی بک‌اند داخلی
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: 'درحال تلاش برای برقراری ارتباط با سرور...' };
}
}
// فرآیند فالبک تشخیص لایه‌های قدیمی
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<JobData> => {
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);
}
};