sofiamak commited on
Commit
cb1ecf1
·
verified ·
1 Parent(s): bbee108

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -213
app.py CHANGED
@@ -1,276 +1,181 @@
1
- import os, random, json, datetime, html
2
  import gradio as gr
3
 
4
- APP_TITLE = "100个不|社交暴露练习(MVP)"
5
- TARGET = 100 # 目标完成数
6
-
7
- # ========== 题库(精简示例版:按类别+难度;可随时追加) ==========
8
- # level: easy / medium / hard
9
- # cat: compliment / ask / smalltalk / assert / shop / phone / social
10
- CHALLENGES = [
11
- # compliment
12
- {"text":"夸路上一个陌生人的衣服很好看", "cat":"compliment", "level":"easy"},
13
- {"text":"夸便利店店员今天的笑容很治愈", "cat":"compliment", "level":"easy"},
14
- {"text":"夸同电梯的人发型很利落", "cat":"compliment", "level":"easy"},
15
- {"text":"夸咖啡师今天做的拉花很好看", "cat":"compliment", "level":"medium"},
16
- {"text":"夸路人包包/鞋子很有质感并问在哪买的", "cat":"compliment", "level":"medium"},
17
- {"text":"夸一位保安/清洁阿姨辛苦了并说谢谢", "cat":"compliment", "level":"easy"},
18
- {"text":"在不打扰的前提下,对街头艺人说“你很棒”", "cat":"compliment", "level":"medium"},
19
- # ask(小请求)
20
- {"text":"在便利店询问:有没有免费的试吃/样品?", "cat":"ask", "level":"easy"},
21
- {"text":"向路人问路:最近的地铁站怎么走?", "cat":"ask", "level":"easy"},
22
- {"text":"在咖啡店问:可以给我多一杯常温水吗?", "cat":"ask", "level":"easy"},
23
- {"text":"在小店讨价:这件能再便宜一点吗?", "cat":"ask", "level":"medium"},
24
- {"text":"向同事/同学借用 1 支笔并说谢谢", "cat":"ask", "level":"easy"},
25
- {"text":"向邻座借充电器 5 分钟", "cat":"ask", "level":"medium"},
26
- {"text":"在餐厅请求换一个靠窗的位置", "cat":"ask", "level":"medium"},
27
- # smalltalk(闲聊开场)
28
- {"text":"收银排队时说:今天人好多呀(对前后的人)", "cat":"smalltalk", "level":"easy"},
29
- {"text":"问同电梯的人:你也去几楼?(简短寒暄)", "cat":"smalltalk", "level":"easy"},
30
- {"text":"超市排队时评论:这个水果看起来很新鲜对吧?", "cat":"smalltalk", "level":"easy"},
31
- {"text":"对邻座说:你在看哪本书/听什么歌?", "cat":"smalltalk", "level":"medium"},
32
- {"text":"在宠物店对别人家的宠物夸两句并问名字", "cat":"smalltalk", "level":"medium"},
33
- # assert(温柔表达边界/需要)
34
- {"text":"对外卖/快递:请放在门口,谢谢(不需要多聊)", "cat":"assert", "level":"easy"},
35
- {"text":"对推销人员:谢谢,我现在不需要(保持微笑)", "cat":"assert", "level":"easy"},
36
- {"text":"对熟人:我今天有点累,改天再聊可以吗?", "cat":"assert", "level":"medium"},
37
- {"text":"餐厅太吵时礼貌请求调低音量", "cat":"assert", "level":"hard"},
38
- # shop(店铺场景)
39
- {"text":"进店问:这件有小一号的尺码吗?", "cat":"shop", "level":"easy"},
40
- {"text":"问:可以试穿/试戴一下吗?", "cat":"shop", "level":"easy"},
41
- {"text":"问:这件打折到什么时候?有会员价吗?", "cat":"shop", "level":"medium"},
42
- {"text":"问:可以帮我推荐一个 200 元以内的礼物吗?", "cat":"shop", "level":"medium"},
43
- {"text":"点单时提出“少糖/分开装/去冰”等个性化需求", "cat":"shop", "level":"easy"},
44
- # phone(电话/线上)
45
- {"text":"给商家打电话:今天营业到几点?", "cat":"phone", "level":"medium"},
46
- {"text":"给医院/理发店电话预约/改期", "cat":"phone", "level":"hard"},
47
- {"text":"打客服电话并友善表达问题", "cat":"phone", "level":"hard"},
48
- # social(社交平台)
49
- {"text":"在朋友圈/社交平台发一条正面感受(非炫耀)", "cat":"social", "level":"easy"},
50
- {"text":"给朋友/同事发消息:这周一起喝咖啡吗?", "cat":"social", "level":"medium"},
51
- {"text":"在认识的人动态下留一句温和真诚的评论", "cat":"social", "level":"easy"},
52
- # 更多“生活灵感”型
53
- {"text":"在健身房对陌生人说:加油!", "cat":"smalltalk", "level":"easy"},
54
- {"text":"问地铁工作人员:下一班车还有多久?", "cat":"ask", "level":"easy"},
55
- {"text":"对保洁人员说:谢谢你们打扫得这么干净", "cat":"compliment", "level":"easy"},
56
- {"text":"在图书馆借书台问:可以推荐一本入门书吗?", "cat":"ask", "level":"medium"},
57
- {"text":"在菜市场问摊主:今天哪样最鲜?", "cat":"ask", "level":"easy"},
58
- ]
59
-
60
- CATS = [
61
- ("夸赞类","compliment"),
62
- ("小请求","ask"),
63
- ("闲聊开场","smalltalk"),
64
- ("表达边界","assert"),
65
- ("逛店购物","shop"),
66
- ("电话沟通","phone"),
67
- ("社交平台","social"),
68
- ]
69
-
70
- LEVELS = [("简单","easy"), ("进阶","medium"), ("挑战","hard")]
71
-
72
- # 安全开关(过滤掉潜在不适/风险项;本示例题库都较温和)
73
- SAFETY_FLAGS = [
74
- ("不涉及身体接触","no_touch"),
75
- ("不涉及调情/暧昧","no_flirt"),
76
- ("不拍照/录音陌生人","no_record"),
77
- ]
78
-
79
- def filter_pool(selected_cats, selected_levels, flags):
80
- pool = []
81
- cat_set = set(selected_cats)
82
- lvl_set = set(selected_levels)
83
- for item in CHALLENGES:
84
- if item["cat"] in cat_set and item["level"] in lvl_set:
85
- # 这里可以根据 flags 扩展更严格的过滤逻辑
86
- pool.append(item["text"])
87
- # 若筛选太窄,兜底全部
88
- return pool if pool else [x["text"] for x in CHALLENGES]
89
-
90
  def init_state():
91
  return {
92
- "done": 0,
93
- "history": [], # [(text, datetime, note)]
94
  "current": None,
95
- "pool": [],
96
- "target": TARGET
97
  }
98
 
99
- def draw_task(state, selected_cats, selected_levels, flags):
100
- if not state["pool"]:
101
- state["pool"] = filter_pool(selected_cats, selected_levels, flags).copy()
102
- random.shuffle(state["pool"])
103
- # 从池子里抽一道
104
- if not state["pool"]:
105
- return state, "(没有可用任务,请调整筛选)", progress_bar(state)
106
- task = state["pool"].pop()
107
  state["current"] = task
108
- return state, task, progress_bar(state)
 
109
 
110
  def complete_task(state, note):
111
  if not state["current"]:
112
- return state, "(请先抽取一题)", progress_bar(state)
113
- state["done"] += 1
114
- state["history"].append((state["current"], datetime.datetime.now().isoformat(timespec="seconds"), (note or "").strip()))
115
- done_text = f"✔ 已完成:{state['current']}"
116
- state["current"] = None
117
- return state, done_text, progress_bar(state)
 
 
 
118
 
119
  def skip_task(state):
120
  if not state["current"]:
121
- return state, "(没有正在进行的任务,先抽一题吧)", progress_bar(state)
122
  skipped = state["current"]
123
- state["current"] = None
124
- return state, f"已跳过:{skipped}", progress_bar(state)
125
 
126
  def reset_state():
127
- return init_state(), "已重置进度", progress_bar(init_state())
128
-
129
- def progress_bar(state):
130
- pct = min(100, int(state["done"] / state["target"] * 100))
131
- return f"完成进度:{state['done']}/{state['target']}({pct}%)"
132
 
133
  def export_progress(state):
134
- lines = ["# 100个不|练习记录", f"目标:{state['target']},已完成:{state['done']}", ""]
135
- for i, (t, ts, note) in enumerate(state["history"], 1):
136
- note_part = f";反思:{note}" if note else ""
137
- lines.append(f"{i}. [{ts}] {t}{note_part}")
138
- content = "\n".join(lines) if len(lines)>2 else "暂时没有完成记录。"
 
 
139
  fname = "/tmp/100no_progress.txt"
140
  with open(fname, "w", encoding="utf-8") as f:
141
- f.write(content)
142
  return fname
143
 
144
- # ========== UI ==========
145
- theme = gr.themes.Soft(primary_hue="pink", secondary_hue="cyan", neutral_hue="slate")
 
 
146
 
147
- CUSTOM_CSS = """
148
- :root{--c-border:#eddcf3;}
 
149
  body,.gradio-container{background:#faf7fb!important;}
150
- .topbar{display:flex;align-items:center;gap:8px;padding:12px;border:1px solid var(--c-border);border-radius:12px;background:linear-gradient(90deg,#ffe8f3,#e8fbff);}
151
- .logo{font-size:22px} .title{font-weight:800;font-size:20px}
152
- .card{border:1px solid var(--c-border);border-radius:12px;background:#fff;box-shadow:0 4px 18px rgba(17,17,17,0.04);padding:14px}
153
- .taskbox{border:1px dashed var(--c-border);border-radius:10px;padding:12px;font-weight:700}
154
- .tip{color:#6b7280;font-size:12px}
155
  """
156
 
157
- with gr.Blocks(title="100个不", theme=theme, css=CUSTOM_CSS) as demo:
158
- gr.HTML(f"""
159
- <div class="topbar"><div class="logo">🧠</div><div class="title">{APP_TITLE}</div></div>
160
- """)
161
 
162
  state = gr.State(init_state())
163
 
164
  with gr.Row():
165
- # 左侧:筛选与操作
166
  with gr.Column(scale=5):
167
  with gr.Group(elem_classes=["card"]):
168
- gr.Markdown("#### 选择你的练习范围(可多选)")
169
- cat_choices = gr.CheckboxGroup(
170
- choices=[c[0] for c in CATS],
171
- value=[c[0] for c in CATS], # 默认全选
172
- label="类别"
173
  )
174
- # 映射中文label -> 英文key
175
- cat_map = {c[0]:c[1] for c in CATS}
176
-
177
- level_choices = gr.CheckboxGroup(
178
- choices=[l[0] for l in LEVELS],
179
- value=[LEVELS[0][0], LEVELS[1][0]], # 默认 简单+进阶
180
- label="难度"
181
- )
182
- level_map = {l[0]:l[1] for l in LEVELS}
183
-
184
- safety = gr.CheckboxGroup(
185
- choices=[s[0] for s in SAFETY_FLAGS],
186
- value=[s[0] for s in SAFETY_FLAGS],
187
- label="安全开关(建议全部开启)",
188
- info="尊重他人、遵守礼法与当地规定;如遇到“不舒服”立即停止。"
189
- )
190
-
191
- with gr.Group(elem_classes=["card"]):
192
- gr.Markdown("#### 操作")
193
- with gr.Row():
194
- btn_draw = gr.Button("🎲 抽取任务", variant="primary")
195
- btn_complete = gr.Button("✅ 标记完成")
196
- btn_skip = gr.Button("⏭ 跳过")
197
- reflect = gr.Textbox(label="完成后写一句反思(可选)", placeholder="例:开始很紧张,但说出口后对方很友好。", lines=2)
198
  with gr.Row():
199
  btn_export = gr.Button("📤 导出记录")
200
  btn_reset = gr.Button("🧹 重置进度")
201
 
202
- # 右侧:当前任务与进度
203
  with gr.Column(scale=7):
204
  with gr.Group(elem_classes=["card"]):
205
  gr.Markdown("#### 当前任务")
206
- current_task = gr.Markdown("(点击“🎲 抽取任务”开始)", elem_classes=["taskbox"])
207
- progress = gr.Markdown("完成进度:0/100(0%)")
208
- tips = gr.Markdown(
209
- "- 微笑、简短、真诚。\n"
210
- "- 尊重“拒绝权”,不追问���纠缠。\n"
211
- "- 如有不适,立刻停止并换一题。\n",
212
- elem_classes=["tip"]
213
- )
214
-
215
- with gr.Group(elem_classes=["card"]):
216
- gr.Markdown("#### 说明(MVP)")
217
  gr.Markdown(
218
- "- 本工具**不存储个人数据**,进度只在当前会话有效。\n"
219
- "- 你可以随时点击**导出记录**保存本地 TXT。\n"
220
- "- 若想要跨设备/长期保存,我可以下个版本加登录或云存储。"
 
221
  )
222
 
223
- # ========== 交互逻辑 ==========
224
- def _map_multi(labels, mapping):
225
- return [mapping[l] for l in labels if l in mapping]
226
 
227
- def on_draw(st, cats_cn, levels_cn, safety_cn):
228
- cats = _map_multi(cats_cn, cat_map)
229
- lvls = _map_multi(levels_cn, level_map)
230
- flgs = safety_cn # 目前未细分使用
231
- st, task, prog = draw_task(st, cats, lvls, flgs)
232
- return st, task, prog
233
 
234
  def on_complete(st, note):
235
  st, msg, prog = complete_task(st, note)
236
- return st, msg, prog, "" # 清空反思框
237
 
238
  def on_skip(st):
239
  st, msg, prog = skip_task(st)
240
  return st, msg, prog
241
 
242
  def on_export(st):
243
- path = export_progress(st)
244
- return path
245
 
246
  def on_reset():
247
  st, msg, prog = reset_state()
248
- return st, "(点击“🎲 抽取任务”开始)", prog, "", None
249
 
250
- btn_draw.click(
251
- on_draw,
252
- inputs=[state, cat_choices, level_choices, safety],
253
- outputs=[state, current_task, progress]
254
- )
255
 
256
- btn_complete.click(
257
- on_complete,
258
- inputs=[state, reflect],
259
- outputs=[state, current_task, progress, reflect]
260
- )
261
-
262
- btn_skip.click(
263
- on_skip,
264
- inputs=[state],
265
- outputs=[state, current_task, progress]
266
- )
267
-
268
- file_out = gr.File(label="导出TXT(点击下载)", visible=True)
269
  btn_export.click(on_export, inputs=[state], outputs=[file_out])
270
-
271
- btn_reset.click(on_reset, inputs=None, outputs=[state, current_task, progress, reflect, file_out])
272
 
273
  if __name__ == "__main__":
274
- # 关闭 SSR 提示,Spaces 也可运行;不自动 share,交给 HF Spaces 控制
275
- gr.close_all()
276
  demo.launch(ssr_mode=False)
 
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;}
107
+ .topbar{display:flex;align-items:center;gap:8px;padding:12px;background:linear-gradient(90deg,#ffe8f3,#e8fbff);border:1px solid #eddcf3;border-radius:12px;}
108
+ .title{font-weight:800;font-size:20px;color:#111;}
109
+ .card{background:#fff;border:1px solid #eddcf3;border-radius:12px;box-shadow:0 4px 18px rgba(0,0,0,.05);padding:14px;}
110
+ .taskbox{border:1px dashed #eddcf3;border-radius:10px;padding:12px;font-weight:700;}
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())
118
 
119
  with gr.Row():
 
120
  with gr.Column(scale=5):
121
  with gr.Group(elem_classes=["card"]):
122
+ gr.Markdown("#### 选择难度")
123
+ level_choice = gr.Radio(
124
+ label="难度",
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("📤 导出记录")
134
  btn_reset = gr.Button("🧹 重置进度")
135
 
 
136
  with gr.Column(scale=7):
137
  with gr.Group(elem_classes=["card"]):
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)