dure-waseem commited on
Commit
9c60c5f
Β·
1 Parent(s): 79b6caa

initial code

Browse files
Files changed (6) hide show
  1. app.py +785 -0
  2. config/agents.yaml +20 -0
  3. config/tasks.yaml +30 -0
  4. crew.py +132 -0
  5. requirements.txt +3 -0
  6. 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('"', '&quot;')}"
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()