Spaces:
Build error
Build error
| """Transactable Gradio App for Hugging Face Spaces.""" | |
| import asyncio | |
| import os | |
| import gradio as gr | |
| import httpx | |
| # API configuration | |
| API_BASE_URL = os.getenv("API_BASE_URL", "https://transactable-api-4uhaa7eqfq-uc.a.run.app") | |
| def get_headers(api_key: str): | |
| headers = {"Content-Type": "application/json"} | |
| if api_key: | |
| headers["X-API-Key"] = api_key | |
| return headers | |
| # ============================================================================= | |
| # API FUNCTIONS | |
| # ============================================================================= | |
| async def upload_files(files, api_key: str, progress=gr.Progress()): | |
| """Upload files with progress tracking.""" | |
| if not files: | |
| return "Please select files to upload.", "" | |
| if not isinstance(files, list): | |
| files = [files] | |
| total = len(files) | |
| file_ids = [] | |
| failed = 0 | |
| semaphore = asyncio.Semaphore(10) | |
| async def upload_one(file): | |
| nonlocal failed | |
| async with semaphore: | |
| try: | |
| async with httpx.AsyncClient(timeout=60.0) as client: | |
| headers = {"X-API-Key": api_key} if api_key else {} | |
| with open(file.name, "rb") as f: | |
| response = await client.post( | |
| f"{API_BASE_URL}/api/v1/files/upload", | |
| files={"file": (file.name.split("/")[-1], f)}, | |
| headers=headers, | |
| ) | |
| if response.status_code == 200: | |
| return response.json().get("id") | |
| except Exception: | |
| pass | |
| failed += 1 | |
| return None | |
| progress(0, desc="Uploading...") | |
| batch_size = 50 | |
| for i in range(0, total, batch_size): | |
| batch = files[i : i + batch_size] | |
| results = await asyncio.gather(*[upload_one(f) for f in batch]) | |
| file_ids.extend([r for r in results if r]) | |
| progress((i + len(batch)) / total) | |
| return ( | |
| f"**Uploaded:** {len(file_ids)} | **Failed:** {failed} | **Total:** {total}", | |
| ",".join(file_ids), | |
| ) | |
| async def analyze_files(file_ids: str, analysis_type: str, api_key: str, progress=gr.Progress()): | |
| """Analyze uploaded files.""" | |
| if not file_ids: | |
| return "Upload files first." | |
| ids = [fid.strip() for fid in file_ids.split(",") if fid.strip()] | |
| total = len(ids) | |
| if total == 0: | |
| return "No file IDs." | |
| results = {"success": 0, "failed": 0, "spending": 0, "categories": {}} | |
| semaphore = asyncio.Semaphore(5) | |
| async def analyze_one(file_id): | |
| async with semaphore: | |
| try: | |
| async with httpx.AsyncClient(timeout=180.0) as client: | |
| response = await client.post( | |
| f"{API_BASE_URL}/api/v1/files/{file_id}/analyze", | |
| json={"analysis_type": analysis_type}, | |
| headers=get_headers(api_key), | |
| ) | |
| if response.status_code == 200: | |
| return response.json() | |
| except Exception: | |
| pass | |
| return None | |
| progress(0, desc="Analyzing...") | |
| batch_size = 20 | |
| for i in range(0, total, batch_size): | |
| batch = ids[i : i + batch_size] | |
| batch_results = await asyncio.gather(*[analyze_one(fid) for fid in batch]) | |
| for r in batch_results: | |
| if r: | |
| results["success"] += 1 | |
| results["spending"] += r.get("total_spending", 0) or 0 | |
| for cat, amt in (r.get("categories") or {}).items(): | |
| results["categories"][cat] = results["categories"].get(cat, 0) + (amt or 0) | |
| else: | |
| results["failed"] += 1 | |
| progress((i + len(batch)) / total) | |
| output = f"""## Analysis Complete | |
| **✓ Success:** {results['success']} | **✗ Failed:** {results['failed']} | |
| **Total Spending:** ${results['spending']:,.2f} | |
| ### Top Categories | |
| """ | |
| for cat, amt in sorted(results["categories"].items(), key=lambda x: -x[1])[:10]: | |
| output += f"- {cat}: ${amt:,.2f}\n" | |
| return output | |
| async def ask_question(question: str, conversation_id: str, api_key: str): | |
| """Ask a question about documents.""" | |
| if not question.strip(): | |
| return "Enter a question.", conversation_id | |
| async with httpx.AsyncClient(timeout=60.0) as client: | |
| payload = {"question": question} | |
| if conversation_id: | |
| payload["conversation_id"] = conversation_id | |
| response = await client.post( | |
| f"{API_BASE_URL}/api/v1/ask", | |
| json=payload, | |
| headers=get_headers(api_key), | |
| ) | |
| if response.status_code == 200: | |
| data = response.json() | |
| return data.get("answer", "No answer."), data.get("conversation_id", conversation_id) | |
| return f"Error: {response.status_code}", conversation_id | |
| # ============================================================================= | |
| # GRADIO UI | |
| # ============================================================================= | |
| with gr.Blocks(title="Transactable", theme=gr.themes.Soft()) as app: | |
| gr.Markdown( | |
| """ | |
| # 💰 Transactable | |
| Upload financial documents, analyze spending, and ask questions. | |
| """ | |
| ) | |
| with gr.Row(): | |
| api_key = gr.Textbox( | |
| label="API Key", | |
| placeholder="Enter your API key", | |
| type="password", | |
| scale=3, | |
| ) | |
| with gr.Tabs(): | |
| # Upload Tab | |
| with gr.TabItem("📤 Upload & Analyze"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| files = gr.File( | |
| label="Documents (PDF, PNG, JPG)", | |
| file_count="multiple", | |
| file_types=[".pdf", ".png", ".jpg", ".jpeg"], | |
| ) | |
| upload_btn = gr.Button("⬆️ Upload", variant="primary") | |
| with gr.Column(): | |
| upload_status = gr.Markdown("Select files and click Upload.") | |
| file_ids = gr.Textbox(label="File IDs", lines=2) | |
| upload_btn.click(upload_files, [files, api_key], [upload_status, file_ids]) | |
| gr.Markdown("---") | |
| with gr.Row(): | |
| analysis_type = gr.Dropdown( | |
| ["spending", "income", "general"], | |
| value="spending", | |
| label="Analysis Type", | |
| ) | |
| analyze_btn = gr.Button("🔍 Analyze All", variant="primary") | |
| analysis_result = gr.Markdown() | |
| analyze_btn.click(analyze_files, [file_ids, analysis_type, api_key], analysis_result) | |
| # Q&A Tab | |
| with gr.TabItem("💬 Ask"): | |
| conversation = gr.State(value=None) | |
| question = gr.Textbox(label="Question", placeholder="What was my total spending?") | |
| ask_btn = gr.Button("Ask", variant="primary") | |
| answer = gr.Markdown() | |
| ask_btn.click(ask_question, [question, conversation, api_key], [answer, conversation]) | |
| question.submit(ask_question, [question, conversation, api_key], [answer, conversation]) | |
| gr.Markdown("---\n*Powered by [Transactable API](https://github.com/transactable)*") | |
| if __name__ == "__main__": | |
| app.launch() | |