sofiamak commited on
Commit
ca323ef
·
verified ·
1 Parent(s): 44bff2a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -99
app.py CHANGED
@@ -1,106 +1,110 @@
1
- import os, random, datetime
2
  import gradio as gr
3
 
4
  APP_TITLE = "100个不"
 
5
  TARGET_PERCENT = 100
6
 
7
- # ====== 题库按难度分组 ======
8
- TASKS = {
9
- "easy": [
10
- "夸陌生人的衣服很好看",
11
- "向便利店店员说谢谢并微笑",
12
- "问路人:请问地铁站怎么走?",
13
- "在咖啡店点单时加一句“今天人真多呀”",
14
- "向同事或同学说:今天辛苦啦!",
15
- ],
16
- "medium": [
17
- "在便利店问:有没有免费的样品?",
18
- "夸陌生人的发型好看并问在哪剪的",
19
- "向陌生人打招呼并介绍自己名字",
20
- "打电话咨询商家一个简单问题",
21
- "向店员要求换个靠窗的位置",
22
- ],
23
- "hard": [
24
- "在街上送给陌生人一朵花",
25
- "在餐厅请求调低音量",
26
- "主动邀请一个不太熟的人喝咖啡",
27
- "在朋友圈发一条真诚但不完美的内容",
28
- "打客服电话表达不满并保持冷静",
29
- ]
30
- }
31
-
32
- LEVEL_REWARDS = {
33
- "easy": 1,
34
- "medium": 5,
35
- "hard": 10
36
- }
37
-
38
- LEVEL_LABELS = {
39
- "easy": "简单",
40
- "medium": "进阶",
41
- "hard": "挑战"
42
- }
43
-
44
- # ====== State 初始化 ======
45
  def init_state():
 
46
  return {
47
  "done_percent": 0,
48
- "history": [], # [(task, level, datetime, note)]
49
  "current": None,
50
  "current_level": None,
 
 
51
  }
52
 
53
- # ====== 核心函数 ======
54
- def draw_task(state, selected_level):
55
- if selected_level not in TASKS:
56
- return state, "请选择难度后再抽题。", progress_bar(state)
57
- task = random.choice(TASKS[selected_level])
 
 
 
 
 
 
 
58
  state["current"] = task
59
- state["current_level"] = selected_level
60
- return state, f"难度:{LEVEL_LABELS[selected_level]}\n\n任务:{task}", progress_bar(state)
61
 
62
  def complete_task(state, note):
63
  if not state["current"]:
64
- return state, "(请先抽取任务)", progress_bar(state)
65
  level = state["current_level"]
66
  reward = LEVEL_REWARDS.get(level, 0)
67
- state["done_percent"] += reward
68
- state["done_percent"] = min(state["done_percent"], TARGET_PERCENT)
69
- state["history"].append((state["current"], level, datetime.datetime.now().isoformat(timespec="seconds"), note or ""))
70
- done_msg = f"✅ 已完成:{state['current']} (+{reward}%)"
 
 
 
71
  state["current"], state["current_level"] = None, None
72
- return state, done_msg, progress_bar(state)
73
 
74
  def skip_task(state):
75
  if not state["current"]:
76
- return state, "(当前没有任务)", progress_bar(state)
77
  skipped = state["current"]
78
  state["current"], state["current_level"] = None, None
79
- return state, f"⏭ 已跳过:{skipped}", progress_bar(state)
80
 
81
  def reset_state():
82
  st = init_state()
83
- return st, "已重置进度。", progress_bar(st)
 
 
 
 
 
 
 
 
84
 
85
  def export_progress(state):
86
  lines = [
87
- f"# 100个不|练习记录",
88
  f"已完成:{state['done_percent']}%",
89
  ""
90
  ]
91
  for i, (task, lvl, ts, note) in enumerate(state["history"], 1):
92
- lines.append(f"{i}. [{ts}] {LEVEL_LABELS[lvl]}任务:{task} 反思:{note}")
93
- fname = "/tmp/100no_progress.txt"
94
- with open(fname, "w", encoding="utf-8") as f:
95
- f.write("\n".join(lines))
96
- return fname
97
-
98
- def progress_bar(state):
99
- pct = min(100, int(state["done_percent"]))
100
- bar = f"{'█' * (pct//5)}{'░' * (20 - pct//5)}"
101
- return f"进度:{pct}%\n{bar}"
102
 
103
- # ====== UI 布局 ======
104
  theme = gr.themes.Soft(primary_hue="pink", secondary_hue="cyan", neutral_hue="slate")
105
  CSS = """
106
  body,.gradio-container{background:#faf7fb!important;}
@@ -111,7 +115,8 @@ body,.gradio-container{background:#faf7fb!important;}
111
  .tip{color:#6b7280;font-size:12px;}
112
  """
113
 
114
- with gr.Blocks(title="100个不", theme=theme, css=CSS) as demo:
 
115
  gr.HTML(f"""<div class='topbar'><div class='title'>{APP_TITLE}</div></div>""")
116
 
117
  state = gr.State(init_state())
@@ -125,9 +130,12 @@ with gr.Blocks(title="100个不", theme=theme, css=CSS) as demo:
125
  choices=[LEVEL_LABELS["easy"], LEVEL_LABELS["medium"], LEVEL_LABELS["hard"]],
126
  value=LEVEL_LABELS["easy"]
127
  )
128
- btn_draw = gr.Button("🎲 抽取任务", variant="primary")
129
- btn_complete = gr.Button("✅ 标记完成")
130
- btn_skip = gr.Button("⏭ 跳过")
 
 
 
131
  reflect = gr.Textbox(label="反思(可选)", placeholder="完成后写一句感受", lines=2)
132
  with gr.Row():
133
  btn_export = gr.Button("📤 导出记录")
@@ -138,44 +146,44 @@ with gr.Blocks(title="100个不", theme=theme, css=CSS) as demo:
138
  gr.Markdown("#### 当前任务")
139
  current_task = gr.Markdown("(点击 🎲 抽取任务)", elem_classes=["taskbox"])
140
  progress = gr.Markdown("进度:0%\n░░░░░░░░░░░░░░░░░░░░░░")
141
- gr.Markdown(
142
- "- 每完成一个任务就标记 ✅。\n"
143
- "- 简单任务 +1%,进阶 +5%,挑战 +10%。\n"
144
- "- 当你达到 100%,恭喜你完成了“100个不”。",
145
- elem_classes=["tip"]
146
- )
147
 
148
- def map_level(label_cn):
149
- rev = {v:k for k,v in LEVEL_LABELS.items()}
150
- return rev.get(label_cn, "easy")
151
 
152
- def on_draw(st, level_cn):
153
- lvl = map_level(level_cn)
154
- st, msg, prog = draw_task(st, lvl)
155
- return st, msg, prog
 
156
 
157
  def on_complete(st, note):
158
  st, msg, prog = complete_task(st, note)
159
- return st, msg, prog, ""
160
-
161
  def on_skip(st):
162
  st, msg, prog = skip_task(st)
163
- return st, msg, prog
164
-
165
  def on_export(st):
166
  return export_progress(st)
167
-
168
  def on_reset():
169
- st, msg, prog = reset_state()
170
- return st, "(点击 🎲 抽取任务)", prog, "", None
171
-
172
- file_out = gr.File(label="导出TXT", visible=True)
173
-
174
- btn_draw.click(on_draw, inputs=[state, level_choice], outputs=[state, current_task, progress])
175
- btn_complete.click(on_complete, inputs=[state, reflect], outputs=[state, current_task, progress, reflect])
176
- btn_skip.click(on_skip, inputs=[state], outputs=[state, current_task, progress])
 
 
177
  btn_export.click(on_export, inputs=[state], outputs=[file_out])
178
- btn_reset.click(on_reset, outputs=[state, current_task, progress, reflect, file_out])
 
 
 
 
 
 
179
 
180
  if __name__ == "__main__":
 
181
  demo.launch(ssr_mode=False)
 
1
+ import os, json, random, datetime, io
2
  import gradio as gr
3
 
4
  APP_TITLE = "100个不"
5
+ TASKS_FILE = "tasks.json" # 你的题库文件
6
  TARGET_PERCENT = 100
7
 
8
+ # 难度映射与奖励
9
+ LEVEL_LABELS = {"easy": "简单", "medium": "进阶", "hard": "挑战"}
10
+ LABEL_TO_KEY = {v: k for k, v in LEVEL_LABELS.items()}
11
+ LEVEL_REWARDS = {"easy": 1, "medium": 5, "hard": 10}
12
+
13
+ # ====== 读取 tasks.json ======
14
+ def load_tasks_from_file():
15
+ """
16
+ 从 tasks.json 读取:必须包含 easy/medium/hard 三个数组。
17
+ 返回 (tasks, message);message 为 None 表示正常,否则是提示信息。
18
+ """
19
+ if not os.path.exists(TASKS_FILE):
20
+ return {"easy": [], "medium": [], "hard": []}, f"未找到 {TASKS_FILE},使用空题库。"
21
+ try:
22
+ with open(TASKS_FILE, "r", encoding="utf-8") as f:
23
+ data = json.load(f)
24
+ # 基本校验
25
+ for k in ["easy", "medium", "hard"]:
26
+ if k not in data or not isinstance(data[k], list):
27
+ raise ValueError("JSON 结构不正确,应包含 easy/medium/hard 三个数组。")
28
+ return data, None
29
+ except Exception as e:
30
+ return {"easy": [], "medium": [], "hard": []}, f"{TASKS_FILE} 解析失败:{e}。已使用空题库。"
31
+
32
+ # ====== 进度状态 ======
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def init_state():
34
+ tasks, msg = load_tasks_from_file()
35
  return {
36
  "done_percent": 0,
37
+ "history": [], # [(task, level, datetime, note)]
38
  "current": None,
39
  "current_level": None,
40
+ "tasks": tasks,
41
+ "load_msg": msg # 若有加载提示,首次显示
42
  }
43
 
44
+ # ====== 业务逻辑 ======
45
+ def progress_text(state):
46
+ pct = min(100, int(state["done_percent"]))
47
+ bar = f"{'█' * (pct//5)}{'░' * (20 - pct//5)}" # 20格进度条
48
+ return f"进度:{pct}%\n{bar}"
49
+
50
+ def draw_task(state, level_cn):
51
+ level = LABEL_TO_KEY.get(level_cn, "easy")
52
+ pool = state["tasks"].get(level, [])
53
+ if not pool:
54
+ return state, f"({LEVEL_LABELS[level]}暂无任务,请检查 {TASKS_FILE})", progress_text(state), state.get("load_msg") or ""
55
+ task = random.choice(pool)
56
  state["current"] = task
57
+ state["current_level"] = level
58
+ return state, f"难度:{LEVEL_LABELS[level]}\n\n任务:{task}", progress_text(state), ""
59
 
60
  def complete_task(state, note):
61
  if not state["current"]:
62
+ return state, "(请先抽取任务)", progress_text(state)
63
  level = state["current_level"]
64
  reward = LEVEL_REWARDS.get(level, 0)
65
+ state["done_percent"] = min(TARGET_PERCENT, state["done_percent"] + reward)
66
+ state["history"].append((
67
+ state["current"], level,
68
+ datetime.datetime.now().isoformat(timespec="seconds"),
69
+ (note or "").strip()
70
+ ))
71
+ msg = f"✅ 已完成:{state['current']}(+{reward}%)"
72
  state["current"], state["current_level"] = None, None
73
+ return state, msg, progress_text(state)
74
 
75
  def skip_task(state):
76
  if not state["current"]:
77
+ return state, "(当前没有任务)", progress_text(state)
78
  skipped = state["current"]
79
  state["current"], state["current_level"] = None, None
80
+ return state, f"⏭ 已跳过:{skipped}", progress_text(state)
81
 
82
  def reset_state():
83
  st = init_state()
84
+ return st, "已重置进度。", progress_text(st), (st["load_msg"] or "")
85
+
86
+ def reload_tasks(state):
87
+ tasks, msg = load_tasks_from_file()
88
+ state["tasks"] = tasks
89
+ # 若当前难度没题,将 current 清空,避免脏状态
90
+ state["current"], state["current_level"] = None, None
91
+ tip = msg if msg else "已从 tasks.json 重新加载题库。"
92
+ return state, tip
93
 
94
  def export_progress(state):
95
  lines = [
96
+ "# 100个不|练习记录",
97
  f"已完成:{state['done_percent']}%",
98
  ""
99
  ]
100
  for i, (task, lvl, ts, note) in enumerate(state["history"], 1):
101
+ note_part = f" 反思:{note}" if note else ""
102
+ lines.append(f"{i}. [{ts}] {LEVEL_LABELS[lvl]}任务:{task}{note_part}")
103
+ content = "\n".join(lines) if len(lines) > 3 else "暂无记录"
104
+ # 使用内存文件导出(避免路径权限问题)
105
+ return gr.File.update(value=("progress.txt", content))
 
 
 
 
 
106
 
107
+ # ====== 界面样式 ======
108
  theme = gr.themes.Soft(primary_hue="pink", secondary_hue="cyan", neutral_hue="slate")
109
  CSS = """
110
  body,.gradio-container{background:#faf7fb!important;}
 
115
  .tip{color:#6b7280;font-size:12px;}
116
  """
117
 
118
+ # ====== 构建 UI ======
119
+ with gr.Blocks(title=APP_TITLE, theme=theme, css=CSS) as demo:
120
  gr.HTML(f"""<div class='topbar'><div class='title'>{APP_TITLE}</div></div>""")
121
 
122
  state = gr.State(init_state())
 
130
  choices=[LEVEL_LABELS["easy"], LEVEL_LABELS["medium"], LEVEL_LABELS["hard"]],
131
  value=LEVEL_LABELS["easy"]
132
  )
133
+ with gr.Row():
134
+ btn_draw = gr.Button("🎲 抽取任务", variant="primary")
135
+ btn_reload = gr.Button("🔁 刷新题库(读取 tasks.json)")
136
+ with gr.Row():
137
+ btn_complete = gr.Button("✅ 标记完成")
138
+ btn_skip = gr.Button("⏭ 跳过")
139
  reflect = gr.Textbox(label="反思(可选)", placeholder="完成后写一句感受", lines=2)
140
  with gr.Row():
141
  btn_export = gr.Button("📤 导出记录")
 
146
  gr.Markdown("#### 当前任务")
147
  current_task = gr.Markdown("(点击 🎲 抽取任务)", elem_classes=["taskbox"])
148
  progress = gr.Markdown("进度:0%\n░░░░░░░░░░░░░░░░░░░░░░")
149
+ tip = gr.Markdown("", elem_classes=["tip"]) # 用于显示加载/刷新提示
 
 
 
 
 
150
 
151
+ file_out = gr.File(label="下载导出记录", visible=True)
 
 
152
 
153
+ # ====== 事件处理 ======
154
+ def on_draw(st, lvl_cn):
155
+ st["load_msg"] = "" # 清理一次性提示
156
+ st, msg, prog, info = draw_task(st, lvl_cn)
157
+ return st, msg, prog, (info or "")
158
 
159
  def on_complete(st, note):
160
  st, msg, prog = complete_task(st, note)
161
+ return st, msg, prog, "" , "" # 清空提示 & 反思框
 
162
  def on_skip(st):
163
  st, msg, prog = skip_task(st)
164
+ return st, msg, prog, ""
 
165
  def on_export(st):
166
  return export_progress(st)
 
167
  def on_reset():
168
+ st, msg, prog, info = reset_state()
169
+ return st, "(点击 🎲 抽取任务)", prog, "", None, info
170
+ def on_reload(st):
171
+ st, info = reload_tasks(st)
172
+ return st, info
173
+
174
+ # 绑定
175
+ btn_draw.click(on_draw, inputs=[state, level_choice], outputs=[state, current_task, progress, tip])
176
+ btn_complete.click(on_complete, inputs=[state, reflect], outputs=[state, current_task, progress, tip, reflect])
177
+ btn_skip.click(on_skip, inputs=[state], outputs=[state, current_task, progress, tip])
178
  btn_export.click(on_export, inputs=[state], outputs=[file_out])
179
+ btn_reset.click(on_reset, outputs=[state, current_task, progress, tip, file_out, tip])
180
+ btn_reload.click(on_reload, inputs=[state], outputs=[state, tip])
181
+
182
+ # 初次加载,如有提示显示出来
183
+ def show_first_tip(st):
184
+ return st.get("load_msg") or ""
185
+ tip.load(show_first_tip, inputs=[state], outputs=[tip])
186
 
187
  if __name__ == "__main__":
188
+ # 在 HF Spaces 上建议关闭 SSR 提示;share 是否公开交给 Space 设置
189
  demo.launch(ssr_mode=False)