GeetaAIVisionary commited on
Commit
eda4493
·
verified ·
1 Parent(s): ea612ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -191
app.py CHANGED
@@ -1,204 +1,108 @@
1
- # app.py
2
- # CLI-like Spelling Bee Tutor as a simple Gradio UI (no LLMs, all offline)
3
- import gradio as gr
4
- import pandas as pd
5
  from pathlib import Path
 
 
6
 
7
- WORDS_PATH = Path("words.csv")
8
-
9
- def _load_words():
10
- """Load CSV; ensure we have a 'word' column; create optional columns if missing.
11
- Also compute a difficulty score (fallback = word length)."""
12
- if not WORDS_PATH.exists():
13
- # return empty df with expected columns
14
- return pd.DataFrame(columns=["word", "definition", "origin", "sentence", "difficulty_score"])
15
-
16
- df = pd.read_csv(WORDS_PATH)
17
 
18
- # normalize required/optional columns
19
- if "word" not in df.columns:
20
- # treat first column as 'word'
21
- df = df.rename(columns={df.columns[0]: "word"})
22
 
23
- for col in ["definition", "origin", "sentence"]:
24
- if col not in df.columns:
25
- df[col] = ""
 
 
 
 
 
26
 
27
- # difficulty: use numeric 'difficulty' if present; else use length
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  if "difficulty" in df.columns:
29
  ds = pd.to_numeric(df["difficulty"], errors="coerce").fillna(0)
30
  df["difficulty_score"] = ds
31
  else:
32
  df["difficulty_score"] = df["word"].astype(str).str.len()
33
 
34
- # sort hardest -> easiest
35
  df = df.sort_values("difficulty_score", ascending=False).reset_index(drop=True)
36
  return df
37
-
38
- DF = _load_words()
39
-
40
- def _summary(state):
41
- if not state:
42
- return "No round active."
43
- n = state["n"]
44
- score = state["score"]
45
- return f"Round complete. Score this round: {score}/{n}."
46
-
47
- def start_quiz(n_words, state):
48
- df = DF
49
- if df.empty:
50
- # disable input if no words
51
- return (state,
52
- "words.csv not found or empty.",
53
- gr.update(value="", interactive=False),
54
- "Please add a words.csv with at least one 'word' column.",
55
- "Score: 0/0",
56
- gr.update(value="", interactive=False))
57
-
58
- try:
59
- n = int(n_words or 5)
60
- except Exception:
61
- n = 5
62
-
63
- # clip to available size
64
- n = max(1, min(n, len(df)))
65
-
66
- # take top-n hardest words
67
- block = df.head(n).reset_index(drop=True)
68
-
69
- s = {
70
- "i": 0,
71
- "n": n,
72
- "score": 0,
73
- "words": block["word"].astype(str).tolist(),
74
- "defs": block["definition"].astype(str).tolist(),
75
- "orig": block["origin"].astype(str).tolist(),
76
- "sent": block["sentence"].astype(str).tolist(),
77
- }
78
-
79
- current = s["words"][0]
80
- hist = f"Okay! We'll do {n} words this round, hardest → easiest.\nSpell this word: {current}"
81
- status = "Type your spelling attempt, or click definition/origin/sentence."
82
- return s, hist, gr.update(value=current, interactive=False), status, f"Score: 0/{n}", gr.update(value="", interactive=True)
83
-
84
- def _check_state(state):
85
- if not state or "words" not in state or state["i"] >= state["n"]:
86
- return False
87
- return True
88
-
89
- def check_attempt(state, attempt, history, current_word, score_md):
90
- if not _check_state(state):
91
- return state, history, current_word, "No round active. Click Start.", score_md, gr.update(value="")
92
- attempt = (attempt or "").strip()
93
- if not attempt:
94
- return state, history, current_word, "Type your attempt first.", score_md, gr.update(value="")
95
-
96
- target = state["words"][state["i"]]
97
- if attempt.lower() == target.lower():
98
- state["score"] += 1
99
- msg = "✅ Correct!"
100
- else:
101
- msg = "❌ Not quite. Try again or ask for definition/origin/sentence."
102
-
103
- # append to history
104
- history = f"{history}\nYou: {attempt}\nTutor: {msg}"
105
- score_md = f"Score: {state['score']}/{state['n']}"
106
-
107
- return state, history, current_word, msg, score_md, gr.update(value="")
108
-
109
- def _safe_text(val, fallback="Not available."):
110
- v = (val or "").strip()
111
- return v if v else fallback
112
-
113
- def show_def(state, history, current_word, score_md):
114
- if not _check_state(state):
115
- return state, history, current_word, "No round active. Click Start.", score_md
116
- i = state["i"]
117
- word = state["words"][i]
118
- text = _safe_text(state["defs"][i])
119
- history = f"{history}\nTutor (definition of {word}): {text}"
120
- return state, history, current_word, f"Definition of {word}: {text}", score_md
121
-
122
- def show_origin(state, history, current_word, score_md):
123
- if not _check_state(state):
124
- return state, history, current_word, "No round active. Click Start.", score_md
125
- i = state["i"]
126
- word = state["words"][i]
127
- text = _safe_text(state["orig"][i])
128
- history = f"{history}\nTutor (origin of {word}): {text}"
129
- return state, history, current_word, f"Origin of {word}: {text}", score_md
130
-
131
- def show_sentence(state, history, current_word, score_md):
132
- if not _check_state(state):
133
- return state, history, current_word, "No round active. Click Start.", score_md
134
- i = state["i"]
135
- word = state["words"][i]
136
- text = _safe_text(state["sent"][i])
137
- history = f"{history}\nTutor (sentence with {word}): {text}"
138
- return state, history, current_word, f"Sentence: {text}", score_md
139
-
140
- def next_word(state, history, current_word, score_md):
141
- if not _check_state(state):
142
- return state, history, current_word, "No round active. Click Start.", score_md
143
- state["i"] += 1
144
- if state["i"] >= state["n"]:
145
- # finished
146
- summary = _summary(state)
147
- history = f"{history}\n{summary}"
148
- return {}, history, gr.update(value="", interactive=False), summary, f"Score: {state['score']}/{state['n']}"
149
- # go to next word
150
- w = state["words"][state["i"]]
151
- history = f"{history}\nNext word: {w}"
152
- status = "Type your spelling attempt, or click definition/origin/sentence."
153
- return state, history, gr.update(value=w, interactive=False), status, f"Score: {state['score']}/{state['n']}"
154
-
155
- def stop_round(state, history, current_word, score_md):
156
- if not state:
157
- return {}, history, current_word, "Stopped.", score_md
158
- summary = _summary(state)
159
- history = f"{history}\n{summary}"
160
- return {}, history, gr.update(value="", interactive=False), "Stopped.", f"Score: {state['score']}/{state['n']}"
161
-
162
- with gr.Blocks() as demo:
163
- gr.Markdown("# NeMo Guardrails Demo (starter UI)\n**CLI-style spelling quiz** — works offline from `words.csv`.")
164
-
165
- with gr.Row():
166
- n_words = gr.Number(value=5, precision=0, label="Words this round")
167
- start = gr.Button("Start quiz")
168
-
169
- current_word = gr.Textbox(label="Spell this word", interactive=False)
170
- attempt = gr.Textbox(label="Your attempt")
171
- with gr.Row():
172
- check = gr.Button("Check")
173
- bdef = gr.Button("definition")
174
- borg = gr.Button("origin")
175
- bsent = gr.Button("sentence")
176
- bnext = gr.Button("next")
177
- bstop = gr.Button("stop")
178
-
179
- status = gr.Markdown("")
180
- score_md = gr.Markdown("Score: 0/0")
181
- history = gr.Textbox(label="History", lines=14)
182
-
183
- state = gr.State({})
184
-
185
- # wire events
186
- start.click(start_quiz, [n_words, state],
187
- [state, history, current_word, status, score_md, attempt])
188
-
189
- check.click(check_attempt, [state, attempt, history, current_word, score_md],
190
- [state, history, current_word, status, score_md, attempt])
191
-
192
- bdef.click(show_def, [state, history, current_word, score_md],
193
- [state, history, current_word, status, score_md], queue=False)
194
- borg.click(show_origin, [state, history, current_word, score_md],
195
- [state, history, current_word, status, score_md], queue=False)
196
- bsent.click(show_sentence, [state, history, current_word, score_md],
197
- [state, history, current_word, status, score_md], queue=False)
198
- bnext.click(next_word, [state, history, current_word, score_md],
199
- [state, history, current_word, status, score_md], queue=False)
200
- bstop.click(stop_round, [state, history, current_word, score_md],
201
- [state, history, current_word, status, score_md], queue=False)
202
-
203
- if __name__ == "__main__":
204
- demo.launch()
 
 
 
 
 
1
  from pathlib import Path
2
+ import pandas as pd
3
+ import csv
4
 
5
+ # make path robust in Spaces
6
+ WORDS_PATH = Path(__file__).parent / "words.csv"
 
 
 
 
 
 
 
 
7
 
8
+ EXPECTED_COLS = ["word", "difficulty", "definition", "origin", "sentence"]
 
 
 
9
 
10
+ def _load_words(path: Path = WORDS_PATH) -> pd.DataFrame:
11
+ """Load a possibly-messy CSV:
12
+ - tolerate commas inside sentence/origin
13
+ - accept exact 5 columns or more (extras glued into sentence)
14
+ - create missing optional columns
15
+ """
16
+ if not path.exists():
17
+ return pd.DataFrame(columns=["word", "definition", "origin", "sentence", "difficulty_score"])
18
 
19
+ rows = []
20
+ with open(path, "r", encoding="utf-8", newline="") as f:
21
+ # try a forgiving CSV read first (honors quotes like "…")
22
+ try:
23
+ df_try = pd.read_csv(
24
+ f,
25
+ engine="python",
26
+ quotechar='"',
27
+ escapechar='\\',
28
+ dtype=str,
29
+ keep_default_na=False
30
+ )
31
+ # If it parsed to 5+ columns, normalize below
32
+ df = df_try
33
+ except Exception:
34
+ # fall back to manual glue if pandas fails
35
+ f.seek(0)
36
+ reader = csv.reader(f)
37
+ header = next(reader, None)
38
+ header_ok = header and [h.strip().lower() for h in header[:5]] == EXPECTED_COLS
39
+ if not header_ok:
40
+ # treat first line as data
41
+ f.seek(0)
42
+ reader = csv.reader(f)
43
+
44
+ for row in reader:
45
+ if not row or all((x is None or str(x).strip() == "") for x in row):
46
+ continue
47
+ # expect at least 5 fields; if more, glue extras into sentence
48
+ if len(row) < 5:
49
+ # pad empty fields to avoid crash
50
+ row = row + [""] * (5 - len(row))
51
+ base = row[:4]
52
+ sentence = ",".join(row[4:]) # glue any extras back
53
+ rows.append([*(str(x).strip() for x in base), sentence.strip()])
54
+ df = pd.DataFrame(rows, columns=EXPECTED_COLS)
55
+
56
+ # --- Normalize columns ---
57
+ cols = [c.strip().lower() for c in df.columns]
58
+ mapper = {c: c.strip().lower() for c in df.columns}
59
+ df = df.rename(columns=mapper)
60
+
61
+ # If there are more than 5 columns, rebuild to our schema
62
+ if df.shape[1] >= 5:
63
+ # take first four known fields if present, then glue rest into sentence
64
+ def pick(colname, default=""):
65
+ return df[colname] if colname in df.columns else default
66
+ base_df = pd.DataFrame({
67
+ "word": pick("word", ""),
68
+ "difficulty": pick("difficulty", ""),
69
+ "definition": pick("definition", ""),
70
+ "origin": pick("origin", ""),
71
+ })
72
+ # sentence = existing sentence + any extra cols joined by commas
73
+ extras = []
74
+ if "sentence" in df.columns:
75
+ extras.append(df["sentence"].astype(str))
76
+ extra_cols = [c for c in df.columns if c not in {"word","difficulty","definition","origin","sentence"}]
77
+ for c in extra_cols:
78
+ extras.append(df[c].astype(str))
79
+ if extras:
80
+ sentence_series = extras[0]
81
+ for s in extras[1:]:
82
+ sentence_series = sentence_series.str.cat(s, sep=",", na_rep="")
83
+ else:
84
+ sentence_series = pd.Series([""] * len(base_df))
85
+ base_df["sentence"] = sentence_series
86
+ df = base_df
87
+ else:
88
+ # ensure missing optional cols exist
89
+ for c in ["definition", "origin", "sentence", "difficulty"]:
90
+ if c not in df.columns:
91
+ df[c] = ""
92
+
93
+ # Clean and type
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
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
+ # sort hardest easiest
107
  df = df.sort_values("difficulty_score", ascending=False).reset_index(drop=True)
108
  return df