apolinario commited on
Commit
e4c1bcb
·
1 Parent(s): cdac885

Add Transcribe tab powered by Cohere Transcribe

Browse files
Files changed (1) hide show
  1. 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">k2-fsa/OmniVoice</a> & <a href="https://huggingface.co/spaces/Qwen/Qwen3-TTS" target="_blank">Qwen3-TTS</a></div>
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() {