Spaces:
Sleeping
Sleeping
Commit
Β·
9c60c5f
1
Parent(s):
79b6caa
initial code
Browse files- app.py +785 -0
- config/agents.yaml +20 -0
- config/tasks.yaml +30 -0
- crew.py +132 -0
- requirements.txt +3 -0
- tools/custom_tool.py +382 -0
app.py
ADDED
|
@@ -0,0 +1,785 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
# #main.py
|
| 4 |
+
# import sys
|
| 5 |
+
# import os
|
| 6 |
+
# import warnings
|
| 7 |
+
# from crew import DocProcessing
|
| 8 |
+
# import re
|
| 9 |
+
|
| 10 |
+
# # Fix Unicode encoding issues on Windows
|
| 11 |
+
# if sys.platform.startswith('win'):
|
| 12 |
+
# import codecs
|
| 13 |
+
# sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach())
|
| 14 |
+
# sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach())
|
| 15 |
+
|
| 16 |
+
# warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
| 17 |
+
|
| 18 |
+
# def determine_file_type(file_path):
|
| 19 |
+
# """
|
| 20 |
+
# Determine the file type based on the file extension.
|
| 21 |
+
|
| 22 |
+
# Args:
|
| 23 |
+
# file_path (str): Path to the file
|
| 24 |
+
|
| 25 |
+
# Returns:
|
| 26 |
+
# str: 'pdf' if the file is a PDF, 'image' otherwise
|
| 27 |
+
# """
|
| 28 |
+
# _, ext = os.path.splitext(file_path)
|
| 29 |
+
# if ext.lower() == '.pdf':
|
| 30 |
+
# return 'pdf'
|
| 31 |
+
# return 'image'
|
| 32 |
+
|
| 33 |
+
# def run():
|
| 34 |
+
# """
|
| 35 |
+
# Run the crew with file paths received from command line arguments.
|
| 36 |
+
# """
|
| 37 |
+
# # Get file paths from command line arguments
|
| 38 |
+
# file_paths = sys.argv[1:] if len(sys.argv) > 1 else []
|
| 39 |
+
|
| 40 |
+
# if not file_paths:
|
| 41 |
+
# print("No file paths provided. Usage: python main.py <file_path1> <file_path2> ...")
|
| 42 |
+
# return
|
| 43 |
+
|
| 44 |
+
# # Process the first file (you can modify this to handle multiple files if needed)
|
| 45 |
+
# file_path = file_paths[0]
|
| 46 |
+
# file_type = determine_file_type(file_path)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# # Prepare inputs for the CrewAI
|
| 50 |
+
# inputs = {
|
| 51 |
+
# "file_path": file_path,
|
| 52 |
+
# "file_type": file_type,
|
| 53 |
+
# }
|
| 54 |
+
|
| 55 |
+
# try:
|
| 56 |
+
# # Pass the inputs to the crew kickoff method
|
| 57 |
+
# result = DocProcessing().crew().kickoff(inputs=inputs)
|
| 58 |
+
|
| 59 |
+
# # Try to get the actual output content from CrewOutput
|
| 60 |
+
|
| 61 |
+
# return result
|
| 62 |
+
# except Exception as e:
|
| 63 |
+
# error_msg = f"An error occurred while running the crew: {e}"
|
| 64 |
+
# print(error_msg)
|
| 65 |
+
# raise Exception(error_msg)
|
| 66 |
+
|
| 67 |
+
# if __name__ == "__main__":
|
| 68 |
+
# run()
|
| 69 |
+
|
| 70 |
+
#&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
| 71 |
+
# import gradio as gr
|
| 72 |
+
# import os
|
| 73 |
+
# import tempfile
|
| 74 |
+
# import shutil
|
| 75 |
+
# from pathlib import Path
|
| 76 |
+
# import warnings
|
| 77 |
+
# from typing import Tuple, Optional
|
| 78 |
+
# import sys
|
| 79 |
+
|
| 80 |
+
# # Suppress warnings
|
| 81 |
+
# warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
| 82 |
+
|
| 83 |
+
# # Import your existing modules
|
| 84 |
+
# from crew import DocProcessing
|
| 85 |
+
# from tools.custom_tool import landing_ai_document_analysis
|
| 86 |
+
|
| 87 |
+
# def get_landing_ai_key():
|
| 88 |
+
# """Get Landing AI API key from environment (HF secrets or local .env)"""
|
| 89 |
+
# # Try multiple environment variable names for flexibility
|
| 90 |
+
# return (
|
| 91 |
+
# os.getenv("LANDING_AI_API_KEY") or # HuggingFace secrets or production
|
| 92 |
+
# os.getenv("LANDING_AI_API_KEY_LOCAL") or # Local development alternative
|
| 93 |
+
# os.getenv("LANDINGAI_API_KEY") or # Alternative naming
|
| 94 |
+
# os.getenv("LANDING_API_KEY") # Another alternative
|
| 95 |
+
# )
|
| 96 |
+
|
| 97 |
+
# def validate_api_key(api_key: str) -> bool:
|
| 98 |
+
# """Validate Anthropic API key format"""
|
| 99 |
+
# if not api_key:
|
| 100 |
+
# return False
|
| 101 |
+
# return api_key.startswith("sk-ant-") and len(api_key) > 20
|
| 102 |
+
|
| 103 |
+
# def determine_file_type(file_path: str) -> str:
|
| 104 |
+
# """Determine file type based on extension"""
|
| 105 |
+
# _, ext = os.path.splitext(file_path)
|
| 106 |
+
# return 'pdf' if ext.lower() == '.pdf' else 'image'
|
| 107 |
+
|
| 108 |
+
# def process_document(file, anthropic_api_key: str) -> Tuple[str, str]:
|
| 109 |
+
# """
|
| 110 |
+
# Process the uploaded document and return analysis + generated code
|
| 111 |
+
|
| 112 |
+
# Args:
|
| 113 |
+
# file: Uploaded file object from Gradio
|
| 114 |
+
# anthropic_api_key: User's Anthropic API key
|
| 115 |
+
|
| 116 |
+
# Returns:
|
| 117 |
+
# Tuple of (analysis_result, generated_code)
|
| 118 |
+
# """
|
| 119 |
+
# try:
|
| 120 |
+
# # Validate inputs
|
| 121 |
+
# if not file:
|
| 122 |
+
# return "β No file uploaded", ""
|
| 123 |
+
|
| 124 |
+
# if not validate_api_key(anthropic_api_key):
|
| 125 |
+
# return "β Invalid Anthropic API key. Please ensure it starts with 'sk-ant-' and is complete.", ""
|
| 126 |
+
|
| 127 |
+
# # Check if Landing AI key is available
|
| 128 |
+
# landing_ai_key = get_landing_ai_key()
|
| 129 |
+
# if not landing_ai_key:
|
| 130 |
+
# return "β Landing AI API key not configured. Please ensure LANDING_AI_API_KEY is set in environment variables or HuggingFace secrets.", ""
|
| 131 |
+
|
| 132 |
+
# # Set environment variables securely
|
| 133 |
+
# os.environ["ANTHROPIC_API_KEY"] = anthropic_api_key
|
| 134 |
+
# os.environ["LANDING_AI_API_KEY"] = landing_ai_key
|
| 135 |
+
|
| 136 |
+
# # Create temporary directory for processing
|
| 137 |
+
# with tempfile.TemporaryDirectory() as temp_dir:
|
| 138 |
+
# # Save uploaded file
|
| 139 |
+
# file_extension = Path(file.name).suffix
|
| 140 |
+
# temp_file_path = os.path.join(temp_dir, f"uploaded_file{file_extension}")
|
| 141 |
+
|
| 142 |
+
# # Copy uploaded file to temp location
|
| 143 |
+
# shutil.copy2(file.name, temp_file_path)
|
| 144 |
+
|
| 145 |
+
# # Determine file type
|
| 146 |
+
# file_type = determine_file_type(temp_file_path)
|
| 147 |
+
|
| 148 |
+
# # Prepare inputs for CrewAI
|
| 149 |
+
# inputs = {
|
| 150 |
+
# "file_path": temp_file_path,
|
| 151 |
+
# "file_type": file_type,
|
| 152 |
+
# }
|
| 153 |
+
|
| 154 |
+
# # Initialize and run the crew with the API key
|
| 155 |
+
# doc_processing = DocProcessing(anthropic_api_key=anthropic_api_key)
|
| 156 |
+
# result = doc_processing.crew().kickoff(inputs=inputs)
|
| 157 |
+
|
| 158 |
+
# # Extract results from CrewOutput
|
| 159 |
+
# if hasattr(result, 'tasks_output') and result.tasks_output:
|
| 160 |
+
# analysis_result = ""
|
| 161 |
+
# generated_code = ""
|
| 162 |
+
|
| 163 |
+
# for i, task_output in enumerate(result.tasks_output):
|
| 164 |
+
# if i == 0: # First task is document analysis
|
| 165 |
+
# analysis_result = str(task_output.raw) if hasattr(task_output, 'raw') else str(task_output)
|
| 166 |
+
# elif i == 1: # Second task is code implementation
|
| 167 |
+
# generated_code = str(task_output.raw) if hasattr(task_output, 'raw') else str(task_output)
|
| 168 |
+
|
| 169 |
+
# return analysis_result, generated_code
|
| 170 |
+
# else:
|
| 171 |
+
# # Fallback if structure is different
|
| 172 |
+
# result_str = str(result)
|
| 173 |
+
# return result_str, "Code generation completed. Check the analysis section for details."
|
| 174 |
+
|
| 175 |
+
# except Exception as e:
|
| 176 |
+
# error_msg = f"β Error processing document: {str(e)}"
|
| 177 |
+
# return error_msg, ""
|
| 178 |
+
|
| 179 |
+
# finally:
|
| 180 |
+
# # Clean up environment variables for security
|
| 181 |
+
# if "ANTHROPIC_API_KEY" in os.environ:
|
| 182 |
+
# del os.environ["ANTHROPIC_API_KEY"]
|
| 183 |
+
|
| 184 |
+
# def create_demo_interface():
|
| 185 |
+
# """Create the Gradio interface"""
|
| 186 |
+
|
| 187 |
+
# # Custom CSS for better styling
|
| 188 |
+
# custom_css = """
|
| 189 |
+
# .gradio-container {
|
| 190 |
+
# max-width: 1200px !important;
|
| 191 |
+
# margin: auto !important;
|
| 192 |
+
# }
|
| 193 |
+
# .header {
|
| 194 |
+
# text-align: center;
|
| 195 |
+
# margin-bottom: 2rem;
|
| 196 |
+
# }
|
| 197 |
+
# .api-key-input {
|
| 198 |
+
# margin-bottom: 1rem;
|
| 199 |
+
# }
|
| 200 |
+
# .output-section {
|
| 201 |
+
# margin-top: 2rem;
|
| 202 |
+
# }
|
| 203 |
+
# """
|
| 204 |
+
|
| 205 |
+
# with gr.Blocks(css=custom_css, title="Document to Code Converter") as demo:
|
| 206 |
+
# gr.HTML("""
|
| 207 |
+
# <div class="header">
|
| 208 |
+
# <h1>π Document to Code Converter</h1>
|
| 209 |
+
# <p>Upload your design documents (images/PDFs) and convert them to working HTML/CSS/JS code!</p>
|
| 210 |
+
# </div>
|
| 211 |
+
# """)
|
| 212 |
+
|
| 213 |
+
# with gr.Row():
|
| 214 |
+
# with gr.Column(scale=1):
|
| 215 |
+
# gr.HTML("<h3>π€ Upload & Configuration</h3>")
|
| 216 |
+
|
| 217 |
+
# # API Key input
|
| 218 |
+
# api_key_input = gr.Textbox(
|
| 219 |
+
# label="π Anthropic API Key",
|
| 220 |
+
# placeholder="sk-ant-...",
|
| 221 |
+
# type="password",
|
| 222 |
+
# info="Enter your Anthropic API key. It will be used securely and not stored.",
|
| 223 |
+
# elem_classes=["api-key-input"]
|
| 224 |
+
# )
|
| 225 |
+
|
| 226 |
+
# # File upload
|
| 227 |
+
# file_input = gr.File(
|
| 228 |
+
# label="π Upload Document",
|
| 229 |
+
# file_types=[".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff"],
|
| 230 |
+
|
| 231 |
+
# )
|
| 232 |
+
|
| 233 |
+
# # Process button
|
| 234 |
+
# process_btn = gr.Button(
|
| 235 |
+
# "π Process Document",
|
| 236 |
+
# variant="primary",
|
| 237 |
+
# size="lg"
|
| 238 |
+
# )
|
| 239 |
+
|
| 240 |
+
# # Status indicator
|
| 241 |
+
# gr.HTML("""
|
| 242 |
+
# <div style="margin-top: 1rem; padding: 1rem; background: #f0f9ff; border-radius: 8px; border-left: 4px solid #0ea5e9;">
|
| 243 |
+
# <h4 style="margin: 0; color: #0c4a6e;">βΉοΈ How it works:</h4>
|
| 244 |
+
# <ol style="margin: 0.5rem 0; color: #164e63;">
|
| 245 |
+
# <li>Upload your design document (image or PDF)</li>
|
| 246 |
+
# <li>Enter your Anthropic API key</li>
|
| 247 |
+
# <li>Click "Process Document" to analyze and generate code</li>
|
| 248 |
+
# <li>Get detailed analysis and working HTML/CSS/JS code</li>
|
| 249 |
+
# </ol>
|
| 250 |
+
# </div>
|
| 251 |
+
# """)
|
| 252 |
+
|
| 253 |
+
# with gr.Row():
|
| 254 |
+
# with gr.Column(scale=1):
|
| 255 |
+
# gr.HTML("<h3 class='output-section'>π Document Analysis</h3>")
|
| 256 |
+
# analysis_output = gr.Textbox(
|
| 257 |
+
# label="Analysis Results",
|
| 258 |
+
# lines=15,
|
| 259 |
+
# placeholder="Document analysis will appear here...",
|
| 260 |
+
# show_copy_button=True
|
| 261 |
+
# )
|
| 262 |
+
|
| 263 |
+
# with gr.Column(scale=1):
|
| 264 |
+
# gr.HTML("<h3 class='output-section'>π» Generated Code</h3>")
|
| 265 |
+
# code_output = gr.Textbox(
|
| 266 |
+
# label="HTML/CSS/JS Code",
|
| 267 |
+
# lines=15,
|
| 268 |
+
# placeholder="Generated code will appear here...",
|
| 269 |
+
# show_copy_button=True
|
| 270 |
+
# )
|
| 271 |
+
|
| 272 |
+
# # Add examples section
|
| 273 |
+
# gr.HTML("""
|
| 274 |
+
# <div style="margin-top: 2rem; padding: 1rem; background: #f8fafc; border-radius: 8px;">
|
| 275 |
+
# <h4>π― Best Results Tips:</h4>
|
| 276 |
+
# <ul>
|
| 277 |
+
# <li><strong>High Quality Images:</strong> Use clear, high-resolution images for better analysis</li>
|
| 278 |
+
# <li><strong>Design Mockups:</strong> Website mockups, app designs, and UI sketches work great</li>
|
| 279 |
+
# <li><strong>PDF Documents:</strong> Multi-page design documents are supported</li>
|
| 280 |
+
# <li><strong>Clear Layouts:</strong> Well-organized designs produce better code structure</li>
|
| 281 |
+
# </ul>
|
| 282 |
+
# </div>
|
| 283 |
+
# """)
|
| 284 |
+
|
| 285 |
+
# # Set up the processing event
|
| 286 |
+
# process_btn.click(
|
| 287 |
+
# fn=process_document,
|
| 288 |
+
# inputs=[file_input, api_key_input],
|
| 289 |
+
# outputs=[analysis_output, code_output],
|
| 290 |
+
# show_progress=True
|
| 291 |
+
# )
|
| 292 |
+
|
| 293 |
+
# # Add footer
|
| 294 |
+
# gr.HTML("""
|
| 295 |
+
# <div style="text-align: center; margin-top: 2rem; padding: 1rem; border-top: 1px solid #e2e8f0; color: #64748b;">
|
| 296 |
+
# <p>π Your API keys are handled securely and never stored.
|
| 297 |
+
# <br>Built with CrewAI, LandingAI, and Anthropic Claude.</p>
|
| 298 |
+
# </div>
|
| 299 |
+
# """)
|
| 300 |
+
|
| 301 |
+
# return demo
|
| 302 |
+
|
| 303 |
+
# if __name__ == "__main__":
|
| 304 |
+
# # Test Landing AI connection on startup
|
| 305 |
+
# from tools.custom_tool import test_landing_ai_connection
|
| 306 |
+
|
| 307 |
+
# print("π Testing API connections...")
|
| 308 |
+
# landing_ai_available = test_landing_ai_connection()
|
| 309 |
+
|
| 310 |
+
# if not landing_ai_available:
|
| 311 |
+
# print("\nβ οΈ Warning: Landing AI API key not properly configured!")
|
| 312 |
+
# print("For local development: Set LANDING_AI_API_KEY in your .env file")
|
| 313 |
+
# print("For HuggingFace deployment: Add LANDING_AI_API_KEY to your Space secrets")
|
| 314 |
+
# print("The app will still start, but document analysis will fail without the API key.\n")
|
| 315 |
+
# else:
|
| 316 |
+
# print("β
Landing AI API key configured correctly!\n")
|
| 317 |
+
|
| 318 |
+
# # Create and launch the demo
|
| 319 |
+
# demo = create_demo_interface()
|
| 320 |
+
|
| 321 |
+
# # Launch configuration
|
| 322 |
+
# demo.launch(
|
| 323 |
+
# server_name="0.0.0.0", # Required for HuggingFace deployment
|
| 324 |
+
# server_port=7860, # Standard port for HuggingFace
|
| 325 |
+
# share=False, # Set to True for temporary public link during development
|
| 326 |
+
# debug=False, # Set to True for development
|
| 327 |
+
# show_error=True, # Show errors in the interface
|
| 328 |
+
# quiet=False # Set to True to reduce console output
|
| 329 |
+
# )
|
| 330 |
+
|
| 331 |
+
import gradio as gr
|
| 332 |
+
import os
|
| 333 |
+
import tempfile
|
| 334 |
+
import shutil
|
| 335 |
+
from pathlib import Path
|
| 336 |
+
import warnings
|
| 337 |
+
from typing import Tuple, Optional
|
| 338 |
+
import sys
|
| 339 |
+
|
| 340 |
+
# Suppress warnings
|
| 341 |
+
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
| 342 |
+
|
| 343 |
+
# Import your existing modules
|
| 344 |
+
from crew import DocProcessing
|
| 345 |
+
from tools.custom_tool import landing_ai_document_analysis
|
| 346 |
+
|
| 347 |
+
def get_landing_ai_key():
|
| 348 |
+
"""Get Landing AI API key from environment (HF secrets or local .env)"""
|
| 349 |
+
# Try multiple environment variable names for flexibility
|
| 350 |
+
return (
|
| 351 |
+
os.getenv("LANDING_AI_API_KEY") or # HuggingFace secrets or production
|
| 352 |
+
os.getenv("LANDING_AI_API_KEY_LOCAL") or # Local development alternative
|
| 353 |
+
os.getenv("LANDINGAI_API_KEY") or # Alternative naming
|
| 354 |
+
os.getenv("LANDING_API_KEY") # Another alternative
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
def validate_api_key(api_key: str) -> bool:
|
| 358 |
+
"""Validate Anthropic API key format"""
|
| 359 |
+
if not api_key:
|
| 360 |
+
return False
|
| 361 |
+
return api_key.startswith("sk-ant-") and len(api_key) > 20
|
| 362 |
+
|
| 363 |
+
def create_html_file(code_content: str, filename: str = "generated_page.html") -> str:
|
| 364 |
+
"""
|
| 365 |
+
Create an HTML file from the generated code
|
| 366 |
+
|
| 367 |
+
Args:
|
| 368 |
+
code_content: The HTML/CSS/JS code
|
| 369 |
+
filename: Name for the HTML file
|
| 370 |
+
|
| 371 |
+
Returns:
|
| 372 |
+
str: Path to the created file
|
| 373 |
+
"""
|
| 374 |
+
try:
|
| 375 |
+
# Create temporary file
|
| 376 |
+
temp_dir = tempfile.gettempdir()
|
| 377 |
+
file_path = os.path.join(temp_dir, filename)
|
| 378 |
+
|
| 379 |
+
# Clean the code content - remove any markdown formatting if present
|
| 380 |
+
cleaned_code = code_content.strip()
|
| 381 |
+
if cleaned_code.startswith("```html"):
|
| 382 |
+
cleaned_code = cleaned_code[7:]
|
| 383 |
+
if cleaned_code.endswith("```"):
|
| 384 |
+
cleaned_code = cleaned_code[:-3]
|
| 385 |
+
cleaned_code = cleaned_code.strip()
|
| 386 |
+
|
| 387 |
+
# Ensure it's a complete HTML document
|
| 388 |
+
if not cleaned_code.lower().startswith('<!doctype') and not cleaned_code.lower().startswith('<html'):
|
| 389 |
+
cleaned_code = f"""<!DOCTYPE html>
|
| 390 |
+
<html lang="en">
|
| 391 |
+
<head>
|
| 392 |
+
<meta charset="UTF-8">
|
| 393 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 394 |
+
<title>Generated Page</title>
|
| 395 |
+
</head>
|
| 396 |
+
<body>
|
| 397 |
+
{cleaned_code}
|
| 398 |
+
</body>
|
| 399 |
+
</html>"""
|
| 400 |
+
|
| 401 |
+
# Write to file
|
| 402 |
+
with open(file_path, 'w', encoding='utf-8') as f:
|
| 403 |
+
f.write(cleaned_code)
|
| 404 |
+
|
| 405 |
+
return file_path
|
| 406 |
+
|
| 407 |
+
except Exception as e:
|
| 408 |
+
print(f"Error creating HTML file: {e}")
|
| 409 |
+
return None
|
| 410 |
+
|
| 411 |
+
def determine_file_type(file_path: str) -> str:
|
| 412 |
+
"""Determine file type based on extension"""
|
| 413 |
+
_, ext = os.path.splitext(file_path)
|
| 414 |
+
return 'pdf' if ext.lower() == '.pdf' else 'image'
|
| 415 |
+
def prepare_preview_content(code_content: str) -> str:
|
| 416 |
+
"""
|
| 417 |
+
Prepare code content for safe preview in iframe
|
| 418 |
+
|
| 419 |
+
Args:
|
| 420 |
+
code_content: The HTML/CSS/JS code
|
| 421 |
+
|
| 422 |
+
Returns:
|
| 423 |
+
str: Cleaned and safe HTML content for preview
|
| 424 |
+
"""
|
| 425 |
+
try:
|
| 426 |
+
if not code_content or code_content.strip() == "":
|
| 427 |
+
return "<div style='padding: 20px; text-align: center; color: #666;'>No code generated yet...</div>"
|
| 428 |
+
|
| 429 |
+
# Clean the code content
|
| 430 |
+
cleaned_code = code_content.strip()
|
| 431 |
+
|
| 432 |
+
# Remove markdown formatting if present
|
| 433 |
+
if cleaned_code.startswith("```html"):
|
| 434 |
+
cleaned_code = cleaned_code[7:]
|
| 435 |
+
if cleaned_code.startswith("```"):
|
| 436 |
+
cleaned_code = cleaned_code[3:]
|
| 437 |
+
if cleaned_code.endswith("```"):
|
| 438 |
+
cleaned_code = cleaned_code[:-3]
|
| 439 |
+
cleaned_code = cleaned_code.strip()
|
| 440 |
+
|
| 441 |
+
# If it's not a complete HTML document, wrap it
|
| 442 |
+
if not cleaned_code.lower().startswith('<!doctype') and not cleaned_code.lower().startswith('<html'):
|
| 443 |
+
cleaned_code = f"""<!DOCTYPE html>
|
| 444 |
+
<html lang="en">
|
| 445 |
+
<head>
|
| 446 |
+
<meta charset="UTF-8">
|
| 447 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 448 |
+
<title>Preview</title>
|
| 449 |
+
<style>
|
| 450 |
+
body {{ margin: 0; padding: 10px; font-family: Arial, sans-serif; }}
|
| 451 |
+
* {{ box-sizing: border-box; }}
|
| 452 |
+
</style>
|
| 453 |
+
</head>
|
| 454 |
+
<body>
|
| 455 |
+
{cleaned_code}
|
| 456 |
+
</body>
|
| 457 |
+
</html>"""
|
| 458 |
+
|
| 459 |
+
# Wrap in iframe for safe rendering
|
| 460 |
+
iframe_content = f"""
|
| 461 |
+
<iframe
|
| 462 |
+
srcdoc="{cleaned_code.replace('"', '"')}"
|
| 463 |
+
style="width: 100%; height: 400px; border: 1px solid #ddd; border-radius: 8px;"
|
| 464 |
+
sandbox="allow-scripts allow-same-origin">
|
| 465 |
+
</iframe>
|
| 466 |
+
"""
|
| 467 |
+
|
| 468 |
+
return iframe_content
|
| 469 |
+
|
| 470 |
+
except Exception as e:
|
| 471 |
+
return f"<div style='padding: 20px; color: red;'>Error preparing preview: {str(e)}</div>"
|
| 472 |
+
|
| 473 |
+
def toggle_to_code():
|
| 474 |
+
"""Show code view and hide preview"""
|
| 475 |
+
return (
|
| 476 |
+
gr.update(visible=True), # code_output
|
| 477 |
+
gr.update(visible=False), # preview_output
|
| 478 |
+
gr.update(variant="primary"), # code_view_btn
|
| 479 |
+
gr.update(variant="secondary") # preview_btn
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
def toggle_to_preview(code_content):
|
| 483 |
+
"""Show preview and hide code view"""
|
| 484 |
+
preview_html = prepare_preview_content(code_content)
|
| 485 |
+
return (
|
| 486 |
+
gr.update(visible=False), # code_output
|
| 487 |
+
gr.update(visible=True, value=preview_html), # preview_output
|
| 488 |
+
gr.update(variant="secondary"), # code_view_btn
|
| 489 |
+
gr.update(variant="primary") # preview_btn
|
| 490 |
+
)
|
| 491 |
+
|
| 492 |
+
# def download_html_file(code_content):
|
| 493 |
+
# """Create downloadable HTML file"""
|
| 494 |
+
# if not code_content or code_content.strip() == "":
|
| 495 |
+
# return gr.update(visible=False)
|
| 496 |
+
|
| 497 |
+
# file_path = create_html_file(code_content)
|
| 498 |
+
# if file_path:
|
| 499 |
+
# return gr.update(visible=True, value=file_path)
|
| 500 |
+
# else:
|
| 501 |
+
# return gr.update(visible=False)
|
| 502 |
+
|
| 503 |
+
def process_document(file, anthropic_api_key: str) -> Tuple[str, str]:
|
| 504 |
+
"""
|
| 505 |
+
Process the uploaded document and return analysis + generated code
|
| 506 |
+
|
| 507 |
+
Args:
|
| 508 |
+
file: Uploaded file object from Gradio
|
| 509 |
+
anthropic_api_key: User's Anthropic API key
|
| 510 |
+
|
| 511 |
+
Returns:
|
| 512 |
+
Tuple of (analysis_result, generated_code)
|
| 513 |
+
"""
|
| 514 |
+
try:
|
| 515 |
+
# Validate inputs
|
| 516 |
+
if not file:
|
| 517 |
+
return "β No file uploaded", ""
|
| 518 |
+
|
| 519 |
+
if not validate_api_key(anthropic_api_key):
|
| 520 |
+
return "β Invalid Anthropic API key. Please ensure it starts with 'sk-ant-' and is complete.", ""
|
| 521 |
+
|
| 522 |
+
# Check if Landing AI key is available
|
| 523 |
+
landing_ai_key = get_landing_ai_key()
|
| 524 |
+
if not landing_ai_key:
|
| 525 |
+
return "β Landing AI API key not configured. Please ensure LANDING_AI_API_KEY is set in environment variables or HuggingFace secrets.", ""
|
| 526 |
+
|
| 527 |
+
# Set environment variables securely
|
| 528 |
+
os.environ["ANTHROPIC_API_KEY"] = anthropic_api_key
|
| 529 |
+
os.environ["LANDING_AI_API_KEY"] = landing_ai_key
|
| 530 |
+
|
| 531 |
+
# Create temporary directory for processing
|
| 532 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 533 |
+
# Save uploaded file
|
| 534 |
+
file_extension = Path(file.name).suffix
|
| 535 |
+
temp_file_path = os.path.join(temp_dir, f"uploaded_file{file_extension}")
|
| 536 |
+
|
| 537 |
+
# Copy uploaded file to temp location
|
| 538 |
+
shutil.copy2(file.name, temp_file_path)
|
| 539 |
+
|
| 540 |
+
# Determine file type
|
| 541 |
+
file_type = determine_file_type(temp_file_path)
|
| 542 |
+
|
| 543 |
+
# Prepare inputs for CrewAI
|
| 544 |
+
inputs = {
|
| 545 |
+
"file_path": temp_file_path,
|
| 546 |
+
"file_type": file_type,
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
# Initialize and run the crew with the API key
|
| 550 |
+
doc_processing = DocProcessing(anthropic_api_key=anthropic_api_key)
|
| 551 |
+
result = doc_processing.crew().kickoff(inputs=inputs)
|
| 552 |
+
|
| 553 |
+
# Extract results from CrewOutput
|
| 554 |
+
if hasattr(result, 'tasks_output') and result.tasks_output:
|
| 555 |
+
analysis_result = ""
|
| 556 |
+
generated_code = ""
|
| 557 |
+
|
| 558 |
+
for i, task_output in enumerate(result.tasks_output):
|
| 559 |
+
if i == 0: # First task is document analysis
|
| 560 |
+
analysis_result = str(task_output.raw) if hasattr(task_output, 'raw') else str(task_output)
|
| 561 |
+
elif i == 1: # Second task is code implementation
|
| 562 |
+
generated_code = str(task_output.raw) if hasattr(task_output, 'raw') else str(task_output)
|
| 563 |
+
|
| 564 |
+
return generated_code
|
| 565 |
+
# analysis_result,
|
| 566 |
+
else:
|
| 567 |
+
# Fallback if structure is different
|
| 568 |
+
result_str = str(result)
|
| 569 |
+
return result_str, "Code generation completed. Check the analysis section for details."
|
| 570 |
+
|
| 571 |
+
except Exception as e:
|
| 572 |
+
error_msg = f"β Error processing document: {str(e)}"
|
| 573 |
+
return error_msg, ""
|
| 574 |
+
|
| 575 |
+
finally:
|
| 576 |
+
# Clean up environment variables for security
|
| 577 |
+
if "ANTHROPIC_API_KEY" in os.environ:
|
| 578 |
+
del os.environ["ANTHROPIC_API_KEY"]
|
| 579 |
+
|
| 580 |
+
def create_demo_interface():
|
| 581 |
+
"""Create the Gradio interface"""
|
| 582 |
+
|
| 583 |
+
# Custom CSS for better styling
|
| 584 |
+
custom_css = """
|
| 585 |
+
.gradio-container {
|
| 586 |
+
max-width: 1200px !important;
|
| 587 |
+
margin: auto !important;
|
| 588 |
+
}
|
| 589 |
+
.header {
|
| 590 |
+
text-align: center;
|
| 591 |
+
margin-bottom: 2rem;
|
| 592 |
+
}
|
| 593 |
+
.api-key-input {
|
| 594 |
+
margin-bottom: 1rem;
|
| 595 |
+
}
|
| 596 |
+
.output-section {
|
| 597 |
+
margin-top: 2rem;
|
| 598 |
+
}
|
| 599 |
+
.toggle-buttons {
|
| 600 |
+
margin-bottom: 1rem;
|
| 601 |
+
}
|
| 602 |
+
.preview-container {
|
| 603 |
+
border: 1px solid #e2e8f0;
|
| 604 |
+
border-radius: 8px;
|
| 605 |
+
background: #f8fafc;
|
| 606 |
+
min-height: 400px;
|
| 607 |
+
}
|
| 608 |
+
"""
|
| 609 |
+
|
| 610 |
+
with gr.Blocks(css=custom_css, title="Document to Code Converter") as demo:
|
| 611 |
+
gr.HTML("""
|
| 612 |
+
<div class="header">
|
| 613 |
+
<h1>π Document to Code Converter</h1>
|
| 614 |
+
<p>Upload your design documents (images/PDFs) and convert them to working HTML/CSS/JS code!</p>
|
| 615 |
+
</div>
|
| 616 |
+
""")
|
| 617 |
+
|
| 618 |
+
with gr.Row():
|
| 619 |
+
with gr.Column(scale=1):
|
| 620 |
+
gr.HTML("<h3>π€ Upload & Configuration</h3>")
|
| 621 |
+
|
| 622 |
+
# API Key input
|
| 623 |
+
api_key_input = gr.Textbox(
|
| 624 |
+
label="π Anthropic API Key",
|
| 625 |
+
placeholder="sk-ant-...",
|
| 626 |
+
type="password",
|
| 627 |
+
info="Enter your Anthropic API key. It will be used securely and not stored.",
|
| 628 |
+
elem_classes=["api-key-input"]
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
# File upload
|
| 632 |
+
file_input = gr.File(
|
| 633 |
+
label="π Upload Document",
|
| 634 |
+
file_types=[".pdf", ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff"],
|
| 635 |
+
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
+
# Process button
|
| 639 |
+
process_btn = gr.Button(
|
| 640 |
+
"π Process Document",
|
| 641 |
+
variant="primary",
|
| 642 |
+
size="lg"
|
| 643 |
+
)
|
| 644 |
+
|
| 645 |
+
# Status indicator
|
| 646 |
+
gr.HTML("""
|
| 647 |
+
<div style="margin-top: 1rem; padding: 1rem; background: #f0f9ff; border-radius: 8px; border-left: 4px solid #0ea5e9;">
|
| 648 |
+
<h4 style="margin: 0; color: #0c4a6e;">βΉοΈ How it works:</h4>
|
| 649 |
+
<ol style="margin: 0.5rem 0; color: #164e63;">
|
| 650 |
+
<li>Upload your design document (image or PDF)</li>
|
| 651 |
+
<li>Enter your Anthropic API key</li>
|
| 652 |
+
<li>Click "Process Document" to analyze and generate code</li>
|
| 653 |
+
<li>Use toggle buttons to switch between code view and live preview</li>
|
| 654 |
+
|
| 655 |
+
</ol>
|
| 656 |
+
</div>
|
| 657 |
+
""")
|
| 658 |
+
gr.HTML("<h3 class='output-section'>π» Generated Code & Preview</h3>")
|
| 659 |
+
|
| 660 |
+
# Toggle buttons for code/preview
|
| 661 |
+
with gr.Row():
|
| 662 |
+
code_view_btn = gr.Button("π View Code", variant="primary", size="sm")
|
| 663 |
+
preview_btn = gr.Button("ποΈ Preview Result", variant="secondary", size="sm")
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
# Code output (visible by default)
|
| 667 |
+
code_output = gr.Textbox(
|
| 668 |
+
label="HTML/CSS/JS Code",
|
| 669 |
+
lines=15,
|
| 670 |
+
placeholder="Generated code will appear here...",
|
| 671 |
+
show_copy_button=True,
|
| 672 |
+
visible=True
|
| 673 |
+
)
|
| 674 |
+
|
| 675 |
+
# Preview output (hidden by default)
|
| 676 |
+
preview_output = gr.HTML(
|
| 677 |
+
label="Live Preview",
|
| 678 |
+
value="<div style='padding: 20px; text-align: center; color: #666;'>Preview will appear here after code generation...</div>",
|
| 679 |
+
visible=False
|
| 680 |
+
)
|
| 681 |
+
|
| 682 |
+
# with gr.Row():
|
| 683 |
+
# with gr.Column(scale=1):
|
| 684 |
+
# gr.HTML("<h3 class='output-section'>π Document Analysis</h3>")
|
| 685 |
+
# analysis_output = gr.Textbox(
|
| 686 |
+
# label="Analysis Results",
|
| 687 |
+
# lines=15,
|
| 688 |
+
# placeholder="Document analysis will appear here...",
|
| 689 |
+
# show_copy_button=True
|
| 690 |
+
# )
|
| 691 |
+
|
| 692 |
+
# with gr.Column(scale=1):
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
# Download file component (hidden)
|
| 696 |
+
# download_file = gr.File(
|
| 697 |
+
# label="Download",
|
| 698 |
+
# visible=False
|
| 699 |
+
# )
|
| 700 |
+
|
| 701 |
+
# Add examples section
|
| 702 |
+
gr.HTML("""
|
| 703 |
+
<div style="margin-top: 2rem; padding: 1rem; background: #f8fafc; border-radius: 8px;">
|
| 704 |
+
<h4>π― New Features:</h4>
|
| 705 |
+
<ul>
|
| 706 |
+
<li><strong>π Code View:</strong> See the raw HTML/CSS/JS code generated from your design</li>
|
| 707 |
+
<li><strong>ποΈ Live Preview:</strong> Toggle to see how your code will actually look when rendered</li>
|
| 708 |
+
<li><strong>π Real-time Toggle:</strong> Switch between code and preview instantly</li>
|
| 709 |
+
</ul>
|
| 710 |
+
<h4>π― Best Results Tips:</h4>
|
| 711 |
+
<ul>
|
| 712 |
+
<li><strong>High Quality Images:</strong> Use clear, high-resolution images for better analysis</li>
|
| 713 |
+
<li><strong>Design Mockups:</strong> Website mockups, app designs, and UI sketches work great</li>
|
| 714 |
+
<li><strong>PDF Documents:</strong> Multi-page design documents are supported</li>
|
| 715 |
+
<li><strong>Clear Layouts:</strong> Well-organized designs produce better code structure</li>
|
| 716 |
+
</ul>
|
| 717 |
+
</div>
|
| 718 |
+
""")
|
| 719 |
+
|
| 720 |
+
# Set up the processing event
|
| 721 |
+
process_btn.click(
|
| 722 |
+
fn=process_document,
|
| 723 |
+
inputs=[file_input, api_key_input],
|
| 724 |
+
outputs=[code_output],
|
| 725 |
+
# analysis_output,
|
| 726 |
+
show_progress=True
|
| 727 |
+
)
|
| 728 |
+
|
| 729 |
+
# Set up toggle events
|
| 730 |
+
code_view_btn.click(
|
| 731 |
+
fn=toggle_to_code,
|
| 732 |
+
inputs=[],
|
| 733 |
+
outputs=[code_output, preview_output, code_view_btn, preview_btn]
|
| 734 |
+
)
|
| 735 |
+
|
| 736 |
+
preview_btn.click(
|
| 737 |
+
fn=toggle_to_preview,
|
| 738 |
+
inputs=[code_output],
|
| 739 |
+
outputs=[code_output, preview_output, code_view_btn, preview_btn]
|
| 740 |
+
)
|
| 741 |
+
|
| 742 |
+
# Set up download event
|
| 743 |
+
# download_btn.click(
|
| 744 |
+
# fn=download_html_file,
|
| 745 |
+
# inputs=[code_output],
|
| 746 |
+
# outputs=[download_file]
|
| 747 |
+
# )
|
| 748 |
+
|
| 749 |
+
# Add footer
|
| 750 |
+
gr.HTML("""
|
| 751 |
+
<div style="text-align: center; margin-top: 2rem; padding: 1rem; border-top: 1px solid #e2e8f0; color: #64748b;">
|
| 752 |
+
<p>π Your API keys are handled securely and never stored.
|
| 753 |
+
<br>Built with CrewAI, LandingAI, and Anthropic Claude.</p>
|
| 754 |
+
</div>
|
| 755 |
+
""")
|
| 756 |
+
|
| 757 |
+
return demo
|
| 758 |
+
|
| 759 |
+
if __name__ == "__main__":
|
| 760 |
+
# Test Landing AI connection on startup
|
| 761 |
+
from tools.custom_tool import test_landing_ai_connection
|
| 762 |
+
|
| 763 |
+
print("π Testing API connections...")
|
| 764 |
+
landing_ai_available = test_landing_ai_connection()
|
| 765 |
+
|
| 766 |
+
if not landing_ai_available:
|
| 767 |
+
print("\nβ οΈ Warning: Landing AI API key not properly configured!")
|
| 768 |
+
print("For local development: Set LANDING_AI_API_KEY in your .env file")
|
| 769 |
+
print("For HuggingFace deployment: Add LANDING_AI_API_KEY to your Space secrets")
|
| 770 |
+
print("The app will still start, but document analysis will fail without the API key.\n")
|
| 771 |
+
else:
|
| 772 |
+
print("β
Landing AI API key configured correctly!\n")
|
| 773 |
+
|
| 774 |
+
# Create and launch the demo
|
| 775 |
+
demo = create_demo_interface()
|
| 776 |
+
|
| 777 |
+
# Launch configuration
|
| 778 |
+
demo.launch(
|
| 779 |
+
server_name="0.0.0.0", # Required for HuggingFace deployment
|
| 780 |
+
server_port=7860, # Standard port for HuggingFace
|
| 781 |
+
share=False, # Set to True for temporary public link during development
|
| 782 |
+
debug=False, # Set to True for development
|
| 783 |
+
show_error=True, # Show errors in the interface
|
| 784 |
+
quiet=False # Set to True to reduce console output
|
| 785 |
+
)
|
config/agents.yaml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#agents.yaml
|
| 2 |
+
document_analyst:
|
| 3 |
+
role: >
|
| 4 |
+
Senior Document Analyst
|
| 5 |
+
goal: >
|
| 6 |
+
To analyze the document
|
| 7 |
+
backstory: >
|
| 8 |
+
You have a keen eye to details and you are able to analyze the document with precision.
|
| 9 |
+
You role is to analyze the document and extract the exact information from it.DO NOT miss any point, cover all the points from the start to the end of the document.
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
developer_agent:
|
| 13 |
+
role: >
|
| 14 |
+
HTML/CSS/JS Implementation Specialist
|
| 15 |
+
goal: >
|
| 16 |
+
Turn design descriptions into working web code using HTML, CSS, and JavaScript.
|
| 17 |
+
backstory: >
|
| 18 |
+
You are a web developer who creates clean code using HTML, CSS, and JavaScript without frameworks.
|
| 19 |
+
You write semantically correct HTML, use inline CSS styles, and add vanilla JavaScript when needed. Your code is cross-browser compatible and matches designs exactly. You only output code - no explanations, no comments, no conversation.
|
| 20 |
+
You receive detailed descriptions and transform them directly into working code.
|
config/tasks.yaml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#tasks.yaml
|
| 2 |
+
document_analysis:
|
| 3 |
+
description: >
|
| 4 |
+
Analyze the document and extract the exact information from it. {file_path} is the path of the document.
|
| 5 |
+
{file_type} is the type of the document.
|
| 6 |
+
expected_output: >
|
| 7 |
+
The document is analyzed and the exact information is extracted from it.
|
| 8 |
+
Return in Markdown format. Include all the details and information from the document. Do not miss any details.
|
| 9 |
+
agent: document_analyst
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
code_implementation:
|
| 15 |
+
description: >
|
| 16 |
+
Turn design specifications into working web code using HTML, CSS, and JavaScript.
|
| 17 |
+
Do not miss any details from the design and complete all the said pages.
|
| 18 |
+
Use inline styles directly in HTML elements rather than separate stylesheets.
|
| 19 |
+
Write vanilla JavaScript without frameworks unless absolutely necessary.
|
| 20 |
+
Match all measurements, colors, positions, and text exactly as specified in the design.
|
| 21 |
+
expected_output: >
|
| 22 |
+
Clean HTML with inline CSS that perfectly matches the design specifications.
|
| 23 |
+
The code should work as a standalone file without external dependencies.
|
| 24 |
+
DO NOT,I REPEAT DO NOT include any comments ,thoughts or explanations - only write working code.
|
| 25 |
+
Example:
|
| 26 |
+
<!-- Header implementation with positioned styling -->
|
| 27 |
+
<div style="position: absolute; top: 20px; left: 10px; width: 100px; height: 50px; background: #f0f0f0;">
|
| 28 |
+
<h1 style="margin: 0; font-size: 1.2rem;">Header</h1>
|
| 29 |
+
</div>
|
| 30 |
+
agent: developer_agent
|
crew.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# # crew.py file of design_to_code
|
| 2 |
+
# from crewai import Agent, Crew, Process, Task, LLM
|
| 3 |
+
# from crewai.project import CrewBase, agent, crew, task
|
| 4 |
+
# from tools.custom_tool import landing_ai_document_analysis
|
| 5 |
+
# from dotenv import load_dotenv
|
| 6 |
+
# import os
|
| 7 |
+
|
| 8 |
+
# # Load environment variables
|
| 9 |
+
# load_dotenv()
|
| 10 |
+
|
| 11 |
+
# @CrewBase
|
| 12 |
+
# class DocProcessing():
|
| 13 |
+
# """DocProcessing crew"""
|
| 14 |
+
|
| 15 |
+
# agents_config = 'config/agents.yaml'
|
| 16 |
+
# tasks_config = 'config/tasks.yaml'
|
| 17 |
+
|
| 18 |
+
# llm = LLM(
|
| 19 |
+
# model = "claude-3-haiku-20240307",
|
| 20 |
+
# api_key = os.getenv("ANTHROPIC_API_KEY"),
|
| 21 |
+
# temperature = 0,
|
| 22 |
+
# )
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# @agent
|
| 26 |
+
# def document_analyst(self) -> Agent:
|
| 27 |
+
# return Agent(
|
| 28 |
+
# config=self.agents_config['document_analyst'],
|
| 29 |
+
# tools=[landing_ai_document_analysis],
|
| 30 |
+
# llm=self.llm,
|
| 31 |
+
# )
|
| 32 |
+
|
| 33 |
+
# @agent
|
| 34 |
+
# def developer_agent(self) -> Agent:
|
| 35 |
+
# return Agent(
|
| 36 |
+
# config=self.agents_config['developer_agent'],
|
| 37 |
+
# llm=self.llm,
|
| 38 |
+
# )
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# @task
|
| 42 |
+
# def document_analysis(self) -> Task:
|
| 43 |
+
# return Task(
|
| 44 |
+
# config=self.tasks_config['document_analysis'],
|
| 45 |
+
# )
|
| 46 |
+
|
| 47 |
+
# @task
|
| 48 |
+
# def code_implementation(self) -> Task:
|
| 49 |
+
# return Task(
|
| 50 |
+
# config=self.tasks_config['code_implementation'],
|
| 51 |
+
# )
|
| 52 |
+
|
| 53 |
+
# @crew
|
| 54 |
+
# def crew(self) -> Crew:
|
| 55 |
+
# """Creates the DocProcessing crew"""
|
| 56 |
+
# return Crew(
|
| 57 |
+
# agents=self.agents, # Automatically created by the @agent decorator
|
| 58 |
+
# tasks=self.tasks, # Automatically created by the @task decorator
|
| 59 |
+
# process=Process.sequential,
|
| 60 |
+
# verbose=True,
|
| 61 |
+
# )
|
| 62 |
+
from crewai import Agent, Crew, Process, Task, LLM
|
| 63 |
+
from crewai.project import CrewBase, agent, crew, task
|
| 64 |
+
from tools.custom_tool import landing_ai_document_analysis
|
| 65 |
+
from dotenv import load_dotenv
|
| 66 |
+
import os
|
| 67 |
+
|
| 68 |
+
# Load environment variables
|
| 69 |
+
load_dotenv()
|
| 70 |
+
|
| 71 |
+
@CrewBase
|
| 72 |
+
class DocProcessing():
|
| 73 |
+
"""DocProcessing crew"""
|
| 74 |
+
|
| 75 |
+
agents_config = 'config/agents.yaml'
|
| 76 |
+
tasks_config = 'config/tasks.yaml'
|
| 77 |
+
|
| 78 |
+
def __init__(self, anthropic_api_key: str = None):
|
| 79 |
+
"""
|
| 80 |
+
Initialize DocProcessing crew with API key
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
anthropic_api_key: The Anthropic API key to use for LLM
|
| 84 |
+
"""
|
| 85 |
+
# Use provided API key or fall back to environment variable
|
| 86 |
+
api_key = anthropic_api_key or os.getenv("ANTHROPIC_API_KEY")
|
| 87 |
+
|
| 88 |
+
if not api_key:
|
| 89 |
+
raise ValueError("Anthropic API key is required. Please provide it as parameter or set ANTHROPIC_API_KEY environment variable.")
|
| 90 |
+
|
| 91 |
+
self.llm = LLM(
|
| 92 |
+
model="claude-3-haiku-20240307",
|
| 93 |
+
api_key=api_key,
|
| 94 |
+
temperature=0,
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
@agent
|
| 98 |
+
def document_analyst(self) -> Agent:
|
| 99 |
+
return Agent(
|
| 100 |
+
config=self.agents_config['document_analyst'],
|
| 101 |
+
tools=[landing_ai_document_analysis],
|
| 102 |
+
llm=self.llm,
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
@agent
|
| 106 |
+
def developer_agent(self) -> Agent:
|
| 107 |
+
return Agent(
|
| 108 |
+
config=self.agents_config['developer_agent'],
|
| 109 |
+
llm=self.llm,
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
@task
|
| 113 |
+
def document_analysis(self) -> Task:
|
| 114 |
+
return Task(
|
| 115 |
+
config=self.tasks_config['document_analysis'],
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
@task
|
| 119 |
+
def code_implementation(self) -> Task:
|
| 120 |
+
return Task(
|
| 121 |
+
config=self.tasks_config['code_implementation'],
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
@crew
|
| 125 |
+
def crew(self) -> Crew:
|
| 126 |
+
"""Creates the DocProcessing crew"""
|
| 127 |
+
return Crew(
|
| 128 |
+
agents=self.agents, # Automatically created by the @agent decorator
|
| 129 |
+
tasks=self.tasks, # Automatically created by the @task decorator
|
| 130 |
+
process=Process.sequential,
|
| 131 |
+
verbose=True,
|
| 132 |
+
)
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
crewai
|
| 3 |
+
python-dotenv
|
tools/custom_tool.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# #crew_tool.py
|
| 2 |
+
# import json
|
| 3 |
+
# import os
|
| 4 |
+
# import requests
|
| 5 |
+
# from crewai.tools import tool
|
| 6 |
+
# from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
# # Load environment variables
|
| 9 |
+
# load_dotenv()
|
| 10 |
+
|
| 11 |
+
# def safe_format_with_indicators(text):
|
| 12 |
+
# """Replace emojis with text indicators"""
|
| 13 |
+
# if not isinstance(text, str):
|
| 14 |
+
# text = str(text)
|
| 15 |
+
|
| 16 |
+
# emoji_replacements = {
|
| 17 |
+
# 'π': '[BATTERY]',
|
| 18 |
+
# 'β
': '[SUCCESS]',
|
| 19 |
+
# 'β': '[ERROR]',
|
| 20 |
+
# 'π': '[DOCUMENT]',
|
| 21 |
+
# 'π': '[CHART]',
|
| 22 |
+
# 'β οΈ': '[WARNING]',
|
| 23 |
+
# 'π': '[SEARCH]',
|
| 24 |
+
# 'π‘': '[INSIGHT]',
|
| 25 |
+
# 'π―': '[TARGET]',
|
| 26 |
+
# 'β¨': '[HIGHLIGHT]',
|
| 27 |
+
# 'π': '[LAUNCH]',
|
| 28 |
+
# 'π': '[GROWTH]',
|
| 29 |
+
# 'π': '[DECLINE]',
|
| 30 |
+
# 'π§': '[TOOL]',
|
| 31 |
+
# 'π»': '[CODE]',
|
| 32 |
+
# 'π': '[STAR]',
|
| 33 |
+
# 'π¨': '[DESIGN]',
|
| 34 |
+
# 'π±': '[MOBILE]',
|
| 35 |
+
# 'π₯οΈ': '[DESKTOP]',
|
| 36 |
+
# 'π': '[LINK]',
|
| 37 |
+
# 'π': '[NOTE]',
|
| 38 |
+
# 'π': '[ACHIEVEMENT]',
|
| 39 |
+
# 'π°': '[MONEY]',
|
| 40 |
+
# 'π': '[CELEBRATION]',
|
| 41 |
+
# 'π': '[SECURE]',
|
| 42 |
+
# 'π': '[UNLOCKED]',
|
| 43 |
+
# 'β': '[RATING]',
|
| 44 |
+
# 'β€οΈ': '[FAVORITE]',
|
| 45 |
+
# 'π': '[THUMBS_UP]',
|
| 46 |
+
# 'π': '[THUMBS_DOWN]',
|
| 47 |
+
# 'π₯': '[HOT]',
|
| 48 |
+
# 'βοΈ': '[COLD]',
|
| 49 |
+
# 'π‘οΈ': '[TEMPERATURE]'
|
| 50 |
+
# }
|
| 51 |
+
|
| 52 |
+
# for emoji, replacement in emoji_replacements.items():
|
| 53 |
+
# text = text.replace(emoji, replacement)
|
| 54 |
+
|
| 55 |
+
# return text
|
| 56 |
+
|
| 57 |
+
# def format_api_response(response_data):
|
| 58 |
+
# """
|
| 59 |
+
# Format API response and replace emojis with safe indicators.
|
| 60 |
+
|
| 61 |
+
# Args:
|
| 62 |
+
# response_data: Raw API response (dict, str, or other)
|
| 63 |
+
|
| 64 |
+
# Returns:
|
| 65 |
+
# str: Formatted response with safe emoji replacements
|
| 66 |
+
# """
|
| 67 |
+
# try:
|
| 68 |
+
# if isinstance(response_data, dict):
|
| 69 |
+
# # Convert dict to readable format
|
| 70 |
+
# formatted_parts = []
|
| 71 |
+
|
| 72 |
+
# for key, value in response_data.items():
|
| 73 |
+
# if isinstance(value, (str, int, float)):
|
| 74 |
+
# safe_value = safe_format_with_indicators(str(value))
|
| 75 |
+
# formatted_parts.append(f"{key}: {safe_value}")
|
| 76 |
+
# elif isinstance(value, list):
|
| 77 |
+
# safe_items = [safe_format_with_indicators(str(item)) for item in value]
|
| 78 |
+
# formatted_parts.append(f"{key}: {', '.join(safe_items)}")
|
| 79 |
+
# elif isinstance(value, dict):
|
| 80 |
+
# # Handle nested dictionaries
|
| 81 |
+
# nested_items = []
|
| 82 |
+
# for nested_key, nested_value in value.items():
|
| 83 |
+
# safe_nested = safe_format_with_indicators(str(nested_value))
|
| 84 |
+
# nested_items.append(f"{nested_key}: {safe_nested}")
|
| 85 |
+
# formatted_parts.append(f"{key}: {'; '.join(nested_items)}")
|
| 86 |
+
|
| 87 |
+
# result = "\n".join(formatted_parts)
|
| 88 |
+
# return safe_format_with_indicators(result)
|
| 89 |
+
# else:
|
| 90 |
+
# return safe_format_with_indicators(str(response_data))
|
| 91 |
+
|
| 92 |
+
# except Exception as e:
|
| 93 |
+
# error_msg = f"Error formatting response: {str(e)}"
|
| 94 |
+
# return safe_format_with_indicators(error_msg)
|
| 95 |
+
|
| 96 |
+
# @tool("LandingAI Document Analysis")
|
| 97 |
+
# def landing_ai_document_analysis(file_path: str, file_type: str = "image") -> str:
|
| 98 |
+
# """
|
| 99 |
+
# Analyze images or PDFs using LandingAI's document analysis API.
|
| 100 |
+
|
| 101 |
+
# Args:
|
| 102 |
+
# file_path (str): Path to the image or PDF file to analyze
|
| 103 |
+
# file_type (str): Type of file, either "image" or "pdf"
|
| 104 |
+
|
| 105 |
+
# Returns:
|
| 106 |
+
# str: Analysis results from the API
|
| 107 |
+
# """
|
| 108 |
+
# # Get API key from environment variable
|
| 109 |
+
# api_key = os.getenv("LANDING_AI_API_KEY")
|
| 110 |
+
|
| 111 |
+
# # API endpoint
|
| 112 |
+
# url = "https://api.va.landing.ai/v1/tools/agentic-document-analysis"
|
| 113 |
+
|
| 114 |
+
# # Prepare the file for upload based on file_type
|
| 115 |
+
# with open(file_path, "rb") as file_obj:
|
| 116 |
+
# if file_type.lower() == "pdf":
|
| 117 |
+
# files = {"pdf": file_obj}
|
| 118 |
+
# else:
|
| 119 |
+
# files = {"image": file_obj}
|
| 120 |
+
|
| 121 |
+
# # Prepare headers with authentication
|
| 122 |
+
# headers = {"Authorization": f"Basic {api_key}"}
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# # Make the API request
|
| 127 |
+
# response = requests.post(url, files=files, headers=headers)
|
| 128 |
+
# if response.status_code == 200:
|
| 129 |
+
# try:
|
| 130 |
+
# response_json = response.json()
|
| 131 |
+
# formatted_result = format_api_response(response_json)
|
| 132 |
+
# # result += f"π ANALYSIS:\n{formatted_result}"
|
| 133 |
+
# return safe_format_with_indicators(formatted_result)
|
| 134 |
+
|
| 135 |
+
# except json.JSONDecodeError:
|
| 136 |
+
# # If response isn't JSON, format the text response
|
| 137 |
+
# safe_text = safe_format_with_indicators(response.text)
|
| 138 |
+
# return safe_format_with_indicators(safe_text)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
# return response.json()
|
| 142 |
+
|
| 143 |
+
import json
|
| 144 |
+
import os
|
| 145 |
+
import requests
|
| 146 |
+
from crewai.tools import tool
|
| 147 |
+
from dotenv import load_dotenv
|
| 148 |
+
|
| 149 |
+
# Load environment variables
|
| 150 |
+
load_dotenv()
|
| 151 |
+
|
| 152 |
+
def get_landing_ai_api_key():
|
| 153 |
+
"""
|
| 154 |
+
Get Landing AI API key from environment variables.
|
| 155 |
+
Tries multiple sources for compatibility with different deployment environments.
|
| 156 |
+
|
| 157 |
+
Returns:
|
| 158 |
+
str: The API key if found, None otherwise
|
| 159 |
+
"""
|
| 160 |
+
# Try different environment variable names for flexibility
|
| 161 |
+
api_key = (
|
| 162 |
+
os.getenv("LANDING_AI_API_KEY") or # HuggingFace secrets or production
|
| 163 |
+
os.getenv("LANDING_AI_API_KEY_LOCAL") or # Local development alternative
|
| 164 |
+
os.getenv("LANDINGAI_API_KEY") or # Alternative naming
|
| 165 |
+
os.getenv("LANDING_API_KEY") # Another alternative
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
return api_key
|
| 169 |
+
|
| 170 |
+
def safe_format_with_indicators(text):
|
| 171 |
+
"""Replace emojis with text indicators"""
|
| 172 |
+
if not isinstance(text, str):
|
| 173 |
+
text = str(text)
|
| 174 |
+
|
| 175 |
+
emoji_replacements = {
|
| 176 |
+
'π': '[BATTERY]',
|
| 177 |
+
'β
': '[SUCCESS]',
|
| 178 |
+
'β': '[ERROR]',
|
| 179 |
+
'π': '[DOCUMENT]',
|
| 180 |
+
'π': '[CHART]',
|
| 181 |
+
'β οΈ': '[WARNING]',
|
| 182 |
+
'π': '[SEARCH]',
|
| 183 |
+
'π‘': '[INSIGHT]',
|
| 184 |
+
'π―': '[TARGET]',
|
| 185 |
+
'β¨': '[HIGHLIGHT]',
|
| 186 |
+
'π': '[LAUNCH]',
|
| 187 |
+
'π': '[GROWTH]',
|
| 188 |
+
'π': '[DECLINE]',
|
| 189 |
+
'π§': '[TOOL]',
|
| 190 |
+
'π»': '[CODE]',
|
| 191 |
+
'π': '[STAR]',
|
| 192 |
+
'π¨': '[DESIGN]',
|
| 193 |
+
'π±': '[MOBILE]',
|
| 194 |
+
'π₯οΈ': '[DESKTOP]',
|
| 195 |
+
'π': '[LINK]',
|
| 196 |
+
'π': '[NOTE]',
|
| 197 |
+
'π': '[ACHIEVEMENT]',
|
| 198 |
+
'π°': '[MONEY]',
|
| 199 |
+
'π': '[CELEBRATION]',
|
| 200 |
+
'π': '[SECURE]',
|
| 201 |
+
'π': '[UNLOCKED]',
|
| 202 |
+
'β': '[RATING]',
|
| 203 |
+
'β€οΈ': '[FAVORITE]',
|
| 204 |
+
'π': '[THUMBS_UP]',
|
| 205 |
+
'π': '[THUMBS_DOWN]',
|
| 206 |
+
'π₯': '[HOT]',
|
| 207 |
+
'βοΈ': '[COLD]',
|
| 208 |
+
'π‘οΈ': '[TEMPERATURE]'
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
for emoji, replacement in emoji_replacements.items():
|
| 212 |
+
text = text.replace(emoji, replacement)
|
| 213 |
+
|
| 214 |
+
return text
|
| 215 |
+
|
| 216 |
+
def format_api_response(response_data):
|
| 217 |
+
"""
|
| 218 |
+
Format API response and replace emojis with safe indicators.
|
| 219 |
+
|
| 220 |
+
Args:
|
| 221 |
+
response_data: Raw API response (dict, str, or other)
|
| 222 |
+
|
| 223 |
+
Returns:
|
| 224 |
+
str: Formatted response with safe emoji replacements
|
| 225 |
+
"""
|
| 226 |
+
try:
|
| 227 |
+
if isinstance(response_data, dict):
|
| 228 |
+
# Convert dict to readable format
|
| 229 |
+
formatted_parts = []
|
| 230 |
+
|
| 231 |
+
for key, value in response_data.items():
|
| 232 |
+
if isinstance(value, (str, int, float)):
|
| 233 |
+
safe_value = safe_format_with_indicators(str(value))
|
| 234 |
+
formatted_parts.append(f"{key}: {safe_value}")
|
| 235 |
+
elif isinstance(value, list):
|
| 236 |
+
safe_items = [safe_format_with_indicators(str(item)) for item in value]
|
| 237 |
+
formatted_parts.append(f"{key}: {', '.join(safe_items)}")
|
| 238 |
+
elif isinstance(value, dict):
|
| 239 |
+
# Handle nested dictionaries
|
| 240 |
+
nested_items = []
|
| 241 |
+
for nested_key, nested_value in value.items():
|
| 242 |
+
safe_nested = safe_format_with_indicators(str(nested_value))
|
| 243 |
+
nested_items.append(f"{nested_key}: {safe_nested}")
|
| 244 |
+
formatted_parts.append(f"{key}: {'; '.join(nested_items)}")
|
| 245 |
+
|
| 246 |
+
result = "\n".join(formatted_parts)
|
| 247 |
+
return safe_format_with_indicators(result)
|
| 248 |
+
else:
|
| 249 |
+
return safe_format_with_indicators(str(response_data))
|
| 250 |
+
|
| 251 |
+
except Exception as e:
|
| 252 |
+
error_msg = f"Error formatting response: {str(e)}"
|
| 253 |
+
return safe_format_with_indicators(error_msg)
|
| 254 |
+
|
| 255 |
+
@tool("LandingAI Document Analysis")
|
| 256 |
+
def landing_ai_document_analysis(file_path: str, file_type: str = "image") -> str:
|
| 257 |
+
"""
|
| 258 |
+
Analyze images or PDFs using LandingAI's document analysis API.
|
| 259 |
+
|
| 260 |
+
Args:
|
| 261 |
+
file_path (str): Path to the image or PDF file to analyze
|
| 262 |
+
file_type (str): Type of file, either "image" or "pdf"
|
| 263 |
+
|
| 264 |
+
Returns:
|
| 265 |
+
str: Analysis results from the API
|
| 266 |
+
"""
|
| 267 |
+
try:
|
| 268 |
+
# Get API key with improved error handling
|
| 269 |
+
api_key = get_landing_ai_api_key()
|
| 270 |
+
|
| 271 |
+
if not api_key:
|
| 272 |
+
error_msg = "Landing AI API key not found. Please ensure LANDING_AI_API_KEY is set in environment variables or HuggingFace secrets."
|
| 273 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 274 |
+
|
| 275 |
+
# Validate file exists
|
| 276 |
+
if not os.path.exists(file_path):
|
| 277 |
+
error_msg = f"File not found: {file_path}"
|
| 278 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 279 |
+
|
| 280 |
+
# API endpoint
|
| 281 |
+
url = "https://api.va.landing.ai/v1/tools/agentic-document-analysis"
|
| 282 |
+
|
| 283 |
+
# Prepare the file for upload based on file_type
|
| 284 |
+
try:
|
| 285 |
+
with open(file_path, "rb") as file_obj:
|
| 286 |
+
if file_type.lower() == "pdf":
|
| 287 |
+
files = {"pdf": file_obj}
|
| 288 |
+
else:
|
| 289 |
+
files = {"image": file_obj}
|
| 290 |
+
|
| 291 |
+
# Prepare headers with authentication
|
| 292 |
+
headers = {"Authorization": f"Basic {api_key}"}
|
| 293 |
+
|
| 294 |
+
# Make the API request with timeout
|
| 295 |
+
response = requests.post(
|
| 296 |
+
url,
|
| 297 |
+
files=files,
|
| 298 |
+
headers=headers,
|
| 299 |
+
timeout=60 # 60 second timeout
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
# Handle different response status codes
|
| 303 |
+
if response.status_code == 200:
|
| 304 |
+
try:
|
| 305 |
+
response_json = response.json()
|
| 306 |
+
formatted_result = format_api_response(response_json)
|
| 307 |
+
return safe_format_with_indicators(formatted_result)
|
| 308 |
+
|
| 309 |
+
except json.JSONDecodeError:
|
| 310 |
+
# If response isn't JSON, format the text response
|
| 311 |
+
safe_text = safe_format_with_indicators(response.text)
|
| 312 |
+
return safe_format_with_indicators(safe_text)
|
| 313 |
+
|
| 314 |
+
elif response.status_code == 401:
|
| 315 |
+
error_msg = "Authentication failed. Please check your Landing AI API key."
|
| 316 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 317 |
+
|
| 318 |
+
elif response.status_code == 403:
|
| 319 |
+
error_msg = "Access forbidden. Your API key may not have the required permissions."
|
| 320 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 321 |
+
|
| 322 |
+
elif response.status_code == 429:
|
| 323 |
+
error_msg = "Rate limit exceeded. Please try again later."
|
| 324 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 325 |
+
|
| 326 |
+
elif response.status_code == 500:
|
| 327 |
+
error_msg = "Server error from Landing AI. Please try again later."
|
| 328 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 329 |
+
|
| 330 |
+
else:
|
| 331 |
+
error_msg = f"API request failed with status code {response.status_code}: {response.text}"
|
| 332 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 333 |
+
|
| 334 |
+
except FileNotFoundError:
|
| 335 |
+
error_msg = f"File not found: {file_path}"
|
| 336 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 337 |
+
|
| 338 |
+
except requests.exceptions.Timeout:
|
| 339 |
+
error_msg = "Request timed out. The file may be too large or the service is slow."
|
| 340 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 341 |
+
|
| 342 |
+
except requests.exceptions.ConnectionError:
|
| 343 |
+
error_msg = "Connection error. Please check your internet connection."
|
| 344 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 345 |
+
|
| 346 |
+
except requests.exceptions.RequestException as e:
|
| 347 |
+
error_msg = f"Request failed: {str(e)}"
|
| 348 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 349 |
+
|
| 350 |
+
except Exception as e:
|
| 351 |
+
error_msg = f"Unexpected error in document analysis: {str(e)}"
|
| 352 |
+
return safe_format_with_indicators(f"[ERROR] {error_msg}")
|
| 353 |
+
|
| 354 |
+
def test_landing_ai_connection():
|
| 355 |
+
"""
|
| 356 |
+
Test function to verify Landing AI API connection.
|
| 357 |
+
This can be called to verify the setup before processing documents.
|
| 358 |
+
|
| 359 |
+
Returns:
|
| 360 |
+
bool: True if API key is available and valid format, False otherwise
|
| 361 |
+
"""
|
| 362 |
+
api_key = get_landing_ai_api_key()
|
| 363 |
+
|
| 364 |
+
if not api_key:
|
| 365 |
+
print("β Landing AI API key not found in environment variables")
|
| 366 |
+
print("Available environment variables:")
|
| 367 |
+
for key in os.environ.keys():
|
| 368 |
+
if 'LANDING' in key.upper() or 'API' in key.upper():
|
| 369 |
+
print(f" - {key}: {'SET' if os.environ[key] else 'EMPTY'}")
|
| 370 |
+
return False
|
| 371 |
+
|
| 372 |
+
# Basic format validation (adjust based on Landing AI key format)
|
| 373 |
+
if len(api_key) < 10:
|
| 374 |
+
print("β Landing AI API key appears to be too short")
|
| 375 |
+
return False
|
| 376 |
+
|
| 377 |
+
print("β
Landing AI API key found and appears valid")
|
| 378 |
+
return True
|
| 379 |
+
|
| 380 |
+
# Test the connection when module is imported (optional)
|
| 381 |
+
if __name__ == "__main__":
|
| 382 |
+
test_landing_ai_connection()
|