Aigenthix commited on
Commit
261f314
·
verified ·
1 Parent(s): bf0c315

Upload 2580 files

Browse files
Files changed (1) hide show
  1. app_docker.py +438 -85
app_docker.py CHANGED
@@ -1,10 +1,13 @@
1
- """Flask app for Hugging Face Spaces - RAG with Multiple Modes"""
2
 
3
  from flask import Flask, render_template_string, request, jsonify
4
  import os
5
  from pathlib import Path
6
  import sys
7
  import json
 
 
 
8
 
9
  # Add backend to path
10
  sys.path.insert(0, str(Path(__file__).parent / "backend"))
@@ -30,6 +33,7 @@ services = {
30
  "retrieval_service": None,
31
  }
32
  documents = {}
 
33
 
34
  HTML_TEMPLATE = """
35
  <!DOCTYPE html>
@@ -37,7 +41,7 @@ HTML_TEMPLATE = """
37
  <head>
38
  <meta charset="UTF-8">
39
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
40
- <title>RAG Application - Multi Mode</title>
41
  <style>
42
  * {
43
  margin: 0;
@@ -67,6 +71,32 @@ HTML_TEMPLATE = """
67
  color: #999;
68
  font-size: 1rem;
69
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  .main-content {
71
  display: grid;
72
  grid-template-columns: 1fr 1fr;
@@ -143,6 +173,65 @@ HTML_TEMPLATE = """
143
  justify-content: space-between;
144
  align-items: center;
145
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  .controls {
147
  display: grid;
148
  grid-template-columns: 1fr 1fr;
@@ -266,87 +355,166 @@ HTML_TEMPLATE = """
266
  <body>
267
  <div class="container">
268
  <div class="header">
269
- <h1>🧠 RAG Application</h1>
270
- <p>Retrieval-Augmented Generation with Multiple Modes</p>
271
  </div>
272
 
273
  <div id="statusDiv"></div>
274
 
275
- <div class="main-content">
276
- <!-- Left Column: Configuration & Documents -->
277
- <div>
278
- <div class="card">
279
- <h2>🔐 API Configuration</h2>
280
- <p style="color: #2d3e50; margin-bottom: 10px;"><strong>✅ Using HF Spaces Secrets</strong></p>
281
- <small style="color: #999;">API key is set via Hugging Face Spaces Settings → Secrets and variables → GROQ_API_KEY</small>
282
- <div style="margin-top: 15px; padding: 12px; background: #d1fae5; border-radius: 8px; border-left: 4px solid #10b981; color: #065f46;">
283
- <strong>✓</strong> Ready to use
284
- </div>
285
- </div>
286
 
287
- <div class="card" style="margin-top: 20px;">
288
- <h2>📄 Documents</h2>
289
- <div class="file-upload">
290
- <label for="fileInput">
291
- 📁 Click to upload PDF/CSV
292
- </label>
293
- <input type="file" id="fileInput" accept=".pdf,.csv" multiple>
 
 
 
 
 
294
  </div>
295
- <div id="documentsList" class="documents-list"></div>
296
- </div>
297
 
298
- <div class="card" style="margin-top: 20px;">
299
- <h2>⚙️ Settings</h2>
300
- <div class="controls">
301
- <div class="control-group">
302
- <label>RAG Mode</label>
303
- <select id="ragMode">
304
- <option value="simple">📄 Simple RAG</option>
305
- <option value="agentic">🤖 Agentic RAG</option>
306
- <option value="graph">🕸️ Graph RAG</option>
307
- </select>
308
- </div>
309
- <div class="control-group">
310
- <label>Model</label>
311
- <select id="model">
312
- <option value="llama-3.1-8b-instant">Llama 3.1 8B (Fast)</option>
313
- <option value="llama-3.3-70b-versatile">Llama 3.3 70B (Versatile)</option>
314
- <option value="openai/gpt-oss-120b">OpenAI GPT-OSS 120B</option>
315
- <option value="openai/gpt-oss-20b">OpenAI GPT-OSS 20B</option>
316
- </select>
317
  </div>
318
- <div class="control-group">
319
- <label>Temperature</label>
320
- <input type="range" id="temperature" min="0" max="2" step="0.1" value="0.7">
 
 
 
 
 
321
  </div>
322
- <div class="control-group">
323
- <label>Top K</label>
324
- <input type="number" id="topK" min="1" max="20" value="5">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  </div>
326
  </div>
327
  </div>
 
 
 
 
 
 
 
 
 
 
328
  </div>
 
329
 
330
- <!-- Right Column: Query & Results -->
331
- <div>
332
- <div class="card">
333
- <h2>🤔 Ask Question</h2>
334
- <textarea id="query" placeholder="What would you like to know about your documents?"></textarea>
335
- <button class="primary" onclick="submitQuery()">🔍 Search & Generate</button>
336
- <div id="resultDiv" style="margin-top: 20px;"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  </div>
338
  </div>
339
  </div>
340
  </div>
341
 
342
  <script>
 
 
 
 
 
 
 
 
 
343
  function submitQuery() {
344
  const query = document.getElementById('query').value;
345
  if (!query) {
346
  showStatus('Please enter a question', 'warning');
347
  return;
348
  }
349
- showStatus('<div class="loading"></div> Processing...', 'warning');
350
  fetch('/query', {
351
  method: 'POST',
352
  headers: {'Content-Type': 'application/json'},
@@ -369,12 +537,73 @@ HTML_TEMPLATE = """
369
  .catch(e => showStatus('❌ Error: ' + e, 'error'));
370
  }
371
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  function displayResult(result) {
373
  let html = '<div class="result">';
374
  html += '<h3>Answer</h3><p>' + result.answer + '</p>';
375
  html += '<div class="metrics">';
376
  html += '<div class="metric"><div class="metric-label">Time</div><div class="metric-value">' + result.response_time_ms.toFixed(0) + 'ms</div></div>';
377
- html += '<div class="metric"><div class="metric-label">Tokens</div><div class="metric-value">' + result.tokens_used.total + '</div></div>';
378
  html += '<div class="metric"><div class="metric-label">Mode</div><div class="metric-value">' + result.rag_mode.toUpperCase() + '</div></div>';
379
  html += '</div>';
380
  if (result.sources && result.sources.length > 0) {
@@ -406,11 +635,21 @@ HTML_TEMPLATE = """
406
  fetch('/documents').then(r => r.json()).then(d => {
407
  const list = document.getElementById('documentsList');
408
  if (d.documents.length === 0) {
409
- list.innerHTML = '<p style="color: #999;">No documents uploaded</p>';
410
  } else {
411
- list.innerHTML = d.documents.map(doc =>
412
- '<div class="document-item"><span>📄 ' + doc + '</span></div>'
413
- ).join('');
 
 
 
 
 
 
 
 
 
 
414
  }
415
  });
416
  }
@@ -418,18 +657,47 @@ HTML_TEMPLATE = """
418
  document.getElementById('fileInput').addEventListener('change', function(e) {
419
  const files = e.target.files;
420
  if (files.length === 0) return;
 
 
 
 
421
  const formData = new FormData();
 
 
 
422
  for (let file of files) {
423
  formData.append('files', file);
424
  }
 
 
 
 
 
 
 
 
 
 
425
  fetch('/upload', {method: 'POST', body: formData})
426
  .then(r => r.json())
427
  .then(d => {
428
- showStatus(d.message, d.success ? 'success' : 'error');
429
- updateDocumentsList();
430
- document.getElementById('fileInput').value = '';
 
 
 
 
 
431
  });
432
  });
 
 
 
 
 
 
 
433
  </script>
434
  </body>
435
  </html>
@@ -463,7 +731,7 @@ def set_api_key():
463
  def get_documents():
464
  initialize_groq_from_env()
465
  return jsonify({
466
- 'documents': list(documents.keys()),
467
  'api_key_set': services['retrieval_service'] is not None
468
  })
469
 
@@ -474,34 +742,61 @@ def upload_files():
474
  return jsonify({'success': False, 'message': 'No files uploaded'})
475
 
476
  try:
 
 
 
477
  for file in files:
478
  if not file.filename:
479
  continue
 
480
  file_content = file.read()
481
  doc_type = 'pdf' if file.filename.endswith('.pdf') else 'csv'
482
 
483
- # Process file
484
- import tempfile
485
- with tempfile.NamedTemporaryFile(delete=False, suffix=f".{doc_type}") as f:
486
- f.write(file_content)
487
- if doc_type == 'pdf':
488
- text = PDFProcessor.extract_text(f.name)
489
- else:
490
- text = CSVProcessor.extract_text(f.name)
491
-
492
- # Chunk and embed
493
- chunker = ChunkerService(512, 50)
494
- chunks = chunker.chunk_text(text, {"doc_id": file.filename})
495
- embedded_chunks = services['embedding_service'].embed_chunks(chunks)
496
- services['vector_db_service'].add_chunks(embedded_chunks)
497
- documents[file.filename] = {'type': doc_type, 'size': len(file_content)}
498
-
499
- return jsonify({'success': True, 'message': f'✅ Processed {len(files)} file(s)'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  except Exception as e:
501
  return jsonify({'success': False, 'message': f'❌ Error: {str(e)}'})
502
 
503
  @app.route('/query', methods=['POST'])
504
  def query():
 
505
  initialize_groq_from_env()
506
 
507
  if not services['retrieval_service']:
@@ -520,7 +815,7 @@ def query():
520
  )
521
 
522
  if not search_results:
523
- return jsonify({'success': False, 'error': 'No relevant documents found'})
524
 
525
  result = services['retrieval_service'].generate_with_pipeline(
526
  query_text,
@@ -535,7 +830,65 @@ def query():
535
  except Exception as e:
536
  return jsonify({'success': False, 'error': str(e)})
537
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  if __name__ == '__main__':
539
  os.makedirs('./data/uploads', exist_ok=True)
540
  os.makedirs('./data/chroma_data', exist_ok=True)
541
- app.run(host='0.0.0.0', port=7860, debug=False)
 
1
+ """Flask app for Hugging Face Spaces - RAG with Multiple Modes + Direct Chatbot"""
2
 
3
  from flask import Flask, render_template_string, request, jsonify
4
  import os
5
  from pathlib import Path
6
  import sys
7
  import json
8
+ import time
9
+ import threading
10
+ from datetime import datetime
11
 
12
  # Add backend to path
13
  sys.path.insert(0, str(Path(__file__).parent / "backend"))
 
33
  "retrieval_service": None,
34
  }
35
  documents = {}
36
+ upload_status = {} # Track upload progress
37
 
38
  HTML_TEMPLATE = """
39
  <!DOCTYPE html>
 
41
  <head>
42
  <meta charset="UTF-8">
43
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
44
+ <title>RAG Application - Multi Mode + Chatbot</title>
45
  <style>
46
  * {
47
  margin: 0;
 
71
  color: #999;
72
  font-size: 1rem;
73
  }
74
+ .tabs {
75
+ display: flex;
76
+ gap: 10px;
77
+ margin-bottom: 30px;
78
+ border-bottom: 2px solid #e0e6ed;
79
+ }
80
+ .tab-button {
81
+ padding: 12px 24px;
82
+ background: transparent;
83
+ border: none;
84
+ cursor: pointer;
85
+ color: #999;
86
+ font-weight: 500;
87
+ border-bottom: 3px solid transparent;
88
+ margin-bottom: -2px;
89
+ }
90
+ .tab-button.active {
91
+ color: #5b7fff;
92
+ border-bottom-color: #5b7fff;
93
+ }
94
+ .tab-content {
95
+ display: none;
96
+ }
97
+ .tab-content.active {
98
+ display: block;
99
+ }
100
  .main-content {
101
  display: grid;
102
  grid-template-columns: 1fr 1fr;
 
173
  justify-content: space-between;
174
  align-items: center;
175
  }
176
+ .upload-progress {
177
+ margin: 15px 0;
178
+ }
179
+ .progress-bar {
180
+ width: 100%;
181
+ height: 8px;
182
+ background: #e0e6ed;
183
+ border-radius: 4px;
184
+ overflow: hidden;
185
+ margin-bottom: 8px;
186
+ }
187
+ .progress-fill {
188
+ height: 100%;
189
+ background: linear-gradient(90deg, #5b7fff 0%, #4a6de8 100%);
190
+ width: 0%;
191
+ transition: width 0.3s ease;
192
+ }
193
+ .progress-text {
194
+ font-size: 0.9rem;
195
+ color: #999;
196
+ display: flex;
197
+ justify-content: space-between;
198
+ }
199
+ .document-status {
200
+ display: flex;
201
+ align-items: center;
202
+ gap: 8px;
203
+ padding: 8px 12px;
204
+ background: #f8fafc;
205
+ border-radius: 6px;
206
+ border-left: 4px solid #5b7fff;
207
+ }
208
+ .status-icon {
209
+ font-size: 1.2rem;
210
+ }
211
+ .mode-toggle {
212
+ display: flex;
213
+ gap: 10px;
214
+ margin-bottom: 20px;
215
+ }
216
+ .mode-button {
217
+ flex: 1;
218
+ padding: 10px;
219
+ border: 2px solid #e0e6ed;
220
+ border-radius: 8px;
221
+ background: white;
222
+ cursor: pointer;
223
+ font-weight: 500;
224
+ color: #2d3e50;
225
+ transition: all 0.3s ease;
226
+ }
227
+ .mode-button.active {
228
+ background: #5b7fff;
229
+ color: white;
230
+ border-color: #5b7fff;
231
+ }
232
+ .mode-button:hover {
233
+ border-color: #5b7fff;
234
+ }
235
  .controls {
236
  display: grid;
237
  grid-template-columns: 1fr 1fr;
 
355
  <body>
356
  <div class="container">
357
  <div class="header">
358
+ <h1>🧠 RAG Application + Chatbot</h1>
359
+ <p>Retrieval-Augmented Generation with Multiple Modes + Direct Chat</p>
360
  </div>
361
 
362
  <div id="statusDiv"></div>
363
 
364
+ <!-- Tab Navigation -->
365
+ <div class="tabs">
366
+ <button class="tab-button active" onclick="switchTab('rag')">📄 RAG Mode (with Documents)</button>
367
+ <button class="tab-button" onclick="switchTab('chat')">💬 Direct Chatbot</button>
368
+ </div>
 
 
 
 
 
 
369
 
370
+ <!-- RAG Tab -->
371
+ <div id="rag" class="tab-content active">
372
+ <div class="main-content">
373
+ <!-- Left Column: Configuration & Documents -->
374
+ <div>
375
+ <div class="card">
376
+ <h2>🔐 API Configuration</h2>
377
+ <p style="color: #2d3e50; margin-bottom: 10px;"><strong>✅ Using HF Spaces Secrets</strong></p>
378
+ <small style="color: #999;">API key is set via Hugging Face Spaces Settings → Secrets and variables → GROQ_API_KEY</small>
379
+ <div style="margin-top: 15px; padding: 12px; background: #d1fae5; border-radius: 8px; border-left: 4px solid #10b981; color: #065f46;">
380
+ <strong>✓</strong> Ready to use
381
+ </div>
382
  </div>
 
 
383
 
384
+ <div class="card" style="margin-top: 20px;">
385
+ <h2>📄 Document Upload</h2>
386
+ <div class="file-upload">
387
+ <label for="fileInput">
388
+ 📁 Click to upload PDF/CSV
389
+ </label>
390
+ <input type="file" id="fileInput" accept=".pdf,.csv" multiple>
 
 
 
 
 
 
 
 
 
 
 
 
391
  </div>
392
+ <div id="uploadProgressDiv" class="upload-progress" style="display:none;">
393
+ <div class="progress-bar">
394
+ <div id="progressFill" class="progress-fill"></div>
395
+ </div>
396
+ <div class="progress-text">
397
+ <span id="progressStatus">Uploading...</span>
398
+ <span id="progressPercent">0%</span>
399
+ </div>
400
  </div>
401
+ <div id="documentsList" class="documents-list"></div>
402
+ </div>
403
+
404
+ <div class="card" style="margin-top: 20px;">
405
+ <h2>⚙️ Settings</h2>
406
+ <div class="controls">
407
+ <div class="control-group">
408
+ <label>RAG Mode</label>
409
+ <select id="ragMode">
410
+ <option value="simple">📄 Simple RAG</option>
411
+ <option value="agentic">🤖 Agentic RAG</option>
412
+ <option value="graph">🕸️ Graph RAG</option>
413
+ </select>
414
+ </div>
415
+ <div class="control-group">
416
+ <label>Model</label>
417
+ <select id="model">
418
+ <option value="llama-3.1-8b-instant">Llama 3.1 8B (Fast)</option>
419
+ <option value="llama-3.3-70b-versatile">Llama 3.3 70B (Versatile)</option>
420
+ <option value="openai/gpt-oss-120b">OpenAI GPT-OSS 120B</option>
421
+ <option value="openai/gpt-oss-20b">OpenAI GPT-OSS 20B</option>
422
+ </select>
423
+ </div>
424
+ <div class="control-group">
425
+ <label>Temperature</label>
426
+ <input type="range" id="temperature" min="0" max="2" step="0.1" value="0.7">
427
+ </div>
428
+ <div class="control-group">
429
+ <label>Top K</label>
430
+ <input type="number" id="topK" min="1" max="20" value="5">
431
+ </div>
432
  </div>
433
  </div>
434
  </div>
435
+
436
+ <!-- Right Column: Query & Results -->
437
+ <div>
438
+ <div class="card">
439
+ <h2>🤔 RAG Query</h2>
440
+ <textarea id="query" placeholder="What would you like to know about your documents?"></textarea>
441
+ <button class="primary" onclick="submitQuery()">🔍 Search & Generate</button>
442
+ <div id="resultDiv" style="margin-top: 20px;"></div>
443
+ </div>
444
+ </div>
445
  </div>
446
+ </div>
447
 
448
+ <!-- Chat Tab -->
449
+ <div id="chat" class="tab-content">
450
+ <div class="main-content">
451
+ <!-- Left Column: Chat Settings -->
452
+ <div>
453
+ <div class="card">
454
+ <h2>⚙️ Chat Settings</h2>
455
+ <div class="controls">
456
+ <div class="control-group">
457
+ <label>Model</label>
458
+ <select id="chatModel">
459
+ <option value="llama-3.1-8b-instant">Llama 3.1 8B (Fast)</option>
460
+ <option value="llama-3.3-70b-versatile">Llama 3.3 70B (Versatile)</option>
461
+ </select>
462
+ </div>
463
+ <div class="control-group">
464
+ <label>Temperature</label>
465
+ <input type="range" id="chatTemperature" min="0" max="2" step="0.1" value="0.7">
466
+ </div>
467
+ </div>
468
+ </div>
469
+ <div class="card" style="margin-top: 20px;">
470
+ <h2>ℹ️ Direct Chat</h2>
471
+ <p style="color: #999; margin-bottom: 10px;">Chat directly with the AI without needing documents. Perfect for:</p>
472
+ <ul style="color: #999; margin-left: 20px;">
473
+ <li>General questions</li>
474
+ <li>Getting explanations</li>
475
+ <li>Brainstorming ideas</li>
476
+ <li>Learning new topics</li>
477
+ </ul>
478
+ </div>
479
+ </div>
480
+
481
+ <!-- Right Column: Chat Interface -->
482
+ <div>
483
+ <div class="card">
484
+ <h2>💬 Chat</h2>
485
+ <div id="chatMessages" style="height: 400px; overflow-y: auto; border: 1px solid #e0e6ed; border-radius: 8px; padding: 15px; margin-bottom: 15px; background: #f8fafc;">
486
+ <div style="text-align: center; color: #999; padding: 20px;">
487
+ <p>Start a conversation...</p>
488
+ </div>
489
+ </div>
490
+ <div style="display: flex; gap: 10px;">
491
+ <textarea id="chatInput" placeholder="Ask me anything..." style="margin-bottom: 0;"></textarea>
492
+ <button class="primary" onclick="submitChat()" style="flex: 0 0 100px;">Send</button>
493
+ </div>
494
+ <div id="chatResultDiv" style="margin-top: 20px;"></div>
495
+ </div>
496
  </div>
497
  </div>
498
  </div>
499
  </div>
500
 
501
  <script>
502
+ // Tab switching
503
+ function switchTab(tab) {
504
+ document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
505
+ document.querySelectorAll('.tab-button').forEach(el => el.classList.remove('active'));
506
+ document.getElementById(tab).classList.add('active');
507
+ event.target.classList.add('active');
508
+ }
509
+
510
+ // RAG Mode - Query with documents
511
  function submitQuery() {
512
  const query = document.getElementById('query').value;
513
  if (!query) {
514
  showStatus('Please enter a question', 'warning');
515
  return;
516
  }
517
+ showStatus('<div class="loading"></div> Processing RAG query...', 'warning');
518
  fetch('/query', {
519
  method: 'POST',
520
  headers: {'Content-Type': 'application/json'},
 
537
  .catch(e => showStatus('❌ Error: ' + e, 'error'));
538
  }
539
 
540
+ // Direct Chat - No documents needed
541
+ function submitChat() {
542
+ const message = document.getElementById('chatInput').value;
543
+ if (!message) {
544
+ showStatus('Please enter a message', 'warning');
545
+ return;
546
+ }
547
+ showStatus('<div class="loading"></div> Chatting...', 'warning');
548
+
549
+ // Add user message to chat
550
+ addChatMessage(message, 'user');
551
+ document.getElementById('chatInput').value = '';
552
+
553
+ fetch('/chat', {
554
+ method: 'POST',
555
+ headers: {'Content-Type': 'application/json'},
556
+ body: JSON.stringify({
557
+ message: message,
558
+ model: document.getElementById('chatModel').value,
559
+ temperature: parseFloat(document.getElementById('chatTemperature').value)
560
+ })
561
+ })
562
+ .then(r => r.json())
563
+ .then(data => {
564
+ if (data.success) {
565
+ addChatMessage(data.result.answer, 'assistant');
566
+ showStatus('', '');
567
+ } else {
568
+ addChatMessage('❌ Error: ' + data.error, 'error');
569
+ }
570
+ })
571
+ .catch(e => {
572
+ addChatMessage('❌ Error: ' + e, 'error');
573
+ });
574
+ }
575
+
576
+ function addChatMessage(text, sender) {
577
+ const chatDiv = document.getElementById('chatMessages');
578
+ const messageDiv = document.createElement('div');
579
+ messageDiv.style.marginBottom = '10px';
580
+ messageDiv.style.padding = '10px';
581
+ messageDiv.style.borderRadius = '8px';
582
+
583
+ if (sender === 'user') {
584
+ messageDiv.style.background = '#5b7fff';
585
+ messageDiv.style.color = 'white';
586
+ messageDiv.style.marginLeft = '20px';
587
+ } else if (sender === 'assistant') {
588
+ messageDiv.style.background = '#e0e6ed';
589
+ messageDiv.style.color = '#2d3e50';
590
+ messageDiv.style.marginRight = '20px';
591
+ } else {
592
+ messageDiv.style.background = '#fee2e2';
593
+ messageDiv.style.color = '#7f1d1d';
594
+ }
595
+
596
+ messageDiv.textContent = text;
597
+ chatDiv.appendChild(messageDiv);
598
+ chatDiv.scrollTop = chatDiv.scrollHeight;
599
+ }
600
+
601
  function displayResult(result) {
602
  let html = '<div class="result">';
603
  html += '<h3>Answer</h3><p>' + result.answer + '</p>';
604
  html += '<div class="metrics">';
605
  html += '<div class="metric"><div class="metric-label">Time</div><div class="metric-value">' + result.response_time_ms.toFixed(0) + 'ms</div></div>';
606
+ html += '<div class="metric"><div class="metric-label">Tokens</div><div class="metric-value">' + (result.tokens_used.total || 0) + '</div></div>';
607
  html += '<div class="metric"><div class="metric-label">Mode</div><div class="metric-value">' + result.rag_mode.toUpperCase() + '</div></div>';
608
  html += '</div>';
609
  if (result.sources && result.sources.length > 0) {
 
635
  fetch('/documents').then(r => r.json()).then(d => {
636
  const list = document.getElementById('documentsList');
637
  if (d.documents.length === 0) {
638
+ list.innerHTML = '<p style="color: #999;">No documents uploaded yet</p>';
639
  } else {
640
+ let html = '';
641
+ for (let doc in d.documents) {
642
+ const info = d.documents[doc];
643
+ const status = info.status || 'ready';
644
+ const icon = status === 'processing' ? '⏳' : '✅';
645
+ html += '<div class="document-status">';
646
+ html += '<span class="status-icon">' + icon + '</span>';
647
+ html += '<div style="flex: 1;">';
648
+ html += '<div style="font-weight: 500; color: #2d3e50;">📄 ' + doc + '</div>';
649
+ html += '<div style="font-size: 0.85rem; color: #999;">Status: ' + status + ' | Type: ' + info.type + '</div>';
650
+ html += '</div></div>';
651
+ }
652
+ list.innerHTML = html;
653
  }
654
  });
655
  }
 
657
  document.getElementById('fileInput').addEventListener('change', function(e) {
658
  const files = e.target.files;
659
  if (files.length === 0) return;
660
+
661
+ const progressDiv = document.getElementById('uploadProgressDiv');
662
+ progressDiv.style.display = 'block';
663
+
664
  const formData = new FormData();
665
+ let totalFiles = files.length;
666
+ let processedFiles = 0;
667
+
668
  for (let file of files) {
669
  formData.append('files', file);
670
  }
671
+
672
+ // Simulate progress
673
+ let progress = 0;
674
+ const progressInterval = setInterval(() => {
675
+ if (progress < 90) {
676
+ progress += Math.random() * 30;
677
+ updateProgress(Math.min(progress, 90));
678
+ }
679
+ }, 500);
680
+
681
  fetch('/upload', {method: 'POST', body: formData})
682
  .then(r => r.json())
683
  .then(d => {
684
+ clearInterval(progressInterval);
685
+ updateProgress(100);
686
+ setTimeout(() => {
687
+ progressDiv.style.display = 'none';
688
+ showStatus(d.message, d.success ? 'success' : 'error');
689
+ updateDocumentsList();
690
+ document.getElementById('fileInput').value = '';
691
+ }, 1000);
692
  });
693
  });
694
+
695
+ function updateProgress(percent) {
696
+ document.getElementById('progressFill').style.width = percent + '%';
697
+ document.getElementById('progressPercent').textContent = Math.round(percent) + '%';
698
+ const status = percent < 100 ? 'Processing...' : 'Complete!';
699
+ document.getElementById('progressStatus').textContent = status;
700
+ }
701
  </script>
702
  </body>
703
  </html>
 
731
  def get_documents():
732
  initialize_groq_from_env()
733
  return jsonify({
734
+ 'documents': documents,
735
  'api_key_set': services['retrieval_service'] is not None
736
  })
737
 
 
742
  return jsonify({'success': False, 'message': 'No files uploaded'})
743
 
744
  try:
745
+ successful = 0
746
+ failed = 0
747
+
748
  for file in files:
749
  if not file.filename:
750
  continue
751
+
752
  file_content = file.read()
753
  doc_type = 'pdf' if file.filename.endswith('.pdf') else 'csv'
754
 
755
+ try:
756
+ # Update status: processing
757
+ documents[file.filename] = {
758
+ 'type': doc_type,
759
+ 'size': len(file_content),
760
+ 'status': 'processing',
761
+ 'uploaded_at': str(__import__('datetime').datetime.now())
762
+ }
763
+
764
+ # Process file
765
+ import tempfile
766
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{doc_type}") as f:
767
+ f.write(file_content)
768
+ if doc_type == 'pdf':
769
+ text = PDFProcessor.extract_text(f.name)
770
+ else:
771
+ text = CSVProcessor.extract_text(f.name)
772
+
773
+ # Chunk and embed
774
+ chunker = ChunkerService(512, 50)
775
+ chunks = chunker.chunk_text(text, {"doc_id": file.filename})
776
+ embedded_chunks = services['embedding_service'].embed_chunks(chunks)
777
+ services['vector_db_service'].add_chunks(embedded_chunks)
778
+
779
+ # Update status: ready
780
+ documents[file.filename]['status'] = 'ready'
781
+ documents[file.filename]['chunks'] = len(chunks)
782
+ successful += 1
783
+
784
+ except Exception as file_error:
785
+ documents[file.filename]['status'] = 'error'
786
+ documents[file.filename]['error'] = str(file_error)
787
+ failed += 1
788
+
789
+ message = f'✅ Processed {successful} file(s)'
790
+ if failed > 0:
791
+ message += f', ❌ Failed {failed} file(s)'
792
+
793
+ return jsonify({'success': failed == 0, 'message': message})
794
  except Exception as e:
795
  return jsonify({'success': False, 'message': f'❌ Error: {str(e)}'})
796
 
797
  @app.route('/query', methods=['POST'])
798
  def query():
799
+ """RAG Query - with document retrieval"""
800
  initialize_groq_from_env()
801
 
802
  if not services['retrieval_service']:
 
815
  )
816
 
817
  if not search_results:
818
+ return jsonify({'success': False, 'error': '❌ No relevant documents found. Please upload documents first.'})
819
 
820
  result = services['retrieval_service'].generate_with_pipeline(
821
  query_text,
 
830
  except Exception as e:
831
  return jsonify({'success': False, 'error': str(e)})
832
 
833
+
834
+ @app.route('/chat', methods=['POST'])
835
+ def chat():
836
+ """Direct Chat - without document retrieval"""
837
+ initialize_groq_from_env()
838
+
839
+ if not services['retrieval_service']:
840
+ return jsonify({'success': False, 'error': 'GROQ_API_KEY not configured in Hugging Face Spaces secrets'})
841
+
842
+ data = request.json
843
+ message = data.get('message', '')
844
+ if not message:
845
+ return jsonify({'success': False, 'error': 'Message is required'})
846
+
847
+ try:
848
+ model = data.get('model', 'llama-3.1-8b-instant')
849
+ temperature = data.get('temperature', 0.7)
850
+ max_tokens = data.get('max_tokens', 1024)
851
+
852
+ start_time = __import__('time').time()
853
+
854
+ # Direct LLM call without document retrieval
855
+ groq_client = services['retrieval_service'].groq_client
856
+ response = groq_client.create_chat_completion(
857
+ model=model,
858
+ messages=[
859
+ {
860
+ "role": "system",
861
+ "content": "You are a helpful AI assistant. Provide clear, concise, and accurate answers."
862
+ },
863
+ {
864
+ "role": "user",
865
+ "content": message
866
+ }
867
+ ],
868
+ temperature=temperature,
869
+ max_tokens=max_tokens
870
+ )
871
+
872
+ answer = response['choices'][0]['message']['content']
873
+ response_time_ms = (__import__('time').time() - start_time) * 1000
874
+
875
+ tokens_used = {
876
+ 'prompt': response['usage'].get('prompt_tokens', 0),
877
+ 'completion': response['usage'].get('completion_tokens', 0),
878
+ 'total': response['usage'].get('total_tokens', 0)
879
+ }
880
+
881
+ return jsonify({'success': True, 'result': {
882
+ 'answer': answer,
883
+ 'model': model,
884
+ 'tokens_used': tokens_used,
885
+ 'response_time_ms': response_time_ms,
886
+ 'mode': 'direct_chat'
887
+ }})
888
+ except Exception as e:
889
+ return jsonify({'success': False, 'error': str(e)})
890
+
891
  if __name__ == '__main__':
892
  os.makedirs('./data/uploads', exist_ok=True)
893
  os.makedirs('./data/chroma_data', exist_ok=True)
894
+ app.run(host='0.0.0.0', port=7860, debug=False)