Spaces:
Running
Running
| // shared.js β common catalog, system prompts, and 0G helpers for Oga Market | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // GOOD ONLINE PRODUCT CATALOG (lifted from Oga Mi) | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| window.GOOD_CATALOG = ` | |
| ## GOOD ONLINE PRODUCT CATALOG (good.online) | |
| "Made by Nigerians. Curated for you." β A marketplace connecting you with Nigerian creators committed to quality and positive impact. Delivers within Lagos only. | |
| ### FEATURED PRODUCTS | |
| - Coconut Rice (430g) by Chumbys β N5,400 [Cook it Yourself] | |
| - Glow Oil by Arami β N13,600 [Beauty] | |
| - Whole Cashew Jar by The M & W Nuts Company β N11,875 [Snacks] | |
| - Gym Socks Box by Smileys Africa β N17,500 [Fashion] | |
| - Miniature Trio by Dala Craft β N54,000 [Home & Living] | |
| - Sugar Wax Kit by Sugar Wax By Audrey β N17,000 [Beauty] | |
| - Dudu Ball Chain by ITURA Jewelry β N40,000 [Fashion/Jewelry] | |
| - Mini Scented Candle Gift Set by Jacque Lagos β N70,000 [Gifts/Home] | |
| - Plantain Clusters by Good Originals β N5,520 [Snacks] | |
| ### CATEGORIES | |
| Fashion, Beauty, Home & Living, Wellness, Gifts, Leisure, Good Originals, Snacks, Drinks, Cook it Yourself, Worklife, For Moms, For Women | |
| ### CURATED LISTS (with starting prices) | |
| - "For Everyone Carrying This Economy on Their Back" β from N3,000 | |
| - "A Good Place to Start" β from N500 | |
| - "Something to Add to Your Skincare Routine" β from N4,000 | |
| - "Some Thoughtful Gifts for Everyone" β from N13,000 | |
| - "The Faster Way to Familiar Dishes" β from N2,250 | |
| - "Everyday Comforts That Make Life Feel Lighter" β from N7,500 | |
| - "For the Kids We're All Soft For" β from N5,160 | |
| - "Cyn Ugwu's Picks to Living a Good Life" β from N4,200 | |
| - "Travel Essentials That Actually Come in Handy" β from N4,500 | |
| ### TOP CREATORS (50+) | |
| Fashion: Smileys Africa, TETE AFRICA, Garmisland, Asabithebrand, AZACH, Claic Design, Captain Atelier, Marte Egele, Sarima By Ama, TwentySix, Crivel, OhMyCowries, Floy NG, Rade Label, 1403 Luxury, Lano Lagos, THESHOEBLOCC, Outlash, Vinc & Eliz, ITURA Jewelry, KunbiWorks | |
| Beauty: Arami, Tamed Lux, Infused Organics, ESTEVAN PERFUMERY, Sugar Wax By Audrey, Sublime Skin by Jumai, Msmetics, Viektors Bath and Body, TGIN, Onoh Naturals | |
| Home & Living: Jacque Lagos, Wallinggs Interior, Silkluxe Hub, Oeuvre Designs, Alore Lifestyle, Ediye Home Scents, Iluna Candles & Co, Hingees, The DA Brand, Scent L'avie | |
| Wellness: Soft Mum Era, Enjoca, Olile Enterprises, Clove and Nectar, Just Journals | |
| Snacks & Food: Chumbys, The M & W Nuts Company, Good Originals | |
| Gifts: The Kulture Company, Not Just Pulp, FCAL (FC Accessories), The Ajala Game Store | |
| Leisure: Atito, The Ajala Game Store | |
| ### STORE INFO | |
| - Website: good.online | |
| - Good House: 19B Sasegbon Street, Ikeja GRA, Lagos | |
| - Good Village: Plot 33 Block 15, Admiralty Way, Lekki Phase 1, Lagos | |
| - Contact: +2348142511154 / support@goodthingco.xyz | |
| - Delivery: Lagos only | |
| - Gift vouchers available at good.online | |
| `; | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SYSTEM PROMPTS | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| window.buildShopperPrompt = function (customerName, history, wishlist) { | |
| let prompt = `You are Oga Mi, a warm AI shopping assistant for Good Online (good.online), Nigeria's curated marketplace. | |
| PERSONALITY: | |
| - Warm, like a helpful Lagos market seller who genuinely cares | |
| - Mix English with light Pidgin/Yoruba naturally ("Oga mi!", "Na good choice!", "E kaaro!") | |
| - Enthusiastic about Nigerian creators | |
| - Keep responses to 2-4 sentences. Recommend 1-2 products, not a long list | |
| - Always finish your sentences | |
| - Use Naira prices (write as N5,400) | |
| ${window.GOOD_CATALOG} | |
| WHEN RECOMMENDING: | |
| - Always mention price + creator | |
| - Budget: "A Good Place to Start" (from N500) | |
| - Gifts: "Some Thoughtful Gifts for Everyone" (from N13,000) | |
| - Skincare: "Something to Add to Your Skincare Routine" (from N4,000) | |
| When you recommend a specific product, end your message with [PRODUCT:Product Name|Creator|Price] so the UI can show an add-to-wishlist button. Example: [PRODUCT:Glow Oil|Arami|13600]. Only include this tag for products from the catalog above.`; | |
| if (customerName) { | |
| prompt += `\n\nCUSTOMER: ${customerName}. Use their name naturally.`; | |
| } | |
| if (wishlist && wishlist.length > 0) { | |
| prompt += `\n\nWISHLIST: ${wishlist.map(w => w.name).join(', ')}. Reference these naturally if relevant.`; | |
| } | |
| if (history && history.length > 0) { | |
| prompt += `\n\nPREVIOUS INTERACTIONS:\n${history.map(h => `- ${h}`).join('\n')}`; | |
| } | |
| return prompt; | |
| }; | |
| window.buildCreatorPrompt = function (visionDescription, productName, price, creator) { | |
| return `You are a copywriter for Good Online (good.online), a Nigerian curated marketplace. A creator is listing a new product. Generate a beautiful, warm, Nigerian-style product listing. | |
| PRODUCT INFO: | |
| - Name from creator: "${productName}" | |
| - Creator: "${creator}" | |
| - Price: N${Number(price).toLocaleString()} | |
| - Vision analysis of the photo: "${visionDescription}" | |
| Reference catalog (for style + category matching): | |
| ${window.GOOD_CATALOG} | |
| OUTPUT REQUIREMENTS β respond ONLY with valid JSON, no other text: | |
| { | |
| "title": "A polished product name (max 6 words, sentence case)", | |
| "tagline": "A 1-sentence editorial hook in warm, Nigerian voice (max 14 words)", | |
| "description": "A 2-3 sentence description that tells the product's story, mentions the creator, and feels handmade/curated. Sentence case. No emoji.", | |
| "category": "One category from this list: Fashion, Beauty, Home & Living, Wellness, Gifts, Leisure, Snacks, Drinks, Cook it Yourself, Worklife, For Moms, For Women", | |
| "curatedList": "One of these curated lists if relevant, or null: A Good Place to Start | Some Thoughtful Gifts for Everyone | Something to Add to Your Skincare Routine | Everyday Comforts That Make Life Feel Lighter | For the Kids We're All Soft For | Travel Essentials That Actually Come in Handy", | |
| "priceFeedback": "1 sentence: is this price aligned with similar products on Good? Be honest." | |
| }`; | |
| }; | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // 0G COMPUTE HELPERS (Direct mode, browser-CORS-friendly) | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| window.OG_ENDPOINTS = { | |
| chat: 'https://compute-network-1.integratenetwork.work/v1/proxy/chat/completions', | |
| whisper: 'https://compute-network-16.integratenetwork.work/v1/proxy/audio/transcriptions', | |
| // Vision provider may live on a different compute-network β verify on pc.0g.ai | |
| vision: 'https://compute-network-1.integratenetwork.work/v1/proxy/chat/completions', | |
| }; | |
| window.askAI = async function (chatKey, model, messages, { maxTokens = 512 } = {}) { | |
| const res = await fetch(window.OG_ENDPOINTS.chat, { | |
| method: 'POST', | |
| headers: { 'Authorization': `Bearer ${chatKey}`, 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ model, messages, max_tokens: maxTokens }), | |
| }); | |
| if (!res.ok) throw new Error(`Chat ${res.status}: ${(await res.text()).slice(0, 200)}`); | |
| const data = await res.json(); | |
| return (data.choices?.[0]?.message?.content || '').trim(); | |
| }; | |
| window.askVision = async function (chatKey, model, imageDataUrl, instruction, { maxTokens = 256 } = {}) { | |
| // OpenAI-compatible vision message format | |
| const res = await fetch(window.OG_ENDPOINTS.vision, { | |
| method: 'POST', | |
| headers: { 'Authorization': `Bearer ${chatKey}`, 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| model, | |
| messages: [{ | |
| role: 'user', | |
| content: [ | |
| { type: 'text', text: instruction }, | |
| { type: 'image_url', image_url: { url: imageDataUrl } }, | |
| ], | |
| }], | |
| max_tokens: maxTokens, | |
| }), | |
| }); | |
| if (!res.ok) throw new Error(`Vision ${res.status}: ${(await res.text()).slice(0, 200)}`); | |
| const data = await res.json(); | |
| return (data.choices?.[0]?.message?.content || '').trim(); | |
| }; | |
| // Convert browser webm/opus audio to PCM WAV (0G Whisper rejects raw webm) | |
| window.webmBlobToWav = async function (blob) { | |
| const buf = await blob.arrayBuffer(); | |
| const ctx = new (window.AudioContext || window.webkitAudioContext)(); | |
| const audio = await ctx.decodeAudioData(buf); | |
| try { ctx.close(); } catch (_) {} | |
| const ch0 = audio.getChannelData(0); | |
| const len = ch0.length; | |
| const sampleRate = audio.sampleRate; | |
| const out = new ArrayBuffer(44 + len * 2); | |
| const v = new DataView(out); | |
| const ws = (off, s) => { for (let i = 0; i < s.length; i++) v.setUint8(off + i, s.charCodeAt(i)); }; | |
| ws(0, 'RIFF'); v.setUint32(4, 36 + len * 2, true); ws(8, 'WAVE'); | |
| ws(12, 'fmt '); v.setUint32(16, 16, true); | |
| v.setUint16(20, 1, true); v.setUint16(22, 1, true); | |
| v.setUint32(24, sampleRate, true); v.setUint32(28, sampleRate * 2, true); | |
| v.setUint16(32, 2, true); v.setUint16(34, 16, true); | |
| ws(36, 'data'); v.setUint32(40, len * 2, true); | |
| let off = 44; | |
| for (let i = 0; i < len; i++) { | |
| const s = Math.max(-1, Math.min(1, ch0[i])); | |
| v.setInt16(off, s < 0 ? s * 0x8000 : s * 0x7FFF, true); | |
| off += 2; | |
| } | |
| return new Blob([out], { type: 'audio/wav' }); | |
| }; | |
| window.transcribe = async function (sttKey, webmBlob) { | |
| const wav = await window.webmBlobToWav(webmBlob); | |
| const form = new FormData(); | |
| form.append('file', wav, 'audio.wav'); | |
| form.append('model', 'openai/whisper-large-v3'); | |
| form.append('response_format', 'json'); | |
| const res = await fetch(window.OG_ENDPOINTS.whisper, { | |
| method: 'POST', | |
| headers: { 'Authorization': `Bearer ${sttKey}` }, | |
| body: form, | |
| }); | |
| if (!res.ok) throw new Error(`STT ${res.status}: ${(await res.text()).slice(0, 200)}`); | |
| return (await res.json()).text || ''; | |
| }; | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // 0G CHAIN HELPERS | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| window.OG_CHAIN = { | |
| rpc: 'https://evmrpc-testnet.0g.ai', | |
| chainId: 16602, | |
| explorerTx: (h) => `https://chainscan-galileo.0g.ai/tx/${h}`, | |
| // Deployed ListingNFT contract address β paste after running contract/deploy.js | |
| listingNFT: '', | |
| }; | |
| window.LISTING_NFT_ABI = [ | |
| 'function mint(string dataDescription, bytes32 dataHash) external returns (uint256)', | |
| 'function getData(uint256 tokenId) external view returns (string dataDescription, bytes32 dataHash, address creator, uint256 timestamp)', | |
| 'function totalSupply() external view returns (uint256)', | |
| 'function ownerOf(uint256 tokenId) external view returns (address)', | |
| 'function transferFrom(address from, address to, uint256 tokenId) external', | |
| 'event ListingMinted(uint256 indexed tokenId, address indexed creator, string dataDescription, bytes32 dataHash, uint256 timestamp)', | |
| ]; | |
| window.shortHash = function (h) { | |
| return h.slice(0, 10) + 'β¦' + h.slice(-6); | |
| }; | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // LOCAL STORAGE HELPERS (0G Storage proxy) | |
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| window.OG_STORAGE = { | |
| load(key) { | |
| try { return JSON.parse(localStorage.getItem(key) || 'null'); } catch (_) { return null; } | |
| }, | |
| save(key, value) { | |
| localStorage.setItem(key, JSON.stringify(value)); | |
| }, | |
| }; | |