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

134 lines
6.4 KiB
JavaScript

require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') });
const axios = require('axios');
const WORKFLOW_URL_SCRAPE = process.env.WORKFLOW_URL_SCRAPE;
const WORKFLOW_URL_TEMPLATE = process.env.WORKFLOW_URL_TEMPLATE;
const WORKFLOW_URL_CHECK_CURL = process.env.WORKFLOW_URL_CHECK_CURL;
if (!WORKFLOW_URL_SCRAPE) throw new Error('Missing WORKFLOW_URL_SCRAPE environment variable');
if (!WORKFLOW_URL_TEMPLATE) throw new Error('Missing WORKFLOW_URL_TEMPLATE environment variable');
if (!WORKFLOW_URL_CHECK_CURL) throw new Error('Missing WORKFLOW_URL_CHECK_CURL environment variable');
const TRAI_RULES_TEXT = '1) Max 160 chars. 2) Dynamic vars use {#var#}. 3) Transactional: no promo/URLs unless required. 4) Sender ID DLT-compliant. 5) Allowed punctuation only. 6) Must match event type. 7) Avoid URLs unless explicitly needed. 8) Start with event/order context.';
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',
};
function requestId(prefix) {
return `${prefix}_${Date.now()}`;
}
function parseJsonField(value, fallback) {
if (typeof value !== 'string') return value ?? fallback;
try {
return JSON.parse(value);
} catch {
return fallback;
}
}
async function postWorkflow(url, payload) {
try {
const response = await axios.post(url, payload, {
headers: { 'Content-Type': 'application/json' },
maxBodyLength: Infinity,
timeout: 60000,
});
return response.data;
} catch (error) {
const details = error.response?.data ? ` | response: ${JSON.stringify(error.response.data)}` : '';
throw new Error(`Workflow API error (${url}): ${error.message}${details}`);
}
}
async function parseBrandContext(scrapedData = {}) {
const markdown = String(scrapedData.markdown || '').slice(0, 8000);
const links = Array.isArray(scrapedData.links) ? scrapedData.links.slice(0, 200) : [];
const payload = {
task: 'parse_brand_context',
request_id: requestId('parse_brand_context'),
markdown,
links_json: JSON.stringify(links),
metadata_json: JSON.stringify(scrapedData.metadata || {}),
images_json: JSON.stringify(scrapedData.images || []),
raw_json_blob: JSON.stringify(scrapedData.json || {}),
output_schema_text: 'Return ONLY valid JSON object with exactly these keys: brandName (string), tone (one of: friendly, professional, formal, casual, energetic), taglines (array of strings, max 3), colors (array of hex color strings, or empty array), relevantImageUrls (array of 3-5 absolute image URLs for logo/hero/product images only; no icons/tracking/data URLs). No markdown, no prose, no extra keys.',
must_return_json_only: 'true',
};
const data = await postWorkflow(WORKFLOW_URL_SCRAPE, payload);
const output = typeof data === 'string' ? parseJsonField(data, {}) : (data || {});
return {
brandName: String(output.brandName || '').trim() || 'Unknown Brand',
tone: ['friendly', 'professional', 'formal', 'casual', 'energetic'].includes(String(output.tone || '').toLowerCase())
? String(output.tone).toLowerCase()
: 'professional',
taglines: Array.isArray(output.taglines) ? output.taglines.slice(0, 3).map(String) : [],
colors: Array.isArray(output.colors) ? output.colors.map(String) : [],
relevantImageUrls: Array.isArray(output.relevantImageUrls) ? output.relevantImageUrls.map(String) : [],
};
}
async function generateTemplates(brandContext = {}, eventSlug, eventLabel) {
const eventDesc = EVENT_DESCRIPTIONS[eventSlug] || `A "${eventLabel}" event in the order lifecycle`;
const payload = {
task: 'generate_sms_templates',
request_id: requestId('generate_sms_templates'),
brand_name: String(brandContext.brandName || ''),
tone: String(brandContext.tone || ''),
taglines_json: JSON.stringify(Array.isArray(brandContext.taglines) ? brandContext.taglines : []),
event_slug: String(eventSlug || ''),
event_label: String(eventLabel || ''),
event_description: eventDesc,
trai_rules_text: TRAI_RULES_TEXT,
templates_count: '3',
max_chars: '160',
variable_format: '{#var#}',
output_schema_text: 'Return ONLY valid JSON object with exactly one key: templates (array of exactly 3 strings). No extra keys.',
must_return_json_only: 'true',
};
const data = await postWorkflow(WORKFLOW_URL_TEMPLATE, payload);
const output = typeof data === 'string' ? parseJsonField(data, {}) : (data || {});
const templates = Array.isArray(output.templates)
? output.templates
: parseJsonField(output.templates_json, []);
return Array.isArray(templates) ? templates.map(String).slice(0, 3) : [];
}
async function processCurl(rawCurl, approvedTemplate, eventSlug) {
const payload = {
task: 'process_provider_curl',
request_id: requestId('process_provider_curl'),
raw_curl: String(rawCurl || ''),
approved_template: String(approvedTemplate || ''),
event_slug: String(eventSlug || ''),
instructions_text: 'Identify placeholders, map to semantic field names, normalize placeholders in curl to camelCase, and build positional mapping for {#var#} tokens in approved_template.',
output_schema_text: 'Return ONLY valid JSON object with exactly these keys: processedCurl (string), variableMap (object where keys are {#var#}[index] and values are field names in camelCase). No extra keys.',
must_return_json_only: 'true',
};
const data = await postWorkflow(WORKFLOW_URL_CHECK_CURL, payload);
const output = typeof data === 'string' ? parseJsonField(data, {}) : (data || {});
const variableMap = typeof output.variableMap === 'object' && output.variableMap !== null
? output.variableMap
: parseJsonField(output.variable_map_json, {});
return {
processedCurl: String(output.processedCurl || ''),
variableMap: variableMap && typeof variableMap === 'object' ? variableMap : {},
};
}
module.exports = { parseBrandContext, generateTemplates, processCurl };