Spaces:
Running
Running
Update app.js
Browse files
app.js
CHANGED
|
@@ -7,6 +7,7 @@ window.onerror = (msg, url, line) => {
|
|
| 7 |
// CONFIG & STATE
|
| 8 |
// ============================================================
|
| 9 |
const STREAM_API_URL = "https://aqhedgyiworhbfkrzreb.supabase.co/functions/v1/quick-processor";
|
|
|
|
| 10 |
const RAZORPAY_KEY_ID = "rzp_test_TZ9SeSSO5G8eCq";
|
| 11 |
const firebaseConfig = { apiKey: "AIzaSyAChu97QI-tyulpisxH6aECBYlj88DGB1o", authDomain: "arki-ai-280b8.firebaseapp.com", projectId: "arki-ai-280b8", storageBucket: "arki-ai-280b8.firebasestorage.app", messagingSenderId: "1075792140029", appId: "1:1075792140029:web:9f3af7c6b89530c7ee9094", measurementId: "G-PL1YY34MBZ" };
|
| 12 |
|
|
@@ -35,7 +36,7 @@ let userPersona = safeGet('arki_persona', ''), userName = safeGet('arki_username
|
|
| 35 |
let currentUser = null, currentPlan = "Free", selectedModel = 'orki-3.0', currentDailyUsage = 0;
|
| 36 |
let isAuthRegister = false, abortController = null, isGenerating = false, currentChatId = null;
|
| 37 |
let currentChatMessages = [], selectedCycle = 'monthly', cachedThreads = [], isIncognito = false;
|
| 38 |
-
let mediaRecorder = null, audioChunks = [], isRecording = false
|
| 39 |
let currentAudio = null, activeTtsBtn = null, ttsAudioCache = new Map();
|
| 40 |
let auth = null, db = null;
|
| 41 |
|
|
@@ -147,7 +148,7 @@ function updateUiLangUI() {
|
|
| 147 |
}
|
| 148 |
|
| 149 |
// ============================================================
|
| 150 |
-
// CORE LOGIC
|
| 151 |
// ============================================================
|
| 152 |
function buildPersonaContext() {
|
| 153 |
return [userName ? `The user's name is ${userName}; address them by name naturally.` : '', userPersona].filter(Boolean).join(' ');
|
|
@@ -178,26 +179,30 @@ function toggleDrawer(open) {
|
|
| 178 |
drawerBackdrop.classList.toggle('hidden', !open);
|
| 179 |
}
|
| 180 |
|
| 181 |
-
async function getGradioClient() {
|
| 182 |
-
if (!gradioClient) {
|
| 183 |
-
const { Client } = await import("https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js");
|
| 184 |
-
gradioClient = await Client.connect("ai4bharat/indic-parler-tts");
|
| 185 |
-
}
|
| 186 |
-
return gradioClient;
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
function cleanTextForTTS(rawText) {
|
| 190 |
-
return rawText
|
|
|
|
|
|
|
|
|
|
| 191 |
}
|
| 192 |
|
| 193 |
async function playBodoSpeech(btn, text) {
|
| 194 |
-
if (activeTtsBtn === btn && currentAudio && !currentAudio.paused) {
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
const cleanText = cleanTextForTTS(text);
|
| 197 |
if (!cleanText) return;
|
| 198 |
|
| 199 |
if (ttsAudioCache.has(cleanText)) {
|
| 200 |
-
activeTtsBtn = btn;
|
|
|
|
|
|
|
| 201 |
currentAudio = new Audio(ttsAudioCache.get(cleanText));
|
| 202 |
currentAudio.onended = () => { btn.innerHTML = `🔊 Listen`; activeTtsBtn = null; currentAudio = null; };
|
| 203 |
try { await currentAudio.play(); } catch { btn.innerHTML = `⚠️ Retry`; }
|
|
@@ -205,36 +210,50 @@ async function playBodoSpeech(btn, text) {
|
|
| 205 |
}
|
| 206 |
|
| 207 |
try {
|
| 208 |
-
activeTtsBtn = btn;
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
ttsAudioCache.set(cleanText, audioUrl);
|
| 219 |
-
btn.disabled = false;
|
|
|
|
| 220 |
currentAudio = new Audio(audioUrl);
|
| 221 |
currentAudio.onended = () => { btn.innerHTML = `🔊 Listen`; activeTtsBtn = null; currentAudio = null; };
|
| 222 |
await currentAudio.play();
|
| 223 |
} catch (err) {
|
| 224 |
-
console.error("TTS error:", err);
|
|
|
|
|
|
|
| 225 |
alert("Voice queue is busy or timed out. Please tap retry in a moment.");
|
| 226 |
}
|
| 227 |
}
|
| 228 |
|
| 229 |
function attachTtsButton(bubble, text) {
|
| 230 |
-
if (!bubble) return;
|
| 231 |
const actionRow = document.createElement('div');
|
| 232 |
actionRow.className = "flex items-center gap-2 mt-2 pt-1 border-t border-zinc-700/30";
|
| 233 |
const ttsBtn = document.createElement('button');
|
| 234 |
-
ttsBtn.type = "button";
|
|
|
|
| 235 |
ttsBtn.innerHTML = `🔊 Listen`;
|
| 236 |
ttsBtn.onclick = () => playBodoSpeech(ttsBtn, text.trim());
|
| 237 |
-
actionRow.appendChild(ttsBtn);
|
|
|
|
| 238 |
}
|
| 239 |
|
| 240 |
function createBubbleElement(role) {
|
|
@@ -358,7 +377,13 @@ async function handleSend() {
|
|
| 358 |
attachTtsButton(aiBubble, finalizedText);
|
| 359 |
} else {
|
| 360 |
thinking && thinking.classList.add('hidden');
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
}
|
| 363 |
} finally {
|
| 364 |
thinking && thinking.classList.add('hidden');
|
|
@@ -762,4 +787,4 @@ on('auth-submit-btn', 'click', async () => {
|
|
| 762 |
// ============================================================
|
| 763 |
// INIT
|
| 764 |
// ============================================================
|
| 765 |
-
updateScriptUI(); updateUiLangUI(); updatePricingUI(); updateUsageUI(); updateIncognitoUI(); applyUILanguage(uiLanguage);
|
|
|
|
| 7 |
// CONFIG & STATE
|
| 8 |
// ============================================================
|
| 9 |
const STREAM_API_URL = "https://aqhedgyiworhbfkrzreb.supabase.co/functions/v1/quick-processor";
|
| 10 |
+
const TTS_API_URL = "https://aqhedgyiworhbfkrzreb.supabase.co/functions/v1/swift-worker";
|
| 11 |
const RAZORPAY_KEY_ID = "rzp_test_TZ9SeSSO5G8eCq";
|
| 12 |
const firebaseConfig = { apiKey: "AIzaSyAChu97QI-tyulpisxH6aECBYlj88DGB1o", authDomain: "arki-ai-280b8.firebaseapp.com", projectId: "arki-ai-280b8", storageBucket: "arki-ai-280b8.firebasestorage.app", messagingSenderId: "1075792140029", appId: "1:1075792140029:web:9f3af7c6b89530c7ee9094", measurementId: "G-PL1YY34MBZ" };
|
| 13 |
|
|
|
|
| 36 |
let currentUser = null, currentPlan = "Free", selectedModel = 'orki-3.0', currentDailyUsage = 0;
|
| 37 |
let isAuthRegister = false, abortController = null, isGenerating = false, currentChatId = null;
|
| 38 |
let currentChatMessages = [], selectedCycle = 'monthly', cachedThreads = [], isIncognito = false;
|
| 39 |
+
let mediaRecorder = null, audioChunks = [], isRecording = false;
|
| 40 |
let currentAudio = null, activeTtsBtn = null, ttsAudioCache = new Map();
|
| 41 |
let auth = null, db = null;
|
| 42 |
|
|
|
|
| 148 |
}
|
| 149 |
|
| 150 |
// ============================================================
|
| 151 |
+
// CORE LOGIC & TTS ENGINE
|
| 152 |
// ============================================================
|
| 153 |
function buildPersonaContext() {
|
| 154 |
return [userName ? `The user's name is ${userName}; address them by name naturally.` : '', userPersona].filter(Boolean).join(' ');
|
|
|
|
| 179 |
drawerBackdrop.classList.toggle('hidden', !open);
|
| 180 |
}
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
function cleanTextForTTS(rawText) {
|
| 183 |
+
return rawText
|
| 184 |
+
.replace(/[#*_`~>\[\]\(\)]/g, '')
|
| 185 |
+
.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{2600}-\u{26FF}]/gu, '')
|
| 186 |
+
.trim();
|
| 187 |
}
|
| 188 |
|
| 189 |
async function playBodoSpeech(btn, text) {
|
| 190 |
+
if (activeTtsBtn === btn && currentAudio && !currentAudio.paused) {
|
| 191 |
+
currentAudio.pause();
|
| 192 |
+
btn.innerHTML = `🔊 Listen`;
|
| 193 |
+
return;
|
| 194 |
+
}
|
| 195 |
+
if (currentAudio) {
|
| 196 |
+
currentAudio.pause();
|
| 197 |
+
if (activeTtsBtn) activeTtsBtn.innerHTML = `🔊 Listen`;
|
| 198 |
+
}
|
| 199 |
const cleanText = cleanTextForTTS(text);
|
| 200 |
if (!cleanText) return;
|
| 201 |
|
| 202 |
if (ttsAudioCache.has(cleanText)) {
|
| 203 |
+
activeTtsBtn = btn;
|
| 204 |
+
btn.disabled = false;
|
| 205 |
+
btn.innerHTML = `⏹ Stop`;
|
| 206 |
currentAudio = new Audio(ttsAudioCache.get(cleanText));
|
| 207 |
currentAudio.onended = () => { btn.innerHTML = `🔊 Listen`; activeTtsBtn = null; currentAudio = null; };
|
| 208 |
try { await currentAudio.play(); } catch { btn.innerHTML = `⚠️ Retry`; }
|
|
|
|
| 210 |
}
|
| 211 |
|
| 212 |
try {
|
| 213 |
+
activeTtsBtn = btn;
|
| 214 |
+
btn.disabled = true;
|
| 215 |
+
btn.innerHTML = `<span class="animate-pulse">⏳ Rendering...</span>`;
|
| 216 |
+
|
| 217 |
+
const res = await fetch(TTS_API_URL, {
|
| 218 |
+
method: 'POST',
|
| 219 |
+
headers: { 'Content-Type': 'application/json' },
|
| 220 |
+
body: JSON.stringify({ text: cleanText })
|
| 221 |
+
});
|
| 222 |
+
|
| 223 |
+
if (!res.ok) {
|
| 224 |
+
const errData = await res.json().catch(() => ({}));
|
| 225 |
+
throw new Error(errData.error || `Server status ${res.status}`);
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
const data = await res.json();
|
| 229 |
+
const audioUrl = data.audioUrl || (data.data && (typeof data.data[0] === 'string' ? data.data[0] : data.data[0]?.url));
|
| 230 |
+
if (!audioUrl) throw new Error("Audio URL missing from TTS worker");
|
| 231 |
+
|
| 232 |
ttsAudioCache.set(cleanText, audioUrl);
|
| 233 |
+
btn.disabled = false;
|
| 234 |
+
btn.innerHTML = `⏹ Stop`;
|
| 235 |
currentAudio = new Audio(audioUrl);
|
| 236 |
currentAudio.onended = () => { btn.innerHTML = `🔊 Listen`; activeTtsBtn = null; currentAudio = null; };
|
| 237 |
await currentAudio.play();
|
| 238 |
} catch (err) {
|
| 239 |
+
console.error("TTS generation error:", err);
|
| 240 |
+
btn.disabled = false;
|
| 241 |
+
btn.innerHTML = `⚠️ Retry`;
|
| 242 |
alert("Voice queue is busy or timed out. Please tap retry in a moment.");
|
| 243 |
}
|
| 244 |
}
|
| 245 |
|
| 246 |
function attachTtsButton(bubble, text) {
|
| 247 |
+
if (!bubble || !text.trim()) return;
|
| 248 |
const actionRow = document.createElement('div');
|
| 249 |
actionRow.className = "flex items-center gap-2 mt-2 pt-1 border-t border-zinc-700/30";
|
| 250 |
const ttsBtn = document.createElement('button');
|
| 251 |
+
ttsBtn.type = "button";
|
| 252 |
+
ttsBtn.className = "text-[11px] font-medium text-zinc-400 hover:text-emerald-400 flex items-center gap-1 transition cursor-pointer";
|
| 253 |
ttsBtn.innerHTML = `🔊 Listen`;
|
| 254 |
ttsBtn.onclick = () => playBodoSpeech(ttsBtn, text.trim());
|
| 255 |
+
actionRow.appendChild(ttsBtn);
|
| 256 |
+
bubble.appendChild(actionRow);
|
| 257 |
}
|
| 258 |
|
| 259 |
function createBubbleElement(role) {
|
|
|
|
| 377 |
attachTtsButton(aiBubble, finalizedText);
|
| 378 |
} else {
|
| 379 |
thinking && thinking.classList.add('hidden');
|
| 380 |
+
let friendlyMsg = err.message;
|
| 381 |
+
if (err.message.includes('429')) {
|
| 382 |
+
friendlyMsg = currentScript === 'roman' ? 'Rate limit hit. 15s nena-nwi fin lwir.' : 'रेत लिमिट थफैबाय। 15 सेकेन्द नेनानै फिन लिर।';
|
| 383 |
+
} else if (err.message.includes('high demand') || err.message.includes('503')) {
|
| 384 |
+
friendlyMsg = currentScript === 'roman' ? 'Server-ao traffic baraydwng. 10s unao fin sonanai nay.' : 'सार्भार आव मानसि बारा जादों। 10 सेकेन्द उनाव फिन नाजा।';
|
| 385 |
+
}
|
| 386 |
+
appendMessage('model', friendlyMsg);
|
| 387 |
}
|
| 388 |
} finally {
|
| 389 |
thinking && thinking.classList.add('hidden');
|
|
|
|
| 787 |
// ============================================================
|
| 788 |
// INIT
|
| 789 |
// ============================================================
|
| 790 |
+
updateScriptUI(); updateUiLangUI(); updatePricingUI(); updateUsageUI(); updateIncognitoUI(); applyUILanguage(uiLanguage);
|