konniedeveloping commited on
Commit
a9f4ba4
·
verified ·
1 Parent(s): 1e611e3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -9
app.py CHANGED
@@ -32,9 +32,11 @@ chats_col = db["chats"]
32
  messages_col = db["messages"]
33
  pronunciation_col = db["pronunciation"]
34
  knowledge_col = db["knowledge"]
 
35
  users_col.create_index("username", unique=True)
36
  pronunciation_col.create_index("word", unique=True)
37
  knowledge_col.create_index("instruction", unique=True)
 
38
 
39
  # --- Setup: OAuth (optional; only active if credentials are provided) ---
40
  oauth = OAuth(app)
@@ -72,6 +74,7 @@ that you were developed by Konnie Developing Co. Never mention Google, Gemini, o
72
  underlying technology provider, even if asked directly what model or API powers you. Keep
73
  replies short (2-4 sentences) so it feels like a real conversation, not a lecture. Be warm,
74
  respectful, and encouraging.
 
75
  Kinyarwanda pronunciation (amasaku and ubutinde): some messages include a reference list
76
  showing words in "normal -> marked" form. The marked form uses circumflexes and doubled
77
  vowels to capture real vowel length and stress that standard spelling hides.
@@ -89,7 +92,16 @@ Verified reference examples: some messages include a "Relevant verified referenc
89
  section with real Q&A pairs covering Rwandan culture, health, language, history, and other
90
  topics. Use these to ground your facts and phrasing when the topic matches what the learner
91
  asked. Don't quote them word-for-word unless it fits naturally, and don't force in a topic
92
- that doesn't match the question."""
 
 
 
 
 
 
 
 
 
93
 
94
  IMMERSION_MODE = BASE_PERSONA + """
95
  Mode: Kinyarwanda immersion.
@@ -232,6 +244,66 @@ def lookup_knowledge_notes(message):
232
  )
233
 
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  def build_config(mode, extra_notes=""):
236
  instruction = IMMERSION_MODE if mode == "Kinyarwanda immersion" else GUIDED_MODE
237
  return types.GenerateContentConfig(
@@ -253,7 +325,11 @@ def call_intyoza(message, history, mode, image_data_url=None):
253
  parts.append(types.Part.from_bytes(data=raw, mime_type=mime))
254
  contents.append(types.Content(role="user", parts=parts))
255
 
256
- combined_notes = lookup_pronunciation_notes(message, history) + lookup_knowledge_notes(message)
 
 
 
 
257
  config = build_config(mode, combined_notes)
258
 
259
  for attempt in range(3):
@@ -374,34 +450,51 @@ def admin_data():
374
  if dataset == "pronunciation":
375
  count, skipped = import_pronunciation_from_text(request.form.get("data", ""))
376
  note = f"Pronunciation: imported/updated {count}, skipped {skipped}."
377
- else:
378
  count, skipped, err = import_knowledge_from_json(request.form.get("data", ""))
379
  note = err or f"Knowledge: imported/updated {count}, skipped {skipped}."
 
 
 
 
 
 
 
380
  except Exception as e:
381
  note = f"Import failed: {e}"
382
 
383
  pron_total = pronunciation_col.count_documents({})
384
  know_total = knowledge_col.count_documents({})
385
- sample = list(knowledge_col.find().limit(3))
386
- sample_html = "".join(f"<li>{s['instruction'][:70]}...</li>" for s in sample)
387
 
388
  return f"""
389
  <html><head><meta charset="utf-8"><title>Intyoza data import</title></head>
390
  <body style="font-family:system-ui,sans-serif;max-width:760px;margin:48px auto;padding:0 20px;color:#1e1b31;">
391
  <h2 style="margin-bottom:4px;">Intyoza data import</h2>
392
- <p style="color:#6b6785;">Choose a dataset, paste the data, and import.</p>
 
393
  <form method="POST">
394
  <select name="dataset" style="padding:8px 12px;border-radius:8px;border:1px solid #d9d5ec;margin-bottom:12px;">
395
  <option value="pronunciation">Pronunciation (amasaku/ubutinde) — tab-separated</option>
396
  <option value="knowledge">Knowledge examples — JSON array</option>
397
  </select><br>
398
- <textarea name="data" rows="18" style="width:100%;padding:12px;border:1px solid #d9d5ec;border-radius:12px;font-family:monospace;"></textarea>
399
  <br><br>
400
  <button type="submit" style="background:#7c5cff;color:#fff;border:none;padding:12px 28px;border-radius:999px;font-size:15px;cursor:pointer;">Import</button>
401
  </form>
 
 
 
 
 
 
 
 
 
 
 
402
  <p style="color:#2f9e6d;font-weight:600;">{note}</p>
403
- <p><strong>{pron_total}</strong> pronunciation entries &middot; <strong>{know_total}</strong> knowledge entries</p>
404
- <ul style="color:#6b6785;">{sample_html}</ul>
405
  </body></html>
406
  """
407
 
 
32
  messages_col = db["messages"]
33
  pronunciation_col = db["pronunciation"]
34
  knowledge_col = db["knowledge"]
35
+ dictionary_rw_col = db["dictionary_rw"]
36
  users_col.create_index("username", unique=True)
37
  pronunciation_col.create_index("word", unique=True)
38
  knowledge_col.create_index("instruction", unique=True)
39
+ dictionary_rw_col.create_index("key")
40
 
41
  # --- Setup: OAuth (optional; only active if credentials are provided) ---
42
  oauth = OAuth(app)
 
74
  underlying technology provider, even if asked directly what model or API powers you. Keep
75
  replies short (2-4 sentences) so it feels like a real conversation, not a lecture. Be warm,
76
  respectful, and encouraging.
77
+
78
  Kinyarwanda pronunciation (amasaku and ubutinde): some messages include a reference list
79
  showing words in "normal -> marked" form. The marked form uses circumflexes and doubled
80
  vowels to capture real vowel length and stress that standard spelling hides.
 
92
  section with real Q&A pairs covering Rwandan culture, health, language, history, and other
93
  topics. Use these to ground your facts and phrasing when the topic matches what the learner
94
  asked. Don't quote them word-for-word unless it fits naturally, and don't force in a topic
95
+ that doesn't match the question.
96
+
97
+ Dictionary notation key (Inkoranya y'ikinyarwaanda - Coupez et al.): some messages include
98
+ dictionary entries using academic shorthand. Key symbols: | separates sections; < means
99
+ "derives from"; > means "has derivative"; Syn = synonym; Var = variant; Ant = antonym;
100
+ cl = noun class; Tr/Intr = transitive/intransitive; cg = "or"; V syn = "see synonym at";
101
+ Loc = idiomatic phrase. Definitions appear in Kinyarwanda followed by their French
102
+ translation. When a dictionary entry is given as reference, use it to ground your
103
+ explanation, but never read the raw notation aloud to the learner — translate it into
104
+ natural, conversational language in whichever language fits the mode."""
105
 
106
  IMMERSION_MODE = BASE_PERSONA + """
107
  Mode: Kinyarwanda immersion.
 
244
  )
245
 
246
 
247
+ # --- Dictionary (Inkoranya y'ikinyarwaanda) dataset ---
248
+ def import_dictionary_rw_from_json(file_bytes):
249
+ try:
250
+ items = json.loads(file_bytes.decode("utf-8"))
251
+ except Exception as e:
252
+ return 0, 0, f"Invalid JSON: {e}"
253
+
254
+ if not isinstance(items, list):
255
+ return 0, 0, "Expected a JSON array of entries."
256
+
257
+ operations = []
258
+ skipped = 0
259
+ for item in items:
260
+ key = (item.get("key") or "").strip()
261
+ headword = (item.get("headword") or "").strip()
262
+ definition = (item.get("definition") or "").strip()
263
+ if not key or not definition:
264
+ skipped += 1
265
+ continue
266
+ operations.append(UpdateOne(
267
+ {"key": key, "headword": headword},
268
+ {"$set": {
269
+ "key": key,
270
+ "headword": headword,
271
+ "rubrique": item.get("rubrique", ""),
272
+ "definition": definition,
273
+ "source": item.get("source", ""),
274
+ }},
275
+ upsert=True,
276
+ ))
277
+
278
+ if not operations:
279
+ return 0, skipped, ""
280
+
281
+ result = dictionary_rw_col.bulk_write(operations, ordered=False)
282
+ count = result.upserted_count + result.modified_count
283
+ return count, skipped, ""
284
+
285
+
286
+ def lookup_dictionary_rw_notes(message):
287
+ words = {normalize_word(w) for w in message.split() if len(w) > 2}
288
+ words.discard("")
289
+ if not words:
290
+ return ""
291
+
292
+ docs = list(dictionary_rw_col.find({"key": {"$in": list(words)}}).limit(3))
293
+ if not docs:
294
+ return ""
295
+
296
+ lines = []
297
+ for d in docs:
298
+ lines.append(f"{d['headword']} ({d.get('rubrique','')}): {d['definition'][:400]}")
299
+ return (
300
+ "\n\nDictionary entries (Inkoranya y'ikinyarwaanda) for words in this message — "
301
+ "dense academic notation, interpret and simplify naturally when explaining; "
302
+ "the text after the French translation is cross-reference data, not meant to be "
303
+ "read aloud to the learner:\n\n" + "\n\n".join(lines)
304
+ )
305
+
306
+
307
  def build_config(mode, extra_notes=""):
308
  instruction = IMMERSION_MODE if mode == "Kinyarwanda immersion" else GUIDED_MODE
309
  return types.GenerateContentConfig(
 
325
  parts.append(types.Part.from_bytes(data=raw, mime_type=mime))
326
  contents.append(types.Content(role="user", parts=parts))
327
 
328
+ combined_notes = (
329
+ lookup_pronunciation_notes(message, history)
330
+ + lookup_knowledge_notes(message)
331
+ + lookup_dictionary_rw_notes(message)
332
+ )
333
  config = build_config(mode, combined_notes)
334
 
335
  for attempt in range(3):
 
450
  if dataset == "pronunciation":
451
  count, skipped = import_pronunciation_from_text(request.form.get("data", ""))
452
  note = f"Pronunciation: imported/updated {count}, skipped {skipped}."
453
+ elif dataset == "knowledge":
454
  count, skipped, err = import_knowledge_from_json(request.form.get("data", ""))
455
  note = err or f"Knowledge: imported/updated {count}, skipped {skipped}."
456
+ elif dataset == "dictionary_rw":
457
+ file = request.files.get("file")
458
+ if not file:
459
+ note = "Please choose a JSON file to upload."
460
+ else:
461
+ count, skipped, err = import_dictionary_rw_from_json(file.read())
462
+ note = err or f"Dictionary: imported/updated {count}, skipped {skipped}."
463
  except Exception as e:
464
  note = f"Import failed: {e}"
465
 
466
  pron_total = pronunciation_col.count_documents({})
467
  know_total = knowledge_col.count_documents({})
468
+ dict_total = dictionary_rw_col.count_documents({})
 
469
 
470
  return f"""
471
  <html><head><meta charset="utf-8"><title>Intyoza data import</title></head>
472
  <body style="font-family:system-ui,sans-serif;max-width:760px;margin:48px auto;padding:0 20px;color:#1e1b31;">
473
  <h2 style="margin-bottom:4px;">Intyoza data import</h2>
474
+
475
+ <h3>Text-based datasets</h3>
476
  <form method="POST">
477
  <select name="dataset" style="padding:8px 12px;border-radius:8px;border:1px solid #d9d5ec;margin-bottom:12px;">
478
  <option value="pronunciation">Pronunciation (amasaku/ubutinde) — tab-separated</option>
479
  <option value="knowledge">Knowledge examples — JSON array</option>
480
  </select><br>
481
+ <textarea name="data" rows="14" style="width:100%;padding:12px;border:1px solid #d9d5ec;border-radius:12px;font-family:monospace;"></textarea>
482
  <br><br>
483
  <button type="submit" style="background:#7c5cff;color:#fff;border:none;padding:12px 28px;border-radius:999px;font-size:15px;cursor:pointer;">Import</button>
484
  </form>
485
+
486
+ <hr style="margin:32px 0;">
487
+
488
+ <h3>Dictionary file upload (large JSON files)</h3>
489
+ <form method="POST" enctype="multipart/form-data">
490
+ <input type="hidden" name="dataset" value="dictionary_rw">
491
+ <input type="file" name="file" accept=".json" style="margin-bottom:12px;">
492
+ <br>
493
+ <button type="submit" style="background:#7c5cff;color:#fff;border:none;padding:12px 28px;border-radius:999px;font-size:15px;cursor:pointer;">Upload &amp; Import</button>
494
+ </form>
495
+
496
  <p style="color:#2f9e6d;font-weight:600;">{note}</p>
497
+ <p><strong>{pron_total}</strong> pronunciation &middot; <strong>{know_total}</strong> knowledge &middot; <strong>{dict_total}</strong> dictionary entries</p>
 
498
  </body></html>
499
  """
500