kuro223 commited on
Commit
fd036de
·
1 Parent(s): 89db25a
Files changed (5) hide show
  1. .env +3 -0
  2. AGENTS.md +38 -0
  3. README.md +60 -0
  4. __pycache__/app.cpython-311.pyc +0 -0
  5. app.py +110 -32
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Configuration pour Chatm2
2
+ # Obtenez votre clé API Google Gemini sur https://aistudio.google.com/app/apikey
3
+ GOOGLE_API_KEY=votre_cle_api_ici
AGENTS.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AGENTS.md - Chatm2 Project Guidelines
2
+
3
+ ## Build/Lint/Test Commands
4
+ - **Run app**: `python app.py` or `flask run --host=0.0.0.0 --port=7860`
5
+ - **Install dependencies**: `pip install -r requirements.txt`
6
+ - **Docker build**: `docker build -t chatm2 .`
7
+ - **Docker run**: `docker run -p 7860:7860 chatm2`
8
+
9
+ ## Code Style Guidelines
10
+
11
+ ### Python Standards
12
+ - **Imports**: Standard library first, then third-party, then local imports. One import per line.
13
+ - **Naming**: snake_case for functions/variables, PascalCase for classes, UPPER_CASE for constants
14
+ - **Types**: Use type hints for function parameters and return values when possible
15
+ - **Error Handling**: Use try/except blocks with specific exceptions, log errors appropriately
16
+ - **Docstrings**: Use triple quotes for function/class documentation in French (matching existing code)
17
+
18
+ ### Flask/Web Standards
19
+ - **Routes**: Use descriptive route names, handle both GET/POST appropriately
20
+ - **JSON Responses**: Use `jsonify()` for API responses, include error handling
21
+ - **File Uploads**: Validate file types and sizes, use secure_filename()
22
+ - **Templates**: Use Jinja2 templating with Tailwind CSS classes
23
+
24
+ ### Frontend/JavaScript
25
+ - **Styling**: Use Tailwind CSS utility classes, maintain dark theme consistency
26
+ - **Markdown**: Use marked.js for rendering, apply consistent styling
27
+ - **Async**: Use modern async/await patterns for API calls
28
+
29
+ ### Security
30
+ - **Environment Variables**: Store sensitive data in .env files, never commit secrets
31
+ - **File Handling**: Validate uploads, limit file sizes (currently 16MB max)
32
+ - **API Keys**: Load from environment variables only
33
+
34
+ ### Project Structure
35
+ - Keep documentation in `documentation_gemini/` directory
36
+ - Store templates in `templates/` directory
37
+ - Use `SESSION_FILE_DIR/` for temporary file storage
38
+ - Place system instructions in `instructions/` directory
README.md CHANGED
@@ -1,3 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Chatm
3
  emoji: 📚
 
1
+ # Chatm2 - AI Assistant
2
+
3
+ Assistant IA intelligent utilisant Google Gemini API avec interface web moderne.
4
+
5
+ ## Installation
6
+
7
+ 1. **Cloner le repository**
8
+ ```bash
9
+ git clone <repository-url>
10
+ cd chatm2
11
+ ```
12
+
13
+ 2. **Installer les dépendances**
14
+ ```bash
15
+ pip install -r requirements.txt
16
+ ```
17
+
18
+ 3. **Configuration de l'API**
19
+ - Obtenez une clé API Google Gemini sur [Google AI Studio](https://aistudio.google.com/app/apikey)
20
+ - Créez un fichier `.env` dans le répertoire racine :
21
+ ```
22
+ GOOGLE_API_KEY=votre_cle_api_ici
23
+ ```
24
+
25
+ ## Utilisation
26
+
27
+ ### Démarrage local
28
+ ```bash
29
+ python app.py
30
+ ```
31
+ L'application sera accessible sur http://localhost:5000
32
+
33
+ ### Avec Docker
34
+ ```bash
35
+ docker build -t chatm2 .
36
+ docker run -p 7860:7860 -e GOOGLE_API_KEY=votre_cle_api chatm2
37
+ ```
38
+
39
+ ## Fonctionnalités
40
+
41
+ - Chat en temps réel avec streaming
42
+ - Support des fichiers (images, documents)
43
+ - Mode réflexion (thinking) activable
44
+ - Historique des conversations
45
+ - Interface d'administration
46
+
47
+ ## Debug
48
+
49
+ - Endpoint de test API : `/debug/api_test`
50
+ - Page admin : `/admin1`
51
+ - Statistiques : `/admin/stats`
52
+
53
+ ## Dépannage
54
+
55
+ Si vous ne recevez pas de réponses :
56
+ 1. Vérifiez que `GOOGLE_API_KEY` est définie
57
+ 2. Testez l'endpoint `/debug/api_test`
58
+ 3. Vérifiez les logs de l'application
59
+ 4. Assurez-vous que le modèle `gemini-2.5-flash` est accessible
60
+
61
  ---
62
  title: Chatm
63
  emoji: 📚
__pycache__/app.cpython-311.pyc ADDED
Binary file (25.5 kB). View file
 
app.py CHANGED
@@ -29,7 +29,17 @@ def load_system_instruction():
29
  API_KEY = os.getenv("GOOGLE_API_KEY")
30
  SYSTEM_INSTRUCTION = load_system_instruction()
31
 
32
- client = genai.Client(api_key=API_KEY)
 
 
 
 
 
 
 
 
 
 
33
 
34
  # Configuration par défaut
35
  MODEL = "gemini-2.5-flash"
@@ -43,8 +53,7 @@ DEFAULT_CONFIG = {
43
  # Outils activés par défaut
44
  DEFAULT_TOOLS = [
45
  types.Tool(code_execution=types.ToolCodeExecution()),
46
- types.Tool(google_search=types.GoogleSearch()),
47
- types.Tool(url_context=types.UrlContext())
48
  ]
49
 
50
  # Stockage des conversations avec métadonnées (en production, utilisez une base de données)
@@ -122,25 +131,30 @@ def get_conversations():
122
  @app.route('/chat', methods=['POST'])
123
  def chat():
124
  try:
 
 
 
125
  data = request.get_json()
126
  message = data.get('message', '')
127
  thinking_enabled = data.get('thinking_enabled', True)
128
  conversation_id = data.get('conversation_id', 'default')
129
-
 
 
130
  # Ajouter le message de l'utilisateur à l'historique
131
  add_message_to_history(conversation_id, 'user', message)
132
-
133
  # Configuration du thinking
134
  config_dict = DEFAULT_CONFIG.copy()
135
  config_dict["system_instruction"] = SYSTEM_INSTRUCTION
136
  config_dict["tools"] = DEFAULT_TOOLS
137
 
138
- # Thinking not supported for chat API
139
- # if thinking_enabled:
140
- # config_dict["thinking_config"] = types.ThinkingConfig(
141
- # thinking_budget=-1, # Dynamic thinking
142
- # include_thoughts=True
143
- # )
144
  generation_config = types.GenerateContentConfig(**config_dict)
145
 
146
  # Gestion de la conversation
@@ -155,15 +169,23 @@ def chat():
155
  # Génération de la réponse avec streaming
156
  def generate():
157
  try:
 
 
 
 
 
158
  response_stream = chat.send_message_stream(
159
  message,
160
  config=generation_config
161
  )
162
-
163
  full_response = ""
164
  thoughts = ""
 
165
 
166
  for chunk in response_stream:
 
 
167
  for part in chunk.candidates[0].content.parts:
168
  if part.text:
169
  if part.thought and thinking_enabled:
@@ -172,15 +194,18 @@ def chat():
172
  full_response += part.text
173
  yield f"data: {json.dumps({'type': 'text', 'content': part.text})}\n\n"
174
 
 
 
175
  # Ajouter la réponse de l'assistant à l'historique
176
  if full_response:
177
  add_message_to_history(conversation_id, 'assistant', full_response)
178
-
179
  # Signal de fin
180
  yield f"data: {json.dumps({'type': 'end'})}\n\n"
181
-
182
  except Exception as e:
183
- yield f"data: {json.dumps({'type': 'error', 'content': str(e)})}\n\n"
 
184
 
185
  return Response(generate(), mimetype='text/plain')
186
 
@@ -216,12 +241,17 @@ def upload_file():
216
  @app.route('/chat_with_file', methods=['POST'])
217
  def chat_with_file():
218
  try:
 
 
 
219
  data = request.get_json()
220
  message = data.get('message', '')
221
  file_data_list = data.get('file_data', [])
222
  thinking_enabled = data.get('thinking_enabled', True)
223
  conversation_id = data.get('conversation_id', 'default')
224
 
 
 
225
  # Ensure file_data_list is a list
226
  if not isinstance(file_data_list, list):
227
  file_data_list = [file_data_list]
@@ -232,18 +262,18 @@ def chat_with_file():
232
  file_count = len(file_data_list)
233
  display_message += f" [{file_count} fichier{'s' if file_count > 1 else ''}]"
234
  add_message_to_history(conversation_id, 'user', display_message, has_file=len(file_data_list) > 0, file_data=file_data_list)
235
-
236
  # Configuration du thinking
237
  config_dict = DEFAULT_CONFIG.copy()
238
  config_dict["tools"] = DEFAULT_TOOLS
239
  config_dict["system_instruction"] = SYSTEM_INSTRUCTION
240
 
241
- # Thinking not supported for chat API
242
- # if thinking_enabled:
243
- # config_dict["thinking_config"] = types.ThinkingConfig(
244
- # thinking_budget=-1,
245
- # include_thoughts=True
246
- # )
247
  generation_config = types.GenerateContentConfig(**config_dict)
248
 
249
  # Gestion de la conversation
@@ -269,15 +299,23 @@ def chat_with_file():
269
  # Génération de la réponse avec streaming
270
  def generate():
271
  try:
 
 
 
 
 
272
  response_stream = chat.send_message_stream(
273
  contents,
274
  config=generation_config
275
  )
276
-
277
  full_response = ""
278
  thoughts = ""
 
279
 
280
  for chunk in response_stream:
 
 
281
  for part in chunk.candidates[0].content.parts:
282
  if part.text:
283
  if part.thought and thinking_enabled:
@@ -286,14 +324,18 @@ def chat_with_file():
286
  full_response += part.text
287
  yield f"data: {json.dumps({'type': 'text', 'content': part.text})}\n\n"
288
 
 
 
289
  # Ajouter la réponse de l'assistant à l'historique
290
  if full_response:
291
  add_message_to_history(conversation_id, 'assistant', full_response)
292
-
 
293
  yield f"data: {json.dumps({'type': 'end'})}\n\n"
294
-
295
  except Exception as e:
296
- yield f"data: {json.dumps({'type': 'error', 'content': str(e)})}\n\n"
 
297
 
298
  return Response(generate(), mimetype='text/plain')
299
 
@@ -357,21 +399,21 @@ def get_admin_stats():
357
  # Statistiques générales
358
  total_conversations = len(conversation_metadata)
359
  total_messages = sum(len(conv['messages']) for conv in conversation_metadata.values())
360
-
361
  # Statistiques par statut
362
  status_stats = {}
363
  for conv in conversation_metadata.values():
364
  status = conv.get('status', 'active')
365
  status_stats[status] = status_stats.get(status, 0) + 1
366
-
367
  # Conversations avec fichiers
368
- conversations_with_files = sum(1 for conv in conversation_metadata.values()
369
  if any(msg.get('hasFile') for msg in conv['messages']))
370
-
371
  # Activité par jour (derniers 7 jours)
372
  from collections import defaultdict
373
  daily_activity = defaultdict(int)
374
-
375
  for conv in conversation_metadata.values():
376
  for message in conv['messages']:
377
  if message.get('timestamp'):
@@ -380,7 +422,7 @@ def get_admin_stats():
380
  daily_activity[date.isoformat()] += 1
381
  except:
382
  continue
383
-
384
  return jsonify({
385
  'total_conversations': total_conversations,
386
  'total_messages': total_messages,
@@ -391,5 +433,41 @@ def get_admin_stats():
391
  except Exception as e:
392
  return jsonify({'error': str(e)}), 500
393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  if __name__ == '__main__':
395
  app.run(debug=True, host='0.0.0.0', port=5000)
 
29
  API_KEY = os.getenv("GOOGLE_API_KEY")
30
  SYSTEM_INSTRUCTION = load_system_instruction()
31
 
32
+ if not API_KEY:
33
+ print("ATTENTION: GOOGLE_API_KEY non définie dans les variables d'environnement")
34
+ print("L'application démarrera mais les fonctionnalités de chat seront limitées")
35
+ client = None
36
+ else:
37
+ try:
38
+ client = genai.Client(api_key=API_KEY)
39
+ print("Client Gemini initialisé avec succès")
40
+ except Exception as e:
41
+ print(f"ERREUR lors de l'initialisation du client Gemini: {e}")
42
+ client = None
43
 
44
  # Configuration par défaut
45
  MODEL = "gemini-2.5-flash"
 
53
  # Outils activés par défaut
54
  DEFAULT_TOOLS = [
55
  types.Tool(code_execution=types.ToolCodeExecution()),
56
+ types.Tool(google_search=types.GoogleSearch())
 
57
  ]
58
 
59
  # Stockage des conversations avec métadonnées (en production, utilisez une base de données)
 
131
  @app.route('/chat', methods=['POST'])
132
  def chat():
133
  try:
134
+ if not client:
135
+ return jsonify({'error': 'Client Gemini non initialisé. Vérifiez GOOGLE_API_KEY.'}), 500
136
+
137
  data = request.get_json()
138
  message = data.get('message', '')
139
  thinking_enabled = data.get('thinking_enabled', True)
140
  conversation_id = data.get('conversation_id', 'default')
141
+
142
+ print(f"Requête chat reçue: message='{message[:50]}...', conversation_id={conversation_id}")
143
+
144
  # Ajouter le message de l'utilisateur à l'historique
145
  add_message_to_history(conversation_id, 'user', message)
146
+
147
  # Configuration du thinking
148
  config_dict = DEFAULT_CONFIG.copy()
149
  config_dict["system_instruction"] = SYSTEM_INSTRUCTION
150
  config_dict["tools"] = DEFAULT_TOOLS
151
 
152
+ # Activer thinking si demandé
153
+ if thinking_enabled:
154
+ config_dict["thinking_config"] = types.ThinkingConfig(
155
+ thinking_budget=-1, # Dynamic thinking
156
+ include_thoughts=True
157
+ )
158
  generation_config = types.GenerateContentConfig(**config_dict)
159
 
160
  # Gestion de la conversation
 
169
  # Génération de la réponse avec streaming
170
  def generate():
171
  try:
172
+ if not client:
173
+ yield f"data: {json.dumps({'type': 'error', 'content': 'API Gemini non configurée. Définissez GOOGLE_API_KEY.'})}\n\n"
174
+ return
175
+
176
+ print(f"Démarrage du streaming pour conversation {conversation_id}")
177
  response_stream = chat.send_message_stream(
178
  message,
179
  config=generation_config
180
  )
181
+
182
  full_response = ""
183
  thoughts = ""
184
+ chunk_count = 0
185
 
186
  for chunk in response_stream:
187
+ chunk_count += 1
188
+ print(f"Chunk {chunk_count} reçu")
189
  for part in chunk.candidates[0].content.parts:
190
  if part.text:
191
  if part.thought and thinking_enabled:
 
194
  full_response += part.text
195
  yield f"data: {json.dumps({'type': 'text', 'content': part.text})}\n\n"
196
 
197
+ print(f"Streaming terminé, réponse complète: {len(full_response)} caractères")
198
+
199
  # Ajouter la réponse de l'assistant à l'historique
200
  if full_response:
201
  add_message_to_history(conversation_id, 'assistant', full_response)
202
+
203
  # Signal de fin
204
  yield f"data: {json.dumps({'type': 'end'})}\n\n"
205
+
206
  except Exception as e:
207
+ print(f"ERREUR lors du streaming: {e}")
208
+ yield f"data: {json.dumps({'type': 'error', 'content': f'Erreur API: {str(e)}'})}\n\n"
209
 
210
  return Response(generate(), mimetype='text/plain')
211
 
 
241
  @app.route('/chat_with_file', methods=['POST'])
242
  def chat_with_file():
243
  try:
244
+ if not client:
245
+ return jsonify({'error': 'Client Gemini non initialisé. Vérifiez GOOGLE_API_KEY.'}), 500
246
+
247
  data = request.get_json()
248
  message = data.get('message', '')
249
  file_data_list = data.get('file_data', [])
250
  thinking_enabled = data.get('thinking_enabled', True)
251
  conversation_id = data.get('conversation_id', 'default')
252
 
253
+ print(f"Requête chat_with_file reçue: message='{message[:50]}...', fichiers={len(file_data_list)}, conversation_id={conversation_id}")
254
+
255
  # Ensure file_data_list is a list
256
  if not isinstance(file_data_list, list):
257
  file_data_list = [file_data_list]
 
262
  file_count = len(file_data_list)
263
  display_message += f" [{file_count} fichier{'s' if file_count > 1 else ''}]"
264
  add_message_to_history(conversation_id, 'user', display_message, has_file=len(file_data_list) > 0, file_data=file_data_list)
265
+
266
  # Configuration du thinking
267
  config_dict = DEFAULT_CONFIG.copy()
268
  config_dict["tools"] = DEFAULT_TOOLS
269
  config_dict["system_instruction"] = SYSTEM_INSTRUCTION
270
 
271
+ # Activer thinking si demandé
272
+ if thinking_enabled:
273
+ config_dict["thinking_config"] = types.ThinkingConfig(
274
+ thinking_budget=-1,
275
+ include_thoughts=True
276
+ )
277
  generation_config = types.GenerateContentConfig(**config_dict)
278
 
279
  # Gestion de la conversation
 
299
  # Génération de la réponse avec streaming
300
  def generate():
301
  try:
302
+ if not client:
303
+ yield f"data: {json.dumps({'type': 'error', 'content': 'API Gemini non configurée. Définissez GOOGLE_API_KEY.'})}\n\n"
304
+ return
305
+
306
+ print(f"Démarrage du streaming avec fichiers pour conversation {conversation_id}")
307
  response_stream = chat.send_message_stream(
308
  contents,
309
  config=generation_config
310
  )
311
+
312
  full_response = ""
313
  thoughts = ""
314
+ chunk_count = 0
315
 
316
  for chunk in response_stream:
317
+ chunk_count += 1
318
+ print(f"Chunk {chunk_count} reçu (avec fichiers)")
319
  for part in chunk.candidates[0].content.parts:
320
  if part.text:
321
  if part.thought and thinking_enabled:
 
324
  full_response += part.text
325
  yield f"data: {json.dumps({'type': 'text', 'content': part.text})}\n\n"
326
 
327
+ print(f"Streaming avec fichiers terminé, réponse complète: {len(full_response)} caractères")
328
+
329
  # Ajouter la réponse de l'assistant à l'historique
330
  if full_response:
331
  add_message_to_history(conversation_id, 'assistant', full_response)
332
+
333
+ # Signal de fin
334
  yield f"data: {json.dumps({'type': 'end'})}\n\n"
335
+
336
  except Exception as e:
337
+ print(f"ERREUR lors du streaming avec fichiers: {e}")
338
+ yield f"data: {json.dumps({'type': 'error', 'content': f'Erreur API avec fichiers: {str(e)}'})}\n\n"
339
 
340
  return Response(generate(), mimetype='text/plain')
341
 
 
399
  # Statistiques générales
400
  total_conversations = len(conversation_metadata)
401
  total_messages = sum(len(conv['messages']) for conv in conversation_metadata.values())
402
+
403
  # Statistiques par statut
404
  status_stats = {}
405
  for conv in conversation_metadata.values():
406
  status = conv.get('status', 'active')
407
  status_stats[status] = status_stats.get(status, 0) + 1
408
+
409
  # Conversations avec fichiers
410
+ conversations_with_files = sum(1 for conv in conversation_metadata.values()
411
  if any(msg.get('hasFile') for msg in conv['messages']))
412
+
413
  # Activité par jour (derniers 7 jours)
414
  from collections import defaultdict
415
  daily_activity = defaultdict(int)
416
+
417
  for conv in conversation_metadata.values():
418
  for message in conv['messages']:
419
  if message.get('timestamp'):
 
422
  daily_activity[date.isoformat()] += 1
423
  except:
424
  continue
425
+
426
  return jsonify({
427
  'total_conversations': total_conversations,
428
  'total_messages': total_messages,
 
433
  except Exception as e:
434
  return jsonify({'error': str(e)}), 500
435
 
436
+ @app.route('/debug/api_test')
437
+ def debug_api_test():
438
+ """Endpoint de debug pour tester la connectivité API"""
439
+ try:
440
+ if not client:
441
+ return jsonify({
442
+ 'status': 'error',
443
+ 'message': 'Client Gemini non initialisé',
444
+ 'api_key_set': bool(API_KEY)
445
+ })
446
+
447
+ # Test simple de l'API
448
+ response = client.models.generate_content(
449
+ model=MODEL,
450
+ contents="Hello",
451
+ config=types.GenerateContentConfig(
452
+ max_output_tokens=10,
453
+ system_instruction="Réponds brièvement."
454
+ )
455
+ )
456
+
457
+ return jsonify({
458
+ 'status': 'success',
459
+ 'message': 'API Gemini fonctionnelle',
460
+ 'model': MODEL,
461
+ 'response_length': len(response.text) if response.text else 0,
462
+ 'sample_response': response.text[:100] if response.text else None
463
+ })
464
+
465
+ except Exception as e:
466
+ return jsonify({
467
+ 'status': 'error',
468
+ 'message': f'Erreur API: {str(e)}',
469
+ 'api_key_set': bool(API_KEY)
470
+ })
471
+
472
  if __name__ == '__main__':
473
  app.run(debug=True, host='0.0.0.0', port=5000)