Felix Zieger commited on
Commit
47fea40
·
1 Parent(s): 5bd3ab2

larger models again

Browse files
src/components/GameContainer.tsx CHANGED
@@ -78,7 +78,7 @@ export const GameContainer = () => {
78
  setCurrentTheme(theme);
79
  try {
80
  const word = theme === "standard" ?
81
- getRandomWord() :
82
  await getThemedWord(theme, usedWords, language);
83
  setCurrentWord(word);
84
  setGameState("building-sentence");
@@ -158,7 +158,7 @@ export const GameContainer = () => {
158
  const getNewWord = async () => {
159
  try {
160
  const word = currentTheme === "standard" ?
161
- getRandomWord() :
162
  await getThemedWord(currentTheme, usedWords, language);
163
  setCurrentWord(word);
164
  setGameState("building-sentence");
 
78
  setCurrentTheme(theme);
79
  try {
80
  const word = theme === "standard" ?
81
+ getRandomWord(language) :
82
  await getThemedWord(theme, usedWords, language);
83
  setCurrentWord(word);
84
  setGameState("building-sentence");
 
158
  const getNewWord = async () => {
159
  try {
160
  const word = currentTheme === "standard" ?
161
+ getRandomWord(language) :
162
  await getThemedWord(currentTheme, usedWords, language);
163
  setCurrentWord(word);
164
  setGameState("building-sentence");
src/components/HighScoreBoard.tsx CHANGED
@@ -37,6 +37,7 @@ interface HighScoreBoardProps {
37
  onClose: () => void;
38
  onPlayAgain: () => void;
39
  sessionId: string;
 
40
  }
41
 
42
  const ITEMS_PER_PAGE = 5;
@@ -60,6 +61,7 @@ export const HighScoreBoard = ({
60
  avgWordsPerRound,
61
  onClose,
62
  sessionId,
 
63
  }: HighScoreBoardProps) => {
64
  const [playerName, setPlayerName] = useState("");
65
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -188,6 +190,7 @@ export const HighScoreBoard = ({
188
  }
189
 
190
  setHasSubmitted(true);
 
191
  setPlayerName("");
192
  } catch (error) {
193
  console.error("Error submitting score:", error);
 
37
  onClose: () => void;
38
  onPlayAgain: () => void;
39
  sessionId: string;
40
+ onScoreSubmitted?: () => void;
41
  }
42
 
43
  const ITEMS_PER_PAGE = 5;
 
61
  avgWordsPerRound,
62
  onClose,
63
  sessionId,
64
+ onScoreSubmitted,
65
  }: HighScoreBoardProps) => {
66
  const [playerName, setPlayerName] = useState("");
67
  const [isSubmitting, setIsSubmitting] = useState(false);
 
190
  }
191
 
192
  setHasSubmitted(true);
193
+ onScoreSubmitted?.();
194
  setPlayerName("");
195
  } catch (error) {
196
  console.error("Error submitting score:", error);
src/components/game/GuessDisplay.tsx CHANGED
@@ -10,6 +10,16 @@ import { useState, useEffect } from "react";
10
  import { useTranslation } from "@/hooks/useTranslation";
11
  import { supabase } from "@/integrations/supabase/client";
12
  import { House } from "lucide-react";
 
 
 
 
 
 
 
 
 
 
13
 
14
  interface GuessDisplayProps {
15
  sentence: string[];
@@ -37,6 +47,8 @@ export const GuessDisplay = ({
37
  const isGuessCorrect = () => aiGuess.toLowerCase() === currentWord.toLowerCase();
38
  const isCheating = () => aiGuess === 'CHEATING';
39
  const [isDialogOpen, setIsDialogOpen] = useState(false);
 
 
40
  const t = useTranslation();
41
 
42
  useEffect(() => {
@@ -48,7 +60,8 @@ export const GuessDisplay = ({
48
  target_word: currentWord,
49
  description: sentence.join(' '),
50
  ai_guess: aiGuess,
51
- is_correct: isGuessCorrect()
 
52
  });
53
 
54
  if (error) {
@@ -64,39 +77,60 @@ export const GuessDisplay = ({
64
  saveGameResult();
65
  }, []);
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  return (
68
  <motion.div
69
  initial={{ opacity: 0 }}
70
  animate={{ opacity: 1 }}
71
  className="text-center relative space-y-6"
72
  >
73
- <div className="flex items-center justify-between mb-4">
74
  <Button
75
  variant="ghost"
76
  size="icon"
77
- onClick={onBack}
78
  className="text-gray-600 hover:text-primary"
79
  >
80
  <House className="h-5 w-5" />
81
  </Button>
 
82
  <div className="bg-primary/10 px-3 py-1 rounded-lg">
83
  <span className="text-sm font-medium text-primary">
84
  {t.game.round} {currentScore + 1}
85
  </span>
86
  </div>
87
- <div className="w-8" /> {/* Spacer for centering */}
88
  </div>
89
 
90
- <div>
91
- <h2 className="mb-4 text-2xl font-semibold text-gray-900">Think in Sync</h2>
92
-
93
- <div className="space-y-2">
94
- <p className="text-sm text-gray-600">{t.guess.goalDescription}</p>
95
- <div className="overflow-hidden rounded-lg bg-secondary/10">
96
- <p className="p-4 text-2xl font-bold tracking-wider text-secondary">
97
- {currentWord}
98
- </p>
99
- </div>
100
  </div>
101
  </div>
102
 
@@ -148,6 +182,7 @@ export const GuessDisplay = ({
148
  onPlayAgain();
149
  }}
150
  sessionId={sessionId}
 
151
  />
152
  </DialogContent>
153
  </Dialog>
@@ -160,6 +195,23 @@ export const GuessDisplay = ({
160
  </>
161
  )}
162
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  </motion.div>
164
  );
165
  };
 
10
  import { useTranslation } from "@/hooks/useTranslation";
11
  import { supabase } from "@/integrations/supabase/client";
12
  import { House } from "lucide-react";
13
+ import {
14
+ AlertDialog,
15
+ AlertDialogAction,
16
+ AlertDialogCancel,
17
+ AlertDialogContent,
18
+ AlertDialogDescription,
19
+ AlertDialogFooter,
20
+ AlertDialogHeader,
21
+ AlertDialogTitle,
22
+ } from "@/components/ui/alert-dialog";
23
 
24
  interface GuessDisplayProps {
25
  sentence: string[];
 
47
  const isGuessCorrect = () => aiGuess.toLowerCase() === currentWord.toLowerCase();
48
  const isCheating = () => aiGuess === 'CHEATING';
49
  const [isDialogOpen, setIsDialogOpen] = useState(false);
50
+ const [showConfirmDialog, setShowConfirmDialog] = useState(false);
51
+ const [hasSubmittedScore, setHasSubmittedScore] = useState(false);
52
  const t = useTranslation();
53
 
54
  useEffect(() => {
 
60
  target_word: currentWord,
61
  description: sentence.join(' '),
62
  ai_guess: aiGuess,
63
+ is_correct: isGuessCorrect(),
64
+ session_id: sessionId
65
  });
66
 
67
  if (error) {
 
77
  saveGameResult();
78
  }, []);
79
 
80
+ const handleHomeClick = () => {
81
+ console.log('Home button clicked', { currentScore, hasSubmittedScore });
82
+ if (currentScore > 0 && !hasSubmittedScore) {
83
+ setShowConfirmDialog(true);
84
+ } else {
85
+ if (onBack) {
86
+ console.log('Navigating back to welcome screen');
87
+ onBack();
88
+ }
89
+ }
90
+ };
91
+
92
+ const handleScoreSubmitted = () => {
93
+ console.log('Score submitted, updating state');
94
+ setHasSubmittedScore(true);
95
+ };
96
+
97
+ const handleConfirmHome = () => {
98
+ console.log('Confirmed navigation to home');
99
+ setShowConfirmDialog(false);
100
+ if (onBack) {
101
+ onBack();
102
+ }
103
+ };
104
+
105
  return (
106
  <motion.div
107
  initial={{ opacity: 0 }}
108
  animate={{ opacity: 1 }}
109
  className="text-center relative space-y-6"
110
  >
111
+ <div className="flex items-center justify-between">
112
  <Button
113
  variant="ghost"
114
  size="icon"
115
+ onClick={handleHomeClick}
116
  className="text-gray-600 hover:text-primary"
117
  >
118
  <House className="h-5 w-5" />
119
  </Button>
120
+ <h2 className="text-2xl font-semibold text-gray-900">Think in Sync</h2>
121
  <div className="bg-primary/10 px-3 py-1 rounded-lg">
122
  <span className="text-sm font-medium text-primary">
123
  {t.game.round} {currentScore + 1}
124
  </span>
125
  </div>
 
126
  </div>
127
 
128
+ <div className="space-y-2">
129
+ <p className="text-sm text-gray-600">{t.guess.goalDescription}</p>
130
+ <div className="overflow-hidden rounded-lg bg-secondary/10">
131
+ <p className="p-4 text-2xl font-bold tracking-wider text-secondary">
132
+ {currentWord}
133
+ </p>
 
 
 
 
134
  </div>
135
  </div>
136
 
 
182
  onPlayAgain();
183
  }}
184
  sessionId={sessionId}
185
+ onScoreSubmitted={handleScoreSubmitted}
186
  />
187
  </DialogContent>
188
  </Dialog>
 
195
  </>
196
  )}
197
  </div>
198
+
199
+ <AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
200
+ <AlertDialogContent>
201
+ <AlertDialogHeader>
202
+ <AlertDialogTitle>{t.game.leaveGameTitle}</AlertDialogTitle>
203
+ <AlertDialogDescription>
204
+ {t.game.leaveGameDescription}
205
+ </AlertDialogDescription>
206
+ </AlertDialogHeader>
207
+ <AlertDialogFooter>
208
+ <AlertDialogCancel>{t.game.cancel}</AlertDialogCancel>
209
+ <AlertDialogAction onClick={handleConfirmHome}>
210
+ {t.game.confirm}
211
+ </AlertDialogAction>
212
+ </AlertDialogFooter>
213
+ </AlertDialogContent>
214
+ </AlertDialog>
215
  </motion.div>
216
  );
217
  };
src/components/game/ThemeSelector.tsx CHANGED
@@ -22,23 +22,13 @@ export const ThemeSelector = ({ onThemeSelect, onBack }: ThemeSelectorProps) =>
22
  const t = useTranslation();
23
  const { language } = useContext(LanguageContext);
24
 
25
- useEffect(() => {
26
- if (language !== 'en') {
27
- setSelectedTheme("technology");
28
- }
29
- }, [language]);
30
-
31
  useEffect(() => {
32
  const handleKeyPress = (e: KeyboardEvent) => {
33
  if (e.target instanceof HTMLInputElement) return;
34
 
35
  switch(e.key.toLowerCase()) {
36
  case 'a':
37
- if (language === 'en') {
38
- setSelectedTheme("standard");
39
- } else {
40
- setSelectedTheme("technology");
41
- }
42
  break;
43
  case 'b':
44
  setSelectedTheme("sports");
@@ -112,24 +102,14 @@ export const ThemeSelector = ({ onThemeSelect, onBack }: ThemeSelectorProps) =>
112
 
113
  <p className="text-gray-600 text-center">{t.themes.subtitle}</p>
114
 
115
- <div className="space-y-4">
116
- {language === 'en' ? (
117
- <Button
118
- variant={selectedTheme === "standard" ? "default" : "outline"}
119
- className="w-full justify-between"
120
- onClick={() => setSelectedTheme("standard")}
121
- >
122
- {t.themes.standard} <span className="text-sm opacity-50">{t.themes.pressKey} A</span>
123
- </Button>
124
- ) : (
125
- <Button
126
- variant={selectedTheme === "technology" ? "default" : "outline"}
127
- className="w-full justify-between"
128
- onClick={() => setSelectedTheme("technology")}
129
- >
130
- {t.themes.technology} <span className="text-sm opacity-50">{t.themes.pressKey} A</span>
131
- </Button>
132
- )}
133
 
134
  <Button
135
  variant={selectedTheme === "sports" ? "default" : "outline"}
 
22
  const t = useTranslation();
23
  const { language } = useContext(LanguageContext);
24
 
 
 
 
 
 
 
25
  useEffect(() => {
26
  const handleKeyPress = (e: KeyboardEvent) => {
27
  if (e.target instanceof HTMLInputElement) return;
28
 
29
  switch(e.key.toLowerCase()) {
30
  case 'a':
31
+ setSelectedTheme("standard");
 
 
 
 
32
  break;
33
  case 'b':
34
  setSelectedTheme("sports");
 
102
 
103
  <p className="text-gray-600 text-center">{t.themes.subtitle}</p>
104
 
105
+ <div className="space-y-4">
106
+ <Button
107
+ variant={selectedTheme === "standard" ? "default" : "outline"}
108
+ className="w-full justify-between"
109
+ onClick={() => setSelectedTheme("standard")}
110
+ >
111
+ {t.themes.standard} <span className="text-sm opacity-50">{t.themes.pressKey} A</span>
112
+ </Button>
 
 
 
 
 
 
 
 
 
 
113
 
114
  <Button
115
  variant={selectedTheme === "sports" ? "default" : "outline"}
src/lib/words.ts CHANGED
@@ -1,4 +1,4 @@
1
- export const words = [
2
  "DOG",
3
  "CAT",
4
  "SUN",
@@ -103,6 +103,235 @@ export const words = [
103
  "BALLON"
104
  ];
105
 
106
- export const getRandomWord = () => {
107
- return words[Math.floor(Math.random() * words.length)];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  };
 
1
+ export const englishWords = [
2
  "DOG",
3
  "CAT",
4
  "SUN",
 
103
  "BALLON"
104
  ];
105
 
106
+ export const germanWords = [
107
+ "HUND",
108
+ "KATZE",
109
+ "SONNE",
110
+ "REGEN",
111
+ "BAUM",
112
+ "STERN",
113
+ "MOND",
114
+ "FISCH",
115
+ "VOGEL",
116
+ "WOLKE",
117
+ "HIMMEL",
118
+ "WIND",
119
+ "SCHNEE",
120
+ "BLUME",
121
+ "WASSER",
122
+ "OZEAN",
123
+ "FLUSS",
124
+ "BERG",
125
+ "WALD",
126
+ "HAUS",
127
+ "KERZE",
128
+ "GARTEN",
129
+ "BRÜCKE",
130
+ "INSEL",
131
+ "LICHT",
132
+ "DONNER",
133
+ "LÄCHELN",
134
+ "FREUND",
135
+ "FAMILIE",
136
+ "APFEL",
137
+ "BANANE",
138
+ "AUTO",
139
+ "BOOT",
140
+ "BALL",
141
+ "KUCHEN",
142
+ "FROSCH",
143
+ "PFERD",
144
+ "LÖWE",
145
+ "AFFE",
146
+ "PANDA",
147
+ "FLUGZEUG",
148
+ "ZUG",
149
+ "BONBON",
150
+ "SPRINGEN",
151
+ "SPIELEN",
152
+ "SCHLAFEN",
153
+ "LACHEN",
154
+ "TRAUM",
155
+ "GLÜCK",
156
+ "FARBE"
157
+ ];
158
+
159
+ export const frenchWords = [
160
+ "CHIEN",
161
+ "CHAT",
162
+ "SOLEIL",
163
+ "PLUIE",
164
+ "ARBRE",
165
+ "ÉTOILE",
166
+ "LUNE",
167
+ "POISSON",
168
+ "OISEAU",
169
+ "NUAGE",
170
+ "CIEL",
171
+ "VENT",
172
+ "NEIGE",
173
+ "FLEUR",
174
+ "EAU",
175
+ "OCÉAN",
176
+ "RIVIÈRE",
177
+ "MONTAGNE",
178
+ "FORÊT",
179
+ "MAISON",
180
+ "BOUGIE",
181
+ "JARDIN",
182
+ "PONT",
183
+ "ÎLE",
184
+ "LUMIÈRE",
185
+ "TONNERRE",
186
+ "SOURIRE",
187
+ "AMI",
188
+ "FAMILLE",
189
+ "POMME",
190
+ "BANANE",
191
+ "VOITURE",
192
+ "BATEAU",
193
+ "BALLON",
194
+ "GÂTEAU",
195
+ "GRENOUILLE",
196
+ "CHEVAL",
197
+ "LION",
198
+ "SINGE",
199
+ "PANDA",
200
+ "AVION",
201
+ "TRAIN",
202
+ "BONBON",
203
+ "SAUTER",
204
+ "JOUER",
205
+ "DORMIR",
206
+ "RIRE",
207
+ "RÊVE",
208
+ "BONHEUR",
209
+ "COULEUR"
210
+ ];
211
+
212
+ export const italianWords = [
213
+ "CANE",
214
+ "GATTO",
215
+ "SOLE",
216
+ "PIOGGIA",
217
+ "ALBERO",
218
+ "STELLA",
219
+ "LUNA",
220
+ "PESCE",
221
+ "UCCELLO",
222
+ "NUVOLA",
223
+ "CIELO",
224
+ "VENTO",
225
+ "NEVE",
226
+ "FIORE",
227
+ "ACQUA",
228
+ "OCEANO",
229
+ "FIUME",
230
+ "MONTAGNA",
231
+ "FORESTA",
232
+ "CASA",
233
+ "CANDELA",
234
+ "GIARDINO",
235
+ "PONTE",
236
+ "ISOLA",
237
+ "LUCE",
238
+ "TUONO",
239
+ "SORRISO",
240
+ "AMICO",
241
+ "FAMIGLIA",
242
+ "MELA",
243
+ "BANANA",
244
+ "AUTO",
245
+ "BARCA",
246
+ "PALLA",
247
+ "TORTA",
248
+ "RANA",
249
+ "CAVALLO",
250
+ "LEONE",
251
+ "SCIMMIA",
252
+ "PANDA",
253
+ "AEREO",
254
+ "TRENO",
255
+ "CARAMELLA",
256
+ "SALTARE",
257
+ "GIOCARE",
258
+ "DORMIRE",
259
+ "RIDERE",
260
+ "SOGNO",
261
+ "FELICITÀ",
262
+ "COLORE"
263
+ ];
264
+
265
+ export const spanishWords = [
266
+ "PERRO",
267
+ "GATO",
268
+ "SOL",
269
+ "LLUVIA",
270
+ "ÁRBOL",
271
+ "ESTRELLA",
272
+ "LUNA",
273
+ "PEZ",
274
+ "PÁJARO",
275
+ "NUBE",
276
+ "CIELO",
277
+ "VIENTO",
278
+ "NIEVE",
279
+ "FLOR",
280
+ "AGUA",
281
+ "OCÉANO",
282
+ "RÍO",
283
+ "MONTAÑA",
284
+ "BOSQUE",
285
+ "CASA",
286
+ "VELA",
287
+ "JARDÍN",
288
+ "PUENTE",
289
+ "ISLA",
290
+ "LUZ",
291
+ "TRUENO",
292
+ "SONRISA",
293
+ "AMIGO",
294
+ "FAMILIA",
295
+ "MANZANA",
296
+ "PLÁTANO",
297
+ "COCHE",
298
+ "BARCO",
299
+ "PELOTA",
300
+ "PASTEL",
301
+ "RANA",
302
+ "CABALLO",
303
+ "LEÓN",
304
+ "MONO",
305
+ "PANDA",
306
+ "AVIÓN",
307
+ "TREN",
308
+ "CARAMELO",
309
+ "SALTAR",
310
+ "JUGAR",
311
+ "DORMIR",
312
+ "REÍR",
313
+ "SUEÑO",
314
+ "FELICIDAD",
315
+ "COLOR"
316
+ ];
317
+
318
+ export const getRandomWord = (language: string = 'en') => {
319
+ let wordList;
320
+ switch (language) {
321
+ case 'de':
322
+ wordList = germanWords;
323
+ break;
324
+ case 'fr':
325
+ wordList = frenchWords;
326
+ break;
327
+ case 'it':
328
+ wordList = italianWords;
329
+ break;
330
+ case 'es':
331
+ wordList = spanishWords;
332
+ break;
333
+ default:
334
+ wordList = englishWords;
335
+ }
336
+ return wordList[Math.floor(Math.random() * wordList.length)];
337
  };
src/services/mistralService.ts CHANGED
@@ -31,8 +31,11 @@ export const generateAIResponse = async (currentWord: string, currentSentence: s
31
  export const guessWord = async (sentence: string, language: string): Promise<string> => {
32
  console.log('Processing guess for sentence:', sentence);
33
 
34
- // Check for potential fraud if the sentence has less than 3 words
35
  const words = sentence.trim().split(/\s+/);
 
 
 
36
  if (words.length < 3) {
37
  console.log('Short description detected, checking for fraud...');
38
 
@@ -40,7 +43,7 @@ export const guessWord = async (sentence: string, language: string): Promise<str
40
  const { data: fraudData, error: fraudError } = await supabase.functions.invoke('detect-fraud', {
41
  body: {
42
  sentence,
43
- targetWord: words[0], // First word is usually the target in cheating attempts
44
  language
45
  }
46
  });
@@ -60,7 +63,11 @@ export const guessWord = async (sentence: string, language: string): Promise<str
60
  console.log('Calling guess-word function with sentence:', sentence, 'language:', language);
61
 
62
  const { data, error } = await supabase.functions.invoke('guess-word', {
63
- body: { sentence, language }
 
 
 
 
64
  });
65
 
66
  if (error) {
 
31
  export const guessWord = async (sentence: string, language: string): Promise<string> => {
32
  console.log('Processing guess for sentence:', sentence);
33
 
34
+ // Extract the target word from the sentence (assuming it's the first word)
35
  const words = sentence.trim().split(/\s+/);
36
+ const targetWord = words[0].toLowerCase();
37
+
38
+ // Check for potential fraud if the sentence has less than 3 words
39
  if (words.length < 3) {
40
  console.log('Short description detected, checking for fraud...');
41
 
 
43
  const { data: fraudData, error: fraudError } = await supabase.functions.invoke('detect-fraud', {
44
  body: {
45
  sentence,
46
+ targetWord,
47
  language
48
  }
49
  });
 
63
  console.log('Calling guess-word function with sentence:', sentence, 'language:', language);
64
 
65
  const { data, error } = await supabase.functions.invoke('guess-word', {
66
+ body: {
67
+ sentence,
68
+ targetWord, // Pass the target word to prevent guessing it
69
+ language
70
+ }
71
  });
72
 
73
  if (error) {
supabase/functions/detect-fraud/index.ts CHANGED
@@ -6,6 +6,8 @@ const corsHeaders = {
6
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
7
  };
8
 
 
 
9
  serve(async (req) => {
10
  // Handle CORS preflight requests
11
  if (req.method === 'OPTIONS') {
@@ -26,46 +28,53 @@ serve(async (req) => {
26
 
27
  while (retryCount < maxRetries) {
28
  try {
 
 
29
  const response = await client.chat.complete({
30
- model: "mistral-medium-latest",
31
  messages: [
32
  {
33
  role: "system",
34
  content: `You are a fraud detection system for a word guessing game.
35
  The game is being played in ${language}.
36
- Your task is to detect if a player is trying to cheat by:
37
- 1. Using a misspelling of the target word
38
- 2. Writing a sentence without spaces to bypass word count checks
39
 
40
  Examples for cheating:
41
 
42
- Target word: hand
43
- Player's description: hnd
44
- Language: en
45
- CORRECT ANSWER: cheating
46
-
47
- Target word: barfuß
48
- Player's description: germanwordforbarefoot
49
- Language: de
50
- CORRECT ANSWER: cheating
51
 
52
  Synonyms and names of instances of a class are legitimate descriptions.
53
 
54
- Target word: laptop
55
- Player's description: notebook
56
- Language: en
57
- CORRECT ANSWER: legitimate
58
-
59
- Target word: play
60
- Player's description: children often
61
- Language: en
62
- CORRECT ANSWER: legitimate
63
-
64
- Target word: Pfankuchen
65
- Player's description: Berliner
66
- Language: de
67
- CORRECT ANSWER: legitimate
68
-
 
 
 
 
 
69
  Respond with ONLY "cheating" or "legitimate" (no punctuation or explanation).`
70
  },
71
  {
@@ -81,6 +90,10 @@ serve(async (req) => {
81
  temperature: 0.1
82
  });
83
 
 
 
 
 
84
  const verdict = response.choices[0].message.content.trim().toLowerCase();
85
  console.log('Fraud detection verdict:', verdict);
86
 
@@ -92,24 +105,29 @@ serve(async (req) => {
92
  console.error(`Attempt ${retryCount + 1} failed:`, error);
93
  lastError = error;
94
 
95
- if (error.message?.includes('rate limit') || error.status === 429) {
96
- const waitTime = Math.pow(2, retryCount) * 1000;
97
- console.log(`Rate limit hit, waiting ${waitTime}ms before retry`);
98
- await new Promise(resolve => setTimeout(resolve, waitTime));
 
 
 
99
  retryCount++;
100
  continue;
101
  }
102
 
 
103
  throw error;
104
  }
105
  }
106
 
 
107
  throw new Error(`Failed after ${maxRetries} attempts. Last error: ${lastError?.message}`);
108
 
109
  } catch (error) {
110
  console.error('Error in fraud detection:', error);
111
 
112
- const errorMessage = error.message?.includes('rate limit')
113
  ? "The AI service is currently busy. Please try again in a few moments."
114
  : "Sorry, there was an error checking for fraud. Please try again.";
115
 
@@ -119,7 +137,7 @@ serve(async (req) => {
119
  details: error.message
120
  }),
121
  {
122
- status: error.message?.includes('rate limit') ? 429 : 500,
123
  headers: { ...corsHeaders, 'Content-Type': 'application/json' }
124
  }
125
  );
 
6
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
7
  };
8
 
9
+ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
10
+
11
  serve(async (req) => {
12
  // Handle CORS preflight requests
13
  if (req.method === 'OPTIONS') {
 
28
 
29
  while (retryCount < maxRetries) {
30
  try {
31
+ console.log(`Attempt ${retryCount + 1} to check for fraud`);
32
+
33
  const response = await client.chat.complete({
34
+ model: "mistral-large-latest",
35
  messages: [
36
  {
37
  role: "system",
38
  content: `You are a fraud detection system for a word guessing game.
39
  The game is being played in ${language}.
40
+ Your task is to detect if a player is trying to cheat by one of two methods:
41
+ 1. The Player's description is a misspelling of the target word
42
+ 2. The Player's description is a sentence without spaces
43
 
44
  Examples for cheating:
45
 
46
+ Target word: hand
47
+ Player's description: hnd
48
+ Language: en
49
+ CORRECT ANSWER: cheating
50
+
51
+ Target word: barfuß
52
+ Player's description: germanwordforbarefoot
53
+ Language: de
54
+ CORRECT ANSWER: cheating
55
 
56
  Synonyms and names of instances of a class are legitimate descriptions.
57
 
58
+ Target word: laptop
59
+ Player's description: notebook
60
+ Language: en
61
+ CORRECT ANSWER: legitimate
62
+
63
+ Target word: play
64
+ Player's description: children often
65
+ Language: en
66
+ CORRECT ANSWER: legitimate
67
+
68
+ Target word: Pfankuchen
69
+ Player's description: Berliner
70
+ Language: de
71
+ CORRECT ANSWER: legitimate
72
+
73
+ Target word: Burrito
74
+ Player's description: Wrap
75
+ Language: es
76
+ CORRECT ANSWER: legitimate
77
+
78
  Respond with ONLY "cheating" or "legitimate" (no punctuation or explanation).`
79
  },
80
  {
 
90
  temperature: 0.1
91
  });
92
 
93
+ if (!response?.choices?.[0]?.message?.content) {
94
+ throw new Error('Invalid response format from Mistral API');
95
+ }
96
+
97
  const verdict = response.choices[0].message.content.trim().toLowerCase();
98
  console.log('Fraud detection verdict:', verdict);
99
 
 
105
  console.error(`Attempt ${retryCount + 1} failed:`, error);
106
  lastError = error;
107
 
108
+ // Check if it's a rate limit or service unavailable error
109
+ if (error.message?.includes('rate limit') ||
110
+ error.message?.includes('503') ||
111
+ error.message?.includes('Service unavailable')) {
112
+ const waitTime = Math.pow(2, retryCount) * 1000; // Exponential backoff
113
+ console.log(`Service unavailable or rate limited, waiting ${waitTime}ms before retry`);
114
+ await sleep(waitTime);
115
  retryCount++;
116
  continue;
117
  }
118
 
119
+ // If it's not a retryable error, throw immediately
120
  throw error;
121
  }
122
  }
123
 
124
+ // If we've exhausted all retries
125
  throw new Error(`Failed after ${maxRetries} attempts. Last error: ${lastError?.message}`);
126
 
127
  } catch (error) {
128
  console.error('Error in fraud detection:', error);
129
 
130
+ const errorMessage = error.message?.includes('rate limit') || error.message?.includes('503')
131
  ? "The AI service is currently busy. Please try again in a few moments."
132
  : "Sorry, there was an error checking for fraud. Please try again.";
133
 
 
137
  details: error.message
138
  }),
139
  {
140
+ status: error.message?.includes('rate limit') || error.message?.includes('503') ? 429 : 500,
141
  headers: { ...corsHeaders, 'Content-Type': 'application/json' }
142
  }
143
  );
supabase/functions/generate-themed-word/index.ts CHANGED
@@ -38,28 +38,42 @@ const openRouterModels = [
38
  ];
39
 
40
  async function tryMistral(theme: string, usedWords: string[], language: string) {
 
 
 
 
 
41
  const client = new Mistral({
42
- apiKey: Deno.env.get('MISTRAL_API_KEY'),
43
  });
44
 
45
  const prompts = languagePrompts[language as keyof typeof languagePrompts] || languagePrompts.en;
46
 
47
  const response = await client.chat.complete({
48
- model: "mistral-medium-latest",
49
  messages: [
50
  {
51
  role: "system",
52
  content: `${prompts.systemPrompt} "${theme}".\n${prompts.requirements} ${usedWords.join(', ')}\n\nRespond with just the word in UPPERCASE, nothing else.`
53
  }
54
  ],
55
- maxTokens: 300,
56
  temperature: 0.99
57
  });
58
 
 
 
 
 
59
  return response.choices[0].message.content.trim();
60
  }
61
 
62
  async function tryOpenRouter(theme: string, usedWords: string[], language: string) {
 
 
 
 
 
63
  const prompts = languagePrompts[language as keyof typeof languagePrompts] || languagePrompts.en;
64
  const randomModel = openRouterModels[Math.floor(Math.random() * openRouterModels.length)];
65
 
@@ -68,7 +82,7 @@ async function tryOpenRouter(theme: string, usedWords: string[], language: strin
68
  const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
69
  method: "POST",
70
  headers: {
71
- "Authorization": `Bearer ${Deno.env.get('OPENROUTER_API_KEY')}`,
72
  "HTTP-Referer": "https://think-in-sync.com",
73
  "X-Title": "Think in Sync",
74
  "Content-Type": "application/json"
@@ -85,10 +99,16 @@ async function tryOpenRouter(theme: string, usedWords: string[], language: strin
85
  });
86
 
87
  if (!response.ok) {
88
- throw new Error(`OpenRouter API error: ${response.status}`);
 
89
  }
90
 
91
  const data = await response.json();
 
 
 
 
 
92
  return data.choices[0].message.content.trim();
93
  }
94
 
@@ -101,33 +121,54 @@ serve(async (req) => {
101
  const { theme, usedWords = [], language = 'en' } = await req.json();
102
  console.log('Generating word for theme:', theme, 'language:', language, 'excluding:', usedWords);
103
 
 
 
 
104
  try {
105
  console.log('Attempting with Mistral...');
106
- const word = await tryMistral(theme, usedWords, language);
107
  console.log('Successfully generated word with Mistral:', word);
108
- return new Response(
109
- JSON.stringify({ word }),
110
- { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
111
- );
112
  } catch (mistralError) {
113
  console.error('Mistral error:', mistralError);
114
  console.log('Falling back to OpenRouter...');
115
 
116
- const word = await tryOpenRouter(theme, usedWords, language);
117
- console.log('Successfully generated word with OpenRouter:', word);
 
 
 
 
 
 
 
 
118
  return new Response(
119
- JSON.stringify({ word }),
120
- { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
 
 
 
 
 
 
121
  );
122
  }
 
 
 
 
 
123
  } catch (error) {
124
  console.error('Error generating themed word:', error);
125
  return new Response(
126
- JSON.stringify({ error: error.message }),
 
 
 
127
  {
128
  status: 500,
129
  headers: { ...corsHeaders, 'Content-Type': 'application/json' }
130
  }
131
  );
132
  }
133
- });
 
38
  ];
39
 
40
  async function tryMistral(theme: string, usedWords: string[], language: string) {
41
+ const mistralKey = Deno.env.get('MISTRAL_API_KEY');
42
+ if (!mistralKey) {
43
+ throw new Error('Mistral API key not configured');
44
+ }
45
+
46
  const client = new Mistral({
47
+ apiKey: mistralKey,
48
  });
49
 
50
  const prompts = languagePrompts[language as keyof typeof languagePrompts] || languagePrompts.en;
51
 
52
  const response = await client.chat.complete({
53
+ model: "mistral-large-latest",
54
  messages: [
55
  {
56
  role: "system",
57
  content: `${prompts.systemPrompt} "${theme}".\n${prompts.requirements} ${usedWords.join(', ')}\n\nRespond with just the word in UPPERCASE, nothing else.`
58
  }
59
  ],
60
+ maxTokens: 50,
61
  temperature: 0.99
62
  });
63
 
64
+ if (!response?.choices?.[0]?.message?.content) {
65
+ throw new Error('Invalid response from Mistral API');
66
+ }
67
+
68
  return response.choices[0].message.content.trim();
69
  }
70
 
71
  async function tryOpenRouter(theme: string, usedWords: string[], language: string) {
72
+ const openRouterKey = Deno.env.get('OPENROUTER_API_KEY');
73
+ if (!openRouterKey) {
74
+ throw new Error('OpenRouter API key not configured');
75
+ }
76
+
77
  const prompts = languagePrompts[language as keyof typeof languagePrompts] || languagePrompts.en;
78
  const randomModel = openRouterModels[Math.floor(Math.random() * openRouterModels.length)];
79
 
 
82
  const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
83
  method: "POST",
84
  headers: {
85
+ "Authorization": `Bearer ${openRouterKey}`,
86
  "HTTP-Referer": "https://think-in-sync.com",
87
  "X-Title": "Think in Sync",
88
  "Content-Type": "application/json"
 
99
  });
100
 
101
  if (!response.ok) {
102
+ const errorText = await response.text();
103
+ throw new Error(`OpenRouter API error: ${response.status} - ${errorText}`);
104
  }
105
 
106
  const data = await response.json();
107
+
108
+ if (!data?.choices?.[0]?.message?.content) {
109
+ throw new Error('Invalid response from OpenRouter API');
110
+ }
111
+
112
  return data.choices[0].message.content.trim();
113
  }
114
 
 
121
  const { theme, usedWords = [], language = 'en' } = await req.json();
122
  console.log('Generating word for theme:', theme, 'language:', language, 'excluding:', usedWords);
123
 
124
+ let word;
125
+ let error;
126
+
127
  try {
128
  console.log('Attempting with Mistral...');
129
+ word = await tryMistral(theme, usedWords, language);
130
  console.log('Successfully generated word with Mistral:', word);
 
 
 
 
131
  } catch (mistralError) {
132
  console.error('Mistral error:', mistralError);
133
  console.log('Falling back to OpenRouter...');
134
 
135
+ try {
136
+ word = await tryOpenRouter(theme, usedWords, language);
137
+ console.log('Successfully generated word with OpenRouter:', word);
138
+ } catch (openRouterError) {
139
+ console.error('OpenRouter error:', openRouterError);
140
+ error = openRouterError;
141
+ }
142
+ }
143
+
144
+ if (!word) {
145
  return new Response(
146
+ JSON.stringify({
147
+ error: 'Failed to generate word with both Mistral and OpenRouter',
148
+ details: error?.message || 'Unknown error'
149
+ }),
150
+ {
151
+ status: 500,
152
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
153
+ }
154
  );
155
  }
156
+
157
+ return new Response(
158
+ JSON.stringify({ word }),
159
+ { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
160
+ );
161
  } catch (error) {
162
  console.error('Error generating themed word:', error);
163
  return new Response(
164
+ JSON.stringify({
165
+ error: 'Error generating themed word',
166
+ details: error.message
167
+ }),
168
  {
169
  status: 500,
170
  headers: { ...corsHeaders, 'Content-Type': 'application/json' }
171
  }
172
  );
173
  }
174
+ });
supabase/functions/generate-word/index.ts CHANGED
@@ -49,14 +49,14 @@ async function tryMistral(currentWord: string, existingSentence: string, languag
49
  const prompts = languagePrompts[language as keyof typeof languagePrompts] || languagePrompts.en;
50
 
51
  const response = await client.chat.complete({
52
- model: "mistral-medium-latest",
53
  messages: [
54
  {
55
  role: "system",
56
  content: `${prompts.systemPrompt} "${currentWord}". ${prompts.task} ${prompts.instruction} "${existingSentence}". Do not add quotes or backticks. Just answer with the sentence.`
57
  }
58
  ],
59
- maxTokens: 300,
60
  temperature: 0.5
61
  });
62
 
 
49
  const prompts = languagePrompts[language as keyof typeof languagePrompts] || languagePrompts.en;
50
 
51
  const response = await client.chat.complete({
52
+ model: "mistral-large-latest",
53
  messages: [
54
  {
55
  role: "system",
56
  content: `${prompts.systemPrompt} "${currentWord}". ${prompts.task} ${prompts.instruction} "${existingSentence}". Do not add quotes or backticks. Just answer with the sentence.`
57
  }
58
  ],
59
+ maxTokens: 50,
60
  temperature: 0.5
61
  });
62
 
supabase/functions/guess-word/index.ts CHANGED
@@ -44,7 +44,7 @@ async function tryMistral(sentence: string, language: string) {
44
  const prompts = languagePrompts[language as keyof typeof languagePrompts] || languagePrompts.en;
45
 
46
  const response = await client.chat.complete({
47
- model: "mistral-medium-latest",
48
  messages: [
49
  {
50
  role: "system",
 
44
  const prompts = languagePrompts[language as keyof typeof languagePrompts] || languagePrompts.en;
45
 
46
  const response = await client.chat.complete({
47
+ model: "mistral-large-latest",
48
  messages: [
49
  {
50
  role: "system",
supabase/functions/validate-sentence/index.ts CHANGED
@@ -20,7 +20,7 @@ serve(async (req) => {
20
  });
21
 
22
  const response = await client.chat.complete({
23
- model: "mistral-medium-latest",
24
  messages: [
25
  {
26
  role: "system",
 
20
  });
21
 
22
  const response = await client.chat.complete({
23
+ model: "mistral-large-latest",
24
  messages: [
25
  {
26
  role: "system",