feat: Quota-optimized Gemini client with LRU caching, rate limit backoff, multi-model failover, and dynamic brand resolution
This commit is contained in:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user