import gradio as gr
import networkx as nx
from pyvis.network import Network
import base64
# Entity type colours (matching your NER tool)
# Updated for better distinction between Person, Location, and Event
ENTITY_COLOURS = {
'PERSON': '#00B894', # Green
'LOCATION': '#9B59B6', # Purple (more distinct from green)
'EVENT': '#F39C12', # Orange/Gold (very distinct)
'ORGANIZATION': '#55A3FF', # Light Blue
'DATE': '#FF6B6B' # Red
}
# Relationship types for dropdown
RELATIONSHIP_TYPES = [
'related_to',
'knows',
'works_with',
'located_in',
'participated_in',
'member_of',
'occurred_at',
'employed_by',
'founded',
'attended',
'collaborates_with',
'married_to',
'sibling_of',
'parent_of',
'lives_at',
'wrote',
'visited',
'born_in',
'died_in',
'other'
]
class NetworkGraphBuilder:
def __init__(self):
self.entities = []
self.relationships = []
def add_entity(self, name, entity_type, record_id):
"""Add an entity to the collection"""
if name and name.strip():
# Avoid duplicates
existing = [e for e in self.entities if e['name'].lower() == name.strip().lower()]
if not existing:
self.entities.append({
'name': name.strip(),
'type': entity_type,
'record_id': record_id
})
def add_relationship(self, source, target, rel_type):
"""Add a relationship between entities"""
if source and target and source.strip() and target.strip() and source.strip() != target.strip():
self.relationships.append({
'source': source.strip(),
'target': target.strip(),
'type': rel_type if rel_type else 'related_to'
})
def build_graph(self):
"""Build NetworkX graph from entities and relationships"""
G = nx.Graph()
# Add nodes with attributes
for entity in self.entities:
G.add_node(
entity['name'],
entity_type=entity['type'],
record_id=entity['record_id']
)
# Add edges
for rel in self.relationships:
if rel['source'] in G.nodes and rel['target'] in G.nodes:
G.add_edge(
rel['source'],
rel['target'],
relationship=rel['type']
)
return G
def create_pyvis_graph(self, G):
"""Create interactive PyVis visualisation"""
if len(G.nodes) == 0:
return None
# Create PyVis network with dark theme
net = Network(
height="600px",
width="100%",
bgcolor="#1a1a2e",
font_color="white",
directed=False
)
# Configure physics and styling
net.set_options("""
{
"nodes": {
"borderWidth": 2,
"borderWidthSelected": 4,
"font": {
"size": 18,
"face": "arial",
"color": "white",
"strokeWidth": 3,
"strokeColor": "#1a1a2e"
},
"shadow": {
"enabled": true,
"color": "rgba(0,0,0,0.5)",
"size": 10
}
},
"edges": {
"color": {
"color": "#888888",
"highlight": "#ffffff"
},
"font": {
"size": 14,
"face": "arial",
"color": "#ffffff",
"strokeWidth": 0,
"background": "rgba(26, 26, 46, 0.8)",
"align": "middle"
},
"smooth": {
"enabled": true,
"type": "continuous"
},
"width": 2
},
"physics": {
"enabled": true,
"barnesHut": {
"gravitationalConstant": -8000,
"centralGravity": 0.3,
"springLength": 150,
"springConstant": 0.04,
"damping": 0.09
},
"stabilisation": {
"enabled": true,
"iterations": 100
}
},
"interaction": {
"hover": true,
"tooltipDelay": 200,
"dragNodes": true,
"dragView": true,
"zoomView": true
}
}
""")
# Add nodes with styling based on entity type and degree
for node in G.nodes():
data = G.nodes[node]
entity_type = data.get('entity_type', 'UNKNOWN')
colour = ENTITY_COLOURS.get(entity_type, '#CCCCCC')
# Size based on connections (degree)
degree = G.degree(node)
size = 25 + (degree * 8)
# Create plain text tooltip (no HTML)
connections = list(G.neighbors(node))
title_lines = [
node,
f"Type: {entity_type}",
f"Connections: {len(connections)}"
]
if connections:
connected_to = ', '.join(connections[:5])
if len(connections) > 5:
connected_to += f"... +{len(connections) - 5} more"
title_lines.append(f"Connected to: {connected_to}")
title = '\n'.join(title_lines)
net.add_node(
node,
label=node,
color=colour,
size=size,
title=title,
font={'size': 18, 'color': 'white', 'strokeWidth': 3, 'strokeColor': '#1a1a2e'}
)
# Add edges with relationship labels
for edge in G.edges(data=True):
rel_type = edge[2].get('relationship', '')
net.add_edge(
edge[0],
edge[1],
title=rel_type,
label=rel_type,
color='#888888',
width=2,
font={'size': 14, 'color': '#ffffff', 'strokeWidth': 0, 'background': 'rgba(26,26,46,0.8)'}
)
# Generate HTML
html = net.generate_html()
# Encode as base64 data URI for iframe src
html_bytes = html.encode('utf-8')
b64_html = base64.b64encode(html_bytes).decode('utf-8')
iframe_html = f'''
'''
return iframe_html
def collect_entities_from_records(
p1, l1, e1, o1, d1,
p2, l2, e2, o2, d2,
p3, l3, e3, o3, d3,
p4, l4, e4, o4, d4,
p5, l5, e5, o5, d5,
p6, l6, e6, o6, d6,
p7, l7, e7, o7, d7,
p8, l8, e8, o8, d8
):
"""Collect all entities from the input fields"""
builder = NetworkGraphBuilder()
# Process each record
records = [
(p1, l1, e1, o1, d1),
(p2, l2, e2, o2, d2),
(p3, l3, e3, o3, d3),
(p4, l4, e4, o4, d4),
(p5, l5, e5, o5, d5),
(p6, l6, e6, o6, d6),
(p7, l7, e7, o7, d7),
(p8, l8, e8, o8, d8),
]
for record_id, (person, location, event, org, date) in enumerate(records, 1):
if person:
builder.add_entity(person, 'PERSON', record_id)
if location:
builder.add_entity(location, 'LOCATION', record_id)
if event:
builder.add_entity(event, 'EVENT', record_id)
if org:
builder.add_entity(org, 'ORGANIZATION', record_id)
if date:
builder.add_entity(date, 'DATE', record_id)
# Create list of all entity names for relationship dropdowns
entity_names = sorted([e['name'] for e in builder.entities])
# Count by type
counts = {
'PERSON': sum(1 for e in builder.entities if e['type'] == 'PERSON'),
'LOCATION': sum(1 for e in builder.entities if e['type'] == 'LOCATION'),
'EVENT': sum(1 for e in builder.entities if e['type'] == 'EVENT'),
'ORGANIZATION': sum(1 for e in builder.entities if e['type'] == 'ORGANIZATION'),
'DATE': sum(1 for e in builder.entities if e['type'] == 'DATE'),
}
# Create HTML summary that spans full width
summary_html = f'''
π Identified Entities ({len(builder.entities)} total)
{counts['PERSON']}
π€ People
{counts['LOCATION']}
π Locations
{counts['EVENT']}
π
Events
{counts['ORGANIZATION']}
π’ Organisations
{counts['DATE']}
ποΈ Dates
Entities: {', '.join(entity_names) if entity_names else 'None found'}
'''
# Return summary and update all 10 dropdowns (5 source + 5 target)
return (
summary_html,
gr.update(choices=entity_names, value=None), # source 1
gr.update(choices=entity_names, value=None), # target 1
gr.update(choices=entity_names, value=None), # source 2
gr.update(choices=entity_names, value=None), # target 2
gr.update(choices=entity_names, value=None), # source 3
gr.update(choices=entity_names, value=None), # target 3
gr.update(choices=entity_names, value=None), # source 4
gr.update(choices=entity_names, value=None), # target 4
gr.update(choices=entity_names, value=None), # source 5
gr.update(choices=entity_names, value=None), # target 5
)
def generate_network_graph(
p1, l1, e1, o1, d1,
p2, l2, e2, o2, d2,
p3, l3, e3, o3, d3,
p4, l4, e4, o4, d4,
p5, l5, e5, o5, d5,
p6, l6, e6, o6, d6,
p7, l7, e7, o7, d7,
p8, l8, e8, o8, d8,
src1, rel1, tgt1,
src2, rel2, tgt2,
src3, rel3, tgt3,
src4, rel4, tgt4,
src5, rel5, tgt5
):
"""Generate the network graph from all inputs"""
try:
builder = NetworkGraphBuilder()
# Process each record
records = [
(p1, l1, e1, o1, d1),
(p2, l2, e2, o2, d2),
(p3, l3, e3, o3, d3),
(p4, l4, e4, o4, d4),
(p5, l5, e5, o5, d5),
(p6, l6, e6, o6, d6),
(p7, l7, e7, o7, d7),
(p8, l8, e8, o8, d8),
]
for record_id, (person, location, event, org, date) in enumerate(records, 1):
if person:
builder.add_entity(person, 'PERSON', record_id)
if location:
builder.add_entity(location, 'LOCATION', record_id)
if event:
builder.add_entity(event, 'EVENT', record_id)
if org:
builder.add_entity(org, 'ORGANIZATION', record_id)
if date:
builder.add_entity(date, 'DATE', record_id)
# Process relationships
relationships = [
(src1, rel1, tgt1),
(src2, rel2, tgt2),
(src3, rel3, tgt3),
(src4, rel4, tgt4),
(src5, rel5, tgt5),
]
for source, rel_type, target in relationships:
if source and target:
builder.add_relationship(source, target, rel_type)
# Build graph
G = builder.build_graph()
if len(G.nodes) == 0:
empty_html = '''
π
No entities to display
Enter entities above and click "Identify Entities" first
'''
return empty_html, "β **No entities to display.** Please enter entities in Step 1 first."
# Create visualisation
graph_html = builder.create_pyvis_graph(G)
# Create statistics
stats_html = f'''
π Network Statistics
| Nodes |
{G.number_of_nodes()} |
| Edges |
{G.number_of_edges()} |
'''
if len(G.edges) > 0:
density = nx.density(G)
avg_degree = sum(dict(G.degree()).values()) / G.number_of_nodes()
stats_html += f'''
| Density |
{density:.3f} |
| Avg. Connections |
{avg_degree:.2f} |
'''
# Most connected
degrees = dict(G.degree())
top_nodes = sorted(degrees.items(), key=lambda x: x[1], reverse=True)[:3]
stats_html += '''
Most Connected:
'''
for node, degree in top_nodes:
stats_html += f'- {node}: {degree}
'
stats_html += '
'
else:
stats_html += '''
β οΈ No relationships defined - nodes are isolated
'''
stats_html += '
'
return graph_html, stats_html
except Exception as e:
import traceback
error_trace = traceback.format_exc()
error_html = f'''
β οΈ
Error generating graph
{str(e)}
'''
return error_html, f"β Error: {str(e)}"
def load_austen_example():
"""Load the Jane Austen Pride and Prejudice example"""
return (
# Record 1
"Elizabeth Bennet", "Longbourn", "Meryton Ball", "Bennet Family", "1811",
# Record 2
"Mr. Darcy", "Pemberley", "Netherfield Ball", "Darcy Estate", "",
# Record 3
"Jane Bennet", "Netherfield", "", "", "",
# Record 4
"Mr. Bingley", "London", "", "", "",
# Record 5
"Mr. Wickham", "Meryton", "", "Militia", "",
# Record 6
"Charlotte Lucas", "Hunsford", "", "", "",
# Record 7
"", "", "", "", "",
# Record 8
"", "", "", "", "",
)
def load_wwii_example():
"""Load a WWII history example"""
return (
# Record 1
"Winston Churchill", "London", "Battle of Britain", "War Cabinet", "1940",
# Record 2
"Franklin D. Roosevelt", "Washington D.C.", "D-Day", "Allied Forces", "1944",
# Record 3
"Field Marshal Montgomery", "North Africa", "Battle of El Alamein", "Eighth Army", "1942",
# Record 4
"Clement Attlee", "Potsdam", "Potsdam Conference", "Labour Party", "1945",
# Record 5
"", "", "", "", "",
# Record 6
"", "", "", "", "",
# Record 7
"", "", "", "", "",
# Record 8
"", "", "", "", "",
)
def create_coloured_label(text, colour, emoji):
"""Create a coloured pill-style label HTML"""
return f'''
{emoji} {text}
'''
def create_interface():
with gr.Blocks(title="Network Explorer", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# πΈοΈ Basic Network Explorer
Build interactive network graphs by entering entities extracted through Named Entity Recognition (NER).
### How to use this tool:
1. **π Enter entities** in the records below (or load an example to get started)
2. **π Click "Identify Entities"** to collect and list all entities
3. **π€ Define relationships** between entities using the dropdowns
4. **π¨ Click "Generate Network Graph"** to visualise
5. **ποΈ Explore** - drag nodes to rearrange, scroll to zoom, hover for details
""")
gr.HTML("""
π‘ Top tip: Start with just a few entities and relationships to see how it works!
""")
# Quick start buttons
gr.Markdown("### π‘ Quick Start - Load an Example:")
with gr.Row():
austen_btn = gr.Button("π Jane Austen (Pride & Prejudice)", variant="secondary", size="sm")
wwii_btn = gr.Button("βοΈ WWII History", variant="secondary", size="sm")
gr.HTML("
")
# ==================== STEP 1: ENTITY INPUT (4 per row) ====================
gr.Markdown("## π Step 1: Enter Entities")
entity_inputs = []
# First row: Records 1-4 (all on one line)
with gr.Row():
with gr.Column(scale=1, min_width=200):
gr.Markdown("**Record 1**")
gr.HTML(create_coloured_label("Person", ENTITY_COLOURS['PERSON'], "π€"))
p1 = gr.Textbox(label="", placeholder="e.g., Elizabeth Bennet", show_label=False)
gr.HTML(create_coloured_label("Location", ENTITY_COLOURS['LOCATION'], "π"))
l1 = gr.Textbox(label="", placeholder="e.g., Longbourn", show_label=False)
gr.HTML(create_coloured_label("Event", ENTITY_COLOURS['EVENT'], "π
"))
e1 = gr.Textbox(label="", placeholder="e.g., Meryton Ball", show_label=False)
gr.HTML(create_coloured_label("Organisation", ENTITY_COLOURS['ORGANIZATION'], "π’"))
o1 = gr.Textbox(label="", placeholder="e.g., Bennet Family", show_label=False)
gr.HTML(create_coloured_label("Date", ENTITY_COLOURS['DATE'], "ποΈ"))
d1 = gr.Textbox(label="", placeholder="e.g., 1811", show_label=False)
entity_inputs.extend([p1, l1, e1, o1, d1])
with gr.Column(scale=1, min_width=200):
gr.Markdown("**Record 2**")
gr.HTML(create_coloured_label("Person", ENTITY_COLOURS['PERSON'], "π€"))
p2 = gr.Textbox(label="", placeholder="e.g., Mr. Darcy", show_label=False)
gr.HTML(create_coloured_label("Location", ENTITY_COLOURS['LOCATION'], "π"))
l2 = gr.Textbox(label="", placeholder="e.g., Pemberley", show_label=False)
gr.HTML(create_coloured_label("Event", ENTITY_COLOURS['EVENT'], "π
"))
e2 = gr.Textbox(label="", placeholder="e.g., Netherfield Ball", show_label=False)
gr.HTML(create_coloured_label("Organisation", ENTITY_COLOURS['ORGANIZATION'], "π’"))
o2 = gr.Textbox(label="", placeholder="e.g., Darcy Estate", show_label=False)
gr.HTML(create_coloured_label("Date", ENTITY_COLOURS['DATE'], "ποΈ"))
d2 = gr.Textbox(label="", placeholder="", show_label=False)
entity_inputs.extend([p2, l2, e2, o2, d2])
with gr.Column(scale=1, min_width=200):
gr.Markdown("**Record 3**")
gr.HTML(create_coloured_label("Person", ENTITY_COLOURS['PERSON'], "π€"))
p3 = gr.Textbox(label="", placeholder="e.g., Jane Bennet", show_label=False)
gr.HTML(create_coloured_label("Location", ENTITY_COLOURS['LOCATION'], "π"))
l3 = gr.Textbox(label="", placeholder="e.g., Netherfield", show_label=False)
gr.HTML(create_coloured_label("Event", ENTITY_COLOURS['EVENT'], "π
"))
e3 = gr.Textbox(label="", placeholder="", show_label=False)
gr.HTML(create_coloured_label("Organisation", ENTITY_COLOURS['ORGANIZATION'], "π’"))
o3 = gr.Textbox(label="", placeholder="", show_label=False)
gr.HTML(create_coloured_label("Date", ENTITY_COLOURS['DATE'], "ποΈ"))
d3 = gr.Textbox(label="", placeholder="", show_label=False)
entity_inputs.extend([p3, l3, e3, o3, d3])
with gr.Column(scale=1, min_width=200):
gr.Markdown("**Record 4**")
gr.HTML(create_coloured_label("Person", ENTITY_COLOURS['PERSON'], "π€"))
p4 = gr.Textbox(label="", placeholder="e.g., Mr. Bingley", show_label=False)
gr.HTML(create_coloured_label("Location", ENTITY_COLOURS['LOCATION'], "π"))
l4 = gr.Textbox(label="", placeholder="e.g., London", show_label=False)
gr.HTML(create_coloured_label("Event", ENTITY_COLOURS['EVENT'], "π
"))
e4 = gr.Textbox(label="", placeholder="", show_label=False)
gr.HTML(create_coloured_label("Organisation", ENTITY_COLOURS['ORGANIZATION'], "π’"))
o4 = gr.Textbox(label="", placeholder="", show_label=False)
gr.HTML(create_coloured_label("Date", ENTITY_COLOURS['DATE'], "ποΈ"))
d4 = gr.Textbox(label="", placeholder="", show_label=False)
entity_inputs.extend([p4, l4, e4, o4, d4])
# Additional records 5-8
with gr.Accordion("β Additional Records (5-8)", open=False):
with gr.Row():
with gr.Column(scale=1, min_width=200):
gr.Markdown("**Record 5**")
gr.HTML(create_coloured_label("Person", ENTITY_COLOURS['PERSON'], "π€"))
p5 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Location", ENTITY_COLOURS['LOCATION'], "π"))
l5 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Event", ENTITY_COLOURS['EVENT'], "π
"))
e5 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Organisation", ENTITY_COLOURS['ORGANIZATION'], "π’"))
o5 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Date", ENTITY_COLOURS['DATE'], "ποΈ"))
d5 = gr.Textbox(label="", show_label=False)
entity_inputs.extend([p5, l5, e5, o5, d5])
with gr.Column(scale=1, min_width=200):
gr.Markdown("**Record 6**")
gr.HTML(create_coloured_label("Person", ENTITY_COLOURS['PERSON'], "π€"))
p6 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Location", ENTITY_COLOURS['LOCATION'], "π"))
l6 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Event", ENTITY_COLOURS['EVENT'], "π
"))
e6 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Organisation", ENTITY_COLOURS['ORGANIZATION'], "π’"))
o6 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Date", ENTITY_COLOURS['DATE'], "ποΈ"))
d6 = gr.Textbox(label="", show_label=False)
entity_inputs.extend([p6, l6, e6, o6, d6])
with gr.Column(scale=1, min_width=200):
gr.Markdown("**Record 7**")
gr.HTML(create_coloured_label("Person", ENTITY_COLOURS['PERSON'], "π€"))
p7 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Location", ENTITY_COLOURS['LOCATION'], "π"))
l7 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Event", ENTITY_COLOURS['EVENT'], "π
"))
e7 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Organisation", ENTITY_COLOURS['ORGANIZATION'], "π’"))
o7 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Date", ENTITY_COLOURS['DATE'], "ποΈ"))
d7 = gr.Textbox(label="", show_label=False)
entity_inputs.extend([p7, l7, e7, o7, d7])
with gr.Column(scale=1, min_width=200):
gr.Markdown("**Record 8**")
gr.HTML(create_coloured_label("Person", ENTITY_COLOURS['PERSON'], "π€"))
p8 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Location", ENTITY_COLOURS['LOCATION'], "π"))
l8 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Event", ENTITY_COLOURS['EVENT'], "π
"))
e8 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Organisation", ENTITY_COLOURS['ORGANIZATION'], "π’"))
o8 = gr.Textbox(label="", show_label=False)
gr.HTML(create_coloured_label("Date", ENTITY_COLOURS['DATE'], "ποΈ"))
d8 = gr.Textbox(label="", show_label=False)
entity_inputs.extend([p8, l8, e8, o8, d8])
# Identify button
collect_btn = gr.Button("π Identify Entities", variant="primary", size="lg")
# Full-width entity summary
entity_summary = gr.HTML()
gr.HTML("
")
# ==================== STEP 2: RELATIONSHIPS (Grid) ====================
gr.Markdown("## π€ Step 2: Define Relationships")
gr.Markdown("*Select entities from the dropdowns to create connections (click 'Identify Entities' first)*")
# Relationship inputs in a grid (5 columns)
relationship_inputs = []
with gr.Row():
with gr.Column(scale=1, min_width=180):
gr.Markdown("**Relationship 1**")
src1 = gr.Dropdown(label="From", choices=[])
rel1 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="related_to")
tgt1 = gr.Dropdown(label="To", choices=[])
relationship_inputs.extend([src1, rel1, tgt1])
with gr.Column(scale=1, min_width=180):
gr.Markdown("**Relationship 2**")
src2 = gr.Dropdown(label="From", choices=[])
rel2 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="related_to")
tgt2 = gr.Dropdown(label="To", choices=[])
relationship_inputs.extend([src2, rel2, tgt2])
with gr.Column(scale=1, min_width=180):
gr.Markdown("**Relationship 3**")
src3 = gr.Dropdown(label="From", choices=[])
rel3 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="related_to")
tgt3 = gr.Dropdown(label="To", choices=[])
relationship_inputs.extend([src3, rel3, tgt3])
with gr.Column(scale=1, min_width=180):
gr.Markdown("**Relationship 4**")
src4 = gr.Dropdown(label="From", choices=[])
rel4 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="related_to")
tgt4 = gr.Dropdown(label="To", choices=[])
relationship_inputs.extend([src4, rel4, tgt4])
with gr.Column(scale=1, min_width=180):
gr.Markdown("**Relationship 5**")
src5 = gr.Dropdown(label="From", choices=[])
rel5 = gr.Dropdown(label="Type", choices=RELATIONSHIP_TYPES, value="related_to")
tgt5 = gr.Dropdown(label="To", choices=[])
relationship_inputs.extend([src5, rel5, tgt5])
gr.HTML("
")
# ==================== STEP 3: GENERATE & VIEW ====================
gr.Markdown("## π¨ Step 3: Generate Network Graph")
generate_btn = gr.Button("π¨ Generate Network Graph", variant="primary", size="lg")
# Full-width network graph with stats sidebar
with gr.Row():
with gr.Column(scale=3):
network_plot = gr.HTML(label="Interactive Network Graph")
with gr.Column(scale=1):
network_stats = gr.HTML()
# Colour legend
gr.HTML(f"""
π¨ Entity Colour Legend
Person
Location
Event
Organisation
Date
π±οΈ Interaction: Drag nodes to rearrange β’ Scroll to zoom β’ Hover for details
""")
# ==================== WIRE UP EVENTS ====================
# Example buttons
austen_btn.click(
fn=load_austen_example,
inputs=[],
outputs=entity_inputs
)
wwii_btn.click(
fn=load_wwii_example,
inputs=[],
outputs=entity_inputs
)
# Collect entities
collect_btn.click(
fn=collect_entities_from_records,
inputs=entity_inputs,
outputs=[
entity_summary,
src1, tgt1,
src2, tgt2,
src3, tgt3,
src4, tgt4,
src5, tgt5
]
)
# Generate graph
all_inputs = entity_inputs + relationship_inputs
generate_btn.click(
fn=generate_network_graph,
inputs=all_inputs,
outputs=[network_plot, network_stats]
)
# Model Information & Documentation section
gr.HTML("""
π Library Information & Documentation
Learn more about the libraries used in this tool:
""")
# Footer
gr.HTML("""
This Basic Network Explorer tool was developed as part of a Bodleian Libraries (Oxford) Sassoon Research Fellowship.
Built with the aid of Claude Opus 4.5.
""")
return demo
if __name__ == "__main__":
demo = create_interface()
demo.launch()