sms-extension/server/services/openai.js
2026-03-26 14:19:26 +05:30

134 lines
4.8 KiB
JavaScript

const OpenAI = require('openai');
const WORKFLOW = process.env.WORKFLOW_URL;
function getClient() {
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) throw new Error('OPENAI_API_KEY not set');
return new OpenAI({ apiKey });
}
const TRAI_RULES = `TRAI SMS Template Rules:
1. Maximum 160 characters for a single SMS (or 153 per part for multi-part)
2. Dynamic variables must use the format {#var#}
3. Transactional templates must not contain promotional content or URLs
4. The sender ID (header) must be a 6-character alphabetic string registered with DLT
5. No special characters except: . , - _ / @ and standard punctuation
6. Template must clearly relate to the registered event type
7. Do not include any URLs unless explicitly required by the event
8. Template must start with context (e.g. "Your order..." not "Hi! Your order...")`;
const EVENT_DESCRIPTIONS = {
placed: 'The customer has successfully placed an order',
confirmed: 'The order has been confirmed by the seller/warehouse',
dp_assigned: 'A delivery partner has been assigned to deliver the order',
pack: 'The order has been packed and is ready for dispatch',
cancelled: 'The order has been cancelled',
delivery_done: 'The order has been successfully delivered to the customer',
};
/** Parse scraped website data into structured brand context. */
async function parseBrandContext(scrapedData) {
const openai = getClient();
const { markdown, links } = scrapedData;
const prompt = `You are a brand analyst. Analyze the website content below and extract structured brand information.
Website content (markdown):
${markdown.slice(0, 8000)}
All links found on the page:
${JSON.stringify(links.slice(0, 200))}
Return ONLY a valid JSON object:
{
"brandName": "string — the business/brand name",
"tone": "one of: friendly, professional, formal, casual, energetic",
"taglines": ["max 3 key brand taglines or phrases found on the page"],
"colors": ["brand color hex codes if found, else empty array"],
"relevantImageUrls": ["3-5 relevant brand image URLs — logos, hero images, product shots only. Exclude: social icons, nav icons, tracking pixels, data URIs, generic stock images. Only absolute URLs."]
}`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
temperature: 0.3,
});
return JSON.parse(completion.choices[0].message.content);
}
/** Generate 3 TRAI-compliant SMS template variants for a given event. */
async function generateTemplates(brandContext, eventSlug, eventLabel) {
const openai = getClient();
const eventDesc = EVENT_DESCRIPTIONS[eventSlug] || `A "${eventLabel}" event in the order lifecycle`;
const prompt = `You are an SMS template expert for e-commerce in India.
Brand: ${brandContext.brandName}
Tone: ${brandContext.tone}
Taglines: ${(brandContext.taglines || []).join(', ') || 'none'}
Event: "${eventLabel}" — ${eventDesc}
${TRAI_RULES}
Generate exactly 3 distinct SMS templates. Each must:
- Strictly follow all TRAI rules
- Use {#var#} for every dynamic variable (e.g. customer name, order ID, product name, tracking URL, delivery date)
- Be under 160 characters
- Reflect the brand tone
- Start with order/event context
Return ONLY valid JSON:
{ "templates": ["template 1", "template 2", "template 3"] }`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
temperature: 0.7,
});
const result = JSON.parse(completion.choices[0].message.content);
return result.templates || [];
}
/** Parse a raw provider cURL and map its placeholders to the approved template's {#var#} positions. */
async function processCurl(rawCurl, approvedTemplate, eventSlug) {
const openai = getClient();
const prompt = `You are an SMS provider integration expert.
Approved SMS template:
${approvedTemplate}
Event: ${eventSlug}
Raw cURL from SMS provider:
${rawCurl}
Analyze the cURL:
1. Identify all placeholder formats (e.g. {{variable}}, %VAR%, <PLACEHOLDER>)
2. Map each to a semantic field name (e.g. customerName, orderId, productName, trackingUrl, deliveryDate)
3. Normalize to camelCase in processedCurl
4. Build variableMap matching positional {#var#} slots in the approved template
Return ONLY valid JSON:
{
"processedCurl": "cURL with normalized placeholder names",
"variableMap": {
"{#var#}[0]": "fieldName for first {#var#}",
"{#var#}[1]": "fieldName for second {#var#}"
}
}`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
temperature: 0.2,
});
return JSON.parse(completion.choices[0].message.content);
}
module.exports = { parseBrandContext, generateTemplates, processCurl };