Aigenthix commited on
Commit
5a96dcf
·
verified ·
1 Parent(s): af97d31

Update app_docker.py

Browse files
Files changed (1) hide show
  1. app_docker.py +199 -93
app_docker.py CHANGED
@@ -654,25 +654,43 @@ HTML_TEMPLATE = """
654
  });
655
 
656
  function updateDocumentsList() {
657
- fetch('/documents').then(r => r.json()).then(d => {
 
 
 
 
 
658
  const list = document.getElementById('documentsList');
659
- if (d.documents.length === 0) {
660
  list.innerHTML = '<p style="color: #999;">No documents uploaded yet</p>';
661
  } else {
662
  let html = '';
663
  for (let doc in d.documents) {
664
- const info = d.documents[doc];
665
- const status = info.status || 'ready';
666
- const icon = status === 'processing' ? '⏳' : '✅';
667
- html += '<div class="document-status">';
668
- html += '<span class="status-icon">' + icon + '</span>';
669
- html += '<div style="flex: 1;">';
670
- html += '<div style="font-weight: 500; color: #2d3e50;">📄 ' + doc + '</div>';
671
- html += '<div style="font-size: 0.85rem; color: #999;">Status: ' + status + ' | Type: ' + info.type + '</div>';
672
- html += '</div></div>';
 
 
 
 
 
 
 
 
 
673
  }
674
  list.innerHTML = html;
675
  }
 
 
 
 
676
  });
677
  }
678
 
@@ -682,44 +700,68 @@ HTML_TEMPLATE = """
682
 
683
  const progressDiv = document.getElementById('uploadProgressDiv');
684
  progressDiv.style.display = 'block';
 
685
 
686
  const formData = new FormData();
687
  for (let file of files) {
688
  formData.append('files', file);
689
  }
690
 
691
- // Better progress tracking
692
- let progress = 0;
693
  const progressInterval = setInterval(() => {
694
- if (progress < 85) {
695
- progress += Math.random() * 25;
696
- updateProgress(Math.min(progress, 85));
697
  }
698
- }, 300);
699
 
 
700
  fetch('/upload', {
701
  method: 'POST',
702
- body: formData,
703
- timeout: 300000 // 5 minute timeout
704
  })
705
- .then(r => {
 
706
  clearInterval(progressInterval);
707
- return r.json();
 
 
 
 
 
 
708
  })
709
- .then(d => {
 
710
  updateProgress(100);
 
711
  setTimeout(() => {
712
  progressDiv.style.display = 'none';
713
- showStatus(d.message, d.success ? 'success' : 'error');
 
 
 
 
 
714
  updateDocumentsList();
 
 
715
  document.getElementById('fileInput').value = '';
 
 
716
  progress = 0;
717
- }, 1000);
718
  })
719
- .catch(e => {
 
720
  clearInterval(progressInterval);
721
  progressDiv.style.display = 'none';
722
- showStatus('❌ Upload failed: ' + e.message, 'error');
 
 
 
 
723
  document.getElementById('fileInput').value = '';
724
  progress = 0;
725
  });
@@ -770,34 +812,55 @@ def get_documents():
770
 
771
  @app.route('/upload', methods=['POST'])
772
  def upload_files():
 
773
  files = request.files.getlist('files')
 
774
  if not files or len(files) == 0:
775
- return jsonify({'success': False, 'message': '❌ No files uploaded'})
 
 
 
 
 
 
 
 
 
776
 
777
  try:
778
- successful = 0
779
- failed = 0
780
- errors = []
781
 
782
  for file in files:
783
  if not file or not file.filename:
784
  continue
785
 
786
  filename = file.filename
 
 
787
  try:
 
788
  file_content = file.read()
 
789
  if not file_content:
790
- documents[filename] = {'status': 'error', 'error': 'Empty file'}
 
791
  failed += 1
792
  continue
793
 
794
- doc_type = 'pdf' if filename.endswith('.pdf') else 'csv' if filename.endswith('.csv') else None
795
- if not doc_type:
796
- documents[filename] = {'status': 'error', 'error': 'Invalid file type'}
 
 
 
 
 
797
  failed += 1
798
  continue
799
 
800
- # Update status: processing
801
  documents[filename] = {
802
  'type': doc_type,
803
  'size': len(file_content),
@@ -805,108 +868,151 @@ def upload_files():
805
  'uploaded_at': str(__import__('datetime').datetime.now())
806
  }
807
 
808
- # Process file in temp location
809
- import tempfile
810
- import os as os_module
811
-
812
  with tempfile.NamedTemporaryFile(delete=False, suffix=f".{doc_type}") as f:
813
  temp_path = f.name
814
  f.write(file_content)
 
815
 
 
816
  try:
817
- # Extract text
818
  if doc_type == 'pdf':
819
  text = PDFProcessor.extract_text(temp_path)
820
  else:
821
  text = CSVProcessor.extract_text(temp_path)
 
 
822
 
823
- if not text or len(text.strip()) == 0:
824
- raise Exception('No text extracted from file')
825
 
826
- # Chunk and embed
 
827
  chunker = ChunkerService(512, 50)
828
  chunks = chunker.chunk_text(text, {"doc_id": filename})
829
 
830
- if not chunks:
831
- raise Exception('No chunks created from text')
 
 
832
 
 
 
833
  embedded_chunks = services['embedding_service'].embed_chunks(chunks)
834
- services['vector_db_service'].add_chunks(embedded_chunks)
 
835
 
836
- # Update status: ready
837
- documents[filename]['status'] = 'ready'
838
- documents[filename]['chunks'] = len(chunks)
839
- successful += 1
840
 
841
- finally:
842
- # Clean up temp file
843
- try:
844
- os_module.unlink(temp_path)
845
- except:
846
- pass
 
 
 
847
 
848
  except Exception as file_error:
849
  error_msg = str(file_error)[:100]
850
  documents[filename] = {
851
- 'type': 'unknown',
852
  'status': 'error',
853
  'error': error_msg
854
  }
855
  errors.append(f"{filename}: {error_msg}")
856
  failed += 1
 
 
 
 
 
 
 
 
 
857
 
858
  # Build response message
859
- message = f'✅ Processed {successful} file(s)'
860
- if failed > 0:
861
- message += f', ❌ Failed {failed} file(s)'
862
- if errors and len(errors) <= 3:
863
- message += f"\n{'; '.join(errors)}"
 
 
 
864
 
865
  return jsonify({
866
- 'success': failed == 0,
867
  'message': message,
868
  'successful': successful,
869
- 'failed': failed
 
870
  })
 
871
  except Exception as e:
872
  error_msg = str(e)[:200]
873
- return jsonify({'success': False, 'message': f'❌ Upload error: {error_msg}'})
 
 
 
 
 
 
874
 
875
  @app.route('/query', methods=['POST'])
876
  def query():
877
  """RAG Query - with document retrieval"""
878
- initialize_groq_from_env()
 
 
 
 
879
 
880
- if not services['retrieval_service']:
881
- return jsonify({'success': False, 'error': 'GROQ_API_KEY not configured in Hugging Face Spaces secrets'})
 
 
882
 
883
- data = request.json
884
- query_text = data.get('query', '')
885
- if not query_text:
886
- return jsonify({'success': False, 'error': 'Query is required'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
 
888
- try:
889
- query_embedding = services['embedding_service'].embed_text(query_text)
890
- search_results = services['vector_db_service'].search(
891
- query_embedding,
892
- data.get('top_k', 5)
893
- )
894
-
895
- if not search_results:
896
- return jsonify({'success': False, 'error': '❌ No relevant documents found. Please upload documents first.'})
897
-
898
- result = services['retrieval_service'].generate_with_pipeline(
899
- query_text,
900
- search_results,
901
- data.get('model', 'llama-3.1-8b-instant'),
902
- rag_mode=data.get('rag_mode', 'simple'),
903
- temperature=data.get('temperature', 0.7),
904
- max_tokens=data.get('max_tokens', 1024)
905
- )
906
-
907
- return jsonify({'success': True, 'result': result})
908
  except Exception as e:
909
- return jsonify({'success': False, 'error': str(e)})
910
 
911
 
912
  @app.route('/chat', methods=['POST'])
 
654
  });
655
 
656
  function updateDocumentsList() {
657
+ fetch('/documents')
658
+ .then(r => {
659
+ if (!r.ok) throw new Error('Failed to fetch documents');
660
+ return r.json();
661
+ })
662
+ .then(d => {
663
  const list = document.getElementById('documentsList');
664
+ if (!d.documents || Object.keys(d.documents).length === 0) {
665
  list.innerHTML = '<p style="color: #999;">No documents uploaded yet</p>';
666
  } else {
667
  let html = '';
668
  for (let doc in d.documents) {
669
+ if (d.documents.hasOwnProperty(doc)) {
670
+ const info = d.documents[doc];
671
+ const status = info.status || 'ready';
672
+ const icon = status === 'processing' ? '⏳' : status === 'error' ? '❌' : '✅';
673
+ const statusColor = status === 'error' ? '#dc3545' : status === 'processing' ? '#ffc107' : '#10b981';
674
+
675
+ html += '<div class="document-status" style="border-left-color: ' + statusColor + ';">';
676
+ html += '<span class="status-icon">' + icon + '</span>';
677
+ html += '<div style="flex: 1;">';
678
+ html += '<div style="font-weight: 500; color: #2d3e50;">📄 ' + doc + '</div>';
679
+
680
+ let details = 'Status: ' + status + ' | Type: ' + (info.type || 'unknown');
681
+ if (info.chunks) details += ' | Chunks: ' + info.chunks;
682
+ if (info.error) details += ' | Error: ' + info.error.substring(0, 50);
683
+
684
+ html += '<div style="font-size: 0.85rem; color: #999;">' + details + '</div>';
685
+ html += '</div></div>';
686
+ }
687
  }
688
  list.innerHTML = html;
689
  }
690
+ })
691
+ .catch(error => {
692
+ const list = document.getElementById('documentsList');
693
+ list.innerHTML = '<p style="color: #dc3545;">❌ Error loading documents: ' + error.message + '</p>';
694
  });
695
  }
696
 
 
700
 
701
  const progressDiv = document.getElementById('uploadProgressDiv');
702
  progressDiv.style.display = 'block';
703
+ updateProgress(5); // Start at 5%
704
 
705
  const formData = new FormData();
706
  for (let file of files) {
707
  formData.append('files', file);
708
  }
709
 
710
+ // Progress simulation during upload
711
+ let progress = 5;
712
  const progressInterval = setInterval(() => {
713
+ if (progress < 80) {
714
+ progress += Math.random() * 15;
715
+ updateProgress(Math.min(progress, 80));
716
  }
717
+ }, 400);
718
 
719
+ // Send upload request
720
  fetch('/upload', {
721
  method: 'POST',
722
+ body: formData
 
723
  })
724
+ .then(response => {
725
+ // Stop the progress simulation
726
  clearInterval(progressInterval);
727
+ updateProgress(90);
728
+
729
+ // Check if response is ok
730
+ if (!response.ok) {
731
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
732
+ }
733
+ return response.json();
734
  })
735
+ .then(data => {
736
+ // Upload completed successfully
737
  updateProgress(100);
738
+
739
  setTimeout(() => {
740
  progressDiv.style.display = 'none';
741
+
742
+ // Show status message
743
+ const successMsg = data.message || 'Upload completed';
744
+ showStatus(successMsg, data.success ? 'success' : 'warning');
745
+
746
+ // Refresh document list
747
  updateDocumentsList();
748
+
749
+ // Clear file input
750
  document.getElementById('fileInput').value = '';
751
+
752
+ // Clear progress tracking
753
  progress = 0;
754
+ }, 800);
755
  })
756
+ .catch(error => {
757
+ // Upload failed
758
  clearInterval(progressInterval);
759
  progressDiv.style.display = 'none';
760
+
761
+ let errorMsg = error.message || 'Upload failed';
762
+ showStatus('❌ ' + errorMsg, 'error');
763
+
764
+ // Clear file input
765
  document.getElementById('fileInput').value = '';
766
  progress = 0;
767
  });
 
812
 
813
  @app.route('/upload', methods=['POST'])
814
  def upload_files():
815
+ """Handle document upload with proper error handling and status tracking"""
816
  files = request.files.getlist('files')
817
+
818
  if not files or len(files) == 0:
819
+ return jsonify({
820
+ 'success': False,
821
+ 'message': '❌ No files uploaded',
822
+ 'successful': 0,
823
+ 'failed': 0
824
+ })
825
+
826
+ successful = 0
827
+ failed = 0
828
+ errors = []
829
 
830
  try:
831
+ import tempfile
832
+ import os as os_module
833
+ import logging
834
 
835
  for file in files:
836
  if not file or not file.filename:
837
  continue
838
 
839
  filename = file.filename
840
+ temp_path = None
841
+
842
  try:
843
+ # Read file content
844
  file_content = file.read()
845
+
846
  if not file_content:
847
+ documents[filename] = {'status': 'error', 'error': 'Empty file', 'type': 'unknown'}
848
+ errors.append(f"{filename}: Empty file")
849
  failed += 1
850
  continue
851
 
852
+ # Determine file type
853
+ if filename.lower().endswith('.pdf'):
854
+ doc_type = 'pdf'
855
+ elif filename.lower().endswith('.csv'):
856
+ doc_type = 'csv'
857
+ else:
858
+ documents[filename] = {'status': 'error', 'error': 'Invalid file type', 'type': 'unknown'}
859
+ errors.append(f"{filename}: Not PDF or CSV")
860
  failed += 1
861
  continue
862
 
863
+ # Mark as processing
864
  documents[filename] = {
865
  'type': doc_type,
866
  'size': len(file_content),
 
868
  'uploaded_at': str(__import__('datetime').datetime.now())
869
  }
870
 
871
+ # Write to temporary file
 
 
 
872
  with tempfile.NamedTemporaryFile(delete=False, suffix=f".{doc_type}") as f:
873
  temp_path = f.name
874
  f.write(file_content)
875
+ f.flush()
876
 
877
+ # Extract text from file
878
  try:
 
879
  if doc_type == 'pdf':
880
  text = PDFProcessor.extract_text(temp_path)
881
  else:
882
  text = CSVProcessor.extract_text(temp_path)
883
+ except Exception as extract_error:
884
+ raise Exception(f"Text extraction failed: {str(extract_error)[:100]}")
885
 
886
+ if not text or len(text.strip()) == 0:
887
+ raise Exception("No text could be extracted from file")
888
 
889
+ # Create chunks
890
+ try:
891
  chunker = ChunkerService(512, 50)
892
  chunks = chunker.chunk_text(text, {"doc_id": filename})
893
 
894
+ if not chunks or len(chunks) == 0:
895
+ raise Exception("No chunks created from text")
896
+ except Exception as chunk_error:
897
+ raise Exception(f"Chunking failed: {str(chunk_error)[:100]}")
898
 
899
+ # Embed chunks
900
+ try:
901
  embedded_chunks = services['embedding_service'].embed_chunks(chunks)
902
+ if not embedded_chunks:
903
+ raise Exception("Embedding failed")
904
 
905
+ services['vector_db_service'].add_chunks(embedded_chunks)
906
+ except Exception as embed_error:
907
+ raise Exception(f"Embedding failed: {str(embed_error)[:100]}")
 
908
 
909
+ # Mark as ready
910
+ documents[filename] = {
911
+ 'type': doc_type,
912
+ 'size': len(file_content),
913
+ 'status': 'ready',
914
+ 'chunks': len(chunks),
915
+ 'uploaded_at': str(__import__('datetime').datetime.now())
916
+ }
917
+ successful += 1
918
 
919
  except Exception as file_error:
920
  error_msg = str(file_error)[:100]
921
  documents[filename] = {
922
+ 'type': doc_type if 'doc_type' in locals() else 'unknown',
923
  'status': 'error',
924
  'error': error_msg
925
  }
926
  errors.append(f"{filename}: {error_msg}")
927
  failed += 1
928
+ __import__('logging').error(f"Upload error for {filename}: {str(file_error)}")
929
+
930
+ finally:
931
+ # Clean up temporary file
932
+ if temp_path:
933
+ try:
934
+ os_module.unlink(temp_path)
935
+ except:
936
+ pass
937
 
938
  # Build response message
939
+ if successful == 0 and failed == 0:
940
+ message = '❌ No files processed'
941
+ elif successful > 0 and failed == 0:
942
+ message = f'✅ Successfully processed {successful} file(s)'
943
+ elif successful > 0 and failed > 0:
944
+ message = f'⚠️ Processed {successful} file(s), Failed {failed} file(s)'
945
+ else:
946
+ message = f'❌ Failed to process {failed} file(s)'
947
 
948
  return jsonify({
949
+ 'success': failed == 0 and successful > 0,
950
  'message': message,
951
  'successful': successful,
952
+ 'failed': failed,
953
+ 'errors': errors if errors else None
954
  })
955
+
956
  except Exception as e:
957
  error_msg = str(e)[:200]
958
+ __import__('logging').error(f"Upload endpoint error: {error_msg}")
959
+ return jsonify({
960
+ 'success': False,
961
+ 'message': f'❌ Upload error: {error_msg}',
962
+ 'successful': 0,
963
+ 'failed': len(files)
964
+ })
965
 
966
  @app.route('/query', methods=['POST'])
967
  def query():
968
  """RAG Query - with document retrieval"""
969
+ try:
970
+ initialize_groq_from_env()
971
+
972
+ if not services['retrieval_service']:
973
+ return jsonify({'success': False, 'error': '⚠️ GROQ_API_KEY not configured. Add it to HF Spaces Secrets.'})
974
 
975
+ data = request.json
976
+ query_text = data.get('query', '').strip()
977
+ if not query_text:
978
+ return jsonify({'success': False, 'error': 'Query is required'})
979
 
980
+ # Check if documents are uploaded
981
+ if not documents or len(documents) == 0:
982
+ return jsonify({'success': False, 'error': '❌ No documents uploaded. Please upload documents first.'})
983
+
984
+ try:
985
+ # Get embedding for query
986
+ query_embedding = services['embedding_service'].embed_text(query_text)
987
+
988
+ # Search for relevant documents
989
+ search_results = services['vector_db_service'].search(
990
+ query_embedding,
991
+ data.get('top_k', 5)
992
+ )
993
+
994
+ if not search_results or len(search_results) == 0:
995
+ return jsonify({'success': False, 'error': '❌ No relevant documents found. Try a different query.'})
996
+
997
+ # Generate answer using RAG pipeline
998
+ result = services['retrieval_service'].generate_with_pipeline(
999
+ query_text,
1000
+ search_results,
1001
+ data.get('model', 'llama-3.1-8b-instant'),
1002
+ rag_mode=data.get('rag_mode', 'simple'),
1003
+ temperature=float(data.get('temperature', 0.7)),
1004
+ max_tokens=int(data.get('max_tokens', 1024))
1005
+ )
1006
+
1007
+ return jsonify({'success': True, 'result': result})
1008
+
1009
+ except __import__('requests').exceptions.Timeout:
1010
+ return jsonify({'success': False, 'error': 'Request timeout - Groq API is slow. Try again.'})
1011
+ except Exception as query_error:
1012
+ return jsonify({'success': False, 'error': f'Query error: {str(query_error)[:100]}'})
1013
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1014
  except Exception as e:
1015
+ return jsonify({'success': False, 'error': f'Error: {str(e)[:100]}'})
1016
 
1017
 
1018
  @app.route('/chat', methods=['POST'])