gursul commited on
Commit
6fb1157
·
verified ·
1 Parent(s): 106ab46

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +389 -0
app.py ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import json
3
+ import os
4
+ import gradio as gr
5
+ from huggingface_hub import upload_file, hf_hub_download, login
6
+
7
+ DATASET_REPO = "gursul/german_exercise_dataset"
8
+
9
+ ENGLISH_WORDS_FILE = "/content/sample_data/en_de.json" # maps to en_de.json
10
+ GERMAN_WORDS_FILE = "/content/sample_data/de_en.json" # maps to de_en.json
11
+
12
+ def ensure_json_file(path):
13
+ if not os.path.exists(path):
14
+ with open(path, "w", encoding="utf-8") as f:
15
+ json.dump({}, f, ensure_ascii=False, indent=2) # dict, not list
16
+
17
+ def load_json(path):
18
+ with open(path, "r", encoding="utf-8") as f:
19
+ return json.load(f)
20
+
21
+ def save_json(path, data: dict):
22
+ # keep keys sorted for readability
23
+ with open(path, "w", encoding="utf-8") as f:
24
+ json.dump(dict(sorted(data.items(), key=lambda kv: kv[0].lower())), f, ensure_ascii=False, indent=2)
25
+
26
+ def load_english():
27
+ return load_json(ENGLISH_WORDS_FILE)
28
+
29
+ def load_german():
30
+ return load_json(GERMAN_WORDS_FILE)
31
+
32
+ def save_english(d: dict):
33
+ save_json(ENGLISH_WORDS_FILE, d)
34
+ upload_file(
35
+ path_or_fileobj=ENGLISH_WORDS_FILE,
36
+ path_in_repo="en_de.json",
37
+ repo_id=DATASET_REPO,
38
+ repo_type="dataset"
39
+ )
40
+
41
+ def save_german(d: dict):
42
+ save_json(GERMAN_WORDS_FILE, d)
43
+ upload_file(
44
+ path_or_fileobj=GERMAN_WORDS_FILE,
45
+ path_in_repo="de_en.json",
46
+ repo_id=DATASET_REPO,
47
+ repo_type="dataset"
48
+ )
49
+
50
+ def new_word():
51
+ eng_data = load_english()
52
+ if not eng_data:
53
+ # keep inputs disabled; show "-" in artikel
54
+ return "No words in list!", "", "-", "", gr.update(interactive=False), gr.update(interactive=False)
55
+
56
+ eng_word = random.choice(list(eng_data.keys()))
57
+ meta = eng_data[eng_word]
58
+ wtype = meta.get("type", "")
59
+
60
+ # artikel box: enable for nouns (value ""), disable otherwise (value "-")
61
+ artikel_value = "" if wtype == "noun" else "-"
62
+ artikel_update = gr.update(interactive=(wtype == "noun"))
63
+ # translation box becomes active for the round
64
+ german_update = gr.update(interactive=True)
65
+
66
+ return (
67
+ eng_word, # English word shown
68
+ wtype, # hidden type
69
+ artikel_value, # artikel textbox value
70
+ "", # clear translation input
71
+ artikel_update, # artikel interactivity
72
+ german_update # translation interactivity
73
+ )
74
+
75
+ def check_answer(english, wtype, article_in, german_in):
76
+ eng_data = load_english()
77
+ ger_data = load_german()
78
+
79
+ english = (english or "").strip()
80
+ german_in = (german_in or "").strip()
81
+ article_in = (article_in or "").strip()
82
+
83
+ meta = eng_data.get(english)
84
+ if not meta:
85
+ return "⚠ Word not found."
86
+
87
+ translations = [t.strip() for t in meta.get("translations", [])]
88
+ # Case-insensitive compare for membership
89
+ translations_ci = {t.lower(): t for t in translations} # map lower->original for nice feedback
90
+ target_key = german_in.lower()
91
+
92
+ if wtype == "noun":
93
+ # Must match one of the German translations AND the article must match the German word's article
94
+ if target_key in translations_ci:
95
+ german_canonical = translations_ci[target_key]
96
+ g_entry = ger_data.get(german_canonical, {})
97
+ expected_article = (g_entry.get("artikel") or "").strip()
98
+
99
+ if expected_article and article_in.lower() == expected_article.lower():
100
+ return "✅ Correct!"
101
+ else:
102
+ if expected_article:
103
+ return f"❌ Almost. Correct article for **{german_canonical}** is **{expected_article}**."
104
+ else:
105
+ return f"❌ I couldn't verify the article for **{german_canonical}**. Check your article or add it to the dictionary."
106
+ else:
107
+ # show all acceptable answers with their articles
108
+ options = []
109
+ for g in translations:
110
+ art = (ger_data.get(g, {}).get("artikel") or "").strip()
111
+ options.append(f"{(art + ' ') if art else ''}{g}")
112
+ return "❌ Wrong. Acceptable answers: " + ", ".join(options)
113
+ else:
114
+ # Non-noun: only need the German word to be one of the translations
115
+ if target_key in translations_ci:
116
+ return "✅ Correct!"
117
+ else:
118
+ return "❌ Wrong. Acceptable answers: " + ", ".join(translations)
119
+
120
+ def new_word_de_en():
121
+ ger_data = load_german()
122
+ if not ger_data:
123
+ # keep inputs disabled; show "-" in artikel
124
+ return "No words in list!", "", "-", "", gr.update(interactive=False), gr.update(interactive=False)
125
+
126
+ ger_word = random.choice(list(ger_data.keys()))
127
+ meta = ger_data[ger_word]
128
+ wtype = meta.get("type", "")
129
+
130
+ # artikel box: enable for nouns (value ""), disable otherwise (value "-")
131
+ artikel_value = "" if wtype == "noun" else "-"
132
+ artikel_update = gr.update(interactive=(wtype == "noun"))
133
+ # translation box becomes active for the round
134
+ english_update = gr.update(interactive=True)
135
+
136
+ return (
137
+ ger_word, # German word shown
138
+ wtype, # hidden type
139
+ artikel_value, # artikel textbox value
140
+ "", # clear translation input
141
+ artikel_update, # artikel interactivity
142
+ english_update # translation interactivity
143
+ )
144
+
145
+ def check_answer_de_en(german, wtype, artikel_in, english_in):
146
+ ger_data = load_german()
147
+
148
+ german = (german or "").strip()
149
+ english_in = (english_in or "").strip()
150
+ artikel_in = (artikel_in or "").strip()
151
+
152
+ meta = ger_data.get(german)
153
+ if not meta:
154
+ return "⚠ Word not found."
155
+
156
+ translations = [t.strip() for t in meta.get("translations", [])]
157
+ translations_ci = {t.lower(): t for t in translations} # map lower->original
158
+ target_key = english_in.lower()
159
+
160
+ if wtype == "noun":
161
+ expected_article = (meta.get("artikel") or "").strip()
162
+ if target_key in translations_ci and expected_article and artikel_in.lower() == expected_article.lower():
163
+ return "✅ Correct!"
164
+ elif target_key in translations_ci and not expected_article:
165
+ # We don’t have the article stored; accept translation only.
166
+ return "✅ Translation correct (no article recorded in dictionary)."
167
+ else:
168
+ # Build feedback
169
+ if expected_article:
170
+ return f"❌ Wrong. Expected **{expected_article}** + one of: {', '.join(translations)}"
171
+ else:
172
+ return f"❌ Wrong. Acceptable answers: {', '.join(translations)}"
173
+ else:
174
+ if target_key in translations_ci:
175
+ return "✅ Correct!"
176
+ else:
177
+ return "❌ Wrong. Acceptable answers: " + ", ".join(translations)
178
+
179
+ def add_word(english, wtype, article, german):
180
+ # Normalize inputs (trim spaces)
181
+ english = (english or "").strip()
182
+ german = (german or "").strip()
183
+ article = (article or "").strip()
184
+ wtype = (wtype or "").strip()
185
+
186
+ # Validate
187
+ if not english or not german or not wtype:
188
+ return "❌ Please fill English, German and Word Type.", get_words_table_en_de(), get_words_table_de_en()
189
+ if wtype == "noun" and not article:
190
+ return "❌ Please provide the article for nouns.", get_words_table_en_de(), get_words_table_de_en()
191
+
192
+ eng_data = load_english() # { english_word: {artikel, type, translations:[german,...]} }
193
+ ger_data = load_german() # { german_word: {artikel, type, translations:[english,...]} }
194
+
195
+ # --- Update ENGLISH side ---
196
+ if english not in eng_data:
197
+ eng_data[english] = {
198
+ "artikel": (article if wtype == "noun" else ""),
199
+ "type": wtype,
200
+ "translations": [german]
201
+ }
202
+ else:
203
+ # keep existing artikel/type (don’t override silently)
204
+ if german not in eng_data[english].get("translations", []):
205
+ eng_data[english]["translations"].append(german)
206
+
207
+ # --- Update GERMAN side ---
208
+ if german not in ger_data:
209
+ ger_data[german] = {
210
+ "artikel": (article if wtype == "noun" else ""),
211
+ "type": wtype,
212
+ "translations": [english]
213
+ }
214
+ else:
215
+ if english not in ger_data[german].get("translations", []):
216
+ ger_data[german]["translations"].append(english)
217
+
218
+ save_english(eng_data)
219
+ save_german(ger_data)
220
+
221
+ return "✅ Word(s) added!", get_words_table_en_de(), get_words_table_de_en()
222
+
223
+ def toggle_article_input(word_type):
224
+ return gr.update(interactive=(word_type == "noun"))
225
+
226
+ def get_words_table_en_de(search_query=""):
227
+ eng_data = load_english() # Load fresh
228
+ filtered = {}
229
+ for eng_word, meta in sorted(eng_data.items(), key=lambda kv: kv[0].lower()):
230
+ translations = meta.get("translations", [])
231
+ # check if search matches the word or any translation
232
+ if search_query.lower() in eng_word.lower() or any(search_query.lower() in t.lower() for t in translations):
233
+ artikel = meta.get("artikel", "") or "" # ✅ Bug 1: no null
234
+ translations_text = ", ".join(translations) # ✅ Bug 2: join translations
235
+ filtered[eng_word] = [eng_word, artikel, translations_text, meta.get("type", "")]
236
+ return list(filtered.values())
237
+
238
+ def get_words_table_de_en(search_query=""):
239
+ ger_data = load_german() # Load fresh
240
+ filtered = {}
241
+ for ger_word, meta in sorted(ger_data.items(), key=lambda kv: kv[0].lower()):
242
+ translations = meta.get("translations", [])
243
+ # check if search matches the word or any translation
244
+ if search_query.lower() in ger_word.lower() or any(search_query.lower() in t.lower() for t in translations):
245
+ artikel = meta.get("artikel", "") or "" # ✅ Bug 1: no null
246
+ translations_text = ", ".join(translations) # ✅ Bug 2: join translations
247
+ filtered[ger_word] = [ger_word, artikel, translations_text, meta.get("type", "")]
248
+ return list(filtered.values())
249
+
250
+ def reset_filter():
251
+ return "", get_words_table_en_de(), get_words_table_de_en()
252
+
253
+ def sync_from_hf():
254
+ hf_hub_download(repo_id=DATASET_REPO, filename="en_de.json", repo_type="dataset")
255
+ hf_hub_download(repo_id=DATASET_REPO, filename="de_en.json", repo_type="dataset")
256
+ #os.rename("en_de.json", ENGLISH_WORDS_FILE)
257
+ #os.rename("de_en.json", GERMAN_WORDS_FILE)
258
+
259
+ sync_from_hf()
260
+
261
+ ensure_json_file(ENGLISH_WORDS_FILE)
262
+ ensure_json_file(GERMAN_WORDS_FILE)
263
+
264
+ eng_data = load_english()
265
+ ger_data = load_german()
266
+
267
+ # Gradio UI
268
+ with gr.Blocks(fill_width=False) as demo:
269
+ with gr.Tab("Word Translation"):
270
+ with gr.Row():
271
+ # ========= EN → DE =========
272
+ with gr.Column():
273
+ gr.Markdown("### English → German")
274
+ word_display = gr.Textbox(label="English Word", interactive=False)
275
+ word_type = gr.Textbox(visible=False)
276
+
277
+ # always visible; start disabled; show "-" until a noun appears
278
+ article_box = gr.Textbox(label="Artikel (der/die/das)", value="-", visible=True, interactive=False)
279
+ # start disabled until New Word
280
+ german_box = gr.Textbox(label="German Translation", visible=True, interactive=False)
281
+
282
+ # initial message
283
+ result = gr.Markdown("Let's play!")
284
+
285
+ new_btn = gr.Button("New Word (EN→DE)")
286
+ submit_btn = gr.Button("Check Answer (EN→DE)")
287
+
288
+ # ========= DE → EN =========
289
+ with gr.Column():
290
+ gr.Markdown("### German → English")
291
+ de_word_display = gr.Textbox(label="German Word", interactive=False)
292
+ de_word_type = gr.Textbox(visible=False)
293
+
294
+ # always visible; start disabled; show "-" until a noun appears
295
+ de_artikel_box = gr.Textbox(label="Artikel (der/die/das)", value="-", visible=True, interactive=False)
296
+ # start disabled until New Word
297
+ de_english_box = gr.Textbox(label="English Translation", visible=True, interactive=False)
298
+
299
+ # initial message
300
+ de_result = gr.Markdown("Let's play!")
301
+
302
+ de_new_btn = gr.Button("New Word (DE→EN)")
303
+ de_submit_btn = gr.Button("Check Answer (DE→EN)")
304
+
305
+ with gr.Tab("Add Words"):
306
+ english_in = gr.Textbox(label="English Word")
307
+ type_in = gr.Dropdown(label="Word Type", choices=["noun", "verb", "adj"], value="noun")
308
+ article_in = gr.Textbox(label="Artikel (der/die/das) — only for nouns")
309
+ german_in = gr.Textbox(label="German Translation")
310
+ add_btn = gr.Button("Add Word")
311
+ add_result = gr.Markdown()
312
+
313
+ with gr.Row(equal_height=True):
314
+ search_box = gr.Textbox(label="Search words", placeholder="Type to filter...", scale=5)
315
+ reset_btn = gr.Button("Reset Filter", scale=1)
316
+
317
+ with gr.Row():
318
+ # English → German table
319
+ word_table_en_de = gr.DataFrame(
320
+ headers=["English", "Article", "German", "Type"],
321
+ #value=get_words_table_en_de(),
322
+ value=[],
323
+ interactive=False,
324
+ wrap=True,
325
+ max_height=300
326
+ )
327
+ # German → English table
328
+ word_table_de_en = gr.DataFrame(
329
+ headers=["German", "Article", "English", "Type"],
330
+ #value=get_words_table_de_en(),
331
+ value=[],
332
+ interactive=False,
333
+ wrap=True,
334
+ max_height=300
335
+ )
336
+
337
+ demo.load(
338
+ fn=lambda: (get_words_table_en_de(), get_words_table_de_en()),
339
+ inputs=None,
340
+ outputs=[word_table_en_de, word_table_de_en]
341
+ )
342
+
343
+ new_btn.click(
344
+ fn=new_word,
345
+ outputs=[word_display, word_type, article_box, german_box,
346
+ article_box, german_box]
347
+ )
348
+
349
+ submit_btn.click(
350
+ fn=check_answer,
351
+ inputs=[word_display, word_type, article_box, german_box],
352
+ outputs=result
353
+ )
354
+
355
+ de_new_btn.click(
356
+ fn=new_word_de_en,
357
+ outputs=[de_word_display, de_word_type, de_artikel_box, de_english_box, de_artikel_box, de_english_box]
358
+ )
359
+
360
+ de_submit_btn.click(
361
+ fn=check_answer_de_en,
362
+ inputs=[de_word_display, de_word_type, de_artikel_box, de_english_box],
363
+ outputs=de_result
364
+ )
365
+
366
+ add_btn.click(
367
+ fn=add_word,
368
+ inputs=[english_in, type_in, article_in, german_in],
369
+ outputs=[add_result, word_table_en_de, word_table_de_en]
370
+ )
371
+
372
+ type_in.change(
373
+ fn=toggle_article_input,
374
+ inputs=type_in,
375
+ outputs=article_in
376
+ )
377
+
378
+ search_box.change(
379
+ fn=lambda q: (get_words_table_en_de(q), get_words_table_de_en(q)),
380
+ inputs=search_box,
381
+ outputs=[word_table_en_de, word_table_de_en]
382
+ )
383
+
384
+ reset_btn.click(
385
+ fn=reset_filter,
386
+ outputs=[search_box, word_table_en_de, word_table_de_en]
387
+ )
388
+
389
+ demo.launch()