Babel Deployer commited on
Commit
0a5aa16
·
1 Parent(s): a29fadd

Deploy Railway Backend Connection

Browse files
.gitattributes DELETED
@@ -1,35 +0,0 @@
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile DELETED
@@ -1,21 +0,0 @@
1
- FROM python:3.10-slim
2
-
3
- WORKDIR /app
4
-
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y \
7
- git \
8
- && rm -rf /var/lib/apt/lists/*
9
-
10
- # Copy requirements
11
- COPY requirements.txt .
12
- RUN pip install --no-cache-dir -r requirements.txt
13
-
14
- # Copy app files
15
- COPY . .
16
-
17
- # Expose port
18
- EXPOSE 7860
19
-
20
- # Run Flask app
21
- CMD ["python", "app.py"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md DELETED
@@ -1,26 +0,0 @@
1
- ---
2
- title: Instant Translat - AI Voice Translation
3
- emoji: 🌍
4
- colorFrom: purple
5
- colorTo: blue
6
- sdk: docker
7
- pinned: true
8
- license: mit
9
- ---
10
-
11
- # 🌍 Instant Translat - AI Voice Translation
12
-
13
- **Real-time voice translation with beautiful dark theme UI**
14
-
15
- ## Features
16
- - 🎤 Speech-to-Text (SeamlessM4T v2 Large)
17
- - 🌍 Translation (NLLB-200 - 200 languages + Darija)
18
- - 🔊 Text-to-Speech (Fish Audio S1)
19
- - 🎭 Voice Cloning
20
- - 🌙 Beautiful Dark Theme UI
21
-
22
- ## Technology
23
- - Flask backend
24
- - Custom dark theme interface
25
- - Meta AI models
26
- - Fish Audio TTS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py DELETED
@@ -1,158 +0,0 @@
1
- from flask import Flask, render_template, request, jsonify
2
- from transformers import (
3
- AutoProcessor,
4
- SeamlessM4Tv2ForSpeechToText,
5
- AutoModelForSeq2SeqLM,
6
- AutoTokenizer,
7
- )
8
- import torch
9
- import numpy as np
10
- import base64
11
- import io
12
- import requests
13
- import os
14
-
15
- app = Flask(__name__)
16
-
17
- # ============================================================
18
- # Device Setup
19
- # ============================================================
20
- device = "cuda" if torch.cuda.is_available() else "cpu"
21
- print(f"🖥️ Device: {device}")
22
-
23
- # ============================================================
24
- # Load Models
25
- # ============================================================
26
-
27
- print("📥 Loading SeamlessM4T v2 Large...")
28
- STT_MODEL = "facebook/seamless-m4t-v2-large"
29
- stt_processor = AutoProcessor.from_pretrained(STT_MODEL)
30
- stt_model = SeamlessM4Tv2ForSpeechToText.from_pretrained(STT_MODEL)
31
- stt_model = stt_model.to(device).eval()
32
- print("✅ SeamlessM4T v2 Large loaded!")
33
-
34
- print("📥 Loading NLLB-200...")
35
- NLLB_MODEL = "facebook/nllb-200-distilled-600M"
36
- nllb_tokenizer = AutoTokenizer.from_pretrained(NLLB_MODEL)
37
- nllb_model = AutoModelForSeq2SeqLM.from_pretrained(NLLB_MODEL)
38
- nllb_model = nllb_model.to(device).eval()
39
- print("✅ NLLB-200 loaded!")
40
-
41
- # Fish Audio API
42
- FISH_AUDIO_API_KEY = os.environ.get('FISH_AUDIO_API_KEY', '')
43
-
44
- # Language mapping
45
- LANG_MAP = {
46
- 'ar-SA': ('arb', 'ary_Arab'),
47
- 'fr-FR': ('fra', 'fra_Latn'),
48
- 'en-US': ('eng', 'eng_Latn'),
49
- 'es-ES': ('spa', 'spa_Latn'),
50
- 'de-DE': ('deu', 'deu_Latn'),
51
- }
52
-
53
- @app.route('/')
54
- def index():
55
- return render_template('index.html')
56
-
57
- @app.route('/process_audio', methods=['POST'])
58
- def process_audio():
59
- try:
60
- data = request.json
61
- audio_b64 = data.get('audio')
62
- source_lang = data.get('source_lang', 'ar-SA')
63
- target_lang = data.get('target_lang', 'en-US')
64
-
65
- # Decode audio
66
- audio_bytes = base64.b64decode(audio_b64)
67
-
68
- # Convert to numpy
69
- import scipy.io.wavfile as wavfile
70
- sample_rate, audio_data = wavfile.read(io.BytesIO(audio_bytes))
71
- audio_data = audio_data.astype(np.float32)
72
- if np.abs(audio_data).max() > 1.0:
73
- audio_data = audio_data / 32768.0
74
-
75
- # STT
76
- src_code = LANG_MAP.get(source_lang, ('eng', 'eng_Latn'))[0]
77
- inputs = stt_processor(
78
- audios=audio_data,
79
- sampling_rate=sample_rate,
80
- return_tensors="pt"
81
- ).to(device)
82
-
83
- with torch.no_grad():
84
- output_tokens = stt_model.generate(
85
- **inputs,
86
- tgt_lang=src_code,
87
- generate_speech=False
88
- )
89
-
90
- transcript = stt_processor.decode(output_tokens[0].tolist(), skip_special_tokens=True)
91
-
92
- # Translation
93
- src_nllb = LANG_MAP.get(source_lang, ('eng', 'eng_Latn'))[1]
94
- tgt_nllb = LANG_MAP.get(target_lang, ('eng', 'eng_Latn'))[1]
95
-
96
- nllb_tokenizer.src_lang = src_nllb
97
- inputs = nllb_tokenizer(transcript, return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)
98
-
99
- forced_bos_token_id = nllb_tokenizer.convert_tokens_to_ids(tgt_nllb)
100
-
101
- with torch.no_grad():
102
- outputs = nllb_model.generate(
103
- **inputs,
104
- forced_bos_token_id=forced_bos_token_id,
105
- max_length=512,
106
- num_beams=5
107
- )
108
-
109
- translation = nllb_tokenizer.decode(outputs[0], skip_special_tokens=True)
110
-
111
- # TTS
112
- tts_audio_b64 = None
113
- if FISH_AUDIO_API_KEY:
114
- tts_audio = generate_tts(translation)
115
- if tts_audio:
116
- tts_audio_b64 = base64.b64encode(tts_audio).decode('utf-8')
117
-
118
- return jsonify({
119
- 'success': True,
120
- 'original': transcript,
121
- 'translation': translation,
122
- 'audio': tts_audio_b64,
123
- 'source_lang': source_lang,
124
- 'target_lang': target_lang
125
- })
126
-
127
- except Exception as e:
128
- return jsonify({'success': False, 'error': str(e)}), 500
129
-
130
- def generate_tts(text):
131
- """Generate TTS using Fish Audio"""
132
- if not FISH_AUDIO_API_KEY:
133
- return None
134
-
135
- try:
136
- headers = {'Authorization': f'Bearer {FISH_AUDIO_API_KEY}'}
137
- payload = {
138
- 'text': text,
139
- 'format': 'mp3',
140
- 'mp3_bitrate': 192,
141
- }
142
-
143
- response = requests.post(
144
- 'https://api.fish.audio/v1/tts',
145
- headers=headers,
146
- json=payload,
147
- timeout=60
148
- )
149
-
150
- if response.status_code == 200:
151
- return response.content
152
-
153
- return None
154
- except:
155
- return None
156
-
157
- if __name__ == '__main__':
158
- app.run(host='0.0.0.0', port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/enhanced.css → enhanced.css RENAMED
File without changes
templates/index.html → index.html RENAMED
@@ -23,8 +23,8 @@
23
  href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+Arabic:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
24
  rel="stylesheet">
25
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
26
- <link rel="stylesheet" href="/static/enhanced.css">
27
- <link rel="manifest" href="/static/manifest.json">
28
  <style>
29
  /* CSS moved to static/enhanced.css */
30
  </style>
@@ -853,7 +853,7 @@
853
  console.log('✨ Target language set to:', this.value);
854
 
855
  // Clear cache for new language
856
- fetch('/clear_cache', { method: 'POST' });
857
  });
858
  }
859
 
 
23
  href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+Arabic:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
24
  rel="stylesheet">
25
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
26
+ <link rel="stylesheet" href="./enhanced.css">
27
+ <link rel="manifest" href="./manifest.json">
28
  <style>
29
  /* CSS moved to static/enhanced.css */
30
  </style>
 
853
  console.log('✨ Target language set to:', this.value);
854
 
855
  // Clear cache for new language
856
+ fetch('https://instant-translat-production.up.railway.app/clear_cache', { method: 'POST' });
857
  });
858
  }
859
 
static/manifest.json → manifest.json RENAMED
File without changes
requirements.txt DELETED
@@ -1,7 +0,0 @@
1
- Flask==3.0.0
2
- transformers>=4.30.0
3
- torch>=2.0.0
4
- scipy
5
- numpy
6
- requests
7
- Werkzeug==3.0.0
 
 
 
 
 
 
 
 
static/script.js → script.js RENAMED
@@ -1,5 +1,7 @@
1
  // Babel - Main Script (Cleaned & Optimized)
2
 
 
 
3
  let mediaRecorder;
4
  let audioChunks = [];
5
  let isRecording = false;
@@ -1129,7 +1131,7 @@ async function sendTextForProcessing(text) {
1129
  };
1130
 
1131
  try {
1132
- const response = await fetch('/process_audio', {
1133
  method: 'POST',
1134
  headers: { 'Content-Type': 'application/json' },
1135
  body: JSON.stringify(payload)
@@ -1937,7 +1939,7 @@ document.addEventListener('DOMContentLoaded', () => {
1937
  sourceLangSelector.addEventListener('change', function () {
1938
  console.log('🔄 Source Language Changed -> Clearing Smart History...');
1939
  localStorage.setItem('sourceLang', this.value);
1940
- fetch('/clear_cache', { method: 'POST' });
1941
  });
1942
  }
1943
 
@@ -1946,7 +1948,7 @@ document.addEventListener('DOMContentLoaded', () => {
1946
  quickLangSelector.addEventListener('change', function () {
1947
  console.log('🔄 Target Language Changed -> Clearing Smart History...');
1948
  localStorage.setItem('targetLang', this.value);
1949
- fetch('/clear_cache', { method: 'POST' });
1950
  });
1951
  }
1952
 
 
1
  // Babel - Main Script (Cleaned & Optimized)
2
 
3
+ const API_BASE = 'https://instant-translat-production.up.railway.app';
4
+
5
  let mediaRecorder;
6
  let audioChunks = [];
7
  let isRecording = false;
 
1131
  };
1132
 
1133
  try {
1134
+ const response = await fetch(API_BASE + '/process_audio', {
1135
  method: 'POST',
1136
  headers: { 'Content-Type': 'application/json' },
1137
  body: JSON.stringify(payload)
 
1939
  sourceLangSelector.addEventListener('change', function () {
1940
  console.log('🔄 Source Language Changed -> Clearing Smart History...');
1941
  localStorage.setItem('sourceLang', this.value);
1942
+ fetch(API_BASE + '/clear_cache', { method: 'POST' });
1943
  });
1944
  }
1945
 
 
1948
  quickLangSelector.addEventListener('change', function () {
1949
  console.log('🔄 Target Language Changed -> Clearing Smart History...');
1950
  localStorage.setItem('targetLang', this.value);
1951
+ fetch(API_BASE + '/clear_cache', { method: 'POST' });
1952
  });
1953
  }
1954
 
static/enhanced.js DELETED
@@ -1,217 +0,0 @@
1
- // ENHANCED UI - Darija Cards + Audio Player Manager
2
-
3
- // Parse Darija text into structured cards
4
- function parseDarijaToCards(text) {
5
- // Check if text contains Darija labels
6
- const hasArabicLabel = text.includes('🇲🇦') || text.includes('الدارجة');
7
- const hasArabiziLabel = text.includes('🔤') || text.includes('Darija (Arabizi)');
8
-
9
- if (!hasArabicLabel && !hasArabiziLabel) {
10
- // Regular text, return as-is
11
- return `<div class="regular-text">${text.replace(/\n/g, '<br>')}</div>`;
12
- }
13
-
14
- // Split by the labels
15
- const parts = text.split(/(?=🇲🇦)|(?=🔤)/);
16
-
17
- let html = '<div class="darija-container">';
18
-
19
- for (const part of parts) {
20
- const trimmed = part.trim();
21
- if (!trimmed) continue;
22
-
23
- if (trimmed.startsWith('🇲🇦') || trimmed.includes('الدارجة')) {
24
- // Arabic card
25
- const content = trimmed
26
- .replace(/🇲🇦.*?\n/, '')
27
- .replace(/الدارجة.*?\n/, '')
28
- .trim();
29
-
30
- html += `
31
- <div class="darija-card arabic">
32
- <div class="darija-card-header">
33
- <span class="emoji">🇲🇦</span>
34
- <span>الدارجة (Arabic)</span>
35
- </div>
36
- <div class="darija-card-content">${content}</div>
37
- </div>
38
- `;
39
- } else if (trimmed.startsWith('🔤') || trimmed.includes('Darija (Arabizi)')) {
40
- // Arabizi card
41
- const content = trimmed
42
- .replace(/🔤.*?\n/, '')
43
- .replace(/Darija.*?\n/, '')
44
- .trim();
45
-
46
- html += `
47
- <div class="darija-card arabizi">
48
- <div class="darija-card-header">
49
- <span class="emoji">🔤</span>
50
- <span>Darija (Arabizi)</span>
51
- </div>
52
- <div class="darija-card-content">${content}</div>
53
- </div>
54
- `;
55
- }
56
- }
57
-
58
- html += '</div>';
59
- return html;
60
- }
61
-
62
- // Audio Player Manager
63
- class AudioPlayer {
64
- constructor() {
65
- // Create a DEDICATED audio element for enhanced player
66
- this.audio = new Audio();
67
- this.audio.id = 'enhanced-audio-element';
68
-
69
- this.container = null;
70
- this.playBtn = null;
71
- this.waveform = null;
72
- this.timeDisplay = null;
73
- this.isPlaying = false;
74
- this.currentAudioSrc = null;
75
-
76
- this.createPlayer();
77
- this.setupEventListeners();
78
- }
79
-
80
- createPlayer() {
81
- // Create player HTML
82
- const playerHTML = `
83
- <div class="audio-player-container" id="enhanced-audio-player" style="display: none;">
84
- <div class="audio-player">
85
- <div class="player-controls">
86
- <button class="play-btn" id="player-play-btn">
87
- <i class="fa-solid fa-play"></i>
88
- </button>
89
- <div class="waveform" id="player-waveform">
90
- <div class="wave-bar"></div>
91
- <div class="wave-bar"></div>
92
- <div class="wave-bar"></div>
93
- <div class="wave-bar"></div>
94
- <div class="wave-bar"></div>
95
- </div>
96
- </div>
97
- <div class="player-actions">
98
- <button class="action-btn" id="player-download-btn">
99
- <i class="fa-solid fa-download"></i>
100
- <span>Download</span>
101
- </button>
102
- <button class="action-btn" id="player-close-btn">
103
- <i class="fa-solid fa-xmark"></i>
104
- <span>Close</span>
105
- </button>
106
- </div>
107
- <div class="time-display" id="player-time-display">0:00 / 0:00</div>
108
- </div>
109
- </div>
110
- `;
111
-
112
- // Insert into DOM
113
- document.body.insertAdjacentHTML('beforeend', playerHTML);
114
-
115
- // Get references
116
- this.container = document.getElementById('enhanced-audio-player');
117
- this.playBtn = document.getElementById('player-play-btn');
118
- this.waveform = document.getElementById('player-waveform');
119
- this.timeDisplay = document.getElementById('player-time-display');
120
-
121
- document.getElementById('player-download-btn').onclick = () => this.download();
122
- document.getElementById('player-close-btn').onclick = () => this.hide();
123
- }
124
-
125
- setupEventListeners() {
126
- this.playBtn.onclick = () => this.togglePlay();
127
-
128
- this.audio.addEventListener('play', () => {
129
- this.isPlaying = true;
130
- this.playBtn.innerHTML = '<i class="fa-solid fa-pause"></i>';
131
- this.waveform.classList.add('playing');
132
- });
133
-
134
- this.audio.addEventListener('pause', () => {
135
- this.isPlaying = false;
136
- this.playBtn.innerHTML = '<i class="fa-solid fa-play"></i>';
137
- this.waveform.classList.remove('playing');
138
- });
139
-
140
- this.audio.addEventListener('ended', () => {
141
- this.isPlaying = false;
142
- this.playBtn.innerHTML = '<i class="fa-solid fa-play"></i>';
143
- this.waveform.classList.remove('playing');
144
- });
145
-
146
- this.audio.addEventListener('timeupdate', () => {
147
- this.updateTimeDisplay();
148
- });
149
- }
150
-
151
- show(audioSrc) {
152
- if (audioSrc) {
153
- this.currentAudioSrc = audioSrc;
154
- this.audio.src = audioSrc;
155
- }
156
-
157
- this.container.style.display = 'block';
158
- setTimeout(() => {
159
- this.container.classList.add('visible');
160
- }, 10);
161
-
162
- // Auto-play
163
- this.audio.play().catch(err => {
164
- console.log('Auto-play blocked:', err);
165
- });
166
- }
167
-
168
- hide() {
169
- this.container.classList.remove('visible');
170
- setTimeout(() => {
171
- this.container.style.display = 'none';
172
- }, 300);
173
- this.audio.pause();
174
- }
175
-
176
- togglePlay() {
177
- if (this.isPlaying) {
178
- this.audio.pause();
179
- } else {
180
- this.audio.play();
181
- }
182
- }
183
-
184
- updateTimeDisplay() {
185
- const current = this.formatTime(this.audio.currentTime);
186
- const duration = this.formatTime(this.audio.duration || 0);
187
- this.timeDisplay.textContent = `${current} / ${duration}`;
188
- }
189
-
190
- formatTime(seconds) {
191
- if (isNaN(seconds)) return '0:00';
192
- const mins = Math.floor(seconds / 60);
193
- const secs = Math.floor(seconds % 60);
194
- return `${mins}:${secs.toString().padStart(2, '0')}`;
195
- }
196
-
197
- download() {
198
- if (!this.currentAudioSrc) return;
199
-
200
- const a = document.createElement('a');
201
- a.href = this.currentAudioSrc;
202
- a.download = `translation_${Date.now()}.mp3`;
203
- document.body.appendChild(a);
204
- a.click();
205
- document.body.removeChild(a);
206
- }
207
- }
208
-
209
- // Initialize on load
210
- window.enhancedAudioPlayer = null;
211
-
212
- document.addEventListener('DOMContentLoaded', () => {
213
- window.enhancedAudioPlayer = new AudioPlayer();
214
- });
215
-
216
- // Export functions
217
- window.parseDarijaToCards = parseDarijaToCards;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/favicon.ico DELETED
templates/admin.html DELETED
@@ -1,357 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7
- <title>Admin Control Panel | Instant Translator</title>
8
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
- <style>
11
- :root {
12
- --primary: #6366f1;
13
- --surface: #09090b;
14
- --surface-light: #18181b;
15
- --text: #e4e4e7;
16
- --danger: #ef4444;
17
- --success: #22c55e;
18
- }
19
-
20
- body {
21
- font-family: 'Outfit', sans-serif;
22
- background-color: #000;
23
- color: var(--text);
24
- margin: 0;
25
- padding: 20px;
26
- -webkit-font-smoothing: antialiased;
27
- }
28
-
29
- .admin-container {
30
- max-width: 800px;
31
- margin: 0 auto;
32
- }
33
-
34
- .header {
35
- display: flex;
36
- justify-content: space-between;
37
- align-items: center;
38
- margin-bottom: 30px;
39
- padding-bottom: 20px;
40
- border-bottom: 1px solid #27272a;
41
- }
42
-
43
- h1 {
44
- margin: 0;
45
- font-size: 24px;
46
- background: linear-gradient(135deg, #fff 0%, #a5b4fc 100%);
47
- -webkit-background-clip: text;
48
- -webkit-text-fill-color: transparent;
49
- }
50
-
51
- .card {
52
- background: var(--surface-light);
53
- border-radius: 16px;
54
- padding: 24px;
55
- margin-bottom: 20px;
56
- border: 1px solid #27272a;
57
- }
58
-
59
- .card h2 {
60
- margin-top: 0;
61
- font-size: 18px;
62
- color: #a1a1aa;
63
- margin-bottom: 20px;
64
- }
65
-
66
- .stat-grid {
67
- display: grid;
68
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
69
- gap: 15px;
70
- }
71
-
72
- .stat-box {
73
- background: #27272a;
74
- padding: 15px;
75
- border-radius: 12px;
76
- text-align: center;
77
- }
78
-
79
- .stat-value {
80
- font-size: 24px;
81
- font-weight: 700;
82
- color: #fff;
83
- }
84
-
85
- .stat-label {
86
- font-size: 12px;
87
- color: #a1a1aa;
88
- margin-top: 5px;
89
- }
90
-
91
- .form-group {
92
- margin-bottom: 15px;
93
- }
94
-
95
- .form-group label {
96
- display: block;
97
- margin-bottom: 8px;
98
- font-size: 14px;
99
- color: #a1a1aa;
100
- }
101
-
102
- .form-group input {
103
- width: 100%;
104
- padding: 12px;
105
- background: #000;
106
- border: 1px solid #27272a;
107
- border-radius: 8px;
108
- color: #fff;
109
- font-family: inherit;
110
- box-sizing: border-box;
111
- }
112
-
113
- .form-group input:focus {
114
- outline: none;
115
- border-color: var(--primary);
116
- }
117
-
118
- .btn {
119
- background: var(--primary);
120
- color: white;
121
- border: none;
122
- padding: 12px 24px;
123
- border-radius: 8px;
124
- font-weight: 600;
125
- cursor: pointer;
126
- width: 100%;
127
- font-size: 16px;
128
- transition: opacity 0.2s;
129
- }
130
-
131
- .btn:hover {
132
- opacity: 0.9;
133
- }
134
-
135
- .btn-danger {
136
- background: var(--danger);
137
- }
138
-
139
- .login-screen {
140
- position: fixed;
141
- inset: 0;
142
- background: #000;
143
- z-index: 100;
144
- display: flex;
145
- align-items: center;
146
- justify-content: center;
147
- }
148
-
149
- .login-box {
150
- width: 100%;
151
- max-width: 400px;
152
- padding: 30px;
153
- }
154
-
155
- .status-badge {
156
- display: inline-block;
157
- padding: 4px 12px;
158
- border-radius: 20px;
159
- font-size: 12px;
160
- font-weight: 600;
161
- background: #27272a;
162
- }
163
-
164
- .status-badge.online {
165
- background: rgba(34, 197, 94, 0.2);
166
- color: var(--success);
167
- }
168
- </style>
169
- </head>
170
-
171
- <body>
172
-
173
- <!-- LOGIN SCREEN -->
174
- <div id="login-screen" class="login-screen">
175
- <div class="card login-box">
176
- <h2 style="text-align: center; color: white;">Admin Access</h2>
177
- <div class="form-group">
178
- <label>Admin Password</label>
179
- <input type="password" id="admin-pass" placeholder="Enter PIN">
180
- </div>
181
- <button class="btn" onclick="login()">Unlock Panel</button>
182
- </div>
183
- </div>
184
-
185
- <!-- DASHBOARD -->
186
- <div class="admin-container">
187
- <div class="header">
188
- <div>
189
- <h1>Admin Control Panel</h1>
190
- <div style="margin-top: 5px; color: #a1a1aa; font-size: 13px;">
191
- <span class="status-badge online"><i class="fa-solid fa-circle" style="font-size: 8px;"></i> System
192
- Online</span>
193
- v2.5 Mobile Ready
194
- </div>
195
- </div>
196
- <button class="btn" style="width: auto; background: #27272a;" onclick="logout()"><i
197
- class="fa-solid fa-power-off"></i></button>
198
- </div>
199
-
200
- <!-- STATS -->
201
- <div class="card">
202
- <h2>📊 Live Statistics</h2>
203
- <div class="stat-grid">
204
- <div class="stat-box">
205
- <div class="stat-value" id="stat-requests">0</div>
206
- <div class="stat-label">Total Translations</div>
207
- </div>
208
- <div class="stat-box">
209
- <div class="stat-value" id="stat-errors">0</div>
210
- <div class="stat-label">API Errors</div>
211
- </div>
212
- <div class="stat-box">
213
- <div class="stat-value" id="stat-cost">$0.00</div>
214
- <div class="stat-label">Est. Cost</div>
215
- </div>
216
- </div>
217
- </div>
218
-
219
- <!-- MASTER KEYS -->
220
- <div class="card">
221
- <h2>🔑 Master API Configuration</h2>
222
- <p style="font-size: 13px; color: #71717a; margin-bottom: 20px;">
223
- These keys are stored securely on the server. App users will use these keys via the API without ever
224
- seeing them.
225
- <b>Required for App Store compliance.</b>
226
- </p>
227
-
228
- <div class="form-group">
229
- <label>OpenAI API Key (Universal)</label>
230
- <input type="password" id="key-openai" placeholder="sk-...">
231
- </div>
232
-
233
- <div class="form-group">
234
- <label>ElevenLabs API Key</label>
235
- <input type="password" id="key-eleven" placeholder="xi-...">
236
- </div>
237
-
238
- <div class="form-group">
239
- <label>Google Cloud API Key</label>
240
- <input type="password" id="key-google" placeholder="AIza...">
241
- </div>
242
-
243
- <!-- TTS ENGINE SELECTOR -->
244
- <div class="form-group" style="border-top: 1px solid #27272a; padding-top: 20px; margin-top: 20px;">
245
- <label style="margin-bottom: 12px;">🎤 TTS Engine Selection</label>
246
- <div
247
- style="display: flex; gap: 10px; align-items: center; background: #27272a; padding: 12px; border-radius: 8px;">
248
- <div style="flex: 1; text-align: center; padding: 10px; border-radius: 6px; cursor: pointer; transition: 0.3s; background: #6366f1; color: white;"
249
- id="tts-openai" onclick="selectTTS('openai')">
250
- <div style="font-weight: 600;">OpenAI TTS</div>
251
- <div style="font-size: 11px; opacity: 0.8; margin-top: 4px;">$0.015/1K · HD Quality</div>
252
- </div>
253
- <div style="flex: 1; text-align: center; padding: 10px; border-radius: 6px; cursor: pointer; transition: 0.3s; background: transparent; border: 1px solid #3f3f46; color: #a1a1aa;"
254
- id="tts-elevenlabs" onclick="selectTTS('elevenlabs')">
255
- <div style="font-weight: 600;">ElevenLabs</div>
256
- <div style="font-size: 11px; opacity: 0.8; margin-top: 4px">$0.30/1K · Ultra Premium</div>
257
- </div>
258
- </div>
259
- <div style="margin-top: 10px; font-size: 12px; color: #71717a; text-align: center;" id="tts-status">
260
- Current: <span style="color: #6366f1; font-weight: 600;">OpenAI TTS</span>
261
- </div>
262
- </div>
263
-
264
- <button class="btn" onclick="saveKeys()">Save Master Configuration</button>
265
- </div>
266
-
267
- <!-- SYSTEM CONTROL -->
268
- <div class="card">
269
- <h2>🛡️ System Controls</h2>
270
- <div class="form-group">
271
- <label style="display: flex; align-items: center; justify-content: space-between;">
272
- Example Mode (Free for users)
273
- <input type="checkbox" style="width: auto;" checked>
274
- </label>
275
- </div>
276
- <button class="btn btn-danger" onclick="clearLogs()">Clear Server Logs</button>
277
- </div>
278
- </div>
279
-
280
- <script>
281
- // Simple client-side gate (Real auth is server-side)
282
- function login() {
283
- const pass = document.getElementById('admin-pass').value;
284
- if (pass === 'admin123') { // Placeholder logic
285
- document.getElementById('login-screen').style.display = 'none';
286
- loadStats();
287
- } else {
288
- alert('Access Denied');
289
- }
290
- }
291
-
292
- function logout() {
293
- location.reload();
294
- }
295
-
296
- function loadStats() {
297
- // Simulator
298
- document.getElementById('stat-requests').innerText = Math.floor(Math.random() * 500) + 120;
299
- document.getElementById('stat-errors').innerText = '0';
300
-
301
- // Load saved TTS engine
302
- const savedEngine = localStorage.getItem('ttsEngine') || 'openai';
303
- selectTTS(savedEngine, false);
304
- }
305
-
306
- function selectTTS(engine, save = true) {
307
- const openaiBtn = document.getElementById('tts-openai');
308
- const elevenlabsBtn = document.getElementById('tts-elevenlabs');
309
- const status = document.getElementById('tts-status');
310
-
311
- if (engine === 'openai') {
312
- // OpenAI selected
313
- openaiBtn.style.background = '#6366f1';
314
- openaiBtn.style.color = 'white';
315
- openaiBtn.style.border = 'none';
316
-
317
- elevenlabsBtn.style.background = 'transparent';
318
- elevenlabsBtn.style.color = '#a1a1aa';
319
- elevenlabsBtn.style.border = '1px solid #3f3f46';
320
-
321
- status.innerHTML = 'Current: <span style="color: #6366f1; font-weight: 600;">OpenAI TTS</span>';
322
- } else {
323
- // ElevenLabs selected
324
- elevenlabsBtn.style.background = '#6366f1';
325
- elevenlabsBtn.style.color = 'white';
326
- elevenlabsBtn.style.border = 'none';
327
-
328
- openaiBtn.style.background = 'transparent';
329
- openaiBtn.style.color = '#a1a1aa';
330
- openaiBtn.style.border = '1px solid #3f3f46';
331
-
332
- status.innerHTML = 'Current: <span style="color: #6366f1; font-weight: 600;">ElevenLabs</span>';
333
- }
334
-
335
- // Save to localStorage
336
- if (save) {
337
- localStorage.setItem('ttsEngine', engine);
338
- console.log('TTS Engine switched to:', engine);
339
- }
340
- }
341
-
342
- function saveKeys() {
343
- const btn = document.querySelector('button[onclick="saveKeys()"]');
344
- btn.innerText = 'Saving...';
345
- setTimeout(() => {
346
- btn.innerText = '✅ Saved Securely';
347
- btn.style.background = '#22c55e';
348
- setTimeout(() => {
349
- btn.innerText = 'Save Master Configuration';
350
- btn.style.background = '#6366f1';
351
- }, 2000);
352
- }, 800);
353
- }
354
- </script>
355
- </body>
356
-
357
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/index_new.html DELETED
@@ -1,727 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="fr">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport"
7
- content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
8
-
9
- <!-- 🌍 PWA -->
10
- <meta name="theme-color" content="#22c55e">
11
- <meta name="apple-mobile-web-app-capable" content="yes">
12
- <meta name="apple-mobile-web-app-status-bar-style" content="default">
13
- <meta name="application-name" content="Babel">
14
- <meta name="apple-mobile-web-app-title" content="Babel">
15
- <link rel="manifest" href="/static/manifest.json">
16
-
17
- <title>Babel - Traducteur Vocal</title>
18
- <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&display=swap" rel="stylesheet">
19
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
20
-
21
- <style>
22
- :root {
23
- /* 🌿 FRESH GREEN PALETTE (Cloned from reference) */
24
- --bg-gradient: linear-gradient(180deg, #4ade80 0%, #22c55e 50%, #16a34a 100%);
25
- --card-bg: #ffffff;
26
- --card-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
27
- --text-dark: #1a1a2e;
28
- --text-medium: #4a5568;
29
- --text-light: #718096;
30
-
31
- /* Action Colors */
32
- --accent-green: #22c55e;
33
- --accent-yellow: #fbbf24;
34
- --accent-purple: #a855f7;
35
- --accent-teal: #14b8a6;
36
-
37
- /* Bottom Bar */
38
- --bar-bg: #1a1a2e;
39
- --bar-active: #fbbf24;
40
- }
41
-
42
- * {
43
- margin: 0;
44
- padding: 0;
45
- box-sizing: border-box;
46
- -webkit-tap-highlight-color: transparent;
47
- }
48
-
49
- body {
50
- font-family: 'Nunito', -apple-system, BlinkMacSystemFont, sans-serif;
51
- background: var(--bg-gradient);
52
- min-height: 100vh;
53
- min-height: 100dvh;
54
- color: var(--text-dark);
55
- overflow-x: hidden;
56
- padding-bottom: 100px;
57
- }
58
-
59
- /* 🔝 TOP HEADER */
60
- .top-header {
61
- padding: 16px 20px;
62
- padding-top: max(16px, env(safe-area-inset-top));
63
- display: flex;
64
- justify-content: space-between;
65
- align-items: center;
66
- }
67
-
68
- .greeting {
69
- color: white;
70
- font-size: 1.1rem;
71
- font-weight: 700;
72
- }
73
-
74
- .settings-icon {
75
- width: 44px;
76
- height: 44px;
77
- background: rgba(255, 255, 255, 0.2);
78
- border-radius: 12px;
79
- display: flex;
80
- align-items: center;
81
- justify-content: center;
82
- color: white;
83
- font-size: 1.2rem;
84
- cursor: pointer;
85
- transition: all 0.2s;
86
- }
87
-
88
- .settings-icon:hover {
89
- background: rgba(255, 255, 255, 0.3);
90
- transform: scale(1.05);
91
- }
92
-
93
- /* 📦 MAIN CARD */
94
- .main-card {
95
- background: var(--card-bg);
96
- margin: 0 16px;
97
- border-radius: 24px;
98
- padding: 24px;
99
- box-shadow: var(--card-shadow);
100
- }
101
-
102
- .card-label {
103
- font-size: 0.85rem;
104
- color: var(--text-light);
105
- font-weight: 600;
106
- margin-bottom: 8px;
107
- }
108
-
109
- .main-value {
110
- font-size: 2.5rem;
111
- font-weight: 900;
112
- color: var(--text-dark);
113
- margin-bottom: 16px;
114
- }
115
-
116
- .main-value span {
117
- font-size: 1.5rem;
118
- color: var(--text-medium);
119
- }
120
-
121
- /* Language Display */
122
- .language-display {
123
- background: #f1f5f9;
124
- border-radius: 12px;
125
- padding: 12px 16px;
126
- font-family: 'JetBrains Mono', monospace;
127
- font-size: 0.9rem;
128
- color: var(--text-medium);
129
- margin-bottom: 20px;
130
- text-align: center;
131
- overflow: hidden;
132
- text-overflow: ellipsis;
133
- white-space: nowrap;
134
- }
135
-
136
- /* 🔘 ACTION BUTTONS ROW */
137
- .action-row {
138
- display: flex;
139
- justify-content: center;
140
- gap: 16px;
141
- margin-top: 8px;
142
- }
143
-
144
- .action-btn {
145
- display: flex;
146
- flex-direction: column;
147
- align-items: center;
148
- gap: 8px;
149
- cursor: pointer;
150
- transition: all 0.2s;
151
- }
152
-
153
- .action-btn:hover {
154
- transform: translateY(-2px);
155
- }
156
-
157
- .action-icon {
158
- width: 56px;
159
- height: 56px;
160
- border-radius: 16px;
161
- display: flex;
162
- align-items: center;
163
- justify-content: center;
164
- font-size: 1.4rem;
165
- color: white;
166
- }
167
-
168
- .action-icon.green {
169
- background: var(--accent-green);
170
- }
171
-
172
- .action-icon.yellow {
173
- background: var(--accent-yellow);
174
- color: #1a1a2e;
175
- }
176
-
177
- .action-icon.purple {
178
- background: var(--accent-purple);
179
- }
180
-
181
- .action-icon.teal {
182
- background: var(--accent-teal);
183
- }
184
-
185
- .action-label {
186
- font-size: 0.8rem;
187
- font-weight: 700;
188
- color: var(--text-dark);
189
- }
190
-
191
- /* 📊 PROGRESS SECTION */
192
- .progress-card {
193
- background: var(--bar-bg);
194
- margin: 16px;
195
- border-radius: 20px;
196
- padding: 16px 20px;
197
- display: flex;
198
- align-items: center;
199
- gap: 16px;
200
- }
201
-
202
- .progress-icon {
203
- width: 44px;
204
- height: 44px;
205
- background: var(--accent-yellow);
206
- border-radius: 12px;
207
- display: flex;
208
- align-items: center;
209
- justify-content: center;
210
- font-size: 1.2rem;
211
- }
212
-
213
- .progress-info {
214
- flex: 1;
215
- }
216
-
217
- .progress-title {
218
- color: white;
219
- font-weight: 800;
220
- font-size: 1.1rem;
221
- }
222
-
223
- .progress-sub {
224
- color: rgba(255, 255, 255, 0.6);
225
- font-size: 0.85rem;
226
- }
227
-
228
- /* 📜 HISTORY CARD */
229
- .history-card {
230
- background: var(--card-bg);
231
- margin: 16px;
232
- border-radius: 24px;
233
- padding: 20px;
234
- box-shadow: var(--card-shadow);
235
- }
236
-
237
- .history-header {
238
- display: flex;
239
- justify-content: space-between;
240
- align-items: center;
241
- margin-bottom: 16px;
242
- }
243
-
244
- .history-title {
245
- font-size: 1.1rem;
246
- font-weight: 800;
247
- color: var(--text-dark);
248
- }
249
-
250
- .show-all {
251
- font-size: 0.85rem;
252
- color: var(--text-medium);
253
- cursor: pointer;
254
- }
255
-
256
- .history-item {
257
- display: flex;
258
- align-items: center;
259
- gap: 12px;
260
- padding: 12px 0;
261
- border-bottom: 1px solid #f1f5f9;
262
- }
263
-
264
- .history-item:last-child {
265
- border-bottom: none;
266
- }
267
-
268
- .history-icon {
269
- width: 40px;
270
- height: 40px;
271
- border-radius: 12px;
272
- display: flex;
273
- align-items: center;
274
- justify-content: center;
275
- font-size: 1rem;
276
- }
277
-
278
- .history-icon.received {
279
- background: #dcfce7;
280
- color: var(--accent-green);
281
- }
282
-
283
- .history-icon.sent {
284
- background: #fef3c7;
285
- color: var(--accent-yellow);
286
- }
287
-
288
- .history-details {
289
- flex: 1;
290
- }
291
-
292
- .history-type {
293
- font-weight: 700;
294
- font-size: 0.95rem;
295
- color: var(--text-dark);
296
- }
297
-
298
- .history-amount {
299
- font-size: 0.85rem;
300
- color: var(--text-medium);
301
- }
302
-
303
- .history-time {
304
- font-size: 0.8rem;
305
- color: var(--text-light);
306
- }
307
-
308
- /* 📱 BOTTOM NAV BAR */
309
- .bottom-bar {
310
- position: fixed;
311
- bottom: 0;
312
- left: 0;
313
- right: 0;
314
- padding: 12px 24px;
315
- padding-bottom: max(12px, env(safe-area-inset-bottom));
316
- display: flex;
317
- justify-content: center;
318
- }
319
-
320
- .nav-pill {
321
- background: var(--bar-bg);
322
- border-radius: 100px;
323
- padding: 12px 24px;
324
- display: flex;
325
- align-items: center;
326
- gap: 8px;
327
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
328
- }
329
-
330
- .nav-item {
331
- width: 48px;
332
- height: 48px;
333
- border-radius: 50%;
334
- display: flex;
335
- align-items: center;
336
- justify-content: center;
337
- color: rgba(255, 255, 255, 0.5);
338
- font-size: 1.2rem;
339
- cursor: pointer;
340
- transition: all 0.2s;
341
- }
342
-
343
- .nav-item:hover {
344
- color: white;
345
- }
346
-
347
- .nav-item.active {
348
- background: var(--accent-yellow);
349
- color: var(--bar-bg);
350
- }
351
-
352
- /* 🎤 RECORD BUTTON (Special) */
353
- .record-orb {
354
- width: 64px;
355
- height: 64px;
356
- background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
357
- border-radius: 50%;
358
- display: flex;
359
- align-items: center;
360
- justify-content: center;
361
- color: white;
362
- font-size: 1.5rem;
363
- cursor: pointer;
364
- transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
365
- margin: -20px 8px 0 8px;
366
- box-shadow: 0 8px 24px rgba(34, 197, 94, 0.4);
367
- border: 4px solid var(--bar-bg);
368
- }
369
-
370
- .record-orb:hover {
371
- transform: scale(1.1);
372
- }
373
-
374
- .record-orb:active {
375
- transform: scale(0.95);
376
- }
377
-
378
- .record-orb.active {
379
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
380
- box-shadow: 0 8px 24px rgba(239, 68, 68, 0.4);
381
- animation: pulse-record 1.5s infinite;
382
- }
383
-
384
- @keyframes pulse-record {
385
-
386
- 0%,
387
- 100% {
388
- box-shadow: 0 8px 24px rgba(239, 68, 68, 0.4);
389
- }
390
-
391
- 50% {
392
- box-shadow: 0 8px 40px rgba(239, 68, 68, 0.6);
393
- }
394
- }
395
-
396
- /* 📝 CHAT MESSAGES */
397
- .chat-container {
398
- padding: 0 16px;
399
- display: flex;
400
- flex-direction: column;
401
- gap: 12px;
402
- }
403
-
404
- .chat-message {
405
- max-width: 85%;
406
- padding: 16px;
407
- border-radius: 20px;
408
- animation: slideIn 0.3s ease;
409
- }
410
-
411
- @keyframes slideIn {
412
- from {
413
- opacity: 0;
414
- transform: translateY(10px);
415
- }
416
-
417
- to {
418
- opacity: 1;
419
- transform: translateY(0);
420
- }
421
- }
422
-
423
- .chat-message.user {
424
- background: var(--card-bg);
425
- align-self: flex-start;
426
- border-bottom-left-radius: 4px;
427
- box-shadow: var(--card-shadow);
428
- }
429
-
430
- .chat-message.bot {
431
- background: var(--bar-bg);
432
- color: white;
433
- align-self: flex-end;
434
- border-bottom-right-radius: 4px;
435
- }
436
-
437
- .message-text {
438
- font-size: 1rem;
439
- line-height: 1.5;
440
- }
441
-
442
- .message-meta {
443
- font-size: 0.75rem;
444
- opacity: 0.6;
445
- margin-top: 8px;
446
- }
447
-
448
- /* 🔘 LANGUAGE SELECTOR */
449
- .lang-select {
450
- background: #f1f5f9;
451
- border: none;
452
- border-radius: 12px;
453
- padding: 12px 16px;
454
- font-family: 'Nunito', sans-serif;
455
- font-size: 0.9rem;
456
- font-weight: 600;
457
- color: var(--text-dark);
458
- cursor: pointer;
459
- width: 100%;
460
- appearance: none;
461
- }
462
-
463
- /* Empty State */
464
- .empty-state {
465
- text-align: center;
466
- padding: 40px 20px;
467
- }
468
-
469
- .empty-icon {
470
- font-size: 4rem;
471
- margin-bottom: 16px;
472
- }
473
-
474
- .empty-title {
475
- font-size: 1.5rem;
476
- font-weight: 900;
477
- color: white;
478
- margin-bottom: 8px;
479
- }
480
-
481
- .empty-sub {
482
- color: rgba(255, 255, 255, 0.8);
483
- font-size: 1rem;
484
- }
485
-
486
- /* MODAL */
487
- .modal {
488
- position: fixed;
489
- inset: 0;
490
- background: rgba(0, 0, 0, 0.5);
491
- display: none;
492
- align-items: flex-end;
493
- justify-content: center;
494
- z-index: 2000;
495
- backdrop-filter: blur(4px);
496
- }
497
-
498
- .modal.visible {
499
- display: flex;
500
- }
501
-
502
- .modal-content {
503
- background: var(--card-bg);
504
- width: 100%;
505
- max-width: 500px;
506
- border-radius: 24px 24px 0 0;
507
- padding: 24px;
508
- max-height: 80vh;
509
- overflow-y: auto;
510
- animation: slideUp 0.3s ease;
511
- }
512
-
513
- @keyframes slideUp {
514
- from {
515
- transform: translateY(100%);
516
- }
517
-
518
- to {
519
- transform: translateY(0);
520
- }
521
- }
522
-
523
- .modal-handle {
524
- width: 40px;
525
- height: 4px;
526
- background: #e2e8f0;
527
- border-radius: 2px;
528
- margin: 0 auto 20px;
529
- }
530
-
531
- .modal-title {
532
- font-size: 1.3rem;
533
- font-weight: 800;
534
- margin-bottom: 20px;
535
- }
536
-
537
- .form-group {
538
- margin-bottom: 16px;
539
- }
540
-
541
- .form-group label {
542
- display: block;
543
- font-size: 0.85rem;
544
- font-weight: 700;
545
- color: var(--text-medium);
546
- margin-bottom: 8px;
547
- }
548
-
549
- .save-btn {
550
- width: 100%;
551
- padding: 16px;
552
- background: var(--accent-green);
553
- color: white;
554
- border: none;
555
- border-radius: 16px;
556
- font-family: 'Nunito', sans-serif;
557
- font-size: 1rem;
558
- font-weight: 800;
559
- cursor: pointer;
560
- margin-top: 16px;
561
- transition: all 0.2s;
562
- }
563
-
564
- .save-btn:hover {
565
- background: #16a34a;
566
- transform: translateY(-2px);
567
- }
568
- </style>
569
- </head>
570
-
571
- <body>
572
- <!-- TOP HEADER -->
573
- <div class="top-header">
574
- <div class="greeting">👋 Bonjour!</div>
575
- <div class="settings-icon" id="settings-trigger">
576
- <i class="fa-solid fa-gear"></i>
577
- </div>
578
- </div>
579
-
580
- <!-- MAIN CARD -->
581
- <div class="main-card">
582
- <div class="card-label">Traduction Instantanée</div>
583
- <div class="main-value">Babel <span>Matrix</span></div>
584
-
585
- <div class="language-display" id="current-lang">
586
- 🌍 Appuyez sur le micro pour parler...
587
- </div>
588
-
589
- <!-- Language Selector -->
590
- <select class="lang-select" id="target-lang-quick">
591
- <option value="French">🇫🇷 Français</option>
592
- <option value="English">🇬🇧 English</option>
593
- <option value="Moroccan Darija">🇲🇦 Darija</option>
594
- <option value="Egyptian Arabic">🇪🇬 Masri</option>
595
- <option value="Spanish">🇪🇸 Español</option>
596
- <option value="German">🇩🇪 Deutsch</option>
597
- <option value="Arabic">🇸🇦 العربية</option>
598
- </select>
599
-
600
- <!-- Action Buttons -->
601
- <div class="action-row">
602
- <div class="action-btn" id="listen-btn">
603
- <div class="action-icon green">
604
- <i class="fa-solid fa-play"></i>
605
- </div>
606
- <span class="action-label">Écouter</span>
607
- </div>
608
- <div class="action-btn" id="copy-btn">
609
- <div class="action-icon yellow">
610
- <i class="fa-solid fa-copy"></i>
611
- </div>
612
- <span class="action-label">Copier</span>
613
- </div>
614
- <div class="action-btn" id="share-btn">
615
- <div class="action-icon purple">
616
- <i class="fa-solid fa-share"></i>
617
- </div>
618
- <span class="action-label">Partager</span>
619
- </div>
620
- </div>
621
- </div>
622
-
623
- <!-- STATUS BAR -->
624
- <div class="progress-card">
625
- <div class="progress-icon">🎙️</div>
626
- <div class="progress-info">
627
- <div class="progress-title" id="status-text">Prêt</div>
628
- <div class="progress-sub">Mode continu activé</div>
629
- </div>
630
- </div>
631
-
632
- <!-- CHAT HISTORY -->
633
- <div class="history-card">
634
- <div class="history-header">
635
- <span class="history-title">Historique</span>
636
- <span class="show-all">Tout voir →</span>
637
- </div>
638
- <div id="chat-history">
639
- <!-- Messages will appear here -->
640
- <div class="empty-state" id="empty-hint" style="padding: 20px;">
641
- <div style="font-size: 2rem;">💬</div>
642
- <p style="color: var(--text-light); font-size: 0.9rem;">Vos traductions apparaîtront ici</p>
643
- </div>
644
- </div>
645
- </div>
646
-
647
- <!-- BOTTOM NAV BAR -->
648
- <div class="bottom-bar">
649
- <div class="nav-pill">
650
- <div class="nav-item">
651
- <i class="fa-solid fa-clock-rotate-left"></i>
652
- </div>
653
- <div class="nav-item active">
654
- <i class="fa-solid fa-language"></i>
655
- </div>
656
-
657
- <!-- THE RECORD ORB -->
658
- <div class="record-orb" id="record-btn">
659
- <i class="fa-solid fa-microphone"></i>
660
- </div>
661
-
662
- <div class="nav-item">
663
- <i class="fa-solid fa-brain"></i>
664
- </div>
665
- <div class="nav-item">
666
- <i class="fa-solid fa-user"></i>
667
- </div>
668
- </div>
669
- </div>
670
-
671
- <!-- SETTINGS MODAL -->
672
- <div class="modal" id="settings-modal">
673
- <div class="modal-content">
674
- <div class="modal-handle"></div>
675
- <h2 class="modal-title">⚙️ Paramètres</h2>
676
-
677
- <div class="form-group">
678
- <label>Mode d'Intelligence</label>
679
- <select class="lang-select" id="ai-model-selector">
680
- <option value="gpt-4o-mini">🟢 OpenAI GPT-4o (Stable)</option>
681
- <option value="gemini-flash-latest">⚡ Gemini Flash</option>
682
- </select>
683
- </div>
684
-
685
- <div class="form-group">
686
- <label>Moteur Vocal</label>
687
- <select class="lang-select" id="tts-selector">
688
- <option value="openai">🟢 OpenAI TTS</option>
689
- <option value="gemini">💎 Gemini TTS</option>
690
- </select>
691
- </div>
692
-
693
- <button class="save-btn" id="save-settings">Enregistrer</button>
694
- </div>
695
- </div>
696
-
697
- <!-- SCRIPTS -->
698
- <script src="/static/script.js"></script>
699
- <script>
700
- // Settings Modal Logic
701
- document.getElementById('settings-trigger').addEventListener('click', () => {
702
- document.getElementById('settings-modal').classList.add('visible');
703
- });
704
-
705
- document.getElementById('settings-modal').addEventListener('click', (e) => {
706
- if (e.target === document.getElementById('settings-modal')) {
707
- document.getElementById('settings-modal').classList.remove('visible');
708
- }
709
- });
710
-
711
- document.getElementById('save-settings').addEventListener('click', () => {
712
- const aiModel = document.getElementById('ai-model-selector').value;
713
- const ttsEngine = document.getElementById('tts-selector').value;
714
- localStorage.setItem('aiModel', aiModel);
715
- localStorage.setItem('ttsEngine', ttsEngine);
716
- document.getElementById('settings-modal').classList.remove('visible');
717
- document.getElementById('status-text').innerText = '✅ Sauvegardé!';
718
- setTimeout(() => document.getElementById('status-text').innerText = 'Prêt', 2000);
719
- });
720
-
721
- // Load saved values
722
- document.getElementById('ai-model-selector').value = localStorage.getItem('aiModel') || 'gpt-4o-mini';
723
- document.getElementById('tts-selector').value = localStorage.getItem('ttsEngine') || 'openai';
724
- </script>
725
- </body>
726
-
727
- </html>