Update app.py
Browse files
app.py
CHANGED
|
@@ -24,7 +24,7 @@ scheduler = CommitScheduler(
|
|
| 24 |
token=TOKEN
|
| 25 |
)
|
| 26 |
|
| 27 |
-
# --- 3. 数据加载逻辑
|
| 28 |
def load_data():
|
| 29 |
groups = {}
|
| 30 |
if not os.path.exists(DATA_FOLDER):
|
|
@@ -57,13 +57,8 @@ def load_data():
|
|
| 57 |
|
| 58 |
ALL_GROUPS, ALL_GROUP_IDS = load_data()
|
| 59 |
|
| 60 |
-
# --- 4. 保存逻辑
|
| 61 |
def save_user_vote(user_id, group_id, choice_labels, method_names):
|
| 62 |
-
"""
|
| 63 |
-
保存投票。
|
| 64 |
-
choice_labels: 字符串,例如 "Option A; Option B"
|
| 65 |
-
method_names: 字符串,例如 "omnigen; sdxl"
|
| 66 |
-
"""
|
| 67 |
user_filename = f"user_{user_id}.csv"
|
| 68 |
user_file_path = LOG_FOLDER / user_filename
|
| 69 |
|
|
@@ -85,7 +80,7 @@ def save_user_vote(user_id, group_id, choice_labels, method_names):
|
|
| 85 |
|
| 86 |
print(f"Saved: User {user_id} selected {method_names}")
|
| 87 |
|
| 88 |
-
# --- 5. 交互逻辑
|
| 89 |
|
| 90 |
def get_current_question_ui(user_state):
|
| 91 |
"""根据当前索引刷新界面"""
|
|
@@ -100,8 +95,8 @@ def get_current_question_ui(user_state):
|
|
| 100 |
gr.update(visible=False),
|
| 101 |
gr.update(value="## 🎉 测试结束!\n感谢您的参与,所有结果已保存。", visible=True),
|
| 102 |
user_state,
|
| 103 |
-
[],
|
| 104 |
-
[]
|
| 105 |
)
|
| 106 |
|
| 107 |
# 2. 获取数据
|
|
@@ -111,7 +106,7 @@ def get_current_question_ui(user_state):
|
|
| 111 |
# 3. 准备文本
|
| 112 |
instruction_text = f"### 任务 ({current_idx + 1} / {len(ALL_GROUP_IDS)})\n\n{group_data['instruction']}"
|
| 113 |
|
| 114 |
-
# 4. 准备图片
|
| 115 |
original_images = group_data["images"]
|
| 116 |
shuffled_images = original_images.copy()
|
| 117 |
random.shuffle(shuffled_images)
|
|
@@ -119,7 +114,7 @@ def get_current_question_ui(user_state):
|
|
| 119 |
# 构造显示列表
|
| 120 |
display_list = []
|
| 121 |
for i, img_path in enumerate(shuffled_images):
|
| 122 |
-
label = f"Option {chr(65+i)}"
|
| 123 |
display_list.append((img_path, label))
|
| 124 |
|
| 125 |
# 动态列数
|
|
@@ -128,33 +123,25 @@ def get_current_question_ui(user_state):
|
|
| 128 |
|
| 129 |
return (
|
| 130 |
gr.update(value=instruction_text, visible=True),
|
| 131 |
-
gr.update(value=display_list, columns=cols, visible=True),
|
| 132 |
-
gr.update(value="当前未选择任何图片", visible=True),
|
| 133 |
-
gr.update(visible=True),
|
| 134 |
-
gr.update(visible=False),
|
| 135 |
user_state,
|
| 136 |
shuffled_images,
|
| 137 |
-
[]
|
| 138 |
)
|
| 139 |
|
| 140 |
def toggle_selection(evt: gr.SelectData, current_indices):
|
| 141 |
-
"""
|
| 142 |
-
处理图片点击:
|
| 143 |
-
点击一次 -> 选中
|
| 144 |
-
再点一次 -> 取消选中
|
| 145 |
-
"""
|
| 146 |
clicked_idx = evt.index
|
| 147 |
|
| 148 |
-
# 切换状态
|
| 149 |
if clicked_idx in current_indices:
|
| 150 |
current_indices.remove(clicked_idx)
|
| 151 |
else:
|
| 152 |
current_indices.append(clicked_idx)
|
| 153 |
|
| 154 |
-
# 排序一下,让显示更好看 (Option A, Option B)
|
| 155 |
current_indices.sort()
|
| 156 |
|
| 157 |
-
# 更新状态文本
|
| 158 |
if not current_indices:
|
| 159 |
status_text = "当前未选择任何图片"
|
| 160 |
else:
|
|
@@ -164,9 +151,6 @@ def toggle_selection(evt: gr.SelectData, current_indices):
|
|
| 164 |
return current_indices, status_text
|
| 165 |
|
| 166 |
def submit_vote(user_state, current_file_paths, current_indices, is_none=False):
|
| 167 |
-
"""
|
| 168 |
-
提交投票(可能是多选,可能是None)
|
| 169 |
-
"""
|
| 170 |
user_id = user_state["user_id"]
|
| 171 |
current_idx = user_state["index"]
|
| 172 |
|
|
@@ -175,16 +159,14 @@ def submit_vote(user_state, current_file_paths, current_indices, is_none=False):
|
|
| 175 |
|
| 176 |
group_id = ALL_GROUP_IDS[current_idx]
|
| 177 |
|
| 178 |
-
#
|
| 179 |
if is_none:
|
| 180 |
save_user_vote(user_id, group_id, "Rejected All", "None_Satisfied")
|
| 181 |
user_state["index"] += 1
|
| 182 |
return get_current_question_ui(user_state)
|
| 183 |
|
| 184 |
-
#
|
| 185 |
if not current_indices:
|
| 186 |
-
# 如果用户没选图片就点了提交,弹窗提示或者不做反应
|
| 187 |
-
# 这里为了简单,返回原样,并提示
|
| 188 |
return (
|
| 189 |
gr.update(), gr.update(),
|
| 190 |
gr.update(value="❌ 请至少选择一张图片,或者点击“都不满意”"),
|
|
@@ -192,16 +174,13 @@ def submit_vote(user_state, current_file_paths, current_indices, is_none=False):
|
|
| 192 |
user_state, current_file_paths, current_indices
|
| 193 |
)
|
| 194 |
|
| 195 |
-
# 解析所有选中的图片
|
| 196 |
selected_labels = []
|
| 197 |
selected_methods = []
|
| 198 |
|
| 199 |
for idx in current_indices:
|
| 200 |
-
# 1. 记录 Option X
|
| 201 |
label = f"Option {chr(65+idx)}"
|
| 202 |
selected_labels.append(label)
|
| 203 |
|
| 204 |
-
# 2. 提取方法名
|
| 205 |
real_path = current_file_paths[idx]
|
| 206 |
filename = os.path.basename(real_path)
|
| 207 |
name_no_ext = os.path.splitext(filename)[0]
|
|
@@ -209,28 +188,24 @@ def submit_vote(user_state, current_file_paths, current_indices, is_none=False):
|
|
| 209 |
method = parts[1] if len(parts) > 1 else name_no_ext
|
| 210 |
selected_methods.append(method)
|
| 211 |
|
| 212 |
-
# 用分号连接 (CSV友好)
|
| 213 |
str_labels = "; ".join(selected_labels)
|
| 214 |
str_methods = "; ".join(selected_methods)
|
| 215 |
|
| 216 |
save_user_vote(user_id, group_id, str_labels, str_methods)
|
| 217 |
|
| 218 |
-
# 下一题
|
| 219 |
user_state["index"] += 1
|
| 220 |
return get_current_question_ui(user_state)
|
| 221 |
|
| 222 |
-
# --- 6. 界面构建 ---
|
| 223 |
-
with gr.Blocks(title="Multi-Select User Study"
|
| 224 |
|
| 225 |
-
# 状态变量
|
| 226 |
state_user = gr.State(lambda: {"user_id": str(uuid.uuid4())[:8], "index": 0})
|
| 227 |
-
state_files = gr.State([])
|
| 228 |
-
state_indices = gr.State([])
|
| 229 |
|
| 230 |
with gr.Column():
|
| 231 |
instruction_md = gr.Markdown("Loading...")
|
| 232 |
|
| 233 |
-
# 图片区
|
| 234 |
gallery = gr.Gallery(
|
| 235 |
label="请点击选择图片(可多选)",
|
| 236 |
allow_preview=True,
|
|
@@ -239,40 +214,32 @@ with gr.Blocks(title="Multi-Select User Study", theme=gr.themes.Soft()) as demo:
|
|
| 239 |
interactive=True
|
| 240 |
)
|
| 241 |
|
| 242 |
-
# 状态显示区(告诉用户选了啥)
|
| 243 |
status_box = gr.Textbox(value="当前未选择任何图片", label="当前选中状态", interactive=False)
|
| 244 |
|
| 245 |
-
# 按钮区
|
| 246 |
with gr.Row():
|
| 247 |
btn_submit = gr.Button("✅ 提交选择 (Confirm Selection)", variant="primary", scale=2)
|
| 248 |
btn_none = gr.Button("🚫 都不满意 (None of them)", variant="stop", scale=1)
|
| 249 |
|
| 250 |
end_msg = gr.Markdown(visible=False)
|
| 251 |
|
| 252 |
-
# --- 事件流 ---
|
| 253 |
-
|
| 254 |
-
# 1. 启动加载
|
| 255 |
demo.load(
|
| 256 |
fn=get_current_question_ui,
|
| 257 |
inputs=[state_user],
|
| 258 |
outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices]
|
| 259 |
)
|
| 260 |
|
| 261 |
-
# 2. 点击图片 -> 切换选中状态 (不翻页)
|
| 262 |
gallery.select(
|
| 263 |
fn=toggle_selection,
|
| 264 |
inputs=[state_indices],
|
| 265 |
outputs=[state_indices, status_box]
|
| 266 |
)
|
| 267 |
|
| 268 |
-
# 3. 点击提交 -> 保存并下一页
|
| 269 |
btn_submit.click(
|
| 270 |
fn=lambda s, f, i: submit_vote(s, f, i, is_none=False),
|
| 271 |
inputs=[state_user, state_files, state_indices],
|
| 272 |
outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices]
|
| 273 |
)
|
| 274 |
|
| 275 |
-
# 4. 点击都不满意 -> 保存并下一页
|
| 276 |
btn_none.click(
|
| 277 |
fn=lambda s, f, i: submit_vote(s, f, i, is_none=True),
|
| 278 |
inputs=[state_user, state_files, state_indices],
|
|
|
|
| 24 |
token=TOKEN
|
| 25 |
)
|
| 26 |
|
| 27 |
+
# --- 3. 数据加载逻辑 ---
|
| 28 |
def load_data():
|
| 29 |
groups = {}
|
| 30 |
if not os.path.exists(DATA_FOLDER):
|
|
|
|
| 57 |
|
| 58 |
ALL_GROUPS, ALL_GROUP_IDS = load_data()
|
| 59 |
|
| 60 |
+
# --- 4. 保存逻辑 ---
|
| 61 |
def save_user_vote(user_id, group_id, choice_labels, method_names):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
user_filename = f"user_{user_id}.csv"
|
| 63 |
user_file_path = LOG_FOLDER / user_filename
|
| 64 |
|
|
|
|
| 80 |
|
| 81 |
print(f"Saved: User {user_id} selected {method_names}")
|
| 82 |
|
| 83 |
+
# --- 5. 交互逻辑 ---
|
| 84 |
|
| 85 |
def get_current_question_ui(user_state):
|
| 86 |
"""根据当前索引刷新界面"""
|
|
|
|
| 95 |
gr.update(visible=False),
|
| 96 |
gr.update(value="## 🎉 测试结束!\n感谢您的参与,所有结果已保存。", visible=True),
|
| 97 |
user_state,
|
| 98 |
+
[],
|
| 99 |
+
[]
|
| 100 |
)
|
| 101 |
|
| 102 |
# 2. 获取数据
|
|
|
|
| 106 |
# 3. 准备文本
|
| 107 |
instruction_text = f"### 任务 ({current_idx + 1} / {len(ALL_GROUP_IDS)})\n\n{group_data['instruction']}"
|
| 108 |
|
| 109 |
+
# 4. 准备图片
|
| 110 |
original_images = group_data["images"]
|
| 111 |
shuffled_images = original_images.copy()
|
| 112 |
random.shuffle(shuffled_images)
|
|
|
|
| 114 |
# 构造显示列表
|
| 115 |
display_list = []
|
| 116 |
for i, img_path in enumerate(shuffled_images):
|
| 117 |
+
label = f"Option {chr(65+i)}"
|
| 118 |
display_list.append((img_path, label))
|
| 119 |
|
| 120 |
# 动态列数
|
|
|
|
| 123 |
|
| 124 |
return (
|
| 125 |
gr.update(value=instruction_text, visible=True),
|
| 126 |
+
gr.update(value=display_list, columns=cols, visible=True),
|
| 127 |
+
gr.update(value="当前未选择任何图片", visible=True),
|
| 128 |
+
gr.update(visible=True),
|
| 129 |
+
gr.update(visible=False),
|
| 130 |
user_state,
|
| 131 |
shuffled_images,
|
| 132 |
+
[]
|
| 133 |
)
|
| 134 |
|
| 135 |
def toggle_selection(evt: gr.SelectData, current_indices):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
clicked_idx = evt.index
|
| 137 |
|
|
|
|
| 138 |
if clicked_idx in current_indices:
|
| 139 |
current_indices.remove(clicked_idx)
|
| 140 |
else:
|
| 141 |
current_indices.append(clicked_idx)
|
| 142 |
|
|
|
|
| 143 |
current_indices.sort()
|
| 144 |
|
|
|
|
| 145 |
if not current_indices:
|
| 146 |
status_text = "当前未选择任何图片"
|
| 147 |
else:
|
|
|
|
| 151 |
return current_indices, status_text
|
| 152 |
|
| 153 |
def submit_vote(user_state, current_file_paths, current_indices, is_none=False):
|
|
|
|
|
|
|
|
|
|
| 154 |
user_id = user_state["user_id"]
|
| 155 |
current_idx = user_state["index"]
|
| 156 |
|
|
|
|
| 159 |
|
| 160 |
group_id = ALL_GROUP_IDS[current_idx]
|
| 161 |
|
| 162 |
+
# 场景1: 都没有
|
| 163 |
if is_none:
|
| 164 |
save_user_vote(user_id, group_id, "Rejected All", "None_Satisfied")
|
| 165 |
user_state["index"] += 1
|
| 166 |
return get_current_question_ui(user_state)
|
| 167 |
|
| 168 |
+
# 场景2: 提交多选
|
| 169 |
if not current_indices:
|
|
|
|
|
|
|
| 170 |
return (
|
| 171 |
gr.update(), gr.update(),
|
| 172 |
gr.update(value="❌ 请至少选择一张图片,或者点击“都不满意”"),
|
|
|
|
| 174 |
user_state, current_file_paths, current_indices
|
| 175 |
)
|
| 176 |
|
|
|
|
| 177 |
selected_labels = []
|
| 178 |
selected_methods = []
|
| 179 |
|
| 180 |
for idx in current_indices:
|
|
|
|
| 181 |
label = f"Option {chr(65+idx)}"
|
| 182 |
selected_labels.append(label)
|
| 183 |
|
|
|
|
| 184 |
real_path = current_file_paths[idx]
|
| 185 |
filename = os.path.basename(real_path)
|
| 186 |
name_no_ext = os.path.splitext(filename)[0]
|
|
|
|
| 188 |
method = parts[1] if len(parts) > 1 else name_no_ext
|
| 189 |
selected_methods.append(method)
|
| 190 |
|
|
|
|
| 191 |
str_labels = "; ".join(selected_labels)
|
| 192 |
str_methods = "; ".join(selected_methods)
|
| 193 |
|
| 194 |
save_user_vote(user_id, group_id, str_labels, str_methods)
|
| 195 |
|
|
|
|
| 196 |
user_state["index"] += 1
|
| 197 |
return get_current_question_ui(user_state)
|
| 198 |
|
| 199 |
+
# --- 6. 界面构建 (已移除 theme 参数) ---
|
| 200 |
+
with gr.Blocks(title="Multi-Select User Study") as demo:
|
| 201 |
|
|
|
|
| 202 |
state_user = gr.State(lambda: {"user_id": str(uuid.uuid4())[:8], "index": 0})
|
| 203 |
+
state_files = gr.State([])
|
| 204 |
+
state_indices = gr.State([])
|
| 205 |
|
| 206 |
with gr.Column():
|
| 207 |
instruction_md = gr.Markdown("Loading...")
|
| 208 |
|
|
|
|
| 209 |
gallery = gr.Gallery(
|
| 210 |
label="请点击选择图片(可多选)",
|
| 211 |
allow_preview=True,
|
|
|
|
| 214 |
interactive=True
|
| 215 |
)
|
| 216 |
|
|
|
|
| 217 |
status_box = gr.Textbox(value="当前未选择任何图片", label="当前选中状态", interactive=False)
|
| 218 |
|
|
|
|
| 219 |
with gr.Row():
|
| 220 |
btn_submit = gr.Button("✅ 提交选择 (Confirm Selection)", variant="primary", scale=2)
|
| 221 |
btn_none = gr.Button("🚫 都不满意 (None of them)", variant="stop", scale=1)
|
| 222 |
|
| 223 |
end_msg = gr.Markdown(visible=False)
|
| 224 |
|
|
|
|
|
|
|
|
|
|
| 225 |
demo.load(
|
| 226 |
fn=get_current_question_ui,
|
| 227 |
inputs=[state_user],
|
| 228 |
outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices]
|
| 229 |
)
|
| 230 |
|
|
|
|
| 231 |
gallery.select(
|
| 232 |
fn=toggle_selection,
|
| 233 |
inputs=[state_indices],
|
| 234 |
outputs=[state_indices, status_box]
|
| 235 |
)
|
| 236 |
|
|
|
|
| 237 |
btn_submit.click(
|
| 238 |
fn=lambda s, f, i: submit_vote(s, f, i, is_none=False),
|
| 239 |
inputs=[state_user, state_files, state_indices],
|
| 240 |
outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices]
|
| 241 |
)
|
| 242 |
|
|
|
|
| 243 |
btn_none.click(
|
| 244 |
fn=lambda s, f, i: submit_vote(s, f, i, is_none=True),
|
| 245 |
inputs=[state_user, state_files, state_indices],
|