Maximax67 commited on
Commit
358b159
·
1 Parent(s): f36d61b

Add MIT License, update README, refactor app.py, and modify requirements

Browse files
Files changed (4) hide show
  1. LICENSE +21 -0
  2. README.md +4 -4
  3. app.py +178 -68
  4. requirements.txt +2 -2
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bielikov Maksym
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -4,13 +4,13 @@ emoji: 💻
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 4.26.0
8
  app_file: app.py
9
  fullWidth: true
10
- short_description: "cefrpy demo: Highlight English words according to CEFR scale"
11
  models: ["spacy/en_core_web_sm"]
12
- pinned: false
13
  license: mit
14
  ---
15
 
16
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 6.14.0
8
  app_file: app.py
9
  fullWidth: true
10
+ short_description: "Cefrpy Demo: highlight English words according to CEFR scale"
11
  models: ["spacy/en_core_web_sm"]
12
+ pinned: true
13
  license: mit
14
  ---
15
 
16
+ Check out the configuration reference at <https://huggingface.co/docs/hub/spaces-config-reference>
app.py CHANGED
@@ -6,25 +6,40 @@ from cefrpy import CEFRSpaCyAnalyzer, CEFRLevel
6
  MODEL = "en_core_web_sm"
7
 
8
  ALL_ENTS = [
9
- 'CARDINAL', 'DATE', 'EVENT', 'FAC', 'GPE', 'LANGUAGE',
10
- 'LAW', 'LOC', 'MONEY', 'NORP', 'ORDINAL', 'ORG', 'PERCENT',
11
- 'PERSON', 'PRODUCT', 'QUANTITY', 'TIME', 'WORK_OF_ART'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  ]
13
 
14
  DEFAULT_ENTITY_ITEMS_TO_SKIP = [
15
- 'QUANTITY', 'MONEY', 'LANGUAGE', 'LAW',
16
- 'WORK_OF_ART', 'PRODUCT', 'GPE',
17
- 'ORG', 'FAC', 'PERSON'
 
 
 
 
 
 
 
18
  ]
19
 
20
- TOKEN_ATTRIBUTES = [
21
- "Token",
22
- "POS",
23
- "Skipped",
24
- "Level",
25
- "Start",
26
- "End"
27
- ]
28
 
29
  WORDLIST_HEADER = ["Word", "Pos", "CEFR", "Level"]
30
 
@@ -40,6 +55,7 @@ In 2006, a Coca-Cola employee offered to sell Coca-Cola secrets to Pepsi. Pepsi
40
 
41
  Like humans, cows form strong social bonds and often have "best friends" within their herds. They display complex social behaviors, including grooming, playing, and even grieving when separated from their friends."""
42
 
 
43
  DISPLACY_RENDER_OPTIONS = {
44
  "colors": {
45
  "A1": "#b0c4de",
@@ -49,7 +65,7 @@ DISPLACY_RENDER_OPTIONS = {
49
  "C1": "#ffd700",
50
  "C2": "#ff9380",
51
  "SKIP": "#ffafed",
52
- "UNKNOWN": "#BCAAA4"
53
  }
54
  }
55
 
@@ -60,77 +76,134 @@ ABBREVIATION_MAPPING = {
60
  "'ve": "have",
61
  "'d": "had",
62
  "n't": "not",
63
- "'ll": "will"
64
  }
65
 
66
- LINKS_HTML = """
67
- <p>
68
- &ensp;Github: <a href="https://github.com/Maximax67/cefrpy">link</a><br>
69
- &ensp;Docs: <a href="https://maximax67.github.io/cefrpy">link</a><br>
70
- </p>
71
- """
72
-
73
  CSS = """
74
  h1 {
75
  padding-top: 5px;
76
  text-align: center;
77
  display:block;
78
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  """
80
 
81
  nlp = spacy.load(MODEL)
82
 
83
- def get_dict_ents(text: str, tokens: list[tuple[str, str, bool, float, int, int]]) -> dict:
 
 
 
84
  ents = []
85
 
86
  for token in tokens:
87
  if token[3]:
88
- ents.append({
89
- "start": token[4],
90
- "end": token[5],
91
- "label": str(CEFRLevel(round(token[3])))
92
- })
 
 
93
  elif token[0].isalpha():
94
- ents.append({
95
- "start": token[4],
96
- "end": token[5],
97
- "label": "SKIP" if token[2] else "UNKNOWN"
98
- })
99
-
100
- dict_ents = {
101
- "text": text,
102
- "ents": ents
103
- }
104
 
105
  return dict_ents
106
 
107
 
108
- def get_cefr_tokens(text: str, ents_to_skip: list[str]) -> list[tuple[str, str, bool, float, int, int]]:
 
 
109
  doc = nlp(text)
110
- text_analyzer = CEFRSpaCyAnalyzer(entity_types_to_skip=ents_to_skip, abbreviation_mapping=ABBREVIATION_MAPPING)
111
- tokens = text_analyzer.analize_doc(doc)
 
 
112
 
113
  return tokens
114
 
115
 
116
- def get_html_visualization(text: str, tokens: list[tuple[str, str, bool, float, int, int]]) -> str:
 
 
117
  dict_ents = get_dict_ents(text, tokens)
118
- html = displacy.render(dict_ents, manual=True, style="ent", options=DISPLACY_RENDER_OPTIONS)
 
 
119
 
120
  return html
121
 
122
 
123
- def get_wordlist_set(tokens: list[tuple[str, str, bool, float, int, int]],
124
- min_level: float) -> set[tuple[str, str, bool, float, int, int]]:
 
125
  filtered_tokens = set()
126
  for word, pos, _, level, _, _ in tokens:
127
  if level and level >= min_level:
128
- filtered_tokens.add((word.lower(), pos, str(CEFRLevel(round(level))), level))
 
 
129
 
130
  return filtered_tokens
131
 
132
 
133
- def get_wordlist(tokens: list[tuple[str, str, bool, float, int, int]], min_level: float):
 
 
134
  wordlist_set = get_wordlist_set(tokens, min_level)
135
  wordlist = list(wordlist_set)
136
  wordlist.sort()
@@ -142,7 +215,11 @@ def get_wordlist_from_dataframe(dataframe, min_level: float):
142
  return get_wordlist(dataframe.values, min_level)
143
 
144
 
145
- def process_text(text: str, ents_to_skip: list[str] | None = DEFAULT_ENTITY_ITEMS_TO_SKIP, min_level: float = DEFAULT_WORDLIST_SLIDER_LEVEL) -> tuple[list[list], str]:
 
 
 
 
146
  tokens = get_cefr_tokens(text, ents_to_skip)
147
  html = get_html_visualization(text, tokens)
148
  wordlist = get_wordlist(tokens, min_level)
@@ -152,15 +229,49 @@ def process_text(text: str, ents_to_skip: list[str] | None = DEFAULT_ENTITY_ITEM
152
 
153
  initial_tokens, initial_wordlist, initial_html = process_text(DEFAULT_TEXT)
154
 
155
- demo = gr.Blocks(css=CSS)
156
 
157
  with demo:
158
  with gr.Row():
159
  with gr.Column():
160
  with gr.Column():
161
- with gr.Row():
162
- gr.Markdown("# Gradio Demo: cefrpy")
163
- gr.HTML(LINKS_HTML)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  with gr.Row():
166
  text_input = gr.TextArea(
@@ -168,27 +279,23 @@ with demo:
168
  interactive=True,
169
  max_lines=500,
170
  label="Input Text",
171
- show_copy_button=True
172
  )
173
 
174
  with gr.Row():
175
  ent_input = gr.CheckboxGroup(
176
  ALL_ENTS,
177
  value=DEFAULT_ENTITY_ITEMS_TO_SKIP,
178
- label="Entity types to skip CEFR"
179
  )
180
 
181
  with gr.Row():
182
  clear_button = gr.ClearButton(text_input)
183
-
184
- render_button = gr.Button(
185
- "Render",
186
- variant="primary"
187
- )
188
 
189
  with gr.Column():
190
  with gr.Row():
191
- gr.Markdown("# Words CEFR level visualization")
192
 
193
  with gr.Row():
194
  rendered_html = gr.HTML(initial_html)
@@ -196,34 +303,37 @@ with demo:
196
  with gr.Row():
197
  with gr.Column():
198
  with gr.Row():
199
- tokens_output = gr.Dataframe(headers=TOKEN_ATTRIBUTES, value=initial_tokens, interactive=False)
 
 
200
 
201
  with gr.Column():
202
  with gr.Row():
203
  min_level_slider = gr.Slider(
204
  minimum=1.0,
205
- maximum=6.0,
206
  value=DEFAULT_WORDLIST_SLIDER_LEVEL,
207
  step=0.02,
208
  interactive=True,
209
- label="Min level to generate word list"
210
  )
211
 
212
  with gr.Row():
213
- wordlist = gr.Dataframe(headers=WORDLIST_HEADER, value=initial_wordlist, interactive=False)
 
 
214
 
215
  render_button.click(
216
  process_text,
217
  inputs=[text_input, ent_input],
218
  outputs=[tokens_output, wordlist, rendered_html],
219
- api_name="process_text"
220
  )
221
 
222
  min_level_slider.release(
223
  get_wordlist_from_dataframe,
224
  inputs=[tokens_output, min_level_slider],
225
  outputs=[wordlist],
226
- api_name=False
227
  )
228
 
229
- demo.launch(show_api=True)
 
6
  MODEL = "en_core_web_sm"
7
 
8
  ALL_ENTS = [
9
+ "CARDINAL",
10
+ "DATE",
11
+ "EVENT",
12
+ "FAC",
13
+ "GPE",
14
+ "LANGUAGE",
15
+ "LAW",
16
+ "LOC",
17
+ "MONEY",
18
+ "NORP",
19
+ "ORDINAL",
20
+ "ORG",
21
+ "PERCENT",
22
+ "PERSON",
23
+ "PRODUCT",
24
+ "QUANTITY",
25
+ "TIME",
26
+ "WORK_OF_ART",
27
  ]
28
 
29
  DEFAULT_ENTITY_ITEMS_TO_SKIP = [
30
+ "QUANTITY",
31
+ "MONEY",
32
+ "LANGUAGE",
33
+ "LAW",
34
+ "WORK_OF_ART",
35
+ "PRODUCT",
36
+ "GPE",
37
+ "ORG",
38
+ "FAC",
39
+ "PERSON",
40
  ]
41
 
42
+ TOKEN_ATTRIBUTES = ["Token", "POS", "Skipped", "Level", "Start", "End"]
 
 
 
 
 
 
 
43
 
44
  WORDLIST_HEADER = ["Word", "Pos", "CEFR", "Level"]
45
 
 
55
 
56
  Like humans, cows form strong social bonds and often have "best friends" within their herds. They display complex social behaviors, including grooming, playing, and even grieving when separated from their friends."""
57
 
58
+ # Light-mode colors (used by displacy; must match the hex values in the CSS below)
59
  DISPLACY_RENDER_OPTIONS = {
60
  "colors": {
61
  "A1": "#b0c4de",
 
65
  "C1": "#ffd700",
66
  "C2": "#ff9380",
67
  "SKIP": "#ffafed",
68
+ "UNKNOWN": "#BCAAA4",
69
  }
70
  }
71
 
 
76
  "'ve": "have",
77
  "'d": "had",
78
  "n't": "not",
79
+ "'ll": "will",
80
  }
81
 
82
+ # Minimal CSS: only the dual-theme entity colors. All other styling is unchanged.
 
 
 
 
 
 
83
  CSS = """
84
  h1 {
85
  padding-top: 5px;
86
  text-align: center;
87
  display:block;
88
  }
89
+
90
+ .cefr-link-btn:hover {
91
+ background: #f6f8fa;
92
+ border-color: #999 !important;
93
+ }
94
+
95
+ .dark .cefr-link-btn:hover {
96
+ background: #30363d;
97
+ border-color: #888 !important;
98
+ }
99
+
100
+ /* Light-mode CEFR entity colors (matches DISPLACY_RENDER_OPTIONS above) */
101
+ :root {
102
+ --cefr-a1: #b0c4de;
103
+ --cefr-a2: #87ceeb;
104
+ --cefr-b1: #90ee90;
105
+ --cefr-b2: #adff2f;
106
+ --cefr-c1: #ffd700;
107
+ --cefr-c2: #ff9380;
108
+ --cefr-skip: #ffafed;
109
+ --cefr-unknown: #BCAAA4;
110
+ }
111
+
112
+ /* Dark-mode overrides — Gradio adds class="dark" to <html> */
113
+ .dark {
114
+ --cefr-a1: #1e4e8c;
115
+ --cefr-a2: #0c6080;
116
+ --cefr-b1: #145c2e;
117
+ --cefr-b2: #4a7200;
118
+ --cefr-c1: #8a6400;
119
+ --cefr-c2: #952e1e;
120
+ --cefr-skip: #7a2070;
121
+ --cefr-unknown: #4a3c38;
122
+ }
123
+
124
+ /* Override displacy's inline background colors using attribute selectors.
125
+ Both cases (lower and upper) are covered since different browsers/displacy
126
+ versions may render hex in either case. */
127
+ mark[style*="#b0c4de"], mark[style*="#B0C4DE"] { background: var(--cefr-a1) !important; }
128
+ mark[style*="#87ceeb"], mark[style*="#87CEEB"] { background: var(--cefr-a2) !important; }
129
+ mark[style*="#90ee90"], mark[style*="#90EE90"] { background: var(--cefr-b1) !important; }
130
+ mark[style*="#adff2f"], mark[style*="#ADFF2F"] { background: var(--cefr-b2) !important; }
131
+ mark[style*="#ffd700"], mark[style*="#FFD700"] { background: var(--cefr-c1) !important; }
132
+ mark[style*="#ff9380"], mark[style*="#FF9380"] { background: var(--cefr-c2) !important; }
133
+ mark[style*="#ffafed"], mark[style*="#FFAFED"] { background: var(--cefr-skip) !important; }
134
+ mark[style*="#BCAAA4"], mark[style*="#bcaaa4"] { background: var(--cefr-unknown) !important; }
135
  """
136
 
137
  nlp = spacy.load(MODEL)
138
 
139
+
140
+ def get_dict_ents(
141
+ text: str, tokens: list[tuple[str, str, bool, float, int, int]]
142
+ ) -> dict:
143
  ents = []
144
 
145
  for token in tokens:
146
  if token[3]:
147
+ ents.append(
148
+ {
149
+ "start": token[4],
150
+ "end": token[5],
151
+ "label": str(CEFRLevel(round(token[3]))),
152
+ }
153
+ )
154
  elif token[0].isalpha():
155
+ ents.append(
156
+ {
157
+ "start": token[4],
158
+ "end": token[5],
159
+ "label": "SKIP" if token[2] else "UNKNOWN",
160
+ }
161
+ )
162
+
163
+ dict_ents = {"text": text, "ents": ents}
 
164
 
165
  return dict_ents
166
 
167
 
168
+ def get_cefr_tokens(
169
+ text: str, ents_to_skip: list[str]
170
+ ) -> list[tuple[str, str, bool, float, int, int]]:
171
  doc = nlp(text)
172
+ text_analyzer = CEFRSpaCyAnalyzer(
173
+ entity_types_to_skip=ents_to_skip, abbreviation_mapping=ABBREVIATION_MAPPING
174
+ )
175
+ tokens = text_analyzer.analyze_doc(doc)
176
 
177
  return tokens
178
 
179
 
180
+ def get_html_visualization(
181
+ text: str, tokens: list[tuple[str, str, bool, float, int, int]]
182
+ ) -> str:
183
  dict_ents = get_dict_ents(text, tokens)
184
+ html = displacy.render(
185
+ dict_ents, manual=True, style="ent", options=DISPLACY_RENDER_OPTIONS
186
+ )
187
 
188
  return html
189
 
190
 
191
+ def get_wordlist_set(
192
+ tokens: list[tuple[str, str, bool, float, int, int]], min_level: float
193
+ ) -> set[tuple[str, str, bool, float, int, int]]:
194
  filtered_tokens = set()
195
  for word, pos, _, level, _, _ in tokens:
196
  if level and level >= min_level:
197
+ filtered_tokens.add(
198
+ (word.lower(), pos, str(CEFRLevel(round(level))), level)
199
+ )
200
 
201
  return filtered_tokens
202
 
203
 
204
+ def get_wordlist(
205
+ tokens: list[tuple[str, str, bool, float, int, int]], min_level: float
206
+ ):
207
  wordlist_set = get_wordlist_set(tokens, min_level)
208
  wordlist = list(wordlist_set)
209
  wordlist.sort()
 
215
  return get_wordlist(dataframe.values, min_level)
216
 
217
 
218
+ def process_text(
219
+ text: str,
220
+ ents_to_skip: list[str] = DEFAULT_ENTITY_ITEMS_TO_SKIP,
221
+ min_level: float = DEFAULT_WORDLIST_SLIDER_LEVEL,
222
+ ) -> tuple[list[tuple], list[tuple], str]:
223
  tokens = get_cefr_tokens(text, ents_to_skip)
224
  html = get_html_visualization(text, tokens)
225
  wordlist = get_wordlist(tokens, min_level)
 
229
 
230
  initial_tokens, initial_wordlist, initial_html = process_text(DEFAULT_TEXT)
231
 
232
+ demo = gr.Blocks()
233
 
234
  with demo:
235
  with gr.Row():
236
  with gr.Column():
237
  with gr.Column():
238
+ gr.HTML("""
239
+ <div style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px;">
240
+ <h1 style="margin:0; font-size:1.6rem; font-weight:700;">Gradio Demo: cefrpy</h1>
241
+ <div style="display:flex; gap:10px; align-items:center; flex-wrap:wrap;">
242
+ <a class="cefr-link-btn" href="https://github.com/Maximax67/cefrpy" target="_blank"
243
+ style="display:inline-flex; align-items:center; gap:6px; padding:5px 12px;
244
+ border-radius:6px; border:1px solid #d0d7de; text-decoration:none;
245
+ color:inherit; font-size:0.9rem; font-weight:500; transition:background 0.15s, border-color 0.15s;">
246
+ <svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
247
+ <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38
248
+ 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13
249
+ -.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66
250
+ .07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15
251
+ -.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0
252
+ 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82
253
+ 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01
254
+ 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
255
+ </svg>
256
+ GitHub
257
+ </a>
258
+ <a class="cefr-link-btn" href="https://maximax67.github.io/cefrpy" target="_blank"
259
+ style="display:inline-flex; align-items:center; gap:6px; padding:5px 12px;
260
+ border-radius:6px; border:1px solid #d0d7de; text-decoration:none;
261
+ color:inherit; font-size:0.9rem; font-weight:500; transition:background 0.15s, border-color 0.15s;">
262
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none"
263
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
264
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
265
+ <polyline points="14 2 14 8 20 8"/>
266
+ <line x1="16" y1="13" x2="8" y2="13"/>
267
+ <line x1="16" y1="17" x2="8" y2="17"/>
268
+ <polyline points="10 9 9 9 8 9"/>
269
+ </svg>
270
+ Docs
271
+ </a>
272
+ </div>
273
+ </div>
274
+ """)
275
 
276
  with gr.Row():
277
  text_input = gr.TextArea(
 
279
  interactive=True,
280
  max_lines=500,
281
  label="Input Text",
282
+ buttons=["copy"],
283
  )
284
 
285
  with gr.Row():
286
  ent_input = gr.CheckboxGroup(
287
  ALL_ENTS,
288
  value=DEFAULT_ENTITY_ITEMS_TO_SKIP,
289
+ label="Entity types to skip CEFR",
290
  )
291
 
292
  with gr.Row():
293
  clear_button = gr.ClearButton(text_input)
294
+ render_button = gr.Button("Render", variant="primary")
 
 
 
 
295
 
296
  with gr.Column():
297
  with gr.Row():
298
+ gr.Markdown("# Words CEFR level visualization", padding=True)
299
 
300
  with gr.Row():
301
  rendered_html = gr.HTML(initial_html)
 
303
  with gr.Row():
304
  with gr.Column():
305
  with gr.Row():
306
+ tokens_output = gr.Dataframe(
307
+ headers=TOKEN_ATTRIBUTES, value=initial_tokens, interactive=False
308
+ )
309
 
310
  with gr.Column():
311
  with gr.Row():
312
  min_level_slider = gr.Slider(
313
  minimum=1.0,
314
+ maximum=6.0,
315
  value=DEFAULT_WORDLIST_SLIDER_LEVEL,
316
  step=0.02,
317
  interactive=True,
318
+ label="Min level to generate word list",
319
  )
320
 
321
  with gr.Row():
322
+ wordlist = gr.Dataframe(
323
+ headers=WORDLIST_HEADER, value=initial_wordlist, interactive=False
324
+ )
325
 
326
  render_button.click(
327
  process_text,
328
  inputs=[text_input, ent_input],
329
  outputs=[tokens_output, wordlist, rendered_html],
330
+ api_name="process_text",
331
  )
332
 
333
  min_level_slider.release(
334
  get_wordlist_from_dataframe,
335
  inputs=[tokens_output, min_level_slider],
336
  outputs=[wordlist],
 
337
  )
338
 
339
+ demo.launch(css=CSS)
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- cefrpy
2
- spacy~=3.7.4
3
 
4
  https://huggingface.co/spacy/en_core_web_sm/resolve/main/en_core_web_sm-any-py3-none-any.whl
 
1
+ cefrpy>=1.0.2
2
+ spacy>=3.7,<4.0
3
 
4
  https://huggingface.co/spacy/en_core_web_sm/resolve/main/en_core_web_sm-any-py3-none-any.whl