ginipick commited on
Commit
f5a9f9e
Β·
verified Β·
1 Parent(s): fcc0582

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +294 -104
main.py CHANGED
@@ -1,39 +1,79 @@
1
  import spaces
2
  import gradio as gr
3
  from phi3_instruct_graph import MODEL_LIST, Phi3InstructGraph
4
- from textwrap import dedent
5
  import rapidjson
6
- import spaces
7
  from pyvis.network import Network
8
  import networkx as nx
9
  import spacy
10
  from spacy import displacy
11
  from spacy.tokens import Span
12
  import random
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- json_example = {'nodes': [{'id': 'Aerosmith', 'type': 'organization', 'detailed_type': 'rock band'}, {'id': 'Steven Tyler', 'type': 'person', 'detailed_type': 'lead singer'}, {'id': 'vocal cord injury', 'type': 'medical condition', 'detailed_type': 'fractured larynx'}, {'id': 'retirement', 'type': 'event', 'detailed_type': 'announcement'}, {'id': 'touring', 'type': 'activity', 'detailed_type': 'musical performance'}, {'id': 'September 2023', 'type': 'date', 'detailed_type': 'specific time'}], 'edges': [{'from': 'Aerosmith', 'to': 'Steven Tyler', 'label': 'led by'}, {'from': 'Steven Tyler', 'to': 'vocal cord injury', 'label': 'suffered'}, {'from': 'vocal cord injury', 'to': 'retirement', 'label': 'caused'}, {'from': 'retirement', 'to': 'touring', 'label': 'ended'}, {'from': 'vocal cord injury', 'to': 'September 2023', 'label': 'occurred in'}]}
 
 
15
 
 
16
  @spaces.GPU
17
  def extract(text, model):
18
  model = Phi3InstructGraph(model=model)
19
- result = model.extract(text)
20
- return rapidjson.loads(result)
21
-
22
- def handle_text(text):
23
- return " ".join(text.split())
24
-
25
- def get_random_color():
26
- return f"#{random.randint(0, 0xFFFFFF):06x}"
27
-
28
- def get_random_light_color():
29
- # Generate higher RGB values to ensure a lighter color
30
- r = random.randint(128, 255)
31
- g = random.randint(128, 255)
32
- b = random.randint(128, 255)
33
- return f"#{r:02x}{g:02x}{b:02x}"
34
-
35
- def get_random_color():
36
- return f"#{random.randint(0, 0xFFFFFF):06x}"
37
 
38
  def find_token_indices(doc, substring, text):
39
  result = []
@@ -50,9 +90,7 @@ def find_token_indices(doc, substring, text):
50
  if token.idx + len(token) == end_index:
51
  end_token = token.i + 1
52
 
53
- if start_token is None or end_token is None:
54
- print(f"Token boundaries not found for '{substring}' at index {start_index}")
55
- else:
56
  result.append({
57
  "start": start_token,
58
  "end": end_token
@@ -61,36 +99,29 @@ def find_token_indices(doc, substring, text):
61
  # Search for next occurrence
62
  start_index = text.find(substring, end_index)
63
 
64
- if not result:
65
- print(f"Token boundaries not found for '{substring}'")
66
-
67
  return result
68
 
69
-
70
  def create_custom_entity_viz(data, full_text):
71
  nlp = spacy.blank("xx")
72
  doc = nlp(full_text)
73
 
74
  spans = []
75
  colors = {}
 
76
  for node in data["nodes"]:
77
- # entity_spans = [m.span() for m in re.finditer(re.escape(node["id"]), full_text)]
78
  entity_spans = find_token_indices(doc, node["id"], full_text)
79
  for dataentity in entity_spans:
80
  start = dataentity["start"]
81
  end = dataentity["end"]
82
 
83
- print("entity spans:", entity_spans)
84
  if start < len(doc) and end <= len(doc):
85
- span = Span(doc, start, end, label=node["type"])
86
-
87
- # print(span)
88
- spans.append(span)
89
- if node["type"] not in colors:
90
- colors[node["type"]] = get_random_light_color()
91
-
92
- for span in spans:
93
- print(f"Span: {span.text}, Label: {span.label_}")
94
 
95
  doc.set_ents(spans, default="unmodified")
96
  doc.spans["sc"] = spans
@@ -103,108 +134,267 @@ def create_custom_entity_viz(data, full_text):
103
  }
104
 
105
  html = displacy.render(doc, style="span", options=options)
106
- return html
107
-
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
  def create_graph(json_data):
110
- G = nx.Graph()
111
 
 
112
  for node in json_data['nodes']:
113
- G.add_node(node['id'], title=f"{node['type']}: {node['detailed_type']}")
 
 
114
 
 
115
  for edge in json_data['edges']:
116
  G.add_edge(edge['from'], edge['to'], title=edge['label'], label=edge['label'])
117
 
 
118
  nt = Network(
119
- width="720px",
120
  height="600px",
121
  directed=True,
122
  notebook=False,
123
- # bgcolor="#111827",
124
- # font_color="white"
125
- bgcolor="#FFFFFF",
126
- font_color="#111827"
127
  )
 
 
128
  nt.from_nx(G)
129
  nt.barnes_hut(
130
  gravity=-3000,
131
  central_gravity=0.3,
132
- spring_length=50,
133
  spring_strength=0.001,
134
  damping=0.09,
135
  overlap=0,
136
  )
137
 
138
- # Customize edge appearance
139
- # for edge in nt.edges:
140
- # edge['font'] = {'size': 12, 'color': '#FFD700', 'face': 'Arial'} # Removed strokeWidth
141
- # edge['color'] = {'color': '#FF4500', 'highlight': '#FF4500'}
142
- # edge['width'] = 1
143
- # edge['arrows'] = {'to': {'enabled': True, 'type': 'arrow'}}
144
- # edge['smooth'] = {'type': 'curvedCW', 'roundness': 0.2}
145
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  html = nt.generate_html()
147
- # need to remove ' from HTML
148
  html = html.replace("'", '"')
149
- # return html
150
 
151
- return f"""<iframe style="width: 140%; height: 620px; margin: 0 auto;" name="result"
152
- allow="midi; geolocation; microphone; camera; display-capture; encrypted-media;"
153
  sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups
154
  allow-top-navigation-by-user-activation allow-downloads" allowfullscreen=""
155
  allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>"""
156
-
157
 
158
- def process_and_visualize(text, model):
159
  if not text or not model:
160
- raise gr.Error("Text and model must be provided.")
 
 
 
 
 
 
 
161
  json_data = extract(text, model)
162
- # json_data = json_example
163
- print(json_data)
 
164
  entities_viz = create_custom_entity_viz(json_data, text)
165
 
 
 
166
  graph_html = create_graph(json_data)
167
- return graph_html, entities_viz, json_data
168
-
169
-
170
-
171
- with gr.Blocks(title="Phi-3 Mini 4k Instruct Graph (by Emergent Methods") as demo:
172
- gr.Markdown("# Phi-3 Mini 4k Instruct Graph (by Emergent Methods)")
173
- gr.Markdown("Extract a JSON graph from a text input and visualize it.")
174
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  with gr.Row():
176
- with gr.Column(scale=1):
177
- input_model = gr.Dropdown(
178
- MODEL_LIST, label="Model",
179
- # value=MODEL_LIST[0]
 
180
  )
181
- input_text = gr.TextArea(label="Text", info="The text to be extracted")
182
-
183
- examples = gr.Examples(
184
- examples=[
185
- handle_text("""Legendary rock band Aerosmith has officially announced their retirement from touring after 54 years, citing
186
- lead singer Steven Tyler's unrecoverable vocal cord injury.
187
- The decision comes after months of unsuccessful treatment for Tyler's fractured larynx,
188
- which he suffered in September 2023."""),
189
- handle_text("""Pop star Justin Timberlake, 43, had his driver's license suspended by a New York judge during a virtual
190
- court hearing on August 2, 2024. The suspension follows Timberlake's arrest for driving while intoxicated (DWI)
191
- in Sag Harbor on June 18. Timberlake, who is currently on tour in Europe,
192
- pleaded not guilty to the charges."""),
193
- ],
194
- inputs=input_text
 
 
 
 
 
 
 
195
  )
196
-
197
- submit_button = gr.Button("Extract and Visualize")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
- with gr.Column(scale=1):
200
- output_entity_viz = gr.HTML(label="Entities Visualization", show_label=True)
201
- output_graph = gr.HTML(label="Graph Visualization", show_label=True)
202
- # output_json = gr.JSON(label="JSON Graph")
203
-
204
- submit_button.click(
205
- fn=process_and_visualize,
206
- inputs=[input_text, input_model],
207
- outputs=[output_graph, output_entity_viz]
208
- )
 
 
 
 
 
 
 
 
 
 
209
 
 
210
  demo.launch(share=False)
 
1
  import spaces
2
  import gradio as gr
3
  from phi3_instruct_graph import MODEL_LIST, Phi3InstructGraph
 
4
  import rapidjson
 
5
  from pyvis.network import Network
6
  import networkx as nx
7
  import spacy
8
  from spacy import displacy
9
  from spacy.tokens import Span
10
  import random
11
+ import time
12
+
13
+ # Set up the theme and styling
14
+ CUSTOM_CSS = """
15
+ .gradio-container {
16
+ font-family: 'Inter', 'Segoe UI', Roboto, sans-serif;
17
+ }
18
+ .gr-prose h1 {
19
+ font-size: 2.5rem !important;
20
+ margin-bottom: 0.5rem !important;
21
+ background: linear-gradient(90deg, #4338ca, #a855f7);
22
+ -webkit-background-clip: text;
23
+ -webkit-text-fill-color: transparent;
24
+ }
25
+ .gr-prose h2 {
26
+ font-size: 1.8rem !important;
27
+ margin-top: 1rem !important;
28
+ }
29
+ .info-box {
30
+ padding: 1rem;
31
+ border-radius: 0.5rem;
32
+ background-color: #f3f4f6;
33
+ margin-bottom: 1rem;
34
+ border-left: 4px solid #6366f1;
35
+ }
36
+ .language-badge {
37
+ display: inline-block;
38
+ padding: 0.25rem 0.5rem;
39
+ border-radius: 9999px;
40
+ font-size: 0.75rem;
41
+ font-weight: 600;
42
+ background-color: #e0e7ff;
43
+ color: #4338ca;
44
+ margin-right: 0.5rem;
45
+ margin-bottom: 0.5rem;
46
+ }
47
+ .footer {
48
+ text-align: center;
49
+ margin-top: 2rem;
50
+ padding-top: 1rem;
51
+ border-top: 1px solid #e2e8f0;
52
+ font-size: 0.875rem;
53
+ color: #64748b;
54
+ }
55
+ """
56
+
57
+ # Color utilities
58
+ def get_random_light_color():
59
+ r = random.randint(150, 255)
60
+ g = random.randint(150, 255)
61
+ b = random.randint(150, 255)
62
+ return f"#{r:02x}{g:02x}{b:02x}"
63
 
64
+ # Text processing helper
65
+ def handle_text(text):
66
+ return " ".join(text.split())
67
 
68
+ # Core extraction function
69
  @spaces.GPU
70
  def extract(text, model):
71
  model = Phi3InstructGraph(model=model)
72
+ try:
73
+ result = model.extract(text)
74
+ return rapidjson.loads(result)
75
+ except Exception as e:
76
+ raise gr.Error(f"🚨 Extraction failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  def find_token_indices(doc, substring, text):
79
  result = []
 
90
  if token.idx + len(token) == end_index:
91
  end_token = token.i + 1
92
 
93
+ if start_token is not None and end_token is not None:
 
 
94
  result.append({
95
  "start": start_token,
96
  "end": end_token
 
99
  # Search for next occurrence
100
  start_index = text.find(substring, end_index)
101
 
 
 
 
102
  return result
103
 
 
104
  def create_custom_entity_viz(data, full_text):
105
  nlp = spacy.blank("xx")
106
  doc = nlp(full_text)
107
 
108
  spans = []
109
  colors = {}
110
+
111
  for node in data["nodes"]:
 
112
  entity_spans = find_token_indices(doc, node["id"], full_text)
113
  for dataentity in entity_spans:
114
  start = dataentity["start"]
115
  end = dataentity["end"]
116
 
 
117
  if start < len(doc) and end <= len(doc):
118
+ # Check for overlapping spans
119
+ overlapping = any(s.start < end and start < s.end for s in spans)
120
+ if not overlapping:
121
+ span = Span(doc, start, end, label=node["type"])
122
+ spans.append(span)
123
+ if node["type"] not in colors:
124
+ colors[node["type"]] = get_random_light_color()
 
 
125
 
126
  doc.set_ents(spans, default="unmodified")
127
  doc.spans["sc"] = spans
 
134
  }
135
 
136
  html = displacy.render(doc, style="span", options=options)
137
+
138
+ # Add custom styling to the entity visualization
139
+ styled_html = f"""
140
+ <div style="border-radius: 0.5rem; padding: 1rem; background-color: white;
141
+ border: 1px solid #e2e8f0; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);">
142
+ <div style="margin-bottom: 0.75rem; font-weight: 500; color: #4b5563;">
143
+ Entity types found:
144
+ {' '.join([f'<span style="display: inline-block; margin-right: 0.5rem; margin-bottom: 0.5rem; padding: 0.25rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; background-color: {colors[entity_type]}; color: #1e293b;">{entity_type}</span>' for entity_type in colors.keys()])}
145
+ </div>
146
+ {html}
147
+ </div>
148
+ """
149
+
150
+ return styled_html
151
 
152
  def create_graph(json_data):
153
+ G = nx.DiGraph() # Using DiGraph for directed graph
154
 
155
+ # Add nodes
156
  for node in json_data['nodes']:
157
+ G.add_node(node['id'],
158
+ title=f"{node['type']}: {node['detailed_type']}",
159
+ group=node['type']) # Group nodes by type
160
 
161
+ # Add edges
162
  for edge in json_data['edges']:
163
  G.add_edge(edge['from'], edge['to'], title=edge['label'], label=edge['label'])
164
 
165
+ # Create network visualization
166
  nt = Network(
167
+ width="100%",
168
  height="600px",
169
  directed=True,
170
  notebook=False,
171
+ bgcolor="#fafafa",
172
+ font_color="#1e293b"
 
 
173
  )
174
+
175
+ # Configure network
176
  nt.from_nx(G)
177
  nt.barnes_hut(
178
  gravity=-3000,
179
  central_gravity=0.3,
180
+ spring_length=150,
181
  spring_strength=0.001,
182
  damping=0.09,
183
  overlap=0,
184
  )
185
 
186
+ # Create color groups for node types
187
+ node_types = {node['type'] for node in json_data['nodes']}
188
+ colors = {}
189
+ for i, node_type in enumerate(node_types):
190
+ hue = (i * 137) % 360 # Golden ratio to distribute colors
191
+ colors[node_type] = f"hsl({hue}, 70%, 70%)"
192
+
193
+ # Customize nodes
194
+ for node in nt.nodes:
195
+ node_data = next((n for n in json_data['nodes'] if n['id'] == node['id']), None)
196
+ if node_data:
197
+ node_type = node_data['type']
198
+ node['color'] = colors.get(node_type, "#bfdbfe")
199
+ node['shape'] = 'dot'
200
+ node['size'] = 20
201
+ node['borderWidth'] = 2
202
+ node['borderWidthSelected'] = 4
203
+ node['font'] = {'size': 14, 'color': '#1e293b', 'face': 'Inter, Arial'}
204
+
205
+ # Customize edges
206
+ for edge in nt.edges:
207
+ edge['color'] = {'color': '#94a3b8', 'highlight': '#6366f1', 'hover': '#818cf8'}
208
+ edge['width'] = 1.5
209
+ edge['selectionWidth'] = 2
210
+ edge['hoverWidth'] = 2
211
+ edge['arrows'] = {'to': {'enabled': True, 'type': 'arrow'}}
212
+ edge['smooth'] = {'type': 'continuous', 'roundness': 0.2}
213
+ edge['font'] = {'size': 12, 'color': '#4b5563', 'face': 'Inter, Arial', 'strokeWidth': 2, 'strokeColor': '#ffffff'}
214
+
215
+ # Generate HTML
216
  html = nt.generate_html()
 
217
  html = html.replace("'", '"')
218
+ html = html.replace('height: 600px;', 'height: 600px; border-radius: 8px;')
219
 
220
+ return f"""<iframe style="width: 100%; height: 620px; margin: 0 auto; border-radius: 8px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);"
221
+ name="result" allow="midi; geolocation; microphone; camera; display-capture; encrypted-media;"
222
  sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups
223
  allow-top-navigation-by-user-activation allow-downloads" allowfullscreen=""
224
  allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>"""
 
225
 
226
+ def process_and_visualize(text, model, progress=gr.Progress()):
227
  if not text or not model:
228
+ raise gr.Error("⚠️ Please provide both text and model")
229
+
230
+ # Progress updates
231
+ progress(0.1, "Initializing...")
232
+ time.sleep(0.2) # Small delay for UI feedback
233
+
234
+ # Extract graph
235
+ progress(0.2, "Extracting knowledge graph...")
236
  json_data = extract(text, model)
237
+
238
+ # Entity visualization
239
+ progress(0.6, "Identifying entities...")
240
  entities_viz = create_custom_entity_viz(json_data, text)
241
 
242
+ # Graph visualization
243
+ progress(0.8, "Building graph visualization...")
244
  graph_html = create_graph(json_data)
245
+
246
+ # Statistics
247
+ entity_types = {}
248
+ for node in json_data['nodes']:
249
+ entity_type = node['type']
250
+ if entity_type in entity_types:
251
+ entity_types[entity_type] += 1
252
+ else:
253
+ entity_types[entity_type] = 1
254
+
255
+ stats_html = f"""
256
+ <div class="info-box">
257
+ <h3 style="margin-top: 0;">πŸ“Š Extraction Results</h3>
258
+ <p>βœ… Successfully extracted <b>{len(json_data['nodes'])}</b> entities and <b>{len(json_data['edges'])}</b> relationships.</p>
259
+
260
+ <div>
261
+ <h4>Entity Types:</h4>
262
+ <div>
263
+ {''.join([f'<span class="language-badge">{entity_type}: {count}</span>' for entity_type, count in entity_types.items()])}
264
+ </div>
265
+ </div>
266
+ </div>
267
+ """
268
+
269
+ progress(1.0, "Done!")
270
+ return graph_html, entities_viz, json_data, stats_html
271
+
272
+ def language_info():
273
+ return """
274
+ <div class="info-box">
275
+ <h3 style="margin-top: 0;">🌍 Multilingual Support</h3>
276
+ <p>This application supports text analysis in multiple languages, including:</p>
277
+ <div>
278
+ <span class="language-badge">English πŸ‡¬πŸ‡§</span>
279
+ <span class="language-badge">Korean πŸ‡°πŸ‡·</span>
280
+ <span class="language-badge">Spanish πŸ‡ͺπŸ‡Έ</span>
281
+ <span class="language-badge">French πŸ‡«πŸ‡·</span>
282
+ <span class="language-badge">German πŸ‡©πŸ‡ͺ</span>
283
+ <span class="language-badge">Japanese πŸ‡―πŸ‡΅</span>
284
+ <span class="language-badge">Chinese πŸ‡¨πŸ‡³</span>
285
+ <span class="language-badge">And more...</span>
286
+ </div>
287
+ </div>
288
+ """
289
+
290
+ def tips_html():
291
+ return """
292
+ <div class="info-box">
293
+ <h3 style="margin-top: 0;">πŸ’‘ Tips for Best Results</h3>
294
+ <ul>
295
+ <li>Use clear, descriptive sentences with well-defined relationships</li>
296
+ <li>Include specific entities, events, dates, and locations for better extraction</li>
297
+ <li>Longer texts provide more context for relationship identification</li>
298
+ <li>Try different models to compare extraction results</li>
299
+ </ul>
300
+ </div>
301
+ """
302
+
303
+ # Examples in multiple languages
304
+ EXAMPLES = [
305
+ [handle_text("""Legendary rock band Aerosmith has officially announced their retirement from touring after 54 years, citing
306
+ lead singer Steven Tyler's unrecoverable vocal cord injury.
307
+ The decision comes after months of unsuccessful treatment for Tyler's fractured larynx,
308
+ which he suffered in September 2023.""")],
309
+
310
+ [handle_text("""Pop star Justin Timberlake, 43, had his driver's license suspended by a New York judge during a virtual
311
+ court hearing on August 2, 2024. The suspension follows Timberlake's arrest for driving while intoxicated (DWI)
312
+ in Sag Harbor on June 18. Timberlake, who is currently on tour in Europe,
313
+ pleaded not guilty to the charges.""")],
314
+
315
+ [handle_text("""세계적인 기술 κΈ°μ—… μ‚Όμ„±μ „μžλŠ” μƒˆλ‘œμš΄ 인곡지λŠ₯ 기반 μŠ€λ§ˆνŠΈν°μ„ μ˜¬ν•΄ ν•˜λ°˜κΈ°μ— μΆœμ‹œν•  μ˜ˆμ •μ΄λΌκ³  λ°œν‘œν–ˆλ‹€.
316
+ 이 μŠ€λ§ˆνŠΈν°μ€ ν˜„μž¬ 개발 쀑인 κ°€λŸ­μ‹œ μ‹œλ¦¬μ¦ˆμ˜ μ΅œμ‹ μž‘μœΌλ‘œ, κ°•λ ₯ν•œ AI κΈ°λŠ₯κ³Ό ν˜μ‹ μ μΈ 카메라 μ‹œμŠ€ν…œμ„ νƒ‘μž¬ν•  κ²ƒμœΌλ‘œ μ•Œλ €μ‘Œλ‹€.
317
+ μ‚Όμ„±μ „μžμ˜ CEOλŠ” 이번 μ‹ μ œν’ˆμ΄ 슀마트폰 μ‹œμž₯에 μƒˆλ‘œμš΄ ν˜μ‹ μ„ κ°€μ Έμ˜¬ 것이라고 μ „λ§ν–ˆλ‹€.""")],
318
+
319
+ [handle_text("""ν•œκ΅­ μ˜ν™” '기생좩'은 2020λ…„ 아카데미 μ‹œμƒμ‹μ—μ„œ μž‘ν’ˆμƒ, 감독상, 각본상, κ΅­μ œμ˜ν™”μƒ λ“± 4개 뢀문을 μˆ˜μƒν•˜λ©° 역사λ₯Ό μƒˆλ‘œ 썼닀.
320
+ λ΄‰μ€€ν˜Έ 감독이 μ—°μΆœν•œ 이 μ˜ν™”λŠ” ν•œκ΅­ μ˜ν™” 졜초둜 μΉΈ μ˜ν™”μ œ ν™©κΈˆμ’…λ €μƒλ„ μˆ˜μƒν–ˆμœΌλ©°, μ „ μ„Έκ³„μ μœΌλ‘œ μ—„μ²­λ‚œ ν₯ν–‰κ³Ό
321
+ ν‰λ‹¨μ˜ ν˜Έν‰μ„ λ°›μ•˜λ‹€.""")]
322
+ ]
323
+
324
+ # Main UI
325
+ with gr.Blocks(css=CUSTOM_CSS, title="🧠 Phi-3 Knowledge Graph Explorer") as demo:
326
+ # Header
327
+ gr.Markdown("# 🧠 Phi-3 Knowledge Graph Explorer")
328
+ gr.Markdown("### ✨ Extract and visualize knowledge graphs from text in any language")
329
+
330
  with gr.Row():
331
+ with gr.Column(scale=2):
332
+ input_text = gr.TextArea(
333
+ label="πŸ“ Enter your text",
334
+ placeholder="Paste or type your text here...",
335
+ lines=10
336
  )
337
+
338
+ with gr.Row():
339
+ input_model = gr.Dropdown(
340
+ MODEL_LIST,
341
+ label="πŸ€– Model",
342
+ value=MODEL_LIST[0] if MODEL_LIST else None,
343
+ info="Select the model to use for extraction"
344
+ )
345
+
346
+ with gr.Column():
347
+ submit_button = gr.Button("πŸ” Extract & Visualize", variant="primary")
348
+ clear_button = gr.Button("πŸ”„ Clear", variant="secondary")
349
+
350
+ # Multilingual support info
351
+ gr.HTML(language_info())
352
+
353
+ # Examples section
354
+ gr.Examples(
355
+ examples=EXAMPLES,
356
+ inputs=input_text,
357
+ label="πŸ“š Example Texts (English & Korean)"
358
  )
359
+
360
+ # Tips
361
+ gr.HTML(tips_html())
362
+
363
+ with gr.Column(scale=3):
364
+ # Stats output
365
+ stats_output = gr.HTML(label="")
366
+
367
+ # Tabs for different visualizations
368
+ with gr.Tabs():
369
+ with gr.TabItem("πŸ”„ Knowledge Graph"):
370
+ output_graph = gr.HTML()
371
+
372
+ with gr.TabItem("🏷️ Entity Recognition"):
373
+ output_entity_viz = gr.HTML()
374
+
375
+ with gr.TabItem("πŸ“Š JSON Data"):
376
+ output_json = gr.JSON()
377
 
378
+ # Footer
379
+ gr.HTML("""
380
+ <div class="footer">
381
+ <p>🌐 Powered by Phi-3 Instruct Graph | Created by Emergent Methods</p>
382
+ <p>Β© 2025 | Knowledge Graph Explorer</p>
383
+ </div>
384
+ """)
385
+
386
+ # Set up event handlers
387
+ submit_button.click(
388
+ fn=process_and_visualize,
389
+ inputs=[input_text, input_model],
390
+ outputs=[output_graph, output_entity_viz, output_json, stats_output]
391
+ )
392
+
393
+ clear_button.click(
394
+ fn=lambda: [None, None, None, ""],
395
+ inputs=[],
396
+ outputs=[output_graph, output_entity_viz, output_json, stats_output]
397
+ )
398
 
399
+ # Launch the app
400
  demo.launch(share=False)