Spaces:
Sleeping
Sleeping
Commit
·
b4b0135
1
Parent(s):
f266ceb
feat: Complete file upload V3 integration
Browse filesFix 4: Integrated Gradio file uploads with V3 architecture
What Changed:
- File uploads now go to S3 (sessions/{id}/files/{file_id}_{name})
- Creates records in MongoDB 'files' collection
- Generates presigned URLs (7-day expiry)
- Stores file metadata (file_id, size, mime_type, s3_key)
- Adds file_id reference to session
- Includes file_data in system messages
- Graceful fallback if S3 upload fails
Files Collection Now Populated:
- file_id, session_id, file_name
- s3_bucket, s3_key
- presigned_url, presigned_expires_at
- file_size, mime_type, uploaded_at
Benefits:
- Gradio UI and chat/unified API now in sync
- Files stored permanently in S3
- File access via presigned URLs
- Consistent behavior across interfaces
All 5 critical V3 fixes now complete!
app.py
CHANGED
|
@@ -752,7 +752,13 @@ def create_new_session():
|
|
| 752 |
|
| 753 |
|
| 754 |
def handle_file_upload(file_path, session_id):
|
| 755 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 756 |
if not file_path:
|
| 757 |
return None, json.dumps({
|
| 758 |
"status": "error",
|
|
@@ -764,31 +770,128 @@ def handle_file_upload(file_path, session_id):
|
|
| 764 |
|
| 765 |
file_name = os.path.basename(file_path)
|
| 766 |
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 792 |
|
| 793 |
|
| 794 |
def format_chat_history(history, new_user_msg, new_assistant_msg):
|
|
|
|
| 752 |
|
| 753 |
|
| 754 |
def handle_file_upload(file_path, session_id):
|
| 755 |
+
"""
|
| 756 |
+
Handle file upload with V3 architecture integration
|
| 757 |
+
- Uploads file to S3
|
| 758 |
+
- Creates record in files collection
|
| 759 |
+
- Generates presigned URL
|
| 760 |
+
- Stores file metadata in MongoDB
|
| 761 |
+
"""
|
| 762 |
if not file_path:
|
| 763 |
return None, json.dumps({
|
| 764 |
"status": "error",
|
|
|
|
| 770 |
|
| 771 |
file_name = os.path.basename(file_path)
|
| 772 |
|
| 773 |
+
try:
|
| 774 |
+
# V3: Upload to S3 and create file record
|
| 775 |
+
from services.s3_manager import get_s3_manager
|
| 776 |
+
from services.schemas import FileSchema, schema_to_dict
|
| 777 |
+
from datetime import datetime
|
| 778 |
+
|
| 779 |
+
s3 = get_s3_manager()
|
| 780 |
+
file_id = str(uuid.uuid4())
|
| 781 |
+
file_size = os.path.getsize(file_path) if os.path.exists(file_path) else 0
|
| 782 |
+
|
| 783 |
+
# Determine MIME type
|
| 784 |
+
mime_type = "application/pdf"
|
| 785 |
+
if file_path.lower().endswith(('.png', '.jpg', '.jpeg')):
|
| 786 |
+
mime_type = "image/jpeg"
|
| 787 |
+
elif file_path.lower().endswith('.gif'):
|
| 788 |
+
mime_type = "image/gif"
|
| 789 |
+
|
| 790 |
+
# Upload to S3
|
| 791 |
+
s3_key = f"sessions/{session_id}/files/{file_id}_{file_name}"
|
| 792 |
+
with open(file_path, 'rb') as f:
|
| 793 |
+
s3_result = s3.upload_file(
|
| 794 |
+
key=s3_key,
|
| 795 |
+
file_obj=f,
|
| 796 |
+
content_type=mime_type,
|
| 797 |
+
add_prefix=False
|
| 798 |
+
)
|
| 799 |
+
|
| 800 |
+
# Generate presigned URL (7-day max)
|
| 801 |
+
presigned = s3.generate_presigned_url(
|
| 802 |
+
s3_key,
|
| 803 |
+
expires_in=604800, # 7 days
|
| 804 |
+
add_prefix=False
|
| 805 |
+
)
|
| 806 |
+
|
| 807 |
+
# Create file record in MongoDB
|
| 808 |
+
mongodb_uri = os.getenv("MONGODB_URI")
|
| 809 |
+
mongodb_db = os.getenv("MONGODB_DB", "masterllm")
|
| 810 |
+
from pymongo import MongoClient
|
| 811 |
+
|
| 812 |
+
mongo_client = MongoClient(mongodb_uri)
|
| 813 |
+
files_collection = mongo_client[mongodb_db]["files"]
|
| 814 |
+
|
| 815 |
+
file_record = FileSchema(
|
| 816 |
+
file_id=file_id,
|
| 817 |
+
session_id=session_id,
|
| 818 |
+
uploaded_at=datetime.utcnow(),
|
| 819 |
+
file_name=file_name,
|
| 820 |
+
file_size=file_size,
|
| 821 |
+
mime_type=mime_type,
|
| 822 |
+
s3_bucket=s3.bucket_name,
|
| 823 |
+
s3_key=s3_key,
|
| 824 |
+
presigned_url=presigned["presigned_url"],
|
| 825 |
+
presigned_expires_at=presigned["presigned_expires_at"]
|
| 826 |
+
)
|
| 827 |
+
|
| 828 |
+
files_collection.insert_one(schema_to_dict(file_record))
|
| 829 |
+
|
| 830 |
+
print(f"✅ File uploaded to S3 and recorded: {file_id}")
|
| 831 |
+
|
| 832 |
+
# Update session with file reference
|
| 833 |
+
session_manager.update_session(session_id, {
|
| 834 |
+
"current_file": file_path, # Keep local path for backward compatibility
|
| 835 |
+
"current_file_id": file_id, # V3: file_id reference
|
| 836 |
+
"state": ConversationState.INITIAL
|
| 837 |
+
})
|
| 838 |
+
|
| 839 |
+
# Add system message with file info
|
| 840 |
+
session_manager.add_message(
|
| 841 |
+
session_id,
|
| 842 |
+
"system",
|
| 843 |
+
f"File uploaded: {file_name}",
|
| 844 |
+
file_data={"file_id": file_id, "file_name": file_name}
|
| 845 |
+
)
|
| 846 |
+
|
| 847 |
+
status = {
|
| 848 |
+
"status": "success",
|
| 849 |
+
"message": f"File '{file_name}' uploaded successfully",
|
| 850 |
+
"file_info": {
|
| 851 |
+
"file_id": file_id,
|
| 852 |
+
"name": file_name,
|
| 853 |
+
"size_bytes": file_size,
|
| 854 |
+
"s3_key": s3_key,
|
| 855 |
+
"presigned_url": presigned["presigned_url"],
|
| 856 |
+
"expires_at": presigned["presigned_expires_at"]
|
| 857 |
+
},
|
| 858 |
+
"next_action": "💬 Now tell me what you'd like to do with this document"
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
+
return file_path, json.dumps(status, indent=2), session_id
|
| 862 |
+
|
| 863 |
+
except Exception as e:
|
| 864 |
+
# Fallback to old behavior if V3 integration fails
|
| 865 |
+
print(f"⚠️ File upload V3 integration failed: {e}")
|
| 866 |
+
print(f" Falling back to local file path")
|
| 867 |
+
|
| 868 |
+
# Update session with local path only
|
| 869 |
+
session_manager.update_session(session_id, {
|
| 870 |
+
"current_file": file_path,
|
| 871 |
+
"state": ConversationState.INITIAL
|
| 872 |
+
})
|
| 873 |
+
|
| 874 |
+
# Add system message
|
| 875 |
+
session_manager.add_message(
|
| 876 |
+
session_id,
|
| 877 |
+
"system",
|
| 878 |
+
f"File uploaded: {file_name}"
|
| 879 |
+
)
|
| 880 |
+
|
| 881 |
+
status = {
|
| 882 |
+
"status": "partial_success",
|
| 883 |
+
"message": f"File '{file_name}' uploaded locally (S3 upload failed)",
|
| 884 |
+
"file_info": {
|
| 885 |
+
"name": file_name,
|
| 886 |
+
"path": file_path,
|
| 887 |
+
"size_bytes": os.path.getsize(file_path) if os.path.exists(file_path) else 0
|
| 888 |
+
},
|
| 889 |
+
"warning": str(e),
|
| 890 |
+
"next_action": "💬 Now tell me what you'd like to do with this document"
|
| 891 |
+
}
|
| 892 |
+
|
| 893 |
+
return file_path, json.dumps(status, indent=2), session_id
|
| 894 |
+
|
| 895 |
|
| 896 |
|
| 897 |
def format_chat_history(history, new_user_msg, new_assistant_msg):
|