antenna-fyi 1.3.26 → 1.3.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core.js +62 -0
- package/package.json +1 -1
package/lib/core.js
CHANGED
|
@@ -45,6 +45,22 @@ async function generateMatchReason(myLines, theirLines) {
|
|
|
45
45
|
} catch { return null; }
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
async function generateArchetypeReason(archetype, profileText) {
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(`${_url || DEFAULT_URL}/functions/v1/generate-archetype`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
"Authorization": `Bearer ${process.env.ANTENNA_SUPABASE_KEY || process.env.ANTENNA_KEY || DEFAULT_KEY}`,
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({ archetype, profile_text: profileText }),
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok) return null;
|
|
59
|
+
const data = await res.json();
|
|
60
|
+
return data || null;
|
|
61
|
+
} catch { return null; }
|
|
62
|
+
}
|
|
63
|
+
|
|
48
64
|
export function getClient(url, key) {
|
|
49
65
|
const u = url || process.env.ANTENNA_SUPABASE_URL || process.env.ANTENNA_URL || DEFAULT_URL;
|
|
50
66
|
const k = key || process.env.ANTENNA_SUPABASE_KEY || process.env.ANTENNA_KEY || DEFAULT_KEY;
|
|
@@ -311,6 +327,7 @@ export async function setProfile({
|
|
|
311
327
|
// Read back profile to get slug for public page link
|
|
312
328
|
let publicUrl = null;
|
|
313
329
|
let bindUrl = null;
|
|
330
|
+
let archetypeResult = null;
|
|
314
331
|
try {
|
|
315
332
|
const profile = await getProfile({ device_id, supabaseUrl, supabaseKey });
|
|
316
333
|
if (profile?.profile_slug) {
|
|
@@ -318,6 +335,50 @@ export async function setProfile({
|
|
|
318
335
|
}
|
|
319
336
|
} catch {}
|
|
320
337
|
|
|
338
|
+
// Generate personalized archetype description via LLM
|
|
339
|
+
try {
|
|
340
|
+
const profileText = [line1, line2, line3, matching_context].filter(Boolean).join(". ");
|
|
341
|
+
if (profileText) {
|
|
342
|
+
// Simple keyword-based archetype detection (same logic as frontend)
|
|
343
|
+
const corpus = profileText.toLowerCase();
|
|
344
|
+
const archetypeKeywords = {
|
|
345
|
+
Prometheus: ["ai", "agent", "llm", "founder", "startup", "build", "developer", "hacker", "tools", "\u667a\u80fd\u4f53", "\u521b\u4e1a", "\u5f00\u53d1"],
|
|
346
|
+
Athena: ["product", "strategy", "research", "design", "craft", "pm", "ux", "\u4ea7\u54c1", "\u8bbe\u8ba1", "\u7814\u7a76"],
|
|
347
|
+
Hermes: ["network", "connect", "community", "social", "bridge", "\u793e\u4ea4", "\u8fde\u63a5", "\u793e\u533a"],
|
|
348
|
+
Apollo: ["music", "media", "content", "creator", "writing", "taste", "\u97f3\u4e50", "\u5185\u5bb9", "\u521b\u4f5c"],
|
|
349
|
+
Artemis: ["independent", "explore", "freelance", "health", "outdoor", "\u72ec\u7acb", "\u63a2\u7d22"],
|
|
350
|
+
Aphrodite: ["beauty", "brand", "fashion", "relationship", "\u7f8e", "\u54c1\u724c", "\u65f6\u5c1a"],
|
|
351
|
+
Dionysus: ["event", "culture", "party", "art", "festival", "\u6d3b\u52a8", "\u6587\u5316", "\u827a\u672f"],
|
|
352
|
+
Hades: ["finance", "invest", "infrastructure", "backend", "security", "\u6295\u8d44", "\u91d1\u878d", "\u67b6\u6784"],
|
|
353
|
+
Persephone: ["transform", "cross", "research", "academic", "bridge", "\u8de8\u754c", "\u7814\u7a76", "\u5b66\u672f"],
|
|
354
|
+
Odysseus: ["founder", "journey", "resilience", "travel", "startup", "\u521b\u4e1a", "\u65c5\u884c"],
|
|
355
|
+
};
|
|
356
|
+
let bestArchetype = "Prometheus";
|
|
357
|
+
let bestScore = 0;
|
|
358
|
+
for (const [role, keywords] of Object.entries(archetypeKeywords)) {
|
|
359
|
+
const score = keywords.reduce((s, kw) => s + (corpus.includes(kw) ? 1 : 0), 0);
|
|
360
|
+
if (score > bestScore) { bestScore = score; bestArchetype = role; }
|
|
361
|
+
}
|
|
362
|
+
archetypeResult = await generateArchetypeReason(bestArchetype, profileText);
|
|
363
|
+
if (archetypeResult?.reason) {
|
|
364
|
+
archetypeResult.archetype = bestArchetype;
|
|
365
|
+
// Write archetype back to matching_context so dashboard can display it
|
|
366
|
+
try {
|
|
367
|
+
const profile = await getProfile({ device_id, supabaseUrl, supabaseKey });
|
|
368
|
+
let ctx = {};
|
|
369
|
+
try { ctx = JSON.parse(profile?.matching_context || "{}"); } catch {}
|
|
370
|
+
ctx.archetypeOverride = { name: bestArchetype, reason: archetypeResult.reason, reasonZh: archetypeResult.reasonZh };
|
|
371
|
+
await sb.rpc("upsert_profile", {
|
|
372
|
+
p_device_id: device_id,
|
|
373
|
+
p_matching_context: JSON.stringify(ctx),
|
|
374
|
+
});
|
|
375
|
+
} catch {}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} catch (e) {
|
|
379
|
+
console.error("Archetype generation failed (non-fatal):", e.message);
|
|
380
|
+
}
|
|
381
|
+
|
|
321
382
|
// Auto-generate GPS bind link
|
|
322
383
|
try {
|
|
323
384
|
const bind = await createBindToken({ device_id, supabaseUrl, supabaseKey });
|
|
@@ -328,6 +389,7 @@ export async function setProfile({
|
|
|
328
389
|
...data,
|
|
329
390
|
public_url: publicUrl,
|
|
330
391
|
gps_bind_url: bindUrl,
|
|
392
|
+
archetype: archetypeResult || null,
|
|
331
393
|
next_step: "Send the public_url and gps_bind_url to the user. The GPS link should be opened on their phone to share location.",
|
|
332
394
|
};
|
|
333
395
|
}
|