Spaces:
Runtime error
Runtime error
add front et requirements
Browse files- front/.gitattributes +35 -0
- front/.gitignore copy +24 -0
- front/eslint.config copy.js +28 -0
- front/index copy.html +13 -0
- front/package copy.json +35 -0
- front/package-lock copy.json +0 -0
- front/postcss.config copy.js +6 -0
- front/src/App.tsx +4 -4
- front/src/context/ChatContext.tsx +38 -2
- front/src/pages/ArabicChat.tsx +34 -15
- front/src/pages/FrenchChat.tsx +28 -11
- front/src/pages/LanguageSelection.tsx +8 -8
- front/tailwind.config copy.js +8 -0
- front/tsconfig copy.json +7 -0
- front/tsconfig.app copy.json +24 -0
- front/tsconfig.node copy.json +22 -0
- front/vite.config copy.ts +10 -0
- requirements.txt +2 -1
front/.gitattributes
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
front/.gitignore copy
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Logs
|
2 |
+
logs
|
3 |
+
*.log
|
4 |
+
npm-debug.log*
|
5 |
+
yarn-debug.log*
|
6 |
+
yarn-error.log*
|
7 |
+
pnpm-debug.log*
|
8 |
+
lerna-debug.log*
|
9 |
+
|
10 |
+
node_modules
|
11 |
+
dist
|
12 |
+
dist-ssr
|
13 |
+
*.local
|
14 |
+
|
15 |
+
# Editor directories and files
|
16 |
+
.vscode/*
|
17 |
+
!.vscode/extensions.json
|
18 |
+
.idea
|
19 |
+
.DS_Store
|
20 |
+
*.suo
|
21 |
+
*.ntvs*
|
22 |
+
*.njsproj
|
23 |
+
*.sln
|
24 |
+
*.sw?
|
front/eslint.config copy.js
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import js from '@eslint/js';
|
2 |
+
import globals from 'globals';
|
3 |
+
import reactHooks from 'eslint-plugin-react-hooks';
|
4 |
+
import reactRefresh from 'eslint-plugin-react-refresh';
|
5 |
+
import tseslint from 'typescript-eslint';
|
6 |
+
|
7 |
+
export default tseslint.config(
|
8 |
+
{ ignores: ['dist'] },
|
9 |
+
{
|
10 |
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
11 |
+
files: ['**/*.{ts,tsx}'],
|
12 |
+
languageOptions: {
|
13 |
+
ecmaVersion: 2020,
|
14 |
+
globals: globals.browser,
|
15 |
+
},
|
16 |
+
plugins: {
|
17 |
+
'react-hooks': reactHooks,
|
18 |
+
'react-refresh': reactRefresh,
|
19 |
+
},
|
20 |
+
rules: {
|
21 |
+
...reactHooks.configs.recommended.rules,
|
22 |
+
'react-refresh/only-export-components': [
|
23 |
+
'warn',
|
24 |
+
{ allowConstantExport: true },
|
25 |
+
],
|
26 |
+
},
|
27 |
+
}
|
28 |
+
);
|
front/index copy.html
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!doctype html>
|
2 |
+
<html lang="fr">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
+
<title>MSA TRANSLATE</title>
|
8 |
+
</head>
|
9 |
+
<body>
|
10 |
+
<div id="root"></div>
|
11 |
+
<script type="module" src="/src/main.tsx"></script>
|
12 |
+
</body>
|
13 |
+
</html>
|
front/package copy.json
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "voice-chat-translation-app",
|
3 |
+
"private": true,
|
4 |
+
"version": "0.0.0",
|
5 |
+
"type": "module",
|
6 |
+
"scripts": {
|
7 |
+
"dev": "vite",
|
8 |
+
"build": "vite build",
|
9 |
+
"lint": "eslint .",
|
10 |
+
"preview": "vite preview"
|
11 |
+
},
|
12 |
+
"dependencies": {
|
13 |
+
"@microsoft/fetch-event-source": "^2.0.1",
|
14 |
+
"lucide-react": "^0.344.0",
|
15 |
+
"react": "^18.3.1",
|
16 |
+
"react-dom": "^18.3.1",
|
17 |
+
"react-router-dom": "^6.22.3"
|
18 |
+
},
|
19 |
+
"devDependencies": {
|
20 |
+
"@eslint/js": "^9.9.1",
|
21 |
+
"@types/react": "^18.3.5",
|
22 |
+
"@types/react-dom": "^18.3.0",
|
23 |
+
"@vitejs/plugin-react": "^4.3.1",
|
24 |
+
"autoprefixer": "^10.4.18",
|
25 |
+
"eslint": "^9.9.1",
|
26 |
+
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
27 |
+
"eslint-plugin-react-refresh": "^0.4.11",
|
28 |
+
"globals": "^15.9.0",
|
29 |
+
"postcss": "^8.4.35",
|
30 |
+
"tailwindcss": "^3.4.1",
|
31 |
+
"typescript": "^5.5.3",
|
32 |
+
"typescript-eslint": "^8.3.0",
|
33 |
+
"vite": "^5.4.2"
|
34 |
+
}
|
35 |
+
}
|
front/package-lock copy.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
front/postcss.config copy.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export default {
|
2 |
+
plugins: {
|
3 |
+
tailwindcss: {},
|
4 |
+
autoprefixer: {},
|
5 |
+
},
|
6 |
+
};
|
front/src/App.tsx
CHANGED
@@ -15,12 +15,12 @@ function App() {
|
|
15 |
<div className="container mx-auto flex items-center justify-between">
|
16 |
<Link to="/" className="flex items-center space-x-2">
|
17 |
<Globe2 className="h-6 w-6 text-blue-600" />
|
18 |
-
<span className="text-xl font-semibold">MSA
|
19 |
</Link>
|
20 |
<div className="flex space-x-4">
|
21 |
-
<Link to="/" className="px-4 py-2 rounded hover:bg-gray-100">
|
22 |
-
<Link to="/arabic" className="px-4 py-2 rounded hover:bg-gray-100">
|
23 |
-
<Link to="/french" className="px-4 py-2 rounded hover:bg-gray-100">
|
24 |
</div>
|
25 |
</div>
|
26 |
</nav>
|
|
|
15 |
<div className="container mx-auto flex items-center justify-between">
|
16 |
<Link to="/" className="flex items-center space-x-2">
|
17 |
<Globe2 className="h-6 w-6 text-blue-600" />
|
18 |
+
<span className="text-xl font-semibold">MSA Translate</span>
|
19 |
</Link>
|
20 |
<div className="flex space-x-4">
|
21 |
+
<Link to="/" className="px-4 py-2 rounded hover:bg-gray-100">Choix langue</Link>
|
22 |
+
<Link to="/arabic" className="px-4 py-2 rounded hover:bg-gray-100">Adherent</Link>
|
23 |
+
<Link to="/french" className="px-4 py-2 rounded hover:bg-gray-100">Agent</Link>
|
24 |
</div>
|
25 |
</div>
|
26 |
</nav>
|
front/src/context/ChatContext.tsx
CHANGED
@@ -3,6 +3,7 @@ import React, { createContext, useContext, useState } from 'react';
|
|
3 |
interface Message {
|
4 |
id: number;
|
5 |
text: string;
|
|
|
6 |
isArabic?: boolean;
|
7 |
isFrench?: boolean;
|
8 |
timestamp: Date;
|
@@ -18,8 +19,43 @@ const ChatContext = createContext<ChatContextType | undefined>(undefined);
|
|
18 |
export function ChatProvider({ children }: { children: React.ReactNode }) {
|
19 |
const [messages, setMessages] = useState<Message[]>([]);
|
20 |
|
21 |
-
const
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
};
|
24 |
|
25 |
return (
|
|
|
3 |
interface Message {
|
4 |
id: number;
|
5 |
text: string;
|
6 |
+
translatedText?: string;
|
7 |
isArabic?: boolean;
|
8 |
isFrench?: boolean;
|
9 |
timestamp: Date;
|
|
|
19 |
export function ChatProvider({ children }: { children: React.ReactNode }) {
|
20 |
const [messages, setMessages] = useState<Message[]>([]);
|
21 |
|
22 |
+
const translateText = async (text: string, from: string, to: string) => {
|
23 |
+
try {
|
24 |
+
// Utilisation de l'API Google Translate via RapidAPI
|
25 |
+
const response = await fetch('https://translate.googleapis.com/translate_a/single?client=gtx&sl=' + from + '&tl=' + to + '&dt=t&q=' + encodeURI(text));
|
26 |
+
|
27 |
+
if (!response.ok) {
|
28 |
+
throw new Error('Translation failed');
|
29 |
+
}
|
30 |
+
|
31 |
+
const data = await response.json();
|
32 |
+
// La traduction se trouve dans le premier élément du premier tableau
|
33 |
+
const translatedText = data[0][0][0];
|
34 |
+
|
35 |
+
return translatedText;
|
36 |
+
} catch (error) {
|
37 |
+
console.error('Translation error:', error);
|
38 |
+
return text;
|
39 |
+
}
|
40 |
+
};
|
41 |
+
|
42 |
+
const addMessage = async (message: Message) => {
|
43 |
+
const from = message.isArabic ? 'ar' : 'fr';
|
44 |
+
const to = message.isArabic ? 'fr' : 'ar';
|
45 |
+
|
46 |
+
try {
|
47 |
+
const translatedText = await translateText(message.text, from, to);
|
48 |
+
|
49 |
+
const messageWithTranslation = {
|
50 |
+
...message,
|
51 |
+
translatedText,
|
52 |
+
};
|
53 |
+
|
54 |
+
setMessages(prev => [...prev, messageWithTranslation]);
|
55 |
+
} catch (error) {
|
56 |
+
console.error('Error in message translation:', error);
|
57 |
+
setMessages(prev => [...prev, message]);
|
58 |
+
}
|
59 |
};
|
60 |
|
61 |
return (
|
front/src/pages/ArabicChat.tsx
CHANGED
@@ -1,17 +1,17 @@
|
|
1 |
import React, { useState, useRef } from 'react';
|
2 |
-
import { Mic, Play, Square } from 'lucide-react';
|
3 |
import { useChat } from '../context/ChatContext';
|
4 |
|
5 |
const ArabicChat = () => {
|
6 |
const { messages, addMessage } = useChat();
|
7 |
const [isRecording, setIsRecording] = useState(false);
|
|
|
8 |
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
9 |
const chunksRef = useRef<Blob[]>([]);
|
10 |
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
11 |
|
12 |
const startRecording = async () => {
|
13 |
try {
|
14 |
-
// Initialize speech recognition
|
15 |
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
16 |
const recognition = new SpeechRecognition();
|
17 |
recognitionRef.current = recognition;
|
@@ -39,7 +39,6 @@ const ArabicChat = () => {
|
|
39 |
recognition.start();
|
40 |
setIsRecording(true);
|
41 |
|
42 |
-
// Also start audio recording for WAV file
|
43 |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
44 |
const mediaRecorder = new MediaRecorder(stream);
|
45 |
mediaRecorderRef.current = mediaRecorder;
|
@@ -67,28 +66,42 @@ const ArabicChat = () => {
|
|
67 |
}
|
68 |
};
|
69 |
|
70 |
-
const speakText = (text: string
|
71 |
if ('speechSynthesis' in window) {
|
72 |
-
window.speechSynthesis.cancel();
|
|
|
73 |
|
74 |
const utterance = new SpeechSynthesisUtterance(text);
|
75 |
-
utterance.lang =
|
76 |
-
utterance.rate =
|
77 |
utterance.pitch = 1.0;
|
78 |
utterance.volume = 1.0;
|
79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
window.speechSynthesis.speak(utterance);
|
81 |
}
|
82 |
};
|
83 |
|
|
|
|
|
|
|
|
|
|
|
84 |
return (
|
85 |
-
<div className="container mx-auto p-4 max-w-2xl">
|
86 |
<div className="bg-white rounded-lg shadow-md h-[calc(100vh-12rem)] flex flex-col">
|
87 |
<div className="flex-1 overflow-y-auto p-4">
|
88 |
{messages.map((message) => (
|
89 |
<div
|
90 |
key={message.id}
|
91 |
-
className={`flex ${message.isArabic ? 'justify-
|
92 |
>
|
93 |
<div
|
94 |
className={`rounded-lg p-3 max-w-[70%] ${
|
@@ -97,14 +110,20 @@ const ArabicChat = () => {
|
|
97 |
: 'bg-white border border-gray-300'
|
98 |
}`}
|
99 |
>
|
100 |
-
<p
|
|
|
|
|
101 |
{!message.isArabic && (
|
102 |
<button
|
103 |
className="mt-2 p-2 bg-gray-100 rounded-full hover:bg-gray-200 transition-colors"
|
104 |
-
onClick={() => speakText(message.
|
105 |
-
title=
|
106 |
>
|
107 |
-
|
|
|
|
|
|
|
|
|
108 |
</button>
|
109 |
)}
|
110 |
</div>
|
@@ -113,7 +132,7 @@ const ArabicChat = () => {
|
|
113 |
</div>
|
114 |
|
115 |
<div className="border-t p-4">
|
116 |
-
<div className="flex items-center justify-center space-x-4">
|
117 |
<button
|
118 |
onClick={isRecording ? stopRecording : startRecording}
|
119 |
className="relative p-4 rounded-full bg-blue-500 hover:bg-blue-600 text-white"
|
@@ -127,7 +146,7 @@ const ArabicChat = () => {
|
|
127 |
<div className="absolute inset-0 animate-ping rounded-full bg-blue-400 opacity-75"></div>
|
128 |
)}
|
129 |
</button>
|
130 |
-
<p className="text-gray-600
|
131 |
</div>
|
132 |
</div>
|
133 |
</div>
|
|
|
1 |
import React, { useState, useRef } from 'react';
|
2 |
+
import { Mic, Play, Square, StopCircle } from 'lucide-react';
|
3 |
import { useChat } from '../context/ChatContext';
|
4 |
|
5 |
const ArabicChat = () => {
|
6 |
const { messages, addMessage } = useChat();
|
7 |
const [isRecording, setIsRecording] = useState(false);
|
8 |
+
const [isPlaying, setIsPlaying] = useState(false);
|
9 |
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
10 |
const chunksRef = useRef<Blob[]>([]);
|
11 |
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
12 |
|
13 |
const startRecording = async () => {
|
14 |
try {
|
|
|
15 |
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
16 |
const recognition = new SpeechRecognition();
|
17 |
recognitionRef.current = recognition;
|
|
|
39 |
recognition.start();
|
40 |
setIsRecording(true);
|
41 |
|
|
|
42 |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
43 |
const mediaRecorder = new MediaRecorder(stream);
|
44 |
mediaRecorderRef.current = mediaRecorder;
|
|
|
66 |
}
|
67 |
};
|
68 |
|
69 |
+
const speakText = (text: string) => {
|
70 |
if ('speechSynthesis' in window) {
|
71 |
+
window.speechSynthesis.cancel(); // Arrête toute lecture en cours
|
72 |
+
setIsPlaying(true);
|
73 |
|
74 |
const utterance = new SpeechSynthesisUtterance(text);
|
75 |
+
utterance.lang = 'ar-SA';
|
76 |
+
utterance.rate = 0.9;
|
77 |
utterance.pitch = 1.0;
|
78 |
utterance.volume = 1.0;
|
79 |
|
80 |
+
utterance.onend = () => {
|
81 |
+
setIsPlaying(false);
|
82 |
+
};
|
83 |
+
|
84 |
+
utterance.onerror = () => {
|
85 |
+
setIsPlaying(false);
|
86 |
+
};
|
87 |
+
|
88 |
window.speechSynthesis.speak(utterance);
|
89 |
}
|
90 |
};
|
91 |
|
92 |
+
const stopSpeaking = () => {
|
93 |
+
window.speechSynthesis.cancel();
|
94 |
+
setIsPlaying(false);
|
95 |
+
};
|
96 |
+
|
97 |
return (
|
98 |
+
<div className="container mx-auto p-4 max-w-2xl" dir="rtl">
|
99 |
<div className="bg-white rounded-lg shadow-md h-[calc(100vh-12rem)] flex flex-col">
|
100 |
<div className="flex-1 overflow-y-auto p-4">
|
101 |
{messages.map((message) => (
|
102 |
<div
|
103 |
key={message.id}
|
104 |
+
className={`flex ${message.isArabic ? 'justify-start' : 'justify-end'} mb-4`}
|
105 |
>
|
106 |
<div
|
107 |
className={`rounded-lg p-3 max-w-[70%] ${
|
|
|
110 |
: 'bg-white border border-gray-300'
|
111 |
}`}
|
112 |
>
|
113 |
+
<p className="text-right" style={{ direction: 'rtl' }}>
|
114 |
+
{message.isArabic ? message.text : message.translatedText}
|
115 |
+
</p>
|
116 |
{!message.isArabic && (
|
117 |
<button
|
118 |
className="mt-2 p-2 bg-gray-100 rounded-full hover:bg-gray-200 transition-colors"
|
119 |
+
onClick={() => isPlaying ? stopSpeaking() : speakText(message.translatedText || '')}
|
120 |
+
title={isPlaying ? 'إيقاف الاستماع' : 'الاستماع إلى الرسالة'}
|
121 |
>
|
122 |
+
{isPlaying ? (
|
123 |
+
<StopCircle className="h-4 w-4 text-red-500" />
|
124 |
+
) : (
|
125 |
+
<Play className="h-4 w-4" />
|
126 |
+
)}
|
127 |
</button>
|
128 |
)}
|
129 |
</div>
|
|
|
132 |
</div>
|
133 |
|
134 |
<div className="border-t p-4">
|
135 |
+
<div className="flex items-center justify-center space-x-4 flex-row-reverse">
|
136 |
<button
|
137 |
onClick={isRecording ? stopRecording : startRecording}
|
138 |
className="relative p-4 rounded-full bg-blue-500 hover:bg-blue-600 text-white"
|
|
|
146 |
<div className="absolute inset-0 animate-ping rounded-full bg-blue-400 opacity-75"></div>
|
147 |
)}
|
148 |
</button>
|
149 |
+
<p className="text-gray-600">انقر ثم تحدث</p>
|
150 |
</div>
|
151 |
</div>
|
152 |
</div>
|
front/src/pages/FrenchChat.tsx
CHANGED
@@ -1,17 +1,17 @@
|
|
1 |
import React, { useState, useRef } from 'react';
|
2 |
-
import { Mic, Play, Square } from 'lucide-react';
|
3 |
import { useChat } from '../context/ChatContext';
|
4 |
|
5 |
const FrenchChat = () => {
|
6 |
const { messages, addMessage } = useChat();
|
7 |
const [isRecording, setIsRecording] = useState(false);
|
|
|
8 |
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
9 |
const chunksRef = useRef<Blob[]>([]);
|
10 |
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
11 |
|
12 |
const startRecording = async () => {
|
13 |
try {
|
14 |
-
// Initialize speech recognition
|
15 |
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
16 |
const recognition = new SpeechRecognition();
|
17 |
recognitionRef.current = recognition;
|
@@ -39,7 +39,6 @@ const FrenchChat = () => {
|
|
39 |
recognition.start();
|
40 |
setIsRecording(true);
|
41 |
|
42 |
-
// Also start audio recording for WAV file
|
43 |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
44 |
const mediaRecorder = new MediaRecorder(stream);
|
45 |
mediaRecorderRef.current = mediaRecorder;
|
@@ -67,20 +66,34 @@ const FrenchChat = () => {
|
|
67 |
}
|
68 |
};
|
69 |
|
70 |
-
const speakText = (text: string
|
71 |
if ('speechSynthesis' in window) {
|
72 |
-
window.speechSynthesis.cancel();
|
|
|
73 |
|
74 |
const utterance = new SpeechSynthesisUtterance(text);
|
75 |
-
utterance.lang =
|
76 |
-
utterance.rate =
|
77 |
utterance.pitch = 1.0;
|
78 |
utterance.volume = 1.0;
|
79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
window.speechSynthesis.speak(utterance);
|
81 |
}
|
82 |
};
|
83 |
|
|
|
|
|
|
|
|
|
|
|
84 |
return (
|
85 |
<div className="container mx-auto p-4 max-w-2xl">
|
86 |
<div className="bg-white rounded-lg shadow-md h-[calc(100vh-12rem)] flex flex-col">
|
@@ -97,14 +110,18 @@ const FrenchChat = () => {
|
|
97 |
: 'bg-white border border-gray-300'
|
98 |
}`}
|
99 |
>
|
100 |
-
<p>{message.text}</p>
|
101 |
{!message.isFrench && (
|
102 |
<button
|
103 |
className="mt-2 p-2 bg-gray-100 rounded-full hover:bg-gray-200 transition-colors"
|
104 |
-
onClick={() => speakText(message.
|
105 |
-
title=
|
106 |
>
|
107 |
-
|
|
|
|
|
|
|
|
|
108 |
</button>
|
109 |
)}
|
110 |
</div>
|
|
|
1 |
import React, { useState, useRef } from 'react';
|
2 |
+
import { Mic, Play, Square, StopCircle } from 'lucide-react';
|
3 |
import { useChat } from '../context/ChatContext';
|
4 |
|
5 |
const FrenchChat = () => {
|
6 |
const { messages, addMessage } = useChat();
|
7 |
const [isRecording, setIsRecording] = useState(false);
|
8 |
+
const [isPlaying, setIsPlaying] = useState(false);
|
9 |
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
10 |
const chunksRef = useRef<Blob[]>([]);
|
11 |
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
12 |
|
13 |
const startRecording = async () => {
|
14 |
try {
|
|
|
15 |
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
16 |
const recognition = new SpeechRecognition();
|
17 |
recognitionRef.current = recognition;
|
|
|
39 |
recognition.start();
|
40 |
setIsRecording(true);
|
41 |
|
|
|
42 |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
43 |
const mediaRecorder = new MediaRecorder(stream);
|
44 |
mediaRecorderRef.current = mediaRecorder;
|
|
|
66 |
}
|
67 |
};
|
68 |
|
69 |
+
const speakText = (text: string) => {
|
70 |
if ('speechSynthesis' in window) {
|
71 |
+
window.speechSynthesis.cancel(); // Arrête toute lecture en cours
|
72 |
+
setIsPlaying(true);
|
73 |
|
74 |
const utterance = new SpeechSynthesisUtterance(text);
|
75 |
+
utterance.lang = 'fr-FR';
|
76 |
+
utterance.rate = 0.9;
|
77 |
utterance.pitch = 1.0;
|
78 |
utterance.volume = 1.0;
|
79 |
|
80 |
+
utterance.onend = () => {
|
81 |
+
setIsPlaying(false);
|
82 |
+
};
|
83 |
+
|
84 |
+
utterance.onerror = () => {
|
85 |
+
setIsPlaying(false);
|
86 |
+
};
|
87 |
+
|
88 |
window.speechSynthesis.speak(utterance);
|
89 |
}
|
90 |
};
|
91 |
|
92 |
+
const stopSpeaking = () => {
|
93 |
+
window.speechSynthesis.cancel();
|
94 |
+
setIsPlaying(false);
|
95 |
+
};
|
96 |
+
|
97 |
return (
|
98 |
<div className="container mx-auto p-4 max-w-2xl">
|
99 |
<div className="bg-white rounded-lg shadow-md h-[calc(100vh-12rem)] flex flex-col">
|
|
|
110 |
: 'bg-white border border-gray-300'
|
111 |
}`}
|
112 |
>
|
113 |
+
<p>{message.isFrench ? message.text : message.translatedText}</p>
|
114 |
{!message.isFrench && (
|
115 |
<button
|
116 |
className="mt-2 p-2 bg-gray-100 rounded-full hover:bg-gray-200 transition-colors"
|
117 |
+
onClick={() => isPlaying ? stopSpeaking() : speakText(message.translatedText || '')}
|
118 |
+
title={isPlaying ? 'Arrêter la lecture' : 'Écouter le message'}
|
119 |
>
|
120 |
+
{isPlaying ? (
|
121 |
+
<StopCircle className="h-4 w-4 text-red-500" />
|
122 |
+
) : (
|
123 |
+
<Play className="h-4 w-4" />
|
124 |
+
)}
|
125 |
</button>
|
126 |
)}
|
127 |
</div>
|
front/src/pages/LanguageSelection.tsx
CHANGED
@@ -8,22 +8,22 @@ const LanguageSelection = () => {
|
|
8 |
{
|
9 |
name: 'Bulgarian',
|
10 |
nativeName: 'Говоря български',
|
11 |
-
flag: 'https://
|
12 |
},
|
13 |
{
|
14 |
name: 'Arabic',
|
15 |
nativeName: 'أتحدث العربية',
|
16 |
-
flag: 'https://
|
17 |
},
|
18 |
{
|
19 |
name: 'Spanish',
|
20 |
nativeName: 'Hablo español',
|
21 |
-
flag: 'https://
|
22 |
},
|
23 |
{
|
24 |
name: 'Romanian',
|
25 |
nativeName: 'Vorbesc română',
|
26 |
-
flag: 'https://
|
27 |
},
|
28 |
];
|
29 |
|
@@ -32,9 +32,9 @@ const LanguageSelection = () => {
|
|
32 |
<div className="max-w-2xl mx-auto">
|
33 |
<div className="flex justify-center mb-8">
|
34 |
<img
|
35 |
-
src="https://
|
36 |
alt="MSA Logo"
|
37 |
-
className="h-
|
38 |
/>
|
39 |
</div>
|
40 |
|
@@ -48,10 +48,10 @@ const LanguageSelection = () => {
|
|
48 |
<img
|
49 |
src={lang.flag}
|
50 |
alt={`${lang.name} flag`}
|
51 |
-
className="w-12
|
52 |
/>
|
53 |
<div className="text-left">
|
54 |
-
|
55 |
<p className="text-lg font-semibold">{lang.nativeName}</p>
|
56 |
</div>
|
57 |
</button>
|
|
|
8 |
{
|
9 |
name: 'Bulgarian',
|
10 |
nativeName: 'Говоря български',
|
11 |
+
flag: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Flag_of_Bulgaria.svg/langfr-2560px-Flag_of_Bulgaria.svg.png',
|
12 |
},
|
13 |
{
|
14 |
name: 'Arabic',
|
15 |
nativeName: 'أتحدث العربية',
|
16 |
+
flag: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Flag_of_Morocco.svg/langfr-1920px-Flag_of_Morocco.svg.png',
|
17 |
},
|
18 |
{
|
19 |
name: 'Spanish',
|
20 |
nativeName: 'Hablo español',
|
21 |
+
flag: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Flag_of_Spain.svg/langfr-1920px-Flag_of_Spain.svg.png',
|
22 |
},
|
23 |
{
|
24 |
name: 'Romanian',
|
25 |
nativeName: 'Vorbesc română',
|
26 |
+
flag: 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Flag_of_Romania.svg/langfr-1920px-Flag_of_Romania.svg.png',
|
27 |
},
|
28 |
];
|
29 |
|
|
|
32 |
<div className="max-w-2xl mx-auto">
|
33 |
<div className="flex justify-center mb-8">
|
34 |
<img
|
35 |
+
src="https://upload.wikimedia.org/wikipedia/fr/1/18/Mutualite_sociale_agricole_logo.svg"
|
36 |
alt="MSA Logo"
|
37 |
+
className="h-16"
|
38 |
/>
|
39 |
</div>
|
40 |
|
|
|
48 |
<img
|
49 |
src={lang.flag}
|
50 |
alt={`${lang.name} flag`}
|
51 |
+
className="w-12 shadow-md border-solid"
|
52 |
/>
|
53 |
<div className="text-left">
|
54 |
+
|
55 |
<p className="text-lg font-semibold">{lang.nativeName}</p>
|
56 |
</div>
|
57 |
</button>
|
front/tailwind.config copy.js
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** @type {import('tailwindcss').Config} */
|
2 |
+
export default {
|
3 |
+
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
4 |
+
theme: {
|
5 |
+
extend: {},
|
6 |
+
},
|
7 |
+
plugins: [],
|
8 |
+
};
|
front/tsconfig copy.json
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"files": [],
|
3 |
+
"references": [
|
4 |
+
{ "path": "./tsconfig.app.json" },
|
5 |
+
{ "path": "./tsconfig.node.json" }
|
6 |
+
]
|
7 |
+
}
|
front/tsconfig.app copy.json
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "ES2020",
|
4 |
+
"useDefineForClassFields": true,
|
5 |
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
6 |
+
"module": "ESNext",
|
7 |
+
"skipLibCheck": true,
|
8 |
+
|
9 |
+
/* Bundler mode */
|
10 |
+
"moduleResolution": "bundler",
|
11 |
+
"allowImportingTsExtensions": true,
|
12 |
+
"isolatedModules": true,
|
13 |
+
"moduleDetection": "force",
|
14 |
+
"noEmit": true,
|
15 |
+
"jsx": "react-jsx",
|
16 |
+
|
17 |
+
/* Linting */
|
18 |
+
"strict": true,
|
19 |
+
"noUnusedLocals": true,
|
20 |
+
"noUnusedParameters": true,
|
21 |
+
"noFallthroughCasesInSwitch": true
|
22 |
+
},
|
23 |
+
"include": ["src"]
|
24 |
+
}
|
front/tsconfig.node copy.json
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "ES2022",
|
4 |
+
"lib": ["ES2023"],
|
5 |
+
"module": "ESNext",
|
6 |
+
"skipLibCheck": true,
|
7 |
+
|
8 |
+
/* Bundler mode */
|
9 |
+
"moduleResolution": "bundler",
|
10 |
+
"allowImportingTsExtensions": true,
|
11 |
+
"isolatedModules": true,
|
12 |
+
"moduleDetection": "force",
|
13 |
+
"noEmit": true,
|
14 |
+
|
15 |
+
/* Linting */
|
16 |
+
"strict": true,
|
17 |
+
"noUnusedLocals": true,
|
18 |
+
"noUnusedParameters": true,
|
19 |
+
"noFallthroughCasesInSwitch": true
|
20 |
+
},
|
21 |
+
"include": ["vite.config.ts"]
|
22 |
+
}
|
front/vite.config copy.ts
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { defineConfig } from 'vite';
|
2 |
+
import react from '@vitejs/plugin-react';
|
3 |
+
|
4 |
+
// https://vitejs.dev/config/
|
5 |
+
export default defineConfig({
|
6 |
+
plugins: [react()],
|
7 |
+
optimizeDeps: {
|
8 |
+
exclude: ['lucide-react'],
|
9 |
+
},
|
10 |
+
});
|
requirements.txt
CHANGED
@@ -7,4 +7,5 @@ huggingface-hub
|
|
7 |
tiktoken
|
8 |
blobfile
|
9 |
protobuf
|
10 |
-
sentencepiece
|
|
|
|
7 |
tiktoken
|
8 |
blobfile
|
9 |
protobuf
|
10 |
+
sentencepiece
|
11 |
+
torchaudio
|