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 };