Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@ from google.genai import types
|
|
| 5 |
from pymongo import UpdateOne
|
| 6 |
from pymongo import MongoClient
|
| 7 |
from bson import ObjectId
|
|
|
|
| 8 |
import bcrypt
|
| 9 |
import os
|
| 10 |
import re
|
|
@@ -106,6 +107,14 @@ Mode: English-guided learning.
|
|
| 106 |
pushing.
|
| 107 |
- Correct mistakes gently and explain the grammar point in English."""
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
def normalize_word(w):
|
| 111 |
return re.sub(r"[^\w']", "", w, flags=re.UNICODE).strip().lower()
|
|
@@ -164,12 +173,18 @@ def build_config(mode, pronunciation_notes=""):
|
|
| 164 |
)
|
| 165 |
|
| 166 |
|
| 167 |
-
def call_intyoza(message, history, mode):
|
| 168 |
contents = []
|
| 169 |
for turn in history:
|
| 170 |
role = "user" if turn["role"] == "user" else "model"
|
| 171 |
contents.append(types.Content(role=role, parts=[types.Part(text=str(turn["content"]))]))
|
| 172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
|
| 174 |
config = build_config(mode, lookup_pronunciation_notes(message, history))
|
| 175 |
|
|
@@ -457,14 +472,17 @@ def send_message():
|
|
| 457 |
message = (data.get("message") or "").strip()
|
| 458 |
mode = data.get("mode", "Kinyarwanda immersion")
|
| 459 |
chat_id = data.get("chat_id")
|
|
|
|
| 460 |
|
| 461 |
-
if not message:
|
| 462 |
return jsonify(ok=False, message="Empty message."), 400
|
|
|
|
|
|
|
| 463 |
|
| 464 |
username = require_login()
|
| 465 |
|
| 466 |
if not (username and chat_id):
|
| 467 |
-
reply = call_intyoza(message, data.get("history", []), mode)
|
| 468 |
return jsonify(ok=True, reply=reply)
|
| 469 |
|
| 470 |
chat_doc = chats_col.find_one({"_id": ObjectId(chat_id), "username": username})
|
|
@@ -476,24 +494,24 @@ def send_message():
|
|
| 476 |
for d in messages_col.find({"chat_id": chat_id}).sort("timestamp", 1)
|
| 477 |
]
|
| 478 |
|
| 479 |
-
reply = call_intyoza(message, history, mode)
|
| 480 |
now = datetime.datetime.utcnow()
|
| 481 |
|
|
|
|
| 482 |
user_doc = messages_col.insert_one(
|
| 483 |
-
{"chat_id": chat_id, "role": "user", "content":
|
| 484 |
bot_doc = messages_col.insert_one(
|
| 485 |
{"chat_id": chat_id, "role": "assistant", "content": reply,
|
| 486 |
"timestamp": now + datetime.timedelta(milliseconds=1)})
|
| 487 |
|
| 488 |
full_history = history + [
|
| 489 |
-
{"role": "user", "content":
|
| 490 |
{"role": "assistant", "content": reply},
|
| 491 |
]
|
| 492 |
exchanges = len(full_history) // 2
|
| 493 |
stage = chat_doc.get("auto_titled_at", 0)
|
| 494 |
|
| 495 |
new_title = None
|
| 496 |
-
# Title after the first exchange, then refine once the conversation has shape.
|
| 497 |
if stage < 999 and (exchanges == 1 or (exchanges >= 4 and stage < 4)):
|
| 498 |
candidate = generate_title(snippet_from(full_history))
|
| 499 |
if candidate:
|
|
|
|
| 5 |
from pymongo import UpdateOne
|
| 6 |
from pymongo import MongoClient
|
| 7 |
from bson import ObjectId
|
| 8 |
+
import base64
|
| 9 |
import bcrypt
|
| 10 |
import os
|
| 11 |
import re
|
|
|
|
| 107 |
pushing.
|
| 108 |
- Correct mistakes gently and explain the grammar point in English."""
|
| 109 |
|
| 110 |
+
def parse_data_url(data_url):
|
| 111 |
+
try:
|
| 112 |
+
header, b64data = data_url.split(",", 1)
|
| 113 |
+
mime = header.split(":")[1].split(";")[0]
|
| 114 |
+
return mime, base64.b64decode(b64data)
|
| 115 |
+
except Exception:
|
| 116 |
+
return None, None
|
| 117 |
+
|
| 118 |
|
| 119 |
def normalize_word(w):
|
| 120 |
return re.sub(r"[^\w']", "", w, flags=re.UNICODE).strip().lower()
|
|
|
|
| 173 |
)
|
| 174 |
|
| 175 |
|
| 176 |
+
def call_intyoza(message, history, mode, image_data_url=None):
|
| 177 |
contents = []
|
| 178 |
for turn in history:
|
| 179 |
role = "user" if turn["role"] == "user" else "model"
|
| 180 |
contents.append(types.Content(role=role, parts=[types.Part(text=str(turn["content"]))]))
|
| 181 |
+
|
| 182 |
+
parts = [types.Part(text=message)]
|
| 183 |
+
if image_data_url:
|
| 184 |
+
mime, raw = parse_data_url(image_data_url)
|
| 185 |
+
if raw:
|
| 186 |
+
parts.append(types.Part.from_bytes(data=raw, mime_type=mime))
|
| 187 |
+
contents.append(types.Content(role="user", parts=parts))
|
| 188 |
|
| 189 |
config = build_config(mode, lookup_pronunciation_notes(message, history))
|
| 190 |
|
|
|
|
| 472 |
message = (data.get("message") or "").strip()
|
| 473 |
mode = data.get("mode", "Kinyarwanda immersion")
|
| 474 |
chat_id = data.get("chat_id")
|
| 475 |
+
image_data_url = data.get("image")
|
| 476 |
|
| 477 |
+
if not message and not image_data_url:
|
| 478 |
return jsonify(ok=False, message="Empty message."), 400
|
| 479 |
+
if image_data_url and len(image_data_url) > 4_000_000:
|
| 480 |
+
return jsonify(ok=False, message="Image is too large (4MB max)."), 400
|
| 481 |
|
| 482 |
username = require_login()
|
| 483 |
|
| 484 |
if not (username and chat_id):
|
| 485 |
+
reply = call_intyoza(message or "What is in this image?", data.get("history", []), mode, image_data_url)
|
| 486 |
return jsonify(ok=True, reply=reply)
|
| 487 |
|
| 488 |
chat_doc = chats_col.find_one({"_id": ObjectId(chat_id), "username": username})
|
|
|
|
| 494 |
for d in messages_col.find({"chat_id": chat_id}).sort("timestamp", 1)
|
| 495 |
]
|
| 496 |
|
| 497 |
+
reply = call_intyoza(message or "What is in this image?", history, mode, image_data_url)
|
| 498 |
now = datetime.datetime.utcnow()
|
| 499 |
|
| 500 |
+
stored_message = message or "[Sent an image]"
|
| 501 |
user_doc = messages_col.insert_one(
|
| 502 |
+
{"chat_id": chat_id, "role": "user", "content": stored_message, "timestamp": now})
|
| 503 |
bot_doc = messages_col.insert_one(
|
| 504 |
{"chat_id": chat_id, "role": "assistant", "content": reply,
|
| 505 |
"timestamp": now + datetime.timedelta(milliseconds=1)})
|
| 506 |
|
| 507 |
full_history = history + [
|
| 508 |
+
{"role": "user", "content": stored_message},
|
| 509 |
{"role": "assistant", "content": reply},
|
| 510 |
]
|
| 511 |
exchanges = len(full_history) // 2
|
| 512 |
stage = chat_doc.get("auto_titled_at", 0)
|
| 513 |
|
| 514 |
new_title = None
|
|
|
|
| 515 |
if stage < 999 and (exchanges == 1 or (exchanges >= 4 and stage < 4)):
|
| 516 |
candidate = generate_title(snippet_from(full_history))
|
| 517 |
if candidate:
|