Toya0421 commited on
Commit
4ebe82b
·
verified ·
1 Parent(s): 624ee76

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -133
app.py CHANGED
@@ -14,23 +14,20 @@ LOG_FILE = "reading_logs.csv"
14
 
15
  client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
16
 
17
- # --- passage.csv 読み込み(columns: passage_id, genre, text, original_lexile_score)---
18
  passages_df = pd.read_csv("passage.csv")
19
 
20
- genres = [
21
- "Literature","Science&Technology","History","Social Science&Society",
22
- "Arts&Culture","Religion&Philosophy","Lifestyle&Hobbies",
23
- "Health&Medicine","Education&Reference"
24
- ]
25
-
26
  # --- 状態変数 ---
27
  used_passages = set()
28
  current_user_id = None
29
- current_genre = None
30
  current_lexile = None
31
  action_log = [] # ページ操作ログ
32
 
33
- # --- ヘルパー関数 ---
 
 
 
 
34
  def rewrite_to_lexile(text, target_lexile):
35
  prompt = f"""
36
  Rewrite the following passage so it fits about {target_lexile} Lexile.
@@ -38,7 +35,6 @@ Rewrite the following passage so it fits about {target_lexile} Lexile.
38
  - Avoid figurative language
39
  - Use simple syntax
40
  - Output only the rewritten passage
41
-
42
  {text}
43
  """
44
  resp = client.chat.completions.create(
@@ -49,33 +45,31 @@ Rewrite the following passage so it fits about {target_lexile} Lexile.
49
  )
50
  return resp.choices[0].message.content.strip()
51
 
 
52
  def split_pages(text, words=120):
53
  w = text.split()
54
  pages = [" ".join(w[i:i+words]) for i in range(0, len(w), words)]
55
  return pages if pages else [text]
56
 
57
- def get_new_passage(same_genre=True):
58
- """同ジャンル or 別ジャンルでランダムに passage を返す (passage_id, text, original_lexile)"""
59
- global used_passages, current_genre
60
- if same_genre:
61
- df = passages_df[passages_df["genre"] == current_genre]
62
- else:
63
- df = passages_df[passages_df["genre"] != current_genre]
64
 
65
- if df.empty:
66
- return None, None, None
 
 
67
 
68
  available = [pid for pid in df["passage_id"].unique() if pid not in used_passages]
69
  if not available:
70
- # 使い切ったら既読クリアして再利用
71
  used_passages.clear()
72
  available = list(df["passage_id"].unique())
73
 
74
  pid = random.choice(available)
75
  row = df[df["passage_id"] == pid].iloc[0]
 
76
  used_passages.add(pid)
 
77
  return pid, row["text"], row.get("original_lexile_score", None)
78
 
 
79
  def save_log(entry):
80
  df = pd.DataFrame([entry])
81
  if os.path.exists(LOG_FILE):
@@ -90,40 +84,33 @@ def save_log(entry):
90
  dataset = Dataset.from_parquet(tmp_path)
91
  dataset.push_to_hub(DATASET_REPO, token=HF_TOKEN)
92
 
 
93
  # ============================
94
- # Gradio コールバック関数
95
  # ============================
96
 
97
- def start_test(student_id, genre, lexile_input):
98
- """開始:学生番号・Reading Levelの入力は必須"""
99
- global current_user_id, current_genre, current_lexile, used_passages, action_log
100
  used_passages = set()
101
  action_log = []
102
 
103
  if not student_id or str(student_id).strip() == "":
104
- # テキストとページ表示だけ空で返し、ボタンはすべて disabled
105
  return (
106
- "", # text_display
107
- "", # page_display
108
- json.dumps([]), # hidden_pages
109
- 0, # hidden_page_index
110
- "", # start_time
111
- 0, # total_pages
112
- "", None, None, # pid, orig_lex, assigned_lex
113
- gr.update(interactive=False), # prev_btn update
114
- gr.update(interactive=False), # next_btn update
115
- gr.update(interactive=False) # finish_btn update
116
  )
117
 
118
  current_user_id = str(student_id).strip()
119
- current_genre = genre
120
  current_lexile = int(lexile_input)
121
 
122
- pid, text, orig_lex = get_new_passage(same_genre=True)
123
  if text is None:
124
  return (
125
- "教材が見つかりません", "", json.dumps([]), 0, "", 0, "", None, None,
126
- gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)
 
127
  )
128
 
129
  rewritten = rewrite_to_lexile(text, current_lexile)
@@ -131,76 +118,81 @@ def start_test(student_id, genre, lexile_input):
131
  start_time = (datetime.utcnow() + timedelta(hours=9)).isoformat()
132
  total = len(pages)
133
 
134
- # ボタン状態(最初は prev 無効、 next 有効 if pages>1、 finish 有効 only if single page)
135
  prev_upd = gr.update(interactive=False)
136
- next_upd = gr.update(interactive=(total > 1))
137
- finish_upd = gr.update(interactive=(0 == total - 1)) # True if only 1 page
 
138
 
139
  return (
140
- pages[0], # text_display
141
- f"1 / {total}", # page_display
142
- json.dumps(pages, ensure_ascii=False), # hidden_pages
143
- 0, # hidden_page_index
144
- start_time, # hidden_start_time
145
- total, # hidden_total_pages
146
- pid, # hidden_passage_id
147
- orig_lex, # hidden_orig_lex
148
- current_lexile, # hidden_assigned_lex
149
  prev_upd,
150
  next_upd,
151
- finish_upd
152
  )
153
 
154
- def next_page(pages_json, current_page, total_pages):
 
155
  pages = json.loads(pages_json)
156
- if not pages:
157
- return "", "", json.dumps([]), 0, gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)
158
- # advance safely
159
  new_page = min(current_page + 1, total_pages - 1)
 
 
 
 
 
 
 
 
160
  if new_page != current_page:
161
  action_log.append({"action": "next", "time": (datetime.utcnow() + timedelta(hours=9)).isoformat()})
162
- finish_enabled = (new_page == total_pages - 1)
163
  prev_enabled = (new_page > 0)
164
- next_enabled = (new_page < total_pages - 1)
165
  return (
166
  pages[new_page],
167
  f"{new_page+1} / {total_pages}",
168
- json.dumps(pages, ensure_ascii=False),
169
  new_page,
170
  gr.update(interactive=prev_enabled),
171
- gr.update(interactive=next_enabled),
172
- gr.update(interactive=finish_enabled)
173
  )
174
 
 
175
  def prev_page(pages_json, current_page, total_pages):
176
  pages = json.loads(pages_json)
177
- if not pages:
178
- return "", "", json.dumps([]), 0, gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)
179
  new_page = max(current_page - 1, 0)
 
180
  if new_page != current_page:
181
  action_log.append({"action": "prev", "time": (datetime.utcnow() + timedelta(hours=9)).isoformat()})
182
- finish_enabled = (new_page == total_pages - 1)
183
  prev_enabled = (new_page > 0)
184
- next_enabled = (new_page < total_pages - 1)
 
185
  return (
186
  pages[new_page],
187
  f"{new_page+1} / {total_pages}",
188
- json.dumps(pages, ensure_ascii=False),
189
  new_page,
190
  gr.update(interactive=prev_enabled),
191
- gr.update(interactive=next_enabled),
192
- gr.update(interactive=finish_enabled)
193
  )
194
 
 
195
  def finish_or_retire(pages_json, current_page, pid, orig_lex, start_time, action):
196
- """action == 'finished' or 'retire'"""
197
  pages = json.loads(pages_json)
198
  now = (datetime.utcnow() + timedelta(hours=9)).isoformat()
199
 
200
- # ログ
201
  entry = {
202
  "user_id": current_user_id,
203
- "genre": current_genre,
204
  "assigned_lexile": current_lexile,
205
  "passage_id": pid,
206
  "original_lexile": orig_lex,
@@ -209,27 +201,15 @@ def finish_or_retire(pages_json, current_page, pid, orig_lex, start_time, action
209
  "actions": json.dumps(action_log, ensure_ascii=False),
210
  "result": action
211
  }
212
- save_log(entry=entry) if 'save_log' in globals() else save_log_local(entry)
213
-
214
- # 次教材を取得
215
- if action == "finished":
216
- new_pid, new_text, new_orig_lex = get_new_passage(same_genre=True)
217
- else:
218
- new_pid, new_text, new_orig_lex = get_new_passage(same_genre=False)
219
 
 
 
220
  if new_text is None:
221
- # 全部終わった or 該当なし
222
  return (
223
- "教材がありません", # text_display
224
- "", # page_display
225
- json.dumps([]), # hidden_pages
226
- 0, # page_index
227
- "", # start_time
228
- 0, # total_pages
229
- "", None, None, # pid, orig_lex, assigned_lex
230
- gr.update(interactive=False),
231
- gr.update(interactive=False),
232
- gr.update(interactive=False)
233
  )
234
 
235
  rewritten = rewrite_to_lexile(new_text, current_lexile)
@@ -237,12 +217,11 @@ def finish_or_retire(pages_json, current_page, pid, orig_lex, start_time, action
237
  new_start = (datetime.utcnow() + timedelta(hours=9)).isoformat()
238
  total = len(new_pages)
239
 
240
- # reset action log for new passage
241
  action_log.clear()
242
 
243
  prev_upd = gr.update(interactive=False)
244
- next_upd = gr.update(interactive=(total > 1))
245
- finish_upd = gr.update(interactive=(0 == total - 1))
246
 
247
  return (
248
  new_pages[0],
@@ -254,26 +233,21 @@ def finish_or_retire(pages_json, current_page, pid, orig_lex, start_time, action
254
  new_pid,
255
  new_orig_lex,
256
  current_lexile,
257
- prev_upd, next_upd, finish_upd
 
 
258
  )
259
 
260
- # fallback local saver if earlier definitions differ
261
- def save_log_local(entry):
262
- df = pd.DataFrame([entry])
263
- if os.path.exists(LOG_FILE):
264
- df.to_csv(LOG_FILE, mode="a", index=False, header=False)
265
- else:
266
- df.to_csv(LOG_FILE, index=False)
267
 
268
  # ============================
269
- # UI 部分(Lexile 入力は復活)
270
  # ============================
 
271
  with gr.Blocks() as demo:
272
- gr.Markdown("# 📚 Reading Exercise")
273
 
274
  student_id_input = gr.Textbox(label="学生番号(必須)")
275
  lexile_input = gr.Number(label="あなたの Reading Level(例: 3)", precision=0)
276
- genre_input = gr.Dropdown(choices=genres, label="ジャンル(1つ選択)")
277
 
278
  start_btn = gr.Button("スタート")
279
 
@@ -289,70 +263,67 @@ with gr.Blocks() as demo:
289
  hidden_orig_lex = gr.Textbox(visible=False)
290
  hidden_assigned_lex = gr.Textbox(visible=False)
291
 
292
- # buttons row (always visible)
293
  with gr.Row():
294
  prev_btn = gr.Button("◀ 前へ", interactive=False)
295
  next_btn = gr.Button("次へ ▶", interactive=False)
296
-
297
- finish_btn = gr.Button("読み終えた", interactive=False)
298
  retire_btn = gr.Button("リタイア")
299
 
300
- # Start click
301
  start_btn.click(
302
  fn=start_test,
303
- inputs=[student_id_input, genre_input, lexile_input],
304
  outputs=[
305
  text_display, page_display,
306
  hidden_pages, hidden_page_index,
307
- hidden_start_time, hidden_total_pages, hidden_passage_id,
308
- hidden_orig_lex, hidden_assigned_lex,
309
- prev_btn, next_btn, finish_btn
310
  ]
311
  )
312
 
313
- # Next / Prev click
314
  next_btn.click(
315
  fn=next_page,
316
- inputs=[hidden_pages, hidden_page_index, hidden_total_pages],
 
 
 
 
317
  outputs=[
318
  text_display, page_display,
319
  hidden_pages, hidden_page_index,
320
- prev_btn, next_btn, finish_btn
321
  ]
322
  )
323
 
 
324
  prev_btn.click(
325
  fn=prev_page,
326
  inputs=[hidden_pages, hidden_page_index, hidden_total_pages],
327
  outputs=[
328
  text_display, page_display,
329
  hidden_pages, hidden_page_index,
330
- prev_btn, next_btn, finish_btn
331
- ]
332
- )
333
-
334
- # Finish / Retire click
335
- finish_btn.click(
336
- fn=lambda p, i, pid, o, st: finish_or_retire(p, i, pid, o, st, "finished"),
337
- inputs=[hidden_pages, hidden_page_index, hidden_passage_id, hidden_orig_lex, hidden_start_time],
338
- outputs=[
339
- text_display, page_display,
340
- hidden_pages, hidden_page_index,
341
- hidden_start_time, hidden_total_pages, hidden_passage_id,
342
- hidden_orig_lex, hidden_assigned_lex,
343
- prev_btn, next_btn, finish_btn
344
  ]
345
  )
346
 
 
347
  retire_btn.click(
348
- fn=lambda p, i, pid, o, st: finish_or_retire(p, i, pid, o, st, "retire"),
349
- inputs=[hidden_pages, hidden_page_index, hidden_passage_id, hidden_orig_lex, hidden_start_time],
 
 
 
 
 
 
350
  outputs=[
351
  text_display, page_display,
352
  hidden_pages, hidden_page_index,
353
- hidden_start_time, hidden_total_pages, hidden_passage_id,
354
- hidden_orig_lex, hidden_assigned_lex,
355
- prev_btn, next_btn, finish_btn
356
  ]
357
  )
358
 
 
14
 
15
  client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
16
 
17
+ # --- passage.csv 読み込み ---
18
  passages_df = pd.read_csv("passage.csv")
19
 
 
 
 
 
 
 
20
  # --- 状態変数 ---
21
  used_passages = set()
22
  current_user_id = None
 
23
  current_lexile = None
24
  action_log = [] # ページ操作ログ
25
 
26
+
27
+ # ============================
28
+ # ヘルパー
29
+ # ============================
30
+
31
  def rewrite_to_lexile(text, target_lexile):
32
  prompt = f"""
33
  Rewrite the following passage so it fits about {target_lexile} Lexile.
 
35
  - Avoid figurative language
36
  - Use simple syntax
37
  - Output only the rewritten passage
 
38
  {text}
39
  """
40
  resp = client.chat.completions.create(
 
45
  )
46
  return resp.choices[0].message.content.strip()
47
 
48
+
49
  def split_pages(text, words=120):
50
  w = text.split()
51
  pages = [" ".join(w[i:i+words]) for i in range(0, len(w), words)]
52
  return pages if pages else [text]
53
 
 
 
 
 
 
 
 
54
 
55
+ def get_new_passage_random():
56
+ """ジャンル関係なくランダムに取得"""
57
+ global used_passages
58
+ df = passages_df
59
 
60
  available = [pid for pid in df["passage_id"].unique() if pid not in used_passages]
61
  if not available:
 
62
  used_passages.clear()
63
  available = list(df["passage_id"].unique())
64
 
65
  pid = random.choice(available)
66
  row = df[df["passage_id"] == pid].iloc[0]
67
+
68
  used_passages.add(pid)
69
+
70
  return pid, row["text"], row.get("original_lexile_score", None)
71
 
72
+
73
  def save_log(entry):
74
  df = pd.DataFrame([entry])
75
  if os.path.exists(LOG_FILE):
 
84
  dataset = Dataset.from_parquet(tmp_path)
85
  dataset.push_to_hub(DATASET_REPO, token=HF_TOKEN)
86
 
87
+
88
  # ============================
89
+ # コールバック
90
  # ============================
91
 
92
+ def start_test(student_id, lexile_input):
93
+ """開始:ジャンル選択なし版"""
94
+ global current_user_id, current_lexile, used_passages, action_log
95
  used_passages = set()
96
  action_log = []
97
 
98
  if not student_id or str(student_id).strip() == "":
 
99
  return (
100
+ "", "", json.dumps([]), 0, "",
101
+ 0, "", None, None,
102
+ gr.update(interactive=False), gr.update(interactive=False), "次へ ▶"
 
 
 
 
 
 
 
103
  )
104
 
105
  current_user_id = str(student_id).strip()
 
106
  current_lexile = int(lexile_input)
107
 
108
+ pid, text, orig_lex = get_new_passage_random()
109
  if text is None:
110
  return (
111
+ "教材が見つかりません", "", json.dumps([]), 0, "",
112
+ 0, "", None, None,
113
+ gr.update(interactive=False), gr.update(interactive=False), "次へ ▶"
114
  )
115
 
116
  rewritten = rewrite_to_lexile(text, current_lexile)
 
118
  start_time = (datetime.utcnow() + timedelta(hours=9)).isoformat()
119
  total = len(pages)
120
 
 
121
  prev_upd = gr.update(interactive=False)
122
+ next_upd = gr.update(interactive=True)
123
+
124
+ next_label = "読み終えた" if total == 1 else "次へ ▶"
125
 
126
  return (
127
+ pages[0],
128
+ f"1 / {total}",
129
+ json.dumps(pages, ensure_ascii=False),
130
+ 0,
131
+ start_time,
132
+ total,
133
+ pid,
134
+ orig_lex,
135
+ current_lexile,
136
  prev_upd,
137
  next_upd,
138
+ next_label
139
  )
140
 
141
+
142
+ def next_page(pages_json, current_page, total_pages, pid, orig_lex, start_time):
143
  pages = json.loads(pages_json)
 
 
 
144
  new_page = min(current_page + 1, total_pages - 1)
145
+
146
+ # 最終ページ → 自動で finish
147
+ if new_page == total_pages - 1:
148
+ return finish_or_retire(
149
+ pages_json, current_page, pid, orig_lex, start_time, "finished"
150
+ )
151
+
152
+ # 通常のページ移動
153
  if new_page != current_page:
154
  action_log.append({"action": "next", "time": (datetime.utcnow() + timedelta(hours=9)).isoformat()})
155
+
156
  prev_enabled = (new_page > 0)
157
+
158
  return (
159
  pages[new_page],
160
  f"{new_page+1} / {total_pages}",
161
+ json.dumps(pages),
162
  new_page,
163
  gr.update(interactive=prev_enabled),
164
+ gr.update(interactive=True),
165
+ "次へ ▶"
166
  )
167
 
168
+
169
  def prev_page(pages_json, current_page, total_pages):
170
  pages = json.loads(pages_json)
 
 
171
  new_page = max(current_page - 1, 0)
172
+
173
  if new_page != current_page:
174
  action_log.append({"action": "prev", "time": (datetime.utcnow() + timedelta(hours=9)).isoformat()})
175
+
176
  prev_enabled = (new_page > 0)
177
+ next_label = "読み終えた" if new_page == total_pages - 1 else "次へ ▶"
178
+
179
  return (
180
  pages[new_page],
181
  f"{new_page+1} / {total_pages}",
182
+ json.dumps(pages),
183
  new_page,
184
  gr.update(interactive=prev_enabled),
185
+ gr.update(interactive=True),
186
+ next_label
187
  )
188
 
189
+
190
  def finish_or_retire(pages_json, current_page, pid, orig_lex, start_time, action):
 
191
  pages = json.loads(pages_json)
192
  now = (datetime.utcnow() + timedelta(hours=9)).isoformat()
193
 
 
194
  entry = {
195
  "user_id": current_user_id,
 
196
  "assigned_lexile": current_lexile,
197
  "passage_id": pid,
198
  "original_lexile": orig_lex,
 
201
  "actions": json.dumps(action_log, ensure_ascii=False),
202
  "result": action
203
  }
204
+ save_log(entry)
 
 
 
 
 
 
205
 
206
+ # 新しい教材取得
207
+ new_pid, new_text, new_orig_lex = get_new_passage_random()
208
  if new_text is None:
 
209
  return (
210
+ "教材がありません", "", json.dumps([]), 0, "",
211
+ 0, "", None, None,
212
+ gr.update(interactive=False), gr.update(interactive=False), "次へ ▶"
 
 
 
 
 
 
 
213
  )
214
 
215
  rewritten = rewrite_to_lexile(new_text, current_lexile)
 
217
  new_start = (datetime.utcnow() + timedelta(hours=9)).isoformat()
218
  total = len(new_pages)
219
 
 
220
  action_log.clear()
221
 
222
  prev_upd = gr.update(interactive=False)
223
+ next_upd = gr.update(interactive=True)
224
+ next_label = "読み終えた" if total == 1 else "次へ ▶"
225
 
226
  return (
227
  new_pages[0],
 
233
  new_pid,
234
  new_orig_lex,
235
  current_lexile,
236
+ prev_upd,
237
+ next_upd,
238
+ next_label
239
  )
240
 
 
 
 
 
 
 
 
241
 
242
  # ============================
243
+ # UI
244
  # ============================
245
+
246
  with gr.Blocks() as demo:
247
+ gr.Markdown("# 📚 Reading Exercise(ジャンル選択なし版)")
248
 
249
  student_id_input = gr.Textbox(label="学生番号(必須)")
250
  lexile_input = gr.Number(label="あなたの Reading Level(例: 3)", precision=0)
 
251
 
252
  start_btn = gr.Button("スタート")
253
 
 
263
  hidden_orig_lex = gr.Textbox(visible=False)
264
  hidden_assigned_lex = gr.Textbox(visible=False)
265
 
 
266
  with gr.Row():
267
  prev_btn = gr.Button("◀ 前へ", interactive=False)
268
  next_btn = gr.Button("次へ ▶", interactive=False)
269
+
 
270
  retire_btn = gr.Button("リタイア")
271
 
272
+ # Start
273
  start_btn.click(
274
  fn=start_test,
275
+ inputs=[student_id_input, lexile_input],
276
  outputs=[
277
  text_display, page_display,
278
  hidden_pages, hidden_page_index,
279
+ hidden_start_time, hidden_total_pages,
280
+ hidden_passage_id, hidden_orig_lex, hidden_assigned_lex,
281
+ prev_btn, next_btn, next_btn
282
  ]
283
  )
284
 
285
+ # Next
286
  next_btn.click(
287
  fn=next_page,
288
+ inputs=[
289
+ hidden_pages, hidden_page_index,
290
+ hidden_total_pages, hidden_passage_id,
291
+ hidden_orig_lex, hidden_start_time
292
+ ],
293
  outputs=[
294
  text_display, page_display,
295
  hidden_pages, hidden_page_index,
296
+ prev_btn, next_btn, next_btn
297
  ]
298
  )
299
 
300
+ # Prev
301
  prev_btn.click(
302
  fn=prev_page,
303
  inputs=[hidden_pages, hidden_page_index, hidden_total_pages],
304
  outputs=[
305
  text_display, page_display,
306
  hidden_pages, hidden_page_index,
307
+ prev_btn, next_btn, next_btn
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  ]
309
  )
310
 
311
+ # Retire
312
  retire_btn.click(
313
+ fn=lambda p, i, pid, o, st: finish_or_retire(
314
+ p, i, pid, o, st, "retire"
315
+ ),
316
+ inputs=[
317
+ hidden_pages, hidden_page_index,
318
+ hidden_passage_id, hidden_orig_lex,
319
+ hidden_start_time
320
+ ],
321
  outputs=[
322
  text_display, page_display,
323
  hidden_pages, hidden_page_index,
324
+ hidden_start_time, hidden_total_pages,
325
+ hidden_passage_id, hidden_orig_lex,
326
+ hidden_assigned_lex, prev_btn, next_btn, next_btn
327
  ]
328
  )
329