lkjjj26 commited on
Commit
d077a6b
ยท
verified ยท
1 Parent(s): 23e94ce

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +414 -129
app.py CHANGED
@@ -492,35 +492,44 @@ def pdbsummary(name):
492
 
493
  return answer
494
 
495
- def render_html(pdb_id, chain="A"):
496
- if pdb_id is None or chain is None:
497
  return ""
498
  html_content = f"""
 
499
  <html>
500
- <header>
501
  <script src="https://3Dmol.org/build/3Dmol-min.js"></script>
502
  <script src="https://3Dmol.org/build/3Dmol.ui-min.js"></script>
503
- </header>
 
 
 
 
 
 
 
504
  <body>
505
- <div style="height: 400px; position: relative;" class="viewer_3Dmoljs"
506
  data-pdb="{pdb_id}"
507
- data-backgroundalpha="0.0"
508
- data-style="cartoon:color=white"
509
- data-select1="chain:{chain}"
510
- data-zoomto="chain:{chain}"
511
- data-style1="cartoon:color=spectrum"
512
  data-spin="axis:y;speed:0.2">
513
  </div>
514
  </body>
515
  </html>
516
  """
517
- iframe = f"""
518
- <iframe style="width: 100%; height: 480px; border: none;"
519
- srcdoc='{html_content}'>
520
- </iframe>
521
- """
522
- return iframe
523
 
 
 
 
 
 
 
 
 
 
 
524
  def create_interactive_table(df):
525
  if df.empty:
526
  return go.Figure()
@@ -538,7 +547,7 @@ def create_interactive_table(df):
538
  values=list(df.columns),
539
  fill_color='paleturquoise',
540
  align='center', # ํ—ค๋” ์ค‘์•™ ์ •๋ ฌ
541
- font=dict(size=16), # ํ—ค๋” ๊ธ€์ž ํฌ๊ธฐ ์ฆ๊ฐ€
542
  ),
543
  cells=dict(
544
  values=[
@@ -588,7 +597,7 @@ app_ui = ui.page_fluid(
588
  margin: 0 auto;
589
  }
590
  #query {
591
- height: 150px;
592
  font-size: 16px;
593
  padding: 15px;
594
  width: 80%;
@@ -612,26 +621,29 @@ app_ui = ui.page_fluid(
612
  margin: 20px 0;
613
  }
614
  .example-box {
615
- background-color: #f8f9fa;
616
- border-radius: 8px;
 
 
617
  padding: 20px;
618
- margin: 20px auto;
619
- width: 80%;
620
  text-align: left;
621
  }
622
  .example-box p {
623
  font-weight: bold;
624
  margin-bottom: 10px;
625
- padding-left: 20px;
626
  }
627
  .example-box ul {
628
  margin: 0;
629
- padding-left: 40px;
630
  }
631
  .example-box li {
632
  word-wrap: break-word;
633
  margin: 10px 0;
634
  line-height: 1.5;
 
635
  }
636
  .query-label {
637
  display: block;
@@ -681,87 +693,326 @@ app_ui = ui.page_fluid(
681
  .status-spinner.active {
682
  display: inline-block;
683
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
  """)
685
  ),
686
  ui.div(
687
  {"class": "content-wrapper"},
688
  ui.h2("Advanced PDB Structure Search Tool"),
689
- ui.row(
690
- ui.column(12,
691
- ui.tags.label(
692
- "Search Query",
693
- {"class": "query-label", "for": "query"}
694
- ),
695
- ui.input_text_area(
696
- "query",
697
- "",
698
- value="Human insulin",
699
- width="100%",
700
- resize="vertical"
701
- ),
702
- )
703
  ),
704
- ui.row(
705
- ui.column(12,
706
- ui.div(
707
- {"class": "example-box"},
708
- ui.p("Example queries:"),
709
- ui.tags.ul(
710
- ui.tags.li("Human hemoglobin C resolution better than 2.5ร…"),
711
- ui.tags.li("Find structures containing sequence MNIFEMLRIDEGLRLKIYKDTEGYYTIGIGHLLTKSPSLNAAKSELDKAIGRNTNGVITKDEAEKLFNQDVDAAVRGILRNAKLKPVYDSLDAVRRAALINMVFQMGETGVAGFTNSLRMLQQKRWDEAAVNLAKSRWYNQTPNRAKRVITTFRTGTWDAYKNL"),
712
- ui.tags.li("Sequence of PDB ID 8ET6")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  )
714
- )
715
- )
716
- ),
717
- ui.row(
718
- ui.column(12,
719
- ui.div(
720
- {"class": "search-button"},
721
- ui.input_action_button("search", "Search",
722
- class_="btn-primary btn-lg") # ๋ฒ„ํŠผ ํฌ๊ธฐ ์ฆ๊ฐ€
723
- )
724
- )
725
- ),
726
- ui.row(
727
- ui.column(12,
728
- ui.h4("Search Parameters:"),
729
- ui.div(
730
- {"class": "status-box"},
731
- ui.tags.span("Status: ", class_="status-label"),
732
- ui.output_text("search_status", inline=True),
733
- ui.tags.div(
734
- {"class": "status-spinner"},
735
- ui.tags.i({"class": "fas fa-spinner fa-spin"})
736
  )
737
  )
738
- )
739
- ),
740
- ui.row(
741
- ui.column(12,
742
- ui.h4("Top 10 Results:"),
743
- output_widget("results_table"),
744
- ui.download_button("download", "Download Results",
745
- class_="btn btn-info btn-lg") # ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ์Šคํƒ€์ผ ๊ฐœ์„ 
746
- )
747
- ),
748
- ui.row(
749
- ui.column(12,
750
- ui.div(
751
- {"class": "sequence-results", "id": "sequence-results"},
752
- ui.h4("Sequences:"),
753
- ui.output_text("sequence_output")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754
  )
755
  )
756
- ),
757
- ui.row(
758
- ui.column(12,
759
- ui.div(
760
- {"class": "3d-iframe", "id": "3d-iframe"}, # css ๋ฏธ์„ค์ •
761
- ui.h4("3D Render"),
762
- ui.output_ui("output_iframe")
763
- )
764
- )
765
  )
766
  )
767
  )
@@ -770,6 +1021,7 @@ def server(input, output, session):
770
  assistant = PDBSearchAssistant()
771
  results_store = reactive.Value({"type": None, "results": []})
772
  status_store = reactive.Value("Ready")
 
773
 
774
  @reactive.Effect
775
  @reactive.event(input.search)
@@ -779,72 +1031,105 @@ def server(input, output, session):
779
  query_results = assistant.process_query(input.query())
780
  results_store.set(query_results)
781
 
 
 
782
  if query_results["type"] == "sequence":
783
  if not query_results["results"]:
784
  status_store.set("No sequences found")
785
  else:
786
  status_store.set("Ready")
 
 
 
787
  else:
788
  df = pd.DataFrame(query_results["results"])
789
  if df.empty:
790
  status_store.set("No structures found")
791
  else:
792
  status_store.set("Ready")
 
793
  @output
794
  @render_widget
795
  def results_table():
796
  return create_interactive_table(df)
797
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
798
  @output
799
  @render.text
800
  def search_status():
801
  return status_store.get()
802
 
803
- @output
804
- @render.download(filename="pdb_search_results.csv")
805
- def download():
806
- current_results = results_store.get()
807
- if current_results["type"] == "structure":
808
- df = pd.DataFrame(current_results["results"])
809
- else:
810
- df = pd.DataFrame(current_results["results"])
811
- return df.to_csv(index=False)
812
-
813
  @output
814
  @render.text
815
  def sequence_output():
816
- current_results = results_store.get()
817
- if current_results["type"] == "sequence":
818
- sequences = current_results["results"]
819
- if not sequences:
820
- return "No sequences found"
821
 
822
- output_text = []
823
- for seq in sequences:
824
- output_text.append(f"\nChain {seq['chain_id']} (Entity {seq['entity_id']}):")
825
- output_text.append(f"Description: {seq['description']}")
826
- output_text.append(f"Length: {seq['length']} residues")
827
- output_text.append("Sequence:")
828
-
829
- # ์‹œํ€€์Šค๋ฅผ 60๊ธ€์ž์”ฉ ๋‚˜๋ˆ„์–ด ์ค„๋ฐ”๊ฟˆ
830
- sequence = seq['sequence']
831
- formatted_sequence = '\n'.join([sequence[i:i+60] for i in range(0, len(sequence), 60)])
832
- output_text.append(formatted_sequence)
833
- output_text.append("-" * 60) # ๊ตฌ๋ถ„์„  ๊ธธ์ด๋„ ์กฐ์ •
834
 
835
- return "\n".join(output_text)
836
- return ""
 
 
 
 
 
 
 
 
837
 
838
  @output
839
- @render.text
840
  def output_iframe():
 
 
 
 
 
 
 
 
841
  current_results = results_store.get()
842
  if current_results["type"] == "structure":
843
- pdb_id = current_results["results"][0]['PDB ID']
844
- # chain ๊ฐ€์ ธ์˜ค๋Š” ๊ฑด ์•„์ง
845
- return render_html(pdb_id, "A")
846
  else:
847
- return ""
 
848
 
849
  app = App(app_ui, server)
850
 
 
492
 
493
  return answer
494
 
495
+ def render_html(pdb_id):
496
+ if pdb_id is None:
497
  return ""
498
  html_content = f"""
499
+ <!DOCTYPE html>
500
  <html>
501
+ <head>
502
  <script src="https://3Dmol.org/build/3Dmol-min.js"></script>
503
  <script src="https://3Dmol.org/build/3Dmol.ui-min.js"></script>
504
+ <style>
505
+ .viewer_3Dmoljs {{
506
+ width: 100%;
507
+ height: 400px;
508
+ position: relative;
509
+ }}
510
+ </style>
511
+ </head>
512
  <body>
513
+ <div class="viewer_3Dmoljs"
514
  data-pdb="{pdb_id}"
515
+ data-backgroundcolor="0xffffff"
516
+ data-style="cartoon:color=spectrum"
 
 
 
517
  data-spin="axis:y;speed:0.2">
518
  </div>
519
  </body>
520
  </html>
521
  """
 
 
 
 
 
 
522
 
523
+ # HTML ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ
524
+ escaped_content = (html_content
525
+ .replace('"', '&quot;')
526
+ .replace('<', '&lt;')
527
+ .replace('>', '&gt;')
528
+ .replace('\n', '')
529
+ )
530
+
531
+ return f'<iframe style="width: 100%; height: 480px; border: none;" srcdoc=\'{escaped_content}\'></iframe>'
532
+
533
  def create_interactive_table(df):
534
  if df.empty:
535
  return go.Figure()
 
547
  values=list(df.columns),
548
  fill_color='paleturquoise',
549
  align='center', # ํ—ค๋” ์ค‘์•™ ์ •๋ ฌ
550
+ font=dict(size=16), # ํ—ค๋” ๊ธ€์ž ํฌ๊ธฐ ์ฆ๊ฐ€s
551
  ),
552
  cells=dict(
553
  values=[
 
597
  margin: 0 auto;
598
  }
599
  #query {
600
+ height: 300px;
601
  font-size: 16px;
602
  padding: 15px;
603
  width: 80%;
 
621
  margin: 20px 0;
622
  }
623
  .example-box {
624
+ height: 250px;
625
+ margin: 0;
626
+ background-color: white;
627
+ border: 1px solid #dee2e6;
628
  padding: 20px;
629
+ border-radius: 8px;
630
+ overflow-y: auto;
631
  text-align: left;
632
  }
633
  .example-box p {
634
  font-weight: bold;
635
  margin-bottom: 10px;
636
+ padding-left: 0;
637
  }
638
  .example-box ul {
639
  margin: 0;
640
+ padding-left: 20px;
641
  }
642
  .example-box li {
643
  word-wrap: break-word;
644
  margin: 10px 0;
645
  line-height: 1.5;
646
+ text-align: left;
647
  }
648
  .query-label {
649
  display: block;
 
693
  .status-spinner.active {
694
  display: inline-block;
695
  }
696
+ .3d-viewer-container {
697
+ text-align: center;
698
+ margin: 20px auto;
699
+ padding: 20px;
700
+ background-color: #f8f9fa;
701
+ border-radius: 8px;
702
+ width: 90%;
703
+ }
704
+ .3d-iframe {
705
+ margin-top: 15px;
706
+ border: 1px solid #ddd;
707
+ border-radius: 4px;
708
+ }
709
+ .3d-viewer-container select {
710
+ margin: 15px auto;
711
+ padding: 8px;
712
+ font-size: 16px;
713
+ border-radius: 4px;
714
+ border: 1px solid #ced4da;
715
+ }
716
+ .tool-description {
717
+ text-align: center;
718
+ color: #666;
719
+ margin: 0 auto 30px;
720
+ max-width: 800px;
721
+ line-height: 1.6;
722
+ font-size: 1.1em;
723
+ }
724
+ .main-content {
725
+ display: flex;
726
+ flex-direction: column;
727
+ gap: 20px;
728
+ }
729
+ .search-section {
730
+ background-color: #f8f9fa;
731
+ border-radius: 12px;
732
+ padding: 25px;
733
+ margin-bottom: 20px;
734
+ }
735
+ .example-box {
736
+ height: 100%;
737
+ margin: 0;
738
+ background-color: white;
739
+ border: 1px solid #dee2e6;
740
+ padding: 20px;
741
+ border-radius: 8px;
742
+ }
743
+ .status-text {
744
+ margin-top: 10px;
745
+ color: #666;
746
+ font-size: 0.9em;
747
+ }
748
+ .status-label {
749
+ font-weight: bold;
750
+ margin-right: 5px;
751
+ }
752
+ .status-spinner {
753
+ display: none;
754
+ margin-left: 10px;
755
+ vertical-align: middle;
756
+ }
757
+ .status-spinner.active {
758
+ display: inline-block;
759
+ }
760
+ .query-header {
761
+ display: flex;
762
+ justify-content: space-between;
763
+ align-items: center;
764
+ margin-bottom: 10px;
765
+ }
766
+ .query-label {
767
+ margin: 0;
768
+ font-weight: bold;
769
+ }
770
+ .btn-primary {
771
+ margin-left: 15px;
772
+ }
773
+ .query-header {
774
+ margin-bottom: 10px;
775
+ }
776
+ .query-label-group {
777
+ display: flex;
778
+ align-items: center;
779
+ gap: 10px; /* ๋ผ๋ฒจ๊ณผ ๋ฒ„ํŠผ ์‚ฌ์ด ๊ฐ„๊ฒฉ */
780
+ }
781
+ .query-label {
782
+ margin: 0;
783
+ font-weight: bold;
784
+ }
785
+ .btn-primary {
786
+ padding: 5px 15px;
787
+ }
788
+ .viewer-section {
789
+ background-color: #f8f9fa;
790
+ border: 1px solid #dee2e6;
791
+ border-radius: 8px;
792
+ padding: 20px;
793
+ margin: 20px 0;
794
+ }
795
+ .viewer-content {
796
+ margin-top: 15px;
797
+ }
798
+ .viewer-content select {
799
+ max-width: 200px;
800
+ margin: 0 auto 15px;
801
+ display: block;
802
+ }
803
+ .viewer-iframe {
804
+ background-color: white;
805
+ border-radius: 4px;
806
+ padding: 10px;
807
+ }
808
+ h4 {
809
+ margin: 0;
810
+ color: #333;
811
+ }
812
+ .results-section {
813
+ background-color: #f8f9fa;
814
+ border: 1px solid #dee2e6;
815
+ border-radius: 8px;
816
+ padding: 20px;
817
+ margin: 20px 0;
818
+ }
819
+ .viewer-section, .sequence-section {
820
+ background-color: #f8f9fa;
821
+ border: 1px solid #dee2e6;
822
+ border-radius: 8px;
823
+ padding: 20px;
824
+ margin: 20px 0;
825
+ height: 100%;
826
+ }
827
+ .sequence-content {
828
+ background-color: white;
829
+ border-radius: 4px;
830
+ padding: 15px;
831
+ margin-top: 15px;
832
+ max-height: 600px;
833
+ overflow-y: auto;
834
+ font-family: monospace;
835
+ white-space: pre-wrap;
836
+ word-wrap: break-word;
837
+ overflow-x: hidden;
838
+ text-align: left;
839
+ }
840
+ .sequence-text {
841
+ word-break: break-all;
842
+ margin: 10px 0;
843
+ line-height: 1.5;
844
+ text-align: left;
845
+ }
846
+ .status-spinner {
847
+ display: none;
848
+ margin-left: 10px;
849
+ vertical-align: middle;
850
+ }
851
+ .status-spinner.active {
852
+ display: inline-block;
853
+ }
854
+ .query-header {
855
+ display: flex;
856
+ justify-content: space-between;
857
+ align-items: center;
858
+ margin-bottom: 10px;
859
+ }
860
+ .query-label {
861
+ margin: 0;
862
+ font-weight: bold;
863
+ }
864
+ .btn-primary {
865
+ margin-left: 15px;
866
+ }
867
+ .query-header {
868
+ margin-bottom: 10px;
869
+ }
870
+ .query-label-group {
871
+ display: flex;
872
+ align-items: center;
873
+ gap: 10px; /* ๋ผ๋ฒจ๊ณผ ๋ฒ„ํŠผ ์‚ฌ์ด ๊ฐ„๊ฒฉ */
874
+ }
875
+ .query-label {
876
+ margin: 0;
877
+ font-weight: bold;
878
+ }
879
+ .btn-primary {
880
+ padding: 5px 15px;
881
+ }
882
+ .viewer-section {
883
+ background-color: #f8f9fa;
884
+ border: 1px solid #dee2e6;
885
+ border-radius: 8px;
886
+ padding: 20px;
887
+ margin: 20px 0;
888
+ }
889
+ .viewer-content {
890
+ margin-top: 15px;
891
+ }
892
+ .viewer-content select {
893
+ max-width: 200px;
894
+ margin: 0 auto 15px;
895
+ display: block;
896
+ }
897
+ .viewer-iframe {
898
+ background-color: white;
899
+ border-radius: 4px;
900
+ padding: 10px;
901
+ }
902
+ h4 {
903
+ margin: 0;
904
+ color: #333;
905
+ }
906
+ .btn-info {
907
+ margin-top: 15px;
908
+ }
909
  """)
910
  ),
911
  ui.div(
912
  {"class": "content-wrapper"},
913
  ui.h2("Advanced PDB Structure Search Tool"),
914
+ ui.div(
915
+ {"class": "tool-description"},
916
+ "An AI-powered search tool for exploring protein structures in the Protein Data Bank (PDB). ",
917
+ "Search by protein name, sequence, resolution, experimental method, or organism to find relevant structures. ",
918
+ "You can also retrieve amino acid sequences for specific PDB IDs."
 
 
 
 
 
 
 
 
 
919
  ),
920
+ ui.div(
921
+ {"class": "main-content"},
922
+ ui.div(
923
+ {"class": "search-section"},
924
+ ui.row(
925
+ ui.column(8,
926
+ ui.div(
927
+ {"class": "query-header"},
928
+ ui.div(
929
+ {"class": "query-label-group"},
930
+ ui.tags.label(
931
+ "Search Query",
932
+ {"class": "query-label", "for": "query"}
933
+ ),
934
+ ui.input_action_button("search", "Search",
935
+ class_="btn-primary")
936
+ )
937
+ ),
938
+ ui.input_text_area(
939
+ "query",
940
+ "",
941
+ value="Human insulin",
942
+ width="100%",
943
+ resize="vertical"
944
+ ),
945
+ ui.div(
946
+ {"class": "status-text"},
947
+ ui.tags.span("Status: ", class_="status-label"),
948
+ ui.output_text("search_status", inline=True),
949
+ ui.tags.i({"class": "fas fa-spinner fa-spin status-spinner"})
950
+ )
951
+ ),
952
+ ui.column(4,
953
+ ui.div(
954
+ {"class": "example-box"},
955
+ ui.p("Example queries:"),
956
+ ui.tags.ul(
957
+ ui.tags.li("Human hemoglobin C resolution better than 2.5ร…"),
958
+ ui.tags.li("Find structures containing sequence MNIFEMLRIDEGLRLKIYKDTEGYYTIGIGHLLTKSPSLNAAKSELDKAIGRNTNGVITKDEAEKLFNQDVDAAVRGILRNAKLKPVYDSLDAVRRAALINMVFQMGETGVAGFTNSLRMLQQKRWDEAAVNLAKSRWYNQTPNRAKRVITTFRTGTWDAYKNL"),
959
+ ui.tags.li("Sequence of PDB ID 8ET6")
960
+ )
961
+ )
962
  )
963
+ ),
964
+ ),
965
+ ui.row(
966
+ ui.column(12,
967
+ ui.div(
968
+ {"class": "results-section"},
969
+ ui.h4("Top 10 PDBs Results"),
970
+ output_widget("results_table"),
971
+ ui.download_button("download", "Download Results",
972
+ class_="btn btn-info")
 
 
 
 
 
 
 
 
 
 
 
 
973
  )
974
  )
975
+ ),
976
+ ui.row(
977
+ # 3D Viewer Column
978
+ ui.column(6,
979
+ ui.div(
980
+ {"class": "viewer-section"},
981
+ ui.h4("3D Structure Viewer"),
982
+ ui.div(
983
+ {"class": "viewer-content"},
984
+ ui.input_select(
985
+ "selected_pdb",
986
+ "Select PDB ID",
987
+ choices=[]
988
+ ),
989
+ ui.div(
990
+ {"class": "viewer-iframe"},
991
+ ui.output_ui("output_iframe")
992
+ )
993
+ )
994
+ )
995
+ ),
996
+ # Sequence Results Column
997
+ ui.column(6,
998
+ ui.div(
999
+ {"class": "sequence-section"},
1000
+ ui.h4("Sequences"),
1001
+ ui.div(
1002
+ {"class": "viewer-content"},
1003
+ ui.input_select(
1004
+ "selected_seq_pdb",
1005
+ "Select PDB ID",
1006
+ choices=[]
1007
+ ),
1008
+ ui.div(
1009
+ {"class": "sequence-content"},
1010
+ ui.output_text("sequence_output")
1011
+ )
1012
+ )
1013
+ )
1014
  )
1015
  )
 
 
 
 
 
 
 
 
 
1016
  )
1017
  )
1018
  )
 
1021
  assistant = PDBSearchAssistant()
1022
  results_store = reactive.Value({"type": None, "results": []})
1023
  status_store = reactive.Value("Ready")
1024
+ pdb_ids_store = reactive.Value([])
1025
 
1026
  @reactive.Effect
1027
  @reactive.event(input.search)
 
1031
  query_results = assistant.process_query(input.query())
1032
  results_store.set(query_results)
1033
 
1034
+ pdb_ids = []
1035
+
1036
  if query_results["type"] == "sequence":
1037
  if not query_results["results"]:
1038
  status_store.set("No sequences found")
1039
  else:
1040
  status_store.set("Ready")
1041
+ for line in input.query().split():
1042
+ if re.match(r'^[0-9A-Za-z]{4}$', line):
1043
+ pdb_ids.append(line.upper())
1044
  else:
1045
  df = pd.DataFrame(query_results["results"])
1046
  if df.empty:
1047
  status_store.set("No structures found")
1048
  else:
1049
  status_store.set("Ready")
1050
+ pdb_ids = df['PDB ID'].tolist()
1051
  @output
1052
  @render_widget
1053
  def results_table():
1054
  return create_interactive_table(df)
1055
+
1056
+ if pdb_ids:
1057
+ pdb_ids_store.set(pdb_ids)
1058
+ # Update both dropdowns with the same PDB IDs
1059
+ ui.update_select(
1060
+ "selected_pdb",
1061
+ choices=pdb_ids,
1062
+ selected=pdb_ids[0]
1063
+ )
1064
+ ui.update_select(
1065
+ "selected_seq_pdb",
1066
+ choices=pdb_ids,
1067
+ selected=pdb_ids[0]
1068
+ )
1069
+ else:
1070
+ pdb_ids_store.set([])
1071
+ ui.update_select(
1072
+ "selected_pdb",
1073
+ choices=[],
1074
+ selected=None
1075
+ )
1076
+ ui.update_select(
1077
+ "selected_seq_pdb",
1078
+ choices=[],
1079
+ selected=None
1080
+ )
1081
+
1082
  @output
1083
  @render.text
1084
  def search_status():
1085
  return status_store.get()
1086
 
 
 
 
 
 
 
 
 
 
 
1087
  @output
1088
  @render.text
1089
  def sequence_output():
1090
+ selected_pdb = input.selected_seq_pdb()
1091
+ if not selected_pdb:
1092
+ return "No PDB ID selected"
 
 
1093
 
1094
+ sequences = assistant.get_sequences_by_pdb_id(selected_pdb)
1095
+ if not sequences:
1096
+ return f"No sequences found for PDB ID: {selected_pdb}"
1097
+
1098
+ output_text = []
1099
+ for seq in sequences:
1100
+ output_text.append(f"\nChain {seq['chain_id']} (Entity {seq['entity_id']}):")
1101
+ output_text.append(f"Description: {seq['description']}")
1102
+ output_text.append(f"Length: {seq['length']} residues")
1103
+ output_text.append("Sequence:")
 
 
1104
 
1105
+ # Format sequence with line breaks every 60 characters
1106
+ sequence = seq['sequence']
1107
+ # Add spaces every 10 characters for better readability
1108
+ sequence = ' '.join(sequence[i:i+10] for i in range(0, len(sequence), 10))
1109
+ # Then split into lines of 60 characters (plus spaces)
1110
+ formatted_sequence = '\n'.join([sequence[i:i+66] for i in range(0, len(sequence), 66)])
1111
+ output_text.append(formatted_sequence)
1112
+ output_text.append("-" * 60)
1113
+
1114
+ return "\n".join(output_text)
1115
 
1116
  @output
1117
+ @render.ui
1118
  def output_iframe():
1119
+ selected_pdb = input.selected_pdb()
1120
+ if selected_pdb:
1121
+ return ui.HTML(render_html(selected_pdb))
1122
+ return ui.HTML("")
1123
+
1124
+ @output
1125
+ @render.download(filename="pdb_search_results.csv")
1126
+ def download():
1127
  current_results = results_store.get()
1128
  if current_results["type"] == "structure":
1129
+ df = pd.DataFrame(current_results["results"])
 
 
1130
  else:
1131
+ df = pd.DataFrame(current_results["results"])
1132
+ return df.to_csv(index=False)
1133
 
1134
  app = App(app_ui, server)
1135