diff --git a/app.js b/app.js index a464ae5..66d7cb2 100644 --- a/app.js +++ b/app.js @@ -598,27 +598,107 @@ function initSettings() { } } -async function callGeminiApi(apiKey, model, prompt) { - const activeModel = model || localStorage.getItem('opal_selected_model') || 'gemini-2.0-flash'; - const url = `https://generativelanguage.googleapis.com/v1beta/models/${activeModel}:generateContent?key=${apiKey}`; - const payload = { - contents: [{ parts: [{ text: prompt }] }], - generationConfig: { temperature: 0.7, maxOutputTokens: 2048 } - }; +// --- Google Gemini 2.0 / Gemma Quota-Optimized API Client --- +const GEMINI_CACHE = new Map(); - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); +function getCachedPromptResult(cacheKey) { + if (GEMINI_CACHE.has(cacheKey)) { + return GEMINI_CACHE.get(cacheKey); + } + try { + const stored = sessionStorage.getItem(`opal_cache_${cacheKey}`); + if (stored) { + const parsed = JSON.parse(stored); + if (Date.now() - parsed.ts < 1000 * 60 * 60 * 2) { // 2 hour cache + GEMINI_CACHE.set(cacheKey, parsed.data); + return parsed.data; + } + } + } catch (e) {} + return null; +} - if (!response.ok) { - const errData = await response.json().catch(() => ({})); - throw new Error(errData?.error?.message || `HTTP ${response.status}`); +function setCachedPromptResult(cacheKey, data) { + GEMINI_CACHE.set(cacheKey, data); + try { + sessionStorage.setItem(`opal_cache_${cacheKey}`, JSON.stringify({ ts: Date.now(), data })); + } catch (e) {} +} + +async function callGeminiApi(apiKey, model, prompt, retries = 2) { + if (!apiKey) { + throw new Error("API Key is missing. Please configure your Google Gemini API Key in Settings."); } - const data = await response.json(); - return data?.candidates?.[0]?.content?.parts?.[0]?.text || ''; + // Generate lightweight hash for cache (0 tokens consumed on repeat queries) + const cacheKey = btoa(unescape(encodeURIComponent((model || 'gemini') + ':' + prompt.trim()))).slice(0, 48); + const cached = getCachedPromptResult(cacheKey); + if (cached) { + return cached; + } + + const modelHierarchy = [ + model || localStorage.getItem('opal_selected_model') || 'gemini-2.0-flash', + 'gemini-1.5-flash', + 'gemma-2-27b-it' + ]; + + let lastError = null; + + for (const currentModel of modelHierarchy) { + for (let attempt = 0; attempt <= retries; attempt++) { + try { + const url = `https://generativelanguage.googleapis.com/v1beta/models/${currentModel}:generateContent?key=${apiKey}`; + const payload = { + contents: [{ parts: [{ text: prompt.trim() }] }], + generationConfig: { + temperature: 0.65, + maxOutputTokens: 1024, + topK: 40, + topP: 0.92 + } + }; + + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + + if (response.ok) { + const data = await response.json(); + const text = data?.candidates?.[0]?.content?.parts?.[0]?.text || ''; + if (text) { + setCachedPromptResult(cacheKey, text); + return text; + } + } + + const status = response.status; + const errData = await response.json().catch(() => ({})); + const errMsg = errData?.error?.message || `HTTP ${status}`; + + // If rate limited (429) or server busy (503), wait with exponential backoff & failover + if (status === 429 || status === 503) { + lastError = new Error(`Quota limit reached on ${currentModel}. Retrying with model failover...`); + if (attempt < retries) { + const delay = (attempt + 1) * 1200 + Math.random() * 400; + await new Promise(r => setTimeout(r, delay)); + continue; + } + break; // Move to next model in hierarchy + } else { + throw new Error(errMsg); + } + } catch (err) { + lastError = err; + if (attempt === retries) break; + await new Promise(r => setTimeout(r, 1000)); + } + } + } + + throw lastError || new Error("Failed to generate response after trying available AI models."); } // --- Curated Pre-Built Mini-Apps Suite (Universal E-Commerce) --- @@ -1128,8 +1208,13 @@ document.addEventListener('DOMContentLoaded', () => { statusMsg.textContent = '⏳ Processing with Google Gemini 2.0 AI...'; execBtn.disabled = true; - // Extract form values + // Extract form values & dynamic store metadata const inputs = document.querySelectorAll('.runner-input'); + const selectedBrandKey = document.getElementById('brandWatermarkSelect')?.value || 'glamourstouch'; + const activeBrand = getBrandProfile(selectedBrandKey); + const cleanPhone = (activeBrand.footerHotline || '01712-426871').replace(/\D/g, ''); + const dynamicWaLink = `https://wa.me/88${cleanPhone}`; + let prompt = currentRunningApp.promptTemplate || 'Generate helpful response for the user.'; inputs.forEach(input => { @@ -1139,6 +1224,13 @@ document.addEventListener('DOMContentLoaded', () => { prompt = prompt.replace(regex, val); }); + // Dynamically replace store tokens + prompt = prompt.replace(/{{brand_hotline}}/g, activeBrand.footerHotline); + prompt = prompt.replace(/{{brand_site}}/g, activeBrand.footerSite); + prompt = prompt.replace(/{{brand_name}}/g, activeBrand.headerTitle); + prompt = prompt.replace(/{{wa_link}}/g, dynamicWaLink); + prompt = prompt.replace(/https:\/\/wa\.me\/8801712426871/g, dynamicWaLink); + const apiKey = localStorage.getItem('opal_gemini_api_key') || ''; const model = currentRunningApp.model || localStorage.getItem('opal_selected_model') || 'gemini-2.0-flash'; @@ -1151,9 +1243,10 @@ document.addEventListener('DOMContentLoaded', () => { output = `✨ [Google Opal AI Offline Mode]\n\n` + `Generated Output for: ${currentRunningApp.name}\n` + `----------------------------------------\n` + - `১. অফার ও কাস্টমার রেসপন্স প্রস্তুত করা হয়েছে।\n` + - `২. ১০০% জেনুইন কোয়ালিটি ও সুপারফাস্ট ডেলিভারি নিশ্চিত করুন।\n` + - `৩. সরাসরি যোগাযোগ: https://wa.me/8801712426871\n\n` + + `১. অফার ও কাস্টমার রেসপন্স সফলভাবে প্রস্তুত হয়েছে।\n` + + `২. ১০০% জেনুইন কোয়ালিটি ও সুপারফাস্ট ক্যাশ অন ডেলিভারি নিশ্চিত।\n` + + `৩. সরাসরি WhatsApp এ অর্ডার করতে ক্লিক করুন: ${dynamicWaLink}\n` + + `📞 হটলাইন: ${activeBrand.footerHotline} | 🌐 ওয়েবসাইট: https://${activeBrand.footerSite}\n\n` + `💡 টিপস: Settings-এ গিয়ে আপনার Google Gemini API Key বসালে রিয়েল-টাইম আনলিমিটেড লাইভ রেসপন্স পাবেন!`; } @@ -1610,8 +1703,12 @@ function initPosterLab() { // WhatsApp shareWhatsAppBtn.addEventListener('click', () => { - const text = encodeURIComponent(`✨ 100% Authentic Product Order!\nWebsite: https://glamourstouch.com`); - window.open(`https://wa.me/8801712426871?text=${text}`, '_blank'); + const selectedBrand = document.getElementById('brandWatermarkSelect')?.value || 'glamourstouch'; + const activeBrand = getBrandProfile(selectedBrand); + const cleanPhone = (activeBrand.footerHotline || '01712-426871').replace(/\D/g, ''); + const pTitle = productInput.value.trim() || 'Authentic Product'; + const text = encodeURIComponent(`✨ Hello! I want to order: ${pTitle}\nWebsite: https://${activeBrand.footerSite || 'glamourstouch.com'}\nHotline: ${activeBrand.footerHotline}`); + window.open(`https://wa.me/88${cleanPhone}?text=${text}`, '_blank'); }); // n8n Auto-Post Webhook @@ -1633,15 +1730,21 @@ function initPosterLab() { autoPostN8nBtn.disabled = true; try { + const selectedBrand = document.getElementById('brandWatermarkSelect')?.value || 'glamourstouch'; + const activeBrand = getBrandProfile(selectedBrand); + const cleanPhone = (activeBrand.footerHotline || '01712-426871').replace(/\D/g, ''); + const pTitle = productInput.value.trim() || 'Authentic Product'; + const pPrice = productPrice.value.trim() || '৳ ১,০৬০'; const posterDataUrl = posterCanvas.toDataURL('image/jpeg', 0.92); + const payload = { - title: productInput.value.trim() || 'Authentic K-Beauty Skincare', - price: productPrice.value.trim() || '৳ ১,০৬০', - caption: `✨ ${productInput.value.trim()} ✨\n\n💰 অফার মূল্য: ${productPrice.value.trim()}\n🛡️ ১০০% অরিজিনাল গ্যারান্টি!\n\n💬 সরাসরি WhatsApp এ অর্ডার করতে ইনবক্স করুন:\n👉 https://wa.me/8801712426871\n📞 হটলাইন: 01712-426871\n🌐 ওয়েবসাইট: https://glamourstouch.com`, + title: pTitle, + price: pPrice, + caption: `✨ ${pTitle} ✨\n\n💰 অফার মূল্য: ${pPrice}\n🛡️ ১০০% অরিজিনাল গ্যারান্টি!\n\n💬 সরাসরি WhatsApp এ অর্ডার করতে ইনবক্স করুন:\n👉 https://wa.me/88${cleanPhone}\n📞 হটলাইন: ${activeBrand.footerHotline}\n🌐 ওয়েবসাইট: https://${activeBrand.footerSite}`, image_url: posterDataUrl, - store: "Glamour's Touch", - website: "https://glamourstouch.com", - whatsapp: "01712-426871", + store: activeBrand.headerTitle || "Official Store", + website: `https://${activeBrand.footerSite}`, + whatsapp: activeBrand.footerHotline, timestamp: new Date().toISOString() }; @@ -6166,6 +6269,11 @@ function initInboxAI() { const apiKey = localStorage.getItem('opal_gemini_api_key') || ''; const model = localStorage.getItem('opal_selected_model') || 'gemini-2.0-flash'; + const selectedBrand = document.getElementById('brandWatermarkSelect')?.value || 'glamourstouch'; + const activeBrand = getBrandProfile(selectedBrand); + const cleanPhone = (activeBrand.footerHotline || '01712-426871').replace(/\D/g, ''); + const dynamicWaLink = `https://wa.me/88${cleanPhone}`; + if (!query) { alert('Please enter a customer message'); return; @@ -6176,10 +6284,12 @@ function initInboxAI() { generateBtn.disabled = true; try { + const activeContext = storeInfo || `Store: ${activeBrand.headerTitle}, Website: ${activeBrand.footerSite}, Hotline: ${activeBrand.footerHotline}`; + if (apiKey) { const prompt = `A customer sent this message in e-commerce inbox: '${query}' -Store Info: ${storeInfo} +Store Info: ${activeContext} Write 3 ready-to-copy customer responses in natural, sweet BENGALI (বাংলা). Format exactly with: @@ -6188,18 +6298,18 @@ Format exactly with: ---REPLY2--- (Order confirmation closer asking for Name, Address, and Phone number) ---REPLY3--- -(1-click WhatsApp order link: https://wa.me/8801712426871 and hotline 01712-426871)`; +(1-click WhatsApp order link: ${dynamicWaLink} and hotline ${activeBrand.footerHotline})`; const result = await callGeminiApi(apiKey, model, prompt); const parts = result.split(/---REPLY[123]---/); - reply1.textContent = (parts[1] || 'আসসালামু আলাইকুম প্রিয় কাস্টমার! আমাদের প্রডাক্টের অফার মূল্য মাত্র ৳১০৬০। এটি ১০০% আসল প্রডাক্ট।').trim(); + reply1.textContent = (parts[1] || 'আসসালামু আলাইকুম প্রিয় কাস্টমার! আমাদের প্রডাক্টের অফার মূল্য আকর্ষণীয় ছাড়ে দেওয়া হচ্ছে। এটি ১০০% আসল প্রডাক্ট।').trim(); reply2.textContent = (parts[2] || 'অর্ডারটি কনফার্ম করতে আপনার নাম, সম্পূর্ণ ঠিকানা এবং মোবাইল নম্বরটি লিখে পাঠান। আমরা দ্রুত ক্যাশ অন ডেলিভারিতে পাঠিয়ে দেবো!').trim(); - reply3.textContent = (parts[3] || 'সরাসরি হোয়াটসঅ্যাপে দ্রুত অর্ডার করতে ক্লিক করুন: https://wa.me/8801712426871\nহটলাইন: 01712-426871').trim(); + reply3.textContent = (parts[3] || `সরাসরি হোয়াটসঅ্যাপে দ্রুত অর্ডার করতে ক্লিক করুন: ${dynamicWaLink}\nহটলাইন: ${activeBrand.footerHotline}`).trim(); } else { - reply1.textContent = `আসসালামু আলাইকুম প্রিয় আপু/ভাইয়া! 🥰\nআমাদের কাছে ১০০% অরিজিনাল কোরিয়ান স্কিনকেয়ার প্রোডাক্ট রয়েছে।\n\n💰 অফার মূল্য: ৳ ১,০৬০ (রেগুলার ৳ ১,৪৭০)\n🚚 হোম ডেলিভারি: ঢাকায় ৳৭০, ঢাকার বাইরে ৳১৩০\n\nএটি নিয়মিত ব্যবহারে ত্বক হবে কাচের মতো চকচকে ও দাগহীন! 🌸`; + reply1.textContent = `আসসালামু আলাইকুম প্রিয় কাস্টমার! 🥰\nআমাদের কাছে ${activeBrand.headerTitle}-এর ১০০% অরিজিনাল অথেনটিক প্রোডাক্ট রয়েছে।\n\n💰 আকর্ষণীয় অফার মূল্য ও সুপারফাস্ট হোম ডেলিভারি সুবিধা!\n🚚 দ্রুত সারা দেশে ক্যাশ অন ডেলিভারি দেওয়া হয়।`; reply2.textContent = `অর্ডারটি কনফার্ম করতে অনুগ্রহ করে আপনার:\n১. নাম\n২. সম্পূর্ণ ঠিকানা (জেলা/থানা সহ)\n৩. মোবাইল নম্বর\n\nলিখে পাঠিয়ে দিন। আমরা দ্রুত প্রোডাক্ট পাঠিয়ে দেবো! ধন্যবাদ। 🛍️`; - reply3.textContent = `সরাসরি হোয়াটসঅ্যাপে ১-ক্লিকে অর্ডার করতে আমাদের ইনবক্স করুন:\n💬 WhatsApp: https://wa.me/8801712426871\n📞 হটলাইন: 01712-426871\n🌐 ওয়েবসাইট: https://glamourstouch.com`; + reply3.textContent = `সরাসরি হোয়াটসঅ্যাপে ১-ক্লিকে অর্ডার করতে আমাদের ইনবক্স করুন:\n💬 WhatsApp: ${dynamicWaLink}\n📞 হটলাইন: ${activeBrand.footerHotline}\n🌐 ওয়েবসাইট: https://${activeBrand.footerSite}`; } repliesCard.classList.remove('hidden');