cnhkmcp 2.1.1__py3-none-any.whl → 2.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {cnhkmcp-2.1.1.dist-info → cnhkmcp-2.1.3.dist-info}/METADATA +1 -1
  2. cnhkmcp-2.1.3.dist-info/RECORD +6 -0
  3. cnhkmcp-2.1.3.dist-info/top_level.txt +1 -0
  4. cnhkmcp/__init__.py +0 -125
  5. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/README.md +0 -38
  6. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/ace.log +0 -0
  7. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/config.json +0 -6
  8. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/ace_lib.py +0 -1510
  9. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_datasets.py +0 -157
  10. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_documentation.py +0 -132
  11. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_operators.py +0 -99
  12. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/helpful_functions.py +0 -180
  13. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/icon.ico +0 -0
  14. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/icon.png +0 -0
  15. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/knowledge/test.txt +0 -1
  16. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/main.py +0 -576
  17. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/process_knowledge_base.py +0 -280
  18. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/rag_engine.py +0 -356
  19. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/requirements.txt +0 -7
  20. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/run.bat +0 -3
  21. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/vector_db/_manifest.json +0 -326
  22. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/vector_db/_meta.json +0 -1
  23. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/vector_db/be5d957c-b724-46e3-91d1-999e9f5f7d28/index_metadata.pickle +0 -0
  24. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/vector_db/chroma.sqlite3 +0 -0
  25. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242//321/211/320/266/320/246/321/206/320/274/320/261/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +0 -265
  26. cnhkmcp/untracked/APP/.gitignore +0 -32
  27. cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md +0 -112
  28. cnhkmcp/untracked/APP/README.md +0 -309
  29. cnhkmcp/untracked/APP/Tranformer/Transformer.py +0 -4985
  30. cnhkmcp/untracked/APP/Tranformer/ace.log +0 -0
  31. cnhkmcp/untracked/APP/Tranformer/ace_lib.py +0 -1510
  32. cnhkmcp/untracked/APP/Tranformer/helpful_functions.py +0 -180
  33. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates.json +0 -2421
  34. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates_/321/207/320/264/342/225/221/321/204/342/225/233/320/233.json +0 -654
  35. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_error.json +0 -1034
  36. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_success.json +0 -444
  37. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_/321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/320/237/320/277/321/207/320/253/342/224/244/321/206/320/236/320/265/321/210/342/225/234/342/225/234/321/205/320/225/320/265Machine_lib.json +0 -22
  38. cnhkmcp/untracked/APP/Tranformer/parsetab.py +0 -60
  39. cnhkmcp/untracked/APP/Tranformer/template_summary.txt +0 -3182
  40. cnhkmcp/untracked/APP/Tranformer/transformer_config.json +0 -7
  41. cnhkmcp/untracked/APP/Tranformer/validator.py +0 -889
  42. cnhkmcp/untracked/APP/ace.log +0 -69
  43. cnhkmcp/untracked/APP/ace_lib.py +0 -1510
  44. cnhkmcp/untracked/APP/blueprints/__init__.py +0 -6
  45. cnhkmcp/untracked/APP/blueprints/feature_engineering.py +0 -347
  46. cnhkmcp/untracked/APP/blueprints/idea_house.py +0 -221
  47. cnhkmcp/untracked/APP/blueprints/inspiration_house.py +0 -432
  48. cnhkmcp/untracked/APP/blueprints/paper_analysis.py +0 -570
  49. cnhkmcp/untracked/APP/custom_templates/templates.json +0 -1257
  50. cnhkmcp/untracked/APP/give_me_idea/BRAIN_Alpha_Template_Expert_SystemPrompt.md +0 -400
  51. cnhkmcp/untracked/APP/give_me_idea/ace_lib.py +0 -1510
  52. cnhkmcp/untracked/APP/give_me_idea/alpha_data_specific_template_master.py +0 -252
  53. cnhkmcp/untracked/APP/give_me_idea/fetch_all_datasets.py +0 -157
  54. cnhkmcp/untracked/APP/give_me_idea/fetch_all_operators.py +0 -99
  55. cnhkmcp/untracked/APP/give_me_idea/helpful_functions.py +0 -180
  56. cnhkmcp/untracked/APP/give_me_idea/what_is_Alpha_template.md +0 -11
  57. cnhkmcp/untracked/APP/helpful_functions.py +0 -180
  58. cnhkmcp/untracked/APP/hkSimulator/ace_lib.py +0 -1497
  59. cnhkmcp/untracked/APP/hkSimulator/autosimulator.py +0 -447
  60. cnhkmcp/untracked/APP/hkSimulator/helpful_functions.py +0 -180
  61. cnhkmcp/untracked/APP/mirror_config.txt +0 -20
  62. cnhkmcp/untracked/APP/operaters.csv +0 -129
  63. cnhkmcp/untracked/APP/requirements.txt +0 -53
  64. cnhkmcp/untracked/APP/run_app.bat +0 -28
  65. cnhkmcp/untracked/APP/run_app.sh +0 -34
  66. cnhkmcp/untracked/APP/setup_tsinghua.bat +0 -39
  67. cnhkmcp/untracked/APP/setup_tsinghua.sh +0 -43
  68. cnhkmcp/untracked/APP/simulator/alpha_submitter.py +0 -404
  69. cnhkmcp/untracked/APP/simulator/simulator_wqb.py +0 -618
  70. cnhkmcp/untracked/APP/ssrn-3332513.pdf +6 -109201
  71. cnhkmcp/untracked/APP/static/brain.js +0 -589
  72. cnhkmcp/untracked/APP/static/decoder.js +0 -1540
  73. cnhkmcp/untracked/APP/static/feature_engineering.js +0 -1729
  74. cnhkmcp/untracked/APP/static/idea_house.js +0 -937
  75. cnhkmcp/untracked/APP/static/inspiration.js +0 -465
  76. cnhkmcp/untracked/APP/static/inspiration_house.js +0 -868
  77. cnhkmcp/untracked/APP/static/paper_analysis.js +0 -390
  78. cnhkmcp/untracked/APP/static/script.js +0 -3082
  79. cnhkmcp/untracked/APP/static/simulator.js +0 -597
  80. cnhkmcp/untracked/APP/static/styles.css +0 -3127
  81. cnhkmcp/untracked/APP/static/usage_widget.js +0 -508
  82. cnhkmcp/untracked/APP/templates/alpha_inspector.html +0 -511
  83. cnhkmcp/untracked/APP/templates/feature_engineering.html +0 -960
  84. cnhkmcp/untracked/APP/templates/idea_house.html +0 -564
  85. cnhkmcp/untracked/APP/templates/index.html +0 -932
  86. cnhkmcp/untracked/APP/templates/inspiration_house.html +0 -861
  87. cnhkmcp/untracked/APP/templates/paper_analysis.html +0 -91
  88. cnhkmcp/untracked/APP/templates/simulator.html +0 -343
  89. cnhkmcp/untracked/APP/templates/transformer_web.html +0 -580
  90. cnhkmcp/untracked/APP/usage.md +0 -351
  91. cnhkmcp/untracked/APP//321/207/342/225/235/320/250/321/205/320/230/320/226/321/204/342/225/225/320/220/321/211/320/221/320/243/321/206/320/261/320/265/ace_lib.py +0 -1510
  92. cnhkmcp/untracked/APP//321/207/342/225/235/320/250/321/205/320/230/320/226/321/204/342/225/225/320/220/321/211/320/221/320/243/321/206/320/261/320/265/brain_alpha_inspector.py +0 -712
  93. cnhkmcp/untracked/APP//321/207/342/225/235/320/250/321/205/320/230/320/226/321/204/342/225/225/320/220/321/211/320/221/320/243/321/206/320/261/320/265/helpful_functions.py +0 -180
  94. cnhkmcp/untracked/APP//321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +0 -2456
  95. cnhkmcp/untracked/arXiv_API_Tool_Manual.md +0 -490
  96. cnhkmcp/untracked/arxiv_api.py +0 -229
  97. cnhkmcp/untracked/forum_functions.py +0 -998
  98. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/forum_functions.py +0 -407
  99. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/platform_functions.py +0 -2415
  100. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/user_config.json +0 -31
  101. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272//321/210/320/276/320/271AI/321/210/320/277/342/225/227/321/210/342/224/220/320/251/321/204/342/225/225/320/272/321/206/320/246/320/227/321/206/320/261/320/263/321/206/320/255/320/265/321/205/320/275/320/266/321/204/342/225/235/320/252/321/204/342/225/225/320/233/321/210/342/225/234/342/225/234/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270.md +0 -101
  102. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272//321/211/320/225/320/235/321/207/342/225/234/320/276/321/205/320/231/320/235/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/230/320/241_/321/205/320/276/320/231/321/210/320/263/320/225/321/205/342/224/220/320/225/321/210/320/266/320/221/321/204/342/225/233/320/255/321/210/342/225/241/320/246/321/205/320/234/320/225.py +0 -190
  103. cnhkmcp/untracked/platform_functions.py +0 -2886
  104. cnhkmcp/untracked/sample_mcp_config.json +0 -11
  105. cnhkmcp/untracked/user_config.json +0 -31
  106. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/320/237/320/222/321/210/320/220/320/223/321/206/320/246/320/227/321/206/320/261/320/263_BRAIN_Alpha_Test_Requirements_and_Tips.md +0 -202
  107. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_Alpha_explaination_workflow.md +0 -56
  108. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_BRAIN_6_Tips_Datafield_Exploration_Guide.md +0 -194
  109. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_BRAIN_Alpha_Improvement_Workflow.md +0 -101
  110. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_Dataset_Exploration_Expert_Manual.md +0 -436
  111. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_daily_report_workflow.md +0 -128
  112. cnhkmcp/untracked//321/211/320/225/320/235/321/207/342/225/234/320/276/321/205/320/231/320/235/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/230/320/241_/321/205/320/276/320/231/321/210/320/263/320/225/321/205/342/224/220/320/225/321/210/320/266/320/221/321/204/342/225/233/320/255/321/210/342/225/241/320/246/321/205/320/234/320/225.py +0 -190
  113. cnhkmcp-2.1.1.dist-info/RECORD +0 -115
  114. cnhkmcp-2.1.1.dist-info/top_level.txt +0 -1
  115. {cnhkmcp-2.1.1.dist-info → cnhkmcp-2.1.3.dist-info}/WHEEL +0 -0
  116. {cnhkmcp-2.1.1.dist-info → cnhkmcp-2.1.3.dist-info}/entry_points.txt +0 -0
  117. {cnhkmcp-2.1.1.dist-info → cnhkmcp-2.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,3082 +0,0 @@
1
- /**
2
- * Main Application Script
3
- * Handles editor functionality, grammar checking, and template management
4
- * The 'templates' global variable is used by decoder.js module
5
- */
6
-
7
- // Global variables
8
- let currentTemplate = null;
9
- let currentConfigType = null;
10
- let templates = new Map(); // Used by decoder.js for template decoding
11
- let currentTransformerExplanation = ''; // Store explanation for current transformer template
12
-
13
- // Initialize the application
14
- document.addEventListener('DOMContentLoaded', function() {
15
- const editor = document.getElementById('expressionEditor');
16
- const decodeTemplatesBtn = document.getElementById('decodeTemplates');
17
- const detectTemplatesBtn = document.getElementById('detectTemplates');
18
- const clearEditorBtn = document.getElementById('clearEditor');
19
-
20
- // Initialize navigation
21
- initializeNavigation();
22
-
23
- // Debounce timer for automatic grammar checking
24
- let grammarCheckTimer;
25
-
26
- // Update line numbers when content changes
27
- editor.addEventListener('input', function(e) {
28
- updateLineNumbers();
29
- updateSyntaxHighlight();
30
-
31
- // Handle auto-completion
32
- handleAutoComplete(e);
33
-
34
- // Clear previous timer
35
- clearTimeout(grammarCheckTimer);
36
-
37
- // Set new timer for automatic grammar check (300ms delay)
38
- grammarCheckTimer = setTimeout(function() {
39
- checkGrammar();
40
- detectTemplates();
41
- }, 300);
42
- });
43
-
44
- // Handle keydown events for Tab completion and other keys
45
- editor.addEventListener('keydown', function(e) {
46
- if (e.key === 'Tab') {
47
- e.preventDefault();
48
- handleTabCompletion();
49
- } else if (e.key === 'Escape') {
50
- // Allow users to dismiss the shadow suggestion
51
- hideShadowSuggestion();
52
- autoCompleteActive = false;
53
- }
54
- });
55
-
56
- editor.addEventListener('scroll', syncScroll);
57
-
58
- // Hide shadow suggestion when editor loses focus
59
- editor.addEventListener('blur', function() {
60
- hideShadowSuggestion();
61
- autoCompleteActive = false;
62
- });
63
-
64
- // Button event listeners
65
- decodeTemplatesBtn.addEventListener('click', decodeTemplates);
66
- clearEditorBtn.addEventListener('click', clearEditor);
67
-
68
- // Random iteration button
69
- const randomIterationBtn = document.getElementById('randomIterationBtn');
70
- if (randomIterationBtn) {
71
- randomIterationBtn.addEventListener('click', randomIteration);
72
- }
73
-
74
- // BRAIN connection button
75
- const connectToBrainBtn = document.getElementById('connectToBrain');
76
- connectToBrainBtn.addEventListener('click', openBrainLoginModal);
77
-
78
- // Simulator button - removed as requested
79
- // const runSimulatorBtn = document.getElementById('runSimulator');
80
- // if (runSimulatorBtn) {
81
- // runSimulatorBtn.addEventListener('click', runSimulator);
82
- // }
83
-
84
- // Results button listeners
85
- const copyDisplayedBtn = document.getElementById('copyDisplayedResults');
86
- const copyAllBtn = document.getElementById('copyAllResults');
87
- const downloadBtn = document.getElementById('downloadResults');
88
- const nextMoveBtn = document.getElementById('nextMoveBtn');
89
- if (copyDisplayedBtn) copyDisplayedBtn.addEventListener('click', copyDisplayedResults);
90
- if (copyAllBtn) copyAllBtn.addEventListener('click', copyAllResults);
91
- if (downloadBtn) downloadBtn.addEventListener('click', downloadResults);
92
- if (nextMoveBtn) nextMoveBtn.addEventListener('click', openSettingsModal);
93
-
94
- // Initialize line numbers and syntax highlighting
95
- updateLineNumbers();
96
- updateSyntaxHighlight();
97
-
98
- // Auto-detect templates and check grammar on load
99
- detectTemplates();
100
- checkGrammar();
101
-
102
- // Handle Enter key in variable input
103
- const variableInput = document.getElementById('variableInput');
104
- variableInput.addEventListener('keydown', function(event) {
105
- if (event.key === 'Enter' && !event.shiftKey) {
106
- event.preventDefault();
107
- applyTemplate();
108
- }
109
- });
110
-
111
- // Update line numbers on window resize
112
- window.addEventListener('resize', function() {
113
- updateLineNumbers();
114
- });
115
-
116
- // Load custom templates on startup
117
- loadCustomTemplates();
118
- });
119
-
120
- // Custom Templates Management (Server-side storage)
121
-
122
- // Load custom templates from server
123
- async function loadCustomTemplates() {
124
- try {
125
- const response = await fetch('/api/templates');
126
- const customTemplates = await response.json();
127
-
128
- const buttonsContainer = document.getElementById('customTemplateButtons');
129
- const noTemplatesInfo = document.getElementById('noCustomTemplates');
130
-
131
- if (!buttonsContainer) {
132
- console.error('customTemplateButtons container not found!');
133
- return;
134
- }
135
-
136
- buttonsContainer.innerHTML = '';
137
-
138
- if (!Array.isArray(customTemplates) || customTemplates.length === 0) {
139
- // Only show "no templates" message if we're viewing custom or all templates
140
- if (noTemplatesInfo && (currentTemplateView === 'all' || currentTemplateView === 'custom')) {
141
- noTemplatesInfo.style.display = 'block';
142
- }
143
- } else {
144
- if (noTemplatesInfo) {
145
- noTemplatesInfo.style.display = 'none';
146
- }
147
-
148
- customTemplates.forEach((template, index) => {
149
- const button = document.createElement('button');
150
- button.className = 'btn btn-template btn-template-custom';
151
- button.setAttribute('data-template-type', 'custom');
152
- button.innerHTML = `
153
- ${template.name}
154
- <span class="delete-btn" onclick="deleteCustomTemplate(${index}, event)" title="Delete template">×</span>
155
- `;
156
- button.onclick = () => loadCustomTemplate(index);
157
- button.title = template.description || 'Click to load this template';
158
-
159
- buttonsContainer.appendChild(button);
160
- });
161
- }
162
- } catch (error) {
163
- console.error('Error loading templates:', error);
164
- showNotification('Error loading templates', 'error');
165
- }
166
- }
167
-
168
- // Save current template
169
- function saveCurrentTemplate() {
170
- const editor = document.getElementById('expressionEditor');
171
- const expression = editor.value.trim();
172
-
173
- if (!expression) {
174
- showNotification('Please enter an expression before saving', 'error');
175
- return;
176
- }
177
-
178
- // Show save modal
179
- const modal = document.getElementById('saveTemplateModal');
180
- const preview = document.getElementById('templatePreview');
181
- const nameInput = document.getElementById('templateName');
182
- const descInput = document.getElementById('templateDescription');
183
- const configurationsInfo = document.getElementById('templateConfigurationsInfo');
184
- const configurationsList = document.getElementById('configurationsList');
185
-
186
- preview.textContent = expression;
187
- nameInput.value = '';
188
- // Auto-fill description if available from Transformer template
189
- descInput.value = currentTransformerExplanation || '';
190
-
191
- // Check for configured templates and show info
192
- const configuredTemplates = [];
193
- templates.forEach((template, templateName) => {
194
- if (template.variables && template.variables.length > 0 && template.configType) {
195
- configuredTemplates.push({
196
- name: templateName,
197
- type: template.configType,
198
- count: template.variables.length
199
- });
200
- }
201
- });
202
-
203
- if (configuredTemplates.length > 0) {
204
- configurationsList.innerHTML = configuredTemplates.map(config =>
205
- `<li>&lt;${config.name}/&gt; - ${config.type} (${config.count} values)</li>`
206
- ).join('');
207
- configurationsInfo.style.display = 'block';
208
- } else {
209
- configurationsInfo.style.display = 'none';
210
- }
211
-
212
- modal.style.display = 'block';
213
- nameInput.focus();
214
-
215
- // Add Enter key support
216
- const handleEnter = (event) => {
217
- if (event.key === 'Enter' && !event.shiftKey) {
218
- event.preventDefault();
219
- confirmSaveTemplate();
220
- }
221
- };
222
- nameInput.addEventListener('keydown', handleEnter);
223
- descInput.addEventListener('keydown', handleEnter);
224
-
225
- // Clean up event listeners when modal closes
226
- modal.addEventListener('close', () => {
227
- nameInput.removeEventListener('keydown', handleEnter);
228
- descInput.removeEventListener('keydown', handleEnter);
229
- });
230
- }
231
-
232
- // Close save template modal
233
- function closeSaveTemplateModal() {
234
- const modal = document.getElementById('saveTemplateModal');
235
- modal.style.display = 'none';
236
- }
237
-
238
- // Overwrite existing template
239
- async function overwriteExistingTemplate() {
240
- const editor = document.getElementById('expressionEditor');
241
- const expression = editor.value.trim();
242
-
243
- if (!expression) {
244
- showNotification('Please enter an expression before overwriting a template', 'error');
245
- return;
246
- }
247
-
248
- // Check if there are any custom templates first
249
- try {
250
- const response = await fetch('/api/templates');
251
- const customTemplates = await response.json();
252
-
253
- if (!Array.isArray(customTemplates) || customTemplates.length === 0) {
254
- showNotification('No custom templates available to overwrite. Create a template first using "Save Current Template".', 'warning');
255
- return;
256
- }
257
- } catch (error) {
258
- console.error('Error checking templates:', error);
259
- showNotification('Error checking existing templates', 'error');
260
- return;
261
- }
262
-
263
- // Show overwrite modal
264
- const modal = document.getElementById('overwriteTemplateModal');
265
- const preview = document.getElementById('overwriteTemplatePreview');
266
- const templateSelect = document.getElementById('existingTemplateSelect');
267
- const confirmBtn = document.getElementById('overwriteConfirmBtn');
268
- const configurationsInfo = document.getElementById('overwriteConfigurationsInfo');
269
- const configurationsList = document.getElementById('overwriteConfigurationsList');
270
-
271
- preview.textContent = expression;
272
-
273
- // Reset UI
274
- templateSelect.value = '';
275
- confirmBtn.disabled = true;
276
- document.getElementById('selectedTemplateInfo').style.display = 'none';
277
-
278
- // Check for configured templates and show info
279
- const configuredTemplates = [];
280
- templates.forEach((template, templateName) => {
281
- if (template.variables && template.variables.length > 0 && template.configType) {
282
- configuredTemplates.push({
283
- name: templateName,
284
- type: template.configType,
285
- count: template.variables.length
286
- });
287
- }
288
- });
289
-
290
- if (configuredTemplates.length > 0) {
291
- configurationsList.innerHTML = configuredTemplates.map(config =>
292
- `<li>&lt;${config.name}/&gt; - ${config.type} (${config.count} values)</li>`
293
- ).join('');
294
- configurationsInfo.style.display = 'block';
295
- } else {
296
- configurationsInfo.style.display = 'none';
297
- }
298
-
299
- // Load existing templates for dropdown
300
- loadExistingTemplatesForOverwrite();
301
-
302
- // Add event listener for template selection
303
- templateSelect.onchange = handleTemplateSelectionForOverwrite;
304
-
305
- modal.style.display = 'block';
306
- }
307
-
308
- // Load existing templates for the overwrite dropdown
309
- async function loadExistingTemplatesForOverwrite() {
310
- try {
311
- const response = await fetch('/api/templates');
312
- const customTemplates = await response.json();
313
- const templateSelect = document.getElementById('existingTemplateSelect');
314
-
315
- // Clear existing options except the first one
316
- templateSelect.innerHTML = '<option value="">Select a template...</option>';
317
-
318
- if (Array.isArray(customTemplates) && customTemplates.length > 0) {
319
- customTemplates.forEach((template, index) => {
320
- const option = document.createElement('option');
321
- option.value = index;
322
- option.textContent = template.name;
323
- option.dataset.description = template.description || '';
324
- templateSelect.appendChild(option);
325
- });
326
- } else {
327
- const option = document.createElement('option');
328
- option.textContent = 'No custom templates available';
329
- option.disabled = true;
330
- templateSelect.appendChild(option);
331
- }
332
- } catch (error) {
333
- console.error('Error loading templates for overwrite:', error);
334
- showNotification('Error loading templates', 'error');
335
- }
336
- }
337
-
338
- // Handle template selection for overwrite
339
- function handleTemplateSelectionForOverwrite() {
340
- const templateSelect = document.getElementById('existingTemplateSelect');
341
- const selectedTemplateInfo = document.getElementById('selectedTemplateInfo');
342
- const currentTemplateDescription = document.getElementById('currentTemplateDescription');
343
- const confirmBtn = document.getElementById('overwriteConfirmBtn');
344
-
345
- if (templateSelect.value === '') {
346
- selectedTemplateInfo.style.display = 'none';
347
- confirmBtn.disabled = true;
348
- return;
349
- }
350
-
351
- // Show selected template info
352
- const selectedOption = templateSelect.options[templateSelect.selectedIndex];
353
- const description = selectedOption.dataset.description || 'No description';
354
-
355
- currentTemplateDescription.textContent = description;
356
- selectedTemplateInfo.style.display = 'block';
357
- confirmBtn.disabled = false;
358
- }
359
-
360
- // Close overwrite template modal
361
- function closeOverwriteTemplateModal() {
362
- const modal = document.getElementById('overwriteTemplateModal');
363
- modal.style.display = 'none';
364
- }
365
-
366
- // Confirm and overwrite template
367
- async function confirmOverwriteTemplate() {
368
- const templateSelect = document.getElementById('existingTemplateSelect');
369
- const editor = document.getElementById('expressionEditor');
370
-
371
- if (templateSelect.value === '') {
372
- showNotification('Please select a template to overwrite', 'error');
373
- return;
374
- }
375
-
376
- const selectedIndex = parseInt(templateSelect.value);
377
- const selectedTemplateName = templateSelect.options[templateSelect.selectedIndex].textContent;
378
-
379
- // Confirm the overwrite action
380
- if (!confirm(`Are you sure you want to overwrite the template "${selectedTemplateName}"? This action cannot be undone.`)) {
381
- return;
382
- }
383
-
384
- const expression = editor.value.trim();
385
-
386
- // Capture current template configurations
387
- const templateConfigurations = {};
388
- templates.forEach((template, templateName) => {
389
- if (template.variables && template.variables.length > 0 && template.configType) {
390
- templateConfigurations[templateName] = {
391
- variables: template.variables,
392
- configType: template.configType
393
- };
394
- }
395
- });
396
-
397
- try {
398
- // First get the existing template to preserve its name and original creation date
399
- const response = await fetch('/api/templates');
400
- const customTemplates = await response.json();
401
-
402
- if (!Array.isArray(customTemplates) || selectedIndex >= customTemplates.length) {
403
- showNotification('Selected template not found', 'error');
404
- return;
405
- }
406
-
407
- const existingTemplate = customTemplates[selectedIndex];
408
-
409
- // Update the template with new expression and configurations
410
- const updateResponse = await fetch('/api/templates', {
411
- method: 'POST',
412
- headers: {
413
- 'Content-Type': 'application/json'
414
- },
415
- body: JSON.stringify({
416
- name: existingTemplate.name, // Keep the original name
417
- description: existingTemplate.description, // Keep the original description
418
- expression: expression,
419
- templateConfigurations: templateConfigurations
420
- })
421
- });
422
-
423
- const result = await updateResponse.json();
424
-
425
- if (result.success) {
426
- // Close modal and reload templates
427
- closeOverwriteTemplateModal();
428
- loadCustomTemplates();
429
- showNotification(`Template "${existingTemplate.name}" overwritten successfully`, 'success');
430
- } else {
431
- showNotification(result.error || 'Error overwriting template', 'error');
432
- }
433
- } catch (error) {
434
- console.error('Error overwriting template:', error);
435
- showNotification('Error overwriting template', 'error');
436
- }
437
- }
438
-
439
- // Confirm and save template
440
- async function confirmSaveTemplate() {
441
- const nameInput = document.getElementById('templateName');
442
- const descInput = document.getElementById('templateDescription');
443
- const editor = document.getElementById('expressionEditor');
444
-
445
- const name = nameInput.value.trim();
446
- const description = descInput.value.trim();
447
- const expression = editor.value.trim();
448
-
449
- if (!name) {
450
- showNotification('Please enter a name for the template', 'error');
451
- nameInput.focus();
452
- return;
453
- }
454
-
455
- // Capture current template configurations
456
- const templateConfigurations = {};
457
- templates.forEach((template, templateName) => {
458
- if (template.variables && template.variables.length > 0 && template.configType) {
459
- templateConfigurations[templateName] = {
460
- variables: template.variables,
461
- configType: template.configType
462
- };
463
- }
464
- });
465
-
466
- try {
467
- const response = await fetch('/api/templates', {
468
- method: 'POST',
469
- headers: {
470
- 'Content-Type': 'application/json'
471
- },
472
- body: JSON.stringify({
473
- name: name,
474
- description: description,
475
- expression: expression,
476
- templateConfigurations: templateConfigurations
477
- })
478
- });
479
-
480
- const result = await response.json();
481
-
482
- if (result.success) {
483
- // Close modal and reload templates
484
- closeSaveTemplateModal();
485
- loadCustomTemplates();
486
- showNotification(result.message, 'success');
487
- } else {
488
- showNotification(result.error || 'Error saving template', 'error');
489
- }
490
- } catch (error) {
491
- console.error('Error saving template:', error);
492
- showNotification('Error saving template', 'error');
493
- }
494
- }
495
-
496
- // Load a custom template
497
- async function loadCustomTemplate(index) {
498
- try {
499
- const response = await fetch('/api/templates');
500
- const customTemplates = await response.json();
501
-
502
- if (Array.isArray(customTemplates) && index >= 0 && index < customTemplates.length) {
503
- const template = customTemplates[index];
504
- const editor = document.getElementById('expressionEditor');
505
-
506
- editor.value = template.expression;
507
- updateLineNumbers();
508
- updateSyntaxHighlight();
509
- checkGrammar();
510
- detectTemplates();
511
-
512
- // Restore template configurations if they exist
513
- if (template.templateConfigurations) {
514
- setTimeout(() => {
515
- Object.entries(template.templateConfigurations).forEach(([templateName, config]) => {
516
- if (templates.has(templateName) && config.variables && config.configType) {
517
- const templateObj = templates.get(templateName);
518
- templateObj.variables = config.variables;
519
- templateObj.configType = config.configType;
520
-
521
- // Update visual state
522
- if (templateObj.element) {
523
- templateObj.element.className = 'template-item configured';
524
- }
525
- updateTemplateCount(templateName);
526
- }
527
- });
528
-
529
- // Update the overall template status
530
- updateTemplateStatus();
531
- }, 100); // Small delay to ensure templates are detected first
532
- }
533
-
534
- showNotification(`Loaded template: ${template.name}`, 'success');
535
- }
536
- } catch (error) {
537
- console.error('Error loading template:', error);
538
- showNotification('Error loading template', 'error');
539
- }
540
- }
541
-
542
- // Delete a custom template
543
- async function deleteCustomTemplate(index, event) {
544
- event.stopPropagation(); // Prevent button click from triggering
545
-
546
- try {
547
- const response = await fetch('/api/templates');
548
- const customTemplates = await response.json();
549
-
550
- if (Array.isArray(customTemplates) && index >= 0 && index < customTemplates.length) {
551
- const template = customTemplates[index];
552
-
553
- if (confirm(`Are you sure you want to delete the template "${template.name}"?`)) {
554
- const deleteResponse = await fetch(`/api/templates/${index}`, {
555
- method: 'DELETE'
556
- });
557
-
558
- const result = await deleteResponse.json();
559
-
560
- if (result.success) {
561
- loadCustomTemplates();
562
- showNotification(result.message, 'info');
563
- } else {
564
- showNotification(result.error || 'Error deleting template', 'error');
565
- }
566
- }
567
- }
568
- } catch (error) {
569
- console.error('Error deleting template:', error);
570
- showNotification('Error deleting template', 'error');
571
- }
572
- }
573
-
574
- // Export custom templates to JSON file
575
- async function exportCustomTemplates() {
576
- try {
577
- const response = await fetch('/api/templates/export');
578
- const customTemplates = await response.json();
579
-
580
- if (!Array.isArray(customTemplates) || customTemplates.length === 0) {
581
- showNotification('No custom templates to export', 'warning');
582
- return;
583
- }
584
-
585
- const dataStr = JSON.stringify(customTemplates, null, 2);
586
- const dataBlob = new Blob([dataStr], { type: 'application/json' });
587
-
588
- const link = document.createElement('a');
589
- link.href = URL.createObjectURL(dataBlob);
590
- link.download = `brain_custom_templates_${new Date().toISOString().slice(0, 10)}.json`;
591
- document.body.appendChild(link);
592
- link.click();
593
- document.body.removeChild(link);
594
-
595
- showNotification(`Exported ${customTemplates.length} template${customTemplates.length > 1 ? 's' : ''}`, 'success');
596
- } catch (error) {
597
- console.error('Error exporting templates:', error);
598
- showNotification('Error exporting templates', 'error');
599
- }
600
- }
601
-
602
- // Import custom templates from JSON file
603
- function importCustomTemplates(event) {
604
- const file = event.target.files[0];
605
- if (!file) return;
606
-
607
- const reader = new FileReader();
608
- reader.onload = async function(e) {
609
- try {
610
- const importedTemplates = JSON.parse(e.target.result);
611
-
612
- if (!Array.isArray(importedTemplates)) {
613
- throw new Error('Invalid template file format');
614
- }
615
-
616
- // Validate template structure
617
- const validTemplates = importedTemplates.filter(t =>
618
- t.name && typeof t.name === 'string' &&
619
- t.expression && typeof t.expression === 'string'
620
- );
621
-
622
- if (validTemplates.length === 0) {
623
- throw new Error('No valid templates found in file');
624
- }
625
-
626
- // Get existing templates to check for duplicates
627
- const response = await fetch('/api/templates');
628
- const existingTemplates = await response.json();
629
-
630
- // Check for duplicates
631
- const duplicates = validTemplates.filter(imported =>
632
- Array.isArray(existingTemplates) && existingTemplates.some(existing => existing.name === imported.name)
633
- );
634
-
635
- let overwrite = false;
636
- if (duplicates.length > 0) {
637
- const duplicateNames = duplicates.map(t => t.name).join(', ');
638
- overwrite = confirm(`The following templates already exist: ${duplicateNames}\n\nDo you want to overwrite them?`);
639
-
640
- if (!overwrite) {
641
- // Filter out duplicates if user doesn't want to overwrite
642
- const nonDuplicates = validTemplates.filter(imported =>
643
- !Array.isArray(existingTemplates) || !existingTemplates.some(existing => existing.name === imported.name)
644
- );
645
-
646
- if (nonDuplicates.length === 0) {
647
- showNotification('Import cancelled - all templates already exist', 'info');
648
- event.target.value = ''; // Reset file input
649
- return;
650
- }
651
- }
652
- }
653
-
654
- // Import templates
655
- const importResponse = await fetch('/api/templates/import', {
656
- method: 'POST',
657
- headers: {
658
- 'Content-Type': 'application/json'
659
- },
660
- body: JSON.stringify({
661
- templates: validTemplates,
662
- overwrite: overwrite
663
- })
664
- });
665
-
666
- const result = await importResponse.json();
667
-
668
- if (result.success) {
669
- loadCustomTemplates();
670
-
671
- let message = `Imported ${result.imported} new template${result.imported !== 1 ? 's' : ''}`;
672
- if (result.overwritten > 0) {
673
- message += `, overwritten ${result.overwritten}`;
674
- }
675
- showNotification(message, 'success');
676
- } else {
677
- showNotification(result.error || 'Import failed', 'error');
678
- }
679
-
680
- } catch (error) {
681
- showNotification(`Import failed: ${error.message}`, 'error');
682
- }
683
-
684
- event.target.value = ''; // Reset file input
685
- };
686
-
687
- reader.readAsText(file);
688
- }
689
-
690
- // Run simulator script
691
- function runSimulator() {
692
- // Show modal with two options
693
- showSimulatorOptionsModal();
694
- }
695
-
696
- function showSimulatorOptionsModal() {
697
- // Create modal HTML if it doesn't exist
698
- let modal = document.getElementById('simulatorOptionsModal');
699
- if (!modal) {
700
- modal = document.createElement('div');
701
- modal.id = 'simulatorOptionsModal';
702
- modal.className = 'modal';
703
- modal.innerHTML = `
704
- <div class="modal-content" style="max-width: 600px;">
705
- <div class="modal-header">
706
- <h3>🚀 Run Simulator</h3>
707
- <span class="close" onclick="closeSimulatorOptionsModal()">&times;</span>
708
- </div>
709
- <div class="modal-body">
710
- <p style="margin-bottom: 20px;">选择您想要运行 BRAIN Alpha 模拟器的方式:</p>
711
-
712
- <div class="simulator-options">
713
- <div class="simulator-option" onclick="runTraditionalSimulator()">
714
- <div class="option-icon">⚙️</div>
715
- <div class="option-content">
716
- <h4>命令行界面</h4>
717
- <p>传统的交互式命令行界面,带有逐步提示。</p>
718
- <ul>
719
- <li>交互式参数输入</li>
720
- <li>逐步配置</li>
721
- <li>适合熟悉命令行的高级用户</li>
722
- </ul>
723
- </div>
724
- </div>
725
-
726
- <div class="simulator-option" onclick="runWebSimulator()">
727
- <div class="option-icon">🌐</div>
728
- <div class="option-content">
729
- <h4>Web 界面</h4>
730
- <p>用户友好的 Web 表单,所有参数集中在一个页面。</p>
731
- <ul>
732
- <li>所有参数在一个表单中</li>
733
- <li>实时日志监控</li>
734
- <li>可视化进度跟踪</li>
735
- <li>对初学者友好的界面</li>
736
- </ul>
737
- </div>
738
- </div>
739
- </div>
740
- </div>
741
- </div>
742
- `;
743
- document.body.appendChild(modal);
744
- }
745
-
746
- modal.style.display = 'block';
747
- }
748
-
749
- function closeSimulatorOptionsModal() {
750
- const modal = document.getElementById('simulatorOptionsModal');
751
- if (modal) {
752
- modal.style.display = 'none';
753
- }
754
- }
755
-
756
- async function runTraditionalSimulator() {
757
- closeSimulatorOptionsModal();
758
-
759
- try {
760
- const response = await fetch('/api/run-simulator', {
761
- method: 'POST',
762
- headers: {
763
- 'Content-Type': 'application/json'
764
- }
765
- });
766
-
767
- const result = await response.json();
768
-
769
- if (result.success) {
770
- showNotification(result.message, 'success');
771
- } else {
772
- showNotification(result.error || 'Failed to run simulator', 'error');
773
- }
774
- } catch (error) {
775
- console.error('Error running simulator:', error);
776
- showNotification('Error running simulator', 'error');
777
- }
778
- }
779
-
780
- function runWebSimulator() {
781
- closeSimulatorOptionsModal();
782
- // Navigate to the new simulator page
783
- window.location.href = '/simulator';
784
- }
785
-
786
- async function openSubmitter() {
787
- try {
788
- const response = await fetch('/api/open-submitter', {
789
- method: 'POST',
790
- headers: {
791
- 'Content-Type': 'application/json'
792
- }
793
- });
794
-
795
- const result = await response.json();
796
-
797
- if (result.success) {
798
- showNotification(result.message, 'success');
799
- } else {
800
- showNotification(result.error || 'Failed to open submitter', 'error');
801
- }
802
- } catch (error) {
803
- console.error('Error opening submitter:', error);
804
- showNotification('Error opening submitter', 'error');
805
- }
806
- }
807
-
808
- async function openHKSimulator() {
809
- try {
810
- const response = await fetch('/api/open-hk-simulator', {
811
- method: 'POST',
812
- headers: {
813
- 'Content-Type': 'application/json'
814
- }
815
- });
816
-
817
- const result = await response.json();
818
-
819
- if (result.success) {
820
- showNotification(result.message, 'success');
821
- } else {
822
- showNotification(result.error || 'Failed to open HK simulator', 'error');
823
- }
824
- } catch (error) {
825
- console.error('Error opening HK simulator:', error);
826
- showNotification('Error opening HK simulator', 'error');
827
- }
828
- }
829
-
830
- function openTransformer() {
831
- showTransformerOptionsModal();
832
- }
833
-
834
- function showTransformerOptionsModal() {
835
- let modal = document.getElementById('transformerOptionsModal');
836
- if (!modal) {
837
- modal = document.createElement('div');
838
- modal.id = 'transformerOptionsModal';
839
- modal.className = 'modal';
840
- modal.innerHTML = `
841
- <div class="modal-content" style="max-width: 600px;">
842
- <div class="modal-header">
843
- <h3>🚀 Run Transformer (72变)</h3>
844
- <span class="close" onclick="closeTransformerOptionsModal()">&times;</span>
845
- </div>
846
- <div class="modal-body">
847
- <p style="margin-bottom: 20px;">选择您想要运行 Transformer 的方式:</p>
848
-
849
- <div class="simulator-options">
850
- <div class="simulator-option" onclick="runTraditionalTransformer()">
851
- <div class="option-icon">⚙️</div>
852
- <div class="option-content">
853
- <h4>命令行界面</h4>
854
- <p>传统的交互式命令行界面。</p>
855
- <ul>
856
- <li>交互式参数输入</li>
857
- <li>适合熟悉命令行的高级用户</li>
858
- </ul>
859
- </div>
860
- </div>
861
-
862
- <div class="simulator-option" onclick="runWebTransformer()">
863
- <div class="option-icon">🌐</div>
864
- <div class="option-content">
865
- <h4>Web 界面</h4>
866
- <p>用户友好的 Web 表单。</p>
867
- <ul>
868
- <li>所有参数在一个表单中</li>
869
- <li>实时日志监控</li>
870
- <li>结果下载</li>
871
- </ul>
872
- </div>
873
- </div>
874
- </div>
875
- </div>
876
- </div>
877
- `;
878
- document.body.appendChild(modal);
879
- }
880
-
881
- modal.style.display = 'block';
882
- }
883
-
884
- function closeTransformerOptionsModal() {
885
- const modal = document.getElementById('transformerOptionsModal');
886
- if (modal) {
887
- modal.style.display = 'none';
888
- }
889
- }
890
-
891
- async function runTraditionalTransformer() {
892
- closeTransformerOptionsModal();
893
- try {
894
- const response = await fetch('/api/open-transformer', {
895
- method: 'POST',
896
- headers: {
897
- 'Content-Type': 'application/json'
898
- }
899
- });
900
-
901
- const result = await response.json();
902
-
903
- if (result.success) {
904
- showNotification(result.message, 'success');
905
- } else {
906
- showNotification(result.error || 'Failed to open Transformer', 'error');
907
- }
908
- } catch (error) {
909
- console.error('Error opening Transformer:', error);
910
- showNotification('Error opening Transformer', 'error');
911
- }
912
- }
913
-
914
- function runWebTransformer() {
915
- closeTransformerOptionsModal();
916
- window.location.href = '/transformer-web';
917
- }
918
-
919
- // Make functions globally accessible
920
- window.saveCurrentTemplate = saveCurrentTemplate;
921
- window.closeSaveTemplateModal = closeSaveTemplateModal;
922
- window.confirmSaveTemplate = confirmSaveTemplate;
923
- window.overwriteExistingTemplate = overwriteExistingTemplate;
924
- window.openSubmitter = openSubmitter;
925
- window.openHKSimulator = openHKSimulator;
926
- window.openTransformer = openTransformer;
927
- window.closeOverwriteTemplateModal = closeOverwriteTemplateModal;
928
- window.confirmOverwriteTemplate = confirmOverwriteTemplate;
929
- window.loadCustomTemplate = loadCustomTemplate;
930
- window.deleteCustomTemplate = deleteCustomTemplate;
931
- window.exportCustomTemplates = exportCustomTemplates;
932
- window.importCustomTemplates = importCustomTemplates;
933
- window.runSimulator = runSimulator;
934
- window.showSimulatorOptionsModal = showSimulatorOptionsModal;
935
- window.closeSimulatorOptionsModal = closeSimulatorOptionsModal;
936
- window.runTraditionalSimulator = runTraditionalSimulator;
937
- window.runWebSimulator = runWebSimulator;
938
-
939
- // Template View Toggle Functionality
940
- let currentTemplateView = 'all'; // 'all', 'custom', 'example'
941
-
942
- function toggleTemplateView() {
943
- const toggleBtn = document.getElementById('toggleTemplateView');
944
- const toggleText = document.getElementById('toggleTemplateViewText');
945
- const exampleTemplates = document.getElementById('exampleTemplateButtons');
946
- const customTemplates = document.getElementById('customTemplateButtons');
947
- const noTemplatesInfo = document.getElementById('noCustomTemplates');
948
-
949
- // Cycle through views: all -> custom -> example -> all
950
- if (currentTemplateView === 'all') {
951
- currentTemplateView = 'custom';
952
- toggleText.textContent = '只展示内置模板';
953
- exampleTemplates.style.display = 'none';
954
- customTemplates.style.display = 'block';
955
-
956
- // Check if there are custom templates
957
- if (customTemplates.children.length === 0 && noTemplatesInfo) {
958
- noTemplatesInfo.style.display = 'block';
959
- }
960
- } else if (currentTemplateView === 'custom') {
961
- currentTemplateView = 'example';
962
- toggleText.textContent = '展示所有模板';
963
- exampleTemplates.style.display = 'block';
964
- customTemplates.style.display = 'none';
965
- if (noTemplatesInfo) {
966
- noTemplatesInfo.style.display = 'none';
967
- }
968
- } else {
969
- currentTemplateView = 'all';
970
- toggleText.textContent = 'Show Custom Only';
971
- exampleTemplates.style.display = 'block';
972
- customTemplates.style.display = 'block';
973
-
974
- // Show no templates info only if in all view and no custom templates
975
- if (customTemplates.children.length === 0 && noTemplatesInfo) {
976
- noTemplatesInfo.style.display = 'block';
977
- } else if (noTemplatesInfo) {
978
- noTemplatesInfo.style.display = 'none';
979
- }
980
- }
981
- }
982
-
983
- // Make toggleTemplateView globally accessible
984
- window.toggleTemplateView = toggleTemplateView;
985
-
986
- // Load template examples
987
- function loadTemplateExample(exampleNumber) {
988
- const editor = document.getElementById('expressionEditor');
989
- const examples = {
990
- 1: `to_nan(
991
- group_normalize(
992
- group_neutralize(
993
- group_rank(
994
- ts_rank(
995
- ts_decay_linear(
996
- ts_returns(
997
- ts_backfill(<data_field/>, <backfill_days/>)/<secondary_data_field/>, <returns_window/>
998
- ), <decay_window/>
999
- ), <rank_window/>
1000
- ), <group/>
1001
- ), <group/>
1002
- ), <market/>
1003
- ), value=<nan_value/>, reverse=<reverse_bool/>
1004
- )`,
1005
- 2: `ts_decay_exp_window(
1006
- ts_max(
1007
- vec_avg(<model_field/>), <max_window/>
1008
- ), <decay_window/>
1009
- )`,
1010
- 3: `financial_data = ts_backfill(vec_func(<analyst_metric/>), <backfill_days/>);
1011
- gp = group_cartesian_product(<group1/>, <group2/>);
1012
- data = <ts_operator/>(
1013
- <group_operator/>(financial_data, gp), <window/>
1014
- )`,
1015
- 4: `alpha = <cross_sectional_transform/>(
1016
- <time_series_transform/>(<feature/>, <ts_window/>), <group/>
1017
- );
1018
- alpha_gpm = group_mean(alpha, <weight/>, <group/>);
1019
- resid = <neutralization_func/>(alpha, alpha_gpm);
1020
- final_signal = <time_series_transform2/>(
1021
- group_neutralize(resid, <group2/>), <final_window/>
1022
- )`,
1023
- 5: `alpha = group_zscore(
1024
- ts_zscore(
1025
- ts_backfill(vec_avg(<analyst_field/>), <backfill_days/>), <zscore_window/>
1026
- ), <exchange/>
1027
- );
1028
- alpha_gpm = group_mean(alpha, <cap_weight/>, <country/>);
1029
- resid = subtract(alpha, alpha_gpm);
1030
- ts_mean(group_neutralize(resid, <market/>), <mean_window/>)`,
1031
- 6: `data = ts_backfill(
1032
- winsorize(vec_avg(<analyst_field/>), std=<std_value/>), <backfill_days/>
1033
- );
1034
- t_data = normalize(data);
1035
- gp = group_cartesian_product(<market/>, <country/>);
1036
- signal = group_normalize(ts_zscore(t_data, <zscore_window/>), gp);
1037
- gpm = group_mean(signal, 1, gp);
1038
- gpm_signal = subtract(signal, gpm);
1039
- opt = group_neutralize(
1040
- arc_tan(ts_decay_exp_window(gpm_signal, <decay_window/>)), gp
1041
- );
1042
- ts_target_tvr_delta_limit(opt, ts_std_dev(opt, <std_window/>), target_tvr=<tvr_value/>)`,
1043
- 7: `group = <industry/>;
1044
- data = ts_min_max_cps(
1045
- group_zscore(
1046
- ts_backfill(vec_min(<model_field/>), <backfill_days/>), group
1047
- ), <minmax_window/>
1048
- );
1049
- ts_data = ts_median(data, <median_window/>);
1050
- ts_target_tvr_hump(
1051
- group_neutralize(subtract(data, ts_data), group), target_tvr=<tvr_value/>
1052
- )`
1053
- };
1054
-
1055
- if (examples[exampleNumber]) {
1056
- editor.value = examples[exampleNumber];
1057
- updateLineNumbers();
1058
- updateSyntaxHighlight();
1059
- checkGrammar();
1060
- detectTemplates();
1061
-
1062
- // Show a notification
1063
- showNotification(`Loaded template example ${exampleNumber}`, 'success');
1064
- }
1065
- }
1066
-
1067
- // Show notification
1068
- function showNotification(message, type = 'info') {
1069
- // Create notification element
1070
- const notification = document.createElement('div');
1071
- notification.className = `notification ${type}`;
1072
- notification.textContent = message;
1073
- notification.style.cssText = `
1074
- position: fixed;
1075
- top: 20px;
1076
- right: 20px;
1077
- padding: 12px 20px;
1078
- background: ${type === 'success' ? '#48bb78' : '#667eea'};
1079
- color: white;
1080
- border-radius: 6px;
1081
- box-shadow: 0 2px 10px rgba(0,0,0,0.2);
1082
- z-index: 10000;
1083
- animation: slideIn 0.3s ease;
1084
- `;
1085
-
1086
- document.body.appendChild(notification);
1087
-
1088
- // Remove after 3 seconds
1089
- setTimeout(() => {
1090
- notification.style.animation = 'fadeOut 0.3s ease';
1091
- setTimeout(() => {
1092
- document.body.removeChild(notification);
1093
- }, 300);
1094
- }, 3000);
1095
- }
1096
-
1097
- // Make loadTemplateExample globally accessible
1098
- window.loadTemplateExample = loadTemplateExample;
1099
-
1100
- // Initialize navigation system
1101
- function initializeNavigation() {
1102
- const navTabs = document.querySelectorAll('.nav-tab');
1103
-
1104
- navTabs.forEach(tab => {
1105
- tab.addEventListener('click', function() {
1106
- const targetPage = this.getAttribute('data-page');
1107
- navigateToPage(targetPage);
1108
- });
1109
- });
1110
- }
1111
-
1112
- // Navigate to a specific page
1113
- function navigateToPage(pageName) {
1114
- // Update nav tabs
1115
- const navTabs = document.querySelectorAll('.nav-tab');
1116
- navTabs.forEach(tab => {
1117
- if (tab.getAttribute('data-page') === pageName) {
1118
- tab.classList.add('active');
1119
- } else {
1120
- tab.classList.remove('active');
1121
- }
1122
- });
1123
-
1124
- // Update page content
1125
- const pages = document.querySelectorAll('.page-content');
1126
- pages.forEach(page => {
1127
- if (page.id === pageName + 'Page') {
1128
- page.classList.add('active');
1129
- } else {
1130
- page.classList.remove('active');
1131
- }
1132
- });
1133
-
1134
- // Update template status when navigating to decode page
1135
- if (pageName === 'decode') {
1136
- updateTemplateStatus();
1137
- }
1138
- }
1139
-
1140
- // Update template status on decode page
1141
- function updateTemplateStatus() {
1142
- const statusDiv = document.getElementById('templateStatus');
1143
- const decodeHeading = document.querySelector('#decodePage h2');
1144
- const totalTemplates = templates.size;
1145
- const configuredTemplates = Array.from(templates.values()).filter(t => t.variables.length > 0).length;
1146
-
1147
- // Reset heading to default
1148
- decodeHeading.textContent = 'Template Decoding Options';
1149
-
1150
- if (totalTemplates === 0) {
1151
- statusDiv.innerHTML = `
1152
- <div class="info-message">
1153
- No templates detected in your expression.
1154
- <button class="btn btn-secondary btn-small" onclick="navigateToPage('editor')">Go back to editor</button>
1155
- </div>
1156
- `;
1157
- } else if (configuredTemplates === 0) {
1158
- statusDiv.innerHTML = `
1159
- <div class="warning-message">
1160
- <strong>⚠️ No templates configured yet!</strong><br>
1161
- You have ${totalTemplates} variable${totalTemplates > 1 ? 's' : ''} in your expression, but none are configured.<br>
1162
- <button class="btn btn-secondary btn-small" onclick="navigateToPage('editor')">Configure templates</button>
1163
- </div>
1164
- `;
1165
- } else if (configuredTemplates < totalTemplates) {
1166
- statusDiv.innerHTML = `
1167
- <div class="warning-message">
1168
- <strong>⚠️ Some templates not configured!</strong><br>
1169
- ${configuredTemplates} out of ${totalTemplates} templates are configured.<br>
1170
- <button class="btn btn-secondary btn-small" onclick="navigateToPage('editor')">Configure remaining templates</button>
1171
- </div>
1172
- `;
1173
- } else {
1174
- // All templates configured - calculate search space
1175
- let searchSpace = [];
1176
- let totalCombinations = 1;
1177
-
1178
- templates.forEach((template, name) => {
1179
- if (template.variables.length > 0) {
1180
- searchSpace.push(template.variables.length);
1181
- totalCombinations *= template.variables.length;
1182
- }
1183
- });
1184
-
1185
- // Update heading with search space
1186
- const searchSpaceStr = searchSpace.join(' × ');
1187
- decodeHeading.innerHTML = `Template Decoding Options <span class="search-space">(SearchSpace: ${searchSpaceStr} = ${totalCombinations.toLocaleString()})</span>`;
1188
-
1189
- let configDetails = '<ul style="margin: 10px 0; padding-left: 20px;">';
1190
- templates.forEach((template, name) => {
1191
- if (template.variables.length > 0) {
1192
- configDetails += `<li><strong>&lt;${name}/&gt;</strong>: ${template.variables.length} ${template.configType || 'values'}</li>`;
1193
- }
1194
- });
1195
- configDetails += '</ul>';
1196
-
1197
- statusDiv.innerHTML = `
1198
- <div class="success-message">
1199
- <strong>✓ All templates configured!</strong><br>
1200
- ${configDetails}
1201
- Ready to decode your expressions.
1202
- </div>
1203
- `;
1204
- }
1205
- }
1206
-
1207
- // Make navigateToPage globally accessible
1208
- window.navigateToPage = navigateToPage;
1209
-
1210
- // Update line numbers in the editor
1211
- function updateLineNumbers() {
1212
- const editor = document.getElementById('expressionEditor');
1213
- const lineNumbers = document.getElementById('lineNumbers');
1214
- const lines = editor.value.split('\n');
1215
-
1216
- // Calculate how many lines we need based on editor height
1217
- const editorHeight = editor.offsetHeight || 500;
1218
- const lineHeight = 25.6; // 16px font-size * 1.6 line-height
1219
- const visibleLines = Math.ceil(editorHeight / lineHeight);
1220
- const totalLines = Math.max(lines.length, visibleLines);
1221
-
1222
- // Build line numbers text
1223
- let lineNumbersText = '';
1224
- for (let i = 1; i <= totalLines; i++) {
1225
- lineNumbersText += i + '\n';
1226
- }
1227
-
1228
- // Remove trailing newline for better alignment
1229
- lineNumbers.textContent = lineNumbersText.trimEnd();
1230
- }
1231
-
1232
- // Sync scroll between editor and line numbers
1233
- function syncScroll() {
1234
- const editor = document.getElementById('expressionEditor');
1235
- const lineNumbers = document.getElementById('lineNumbers');
1236
- const highlightedText = document.getElementById('highlightedText');
1237
-
1238
- lineNumbers.scrollTop = editor.scrollTop;
1239
- highlightedText.scrollTop = editor.scrollTop;
1240
- highlightedText.scrollLeft = editor.scrollLeft;
1241
-
1242
- // Hide shadow suggestion when scrolling
1243
- hideShadowSuggestion();
1244
- }
1245
-
1246
- // Update syntax highlighting
1247
- function updateSyntaxHighlight() {
1248
- const editor = document.getElementById('expressionEditor');
1249
- const highlightedText = document.getElementById('highlightedText');
1250
- const text = editor.value;
1251
-
1252
- // Escape HTML special characters
1253
- let escapedText = text
1254
- .replace(/&/g, '&amp;')
1255
- .replace(/</g, '&lt;')
1256
- .replace(/>/g, '&gt;')
1257
- .replace(/"/g, '&quot;')
1258
- .replace(/'/g, '&#039;');
1259
-
1260
- // Highlight template tags
1261
- escapedText = escapedText.replace(/(&lt;)(\/?)(\w+)(\/&gt;)/g, function(match, open, slash, tagName, close) {
1262
- return `<span class="template-brackets">${open}</span>` +
1263
- `<span class="template-brackets">${slash}</span>` +
1264
- `<span class="template-tag">${tagName}</span>` +
1265
- `<span class="template-brackets">${close}</span>`;
1266
- });
1267
-
1268
- highlightedText.innerHTML = escapedText;
1269
- }
1270
-
1271
- // Grammar checking function
1272
- function checkGrammar() {
1273
- const editor = document.getElementById('expressionEditor');
1274
- const content = editor.value;
1275
- const errorsDiv = document.getElementById('grammarErrors');
1276
- const errors = [];
1277
-
1278
- // Clear previous errors
1279
- errorsDiv.innerHTML = '';
1280
-
1281
- // Check for unclosed block comments
1282
- const commentStart = content.match(/\/\*/g) || [];
1283
- const commentEnd = content.match(/\*\//g) || [];
1284
- if (commentStart.length !== commentEnd.length) {
1285
- errors.push({
1286
- type: 'error',
1287
- message: 'Unclosed block comment detected. Each /* must have a matching */'
1288
- });
1289
- }
1290
-
1291
- // Remove comments for statement detection
1292
- let contentWithoutComments = content.replace(/\/\*[\s\S]*?\*\//g, '').trim();
1293
-
1294
- // Check if content is empty after removing comments
1295
- if (!contentWithoutComments) {
1296
- errorsDiv.innerHTML = '<div class="info-message">Enter an expression to check grammar</div>';
1297
- return;
1298
- }
1299
-
1300
- // Detect statements by looking for assignment patterns (variable = expression)
1301
- // or by semicolons
1302
- const lines = contentWithoutComments.split('\n');
1303
- let statements = [];
1304
- let currentStatement = '';
1305
- let statementStartLine = 0;
1306
- let openParens = 0;
1307
- let openBrackets = 0;
1308
- let inStatement = false;
1309
-
1310
- for (let i = 0; i < lines.length; i++) {
1311
- const line = lines[i];
1312
- const trimmedLine = line.trim();
1313
-
1314
- // Skip empty lines
1315
- if (trimmedLine === '') {
1316
- if (currentStatement.trim()) {
1317
- currentStatement += '\n';
1318
- }
1319
- continue;
1320
- }
1321
-
1322
- // Track parentheses and brackets to handle multi-line expressions
1323
- for (let char of trimmedLine) {
1324
- if (char === '(') openParens++;
1325
- else if (char === ')') openParens--;
1326
- else if (char === '[') openBrackets++;
1327
- else if (char === ']') openBrackets--;
1328
- }
1329
-
1330
- currentStatement += (currentStatement ? '\n' : '') + line;
1331
-
1332
- // Check if this line starts a new statement (has assignment operator)
1333
- if (!inStatement && trimmedLine.match(/^\w+\s*=/)) {
1334
- inStatement = true;
1335
- statementStartLine = i;
1336
- }
1337
-
1338
- // Check if statement is complete
1339
- if (trimmedLine.endsWith(';') ||
1340
- (i === lines.length - 1) || // Last line
1341
- (i < lines.length - 1 && lines[i + 1].trim().match(/^\w+\s*=/))) { // Next line starts new assignment
1342
-
1343
- // Statement is complete
1344
- if (currentStatement.trim()) {
1345
- statements.push({
1346
- text: currentStatement.trim(),
1347
- startLine: statementStartLine,
1348
- endLine: i,
1349
- hasSemicolon: trimmedLine.endsWith(';'),
1350
- isLastStatement: i === lines.length - 1 || (i < lines.length - 1 && !lines.slice(i + 1).some(l => l.trim()))
1351
- });
1352
- }
1353
- currentStatement = '';
1354
- inStatement = false;
1355
- openParens = 0;
1356
- openBrackets = 0;
1357
- }
1358
- }
1359
-
1360
- // Validate statements
1361
- if (statements.length === 0) {
1362
- // Single expression without assignment
1363
- const hasSemicolon = contentWithoutComments.trim().endsWith(';');
1364
- if (hasSemicolon) {
1365
- errors.push({
1366
- type: 'warning',
1367
- message: 'Single expression (Alpha expression) should not end with a semicolon'
1368
- });
1369
- }
1370
-
1371
- // Check if single expression is a variable assignment
1372
- const assignmentPattern = /^\s*\w+\s*=\s*[\s\S]*$/;
1373
- if (assignmentPattern.test(contentWithoutComments)) {
1374
- errors.push({
1375
- type: 'error',
1376
- message: 'The Alpha expression (final result) cannot be assigned to a variable. Remove the variable assignment.'
1377
- });
1378
- }
1379
- } else if (statements.length === 1) {
1380
- // Single statement
1381
- if (statements[0].hasSemicolon && statements[0].isLastStatement) {
1382
- errors.push({
1383
- type: 'warning',
1384
- message: 'The last statement (Alpha expression) should not end with a semicolon'
1385
- });
1386
- }
1387
-
1388
- // Check if single statement is a variable assignment
1389
- const assignmentPattern = /^\s*\w+\s*=\s*[\s\S]*$/;
1390
- if (assignmentPattern.test(statements[0].text)) {
1391
- errors.push({
1392
- type: 'error',
1393
- message: 'The Alpha expression (final result) cannot be assigned to a variable. Remove the variable assignment.'
1394
- });
1395
- }
1396
- } else {
1397
- // Multiple statements
1398
- for (let i = 0; i < statements.length; i++) {
1399
- const stmt = statements[i];
1400
- if (i < statements.length - 1 && !stmt.hasSemicolon) {
1401
- // Not the last statement and missing semicolon
1402
- errors.push({
1403
- type: 'error',
1404
- line: stmt.endLine + 1,
1405
- message: `Line ${stmt.endLine + 1}: Missing semicolon at the end of the statement`
1406
- });
1407
- } else if (i === statements.length - 1 && stmt.hasSemicolon) {
1408
- // Last statement with semicolon
1409
- errors.push({
1410
- type: 'warning',
1411
- line: stmt.endLine + 1,
1412
- message: `Line ${stmt.endLine + 1}: The last statement (Alpha expression) should not end with a semicolon`
1413
- });
1414
- }
1415
-
1416
- // Check if last statement is a variable assignment
1417
- if (i === statements.length - 1) {
1418
- const assignmentPattern = /^\s*\w+\s*=\s*[\s\S]*$/;
1419
- if (assignmentPattern.test(stmt.text)) {
1420
- errors.push({
1421
- type: 'error',
1422
- line: stmt.endLine + 1,
1423
- message: `Line ${stmt.endLine + 1}: The Alpha expression (final result) cannot be assigned to a variable. Remove the variable assignment.`
1424
- });
1425
- }
1426
- }
1427
- }
1428
- }
1429
-
1430
- // Check for forbidden constructs
1431
- const forbiddenPatterns = [
1432
- { pattern: /\bclass\s+\w+/, message: 'Classes are not allowed in this expression language' },
1433
- { pattern: /\bfunction\s+\w+/, message: 'Functions are not allowed in this expression language' },
1434
- { pattern: /\w+\s*\*\s*\w+/, message: 'Pointers are not allowed in this expression language' },
1435
- { pattern: /\bnew\s+\w+/, message: 'Object creation (new) is not allowed in this expression language' }
1436
- ];
1437
-
1438
- forbiddenPatterns.forEach(({ pattern, message }) => {
1439
- const matches = content.match(pattern);
1440
- if (matches) {
1441
- errors.push({
1442
- type: 'error',
1443
- message: message
1444
- });
1445
- }
1446
- });
1447
-
1448
- // Display errors or success message
1449
- if (errors.length === 0) {
1450
- errorsDiv.innerHTML = '<div class="success-message">✓ Grammar check passed! No errors found.</div>';
1451
- } else {
1452
- errors.forEach(error => {
1453
- const errorDiv = document.createElement('div');
1454
- errorDiv.className = 'error-item';
1455
- errorDiv.innerHTML = `<strong>${error.type.toUpperCase()}:</strong> ${error.message}`;
1456
- errorsDiv.appendChild(errorDiv);
1457
- });
1458
- }
1459
- }
1460
-
1461
- // Update template count display
1462
- function updateTemplateCount(templateName) {
1463
- const template = templates.get(templateName);
1464
- if (template && template.element) {
1465
- const countSpan = template.element.querySelector('.template-count');
1466
- if (countSpan) {
1467
- if (template.variables && template.variables.length > 0) {
1468
- countSpan.textContent = ` (${template.variables.length})`;
1469
- countSpan.style.color = '#48bb78';
1470
- countSpan.style.fontWeight = '600';
1471
- } else {
1472
- countSpan.textContent = '';
1473
- }
1474
- }
1475
- }
1476
- }
1477
-
1478
- // Detect templates in the expression
1479
- function detectTemplates() {
1480
- const editor = document.getElementById('expressionEditor');
1481
- const content = editor.value;
1482
- const templateList = document.getElementById('templateList');
1483
-
1484
- // Store existing template configurations
1485
- const existingTemplates = new Map(templates);
1486
-
1487
- // Clear previous templates
1488
- templateList.innerHTML = '';
1489
- templates.clear();
1490
-
1491
- // Regular expression to match templates like <variable_name/>
1492
- const templateRegex = /<(\w+)\/>/g;
1493
- const matches = [...content.matchAll(templateRegex)];
1494
-
1495
- // Get unique templates
1496
- const uniqueTemplates = [...new Set(matches.map(match => match[1]))];
1497
-
1498
- if (uniqueTemplates.length === 0) {
1499
- templateList.innerHTML = '<p style="color: #999; font-style: italic;">No templates detected</p>';
1500
- return;
1501
- }
1502
-
1503
- // Display each template
1504
- uniqueTemplates.forEach(templateName => {
1505
- const templateDiv = document.createElement('div');
1506
- templateDiv.className = 'template-item not-configured'; // Add default not-configured class
1507
-
1508
- const nameSpan = document.createElement('span');
1509
- nameSpan.className = 'template-name';
1510
- nameSpan.innerHTML = `<span class="template-brackets">&lt;</span><span class="template-tag">${templateName}</span><span class="template-brackets">/&gt;</span><span class="template-count"></span>`;
1511
- nameSpan.onclick = () => showTemplateConfig(templateName);
1512
- nameSpan.title = 'Click to view current configuration';
1513
-
1514
- // Create container for the three buttons
1515
- const buttonContainer = document.createElement('div');
1516
- buttonContainer.className = 'template-buttons';
1517
-
1518
- // Create Op button
1519
- const opBtn = document.createElement('button');
1520
- opBtn.className = 'btn btn-primary btn-small';
1521
- opBtn.textContent = 'Op';
1522
- opBtn.onclick = () => openTemplateModal(templateName, 'operator');
1523
-
1524
- // Create Data button
1525
- const dataBtn = document.createElement('button');
1526
- dataBtn.className = 'btn btn-secondary btn-small';
1527
- dataBtn.textContent = 'DataField';
1528
- dataBtn.onclick = () => openTemplateModal(templateName, 'data');
1529
-
1530
- // Create Normal button
1531
- const normalBtn = document.createElement('button');
1532
- normalBtn.className = 'btn btn-outline btn-small';
1533
- normalBtn.textContent = 'Other';
1534
- // Use 'other' instead of 'normal' to match the type set in parseTransformerCandidates
1535
- normalBtn.onclick = () => openTemplateModal(templateName, 'other');
1536
-
1537
- buttonContainer.appendChild(opBtn);
1538
- buttonContainer.appendChild(dataBtn);
1539
- buttonContainer.appendChild(normalBtn);
1540
-
1541
- templateDiv.appendChild(nameSpan);
1542
- templateDiv.appendChild(buttonContainer);
1543
- templateList.appendChild(templateDiv);
1544
-
1545
- // Store template info - restore existing config if available
1546
- const existingTemplate = existingTemplates.get(templateName);
1547
- if (existingTemplate && existingTemplate.variables.length > 0) {
1548
- templates.set(templateName, {
1549
- name: templateName,
1550
- variables: existingTemplate.variables,
1551
- element: templateDiv,
1552
- configType: existingTemplate.configType
1553
- });
1554
- // Update visual state
1555
- templateDiv.className = 'template-item configured';
1556
- updateTemplateCount(templateName);
1557
- } else {
1558
- templates.set(templateName, {
1559
- name: templateName,
1560
- variables: [],
1561
- element: templateDiv,
1562
- configType: null
1563
- });
1564
- }
1565
- });
1566
- }
1567
-
1568
- // Open modal for template configuration
1569
- function openTemplateModal(templateName, configType) {
1570
- currentTemplate = templateName;
1571
- currentConfigType = configType; // Store the configuration type
1572
- const modal = document.getElementById('templateModal');
1573
- const modalTitle = document.getElementById('modalTitle');
1574
- const modalDescription = document.getElementById('modalDescription');
1575
- const variableInput = document.getElementById('variableInput');
1576
- const brainChooseSection = document.getElementById('brainChooseSection');
1577
-
1578
- // Update modal content based on configuration type
1579
- let typeDescription = '';
1580
- switch(configType) {
1581
- case 'operator':
1582
- typeDescription = 'operators';
1583
- break;
1584
- case 'data':
1585
- typeDescription = 'data fields';
1586
- break;
1587
- case 'normal':
1588
- case 'other': // Add support for 'other' type
1589
- typeDescription = 'normal parameters (like dates, etc.)';
1590
- break;
1591
- }
1592
-
1593
- modalTitle.textContent = `Configure Template: <${templateName}/> - ${configType.charAt(0).toUpperCase() + configType.slice(1)}`;
1594
- modalDescription.textContent = `Enter a comma-separated list of ${typeDescription} for the ${templateName} template:`;
1595
-
1596
- // Show "Choose from BRAIN" button for operators and data fields if connected to BRAIN
1597
- if ((configType === 'operator' || configType === 'data') && window.brainAPI && window.brainAPI.isConnectedToBrain()) {
1598
- brainChooseSection.style.display = 'block';
1599
- const chooseBrainBtn = document.getElementById('chooseBrainBtn');
1600
- if (configType === 'operator') {
1601
- chooseBrainBtn.textContent = 'Choose Operators from BRAIN';
1602
- chooseBrainBtn.onclick = openBrainOperatorsModal;
1603
- } else if (configType === 'data') {
1604
- chooseBrainBtn.textContent = 'Choose Data Fields from BRAIN';
1605
- chooseBrainBtn.onclick = openBrainDataFieldsModal;
1606
- }
1607
- } else {
1608
- brainChooseSection.style.display = 'none';
1609
- }
1610
-
1611
- // Load existing variables if any
1612
- const template = templates.get(templateName);
1613
- // Allow editing if variables exist, regardless of previous configType (or if it matches)
1614
- // This fixes the issue where 'other' type templates loaded from Transformer couldn't be edited
1615
- // because the modal expects exact type match, but we want to allow editing anyway.
1616
- if (template && template.variables.length > 0) {
1617
- variableInput.value = template.variables.join(', ');
1618
- } else {
1619
- variableInput.value = '';
1620
- }
1621
-
1622
- modal.style.display = 'block';
1623
- variableInput.focus();
1624
- }
1625
-
1626
- // Close modal
1627
- function closeModal() {
1628
- const modal = document.getElementById('templateModal');
1629
- modal.style.display = 'none';
1630
- currentTemplate = null;
1631
- }
1632
-
1633
- // Show current template configuration
1634
- function showTemplateConfig(templateName) {
1635
- const template = templates.get(templateName);
1636
- const modal = document.getElementById('configInfoModal');
1637
- const title = document.getElementById('configInfoTitle');
1638
- const content = document.getElementById('configInfoContent');
1639
-
1640
- title.textContent = `Template: <${templateName}/>`;
1641
-
1642
- if (!template || !template.variables || template.variables.length === 0) {
1643
- content.innerHTML = `
1644
- <div class="config-info-item">
1645
- <strong>Status:</strong> <span class="config-status-unconfigured">Not configured</span><br>
1646
- <strong>Template:</strong> &lt;${templateName}/&gt;<br><br>
1647
- <em>Click one of the configuration buttons (Op, Data, Normal) to set up this template.</em>
1648
- </div>
1649
- `;
1650
- } else {
1651
- const configTypeDisplay = template.configType ?
1652
- template.configType.charAt(0).toUpperCase() + template.configType.slice(1) :
1653
- 'Unknown';
1654
-
1655
- content.innerHTML = `
1656
- <div class="config-info-item">
1657
- <strong>Status:</strong> <span class="config-status-configured">Configured</span><br>
1658
- <strong>Template:</strong> &lt;${templateName}/&gt;<br>
1659
- <strong>Type:</strong> ${configTypeDisplay}<br>
1660
- <strong>Count:</strong> ${template.variables.length} value${template.variables.length > 1 ? 's' : ''}<br>
1661
- <div class="config-info-values">
1662
- <strong>Values:</strong><br>
1663
- ${template.variables.join(', ')}
1664
- </div>
1665
- </div>
1666
- `;
1667
- }
1668
-
1669
- modal.style.display = 'block';
1670
- }
1671
-
1672
- // Close configuration info modal
1673
- function closeConfigInfoModal() {
1674
- const modal = document.getElementById('configInfoModal');
1675
- modal.style.display = 'none';
1676
- }
1677
-
1678
- // Close modal when clicking outside
1679
- window.onclick = function(event) {
1680
- const templateModal = document.getElementById('templateModal');
1681
- const configInfoModal = document.getElementById('configInfoModal');
1682
- const brainLoginModal = document.getElementById('brainLoginModal');
1683
- const brainOperatorsModal = document.getElementById('brainOperatorsModal');
1684
- const brainDataFieldsModal = document.getElementById('brainDataFieldsModal');
1685
- const settingsModal = document.getElementById('settingsModal');
1686
- const saveTemplateModal = document.getElementById('saveTemplateModal');
1687
- const overwriteTemplateModal = document.getElementById('overwriteTemplateModal');
1688
-
1689
- if (event.target === templateModal) {
1690
- closeModal();
1691
- } else if (event.target === configInfoModal) {
1692
- closeConfigInfoModal();
1693
- } else if (event.target === brainLoginModal) {
1694
- // Check if login is in progress
1695
- const loginBtn = document.getElementById('loginBtn');
1696
- if (!loginBtn || !loginBtn.disabled) {
1697
- closeBrainLoginModal();
1698
- }
1699
- } else if (event.target === brainOperatorsModal) {
1700
- closeBrainOperatorsModal();
1701
- } else if (event.target === brainDataFieldsModal) {
1702
- closeBrainDataFieldsModal();
1703
- } else if (event.target === settingsModal) {
1704
- closeSettingsModal();
1705
- } else if (event.target === saveTemplateModal) {
1706
- closeSaveTemplateModal();
1707
- } else if (event.target === overwriteTemplateModal) {
1708
- closeOverwriteTemplateModal();
1709
- }
1710
- }
1711
-
1712
- // Apply template variables
1713
- function applyTemplate() {
1714
- const variableInput = document.getElementById('variableInput');
1715
-
1716
- // Special handling for bucket() functions to avoid splitting on commas inside them
1717
- const variables = parseVariablesWithBucketSupport(variableInput.value);
1718
-
1719
- if (variables.length === 0) {
1720
- alert('Please enter at least one variable');
1721
- return;
1722
- }
1723
-
1724
- // Store variables for the template
1725
- const template = templates.get(currentTemplate);
1726
- if (template) {
1727
- template.variables = variables;
1728
- template.configType = currentConfigType; // Store the configuration type
1729
- // Update the visual indicator
1730
- if (template.element) {
1731
- template.element.className = 'template-item configured';
1732
- }
1733
- // Update the count display
1734
- updateTemplateCount(currentTemplate);
1735
- }
1736
-
1737
- // Close the modal
1738
- closeModal();
1739
-
1740
- // Show success message
1741
- const errorsDiv = document.getElementById('grammarErrors');
1742
- errorsDiv.innerHTML = `<div class="success-message">✓ Template <${currentTemplate}/> configured as ${currentConfigType} with ${variables.length} variable${variables.length > 1 ? 's' : ''}</div>`;
1743
- }
1744
-
1745
- // Parse variables with special support for bucket() functions
1746
- function parseVariablesWithBucketSupport(input) {
1747
- const variables = [];
1748
- let currentVariable = '';
1749
- let i = 0;
1750
-
1751
- while (i < input.length) {
1752
- const char = input[i];
1753
-
1754
- // Check if we're starting a bucket function
1755
- if (char === 'b' && i + 7 <= input.length && input.substring(i, i + 7) === 'bucket(') {
1756
- // Add any previous variable before the bucket function
1757
- const trimmed = currentVariable.trim();
1758
- if (trimmed !== '') {
1759
- variables.push(trimmed);
1760
- }
1761
-
1762
- // Find the complete bucket function
1763
- const bucketFunction = extractBucketFunction(input, i);
1764
- if (bucketFunction) {
1765
- // Add the complete bucket function
1766
- variables.push(bucketFunction.function);
1767
- currentVariable = '';
1768
- i = bucketFunction.endIndex + 1; // Move past the bucket function
1769
- continue;
1770
- } else {
1771
- currentVariable += char;
1772
- }
1773
- }
1774
-
1775
- // Regular comma handling
1776
- if (char === ',') {
1777
- const trimmed = currentVariable.trim();
1778
- if (trimmed !== '') {
1779
- variables.push(trimmed);
1780
- }
1781
- currentVariable = '';
1782
- } else {
1783
- currentVariable += char;
1784
- }
1785
-
1786
- i++;
1787
- }
1788
-
1789
- // Add the last variable if there is one
1790
- const trimmed = currentVariable.trim();
1791
- if (trimmed !== '') {
1792
- variables.push(trimmed);
1793
- }
1794
-
1795
- return variables;
1796
- }
1797
-
1798
- // Extract complete bucket function including all nested content
1799
- function extractBucketFunction(input, startIndex) {
1800
- let parenthesesCount = 0;
1801
- let inQuotes = false;
1802
- let quoteChar = null;
1803
- let i = startIndex;
1804
-
1805
- // Find the start of bucket(
1806
- if (input.substring(i, i + 7) !== 'bucket(') {
1807
- return null;
1808
- }
1809
-
1810
- parenthesesCount = 1;
1811
- i += 7; // Move past "bucket("
1812
-
1813
- while (i < input.length) {
1814
- const char = input[i];
1815
-
1816
- // Handle quotes
1817
- if ((char === '"' || char === "'") && !inQuotes) {
1818
- inQuotes = true;
1819
- quoteChar = char;
1820
- } else if (char === quoteChar && inQuotes) {
1821
- inQuotes = false;
1822
- quoteChar = null;
1823
- }
1824
-
1825
- // Only count parentheses when not inside quotes
1826
- if (!inQuotes) {
1827
- if (char === '(') {
1828
- parenthesesCount++;
1829
- } else if (char === ')') {
1830
- parenthesesCount--;
1831
- if (parenthesesCount === 0) {
1832
- // Found the end of bucket function
1833
- const functionText = input.substring(startIndex, i + 1);
1834
- return {
1835
- function: functionText,
1836
- endIndex: i
1837
- };
1838
- }
1839
- }
1840
- }
1841
-
1842
- i++;
1843
- }
1844
-
1845
- return null; // No matching closing parenthesis found
1846
- }
1847
-
1848
- // Clear editor
1849
- function clearEditor() {
1850
- const editor = document.getElementById('expressionEditor');
1851
- editor.value = '';
1852
- currentTransformerExplanation = ''; // Clear stored explanation
1853
- updateLineNumbers();
1854
- updateSyntaxHighlight();
1855
- document.getElementById('grammarErrors').innerHTML = '';
1856
- document.getElementById('decodedResults').style.display = 'none';
1857
- detectTemplates();
1858
- }
1859
-
1860
- // Auto-completion functionality
1861
- let autoCompleteActive = false;
1862
- let autoCompletePosition = null;
1863
- let shadowSuggestion = null;
1864
-
1865
- function handleAutoComplete(event) {
1866
- const editor = event.target;
1867
- const cursorPos = editor.selectionStart;
1868
- const text = editor.value;
1869
- const lastChar = text[cursorPos - 1];
1870
- const prevChar = cursorPos > 1 ? text[cursorPos - 2] : '';
1871
-
1872
- // If user typed '<', show shadow suggestion
1873
- if (lastChar === '<' && event.inputType === 'insertText') {
1874
- // Show shadow suggestion for template
1875
- showShadowSuggestion(editor, cursorPos, 'variable_name/>');
1876
- autoCompleteActive = true;
1877
- autoCompletePosition = cursorPos;
1878
- }
1879
- // If user typed '/', check if it's after '<'
1880
- else if (lastChar === '/' && prevChar === '<') {
1881
- // Auto-complete the closing '>'
1882
- const before = text.substring(0, cursorPos);
1883
- const after = text.substring(cursorPos);
1884
- editor.value = before + '>' + after;
1885
- editor.setSelectionRange(cursorPos, cursorPos);
1886
-
1887
- // Update shadow to show between < and />
1888
- hideShadowSuggestion();
1889
- autoCompleteActive = false;
1890
- }
1891
- // If user typed something after '<' that's not '/', hide suggestion
1892
- else if (prevChar === '<' && lastChar !== '/' && autoCompleteActive) {
1893
- // User is typing something else after '<', like a comparison
1894
- hideShadowSuggestion();
1895
- autoCompleteActive = false;
1896
- }
1897
- else {
1898
- // Check if we should hide suggestion for other cases
1899
- if (!autoCompleteActive || (autoCompletePosition && cursorPos > autoCompletePosition + 1)) {
1900
- hideShadowSuggestion();
1901
- autoCompleteActive = false;
1902
- }
1903
- }
1904
- }
1905
-
1906
- function handleTabCompletion() {
1907
- const editor = document.getElementById('expressionEditor');
1908
- const cursorPos = editor.selectionStart;
1909
- const text = editor.value;
1910
-
1911
- if (autoCompleteActive && shadowSuggestion) {
1912
- // Check if we're right after '<'
1913
- if (cursorPos > 0 && text[cursorPos - 1] === '<') {
1914
- // Complete the template
1915
- const before = text.substring(0, cursorPos);
1916
- const after = text.substring(cursorPos);
1917
- editor.value = before + '/>' + after;
1918
- editor.setSelectionRange(cursorPos, cursorPos);
1919
-
1920
- hideShadowSuggestion();
1921
- autoCompleteActive = false;
1922
-
1923
- // Trigger input event to update everything
1924
- const inputEvent = new Event('input', { bubbles: true });
1925
- editor.dispatchEvent(inputEvent);
1926
-
1927
- // Update syntax highlighting immediately
1928
- updateSyntaxHighlight();
1929
- }
1930
- }
1931
- }
1932
-
1933
- function showShadowSuggestion(editor, position, suggestion) {
1934
- // Remove any existing shadow
1935
- hideShadowSuggestion();
1936
-
1937
- // Create shadow element
1938
- shadowSuggestion = document.createElement('div');
1939
- shadowSuggestion.className = 'shadow-suggestion';
1940
- shadowSuggestion.textContent = suggestion;
1941
-
1942
- // Get editor wrapper for relative positioning
1943
- const editorWrapper = editor.closest('.editor-wrapper');
1944
- const editorRect = editor.getBoundingClientRect();
1945
-
1946
- // Calculate position based on character position
1947
- const lineHeight = parseInt(window.getComputedStyle(editor).lineHeight);
1948
- const lines = editor.value.substring(0, position).split('\n');
1949
- const currentLine = lines.length;
1950
- const currentCol = lines[lines.length - 1].length;
1951
-
1952
- // Approximate character width (monospace font)
1953
- const charWidth = 9.6; // Approximate width for 16px monospace font
1954
-
1955
- // Position shadow relative to editor
1956
- shadowSuggestion.style.position = 'fixed';
1957
- shadowSuggestion.style.left = (editorRect.left + 15 + (currentCol * charWidth)) + 'px';
1958
- shadowSuggestion.style.top = (editorRect.top + 12 + ((currentLine - 1) * lineHeight) - editor.scrollTop) + 'px';
1959
- shadowSuggestion.style.pointerEvents = 'none';
1960
- shadowSuggestion.style.zIndex = '1000';
1961
-
1962
- // Add hint text below
1963
- const hintText = document.createElement('div');
1964
- hintText.className = 'shadow-hint';
1965
- hintText.textContent = 'Tab to complete template';
1966
- shadowSuggestion.appendChild(hintText);
1967
-
1968
- document.body.appendChild(shadowSuggestion);
1969
- }
1970
-
1971
- function hideShadowSuggestion() {
1972
- if (shadowSuggestion) {
1973
- shadowSuggestion.remove();
1974
- shadowSuggestion = null;
1975
- }
1976
- }
1977
-
1978
- // BRAIN Operators Modal Functions
1979
- let selectedOperators = new Set();
1980
-
1981
- function openBrainOperatorsModal() {
1982
- const modal = document.getElementById('brainOperatorsModal');
1983
- selectedOperators.clear();
1984
-
1985
- // Populate categories
1986
- populateOperatorCategories();
1987
-
1988
- // Populate operators list
1989
- populateOperatorsList();
1990
-
1991
- // Set up event listeners
1992
- setupOperatorsModalEventListeners();
1993
-
1994
- modal.style.display = 'block';
1995
- }
1996
-
1997
- function closeBrainOperatorsModal() {
1998
- const modal = document.getElementById('brainOperatorsModal');
1999
- modal.style.display = 'none';
2000
- selectedOperators.clear();
2001
- updateSelectedOperatorsDisplay();
2002
- }
2003
-
2004
- function populateOperatorCategories() {
2005
- const categoryFilter = document.getElementById('categoryFilter');
2006
- const operators = window.brainAPI ? window.brainAPI.getLoadedOperators() : [];
2007
-
2008
- // Clear existing options except "All Categories"
2009
- categoryFilter.innerHTML = '<option value="">All Categories</option>';
2010
-
2011
- // Get unique categories
2012
- const categories = [...new Set(operators.map(op => op.category))].sort();
2013
-
2014
- categories.forEach(category => {
2015
- const option = document.createElement('option');
2016
- option.value = category;
2017
- option.textContent = category;
2018
- categoryFilter.appendChild(option);
2019
- });
2020
- }
2021
-
2022
- function populateOperatorsList(searchTerm = '', categoryFilter = '') {
2023
- const operatorsList = document.getElementById('operatorsList');
2024
- const operators = window.brainAPI ? window.brainAPI.getLoadedOperators() : [];
2025
-
2026
- // Filter operators
2027
- let filteredOperators = operators;
2028
-
2029
- if (searchTerm) {
2030
- const term = searchTerm.toLowerCase();
2031
- filteredOperators = filteredOperators.filter(op =>
2032
- op.name.toLowerCase().includes(term) ||
2033
- op.category.toLowerCase().includes(term)
2034
- );
2035
- }
2036
-
2037
- if (categoryFilter) {
2038
- filteredOperators = filteredOperators.filter(op => op.category === categoryFilter);
2039
- }
2040
-
2041
- // Clear list
2042
- operatorsList.innerHTML = '';
2043
-
2044
- if (filteredOperators.length === 0) {
2045
- operatorsList.innerHTML = '<p style="text-align: center; color: #666;">No operators found</p>';
2046
- return;
2047
- }
2048
-
2049
- // Create operator items
2050
- filteredOperators.forEach(operator => {
2051
- const item = document.createElement('div');
2052
- item.className = 'operator-item';
2053
- item.dataset.operatorName = operator.name;
2054
-
2055
- // Build tooltip content if description or definition is available
2056
- let tooltipContent = '';
2057
- if (operator.description) {
2058
- tooltipContent += `Description: ${operator.description}`;
2059
- }
2060
- if (operator.definition) {
2061
- tooltipContent += tooltipContent ? `\n\nDefinition: ${operator.definition}` : `Definition: ${operator.definition}`;
2062
- }
2063
- if (operator.example) {
2064
- tooltipContent += tooltipContent ? `\n\nExample: ${operator.example}` : `Example: ${operator.example}`;
2065
- }
2066
- if (operator.usageCount !== undefined) {
2067
- tooltipContent += tooltipContent ? `\n\nUsage Count: ${operator.usageCount}` : `Usage Count: ${operator.usageCount}`;
2068
- }
2069
-
2070
- // Add custom tooltip if we have content
2071
- if (tooltipContent) {
2072
- item.dataset.tooltip = tooltipContent;
2073
- item.style.cursor = 'help';
2074
-
2075
- // Add mouse event listeners for custom tooltip
2076
- item.addEventListener('mouseenter', showCustomTooltip);
2077
- item.addEventListener('mouseleave', hideCustomTooltip);
2078
- item.addEventListener('mousemove', moveCustomTooltip);
2079
- }
2080
-
2081
- // Create description indicator if description or definition is available
2082
- const descriptionIndicator = (operator.description || operator.definition) ?
2083
- '<span class="description-indicator" title="Has description/definition">📖</span>' : '';
2084
-
2085
- item.innerHTML = `
2086
- <input type="checkbox" class="operator-checkbox" ${selectedOperators.has(operator.name) ? 'checked' : ''}>
2087
- <div class="operator-info">
2088
- <span class="operator-name">${operator.name} ${descriptionIndicator}</span>
2089
- <span class="operator-category">${operator.category}</span>
2090
- </div>
2091
- `;
2092
-
2093
- item.onclick = () => toggleOperatorSelection(operator.name, item);
2094
- operatorsList.appendChild(item);
2095
- });
2096
- }
2097
-
2098
- function toggleOperatorSelection(operatorName, item) {
2099
- const checkbox = item.querySelector('.operator-checkbox');
2100
-
2101
- if (selectedOperators.has(operatorName)) {
2102
- selectedOperators.delete(operatorName);
2103
- checkbox.checked = false;
2104
- item.classList.remove('selected');
2105
- } else {
2106
- selectedOperators.add(operatorName);
2107
- checkbox.checked = true;
2108
- item.classList.add('selected');
2109
- }
2110
-
2111
- updateSelectedOperatorsDisplay();
2112
- }
2113
-
2114
- function updateSelectedOperatorsDisplay() {
2115
- const selectedContainer = document.getElementById('selectedOperators');
2116
-
2117
- selectedContainer.innerHTML = '';
2118
-
2119
- if (selectedOperators.size === 0) {
2120
- selectedContainer.innerHTML = '<em style="color: #666;">No operators selected</em>';
2121
- return;
2122
- }
2123
-
2124
- selectedOperators.forEach(operatorName => {
2125
- const item = document.createElement('span');
2126
- item.className = 'selected-item';
2127
- item.innerHTML = `
2128
- ${operatorName}
2129
- <button class="remove-btn" onclick="removeSelectedOperator('${operatorName}')">&times;</button>
2130
- `;
2131
- selectedContainer.appendChild(item);
2132
- });
2133
- }
2134
-
2135
- function removeSelectedOperator(operatorName) {
2136
- selectedOperators.delete(operatorName);
2137
- updateSelectedOperatorsDisplay();
2138
-
2139
- // Update the checkbox in the list
2140
- const operatorItem = document.querySelector(`[data-operator-name="${operatorName}"]`);
2141
- if (operatorItem) {
2142
- const checkbox = operatorItem.querySelector('.operator-checkbox');
2143
- checkbox.checked = false;
2144
- operatorItem.classList.remove('selected');
2145
- }
2146
- }
2147
-
2148
- function setupOperatorsModalEventListeners() {
2149
- const searchInput = document.getElementById('operatorSearch');
2150
- const categoryFilter = document.getElementById('categoryFilter');
2151
- const selectAllBtn = document.getElementById('selectAllFilteredOperators');
2152
- const clearAllBtn = document.getElementById('clearAllOperators');
2153
-
2154
- searchInput.oninput = () => {
2155
- populateOperatorsList(searchInput.value, categoryFilter.value);
2156
- };
2157
-
2158
- categoryFilter.onchange = () => {
2159
- populateOperatorsList(searchInput.value, categoryFilter.value);
2160
- };
2161
-
2162
- selectAllBtn.onclick = selectAllFilteredOperators;
2163
- clearAllBtn.onclick = clearAllOperators;
2164
- }
2165
-
2166
- function selectAllFilteredOperators() {
2167
- const operatorItems = document.querySelectorAll('.operator-item');
2168
- operatorItems.forEach(item => {
2169
- const operatorName = item.dataset.operatorName;
2170
- if (!selectedOperators.has(operatorName)) {
2171
- selectedOperators.add(operatorName);
2172
- const checkbox = item.querySelector('.operator-checkbox');
2173
- checkbox.checked = true;
2174
- item.classList.add('selected');
2175
- }
2176
- });
2177
- updateSelectedOperatorsDisplay();
2178
- }
2179
-
2180
- function clearAllOperators() {
2181
- selectedOperators.clear();
2182
-
2183
- // Update all checkboxes
2184
- document.querySelectorAll('.operator-item').forEach(item => {
2185
- const checkbox = item.querySelector('.operator-checkbox');
2186
- checkbox.checked = false;
2187
- item.classList.remove('selected');
2188
- });
2189
-
2190
- updateSelectedOperatorsDisplay();
2191
- }
2192
-
2193
- function applySelectedOperators() {
2194
- if (selectedOperators.size === 0) {
2195
- alert('Please select at least one operator');
2196
- return;
2197
- }
2198
-
2199
- // Add selected operators to the variable input
2200
- const variableInput = document.getElementById('variableInput');
2201
- const currentValues = variableInput.value.trim();
2202
- const newValues = Array.from(selectedOperators);
2203
-
2204
- if (currentValues) {
2205
- variableInput.value = currentValues + ', ' + newValues.join(', ');
2206
- } else {
2207
- variableInput.value = newValues.join(', ');
2208
- }
2209
-
2210
- closeBrainOperatorsModal();
2211
- }
2212
-
2213
- // BRAIN Data Fields Modal Functions
2214
- let selectedDataFields = new Set();
2215
- let currentDataFields = [];
2216
- let filteredDataFields = [];
2217
- let columnFilters = {
2218
- id: '',
2219
- description: '',
2220
- type: '',
2221
- coverage: { min: null, max: null },
2222
- userCount: null,
2223
- alphaCount: null
2224
- };
2225
- let sortColumn = null;
2226
- let sortOrder = 'asc';
2227
-
2228
- function openBrainDataFieldsModal() {
2229
- const modal = document.getElementById('brainDataFieldsModal');
2230
- selectedDataFields.clear();
2231
- currentDataFields = [];
2232
- filteredDataFields = [];
2233
-
2234
- // Reset column filters
2235
- columnFilters = {
2236
- id: '',
2237
- description: '',
2238
- type: '',
2239
- coverage: { min: null, max: null },
2240
- userCount: null,
2241
- alphaCount: null
2242
- };
2243
- sortColumn = null;
2244
- sortOrder = 'asc';
2245
-
2246
- // Reset UI state
2247
- document.getElementById('dataFieldsContent').style.display = 'none';
2248
- document.getElementById('dataFieldsLoading').style.display = 'none';
2249
-
2250
- // Clear column filter inputs
2251
- document.querySelectorAll('.column-filter').forEach(filter => {
2252
- filter.value = '';
2253
- });
2254
- document.querySelectorAll('.column-filter-min, .column-filter-max').forEach(filter => {
2255
- filter.value = '';
2256
- });
2257
-
2258
- // Reset sort buttons
2259
- document.querySelectorAll('.sort-btn').forEach(btn => {
2260
- btn.classList.remove('asc', 'desc');
2261
- btn.dataset.order = 'asc';
2262
- });
2263
-
2264
- // Set up event listeners
2265
- setupDataFieldsModalEventListeners();
2266
-
2267
- modal.style.display = 'block';
2268
-
2269
- // Auto-load datasets if connected
2270
- if (window.brainAPI && window.brainAPI.isConnectedToBrain()) {
2271
- loadDatasets();
2272
- loadSimulationOptions();
2273
- }
2274
- }
2275
-
2276
- let brainSimulationOptions = null;
2277
-
2278
- async function loadSimulationOptions() {
2279
- if (brainSimulationOptions) return; // Already loaded
2280
-
2281
- try {
2282
- if (!window.brainAPI || !window.brainAPI.isConnectedToBrain()) {
2283
- return;
2284
- }
2285
-
2286
- brainSimulationOptions = await window.brainAPI.getSimulationOptions();
2287
-
2288
- // Populate Region Select
2289
- const regionSelect = document.getElementById('regionSelect');
2290
- const instrumentType = 'EQUITY'; // Default
2291
-
2292
- if (brainSimulationOptions && brainSimulationOptions[instrumentType]) {
2293
- const regions = Object.keys(brainSimulationOptions[instrumentType]);
2294
-
2295
- regionSelect.innerHTML = '<option value="">Select...</option>';
2296
- regions.forEach(r => {
2297
- const option = document.createElement('option');
2298
- option.value = r;
2299
- option.textContent = r;
2300
- regionSelect.appendChild(option);
2301
- });
2302
-
2303
- regionSelect.style.display = 'block';
2304
-
2305
- // Trigger updates if values are already set
2306
- updateDelayOptions();
2307
- }
2308
-
2309
- } catch (error) {
2310
- console.error('Failed to load simulation options:', error);
2311
- }
2312
- }
2313
-
2314
- function updateDelayOptions() {
2315
- const region = document.getElementById('regionInput').value;
2316
- const delaySelect = document.getElementById('delaySelect');
2317
- const instrumentType = 'EQUITY';
2318
-
2319
- if (!brainSimulationOptions || !brainSimulationOptions[instrumentType] || !brainSimulationOptions[instrumentType][region]) {
2320
- delaySelect.style.display = 'none';
2321
- return;
2322
- }
2323
-
2324
- // The structure is brainSimulationOptions[instrumentType][region] = { delays: [], universes: [], ... }
2325
- const delays = brainSimulationOptions[instrumentType][region].delays || [];
2326
-
2327
- delaySelect.innerHTML = '<option value="">Select...</option>';
2328
- delays.forEach(d => {
2329
- const option = document.createElement('option');
2330
- option.value = d;
2331
- option.textContent = d;
2332
- delaySelect.appendChild(option);
2333
- });
2334
-
2335
- delaySelect.style.display = 'block';
2336
- updateUniverseOptions();
2337
- }
2338
-
2339
- function updateUniverseOptions() {
2340
- const region = document.getElementById('regionInput').value;
2341
- const universeSelect = document.getElementById('universeSelect');
2342
- const instrumentType = 'EQUITY';
2343
-
2344
- if (!brainSimulationOptions || !brainSimulationOptions[instrumentType] || !brainSimulationOptions[instrumentType][region]) {
2345
- universeSelect.style.display = 'none';
2346
- return;
2347
- }
2348
-
2349
- // Universe depends on Region, not Delay in this data structure
2350
- const universes = brainSimulationOptions[instrumentType][region].universes || [];
2351
-
2352
- universeSelect.innerHTML = '<option value="">Select...</option>';
2353
- universes.forEach(u => {
2354
- const option = document.createElement('option');
2355
- option.value = u;
2356
- option.textContent = u;
2357
- universeSelect.appendChild(option);
2358
- });
2359
-
2360
- universeSelect.style.display = 'block';
2361
- }
2362
-
2363
- function closeBrainDataFieldsModal() {
2364
- const modal = document.getElementById('brainDataFieldsModal');
2365
- modal.style.display = 'none';
2366
- selectedDataFields.clear();
2367
- updateSelectedDataFieldsDisplay();
2368
- }
2369
-
2370
- async function loadDatasets() {
2371
- const region = document.getElementById('regionInput').value;
2372
- const delay = document.getElementById('delayInput').value;
2373
- const universe = document.getElementById('universeInput').value;
2374
- const select = document.getElementById('datasetSelect');
2375
- const btn = document.getElementById('loadDatasetsBtn');
2376
-
2377
- try {
2378
- const originalText = btn.textContent;
2379
- btn.disabled = true;
2380
- btn.textContent = 'Loading...';
2381
-
2382
- if (!window.brainAPI || !window.brainAPI.isConnectedToBrain()) {
2383
- throw new Error('Not connected to BRAIN');
2384
- }
2385
-
2386
- const datasets = await window.brainAPI.getDatasets(region, parseInt(delay), universe);
2387
-
2388
- // Clear and populate select
2389
- select.innerHTML = '<option value="">Select dataset...</option>';
2390
-
2391
- // Sort datasets by ID
2392
- datasets.sort((a, b) => a.id.localeCompare(b.id));
2393
-
2394
- datasets.forEach(ds => {
2395
- const option = document.createElement('option');
2396
- option.value = ds.id;
2397
- option.textContent = `${ds.id} - ${ds.description || ds.name || ''}`;
2398
- option.title = ds.description || ds.name || '';
2399
- select.appendChild(option);
2400
- });
2401
-
2402
- select.style.display = 'block';
2403
- btn.textContent = 'Reload List';
2404
-
2405
- } catch (error) {
2406
- alert(`Failed to load datasets: ${error.message}`);
2407
- btn.textContent = 'Load List';
2408
- } finally {
2409
- btn.disabled = false;
2410
- }
2411
- }
2412
-
2413
- async function loadDataFields() {
2414
- const region = document.getElementById('regionInput').value;
2415
- const delay = document.getElementById('delayInput').value;
2416
- const universe = document.getElementById('universeInput').value;
2417
- const datasetId = document.getElementById('datasetInput').value;
2418
-
2419
- const loadingDiv = document.getElementById('dataFieldsLoading');
2420
- const contentDiv = document.getElementById('dataFieldsContent');
2421
-
2422
- try {
2423
- loadingDiv.style.display = 'block';
2424
- contentDiv.style.display = 'none';
2425
-
2426
- // Fetch data fields using the brain API
2427
- if (!window.brainAPI || !window.brainAPI.isConnectedToBrain()) {
2428
- throw new Error('Not connected to BRAIN');
2429
- }
2430
-
2431
- const dataFields = await window.brainAPI.getDataFields(region, parseInt(delay), universe, datasetId);
2432
- currentDataFields = dataFields;
2433
- filteredDataFields = [...dataFields];
2434
-
2435
- populateDataFieldsList();
2436
- updateDataFieldsStats();
2437
- populateTypeFilter();
2438
-
2439
- loadingDiv.style.display = 'none';
2440
- contentDiv.style.display = 'block';
2441
-
2442
- } catch (error) {
2443
- loadingDiv.style.display = 'none';
2444
- alert(`Failed to load data fields: ${error.message}`);
2445
- }
2446
- }
2447
-
2448
- function populateDataFieldsList() {
2449
- const tableBody = document.getElementById('dataFieldsTableBody');
2450
- const highCoverageFilter = document.getElementById('filterHighCoverage').checked;
2451
- const popularFilter = document.getElementById('filterPopular').checked;
2452
- const matrixOnlyFilter = document.getElementById('filterMatrixOnly').checked;
2453
-
2454
- // Apply filters
2455
- filteredDataFields = currentDataFields.filter(field => {
2456
- // Column-specific filters
2457
- // ID filter
2458
- if (columnFilters.id && !field.id.toLowerCase().includes(columnFilters.id.toLowerCase())) {
2459
- return false;
2460
- }
2461
-
2462
- // Description filter
2463
- if (columnFilters.description && !field.description.toLowerCase().includes(columnFilters.description.toLowerCase())) {
2464
- return false;
2465
- }
2466
-
2467
- // Type filter
2468
- if (columnFilters.type && field.type !== columnFilters.type) {
2469
- return false;
2470
- }
2471
-
2472
- // Coverage range filter
2473
- if (columnFilters.coverage.min !== null && field.coverage * 100 < columnFilters.coverage.min) {
2474
- return false;
2475
- }
2476
- if (columnFilters.coverage.max !== null && field.coverage * 100 > columnFilters.coverage.max) {
2477
- return false;
2478
- }
2479
-
2480
- // User count filter
2481
- if (columnFilters.userCount !== null && field.userCount < columnFilters.userCount) {
2482
- return false;
2483
- }
2484
-
2485
- // Alpha count filter
2486
- if (columnFilters.alphaCount !== null && field.alphaCount < columnFilters.alphaCount) {
2487
- return false;
2488
- }
2489
-
2490
- // High coverage filter
2491
- if (highCoverageFilter && field.coverage < 0.9) {
2492
- return false;
2493
- }
2494
-
2495
- // Popular filter
2496
- if (popularFilter && field.userCount < 1000) {
2497
- return false;
2498
- }
2499
-
2500
- // Matrix type filter
2501
- if (matrixOnlyFilter && field.type !== 'MATRIX') {
2502
- return false;
2503
- }
2504
-
2505
- return true;
2506
- });
2507
-
2508
- // Sort filtered data fields
2509
- if (sortColumn) {
2510
- filteredDataFields.sort((a, b) => {
2511
- let aVal = a[sortColumn];
2512
- let bVal = b[sortColumn];
2513
-
2514
- // Handle numeric values
2515
- if (sortColumn === 'coverage' || sortColumn === 'userCount' || sortColumn === 'alphaCount') {
2516
- aVal = Number(aVal);
2517
- bVal = Number(bVal);
2518
- } else {
2519
- // String comparison
2520
- aVal = String(aVal).toLowerCase();
2521
- bVal = String(bVal).toLowerCase();
2522
- }
2523
-
2524
- if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1;
2525
- if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1;
2526
- return 0;
2527
- });
2528
- }
2529
-
2530
- // Clear table
2531
- tableBody.innerHTML = '';
2532
-
2533
- if (filteredDataFields.length === 0) {
2534
- tableBody.innerHTML = '<tr><td colspan="7" style="text-align: center; color: #666; padding: 40px;">No data fields found matching the filters</td></tr>';
2535
- updateDataFieldsStats();
2536
- return;
2537
- }
2538
-
2539
- // Create table rows
2540
- filteredDataFields.forEach(field => {
2541
- const row = document.createElement('tr');
2542
- row.dataset.fieldId = field.id;
2543
- if (selectedDataFields.has(field.id)) {
2544
- row.classList.add('selected');
2545
- }
2546
-
2547
- row.innerHTML = `
2548
- <td>
2549
- <input type="checkbox" class="data-field-checkbox" ${selectedDataFields.has(field.id) ? 'checked' : ''}>
2550
- </td>
2551
- <td><span class="data-field-id">${field.id}</span></td>
2552
- <td><span class="data-field-description">${field.description}</span></td>
2553
- <td><span class="data-field-type">${field.type}</span></td>
2554
- <td><span class="data-field-coverage">${(field.coverage * 100).toFixed(1)}%</span></td>
2555
- <td><span class="data-field-count">${field.userCount.toLocaleString()}</span></td>
2556
- <td><span class="data-field-count">${field.alphaCount.toLocaleString()}</span></td>
2557
- `;
2558
-
2559
- row.onclick = (e) => {
2560
- if (e.target.type !== 'checkbox') {
2561
- toggleDataFieldSelection(field.id, row);
2562
- }
2563
- };
2564
-
2565
- const checkbox = row.querySelector('.data-field-checkbox');
2566
- checkbox.onclick = (e) => {
2567
- e.stopPropagation();
2568
- toggleDataFieldSelection(field.id, row);
2569
- };
2570
-
2571
- tableBody.appendChild(row);
2572
- });
2573
-
2574
- updateDataFieldsStats();
2575
- }
2576
-
2577
- function toggleDataFieldSelection(fieldId, row) {
2578
- const checkbox = row.querySelector('.data-field-checkbox');
2579
-
2580
- if (selectedDataFields.has(fieldId)) {
2581
- selectedDataFields.delete(fieldId);
2582
- checkbox.checked = false;
2583
- row.classList.remove('selected');
2584
- } else {
2585
- selectedDataFields.add(fieldId);
2586
- checkbox.checked = true;
2587
- row.classList.add('selected');
2588
- }
2589
-
2590
- updateSelectedDataFieldsDisplay();
2591
- updateDataFieldsStats();
2592
- updateSelectAllCheckbox();
2593
- }
2594
-
2595
- function updateSelectedDataFieldsDisplay() {
2596
- const selectedContainer = document.getElementById('selectedDataFields');
2597
-
2598
- selectedContainer.innerHTML = '';
2599
-
2600
- if (selectedDataFields.size === 0) {
2601
- selectedContainer.innerHTML = '<em style="color: #666;">No data fields selected</em>';
2602
- return;
2603
- }
2604
-
2605
- selectedDataFields.forEach(fieldId => {
2606
- const item = document.createElement('span');
2607
- item.className = 'selected-item';
2608
- item.innerHTML = `
2609
- ${fieldId}
2610
- <button class="remove-btn" onclick="removeSelectedDataField('${fieldId}')">&times;</button>
2611
- `;
2612
- selectedContainer.appendChild(item);
2613
- });
2614
- }
2615
-
2616
- function removeSelectedDataField(fieldId) {
2617
- selectedDataFields.delete(fieldId);
2618
- updateSelectedDataFieldsDisplay();
2619
- updateDataFieldsStats();
2620
-
2621
- // Update the checkbox in the table
2622
- const row = document.querySelector(`tr[data-field-id="${fieldId}"]`);
2623
- if (row) {
2624
- const checkbox = row.querySelector('.data-field-checkbox');
2625
- checkbox.checked = false;
2626
- row.classList.remove('selected');
2627
- }
2628
-
2629
- updateSelectAllCheckbox();
2630
- }
2631
-
2632
- function updateDataFieldsStats() {
2633
- document.getElementById('dataFieldsCount').textContent = `${currentDataFields.length} fields loaded`;
2634
- document.getElementById('filteredCount').textContent = `${filteredDataFields.length} filtered`;
2635
- document.getElementById('selectedCount').textContent = `${selectedDataFields.size} selected`;
2636
- }
2637
-
2638
- function populateTypeFilter() {
2639
- const typeFilter = document.getElementById('typeFilter');
2640
- if (!typeFilter) return;
2641
-
2642
- // Get unique types from current data fields
2643
- const uniqueTypes = [...new Set(currentDataFields.map(field => field.type))].sort();
2644
-
2645
- // Clear existing options except "All Types"
2646
- typeFilter.innerHTML = '<option value="">All Types</option>';
2647
-
2648
- // Add unique types as options
2649
- uniqueTypes.forEach(type => {
2650
- const option = document.createElement('option');
2651
- option.value = type;
2652
- option.textContent = type;
2653
- typeFilter.appendChild(option);
2654
- });
2655
-
2656
- // Restore selected value if it exists
2657
- if (columnFilters.type && uniqueTypes.includes(columnFilters.type)) {
2658
- typeFilter.value = columnFilters.type;
2659
- }
2660
- }
2661
-
2662
- function selectAllFilteredDataFields() {
2663
- filteredDataFields.forEach(field => {
2664
- selectedDataFields.add(field.id);
2665
- const row = document.querySelector(`tr[data-field-id="${field.id}"]`);
2666
- if (row) {
2667
- const checkbox = row.querySelector('.data-field-checkbox');
2668
- checkbox.checked = true;
2669
- row.classList.add('selected');
2670
- }
2671
- });
2672
-
2673
- updateSelectedDataFieldsDisplay();
2674
- updateDataFieldsStats();
2675
- updateSelectAllCheckbox();
2676
- }
2677
-
2678
- function clearAllSelectedDataFields() {
2679
- selectedDataFields.clear();
2680
-
2681
- // Update all checkboxes
2682
- document.querySelectorAll('.data-field-checkbox').forEach(checkbox => {
2683
- checkbox.checked = false;
2684
- checkbox.closest('tr').classList.remove('selected');
2685
- });
2686
-
2687
- updateSelectedDataFieldsDisplay();
2688
- updateDataFieldsStats();
2689
- updateSelectAllCheckbox();
2690
- }
2691
-
2692
- function setupDataFieldsModalEventListeners() {
2693
- const loadBtn = document.getElementById('loadDataFieldsBtn');
2694
- const selectAllBtn = document.getElementById('selectAllFiltered');
2695
- const clearAllBtn = document.getElementById('clearAllSelected');
2696
- const selectAllCheckbox = document.getElementById('selectAllCheckbox');
2697
-
2698
- // Filter checkboxes
2699
- const highCoverageFilter = document.getElementById('filterHighCoverage');
2700
- const popularFilter = document.getElementById('filterPopular');
2701
- const matrixOnlyFilter = document.getElementById('filterMatrixOnly');
2702
-
2703
- loadBtn.onclick = loadDataFields;
2704
-
2705
- // Input listeners for cascading options
2706
- document.getElementById('regionInput').addEventListener('input', updateDelayOptions);
2707
- document.getElementById('delayInput').addEventListener('input', updateUniverseOptions);
2708
-
2709
- // Filter checkbox listeners
2710
- highCoverageFilter.onchange = () => populateDataFieldsList();
2711
- popularFilter.onchange = () => populateDataFieldsList();
2712
- matrixOnlyFilter.onchange = () => populateDataFieldsList();
2713
-
2714
- selectAllBtn.onclick = selectAllFilteredDataFields;
2715
- clearAllBtn.onclick = clearAllSelectedDataFields;
2716
-
2717
- selectAllCheckbox.onclick = (e) => {
2718
- e.stopPropagation();
2719
- if (selectAllCheckbox.checked) {
2720
- selectAllFilteredDataFields();
2721
- } else {
2722
- clearAllFilteredDataFields();
2723
- }
2724
- };
2725
-
2726
- // Column filter listeners
2727
- document.querySelectorAll('.column-filter').forEach(filter => {
2728
- filter.addEventListener('input', (e) => {
2729
- const column = e.target.dataset.column;
2730
- const value = e.target.value;
2731
-
2732
- if (column === 'userCount' || column === 'alphaCount') {
2733
- columnFilters[column] = value ? parseInt(value) : null;
2734
- } else {
2735
- columnFilters[column] = value;
2736
- }
2737
-
2738
- // Add/remove active class
2739
- if (value) {
2740
- e.target.classList.add('active');
2741
- } else {
2742
- e.target.classList.remove('active');
2743
- }
2744
-
2745
- populateDataFieldsList();
2746
- });
2747
- });
2748
-
2749
- // Coverage range filters
2750
- document.querySelectorAll('.column-filter-min, .column-filter-max').forEach(filter => {
2751
- filter.addEventListener('input', (e) => {
2752
- const isMin = e.target.classList.contains('column-filter-min');
2753
- const value = e.target.value;
2754
-
2755
- if (isMin) {
2756
- columnFilters.coverage.min = value ? parseFloat(value) : null;
2757
- } else {
2758
- columnFilters.coverage.max = value ? parseFloat(value) : null;
2759
- }
2760
-
2761
- // Add/remove active class
2762
- const minInput = e.target.parentElement.querySelector('.column-filter-min');
2763
- const maxInput = e.target.parentElement.querySelector('.column-filter-max');
2764
-
2765
- if (minInput.value || maxInput.value) {
2766
- minInput.classList.add('active');
2767
- maxInput.classList.add('active');
2768
- } else {
2769
- minInput.classList.remove('active');
2770
- maxInput.classList.remove('active');
2771
- }
2772
-
2773
- populateDataFieldsList();
2774
- });
2775
- });
2776
-
2777
- // Sort button listeners
2778
- document.querySelectorAll('.sort-btn').forEach(btn => {
2779
- btn.addEventListener('click', (e) => {
2780
- const column = e.target.dataset.column;
2781
-
2782
- // Reset all other sort buttons
2783
- document.querySelectorAll('.sort-btn').forEach(b => {
2784
- if (b !== e.target) {
2785
- b.classList.remove('asc', 'desc');
2786
- b.dataset.order = 'asc';
2787
- }
2788
- });
2789
-
2790
- // Toggle sort order
2791
- if (sortColumn === column) {
2792
- sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
2793
- } else {
2794
- sortColumn = column;
2795
- sortOrder = 'asc';
2796
- }
2797
-
2798
- e.target.dataset.order = sortOrder;
2799
- e.target.classList.remove('asc', 'desc');
2800
- e.target.classList.add(sortOrder);
2801
-
2802
- populateDataFieldsList();
2803
- });
2804
- });
2805
- }
2806
-
2807
- function updateSelectAllCheckbox() {
2808
- const selectAllCheckbox = document.getElementById('selectAllCheckbox');
2809
- if (!selectAllCheckbox) return;
2810
-
2811
- const allFilteredSelected = filteredDataFields.length > 0 &&
2812
- filteredDataFields.every(field => selectedDataFields.has(field.id));
2813
-
2814
- selectAllCheckbox.checked = allFilteredSelected;
2815
- selectAllCheckbox.indeterminate = !allFilteredSelected &&
2816
- filteredDataFields.some(field => selectedDataFields.has(field.id));
2817
- }
2818
-
2819
- function clearAllFilteredDataFields() {
2820
- filteredDataFields.forEach(field => {
2821
- selectedDataFields.delete(field.id);
2822
- const row = document.querySelector(`tr[data-field-id="${field.id}"]`);
2823
- if (row) {
2824
- const checkbox = row.querySelector('.data-field-checkbox');
2825
- checkbox.checked = false;
2826
- row.classList.remove('selected');
2827
- }
2828
- });
2829
-
2830
- updateSelectedDataFieldsDisplay();
2831
- updateDataFieldsStats();
2832
- updateSelectAllCheckbox();
2833
- }
2834
-
2835
- function applySelectedDataFields() {
2836
- if (selectedDataFields.size === 0) {
2837
- alert('Please select at least one data field');
2838
- return;
2839
- }
2840
-
2841
- // Add selected data fields to the variable input
2842
- const variableInput = document.getElementById('variableInput');
2843
- const currentValues = variableInput.value.trim();
2844
- const newValues = Array.from(selectedDataFields);
2845
-
2846
- if (currentValues) {
2847
- variableInput.value = currentValues + ', ' + newValues.join(', ');
2848
- } else {
2849
- variableInput.value = newValues.join(', ');
2850
- }
2851
-
2852
- closeBrainDataFieldsModal();
2853
- }
2854
-
2855
- // Custom tooltip functionality
2856
- let tooltipElement = null;
2857
-
2858
- function createTooltipElement() {
2859
- if (!tooltipElement) {
2860
- tooltipElement = document.createElement('div');
2861
- tooltipElement.className = 'custom-tooltip';
2862
- document.body.appendChild(tooltipElement);
2863
- }
2864
- return tooltipElement;
2865
- }
2866
-
2867
- function showCustomTooltip(event) {
2868
- const tooltip = createTooltipElement();
2869
- const content = event.target.closest('[data-tooltip]')?.dataset.tooltip;
2870
-
2871
- if (content) {
2872
- tooltip.textContent = content;
2873
- tooltip.style.opacity = '1';
2874
- moveCustomTooltip(event);
2875
- }
2876
- }
2877
-
2878
- function hideCustomTooltip() {
2879
- if (tooltipElement) {
2880
- tooltipElement.style.opacity = '0';
2881
- }
2882
- }
2883
-
2884
- function moveCustomTooltip(event) {
2885
- if (!tooltipElement || tooltipElement.style.opacity === '0') return;
2886
-
2887
- const tooltip = tooltipElement;
2888
- const mouseX = event.clientX;
2889
- const mouseY = event.clientY;
2890
- const offset = 10;
2891
-
2892
- // Get tooltip dimensions
2893
- const tooltipRect = tooltip.getBoundingClientRect();
2894
- const windowWidth = window.innerWidth;
2895
- const windowHeight = window.innerHeight;
2896
-
2897
- // Calculate position
2898
- let left = mouseX + offset;
2899
- let top = mouseY + offset;
2900
-
2901
- // Adjust if tooltip would go off-screen to the right
2902
- if (left + tooltipRect.width > windowWidth) {
2903
- left = mouseX - tooltipRect.width - offset;
2904
- }
2905
-
2906
- // Adjust if tooltip would go off-screen at the bottom
2907
- if (top + tooltipRect.height > windowHeight) {
2908
- top = mouseY - tooltipRect.height - offset;
2909
- }
2910
-
2911
- // Ensure tooltip doesn't go off-screen to the left or top
2912
- if (left < 0) left = offset;
2913
- if (top < 0) top = offset;
2914
-
2915
- tooltip.style.left = left + 'px';
2916
- tooltip.style.top = top + 'px';
2917
- }
2918
-
2919
- /**
2920
- * Handle Transformer File Selection
2921
- */
2922
- function handleTransformerFileSelect(event) {
2923
- const file = event.target.files[0];
2924
- if (!file) return;
2925
-
2926
- const reader = new FileReader();
2927
- reader.onload = function(e) {
2928
- try {
2929
- const data = JSON.parse(e.target.result);
2930
- loadTransformerData(data);
2931
- // Reset file input
2932
- event.target.value = '';
2933
- } catch (error) {
2934
- console.error("Error parsing transformer file:", error);
2935
- alert("Error parsing JSON file: " + error.message);
2936
- }
2937
- };
2938
- reader.readAsText(file);
2939
- }
2940
-
2941
- /**
2942
- * Load Transformer generated Alpha Candidates from data object
2943
- */
2944
- function loadTransformerData(data) {
2945
- try {
2946
- const buttonsContainer = document.getElementById('transformerTemplateButtons');
2947
- if (!buttonsContainer) {
2948
- console.error('transformerTemplateButtons container not found!');
2949
- return;
2950
- }
2951
-
2952
- // Clear existing buttons
2953
- buttonsContainer.innerHTML = '';
2954
-
2955
- // Add a header
2956
- const header = document.createElement('div');
2957
- header.innerHTML = '<hr><strong>Transformer Candidates</strong>';
2958
- header.style.margin = '10px 0';
2959
- header.style.color = '#666';
2960
- buttonsContainer.appendChild(header);
2961
-
2962
- // Iterate through expressions
2963
- Object.entries(data).forEach(([expression, details]) => {
2964
- const btn = document.createElement('button');
2965
- // Show first 30 chars of explanation
2966
- const explanation = details.template_explanation || expression;
2967
-
2968
- btn.className = 'btn btn-template btn-template-custom'; // Reuse custom template style
2969
- // Add a distinct border or color to distinguish
2970
- btn.style.borderLeft = '4px solid #9c27b0';
2971
-
2972
- btn.innerHTML = `
2973
- <div style="font-weight:bold; margin-bottom:4px;">Transformer Candidate</div>
2974
- <div style="font-size:0.9em; overflow:hidden; text-overflow:ellipsis;">${explanation.substring(0, 50)}${explanation.length > 50 ? '...' : ''}</div>
2975
- `;
2976
-
2977
- btn.title = explanation + '\n\n' + expression;
2978
-
2979
- btn.onclick = () => {
2980
- // 1. Set editor content
2981
- const editor = document.getElementById('expressionEditor');
2982
- editor.value = expression;
2983
-
2984
- // Store explanation for saving
2985
- currentTransformerExplanation = details.template_explanation || '';
2986
-
2987
- // Trigger input event to update line numbers and syntax highlight
2988
- editor.dispatchEvent(new Event('input'));
2989
-
2990
- // 2. Trigger parsing
2991
- detectTemplates();
2992
-
2993
- // 3. Parse and fill candidates
2994
- parseTransformerCandidates(details.placeholder_candidates);
2995
-
2996
- // Scroll to editor
2997
- document.querySelector('.page-navigation button[data-page="editor"]').click();
2998
- };
2999
-
3000
- buttonsContainer.appendChild(btn);
3001
- });
3002
-
3003
- // Show notification
3004
- if (typeof showNotification === 'function') {
3005
- showNotification(`Loaded ${Object.keys(data).length} transformer templates`, 'success');
3006
- }
3007
-
3008
- } catch (error) {
3009
- console.error("Failed to load transformer candidates:", error);
3010
- alert("Failed to load transformer candidates");
3011
- }
3012
- }
3013
-
3014
- /**
3015
- * Parse candidates and fill into templates Map
3016
- * @param {Object} placeholderCandidates - placeholder_candidates object from JSON
3017
- */
3018
- function parseTransformerCandidates(placeholderCandidates) {
3019
- if (!placeholderCandidates) return;
3020
-
3021
- // Iterate through config
3022
- for (const [phTag, config] of Object.entries(placeholderCandidates)) {
3023
- // Convert "<fundamental_field/>" to "fundamental_field"
3024
- const templateName = phTag.replace(/[<\/>]/g, '');
3025
-
3026
- if (templates.has(templateName)) {
3027
- const templateData = templates.get(templateName);
3028
-
3029
- // Extract values (compatible with id, value, name)
3030
- const values = config.candidates.map(item => {
3031
- if (item.id !== undefined) return item.id;
3032
- if (item.value !== undefined) return item.value;
3033
- if (item.name !== undefined) return item.name;
3034
- return null;
3035
- }).filter(v => v !== null);
3036
-
3037
- // Update template data in memory
3038
- templateData.variables = values;
3039
- templateData.configType = 'other'; // Set to Other as requested
3040
-
3041
- // Update UI
3042
- // We need to find the element in the templateList (Detected Templates panel)
3043
- // The detectTemplates() function recreates these elements, so we need to find them again
3044
- // But detectTemplates() is async? No, it seems synchronous in previous code.
3045
- // However, detectTemplates() clears and rebuilds the list.
3046
-
3047
- // Let's try to update the UI
3048
- updateTemplateUI(templateName, values.length);
3049
- }
3050
- }
3051
- }
3052
-
3053
- /**
3054
- * Helper: Update template UI display
3055
- */
3056
- function updateTemplateUI(templateName, count) {
3057
- const templateList = document.getElementById('templateList');
3058
- if (!templateList) return;
3059
-
3060
- const items = templateList.getElementsByClassName('template-item');
3061
-
3062
- for (let item of items) {
3063
- const nameSpan = item.querySelector('.template-tag');
3064
- if (nameSpan && nameSpan.textContent === templateName) {
3065
- item.classList.remove('not-configured');
3066
- item.classList.add('configured');
3067
-
3068
- const countSpan = item.querySelector('.template-count');
3069
- if (countSpan) {
3070
- countSpan.textContent = ` (${count})`;
3071
- countSpan.style.color = '#48bb78';
3072
- countSpan.style.fontWeight = '600';
3073
- }
3074
-
3075
- // Also update the buttons inside if possible, but the previous code structure
3076
- // for template items wasn't fully visible.
3077
- // Assuming the structure from detectTemplates()
3078
-
3079
- break;
3080
- }
3081
- }
3082
- }