Spaces:
Running
Running
Commit ·
e4c1bcb
1
Parent(s): cdac885
Add Transcribe tab powered by Cohere Transcribe
Browse files- index.html +201 -1
index.html
CHANGED
|
@@ -803,6 +803,10 @@ pre {
|
|
| 803 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
| 804 |
Voice Library
|
| 805 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 806 |
<button class="nav-item" data-page="history" onclick="showPage('history', this)">
|
| 807 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
| 808 |
History
|
|
@@ -1010,6 +1014,54 @@ pre {
|
|
| 1010 |
<div class="voices-grid" id="presets-grid"></div>
|
| 1011 |
</div>
|
| 1012 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1013 |
<!-- History Page -->
|
| 1014 |
<div class="page" id="page-history">
|
| 1015 |
<div class="page-header">
|
|
@@ -1123,7 +1175,7 @@ result = client.<span class="f">predict</span>(
|
|
| 1123 |
</div>
|
| 1124 |
</div>
|
| 1125 |
|
| 1126 |
-
<div class="footer">Powered by <a href="https://huggingface.co/spaces/k2-fsa/OmniVoice" target="_blank">
|
| 1127 |
</div>
|
| 1128 |
</div>
|
| 1129 |
</div>
|
|
@@ -1698,6 +1750,154 @@ window.saveDesignVoice = async function() {
|
|
| 1698 |
}
|
| 1699 |
};
|
| 1700 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1701 |
// ── OAuth ──
|
| 1702 |
|
| 1703 |
async function initOAuth() {
|
|
|
|
| 803 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
| 804 |
Voice Library
|
| 805 |
</button>
|
| 806 |
+
<button class="nav-item" data-page="transcribe" onclick="showPage('transcribe', this)">
|
| 807 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg>
|
| 808 |
+
Transcribe
|
| 809 |
+
</button>
|
| 810 |
<button class="nav-item" data-page="history" onclick="showPage('history', this)">
|
| 811 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
| 812 |
History
|
|
|
|
| 1014 |
<div class="voices-grid" id="presets-grid"></div>
|
| 1015 |
</div>
|
| 1016 |
|
| 1017 |
+
<!-- Transcribe Page -->
|
| 1018 |
+
<div class="page" id="page-transcribe">
|
| 1019 |
+
<div class="page-header">
|
| 1020 |
+
<div class="page-title">Transcribe</div>
|
| 1021 |
+
<div class="page-desc">Upload an audio file to transcribe it to text. Powered by Cohere Transcribe.</div>
|
| 1022 |
+
</div>
|
| 1023 |
+
|
| 1024 |
+
<div class="upload-area" id="transcribe-upload" style="margin-bottom:16px">
|
| 1025 |
+
<div id="transcribe-upload-label">Drop an audio file here or click to upload<div class="upload-area-sub">MP3, WAV, FLAC, M4A supported</div></div>
|
| 1026 |
+
<input type="file" id="transcribe-file-input" accept="audio/*">
|
| 1027 |
+
</div>
|
| 1028 |
+
|
| 1029 |
+
<div style="display:flex;gap:12px;align-items:center;margin-bottom:16px">
|
| 1030 |
+
<select class="lang-select" id="transcribe-lang" style="padding:8px 12px;font-size:13px">
|
| 1031 |
+
<option value="en">English</option>
|
| 1032 |
+
<option value="fr">French</option>
|
| 1033 |
+
<option value="de">German</option>
|
| 1034 |
+
<option value="es">Spanish</option>
|
| 1035 |
+
<option value="pt">Portuguese</option>
|
| 1036 |
+
<option value="it">Italian</option>
|
| 1037 |
+
<option value="nl">Dutch</option>
|
| 1038 |
+
<option value="pl">Polish</option>
|
| 1039 |
+
<option value="el">Greek</option>
|
| 1040 |
+
<option value="ar">Arabic</option>
|
| 1041 |
+
<option value="ja">Japanese</option>
|
| 1042 |
+
<option value="ko">Korean</option>
|
| 1043 |
+
<option value="zh">Chinese</option>
|
| 1044 |
+
<option value="vi">Vietnamese</option>
|
| 1045 |
+
</select>
|
| 1046 |
+
<button class="btn-generate" id="transcribe-btn" onclick="transcribe()" disabled style="flex:1">
|
| 1047 |
+
<span class="btn-text">Transcribe</span>
|
| 1048 |
+
<div class="spinner"></div>
|
| 1049 |
+
</button>
|
| 1050 |
+
</div>
|
| 1051 |
+
|
| 1052 |
+
<div class="status-bar" id="transcribe-status"></div>
|
| 1053 |
+
|
| 1054 |
+
<div id="transcribe-result" style="display:none">
|
| 1055 |
+
<div class="setting-label" style="margin-bottom:8px;margin-top:16px">Transcript</div>
|
| 1056 |
+
<div style="background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:16px;font-size:14px;line-height:1.6;position:relative">
|
| 1057 |
+
<div id="transcribe-text"></div>
|
| 1058 |
+
<button class="copy-btn" onclick="copyTranscript()" style="position:absolute;top:8px;right:8px">
|
| 1059 |
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Copy
|
| 1060 |
+
</button>
|
| 1061 |
+
</div>
|
| 1062 |
+
</div>
|
| 1063 |
+
</div>
|
| 1064 |
+
|
| 1065 |
<!-- History Page -->
|
| 1066 |
<div class="page" id="page-history">
|
| 1067 |
<div class="page-header">
|
|
|
|
| 1175 |
</div>
|
| 1176 |
</div>
|
| 1177 |
|
| 1178 |
+
<div class="footer">Powered by <a href="https://huggingface.co/spaces/k2-fsa/OmniVoice" target="_blank">OmniVoice</a>, <a href="https://huggingface.co/spaces/Qwen/Qwen3-TTS" target="_blank">Qwen3-TTS</a> & <a href="https://huggingface.co/spaces/CohereLabs/cohere-transcribe-03-2026" target="_blank">Cohere Transcribe</a></div>
|
| 1179 |
</div>
|
| 1180 |
</div>
|
| 1181 |
</div>
|
|
|
|
| 1750 |
}
|
| 1751 |
};
|
| 1752 |
|
| 1753 |
+
// ── Transcribe (Cohere) ──
|
| 1754 |
+
|
| 1755 |
+
const COHERE_BASE = "https://coherelabs-cohere-transcribe-03-2026.hf.space/gradio_api";
|
| 1756 |
+
let transcribeFile = null;
|
| 1757 |
+
|
| 1758 |
+
const transcribeUpload = document.getElementById("transcribe-upload");
|
| 1759 |
+
const transcribeFileInput = document.getElementById("transcribe-file-input");
|
| 1760 |
+
const transcribeBtn = document.getElementById("transcribe-btn");
|
| 1761 |
+
|
| 1762 |
+
transcribeUpload.addEventListener("click", () => transcribeFileInput.click());
|
| 1763 |
+
transcribeUpload.addEventListener("dragover", e => e.preventDefault());
|
| 1764 |
+
transcribeUpload.addEventListener("drop", e => {
|
| 1765 |
+
e.preventDefault();
|
| 1766 |
+
if (e.dataTransfer.files.length) handleTranscribeFile(e.dataTransfer.files[0]);
|
| 1767 |
+
});
|
| 1768 |
+
transcribeFileInput.addEventListener("change", () => {
|
| 1769 |
+
if (transcribeFileInput.files.length) handleTranscribeFile(transcribeFileInput.files[0]);
|
| 1770 |
+
});
|
| 1771 |
+
|
| 1772 |
+
function handleTranscribeFile(file) {
|
| 1773 |
+
transcribeFile = file;
|
| 1774 |
+
document.getElementById("transcribe-upload-label").textContent = file.name;
|
| 1775 |
+
transcribeUpload.classList.add("has-file");
|
| 1776 |
+
transcribeBtn.disabled = false;
|
| 1777 |
+
}
|
| 1778 |
+
|
| 1779 |
+
window.transcribe = async function() {
|
| 1780 |
+
if (!transcribeFile) return;
|
| 1781 |
+
const lang = document.getElementById("transcribe-lang").value;
|
| 1782 |
+
const btn = transcribeBtn;
|
| 1783 |
+
const result = document.getElementById("transcribe-result");
|
| 1784 |
+
|
| 1785 |
+
btn.disabled = true;
|
| 1786 |
+
btn.classList.add("loading");
|
| 1787 |
+
result.style.display = "none";
|
| 1788 |
+
showTranscribeStatus("info", "Uploading audio...");
|
| 1789 |
+
|
| 1790 |
+
try {
|
| 1791 |
+
const form = new FormData();
|
| 1792 |
+
form.append("files", transcribeFile);
|
| 1793 |
+
const uploadRes = await fetch(`${COHERE_BASE}/upload`, {
|
| 1794 |
+
method: "POST",
|
| 1795 |
+
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
|
| 1796 |
+
body: form
|
| 1797 |
+
});
|
| 1798 |
+
const [uploadPath] = await uploadRes.json();
|
| 1799 |
+
|
| 1800 |
+
showTranscribeStatus("info", "Transcribing...");
|
| 1801 |
+
const sessionHash = crypto.randomUUID();
|
| 1802 |
+
|
| 1803 |
+
const joinRes = await fetch(`${COHERE_BASE}/queue/join`, {
|
| 1804 |
+
method: "POST",
|
| 1805 |
+
headers: {
|
| 1806 |
+
"Content-Type": "application/json",
|
| 1807 |
+
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {})
|
| 1808 |
+
},
|
| 1809 |
+
body: JSON.stringify({
|
| 1810 |
+
data: [
|
| 1811 |
+
{ path: uploadPath, meta: { _type: "gradio.FileData" }, orig_name: transcribeFile.name },
|
| 1812 |
+
lang
|
| 1813 |
+
],
|
| 1814 |
+
fn_index: 2,
|
| 1815 |
+
session_hash: sessionHash
|
| 1816 |
+
})
|
| 1817 |
+
});
|
| 1818 |
+
if (!joinRes.ok) throw new Error(`Queue join failed: ${joinRes.status}`);
|
| 1819 |
+
|
| 1820 |
+
const streamRes = await fetch(
|
| 1821 |
+
`${COHERE_BASE}/queue/data?session_hash=${sessionHash}`,
|
| 1822 |
+
{ headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {} }
|
| 1823 |
+
);
|
| 1824 |
+
|
| 1825 |
+
const reader = streamRes.body.getReader();
|
| 1826 |
+
const decoder = new TextDecoder();
|
| 1827 |
+
let buffer = "";
|
| 1828 |
+
|
| 1829 |
+
while (true) {
|
| 1830 |
+
const { done, value } = await reader.read();
|
| 1831 |
+
if (done) break;
|
| 1832 |
+
buffer += decoder.decode(value, { stream: true });
|
| 1833 |
+
const lines = buffer.split("\n\n");
|
| 1834 |
+
buffer = lines.pop();
|
| 1835 |
+
|
| 1836 |
+
for (const line of lines) {
|
| 1837 |
+
if (!line.startsWith("data: ")) continue;
|
| 1838 |
+
const data = JSON.parse(line.slice(6));
|
| 1839 |
+
|
| 1840 |
+
if (data.msg === "estimation") showTranscribeStatus("info", `In queue (position ${data.rank})...`);
|
| 1841 |
+
else if (data.msg === "process_starts") showTranscribeStatus("info", "Transcribing...");
|
| 1842 |
+
else if (data.msg === "log") showTranscribeStatus("info", data.log);
|
| 1843 |
+
else if (data.msg === "process_completed") {
|
| 1844 |
+
if (data.success) {
|
| 1845 |
+
const transcript = data.output.data[0];
|
| 1846 |
+
document.getElementById("transcribe-text").textContent = transcript;
|
| 1847 |
+
result.style.display = "block";
|
| 1848 |
+
showTranscribeStatus("success", `Transcribed in ${data.output.duration?.toFixed(1)}s`);
|
| 1849 |
+
updateUsageTracker(data.output.duration * 0.5);
|
| 1850 |
+
} else {
|
| 1851 |
+
showTranscribeStatusWithCTA(data.output?.error || "Transcription failed");
|
| 1852 |
+
}
|
| 1853 |
+
}
|
| 1854 |
+
}
|
| 1855 |
+
}
|
| 1856 |
+
} catch (err) {
|
| 1857 |
+
showTranscribeStatusWithCTA(err.message);
|
| 1858 |
+
} finally {
|
| 1859 |
+
btn.disabled = false;
|
| 1860 |
+
btn.classList.remove("loading");
|
| 1861 |
+
}
|
| 1862 |
+
};
|
| 1863 |
+
|
| 1864 |
+
function showTranscribeStatus(type, msg) {
|
| 1865 |
+
const el = document.getElementById("transcribe-status");
|
| 1866 |
+
el.className = `status-bar visible ${type}`;
|
| 1867 |
+
el.innerHTML = `<div class="status-text">${msg}</div>`;
|
| 1868 |
+
}
|
| 1869 |
+
|
| 1870 |
+
function showTranscribeStatusWithCTA(errMsg) {
|
| 1871 |
+
const el = document.getElementById("transcribe-status");
|
| 1872 |
+
const isQuotaError = errMsg.includes("GPU quota");
|
| 1873 |
+
const isFreeQuota = errMsg.includes("free GPU quota");
|
| 1874 |
+
let html = `<div class="status-text">${errMsg}</div>`;
|
| 1875 |
+
if (isQuotaError) {
|
| 1876 |
+
if (isFreeQuota) {
|
| 1877 |
+
html += `<a class="status-cta upgrade" href="https://huggingface.co/subscribe/pro" target="_blank">🤗 Upgrade to HF Pro — 25 min/day GPU quota</a>`;
|
| 1878 |
+
} else {
|
| 1879 |
+
html += `<a class="status-cta credits" href="https://huggingface.co/settings/billing" target="_blank">Buy additional GPU credits — $1 per 10 min</a>`;
|
| 1880 |
+
}
|
| 1881 |
+
const m = errMsg.match(/Try again in ([\d:]+)/);
|
| 1882 |
+
if (m && m[1] !== "0:00:00") html += `<div style="font-size:11px;color:var(--text-muted);margin-top:2px">Or wait ${m[1]} for your quota to reset</div>`;
|
| 1883 |
+
}
|
| 1884 |
+
el.className = "status-bar visible error";
|
| 1885 |
+
el.innerHTML = html;
|
| 1886 |
+
}
|
| 1887 |
+
|
| 1888 |
+
window.copyTranscript = function() {
|
| 1889 |
+
const text = document.getElementById("transcribe-text").textContent;
|
| 1890 |
+
navigator.clipboard.writeText(text).then(() => {
|
| 1891 |
+
const btn = document.querySelector("#transcribe-result .copy-btn");
|
| 1892 |
+
btn.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg> Copied!';
|
| 1893 |
+
btn.classList.add("copied");
|
| 1894 |
+
setTimeout(() => {
|
| 1895 |
+
btn.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Copy';
|
| 1896 |
+
btn.classList.remove("copied");
|
| 1897 |
+
}, 2000);
|
| 1898 |
+
});
|
| 1899 |
+
};
|
| 1900 |
+
|
| 1901 |
// ── OAuth ──
|
| 1902 |
|
| 1903 |
async function initOAuth() {
|