summaryrefslogtreecommitdiff
path: root/api/ai.ts
blob: b2a3ee728f8db240a7a3057f468e15b58240c88d (plain)
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
import { OpenAI } from "openai";

const basePrompt = `
You are the backend logic for a game like the New York Times’ *Connections*.

A player gives you four words. Your job is to return the **most clever, satisfying category** that all four words fit into — as if you were designing a high-quality puzzle.

🎯 Output must be JSON:
{"categoryName": "Short Title (≤5 words)", "reason": "Brief, clear explanation why each word fits"}

🧠 Your answer should feel:
- **Clever and insightful** (not generic)
- **Tight and specific** (all 4 words must fit cleanly)
- **Surprising but satisfying** (think lateral thinking, not just surface meaning)

🧩 Great connections are often based on:
1. **Grammar or structure** (e.g., homonyms, stress-shifting words)
2. **Wordplay** (prefixes, rhymes, common idioms)
3. **Cultural patterns** (slang, media, jokes, Jeopardy-style trivia)
4. **Domain-specific themes** (tech terms, sports slang, myth references)

💡 Think like a puzzle maker. Test your idea:
- Would a smart, skeptical puzzle fan say “Ohhh, nice”?
- Does **each word** clearly belong?
- If not, scrap it and try another approach.

Avoid weak categories like:
- “Verbs” ❌ (too broad)
- “Things you can flip” ❌ (tenuous logic)
- “Nice things” ❌ (vague)

✔ Examples of great answers:
[record, permit, insult, reject] → "Stress-Shifting Words"  
[day, head, toe, man] → "___ to ___"  
[brew, java, mud, rocketfuel] → "Slang for Coffee"  
[duck, bank, mail, plant] → "Nouns That Are Verbs"  
[transexual, muslims, media, taxes] → "Fox News Scapegoats"  

⚠️ Don’t overreach. Do not invent connections — if it’s not clean, try a new angle.

Mandatory check before submitting:
- Word 1: does it clearly fit?
- Word 2: does it clearly fit?
- Word 3: does it clearly fit?
- Word 4: does it clearly fit?

If even one doesn’t, the category is wrong. Start over.

Keep it **fun**, **tight**, and **clever**. Never lazy. Never vague.

🎯 Output must be JSON:
{"categoryName": "Short Title (≤5 words)", "reason": "Brief, clear explanation why each word fits"}
`;

let client: OpenAI;

const getCompletion = async ({
  messages,
}: {
  messages: string[];
}): Promise<string> => {
  if (!client) {
    client = new OpenAI({ apiKey: process.env.OPENAI_API! });
  }
  const completion = await client.chat.completions.create({
    model: "gpt-4.1",
    messages: messages.map((message) => ({
      role: "developer",
      content: message,
    })),
  });
  return completion.choices[0].message.content!;
};

export const getGroupName = async (
  words: string[]
): Promise<{
  categoryName: string;
  reason: string;
}> => {
  let candidate: { categoryName: string; reason: string };
  const messages = [
    basePrompt,
    `Now, given these four words: ${[words.join(", ")]}`,
  ];
  candidate = JSON.parse(await getCompletion({ messages }));
  if (!candidate.categoryName || !candidate.reason) {
    throw new Error(`Got invalid response!`);
  }
  console.log(`Got candidate: ${JSON.stringify(candidate)}`);

  return candidate!;
};