app.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366
  1. """
  2. BRAIN 表达式模板解码器 - Flask 网络应用程序
  3. 一个完整的网络应用程序,用于解码字符串模板并与 WorldQuant BRAIN 集成
  4. """
  5. # 自动安装缺失的依赖
  6. import subprocess
  7. import sys
  8. import os
  9. def install_requirements():
  10. """如果缺少必需的包,则从 requirements.txt 安装"""
  11. print("🔍 检查并安装必需的依赖...")
  12. print("📋 验证 BRAIN 表达式模板解码器所需的包...")
  13. # 获取此脚本所在的目录
  14. script_dir = os.path.dirname(os.path.abspath(__file__))
  15. # 检查 requirements.txt 是否存在于脚本目录中
  16. req_file = os.path.join(script_dir, 'requirements.txt')
  17. if not os.path.exists(req_file):
  18. print("❌ 错误: 未找到 requirements.txt!")
  19. print(f"查找路径: {req_file}")
  20. return False
  21. # 如果存在镜像配置则读取
  22. mirror_url = 'https://pypi.tuna.tsinghua.edu.cn/simple' # 默认为清华镜像
  23. mirror_config_file = os.path.join(script_dir, 'mirror_config.txt')
  24. if os.path.exists(mirror_config_file):
  25. try:
  26. with open(mirror_config_file, 'r', encoding='utf-8') as f:
  27. for line in f:
  28. line = line.strip()
  29. if line and not line.startswith('#') and line.startswith('http'):
  30. mirror_url = line
  31. break
  32. except Exception as e:
  33. print(f"警告: 无法读取镜像配置: {e}")
  34. # 尝试导入主要包以检查是否已安装
  35. packages_to_check = {
  36. 'flask': 'flask',
  37. 'flask_cors': 'flask-cors',
  38. 'requests': 'requests',
  39. 'pandas': 'pandas',
  40. 'PyPDF2': 'PyPDF2',
  41. 'docx': 'python-docx',
  42. 'pdfplumber': 'pdfplumber',
  43. 'fitz': 'PyMuPDF',
  44. 'cozepy': 'cozepy',
  45. 'lxml': 'lxml',
  46. 'bs4': 'beautifulsoup4'
  47. }
  48. missing_packages = []
  49. for import_name, pip_name in packages_to_check.items():
  50. try:
  51. __import__(import_name)
  52. except ImportError:
  53. missing_packages.append(pip_name)
  54. print(f"缺失的包: {pip_name} (导入名: {import_name})")
  55. if missing_packages:
  56. print(f"⚠️ 检测到缺失的包: {', '.join(missing_packages)}")
  57. print("📦 从 requirements.txt 安装依赖...")
  58. print(f"🌐 使用镜像: {mirror_url}")
  59. try:
  60. # 使用配置的镜像安装所有 requirements
  61. subprocess.check_call([
  62. sys.executable, '-m', 'pip', 'install',
  63. '-i', mirror_url,
  64. '-r', req_file
  65. ])
  66. print("✅ 所有依赖安装成功!")
  67. return True
  68. except subprocess.CalledProcessError:
  69. print(f"❌ 错误: 使用 {mirror_url} 安装依赖失败")
  70. print("🔄 尝试使用默认 PyPI...")
  71. try:
  72. # 回退到默认 PyPI
  73. subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', req_file])
  74. print("✅ 所有依赖安装成功!")
  75. return True
  76. except subprocess.CalledProcessError:
  77. print("❌ 错误: 安装依赖失败。请手动运行:")
  78. print(f" {sys.executable} -m pip install -i {mirror_url} -r requirements.txt")
  79. return False
  80. else:
  81. print("✅ 所有必需的依赖已安装!")
  82. return True
  83. # 在导入前检查并安装依赖
  84. # 每次导入模块时都会运行,但只在需要时安装
  85. def check_and_install_dependencies():
  86. """检查并在需要时安装依赖"""
  87. if not globals().get('_dependencies_checked'):
  88. if install_requirements():
  89. globals()['_dependencies_checked'] = True
  90. return True
  91. else:
  92. print("\n请手动安装依赖并重试。")
  93. return False
  94. return True
  95. # 导入此模块时始终运行依赖检查
  96. print("🚀 初始化 BRAIN 表达式模板解码器...")
  97. if not check_and_install_dependencies():
  98. if __name__ == "__main__":
  99. sys.exit(1)
  100. else:
  101. print("⚠️ 警告: 可能缺少某些依赖。请运行 'pip install -r requirements.txt'")
  102. print("🔄 继续导入,但某些功能可能无法正常工作。")
  103. # 现在导入包
  104. try:
  105. from flask import Flask, render_template, request, jsonify, session as flask_session
  106. from flask_cors import CORS
  107. import requests
  108. import json
  109. import time
  110. import os
  111. from datetime import datetime
  112. print("📚 核心包导入成功!")
  113. except ImportError as e:
  114. print(f"❌ 导入核心包失败: {e}")
  115. print("请运行: pip install -r requirements.txt")
  116. if __name__ == "__main__":
  117. sys.exit(1)
  118. raise
  119. app = Flask(__name__)
  120. app.secret_key = 'brain_template_decoder_secret_key_change_in_production'
  121. CORS(app)
  122. print("🌐 Flask 应用程序已初始化,支持 CORS!")
  123. # BRAIN API 配置
  124. BRAIN_API_BASE = 'https://api.worldquantbrain.com'
  125. # 存储 BRAIN 会话(在生产环境中,使用适当的会话管理如 Redis)
  126. brain_sessions = {}
  127. print("🧠 BRAIN API 集成已配置!")
  128. def sign_in_to_brain(username, password):
  129. """使用重试逻辑和生物识别认证支持登录 BRAIN API"""
  130. from urllib.parse import urljoin
  131. session = requests.Session()
  132. session.auth = (username, password)
  133. retry_count = 0
  134. max_retries = 3
  135. while retry_count < max_retries:
  136. try:
  137. response = session.post(f'{BRAIN_API_BASE}/authentication')
  138. # 检查是否需要生物识别认证
  139. if response.status_code == 401:
  140. # 检查是否为生物识别认证要求
  141. if response.headers.get("WWW-Authenticate") == "persona":
  142. # 获取 location 头
  143. location = response.headers.get("Location")
  144. if location:
  145. # 构建生物识别认证的完整 URL
  146. biometric_url = urljoin(response.url, location)
  147. # 返回特殊响应,指示需要生物识别认证
  148. return {
  149. 'requires_biometric': True,
  150. 'biometric_url': biometric_url,
  151. 'session': session,
  152. 'location': location
  153. }
  154. else:
  155. raise Exception("需要生物识别认证但未提供 Location 头")
  156. else:
  157. # 常规认证失败
  158. raise requests.HTTPError("认证失败: 用户名或密码无效")
  159. # 如果执行到这里,认证成功
  160. response.raise_for_status()
  161. print("认证成功。")
  162. return session
  163. except requests.HTTPError as e:
  164. if "Invalid username or password" in str(e):
  165. raise # 对于无效凭证不重试
  166. print(f"发生 HTTP 错误: {e}。正在重试...")
  167. retry_count += 1
  168. if retry_count >= max_retries:
  169. raise
  170. time.sleep(2)
  171. except Exception as e:
  172. print(f"认证期间发生错误: {e}")
  173. retry_count += 1
  174. if retry_count >= max_retries:
  175. raise
  176. time.sleep(2)
  177. # 路由
  178. @app.route('/')
  179. def index():
  180. """主应用程序页面"""
  181. return render_template('index.html')
  182. @app.route('/simulator')
  183. def simulator():
  184. """用户友好的模拟器界面"""
  185. return render_template('simulator.html')
  186. @app.route('/api/simulator/logs', methods=['GET'])
  187. def get_simulator_logs():
  188. """获取模拟器目录中可用的日志文件"""
  189. try:
  190. import glob
  191. import os
  192. from datetime import datetime
  193. # 在当前目录和模拟器目录中查找日志文件
  194. script_dir = os.path.dirname(os.path.abspath(__file__))
  195. simulator_dir = os.path.join(script_dir, 'simulator')
  196. log_files = []
  197. # 检查当前目录和模拟器目录
  198. for directory in [script_dir, simulator_dir]:
  199. if os.path.exists(directory):
  200. pattern = os.path.join(directory, 'wqb*.log')
  201. for log_file in glob.glob(pattern):
  202. try:
  203. stat = os.stat(log_file)
  204. log_files.append({
  205. 'filename': os.path.basename(log_file),
  206. 'path': log_file,
  207. 'size': f"{stat.st_size / 1024:.1f} KB",
  208. 'modified': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S'),
  209. 'mtime': stat.st_mtime
  210. })
  211. except Exception as e:
  212. print(f"读取日志文件 {log_file} 时出错: {e}")
  213. # 按修改时间排序(最新的在前)
  214. log_files.sort(key=lambda x: x['mtime'], reverse=True)
  215. # 查找最新的日志文件
  216. latest = log_files[0]['filename'] if log_files else None
  217. return jsonify({
  218. 'logs': log_files,
  219. 'latest': latest,
  220. 'count': len(log_files)
  221. })
  222. except Exception as e:
  223. return jsonify({'error': f'获取日志文件时出错: {str(e)}'}), 500
  224. @app.route('/api/simulator/logs/<filename>', methods=['GET'])
  225. def get_simulator_log_content(filename):
  226. """获取特定日志文件的内容"""
  227. try:
  228. import os
  229. # 安全性: 只允许使用安全名称的日志文件
  230. if not filename.startswith('wqb') or not filename.endswith('.log'):
  231. return jsonify({'error': '无效的日志文件名'}), 400
  232. script_dir = os.path.dirname(os.path.abspath(__file__))
  233. simulator_dir = os.path.join(script_dir, 'simulator')
  234. # 在两个目录中查找文件
  235. log_path = None
  236. for directory in [script_dir, simulator_dir]:
  237. potential_path = os.path.join(directory, filename)
  238. if os.path.exists(potential_path):
  239. log_path = potential_path
  240. break
  241. if not log_path:
  242. return jsonify({'error': '未找到日志文件'}), 404
  243. # 使用多种编码尝试读取文件内容
  244. content = None
  245. encodings_to_try = ['utf-8', 'gbk', 'gb2312', 'big5', 'latin-1', 'cp1252']
  246. for encoding in encodings_to_try:
  247. try:
  248. with open(log_path, 'r', encoding=encoding) as f:
  249. content = f.read()
  250. print(f"使用 {encoding} 编码成功读取日志文件")
  251. break
  252. except UnicodeDecodeError:
  253. continue
  254. except Exception as e:
  255. print(f"使用 {encoding} 读取时出错: {e}")
  256. continue
  257. if content is None:
  258. # 最后手段: 以二进制读取并使用错误处理解码
  259. try:
  260. with open(log_path, 'rb') as f:
  261. raw_content = f.read()
  262. content = raw_content.decode('utf-8', errors='replace')
  263. print("使用 UTF-8 带错误替换读取日志内容")
  264. except Exception as e:
  265. content = f"错误: 无法解码文件内容 - {str(e)}"
  266. response = jsonify({
  267. 'content': content,
  268. 'filename': filename,
  269. 'size': len(content)
  270. })
  271. response.headers['Content-Type'] = 'application/json; charset=utf-8'
  272. return response
  273. except Exception as e:
  274. return jsonify({'error': f'读取日志文件时出错: {str(e)}'}), 500
  275. @app.route('/api/simulator/test-connection', methods=['POST'])
  276. def test_simulator_connection():
  277. """测试模拟器的 BRAIN API 连接"""
  278. try:
  279. data = request.get_json()
  280. username = data.get('username')
  281. password = data.get('password')
  282. if not username or not password:
  283. return jsonify({'error': '需要用户名和密码'}), 400
  284. # 使用现有的 sign_in_to_brain 函数测试连接
  285. result = sign_in_to_brain(username, password)
  286. # 处理生物识别认证要求
  287. if isinstance(result, dict) and result.get('requires_biometric'):
  288. return jsonify({
  289. 'success': False,
  290. 'error': '需要生物识别认证。请先使用主界面完成认证。',
  291. 'requires_biometric': True
  292. })
  293. # 测试简单的 API 调用以验证连接
  294. brain_session = result
  295. response = brain_session.get(f'{BRAIN_API_BASE}/data-fields/open')
  296. if response.ok:
  297. return jsonify({
  298. 'success': True,
  299. 'message': '连接成功'
  300. })
  301. else:
  302. return jsonify({
  303. 'success': False,
  304. 'error': f'API 测试失败: {response.status_code}'
  305. })
  306. except Exception as e:
  307. return jsonify({
  308. 'success': False,
  309. 'error': f'连接失败: {str(e)}'
  310. })
  311. @app.route('/api/simulator/run', methods=['POST'])
  312. def run_simulator_with_params():
  313. """在新终端中使用用户提供的参数运行模拟器"""
  314. try:
  315. import subprocess
  316. import threading
  317. import json
  318. import os
  319. import tempfile
  320. import sys
  321. import time
  322. # 获取表单数据
  323. json_file = request.files.get('jsonFile')
  324. username = request.form.get('username')
  325. password = request.form.get('password')
  326. start_position = int(request.form.get('startPosition', 0))
  327. concurrent_count = int(request.form.get('concurrentCount', 3))
  328. random_shuffle = request.form.get('randomShuffle') == 'true'
  329. use_multi_sim = request.form.get('useMultiSim') == 'true'
  330. alpha_count_per_slot = int(request.form.get('alphaCountPerSlot', 3))
  331. if not json_file or not username or not password:
  332. return jsonify({'error': '缺少必需参数'}), 400
  333. # 验证并读取 JSON 文件
  334. try:
  335. json_content = json_file.read().decode('utf-8')
  336. expressions_data = json.loads(json_content)
  337. if not isinstance(expressions_data, list):
  338. return jsonify({'error': 'JSON 文件必须包含表达式数组'}), 400
  339. except Exception as e:
  340. return jsonify({'error': f'无效的 JSON 文件: {str(e)}'}), 400
  341. # 获取路径
  342. script_dir = os.path.dirname(os.path.abspath(__file__))
  343. simulator_dir = os.path.join(script_dir, 'simulator')
  344. # 为自动化运行创建临时文件
  345. temp_json_path = os.path.join(simulator_dir, f'temp_expressions_{int(time.time())}.json')
  346. temp_script_path = os.path.join(simulator_dir, f'temp_automated_{int(time.time())}.py')
  347. temp_batch_path = os.path.join(simulator_dir, f'temp_run_{int(time.time())}.bat')
  348. try:
  349. # 将 JSON 数据保存到临时文件
  350. with open(temp_json_path, 'w', encoding='utf-8') as f:
  351. json.dump(expressions_data, f, ensure_ascii=False, indent=2)
  352. # 创建调用 automated_main 的自动化脚本
  353. script_content = f'''
  354. import asyncio
  355. import sys
  356. import os
  357. import json
  358. # 将当前目录添加到路径
  359. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
  360. import simulator_wqb
  361. async def run_automated():
  362. """使用 Web 界面的参数运行自动化模拟器"""
  363. try:
  364. # 加载 JSON 数据
  365. with open(r"{temp_json_path}", 'r', encoding='utf-8') as f:
  366. json_content = f.read()
  367. # 使用参数调用 automated_main
  368. result = await simulator_wqb.automated_main(
  369. json_file_content=json_content,
  370. username="{username}",
  371. password="{password}",
  372. start_position={start_position},
  373. concurrent_count={concurrent_count},
  374. random_shuffle={random_shuffle},
  375. use_multi_sim={use_multi_sim},
  376. alpha_count_per_slot={alpha_count_per_slot}
  377. )
  378. if result['success']:
  379. print("\\n" + "="*60)
  380. print("🎉 Web 界面自动化成功 🎉")
  381. print("="*60)
  382. print(f"✅ 总模拟次数: {{result['results']['total']}}")
  383. print(f"✅ 成功: {{result['results']['successful']}}")
  384. print(f"❌ 失败: {{result['results']['failed']}}")
  385. if result['results']['alphaIds']:
  386. print(f"📊 生成 {{len(result['results']['alphaIds'])}} 个 Alpha ID")
  387. print("="*60)
  388. else:
  389. print("\\n" + "="*60)
  390. print("❌ Web 界面自动化失败")
  391. print("="*60)
  392. print(f"错误: {{result['error']}}")
  393. print("="*60)
  394. except Exception as e:
  395. print(f"\\n❌ 脚本执行错误: {{e}}")
  396. finally:
  397. # 清理临时文件
  398. try:
  399. if os.path.exists(r"{temp_json_path}"):
  400. os.remove(r"{temp_json_path}")
  401. if os.path.exists(r"{temp_script_path}"):
  402. os.remove(r"{temp_script_path}")
  403. if os.path.exists(r"{temp_batch_path}"):
  404. os.remove(r"{temp_batch_path}")
  405. except:
  406. pass
  407. print("\\n🔄 按任意键关闭此窗口...")
  408. input()
  409. if __name__ == '__main__':
  410. asyncio.run(run_automated())
  411. '''
  412. # 保存脚本
  413. with open(temp_script_path, 'w', encoding='utf-8') as f:
  414. f.write(script_content)
  415. # 为 Windows 创建批处理文件
  416. batch_content = f'''@echo off
  417. cd /d "{simulator_dir}"
  418. python "{os.path.basename(temp_script_path)}"
  419. '''
  420. with open(temp_batch_path, 'w', encoding='utf-8') as f:
  421. f.write(batch_content)
  422. # 在新终端中启动
  423. def launch_simulator():
  424. try:
  425. if os.name == 'nt': # Windows
  426. # 使用批处理文件避免路径问题
  427. subprocess.Popen([
  428. temp_batch_path
  429. ], creationflags=subprocess.CREATE_NEW_CONSOLE)
  430. else: # Unix 类系统
  431. # 尝试不同的终端模拟器
  432. terminals = ['gnome-terminal', 'xterm', 'konsole', 'terminal']
  433. for terminal in terminals:
  434. try:
  435. if terminal == 'gnome-terminal':
  436. subprocess.Popen([
  437. terminal, '--working-directory', simulator_dir,
  438. '--', 'python3', os.path.basename(temp_script_path)
  439. ])
  440. else:
  441. subprocess.Popen([
  442. terminal, '-e',
  443. f'cd "{simulator_dir}" && python3 "{os.path.basename(temp_script_path)}"'
  444. ])
  445. break
  446. except FileNotFoundError:
  447. continue
  448. else:
  449. # 回退: 如果找不到终端则在后台运行
  450. subprocess.Popen([
  451. sys.executable, temp_script_path
  452. ], cwd=simulator_dir)
  453. except Exception as e:
  454. print(f"启动模拟器时出错: {e}")
  455. # 在单独的线程中启动模拟器
  456. thread = threading.Thread(target=launch_simulator)
  457. thread.daemon = True
  458. thread.start()
  459. return jsonify({
  460. 'success': True,
  461. 'message': '模拟器已在新终端窗口中启动',
  462. 'parameters': {
  463. 'expressions_count': len(expressions_data),
  464. 'concurrent_count': concurrent_count,
  465. 'use_multi_sim': use_multi_sim,
  466. 'alpha_count_per_slot': alpha_count_per_slot if use_multi_sim else None
  467. }
  468. })
  469. except Exception as e:
  470. # 出错时清理
  471. try:
  472. if os.path.exists(temp_json_path):
  473. os.remove(temp_json_path)
  474. if os.path.exists(temp_script_path):
  475. os.remove(temp_script_path)
  476. if os.path.exists(temp_batch_path):
  477. os.remove(temp_batch_path)
  478. except:
  479. pass
  480. raise e
  481. except Exception as e:
  482. return jsonify({'error': f'运行模拟器失败: {str(e)}'}), 500
  483. @app.route('/api/simulator/stop', methods=['POST'])
  484. def stop_simulator():
  485. """停止运行的模拟器"""
  486. try:
  487. # 这是一个占位符 - 在生产环境中,您需要
  488. # 实现适当的进程管理来停止运行的模拟
  489. return jsonify({
  490. 'success': True,
  491. 'message': '停止信号已发送'
  492. })
  493. except Exception as e:
  494. return jsonify({'error': f'停止模拟器失败: {str(e)}'}), 500
  495. @app.route('/api/authenticate', methods=['POST'])
  496. def authenticate():
  497. """使用 BRAIN API 进行认证"""
  498. try:
  499. data = request.get_json()
  500. username = data.get('username')
  501. password = data.get('password')
  502. if not username or not password:
  503. return jsonify({'error': '需要用户名和密码'}), 400
  504. # 使用 BRAIN 进行认证
  505. result = sign_in_to_brain(username, password)
  506. # 检查是否需要生物识别认证
  507. if isinstance(result, dict) and result.get('requires_biometric'):
  508. # 临时存储具有生物识别待处理状态的会话
  509. session_id = f"{username}_{int(time.time())}_biometric_pending"
  510. brain_sessions[session_id] = {
  511. 'session': result['session'],
  512. 'username': username,
  513. 'timestamp': time.time(),
  514. 'biometric_pending': True,
  515. 'biometric_location': result['location']
  516. }
  517. # 在 Flask 会话中存储会话 ID
  518. flask_session['brain_session_id'] = session_id
  519. return jsonify({
  520. 'success': False,
  521. 'requires_biometric': True,
  522. 'biometric_url': result['biometric_url'],
  523. 'session_id': session_id,
  524. 'message': '请通过访问提供的 URL 完成生物识别认证'
  525. })
  526. # 常规成功认证
  527. brain_session = result
  528. # 存储会话
  529. session_id = f"{username}_{int(time.time())}"
  530. brain_sessions[session_id] = {
  531. 'session': brain_session,
  532. 'username': username,
  533. 'timestamp': time.time()
  534. }
  535. # 在 Flask 会话中存储会话 ID
  536. flask_session['brain_session_id'] = session_id
  537. return jsonify({
  538. 'success': True,
  539. 'session_id': session_id,
  540. 'message': '认证成功'
  541. })
  542. except requests.HTTPError as e:
  543. if e.response.status_code == 401:
  544. return jsonify({'error': '用户名或密码无效'}), 401
  545. else:
  546. return jsonify({'error': f'认证失败: {str(e)}'}), 500
  547. except Exception as e:
  548. return jsonify({'error': f'认证错误: {str(e)}'}), 500
  549. @app.route('/api/complete-biometric', methods=['POST'])
  550. def complete_biometric():
  551. """在用户在浏览器中完成生物识别认证后完成认证"""
  552. try:
  553. from urllib.parse import urljoin
  554. session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
  555. if not session_id or session_id not in brain_sessions:
  556. return jsonify({'error': '无效或过期的会话'}), 401
  557. session_info = brain_sessions[session_id]
  558. # 检查此会话是否正在等待生物识别完成
  559. if not session_info.get('biometric_pending'):
  560. return jsonify({'error': '会话不处于生物识别认证待处理状态'}), 400
  561. brain_session = session_info['session']
  562. location = session_info['biometric_location']
  563. # 完成生物识别认证
  564. try:
  565. # 向 location 发出后续 POST 请求
  566. complete_url = urljoin(f'{BRAIN_API_BASE}/authentication', location)
  567. response = brain_session.post(complete_url)
  568. response.raise_for_status()
  569. # 更新会话信息 - 移除生物识别待处理状态
  570. session_info['biometric_pending'] = False
  571. del session_info['biometric_location']
  572. # 创建没有 biometric_pending 后缀的新会话 ID
  573. new_session_id = f"{session_info['username']}_{int(time.time())}"
  574. brain_sessions[new_session_id] = {
  575. 'session': brain_session,
  576. 'username': session_info['username'],
  577. 'timestamp': time.time()
  578. }
  579. # 删除旧会话
  580. del brain_sessions[session_id]
  581. # 更新 Flask 会话
  582. flask_session['brain_session_id'] = new_session_id
  583. return jsonify({
  584. 'success': True,
  585. 'session_id': new_session_id,
  586. 'message': '生物识别认证成功完成'
  587. })
  588. except requests.HTTPError as e:
  589. return jsonify({
  590. 'error': f'完成生物识别认证失败: {str(e)}'
  591. }), 500
  592. except Exception as e:
  593. return jsonify({'error': f'完成生物识别认证时出错: {str(e)}'}), 500
  594. @app.route('/api/operators', methods=['GET'])
  595. def get_operators():
  596. """从 BRAIN API 获取用户运算符"""
  597. try:
  598. session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
  599. if not session_id or session_id not in brain_sessions:
  600. return jsonify({'error': '无效或过期的会话'}), 401
  601. session_info = brain_sessions[session_id]
  602. brain_session = session_info['session']
  603. # 首先尝试不使用分页参数(大多数 API 一次返回所有运算符)
  604. try:
  605. response = brain_session.get(f'{BRAIN_API_BASE}/operators')
  606. response.raise_for_status()
  607. data = response.json()
  608. # 如果是列表,我们获得了所有运算符
  609. if isinstance(data, list):
  610. all_operators = data
  611. print(f"从 BRAIN API 获取了 {len(all_operators)} 个运算符(直接)")
  612. # 如果是包含 results 的字典,处理分页
  613. elif isinstance(data, dict) and 'results' in data:
  614. all_operators = []
  615. total_count = data.get('count', len(data['results']))
  616. print(f"找到 {total_count} 个总运算符,正在获取全部...")
  617. # 获取第一批
  618. all_operators.extend(data['results'])
  619. # 如果需要,获取剩余批次
  620. limit = 100
  621. offset = len(data['results'])
  622. while len(all_operators) < total_count:
  623. params = {'limit': limit, 'offset': offset}
  624. batch_response = brain_session.get(f'{BRAIN_API_BASE}/operators', params=params)
  625. batch_response.raise_for_status()
  626. batch_data = batch_response.json()
  627. if isinstance(batch_data, dict) and 'results' in batch_data:
  628. batch_operators = batch_data['results']
  629. if not batch_operators: # 没有更多数据
  630. break
  631. all_operators.extend(batch_operators)
  632. offset += len(batch_operators)
  633. else:
  634. break
  635. print(f"从 BRAIN API 获取了 {len(all_operators)} 个运算符(分页)")
  636. else:
  637. # 未知格式,视为空
  638. all_operators = []
  639. print("运算符 API 的响应格式未知")
  640. except Exception as e:
  641. print(f"获取运算符时出错: {str(e)}")
  642. # 回退: 尝试显式分页
  643. all_operators = []
  644. limit = 100
  645. offset = 0
  646. while True:
  647. params = {'limit': limit, 'offset': offset}
  648. response = brain_session.get(f'{BRAIN_API_BASE}/operators', params=params)
  649. response.raise_for_status()
  650. data = response.json()
  651. if isinstance(data, list):
  652. all_operators.extend(data)
  653. if len(data) < limit:
  654. break
  655. elif isinstance(data, dict) and 'results' in data:
  656. batch_operators = data['results']
  657. all_operators.extend(batch_operators)
  658. if len(batch_operators) < limit:
  659. break
  660. else:
  661. break
  662. offset += limit
  663. print(f"从 BRAIN API 获取了 {len(all_operators)} 个运算符(回退)")
  664. # 提取名称、类别、描述、定义和其他字段(如果可用)
  665. filtered_operators = []
  666. for op in all_operators:
  667. operator_data = {
  668. 'name': op['name'],
  669. 'category': op['category']
  670. }
  671. # 如果可用则包含描述
  672. if 'description' in op and op['description']:
  673. operator_data['description'] = op['description']
  674. # 如果可用则包含定义
  675. if 'definition' in op and op['definition']:
  676. operator_data['definition'] = op['definition']
  677. # 如果可用则包含使用计数
  678. if 'usageCount' in op:
  679. operator_data['usageCount'] = op['usageCount']
  680. # 如果可用则包含其他有用字段
  681. if 'example' in op and op['example']:
  682. operator_data['example'] = op['example']
  683. filtered_operators.append(operator_data)
  684. return jsonify(filtered_operators)
  685. except Exception as e:
  686. print(f"获取运算符时出错: {str(e)}")
  687. return jsonify({'error': f'获取运算符失败: {str(e)}'}), 500
  688. @app.route('/api/datafields', methods=['GET'])
  689. def get_datafields():
  690. """从 BRAIN API 获取数据字段"""
  691. try:
  692. session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
  693. if not session_id or session_id not in brain_sessions:
  694. return jsonify({'error': '无效或过期的会话'}), 401
  695. session_info = brain_sessions[session_id]
  696. brain_session = session_info['session']
  697. # 获取参数
  698. region = request.args.get('region', 'USA')
  699. delay = request.args.get('delay', '1')
  700. universe = request.args.get('universe', 'TOP3000')
  701. dataset_id = request.args.get('dataset_id', 'fundamental6')
  702. search = ''
  703. # 基于笔记本实现构建 URL 模板
  704. if len(search) == 0:
  705. url_template = f"{BRAIN_API_BASE}/data-fields?" + \
  706. f"&instrumentType=EQUITY" + \
  707. f"&region={region}&delay={delay}&universe={universe}&dataset.id={dataset_id}&limit=50" + \
  708. "&offset={x}"
  709. # 从第一个请求获取计数
  710. first_response = brain_session.get(url_template.format(x=0))
  711. first_response.raise_for_status()
  712. count = first_response.json()['count']
  713. else:
  714. url_template = f"{BRAIN_API_BASE}/data-fields?" + \
  715. f"&instrumentType=EQUITY" + \
  716. f"&region={region}&delay={delay}&universe={universe}&limit=50" + \
  717. f"&search={search}" + \
  718. "&offset={x}"
  719. count = 100 # 搜索查询的默认值
  720. # 分批获取所有数据字段
  721. datafields_list = []
  722. for x in range(0, count, 50):
  723. response = brain_session.get(url_template.format(x=x))
  724. response.raise_for_status()
  725. datafields_list.append(response.json()['results'])
  726. # 展平列表
  727. datafields_list_flat = [item for sublist in datafields_list for item in sublist]
  728. # 过滤字段,只包含必要信息
  729. filtered_fields = [
  730. {
  731. 'id': field['id'],
  732. 'description': field['description'],
  733. 'type': field['type'],
  734. 'coverage': field.get('coverage', 0),
  735. 'userCount': field.get('userCount', 0),
  736. 'alphaCount': field.get('alphaCount', 0)
  737. }
  738. for field in datafields_list_flat
  739. ]
  740. return jsonify(filtered_fields)
  741. except Exception as e:
  742. return jsonify({'error': f'获取数据字段失败: {str(e)}'}), 500
  743. @app.route('/api/dataset-description', methods=['GET'])
  744. def get_dataset_description():
  745. """从 BRAIN API 获取数据集描述"""
  746. try:
  747. session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
  748. if not session_id or session_id not in brain_sessions:
  749. return jsonify({'error': '无效或过期的会话'}), 401
  750. session_info = brain_sessions[session_id]
  751. brain_session = session_info['session']
  752. # 获取参数
  753. region = request.args.get('region', 'USA')
  754. delay = request.args.get('delay', '1')
  755. universe = request.args.get('universe', 'TOP3000')
  756. dataset_id = request.args.get('dataset_id', 'analyst10')
  757. # 构建数据集描述的 URL
  758. url = f"{BRAIN_API_BASE}/data-sets/{dataset_id}?" + \
  759. f"instrumentType=EQUITY&region={region}&delay={delay}&universe={universe}"
  760. print(f"从以下位置获取数据集描述: {url}")
  761. # 向 BRAIN API 发出请求
  762. response = brain_session.get(url)
  763. response.raise_for_status()
  764. data = response.json()
  765. description = data.get('description', '无描述可用')
  766. print(f"数据集描述已检索: {description[:100]}...")
  767. return jsonify({
  768. 'success': True,
  769. 'description': description,
  770. 'dataset_id': dataset_id
  771. })
  772. except Exception as e:
  773. print(f"数据集描述错误: {str(e)}")
  774. return jsonify({'error': f'获取数据集描述失败: {str(e)}'}), 500
  775. @app.route('/api/status', methods=['GET'])
  776. def check_status():
  777. """检查会话是否仍然有效"""
  778. try:
  779. session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
  780. if not session_id or session_id not in brain_sessions:
  781. return jsonify({'valid': False})
  782. session_info = brain_sessions[session_id]
  783. # 检查会话是否不太旧(24 小时)
  784. if time.time() - session_info['timestamp'] > 86400:
  785. del brain_sessions[session_id]
  786. return jsonify({'valid': False})
  787. # 检查生物识别认证是否待处理
  788. if session_info.get('biometric_pending'):
  789. return jsonify({
  790. 'valid': False,
  791. 'biometric_pending': True,
  792. 'username': session_info['username'],
  793. 'message': '生物识别认证待处理'
  794. })
  795. return jsonify({
  796. 'valid': True,
  797. 'username': session_info['username']
  798. })
  799. except Exception as e:
  800. return jsonify({'error': f'状态检查失败: {str(e)}'}), 500
  801. @app.route('/api/logout', methods=['POST'])
  802. def logout():
  803. """注销并清理会话"""
  804. try:
  805. session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
  806. if session_id and session_id in brain_sessions:
  807. del brain_sessions[session_id]
  808. if 'brain_session_id' in flask_session:
  809. flask_session.pop('brain_session_id')
  810. return jsonify({'success': True, 'message': '注销成功'})
  811. except Exception as e:
  812. return jsonify({'error': f'注销失败: {str(e)}'}), 500
  813. @app.route('/api/test-expression', methods=['POST'])
  814. def test_expression():
  815. """使用 BRAIN API 模拟测试表达式"""
  816. try:
  817. session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
  818. if not session_id or session_id not in brain_sessions:
  819. return jsonify({'error': '无效或过期的会话'}), 401
  820. session_info = brain_sessions[session_id]
  821. brain_session = session_info['session']
  822. # 从请求获取模拟数据
  823. simulation_data = request.get_json()
  824. # 确保必需字段存在
  825. if 'type' not in simulation_data:
  826. simulation_data['type'] = 'REGULAR'
  827. # 确保设置具有必需字段
  828. if 'settings' not in simulation_data:
  829. simulation_data['settings'] = {}
  830. # 为缺失的设置设置默认值
  831. default_settings = {
  832. 'instrumentType': 'EQUITY',
  833. 'region': 'USA',
  834. 'universe': 'TOP3000',
  835. 'delay': 1,
  836. 'decay': 15,
  837. 'neutralization': 'SUBINDUSTRY',
  838. 'truncation': 0.08,
  839. 'pasteurization': 'ON',
  840. 'testPeriod': 'P1Y6M',
  841. 'unitHandling': 'VERIFY',
  842. 'nanHandling': 'OFF',
  843. 'language': 'FASTEXPR',
  844. 'visualization': False
  845. }
  846. for key, value in default_settings.items():
  847. if key not in simulation_data['settings']:
  848. simulation_data['settings'][key] = value
  849. # 将字符串布尔值转换为实际布尔值
  850. if isinstance(simulation_data['settings'].get('visualization'), str):
  851. viz_value = simulation_data['settings']['visualization'].lower()
  852. simulation_data['settings']['visualization'] = viz_value == 'true'
  853. # 发送模拟请求(遵循笔记本模式)
  854. try:
  855. message = {}
  856. simulation_response = brain_session.post(f'{BRAIN_API_BASE}/simulations', json=simulation_data)
  857. # 检查是否收到 Location 头(遵循笔记本模式)
  858. if 'Location' in simulation_response.headers:
  859. # 跟随 location 获取实际状态
  860. message = brain_session.get(simulation_response.headers['Location']).json()
  861. # 检查模拟是否正在运行或已完成
  862. if 'progress' in message.keys():
  863. info_to_print = "模拟正在运行"
  864. return jsonify({
  865. 'success': True,
  866. 'status': 'RUNNING',
  867. 'message': info_to_print,
  868. 'full_response': message
  869. })
  870. else:
  871. # 返回完整消息,如笔记本中所示
  872. return jsonify({
  873. 'success': message.get('status') != 'ERROR',
  874. 'status': message.get('status', 'UNKNOWN'),
  875. 'message': str(message),
  876. 'full_response': message
  877. })
  878. else:
  879. # 尝试从响应体获取错误(遵循笔记本模式)
  880. try:
  881. message = simulation_response.json()
  882. return jsonify({
  883. 'success': False,
  884. 'status': 'ERROR',
  885. 'message': str(message),
  886. 'full_response': message
  887. })
  888. except:
  889. return jsonify({
  890. 'success': False,
  891. 'status': 'ERROR',
  892. 'message': '网络连接错误',
  893. 'full_response': {}
  894. })
  895. except Exception as e:
  896. return jsonify({
  897. 'success': False,
  898. 'status': 'ERROR',
  899. 'message': '网络连接错误',
  900. 'full_response': {'error': str(e)}
  901. })
  902. except Exception as e:
  903. import traceback
  904. return jsonify({
  905. 'success': False,
  906. 'status': 'ERROR',
  907. 'message': f'测试表达式失败: {str(e)}',
  908. 'full_response': {'error': str(e), 'traceback': traceback.format_exc()}
  909. }), 500
  910. @app.route('/api/test-operators', methods=['GET'])
  911. def test_operators():
  912. """测试端点以检查原始 BRAIN 运算符 API 响应"""
  913. try:
  914. session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
  915. if not session_id or session_id not in brain_sessions:
  916. return jsonify({'error': '无效或过期的会话'}), 401
  917. session_info = brain_sessions[session_id]
  918. brain_session = session_info['session']
  919. # 从 BRAIN API 获取原始响应
  920. response = brain_session.get(f'{BRAIN_API_BASE}/operators')
  921. response.raise_for_status()
  922. data = response.json()
  923. # 返回原始响应信息用于调试
  924. result = {
  925. 'type': str(type(data)),
  926. 'is_list': isinstance(data, list),
  927. 'is_dict': isinstance(data, dict),
  928. 'length': len(data) if isinstance(data, list) else None,
  929. 'keys': list(data.keys()) if isinstance(data, dict) else None,
  930. 'count_key': data.get('count') if isinstance(data, dict) else None,
  931. 'first_few_items': data[:3] if isinstance(data, list) else (data.get('results', [])[:3] if isinstance(data, dict) else None)
  932. }
  933. return jsonify(result)
  934. except Exception as e:
  935. return jsonify({'error': f'测试失败: {str(e)}'}), 500
  936. # 导入蓝图
  937. try:
  938. from blueprints import idea_house_bp, paper_analysis_bp, feature_engineering_bp
  939. print("📦 蓝图导入成功!")
  940. except ImportError as e:
  941. print(f"❌ 导入蓝图失败: {e}")
  942. print("某些功能可能不可用。")
  943. # 注册蓝图
  944. app.register_blueprint(idea_house_bp, url_prefix='/idea-house')
  945. app.register_blueprint(paper_analysis_bp, url_prefix='/paper-analysis')
  946. app.register_blueprint(feature_engineering_bp, url_prefix='/feature-engineering')
  947. print("🔧 所有蓝图注册成功!")
  948. print(" - idea库: /idea-house")
  949. print(" - 论文分析: /paper-analysis")
  950. print(" - 特征工程: /feature-engineering")
  951. # 模板管理路由
  952. # 获取此脚本所在的目录以获取模板
  953. script_dir = os.path.dirname(os.path.abspath(__file__))
  954. TEMPLATES_DIR = os.path.join(script_dir, 'custom_templates')
  955. # 确保模板目录存在
  956. if not os.path.exists(TEMPLATES_DIR):
  957. os.makedirs(TEMPLATES_DIR)
  958. print(f"📁 已创建模板目录: {TEMPLATES_DIR}")
  959. else:
  960. print(f"📁 模板目录已就绪: {TEMPLATES_DIR}")
  961. print("✅ BRAIN 表达式模板解码器完全初始化!")
  962. print("🎯 准备处理模板并与 BRAIN API 集成!")
  963. @app.route('/api/templates', methods=['GET'])
  964. def get_templates():
  965. """获取所有自定义模板"""
  966. try:
  967. templates = []
  968. templates_file = os.path.join(TEMPLATES_DIR, 'templates.json')
  969. if os.path.exists(templates_file):
  970. with open(templates_file, 'r', encoding='utf-8') as f:
  971. templates = json.load(f)
  972. return jsonify(templates)
  973. except Exception as e:
  974. return jsonify({'error': f'加载模板时出错: {str(e)}'}), 500
  975. @app.route('/api/templates', methods=['POST'])
  976. def save_template():
  977. """保存新的自定义模板"""
  978. try:
  979. data = request.get_json()
  980. name = data.get('name', '').strip()
  981. description = data.get('description', '').strip()
  982. expression = data.get('expression', '').strip()
  983. template_configurations = data.get('templateConfigurations', {})
  984. if not name or not expression:
  985. return jsonify({'error': '名称和表达式是必需的'}), 400
  986. # 加载现有模板
  987. templates_file = os.path.join(TEMPLATES_DIR, 'templates.json')
  988. templates = []
  989. if os.path.exists(templates_file):
  990. with open(templates_file, 'r', encoding='utf-8') as f:
  991. templates = json.load(f)
  992. # 检查重复名称
  993. existing_index = next((i for i, t in enumerate(templates) if t['name'] == name), None)
  994. new_template = {
  995. 'name': name,
  996. 'description': description,
  997. 'expression': expression,
  998. 'templateConfigurations': template_configurations,
  999. 'createdAt': datetime.now().isoformat()
  1000. }
  1001. if existing_index is not None:
  1002. # 更新现有模板但保留 createdAt(如果存在)
  1003. if 'createdAt' in templates[existing_index]:
  1004. new_template['createdAt'] = templates[existing_index]['createdAt']
  1005. new_template['updatedAt'] = datetime.now().isoformat()
  1006. templates[existing_index] = new_template
  1007. message = f'模板 "{name}" 更新成功'
  1008. else:
  1009. # 添加新模板
  1010. templates.append(new_template)
  1011. message = f'模板 "{name}" 保存成功'
  1012. # 保存到文件
  1013. with open(templates_file, 'w', encoding='utf-8') as f:
  1014. json.dump(templates, f, indent=2, ensure_ascii=False)
  1015. return jsonify({'success': True, 'message': message})
  1016. except Exception as e:
  1017. return jsonify({'error': f'保存模板时出错: {str(e)}'}), 500
  1018. @app.route('/api/templates/<int:template_id>', methods=['DELETE'])
  1019. def delete_template(template_id):
  1020. """删除自定义模板"""
  1021. try:
  1022. templates_file = os.path.join(TEMPLATES_DIR, 'templates.json')
  1023. templates = []
  1024. if os.path.exists(templates_file):
  1025. with open(templates_file, 'r', encoding='utf-8') as f:
  1026. templates = json.load(f)
  1027. if 0 <= template_id < len(templates):
  1028. deleted_template = templates.pop(template_id)
  1029. # 保存更新后的模板
  1030. with open(templates_file, 'w', encoding='utf-8') as f:
  1031. json.dump(templates, f, indent=2, ensure_ascii=False)
  1032. return jsonify({'success': True, 'message': f'模板 "{deleted_template["name"]}" 删除成功'})
  1033. else:
  1034. return jsonify({'error': '未找到模板'}), 404
  1035. except Exception as e:
  1036. return jsonify({'error': f'删除模板时出错: {str(e)}'}), 500
  1037. @app.route('/api/templates/export', methods=['GET'])
  1038. def export_templates():
  1039. """将所有模板导出为 JSON"""
  1040. try:
  1041. templates_file = os.path.join(TEMPLATES_DIR, 'templates.json')
  1042. templates = []
  1043. if os.path.exists(templates_file):
  1044. with open(templates_file, 'r', encoding='utf-8') as f:
  1045. templates = json.load(f)
  1046. return jsonify(templates)
  1047. except Exception as e:
  1048. return jsonify({'error': f'导出模板时出错: {str(e)}'}), 500
  1049. @app.route('/api/templates/import', methods=['POST'])
  1050. def import_templates():
  1051. """从 JSON 导入模板"""
  1052. try:
  1053. data = request.get_json()
  1054. imported_templates = data.get('templates', [])
  1055. overwrite = data.get('overwrite', False)
  1056. if not isinstance(imported_templates, list):
  1057. return jsonify({'error': '无效的模板格式'}), 400
  1058. # 验证模板结构
  1059. valid_templates = []
  1060. for template in imported_templates:
  1061. if (isinstance(template, dict) and
  1062. 'name' in template and 'expression' in template and
  1063. template['name'].strip() and template['expression'].strip()):
  1064. valid_templates.append({
  1065. 'name': template['name'].strip(),
  1066. 'description': template.get('description', '').strip(),
  1067. 'expression': template['expression'].strip(),
  1068. 'templateConfigurations': template.get('templateConfigurations', {}),
  1069. 'createdAt': template.get('createdAt', datetime.now().isoformat())
  1070. })
  1071. if not valid_templates:
  1072. return jsonify({'error': '未找到有效模板'}), 400
  1073. # 加载现有模板
  1074. templates_file = os.path.join(TEMPLATES_DIR, 'templates.json')
  1075. existing_templates = []
  1076. if os.path.exists(templates_file):
  1077. with open(templates_file, 'r', encoding='utf-8') as f:
  1078. existing_templates = json.load(f)
  1079. # 处理重复项
  1080. duplicates = []
  1081. new_templates = []
  1082. for template in valid_templates:
  1083. existing_index = next((i for i, t in enumerate(existing_templates) if t['name'] == template['name']), None)
  1084. if existing_index is not None:
  1085. duplicates.append(template['name'])
  1086. if overwrite:
  1087. existing_templates[existing_index] = template
  1088. else:
  1089. new_templates.append(template)
  1090. # 添加新模板
  1091. existing_templates.extend(new_templates)
  1092. # 保存到文件
  1093. with open(templates_file, 'w', encoding='utf-8') as f:
  1094. json.dump(existing_templates, f, indent=2, ensure_ascii=False)
  1095. result = {
  1096. 'success': True,
  1097. 'imported': len(new_templates),
  1098. 'duplicates': duplicates,
  1099. 'overwritten': len(duplicates) if overwrite else 0
  1100. }
  1101. return jsonify(result)
  1102. except Exception as e:
  1103. return jsonify({'error': f'导入模板时出错: {str(e)}'}), 500
  1104. @app.route('/api/run-simulator', methods=['POST'])
  1105. def run_simulator():
  1106. """运行 simulator_wqb.py 脚本"""
  1107. try:
  1108. import subprocess
  1109. import threading
  1110. from pathlib import Path
  1111. # 获取脚本路径(现在在模拟器子文件夹中)
  1112. script_dir = os.path.dirname(os.path.abspath(__file__))
  1113. simulator_dir = os.path.join(script_dir, 'simulator')
  1114. simulator_path = os.path.join(simulator_dir, 'simulator_wqb.py')
  1115. # 检查脚本是否存在
  1116. if not os.path.exists(simulator_path):
  1117. return jsonify({'error': '在模拟器文件夹中未找到 simulator_wqb.py'}), 404
  1118. # 在新终端窗口中运行脚本
  1119. def run_script():
  1120. try:
  1121. # 对于 Windows
  1122. if os.name == 'nt':
  1123. # 使用子进程和适当的工作目录(模拟器文件夹)
  1124. subprocess.Popen(['cmd', '/k', 'python', 'simulator_wqb.py'],
  1125. cwd=simulator_dir,
  1126. creationflags=subprocess.CREATE_NEW_CONSOLE)
  1127. else:
  1128. # 对于 Unix 类系统
  1129. subprocess.Popen(['gnome-terminal', '--working-directory', simulator_dir, '--', 'python3', 'simulator_wqb.py'])
  1130. except Exception as e:
  1131. print(f"运行模拟器时出错: {e}")
  1132. # 在单独线程中启动脚本
  1133. thread = threading.Thread(target=run_script)
  1134. thread.daemon = True
  1135. thread.start()
  1136. return jsonify({
  1137. 'success': True,
  1138. 'message': '模拟器脚本已在新终端窗口中启动'
  1139. })
  1140. except Exception as e:
  1141. return jsonify({'error': f'运行模拟器失败: {str(e)}'}), 500
  1142. if __name__ == '__main__':
  1143. port = 5190
  1144. print("启动 BRAIN 表达式模板解码器 Web 应用程序...")
  1145. print(f"应用程序将在 http://localhost:{port} 运行")
  1146. print("包含 BRAIN API 集成 - 不需要单独的代理!")
  1147. app.run(debug=True, host='0.0.0.0', port=port)