Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
@@ -771,26 +771,57 @@ from fastapi.responses import FileResponse # Add this import at the top
|
|
771 |
|
772 |
|
773 |
|
|
|
|
|
774 |
@app.post("/visualize/natural")
|
775 |
@limiter.limit("5/minute")
|
776 |
async def visualize_with_natural_language(
|
777 |
request: Request,
|
778 |
file: UploadFile = File(...),
|
779 |
-
prompt: str = Form(""),
|
780 |
style: str = Form("seaborn-v0_8")
|
781 |
):
|
782 |
try:
|
783 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
784 |
if file_ext not in {"xlsx", "xls"}:
|
785 |
-
raise HTTPException(400, "Only Excel files are supported for visualization")
|
786 |
-
|
787 |
-
#
|
788 |
-
|
789 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
790 |
# Generate smart prompt if none provided
|
791 |
if not prompt.strip():
|
792 |
prompt = generate_smart_prompt(df)
|
793 |
-
|
|
|
794 |
# Generate visualization request
|
795 |
vis_request = interpret_natural_language(prompt, df.columns.tolist())
|
796 |
if not vis_request:
|
@@ -807,7 +838,11 @@ async def visualize_with_natural_language(
|
|
807 |
|
808 |
# Execute the visualization code safely
|
809 |
local_vars = {'plt': plt, 'sns': sns, 'df': df}
|
810 |
-
|
|
|
|
|
|
|
|
|
811 |
|
812 |
# Save to bytes buffer
|
813 |
buffer = BytesIO()
|
@@ -825,23 +860,17 @@ async def visualize_with_natural_language(
|
|
825 |
"interpreted_parameters": vis_request.dict()
|
826 |
}
|
827 |
|
828 |
-
except HTTPException:
|
|
|
829 |
raise
|
830 |
except Exception as e:
|
831 |
-
logger.error(f"
|
832 |
raise HTTPException(500, detail=f"Visualization failed: {str(e)}")
|
833 |
-
return {
|
834 |
-
"status": "success",
|
835 |
-
"image_data": image_base64,
|
836 |
-
"code": visualization_code,
|
837 |
-
"interpreted_parameters": vis_request.dict()
|
838 |
-
}
|
839 |
|
840 |
-
|
841 |
-
|
842 |
-
|
843 |
-
|
844 |
-
raise HTTPException(500, detail=f"Visualization failed: {str(e)}")
|
845 |
@app.get("/visualize/styles")
|
846 |
@limiter.limit("10/minute")
|
847 |
async def list_available_styles(request: Request):
|
|
|
771 |
|
772 |
|
773 |
|
774 |
+
# [Previous imports remain exactly the same...]
|
775 |
+
|
776 |
@app.post("/visualize/natural")
|
777 |
@limiter.limit("5/minute")
|
778 |
async def visualize_with_natural_language(
|
779 |
request: Request,
|
780 |
file: UploadFile = File(...),
|
781 |
+
prompt: str = Form(""),
|
782 |
style: str = Form("seaborn-v0_8")
|
783 |
):
|
784 |
try:
|
785 |
+
# First verify the file exists and has content
|
786 |
+
if not file.filename:
|
787 |
+
raise HTTPException(400, "No file uploaded")
|
788 |
+
|
789 |
+
# Read content first (don't rely just on extension)
|
790 |
+
content = await file.read()
|
791 |
+
if not content:
|
792 |
+
raise HTTPException(400, "Empty file uploaded")
|
793 |
+
|
794 |
+
# Verify Excel signature (magic numbers)
|
795 |
+
def is_valid_excel(content: bytes) -> bool:
|
796 |
+
excel_signatures = [
|
797 |
+
b'\x50\x4B\x03\x04', # ZIP header (modern Excel)
|
798 |
+
b'\xD0\xCF\x11\xE0' # OLE header (older Excel)
|
799 |
+
]
|
800 |
+
return any(content.startswith(sig) for sig in excel_signatures)
|
801 |
+
|
802 |
+
if not is_valid_excel(content):
|
803 |
+
raise HTTPException(400, "File doesn't contain Excel data")
|
804 |
+
|
805 |
+
# Now check extension for supported types
|
806 |
+
file_ext = file.filename.split('.')[-1].lower()
|
807 |
if file_ext not in {"xlsx", "xls"}:
|
808 |
+
raise HTTPException(400, "Only Excel files (.xlsx, .xls) are supported for visualization")
|
809 |
+
|
810 |
+
# Now try to process
|
811 |
+
try:
|
812 |
+
df = pd.read_excel(BytesIO(content))
|
813 |
+
except Exception as e:
|
814 |
+
logger.error(f"Excel read error: {str(e)}")
|
815 |
+
raise HTTPException(400, f"Invalid Excel file: {str(e)}")
|
816 |
+
|
817 |
+
if df.empty:
|
818 |
+
raise HTTPException(400, "The Excel file contains no data")
|
819 |
+
|
820 |
# Generate smart prompt if none provided
|
821 |
if not prompt.strip():
|
822 |
prompt = generate_smart_prompt(df)
|
823 |
+
logger.info(f"Generated automatic prompt: {prompt}")
|
824 |
+
|
825 |
# Generate visualization request
|
826 |
vis_request = interpret_natural_language(prompt, df.columns.tolist())
|
827 |
if not vis_request:
|
|
|
838 |
|
839 |
# Execute the visualization code safely
|
840 |
local_vars = {'plt': plt, 'sns': sns, 'df': df}
|
841 |
+
try:
|
842 |
+
exec(visualization_code, globals(), local_vars)
|
843 |
+
except Exception as e:
|
844 |
+
logger.error(f"Visualization code execution failed: {str(e)}")
|
845 |
+
raise HTTPException(400, f"Failed to generate visualization: {str(e)}")
|
846 |
|
847 |
# Save to bytes buffer
|
848 |
buffer = BytesIO()
|
|
|
860 |
"interpreted_parameters": vis_request.dict()
|
861 |
}
|
862 |
|
863 |
+
except HTTPException as he:
|
864 |
+
logger.error(f"HTTPException in visualization: {he.detail}")
|
865 |
raise
|
866 |
except Exception as e:
|
867 |
+
logger.error(f"Unexpected error in visualization: {str(e)}\n{traceback.format_exc()}")
|
868 |
raise HTTPException(500, detail=f"Visualization failed: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
869 |
|
870 |
+
# [Rest of your code remains exactly the same...]
|
871 |
+
|
872 |
+
|
873 |
+
|
|
|
874 |
@app.get("/visualize/styles")
|
875 |
@limiter.limit("10/minute")
|
876 |
async def list_available_styles(request: Request):
|