SorrelC commited on
Commit
6ce47a1
·
verified ·
1 Parent(s): a27a6d3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -542
app.py CHANGED
@@ -1,549 +1,68 @@
1
- import gradio as gr
2
- import plotly.graph_objects as go
3
- import networkx as nx
4
- import pandas as pd
5
- from collections import defaultdict
6
 
7
- # Entity type colors
8
- ENTITY_COLORS = {
9
- 'PERSON': '#00B894', # Green
10
- 'LOCATION': '#A0E7E5', # Light Cyan
11
- 'EVENT': '#4ECDC4', # Teal
12
- 'ORGANIZATION': '#55A3FF', # Light Blue
13
- 'DATE': '#FF6B6B' # Red
14
- }
15
 
16
- # Relationship types for dropdown
17
- RELATIONSHIP_TYPES = [
18
- 'works_with',
19
- 'located_in',
20
- 'participated_in',
21
- 'member_of',
22
- 'occurred_at',
23
- 'employed_by',
24
- 'founded',
25
- 'attended',
26
- 'knows',
27
- 'related_to',
28
- 'collaborates_with',
29
- 'other'
30
- ]
31
 
32
- class NetworkGraphBuilder:
33
- def __init__(self):
34
- self.entities = []
35
- self.relationships = []
36
-
37
- def add_entity(self, name, entity_type, record_id):
38
- """Add an entity to the collection"""
39
- if name.strip():
40
- self.entities.append({
41
- 'name': name.strip(),
42
- 'type': entity_type,
43
- 'record_id': record_id
44
- })
45
-
46
- def add_relationship(self, source, target, rel_type):
47
- """Add a relationship between entities"""
48
- if source and target and source != target:
49
- self.relationships.append({
50
- 'source': source.strip(),
51
- 'target': target.strip(),
52
- 'type': rel_type
53
- })
54
-
55
- def build_graph(self):
56
- """Build NetworkX graph from entities and relationships"""
57
- G = nx.Graph()
58
-
59
- # Add nodes with attributes
60
- for entity in self.entities:
61
- G.add_node(
62
- entity['name'],
63
- entity_type=entity['type'],
64
- record_id=entity['record_id']
65
- )
66
-
67
- # Add edges
68
- for rel in self.relationships:
69
- if rel['source'] in G.nodes and rel['target'] in G.nodes:
70
- G.add_edge(
71
- rel['source'],
72
- rel['target'],
73
- relationship=rel['type']
74
- )
75
-
76
- return G
77
-
78
- def create_plotly_graph(self, G, layout_type='spring'):
79
- """Create interactive Plotly visualization"""
80
- if len(G.nodes) == 0:
81
- return None
82
-
83
- # Choose layout
84
- if layout_type == 'spring':
85
- pos = nx.spring_layout(G, k=2, iterations=50)
86
- elif layout_type == 'circular':
87
- pos = nx.circular_layout(G)
88
- elif layout_type == 'kamada_kawai':
89
- pos = nx.kamada_kawai_layout(G)
90
- else:
91
- pos = nx.shell_layout(G)
92
-
93
- # Create edge traces
94
- edge_traces = []
95
- edge_labels = []
96
-
97
- for edge in G.edges(data=True):
98
- x0, y0 = pos[edge[0]]
99
- x1, y1 = pos[edge[1]]
100
-
101
- # Edge line
102
- edge_trace = go.Scatter(
103
- x=[x0, x1, None],
104
- y=[y0, y1, None],
105
- mode='lines',
106
- line=dict(width=2, color='#888'),
107
- hoverinfo='none',
108
- showlegend=False
109
- )
110
- edge_traces.append(edge_trace)
111
-
112
- # Edge label (relationship type)
113
- rel_type = edge[2].get('relationship', '')
114
- edge_label = go.Scatter(
115
- x=[(x0 + x1) / 2],
116
- y=[(y0 + y1) / 2],
117
- mode='text',
118
- text=[rel_type],
119
- textfont=dict(size=10, color='#555'),
120
- hoverinfo='text',
121
- hovertext=f"{edge[0]} → {rel_type} → {edge[1]}",
122
- showlegend=False
123
- )
124
- edge_labels.append(edge_label)
125
-
126
- # Create node traces (one per entity type for legend)
127
- node_traces = {}
128
- for node, data in G.nodes(data=True):
129
- entity_type = data.get('entity_type', 'UNKNOWN')
130
-
131
- if entity_type not in node_traces:
132
- node_traces[entity_type] = {
133
- 'x': [],
134
- 'y': [],
135
- 'text': [],
136
- 'hovertext': [],
137
- 'degree': []
138
- }
139
-
140
- x, y = pos[node]
141
- node_traces[entity_type]['x'].append(x)
142
- node_traces[entity_type]['y'].append(y)
143
- node_traces[entity_type]['text'].append(node)
144
-
145
- # Create hover text with connections
146
- connections = list(G.neighbors(node))
147
- hover_info = f"<b>{node}</b><br>"
148
- hover_info += f"Type: {entity_type}<br>"
149
- hover_info += f"Connections: {len(connections)}<br>"
150
- if connections:
151
- hover_info += f"Connected to: {', '.join(connections[:5])}"
152
- if len(connections) > 5:
153
- hover_info += f"... and {len(connections) - 5} more"
154
-
155
- node_traces[entity_type]['hovertext'].append(hover_info)
156
- node_traces[entity_type]['degree'].append(G.degree(node))
157
-
158
- # Create Plotly traces for each entity type
159
- data = edge_traces + edge_labels
160
-
161
- for entity_type, trace_data in node_traces.items():
162
- # Calculate node sizes based on degree
163
- max_degree = max(trace_data['degree']) if trace_data['degree'] else 1
164
- sizes = [20 + (degree / max_degree) * 30 for degree in trace_data['degree']]
165
-
166
- node_trace = go.Scatter(
167
- x=trace_data['x'],
168
- y=trace_data['y'],
169
- mode='markers+text',
170
- marker=dict(
171
- size=sizes,
172
- color=ENTITY_COLORS.get(entity_type, '#CCCCCC'),
173
- line=dict(width=2, color='white')
174
- ),
175
- text=trace_data['text'],
176
- textposition='top center',
177
- textfont=dict(size=10, color='#333'),
178
- hovertext=trace_data['hovertext'],
179
- hoverinfo='text',
180
- name=entity_type,
181
- showlegend=True
182
- )
183
- data.append(node_trace)
184
-
185
- # Create figure
186
- fig = go.Figure(
187
- data=data,
188
- layout=go.Layout(
189
- title=dict(
190
- text='<b>Entity Network Graph</b><br><sub>Node size indicates number of connections</sub>',
191
- x=0.5,
192
- xanchor='center'
193
- ),
194
- showlegend=True,
195
- hovermode='closest',
196
- margin=dict(b=20, l=5, r=5, t=80),
197
- xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
198
- yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
199
- plot_bgcolor='#fafafa',
200
- height=700,
201
- legend=dict(
202
- title=dict(text='<b>Entity Types</b>'),
203
- orientation='v',
204
- yanchor='top',
205
- y=1,
206
- xanchor='left',
207
- x=1.02
208
- )
209
- )
210
- )
211
-
212
- return fig
213
 
214
- def collect_entities_from_records(*args):
215
- """Collect all entities from the input fields"""
216
- builder = NetworkGraphBuilder()
217
-
218
- # Each record has 5 entity fields (person, location, event, org, date)
219
- num_records = 6
220
- fields_per_record = 5
221
-
222
- for i in range(num_records):
223
- record_id = i + 1
224
- base_idx = i * fields_per_record
225
-
226
- # Extract entities for this record
227
- person = args[base_idx] if base_idx < len(args) else ""
228
- location = args[base_idx + 1] if base_idx + 1 < len(args) else ""
229
- event = args[base_idx + 2] if base_idx + 2 < len(args) else ""
230
- org = args[base_idx + 3] if base_idx + 3 < len(args) else ""
231
- date = args[base_idx + 4] if base_idx + 4 < len(args) else ""
232
-
233
- if person:
234
- builder.add_entity(person, 'PERSON', record_id)
235
- if location:
236
- builder.add_entity(location, 'LOCATION', record_id)
237
- if event:
238
- builder.add_entity(event, 'EVENT', record_id)
239
- if org:
240
- builder.add_entity(org, 'ORGANIZATION', record_id)
241
- if date:
242
- builder.add_entity(date, 'DATE', record_id)
243
-
244
- # Create list of all entity names for relationship dropdowns
245
- entity_names = [e['name'] for e in builder.entities]
246
-
247
- # Create summary
248
- summary = f"""
249
- ### 📊 Entities Collected
250
- - **Total entities:** {len(builder.entities)}
251
- - **People:** {sum(1 for e in builder.entities if e['type'] == 'PERSON')}
252
- - **Locations:** {sum(1 for e in builder.entities if e['type'] == 'LOCATION')}
253
- - **Events:** {sum(1 for e in builder.entities if e['type'] == 'EVENT')}
254
- - **Organizations:** {sum(1 for e in builder.entities if e['type'] == 'ORGANIZATION')}
255
- - **Dates:** {sum(1 for e in builder.entities if e['type'] == 'DATE')}
256
-
257
- Now define relationships between these entities below.
258
- """
259
-
260
- # Return summary and update dropdowns
261
- return (
262
- summary,
263
- gr.update(visible=True), # Show relationship section
264
- gr.update(choices=entity_names, value=None), # Update all relationship dropdowns
265
- gr.update(choices=entity_names, value=None),
266
- gr.update(choices=entity_names, value=None),
267
- gr.update(choices=entity_names, value=None),
268
- gr.update(choices=entity_names, value=None),
269
- gr.update(choices=entity_names, value=None),
270
- gr.update(choices=entity_names, value=None),
271
- gr.update(choices=entity_names, value=None),
272
- gr.update(choices=entity_names, value=None),
273
- gr.update(choices=entity_names, value=None)
274
- )
275
 
276
- def generate_network_graph(*args):
277
- """Generate the network graph from all inputs"""
278
- builder = NetworkGraphBuilder()
279
-
280
- # Collect entities (first 30 args: 6 records × 5 fields)
281
- num_records = 6
282
- fields_per_record = 5
283
-
284
- for i in range(num_records):
285
- record_id = i + 1
286
- base_idx = i * fields_per_record
287
-
288
- person = args[base_idx] if base_idx < len(args) else ""
289
- location = args[base_idx + 1] if base_idx + 1 < len(args) else ""
290
- event = args[base_idx + 2] if base_idx + 2 < len(args) else ""
291
- org = args[base_idx + 3] if base_idx + 3 < len(args) else ""
292
- date = args[base_idx + 4] if base_idx + 4 < len(args) else ""
293
-
294
- if person:
295
- builder.add_entity(person, 'PERSON', record_id)
296
- if location:
297
- builder.add_entity(location, 'LOCATION', record_id)
298
- if event:
299
- builder.add_entity(event, 'EVENT', record_id)
300
- if org:
301
- builder.add_entity(org, 'ORGANIZATION', record_id)
302
- if date:
303
- builder.add_entity(date, 'DATE', record_id)
304
-
305
- # Collect relationships (next args: 5 relationships × 3 fields)
306
- relationship_start = 30
307
- num_relationships = 5
308
-
309
- for i in range(num_relationships):
310
- base_idx = relationship_start + (i * 3)
311
- source = args[base_idx] if base_idx < len(args) else None
312
- target = args[base_idx + 1] if base_idx + 1 < len(args) else None
313
- rel_type = args[base_idx + 2] if base_idx + 2 < len(args) else None
314
-
315
- if source and target:
316
- builder.add_relationship(source, target, rel_type)
317
-
318
- # Get layout type (last arg)
319
- layout_type = args[-1] if len(args) > relationship_start else 'spring'
320
-
321
- # Build graph
322
- G = builder.build_graph()
323
-
324
- if len(G.nodes) == 0:
325
- return None, "❌ No entities to display. Please add some entities first."
326
-
327
- if len(G.edges) == 0:
328
- return None, "⚠️ No relationships defined. The graph will show isolated nodes."
329
-
330
- # Create visualization
331
- fig = builder.create_plotly_graph(G, layout_type)
332
-
333
- # Create statistics
334
- stats = f"""
335
- ### 📈 Network Statistics
336
- - **Nodes (Entities):** {G.number_of_nodes()}
337
- - **Edges (Relationships):** {G.number_of_edges()}
338
- - **Network Density:** {nx.density(G):.3f}
339
- - **Average Connections per Node:** {sum(dict(G.degree()).values()) / G.number_of_nodes():.2f}
340
- """
341
-
342
- if G.number_of_edges() > 0:
343
- # Find most connected nodes
344
- degrees = dict(G.degree())
345
- top_nodes = sorted(degrees.items(), key=lambda x: x[1], reverse=True)[:3]
346
- stats += "\n**Most Connected Entities:**\n"
347
- for node, degree in top_nodes:
348
- stats += f"- {node}: {degree} connections\n"
349
-
350
- return fig, stats
351
 
352
- def create_interface():
353
- with gr.Blocks(title="Basic Networks Explorer", theme=gr.themes.Soft()) as demo:
354
- gr.Markdown("""
355
- # Basic Networks Explorer
356
-
357
- Build interactive social network graphs by entering entities extracted through Named Entity Recognition (NER).
358
- This tool demonstrates how NER can be used to visualize relationships and connections in text data.
359
-
360
- ### How to use this tool:
361
- 1. **📝 Enter entities** in the records below (people, locations, events, organizations, dates)
362
- 2. **🔗 Click "Collect Entities"** to gather all your inputs
363
- 3. **🤝 Define relationships** between entities in the relationship builder
364
- 4. **🎨 Choose a layout style** and click "Generate Network Graph"
365
- 5. **👁️ Explore** the interactive visualization
366
- 6. **🔄 Refresh the page** to start over with new data
367
- """)
368
-
369
- # Add tip box
370
- gr.HTML("""
371
- <div style="background-color: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; padding: 12px; margin: 15px 0;">
372
- <strong style="color: #856404;">💡 Top tip:</strong> This tool works best when you have already identified entities from text using NER. Try the NER Explorer Tool first to extract entities automatically!
373
- </div>
374
- """)
375
-
376
- # Entity input section
377
- entity_inputs = []
378
-
379
- with gr.Accordion("📚 Step 1: Enter Entities from Your Records", open=True):
380
- for i in range(6):
381
- with gr.Group():
382
- gr.Markdown(f"### Record {i+1}")
383
- with gr.Row():
384
- person = gr.Textbox(label="👤 Person", placeholder="e.g., Albert Einstein")
385
- location = gr.Textbox(label="📍 Location", placeholder="e.g., Berlin")
386
- event = gr.Textbox(label="📅 Event", placeholder="e.g., Nobel Prize Ceremony")
387
- with gr.Row():
388
- org = gr.Textbox(label="🏢 Organization", placeholder="e.g., Princeton University")
389
- date = gr.Textbox(label="🗓️ Date", placeholder="e.g., 1921")
390
-
391
- entity_inputs.extend([person, location, event, org, date])
392
-
393
- collect_btn = gr.Button("🔍 Collect Entities", variant="primary", size="lg")
394
-
395
- entity_summary = gr.Markdown()
396
-
397
- # Relationship section (initially hidden)
398
- with gr.Column(visible=False) as relationship_section:
399
- with gr.Accordion("🤝 Step 2: Define Relationships Between Entities", open=True):
400
- gr.Markdown("Select entities and specify how they're connected:")
401
-
402
- relationship_inputs = []
403
-
404
- for i in range(5):
405
- with gr.Row():
406
- source = gr.Dropdown(label=f"From Entity {i+1}", choices=[], interactive=True)
407
- rel_type = gr.Dropdown(
408
- label="Relationship Type",
409
- choices=RELATIONSHIP_TYPES,
410
- value="related_to",
411
- interactive=True
412
- )
413
- target = gr.Dropdown(label=f"To Entity {i+1}", choices=[], interactive=True)
414
-
415
- relationship_inputs.extend([source, rel_type, target])
416
-
417
- with gr.Accordion("🎨 Step 3: Customize and Generate", open=True):
418
- layout_type = gr.Dropdown(
419
- label="Graph Layout",
420
- choices=['spring', 'circular', 'kamada_kawai', 'shell'],
421
- value='spring',
422
- info="Choose how nodes are arranged"
423
- )
424
-
425
- generate_btn = gr.Button("🔍 Generate Network Graph", variant="primary", size="lg")
426
-
427
- # Output section
428
- gr.HTML("<hr style='margin: 30px 0;'>")
429
-
430
- with gr.Row():
431
- network_stats = gr.Markdown()
432
-
433
- with gr.Row():
434
- network_plot = gr.Plot(label="Interactive Network Graph")
435
-
436
- # Examples
437
- with gr.Column():
438
- gr.Markdown("""
439
- ### 💡 No example entities to test? No problem!
440
- Simply click on one of the examples provided below, and the fields will be populated for you.
441
- """, elem_id="examples-heading")
442
- gr.Examples(
443
- examples=[
444
- [
445
- # Record 1
446
- "Winston Churchill", "London", "Battle of Britain", "Royal Air Force", "1940",
447
- # Record 2
448
- "Franklin D. Roosevelt", "Washington D.C.", "Pearl Harbor Attack", "United States Navy", "December 7, 1941",
449
- # Record 3
450
- "Dwight D. Eisenhower", "Normandy", "D-Day Invasion", "Allied Forces", "June 6, 1944",
451
- # Record 4
452
- "Winston Churchill", "Yalta", "Yalta Conference", "Allied Powers", "February 1945",
453
- # Record 5
454
- "Harry S. Truman", "Potsdam", "Potsdam Conference", "Allied Powers", "July 1945",
455
- # Record 6
456
- "Douglas MacArthur", "Tokyo Bay", "Japanese Surrender", "United States Military", "September 2, 1945"
457
- ],
458
- [
459
- # Record 1 - Pride and Prejudice
460
- "Elizabeth Bennet", "Longbourn", "First Ball", "", "1811",
461
- # Record 2
462
- "Mr. Darcy", "Pemberley", "First Ball", "", "1811",
463
- # Record 3
464
- "Jane Bennet", "Netherfield", "Dinner Party", "", "1811",
465
- # Record 4
466
- "Mr. Bingley", "Netherfield", "Dinner Party", "", "1811",
467
- # Record 5
468
- "Elizabeth Bennet", "Rosings Park", "Easter Visit", "", "1812",
469
- # Record 6
470
- "Mr. Darcy", "Rosings Park", "Easter Visit", "", "1812"
471
- ]
472
- ],
473
- inputs=entity_inputs,
474
- label="Examples"
475
- )
476
-
477
- # Add custom CSS to match NER tool styling
478
- gr.HTML("""
479
- <style>
480
- /* Make the Examples label text black */
481
- .gradio-examples-label {
482
- color: black !important;
483
- }
484
- h4.examples-label, .examples-label {
485
- color: black !important;
486
- }
487
- #examples-heading + div label,
488
- #examples-heading + div .label-text {
489
- color: black !important;
490
- }
491
- </style>
492
- """)
493
-
494
- # Wire up the interface
495
- # Collect entities button
496
- collect_btn.click(
497
- fn=collect_entities_from_records,
498
- inputs=entity_inputs,
499
- outputs=[
500
- entity_summary,
501
- relationship_section
502
- ] + relationship_inputs[::3] + relationship_inputs[2::3] # Update source and target dropdowns
503
- )
504
-
505
- # Generate graph button
506
- all_inputs = entity_inputs + relationship_inputs + [layout_type]
507
- generate_btn.click(
508
- fn=generate_network_graph,
509
- inputs=all_inputs,
510
- outputs=[network_plot, network_stats]
511
- )
512
-
513
- # Information footer
514
- gr.HTML("""
515
- <hr style="margin-top: 40px; margin-bottom: 20px;">
516
- <div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin-top: 20px;">
517
- <h4 style="margin-top: 0;">ℹ️ About This Tool</h4>
518
- <p style="font-size: 14px; line-height: 1.8;">
519
- This tool demonstrates how <strong>Named Entity Recognition (NER)</strong> can be combined with
520
- <strong>network analysis</strong> to visualize relationships in text data. In real-world applications,
521
- entities would be automatically extracted from text using NER models, and relationships could be
522
- identified through co-occurrence analysis, dependency parsing, or machine learning.
523
- </p>
524
- <p style="font-size: 14px; line-height: 1.8; margin-bottom: 0;">
525
- <strong>Built with:</strong> Gradio, NetworkX, and Plotly |
526
- <strong>Graph Layouts:</strong> Spring (force-directed), Circular, Kamada-Kawai, Shell
527
- </p>
528
- </div>
529
-
530
- <br>
531
- <hr style="margin-top: 40px; margin-bottom: 20px;">
532
- <div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin-top: 20px; text-align: center;">
533
- <p style="font-size: 14px; line-height: 1.8; margin: 0;">
534
- This <strong>Basic Networks Explorer</strong> was created as part of a Bodleian Libraries Oxford Sassoon Research Fellowship.
535
- </a>
536
- funded research project:<br>
537
- <em>Extracting Keywords from Crowdsourced Collections</em>.
538
- </p><br><br>
539
- <p style="font-size: 14px; line-height: 1.8; margin: 0;">
540
- The code for this tool was built with the aid of Claude Sonnet 4.5.
541
- </p>
542
- </div>
543
- """)
544
-
545
- return demo
546
 
547
- if __name__ == "__main__":
548
- demo = create_interface()
549
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Basic Network Explorer - Updates
 
 
 
 
2
 
3
+ ## Changes Made Based on Feedback
 
 
 
 
 
 
 
4
 
5
+ ### 1. Name Change
6
+ - Changed from "Basic Networks Explorer" to **"Basic Network Explorer"** (singular)
7
+ - Updated throughout code and documentation
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ ### 2. ✅ Footer Attribution Updated
10
+ **Old:**
11
+ - DiSc project at Oxford
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ **New:**
14
+ - "This Basic Network Explorer tool was created as part of a Bodleian Libraries (University of Oxford) Sassoon Research Fellowship."
15
+ - "The code for this tool was built with the aid of Claude Sonnet 4.5."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ ### 3. ✅ Terminology Change
18
+ - Changed "Collected Entities" to **"Identified Entities"**
19
+ - Updated button text from "Collect Entities" to **"Identify Entities"**
20
+ - Updated instructions accordingly
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ ### 4. ✅ Layout Restructured
23
+ **Before:** Stacked vertically (busy at top)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ **After:** Two-column layout
26
+ - **Left Column:** Entity input records (Step 1)
27
+ - **Right Column:** Relationship builder (Step 2) and customization (Step 3)
28
+
29
+ This provides more breathing space and clearer visual organization.
30
+
31
+ ### 5. ✅ Bug Fix: Network Graph Generation
32
+ **Issue:** Graph wasn't generating
33
+
34
+ **Root Cause:** Incorrect argument order in relationship parsing
35
+ - Expected: source, target, rel_type
36
+ - Actual input: source, rel_type, target
37
+
38
+ **Fix:** Corrected the argument extraction order to match the actual input order
39
+
40
+ **Additional Improvement:** Graph now displays even without relationships (showing isolated nodes with a warning)
41
+
42
+ ### 6. ✅ British Examples with Auto-populated Relationships
43
+
44
+ **Example 1: British WWII**
45
+ - Winston Churchill, Clement Attlee, Field Marshal Montgomery, King George VI
46
+ - Locations: London, North Africa, Yalta, Lüneburg Heath
47
+ - Events: Battle of Britain, Battle of El Alamein, Yalta Conference, VE Day
48
+ - **Relationships auto-populate:** works_with, participated_in connections
49
+
50
+ **Example 2: Pride and Prejudice** (Already British)
51
+ - Elizabeth Bennet, Mr Darcy, Jane Bennet, Mr Bingley
52
+ - Locations: Longbourn, Pemberley, Rosings, Netherfield
53
+ - Events: Meryton Assembly, Netherfield Ball, First Proposal
54
+ - **Relationships auto-populate:** knows, located_in, participated_in connections
55
+
56
+ ### Additional Improvements
57
+ - Simplified relationship dropdown labels (shortened from "From Entity 1" to just "From")
58
+ - Better placeholder text in entity fields (British examples)
59
+ - Graph now shows isolated nodes with warning if no relationships defined
60
+ - More robust error handling
61
+
62
+ ## Files Updated
63
+ 1. `app.py` - Main application file with all changes
64
+ 2. `requirements.txt` - No changes needed
65
+ 3. `README.md` - Updated with new tool name
66
+
67
+ ## Ready for Deployment
68
+ All files are updated and ready to upload to your Hugging Face Space: **Basic-Network-Explorer**