ryanDing26 commited on
Commit
ba91360
Β·
1 Parent(s): 81e73c2

app.py way too heavy before

Browse files
Files changed (2) hide show
  1. app.py +32 -638
  2. app_old.py +602 -0
app.py CHANGED
@@ -1,605 +1,3 @@
1
- # import os
2
- # import re
3
- # import shutil
4
- # import traceback
5
- # import gradio as gr
6
- # from pathlib import Path
7
- # from histopath.agent import A1
8
- # from dotenv import load_dotenv
9
-
10
- # # Load environment variables
11
- # load_dotenv()
12
-
13
- # # Get passcode from environment
14
- # PASSCODE = os.getenv("GRADIO_PASSWORD")
15
-
16
- # # Initialize agent (will be created after passcode validation)
17
- # agent = None
18
-
19
-
20
- # def check_for_output_files():
21
- # """Check for all files in the output directory and return their paths."""
22
- # output_dir = Path("./output")
23
- # if not output_dir.exists():
24
- # return [], []
25
-
26
- # image_extensions = {".png", ".jpg", ".jpeg", ".svg", ".tif", ".tiff"}
27
- # data_extensions = {".csv", ".txt", ".json", ".npy"}
28
-
29
- # images = []
30
- # data_files = []
31
-
32
- # for file in output_dir.iterdir():
33
- # if file.is_file():
34
- # if file.suffix.lower() in image_extensions:
35
- # images.append(str(file))
36
- # elif file.suffix.lower() in data_extensions:
37
- # data_files.append(str(file))
38
-
39
- # return images, data_files
40
-
41
-
42
- # def preview_uploaded_file(uploaded_file):
43
- # """Preview the uploaded file - show image or file info."""
44
- # if uploaded_file is None:
45
- # return None, None, "No file uploaded"
46
-
47
- # file_path = Path(uploaded_file.name)
48
- # file_ext = file_path.suffix.lower()
49
-
50
- # image_extensions = {".png", ".jpg", ".jpeg", ".svg", ".tif", ".tiff", ".svs"}
51
-
52
- # if file_ext in image_extensions:
53
- # # Show image preview
54
- # return uploaded_file.name, None, f"πŸ“· Previewing: {file_path.name}"
55
- # else:
56
- # # Show file info
57
- # file_size = Path(uploaded_file.name).stat().st_size / 1024 # KB
58
- # return None, uploaded_file.name, f"πŸ“„ File: {file_path.name} ({file_size:.1f} KB)"
59
-
60
-
61
- # def parse_agent_output(output):
62
- # """Parse agent output to extract code blocks, observations, and regular text."""
63
- # # Strip out the message divider bars
64
- # output = re.sub(r'={30,}\s*(Human|Ai)\s+Message\s*={30,}', '', output)
65
- # output = output.strip()
66
-
67
- # parsed = {
68
- # "type": "text",
69
- # "content": output,
70
- # "code": None,
71
- # "observation": None,
72
- # "thinking": None
73
- # }
74
-
75
- # # Check for code execution block
76
- # execute_match = re.search(r'<execute>(.*?)</execute>', output, re.DOTALL)
77
- # if execute_match:
78
- # parsed["type"] = "code"
79
- # parsed["code"] = execute_match.group(1).strip()
80
- # # Extract text before the code block (thinking/explanation)
81
- # text_before = output[:execute_match.start()].strip()
82
- # # Remove any think tags but keep the content
83
- # text_before = re.sub(r'<think>(.*?)</think>', r'\1', text_before, flags=re.DOTALL)
84
- # text_before = re.sub(r'={30,}.*?={30,}', '', text_before).strip()
85
- # parsed["thinking"] = text_before if text_before else None
86
- # return parsed
87
-
88
- # # Check for observation block
89
- # observation_match = re.search(r'<observation>(.*?)</observation>', output, re.DOTALL)
90
- # if observation_match:
91
- # parsed["type"] = "observation"
92
- # parsed["observation"] = observation_match.group(1).strip()
93
- # # Extract text before observation if any
94
- # text_before = output[:observation_match.start()].strip()
95
- # text_before = re.sub(r'<think>(.*?)</think>', r'\1', text_before, flags=re.DOTALL)
96
- # text_before = re.sub(r'={30,}.*?={30,}', '', text_before).strip()
97
- # parsed["thinking"] = text_before if text_before else None
98
- # return parsed
99
-
100
- # # Check for solution block
101
- # solution_match = re.search(r'<solution>(.*?)</solution>', output, re.DOTALL)
102
- # if solution_match:
103
- # parsed["type"] = "solution"
104
- # parsed["content"] = solution_match.group(1).strip()
105
- # # Get thinking before solution
106
- # text_before = output[:solution_match.start()].strip()
107
- # text_before = re.sub(r'<think>(.*?)</think>', r'\1', text_before, flags=re.DOTALL)
108
- # text_before = re.sub(r'={30,}.*?={30,}', '', text_before).strip()
109
- # parsed["thinking"] = text_before if text_before else None
110
- # return parsed
111
-
112
- # # Clean up any remaining tags for display
113
- # cleaned = re.sub(r'<think>(.*?)</think>', r'\1', output, flags=re.DOTALL)
114
- # cleaned = re.sub(r'={30,}.*?={30,}', '', cleaned).strip()
115
- # parsed["content"] = cleaned
116
-
117
- # return parsed
118
-
119
-
120
- # def format_message_for_display(parsed_output):
121
- # """Format parsed output into a readable message for the chatbot."""
122
- # msg_parts = []
123
-
124
- # # Add thinking/explanation text first if present
125
- # if parsed_output.get("thinking"):
126
- # msg_parts.append(parsed_output["thinking"])
127
-
128
- # if parsed_output["type"] == "code":
129
- # # Add separator if there was thinking text
130
- # if parsed_output.get("thinking"):
131
- # msg_parts.append("\n---\n")
132
-
133
- # msg_parts.append("### πŸ’» Executing Code\n")
134
- # msg_parts.append(f"```python\n{parsed_output['code']}\n```")
135
-
136
- # elif parsed_output["type"] == "observation":
137
- # # Add separator if there was thinking text
138
- # if parsed_output.get("thinking"):
139
- # msg_parts.append("\n---\n")
140
-
141
- # msg_parts.append("### πŸ“Š Observation\n")
142
- # msg_parts.append(f"```\n{parsed_output['observation']}\n```")
143
-
144
- # elif parsed_output["type"] == "solution":
145
- # # Add separator if there was thinking text
146
- # if parsed_output.get("thinking"):
147
- # msg_parts.append("\n---\n")
148
-
149
- # msg_parts.append("### βœ… Solution\n")
150
- # msg_parts.append(parsed_output['content'])
151
-
152
- # else:
153
- # # For regular text, just add the content if thinking wasn't already set
154
- # if not parsed_output.get("thinking"):
155
- # msg_parts.append(parsed_output["content"])
156
-
157
- # return "\n\n".join(msg_parts)
158
-
159
-
160
- # def process_agent_response(prompt, uploaded_file, chatbot_history):
161
- # """Process the agent response and update chatbot."""
162
- # global agent
163
-
164
- # if agent is None:
165
- # chatbot_history.append({
166
- # "role": "assistant",
167
- # "content": "⚠️ Please enter the passcode first to initialize the agent."
168
- # })
169
- # yield chatbot_history, None, None, None, None, "⚠️ Agent not initialized"
170
- # return
171
-
172
- # if not prompt.strip() and uploaded_file is None:
173
- # chatbot_history.append({
174
- # "role": "assistant",
175
- # "content": "⚠️ Please provide a prompt or upload a file."
176
- # })
177
- # yield chatbot_history, None, None, None, None, "⚠️ No input provided"
178
- # return
179
-
180
- # # Handle file upload
181
- # file_path = None
182
- # file_info = ""
183
- # if uploaded_file is not None:
184
- # try:
185
- # # Create data directory if it doesn't exist
186
- # data_dir = Path("./data")
187
- # data_dir.mkdir(exist_ok=True)
188
-
189
- # # Copy uploaded file to data directory
190
- # file_name = Path(uploaded_file.name).name
191
- # file_path = data_dir / file_name
192
- # shutil.copy(uploaded_file.name, file_path)
193
-
194
- # file_info = f"\n\nπŸ“Ž **Uploaded file:** `{file_path}`\n"
195
-
196
- # # Augment prompt with file path
197
- # if prompt.strip():
198
- # prompt = f"{prompt}\n\nUploaded file path: {file_path}"
199
- # else:
200
- # prompt = f"I have uploaded a file at: {file_path}. Please analyze it."
201
-
202
- # except Exception as e:
203
- # error_msg = f"❌ Error handling file upload: {str(e)}"
204
- # chatbot_history.append({
205
- # "role": "assistant",
206
- # "content": error_msg
207
- # })
208
- # yield chatbot_history, None, None, None, None, error_msg
209
- # return
210
-
211
- # # Add user message to chat
212
- # user_message = prompt if not file_info else f"{prompt}{file_info}"
213
- # chatbot_history.append({"role": "user", "content": user_message})
214
- # yield chatbot_history, None, None, None, None, "πŸ”„ Processing..."
215
-
216
- # try:
217
- # # Stream agent responses
218
- # step_count = 0
219
- # for step in agent.go_stream(prompt):
220
- # step_count += 1
221
- # output = step.get("output", "")
222
-
223
- # if output:
224
- # # Parse the output
225
- # parsed = parse_agent_output(output)
226
-
227
- # # Add thinking text as separate message if present
228
- # if parsed.get("thinking"):
229
- # chatbot_history.append({
230
- # "role": "assistant",
231
- # "content": parsed["thinking"]
232
- # })
233
-
234
- # # Add the block (code/observation/solution) as separate message if present
235
- # if parsed["type"] == "code" and parsed["code"]:
236
- # chatbot_history.append({
237
- # "role": "assistant",
238
- # "content": f"### πŸ’» Executing Code\n\n```python\n{parsed['code']}\n```"
239
- # })
240
- # elif parsed["type"] == "observation" and parsed["observation"]:
241
- # chatbot_history.append({
242
- # "role": "assistant",
243
- # "content": f"### πŸ“Š Observation\n\n```\n{parsed['observation']}\n```"
244
- # })
245
- # elif parsed["type"] == "solution":
246
- # chatbot_history.append({
247
- # "role": "assistant",
248
- # "content": f"### βœ… Solution\n\n{parsed['content']}"
249
- # })
250
- # elif parsed["type"] == "text" and parsed["content"]:
251
- # # Only add if we haven't already added it as thinking
252
- # if not parsed.get("thinking"):
253
- # chatbot_history.append({
254
- # "role": "assistant",
255
- # "content": parsed["content"]
256
- # })
257
-
258
- # # Check for output files after each step
259
- # images, data_files = check_for_output_files()
260
-
261
- # # Create status message
262
- # status = f"πŸ”„ Step {step_count}"
263
- # if parsed["type"] == "code":
264
- # status += " - Executing code..."
265
- # elif parsed["type"] == "observation":
266
- # status += " - Processing results..."
267
- # elif parsed["type"] == "solution":
268
- # status += " - Finalizing solution..."
269
-
270
- # yield (
271
- # chatbot_history,
272
- # images if images else None,
273
- # data_files if data_files else None,
274
- # None,
275
- # None,
276
- # status
277
- # )
278
-
279
- # # Final check for files
280
- # final_images, final_data = check_for_output_files()
281
-
282
- # # Create download links message if files were generated
283
- # if final_images or final_data:
284
- # download_msg = "\n\n---\n\n### πŸ“ Generated Files Ready for Download\n\n"
285
-
286
- # if final_images:
287
- # download_msg += f"**πŸ–ΌοΈ Images ({len(final_images)})** - Available in the **Images** tab β†’\n"
288
- # for img_path in final_images:
289
- # img_name = Path(img_path).name
290
- # download_msg += f"- `{img_name}`\n"
291
- # download_msg += "\n"
292
-
293
- # if final_data:
294
- # download_msg += f"**πŸ“„ Data Files ({len(final_data)})** - Available in the **Data** tab β†’\n"
295
- # for data_path in final_data:
296
- # data_name = Path(data_path).name
297
- # download_msg += f"- `{data_name}`\n"
298
-
299
- # download_msg += "\n*Click the download button on each file in the respective tabs above.*"
300
-
301
- # # Add download message as separate bubble
302
- # chatbot_history.append({
303
- # "role": "assistant",
304
- # "content": download_msg
305
- # })
306
-
307
- # status = "βœ… Complete"
308
- # if final_images:
309
- # status += f" | {len(final_images)} image(s)"
310
- # if final_data:
311
- # status += f" | {len(final_data)} data file(s)"
312
-
313
- # yield chatbot_history, final_images if final_images else None, final_data if final_data else None, None, None, status
314
-
315
- # except Exception as e:
316
- # error_msg = f"❌ Error: {str(e)}\n\n```\n{traceback.format_exc()}\n```"
317
- # chatbot_history.append({
318
- # "role": "assistant",
319
- # "content": error_msg
320
- # })
321
- # yield chatbot_history, None, None, None, None, "❌ Error occurred"
322
-
323
-
324
- # def validate_passcode(passcode):
325
- # """Validate the passcode and initialize the agent."""
326
- # global agent
327
-
328
- # if passcode == PASSCODE:
329
- # # Initialize agent
330
- # try:
331
- # agent = A1()
332
- # return (
333
- # gr.update(visible=False), # Hide passcode section
334
- # gr.update(visible=True), # Show main interface
335
- # "βœ… Access granted! Agent initialized and ready."
336
- # )
337
- # except Exception as e:
338
- # error_trace = traceback.format_exc()
339
- # return (
340
- # gr.update(visible=True),
341
- # gr.update(visible=False),
342
- # f"❌ Error initializing agent:\n{str(e)}\n\n{error_trace}"
343
- # )
344
- # else:
345
- # return (
346
- # gr.update(visible=True),
347
- # gr.update(visible=False),
348
- # "❌ Invalid passcode. Please try again."
349
- # )
350
-
351
-
352
- # def clear_chat():
353
- # """Clear the chat history and output files."""
354
- # # Clean up output directory
355
- # output_dir = Path("./output")
356
- # if output_dir.exists():
357
- # shutil.rmtree(output_dir)
358
- # output_dir.mkdir(exist_ok=True)
359
-
360
- # # Clean up data directory
361
- # data_dir = Path("./data")
362
- # if data_dir.exists():
363
- # for file in data_dir.iterdir():
364
- # if file.is_file():
365
- # file.unlink()
366
-
367
- # return [], None, None, None, None, "πŸ—‘οΈ Chat cleared"
368
-
369
-
370
- # # Create Gradio interface with custom theme
371
- # custom_theme = gr.themes.Soft(
372
- # primary_hue="blue",
373
- # secondary_hue="slate",
374
- # spacing_size="sm",
375
- # radius_size="md",
376
- # ).set(
377
- # button_primary_background_fill="*primary_500",
378
- # button_primary_background_fill_hover="*primary_600",
379
- # block_label_text_weight="600",
380
- # block_title_text_weight="600",
381
- # )
382
-
383
- # with gr.Blocks(title="HistoPath Agent", theme=custom_theme, css="""
384
- # .gradio-container {
385
- # max-width: 100% !important;
386
- # }
387
- # .main-header {
388
- # text-align: center;
389
- # padding: 1.5rem 0;
390
- # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
391
- # color: white;
392
- # border-radius: 8px;
393
- # margin-bottom: 1.5rem;
394
- # }
395
- # .main-header h1 {
396
- # margin: 0;
397
- # font-size: 2.2rem;
398
- # font-weight: 700;
399
- # }
400
- # .main-header p {
401
- # margin: 0.5rem 0 0 0;
402
- # opacity: 0.95;
403
- # font-size: 1.1rem;
404
- # }
405
- # .file-upload-box .wrap {
406
- # min-width: 0 !important;
407
- # }
408
- # .file-upload-box .file-name {
409
- # word-break: break-word !important;
410
- # white-space: normal !important;
411
- # overflow-wrap: break-word !important;
412
- # }
413
- # .tab-nav {
414
- # margin-bottom: 0.5rem;
415
- # }
416
- # /* Better styling for code and observation blocks */
417
- # .message.bot pre {
418
- # background-color: #f6f8fa !important;
419
- # border: 1px solid #d0d7de !important;
420
- # border-radius: 6px !important;
421
- # padding: 12px !important;
422
- # margin: 8px 0 !important;
423
- # }
424
- # .message.bot h3 {
425
- # margin-top: 12px !important;
426
- # margin-bottom: 8px !important;
427
- # font-weight: 600 !important;
428
- # }
429
- # .message.bot hr {
430
- # border: none !important;
431
- # border-top: 2px solid #e1e4e8 !important;
432
- # margin: 16px 0 !important;
433
- # }
434
- # """) as demo:
435
-
436
- # # Header
437
- # gr.HTML("""
438
- # <div class="main-header">
439
- # <h1>πŸ”¬ HistoPath Agent</h1>
440
- # <p>AI-Powered Histopathology Analysis Assistant</p>
441
- # </div>
442
- # """)
443
-
444
- # # Passcode section
445
- # with gr.Group(visible=True) as passcode_section:
446
- # gr.Markdown("### πŸ” Authentication Required")
447
-
448
- # with gr.Row():
449
- # passcode_input = gr.Textbox(
450
- # label="Passcode",
451
- # type="password",
452
- # placeholder="Enter your passcode...",
453
- # scale=3
454
- # )
455
- # passcode_btn = gr.Button("πŸ”“ Unlock", variant="primary", scale=1, size="lg")
456
-
457
- # passcode_status = gr.Textbox(
458
- # label="Status",
459
- # interactive=False,
460
- # lines=2
461
- # )
462
-
463
- # # Main interface (hidden initially)
464
- # with gr.Group(visible=False) as main_interface:
465
- # with gr.Row(equal_height=True):
466
- # # Left column - Chat interface
467
- # with gr.Column(scale=3):
468
- # chatbot = gr.Chatbot(
469
- # label="πŸ’¬ Conversation",
470
- # height=550,
471
- # # type="messages",
472
- # show_label=True,
473
- # render_markdown=True,
474
- # )
475
-
476
- # # Input area
477
- # with gr.Row():
478
- # with gr.Column(scale=7):
479
- # prompt_input = gr.Textbox(
480
- # label="Your Query",
481
- # placeholder="E.g., 'Caption the uploaded whole slide image' or 'Segment cells using instanseg model'",
482
- # lines=2,
483
- # max_lines=5,
484
- # show_label=False,
485
- # )
486
- # with gr.Column(scale=3):
487
- # file_upload = gr.File(
488
- # label="πŸ“Ž Upload File",
489
- # file_types=[".svs", ".png", ".jpg", ".jpeg", ".tif", ".tiff", ".csv", ".txt", ".json", ".npy"],
490
- # height=75,
491
- # elem_classes="file-upload-box",
492
- # )
493
-
494
- # with gr.Row():
495
- # submit_btn = gr.Button("πŸš€ Submit", variant="primary", scale=3, size="lg")
496
- # clear_btn = gr.Button("πŸ—‘οΈ Clear", scale=1, size="lg", variant="secondary")
497
-
498
- # status_text = gr.Textbox(
499
- # label="Status",
500
- # interactive=False,
501
- # value="Ready",
502
- # show_label=False,
503
- # container=False,
504
- # )
505
-
506
- # # Right column - Outputs
507
- # with gr.Column(scale=2):
508
- # with gr.Tabs():
509
- # with gr.Tab("πŸ“₯ Input"):
510
- # with gr.Column():
511
- # input_image_preview = gr.Image(
512
- # label="Input Image",
513
- # height=400,
514
- # show_label=False,
515
- # container=True,
516
- # )
517
- # input_file_preview = gr.File(
518
- # label="Input File",
519
- # interactive=False,
520
- # height=100,
521
- # show_label=False,
522
- # container=True,
523
- # )
524
- # input_status = gr.Textbox(
525
- # value="Upload a file to preview",
526
- # show_label=False,
527
- # interactive=False,
528
- # container=False,
529
- # )
530
-
531
- # with gr.Tab("πŸ–ΌοΈ Images"):
532
- # output_gallery = gr.Gallery(
533
- # label="Generated Visualizations",
534
- # columns=1,
535
- # height=600,
536
- # object_fit="contain",
537
- # show_label=False,
538
- # show_download_button=True,
539
- # )
540
-
541
- # with gr.Tab("πŸ“„ Data"):
542
- # data_files = gr.File(
543
- # label="Generated Data Files",
544
- # file_count="multiple",
545
- # interactive=False,
546
- # height=600,
547
- # show_label=False,
548
- # )
549
-
550
- # # Event handlers
551
- # passcode_btn.click(
552
- # fn=validate_passcode,
553
- # inputs=[passcode_input],
554
- # outputs=[passcode_section, main_interface, passcode_status]
555
- # )
556
-
557
- # # File upload preview
558
- # file_upload.change(
559
- # fn=preview_uploaded_file,
560
- # inputs=[file_upload],
561
- # outputs=[input_image_preview, input_file_preview, input_status]
562
- # )
563
-
564
- # submit_btn.click(
565
- # fn=process_agent_response,
566
- # inputs=[prompt_input, file_upload, chatbot],
567
- # outputs=[chatbot, output_gallery, data_files, input_image_preview, input_file_preview, status_text]
568
- # )
569
-
570
- # clear_btn.click(
571
- # fn=clear_chat,
572
- # outputs=[chatbot, output_gallery, data_files, input_image_preview, input_file_preview, status_text]
573
- # )
574
-
575
- # # Allow enter key to submit
576
- # prompt_input.submit(
577
- # fn=process_agent_response,
578
- # inputs=[prompt_input, file_upload, chatbot],
579
- # outputs=[chatbot, output_gallery, data_files, input_image_preview, input_file_preview, status_text]
580
- # )
581
-
582
-
583
- # if __name__ == "__main__":
584
- # # Create necessary directories
585
- # Path("./data").mkdir(exist_ok=True)
586
- # Path("./output").mkdir(exist_ok=True)
587
-
588
- # print("=" * 60)
589
- # print("πŸ”¬ HistoPath Agent - Gradio Interface")
590
- # print("=" * 60)
591
- # # print(f"Passcode: {PASSCODE}")
592
- # print("Starting server...")
593
- # print("=" * 60)
594
-
595
- # # Launch the app
596
- # #demo.launch(
597
- # # server_name="0.0.0.0",
598
- # # server_port=7860, # Let Gradio auto-pick an available port
599
- # # share=True,
600
- # # show_error=True,
601
- # #)
602
- # demo.launch()
603
  import os
604
  import re
605
  import shutil
@@ -760,7 +158,7 @@ def format_message_for_display(parsed_output):
760
 
761
 
762
  def process_agent_response(prompt, uploaded_file, chatbot_history):
763
- """Process the agent response and update chatbot."""
764
  global agent
765
 
766
  if agent is None:
@@ -768,7 +166,7 @@ def process_agent_response(prompt, uploaded_file, chatbot_history):
768
  "role": "assistant",
769
  "content": "⚠️ Please enter the passcode first to initialize the agent."
770
  })
771
- yield chatbot_history, gr.update(value=None), gr.update(value=None), None, None, "⚠️ Agent not initialized"
772
  return
773
 
774
  if not prompt.strip() and uploaded_file is None:
@@ -776,7 +174,7 @@ def process_agent_response(prompt, uploaded_file, chatbot_history):
776
  "role": "assistant",
777
  "content": "⚠️ Please provide a prompt or upload a file."
778
  })
779
- yield chatbot_history, gr.update(value=None), gr.update(value=None), None, None, "⚠️ No input provided"
780
  return
781
 
782
  # Handle file upload
@@ -807,49 +205,46 @@ def process_agent_response(prompt, uploaded_file, chatbot_history):
807
  "role": "assistant",
808
  "content": error_msg
809
  })
810
- yield chatbot_history, gr.update(value=None), gr.update(value=None), None, None, error_msg
811
  return
812
 
813
  # Add user message to chat
814
  user_message = prompt if not file_info else f"{prompt}{file_info}"
815
  chatbot_history.append({"role": "user", "content": user_message})
816
- yield chatbot_history, gr.update(value=None), gr.update(value=None), None, None, "πŸ”„ Processing..."
 
 
817
 
818
  try:
819
- # Stream agent responses
820
  step_count = 0
 
 
821
  for step in agent.go_stream(prompt):
822
  step_count += 1
823
  output = step.get("output", "")
824
 
825
  if output:
826
- # Parse the output
827
- parsed = parse_agent_output(output)
828
-
829
- # Format for display
830
- formatted_message = format_message_for_display(parsed)
831
-
832
- # Update or append to chatbot history
833
- if chatbot_history and chatbot_history[-1]["role"] == "assistant":
834
- # Update the last assistant message
835
- chatbot_history[-1]["content"] = formatted_message
836
- else:
837
- # Add new assistant message
838
- chatbot_history.append({
839
- "role": "assistant",
840
- "content": formatted_message
841
- })
842
-
843
- # CRITICAL FIX: Don't update files during streaming
844
- # Only yield chatbot updates, keep files unchanged
845
- yield chatbot_history, gr.update(), gr.update(), None, None, f"πŸ”„ Step {step_count}..."
846
 
847
- # CRITICAL FIX: Only check and return files AFTER streaming completes
848
- images, data = check_for_output_files()
 
 
 
 
 
 
 
 
 
 
 
 
 
849
 
850
- # Use gr.update with proper value to avoid Content-Length issues
851
- final_images = gr.update(value=images if images else None)
852
- final_data = gr.update(value=data if data else None)
853
 
854
  status_msg = f"βœ… Complete ({step_count} steps)"
855
  if images:
@@ -857,7 +252,8 @@ def process_agent_response(prompt, uploaded_file, chatbot_history):
857
  if data:
858
  status_msg += f" | {len(data)} data file(s)"
859
 
860
- yield chatbot_history, final_images, final_data, None, None, status_msg
 
861
 
862
  except Exception as e:
863
  error_trace = traceback.format_exc()
@@ -868,12 +264,12 @@ def process_agent_response(prompt, uploaded_file, chatbot_history):
868
  "content": error_msg
869
  })
870
 
871
- yield chatbot_history, gr.update(value=None), gr.update(value=None), None, None, f"❌ Error: {str(e)}"
872
 
873
 
874
  def clear_chat():
875
  """Clear the chat history and outputs."""
876
- return [], gr.update(value=None), gr.update(value=None), None, None, "Ready"
877
 
878
 
879
  def validate_passcode(input_passcode):
@@ -1011,7 +407,6 @@ with gr.Blocks(title="HistoPath Agent", theme=custom_theme, css="""
1011
  chatbot = gr.Chatbot(
1012
  label="πŸ’¬ Conversation",
1013
  height=550,
1014
- # type="messages",
1015
  show_label=True,
1016
  render_markdown=True,
1017
  )
@@ -1131,7 +526,6 @@ if __name__ == "__main__":
1131
  print("=" * 60)
1132
  print("πŸ”¬ HistoPath Agent - Gradio Interface")
1133
  print("=" * 60)
1134
- # print(f"Passcode: {PASSCODE}")
1135
  print("Starting server...")
1136
  print("=" * 60)
1137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import re
3
  import shutil
 
158
 
159
 
160
  def process_agent_response(prompt, uploaded_file, chatbot_history):
161
+ """Process the agent response and update chatbot - AGGRESSIVE FIX: Minimal yields."""
162
  global agent
163
 
164
  if agent is None:
 
166
  "role": "assistant",
167
  "content": "⚠️ Please enter the passcode first to initialize the agent."
168
  })
169
+ yield chatbot_history, None, None, None, None, "⚠️ Agent not initialized"
170
  return
171
 
172
  if not prompt.strip() and uploaded_file is None:
 
174
  "role": "assistant",
175
  "content": "⚠️ Please provide a prompt or upload a file."
176
  })
177
+ yield chatbot_history, None, None, None, None, "⚠️ No input provided"
178
  return
179
 
180
  # Handle file upload
 
205
  "role": "assistant",
206
  "content": error_msg
207
  })
208
+ yield chatbot_history, None, None, None, None, error_msg
209
  return
210
 
211
  # Add user message to chat
212
  user_message = prompt if not file_info else f"{prompt}{file_info}"
213
  chatbot_history.append({"role": "user", "content": user_message})
214
+
215
+ # CRITICAL FIX: Only yield once at the start to show user message
216
+ yield chatbot_history, None, None, None, None, "πŸ”„ Processing..."
217
 
218
  try:
219
+ # CRITICAL FIX: Collect ALL steps without yielding
220
  step_count = 0
221
+ collected_outputs = []
222
+
223
  for step in agent.go_stream(prompt):
224
  step_count += 1
225
  output = step.get("output", "")
226
 
227
  if output:
228
+ collected_outputs.append(output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
+ # CRITICAL FIX: Process ALL collected outputs at once
231
+ for output in collected_outputs:
232
+ parsed = parse_agent_output(output)
233
+ formatted_message = format_message_for_display(parsed)
234
+
235
+ # Update or append to chatbot history
236
+ if chatbot_history and chatbot_history[-1]["role"] == "assistant":
237
+ # Update the last assistant message
238
+ chatbot_history[-1]["content"] = formatted_message
239
+ else:
240
+ # Add new assistant message
241
+ chatbot_history.append({
242
+ "role": "assistant",
243
+ "content": formatted_message
244
+ })
245
 
246
+ # CRITICAL FIX: Check files only ONCE after all processing
247
+ images, data = check_for_output_files()
 
248
 
249
  status_msg = f"βœ… Complete ({step_count} steps)"
250
  if images:
 
252
  if data:
253
  status_msg += f" | {len(data)} data file(s)"
254
 
255
+ # CRITICAL FIX: Final single yield with all results
256
+ yield chatbot_history, images, data, None, None, status_msg
257
 
258
  except Exception as e:
259
  error_trace = traceback.format_exc()
 
264
  "content": error_msg
265
  })
266
 
267
+ yield chatbot_history, None, None, None, None, f"❌ Error: {str(e)}"
268
 
269
 
270
  def clear_chat():
271
  """Clear the chat history and outputs."""
272
+ return [], None, None, None, None, "Ready"
273
 
274
 
275
  def validate_passcode(input_passcode):
 
407
  chatbot = gr.Chatbot(
408
  label="πŸ’¬ Conversation",
409
  height=550,
 
410
  show_label=True,
411
  render_markdown=True,
412
  )
 
526
  print("=" * 60)
527
  print("πŸ”¬ HistoPath Agent - Gradio Interface")
528
  print("=" * 60)
 
529
  print("Starting server...")
530
  print("=" * 60)
531
 
app_old.py ADDED
@@ -0,0 +1,602 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import os
2
+ # import re
3
+ # import shutil
4
+ # import traceback
5
+ # import gradio as gr
6
+ # from pathlib import Path
7
+ # from histopath.agent import A1
8
+ # from dotenv import load_dotenv
9
+
10
+ # # Load environment variables
11
+ # load_dotenv()
12
+
13
+ # # Get passcode from environment
14
+ # PASSCODE = os.getenv("GRADIO_PASSWORD")
15
+
16
+ # # Initialize agent (will be created after passcode validation)
17
+ # agent = None
18
+
19
+
20
+ # def check_for_output_files():
21
+ # """Check for all files in the output directory and return their paths."""
22
+ # output_dir = Path("./output")
23
+ # if not output_dir.exists():
24
+ # return [], []
25
+
26
+ # image_extensions = {".png", ".jpg", ".jpeg", ".svg", ".tif", ".tiff"}
27
+ # data_extensions = {".csv", ".txt", ".json", ".npy"}
28
+
29
+ # images = []
30
+ # data_files = []
31
+
32
+ # for file in output_dir.iterdir():
33
+ # if file.is_file():
34
+ # if file.suffix.lower() in image_extensions:
35
+ # images.append(str(file))
36
+ # elif file.suffix.lower() in data_extensions:
37
+ # data_files.append(str(file))
38
+
39
+ # return images, data_files
40
+
41
+
42
+ # def preview_uploaded_file(uploaded_file):
43
+ # """Preview the uploaded file - show image or file info."""
44
+ # if uploaded_file is None:
45
+ # return None, None, "No file uploaded"
46
+
47
+ # file_path = Path(uploaded_file.name)
48
+ # file_ext = file_path.suffix.lower()
49
+
50
+ # image_extensions = {".png", ".jpg", ".jpeg", ".svg", ".tif", ".tiff", ".svs"}
51
+
52
+ # if file_ext in image_extensions:
53
+ # # Show image preview
54
+ # return uploaded_file.name, None, f"πŸ“· Previewing: {file_path.name}"
55
+ # else:
56
+ # # Show file info
57
+ # file_size = Path(uploaded_file.name).stat().st_size / 1024 # KB
58
+ # return None, uploaded_file.name, f"πŸ“„ File: {file_path.name} ({file_size:.1f} KB)"
59
+
60
+
61
+ # def parse_agent_output(output):
62
+ # """Parse agent output to extract code blocks, observations, and regular text."""
63
+ # # Strip out the message divider bars
64
+ # output = re.sub(r'={30,}\s*(Human|Ai)\s+Message\s*={30,}', '', output)
65
+ # output = output.strip()
66
+
67
+ # parsed = {
68
+ # "type": "text",
69
+ # "content": output,
70
+ # "code": None,
71
+ # "observation": None,
72
+ # "thinking": None
73
+ # }
74
+
75
+ # # Check for code execution block
76
+ # execute_match = re.search(r'<execute>(.*?)</execute>', output, re.DOTALL)
77
+ # if execute_match:
78
+ # parsed["type"] = "code"
79
+ # parsed["code"] = execute_match.group(1).strip()
80
+ # # Extract text before the code block (thinking/explanation)
81
+ # text_before = output[:execute_match.start()].strip()
82
+ # # Remove any think tags but keep the content
83
+ # text_before = re.sub(r'<think>(.*?)</think>', r'\1', text_before, flags=re.DOTALL)
84
+ # text_before = re.sub(r'={30,}.*?={30,}', '', text_before).strip()
85
+ # parsed["thinking"] = text_before if text_before else None
86
+ # return parsed
87
+
88
+ # # Check for observation block
89
+ # observation_match = re.search(r'<observation>(.*?)</observation>', output, re.DOTALL)
90
+ # if observation_match:
91
+ # parsed["type"] = "observation"
92
+ # parsed["observation"] = observation_match.group(1).strip()
93
+ # # Extract text before observation if any
94
+ # text_before = output[:observation_match.start()].strip()
95
+ # text_before = re.sub(r'<think>(.*?)</think>', r'\1', text_before, flags=re.DOTALL)
96
+ # text_before = re.sub(r'={30,}.*?={30,}', '', text_before).strip()
97
+ # parsed["thinking"] = text_before if text_before else None
98
+ # return parsed
99
+
100
+ # # Check for solution block
101
+ # solution_match = re.search(r'<solution>(.*?)</solution>', output, re.DOTALL)
102
+ # if solution_match:
103
+ # parsed["type"] = "solution"
104
+ # parsed["content"] = solution_match.group(1).strip()
105
+ # # Get thinking before solution
106
+ # text_before = output[:solution_match.start()].strip()
107
+ # text_before = re.sub(r'<think>(.*?)</think>', r'\1', text_before, flags=re.DOTALL)
108
+ # text_before = re.sub(r'={30,}.*?={30,}', '', text_before).strip()
109
+ # parsed["thinking"] = text_before if text_before else None
110
+ # return parsed
111
+
112
+ # # Clean up any remaining tags for display
113
+ # cleaned = re.sub(r'<think>(.*?)</think>', r'\1', output, flags=re.DOTALL)
114
+ # cleaned = re.sub(r'={30,}.*?={30,}', '', cleaned).strip()
115
+ # parsed["content"] = cleaned
116
+
117
+ # return parsed
118
+
119
+
120
+ # def format_message_for_display(parsed_output):
121
+ # """Format parsed output into a readable message for the chatbot."""
122
+ # msg_parts = []
123
+
124
+ # # Add thinking/explanation text first if present
125
+ # if parsed_output.get("thinking"):
126
+ # msg_parts.append(parsed_output["thinking"])
127
+
128
+ # if parsed_output["type"] == "code":
129
+ # # Add separator if there was thinking text
130
+ # if parsed_output.get("thinking"):
131
+ # msg_parts.append("\n---\n")
132
+
133
+ # msg_parts.append("### πŸ’» Executing Code\n")
134
+ # msg_parts.append(f"```python\n{parsed_output['code']}\n```")
135
+
136
+ # elif parsed_output["type"] == "observation":
137
+ # # Add separator if there was thinking text
138
+ # if parsed_output.get("thinking"):
139
+ # msg_parts.append("\n---\n")
140
+
141
+ # msg_parts.append("### πŸ“Š Observation\n")
142
+ # msg_parts.append(f"```\n{parsed_output['observation']}\n```")
143
+
144
+ # elif parsed_output["type"] == "solution":
145
+ # # Add separator if there was thinking text
146
+ # if parsed_output.get("thinking"):
147
+ # msg_parts.append("\n---\n")
148
+
149
+ # msg_parts.append("### βœ… Solution\n")
150
+ # msg_parts.append(parsed_output['content'])
151
+
152
+ # else:
153
+ # # For regular text, just add the content if thinking wasn't already set
154
+ # if not parsed_output.get("thinking"):
155
+ # msg_parts.append(parsed_output["content"])
156
+
157
+ # return "\n\n".join(msg_parts)
158
+
159
+
160
+ # def process_agent_response(prompt, uploaded_file, chatbot_history):
161
+ # """Process the agent response and update chatbot."""
162
+ # global agent
163
+
164
+ # if agent is None:
165
+ # chatbot_history.append({
166
+ # "role": "assistant",
167
+ # "content": "⚠️ Please enter the passcode first to initialize the agent."
168
+ # })
169
+ # yield chatbot_history, None, None, None, None, "⚠️ Agent not initialized"
170
+ # return
171
+
172
+ # if not prompt.strip() and uploaded_file is None:
173
+ # chatbot_history.append({
174
+ # "role": "assistant",
175
+ # "content": "⚠️ Please provide a prompt or upload a file."
176
+ # })
177
+ # yield chatbot_history, None, None, None, None, "⚠️ No input provided"
178
+ # return
179
+
180
+ # # Handle file upload
181
+ # file_path = None
182
+ # file_info = ""
183
+ # if uploaded_file is not None:
184
+ # try:
185
+ # # Create data directory if it doesn't exist
186
+ # data_dir = Path("./data")
187
+ # data_dir.mkdir(exist_ok=True)
188
+
189
+ # # Copy uploaded file to data directory
190
+ # file_name = Path(uploaded_file.name).name
191
+ # file_path = data_dir / file_name
192
+ # shutil.copy(uploaded_file.name, file_path)
193
+
194
+ # file_info = f"\n\nπŸ“Ž **Uploaded file:** `{file_path}`\n"
195
+
196
+ # # Augment prompt with file path
197
+ # if prompt.strip():
198
+ # prompt = f"{prompt}\n\nUploaded file path: {file_path}"
199
+ # else:
200
+ # prompt = f"I have uploaded a file at: {file_path}. Please analyze it."
201
+
202
+ # except Exception as e:
203
+ # error_msg = f"❌ Error handling file upload: {str(e)}"
204
+ # chatbot_history.append({
205
+ # "role": "assistant",
206
+ # "content": error_msg
207
+ # })
208
+ # yield chatbot_history, None, None, None, None, error_msg
209
+ # return
210
+
211
+ # # Add user message to chat
212
+ # user_message = prompt if not file_info else f"{prompt}{file_info}"
213
+ # chatbot_history.append({"role": "user", "content": user_message})
214
+ # yield chatbot_history, None, None, None, None, "πŸ”„ Processing..."
215
+
216
+ # try:
217
+ # # Stream agent responses
218
+ # step_count = 0
219
+ # for step in agent.go_stream(prompt):
220
+ # step_count += 1
221
+ # output = step.get("output", "")
222
+
223
+ # if output:
224
+ # # Parse the output
225
+ # parsed = parse_agent_output(output)
226
+
227
+ # # Add thinking text as separate message if present
228
+ # if parsed.get("thinking"):
229
+ # chatbot_history.append({
230
+ # "role": "assistant",
231
+ # "content": parsed["thinking"]
232
+ # })
233
+
234
+ # # Add the block (code/observation/solution) as separate message if present
235
+ # if parsed["type"] == "code" and parsed["code"]:
236
+ # chatbot_history.append({
237
+ # "role": "assistant",
238
+ # "content": f"### πŸ’» Executing Code\n\n```python\n{parsed['code']}\n```"
239
+ # })
240
+ # elif parsed["type"] == "observation" and parsed["observation"]:
241
+ # chatbot_history.append({
242
+ # "role": "assistant",
243
+ # "content": f"### πŸ“Š Observation\n\n```\n{parsed['observation']}\n```"
244
+ # })
245
+ # elif parsed["type"] == "solution":
246
+ # chatbot_history.append({
247
+ # "role": "assistant",
248
+ # "content": f"### βœ… Solution\n\n{parsed['content']}"
249
+ # })
250
+ # elif parsed["type"] == "text" and parsed["content"]:
251
+ # # Only add if we haven't already added it as thinking
252
+ # if not parsed.get("thinking"):
253
+ # chatbot_history.append({
254
+ # "role": "assistant",
255
+ # "content": parsed["content"]
256
+ # })
257
+
258
+ # # Check for output files after each step
259
+ # images, data_files = check_for_output_files()
260
+
261
+ # # Create status message
262
+ # status = f"πŸ”„ Step {step_count}"
263
+ # if parsed["type"] == "code":
264
+ # status += " - Executing code..."
265
+ # elif parsed["type"] == "observation":
266
+ # status += " - Processing results..."
267
+ # elif parsed["type"] == "solution":
268
+ # status += " - Finalizing solution..."
269
+
270
+ # yield (
271
+ # chatbot_history,
272
+ # images if images else None,
273
+ # data_files if data_files else None,
274
+ # None,
275
+ # None,
276
+ # status
277
+ # )
278
+
279
+ # # Final check for files
280
+ # final_images, final_data = check_for_output_files()
281
+
282
+ # # Create download links message if files were generated
283
+ # if final_images or final_data:
284
+ # download_msg = "\n\n---\n\n### πŸ“ Generated Files Ready for Download\n\n"
285
+
286
+ # if final_images:
287
+ # download_msg += f"**πŸ–ΌοΈ Images ({len(final_images)})** - Available in the **Images** tab β†’\n"
288
+ # for img_path in final_images:
289
+ # img_name = Path(img_path).name
290
+ # download_msg += f"- `{img_name}`\n"
291
+ # download_msg += "\n"
292
+
293
+ # if final_data:
294
+ # download_msg += f"**πŸ“„ Data Files ({len(final_data)})** - Available in the **Data** tab β†’\n"
295
+ # for data_path in final_data:
296
+ # data_name = Path(data_path).name
297
+ # download_msg += f"- `{data_name}`\n"
298
+
299
+ # download_msg += "\n*Click the download button on each file in the respective tabs above.*"
300
+
301
+ # # Add download message as separate bubble
302
+ # chatbot_history.append({
303
+ # "role": "assistant",
304
+ # "content": download_msg
305
+ # })
306
+
307
+ # status = "βœ… Complete"
308
+ # if final_images:
309
+ # status += f" | {len(final_images)} image(s)"
310
+ # if final_data:
311
+ # status += f" | {len(final_data)} data file(s)"
312
+
313
+ # yield chatbot_history, final_images if final_images else None, final_data if final_data else None, None, None, status
314
+
315
+ # except Exception as e:
316
+ # error_msg = f"❌ Error: {str(e)}\n\n```\n{traceback.format_exc()}\n```"
317
+ # chatbot_history.append({
318
+ # "role": "assistant",
319
+ # "content": error_msg
320
+ # })
321
+ # yield chatbot_history, None, None, None, None, "❌ Error occurred"
322
+
323
+
324
+ # def validate_passcode(passcode):
325
+ # """Validate the passcode and initialize the agent."""
326
+ # global agent
327
+
328
+ # if passcode == PASSCODE:
329
+ # # Initialize agent
330
+ # try:
331
+ # agent = A1()
332
+ # return (
333
+ # gr.update(visible=False), # Hide passcode section
334
+ # gr.update(visible=True), # Show main interface
335
+ # "βœ… Access granted! Agent initialized and ready."
336
+ # )
337
+ # except Exception as e:
338
+ # error_trace = traceback.format_exc()
339
+ # return (
340
+ # gr.update(visible=True),
341
+ # gr.update(visible=False),
342
+ # f"❌ Error initializing agent:\n{str(e)}\n\n{error_trace}"
343
+ # )
344
+ # else:
345
+ # return (
346
+ # gr.update(visible=True),
347
+ # gr.update(visible=False),
348
+ # "❌ Invalid passcode. Please try again."
349
+ # )
350
+
351
+
352
+ # def clear_chat():
353
+ # """Clear the chat history and output files."""
354
+ # # Clean up output directory
355
+ # output_dir = Path("./output")
356
+ # if output_dir.exists():
357
+ # shutil.rmtree(output_dir)
358
+ # output_dir.mkdir(exist_ok=True)
359
+
360
+ # # Clean up data directory
361
+ # data_dir = Path("./data")
362
+ # if data_dir.exists():
363
+ # for file in data_dir.iterdir():
364
+ # if file.is_file():
365
+ # file.unlink()
366
+
367
+ # return [], None, None, None, None, "πŸ—‘οΈ Chat cleared"
368
+
369
+
370
+ # # Create Gradio interface with custom theme
371
+ # custom_theme = gr.themes.Soft(
372
+ # primary_hue="blue",
373
+ # secondary_hue="slate",
374
+ # spacing_size="sm",
375
+ # radius_size="md",
376
+ # ).set(
377
+ # button_primary_background_fill="*primary_500",
378
+ # button_primary_background_fill_hover="*primary_600",
379
+ # block_label_text_weight="600",
380
+ # block_title_text_weight="600",
381
+ # )
382
+
383
+ # with gr.Blocks(title="HistoPath Agent", theme=custom_theme, css="""
384
+ # .gradio-container {
385
+ # max-width: 100% !important;
386
+ # }
387
+ # .main-header {
388
+ # text-align: center;
389
+ # padding: 1.5rem 0;
390
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
391
+ # color: white;
392
+ # border-radius: 8px;
393
+ # margin-bottom: 1.5rem;
394
+ # }
395
+ # .main-header h1 {
396
+ # margin: 0;
397
+ # font-size: 2.2rem;
398
+ # font-weight: 700;
399
+ # }
400
+ # .main-header p {
401
+ # margin: 0.5rem 0 0 0;
402
+ # opacity: 0.95;
403
+ # font-size: 1.1rem;
404
+ # }
405
+ # .file-upload-box .wrap {
406
+ # min-width: 0 !important;
407
+ # }
408
+ # .file-upload-box .file-name {
409
+ # word-break: break-word !important;
410
+ # white-space: normal !important;
411
+ # overflow-wrap: break-word !important;
412
+ # }
413
+ # .tab-nav {
414
+ # margin-bottom: 0.5rem;
415
+ # }
416
+ # /* Better styling for code and observation blocks */
417
+ # .message.bot pre {
418
+ # background-color: #f6f8fa !important;
419
+ # border: 1px solid #d0d7de !important;
420
+ # border-radius: 6px !important;
421
+ # padding: 12px !important;
422
+ # margin: 8px 0 !important;
423
+ # }
424
+ # .message.bot h3 {
425
+ # margin-top: 12px !important;
426
+ # margin-bottom: 8px !important;
427
+ # font-weight: 600 !important;
428
+ # }
429
+ # .message.bot hr {
430
+ # border: none !important;
431
+ # border-top: 2px solid #e1e4e8 !important;
432
+ # margin: 16px 0 !important;
433
+ # }
434
+ # """) as demo:
435
+
436
+ # # Header
437
+ # gr.HTML("""
438
+ # <div class="main-header">
439
+ # <h1>πŸ”¬ HistoPath Agent</h1>
440
+ # <p>AI-Powered Histopathology Analysis Assistant</p>
441
+ # </div>
442
+ # """)
443
+
444
+ # # Passcode section
445
+ # with gr.Group(visible=True) as passcode_section:
446
+ # gr.Markdown("### πŸ” Authentication Required")
447
+
448
+ # with gr.Row():
449
+ # passcode_input = gr.Textbox(
450
+ # label="Passcode",
451
+ # type="password",
452
+ # placeholder="Enter your passcode...",
453
+ # scale=3
454
+ # )
455
+ # passcode_btn = gr.Button("πŸ”“ Unlock", variant="primary", scale=1, size="lg")
456
+
457
+ # passcode_status = gr.Textbox(
458
+ # label="Status",
459
+ # interactive=False,
460
+ # lines=2
461
+ # )
462
+
463
+ # # Main interface (hidden initially)
464
+ # with gr.Group(visible=False) as main_interface:
465
+ # with gr.Row(equal_height=True):
466
+ # # Left column - Chat interface
467
+ # with gr.Column(scale=3):
468
+ # chatbot = gr.Chatbot(
469
+ # label="πŸ’¬ Conversation",
470
+ # height=550,
471
+ # # type="messages",
472
+ # show_label=True,
473
+ # render_markdown=True,
474
+ # )
475
+
476
+ # # Input area
477
+ # with gr.Row():
478
+ # with gr.Column(scale=7):
479
+ # prompt_input = gr.Textbox(
480
+ # label="Your Query",
481
+ # placeholder="E.g., 'Caption the uploaded whole slide image' or 'Segment cells using instanseg model'",
482
+ # lines=2,
483
+ # max_lines=5,
484
+ # show_label=False,
485
+ # )
486
+ # with gr.Column(scale=3):
487
+ # file_upload = gr.File(
488
+ # label="πŸ“Ž Upload File",
489
+ # file_types=[".svs", ".png", ".jpg", ".jpeg", ".tif", ".tiff", ".csv", ".txt", ".json", ".npy"],
490
+ # height=75,
491
+ # elem_classes="file-upload-box",
492
+ # )
493
+
494
+ # with gr.Row():
495
+ # submit_btn = gr.Button("πŸš€ Submit", variant="primary", scale=3, size="lg")
496
+ # clear_btn = gr.Button("πŸ—‘οΈ Clear", scale=1, size="lg", variant="secondary")
497
+
498
+ # status_text = gr.Textbox(
499
+ # label="Status",
500
+ # interactive=False,
501
+ # value="Ready",
502
+ # show_label=False,
503
+ # container=False,
504
+ # )
505
+
506
+ # # Right column - Outputs
507
+ # with gr.Column(scale=2):
508
+ # with gr.Tabs():
509
+ # with gr.Tab("πŸ“₯ Input"):
510
+ # with gr.Column():
511
+ # input_image_preview = gr.Image(
512
+ # label="Input Image",
513
+ # height=400,
514
+ # show_label=False,
515
+ # container=True,
516
+ # )
517
+ # input_file_preview = gr.File(
518
+ # label="Input File",
519
+ # interactive=False,
520
+ # height=100,
521
+ # show_label=False,
522
+ # container=True,
523
+ # )
524
+ # input_status = gr.Textbox(
525
+ # value="Upload a file to preview",
526
+ # show_label=False,
527
+ # interactive=False,
528
+ # container=False,
529
+ # )
530
+
531
+ # with gr.Tab("πŸ–ΌοΈ Images"):
532
+ # output_gallery = gr.Gallery(
533
+ # label="Generated Visualizations",
534
+ # columns=1,
535
+ # height=600,
536
+ # object_fit="contain",
537
+ # show_label=False,
538
+ # show_download_button=True,
539
+ # )
540
+
541
+ # with gr.Tab("πŸ“„ Data"):
542
+ # data_files = gr.File(
543
+ # label="Generated Data Files",
544
+ # file_count="multiple",
545
+ # interactive=False,
546
+ # height=600,
547
+ # show_label=False,
548
+ # )
549
+
550
+ # # Event handlers
551
+ # passcode_btn.click(
552
+ # fn=validate_passcode,
553
+ # inputs=[passcode_input],
554
+ # outputs=[passcode_section, main_interface, passcode_status]
555
+ # )
556
+
557
+ # # File upload preview
558
+ # file_upload.change(
559
+ # fn=preview_uploaded_file,
560
+ # inputs=[file_upload],
561
+ # outputs=[input_image_preview, input_file_preview, input_status]
562
+ # )
563
+
564
+ # submit_btn.click(
565
+ # fn=process_agent_response,
566
+ # inputs=[prompt_input, file_upload, chatbot],
567
+ # outputs=[chatbot, output_gallery, data_files, input_image_preview, input_file_preview, status_text]
568
+ # )
569
+
570
+ # clear_btn.click(
571
+ # fn=clear_chat,
572
+ # outputs=[chatbot, output_gallery, data_files, input_image_preview, input_file_preview, status_text]
573
+ # )
574
+
575
+ # # Allow enter key to submit
576
+ # prompt_input.submit(
577
+ # fn=process_agent_response,
578
+ # inputs=[prompt_input, file_upload, chatbot],
579
+ # outputs=[chatbot, output_gallery, data_files, input_image_preview, input_file_preview, status_text]
580
+ # )
581
+
582
+
583
+ # if __name__ == "__main__":
584
+ # # Create necessary directories
585
+ # Path("./data").mkdir(exist_ok=True)
586
+ # Path("./output").mkdir(exist_ok=True)
587
+
588
+ # print("=" * 60)
589
+ # print("πŸ”¬ HistoPath Agent - Gradio Interface")
590
+ # print("=" * 60)
591
+ # # print(f"Passcode: {PASSCODE}")
592
+ # print("Starting server...")
593
+ # print("=" * 60)
594
+
595
+ # # Launch the app
596
+ # #demo.launch(
597
+ # # server_name="0.0.0.0",
598
+ # # server_port=7860, # Let Gradio auto-pick an available port
599
+ # # share=True,
600
+ # # show_error=True,
601
+ # #)
602
+ # demo.launch()