File size: 6,636 Bytes
496ef98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
181
182
183
184
185
186
187
188
189
190
/**
 * PAIPS Caption Generator — core logic
 * Generates Instagram captions, LinkedIn posts, carousel breakdowns.
 * Tracks and analyses post performance.
 */

const fs = require('fs');
const path = require('path');
const fetch = require('node-fetch');

const DB_FILE = path.join(__dirname, 'data', 'performance.json');
const MODEL = 'qwen/qwen3.6-plus:free';

function getApiKey() {
    return process.env.OPENROUTER_API_KEY;
}

async function llm(prompt, maxTokens = 1024) {
    const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${getApiKey()}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            model: MODEL,
            messages: [{ role: 'user', content: prompt }],
            max_tokens: maxTokens,
        }),
    });
    if (!res.ok) throw new Error(`OpenRouter ${res.status}: ${await res.text()}`);
    const data = await res.json();
    return data.choices[0].message.content.trim();
}

async function generateInstagram(tool, creation, beforeAfter, vibe) {
    return llm(`Generate an Instagram caption for an AI demo post using the PAIPS framework.

Input:
- AI Tool: ${tool}
- What was created: ${creation}
- Before/After: ${beforeAfter}
- Vibe: ${vibe}

PAIPS Structure (use internally, do NOT print labels):
1. Problem — The challenge or pain point the audience faces
2. Agitate — Why it matters / what's at stake
3. Intrigue — A "what if..." that opens possibility
4. Positive Future — Paint the result/transformation
5. Solution — CTA to learn more

Rules:
- MUST be 150-200 words
- MUST include relevant emojis throughout
- MUST end with a CTA to follow or DM for more
- Vibe must match: ${vibe}
- First person, conversational tone
- No hashtags
- Output ONLY the caption, nothing else`, 512);
}

async function generateLinkedIn(tool, creation, beforeAfter, vibe) {
    return llm(`Generate a LinkedIn post for an AI demo using the PAIPS framework reframed for thought leadership.

Input:
- AI Tool: ${tool}
- What was created: ${creation}
- Before/After: ${beforeAfter}
- Vibe: ${vibe}

PAIPS Structure (use internally, do NOT print labels):
1. Problem — A business inefficiency or scaling challenge your audience recognises
2. Agitate — The compounding cost of ignoring this (time, revenue, opportunity)
3. Intrigue — "What if your team/business could..." (scale angle, not cool demo)
4. Positive Future — The concrete business outcome this unlocks
5. Solution — CTA: follow for more, connect, or comment for the breakdown

Rules:
- MUST be 200-300 words
- Professional but authentic — no corporate jargon
- Frame around business value and scale, NOT "cool AI tool"
- 2-3 emojis maximum, used sparingly
- Short punchy first line (LinkedIn hook)
- No hashtags
- Output ONLY the post, nothing else`, 700);
}

async function generateCarousel(tool, creation, beforeAfter, vibe) {
    return llm(`Generate a 9-slide Instagram carousel breakdown for an AI demo post.

Input:
- AI Tool: ${tool}
- What was created: ${creation}
- Before/After: ${beforeAfter}
- Vibe: ${vibe}

For each slide provide exactly:
Slide N: [TITLE]
Description: [1-2 sentence copy for the slide]
Visual: [Specific Canva design suggestion — layout, colors, elements]

Slide structure:
1. Hook slide — Bold problem statement or provocative question
2. The old way — What people do without AI (the pain)
3. The turning point — The moment everything changed
4. The tool reveal — What AI tool was used and why
5. Step 1 — First step of the process
6. Step 2 — Second step of the process
7. Step 3 / Result — The output or transformation
8. Business impact — Why this matters for scale/growth
9. CTA slide — What to do next (follow, DM, save)

Rules:
- Each title MUST be under 6 words
- Descriptions are the caption text on the slide
- Visual suggestions must be specific and actionable for Canva
- Match the vibe: ${vibe}
- Output all 9 slides, nothing else`, 900);
}

async function generateAll(tool, creation, beforeAfter, vibe) {
    const [instagram, linkedin, carousel] = await Promise.all([
        generateInstagram(tool, creation, beforeAfter, vibe),
        generateLinkedIn(tool, creation, beforeAfter, vibe),
        generateCarousel(tool, creation, beforeAfter, vibe),
    ]);
    return { instagram, linkedin, carousel };
}

// ── Performance Tracker ───────────────────────────────────────────────────────

function loadDb() {
    if (!fs.existsSync(DB_FILE)) return [];
    return JSON.parse(fs.readFileSync(DB_FILE, 'utf8'));
}

function saveDb(data) {
    fs.mkdirSync(path.dirname(DB_FILE), { recursive: true });
    fs.writeFileSync(DB_FILE, JSON.stringify(data, null, 2), 'utf8');
}

function engagementScore(ig, li) {
    const igScore = ig.likes + ig.comments * 3 + ig.saves * 5 + ig.signups * 10;
    const liScore = li.reactions + li.comments * 3 + li.reposts * 5 + li.signups * 10;
    return { igScore, liScore, winner: igScore >= liScore ? 'Instagram' : 'LinkedIn' };
}

function trackPost(record) {
    const db = loadDb();
    const scores = engagementScore(record.instagram, record.linkedin);
    const entry = { ...record, date: record.date || new Date().toISOString().slice(0, 10), scores };
    db.push(entry);
    saveDb(db);
    return entry;
}

function getInsights() {
    const db = loadDb();
    if (!db.length) return { posts: [], toolStats: {}, totals: { ig: 0, li: 0 }, topTool: null, topPlatform: null };

    const toolStats = {};
    let totalIg = 0, totalLi = 0;

    for (const r of db) {
        const t = r.tool;
        if (!toolStats[t]) toolStats[t] = { posts: 0, igScore: 0, liScore: 0, signups: 0 };
        const ig = r.instagram, li = r.linkedin;
        const igs = ig.likes + ig.comments * 3 + ig.saves * 5 + ig.signups * 10;
        const lis = li.reactions + li.comments * 3 + li.reposts * 5 + li.signups * 10;
        toolStats[t].posts++;
        toolStats[t].igScore += igs;
        toolStats[t].liScore += lis;
        toolStats[t].signups += ig.signups + li.signups;
        totalIg += igs;
        totalLi += lis;
    }

    const topTool = Object.entries(toolStats).sort((a, b) => b[1].signups - a[1].signups)[0]?.[0];

    return {
        posts: db,
        toolStats,
        totals: { ig: totalIg, li: totalLi },
        topTool,
        topPlatform: totalIg >= totalLi ? 'Instagram' : 'LinkedIn',
    };
}

module.exports = { generateAll, trackPost, getInsights };