wip
Browse files- src/app/api/text/verdict/route.ts +129 -0
- src/app/page.tsx +41 -1
- src/components/court/Court.tsx +22 -6
- src/components/end/End.tsx +101 -22
- src/components/lawyer/Lawyer.tsx +23 -7
src/app/api/text/verdict/route.ts
ADDED
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextResponse } from 'next/server';
|
2 |
+
import { Mistral } from "@mistralai/mistralai";
|
3 |
+
|
4 |
+
const mistral = new Mistral({apiKey: process.env.MISTRAL_API_KEY})
|
5 |
+
|
6 |
+
interface Message {
|
7 |
+
content: string;
|
8 |
+
role: 'lawyer' | 'judge';
|
9 |
+
requiredWords?: string[];
|
10 |
+
}
|
11 |
+
|
12 |
+
export async function POST(request: Request) {
|
13 |
+
try {
|
14 |
+
const { language, story, chat } = await request.json();
|
15 |
+
console.log('story:', story)
|
16 |
+
console.log('chat:', chat)
|
17 |
+
// Prepare the context from story and chat history
|
18 |
+
const accusationContext = `Crime description: ${story.accusation.description}\nSuspect's alibi: ${story.accusation.alibi.join(", ")}`;
|
19 |
+
|
20 |
+
const chatContext = chat.messages
|
21 |
+
.map((msg: Message) => `${msg.role}: ${msg.content}`)
|
22 |
+
.join("\n");
|
23 |
+
|
24 |
+
let prompt;
|
25 |
+
|
26 |
+
if (language === 'fr') {
|
27 |
+
prompt = `En tant que juge, sur la base de l'affaire suivante :
|
28 |
+
|
29 |
+
${accusationContext}
|
30 |
+
|
31 |
+
Et considérant l'interrogatoire suivant :
|
32 |
+
${chatContext}
|
33 |
+
|
34 |
+
Vous jouez le rôle d'un juge dans un tribunal. et voici l'affaire que vous jugez.
|
35 |
+
Vous êtes très drôle et n'hésitez pas à faire des blagues.
|
36 |
+
Vous devez me dire si la défense a été suffisamment bonne pour que l'accusé soit acquitté ou au contraire sanctionné.
|
37 |
+
Si le discours n'a aucun sens et qu'il ne répond pas correctement aux questions, prononcez le bon jugement pour lui.
|
38 |
+
Ajoutez un nombre d'années de prison que l'accusé prendra en punition (entre 0 et 10, n'hésitez pas à être sévère si la défense est mauvaise). Si l'accusé est libéré, le chiffre est de 0.
|
39 |
+
|
40 |
+
Verdit est true si l'accusé est acquitté, false si il est sanctionné.
|
41 |
+
|
42 |
+
Soyez concis et répondez en quelques mots. au format json.
|
43 |
+
RÉPONDEZ UNIQUEMENT AVEC LE JSON
|
44 |
+
|
45 |
+
JSON Format : {
|
46 |
+
"verdict": Boolean,
|
47 |
+
"argument":String,
|
48 |
+
"prisonYears": Number
|
49 |
+
}`
|
50 |
+
}
|
51 |
+
else if (language === 'en') {
|
52 |
+
prompt = `As a judge, based on the following case:
|
53 |
+
|
54 |
+
${accusationContext}
|
55 |
+
|
56 |
+
And considering the following interrogation:
|
57 |
+
${chatContext}
|
58 |
+
|
59 |
+
You play as a judge in a court. and this is the case you're judging.
|
60 |
+
You are very funny, and don't hesitate to make jokes.
|
61 |
+
You have to tell me if the defense was good enough for the accused to be acquitted or in the other way, has a penalty.
|
62 |
+
If the speech does not make any sense at all, and he is not answering right at the questions, pronounce the correct judgment for him.
|
63 |
+
Add a number of years of prison that the accused will take for punishment (between 0 and 10. Don't hesitate to be harsh if the defense is bad). If the accused is released, the number is 0.
|
64 |
+
|
65 |
+
|
66 |
+
Verdict is true if the accused is acquitted, false if he is sanctioned.
|
67 |
+
Be concise, and answer with only a few words. in a json format.
|
68 |
+
ANSWER WITH ONLY THE JSON
|
69 |
+
|
70 |
+
JSON Format : {
|
71 |
+
"verdict": Boolean,
|
72 |
+
"argument":String,
|
73 |
+
"prisonYears": Number
|
74 |
+
}`;
|
75 |
+
} else {
|
76 |
+
prompt = `Como juez, basado en el siguiente caso:
|
77 |
+
|
78 |
+
${accusationContext}
|
79 |
+
|
80 |
+
Y considerando el siguiente interrogatorio
|
81 |
+
${chatContext}
|
82 |
+
|
83 |
+
Juegas como juez en un tribunal y este es el caso que estás juzgando.
|
84 |
+
Eres muy gracioso y no dudas en hacer bromas.
|
85 |
+
Tienes que decirme si la defensa fue lo suficientemente buena para que el acusado sea absuelto o por el contrario, tenga una pena.
|
86 |
+
Si el discurso no tiene ningún sentido, y él no está respondiendo a la derecha en las preguntas, pronunciar el juicio correcto para él.
|
87 |
+
Añade un número de años de prisión que el acusado llevará como pena (entre 0 y 10. No dudes en ser duro si la defensa es mala). Si el acusado queda en libertad, el número es 0.
|
88 |
+
|
89 |
+
|
90 |
+
Verdict is true if the accused is acquitted, false if he is sanctioned.
|
91 |
+
|
92 |
+
Sea conciso, y responda con sólo unas pocas palabras. en formato json.
|
93 |
+
RESPONDE SÓLO CON EL JSON
|
94 |
+
|
95 |
+
JSON Format : {
|
96 |
+
"verdict": Boolean,
|
97 |
+
"argument":String,
|
98 |
+
"prisonYears": Number
|
99 |
+
}`
|
100 |
+
}
|
101 |
+
|
102 |
+
const seed = Math.floor(Math.random() * 1000000);
|
103 |
+
|
104 |
+
const response = await mistral.chat.complete({
|
105 |
+
model: "mistral-large-latest",
|
106 |
+
messages: [{ role: "user", content: prompt }],
|
107 |
+
responseFormat: { type: 'json_object' },
|
108 |
+
randomSeed: seed,
|
109 |
+
});
|
110 |
+
|
111 |
+
const functionCall = response.choices?.[0]?.message.content;
|
112 |
+
const JSONResponse = functionCall ? JSON.parse(functionCall as string) : null;
|
113 |
+
console.log('functionCall:', functionCall)
|
114 |
+
console.log('JSONResponse:', JSONResponse)
|
115 |
+
const verdictData = JSONResponse;
|
116 |
+
|
117 |
+
return NextResponse.json({
|
118 |
+
success: true,
|
119 |
+
verdict: verdictData
|
120 |
+
});
|
121 |
+
|
122 |
+
} catch (error) {
|
123 |
+
console.error('Error in verdict route:', error);
|
124 |
+
return NextResponse.json(
|
125 |
+
{ error: 'Failed to generate verdict' },
|
126 |
+
{ status: 500 }
|
127 |
+
);
|
128 |
+
}
|
129 |
+
}
|
src/app/page.tsx
CHANGED
@@ -30,6 +30,11 @@ interface Chat {
|
|
30 |
messages: Message[];
|
31 |
}
|
32 |
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
const intro = {
|
35 |
fr: {
|
@@ -67,6 +72,8 @@ export default function Home() {
|
|
67 |
|
68 |
const [reaction, setReaction] = useState<string>('');
|
69 |
|
|
|
|
|
70 |
const resetGame = () => {
|
71 |
setCurrentScene('menu');
|
72 |
setStory(null);
|
@@ -76,11 +83,42 @@ export default function Home() {
|
|
76 |
setRequiredWords([]);
|
77 |
};
|
78 |
|
79 |
-
const setNextScene = () => {
|
80 |
if (currentScene === 'lawyer') {
|
81 |
if (round < 4) {
|
82 |
setCurrentScene('court');
|
83 |
} else {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
setCurrentScene('end');
|
85 |
}
|
86 |
return;
|
@@ -115,6 +153,8 @@ export default function Home() {
|
|
115 |
setChat,
|
116 |
reaction,
|
117 |
setReaction,
|
|
|
|
|
118 |
};
|
119 |
|
120 |
useEffect(() => {
|
|
|
30 |
messages: Message[];
|
31 |
}
|
32 |
|
33 |
+
interface Verdict {
|
34 |
+
verdict: boolean;
|
35 |
+
argument: string;
|
36 |
+
prisonYears: number;
|
37 |
+
}
|
38 |
|
39 |
const intro = {
|
40 |
fr: {
|
|
|
72 |
|
73 |
const [reaction, setReaction] = useState<string>('');
|
74 |
|
75 |
+
|
76 |
+
const [verdict, setVerdict] = useState<Verdict | null>(null);
|
77 |
const resetGame = () => {
|
78 |
setCurrentScene('menu');
|
79 |
setStory(null);
|
|
|
83 |
setRequiredWords([]);
|
84 |
};
|
85 |
|
86 |
+
const setNextScene = async () => {
|
87 |
if (currentScene === 'lawyer') {
|
88 |
if (round < 4) {
|
89 |
setCurrentScene('court');
|
90 |
} else {
|
91 |
+
// Generate judge's verdict before ending
|
92 |
+
const generateVerdict = async () => {
|
93 |
+
try {
|
94 |
+
const response = await fetch('/api/text/verdict', {
|
95 |
+
method: 'POST',
|
96 |
+
headers: {
|
97 |
+
'Content-Type': 'application/json',
|
98 |
+
},
|
99 |
+
body: JSON.stringify({
|
100 |
+
language,
|
101 |
+
story,
|
102 |
+
chat
|
103 |
+
})
|
104 |
+
});
|
105 |
+
|
106 |
+
const data = await response.json();
|
107 |
+
|
108 |
+
if (!data.success) {
|
109 |
+
throw new Error('Failed to generate verdict');
|
110 |
+
}
|
111 |
+
|
112 |
+
console.log('data.verdict:', data.verdict)
|
113 |
+
|
114 |
+
setVerdict(data.verdict);
|
115 |
+
} catch (error) {
|
116 |
+
console.error('Error generating verdict:', error);
|
117 |
+
}
|
118 |
+
};
|
119 |
+
|
120 |
+
await generateVerdict();
|
121 |
+
|
122 |
setCurrentScene('end');
|
123 |
}
|
124 |
return;
|
|
|
153 |
setChat,
|
154 |
reaction,
|
155 |
setReaction,
|
156 |
+
verdict,
|
157 |
+
setVerdict
|
158 |
};
|
159 |
|
160 |
useEffect(() => {
|
src/components/court/Court.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
'use client';
|
2 |
-
import { FC, useEffect, useState } from 'react';
|
3 |
import Image from 'next/image';
|
4 |
|
5 |
interface CourtSceneProps {
|
@@ -16,6 +16,7 @@ const CourtScene: FC<CourtSceneProps> = ({
|
|
16 |
round
|
17 |
}) => {
|
18 |
const [showFirstImage, setShowFirstImage] = useState(true);
|
|
|
19 |
|
20 |
useEffect(() => {
|
21 |
const playAudio = async () => {
|
@@ -28,7 +29,7 @@ const CourtScene: FC<CourtSceneProps> = ({
|
|
28 |
'Content-Type': 'application/json',
|
29 |
},
|
30 |
body: JSON.stringify({
|
31 |
-
language: 'en',
|
32 |
text: reaction !== '' ? reaction + currentQuestion : currentQuestion,
|
33 |
role: 'judge'
|
34 |
})
|
@@ -41,6 +42,7 @@ const CourtScene: FC<CourtSceneProps> = ({
|
|
41 |
const audioBlob = await response.blob();
|
42 |
const audioUrl = URL.createObjectURL(audioBlob);
|
43 |
const audio = new Audio(audioUrl);
|
|
|
44 |
audio.play();
|
45 |
} catch (error) {
|
46 |
console.error('Error playing audio:', error);
|
@@ -48,10 +50,24 @@ const CourtScene: FC<CourtSceneProps> = ({
|
|
48 |
};
|
49 |
|
50 |
if (currentQuestion) {
|
51 |
-
|
52 |
}
|
53 |
-
|
54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
|
56 |
useEffect(() => {
|
57 |
const timer = setTimeout(() => {
|
@@ -94,7 +110,7 @@ const CourtScene: FC<CourtSceneProps> = ({
|
|
94 |
{/* Flèche à droite */}
|
95 |
<div className="flex justify-end">
|
96 |
<button
|
97 |
-
onClick={
|
98 |
className="text-white hover:text-blue-400 transition-colors"
|
99 |
aria-label="Continuer"
|
100 |
>
|
|
|
1 |
'use client';
|
2 |
+
import { FC, useEffect, useState, useRef } from 'react';
|
3 |
import Image from 'next/image';
|
4 |
|
5 |
interface CourtSceneProps {
|
|
|
16 |
round
|
17 |
}) => {
|
18 |
const [showFirstImage, setShowFirstImage] = useState(true);
|
19 |
+
const audioRef = useRef<HTMLAudioElement | null>(null);
|
20 |
|
21 |
useEffect(() => {
|
22 |
const playAudio = async () => {
|
|
|
29 |
'Content-Type': 'application/json',
|
30 |
},
|
31 |
body: JSON.stringify({
|
32 |
+
language: 'en',
|
33 |
text: reaction !== '' ? reaction + currentQuestion : currentQuestion,
|
34 |
role: 'judge'
|
35 |
})
|
|
|
42 |
const audioBlob = await response.blob();
|
43 |
const audioUrl = URL.createObjectURL(audioBlob);
|
44 |
const audio = new Audio(audioUrl);
|
45 |
+
audioRef.current = audio;
|
46 |
audio.play();
|
47 |
} catch (error) {
|
48 |
console.error('Error playing audio:', error);
|
|
|
50 |
};
|
51 |
|
52 |
if (currentQuestion) {
|
53 |
+
playAudio();
|
54 |
}
|
55 |
+
|
56 |
+
return () => {
|
57 |
+
if (audioRef.current) {
|
58 |
+
audioRef.current.pause();
|
59 |
+
audioRef.current = null;
|
60 |
+
}
|
61 |
+
};
|
62 |
+
}, [currentQuestion, reaction]);
|
63 |
+
|
64 |
+
const handleNextScene = () => {
|
65 |
+
if (audioRef.current) {
|
66 |
+
audioRef.current.pause();
|
67 |
+
audioRef.current = null;
|
68 |
+
}
|
69 |
+
setNextScene();
|
70 |
+
};
|
71 |
|
72 |
useEffect(() => {
|
73 |
const timer = setTimeout(() => {
|
|
|
110 |
{/* Flèche à droite */}
|
111 |
<div className="flex justify-end">
|
112 |
<button
|
113 |
+
onClick={handleNextScene}
|
114 |
className="text-white hover:text-blue-400 transition-colors"
|
115 |
aria-label="Continuer"
|
116 |
>
|
src/components/end/End.tsx
CHANGED
@@ -1,46 +1,125 @@
|
|
1 |
'use client';
|
2 |
-
import { FC } from 'react';
|
3 |
import Image from 'next/image';
|
4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
interface EndSceneProps {
|
6 |
language: 'fr' | 'en' | 'es';
|
7 |
setNextScene: () => void;
|
|
|
8 |
}
|
9 |
|
10 |
const EndScene: FC<EndSceneProps> = ({
|
11 |
language,
|
12 |
setNextScene,
|
|
|
13 |
}) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
return (
|
15 |
<div className="relative w-screen h-screen">
|
16 |
-
{/* Image de fond */}
|
17 |
<Image
|
18 |
-
src=
|
|
|
|
|
|
|
19 |
alt="Background"
|
20 |
fill
|
21 |
className="object-cover"
|
22 |
priority
|
23 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
? 'End of trial'
|
34 |
-
: 'Fin del juicio'
|
35 |
-
}
|
36 |
-
</h1>
|
37 |
-
<button
|
38 |
-
onClick={setNextScene}
|
39 |
-
className="px-8 py-4 text-xl font-bold text-white bg-sky-500 hover:bg-blue-700 transition-colors roboto-slab"
|
40 |
-
>
|
41 |
-
{language === 'fr' ? 'Rejouer' : language === 'en' ? 'Play again' : 'Jugar de nuevo'}
|
42 |
-
</button>
|
43 |
-
</div>
|
44 |
</div>
|
45 |
</div>
|
46 |
</div>
|
|
|
1 |
'use client';
|
2 |
+
import { FC, useEffect, useState } from 'react';
|
3 |
import Image from 'next/image';
|
4 |
|
5 |
+
interface Verdict {
|
6 |
+
verdict: boolean;
|
7 |
+
argument: string;
|
8 |
+
prisonYears: number;
|
9 |
+
}
|
10 |
+
|
11 |
interface EndSceneProps {
|
12 |
language: 'fr' | 'en' | 'es';
|
13 |
setNextScene: () => void;
|
14 |
+
verdict: Verdict | null;
|
15 |
}
|
16 |
|
17 |
const EndScene: FC<EndSceneProps> = ({
|
18 |
language,
|
19 |
setNextScene,
|
20 |
+
verdict
|
21 |
}) => {
|
22 |
+
const [isLoading, setIsLoading] = useState(true);
|
23 |
+
|
24 |
+
useEffect(() => {
|
25 |
+
if (verdict) {
|
26 |
+
console.log('End verdict:', verdict)
|
27 |
+
setIsLoading(false);
|
28 |
+
}
|
29 |
+
}, [verdict]);
|
30 |
+
|
31 |
+
const getLoadingText = () => {
|
32 |
+
if (language === 'fr') {
|
33 |
+
return 'Le juge délibère...';
|
34 |
+
} else if (language === 'en') {
|
35 |
+
return 'The judge is deliberating...';
|
36 |
+
} else {
|
37 |
+
return 'El juez está deliberando...';
|
38 |
+
}
|
39 |
+
};
|
40 |
+
|
41 |
+
const getVerdictText = () => {
|
42 |
+
if (language === 'fr') {
|
43 |
+
return verdict?.verdict ? 'NON COUPABLE' : 'COUPABLE';
|
44 |
+
} else if (language === 'en') {
|
45 |
+
return verdict?.verdict ? 'NOT GUILTY' : 'GUILTY';
|
46 |
+
} else {
|
47 |
+
return verdict?.verdict ? 'NO CULPABLE' : 'CULPABLE';
|
48 |
+
}
|
49 |
+
};
|
50 |
+
|
51 |
+
const getSentenceText = () => {
|
52 |
+
if (language === 'fr') {
|
53 |
+
return verdict?.prisonYears && verdict?.prisonYears > 0
|
54 |
+
? `${verdict?.prisonYears} ans de prison`
|
55 |
+
: 'Libéré';
|
56 |
+
} else if (language === 'en') {
|
57 |
+
return verdict?.prisonYears && verdict?.prisonYears > 0
|
58 |
+
? `${verdict?.prisonYears} years in prison`
|
59 |
+
: 'Released';
|
60 |
+
} else {
|
61 |
+
return verdict?.prisonYears && verdict?.prisonYears > 0
|
62 |
+
? `${verdict?.prisonYears} años de prisión`
|
63 |
+
: 'Liberado';
|
64 |
+
}
|
65 |
+
};
|
66 |
+
|
67 |
return (
|
68 |
<div className="relative w-screen h-screen">
|
|
|
69 |
<Image
|
70 |
+
src={isLoading
|
71 |
+
? "https://ik.imagekit.io/z0tzxea0wgx/MistralGameJam/court_M-RO6txqB.png?updatedAt=1737835884433"
|
72 |
+
: "https://ik.imagekit.io/z0tzxea0wgx/MistralGameJam/DD_judge1_Bn04jNl_E.png?updatedAt=1737835883169"
|
73 |
+
}
|
74 |
alt="Background"
|
75 |
fill
|
76 |
className="object-cover"
|
77 |
priority
|
78 |
/>
|
79 |
+
<div className="absolute inset-0 flex items-center justify-center">
|
80 |
+
<div className="bg-black/60 border-8 border-black w-[600px] aspect-square p-8 m-8 flex flex-col items-center justify-center">
|
81 |
+
{isLoading ? (
|
82 |
+
<div className="text-center">
|
83 |
+
<h1 className="text-4xl text-white roboto-slab mb-8">
|
84 |
+
{getLoadingText()}
|
85 |
+
</h1>
|
86 |
+
<div className="animate-bounce">
|
87 |
+
<svg className="w-16 h-16 text-white mx-auto" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" stroke="currentColor">
|
88 |
+
<path d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
|
89 |
+
</svg>
|
90 |
+
</div>
|
91 |
+
</div>
|
92 |
+
) : (
|
93 |
+
<div className="text-center flex flex-col items-center justify-between h-full">
|
94 |
+
<h1 className="text-5xl text-white roboto-slab mb-8">
|
95 |
+
{language === 'fr'
|
96 |
+
? 'Verdict'
|
97 |
+
: language === 'en'
|
98 |
+
? 'Verdict'
|
99 |
+
: 'Veredicto'
|
100 |
+
}
|
101 |
+
</h1>
|
102 |
+
|
103 |
+
<h2 className={`text-7xl mb-8 roboto-slab ${verdict?.verdict ? 'text-green-500' : 'text-red-500'}`}>
|
104 |
+
{getVerdictText()}
|
105 |
+
</h2>
|
106 |
+
|
107 |
+
<p className="text-3xl text-white mb-8 roboto-slab">
|
108 |
+
{getSentenceText()}
|
109 |
+
</p>
|
110 |
+
|
111 |
+
<p className="text-2xl text-white mb-8 roboto-slab italic max-w-[80%]">
|
112 |
+
{verdict?.argument}
|
113 |
+
</p>
|
114 |
|
115 |
+
<button
|
116 |
+
onClick={setNextScene}
|
117 |
+
className="px-12 py-6 text-2xl font-bold text-white bg-sky-500 hover:bg-blue-700 transition-colors roboto-slab mt-auto"
|
118 |
+
>
|
119 |
+
{language === 'fr' ? 'Rejouer' : language === 'en' ? 'Play again' : 'Jugar de nuevo'}
|
120 |
+
</button>
|
121 |
+
</div>
|
122 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
</div>
|
124 |
</div>
|
125 |
</div>
|
src/components/lawyer/Lawyer.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
'use client';
|
2 |
-
import { FC, useState, useEffect, Dispatch, SetStateAction } from 'react';
|
3 |
import Image from 'next/image';
|
4 |
|
5 |
interface Message {
|
@@ -37,6 +37,8 @@ const LawyerScene: FC<LawyerSceneProps> = ({
|
|
37 |
const [question, setQuestion] = useState('');
|
38 |
const [answer, setAnswer] = useState('');
|
39 |
|
|
|
|
|
40 |
useEffect(() => {
|
41 |
const lastJudgeMessage = chat.messages.filter((message: Message) => message.role === 'judge').slice(-1)[0]?.content;
|
42 |
const lastLawyerMessage = chat.messages.filter((message: Message) => message.role === 'lawyer').slice(-1)[0]?.content;
|
@@ -48,6 +50,7 @@ const LawyerScene: FC<LawyerSceneProps> = ({
|
|
48 |
useEffect(() => {
|
49 |
const playAudio = async () => {
|
50 |
try {
|
|
|
51 |
|
52 |
const response = await fetch('/api/voice', {
|
53 |
method: 'POST',
|
@@ -68,17 +71,22 @@ const LawyerScene: FC<LawyerSceneProps> = ({
|
|
68 |
const audioBlob = await response.blob();
|
69 |
const audioUrl = URL.createObjectURL(audioBlob);
|
70 |
const audio = new Audio(audioUrl);
|
|
|
71 |
audio.play();
|
72 |
} catch (error) {
|
73 |
console.error('Error playing audio:', error);
|
74 |
}
|
75 |
};
|
76 |
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
82 |
|
83 |
useEffect(() => {
|
84 |
setVisible(true);
|
@@ -97,6 +105,14 @@ const LawyerScene: FC<LawyerSceneProps> = ({
|
|
97 |
return () => clearInterval(timer);
|
98 |
}, []);
|
99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
return (
|
101 |
<div className="relative w-screen h-screen">
|
102 |
{/* Image de fond */}
|
@@ -136,7 +152,7 @@ const LawyerScene: FC<LawyerSceneProps> = ({
|
|
136 |
{/* Flèche à droite */}
|
137 |
<div className="absolute bottom-5 right-5">
|
138 |
<button
|
139 |
-
onClick={
|
140 |
disabled={!buttonEnabled || (currentQuestion === "" && round < 3)}
|
141 |
className={`text-white transition-colors ${
|
142 |
buttonEnabled && (currentQuestion !== "" || round === 3)
|
|
|
1 |
'use client';
|
2 |
+
import { FC, useState, useEffect, Dispatch, SetStateAction, useRef } from 'react';
|
3 |
import Image from 'next/image';
|
4 |
|
5 |
interface Message {
|
|
|
37 |
const [question, setQuestion] = useState('');
|
38 |
const [answer, setAnswer] = useState('');
|
39 |
|
40 |
+
const audioRef = useRef<HTMLAudioElement | null>(null);
|
41 |
+
|
42 |
useEffect(() => {
|
43 |
const lastJudgeMessage = chat.messages.filter((message: Message) => message.role === 'judge').slice(-1)[0]?.content;
|
44 |
const lastLawyerMessage = chat.messages.filter((message: Message) => message.role === 'lawyer').slice(-1)[0]?.content;
|
|
|
50 |
useEffect(() => {
|
51 |
const playAudio = async () => {
|
52 |
try {
|
53 |
+
if (!answer) return;
|
54 |
|
55 |
const response = await fetch('/api/voice', {
|
56 |
method: 'POST',
|
|
|
71 |
const audioBlob = await response.blob();
|
72 |
const audioUrl = URL.createObjectURL(audioBlob);
|
73 |
const audio = new Audio(audioUrl);
|
74 |
+
audioRef.current = audio;
|
75 |
audio.play();
|
76 |
} catch (error) {
|
77 |
console.error('Error playing audio:', error);
|
78 |
}
|
79 |
};
|
80 |
|
81 |
+
playAudio();
|
82 |
+
|
83 |
+
return () => {
|
84 |
+
if (audioRef.current) {
|
85 |
+
audioRef.current.pause();
|
86 |
+
audioRef.current = null;
|
87 |
+
}
|
88 |
+
};
|
89 |
+
}, [answer, language]);
|
90 |
|
91 |
useEffect(() => {
|
92 |
setVisible(true);
|
|
|
105 |
return () => clearInterval(timer);
|
106 |
}, []);
|
107 |
|
108 |
+
const handleNextScene = () => {
|
109 |
+
if (audioRef.current) {
|
110 |
+
audioRef.current.pause();
|
111 |
+
audioRef.current = null;
|
112 |
+
}
|
113 |
+
setNextScene();
|
114 |
+
};
|
115 |
+
|
116 |
return (
|
117 |
<div className="relative w-screen h-screen">
|
118 |
{/* Image de fond */}
|
|
|
152 |
{/* Flèche à droite */}
|
153 |
<div className="absolute bottom-5 right-5">
|
154 |
<button
|
155 |
+
onClick={handleNextScene}
|
156 |
disabled={!buttonEnabled || (currentQuestion === "" && round < 3)}
|
157 |
className={`text-white transition-colors ${
|
158 |
buttonEnabled && (currentQuestion !== "" || round === 3)
|