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) {
|
// --- Google Gemini 2.0 / Gemma Quota-Optimized API Client ---
|
||||||
const activeModel = model || localStorage.getItem('opal_selected_model') || 'gemini-2.0-flash';
|
const GEMINI_CACHE = new Map();
|
||||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/${activeModel}:generateContent?key=${apiKey}`;
|
|
||||||
const payload = {
|
|
||||||
contents: [{ parts: [{ text: prompt }] }],
|
|
||||||
generationConfig: { temperature: 0.7, maxOutputTokens: 2048 }
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
function getCachedPromptResult(cacheKey) {
|
||||||
method: 'POST',
|
if (GEMINI_CACHE.has(cacheKey)) {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
return GEMINI_CACHE.get(cacheKey);
|
||||||
body: JSON.stringify(payload)
|
}
|
||||||
});
|
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) {
|
function setCachedPromptResult(cacheKey, data) {
|
||||||
const errData = await response.json().catch(() => ({}));
|
GEMINI_CACHE.set(cacheKey, data);
|
||||||
throw new Error(errData?.error?.message || `HTTP ${response.status}`);
|
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();
|
// Generate lightweight hash for cache (0 tokens consumed on repeat queries)
|
||||||
return data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
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) ---
|
// --- 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...';
|
statusMsg.textContent = '⏳ Processing with Google Gemini 2.0 AI...';
|
||||||
execBtn.disabled = true;
|
execBtn.disabled = true;
|
||||||
|
|
||||||
// Extract form values
|
// Extract form values & dynamic store metadata
|
||||||
const inputs = document.querySelectorAll('.runner-input');
|
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.';
|
let prompt = currentRunningApp.promptTemplate || 'Generate helpful response for the user.';
|
||||||
|
|
||||||
inputs.forEach(input => {
|
inputs.forEach(input => {
|
||||||
@@ -1139,6 +1224,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
prompt = prompt.replace(regex, val);
|
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 apiKey = localStorage.getItem('opal_gemini_api_key') || '';
|
||||||
const model = currentRunningApp.model || localStorage.getItem('opal_selected_model') || 'gemini-2.0-flash';
|
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` +
|
output = `✨ [Google Opal AI Offline Mode]\n\n` +
|
||||||
`Generated Output for: ${currentRunningApp.name}\n` +
|
`Generated Output for: ${currentRunningApp.name}\n` +
|
||||||
`----------------------------------------\n` +
|
`----------------------------------------\n` +
|
||||||
`১. অফার ও কাস্টমার রেসপন্স প্রস্তুত করা হয়েছে।\n` +
|
`১. অফার ও কাস্টমার রেসপন্স সফলভাবে প্রস্তুত হয়েছে।\n` +
|
||||||
`২. ১০০% জেনুইন কোয়ালিটি ও সুপারফাস্ট ডেলিভারি নিশ্চিত করুন।\n` +
|
`২. ১০০% জেনুইন কোয়ালিটি ও সুপারফাস্ট ক্যাশ অন ডেলিভারি নিশ্চিত।\n` +
|
||||||
`৩. সরাসরি যোগাযোগ: https://wa.me/8801712426871\n\n` +
|
`৩. সরাসরি WhatsApp এ অর্ডার করতে ক্লিক করুন: ${dynamicWaLink}\n` +
|
||||||
|
`📞 হটলাইন: ${activeBrand.footerHotline} | 🌐 ওয়েবসাইট: https://${activeBrand.footerSite}\n\n` +
|
||||||
`💡 টিপস: Settings-এ গিয়ে আপনার Google Gemini API Key বসালে রিয়েল-টাইম আনলিমিটেড লাইভ রেসপন্স পাবেন!`;
|
`💡 টিপস: Settings-এ গিয়ে আপনার Google Gemini API Key বসালে রিয়েল-টাইম আনলিমিটেড লাইভ রেসপন্স পাবেন!`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1610,8 +1703,12 @@ function initPosterLab() {
|
|||||||
|
|
||||||
// WhatsApp
|
// WhatsApp
|
||||||
shareWhatsAppBtn.addEventListener('click', () => {
|
shareWhatsAppBtn.addEventListener('click', () => {
|
||||||
const text = encodeURIComponent(`✨ 100% Authentic Product Order!\nWebsite: https://glamourstouch.com`);
|
const selectedBrand = document.getElementById('brandWatermarkSelect')?.value || 'glamourstouch';
|
||||||
window.open(`https://wa.me/8801712426871?text=${text}`, '_blank');
|
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
|
// n8n Auto-Post Webhook
|
||||||
@@ -1633,15 +1730,21 @@ function initPosterLab() {
|
|||||||
autoPostN8nBtn.disabled = true;
|
autoPostN8nBtn.disabled = true;
|
||||||
|
|
||||||
try {
|
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 posterDataUrl = posterCanvas.toDataURL('image/jpeg', 0.92);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
title: productInput.value.trim() || 'Authentic K-Beauty Skincare',
|
title: pTitle,
|
||||||
price: productPrice.value.trim() || '৳ ১,০৬০',
|
price: pPrice,
|
||||||
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`,
|
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,
|
image_url: posterDataUrl,
|
||||||
store: "Glamour's Touch",
|
store: activeBrand.headerTitle || "Official Store",
|
||||||
website: "https://glamourstouch.com",
|
website: `https://${activeBrand.footerSite}`,
|
||||||
whatsapp: "01712-426871",
|
whatsapp: activeBrand.footerHotline,
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6166,6 +6269,11 @@ function initInboxAI() {
|
|||||||
const apiKey = localStorage.getItem('opal_gemini_api_key') || '';
|
const apiKey = localStorage.getItem('opal_gemini_api_key') || '';
|
||||||
const model = localStorage.getItem('opal_selected_model') || 'gemini-2.0-flash';
|
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) {
|
if (!query) {
|
||||||
alert('Please enter a customer message');
|
alert('Please enter a customer message');
|
||||||
return;
|
return;
|
||||||
@@ -6176,10 +6284,12 @@ function initInboxAI() {
|
|||||||
generateBtn.disabled = true;
|
generateBtn.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const activeContext = storeInfo || `Store: ${activeBrand.headerTitle}, Website: ${activeBrand.footerSite}, Hotline: ${activeBrand.footerHotline}`;
|
||||||
|
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
const prompt = `A customer sent this message in e-commerce inbox:
|
const prompt = `A customer sent this message in e-commerce inbox:
|
||||||
'${query}'
|
'${query}'
|
||||||
Store Info: ${storeInfo}
|
Store Info: ${activeContext}
|
||||||
|
|
||||||
Write 3 ready-to-copy customer responses in natural, sweet BENGALI (বাংলা).
|
Write 3 ready-to-copy customer responses in natural, sweet BENGALI (বাংলা).
|
||||||
Format exactly with:
|
Format exactly with:
|
||||||
@@ -6188,18 +6298,18 @@ Format exactly with:
|
|||||||
---REPLY2---
|
---REPLY2---
|
||||||
(Order confirmation closer asking for Name, Address, and Phone number)
|
(Order confirmation closer asking for Name, Address, and Phone number)
|
||||||
---REPLY3---
|
---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 result = await callGeminiApi(apiKey, model, prompt);
|
||||||
const parts = result.split(/---REPLY[123]---/);
|
const parts = result.split(/---REPLY[123]---/);
|
||||||
|
|
||||||
reply1.textContent = (parts[1] || 'আসসালামু আলাইকুম প্রিয় কাস্টমার! আমাদের প্রডাক্টের অফার মূল্য মাত্র ৳১০৬০। এটি ১০০% আসল প্রডাক্ট।').trim();
|
reply1.textContent = (parts[1] || 'আসসালামু আলাইকুম প্রিয় কাস্টমার! আমাদের প্রডাক্টের অফার মূল্য আকর্ষণীয় ছাড়ে দেওয়া হচ্ছে। এটি ১০০% আসল প্রডাক্ট।').trim();
|
||||||
reply2.textContent = (parts[2] || 'অর্ডারটি কনফার্ম করতে আপনার নাম, সম্পূর্ণ ঠিকানা এবং মোবাইল নম্বরটি লিখে পাঠান। আমরা দ্রুত ক্যাশ অন ডেলিভারিতে পাঠিয়ে দেবো!').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 {
|
} else {
|
||||||
reply1.textContent = `আসসালামু আলাইকুম প্রিয় আপু/ভাইয়া! 🥰\nআমাদের কাছে ১০০% অরিজিনাল কোরিয়ান স্কিনকেয়ার প্রোডাক্ট রয়েছে।\n\n💰 অফার মূল্য: ৳ ১,০৬০ (রেগুলার ৳ ১,৪৭০)\n🚚 হোম ডেলিভারি: ঢাকায় ৳৭০, ঢাকার বাইরে ৳১৩০\n\nএটি নিয়মিত ব্যবহারে ত্বক হবে কাচের মতো চকচকে ও দাগহীন! 🌸`;
|
reply1.textContent = `আসসালামু আলাইকুম প্রিয় কাস্টমার! 🥰\nআমাদের কাছে ${activeBrand.headerTitle}-এর ১০০% অরিজিনাল অথেনটিক প্রোডাক্ট রয়েছে।\n\n💰 আকর্ষণীয় অফার মূল্য ও সুপারফাস্ট হোম ডেলিভারি সুবিধা!\n🚚 দ্রুত সারা দেশে ক্যাশ অন ডেলিভারি দেওয়া হয়।`;
|
||||||
reply2.textContent = `অর্ডারটি কনফার্ম করতে অনুগ্রহ করে আপনার:\n১. নাম\n২. সম্পূর্ণ ঠিকানা (জেলা/থানা সহ)\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');
|
repliesCard.classList.remove('hidden');
|
||||||
|
|||||||
Reference in New Issue
Block a user