IdlecloudX commited on
Commit
c01b9a5
·
verified ·
1 Parent(s): 07ef9c9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -226
app.py CHANGED
@@ -6,22 +6,30 @@ import numpy as np
6
  import onnxruntime as rt
7
  import pandas as pd
8
  from PIL import Image
9
- from huggingface_hub import login, HfApi
10
- from translator import translate_texts, set_user_provided_keys, clear_user_provided_keys
 
11
 
12
  MODEL_REPO = "SmilingWolf/wd-eva02-large-tagger-v3"
13
  MODEL_FILENAME = "model.onnx"
14
  LABEL_FILENAME = "selected_tags.csv"
 
 
 
15
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
16
  if HF_TOKEN:
17
  try:
18
  login(token=HF_TOKEN)
19
- print("✅ 应用已使用 HF_TOKEN 登录")
20
  except Exception as e:
21
- print(f"⚠️ 使用 HF_TOKEN 登录失败: {e}")
22
  else:
23
- print("⚠️ 未检测到应用级别的 HF_TOKEN,私有模型可能下载失败")
 
24
 
 
 
 
25
  class Tagger:
26
  def __init__(self):
27
  self.hf_token = HF_TOKEN
@@ -122,7 +130,6 @@ custom_css = """
122
  .tag-zh { color: #666; margin-left: 10px; }
123
  .tag-score { color: #999; font-size: 0.9em; }
124
  .btn-analyze-container { margin-top: 15px; margin-bottom: 15px; }
125
- .user-info { text-align: right; color: #666; font-size: 0.9em; padding: 5px; }
126
  """
127
 
128
  _js_functions = """
@@ -136,243 +143,212 @@ function copyToClipboard(text) {
136
  let displayText = String(text);
137
  displayText = displayText.substring(0, 30) + (displayText.length > 30 ? '...' : '');
138
  feedback.textContent = '已复制: ' + displayText;
139
- feedback.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background-color:#4CAF50; color:white; padding:10px 20px; border-radius:5px; z-index:10000; transition:opacity 0.5s ease-out;';
140
  document.body.appendChild(feedback);
141
  setTimeout(() => {
142
  feedback.style.opacity = '0';
143
- setTimeout(() => { document.body.removeChild(feedback); }, 500);
144
  }, 1500);
145
  }).catch(err => {
146
- console.error('Failed to copy tag.', err, 'Text:', text);
147
  });
148
  }
149
  """
150
 
151
- def main_interface(user_info: gr.UserInfo):
152
- with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, js=_js_functions) as demo:
153
- gr.Markdown(f"<div class='user-info'>已登录: {user_info.name} ({user_info.email})</div>")
154
- gr.Markdown("# 🖼️ AI 图像标签分析器")
155
- gr.Markdown("上传图片自动识别标签,支持中英文显示和一键复制。[NovelAI在线绘画](https://nai.idlecloud.cc/)")
156
-
157
- state_res = gr.State({})
158
- state_translations_dict = gr.State({})
159
- state_tag_categories_for_translation = gr.State({})
160
-
161
- with gr.Row():
162
- with gr.Column(scale=1):
163
- img_in = gr.Image(type="pil", label="上传图片", height=300)
164
- btn = gr.Button("🚀 开始分析", variant="primary", elem_classes=["btn-analyze-container"])
165
-
166
- with gr.Accordion("⚙️ 高级设置", open=False):
167
- gen_slider = gr.Slider(0, 1, value=0.35, step=0.01, label="通用标签阈值", info="越高 → 标签更少更准")
168
- char_slider = gr.Slider(0, 1, value=0.85, step=0.01, label="角色标签阈值", info="推荐保持较高阈值")
169
- show_tag_scores = gr.Checkbox(True, label="在列表中显示标签置信度")
170
-
171
- with gr.Accordion("🔑 翻译服务设置", open=False):
172
- gr.Markdown("如果应用配置了全局翻译密钥,可在此输入访问密钥以使用。否则,请在此处填入您自己的翻译API密钥。")
173
- access_key_input = gr.Textbox(label="访问密钥 (Access Key)", type="password", placeholder="如果需要,请输入访问密钥")
174
-
175
- gr.Markdown("---")
176
- gr.Markdown("**或者**,使用你自己的密钥:")
177
- user_tencent_id = gr.Textbox(label="腾讯云 Secret ID", type="password")
178
- user_tencent_key = gr.Textbox(label="腾讯云 Secret Key", type="password")
179
- user_baidu_json = gr.Textbox(label="百度翻译凭证 (JSON格式)", type="password", lines=3, placeholder='[{"app_id":"...", "secret_key":"..."}]')
180
-
181
- with gr.Accordion("📊 标签汇总设置", open=True):
182
- gr.Markdown("选择要包含在下方汇总文本框中的标签类别:")
183
- with gr.Row():
184
- sum_general = gr.Checkbox(True, label="通用标签", min_width=50)
185
- sum_char = gr.Checkbox(True, label="角色标签", min_width=50)
186
- sum_rating = gr.Checkbox(False, label="评分标签", min_width=50)
187
- sum_sep = gr.Dropdown(["逗号", "换行", "空格"], value="逗号", label="标签之间的分隔符")
188
- sum_show_zh = gr.Checkbox(False, label="在汇总中显示中文翻译")
189
-
190
- processing_info = gr.Markdown("", visible=False)
191
-
192
- with gr.Column(scale=2):
193
- with gr.Tabs():
194
- with gr.TabItem("🏷️ 通用标签"):
195
- out_general = gr.HTML(label="General Tags")
196
- with gr.TabItem("👤 角色标签"):
197
- gr.Markdown("<p style='color:gray; font-size:small;'>提示:角色标签推测基于截至2024年2月的数据。</p>")
198
- out_char = gr.HTML(label="Character Tags")
199
- with gr.TabItem("⭐ 评分标签"):
200
- out_rating = gr.HTML(label="Rating Tags")
201
-
202
- gr.Markdown("### 标签汇总结果")
203
- out_summary = gr.Textbox(label="标签汇总", placeholder="分析完成后,此处将显示汇总的英文标签...", lines=5, show_copy_button=True)
204
-
205
- def format_tags_html(tags_dict, translations_list, category_name, show_scores=True, show_translation_in_list=True):
206
- if not tags_dict: return "<p>暂无标签</p>"
207
- html = '<div class="label-container">'
208
- if not isinstance(translations_list, list): translations_list = []
209
- tag_keys = list(tags_dict.keys())
210
- for i, tag in enumerate(tag_keys):
211
- score = tags_dict[tag]
212
- escaped_tag = tag.replace("'", "\\'")
213
- html += '<div class="tag-item">'
214
- tag_display_html = f'<span class="tag-en" onclick="copyToClipboard(\'{escaped_tag}\')">{tag}</span>'
215
- if show_translation_in_list and i < len(translations_list) and translations_list[i]:
216
- tag_display_html += f'<span class="tag-zh">({translations_list[i]})</span>'
217
- html += f'<div>{tag_display_html}</div>'
218
- if show_scores: html += f'<span class="tag-score">{score:.3f}</span>'
219
- html += '</div>'
220
- html += '</div>'
221
- return html
222
-
223
- def generate_summary_text_content(current_res, current_translations_dict, s_gen, s_char, s_rat, s_sep_type, s_show_zh):
224
- if not current_res: return "请先分析图像或选择要汇总的标签类别。"
225
- summary_parts = []
226
- separator = {"逗号": ", ", "换行": "\n", "空格": " "}.get(s_sep_type, ", ")
227
- categories_to_summarize = []
228
- if s_gen: categories_to_summarize.append("general")
229
- if s_char: categories_to_summarize.append("characters")
230
- if s_rat: categories_to_summarize.append("ratings")
231
- if not categories_to_summarize: return "请至少选择一个标签类别进行汇总。"
232
- for cat_key in categories_to_summarize:
233
- if current_res.get(cat_key):
234
- tags_to_join = []
235
- cat_tags_en = list(current_res[cat_key].keys())
236
- cat_translations = current_translations_dict.get(cat_key, [])
237
- for i, en_tag in enumerate(cat_tags_en):
238
- if s_show_zh and i < len(cat_translations) and cat_translations[i]:
239
- tags_to_join.append(f"{en_tag}({cat_translations[i]})")
240
- else:
241
- tags_to_join.append(en_tag)
242
- if tags_to_join: summary_parts.append(separator.join(tags_to_join))
243
- joiner = "\n\n" if separator != "\n" and len(summary_parts) > 1 else separator if separator == "\n" else " "
244
- final_summary = joiner.join(summary_parts)
245
- return final_summary if final_summary else "选定的类别中没有找到标签。"
246
-
247
- def process_image_and_generate_outputs(
248
- img, g_th, c_th, s_scores,
249
- s_gen, s_char, s_rat, s_sep, s_zh_in_sum,
250
- access_key, u_tencent_id, u_tencent_key, u_baidu_json,
251
- request: gr.Request
252
- ):
253
- if img is None:
254
- yield (gr.update(interactive=True, value="🚀 开始分析"), gr.update(visible=True, value="❌ 请先上传图片。"), "", "", "", "", gr.update(placeholder="请先上传图片并开始分析..."), {}, {}, {})
255
- return
256
-
257
- if tagger_instance is None:
258
- yield (gr.update(interactive=True, value="🚀 开始分析"), gr.update(visible=True, value="❌ ���析器未成功初始化,请检查控制台错误。"), "", "", "", "", gr.update(placeholder="分析器初始化失败..."), {}, {}, {})
259
- return
260
 
261
- yield (gr.update(interactive=False, value="🔄 处理中..."), gr.update(visible=True, value="🔄 正在分析图像,请稍候..."), gr.HTML(value="<p>分析中...</p>"), gr.HTML(value="<p>分析中...</p>"), gr.HTML(value="<p>分析中...</p>"), gr.update(value="分析中,请稍候..."), {}, {}, {})
 
 
 
262
 
263
- try:
264
- set_user_provided_keys(u_tencent_id, u_tencent_key, u_baidu_json)
265
-
266
- res, tag_categories_original_order = tagger_instance.predict(img, g_th, c_th)
267
- all_tags_to_translate = [tag for cat in tag_categories_original_order.values() for tag in cat]
268
-
269
- all_translations_flat = []
270
- if all_tags_to_translate:
271
- all_translations_flat = translate_texts(all_tags_to_translate, src_lang="auto", tgt_lang="zh", access_key=access_key)
272
-
273
- current_translations_dict = {}
274
- offset = 0
275
- for cat_key in ["general", "characters", "ratings"]:
276
- num_tags_in_cat = len(tag_categories_original_order.get(cat_key, []))
277
- current_translations_dict[cat_key] = all_translations_flat[offset : offset + num_tags_in_cat]
278
- offset += num_tags_in_cat
279
 
280
- general_html = format_tags_html(res.get("general", {}), current_translations_dict.get("general", []), "general", s_scores, True)
281
- char_html = format_tags_html(res.get("characters", {}), current_translations_dict.get("characters", []), "characters", s_scores, True)
282
- rating_html = format_tags_html(res.get("ratings", {}), current_translations_dict.get("ratings", []), "ratings", s_scores, True)
283
- summary_text = generate_summary_text_content(res, current_translations_dict, s_gen, s_char, s_rat, s_sep, s_zh_in_sum)
284
-
285
- yield (gr.update(interactive=True, value="🚀 开始分析"), gr.update(visible=True, value="✅ 分析完成!"), general_html, char_html, rating_html, gr.update(value=summary_text), res, current_translations_dict, tag_categories_original_order)
 
 
 
286
 
287
- except Exception as e:
288
- import traceback
289
- tb_str = traceback.format_exc()
290
- print(f"处理时发生错误: {e}\n{tb_str}")
291
- yield (gr.update(interactive=True, value="🚀 开始分析"), gr.update(visible=True, value=f"❌ 处理失败: {str(e)}"), "<p>处理出错</p>", "<p>处理出错</p>", "<p>处理出错</p>", gr.update(value=f"错误: {str(e)}", placeholder="分析失败..."), {}, {}, {})
292
- finally:
293
- clear_user_provided_keys()
294
-
295
- def update_summary_display(s_gen, s_char, s_rat, s_sep, s_zh_in_sum, current_res_from_state, current_translations_from_state):
296
- if not current_res_from_state:
297
- return gr.update(placeholder="请先完成一次图像分析以生成汇总。", value="")
298
- new_summary_text = generate_summary_text_content(current_res_from_state, current_translations_from_state, s_gen, s_char, s_rat, s_sep, s_zh_in_sum)
299
- return gr.update(value=new_summary_text)
300
-
301
- btn.click(
302
- process_image_and_generate_outputs,
303
- inputs=[
304
- img_in, gen_slider, char_slider, show_tag_scores,
305
- sum_general, sum_char, sum_rating, sum_sep, sum_show_zh,
306
- access_key_input, user_tencent_id, user_tencent_key, user_baidu_json
307
- ],
308
- outputs=[
309
- btn, processing_info,
310
- out_general, out_char, out_rating,
311
- out_summary,
312
- state_res, state_translations_dict, state_tag_categories_for_translation
313
- ]
314
- )
315
-
316
- summary_controls = [sum_general, sum_char, sum_rating, sum_sep, sum_show_zh]
317
- for ctrl in summary_controls:
318
- ctrl.change(fn=update_summary_display, inputs=summary_controls + [state_res, state_translations_dict], outputs=[out_summary])
319
-
320
- return demo
321
-
322
- with gr.Blocks(title="登录到图像标签分析器") as demo:
323
- CLIENT_ID = os.environ.get("HUGGING_FACE_CLIENT_ID")
324
- if not CLIENT_ID:
325
- gr.Markdown("# 错误:应用未配置 OIDC 客户端ID\n请在 Space secrets 中设置 `HUGGING_FACE_CLIENT_ID`")
326
- else:
327
- gr.Markdown("# 欢迎使用 AI 图像标签分析器\n请通过 Hugging Face 登录以继续")
328
- login_button = gr.LoginButton(
329
- value="🤗 通过 Hugging Face 登录",
330
- oauth_client_id=CLIENT_ID,
331
- oauth_scopes=["openid", "profile", "email"],
332
- oauth_redirect_uri=f"https://huggingface.co/spaces/{os.environ.get('SPACE_ID')}"
333
- )
334
- user_info_state = gr.State()
335
- login_button.login(lambda: None, None, None, js="""
336
- (btn) => {
337
- const url = new URL(window.location);
338
- if (url.searchParams.has('code')) {
339
- btn.style.display = 'none';
340
- }
341
- return btn;
342
- }
343
- """)
344
- demo.load(
345
- fn=lambda request: request.auth,
346
- inputs=gr.Request(inputs=[]),
347
- outputs=user_info_state,
348
- queue=False,
349
- js="""
350
- (request) => {
351
- const url = new URL(window.location);
352
- if (!url.searchParams.has('code') && !request.auth) {
353
- } else {
354
- document.getElementById('login-interface').style.display = 'none';
355
- document.getElementById('main-app-interface').style.display = 'block';
356
- }
357
- return request;
358
- }
359
- """
360
- )
361
 
362
- with gr.Column(elem_id="login-interface", visible=True):
363
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
 
365
- with gr.Column(elem_id="main-app-interface", visible=False):
366
- main_app = main_interface(gr.UserInfo())
 
 
 
 
367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  if __name__ == "__main__":
370
  if tagger_instance is None:
371
  print("CRITICAL: Tagger 未能初始化,应用功能将受限。请检查之前的错误信息。")
372
- if "SPACE_ID" in os.environ:
373
- demo.launch()
374
- else:
375
- with gr.Blocks() as local_demo:
376
- fake_user_info = gr.UserInfo(name="local_user", email="local@test.com")
377
- main_interface(fake_user_info)
378
- local_demo.launch(server_name="0.0.0.0", server_port=7860)
 
6
  import onnxruntime as rt
7
  import pandas as pd
8
  from PIL import Image
9
+ from huggingface_hub import login
10
+
11
+ from translator import translate_texts, translate_texts_with_dynamic_keys
12
 
13
  MODEL_REPO = "SmilingWolf/wd-eva02-large-tagger-v3"
14
  MODEL_FILENAME = "model.onnx"
15
  LABEL_FILENAME = "selected_tags.csv"
16
+
17
+ OWNER_USERNAME = os.environ.get("OWNER_USERNAME", "").lower()
18
+
19
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
20
  if HF_TOKEN:
21
  try:
22
  login(token=HF_TOKEN)
23
+ print("✅ HF_TOKEN 登录成功")
24
  except Exception as e:
25
+ print(f"⚠️ HF_TOKEN 登录失败: {e}")
26
  else:
27
+ print("⚠️ 未检测到 HF_TOKEN,私有模型可能下载失败")
28
+
29
 
30
+ # ------------------------------------------------------------------
31
+ # Tagger 类 (全局实例化)
32
+ # ------------------------------------------------------------------
33
  class Tagger:
34
  def __init__(self):
35
  self.hf_token = HF_TOKEN
 
130
  .tag-zh { color: #666; margin-left: 10px; }
131
  .tag-score { color: #999; font-size: 0.9em; }
132
  .btn-analyze-container { margin-top: 15px; margin-bottom: 15px; }
 
133
  """
134
 
135
  _js_functions = """
 
143
  let displayText = String(text);
144
  displayText = displayText.substring(0, 30) + (displayText.length > 30 ? '...' : '');
145
  feedback.textContent = '已复制: ' + displayText;
146
+ feedback.style.cssText = 'position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background-color: #4CAF50; color: white; padding: 10px 20px; border-radius: 5px; z-index: 10000; transition: opacity 0.5s ease-out;';
147
  document.body.appendChild(feedback);
148
  setTimeout(() => {
149
  feedback.style.opacity = '0';
150
+ setTimeout(() => { if (document.body.contains(feedback)) document.body.removeChild(feedback); }, 500);
151
  }, 1500);
152
  }).catch(err => {
153
+ console.error('Failed to copy tag. Error:', err, 'Attempted to copy text:', text);
154
  });
155
  }
156
  """
157
 
158
+ with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=custom_css, js=_js_functions) as demo:
159
+ with gr.Row():
160
+ login_button = gr.LoginButton()
161
+ user_info = gr.Markdown("请先登录...", visible=True)
162
+
163
+ gr.Markdown("# 🖼️ AI 图像标签分析器")
164
+ gr.Markdown("上传图片自动识别标签,支持中英文显示和一键复制。[NovelAI在线绘画](https://nai.idlecloud.cc/)")
165
+
166
+ state_res = gr.State({})
167
+ state_translations_dict = gr.State({})
168
+ state_tag_categories_for_translation = gr.State({})
169
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
+ with gr.Row():
172
+ with gr.Column(scale=1):
173
+ img_in = gr.Image(type="pil", label="上传图片", height=300)
174
+ btn = gr.Button("🚀 开始分析", variant="primary", elem_classes=["btn-analyze-container"])
175
 
176
+ with gr.Accordion("⚙️ 高级设置", open=False):
177
+ gen_slider = gr.Slider(0, 1, value=0.35, step=0.01, label="通用标签阈值", info="越高 → 标签更少更准")
178
+ char_slider = gr.Slider(0, 1, value=0.85, step=0.01, label="角色标签阈值", info="推荐保持较高阈值")
179
+ show_tag_scores = gr.Checkbox(True, label="在列表中显示标签置信度")
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
+ with gr.Group(visible=False) as guest_api_group:
182
+ gr.Markdown("### 访客翻译API配置\n由于您不是本空间所有者,需要提供自己的翻译API密钥才能使用翻译功能。")
183
+ guest_tencent_id = gr.Textbox(label="腾讯云 Secret ID", type="password")
184
+ guest_tencent_key = gr.Textbox(label="腾讯云 Secret Key", type="password")
185
+ guest_baidu_json = gr.TextArea(
186
+ label="百度翻译凭证 JSON",
187
+ placeholder='[{"app_id": "...", "secret_key": "..."}, ...]',
188
+ lines=3
189
+ )
190
 
191
+ with gr.Accordion("📊 标签汇总设置", open=True):
192
+ gr.Markdown("选择要包含在下方汇总文本框中的标签类别:")
193
+ with gr.Row():
194
+ sum_general = gr.Checkbox(True, label="通用标签", min_width=50)
195
+ sum_char = gr.Checkbox(True, label="角色标签", min_width=50)
196
+ sum_rating = gr.Checkbox(False, label="评分标签", min_width=50)
197
+ sum_sep = gr.Dropdown(["逗号", "换行", "空格"], value="逗号", label="标签之间的分隔符")
198
+ sum_show_zh = gr.Checkbox(False, label="在汇总中显示中文翻译")
199
+
200
+ processing_info = gr.Markdown("", visible=False)
201
+
202
+ with gr.Column(scale=2):
203
+ with gr.Tabs():
204
+ with gr.TabItem("🏷️ 通用标签"):
205
+ out_general = gr.HTML(label="General Tags")
206
+ with gr.TabItem("👤 角色标签"):
207
+ gr.Markdown("<p style='color:gray; font-size:small;'>提示:角色标签推测基于截至2024年2月的数据。</p>")
208
+ out_char = gr.HTML(label="Character Tags")
209
+ with gr.TabItem("⭐ 评分标签"):
210
+ out_rating = gr.HTML(label="Rating Tags")
211
+
212
+ gr.Markdown("### 标签汇总结果")
213
+ out_summary = gr.Textbox(
214
+ label="标签汇总",
215
+ placeholder="分析完成后,此处将显示汇总的英文标签...",
216
+ lines=5,
217
+ show_copy_button=True
218
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
+ def format_tags_html(tags_dict, translations_list, category_name, show_scores=True, show_translation_in_list=True):
221
+ if not tags_dict: return "<p>暂无标签</p>"
222
+ html = '<div class="label-container">'
223
+ tag_keys = list(tags_dict.keys())
224
+ for i, tag in enumerate(tag_keys):
225
+ score = tags_dict[tag]
226
+ escaped_tag = tag.replace("'", "\\'")
227
+ html += '<div class="tag-item">'
228
+ tag_display_html = f'<span class="tag-en" onclick="copyToClipboard(\'{escaped_tag}\')">{tag}</span>'
229
+ if show_translation_in_list and i < len(translations_list) and translations_list[i]:
230
+ tag_display_html += f'<span class="tag-zh">({translations_list[i]})</span>'
231
+ html += f'<div>{tag_display_html}</div>'
232
+ if show_scores: html += f'<span class="tag-score">{score:.3f}</span>'
233
+ html += '</div>'
234
+ html += '</div>'
235
+ return html
236
+
237
+ def generate_summary_text_content(current_res, current_translations_dict, s_gen, s_char, s_rat, s_sep_type, s_show_zh):
238
+ if not current_res: return "请先分析图像或选择要汇总的标签类别。"
239
+ summary_parts, separator = [], {"逗号": ", ", "换行": "\n", "空格": " "}.get(s_sep_type, ", ")
240
+ categories_to_summarize = []
241
+ if s_gen: categories_to_summarize.append("general")
242
+ if s_char: categories_to_summarize.append("characters")
243
+ if s_rat: categories_to_summarize.append("ratings")
244
+ if not categories_to_summarize: return "请至少选择一个标签类别进行汇总。"
245
+
246
+ for cat_key in categories_to_summarize:
247
+ if current_res.get(cat_key):
248
+ tags_to_join = []
249
+ cat_tags_en = list(current_res[cat_key].keys())
250
+ cat_translations = current_translations_dict.get(cat_key, [])
251
+ for i, en_tag in enumerate(cat_tags_en):
252
+ if s_show_zh and i < len(cat_translations) and cat_translations[i]:
253
+ tags_to_join.append(f"{en_tag}({cat_translations[i]})")
254
+ else:
255
+ tags_to_join.append(en_tag)
256
+ if tags_to_join: summary_parts.append(separator.join(tags_to_join))
257
+ joiner = "\n\n" if separator != "\n" and len(summary_parts) > 1 else separator
258
+ final_summary = joiner.join(summary_parts)
259
+ return final_summary if final_summary else "选定的类别中没有找到标签。"
260
+
261
+ def process_image_and_generate_outputs(img, g_th, c_th, s_scores, s_gen, s_char, s_rat, s_sep, s_zh_in_sum, guest_tc_id, guest_tc_key, guest_bd_json, profile: gr.OAuthProfile | None):
262
+ if profile is None:
263
+ gr.Warning("请先登录后再进行分析!")
264
+ # 返回当前状态,不做任何改变
265
+ yield (gr.update(), gr.update(), gr.HTML(), gr.HTML(), gr.HTML(), gr.update(), gr.State(), gr.State(), gr.State())
266
+ return
267
 
268
+ if img is None:
269
+ yield (gr.update(interactive=True, value="🚀 开始分析"), gr.update(visible=True, value="❌ 请先上传图片。"), "", "", "", "", gr.update(placeholder="请先上传图片并开始分析..."), {}, {}, {})
270
+ return
271
+ if tagger_instance is None:
272
+ yield (gr.update(interactive=True, value="🚀 开始分析"), gr.update(visible=True, value="❌ 分析器未成功初始化,请检查控制台错误。"), "", "", "", "", gr.update(placeholder="分析器初始化失败..."), {}, {}, {})
273
+ return
274
 
275
+ yield (gr.update(interactive=False, value="🔄 处理中..."), gr.update(visible=True, value="🔄 正在分析图像..."), gr.HTML(value="<p>分析中...</p>"), gr.HTML(value="<p>分析中...</p>"), gr.HTML(value="<p>分析中...</p>"), gr.update(value="分析中..."), {}, {}, {})
276
+
277
+ try:
278
+ res, tag_categories_original_order = tagger_instance.predict(img, g_th, c_th)
279
+ all_tags_to_translate = [tag for cat_key in ["general", "characters", "ratings"] for tag in tag_categories_original_order.get(cat_key, [])]
280
+
281
+ all_translations_flat = []
282
+ if all_tags_to_translate:
283
+ is_owner = profile.username.lower() == OWNER_USERNAME
284
+ if is_owner:
285
+ print("- [Auth] 所有者身份,使用预置密钥进行翻译。")
286
+ all_translations_flat = translate_texts(all_tags_to_translate)
287
+ else:
288
+ print("- [Auth] 访客身份,使用用户提供的密钥进行翻译。")
289
+ if not guest_tc_id and not guest_bd_json:
290
+ print(" - [Warning] 访客未提供任何API密钥,将跳过翻译。")
291
+ all_translations_flat = all_tags_to_translate
292
+ else:
293
+ all_translations_flat = translate_texts_with_dynamic_keys(all_tags_to_translate, guest_tc_id, guest_tc_key, guest_bd_json)
294
+
295
+ current_translations_dict = {}
296
+ offset = 0
297
+ for cat_key in ["general", "characters", "ratings"]:
298
+ num_tags = len(tag_categories_original_order.get(cat_key, []))
299
+ current_translations_dict[cat_key] = all_translations_flat[offset : offset + num_tags]
300
+ offset += num_tags
301
+
302
+ general_html = format_tags_html(res.get("general", {}), current_translations_dict.get("general", []), "general", s_scores)
303
+ char_html = format_tags_html(res.get("characters", {}), current_translations_dict.get("characters", []), "characters", s_scores)
304
+ rating_html = format_tags_html(res.get("ratings", {}), current_translations_dict.get("ratings", []), "ratings", s_scores)
305
+ summary_text = generate_summary_text_content(res, current_translations_dict, s_gen, s_char, s_rat, s_sep, s_zh_in_sum)
306
 
307
+ yield (gr.update(interactive=True, value="🚀 开始分析"), gr.update(visible=True, value="✅ 分析完成!"), general_html, char_html, rating_html, gr.update(value=summary_text), res, current_translations_dict, tag_categories_original_order)
308
+
309
+ except Exception as e:
310
+ import traceback
311
+ tb_str = traceback.format_exc()
312
+ print(f"处理时发生错误: {e}\n{tb_str}")
313
+ yield (gr.update(interactive=True, value="🚀 开始分析"), gr.update(visible=True, value=f"❌ 处理失败: {str(e)}"), "<p>处理出错</p>", "<p>处理出错</p>", "<p>处理出错</p>", gr.update(value=f"错误: {str(e)}", placeholder="分析失败..."), {}, {}, {})
314
+
315
+ def update_summary_display(s_gen, s_char, s_rat, s_sep, s_zh_in_sum, current_res, current_translations):
316
+ if not current_res: return gr.update(placeholder="请先完成一次图像分析以生成汇总。", value="")
317
+ new_summary_text = generate_summary_text_content(current_res, current_translations, s_gen, s_char, s_rat, s_sep, s_zh_in_sum)
318
+ return gr.update(value=new_summary_text)
319
+
320
+ # --- 修改后的认证检查函数 ---
321
+ def check_user_auth(profile: gr.OAuthProfile | None):
322
+ if not OWNER_USERNAME: print("⚠️ 警告: 未设置 OWNER_USERNAME 环境变量。所有用户都将被视为访客。")
323
+
324
+ if profile is None:
325
+ print("- [Auth] 用户未登录。")
326
+ return gr.update(visible=True), "请先登录..."
327
+
328
+ username = profile.username.lower()
329
+ if username == OWNER_USERNAME:
330
+ print(f"- [Auth] 所有者 '{profile.username}' 已连接。")
331
+ user_info_md = f"✅ **所有者模式**: 欢迎, {profile.name}! 将使用预置翻译服务。"
332
+ return gr.update(visible=False), user_info_md
333
+ else:
334
+ print(f"- [Auth] 访客 '{profile.username}' 已连接,显示 API Key 输入框。")
335
+ user_info_md = f"👋 **访客模式**: 欢迎, {profile.name}! 请在高级设置中提供您自己的翻译密钥。"
336
+ return gr.update(visible=True), user_info_md
337
+
338
+ # --- 修改事件绑定 ---
339
+ login_button.load(fn=check_user_auth, inputs=login_button, outputs=[guest_api_group, user_info])
340
+
341
+ btn.click(
342
+ process_image_and_generate_outputs,
343
+ inputs=[img_in, gen_slider, char_slider, show_tag_scores, sum_general, sum_char, sum_rating, sum_sep, sum_show_zh, guest_tencent_id, guest_tencent_key, guest_baidu_json, login_button],
344
+ outputs=[btn, processing_info, out_general, out_char, out_rating, out_summary, state_res, state_translations_dict, state_tag_categories_for_translation]
345
+ )
346
+
347
+ summary_controls = [sum_general, sum_char, sum_rating, sum_sep, sum_show_zh]
348
+ for ctrl in summary_controls:
349
+ ctrl.change(fn=update_summary_display, inputs=summary_controls + [state_res, state_translations_dict], outputs=[out_summary])
350
+
351
  if __name__ == "__main__":
352
  if tagger_instance is None:
353
  print("CRITICAL: Tagger 未能初始化,应用功能将受限。请检查之前的错误信息。")
354
+ demo.launch(server_name="0.0.0.0", server_port=7860)