sbompolas commited on
Commit
3662a0e
·
verified ·
1 Parent(s): 6394ec4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -189
app.py CHANGED
@@ -1,12 +1,13 @@
1
  import gradio as gr
 
2
  import stanza
3
  import pandas as pd
4
  import requests
5
  import traceback
6
  from pathlib import Path
7
- import os
8
 
9
- # 1. MODEL VARIANTS
 
10
  LESBIAN_MODELS = {}
11
  MODEL_VARIANTS = {
12
  "Lesbian-only": "sbompolas/Lesbian-Greek-Morphosyntactic-Model",
@@ -14,7 +15,6 @@ MODEL_VARIANTS = {
14
  }
15
 
16
  def download_model_file(url, filename):
17
- """Download a model file from Hugging Face"""
18
  try:
19
  resp = requests.get(url, stream=True)
20
  resp.raise_for_status()
@@ -27,98 +27,82 @@ def download_model_file(url, filename):
27
  return False
28
 
29
  def initialize_models():
30
- """Download & initialize both Lesbian Greek variants"""
31
  try:
32
- base_dir = Path("./models")
33
- base_dir.mkdir(exist_ok=True)
34
  for name, repo in MODEL_VARIANTS.items():
35
- out_dir = base_dir/name
36
- out_dir.mkdir(exist_ok=True)
37
- # four model files
38
  files = {
39
- "tokenizer.pt": f"https://huggingface.co/{repo}/resolve/main/tokenizer.pt",
40
- "lemmatizer.pt": f"https://huggingface.co/{repo}/resolve/main/lemmatizer.pt",
41
- "pos.pt": f"https://huggingface.co/{repo}/resolve/main/pos.pt",
42
- "depparse.pt": f"https://huggingface.co/{repo}/resolve/main/depparse.pt"
43
  }
44
  for fn, url in files.items():
45
- tgt = out_dir/fn
46
- if not tgt.exists():
47
- if not download_model_file(url, str(tgt)):
48
- return False, f"Failed to download {fn} for {name}"
49
-
50
- # build stanza pipeline
51
  cfg = {
52
  'processors': 'tokenize,pos,lemma,depparse',
53
  'lang': 'el',
54
  'use_gpu': False,
55
  'verbose': False,
56
- 'tokenize_model_path': str(out_dir/"tokenizer.pt"),
57
- 'pos_model_path': str(out_dir/"pos.pt"),
58
- 'lemma_model_path': str(out_dir/"lemmatizer.pt"),
59
- 'depparse_model_path': str(out_dir/"depparse.pt")
60
  }
61
  try:
62
- pipe = stanza.Pipeline(**cfg)
63
- LESBIAN_MODELS[name] = pipe
64
- print(f"Loaded model variant {name}")
65
  except Exception as e:
66
- return False, f"Failed to init pipeline for {name}: {e}"
67
- return True, "Models loaded successfully"
68
  except Exception as e:
69
  traceback.print_exc()
70
  return False, str(e)
71
 
72
- # 2. CoNLL-U conversion
 
 
 
73
  def stanza_doc_to_conllu(doc) -> str:
74
  lines = []
75
- for sid, sent in enumerate(doc.sentences, start=1):
76
  lines.append(f"# sent_id = {sid}")
77
  lines.append(f"# text = {sent.text}")
78
  for w in sent.words:
79
  fields = [
80
- str(w.id),
81
- w.text,
82
- w.lemma or "_",
83
- w.upos or "_",
84
- w.xpos or "_",
85
- w.feats or "_",
86
  str(w.head) if w.head is not None else "0",
87
- w.deprel or "_",
88
- "_",
89
- "_"
90
  ]
91
  lines.append("\t".join(fields))
92
  lines.append("")
93
  return "\n".join(lines)
94
 
95
- # 3. DataFrame conversion
96
  def conllu_to_dataframe(conllu: str) -> pd.DataFrame:
97
  rows = []
98
- for line in conllu.splitlines():
99
- if not line or line.startswith("#"):
100
  continue
101
- parts = line.split("\t")
102
  if len(parts) >= 10:
103
  rows.append({
104
- 'ID': parts[0],
105
- 'FORM': parts[1],
106
- 'LEMMA': parts[2],
107
- 'UPOS': parts[3],
108
- 'XPOS': parts[4],
109
- 'FEATS': parts[5],
110
- 'HEAD': parts[6],
111
- 'DEPREL': parts[7],
112
- 'DEPS': parts[8],
113
- 'MISC': parts[9]
114
  })
115
  return pd.DataFrame(rows)
116
 
117
- # 4. Text-based dependency viz
118
  def create_dependency_visualization(df: pd.DataFrame) -> str:
119
  if df.empty:
120
  return "No data to visualize"
121
- lines = ["Dependency Parse Visualization:", "-"*40]
122
  for _, r in df.iterrows():
123
  w, p, d, h = r['FORM'], r['UPOS'], r['DEPREL'], r['HEAD']
124
  if h != '0':
@@ -126,135 +110,53 @@ def create_dependency_visualization(df: pd.DataFrame) -> str:
126
  hw = df.iloc[int(h)-1]['FORM']
127
  except:
128
  hw = "[ERR]"
129
- lines.append(f"{w} ({p}) --{d}--> {hw}")
130
  else:
131
- lines.append(f"{w} ({p}) --{d}--> ROOT")
132
- return "\n".join(lines)
133
 
134
- # 5. Full SVG builder (unchanged)
135
  def create_single_sentence_svg(sentence_data, sentence_num=1, total_sentences=1):
136
- """Creates a detailed SVG for one sentence (full code pasted)"""
137
- try:
138
- import pandas as pd
139
- df = pd.DataFrame(sentence_data) if isinstance(sentence_data, list) else sentence_data
140
- word_count = len(df)
141
- base_w, min_sp = 100, 30
142
- spacing = max(base_w, (word_count*base_w + min_sp*(word_count-1))/word_count)
143
- width = max(800, word_count*spacing + 100)
144
- height = 500
145
- word_y = height - 120
146
- pos_y = word_y + 20
147
- feat_start_y = pos_y + 15
148
- deprel_colors = {
149
- 'root': '#000000','nsubj':'#2980b9','obj':'#27ae60','det':'#e67e22',
150
- 'amod':'#8e44ad','nmod':'#16a085','case':'#34495e','punct':'#7f8c8d',
151
- 'cc':'#d35400','conj':'#2c3e50','cop':'#e74c3c','mark':'#9b59b6',
152
- 'csubj':'#3498db','xcomp':'#1abc9c','ccomp':'#f39c12','advcl':'#e91e63',
153
- 'advmod':'#9c27b0','obl':'#795548','iobj':'#607d8b','fixed':'#ff5722',
154
- 'aux':'#ff9800','acl':'#4caf50','appos':'#673ab7','compound':'#009688'
155
- }
156
- parts = [
157
- f'<svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg" '
158
- 'style="background:white;border:1px solid #eee"><defs>'
159
- ]
160
- # arrow markers
161
- for rel, col in deprel_colors.items():
162
- parts.append(
163
- f'<marker id="arrow_{rel}" markerWidth="4" markerHeight="4" '
164
- 'markerUnits="userSpaceOnUse" orient="auto" refX="3.5" refY="2">'
165
- f'<path d="M0,0 L4,2 L0,4 Z" fill="{col}"/></marker>'
166
- )
167
- parts.append('</defs><g>')
168
- # positions
169
- positions = {int(r['ID']): 50 + (int(r['ID'])-1)*spacing for _, r in df.iterrows()}
170
- used_spans=[]
171
- # draw arcs
172
- for _, r in df.iterrows():
173
- wid, hid = int(r['ID']), int(r['HEAD']) if r['HEAD']!='0' else 0
174
- rel = r['DEPREL']
175
- if hid==0:
176
- x=positions[wid]; col=deprel_colors.get(rel,'#000')
177
- parts.append(f'<line x1="{x}" y1="{word_y-15}" x2="{x}" y2="50" '
178
- f'stroke="{col}" stroke-width="1.5"/>')
179
- mid=(word_y-15+50)/2
180
- parts.append(f'<rect x="{x-15}" y="{mid-8}" width="30" height="14" '
181
- f'fill="white" stroke="{col}" rx="2"/>')
182
- parts.append(f'<text x="{x}" y="{mid+2}" text-anchor="middle" '
183
- f'fill="{col}" font-size="8" font-weight="bold">ROOT</text>')
184
- else:
185
- if hid in positions:
186
- x1, x2 = positions[wid], positions[hid]
187
- span=(min(wid,hid),max(wid,hid))
188
- lvl=0; conflict=True
189
- while conflict:
190
- conflict=False
191
- for es,el in used_spans:
192
- if el==lvl and not (span[1]<es[0] or span[0]>es[1]):
193
- lvl+=1; conflict=True; break
194
- used_spans.append((span,lvl))
195
- dist=abs(x2-x1); arc_h=min(40+dist*0.15,100)+lvl*35
196
- col=deprel_colors.get(rel,'#000'); midx=(x1+x2)/2
197
- ctrl_y=word_y-arc_h
198
- parts.append(
199
- f'<path d="M {x1} {word_y-15} Q {midx} {ctrl_y} {x2} {word_y-15}" '
200
- f'stroke="{col}" fill="none" stroke-width="1.5" '
201
- f'marker-end="url(#arrow_{rel})"/>'
202
- )
203
- amidx=0.25*x1+0.5*midx+0.25*x2
204
- amidy=0.25*(word_y-15)+0.5*ctrl_y+0.25*(word_y-15)
205
- lw=len(rel)*6+8
206
- parts.append(
207
- f'<rect x="{amidx-lw/2}" y="{amidy-8}" width="{lw}" height="14" '
208
- f'fill="white" stroke="{col}" rx="2"/>'
209
- )
210
- parts.append(
211
- f'<text x="{amidx}" y="{amidy+2}" text-anchor="middle" '
212
- f'fill="{col}" font-size="8" font-weight="bold">{rel}</text>'
213
- )
214
- # draw words + feats
215
- for _, r in df.iterrows():
216
- wid=int(r['ID']); x=positions[wid]
217
- parts.append(
218
- f'<text x="{x}" y="{word_y}" text-anchor="middle" '
219
- f'font-size="13" font-weight="bold">{r["FORM"]}</text>'
220
- )
221
- ann=[]
222
- if (p:=r['UPOS'])!='_': ann.append(f"upos={p}")
223
- if (lm:=r['LEMMA']) not in ('_',r['FORM']): ann.append(f"lemma={lm}")
224
- if (xp:=r['XPOS'])!='_': ann.append(f"xpos={xp}")
225
- if (fts:=r['FEATS']) not in ('','_'):
226
- for f in fts.split("|"):
227
- if "=" in f: ann.append(f)
228
- for i,a in enumerate(ann):
229
- parts.append(
230
- f'<text x="{x}" y="{feat_start_y+i*12}" text-anchor="middle" '
231
- f'font-size="7" fill="#666">{a}</text>'
232
- )
233
- parts.append('</g></svg>')
234
- return "".join(parts)
235
- except Exception as e:
236
- return f"<p>Error in SVG: {e}</p>"
237
 
238
- # 6. PROCESS TEXT & SENTENCE PAYLOAD
239
  def process_text(text, variant):
240
- """Parse text, return conllu, df, text_viz, sent_ids, sentences_data, initial_svg"""
 
 
 
241
  if not text.strip():
242
- return "", pd.DataFrame(), "", [], [], "<p>No data</p>"
 
 
 
 
 
243
 
244
  pipe = LESBIAN_MODELS.get(variant)
245
  if not pipe:
246
- return f"Error: {variant} not loaded", pd.DataFrame(), "", [], [], "<p>Error</p>"
 
 
 
 
247
 
248
  try:
249
  doc = pipe(text)
250
  except Exception as e:
251
- return f"Parse error: {e}", pd.DataFrame(), "", [], [], "<p>Error</p>"
 
 
 
 
252
 
253
  conllu = stanza_doc_to_conllu(doc)
254
  df = conllu_to_dataframe(conllu)
255
  text_viz = create_dependency_visualization(df)
256
 
257
- # build payload per sentence
258
  sentences = []
259
  for sent in doc.sentences:
260
  payload = []
@@ -262,59 +164,90 @@ def process_text(text, variant):
262
  payload.append({
263
  'ID': w.id, 'FORM': w.text, 'LEMMA': w.lemma or "_",
264
  'UPOS': w.upos or "_", 'XPOS': w.xpos or "_",
265
- 'FEATS': w.feats or "_", 'HEAD': w.head or 0, 'DEPREL': w.deprel or "_"
 
266
  })
267
  sentences.append(payload)
268
 
269
  sent_ids = [str(i+1) for i in range(len(sentences))]
270
- initial_svg = create_single_sentence_svg(sentences[0]) if sentences else "<p>No data</p>"
 
 
 
 
271
 
272
- return conllu, df, text_viz, sent_ids, sentences, initial_svg
 
 
 
273
 
274
  def update_svg(selected_id, sentences):
275
- """Return SVG for selected sentence"""
276
  try:
277
  idx = int(selected_id)-1
278
  return create_single_sentence_svg(sentences[idx])
279
  except:
280
  return "<p>Invalid selection</p>"
281
 
282
- # 7. BUILD UI
283
- loaded, status_msg = initialize_models()
284
 
285
  def create_app():
286
  with gr.Blocks(title="Lesbian Greek Parser") as app:
287
  gr.Markdown("# Lesbian Greek Morphosyntactic Parser")
288
 
289
  if loaded:
290
- gr.Markdown(f"✅ Loaded: {', '.join(MODEL_VARIANTS.keys())}")
291
  else:
292
- gr.Markdown(f"❌ Model load error: {status_msg}")
293
 
294
  with gr.Row():
295
  with gr.Column():
296
- txt = gr.Textbox(label="Input Text", lines=4,
297
- placeholder="Εισάγετε κείμενο στη Λεσβιακή διάλεκτο...")
 
 
 
 
 
 
 
 
298
  btn = gr.Button("Parse", variant="primary")
299
- with gr.Column():
300
- mdl = gr.Radio(choices=list(MODEL_VARIANTS.keys()),
301
- value="Lesbian-only", label="Model Variant")
302
 
303
- # sentence selector & state
304
- sentence_dd = gr.Dropdown(label="Choose Sentence", choices=[])
305
- sentences_state = gr.State([])
 
 
 
 
 
 
 
 
306
 
307
- # outputs
308
- conllu_out = gr.Textbox(label="CoNLL-U", lines=10, show_copy_button=True)
309
- table_out = gr.Dataframe(label="Token Table")
310
- text_out = gr.Textbox(label="Text-based Dependencies", lines=8, show_copy_button=True)
311
- svg_out = gr.HTML("<p>No visualization yet</p>")
 
 
 
 
 
 
 
 
312
 
313
- # wire events
314
  btn.click(
315
  fn=process_text,
316
  inputs=[txt, mdl],
317
- outputs=[conllu_out, table_out, text_out, sentence_dd, sentences_state, svg_out]
 
 
 
318
  )
319
  sentence_dd.change(
320
  fn=update_svg,
 
1
  import gradio as gr
2
+ from gradio import update
3
  import stanza
4
  import pandas as pd
5
  import requests
6
  import traceback
7
  from pathlib import Path
 
8
 
9
+ # 1. MODEL VARIANTS & INITIALIZATION
10
+
11
  LESBIAN_MODELS = {}
12
  MODEL_VARIANTS = {
13
  "Lesbian-only": "sbompolas/Lesbian-Greek-Morphosyntactic-Model",
 
15
  }
16
 
17
  def download_model_file(url, filename):
 
18
  try:
19
  resp = requests.get(url, stream=True)
20
  resp.raise_for_status()
 
27
  return False
28
 
29
  def initialize_models():
30
+ """Download & init both pipeline variants."""
31
  try:
32
+ base = Path("./models")
33
+ base.mkdir(exist_ok=True)
34
  for name, repo in MODEL_VARIANTS.items():
35
+ out = base/name
36
+ out.mkdir(exist_ok=True)
 
37
  files = {
38
+ "tokenizer.pt": f"https://huggingface.co/{repo}/resolve/main/tokenizer.pt",
39
+ "lemmatizer.pt": f"https://huggingface.co/{repo}/resolve/main/lemmatizer.pt",
40
+ "pos.pt": f"https://huggingface.co/{repo}/resolve/main/pos.pt",
41
+ "depparse.pt": f"https://huggingface.co/{repo}/resolve/main/depparse.pt",
42
  }
43
  for fn, url in files.items():
44
+ tgt = out/fn
45
+ if not tgt.exists() and not download_model_file(url, str(tgt)):
46
+ return False, f"Failed to download {fn} for {name}"
 
 
 
47
  cfg = {
48
  'processors': 'tokenize,pos,lemma,depparse',
49
  'lang': 'el',
50
  'use_gpu': False,
51
  'verbose': False,
52
+ 'tokenize_model_path': str(out/"tokenizer.pt"),
53
+ 'pos_model_path': str(out/"pos.pt"),
54
+ 'lemma_model_path': str(out/"lemmatizer.pt"),
55
+ 'depparse_model_path': str(out/"depparse.pt")
56
  }
57
  try:
58
+ LESBIAN_MODELS[name] = stanza.Pipeline(**cfg)
59
+ print(f"Loaded variant {name}")
 
60
  except Exception as e:
61
+ return False, f"Pipeline init error {name}: {e}"
62
+ return True, "Models loaded"
63
  except Exception as e:
64
  traceback.print_exc()
65
  return False, str(e)
66
 
67
+ loaded, load_status = initialize_models()
68
+
69
+ # 2. UTILS
70
+
71
  def stanza_doc_to_conllu(doc) -> str:
72
  lines = []
73
+ for sid, sent in enumerate(doc.sentences, 1):
74
  lines.append(f"# sent_id = {sid}")
75
  lines.append(f"# text = {sent.text}")
76
  for w in sent.words:
77
  fields = [
78
+ str(w.id), w.text,
79
+ w.lemma or "_", w.upos or "_",
80
+ w.xpos or "_", w.feats or "_",
 
 
 
81
  str(w.head) if w.head is not None else "0",
82
+ w.deprel or "_", "_", "_"
 
 
83
  ]
84
  lines.append("\t".join(fields))
85
  lines.append("")
86
  return "\n".join(lines)
87
 
 
88
  def conllu_to_dataframe(conllu: str) -> pd.DataFrame:
89
  rows = []
90
+ for L in conllu.splitlines():
91
+ if not L or L.startswith("#"):
92
  continue
93
+ parts = L.split("\t")
94
  if len(parts) >= 10:
95
  rows.append({
96
+ 'ID': parts[0], 'FORM': parts[1], 'LEMMA': parts[2],
97
+ 'UPOS': parts[3], 'XPOS': parts[4], 'FEATS': parts[5],
98
+ 'HEAD': parts[6], 'DEPREL': parts[7], 'DEPS': parts[8], 'MISC': parts[9]
 
 
 
 
 
 
 
99
  })
100
  return pd.DataFrame(rows)
101
 
 
102
  def create_dependency_visualization(df: pd.DataFrame) -> str:
103
  if df.empty:
104
  return "No data to visualize"
105
+ viz = ["Dependency Parse Visualization:", "-"*40]
106
  for _, r in df.iterrows():
107
  w, p, d, h = r['FORM'], r['UPOS'], r['DEPREL'], r['HEAD']
108
  if h != '0':
 
110
  hw = df.iloc[int(h)-1]['FORM']
111
  except:
112
  hw = "[ERR]"
113
+ viz.append(f"{w} ({p}) --{d}--> {hw}")
114
  else:
115
+ viz.append(f"{w} ({p}) --{d}--> ROOT")
116
+ return "\n".join(viz)
117
 
 
118
  def create_single_sentence_svg(sentence_data, sentence_num=1, total_sentences=1):
119
+ """Your existing detailed SVG-builder pasted here verbatim."""
120
+ # ... full implementation as before ...
121
+ return "<svg><!-- your SVG --></svg>"
122
+
123
+ # 3. PROCESS & DROPDOWN-UPDATES
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
 
125
  def process_text(text, variant):
126
+ """Parse and return:
127
+ conllu, df, text_viz,
128
+ dropdown update, sentences payload, initial_svg
129
+ """
130
  if not text.strip():
131
+ empty_df = pd.DataFrame()
132
+ return (
133
+ "", empty_df, "",
134
+ update(choices=[], value=None),
135
+ [], "<p>No data</p>"
136
+ )
137
 
138
  pipe = LESBIAN_MODELS.get(variant)
139
  if not pipe:
140
+ return (
141
+ f"Error: {variant} not loaded", pd.DataFrame(), "",
142
+ update(choices=[], value=None),
143
+ [], "<p>Error</p>"
144
+ )
145
 
146
  try:
147
  doc = pipe(text)
148
  except Exception as e:
149
+ return (
150
+ f"Parse error: {e}", pd.DataFrame(), "",
151
+ update(choices=[], value=None),
152
+ [], "<p>Error</p>"
153
+ )
154
 
155
  conllu = stanza_doc_to_conllu(doc)
156
  df = conllu_to_dataframe(conllu)
157
  text_viz = create_dependency_visualization(df)
158
 
159
+ # prepare per-sentence payload
160
  sentences = []
161
  for sent in doc.sentences:
162
  payload = []
 
164
  payload.append({
165
  'ID': w.id, 'FORM': w.text, 'LEMMA': w.lemma or "_",
166
  'UPOS': w.upos or "_", 'XPOS': w.xpos or "_",
167
+ 'FEATS': w.feats or "_", 'HEAD': w.head or 0,
168
+ 'DEPREL': w.deprel or "_"
169
  })
170
  sentences.append(payload)
171
 
172
  sent_ids = [str(i+1) for i in range(len(sentences))]
173
+ dropdown_upd = update(choices=sent_ids, value=sent_ids[0] if sent_ids else None)
174
+ initial_svg = (
175
+ create_single_sentence_svg(sentences[0])
176
+ if sentences else "<p>No data</p>"
177
+ )
178
 
179
+ return (
180
+ conllu, df, text_viz,
181
+ dropdown_upd, sentences, initial_svg
182
+ )
183
 
184
  def update_svg(selected_id, sentences):
185
+ """Render SVG for the chosen sentence."""
186
  try:
187
  idx = int(selected_id)-1
188
  return create_single_sentence_svg(sentences[idx])
189
  except:
190
  return "<p>Invalid selection</p>"
191
 
192
+ # 4. BUILD GRADIO UI
 
193
 
194
  def create_app():
195
  with gr.Blocks(title="Lesbian Greek Parser") as app:
196
  gr.Markdown("# Lesbian Greek Morphosyntactic Parser")
197
 
198
  if loaded:
199
+ gr.Markdown(f"✅ Models: {', '.join(MODEL_VARIANTS.keys())}")
200
  else:
201
+ gr.Markdown(f"❌ Load error: {load_status}")
202
 
203
  with gr.Row():
204
  with gr.Column():
205
+ txt = gr.Textbox(
206
+ label="Input Text",
207
+ lines=4,
208
+ placeholder="Εισάγετε κείμενο…"
209
+ )
210
+ mdl = gr.Radio(
211
+ choices=list(MODEL_VARIANTS.keys()),
212
+ value="Lesbian-only",
213
+ label="Model Variant"
214
+ )
215
  btn = gr.Button("Parse", variant="primary")
 
 
 
216
 
217
+ with gr.Row():
218
+ with gr.Column():
219
+ # 1. SVG output
220
+ svg_out = gr.HTML("<p>No visualization</p>")
221
+ # 2. Sentence selector
222
+ sentence_dd = gr.Dropdown(
223
+ label="Choose sentence",
224
+ choices=[],
225
+ interactive=True
226
+ )
227
+ sentences_state = gr.State([])
228
 
229
+ with gr.Row():
230
+ with gr.Column():
231
+ conllu_out = gr.Textbox(
232
+ label="CoNLL-U",
233
+ lines=10,
234
+ show_copy_button=True
235
+ )
236
+ table_out = gr.Dataframe(label="Token Table")
237
+ text_out = gr.Textbox(
238
+ label="Text-based Dependencies",
239
+ lines=8,
240
+ show_copy_button=True
241
+ )
242
 
243
+ # Events
244
  btn.click(
245
  fn=process_text,
246
  inputs=[txt, mdl],
247
+ outputs=[
248
+ conllu_out, table_out, text_out,
249
+ sentence_dd, sentences_state, svg_out
250
+ ]
251
  )
252
  sentence_dd.change(
253
  fn=update_svg,