SorrelC commited on
Commit
041dfb0
Β·
verified Β·
1 Parent(s): 7d33242

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -21
app.py CHANGED
@@ -2,6 +2,8 @@ import gradio as gr
2
  import networkx as nx
3
  from pyvis.network import Network
4
  import base64
 
 
5
 
6
  # Entity type colours (matching your NER tool)
7
  # Updated for better distinction between Person, Location, and Event
@@ -88,9 +90,9 @@ class NetworkGraphBuilder:
88
  return G
89
 
90
  def create_pyvis_graph(self, G):
91
- """Create interactive PyVis visualisation"""
92
  if len(G.nodes) == 0:
93
- return None
94
 
95
  # Create PyVis network with dark theme
96
  net = Network(
@@ -213,6 +215,9 @@ class NetworkGraphBuilder:
213
  # Generate HTML
214
  html = net.generate_html()
215
 
 
 
 
216
  # Encode as base64 data URI for iframe src
217
  html_bytes = html.encode('utf-8')
218
  b64_html = base64.b64encode(html_bytes).decode('utf-8')
@@ -227,7 +232,7 @@ class NetworkGraphBuilder:
227
  ></iframe>
228
  '''
229
 
230
- return iframe_html
231
 
232
 
233
  def collect_entities_from_records(
@@ -413,7 +418,7 @@ def generate_network_graph(
413
  return empty_html, "❌ **No entities to display.** Please enter entities in Step 1 first."
414
 
415
  # Create visualisation
416
- graph_html = builder.create_pyvis_graph(G)
417
 
418
  # Create statistics
419
  stats_html = f'''
@@ -487,6 +492,96 @@ def generate_network_graph(
487
  return error_html, f"❌ Error: {str(e)}"
488
 
489
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  def load_austen_example():
491
  """Load the Jane Austen Pride and Prejudice example"""
492
  return (
@@ -509,12 +604,6 @@ def load_austen_example():
509
  )
510
 
511
 
512
- def load_austen_example_extra():
513
- """Return additional Austen characters for records 5-8 area - but we handle in main example"""
514
- # This is handled by main example now
515
- pass
516
-
517
-
518
  def load_wwii_example():
519
  """Load a WWII history example"""
520
  return (
@@ -563,9 +652,10 @@ def create_interface():
563
  ### How to use this tool:
564
  1. **πŸ“ Enter entities** in the records below (or load an example to get started)
565
  2. **βš™οΈ Click "Process Entities"** to collect and prepare all entities for relationships
566
- 3. **🀝 Define relationships** between entities using the dropdowns
567
  4. **🎨 Click "Generate Network Graph"** to visualise
568
  5. **πŸ‘οΈ Explore** - drag nodes to rearrange, scroll to zoom, hover for details
 
569
  """)
570
 
571
  gr.HTML("""
@@ -720,7 +810,7 @@ def create_interface():
720
 
721
  # ==================== STEP 3: RELATIONSHIPS (Grid) ====================
722
  gr.Markdown("## 🀝 Step 3: Define Relationships")
723
- gr.Markdown("*Select entities from the dropdowns to create connections (click 'Process Entities' first)*")
724
 
725
  # Relationship inputs in a grid (5 columns for first 5)
726
  relationship_inputs = []
@@ -729,35 +819,35 @@ def create_interface():
729
  with gr.Column(scale=1, min_width=180):
730
  gr.Markdown("**Relationship 1**")
731
  src1 = gr.Dropdown(label="From", choices=[])
732
- rel1 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to")
733
  tgt1 = gr.Dropdown(label="To", choices=[])
734
  relationship_inputs.extend([src1, rel1, tgt1])
735
 
736
  with gr.Column(scale=1, min_width=180):
737
  gr.Markdown("**Relationship 2**")
738
  src2 = gr.Dropdown(label="From", choices=[])
739
- rel2 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to")
740
  tgt2 = gr.Dropdown(label="To", choices=[])
741
  relationship_inputs.extend([src2, rel2, tgt2])
742
 
743
  with gr.Column(scale=1, min_width=180):
744
  gr.Markdown("**Relationship 3**")
745
  src3 = gr.Dropdown(label="From", choices=[])
746
- rel3 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to")
747
  tgt3 = gr.Dropdown(label="To", choices=[])
748
  relationship_inputs.extend([src3, rel3, tgt3])
749
 
750
  with gr.Column(scale=1, min_width=180):
751
  gr.Markdown("**Relationship 4**")
752
  src4 = gr.Dropdown(label="From", choices=[])
753
- rel4 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to")
754
  tgt4 = gr.Dropdown(label="To", choices=[])
755
  relationship_inputs.extend([src4, rel4, tgt4])
756
 
757
  with gr.Column(scale=1, min_width=180):
758
  gr.Markdown("**Relationship 5**")
759
  src5 = gr.Dropdown(label="From", choices=[])
760
- rel5 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to")
761
  tgt5 = gr.Dropdown(label="To", choices=[])
762
  relationship_inputs.extend([src5, rel5, tgt5])
763
 
@@ -767,21 +857,21 @@ def create_interface():
767
  with gr.Column(scale=1, min_width=180):
768
  gr.Markdown("**Relationship 6**")
769
  src6 = gr.Dropdown(label="From", choices=[])
770
- rel6 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to")
771
  tgt6 = gr.Dropdown(label="To", choices=[])
772
  relationship_inputs.extend([src6, rel6, tgt6])
773
 
774
  with gr.Column(scale=1, min_width=180):
775
  gr.Markdown("**Relationship 7**")
776
  src7 = gr.Dropdown(label="From", choices=[])
777
- rel7 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to")
778
  tgt7 = gr.Dropdown(label="To", choices=[])
779
  relationship_inputs.extend([src7, rel7, tgt7])
780
 
781
  with gr.Column(scale=1, min_width=180):
782
  gr.Markdown("**Relationship 8**")
783
  src8 = gr.Dropdown(label="From", choices=[])
784
- rel8 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to")
785
  tgt8 = gr.Dropdown(label="To", choices=[])
786
  relationship_inputs.extend([src8, rel8, tgt8])
787
 
@@ -799,6 +889,22 @@ def create_interface():
799
  with gr.Column(scale=1):
800
  network_stats = gr.HTML()
801
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
802
  # Colour legend
803
  gr.HTML(f"""
804
  <div style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); padding: 20px; border-radius: 10px; margin-top: 20px;">
@@ -863,7 +969,7 @@ def create_interface():
863
  ]
864
  )
865
 
866
- # Generate graph
867
  all_inputs = entity_inputs + relationship_inputs
868
  generate_btn.click(
869
  fn=generate_network_graph,
@@ -871,6 +977,13 @@ def create_interface():
871
  outputs=[network_plot, network_stats]
872
  )
873
 
 
 
 
 
 
 
 
874
  # Model Information & Documentation section
875
  gr.HTML("""
876
  <hr style="margin: 40px 0 20px 0;">
 
2
  import networkx as nx
3
  from pyvis.network import Network
4
  import base64
5
+ import tempfile
6
+ import os
7
 
8
  # Entity type colours (matching your NER tool)
9
  # Updated for better distinction between Person, Location, and Event
 
90
  return G
91
 
92
  def create_pyvis_graph(self, G):
93
+ """Create interactive PyVis visualisation. Returns (iframe_html, standalone_html)"""
94
  if len(G.nodes) == 0:
95
+ return None, None
96
 
97
  # Create PyVis network with dark theme
98
  net = Network(
 
215
  # Generate HTML
216
  html = net.generate_html()
217
 
218
+ # Store standalone HTML for export
219
+ standalone_html = html
220
+
221
  # Encode as base64 data URI for iframe src
222
  html_bytes = html.encode('utf-8')
223
  b64_html = base64.b64encode(html_bytes).decode('utf-8')
 
232
  ></iframe>
233
  '''
234
 
235
+ return iframe_html, standalone_html
236
 
237
 
238
  def collect_entities_from_records(
 
418
  return empty_html, "❌ **No entities to display.** Please enter entities in Step 1 first."
419
 
420
  # Create visualisation
421
+ graph_html, standalone_html = builder.create_pyvis_graph(G)
422
 
423
  # Create statistics
424
  stats_html = f'''
 
492
  return error_html, f"❌ Error: {str(e)}"
493
 
494
 
495
+ def export_network_graph(
496
+ p1, l1, e1, o1, d1,
497
+ p2, l2, e2, o2, d2,
498
+ p3, l3, e3, o3, d3,
499
+ p4, l4, e4, o4, d4,
500
+ p5, l5, e5, o5, d5,
501
+ p6, l6, e6, o6, d6,
502
+ p7, l7, e7, o7, d7,
503
+ p8, l8, e8, o8, d8,
504
+ src1, rel1, tgt1,
505
+ src2, rel2, tgt2,
506
+ src3, rel3, tgt3,
507
+ src4, rel4, tgt4,
508
+ src5, rel5, tgt5,
509
+ src6, rel6, tgt6,
510
+ src7, rel7, tgt7,
511
+ src8, rel8, tgt8
512
+ ):
513
+ """Export the network graph as a standalone HTML file"""
514
+ try:
515
+ builder = NetworkGraphBuilder()
516
+
517
+ # Process each record
518
+ records = [
519
+ (p1, l1, e1, o1, d1),
520
+ (p2, l2, e2, o2, d2),
521
+ (p3, l3, e3, o3, d3),
522
+ (p4, l4, e4, o4, d4),
523
+ (p5, l5, e5, o5, d5),
524
+ (p6, l6, e6, o6, d6),
525
+ (p7, l7, e7, o7, d7),
526
+ (p8, l8, e8, o8, d8),
527
+ ]
528
+
529
+ for record_id, (person, location, event, org, date) in enumerate(records, 1):
530
+ if person:
531
+ builder.add_entity(person, 'PERSON', record_id)
532
+ if location:
533
+ builder.add_entity(location, 'LOCATION', record_id)
534
+ if event:
535
+ builder.add_entity(event, 'EVENT', record_id)
536
+ if org:
537
+ builder.add_entity(org, 'ORGANIZATION', record_id)
538
+ if date:
539
+ builder.add_entity(date, 'DATE', record_id)
540
+
541
+ # Process relationships
542
+ relationships = [
543
+ (src1, rel1, tgt1),
544
+ (src2, rel2, tgt2),
545
+ (src3, rel3, tgt3),
546
+ (src4, rel4, tgt4),
547
+ (src5, rel5, tgt5),
548
+ (src6, rel6, tgt6),
549
+ (src7, rel7, tgt7),
550
+ (src8, rel8, tgt8),
551
+ ]
552
+
553
+ for source, rel_type, target in relationships:
554
+ if source and target:
555
+ builder.add_relationship(source, target, rel_type)
556
+
557
+ # Build graph
558
+ G = builder.build_graph()
559
+
560
+ if len(G.nodes) == 0:
561
+ return None
562
+
563
+ # Create visualisation
564
+ _, standalone_html = builder.create_pyvis_graph(G)
565
+
566
+ # Save standalone HTML to a temporary file for download
567
+ if standalone_html:
568
+ export_file = tempfile.NamedTemporaryFile(
569
+ mode='w',
570
+ suffix='.html',
571
+ prefix='network_graph_',
572
+ delete=False,
573
+ encoding='utf-8'
574
+ )
575
+ export_file.write(standalone_html)
576
+ export_file.close()
577
+ return export_file.name
578
+
579
+ return None
580
+
581
+ except Exception as e:
582
+ return None
583
+
584
+
585
  def load_austen_example():
586
  """Load the Jane Austen Pride and Prejudice example"""
587
  return (
 
604
  )
605
 
606
 
 
 
 
 
 
 
607
  def load_wwii_example():
608
  """Load a WWII history example"""
609
  return (
 
652
  ### How to use this tool:
653
  1. **πŸ“ Enter entities** in the records below (or load an example to get started)
654
  2. **βš™οΈ Click "Process Entities"** to collect and prepare all entities for relationships
655
+ 3. **🀝 Define relationships** between entities using the dropdowns (or type your own relationship type)
656
  4. **🎨 Click "Generate Network Graph"** to visualise
657
  5. **πŸ‘οΈ Explore** - drag nodes to rearrange, scroll to zoom, hover for details
658
+ 6. **πŸ’Ύ Export (optional)** - click "Export as HTML" to download your graph as an interactive file
659
  """)
660
 
661
  gr.HTML("""
 
810
 
811
  # ==================== STEP 3: RELATIONSHIPS (Grid) ====================
812
  gr.Markdown("## 🀝 Step 3: Define Relationships")
813
+ gr.Markdown("*Select entities from the dropdowns to create connections (click 'Process Entities' first). You can also type your own relationship type.*")
814
 
815
  # Relationship inputs in a grid (5 columns for first 5)
816
  relationship_inputs = []
 
819
  with gr.Column(scale=1, min_width=180):
820
  gr.Markdown("**Relationship 1**")
821
  src1 = gr.Dropdown(label="From", choices=[])
822
+ rel1 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to", allow_custom_value=True)
823
  tgt1 = gr.Dropdown(label="To", choices=[])
824
  relationship_inputs.extend([src1, rel1, tgt1])
825
 
826
  with gr.Column(scale=1, min_width=180):
827
  gr.Markdown("**Relationship 2**")
828
  src2 = gr.Dropdown(label="From", choices=[])
829
+ rel2 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to", allow_custom_value=True)
830
  tgt2 = gr.Dropdown(label="To", choices=[])
831
  relationship_inputs.extend([src2, rel2, tgt2])
832
 
833
  with gr.Column(scale=1, min_width=180):
834
  gr.Markdown("**Relationship 3**")
835
  src3 = gr.Dropdown(label="From", choices=[])
836
+ rel3 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to", allow_custom_value=True)
837
  tgt3 = gr.Dropdown(label="To", choices=[])
838
  relationship_inputs.extend([src3, rel3, tgt3])
839
 
840
  with gr.Column(scale=1, min_width=180):
841
  gr.Markdown("**Relationship 4**")
842
  src4 = gr.Dropdown(label="From", choices=[])
843
+ rel4 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to", allow_custom_value=True)
844
  tgt4 = gr.Dropdown(label="To", choices=[])
845
  relationship_inputs.extend([src4, rel4, tgt4])
846
 
847
  with gr.Column(scale=1, min_width=180):
848
  gr.Markdown("**Relationship 5**")
849
  src5 = gr.Dropdown(label="From", choices=[])
850
+ rel5 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to", allow_custom_value=True)
851
  tgt5 = gr.Dropdown(label="To", choices=[])
852
  relationship_inputs.extend([src5, rel5, tgt5])
853
 
 
857
  with gr.Column(scale=1, min_width=180):
858
  gr.Markdown("**Relationship 6**")
859
  src6 = gr.Dropdown(label="From", choices=[])
860
+ rel6 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to", allow_custom_value=True)
861
  tgt6 = gr.Dropdown(label="To", choices=[])
862
  relationship_inputs.extend([src6, rel6, tgt6])
863
 
864
  with gr.Column(scale=1, min_width=180):
865
  gr.Markdown("**Relationship 7**")
866
  src7 = gr.Dropdown(label="From", choices=[])
867
+ rel7 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to", allow_custom_value=True)
868
  tgt7 = gr.Dropdown(label="To", choices=[])
869
  relationship_inputs.extend([src7, rel7, tgt7])
870
 
871
  with gr.Column(scale=1, min_width=180):
872
  gr.Markdown("**Relationship 8**")
873
  src8 = gr.Dropdown(label="From", choices=[])
874
+ rel8 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="Related to", allow_custom_value=True)
875
  tgt8 = gr.Dropdown(label="To", choices=[])
876
  relationship_inputs.extend([src8, rel8, tgt8])
877
 
 
889
  with gr.Column(scale=1):
890
  network_stats = gr.HTML()
891
 
892
+ # Export section
893
+ gr.Markdown("### πŸ’Ύ Export Your Graph")
894
+ gr.Markdown("*Generate a graph first, then click export to download*")
895
+ with gr.Row():
896
+ export_btn = gr.Button("πŸ’Ύ Export as HTML", variant="secondary", size="sm")
897
+ export_file = gr.File(
898
+ label="Download Interactive HTML",
899
+ file_types=[".html"],
900
+ interactive=False
901
+ )
902
+ gr.HTML("""
903
+ <div style="background-color: #e8f4f8; border: 1px solid #bee5eb; border-radius: 8px; padding: 12px; margin: 10px 0;">
904
+ <span style="color: #0c5460;">πŸ’‘ The exported HTML file is fully interactive β€” open it in any web browser to explore your network!</span>
905
+ </div>
906
+ """)
907
+
908
  # Colour legend
909
  gr.HTML(f"""
910
  <div style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); padding: 20px; border-radius: 10px; margin-top: 20px;">
 
969
  ]
970
  )
971
 
972
+ # Generate graph - outputs the visualisation and stats
973
  all_inputs = entity_inputs + relationship_inputs
974
  generate_btn.click(
975
  fn=generate_network_graph,
 
977
  outputs=[network_plot, network_stats]
978
  )
979
 
980
+ # Export graph - separate button for downloading
981
+ export_btn.click(
982
+ fn=export_network_graph,
983
+ inputs=all_inputs,
984
+ outputs=[export_file]
985
+ )
986
+
987
  # Model Information & Documentation section
988
  gr.HTML("""
989
  <hr style="margin: 40px 0 20px 0;">