GeetaAIVisionary commited on
Commit
90abb93
·
verified ·
1 Parent(s): bfc4596

added some enhancements

Browse files
Files changed (1) hide show
  1. app.py +47 -15
app.py CHANGED
@@ -1,5 +1,6 @@
1
  # app.py
2
- # CLI-like Spelling Bee Tutor as a simple Gradio UI (no LLMs, all offline)
 
3
 
4
  from pathlib import Path
5
  import os
@@ -15,7 +16,7 @@ EXPECTED_COLS = ["word", "difficulty", "definition", "origin", "sentence"]
15
  def _load_words(path: Path = WORDS_PATH) -> pd.DataFrame:
16
  """Load a possibly-messy CSV:
17
  - tolerate commas inside sentence/origin
18
- - accept exact 5 columns or more (extras glued into sentence)
19
  - create missing optional columns
20
  """
21
  if not path.exists():
@@ -48,20 +49,18 @@ def _load_words(path: Path = WORDS_PATH) -> pd.DataFrame:
48
  for row in reader:
49
  if not row or all((x is None or str(x).strip() == "") for x in row):
50
  continue
51
- # expect at least 5 fields; if more, glue extras into sentence
52
  if len(row) < 5:
53
- # pad empty fields to avoid crash
54
  row = row + [""] * (5 - len(row))
55
  base = row[:4]
56
  sentence = ",".join(row[4:]) # glue any extras back
57
  rows.append([*(str(x).strip() for x in base), sentence.strip()])
58
  df = pd.DataFrame(rows, columns=EXPECTED_COLS)
59
 
60
- # --- Normalize columns ---
61
  mapper = {c: c.strip().lower() for c in df.columns}
62
  df = df.rename(columns=mapper)
63
 
64
- # If there are more than 5 columns, rebuild to our schema
65
  if df.shape[1] >= 5:
66
  def pick(colname, default=""):
67
  return df[colname] if colname in df.columns else default
@@ -87,25 +86,24 @@ def _load_words(path: Path = WORDS_PATH) -> pd.DataFrame:
87
  base_df["sentence"] = sentence_series
88
  df = base_df
89
  else:
90
- # ensure missing optional cols exist
91
  for c in ["definition", "origin", "sentence", "difficulty"]:
92
  if c not in df.columns:
93
  df[c] = ""
94
 
95
- # Clean and type
96
  df["word"] = df["word"].astype(str).fillna("").str.strip()
97
  df["definition"] = df["definition"].astype(str).fillna("").str.strip()
98
  df["origin"] = df["origin"].astype(str).fillna("").str.strip()
99
  df["sentence"] = df["sentence"].astype(str).fillna("").str.strip()
100
 
101
- # difficulty score
102
  if "difficulty" in df.columns:
103
  ds = pd.to_numeric(df["difficulty"], errors="coerce").fillna(0)
104
  df["difficulty_score"] = ds
105
  else:
106
  df["difficulty_score"] = df["word"].astype(str).str.len()
107
 
108
- # sort hardest → easiest
109
  df = df.sort_values("difficulty_score", ascending=False).reset_index(drop=True)
110
  return df
111
 
@@ -126,7 +124,7 @@ def start_quiz(n_words, state):
126
  return (state,
127
  "words.csv not found or empty.",
128
  gr.update(value="", interactive=False),
129
- "Please add a words.csv with at least one 'word' column.",
130
  "Score: 0/0",
131
  gr.update(value="", interactive=False))
132
 
@@ -134,9 +132,11 @@ def start_quiz(n_words, state):
134
  n = int(n_words or 5)
135
  except Exception:
136
  n = 5
137
- n = max(1, min(n, len(df))) # clip to available size
 
 
 
138
 
139
- block = df.head(n).reset_index(drop=True)
140
  s = {
141
  "i": 0,
142
  "n": n,
@@ -148,7 +148,7 @@ def start_quiz(n_words, state):
148
  }
149
 
150
  current = s["words"][0]
151
- hist = f"Okay! We'll do {n} words this round, hardest → easiest.\nSpell this word: {current}"
152
  status = "Type your spelling attempt, or click definition/origin/sentence."
153
  return s, hist, gr.update(value=current, interactive=False), status, f"Score: 0/{n}", gr.update(value="", interactive=True)
154
 
@@ -204,6 +204,14 @@ def show_sentence(state, history, current_word, score_md):
204
  history = f"{history}\nTutor (sentence with {word}): {text}"
205
  return state, history, current_word, f"Sentence: {text}", score_md
206
 
 
 
 
 
 
 
 
 
207
  def next_word(state, history, current_word, score_md):
208
  if not _check_state(state):
209
  return state, history, current_word, "No round active. Click Start.", score_md
@@ -224,6 +232,20 @@ def stop_round(state, history, current_word, score_md):
224
  history = f"{history}\n{summary}"
225
  return {}, history, gr.update(value="", interactive=False), "Stopped.", f"Score: {state['score']}/{state['n']}"
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  # ---------- UI ----------
228
 
229
  with gr.Blocks() as demo:
@@ -233,17 +255,23 @@ with gr.Blocks() as demo:
233
  n_words = gr.Number(value=5, precision=0, label="Words this round")
234
  start = gr.Button("Start quiz")
235
 
 
 
 
236
  current_word = gr.Textbox(label="Spell this word", interactive=False)
237
  attempt = gr.Textbox(label="Your attempt")
 
238
  with gr.Row():
239
- check = gr.Button("Check")
240
  bdef = gr.Button("definition")
241
  borg = gr.Button("origin")
242
  bsent = gr.Button("sentence")
 
243
  bnext = gr.Button("next")
244
  bstop = gr.Button("stop")
245
 
246
  status = gr.Markdown("")
 
247
  score_md = gr.Markdown("Score: 0/0")
248
  history = gr.Textbox(label="History", lines=14)
249
 
@@ -262,11 +290,15 @@ with gr.Blocks() as demo:
262
  [state, history, current_word, status, score_md], queue=False)
263
  bsent.click(show_sentence, [state, history, current_word, score_md],
264
  [state, history, current_word, status, score_md], queue=False)
 
 
265
  bnext.click(next_word, [state, history, current_word, score_md],
266
  [state, history, current_word, status, score_md], queue=False)
267
  bstop.click(stop_round, [state, history, current_word, score_md],
268
  [state, history, current_word, status, score_md], queue=False)
269
 
 
 
270
  # ---------- Launch (blocking on Spaces) ----------
271
 
272
  if __name__ == "__main__":
 
1
  # app.py
2
+ # NeMo Guardrails Demo (starter UI) CLI-style spelling quiz from words.csv
3
+ # All offline; no LLMs. Ready for Hugging Face Spaces.
4
 
5
  from pathlib import Path
6
  import os
 
16
  def _load_words(path: Path = WORDS_PATH) -> pd.DataFrame:
17
  """Load a possibly-messy CSV:
18
  - tolerate commas inside sentence/origin
19
+ - accept 5+ columns (extras glued into sentence)
20
  - create missing optional columns
21
  """
22
  if not path.exists():
 
49
  for row in reader:
50
  if not row or all((x is None or str(x).strip() == "") for x in row):
51
  continue
 
52
  if len(row) < 5:
 
53
  row = row + [""] * (5 - len(row))
54
  base = row[:4]
55
  sentence = ",".join(row[4:]) # glue any extras back
56
  rows.append([*(str(x).strip() for x in base), sentence.strip()])
57
  df = pd.DataFrame(rows, columns=EXPECTED_COLS)
58
 
59
+ # normalize header names
60
  mapper = {c: c.strip().lower() for c in df.columns}
61
  df = df.rename(columns=mapper)
62
 
63
+ # rebuild to our schema when there are >5 columns
64
  if df.shape[1] >= 5:
65
  def pick(colname, default=""):
66
  return df[colname] if colname in df.columns else default
 
86
  base_df["sentence"] = sentence_series
87
  df = base_df
88
  else:
 
89
  for c in ["definition", "origin", "sentence", "difficulty"]:
90
  if c not in df.columns:
91
  df[c] = ""
92
 
93
+ # clean + types
94
  df["word"] = df["word"].astype(str).fillna("").str.strip()
95
  df["definition"] = df["definition"].astype(str).fillna("").str.strip()
96
  df["origin"] = df["origin"].astype(str).fillna("").str.strip()
97
  df["sentence"] = df["sentence"].astype(str).fillna("").str.strip()
98
 
99
+ # difficulty score (numeric difficulty if present; else word length)
100
  if "difficulty" in df.columns:
101
  ds = pd.to_numeric(df["difficulty"], errors="coerce").fillna(0)
102
  df["difficulty_score"] = ds
103
  else:
104
  df["difficulty_score"] = df["word"].astype(str).str.len()
105
 
106
+ # hardest → easiest
107
  df = df.sort_values("difficulty_score", ascending=False).reset_index(drop=True)
108
  return df
109
 
 
124
  return (state,
125
  "words.csv not found or empty.",
126
  gr.update(value="", interactive=False),
127
+ "Please add a words.csv with header: word,difficulty,definition,origin,sentence",
128
  "Score: 0/0",
129
  gr.update(value="", interactive=False))
130
 
 
132
  n = int(n_words or 5)
133
  except Exception:
134
  n = 5
135
+ n = max(1, min(n, len(df))) # clip
136
+
137
+ # take top-n hardest words, then randomize order for the round
138
+ block = df.head(n).sample(frac=1, random_state=None).reset_index(drop=True)
139
 
 
140
  s = {
141
  "i": 0,
142
  "n": n,
 
148
  }
149
 
150
  current = s["words"][0]
151
+ hist = f"Okay! We'll do {n} words this round, hardest → easiest (shuffled).\nSpell this word: {current}"
152
  status = "Type your spelling attempt, or click definition/origin/sentence."
153
  return s, hist, gr.update(value=current, interactive=False), status, f"Score: 0/{n}", gr.update(value="", interactive=True)
154
 
 
204
  history = f"{history}\nTutor (sentence with {word}): {text}"
205
  return state, history, current_word, f"Sentence: {text}", score_md
206
 
207
+ def show_answer(state, history, current_word, score_md):
208
+ if not _check_state(state):
209
+ return state, history, current_word, "No round active. Click Start.", score_md
210
+ i = state["i"]
211
+ word = state["words"][i]
212
+ history = f"{history}\nTutor: The correct spelling is **{word}**."
213
+ return state, history, current_word, f"Answer: {word}", score_md
214
+
215
  def next_word(state, history, current_word, score_md):
216
  if not _check_state(state):
217
  return state, history, current_word, "No round active. Click Start.", score_md
 
232
  history = f"{history}\n{summary}"
233
  return {}, history, gr.update(value="", interactive=False), "Stopped.", f"Score: {state['score']}/{state['n']}"
234
 
235
+ # ---------- Upload handling ----------
236
+
237
+ def load_uploaded(file):
238
+ """Replace words.csv with the uploaded file and refresh DF."""
239
+ global DF
240
+ try:
241
+ # Copy uploaded file bytes into WORDS_PATH
242
+ with open(file.name, "rb") as src, open(WORDS_PATH, "wb") as dst:
243
+ dst.write(src.read())
244
+ DF = _load_words() # refresh global dataset
245
+ return "Loaded new words.csv!", f"Loaded {len(DF)} rows."
246
+ except Exception as e:
247
+ return "", f"Upload failed: {e}"
248
+
249
  # ---------- UI ----------
250
 
251
  with gr.Blocks() as demo:
 
255
  n_words = gr.Number(value=5, precision=0, label="Words this round")
256
  start = gr.Button("Start quiz")
257
 
258
+ # Optional: allow swapping in a new CSV live
259
+ upload = gr.File(label="Upload words.csv", file_types=[".csv"], interactive=True)
260
+
261
  current_word = gr.Textbox(label="Spell this word", interactive=False)
262
  attempt = gr.Textbox(label="Your attempt")
263
+
264
  with gr.Row():
265
+ check = gr.Button("check")
266
  bdef = gr.Button("definition")
267
  borg = gr.Button("origin")
268
  bsent = gr.Button("sentence")
269
+ bshow = gr.Button("show answer")
270
  bnext = gr.Button("next")
271
  bstop = gr.Button("stop")
272
 
273
  status = gr.Markdown("")
274
+ upload_status = gr.Markdown("") # feedback for uploads
275
  score_md = gr.Markdown("Score: 0/0")
276
  history = gr.Textbox(label="History", lines=14)
277
 
 
290
  [state, history, current_word, status, score_md], queue=False)
291
  bsent.click(show_sentence, [state, history, current_word, score_md],
292
  [state, history, current_word, status, score_md], queue=False)
293
+ bshow.click(show_answer, [state, history, current_word, score_md],
294
+ [state, history, current_word, status, score_md], queue=False)
295
  bnext.click(next_word, [state, history, current_word, score_md],
296
  [state, history, current_word, status, score_md], queue=False)
297
  bstop.click(stop_round, [state, history, current_word, score_md],
298
  [state, history, current_word, status, score_md], queue=False)
299
 
300
+ upload.upload(load_uploaded, [upload], [status, upload_status])
301
+
302
  # ---------- Launch (blocking on Spaces) ----------
303
 
304
  if __name__ == "__main__":