oga-market / shared.js
harystyleseze
Oga Market: Day 2 hybrid shopper + creator app for Good Online
dcc955a
// 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));
},
};