tts10 / templates /music.html
Opera8's picture
Update templates/music.html
847d040 verified
Raw
History Blame Contribute Delete
27.4 kB
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>پنل اختصاصی تولید موسیقی ACE-Step V1.5</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;700&display=swap');
body {
font-family: 'Vazirmatn', sans-serif;
background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
}
.glass-panel {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.progress-slim { height: 4px; border-radius: 10px; background: #1e293b; overflow: hidden; margin-top: 8px; }
.progress-bar-anim {
height: 100%; width: 100%;
animation: shimmy 1.5s infinite linear;
background: linear-gradient(90deg, #8b5cf6, #d946ef, #8b5cf6);
background-size: 200% 100%;
}
@keyframes shimmy { 0%{background-position:100% 0} 100%{background-position:-100% 0} }
</style>
</head>
<body class="min-h-screen text-slate-100 p-4 md:p-8 flex flex-col justify-between">
<div class="max-w-6xl mx-auto w-full">
<header class="text-center mb-8">
<h1 class="text-3xl md:text-5xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-violet-400 to-fuchsia-400 mb-2">
🎛️ کلاینت هوشمند ACE-Step V1.5 💡
</h1>
<p class="text-slate-400 text-sm md:text-base">پردازش و رندر ایمن و توزیع‌شده بر روی سرورهای ابری گیت‌هاب اکشنز</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<div class="lg:col-span-7 space-y-6">
<!-- تنظیمات موسیقی -->
<div class="glass-panel p-6 rounded-2xl shadow-xl space-y-4">
<h2 class="text-xl font-bold text-violet-300 border-b border-slate-700 pb-2 mb-4">
<i class="fa-solid fa-sliders ml-2"></i>تنظیمات موسیقی
</h2>
<div>
<label class="block text-sm font-semibold mb-1 text-slate-300" for="caption">توصیف سبک موسیقی (Prompt):</label>
<textarea id="caption" rows="3" class="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-sm focus:outline-none focus:border-violet-500 transition" placeholder="مثال: warm acoustic guitar, slow tempo, high quality, studio mastering..."></textarea>
</div>
<div>
<label class="block text-sm font-semibold mb-1 text-slate-300" for="lyrics">شعر یا متن لیریک (Lyrics):</label>
<textarea id="lyrics" rows="5" class="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-sm focus:outline-none focus:border-violet-500 transition" placeholder="مثال:&#10;[Verse 1]&#10;تو مثل بارونی رو تن صحرا..."></textarea>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="flex flex-col space-y-2 justify-center bg-slate-900/50 p-3 rounded-lg border border-slate-800">
<label class="flex items-center space-x-2 space-x-reverse cursor-pointer">
<input type="checkbox" id="think_checkbox" class="w-4 h-4 text-violet-600 bg-slate-800 border-slate-700 rounded focus:ring-violet-500" checked>
<span class="text-sm font-medium mr-2">تفکر هوش مصنوعی (Thinking)</span>
</label>
<label class="flex items-center space-x-2 space-x-reverse cursor-pointer">
<input type="checkbox" id="instrumental_checkbox" class="w-4 h-4 text-violet-600 bg-slate-800 border-slate-700 rounded focus:ring-violet-500">
<span class="text-sm font-medium mr-2">بی‌کلام (Instrumental)</span>
</label>
</div>
<div class="space-y-3 bg-slate-900/50 p-3 rounded-lg border border-slate-800">
<div>
<label class="block text-xs font-semibold text-slate-400 mb-1">زبان خواننده:</label>
<select id="vocal_language" class="w-full bg-slate-800 border border-slate-700 rounded-md p-1.5 text-xs">
<option value="fa" selected>فارسی (fa)</option>
<option value="en">انگلیسی (en)</option>
<option value="ar">عربی (ar)</option>
<option value="tr">ترکی (tr)</option>
<option value="unknown">نامشخص/بی‌کلام (unknown)</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-400 mb-1">تعداد تولید همزمان (Batch Size):</label>
<select id="batch_size" class="w-full bg-slate-800 border border-slate-700 rounded-md p-1.5 text-xs">
<option value="1" selected>۱ آهنگ</option>
<option value="2">۲ آهنگ</option>
</select>
</div>
</div>
</div>
</div>
<!-- آپلود فایل‌های صوتی (اختیاری) -->
<div class="glass-panel p-6 rounded-2xl shadow-xl space-y-4">
<h2 class="text-xl font-bold text-violet-300 border-b border-slate-700 pb-2">
<i class="fa-solid fa-music ml-2"></i>آپلود فایل مرجع (اختیاری)
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-400 mb-1">صدا یا سبک مرجع (Reference Audio):</label>
<input type="file" id="reference_audio" accept="audio/*" class="w-full text-xs text-slate-400 file:mr-0 file:ml-4 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:font-semibold file:bg-violet-600 file:text-white hover:file:bg-violet-700 cursor-pointer bg-slate-900 p-2 rounded-lg border border-slate-700">
</div>
<div>
<label class="block text-xs font-semibold text-slate-400 mb-1">آهنگ مبدا جهت بازخوانی (Source Audio):</label>
<input type="file" id="src_audio" accept="audio/*" class="w-full text-xs text-slate-400 file:mr-0 file:ml-4 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:font-semibold file:bg-violet-600 file:text-white hover:file:bg-violet-700 cursor-pointer bg-slate-900 p-2 rounded-lg border border-slate-700">
</div>
</div>
</div>
<!-- تنظیمات پیشرفته -->
<div class="glass-panel p-6 rounded-2xl shadow-xl space-y-4">
<button onclick="toggleAdvanced()" class="w-full text-right flex justify-between items-center text-sm font-bold text-slate-400 hover:text-slate-100 transition">
<span><i class="fa-solid fa-gears ml-2"></i>تنظیمات پیشرفته و ادامه تولید (Save & Resume)</span>
<i id="advanced_chevron" class="fa-solid fa-chevron-down transition-transform"></i>
</button>
<div id="advanced_section" class="hidden space-y-4 pt-2 border-t border-slate-800">
<div class="bg-violet-950/20 p-4 rounded-xl border border-violet-500/30">
<label class="block text-sm font-bold text-violet-300 mb-1">کد پیگیری (Tracking ID) جهت ادامه تولید:</label>
<input type="text" id="tracking_id" class="w-full bg-slate-900 border border-slate-700 rounded-lg p-2.5 text-sm uppercase tracking-widest text-center text-violet-200" placeholder="مثال: DXAVSN">
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-xs">
<div>
<label class="block text-slate-400 mb-1">تمپو (BPM):</label>
<input type="number" id="bpm" class="w-full bg-slate-900 border border-slate-700 rounded-md p-1.5" placeholder="BPM">
</div>
<div>
<label class="block text-slate-400 mb-1">کی‌اسکیل / گام:</label>
<input type="text" id="keyscale" class="w-full bg-slate-900 border border-slate-700 rounded-md p-1.5" placeholder="گام">
</div>
<div>
<label class="block text-slate-400 mb-1">میزان‌نما:</label>
<input type="text" id="timesignature" class="w-full bg-slate-900 border border-slate-700 rounded-md p-1.5" placeholder="کسر میزان">
</div>
<div>
<label class="block text-slate-400 mb-1">مدت زمان (ثانیه):</label>
<input type="number" id="duration" class="w-full bg-slate-900 border border-slate-700 rounded-md p-1.5" value="-1">
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-xs">
<div>
<label class="block text-slate-400 mb-1">تعداد مراحل:</label>
<input type="number" id="inference_steps" class="w-full bg-slate-900 border border-slate-700 rounded-md p-1.5" value="12">
</div>
<div>
<label class="block text-slate-400 mb-1">سید (Seed):</label>
<input type="text" id="seed_input" class="w-full bg-slate-900 border border-slate-700 rounded-md p-1.5" value="-1">
</div>
<div>
<label class="block text-slate-400 mb-1">فرمت فایل نهایی:</label>
<select id="audio_format" class="w-full bg-slate-900 border border-slate-700 rounded-md p-1.5">
<option value="mp3" selected>MP3</option>
<option value="flac">FLAC</option>
</select>
</div>
<div>
<label class="block text-slate-400 mb-1">نوع کار (Task Type):</label>
<select id="task_type" class="w-full bg-slate-900 border border-slate-700 rounded-md p-1.5">
<option value="text2music" selected>text2music</option>
<option value="cover">cover</option>
<option value="repaint">repaint</option>
</select>
</div>
</div>
</div>
</div>
<button id="generate_btn" onclick="startGeneration()" class="w-full py-4 px-6 rounded-2xl text-white font-extrabold text-lg bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 shadow-lg shadow-violet-700/20 active:scale-95 transition transform flex justify-center items-center gap-3">
<i class="fa-solid fa-music"></i>
<span>شروع فرآیند آهنگ‌سازی در گیت‌هاب</span>
</button>
</div>
<div class="lg:col-span-5 flex flex-col space-y-6">
<!-- تاریخچه کارها -->
<div class="glass-panel p-6 rounded-2xl shadow-xl flex-1 flex flex-col justify-between">
<div>
<div class="flex justify-between items-center border-b border-slate-700 pb-3 mb-4">
<h2 class="text-xl font-bold text-fuchsia-300">
<i class="fa-solid fa-history ml-2"></i>پروژه‌های من
</h2>
<button onclick="clearHistory()" class="text-xs text-red-400 hover:text-red-300 font-bold transition">
<i class="fa-solid fa-trash-alt"></i> پاک‌سازی تاریخچه
</button>
</div>
<div id="history_list" class="space-y-4 max-h-[420px] overflow-y-auto">
<!-- به صورت پویا رندر می‌شود -->
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="text-center text-xs text-slate-500 py-6 mt-8 border-t border-slate-900 max-w-6xl mx-auto w-full">
ACE-Step Music Action Client &copy; 2026 | اتصال توزیع شده به گیت‌هاب اکشنز جهت محاسبات نامحدود
</footer>
<script>
let pollIntervals = {};
function toggleAdvanced() {
const advancedSection = document.getElementById("advanced_section");
const chevron = document.getElementById("advanced_chevron");
advancedSection.classList.toggle("hidden");
chevron.classList.toggle("rotate-180");
}
function getJobs() { return JSON.parse(localStorage.getItem('ace_music_jobs_v1') || '[]'); }
function saveJob(job) {
const jobs = getJobs();
jobs.unshift(job);
localStorage.setItem('ace_music_jobs_v1', JSON.stringify(jobs));
renderHistory();
}
function renameJobId(oldId, newId) {
const jobs = getJobs();
const idx = jobs.findIndex(j => j.id === oldId);
if (idx !== -1) {
jobs[idx].id = newId;
localStorage.setItem('ace_music_jobs_v1', JSON.stringify(jobs));
}
}
function updateJobStatus(id, status, filename, logMsg) {
const jobs = getJobs();
const idx = jobs.findIndex(j => j.id === id);
if (idx !== -1) {
jobs[idx].status = status;
if(filename) jobs[idx].filename = filename;
if(logMsg) jobs[idx].log = logMsg;
localStorage.setItem('ace_music_jobs_v1', JSON.stringify(jobs));
renderHistory();
}
}
function deleteJob(id) {
const jobs = getJobs().filter(j => j.id !== id);
localStorage.setItem('ace_music_jobs_v1', JSON.stringify(jobs));
if (pollIntervals[id]) clearInterval(pollIntervals[id]);
renderHistory();
}
function clearHistory() {
if(confirm("تمام پروژه‌های موسیقی شما پاک شود؟")) {
Object.values(pollIntervals).forEach(clearInterval);
localStorage.removeItem('ace_music_jobs_v1');
renderHistory();
}
}
function renderHistory() {
const jobs = getJobs();
const list = document.getElementById('history_list');
list.innerHTML = '';
if (jobs.length === 0) {
list.innerHTML = `<div class="text-center py-8 text-slate-500 text-sm">لیست پروژه‌های تولید شده خالی است.</div>`;
return;
}
jobs.forEach(job => {
let badge = '';
let content = '';
if (job.status === 'completed') {
badge = `<span class="text-xs bg-emerald-500/20 text-emerald-400 px-2.5 py-0.5 rounded font-bold">رندر شد</span>`;
const batch2Url = job.filename.includes("_batch2") ? "" : job.filename.replace(/\.(mp3|flac|wav)$/, "_batch2.$1");
content = `
<div class="mt-2 space-y-2">
<div class="text-xs font-semibold text-violet-400">قطعه شماره ۱:</div>
<audio controls src="${job.filename}" class="w-full h-8 rounded bg-slate-900"></audio>
<div class="flex justify-end pt-1">
<a href="${job.filename}" download class="text-xs text-sky-400 hover:underline"><i class="fa-solid fa-download mr-1"></i>دانلود ترک اصلی</a>
</div>
`;
if (job.batch_size == 2) {
content += `
<div class="text-xs font-semibold text-fuchsia-400 mt-2">قطعه شماره ۲:</div>
<audio controls src="${batch2Url}" class="w-full h-8 rounded bg-slate-900" onerror="this.style.display='none'"></audio>
<div class="flex justify-end">
<a href="${batch2Url}" download class="text-xs text-fuchsia-400 hover:underline" onerror="this.style.display='none'"><i class="fa-solid fa-download mr-1"></i>دانلود ترک دوم</a>
</div>
`;
}
content += `</div>`;
} else if (job.status === 'failed') {
badge = `<span class="text-xs bg-red-500/20 text-red-400 px-2.5 py-0.5 rounded font-bold">ناموفق</span>`;
content = `<div class="mt-2 text-xs text-red-400 bg-red-950/20 p-3 rounded border border-red-900/30">${job.log || 'خطا در رانر گیت‌هاب'}</div>`;
} else {
badge = `<span class="text-xs bg-amber-500/20 text-amber-400 px-2.5 py-0.5 rounded font-bold">در حال پردازش</span>`;
content = `
<div class="mt-2 text-xs text-slate-400">
<div>${job.log || 'دریافت صف...'}</div>
<div class="progress-slim"><div class="progress-bar-anim"></div></div>
</div>`;
}
const item = document.createElement('div');
item.className = "bg-slate-950 p-4 rounded-xl border border-slate-800 relative flex flex-col";
item.innerHTML = `
<button class="absolute top-3 left-3 text-slate-500 hover:text-red-400 text-lg transition" onclick="deleteJob('${job.id}')">&times;</button>
<div class="flex justify-between items-center mb-1">
<span class="text-sm font-bold text-slate-200">پروژه #${job.id.substring(0, 6)}</span>
${badge}
</div>
<span class="text-[10px] text-slate-500">${job.date}</span>
${content}
`;
list.appendChild(item);
});
}
async function uploadFileToDocker(file, runId, suffix) {
const formData = new FormData();
const ext = file.name.split('.').pop().toLowerCase();
formData.append('run_id', `${runId}_music_${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;
}
function safeBase64Encode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => {
return String.fromCharCode('0x' + p1);
}));
}
function startPolling(runId, batchSize = 1) {
if (pollIntervals[runId]) clearInterval(pollIntervals[runId]);
pollIntervals[runId] = setInterval(async () => {
try {
const statusRes = await fetch(`/api/status/${runId}`);
const statusData = await statusRes.json();
if (statusData.status === 'ready') {
clearInterval(pollIntervals[runId]);
updateJobStatus(runId, 'completed', statusData.url, 'تولید موسیقی با موفقیت تکمیل شد.');
} else if (statusData.status === 'failed') {
clearInterval(pollIntervals[runId]);
updateJobStatus(runId, 'failed', null, statusData.message || 'خطا در کامپایل نهایی.');
} else {
const activeRes = await fetch('/api/actions/active');
const activeData = await activeRes.json();
const hasActiveActions = activeData.status === 'success' && activeData.actions.length > 0;
if (hasActiveActions) {
updateJobStatus(runId, 'processing', null, 'رانر فعال شد. مدل در حال تحلیل لیریک و تولید موزیک در کانتینر ZeroGPU است...');
} else {
updateJobStatus(runId, 'processing', null, 'در صف رزرو پردازشگر ابری... (اگر رانر آفلاین باشد، تا ۲۰ تلاش پایتون ادامه می‌یابد)');
}
}
} catch (e) {
console.error("Error polling:", e);
}
}, 5000);
}
async function startGeneration() {
const btn = document.getElementById("generate_btn");
const refFileInput = document.getElementById("reference_audio");
const srcFileInput = document.getElementById("src_audio");
btn.disabled = true;
btn.classList.add("opacity-50", "pointer-events-none");
const currentRunId = "mus" + Math.random().toString(36).substring(2, 14);
const batchSize = Number(document.getElementById("batch_size").value);
try {
saveJob({
id: currentRunId,
status: 'processing',
date: new Date().toLocaleTimeString('fa-IR'),
log: 'در حال بسته‌بندی فایل‌ها و ارسال به هاست واسط...',
filename: null,
batch_size: batchSize
});
let refExt = null;
let srcExt = null;
if (refFileInput.files[0]) {
refExt = await uploadFileToDocker(refFileInput.files[0], currentRunId, 'ref');
}
if (srcFileInput.files[0]) {
srcExt = await uploadFileToDocker(srcFileInput.files[0], currentRunId, 'src');
}
const musicPayload = {
userRunId: currentRunId,
caption: document.getElementById("caption").value,
lyrics: document.getElementById("lyrics").value,
think_checkbox: document.getElementById("think_checkbox").checked,
instrumental_checkbox: document.getElementById("instrumental_checkbox").checked,
vocal_language: document.getElementById("vocal_language").value,
batch_size: batchSize,
bpm: document.getElementById("bpm").value,
keyscale: document.getElementById("keyscale").value,
timesignature: document.getElementById("timesignature").value,
duration: document.getElementById("duration").value,
inference_steps: document.getElementById("inference_steps").value,
seed_input: document.getElementById("seed_input").value,
audio_format: document.getElementById("audio_format").value,
task_type: document.getElementById("task_type").value,
tracking_id: document.getElementById("tracking_id").value,
ref_audio_ext: refExt,
src_audio_ext: srcExt
};
const b64Payload = safeBase64Encode(JSON.stringify(musicPayload));
const finalPrompt = `VOICECONFIG_MUSIC_${b64Payload}`;
updateJobStatus(currentRunId, 'processing', null, 'در حال راه‌اندازی فرآیند در صف موازی گیت‌هاب...');
const response = await fetch('/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: finalPrompt,
action_name: 'music'
})
});
const data = await response.json();
if (data.status === 'success') {
renameJobId(currentRunId, data.run_id);
startPolling(data.run_id, batchSize);
refFileInput.value = "";
srcFileInput.value = "";
} else {
throw new Error(data.message || 'ثبت نوبت در گیت‌هاب ناموفق بود.');
}
} catch (err) {
updateJobStatus(currentRunId, 'failed', null, err.message || "خطا در شروع عملیات.");
} finally {
btn.disabled = false;
btn.classList.remove("opacity-50", "pointer-events-none");
}
}
window.addEventListener('DOMContentLoaded', () => {
renderHistory();
getJobs().forEach(job => {
if (job.status === 'processing') {
startPolling(job.id, job.batch_size || 1);
}
});
});
</script>
</body>
</html>