Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, UploadFile, File, HTTPException | |
| from fastapi.responses import HTMLResponse, JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| import pandas as pd | |
| import openpyxl | |
| import numpy as np | |
| from sentence_transformers import SentenceTransformer | |
| from typing import List | |
| import io | |
| app = FastAPI() | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"] | |
| ) | |
| model = SentenceTransformer('all-MiniLM-L6-v2') | |
| HTML_CONTENT = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Analytics Dashboard</title> | |
| <script src="https://unpkg.com/react@18/umd/react.development.js"></script> | |
| <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> | |
| <script src="https://unpkg.com/recharts@2.1.9/umd/Recharts.js"></script> | |
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| function App() { | |
| const [file, setFile] = React.useState(null); | |
| const [data, setData] = React.useState(null); | |
| const [query, setQuery] = React.useState(''); | |
| const [insights, setInsights] = React.useState([]); | |
| const [loading, setLoading] = React.useState(false); | |
| const [error, setError] = React.useState(null); | |
| const handleFileChange = (event) => { | |
| setFile(event.target.files[0]); | |
| setError(null); | |
| }; | |
| const handleUpload = async () => { | |
| if (!file) return; | |
| setLoading(true); | |
| setError(null); | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch('/api/process-file', { | |
| method: 'POST', | |
| body: formData, | |
| }); | |
| if (!response.ok) { | |
| const error = await response.json(); | |
| throw new Error(error.detail || 'Upload failed'); | |
| } | |
| const result = await response.json(); | |
| setData(result); | |
| } catch (err) { | |
| setError(err.message); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handleQuery = async () => { | |
| if (!data || !query) return; | |
| setLoading(true); | |
| try { | |
| const response = await fetch('/api/query', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({query, embeddings: data.embeddings}) | |
| }); | |
| if (!response.ok) throw new Error('Query failed'); | |
| const result = await response.json(); | |
| setInsights(result.similar_indices.map(idx => data.raw_data[idx])); | |
| } catch (err) { | |
| setError(err.message); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="p-6 max-w-6xl mx-auto"> | |
| <h1 className="text-3xl font-bold mb-8">Data Analytics Dashboard</h1> | |
| {error && ( | |
| <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"> | |
| {error} | |
| </div> | |
| )} | |
| <div className="space-y-4"> | |
| <div className="flex flex-col gap-2"> | |
| <input | |
| type="file" | |
| onChange={handleFileChange} | |
| accept=".xlsx,.csv" | |
| className="block w-full text-sm border rounded p-2" | |
| disabled={loading} | |
| /> | |
| <button | |
| onClick={handleUpload} | |
| disabled={!file || loading} | |
| className="bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50" | |
| > | |
| {loading ? 'Processing...' : 'Upload'} | |
| </button> | |
| </div> | |
| {data && ( | |
| <div className="space-y-4"> | |
| <div className="flex gap-2"> | |
| <input | |
| type="text" | |
| value={query} | |
| onChange={(e) => setQuery(e.target.value)} | |
| placeholder="Ask a question about your data..." | |
| className="flex-1 p-2 border rounded" | |
| disabled={loading} | |
| /> | |
| <button | |
| onClick={handleQuery} | |
| className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50" | |
| disabled={loading || !query} | |
| > | |
| Search | |
| </button> | |
| </div> | |
| {insights.length > 0 && ( | |
| <div className="mt-8"> | |
| <h3 className="text-xl font-semibold mb-4">Related Insights</h3> | |
| <div className="grid gap-4"> | |
| {insights.map((insight, idx) => ( | |
| <div key={idx} className="p-4 bg-gray-50 rounded shadow"> | |
| {Object.entries(insight).map(([key, value]) => ( | |
| <div key={key} className="mb-1"> | |
| <span className="font-medium">{key}:</span>{' '} | |
| {value} | |
| </div> | |
| ))} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| ReactDOM.render(<App />, document.getElementById('root')); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| class QueryRequest(BaseModel): | |
| query: str | |
| embeddings: List[List[float]] | |
| async def root(): | |
| return HTMLResponse(HTML_CONTENT) | |
| async def process_file(file: UploadFile = File(...)): | |
| if not file.filename: | |
| raise HTTPException(status_code=400, detail="No file provided") | |
| try: | |
| contents = await file.read() | |
| if file.filename.endswith('.xlsx'): | |
| wb = openpyxl.load_workbook(io.BytesIO(contents), read_only=True) | |
| sheet = wb.active | |
| data = [] | |
| headers = next(sheet.rows) | |
| header_values = [cell.value for cell in headers] | |
| for row in sheet.rows: | |
| if row != headers: | |
| data.append({h: cell.value for h, cell in zip(header_values, row)}) | |
| df = pd.DataFrame(data) | |
| elif file.filename.endswith('.csv'): | |
| df = pd.read_csv(io.BytesIO(contents)) | |
| else: | |
| raise HTTPException(status_code=400, detail="Unsupported file type") | |
| # Convert timestamps to strings | |
| for col in df.select_dtypes(include=['datetime64[ns]']).columns: | |
| df[col] = df[col].astype(str) | |
| df = df.replace({np.nan: None}) | |
| text_reps = [" ".join(f"{col}: {str(val)}" for col, val in row.items() if val is not None) | |
| for _, row in df.iterrows()] | |
| embeddings = model.encode(text_reps) | |
| # Convert DataFrame to JSON-serializable format | |
| raw_data = df.to_dict('records') | |
| # Convert any non-serializable types to strings | |
| for record in raw_data: | |
| for key, value in record.items(): | |
| if not isinstance(value, (str, int, float, bool, type(None))): | |
| record[key] = str(value) | |
| return JSONResponse({ | |
| 'embeddings': embeddings.tolist(), | |
| 'metadata': { | |
| 'columns': list(df.columns), | |
| 'row_count': len(df) | |
| }, | |
| 'raw_data': raw_data | |
| }) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def query_data(request: QueryRequest): | |
| try: | |
| query_embedding = model.encode([request.query])[0] | |
| similarities = np.dot(request.embeddings, query_embedding) | |
| return JSONResponse({"similar_indices": np.argsort(similarities)[-5:][::-1].tolist()}) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) |