Update app.py
Browse files
app.py
CHANGED
@@ -53,6 +53,9 @@ sys.path.insert(0, src_path)
|
|
53 |
|
54 |
from txagent.txagent import TxAgent
|
55 |
|
|
|
|
|
|
|
56 |
# ==================== UTILITY FUNCTIONS ====================
|
57 |
def sanitize_text(text: str) -> str:
|
58 |
"""Clean and sanitize text input"""
|
@@ -303,9 +306,11 @@ class ClinicalOversightApp:
|
|
303 |
|
304 |
def cleanup_resources(self):
|
305 |
"""Clean up GPU memory and collect garbage"""
|
|
|
306 |
torch.cuda.empty_cache()
|
307 |
gc.collect()
|
308 |
if torch.distributed.is_initialized():
|
|
|
309 |
torch.distributed.destroy_process_group()
|
310 |
|
311 |
def process_response_stream(self, prompt: str, history: List[dict]) -> Generator[dict, None, None]:
|
@@ -453,6 +458,7 @@ Patient Record (Chunk {chunk_idx}/{len(chunks)}):
|
|
453 |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
454 |
background: var(--background);
|
455 |
color: var(--text-color);
|
|
|
456 |
}
|
457 |
.gradio-container {
|
458 |
max-width: 800px;
|
@@ -514,7 +520,9 @@ Patient Record (Chunk {chunk_idx}/{len(chunks)}):
|
|
514 |
color: var(--text-color);
|
515 |
outline: none;
|
516 |
font-size: 1em;
|
517 |
-
|
|
|
|
|
518 |
}
|
519 |
.send-btn {
|
520 |
background: linear-gradient(135deg, #007bff, #0056b3);
|
@@ -529,7 +537,7 @@ Patient Record (Chunk {chunk_idx}/{len(chunks)}):
|
|
529 |
transform: scale(1.05);
|
530 |
}
|
531 |
.send-btn:active {
|
532 |
-
animation:
|
533 |
}
|
534 |
.sidebar {
|
535 |
background: var(--sidebar-bg);
|
@@ -544,6 +552,8 @@ Patient Record (Chunk {chunk_idx}/{len(chunks)}):
|
|
544 |
top: 100px;
|
545 |
width: 300px;
|
546 |
z-index: 1000;
|
|
|
|
|
547 |
}
|
548 |
.sidebar-hidden {
|
549 |
transform: translateX(100%);
|
@@ -578,13 +588,31 @@ Patient Record (Chunk {chunk_idx}/{len(chunks)}):
|
|
578 |
align-items: center;
|
579 |
gap: 8px;
|
580 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
581 |
.loading-spinner {
|
582 |
position: absolute;
|
583 |
bottom: 80px;
|
584 |
left: 50%;
|
585 |
transform: translateX(-50%);
|
586 |
font-size: 1.2em;
|
587 |
-
animation:
|
588 |
}
|
589 |
.typing-indicator {
|
590 |
display: none;
|
@@ -597,20 +625,46 @@ Patient Record (Chunk {chunk_idx}/{len(chunks)}):
|
|
597 |
display: block;
|
598 |
animation: blink 1s step-end infinite;
|
599 |
}
|
600 |
-
|
601 |
-
|
602 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
603 |
}
|
604 |
@keyframes blink {
|
605 |
50% { opacity: 0.3; }
|
606 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
607 |
:root {
|
608 |
--background: #ffffff;
|
609 |
--text-color: #333333;
|
610 |
--chat-bg: #f9fafb;
|
611 |
--message-bg: #e5e5ea;
|
612 |
--sidebar-bg: #f1f3f5;
|
613 |
-
transition: background 0.3s ease, color 0.3s ease;
|
614 |
}
|
615 |
[data-theme="dark"] {
|
616 |
--background: #1e2a44;
|
@@ -655,6 +709,13 @@ Patient Record (Chunk {chunk_idx}/{len(chunks)}):
|
|
655 |
function toggleSidebar() {
|
656 |
const sidebar = document.querySelector('.sidebar');
|
657 |
sidebar.classList.toggle('sidebar-hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
658 |
}
|
659 |
|
660 |
document.addEventListener('DOMContentLoaded', () => {
|
@@ -665,165 +726,170 @@ Patient Record (Chunk {chunk_idx}/{len(chunks)}):
|
|
665 |
"""
|
666 |
|
667 |
with gr.Blocks(theme=gr.themes.Default(), css=css, js=js, title="Clinical Oversight Assistant") as app:
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
<
|
674 |
-
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
680 |
-
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
691 |
)
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
702 |
-
|
703 |
-
|
704 |
-
|
705 |
-
|
|
|
|
|
|
|
|
|
|
|
706 |
)
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
|
|
|
|
712 |
)
|
713 |
-
|
714 |
-
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
720 |
)
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
|
725 |
-
|
726 |
-
|
727 |
-
|
728 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
729 |
)
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
|
|
|
|
|
|
734 |
)
|
735 |
-
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
)
|
742 |
-
|
743 |
-
def show_loading(state: bool) -> dict:
|
744 |
-
return {
|
745 |
-
"value": "<div class='loading-spinner'>⏳</div>" if state else "<div class='loading-spinner' style='display: none;'>⏳</div>",
|
746 |
-
"visible": state
|
747 |
-
}
|
748 |
-
|
749 |
-
def show_typing(state: bool) -> dict:
|
750 |
-
return {
|
751 |
-
"value": f"<div class='typing-indicator{' active' if state else ''}'>Typing...</div>",
|
752 |
-
"visible": state
|
753 |
-
}
|
754 |
-
|
755 |
-
# Theme toggle handler
|
756 |
-
theme_button.click(
|
757 |
-
self.toggle_theme,
|
758 |
-
inputs=[theme_state],
|
759 |
-
outputs=[theme_state, theme_button],
|
760 |
-
_js="theme => applyTheme(theme)"
|
761 |
-
)
|
762 |
-
|
763 |
-
# Sidebar toggle handler
|
764 |
-
tools_button.click(
|
765 |
-
self.toggle_sidebar,
|
766 |
-
inputs=[sidebar_state],
|
767 |
-
outputs=[sidebar_state],
|
768 |
-
_js="() => toggleSidebar()"
|
769 |
-
)
|
770 |
-
|
771 |
-
# Analysis handlers
|
772 |
-
send_btn.click(
|
773 |
-
show_loading,
|
774 |
-
inputs=[gr.State(value=True)],
|
775 |
-
outputs=[chatbot]
|
776 |
-
).then(
|
777 |
-
show_typing,
|
778 |
-
inputs=[gr.State(value=True)],
|
779 |
-
outputs=[chatbot]
|
780 |
-
).then(
|
781 |
-
self.analyze,
|
782 |
-
inputs=[msg_input, chatbot, file_upload],
|
783 |
-
outputs=[chatbot, download_output, final_summary, progress_text],
|
784 |
-
show_progress="hidden"
|
785 |
-
).then(
|
786 |
-
show_loading,
|
787 |
-
inputs=[gr.State(value=False)],
|
788 |
-
outputs=[chatbot]
|
789 |
-
).then(
|
790 |
-
show_typing,
|
791 |
-
inputs=[gr.State(value=False)],
|
792 |
-
outputs=[chatbot]
|
793 |
-
)
|
794 |
-
|
795 |
-
msg_input.submit(
|
796 |
-
show_loading,
|
797 |
-
inputs=[gr.State(value=True)],
|
798 |
-
outputs=[chatbot]
|
799 |
-
).then(
|
800 |
-
show_typing,
|
801 |
-
inputs=[gr.State(value=True)],
|
802 |
-
outputs=[chatbot]
|
803 |
-
).then(
|
804 |
-
self.analyze,
|
805 |
-
inputs=[msg_input, chatbot, file_upload],
|
806 |
-
outputs=[chatbot, download_output, final_summary, progress_text],
|
807 |
-
show_progress="hidden"
|
808 |
-
).then(
|
809 |
-
show_loading,
|
810 |
-
inputs=[gr.State(value=False)],
|
811 |
-
outputs=[chatbot]
|
812 |
-
).then(
|
813 |
-
show_typing,
|
814 |
-
inputs=[gr.State(value=False)],
|
815 |
-
outputs=[chatbot]
|
816 |
-
)
|
817 |
-
|
818 |
-
app.load(
|
819 |
-
lambda: [
|
820 |
-
[], None, "", "", None, {"visible": False}, "light", False, "🌙 Dark Mode"
|
821 |
-
],
|
822 |
-
outputs=[chatbot, download_output, final_summary, msg_input, file_upload, progress_text, theme_state, sidebar_state, theme_button],
|
823 |
-
queue=False
|
824 |
-
)
|
825 |
-
|
826 |
-
return app
|
827 |
|
828 |
# ==================== APPLICATION ENTRY POINT ====================
|
829 |
if __name__ == "__main__":
|
|
|
53 |
|
54 |
from txagent.txagent import TxAgent
|
55 |
|
56 |
+
# Log Gradio version for debugging
|
57 |
+
logger.info(f"Gradio version: {gr.__version__}")
|
58 |
+
|
59 |
# ==================== UTILITY FUNCTIONS ====================
|
60 |
def sanitize_text(text: str) -> str:
|
61 |
"""Clean and sanitize text input"""
|
|
|
306 |
|
307 |
def cleanup_resources(self):
|
308 |
"""Clean up GPU memory and collect garbage"""
|
309 |
+
logger.info("Cleaning up resources...")
|
310 |
torch.cuda.empty_cache()
|
311 |
gc.collect()
|
312 |
if torch.distributed.is_initialized():
|
313 |
+
logger.info("Destroying PyTorch distributed process group...")
|
314 |
torch.distributed.destroy_process_group()
|
315 |
|
316 |
def process_response_stream(self, prompt: str, history: List[dict]) -> Generator[dict, None, None]:
|
|
|
458 |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
459 |
background: var(--background);
|
460 |
color: var(--text-color);
|
461 |
+
transition: all 0.4s ease;
|
462 |
}
|
463 |
.gradio-container {
|
464 |
max-width: 800px;
|
|
|
520 |
color: var(--text-color);
|
521 |
outline: none;
|
522 |
font-size: 1em;
|
523 |
+
}
|
524 |
+
.input-textbox:focus {
|
525 |
+
border-bottom: 2px solid #007bff;
|
526 |
}
|
527 |
.send-btn {
|
528 |
background: linear-gradient(135deg, #007bff, #0056b3);
|
|
|
537 |
transform: scale(1.05);
|
538 |
}
|
539 |
.send-btn:active {
|
540 |
+
animation: glow 0.3s ease;
|
541 |
}
|
542 |
.sidebar {
|
543 |
background: var(--sidebar-bg);
|
|
|
552 |
top: 100px;
|
553 |
width: 300px;
|
554 |
z-index: 1000;
|
555 |
+
backdrop-filter: blur(10px);
|
556 |
+
background: rgba(241, 243, 245, 0.8);
|
557 |
}
|
558 |
.sidebar-hidden {
|
559 |
transform: translateX(100%);
|
|
|
588 |
align-items: center;
|
589 |
gap: 8px;
|
590 |
}
|
591 |
+
.tooltip {
|
592 |
+
position: relative;
|
593 |
+
}
|
594 |
+
.tooltip:hover::after {
|
595 |
+
content: attr(data-tooltip);
|
596 |
+
position: absolute;
|
597 |
+
bottom: 100%;
|
598 |
+
left: 50%;
|
599 |
+
transform: translateX(-50%);
|
600 |
+
background: #333;
|
601 |
+
color: white;
|
602 |
+
padding: 6px 12px;
|
603 |
+
border-radius: 6px;
|
604 |
+
font-size: 0.85em;
|
605 |
+
white-space: nowrap;
|
606 |
+
z-index: 1000;
|
607 |
+
animation: fadeIn 0.3s ease;
|
608 |
+
}
|
609 |
.loading-spinner {
|
610 |
position: absolute;
|
611 |
bottom: 80px;
|
612 |
left: 50%;
|
613 |
transform: translateX(-50%);
|
614 |
font-size: 1.2em;
|
615 |
+
animation: glow 1.5s ease infinite;
|
616 |
}
|
617 |
.typing-indicator {
|
618 |
display: none;
|
|
|
625 |
display: block;
|
626 |
animation: blink 1s step-end infinite;
|
627 |
}
|
628 |
+
.progress-text {
|
629 |
+
position: relative;
|
630 |
+
padding: 8px;
|
631 |
+
background: var(--message-bg);
|
632 |
+
border-radius: 8px;
|
633 |
+
margin-top: 12px;
|
634 |
+
}
|
635 |
+
.progress-text::before {
|
636 |
+
content: '';
|
637 |
+
position: absolute;
|
638 |
+
top: 0;
|
639 |
+
left: 0;
|
640 |
+
height: 100%;
|
641 |
+
width: 0;
|
642 |
+
background: #007bff;
|
643 |
+
opacity: 0.2;
|
644 |
+
animation: progress 2s linear infinite;
|
645 |
+
}
|
646 |
+
@keyframes glow {
|
647 |
+
0%, 100% { transform: translateX(-50%) scale(1); opacity: 1; color: #007bff; }
|
648 |
+
50% { transform: translateX(-50%) scale(1.2); opacity: 0.7; color: #0056b3; }
|
649 |
}
|
650 |
@keyframes blink {
|
651 |
50% { opacity: 0.3; }
|
652 |
}
|
653 |
+
@keyframes fadeIn {
|
654 |
+
from { opacity: 0; }
|
655 |
+
to { opacity: 1; }
|
656 |
+
}
|
657 |
+
@keyframes progress {
|
658 |
+
0% { width: 0; }
|
659 |
+
50% { width: 50%; }
|
660 |
+
100% { width: 0; }
|
661 |
+
}
|
662 |
:root {
|
663 |
--background: #ffffff;
|
664 |
--text-color: #333333;
|
665 |
--chat-bg: #f9fafb;
|
666 |
--message-bg: #e5e5ea;
|
667 |
--sidebar-bg: #f1f3f5;
|
|
|
668 |
}
|
669 |
[data-theme="dark"] {
|
670 |
--background: #1e2a44;
|
|
|
709 |
function toggleSidebar() {
|
710 |
const sidebar = document.querySelector('.sidebar');
|
711 |
sidebar.classList.toggle('sidebar-hidden');
|
712 |
+
if (!sidebar.classList.contains('sidebar-hidden')) {
|
713 |
+
setTimeout(() => {
|
714 |
+
if (window.innerWidth <= 600) {
|
715 |
+
sidebar.classList.add('sidebar-hidden');
|
716 |
+
}
|
717 |
+
}, 5000);
|
718 |
+
}
|
719 |
}
|
720 |
|
721 |
document.addEventListener('DOMContentLoaded', () => {
|
|
|
726 |
"""
|
727 |
|
728 |
with gr.Blocks(theme=gr.themes.Default(), css=css, js=js, title="Clinical Oversight Assistant") as app:
|
729 |
+
try:
|
730 |
+
theme_state = gr.State(value="light")
|
731 |
+
sidebar_state = gr.State(value=False)
|
732 |
+
|
733 |
+
gr.HTML("""
|
734 |
+
<div class='header'>
|
735 |
+
<h1 style='color: var(--text-color);'>🩺 Clinical Oversight Assistant</h1>
|
736 |
+
<p style='color: var(--text-color); opacity: 0.7;'>
|
737 |
+
AI-powered analysis of patient records for missed diagnoses
|
738 |
+
</p>
|
739 |
+
</div>
|
740 |
+
<div class='sidebar-backdrop'></div>
|
741 |
+
""")
|
742 |
+
|
743 |
+
theme_button = gr.Button("🌙 Dark Mode", elem_classes="theme-toggle")
|
744 |
+
|
745 |
+
with gr.Column(elem_classes="chat-container"):
|
746 |
+
chatbot = gr.Chatbot(
|
747 |
+
label="Clinical Analysis",
|
748 |
+
height="100%",
|
749 |
+
show_copy_button=True,
|
750 |
+
type="messages",
|
751 |
+
elem_classes="chatbot",
|
752 |
+
render_markdown=True
|
753 |
+
)
|
754 |
+
gr.HTML("<div class='loading-spinner' style='display: none;'>⏳</div>")
|
755 |
+
gr.HTML("<div class='typing-indicator'>Typing...</div>")
|
756 |
+
|
757 |
+
with gr.Row():
|
758 |
+
tools_button = gr.Button("📂 Tools", variant="secondary")
|
759 |
+
|
760 |
+
with gr.Column(elem_classes="sidebar"):
|
761 |
+
gr.Markdown("### 📎 Upload Records", elem_classes="tooltip", data_tooltip="Upload patient records")
|
762 |
+
file_upload = gr.File(
|
763 |
+
file_types=[".pdf", ".csv", ".xls", ".xlsx"],
|
764 |
+
file_count="multiple",
|
765 |
+
label="Patient Records",
|
766 |
+
elem_classes="tooltip",
|
767 |
+
data_tooltip="Select PDF, CSV, or Excel files"
|
768 |
+
)
|
769 |
+
gr.Markdown("### 📝 Analysis Summary", elem_classes="tooltip", data_tooltip="Summary of findings")
|
770 |
+
final_summary = gr.Markdown(
|
771 |
+
"Analysis results will appear here...",
|
772 |
+
elem_classes="tooltip",
|
773 |
+
data_tooltip="View analysis results"
|
774 |
+
)
|
775 |
+
gr.Markdown("### 📄 Full Report", elem_classes="tooltip", data_tooltip="Download full report")
|
776 |
+
download_output = gr.File(
|
777 |
+
label="Download Report",
|
778 |
+
visible=False,
|
779 |
+
interactive=False,
|
780 |
+
elem_classes="tooltip",
|
781 |
+
data_tooltip="Download analysis report"
|
782 |
+
)
|
783 |
+
|
784 |
+
with gr.Row(elem_classes="input-container"):
|
785 |
+
msg_input = gr.Textbox(
|
786 |
+
placeholder="Ask about potential oversights or upload files...",
|
787 |
+
show_label=False,
|
788 |
+
container=False,
|
789 |
+
elem_classes="input-textbox",
|
790 |
+
autofocus=True
|
791 |
+
)
|
792 |
+
send_btn = gr.Button(
|
793 |
+
"Analyze",
|
794 |
+
variant="primary",
|
795 |
+
elem_classes="send-btn"
|
796 |
+
)
|
797 |
+
|
798 |
+
progress_text = gr.Textbox(
|
799 |
+
label="Progress Status",
|
800 |
+
visible=False,
|
801 |
+
interactive=False,
|
802 |
+
elem_classes="progress-text"
|
803 |
)
|
804 |
+
|
805 |
+
def show_loading(state: bool) -> dict:
|
806 |
+
return {
|
807 |
+
"value": "<div class='loading-spinner'>⏳</div>" if state else "<div class='loading-spinner' style='display: none;'>⏳</div>",
|
808 |
+
"visible": state
|
809 |
+
}
|
810 |
+
|
811 |
+
def show_typing(state: bool) -> dict:
|
812 |
+
return {
|
813 |
+
"value": f"<div class='typing-indicator{' active' if state else ''}'>Typing...</div>",
|
814 |
+
"visible": state
|
815 |
+
}
|
816 |
+
|
817 |
+
# Theme toggle handler
|
818 |
+
theme_button.click(
|
819 |
+
fn=self.toggle_theme,
|
820 |
+
inputs=[theme_state],
|
821 |
+
outputs=[theme_state, theme_button],
|
822 |
+
_js="function(theme) { applyTheme(theme); }"
|
823 |
)
|
824 |
+
|
825 |
+
# Sidebar toggle handler
|
826 |
+
tools_button.click(
|
827 |
+
fn=self.toggle_sidebar,
|
828 |
+
inputs=[sidebar_state],
|
829 |
+
outputs=[sidebar_state],
|
830 |
+
_js="toggleSidebar"
|
831 |
)
|
832 |
+
|
833 |
+
# Analysis handlers
|
834 |
+
send_btn.click(
|
835 |
+
fn=show_loading,
|
836 |
+
inputs=[gr.State(value=True)],
|
837 |
+
outputs=[chatbot]
|
838 |
+
).then(
|
839 |
+
fn=show_typing,
|
840 |
+
inputs=[gr.State(value=True)],
|
841 |
+
outputs=[chatbot]
|
842 |
+
).then(
|
843 |
+
fn=self.analyze,
|
844 |
+
inputs=[msg_input, chatbot, file_upload],
|
845 |
+
outputs=[chatbot, download_output, final_summary, progress_text],
|
846 |
+
show_progress="hidden"
|
847 |
+
).then(
|
848 |
+
fn=show_loading,
|
849 |
+
inputs=[gr.State(value=False)],
|
850 |
+
outputs=[chatbot]
|
851 |
+
).then(
|
852 |
+
fn=show_typing,
|
853 |
+
inputs=[gr.State(value=False)],
|
854 |
+
outputs=[chatbot]
|
855 |
)
|
856 |
+
|
857 |
+
msg_input.submit(
|
858 |
+
fn=show_loading,
|
859 |
+
inputs=[gr.State(value=True)],
|
860 |
+
outputs=[chatbot]
|
861 |
+
).then(
|
862 |
+
fn=show_typing,
|
863 |
+
inputs=[gr.State(value=True)],
|
864 |
+
outputs=[chatbot]
|
865 |
+
).then(
|
866 |
+
fn=self.analyze,
|
867 |
+
inputs=[msg_input, chatbot, file_upload],
|
868 |
+
outputs=[chatbot, download_output, final_summary, progress_text],
|
869 |
+
show_progress="hidden"
|
870 |
+
).then(
|
871 |
+
fn=show_loading,
|
872 |
+
inputs=[gr.State(value=False)],
|
873 |
+
outputs=[chatbot]
|
874 |
+
).then(
|
875 |
+
fn=show_typing,
|
876 |
+
inputs=[gr.State(value=False)],
|
877 |
+
outputs=[chatbot]
|
878 |
)
|
879 |
+
|
880 |
+
app.load(
|
881 |
+
fn=lambda: [
|
882 |
+
[], None, "", "", None, {"visible": False}, "light", False, "🌙 Dark Mode"
|
883 |
+
],
|
884 |
+
outputs=[chatbot, download_output, final_summary, msg_input, file_upload, progress_text, theme_state, sidebar_state, theme_button],
|
885 |
+
queue=False
|
886 |
)
|
887 |
+
|
888 |
+
except Exception as e:
|
889 |
+
logger.error(f"Interface creation failed: {e}")
|
890 |
+
self.cleanup_resources()
|
891 |
+
raise
|
892 |
+
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
893 |
|
894 |
# ==================== APPLICATION ENTRY POINT ====================
|
895 |
if __name__ == "__main__":
|