Toya0421 commited on
Commit
88fcf15
·
verified ·
1 Parent(s): 438763f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +155 -276
app.py CHANGED
@@ -1,308 +1,187 @@
1
  import gradio as gr
2
- from openai import OpenAI
3
- from datasets import Dataset
4
- from datetime import datetime, timedelta
5
  import pandas as pd
6
- import time, os, random, uuid, tempfile, json
7
-
8
- # --- API設定 ---
9
- API_KEY = os.getenv("API_KEY")
10
- BASE_URL = "https://openrouter.ai/api/v1"
11
- HF_TOKEN = os.getenv("HF_TOKEN")
12
- DATASET_REPO = "Toya0421/reading_exercise_logging"
13
- LOG_FILE = "reading_logs.csv"
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
- 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
- def rewrite_to_lexile(text, target_lexile):
34
- prompt = f"""
35
- Rewrite the following passage so it fits about {target_lexile} Lexile.
36
- - Keep original meaning and length
37
- - Avoid figurative language
38
- - Use simple syntax
39
- - Output only the rewritten passage
40
- {text}
41
- """
42
- resp = client.chat.completions.create(
43
- model="google/gemma-3-27b-it:free",
44
- messages=[{"role": "user", "content": prompt}],
45
- temperature=0.4,
46
- max_tokens=1000
47
- )
48
- return resp.choices[0].message.content.strip()
49
-
50
- def split_pages(text, words=120):
51
- w = text.split()
52
- return [" ".join(w[i:i+words]) for i in range(0, len(w), words)]
53
-
54
- def get_new_passage(same_genre=True):
55
- global used_passages, current_genre
56
- df = passages_df[passages_df["genre"] == current_genre] if same_genre else passages_df[passages_df["genre"] != current_genre]
57
-
58
- if len(df) == 0:
59
- return None, None, None
60
-
61
- available = [pid for pid in df["passage_id"] if pid not in used_passages]
62
-
63
- if not available:
64
- used_passages.clear()
65
- available = list(df["passage_id"])
66
-
67
- pid = random.choice(available)
68
- row = df[df["passage_id"] == pid].iloc[0]
69
- used_passages.add(pid)
70
- return pid, row["text"], row["original_lexile_score"]
71
-
72
- def save_log(entry):
73
- df = pd.DataFrame([entry])
74
- if os.path.exists(LOG_FILE):
75
- df.to_csv(LOG_FILE, mode="a", index=False, header=False)
76
- else:
77
- df.to_csv(LOG_FILE, index=False)
78
-
79
- all_logs = pd.read_csv(LOG_FILE)
80
- tmp_dir = tempfile.mkdtemp()
81
- tmp_path = os.path.join(tmp_dir, "data.parquet")
82
- all_logs.to_parquet(tmp_path)
83
- dataset = Dataset.from_parquet(tmp_path)
84
- dataset.push_to_hub(DATASET_REPO, token=HF_TOKEN)
85
-
86
- # ============================
87
- # Gradio 動作関数
88
- # ============================
89
-
90
- def start_test(student_id, genre, lexile):
91
- global current_user_id, current_genre, current_lexile, used_passages, action_log
92
- used_passages = set()
93
- action_log = []
94
-
95
- if not student_id or student_id.strip() == "":
96
- return ("", "", "", 0, False, False, "", 0, "", "", 0)
97
-
98
- current_user_id = student_id.strip()
99
- current_genre = genre
100
- current_lexile = int(lexile)
101
-
102
- pid, text, orig_lex = get_new_passage(same_genre=True)
103
- if text is None:
104
- return ("教材がありません", "", "", 0, False, False, "", 0, "", "", 0)
105
-
106
- rewritten = rewrite_to_lexile(text, current_lexile)
107
- pages = split_pages(rewritten)
108
- start_time = (datetime.utcnow() + timedelta(hours=9)).isoformat()
109
-
110
- finish_enabled = (1 == len(pages))
111
- prev_enabled = False
112
- next_enabled = (len(pages) > 1)
113
-
114
- return (
115
- pages[0],
116
- f"1 / {len(pages)}",
117
- json.dumps(pages, ensure_ascii=False),
118
- 0,
119
- finish_enabled,
120
- prev_enabled,
121
- next_enabled,
122
- start_time,
123
- len(pages),
124
- pid,
125
- orig_lex,
126
- current_lexile
127
- )
128
 
129
- def next_page(pages_json, current_page, total_pages):
130
- pages = json.loads(pages_json)
131
-
132
- if current_page < len(pages) - 1:
133
- current_page += 1
134
- action_log.append({"action": "next", "time": (datetime.utcnow()+timedelta(hours=9)).isoformat()})
135
-
136
- finish_enabled = (current_page == total_pages - 1)
137
- prev_enabled = (current_page > 0)
138
- next_enabled = (current_page < total_pages - 1)
139
-
140
- return (
141
- pages[current_page],
142
- f"{current_page+1} / {total_pages}",
143
- json.dumps(pages, ensure_ascii=False),
144
- current_page,
145
- finish_enabled,
146
- prev_enabled,
147
- next_enabled
148
- )
149
 
150
- def prev_page(pages_json, current_page, total_pages):
151
- pages = json.loads(pages_json)
152
-
153
- if current_page > 0:
154
- current_page -= 1
155
- action_log.append({"action": "prev", "time": (datetime.utcnow()+timedelta(hours=9)).isoformat()})
156
-
157
- finish_enabled = (current_page == total_pages - 1)
158
- prev_enabled = (current_page > 0)
159
- next_enabled = (current_page < total_pages - 1)
160
-
161
- return (
162
- pages[current_page],
163
- f"{current_page+1} / {total_pages}",
164
- json.dumps(pages, ensure_ascii=False),
165
- current_page,
166
- finish_enabled,
167
- prev_enabled,
168
- next_enabled
169
- )
170
 
171
- def finish_or_retire(pages_json, current_page, pid, orig_lex, start_time, action):
172
- global action_log
173
- pages = json.loads(pages_json)
174
- now = (datetime.utcnow()+timedelta(hours=9)).isoformat()
175
-
176
- entry = {
177
- "user_id": current_user_id,
178
- "genre": current_genre,
179
- "lexile_assigned": current_lexile,
180
- "passage_id": pid,
181
- "original_lexile": orig_lex,
182
- "start_time": start_time,
183
- "finished_time": now,
184
- "actions": json.dumps(action_log, ensure_ascii=False),
185
- "result": action
186
  }
187
- save_log(entry)
188
-
189
- if action == "finished":
190
- new_pid, new_text, new_orig_lex = get_new_passage(same_genre=True)
191
- else:
192
- new_pid, new_text, new_orig_lex = get_new_passage(same_genre=False)
193
-
194
- if new_text is None:
195
- return ("教材がありません", "", "", 0, False, False, False, "", 0, "", "", current_lexile)
196
-
197
- rewritten = rewrite_to_lexile(new_text, current_lexile)
198
- new_pages = split_pages(rewritten)
199
- new_start = (datetime.utcnow()+timedelta(hours=9)).isoformat()
200
- action_log = []
201
-
202
- finish_enabled = (1 == len(new_pages))
203
- prev_enabled = False
204
- next_enabled = (len(new_pages) > 1)
205
-
206
- return (
207
- new_pages[0],
208
- f"1 / {len(new_pages)}",
209
- json.dumps(new_pages, ensure_ascii=False),
210
- 0,
211
- finish_enabled,
212
- prev_enabled,
213
- next_enabled,
214
- new_start,
215
- len(new_pages),
216
- new_pid,
217
- new_orig_lex,
218
- current_lexile
219
- )
220
 
221
- # ============================
222
- # UI
223
- # ============================
224
 
225
- with gr.Blocks() as demo:
226
- gr.Markdown("## 📚 Reading Exercise")
 
227
 
228
- student_id_input = gr.Textbox(label="学生番号")
229
- lexile_input = gr.Number(label="Lexile(例: 900)")
230
- genre_input = gr.Dropdown(choices=genres, label="ジャンル")
231
 
232
- start_btn = gr.Button("スタート")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
- text_display = gr.Textbox(label="教材", lines=15, interactive=False)
235
- page_display = gr.Textbox(label="進行状況", lines=1, interactive=False)
236
 
237
- hidden_pages = gr.Textbox(visible=False)
238
- hidden_page_index = gr.Number(visible=False)
239
- hidden_start_time = gr.Textbox(visible=False)
240
- hidden_total_pages = gr.Number(visible=False)
241
- hidden_passage_id = gr.Textbox(visible=False)
242
- hidden_orig_lex = gr.Textbox(visible=False)
243
- hidden_assigned_lex = gr.Textbox(visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
- with gr.Row():
246
- prev_btn = gr.Button("前へ", interactive=False)
247
- next_btn = gr.Button("次へ", interactive=False)
248
 
249
- finish_btn = gr.Button("読み終えた", interactive=False)
 
 
250
  retire_btn = gr.Button("リタイア")
251
 
 
252
  start_btn.click(
253
- fn=start_test,
254
- inputs=[student_id_input, genre_input, lexile_input],
255
- outputs=[
256
- text_display, page_display,
257
- hidden_pages, hidden_page_index,
258
- finish_btn, prev_btn, next_btn,
259
- hidden_start_time, hidden_total_pages,
260
- hidden_passage_id, hidden_orig_lex, hidden_assigned_lex
261
- ]
262
  )
263
 
264
  next_btn.click(
265
- fn=next_page,
266
- inputs=[hidden_pages, hidden_page_index, hidden_total_pages],
267
- outputs=[
268
- text_display, page_display,
269
- hidden_pages, hidden_page_index,
270
- finish_btn, prev_btn, next_btn
271
- ]
272
  )
273
 
274
  prev_btn.click(
275
- fn=prev_page,
276
- inputs=[hidden_pages, hidden_page_index, hidden_total_pages],
277
- outputs=[
278
- text_display, page_display,
279
- hidden_pages, hidden_page_index,
280
- finish_btn, prev_btn, next_btn
281
- ]
282
  )
283
 
284
  finish_btn.click(
285
- fn=lambda p, i, pid, o, st: finish_or_retire(p, i, pid, o, st, "finished"),
286
- inputs=[hidden_pages, hidden_page_index, hidden_passage_id, hidden_orig_lex, hidden_start_time],
287
- outputs=[
288
- text_display, page_display,
289
- hidden_pages, hidden_page_index,
290
- finish_btn, prev_btn, next_btn,
291
- hidden_start_time, hidden_total_pages,
292
- hidden_passage_id, hidden_orig_lex, hidden_assigned_lex
293
- ]
294
  )
295
 
296
  retire_btn.click(
297
- fn=lambda p, i, pid, o, st: finish_or_retire(p, i, pid, o, st, "retire"),
298
- inputs=[hidden_pages, hidden_page_index, hidden_passage_id, hidden_orig_lex, hidden_start_time],
299
- outputs=[
300
- text_display, page_display,
301
- hidden_pages, hidden_page_index,
302
- finish_btn, prev_btn, next_btn,
303
- hidden_start_time, hidden_total_pages,
304
- hidden_passage_id, hidden_orig_lex, hidden_assigned_lex
305
- ]
306
  )
307
 
308
  demo.launch()
 
1
  import gradio as gr
 
 
 
2
  import pandas as pd
3
+ import time
4
+ import random
5
+ from datasets import load_dataset, Dataset
6
+
7
+ # --- Load passages CSV ---
8
+ try:
9
+ passages_df = pd.read_csv("passage.csv")
10
+ except:
11
+ passages_df = pd.DataFrame(columns=["genre", "passage_id", "page", "text"])
12
+
13
+ # --- Logging dataset ---
14
+ log_repo = "Toya0421/reading_exercise_logging"
15
+
16
+
17
+ def start_test(student_id, selected_genre):
18
+ if not student_id:
19
+ return gr.update(value="⚠️ 学籍番号を入力してください", visible=True), gr.update(visible=False)
20
+
21
+ # そのジャンルから教材リストを抽出
22
+ genre_passages = passages_df[passages_df["genre"] == selected_genre]
23
+
24
+ if genre_passages.empty:
25
+ return "⚠️ 選択ジャンルに教材がありません", gr.update(visible=False)
26
+
27
+ # 最初の教材を選ぶ
28
+ passage_id = random.choice(genre_passages["passage_id"].unique())
29
+ pages = genre_passages[genre_passages["passage_id"] == passage_id].sort_values("page")
30
+
31
+ # 初期状態
32
+ state = {
33
+ "student_id": student_id,
34
+ "genre": selected_genre,
35
+ "passage_id": passage_id,
36
+ "pages": pages,
37
+ "page_index": 0,
38
+ "logs": []
39
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ return "", state, pages.iloc[0]["text"], enable_buttons(state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ def enable_buttons(state):
45
+ last_page = len(state["pages"]) - 1
46
+ pi = state["page_index"]
47
+
48
+ return {
49
+ "prev": gr.update(interactive=(pi > 0)),
50
+ "next": gr.update(interactive=(pi < last_page)),
51
+ "finish": gr.update(interactive=(pi == last_page)), # ✅ 最終ページのみ押せる
52
+ "retire": gr.update(interactive=True)
 
 
 
 
 
 
53
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
 
 
 
55
 
56
+ def next_page(state):
57
+ if state is None:
58
+ return None, "", enable_buttons(state)
59
 
60
+ if state["page_index"] < len(state["pages"]) - 1:
61
+ state["page_index"] += 1
 
62
 
63
+ text = state["pages"].iloc[state["page_index"]]["text"]
64
+ return state, text, enable_buttons(state)
65
+
66
+
67
+ def prev_page(state):
68
+ if state is None:
69
+ return None, "", enable_buttons(state)
70
+
71
+ if state["page_index"] > 0:
72
+ state["page_index"] -= 1
73
+
74
+ text = state["pages"].iloc[state["page_index"]]["text"]
75
+ return state, text, enable_buttons(state)
76
+
77
+
78
+ def choose_next_passage(state, finished):
79
+ """finished = True → 読み終えた、False → リタイア"""
80
+
81
+ # ログ追加
82
+ state["logs"].append({
83
+ "student_id": state["student_id"],
84
+ "genre": state["genre"],
85
+ "passage_id": state["passage_id"],
86
+ "completed": finished,
87
+ "timestamp": time.time()
88
+ })
89
 
90
+ # ログ保存
91
+ save_logs(state["logs"])
92
 
93
+ # 次の教材選択
94
+ genre_passages = passages_df[passages_df["genre"] == state["genre"]]
95
+ remaining = genre_passages[genre_passages["passage_id"] != state["passage_id"]]
96
+
97
+ if remaining.empty:
98
+ return state, "✅ 同ジャンルの教材は終了しました", disable_all()
99
+
100
+ # 次の教材
101
+ new_passage_id = random.choice(remaining["passage_id"].unique())
102
+ pages = genre_passages[genre_passages["passage_id"] == new_passage_id].sort_values("page")
103
+
104
+ state["passage_id"] = new_passage_id
105
+ state["pages"] = pages
106
+ state["page_index"] = 0
107
+
108
+ return state, pages.iloc[0]["text"], enable_buttons(state)
109
+
110
+
111
+ def disable_all():
112
+ return {
113
+ "prev": gr.update(interactive=False),
114
+ "next": gr.update(interactive=False),
115
+ "finish": gr.update(interactive=False),
116
+ "retire": gr.update(interactive=False)
117
+ }
118
+
119
+
120
+ def save_logs(logs):
121
+ if not logs:
122
+ return
123
+ df = pd.DataFrame(logs)
124
+ dataset = Dataset.from_pandas(df)
125
+ dataset.push_to_hub(log_repo, private=False)
126
+
127
+
128
+ # ==== UI ====
129
+
130
+ with gr.Blocks() as demo:
131
+ gr.Markdown("### 📘 リーディングアプリ")
132
+
133
+ student_id = gr.Textbox(label="学籍番号を入力", placeholder="例: A12345")
134
+
135
+ genre_select = gr.Dropdown(
136
+ choices=[
137
+ "Literature", "Science&Technology", "History",
138
+ "Social Science&Society", "Arts&Culture",
139
+ "Religion&Philosophy", "Lifestyle&Hobbies",
140
+ "Health&Medicine", "Education&Reference"
141
+ ],
142
+ label="ジャンルを選択"
143
+ )
144
+
145
+ start_btn = gr.Button("スタート")
146
+ warn = gr.Textbox(label="警告", visible=False)
147
 
148
+ passage_state = gr.State()
149
+ passage_text = gr.Textbox(label="教材", lines=15, interactive=False)
 
150
 
151
+ prev_btn = gr.Button(" 前へ")
152
+ next_btn = gr.Button("次へ ▶")
153
+ finish_btn = gr.Button("読み終えた")
154
  retire_btn = gr.Button("リタイア")
155
 
156
+ # --- Event binding ---
157
  start_btn.click(
158
+ start_test,
159
+ [student_id, genre_select],
160
+ [warn, passage_state, passage_text, prev_btn, next_btn, finish_btn, retire_btn]
 
 
 
 
 
 
161
  )
162
 
163
  next_btn.click(
164
+ next_page,
165
+ passage_state,
166
+ [passage_state, passage_text, prev_btn, next_btn, finish_btn, retire_btn]
 
 
 
 
167
  )
168
 
169
  prev_btn.click(
170
+ prev_page,
171
+ passage_state,
172
+ [passage_state, passage_text, prev_btn, next_btn, finish_btn, retire_btn]
 
 
 
 
173
  )
174
 
175
  finish_btn.click(
176
+ lambda s: choose_next_passage(s, True),
177
+ passage_state,
178
+ [passage_state, passage_text, prev_btn, next_btn, finish_btn, retire_btn]
 
 
 
 
 
 
179
  )
180
 
181
  retire_btn.click(
182
+ lambda s: choose_next_passage(s, False),
183
+ passage_state,
184
+ [passage_state, passage_text, prev_btn, next_btn, finish_btn, retire_btn]
 
 
 
 
 
 
185
  )
186
 
187
  demo.launch()