Spaces:
Sleeping
Sleeping
File size: 6,173 Bytes
94c9882 c92b696 f580c68 698bbca f580c68 698bbca 94c9882 f580c68 c92b696 94c9882 f580c68 94c9882 f580c68 698bbca f580c68 c92b696 f580c68 c92b696 f580c68 94c9882 f580c68 698bbca 94c9882 f580c68 94c9882 c92b696 94c9882 f580c68 c92b696 f580c68 698bbca f580c68 c92b696 f580c68 c92b696 f580c68 c92b696 f580c68 c92b696 f580c68 c92b696 f580c68 c92b696 94c9882 f580c68 c92b696 94c9882 c92b696 94c9882 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | import { NextRequest, NextResponse } from 'next/server';
/**
* AI Chat API route — calls NVIDIA NIM directly for fast responses.
* All API keys are read from environment variables (never hardcoded).
*/
const NVIDIA_API_URL = 'https://integrate.api.nvidia.com/v1/chat/completions';
const NVIDIA_API_KEY = process.env.NVIDIA_API_KEY || '';
const BRAIN_MODEL = 'meta/llama-3.1-8b-instruct';
const VISION_MODEL = 'meta/llama-3.2-11b-vision-instruct';
const SUPABASE_URL = process.env.SUPABASE_URL || 'https://tcwdbokruvlizkxcpkzj.supabase.co';
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || '';
const COOKIE_NAME = 'cellex_session_id';
const SYSTEM_PROMPT = `You are Cellex AI, a friendly shopping assistant for Cellex — Nigeria's #1 social commerce marketplace.
Help users find products, compare prices, and make shopping decisions.
Prices are in Nigerian Naira (₦). Be concise, friendly, and helpful (2-3 sentences max).
If users ask about specific products, mention you can search for them.
For return policy: 7-day returns on most items in original condition.
For delivery: 48-hour shipping within Nigeria, free shipping on orders over ₦50,000.
For payment: PalmPay bank transfer with auto-verification, pay on delivery available.`;
const SEARCH_KEYWORDS = ['search', 'find', 'looking for', 'need', 'want', 'buy', 'show me', 'do you have', 'available', 'price', 'how much', 'under', 'over', 'cheap', 'best', 'recommend'];
function needsProductSearch(message: string): boolean {
const lower = message.toLowerCase();
return SEARCH_KEYWORDS.some(kw => lower.includes(kw));
}
async function searchProducts(query: string): Promise<any[]> {
if (!SUPABASE_ANON_KEY) return [];
try {
const resp = await fetch(`${SUPABASE_URL}/functions/v1/products`, {
method: 'POST',
headers: {
'apikey': SUPABASE_ANON_KEY,
'Authorization': `Bearer ${SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ op: 'search', query }),
});
if (!resp.ok) return [];
const data = await resp.json();
return (data.results || data.products || []).slice(0, 6);
} catch {
return [];
}
}
async function describeImage(imageUrl: string): Promise<string> {
if (!NVIDIA_API_KEY) return '';
try {
const resp = await fetch(NVIDIA_API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${NVIDIA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: VISION_MODEL,
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Describe this product in detail (color, type, material, brand if visible) so I can search for it.' },
{ type: 'image_url', image_url: { url: imageUrl } },
],
}],
max_tokens: 150,
stream: false,
}),
});
if (!resp.ok) return '';
const data = await resp.json();
return data?.choices?.[0]?.message?.content || '';
} catch {
return '';
}
}
async function callNVIDIA(messages: Array<{ role: string; content: string }>, maxTokens: number = 200): Promise<string> {
if (!NVIDIA_API_KEY) throw new Error('NVIDIA_API_KEY not set');
const resp = await fetch(NVIDIA_API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${NVIDIA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: BRAIN_MODEL,
messages,
max_tokens: maxTokens,
temperature: 0.5,
stream: false,
}),
});
if (!resp.ok) throw new Error(`NVIDIA HTTP ${resp.status}`);
const data = await resp.json();
return data?.choices?.[0]?.message?.content || '';
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { message, context, history, image } = body;
if (!message || typeof message !== 'string') {
return NextResponse.json({ success: false, error: 'Missing "message"' }, { status: 400 });
}
const messages: Array<{ role: string; content: string }> = [
{ role: 'system', content: SYSTEM_PROMPT },
];
if (Array.isArray(history)) {
for (const msg of history.slice(-6)) {
if (msg.role && msg.content) {
messages.push({ role: msg.role, content: msg.content });
}
}
}
let userMessage = message;
if (image) {
const description = await describeImage(image);
if (description) {
userMessage = `User uploaded an image of: ${description}. User request: ${message}`;
}
}
if (context) {
messages.push({ role: 'system', content: `Context: ${context}` });
}
const shouldSearch = needsProductSearch(message) || (context && context.includes('Search overview'));
let products: any[] = [];
if (shouldSearch) {
const [aiReply, searchResults] = await Promise.all([
callNVIDIA([...messages, { role: 'user', content: userMessage }], 200),
searchProducts(message.replace(/under.*naira|over.*naira|cheap|best/gi, '').trim() || message),
]);
products = searchResults;
let finalReply = aiReply;
if (products.length > 0) {
const productList = products.slice(0, 4).map(p => `${p.name} (₦${Number(p.price).toLocaleString('en-NG')})`).join(', ');
finalReply = `${aiReply}\n\nI found ${products.length} products for you: ${productList}`;
}
return NextResponse.json({
success: true,
reply: finalReply,
message: finalReply,
content: finalReply,
products: products.length > 0 ? products : null,
});
}
messages.push({ role: 'user', content: userMessage });
const reply = await callNVIDIA(messages, 200);
return NextResponse.json({
success: true,
reply,
message: reply,
content: reply,
products: null,
});
} catch (error) {
console.error('AI chat error:', error);
return NextResponse.json({
success: false,
error: String(error),
reply: "I'm having trouble right now. Please try again.",
}, { status: 200 });
}
}
|