@@ -291,35 +291,74 @@ def _indicator_debug_summary(validation: Dict[str, Any] | None = None) -> Dict[s
291291 }
292292
293293
294- def _indicator_hint_to_text (hint_code : str , params : Dict [str , Any ] | None = None ) -> str :
294+ def _request_lang (default : str = "zh-CN" ) -> str :
295+ raw = (
296+ request .headers .get ("X-App-Lang" )
297+ or request .headers .get ("Accept-Language" )
298+ or default
299+ )
300+ lang = str (raw or default ).split ("," , 1 )[0 ].strip ()
301+ return lang or default
302+
303+
304+ def _is_zh_lang (lang : str | None ) -> bool :
305+ return str (lang or "zh-CN" ).strip ().lower ().startswith ("zh" )
306+
307+
308+ def _indicator_ai_text (key : str , lang : str = "zh-CN" ) -> str :
309+ is_zh = _is_zh_lang (lang )
310+ texts = {
311+ "prompt_required" : "提示词不能为空" if is_zh else "Prompt cannot be empty" ,
312+ "insufficient_credits" : "积分不足,请充值后重试" if is_zh else "Insufficient credits. Please top up and try again." ,
313+ }
314+ return texts .get (key , key )
315+
316+
317+ def _indicator_hint_to_text (hint_code : str , params : Dict [str , Any ] | None = None , lang : str = "zh-CN" ) -> str :
295318 params = params or {}
319+ is_zh = _is_zh_lang (lang )
296320 if hint_code == "DECLARED_PARAMS_NOT_READ_VIA_PARAMS_GET" :
297321 names = params .get ("names" ) or []
298- joined = "、" .join (names ) if names else "参数"
299- return f"已检测到声明的参数未通过 params.get(...) 读取:{ joined } 。"
322+ joined = "、" .join (names ) if (names and is_zh ) else ", " .join (names )
323+ if not joined :
324+ joined = "参数" if is_zh else "parameters"
325+ return (
326+ f"已检测到声明的参数未通过 params.get(...) 读取:{ joined } 。"
327+ if is_zh else
328+ f"Declared parameters are not being read via params.get(...): { joined } ."
329+ )
300330 if hint_code == "SIGNAL_MARKERS_USE_WHERE_NONE" :
301- return "已检测到信号标记使用 where(..., None).tolist(),建议改为显式 None 列表以避免 NaN 渲染问题。"
331+ return (
332+ "已检测到信号标记使用 where(..., None).tolist(),建议改为显式 None 列表以避免 NaN 渲染问题。"
333+ if is_zh else
334+ "Signal markers use where(..., None).tolist(); prefer an explicit None list to avoid NaN rendering issues."
335+ )
302336 if hint_code == "MISSING_OUTPUT" :
303- return "缺少 output 字典。"
337+ return "缺少 output 字典。" if is_zh else "Missing output dictionary."
304338 if hint_code == "MISSING_BUY_SELL_COLUMNS" :
305- return "缺少 df['buy'] 或 df['sell'] 信号列。"
339+ return "缺少 df['buy'] 或 df['sell'] 信号列。" if is_zh else "Missing df['buy'] or df['sell'] signal columns."
306340 if hint_code == "MISSING_DF_COPY" :
307- return "缺少 df = df.copy()。"
341+ return "缺少 df = df.copy()。" if is_zh else "Missing df = df.copy()."
308342 if hint_code == "MISSING_INDICATOR_NAME" :
309- return "缺少 my_indicator_name。"
343+ return "缺少 my_indicator_name。" if is_zh else "Missing my_indicator_name."
310344 if hint_code == "MISSING_INDICATOR_DESCRIPTION" :
311- return "缺少 my_indicator_description。"
345+ return "缺少 my_indicator_description。" if is_zh else "Missing my_indicator_description."
312346 if hint_code == "UNKNOWN_STRATEGY_KEY" :
313- return f"存在未知的 @strategy 键:{ params .get ('key' ) or 'unknown' } 。"
347+ key = params .get ('key' ) or 'unknown'
348+ return (
349+ f"存在未知的 @strategy 键:{ key } 。"
350+ if is_zh else
351+ f"Unknown @strategy key detected: { key } ."
352+ )
314353 if hint_code == "NO_STRATEGY_ANNOTATIONS" :
315- return "没有声明任何 @strategy 默认配置。"
354+ return "没有声明任何 @strategy 默认配置。" if is_zh else "No @strategy default configuration was declared."
316355 if hint_code == "NO_STOP_AND_TAKE_PROFIT" :
317- return "未声明止损和止盈默认配置。"
356+ return "未声明止损和止盈默认配置。" if is_zh else "Stop-loss and take-profit defaults are not declared."
318357 if hint_code == "NO_STOP_LOSS" :
319- return "未声明止损默认配置。"
358+ return "未声明止损默认配置。" if is_zh else "Stop-loss default is not declared."
320359 if hint_code == "NO_TAKE_PROFIT" :
321- return "未声明止盈默认配置。"
322- return f"检测到代码提示:{ hint_code } "
360+ return "未声明止盈默认配置。" if is_zh else "Take-profit default is not declared."
361+ return f"检测到代码提示:{ hint_code } " if is_zh else f"Code hint detected: { hint_code } "
323362
324363
325364def _indicator_human_summary (
@@ -328,7 +367,9 @@ def _indicator_human_summary(
328367 auto_fix_applied : bool ,
329368 auto_fix_succeeded : bool ,
330369 returned_candidate : str ,
370+ lang : str = "zh-CN" ,
331371) -> Dict [str , Any ]:
372+ is_zh = _is_zh_lang (lang )
332373 initial_hints = initial_validation .get ("hints" ) or []
333374 final_hints = final_validation .get ("hints" ) or []
334375 initial_codes = {h .get ("code" ) for h in initial_hints if h .get ("code" )}
@@ -337,27 +378,27 @@ def _indicator_human_summary(
337378 remaining_codes = sorted (final_codes )
338379
339380 fixed_messages = [
340- _indicator_hint_to_text (h .get ("code" ), h .get ("params" ))
381+ _indicator_hint_to_text (h .get ("code" ), h .get ("params" ), lang = lang )
341382 for h in initial_hints
342383 if h .get ("code" ) in fixed_codes
343384 ]
344385 remaining_messages = [
345- _indicator_hint_to_text (h .get ("code" ), h .get ("params" ))
386+ _indicator_hint_to_text (h .get ("code" ), h .get ("params" ), lang = lang )
346387 for h in final_hints
347388 if h .get ("code" ) in remaining_codes
348389 ]
349390
350391 if auto_fix_applied and auto_fix_succeeded :
351- title = "AI 已自动修复并返回更稳定的指标代码"
392+ title = "AI 已自动修复并返回更稳定的指标代码" if is_zh else "AI auto-fixed the indicator code and returned a more stable version"
352393 elif auto_fix_applied :
353- title = "AI 尝试自动修复,但仍保留部分问题"
394+ title = "AI 尝试自动修复,但仍保留部分问题" if is_zh else "AI attempted to auto-fix the code, but some issues still remain"
354395 else :
355- title = "AI 已生成指标代码,并通过当前质检流程"
396+ title = "AI 已生成指标代码,并通过当前质检流程" if is_zh else "AI generated indicator code and it passed the current QA flow"
356397
357398 if returned_candidate == "repaired" :
358- returned_text = "当前返回的是自动修复后的代码。"
399+ returned_text = "当前返回的是自动修复后的代码。" if is_zh else "The returned code is the auto-fixed version."
359400 else :
360- returned_text = "当前返回的是首次生成的代码。"
401+ returned_text = "当前返回的是首次生成的代码。" if is_zh else "The returned code is the initially generated version."
361402
362403 return {
363404 "title" : title ,
@@ -687,13 +728,14 @@ def ai_generate():
687728 Local-first: if OpenRouter key is not configured, we return a reasonable template.
688729 """
689730 data = request .get_json () or {}
731+ lang = _request_lang ()
690732 prompt = (data .get ("prompt" ) or "" ).strip ()
691733 existing = (data .get ("existingCode" ) or "" ).strip ()
692734
693735 if not prompt :
694736 # Keep SSE contract (match PHP behavior) so frontend doesn't look "stuck".
695737 def _err_stream ():
696- yield "data: " + json .dumps ({"error" : "提示词不能为空" }, ensure_ascii = False ) + "\n \n "
738+ yield "data: " + json .dumps ({"error" : _indicator_ai_text ( "prompt_required" , lang ) }, ensure_ascii = False ) + "\n \n "
697739 yield "data: [DONE]\n \n "
698740
699741 return Response (
@@ -1019,7 +1061,7 @@ def _generate_final_code() -> tuple[str, Dict[str, Any]]:
10191061 "final_validation" : _indicator_debug_summary (validation ),
10201062 }
10211063 debug ["human_summary" ] = _indicator_human_summary (
1022- validation , validation , False , False , "initial"
1064+ validation , validation , False , False , "initial" , lang = lang
10231065 )
10241066 logger .info ("ai_generate debug=%s" , json .dumps (debug , ensure_ascii = False ))
10251067 return code_text , debug
@@ -1038,7 +1080,7 @@ def _generate_final_code() -> tuple[str, Dict[str, Any]]:
10381080 "auto_fix_error" : str (e ),
10391081 }
10401082 debug ["human_summary" ] = _indicator_human_summary (
1041- validation , validation , True , False , "initial"
1083+ validation , validation , True , False , "initial" , lang = lang
10421084 )
10431085 logger .info ("ai_generate debug=%s" , json .dumps (debug , ensure_ascii = False ))
10441086 return code_text , debug
@@ -1054,7 +1096,7 @@ def _generate_final_code() -> tuple[str, Dict[str, Any]]:
10541096 "final_validation" : _indicator_debug_summary (repaired_validation ),
10551097 }
10561098 debug ["human_summary" ] = _indicator_human_summary (
1057- validation , repaired_validation , True , True , "repaired"
1099+ validation , repaired_validation , True , True , "repaired" , lang = lang
10581100 )
10591101 logger .info ("ai_generate debug=%s" , json .dumps (debug , ensure_ascii = False ))
10601102 return repaired , debug
@@ -1070,7 +1112,7 @@ def _generate_final_code() -> tuple[str, Dict[str, Any]]:
10701112 "final_validation" : _indicator_debug_summary (repaired_validation ),
10711113 }
10721114 debug ["human_summary" ] = _indicator_human_summary (
1073- validation , repaired_validation , True , True , "repaired"
1115+ validation , repaired_validation , True , True , "repaired" , lang = lang
10741116 )
10751117 logger .info ("ai_generate debug=%s" , json .dumps (debug , ensure_ascii = False ))
10761118 return repaired , debug
@@ -1085,7 +1127,7 @@ def _generate_final_code() -> tuple[str, Dict[str, Any]]:
10851127 "final_validation" : _indicator_debug_summary (repaired_validation ),
10861128 }
10871129 debug ["human_summary" ] = _indicator_human_summary (
1088- validation , repaired_validation , True , False , "initial"
1130+ validation , repaired_validation , True , False , "initial" , lang = lang
10891131 )
10901132 logger .info ("ai_generate debug=%s" , json .dumps (debug , ensure_ascii = False ))
10911133 return code_text , debug
@@ -1098,7 +1140,7 @@ def _generate_final_code() -> tuple[str, Dict[str, Any]]:
10981140 "final_validation" : _indicator_debug_summary (repaired_validation ),
10991141 }
11001142 debug ["human_summary" ] = _indicator_human_summary (
1101- validation , repaired_validation , True , False , "repaired"
1143+ validation , repaired_validation , True , False , "repaired" , lang = lang
11021144 )
11031145 logger .info ("ai_generate debug=%s" , json .dumps (debug , ensure_ascii = False ))
11041146 return repaired , debug
@@ -1114,7 +1156,8 @@ def stream():
11141156 reference_id = f"ai_code_gen_{ user_id } _{ int (time .time ())} "
11151157 )
11161158 if not ok :
1117- yield "data: " + json .dumps ({"error" : f"积分不足: { msg } " }, ensure_ascii = False ) + "\n \n "
1159+ error_msg = f"积分不足: { msg } " if _is_zh_lang (lang ) and msg else _indicator_ai_text ("insufficient_credits" , lang )
1160+ yield "data: " + json .dumps ({"error" : error_msg }, ensure_ascii = False ) + "\n \n "
11181161 yield "data: [DONE]\n \n "
11191162 return
11201163
0 commit comments