⚖️ THE FINAL WORD
"..."
diff --git a/functions/api/analyze.js b/functions/api/analyze.js
index c3a53a3..2c6d678 100644
--- a/functions/api/analyze.js
+++ b/functions/api/analyze.js
@@ -16,35 +16,192 @@ export async function onRequestPost(context) {
await env.KV_RATELIMIT.put(limitKey, (count + 1).toString(), { expirationTtl: 3600 });
}
- // 2. AI GENERATION
- let report = null;
- const systemPrompt = "You are 'The Algorithm', a brutally honest relationship analyst. Return ONLY a JSON object: { relationship_persona, compatibility_score, ai_insight: { dynamic_title, reality_check, recent_shift, red_flags: [], green_flags: [], brutal_verdict } }.";
+ // 2. AI GENERATION SETTINGS
+ const ANALYSIS_SCHEMA = {
+ "relationship_persona": "string (creative title)",
+ "compatibility_score": "integer 1-100",
+ "ai_insight": {
+ "dynamic_title": "string (short headline)",
+ "reality_check": "string (1-2 sentences)",
+ "recent_shift": "string (1-2 sentences)",
+ "red_flags": ["string"],
+ "green_flags": ["string"],
+ "brutal_verdict": "string (short impactful summary)",
+ "coaching_advice": "string (2-3 actionable sentences)",
+ "growth_areas": ["string"]
+ }
+ };
+
+ const PROVIDER_SYSTEM_PROMPTS = {
+ "openai": `You are an expert relationship analyst and communication coach. You provide brutally honest but helpful insights.`,
+ "anthropic": `
You are an expert relationship analyst and communication coach. You provide brutally honest but helpful insights.`,
+ "gemini": `You are an expert relationship analyst and communication coach. You provide brutally honest but helpful insights. 1. Be direct. 2. Follow schema exactly.`,
+ "openrouter": `You are an expert relationship analyst and communication coach. You provide brutally honest but helpful insights.`,
+ "cloudflare": `You are an expert relationship analyst and communication coach. You provide brutally honest but helpful insights.`,
+ "grok": `You are an expert relationship analyst and communication coach. You provide brutally honest but helpful insights.`,
+ "groq": `You are an expert relationship analyst and communication coach. You provide brutally honest but helpful insights.`
+ };
+
+ const ANALYSIS_PROMPT_TEMPLATE = `
+Analyze these anonymous conversation statistics and provide deep behavioral insights. Tone: ${tone}.
+
+## Statistics
+{stats_json}
+
+## Relationship Context
+- Person A: ${my_name}
+- Person B: ${partner_name}
+
+## Required Output
+Respond ONLY with valid JSON matching this exact schema. No preamble, no explanation, no markdown blocks.
+
+${JSON.stringify(ANALYSIS_SCHEMA, null, 2)}
+`;
- let userPrompt = `Analyze chat: ${my_name} & ${partner_name}. Tone: ${tone}. Stats: ${JSON.stringify(stats)}.`;
+ let statsJson = JSON.stringify(stats);
if (compare_data) {
- userPrompt = `COMPARE two chats for ${my_name}. Chat A: ${compare_data.nameA} vs Chat B: ${compare_data.nameB}. Stats A: ${JSON.stringify(compare_data.a)}. Stats B: ${JSON.stringify(compare_data.b)}. Be direct.`;
+ statsJson = `COMPARE two chats for ${my_name}. Chat A: ${compare_data.nameA} vs Chat B: ${compare_data.nameB}. Stats A: ${JSON.stringify(compare_data.a)}. Stats B: ${JSON.stringify(compare_data.b)}.`;
}
- if (provider === 'cloudflare' && env.AI) {
- const aiResult = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
- messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }]
- });
- const match = aiResult.response.match(/\{[\s\S]*\}/);
- if (match) report = JSON.parse(match[0]);
- } else if (api_key && (provider === 'openai' || provider === 'groq')) {
- const url = provider === 'openai' ? 'https://api.openai.com/v1/chat/completions' : 'https://api.groq.com/openai/v1/chat/completions';
- const model = provider === 'openai' ? 'gpt-4o-mini' : 'llama-3.1-70b-versatile';
- const resp = await fetch(url, {
- method: 'POST',
- headers: { 'Authorization': `Bearer ${api_key}`, 'Content-Type': 'application/json' },
- body: JSON.stringify({ model, messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }], response_format: { type: "json_object" } })
- });
- const resData = await resp.json();
- if (resData.choices) report = JSON.parse(resData.choices[0].message.content);
+ const systemPrompt = PROVIDER_SYSTEM_PROMPTS[provider] || PROVIDER_SYSTEM_PROMPTS["openai"];
+ const userPrompt = ANALYSIS_PROMPT_TEMPLATE.replace('{stats_json}', statsJson);
+
+ let report = null;
+
+ // Validation Function
+ const validateAnalysisResponse = (response) => {
+ if (!response) return false;
+ const requiredKeys = ['relationship_persona', 'compatibility_score', 'ai_insight'];
+ const hasOuter = requiredKeys.every(key => key in response);
+ if (!hasOuter) return false;
+ const requiredInsightKeys = ['dynamic_title', 'reality_check', 'recent_shift', 'red_flags', 'green_flags', 'brutal_verdict', 'coaching_advice', 'growth_areas'];
+ const hasInner = requiredInsightKeys.every(key => key in response.ai_insight);
+ return hasInner;
+ };
+
+ // LLM Caller
+ const callLLM = async (currentProvider, apiKey, sysPrompt, usrPrompt, strict = false) => {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 30000);
+
+ let finalUserPrompt = usrPrompt;
+ if (strict) {
+ finalUserPrompt += "\n\nCRITICAL: You MUST return ONLY valid JSON. No markdown backticks, no text before or after.";
+ }
+
+ try {
+ let parsed = null;
+
+ if (currentProvider === 'cloudflare' && env.AI) {
+ const aiResult = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
+ messages: [{ role: 'system', content: sysPrompt }, { role: 'user', content: finalUserPrompt }]
+ });
+ const match = aiResult.response.match(/\{[\s\S]*\}/);
+ if (match) parsed = JSON.parse(match[0]);
+ } else if (currentProvider === 'anthropic' && apiKey) {
+ const resp = await fetch('https://api.anthropic.com/v1/messages', {
+ method: 'POST',
+ headers: {
+ 'x-api-key': apiKey,
+ 'anthropic-version': '2023-06-01',
+ 'content-type': 'application/json'
+ },
+ body: JSON.stringify({
+ model: 'claude-3-haiku-20240307',
+ max_tokens: 1000,
+ system: sysPrompt,
+ messages: [{ role: 'user', content: finalUserPrompt }]
+ }),
+ signal: controller.signal
+ });
+ const resData = await resp.json();
+ if (resData.content && resData.content.length > 0) {
+ const text = resData.content[0].text;
+ const match = text.match(/\{[\s\S]*\}/);
+ if (match) parsed = JSON.parse(match[0]);
+ }
+ } else if (currentProvider === 'gemini' && apiKey) {
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`;
+ const resp = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ systemInstruction: { parts: [{ text: sysPrompt }] },
+ contents: [{ parts: [{ text: finalUserPrompt }] }],
+ generationConfig: { responseMimeType: "application/json" }
+ }),
+ signal: controller.signal
+ });
+ const resData = await resp.json();
+ if (resData.candidates && resData.candidates.length > 0) {
+ const text = resData.candidates[0].content.parts[0].text;
+ const match = text.match(/\{[\s\S]*\}/);
+ if (match) parsed = JSON.parse(match[0]);
+ }
+ } else if (apiKey && (currentProvider === 'openai' || currentProvider === 'grok' || currentProvider === 'openrouter' || currentProvider === 'groq')) {
+ let url, model;
+ if (currentProvider === 'openai') {
+ url = 'https://api.openai.com/v1/chat/completions';
+ model = 'gpt-4o-mini';
+ } else if (currentProvider === 'openrouter') {
+ url = 'https://openrouter.ai/api/v1/chat/completions';
+ model = 'openai/gpt-4o-mini';
+ } else if (currentProvider === 'grok') {
+ url = 'https://api.x.ai/v1/chat/completions';
+ model = 'grok-beta';
+ } else {
+ url = 'https://api.groq.com/openai/v1/chat/completions';
+ model = 'llama-3.1-70b-versatile';
+ }
+
+ const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' };
+ if (currentProvider === 'openrouter') {
+ headers['HTTP-Referer'] = 'https://thealgorithm.reports';
+ headers['X-Title'] = 'The Algorithm';
+ }
+
+ const body = {
+ model,
+ messages: [{ role: 'system', content: sysPrompt }, { role: 'user', content: finalUserPrompt }]
+ };
+
+ if (currentProvider !== 'openrouter') {
+ body.response_format = { type: "json_object" };
+ }
+
+ const resp = await fetch(url, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify(body),
+ signal: controller.signal
+ });
+ const resData = await resp.json();
+ if (resData.choices && resData.choices.length > 0) {
+ const text = resData.choices[0].message.content;
+ const match = text.match(/\{[\s\S]*\}/);
+ if (match) parsed = JSON.parse(match[0]);
+ }
+ }
+ clearTimeout(timeoutId);
+ return parsed;
+ } catch (err) {
+ clearTimeout(timeoutId);
+ let safeKey = apiKey ? apiKey.substring(0, 4) + '...' : 'none';
+ console.error(`LLM call failed for ${currentProvider} with key ${safeKey}: ${err.message}`);
+ return null;
+ }
+ };
+
+ // 3. EXECUTE & VALIDATE
+ report = await callLLM(provider, api_key, systemPrompt, userPrompt, false);
+
+ if (!validateAnalysisResponse(report)) {
+ // Retry once with stricter prompt
+ report = await callLLM(provider, api_key, systemPrompt, userPrompt, true);
}
- // 3. FALLBACK
- if (!report) {
+ // 4. FALLBACK
+ if (!validateAnalysisResponse(report)) {
report = {
relationship_persona: "Vibe Explorer",
compatibility_score: 80,
@@ -54,7 +211,9 @@ export async function onRequestPost(context) {
recent_shift: "The energy is stable.",
red_flags: ["Limited data for deep read"],
green_flags: ["Active check-ins"],
- brutal_verdict: "It's a vibe."
+ brutal_verdict: "It's a vibe.",
+ coaching_advice: "Keep communicating openly and building trust.",
+ growth_areas: ["More frequent deep conversations"]
}
};
}
@@ -65,4 +224,3 @@ export async function onRequestPost(context) {
return new Response(JSON.stringify({ error: e.message }), { status: 500 });
}
}
-
diff --git a/static/js/app.js b/static/js/app.js
index 14aa667..9621a31 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -147,14 +147,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
llmProviderEl?.addEventListener('change', (e) => updateProviderHint(e.target.value));
- saveSettingsBtn?.addEventListener('click', () => {
- const key = apiKeyEl ? apiKeyEl.value.trim() : '';
- sessionStorage.setItem('_llm_token', btoa(key));
- localStorage.setItem('hf_url', hfUrlEl ? hfUrlEl.value.trim() : '');
- localStorage.setItem('llm_provider', llmProviderEl ? llmProviderEl.value : 'cloudflare');
- updateApiKeyUI();
- hideModal(settingsModal);
- });
}
// --- UI Utilities ---
@@ -182,11 +174,67 @@ document.addEventListener('DOMContentLoaded', () => {
const hintEl = document.getElementById('providerHint');
const hfContainer = document.getElementById('hfUrlContainer');
if (!hintEl) return;
- const hints = { 'cloudflare': 'Free Tier (2 reports/hr)', 'openai': 'sk-proj-...', 'anthropic': 'sk-ant-...', 'gemini': 'Google API Key' };
+ const hints = {
+ 'cloudflare': 'Free Tier (2 reports/hr)',
+ 'openai': 'sk-proj-...',
+ 'anthropic': 'sk-ant-...',
+ 'gemini': '39-char Google API Key',
+ 'openrouter': 'sk-or-v1-...',
+ 'groq': 'gsk_...',
+ 'grok': 'xai-...'
+ };
hintEl.textContent = hints[provider] || '';
if (hfContainer) hfContainer.classList.toggle('hidden', provider !== 'huggingface');
}
+ const validateApiKeyFormat = (provider, key) => {
+ if (!key || key.trim() === '') return provider === 'cloudflare';
+ if (provider === 'openai' && !key.startsWith('sk-')) return false;
+ if (provider === 'anthropic' && !key.startsWith('sk-ant-')) return false;
+ if (provider === 'openrouter' && !key.startsWith('sk-or-')) return false;
+ if (provider === 'groq' && !key.startsWith('gsk_')) return false;
+ if (provider === 'grok' && !key.startsWith('xai-')) return false;
+ if (provider === 'gemini' && key.length !== 39) return false;
+ return true;
+ };
+
+ if (settingsBtn && settingsModal) {
+ settingsBtn.addEventListener('click', () => {
+ showModal(settingsModal);
+ document.getElementById('llmProvider')?.focus();
+ });
+ closeSettings?.addEventListener('click', () => hideModal(settingsModal));
+
+ const apiKeyEl = document.getElementById('apiKey');
+ const hfUrlEl = document.getElementById('hfUrl');
+ const llmProviderEl = document.getElementById('llmProvider');
+
+ if (apiKeyEl) apiKeyEl.value = sessionStorage.getItem('_llm_token') ? atob(sessionStorage.getItem('_llm_token')) : '';
+ if (hfUrlEl) hfUrlEl.value = localStorage.getItem('hf_url') || '';
+ const savedProvider = localStorage.getItem('llm_provider') || 'cloudflare';
+ if (llmProviderEl) {
+ llmProviderEl.value = savedProvider;
+ updateProviderHint(savedProvider);
+ }
+ llmProviderEl?.addEventListener('change', (e) => updateProviderHint(e.target.value));
+
+ saveSettingsBtn?.addEventListener('click', () => {
+ const key = apiKeyEl ? apiKeyEl.value.trim() : '';
+ const provider = llmProviderEl ? llmProviderEl.value : 'cloudflare';
+
+ if (provider !== 'cloudflare' && !validateApiKeyFormat(provider, key)) {
+ alert(`Invalid API key format for ${provider}. Please check and try again.`);
+ return;
+ }
+
+ sessionStorage.setItem('_llm_token', btoa(key));
+ localStorage.setItem('hf_url', hfUrlEl ? hfUrlEl.value.trim() : '');
+ localStorage.setItem('llm_provider', provider);
+ updateApiKeyUI();
+ hideModal(settingsModal);
+ });
+ }
+
const showError = (message) => {
const container = document.getElementById('errorContainer');
if (container) {
diff --git a/static/js/dashboard.js b/static/js/dashboard.js
index d554955..8c45729 100644
--- a/static/js/dashboard.js
+++ b/static/js/dashboard.js
@@ -262,8 +262,12 @@ document.addEventListener('DOMContentLoaded', () => {
const red = document.getElementById('ai-insight-red-flags');
const green = document.getElementById('ai-insight-green-flags');
+ const growth = document.getElementById('ai-insight-growth');
+ const coaching = document.getElementById('ai-insight-coaching');
if (red) red.innerHTML = (report.ai_insight?.red_flags || []).map(f => `
${f}`).join('');
if (green) green.innerHTML = (report.ai_insight?.green_flags || []).map(f => `
${f}`).join('');
+ if (growth) growth.innerHTML = (report.ai_insight?.growth_areas || []).map(f => `
${f}`).join('');
+ if (coaching) coaching.textContent = report.ai_insight?.coaching_advice || "";
loading.classList.add('hidden');
results.classList.remove('hidden');