zhongchuyi commited on
Commit
e43bcf1
·
1 Parent(s): fad08a2

feature:优化 2026_01_13

Browse files
Files changed (2) hide show
  1. app.py +92 -10
  2. reference_retriever.py +29 -0
app.py CHANGED
@@ -35,6 +35,7 @@ DETECT_FORCE_DARK = '''<script>
35
  })();
36
  </script>'''
37
  from ai_service import design_mahjong_game
 
38
  # 优先尝试原生流式;没有则自动回退为伪流式
39
  try:
40
  from ai_service import design_mahjong_game_stream
@@ -146,6 +147,54 @@ def _chunk_fake_stream(text, step=40):
146
  yield s[i:i + step]
147
 
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  def _load_base64_image(path):
150
  """读取本地图片并返回 data URI"""
151
  import base64
@@ -465,6 +514,7 @@ with gr.Blocks(
465
  ready_to_generate = gr.State(False)
466
  design_state_history = gr.State(create_empty_history()) # 版本历史
467
  interaction_phase_state = gr.State(create_phase_state()) # 多阶段交互状态
 
468
 
469
  with gr.Group(elem_classes="side-card"):
470
  gr.Markdown("### 可控迭代(推荐)")
@@ -590,23 +640,40 @@ with gr.Blocks(
590
  ds_ver_prev,
591
  ready_prev,
592
  history_data,
 
593
  ):
594
  user_text = (user_text or "").strip()
595
  diff_md_prev = "_尚无变更记录_" # 保留上次的差异摘要
596
  cur_choices = get_history_choices(history_data)
 
597
  if not user_text:
598
  # 不提交空消息:输出不变
599
- yield history_msgs, "", history_msgs, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:未变更", ready_prev, "READY_TO_GENERATE:未知", "输出校验:未运行", diff_md_prev, history_data, gr.update(choices=cur_choices), "_无版本历史_" if not cur_choices else f"共 {len(cur_choices)} 个版本"
600
  return
601
 
602
  # 立即显示"用户消息"
603
  history = list(history_msgs or [])
604
  history.append({"role": "user", "content": user_text})
605
- yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:处理中…", ready_prev, "READY_TO_GENERATE:未知", "输出校验:运行中…", "_处理中..._", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_"
606
 
607
  # 添加空的助手气泡,用于逐步填充
608
  history.append({"role": "assistant", "content": ""})
609
- yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:处理中…", ready_prev, "READY_TO_GENERATE:未知", "输出校验:运行中…", "_处理中..._", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
610
 
611
  # 流式生成内容
612
  tuples_hist = _messages_to_tuples(history)
@@ -623,15 +690,22 @@ with gr.Blocks(
623
 
624
  if analyse_enabled:
625
  tuples_to_send = [] # Analyse 模式不吃长history,避免漂移
 
 
 
 
 
626
  effective_user_text = (
627
  "<ANALYSE_MODE>true</ANALYSE_MODE>\n"
628
  "你正在进行【Analyse 模式】(单问题迭代)。\n"
629
  "目标:通过多轮问答把需求场景收敛到可生成完整玩法。\n"
630
  "限制:本轮禁止输出 mGDL/完整规则/自检报告。\n\n"
 
631
  "当前已有 DesignState(如为空表示尚未建立初版):\n"
632
  "<DESIGN_STATE_JSON>\n{ds}\n</DESIGN_STATE_JSON>\n\n"
633
  "用户本轮补充信息:\n{req}\n"
634
- ).format(ds=(ds_raw_prev or "{}").strip(), req=user_text)
 
635
  status_hint = "DesignState:Analyse模式中…"
636
  elif iterative_enabled and (ds_raw_prev or "").strip():
637
  tuples_to_send = [] # 用“当前 DesignState”替代长历史,减少漂移
@@ -653,10 +727,10 @@ with gr.Blocks(
653
  if not piece:
654
  continue
655
  history[-1]["content"] += str(piece)
656
- yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, status_hint, ready_to_gen, ready_md, "输出校验:运行中…", "_处理中..._", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_"
657
  except Exception as e:
658
  history[-1]["content"] += f"\n(流式出错){type(e).__name__}: {e}"
659
- yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:流式出错(未变更)", ready_to_gen, "READY_TO_GENERATE:未知", "输出校验:未运行", "_处理出错_", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_"
660
  else:
661
  try:
662
  full = design_mahjong_game(effective_user_text, tuples_to_send, files, custom_prompt, mode)
@@ -664,7 +738,7 @@ with gr.Blocks(
664
  full = f"(出错){type(e).__name__}: {e}"
665
  for piece in _chunk_fake_stream(str(full), step=40):
666
  history[-1]["content"] += piece
667
- yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, status_hint, ready_to_gen, ready_md, "输出校验:运行中…", "_处理中..._", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_"
668
 
669
  # 提取 GDL 和自然语言描述并保存
670
  new_ds_obj, new_ds_raw = extract_design_state(history[-1]["content"])
@@ -717,7 +791,9 @@ with gr.Blocks(
717
 
718
  # Analyse 模式不产出完整规则,不导出GDL/自然语言文件
719
  if analyse_enabled:
720
- yield history, "", history, "", "", "", (new_ds_raw or ds_raw_prev), (new_ds_obj or ds_obj_prev), ds_ver, ds_status, ready_to_gen, ready_md, "输出校验:Analyse 模式跳过", diff_summary_md, updated_history_data, gr.update(choices=new_choices, value=new_choices[-1] if new_choices else None), history_status_md
 
 
721
  return
722
 
723
  gdl_content, narrative_content = extract_gdl_and_narrative(history[-1]["content"])
@@ -728,10 +804,10 @@ with gr.Blocks(
728
  design_log_path = save_design_log(design_log_content)
729
 
730
  # 返回文件路径,以便下载
731
- yield history, "", history, gdl_path, narrative_path, design_log_path, (new_ds_raw or ds_raw_prev), (new_ds_obj or ds_obj_prev), ds_ver, ds_status, ready_to_gen, ready_md, render_validation_md(history[-1]["content"]), diff_summary_md, updated_history_data, gr.update(choices=new_choices, value=new_choices[-1] if new_choices else None), history_status_md
732
  except Exception as e:
733
  print(f"保存GDL和自然语言文件时出错: {e}")
734
- yield history, "", history, "", "", "", (new_ds_raw or ds_raw_prev), (new_ds_obj or ds_obj_prev), ds_ver, ds_status, ready_to_gen, ready_md, render_validation_md(history[-1]["content"]), diff_summary_md, updated_history_data, gr.update(choices=new_choices, value=new_choices[-1] if new_choices else None), history_status_md
735
 
736
  def on_generate_from_state(
737
  history_msgs,
@@ -838,6 +914,7 @@ with gr.Blocks(
838
  design_state_version,
839
  ready_to_generate,
840
  design_state_history,
 
841
  ],
842
  outputs=[
843
  chatbot,
@@ -857,6 +934,7 @@ with gr.Blocks(
857
  design_state_history,
858
  version_dropdown,
859
  history_status,
 
860
  ],
861
  preprocess=True,
862
  )
@@ -878,6 +956,7 @@ with gr.Blocks(
878
  design_state_version,
879
  ready_to_generate,
880
  design_state_history,
 
881
  ],
882
  outputs=[
883
  chatbot,
@@ -897,6 +976,7 @@ with gr.Blocks(
897
  design_state_history,
898
  version_dropdown,
899
  history_status,
 
900
  ],
901
  preprocess=True,
902
  )
@@ -1212,6 +1292,7 @@ with gr.Blocks(
1212
  gr.update(visible=False), gr.update(choices=[], value=None),
1213
  gr.update(visible=False), gr.update(visible=False),
1214
  create_scope_config(), "_范围约束: 预设模式_",
 
1215
  )
1216
 
1217
  clear_dialog_btn.click(
@@ -1243,6 +1324,7 @@ with gr.Blocks(
1243
  diverge_action_row,
1244
  scope_config_state,
1245
  scope_status,
 
1246
  ],
1247
  )
1248
 
 
35
  })();
36
  </script>'''
37
  from ai_service import design_mahjong_game
38
+ from reference_retriever import match_variants_in_text, load_variant_md
39
  # 优先尝试原生流式;没有则自动回退为伪流式
40
  try:
41
  from ai_service import design_mahjong_game_stream
 
147
  yield s[i:i + step]
148
 
149
 
150
+ def _is_design_intent(text: str) -> bool:
151
+ """判断是否明确进入“新玩法设计/融合/生成”路径。"""
152
+ s = (text or "").strip()
153
+ if not s:
154
+ return False
155
+ keywords = [
156
+ "设计", "生成", "做一个", "做个", "新玩法", "创新", "融合", "组合", "改造", "迭代",
157
+ "玩法设计", "规则设计", "番型设计", "出一套", "给我一套",
158
+ ]
159
+ return any(k in s for k in keywords)
160
+
161
+
162
+ def _is_rules_query(text: str) -> bool:
163
+ """判断是否为“询问现有玩法细节/规则”的问题。"""
164
+ s = (text or "").strip()
165
+ if not s:
166
+ return False
167
+ keywords = [
168
+ "规则", "玩法", "番型", "细节", "说明", "机制", "怎么打", "怎么胡", "如何胡", "计分", "流程",
169
+ "介绍", "讲讲", "是什么", "有哪些",
170
+ ]
171
+ return any(k in s for k in keywords)
172
+
173
+
174
+ def _normalize_question(text: str) -> str:
175
+ s = str(text or "").strip().lower()
176
+ if not s:
177
+ return ""
178
+ punct = "??!!。;;::、,,..()()[]【】<>《》\"'“”‘’ "
179
+ for ch in punct:
180
+ s = s.replace(ch, "")
181
+ return s
182
+
183
+
184
+ def _merge_asked_questions(existing, new_items):
185
+ merged = [str(q).strip() for q in (existing or []) if str(q).strip()]
186
+ seen = {_normalize_question(q) for q in merged if _normalize_question(q)}
187
+ for q in (new_items or []):
188
+ q_text = str(q).strip()
189
+ if not q_text:
190
+ continue
191
+ key = _normalize_question(q_text)
192
+ if key and key not in seen:
193
+ merged.append(q_text)
194
+ seen.add(key)
195
+ return merged
196
+
197
+
198
  def _load_base64_image(path):
199
  """读取本地图片并返回 data URI"""
200
  import base64
 
514
  ready_to_generate = gr.State(False)
515
  design_state_history = gr.State(create_empty_history()) # 版本历史
516
  interaction_phase_state = gr.State(create_phase_state()) # 多阶段交互状态
517
+ asked_questions_state = gr.State([]) # Analyse 模式:已问过的问题
518
 
519
  with gr.Group(elem_classes="side-card"):
520
  gr.Markdown("### 可控迭代(推荐)")
 
640
  ds_ver_prev,
641
  ready_prev,
642
  history_data,
643
+ asked_questions,
644
  ):
645
  user_text = (user_text or "").strip()
646
  diff_md_prev = "_尚无变更记录_" # 保留上次的差异摘要
647
  cur_choices = get_history_choices(history_data)
648
+ asked_questions = asked_questions or []
649
  if not user_text:
650
  # 不提交空消息:输出不变
651
+ yield history_msgs, "", history_msgs, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:未变更", ready_prev, "READY_TO_GENERATE:未知", "输出校验:未运行", diff_md_prev, history_data, gr.update(choices=cur_choices), "_无版本历史_" if not cur_choices else f"共 {len(cur_choices)} 个版本", asked_questions
652
  return
653
 
654
  # 立即显示"用户消息"
655
  history = list(history_msgs or [])
656
  history.append({"role": "user", "content": user_text})
657
+ yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:处理中…", ready_prev, "READY_TO_GENERATE:未知", "输出校验:运行中…", "_处理中..._", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_", asked_questions
658
 
659
  # 添加空的助手气泡,用于逐步填充
660
  history.append({"role": "assistant", "content": ""})
661
+ yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:处理中…", ready_prev, "READY_TO_GENERATE:未知", "输出校验:运行中…", "_处理中..._", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_", asked_questions
662
+
663
+ # ====== 现有玩法细节查询:直接返回本地玩法库内容 ======
664
+ if (not analyse_enabled) and (not iterative_enabled) and _is_rules_query(user_text) and (not _is_design_intent(user_text)):
665
+ matched = match_variants_in_text(user_text)
666
+ if matched:
667
+ parts = []
668
+ for name in matched:
669
+ md_text = (load_variant_md(name) or "").strip()
670
+ if md_text:
671
+ parts.append(f"### {name}\n\n{md_text}")
672
+ else:
673
+ parts.append(f"### {name}\n\n(未找到该玩法的本地规则文件)")
674
+ history[-1]["content"] = "\n\n---\n\n".join(parts).strip()
675
+ yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:未变更", ready_prev, "READY_TO_GENERATE:未知", "输出校验:未运行", "_未生成设计_", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_", asked_questions
676
+ return
677
 
678
  # 流式生成内容
679
  tuples_hist = _messages_to_tuples(history)
 
690
 
691
  if analyse_enabled:
692
  tuples_to_send = [] # Analyse 模式不吃长history,避免漂移
693
+ asked_block = ""
694
+ if asked_questions:
695
+ asked_block = "已问过的问题(禁止重复):\n" + "\n".join(
696
+ f"- {q}" for q in asked_questions[:20]
697
+ ) + "\n\n"
698
  effective_user_text = (
699
  "<ANALYSE_MODE>true</ANALYSE_MODE>\n"
700
  "你正在进行【Analyse 模式】(单问题迭代)。\n"
701
  "目标:通过多轮问答把需求场景收敛到可生成完整玩法。\n"
702
  "限制:本轮禁止输出 mGDL/完整规则/自检报告。\n\n"
703
+ "{asked}"
704
  "当前已有 DesignState(如为空表示尚未建立初版):\n"
705
  "<DESIGN_STATE_JSON>\n{ds}\n</DESIGN_STATE_JSON>\n\n"
706
  "用户本轮补充信息:\n{req}\n"
707
+ "要求:本轮只能问 1 个问题;不得重复已问过的问题;若信息已足够则直接 READY_TO_GENERATE: true。\n"
708
+ ).format(asked=asked_block, ds=(ds_raw_prev or "{}").strip(), req=user_text)
709
  status_hint = "DesignState:Analyse模式中…"
710
  elif iterative_enabled and (ds_raw_prev or "").strip():
711
  tuples_to_send = [] # 用“当前 DesignState”替代长历史,减少漂移
 
727
  if not piece:
728
  continue
729
  history[-1]["content"] += str(piece)
730
+ yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, status_hint, ready_to_gen, ready_md, "输出校验:运行中…", "_处理中..._", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_", asked_questions
731
  except Exception as e:
732
  history[-1]["content"] += f"\n(流式出错){type(e).__name__}: {e}"
733
+ yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, "DesignState:流式出错(未变更)", ready_to_gen, "READY_TO_GENERATE:未知", "输出校验:未运行", "_处理出错_", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_", asked_questions
734
  else:
735
  try:
736
  full = design_mahjong_game(effective_user_text, tuples_to_send, files, custom_prompt, mode)
 
738
  full = f"(出错){type(e).__name__}: {e}"
739
  for piece in _chunk_fake_stream(str(full), step=40):
740
  history[-1]["content"] += piece
741
+ yield history, "", history, "", "", "", ds_raw_prev, ds_obj_prev, ds_ver_prev, status_hint, ready_to_gen, ready_md, "输出校验:运行中…", "_处理中..._", history_data, gr.update(choices=cur_choices), f"共 {len(cur_choices)} 个版本" if cur_choices else "_无版本历史_", asked_questions
742
 
743
  # 提取 GDL 和自然语言描述并保存
744
  new_ds_obj, new_ds_raw = extract_design_state(history[-1]["content"])
 
791
 
792
  # Analyse 模式不产出完整规则,不导出GDL/自然语言文件
793
  if analyse_enabled:
794
+ new_questions = extract_clarify_questions(history[-1]["content"])
795
+ asked_questions = _merge_asked_questions(asked_questions, new_questions)
796
+ yield history, "", history, "", "", "", (new_ds_raw or ds_raw_prev), (new_ds_obj or ds_obj_prev), ds_ver, ds_status, ready_to_gen, ready_md, "输出校验:Analyse 模式跳过", diff_summary_md, updated_history_data, gr.update(choices=new_choices, value=new_choices[-1] if new_choices else None), history_status_md, asked_questions
797
  return
798
 
799
  gdl_content, narrative_content = extract_gdl_and_narrative(history[-1]["content"])
 
804
  design_log_path = save_design_log(design_log_content)
805
 
806
  # 返回文件路径,以便下载
807
+ yield history, "", history, gdl_path, narrative_path, design_log_path, (new_ds_raw or ds_raw_prev), (new_ds_obj or ds_obj_prev), ds_ver, ds_status, ready_to_gen, ready_md, render_validation_md(history[-1]["content"]), diff_summary_md, updated_history_data, gr.update(choices=new_choices, value=new_choices[-1] if new_choices else None), history_status_md, asked_questions
808
  except Exception as e:
809
  print(f"保存GDL和自然语言文件时出错: {e}")
810
+ yield history, "", history, "", "", "", (new_ds_raw or ds_raw_prev), (new_ds_obj or ds_obj_prev), ds_ver, ds_status, ready_to_gen, ready_md, render_validation_md(history[-1]["content"]), diff_summary_md, updated_history_data, gr.update(choices=new_choices, value=new_choices[-1] if new_choices else None), history_status_md, asked_questions
811
 
812
  def on_generate_from_state(
813
  history_msgs,
 
914
  design_state_version,
915
  ready_to_generate,
916
  design_state_history,
917
+ asked_questions_state,
918
  ],
919
  outputs=[
920
  chatbot,
 
934
  design_state_history,
935
  version_dropdown,
936
  history_status,
937
+ asked_questions_state,
938
  ],
939
  preprocess=True,
940
  )
 
956
  design_state_version,
957
  ready_to_generate,
958
  design_state_history,
959
+ asked_questions_state,
960
  ],
961
  outputs=[
962
  chatbot,
 
976
  design_state_history,
977
  version_dropdown,
978
  history_status,
979
+ asked_questions_state,
980
  ],
981
  preprocess=True,
982
  )
 
1292
  gr.update(visible=False), gr.update(choices=[], value=None),
1293
  gr.update(visible=False), gr.update(visible=False),
1294
  create_scope_config(), "_范围约束: 预设模式_",
1295
+ [],
1296
  )
1297
 
1298
  clear_dialog_btn.click(
 
1324
  diverge_action_row,
1325
  scope_config_state,
1326
  scope_status,
1327
+ asked_questions_state,
1328
  ],
1329
  )
1330
 
reference_retriever.py CHANGED
@@ -124,6 +124,35 @@ def _build_variant_index() -> Dict[str, Dict[str, str]]:
124
  return cleaned
125
 
126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  def _find_mentions(text: str, candidates: List[str]) -> List[str]:
128
  """
129
  朴素子串匹配(中文玩法名通常稳定),返回按“更长优先”的去重命中列表。
 
124
  return cleaned
125
 
126
 
127
+ def list_variant_names() -> List[str]:
128
+ """
129
+ 返回本地玩法库中可用的玩法名列表(含“麻将机制说明”)。
130
+ """
131
+ index = _build_variant_index()
132
+ names = sorted(index.keys(), key=lambda x: len(x), reverse=True)
133
+ return names
134
+
135
+
136
+ def match_variants_in_text(text: str) -> List[str]:
137
+ """
138
+ 从用户文本中匹配玩法名(优先更长的名称)。
139
+ """
140
+ candidates = list_variant_names()
141
+ return _find_mentions(text, candidates)
142
+
143
+
144
+ def load_variant_md(name: str) -> str:
145
+ """
146
+ 读取指定玩法的 .md 内容(若不存在则返回空字符串)。
147
+ """
148
+ index = _build_variant_index()
149
+ entry = index.get(name) or {}
150
+ md_path = entry.get("md_path")
151
+ if not md_path or not os.path.exists(md_path):
152
+ return ""
153
+ return _read_text(md_path) or ""
154
+
155
+
156
  def _find_mentions(text: str, candidates: List[str]) -> List[str]:
157
  """
158
  朴素子串匹配(中文玩法名通常稳定),返回按“更长优先”的去重命中列表。