Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files
app.py
CHANGED
|
@@ -261,34 +261,64 @@ FEWSHOTS = {
|
|
| 261 |
# REPOSITORY HELPERS (questions only, per category)
|
| 262 |
|
| 263 |
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
|
| 268 |
-
def load_repo() -> Dict[str, List[str]]:
|
| 269 |
os.makedirs(os.path.dirname(REPO_PATH), exist_ok=True)
|
| 270 |
if not os.path.exists(REPO_PATH):
|
| 271 |
data = _default_repo()
|
| 272 |
with open(REPO_PATH, "w", encoding="utf-8") as f:
|
| 273 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 274 |
return data
|
|
|
|
| 275 |
try:
|
| 276 |
with open(REPO_PATH, "r", encoding="utf-8") as f:
|
| 277 |
data = json.load(f)
|
| 278 |
except Exception:
|
| 279 |
data = _default_repo()
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
base = _default_repo()
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
return base
|
| 284 |
|
| 285 |
|
| 286 |
-
def save_repo(data: Dict[str, List[str]]) -> None:
|
| 287 |
os.makedirs(os.path.dirname(REPO_PATH), exist_ok=True)
|
| 288 |
with open(REPO_PATH, "w", encoding="utf-8") as f:
|
| 289 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 290 |
|
| 291 |
|
|
|
|
| 292 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 293 |
# PROMPT + MODEL HELPERS
|
| 294 |
|
|
@@ -581,18 +611,15 @@ def ai_generate(lang: str, category_key: str, theme: str) -> Dict[str, Any]:
|
|
| 581 |
def get_questions_and_micro(
|
| 582 |
lang: str,
|
| 583 |
category_key: str,
|
| 584 |
-
|
| 585 |
seen: List[str],
|
| 586 |
) -> Dict[str, Any]:
|
| 587 |
-
|
| 588 |
-
1. Load /data/questions.json
|
| 589 |
-
2. If repo has >=4 unseen questions -> sample 4 from repo, micro from local pool.
|
| 590 |
-
3. Else -> call AI, store any new questions into repo, use AI's questions + micro.
|
| 591 |
-
4. Update seen list so this session won't see the same question twice.
|
| 592 |
-
"""
|
| 593 |
seen_set = set(seen or [])
|
| 594 |
repo = load_repo()
|
| 595 |
-
|
|
|
|
|
|
|
| 596 |
|
| 597 |
unseen_repo = [q for q in repo_qs if q and q not in seen_set]
|
| 598 |
|
|
@@ -618,13 +645,15 @@ def get_questions_and_micro(
|
|
| 618 |
safety_notes = ai_out.get("safety_notes", "")
|
| 619 |
used_ai = True
|
| 620 |
|
| 621 |
-
# store new questions in repo
|
| 622 |
new_qs = [q for q in questions if q and q not in repo_qs]
|
| 623 |
if new_qs:
|
| 624 |
-
|
| 625 |
-
repo
|
|
|
|
| 626 |
save_repo(repo)
|
| 627 |
|
|
|
|
| 628 |
# update seen for this session
|
| 629 |
for q in questions:
|
| 630 |
if q:
|
|
@@ -782,6 +811,10 @@ def get_ui_texts(lang: str) -> Dict[str, Any]:
|
|
| 782 |
"category_default": "alimentation π",
|
| 783 |
"theme_choices": theme_choices,
|
| 784 |
"theme_default": THEME_LABELS["fr"]["family"],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 785 |
}
|
| 786 |
else:
|
| 787 |
header = """
|
|
@@ -814,6 +847,10 @@ def get_ui_texts(lang: str) -> Dict[str, Any]:
|
|
| 814 |
"category_default": "Nutrition π",
|
| 815 |
"theme_choices": theme_choices,
|
| 816 |
"theme_default": THEME_LABELS["en"]["family"],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
}
|
| 818 |
|
| 819 |
|
|
@@ -902,11 +939,16 @@ with gr.Blocks(title="Neurovie β Question Studio") as demo:
|
|
| 902 |
q2 = gr.HTML()
|
| 903 |
q3 = gr.HTML()
|
| 904 |
q4 = gr.HTML()
|
|
|
|
| 905 |
with gr.Column():
|
| 906 |
micro_label_html = gr.HTML(f"<div class='nv-label nv-fade'>{ui_texts['micro_label']}</div>")
|
| 907 |
with gr.Column(elem_classes="nv-card-grid"):
|
| 908 |
m1 = gr.HTML()
|
| 909 |
m2 = gr.HTML()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 910 |
|
| 911 |
# Update labels & category choices when language changes
|
| 912 |
lang.change(
|
|
@@ -922,6 +964,7 @@ with gr.Blocks(title="Neurovie β Question Studio") as demo:
|
|
| 922 |
theme,
|
| 923 |
category,
|
| 924 |
btn,
|
|
|
|
| 925 |
],
|
| 926 |
show_progress=False
|
| 927 |
)
|
|
|
|
| 261 |
# REPOSITORY HELPERS (questions only, per category)
|
| 262 |
|
| 263 |
|
| 264 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 265 |
+
# REPOSITORY HELPERS (questions only, per language + category)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def _default_repo() -> Dict[str, Dict[str, List[str]]]:
|
| 269 |
+
"""
|
| 270 |
+
Structure:
|
| 271 |
+
{
|
| 272 |
+
"fr": {"alimentation": [...], "mouvement": [...], ...},
|
| 273 |
+
"en": {"alimentation": [...], "mouvement": [...], ...}
|
| 274 |
+
}
|
| 275 |
+
"""
|
| 276 |
+
base_per_lang = {c["key"]: [] for c in CATEGORIES}
|
| 277 |
+
return {"fr": dict(base_per_lang), "en": dict(base_per_lang)}
|
| 278 |
|
| 279 |
|
| 280 |
+
def load_repo() -> Dict[str, Dict[str, List[str]]]:
|
| 281 |
os.makedirs(os.path.dirname(REPO_PATH), exist_ok=True)
|
| 282 |
if not os.path.exists(REPO_PATH):
|
| 283 |
data = _default_repo()
|
| 284 |
with open(REPO_PATH, "w", encoding="utf-8") as f:
|
| 285 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 286 |
return data
|
| 287 |
+
|
| 288 |
try:
|
| 289 |
with open(REPO_PATH, "r", encoding="utf-8") as f:
|
| 290 |
data = json.load(f)
|
| 291 |
except Exception:
|
| 292 |
data = _default_repo()
|
| 293 |
+
with open(REPO_PATH, "w", encoding="utf-8") as f:
|
| 294 |
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 295 |
+
return data
|
| 296 |
+
|
| 297 |
+
# If this is the old format (categories at top level), reset to new format.
|
| 298 |
+
if not isinstance(data, dict) or "fr" not in data or "en" not in data:
|
| 299 |
+
data = _default_repo()
|
| 300 |
+
with open(REPO_PATH, "w", encoding="utf-8") as f:
|
| 301 |
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 302 |
+
return data
|
| 303 |
+
|
| 304 |
+
# Ensure both languages + all categories exist
|
| 305 |
base = _default_repo()
|
| 306 |
+
for lang in ("fr", "en"):
|
| 307 |
+
src = data.get(lang, {})
|
| 308 |
+
if isinstance(src, dict):
|
| 309 |
+
for k, v in src.items():
|
| 310 |
+
if k in base[lang] and isinstance(v, list):
|
| 311 |
+
base[lang][k] = v
|
| 312 |
return base
|
| 313 |
|
| 314 |
|
| 315 |
+
def save_repo(data: Dict[str, Dict[str, List[str]]]) -> None:
|
| 316 |
os.makedirs(os.path.dirname(REPO_PATH), exist_ok=True)
|
| 317 |
with open(REPO_PATH, "w", encoding="utf-8") as f:
|
| 318 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 319 |
|
| 320 |
|
| 321 |
+
|
| 322 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 323 |
# PROMPT + MODEL HELPERS
|
| 324 |
|
|
|
|
| 611 |
def get_questions_and_micro(
|
| 612 |
lang: str,
|
| 613 |
category_key: str,
|
| 614 |
+
variant: str,
|
| 615 |
seen: List[str],
|
| 616 |
) -> Dict[str, Any]:
|
| 617 |
+
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
seen_set = set(seen or [])
|
| 619 |
repo = load_repo()
|
| 620 |
+
lang_repo = repo.get(lang, {})
|
| 621 |
+
repo_qs = lang_repo.get(category_key, [])
|
| 622 |
+
|
| 623 |
|
| 624 |
unseen_repo = [q for q in repo_qs if q and q not in seen_set]
|
| 625 |
|
|
|
|
| 645 |
safety_notes = ai_out.get("safety_notes", "")
|
| 646 |
used_ai = True
|
| 647 |
|
| 648 |
+
# store new questions in the repo for this language only
|
| 649 |
new_qs = [q for q in questions if q and q not in repo_qs]
|
| 650 |
if new_qs:
|
| 651 |
+
updated = repo_qs + new_qs
|
| 652 |
+
repo.setdefault(lang, {})
|
| 653 |
+
repo[lang][category_key] = updated
|
| 654 |
save_repo(repo)
|
| 655 |
|
| 656 |
+
|
| 657 |
# update seen for this session
|
| 658 |
for q in questions:
|
| 659 |
if q:
|
|
|
|
| 811 |
"category_default": "alimentation π",
|
| 812 |
"theme_choices": theme_choices,
|
| 813 |
"theme_default": THEME_LABELS["fr"]["family"],
|
| 814 |
+
"note_text": (
|
| 815 |
+
"Note : les cartes sβestompent doucement avec le temps, "
|
| 816 |
+
"pour symboliser la mΓ©moire qui sβefface."
|
| 817 |
+
),
|
| 818 |
}
|
| 819 |
else:
|
| 820 |
header = """
|
|
|
|
| 847 |
"category_default": "Nutrition π",
|
| 848 |
"theme_choices": theme_choices,
|
| 849 |
"theme_default": THEME_LABELS["en"]["family"],
|
| 850 |
+
"note_text": (
|
| 851 |
+
"Note: the cards gently fade over time, "
|
| 852 |
+
"to echo how memories can fade."
|
| 853 |
+
),
|
| 854 |
}
|
| 855 |
|
| 856 |
|
|
|
|
| 939 |
q2 = gr.HTML()
|
| 940 |
q3 = gr.HTML()
|
| 941 |
q4 = gr.HTML()
|
| 942 |
+
|
| 943 |
with gr.Column():
|
| 944 |
micro_label_html = gr.HTML(f"<div class='nv-label nv-fade'>{ui_texts['micro_label']}</div>")
|
| 945 |
with gr.Column(elem_classes="nv-card-grid"):
|
| 946 |
m1 = gr.HTML()
|
| 947 |
m2 = gr.HTML()
|
| 948 |
+
# Small explanatory note under the cards
|
| 949 |
+
note_html = gr.HTML(
|
| 950 |
+
f"<div class='nv-note'>{ui_texts['note_text']}</div>"
|
| 951 |
+
)
|
| 952 |
|
| 953 |
# Update labels & category choices when language changes
|
| 954 |
lang.change(
|
|
|
|
| 964 |
theme,
|
| 965 |
category,
|
| 966 |
btn,
|
| 967 |
+
note_html,
|
| 968 |
],
|
| 969 |
show_progress=False
|
| 970 |
)
|
style.css
CHANGED
|
@@ -115,6 +115,18 @@
|
|
| 115 |
margin-bottom: 18px;
|
| 116 |
}
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
/* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 119 |
SECTIONS + LABELS
|
| 120 |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
@@ -283,7 +295,7 @@
|
|
| 283 |
transform: translateY(0) scale(1);
|
| 284 |
animation:
|
| 285 |
nvCardDeal 420ms ease-out forwards,
|
| 286 |
-
nvCardSlowFade
|
| 287 |
cursor: default;
|
| 288 |
transition:
|
| 289 |
box-shadow 180ms ease,
|
|
|
|
| 115 |
margin-bottom: 18px;
|
| 116 |
}
|
| 117 |
|
| 118 |
+
.nv-note {
|
| 119 |
+
margin-top: 10px;
|
| 120 |
+
font-size: 0.78rem;
|
| 121 |
+
font-weight: 400;
|
| 122 |
+
letter-spacing: 0.03em;
|
| 123 |
+
text-transform: none;
|
| 124 |
+
color: rgba(15, 23, 42, 0.55);
|
| 125 |
+
text-align: center;
|
| 126 |
+
font-style: italic;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
|
| 130 |
/* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 131 |
SECTIONS + LABELS
|
| 132 |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
|
|
| 295 |
transform: translateY(0) scale(1);
|
| 296 |
animation:
|
| 297 |
nvCardDeal 420ms ease-out forwards,
|
| 298 |
+
nvCardSlowFade 50s linear forwards;
|
| 299 |
cursor: default;
|
| 300 |
transition:
|
| 301 |
box-shadow 180ms ease,
|