cnhkmcp 2.1.2__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 (113) hide show
  1. {cnhkmcp-2.1.2.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 -281
  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 -408
  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//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
  22. cnhkmcp/untracked/APP/.gitignore +0 -32
  23. cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md +0 -112
  24. cnhkmcp/untracked/APP/README.md +0 -309
  25. cnhkmcp/untracked/APP/Tranformer/Transformer.py +0 -4985
  26. cnhkmcp/untracked/APP/Tranformer/ace.log +0 -0
  27. cnhkmcp/untracked/APP/Tranformer/ace_lib.py +0 -1510
  28. cnhkmcp/untracked/APP/Tranformer/helpful_functions.py +0 -180
  29. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates.json +0 -2421
  30. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates_/321/207/320/264/342/225/221/321/204/342/225/233/320/233.json +0 -654
  31. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_error.json +0 -1034
  32. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_success.json +0 -444
  33. 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
  34. cnhkmcp/untracked/APP/Tranformer/parsetab.py +0 -60
  35. cnhkmcp/untracked/APP/Tranformer/template_summary.txt +0 -3182
  36. cnhkmcp/untracked/APP/Tranformer/transformer_config.json +0 -7
  37. cnhkmcp/untracked/APP/Tranformer/validator.py +0 -889
  38. cnhkmcp/untracked/APP/ace.log +0 -69
  39. cnhkmcp/untracked/APP/ace_lib.py +0 -1510
  40. cnhkmcp/untracked/APP/blueprints/__init__.py +0 -6
  41. cnhkmcp/untracked/APP/blueprints/feature_engineering.py +0 -347
  42. cnhkmcp/untracked/APP/blueprints/idea_house.py +0 -221
  43. cnhkmcp/untracked/APP/blueprints/inspiration_house.py +0 -432
  44. cnhkmcp/untracked/APP/blueprints/paper_analysis.py +0 -570
  45. cnhkmcp/untracked/APP/custom_templates/templates.json +0 -1257
  46. cnhkmcp/untracked/APP/give_me_idea/BRAIN_Alpha_Template_Expert_SystemPrompt.md +0 -400
  47. cnhkmcp/untracked/APP/give_me_idea/ace_lib.py +0 -1510
  48. cnhkmcp/untracked/APP/give_me_idea/alpha_data_specific_template_master.py +0 -252
  49. cnhkmcp/untracked/APP/give_me_idea/fetch_all_datasets.py +0 -157
  50. cnhkmcp/untracked/APP/give_me_idea/fetch_all_operators.py +0 -99
  51. cnhkmcp/untracked/APP/give_me_idea/helpful_functions.py +0 -180
  52. cnhkmcp/untracked/APP/give_me_idea/what_is_Alpha_template.md +0 -11
  53. cnhkmcp/untracked/APP/helpful_functions.py +0 -180
  54. cnhkmcp/untracked/APP/hkSimulator/ace_lib.py +0 -1497
  55. cnhkmcp/untracked/APP/hkSimulator/autosimulator.py +0 -447
  56. cnhkmcp/untracked/APP/hkSimulator/helpful_functions.py +0 -180
  57. cnhkmcp/untracked/APP/mirror_config.txt +0 -20
  58. cnhkmcp/untracked/APP/operaters.csv +0 -129
  59. cnhkmcp/untracked/APP/requirements.txt +0 -53
  60. cnhkmcp/untracked/APP/run_app.bat +0 -28
  61. cnhkmcp/untracked/APP/run_app.sh +0 -34
  62. cnhkmcp/untracked/APP/setup_tsinghua.bat +0 -39
  63. cnhkmcp/untracked/APP/setup_tsinghua.sh +0 -43
  64. cnhkmcp/untracked/APP/simulator/alpha_submitter.py +0 -404
  65. cnhkmcp/untracked/APP/simulator/simulator_wqb.py +0 -618
  66. cnhkmcp/untracked/APP/ssrn-3332513.pdf +6 -109201
  67. cnhkmcp/untracked/APP/static/brain.js +0 -589
  68. cnhkmcp/untracked/APP/static/decoder.js +0 -1540
  69. cnhkmcp/untracked/APP/static/feature_engineering.js +0 -1729
  70. cnhkmcp/untracked/APP/static/idea_house.js +0 -937
  71. cnhkmcp/untracked/APP/static/inspiration.js +0 -465
  72. cnhkmcp/untracked/APP/static/inspiration_house.js +0 -868
  73. cnhkmcp/untracked/APP/static/paper_analysis.js +0 -390
  74. cnhkmcp/untracked/APP/static/script.js +0 -3082
  75. cnhkmcp/untracked/APP/static/simulator.js +0 -597
  76. cnhkmcp/untracked/APP/static/styles.css +0 -3127
  77. cnhkmcp/untracked/APP/static/usage_widget.js +0 -508
  78. cnhkmcp/untracked/APP/templates/alpha_inspector.html +0 -511
  79. cnhkmcp/untracked/APP/templates/feature_engineering.html +0 -960
  80. cnhkmcp/untracked/APP/templates/idea_house.html +0 -564
  81. cnhkmcp/untracked/APP/templates/index.html +0 -932
  82. cnhkmcp/untracked/APP/templates/inspiration_house.html +0 -861
  83. cnhkmcp/untracked/APP/templates/paper_analysis.html +0 -91
  84. cnhkmcp/untracked/APP/templates/simulator.html +0 -343
  85. cnhkmcp/untracked/APP/templates/transformer_web.html +0 -580
  86. cnhkmcp/untracked/APP/usage.md +0 -351
  87. 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
  88. 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
  89. 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
  90. 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
  91. cnhkmcp/untracked/arXiv_API_Tool_Manual.md +0 -490
  92. cnhkmcp/untracked/arxiv_api.py +0 -229
  93. cnhkmcp/untracked/forum_functions.py +0 -998
  94. 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
  95. 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
  96. 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
  97. 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
  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//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
  99. cnhkmcp/untracked/platform_functions.py +0 -2886
  100. cnhkmcp/untracked/sample_mcp_config.json +0 -11
  101. cnhkmcp/untracked/user_config.json +0 -31
  102. 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
  103. 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
  104. 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
  105. 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
  106. 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
  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_daily_report_workflow.md +0 -128
  108. 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
  109. cnhkmcp-2.1.2.dist-info/RECORD +0 -111
  110. cnhkmcp-2.1.2.dist-info/top_level.txt +0 -1
  111. {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/WHEEL +0 -0
  112. {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/entry_points.txt +0 -0
  113. {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,1540 +0,0 @@
1
- /**
2
- * Template Decoder Module - Full List Iteration Method
3
- *
4
- * This module handles the decoding of template expressions using the Cartesian product approach.
5
- * It depends on the global 'templates' variable defined in script.js
6
- *
7
- * Functions:
8
- * - decodeTemplates(): Main function to decode templates
9
- * - generateCombinations(): Generate all possible combinations using Cartesian product
10
- * - displayDecodedResults(): Display the decoded results
11
- * - searchResults(): Search through all decoded results
12
- * - copySingleResult(): Copy a single result to clipboard
13
- * - copyAllResults(): Copy all results to clipboard
14
- * - downloadResults(): Download results as a text file
15
- */
16
-
17
- // Global variable to store all decoded expressions for searching
18
- let allDecodedExpressions = [];
19
- let displayedExpressions = [];
20
- const MAX_DISPLAY_RESULTS = 999;
21
- let simulationOptions = {}; // Store valid simulation options
22
-
23
- // Decode templates with full list approach
24
- function decodeTemplates() {
25
- const editor = document.getElementById('expressionEditor');
26
- const expression = editor.value.trim();
27
- const errorsDiv = document.getElementById('grammarErrors');
28
-
29
- // Check if expression is empty
30
- if (!expression) {
31
- errorsDiv.innerHTML = '<div class="error-item"><strong>ERROR:</strong> Please enter an expression to decode</div>';
32
- return;
33
- }
34
-
35
- // First, detect all templates
36
- const templateRegex = /<(\w+)\/>/g;
37
- const matches = [...expression.matchAll(templateRegex)];
38
- const uniqueTemplates = [...new Set(matches.map(match => match[1]))];
39
-
40
- // Check if there are any templates to decode
41
- if (uniqueTemplates.length === 0) {
42
- errorsDiv.innerHTML = '<div class="error-item"><strong>ERROR:</strong> No templates found in the expression to decode</div>';
43
- return;
44
- }
45
-
46
- // Check if all templates have been configured
47
- const unconfigured = [];
48
- const templateValues = new Map();
49
-
50
- uniqueTemplates.forEach(templateName => {
51
- const template = templates.get(templateName);
52
- if (!template || !template.variables || template.variables.length === 0) {
53
- unconfigured.push(templateName);
54
- } else {
55
- templateValues.set(templateName, template.variables);
56
- }
57
- });
58
-
59
- // Show error if any templates are not configured
60
- if (unconfigured.length > 0) {
61
- errorsDiv.innerHTML = `<div class="error-item">
62
- <strong>ERROR:</strong> The following templates need to be configured before decoding:
63
- ${unconfigured.map(t => `<span class="template-name" style="font-family: monospace;">&lt;${t}/&gt;</span>`).join(', ')}
64
- </div>`;
65
- return;
66
- }
67
-
68
- // Calculate total combinations
69
- let totalCombinations = 1;
70
- templateValues.forEach(values => {
71
- totalCombinations *= values.length;
72
- });
73
-
74
- // Warn if too many combinations
75
- if (totalCombinations > 1000) {
76
- if (!confirm(`This will generate ${totalCombinations} expressions. This might take a while. Continue?`)) {
77
- return;
78
- }
79
- }
80
-
81
- // Generate all combinations (Cartesian product)
82
- const combinations = generateCombinations(templateValues);
83
-
84
- // Generate decoded expressions
85
- const decodedExpressions = combinations.map(combination => {
86
- let decodedExpression = expression;
87
- combination.forEach(({template, value}) => {
88
- const regex = new RegExp(`<${template}/>`, 'g');
89
- decodedExpression = decodedExpression.replace(regex, value);
90
- });
91
- return decodedExpression;
92
- });
93
-
94
- // Store all expressions globally
95
- allDecodedExpressions = decodedExpressions;
96
-
97
- // Display results (limit to MAX_DISPLAY_RESULTS)
98
- displayDecodedResults(decodedExpressions.slice(0, MAX_DISPLAY_RESULTS), decodedExpressions.length);
99
-
100
- // Clear errors and show success
101
- errorsDiv.innerHTML = `<div class="success-message">
102
- ✓ Successfully decoded ${decodedExpressions.length} expressions using full list approach
103
- ${decodedExpressions.length > MAX_DISPLAY_RESULTS ?
104
- `<br>⚠️ Showing first ${MAX_DISPLAY_RESULTS} results. Use search to find specific expressions.` : ''}
105
- </div>`;
106
- }
107
-
108
- // Generate all combinations (Cartesian product) of template values
109
- function generateCombinations(templateValues) {
110
- const templates = Array.from(templateValues.keys());
111
- if (templates.length === 0) return [];
112
-
113
- const combinations = [];
114
-
115
- function generate(index, current) {
116
- if (index === templates.length) {
117
- combinations.push([...current]);
118
- return;
119
- }
120
-
121
- const template = templates[index];
122
- const values = templateValues.get(template);
123
-
124
- for (const value of values) {
125
- current.push({template, value});
126
- generate(index + 1, current);
127
- current.pop();
128
- }
129
- }
130
-
131
- generate(0, []);
132
- return combinations;
133
- }
134
-
135
- // Display decoded results
136
- function displayDecodedResults(expressions, totalCount = null, isRandom = false) {
137
- const resultsList = document.getElementById('resultsList');
138
-
139
- // Clear previous results
140
- resultsList.innerHTML = '';
141
-
142
- // Add search box if there are more results than displayed (only for full iteration)
143
- if (!isRandom && totalCount && totalCount > MAX_DISPLAY_RESULTS) {
144
- const searchContainer = document.createElement('div');
145
- searchContainer.className = 'results-search-container';
146
- searchContainer.innerHTML = `
147
- <input type="text" id="resultsSearchInput" class="results-search-input"
148
- placeholder="Search through all ${totalCount} expressions...">
149
- <button id="resultsSearchBtn" class="btn btn-secondary btn-small">Search</button>
150
- <button id="resultsClearSearchBtn" class="btn btn-outline btn-small" style="display: none;">Clear Search</button>
151
- `;
152
- resultsList.appendChild(searchContainer);
153
-
154
- // Add event listeners for search
155
- document.getElementById('resultsSearchBtn').addEventListener('click', searchResults);
156
- document.getElementById('resultsSearchInput').addEventListener('keypress', (e) => {
157
- if (e.key === 'Enter') searchResults();
158
- });
159
- document.getElementById('resultsClearSearchBtn').addEventListener('click', clearSearch);
160
- }
161
-
162
- // Add info about the number of results
163
- if (expressions.length > 0) {
164
- const infoDiv = document.createElement('div');
165
- infoDiv.className = 'results-info';
166
- if (isRandom) {
167
- // For random results, show the actual selected count vs total combinations
168
- const actualSelectedCount = allDecodedExpressions.length;
169
- if (actualSelectedCount > expressions.length) {
170
- infoDiv.innerHTML = `Randomly selected <strong>${actualSelectedCount}</strong> expressions from <strong>${totalCount}</strong> total combinations<br>
171
- <small>Displaying first <strong>${expressions.length}</strong> results. Download will include all <strong>${actualSelectedCount}</strong> expressions.</small>`;
172
- } else {
173
- infoDiv.innerHTML = `Randomly selected <strong>${expressions.length}</strong> expressions from <strong>${totalCount}</strong> total combinations`;
174
- }
175
- } else if (totalCount && totalCount > expressions.length) {
176
- infoDiv.innerHTML = `Generated <strong>${totalCount}</strong> expressions total.
177
- Displaying <strong>${expressions.length}</strong> results
178
- ${expressions.length === MAX_DISPLAY_RESULTS ? '(first 999)' : '(filtered)'}.`;
179
- } else {
180
- infoDiv.textContent = `Generated ${expressions.length} expressions using full list iteration`;
181
- }
182
- resultsList.appendChild(infoDiv);
183
- }
184
-
185
- // Store displayed expressions globally
186
- displayedExpressions = expressions;
187
-
188
- // Add each expression
189
- expressions.forEach((expr, index) => {
190
- const resultItem = document.createElement('div');
191
- resultItem.className = 'result-item';
192
-
193
- const number = document.createElement('span');
194
- number.className = 'result-number';
195
- number.textContent = `${index + 1}.`;
196
-
197
- const expression = document.createElement('span');
198
- expression.className = 'result-expression';
199
- expression.textContent = expr;
200
-
201
- resultItem.appendChild(number);
202
- resultItem.appendChild(expression);
203
- // Copy button disabled
204
- // resultItem.appendChild(copyBtn);
205
- resultsList.appendChild(resultItem);
206
- });
207
-
208
- // Show the results tab and update badge
209
- const resultsTab = document.getElementById('resultsTab');
210
- const resultsBadge = document.getElementById('resultsBadge');
211
- resultsTab.style.display = 'flex';
212
- resultsBadge.textContent = totalCount || expressions.length;
213
-
214
- // Navigate to results page
215
- navigateToPage('results');
216
- }
217
-
218
- // Search through all results
219
- function searchResults() {
220
- const searchInput = document.getElementById('resultsSearchInput');
221
- const searchTerm = searchInput.value.trim().toLowerCase();
222
-
223
- if (!searchTerm) {
224
- // If empty search, show first 1000 again
225
- displayDecodedResults(allDecodedExpressions.slice(0, MAX_DISPLAY_RESULTS), allDecodedExpressions.length);
226
- return;
227
- }
228
-
229
- // Filter all expressions based on search term
230
- const filteredExpressions = allDecodedExpressions.filter(expr =>
231
- expr.toLowerCase().includes(searchTerm)
232
- );
233
-
234
- // Display filtered results (still limit to MAX_DISPLAY_RESULTS)
235
- displayDecodedResults(filteredExpressions.slice(0, MAX_DISPLAY_RESULTS), allDecodedExpressions.length);
236
-
237
- // Show clear button
238
- document.getElementById('resultsClearSearchBtn').style.display = 'inline-block';
239
-
240
- // Update info message
241
- const errorsDiv = document.getElementById('grammarErrors');
242
- if (filteredExpressions.length === 0) {
243
- errorsDiv.innerHTML = `<div class="warning-message">
244
- No expressions found matching "${searchTerm}"
245
- </div>`;
246
- } else if (filteredExpressions.length > MAX_DISPLAY_RESULTS) {
247
- errorsDiv.innerHTML = `<div class="success-message">
248
- Found ${filteredExpressions.length} expressions matching "${searchTerm}".
249
- Showing first ${MAX_DISPLAY_RESULTS} results.
250
- </div>`;
251
- } else {
252
- errorsDiv.innerHTML = `<div class="success-message">
253
- Found ${filteredExpressions.length} expressions matching "${searchTerm}"
254
- </div>`;
255
- }
256
- }
257
-
258
- // Clear search and show original results
259
- function clearSearch() {
260
- document.getElementById('resultsSearchInput').value = '';
261
- document.getElementById('resultsClearSearchBtn').style.display = 'none';
262
- displayDecodedResults(allDecodedExpressions.slice(0, MAX_DISPLAY_RESULTS), allDecodedExpressions.length);
263
-
264
- const errorsDiv = document.getElementById('grammarErrors');
265
- errorsDiv.innerHTML = `<div class="success-message">
266
- ✓ Showing first ${MAX_DISPLAY_RESULTS} of ${allDecodedExpressions.length} total expressions
267
- </div>`;
268
- }
269
-
270
- // Copy single result
271
- function copySingleResult(expression) {
272
- navigator.clipboard.writeText(expression).then(() => {
273
- // Show temporary success message
274
- const errorsDiv = document.getElementById('grammarErrors');
275
- const prevContent = errorsDiv.innerHTML;
276
- errorsDiv.innerHTML = '<div class="success-message">✓ Copied to clipboard</div>';
277
- setTimeout(() => {
278
- errorsDiv.innerHTML = prevContent;
279
- }, 2000);
280
- });
281
- }
282
-
283
- // Copy displayed results
284
- function copyDisplayedResults() {
285
- // Copy all currently displayed expressions
286
- try {
287
- const expressions = displayedExpressions.join('\n');
288
-
289
- navigator.clipboard.writeText(expressions).then(() => {
290
- const errorsDiv = document.getElementById('grammarErrors');
291
- errorsDiv.innerHTML = `<div class="success-message">
292
- ✓ ${displayedExpressions.length.toLocaleString()} displayed expressions copied to clipboard
293
- </div>`;
294
- }).catch(err => {
295
- const errorsDiv = document.getElementById('grammarErrors');
296
- errorsDiv.innerHTML = `<div class="error-item">
297
- <strong>ERROR:</strong> Failed to copy to clipboard: ${err.message}
298
- </div>`;
299
- });
300
- } catch (error) {
301
- const errorsDiv = document.getElementById('grammarErrors');
302
- errorsDiv.innerHTML = `<div class="error-item">
303
- <strong>ERROR:</strong> Failed to prepare data for clipboard: ${error.message}
304
- </div>`;
305
- }
306
- }
307
-
308
- // Copy all results
309
- function copyAllResults() {
310
- // Copy ALL generated expressions
311
- try {
312
- // Check if the data is too large for clipboard (rough estimate: 1MB limit)
313
- const expressions = allDecodedExpressions.join('\n');
314
- const dataSize = new Blob([expressions]).size;
315
-
316
- if (dataSize > 1024 * 1024) { // 1MB limit
317
- const errorsDiv = document.getElementById('grammarErrors');
318
- errorsDiv.innerHTML = `<div class="error-item">
319
- <strong>ERROR:</strong> Data too large for clipboard (${(dataSize / 1024 / 1024).toFixed(1)}MB).
320
- Please use the Download All button instead.
321
- </div>`;
322
- return;
323
- }
324
-
325
- navigator.clipboard.writeText(expressions).then(() => {
326
- const errorsDiv = document.getElementById('grammarErrors');
327
- errorsDiv.innerHTML = `<div class="success-message">
328
- ✓ ALL ${allDecodedExpressions.length.toLocaleString()} expressions copied to clipboard
329
- </div>`;
330
- }).catch(err => {
331
- // Handle potential errors with large clipboard operations
332
- const errorsDiv = document.getElementById('grammarErrors');
333
- errorsDiv.innerHTML = `<div class="error-item">
334
- <strong>ERROR:</strong> Failed to copy to clipboard. The data might be too large.
335
- Please use the Download All button instead.
336
- </div>`;
337
- });
338
- } catch (error) {
339
- const errorsDiv = document.getElementById('grammarErrors');
340
- errorsDiv.innerHTML = `<div class="error-item">
341
- <strong>ERROR:</strong> Failed to prepare data for clipboard: ${error.message}
342
- </div>`;
343
- }
344
- }
345
-
346
- // Download results as text file
347
- function downloadResults() {
348
- try {
349
- // Download the expressions (all or random selection)
350
- const expressions = allDecodedExpressions.join('\n');
351
-
352
- const blob = new Blob([expressions], { type: 'text/plain' });
353
- const url = URL.createObjectURL(blob);
354
- const a = document.createElement('a');
355
- a.href = url;
356
- a.download = 'decoded_expressions.txt';
357
- document.body.appendChild(a);
358
- a.click();
359
- document.body.removeChild(a);
360
- URL.revokeObjectURL(url);
361
-
362
- const errorsDiv = document.getElementById('grammarErrors');
363
- errorsDiv.innerHTML = `<div class="success-message">
364
- ✓ Downloaded ${allDecodedExpressions.length.toLocaleString()} expressions as decoded_expressions.txt
365
- </div>`;
366
- } catch (error) {
367
- const errorsDiv = document.getElementById('grammarErrors');
368
- errorsDiv.innerHTML = `<div class="error-message">
369
- ❌ Error downloading file: ${error.message}
370
- </div>`;
371
- }
372
- }
373
-
374
- // Random iteration - generate all then randomly pick
375
- function randomIteration() {
376
- const editor = document.getElementById('expressionEditor');
377
- const expression = editor.value.trim();
378
- const errorsDiv = document.getElementById('grammarErrors');
379
- const randomCountInput = document.getElementById('randomCount');
380
- const randomCount = parseInt(randomCountInput.value) || 10;
381
-
382
- // Check if expression is empty
383
- if (!expression) {
384
- errorsDiv.innerHTML = '<div class="error-item"><strong>ERROR:</strong> Please enter an expression to decode</div>';
385
- return;
386
- }
387
-
388
- // First, detect all templates
389
- const templateRegex = /<(\w+)\/>/g;
390
- const matches = [...expression.matchAll(templateRegex)];
391
- const uniqueTemplates = [...new Set(matches.map(match => match[1]))];
392
-
393
- // Check if there are any templates to decode
394
- if (uniqueTemplates.length === 0) {
395
- errorsDiv.innerHTML = '<div class="error-item"><strong>ERROR:</strong> No templates found in the expression to decode</div>';
396
- return;
397
- }
398
-
399
- // Check if all templates have been configured
400
- const unconfigured = [];
401
- const templateValues = new Map();
402
-
403
- uniqueTemplates.forEach(templateName => {
404
- const template = templates.get(templateName);
405
- if (!template || !template.variables || template.variables.length === 0) {
406
- unconfigured.push(templateName);
407
- } else {
408
- templateValues.set(templateName, template.variables);
409
- }
410
- });
411
-
412
- // Show error if any templates are not configured
413
- if (unconfigured.length > 0) {
414
- errorsDiv.innerHTML = `<div class="error-item">
415
- <strong>ERROR:</strong> The following templates need to be configured before decoding:
416
- ${unconfigured.map(t => `<span class="template-name" style="font-family: monospace;">&lt;${t}/&gt;</span>`).join(', ')}
417
- </div>`;
418
- return;
419
- }
420
-
421
- // Calculate total combinations
422
- let totalCombinations = 1;
423
- templateValues.forEach(values => {
424
- totalCombinations *= values.length;
425
- });
426
-
427
- // Validate random count
428
- if (randomCount > totalCombinations) {
429
- errorsDiv.innerHTML = `<div class="warning-message">
430
- ⚠️ Requested ${randomCount} random expressions, but only ${totalCombinations} unique combinations exist.
431
- Generating all ${totalCombinations} expressions instead.
432
- </div>`;
433
- }
434
-
435
- // Generate all combinations (Cartesian product)
436
- const combinations = generateCombinations(templateValues);
437
-
438
- // Generate all decoded expressions
439
- const allExpressions = combinations.map(combination => {
440
- let decodedExpression = expression;
441
- combination.forEach(({template, value}) => {
442
- const regex = new RegExp(`<${template}/>`, 'g');
443
- decodedExpression = decodedExpression.replace(regex, value);
444
- });
445
- return decodedExpression;
446
- });
447
-
448
- // Randomly select the requested number of expressions
449
- const selectedExpressions = [];
450
- const actualCount = Math.min(randomCount, allExpressions.length);
451
-
452
- if (actualCount === allExpressions.length) {
453
- // If requesting all or more, just return all
454
- selectedExpressions.push(...allExpressions);
455
- } else {
456
- // Randomly select without replacement
457
- const indices = new Set();
458
- while (indices.size < actualCount) {
459
- indices.add(Math.floor(Math.random() * allExpressions.length));
460
- }
461
- indices.forEach(index => {
462
- selectedExpressions.push(allExpressions[index]);
463
- });
464
- }
465
-
466
- // Store ALL selected expressions globally for download (not limited by display)
467
- allDecodedExpressions = selectedExpressions;
468
-
469
- // For display, limit to MAX_DISPLAY_RESULTS but keep full set for download
470
- const displayExpressions = selectedExpressions.slice(0, MAX_DISPLAY_RESULTS);
471
-
472
- // Display results (limited for display, but full count for download)
473
- displayDecodedResults(displayExpressions, allExpressions.length, true);
474
-
475
- // Clear errors and show success with clear indication about display vs download
476
- if (selectedExpressions.length > MAX_DISPLAY_RESULTS) {
477
- errorsDiv.innerHTML = `<div class="success-message">
478
- ✓ Randomly selected ${selectedExpressions.length} expressions from ${allExpressions.length} total combinations<br>
479
- 📺 Displaying first ${MAX_DISPLAY_RESULTS} results. Download will include all ${selectedExpressions.length} expressions.
480
- </div>`;
481
- } else {
482
- errorsDiv.innerHTML = `<div class="success-message">
483
- ✓ Randomly selected ${selectedExpressions.length} expressions from ${allExpressions.length} total combinations
484
- </div>`;
485
- }
486
- }
487
-
488
- // Open settings modal for Next Move
489
- function openSettingsModal() {
490
- const modal = document.getElementById('settingsModal');
491
- modal.style.display = 'block';
492
-
493
- // Check if we have simulation options and update UI
494
- if (typeof simulationOptions !== 'undefined' && Object.keys(simulationOptions).length > 0) {
495
- updateSettingsUIWithOptions();
496
- }
497
-
498
- updateTotalCombinations();
499
-
500
- // Add event listeners for setting inputs (inputs/selects/checkbox groups)
501
- document.querySelectorAll('.setting-value-input').forEach(el => {
502
- el.addEventListener('input', updateTotalCombinations);
503
- el.addEventListener('change', updateTotalCombinations);
504
- if (el.classList && el.classList.contains('setting-checkbox-group')) {
505
- el.querySelectorAll('input[type="checkbox"]').forEach(cb => {
506
- cb.addEventListener('change', updateTotalCombinations);
507
- });
508
- }
509
- });
510
-
511
- // Add event listener for add setting button
512
- document.getElementById('addSettingBtn').addEventListener('click', addCustomSetting);
513
-
514
- // Add event listener for test period slider
515
- const testPeriodSlider = document.querySelector('.test-period-slider');
516
- if (testPeriodSlider) {
517
- testPeriodSlider.addEventListener('input', updateTestPeriodValue);
518
- // Initialize the display value
519
- updateTestPeriodValue();
520
- }
521
- }
522
-
523
- function updateSettingsUIWithOptions() {
524
- // Assume EQUITY for now as it's the default
525
- const instType = 'EQUITY';
526
- const options = simulationOptions[instType];
527
-
528
- if (!options) return;
529
-
530
- // 1. Update Region Input to Select
531
- const regionInput = document.querySelector('input[data-setting="region"]');
532
- if (regionInput && regionInput.tagName === 'INPUT') {
533
- const select = document.createElement('select');
534
- select.className = 'setting-value-input';
535
- select.setAttribute('data-setting', 'region');
536
-
537
- // Add empty option
538
- const emptyOpt = document.createElement('option');
539
- emptyOpt.value = '';
540
- emptyOpt.text = 'Select Region...';
541
- select.appendChild(emptyOpt);
542
-
543
- // Add regions
544
- Object.keys(options).forEach(r => {
545
- const opt = document.createElement('option');
546
- opt.value = r;
547
- opt.text = r;
548
- select.appendChild(opt);
549
- });
550
-
551
- // Replace input
552
- regionInput.parentNode.replaceChild(select, regionInput);
553
-
554
- // Add change listener
555
- select.addEventListener('change', function() {
556
- updateDependentSettings(this.value);
557
- updateTotalCombinations();
558
- });
559
- }
560
-
561
- // 2. Convert Universe and Neutralization to CHECKBOX groups if they are inputs/selects
562
- convertInputToCheckboxGroup('universe', { placeholder: 'Select Region first...' });
563
- convertInputToCheckboxGroup('neutralization', { placeholder: 'Select Region first...' });
564
-
565
- // Delay is already a select, but we might want to update it based on region
566
- // For now, we'll leave it as is or update it in updateDependentSettings
567
- }
568
-
569
- function convertInputToCheckboxGroup(settingName, { placeholder = 'Select Region first...' } = {}) {
570
- const current = document.querySelector(`[data-setting="${settingName}"]`);
571
- if (!current) return;
572
-
573
- // If it's already a checkbox group, do nothing
574
- if (current.tagName === 'DIV' && current.classList.contains('setting-checkbox-group')) return;
575
-
576
- const container = document.createElement('div');
577
- container.className = 'setting-value-input setting-checkbox-group';
578
- container.setAttribute('data-setting', settingName);
579
- container.setAttribute('role', 'group');
580
- container.setAttribute('aria-label', settingName);
581
-
582
- // Placeholder text (shown until region populates)
583
- const placeholderEl = document.createElement('div');
584
- placeholderEl.className = 'setting-checkbox-placeholder';
585
- placeholderEl.textContent = placeholder;
586
- container.appendChild(placeholderEl);
587
-
588
- current.parentNode.replaceChild(container, current);
589
- }
590
-
591
- function setCheckboxGroupOptions(settingName, optionValues) {
592
- const container = document.querySelector(`.setting-checkbox-group[data-setting="${settingName}"]`);
593
- if (!container) return;
594
-
595
- const previousSelected = new Set(
596
- Array.from(container.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value)
597
- );
598
-
599
- container.innerHTML = '';
600
-
601
- (optionValues || []).forEach((val, idx) => {
602
- const label = document.createElement('label');
603
- label.className = 'setting-checkbox-item';
604
-
605
- const cb = document.createElement('input');
606
- cb.type = 'checkbox';
607
- cb.value = val;
608
-
609
- // Keep previous selections when possible; otherwise select first item by default
610
- if (previousSelected.has(val) || (previousSelected.size === 0 && idx === 0)) {
611
- cb.checked = true;
612
- }
613
-
614
- cb.addEventListener('change', updateTotalCombinations);
615
-
616
- const text = document.createElement('span');
617
- text.textContent = val;
618
-
619
- label.appendChild(cb);
620
- label.appendChild(text);
621
- container.appendChild(label);
622
- });
623
-
624
- updateTotalCombinations();
625
- }
626
-
627
- function updateDependentSettings(region) {
628
- if (!region) return;
629
-
630
- const instType = 'EQUITY';
631
- const settings = simulationOptions[instType][region];
632
-
633
- if (!settings) return;
634
-
635
- // Update Universe (checkbox group preferred)
636
- if (document.querySelector('.setting-checkbox-group[data-setting="universe"]')) {
637
- setCheckboxGroupOptions('universe', settings.universes);
638
- } else {
639
- // Back-compat if still a select
640
- const univSelect = document.querySelector('select[data-setting="universe"]');
641
- if (univSelect) {
642
- univSelect.innerHTML = '';
643
- settings.universes.forEach(u => {
644
- const opt = document.createElement('option');
645
- opt.value = u;
646
- opt.text = u;
647
- univSelect.appendChild(opt);
648
- });
649
- if (settings.universes.length > 0) univSelect.value = settings.universes[0];
650
- }
651
- }
652
-
653
- // Update Neutralization (checkbox group preferred)
654
- if (document.querySelector('.setting-checkbox-group[data-setting="neutralization"]')) {
655
- setCheckboxGroupOptions('neutralization', settings.neutralizations);
656
- } else {
657
- // Back-compat if still a select
658
- const neutSelect = document.querySelector('select[data-setting="neutralization"]');
659
- if (neutSelect) {
660
- neutSelect.innerHTML = '';
661
- settings.neutralizations.forEach(n => {
662
- const opt = document.createElement('option');
663
- opt.value = n;
664
- opt.text = n;
665
- neutSelect.appendChild(opt);
666
- });
667
- if (settings.neutralizations.length > 0) neutSelect.value = settings.neutralizations[0];
668
- }
669
- }
670
-
671
- // Update Delay
672
- const delaySelect = document.querySelector('select[data-setting="delay"]');
673
- if (delaySelect && settings.delays) {
674
- delaySelect.innerHTML = '';
675
- settings.delays.forEach(d => {
676
- const opt = document.createElement('option');
677
- opt.value = d;
678
- opt.text = d;
679
- delaySelect.appendChild(opt);
680
- });
681
- // Select first
682
- if (settings.delays.length > 0) delaySelect.value = settings.delays[0];
683
- }
684
- }
685
-
686
- // Close settings modal
687
- function closeSettingsModal() {
688
- const modal = document.getElementById('settingsModal');
689
- modal.style.display = 'none';
690
- }
691
-
692
- // Update test period value display
693
- function updateTestPeriodValue() {
694
- const slider = document.querySelector('.test-period-slider');
695
- const valueDisplay = document.getElementById('testPeriodValue');
696
-
697
- if (slider && valueDisplay) {
698
- const totalMonths = parseInt(slider.value);
699
- const years = Math.floor(totalMonths / 12);
700
- const months = totalMonths % 12;
701
- const periodValue = `P${years}Y${months}M`;
702
- valueDisplay.textContent = periodValue;
703
-
704
- // Update the slider's value attribute so it can be read by parseSettingValues
705
- slider.setAttribute('data-period-value', periodValue);
706
- }
707
- }
708
-
709
- // Add custom setting row
710
- function addCustomSetting() {
711
- const tbody = document.getElementById('settingsTableBody');
712
- const row = document.createElement('tr');
713
- row.className = 'custom-setting-row';
714
-
715
- row.innerHTML = `
716
- <td><input type="text" class="setting-name-input form-input" placeholder="Setting name"></td>
717
- <td><input type="text" class="setting-value-input" data-setting="custom" placeholder="Value(s)"></td>
718
- <td><select class="setting-type-select"><option>string</option><option>number</option><option>boolean</option></select></td>
719
- <td><button class="remove-setting-btn" onclick="removeCustomSetting(this)">Remove</button></td>
720
- `;
721
-
722
- tbody.appendChild(row);
723
-
724
- // Add event listener to new input
725
- row.querySelector('.setting-value-input').addEventListener('input', updateTotalCombinations);
726
- row.querySelector('.setting-name-input').addEventListener('input', updateTotalCombinations);
727
- }
728
-
729
- // Remove custom setting row
730
- function removeCustomSetting(button) {
731
- button.closest('tr').remove();
732
- updateTotalCombinations();
733
- }
734
-
735
- // Calculate total combinations
736
- function updateTotalCombinations() {
737
- let totalCombinations = allDecodedExpressions.length;
738
-
739
- // Get all settings and their values
740
- const settingInputs = document.querySelectorAll('.setting-value-input');
741
- settingInputs.forEach(input => {
742
- let values = [];
743
- if (input.tagName === 'DIV' && input.classList.contains('setting-checkbox-group')) {
744
- values = Array.from(input.querySelectorAll('input[type="checkbox"]:checked'))
745
- .map(cb => cb.value)
746
- .filter(v => v !== '');
747
- } else if (input.tagName === 'SELECT') {
748
- if (input.multiple) {
749
- values = Array.from(input.selectedOptions)
750
- .map(o => o.value)
751
- .filter(v => v !== '');
752
- } else {
753
- values = input.value ? [input.value] : [];
754
- }
755
- } else if (input.type === 'range' && input.getAttribute('data-period-value')) {
756
- values = [input.getAttribute('data-period-value')];
757
- } else {
758
- values = (input.value || '').split(',').map(v => v.trim()).filter(v => v !== '');
759
- }
760
-
761
- if (values.length > 1) totalCombinations *= values.length;
762
- });
763
-
764
- document.getElementById('totalCombinations').textContent = totalCombinations.toLocaleString();
765
- }
766
-
767
- // Parse settings values (handle comma-separated values)
768
- function parseSettingValues() {
769
- const settings = {};
770
- const variations = {};
771
- const types = {};
772
-
773
- // Get predefined settings
774
- const settingRows = document.querySelectorAll('#settingsTableBody tr');
775
- settingRows.forEach(row => {
776
- const nameCell = row.cells[0];
777
- // Use select or input for value
778
- let input = row.querySelector('.setting-value-input');
779
- if (input) {
780
- let settingName;
781
- // Check if it's a custom setting
782
- const nameInput = row.querySelector('.setting-name-input');
783
- if (nameInput) {
784
- settingName = nameInput.value.trim();
785
- if (!settingName) return; // Skip if no name
786
- } else {
787
- settingName = nameCell.textContent.trim();
788
- }
789
- // Get the type
790
- const typeSelect = row.querySelector('.setting-type-select');
791
- const type = typeSelect ? typeSelect.value : 'string';
792
- types[settingName] = type;
793
- // For select dropdowns, get value differently
794
- let values = [];
795
- if (input.tagName === 'DIV' && input.classList.contains('setting-checkbox-group')) {
796
- values = Array.from(input.querySelectorAll('input[type="checkbox"]:checked'))
797
- .map(cb => cb.value)
798
- .filter(v => v !== '');
799
- } else if (input.tagName === 'SELECT') {
800
- if (input.multiple) {
801
- values = Array.from(input.selectedOptions)
802
- .map(o => o.value)
803
- .filter(v => v !== '');
804
- } else {
805
- values = input.value ? [input.value] : [];
806
- }
807
- } else if (input.type === 'range' && settingName === 'testPeriod') {
808
- // Special handling for test period slider
809
- const periodValue = input.getAttribute('data-period-value') || 'P0Y0M';
810
- values = [periodValue];
811
- } else {
812
- values = input.value.split(',').map(v => v.trim()).filter(v => v !== '');
813
- }
814
- // Convert values based on type
815
- const convertedValues = values.map(v => {
816
- if (type === 'number') {
817
- const num = parseFloat(v);
818
- return isNaN(num) ? v : num;
819
- } else if (type === 'boolean') {
820
- if (typeof v === 'boolean') return v;
821
- if (typeof v === 'string') {
822
- if (v.toLowerCase() === 'true') return true;
823
- if (v.toLowerCase() === 'false') return false;
824
- }
825
- return false;
826
- } else {
827
- return v;
828
- }
829
- });
830
- if (convertedValues.length === 0) {
831
- // Use empty string if no value
832
- settings[settingName] = '';
833
- } else if (convertedValues.length === 1) {
834
- // Single value
835
- settings[settingName] = convertedValues[0];
836
- } else {
837
- // Multiple values - store for iteration
838
- variations[settingName] = convertedValues;
839
- settings[settingName] = convertedValues[0]; // Default to first value
840
- }
841
- }
842
- });
843
-
844
- return { settings, variations, types };
845
- }
846
-
847
- // Generate all setting combinations
848
- function generateSettingCombinations(baseSettings, variations) {
849
- const variationKeys = Object.keys(variations);
850
- if (variationKeys.length === 0) {
851
- return [baseSettings];
852
- }
853
-
854
- const combinations = [];
855
-
856
- function generate(index, current) {
857
- if (index === variationKeys.length) {
858
- combinations.push({...current});
859
- return;
860
- }
861
-
862
- const key = variationKeys[index];
863
- const values = variations[key];
864
-
865
- for (const value of values) {
866
- current[key] = value;
867
- generate(index + 1, current);
868
- }
869
- }
870
-
871
- generate(0, {...baseSettings});
872
- return combinations;
873
- }
874
-
875
- // Confirm and apply settings with shuffle option
876
- function confirmAndApplySettings() {
877
- const { settings, variations, types } = parseSettingValues();
878
- const settingCombinations = generateSettingCombinations(settings, variations);
879
- const totalCombinations = allDecodedExpressions.length * settingCombinations.length;
880
-
881
- if (totalCombinations > 1000) {
882
- // Show confirmation dialog for large datasets
883
- const shouldShuffle = confirm(`即将生成 ${totalCombinations.toLocaleString()} 个表达式配置。\n\n是否需要随机打乱表达式顺序?\n\n点击"确定"进行随机打乱\n点击"取消"保持原始顺序`);
884
- applySettings(shouldShuffle);
885
- } else {
886
- // For small datasets, ask if user wants to shuffle
887
- const shouldShuffle = confirm(`即将生成 ${totalCombinations.toLocaleString()} 个表达式配置。\n\n是否需要随机打乱表达式顺序?\n\n点击"确定"进行随机打乱\n点击"取消"保持原始顺序`);
888
- applySettings(shouldShuffle);
889
- }
890
- }
891
-
892
- // Apply settings to expressions
893
- async function applySettings(shouldShuffle = false) {
894
- const { settings, variations, types } = parseSettingValues();
895
-
896
- // Always include instrumentType and language
897
- settings.instrumentType = settings.instrumentType || "EQUITY";
898
- settings.language = settings.language || "FASTEXPR";
899
-
900
- // Generate all setting combinations
901
- const settingCombinations = generateSettingCombinations(settings, variations);
902
-
903
- // Calculate total combinations for progress tracking
904
- const totalCombinations = allDecodedExpressions.length * settingCombinations.length;
905
-
906
- // Get the button and show progress
907
- const button = document.getElementById('generateDownloadBtn');
908
- const btnText = button.querySelector('.btn-text');
909
- const btnProgress = button.querySelector('.btn-progress');
910
- const progressBarFill = button.querySelector('.progress-bar-fill');
911
- const progressText = button.querySelector('.progress-text');
912
-
913
- // Disable button and show progress
914
- button.disabled = true;
915
- btnText.style.display = 'none';
916
- btnProgress.style.display = 'flex';
917
-
918
- // Show progress to user
919
- const errorsDiv = document.getElementById('grammarErrors');
920
- errorsDiv.innerHTML = `<div class="info-message">
921
- ⏳ Generating ${totalCombinations.toLocaleString()} expression configurations...
922
- </div>`;
923
-
924
- // Use streaming approach to handle large files
925
- try {
926
- // Create a writable stream for the file
927
- const chunks = [];
928
- let isFirst = true;
929
-
930
- // Start JSON array
931
- chunks.push('[\n');
932
-
933
- let combinationCount = 0;
934
-
935
- // Create all combinations first
936
- const allCombinations = [];
937
- for (let exprIndex = 0; exprIndex < allDecodedExpressions.length; exprIndex++) {
938
- const expr = allDecodedExpressions[exprIndex];
939
-
940
- for (let settingIndex = 0; settingIndex < settingCombinations.length; settingIndex++) {
941
- const settingCombo = settingCombinations[settingIndex];
942
-
943
- const fullExpression = {
944
- type: "REGULAR",
945
- settings: settingCombo,
946
- regular: expr.replace(/\n/g, '') // Remove newline characters
947
- };
948
-
949
- allCombinations.push(fullExpression);
950
- }
951
- }
952
-
953
- // Shuffle if requested
954
- if (shouldShuffle) {
955
- // Fisher-Yates shuffle algorithm
956
- for (let i = allCombinations.length - 1; i > 0; i--) {
957
- const j = Math.floor(Math.random() * (i + 1));
958
- [allCombinations[i], allCombinations[j]] = [allCombinations[j], allCombinations[i]];
959
- }
960
- }
961
-
962
- // Process combinations in order (original or shuffled)
963
- for (let i = 0; i < allCombinations.length; i++) {
964
- const fullExpression = allCombinations[i];
965
-
966
- // Add comma separator if not the first item
967
- if (!isFirst) {
968
- chunks.push(',\n');
969
- } else {
970
- isFirst = false;
971
- }
972
-
973
- // Add the JSON stringified expression
974
- chunks.push(JSON.stringify(fullExpression, null, 2));
975
-
976
- combinationCount++;
977
-
978
- // Update progress every 1000 combinations
979
- if (combinationCount % 1000 === 0) {
980
- const progress = Math.round((combinationCount / totalCombinations) * 100);
981
- errorsDiv.innerHTML = `<div class="info-message">
982
- ⏳ Generating ${totalCombinations.toLocaleString()} expression configurations... ${progress}%
983
- </div>`;
984
-
985
- // Update button progress
986
- progressBarFill.style.width = `${progress}%`;
987
- progressText.textContent = `Generating... ${progress}%`;
988
-
989
- // Allow UI to update
990
- await new Promise(resolve => setTimeout(resolve, 0));
991
- }
992
- }
993
-
994
- // End JSON array
995
- chunks.push('\n]');
996
-
997
- // Create blob from chunks
998
- const blob = new Blob(chunks, { type: 'application/json' });
999
- const url = URL.createObjectURL(blob);
1000
- const a = document.createElement('a');
1001
- a.href = url;
1002
- a.download = 'expressions_with_settings.json';
1003
- document.body.appendChild(a);
1004
- a.click();
1005
- document.body.removeChild(a);
1006
- URL.revokeObjectURL(url);
1007
-
1008
- // Close modal and show success
1009
- closeSettingsModal();
1010
- errorsDiv.innerHTML = `<div class="success-message">
1011
- ✓ Downloaded ${combinationCount.toLocaleString()} expression configurations as expressions_with_settings.json
1012
- </div>`;
1013
-
1014
- } catch (error) {
1015
- console.error('Error generating file:', error);
1016
- errorsDiv.innerHTML = `<div class="error-message">
1017
- ❌ Error generating file: ${error.message}
1018
- </div>`;
1019
- } finally {
1020
- // Restore button state
1021
- if (button) {
1022
- button.disabled = false;
1023
- btnText.style.display = 'inline';
1024
- btnProgress.style.display = 'none';
1025
- progressBarFill.style.width = '0%';
1026
- }
1027
- }
1028
- }
1029
-
1030
- // Store test results globally
1031
- let allTestResults = [];
1032
-
1033
- // Generate and test expressions with BRAIN API
1034
- async function generateAndTest() {
1035
- const { settings, variations, types } = parseSettingValues();
1036
-
1037
- // Check if user is logged in to BRAIN using the proper method
1038
- if (!window.brainAPI || !window.brainAPI.isConnectedToBrain()) {
1039
- alert('Please connect to BRAIN first before testing expressions.');
1040
- return;
1041
- }
1042
-
1043
- // Get the session ID from the global variable
1044
- const sessionId = brainSessionId;
1045
- if (!sessionId) {
1046
- alert('BRAIN session not found. Please reconnect to BRAIN.');
1047
- return;
1048
- }
1049
-
1050
- // Always include instrumentType and language
1051
- settings.instrumentType = settings.instrumentType || "EQUITY";
1052
- settings.language = settings.language || "FASTEXPR";
1053
-
1054
- // Generate all setting combinations
1055
- const settingCombinations = generateSettingCombinations(settings, variations);
1056
-
1057
- // Create all expression-setting combinations
1058
- const allCombinations = [];
1059
-
1060
- allDecodedExpressions.forEach(expr => {
1061
- settingCombinations.forEach(settingCombo => {
1062
- const fullExpression = {
1063
- type: "REGULAR",
1064
- settings: settingCombo,
1065
- regular: expr.replace(/\n/g, '') // Remove newline characters
1066
- };
1067
- allCombinations.push(fullExpression);
1068
- });
1069
- });
1070
-
1071
- // Randomly pick one expression to test
1072
- const randomIndex = Math.floor(Math.random() * allCombinations.length);
1073
- const testExpression = allCombinations[randomIndex];
1074
-
1075
- // Close settings modal and open test results modal
1076
- closeSettingsModal();
1077
- openBrainTestResultsModal();
1078
-
1079
- // Show progress
1080
- const progressDiv = document.getElementById('brainTestProgress');
1081
- const progressBarFill = document.getElementById('progressBarFill');
1082
- const progressText = document.getElementById('progressText');
1083
- const resultsDiv = document.getElementById('brainTestResults');
1084
-
1085
- progressDiv.style.display = 'block';
1086
- resultsDiv.innerHTML = '';
1087
- allTestResults = [];
1088
-
1089
- // Test the single randomly selected expression
1090
- progressText.textContent = `Testing expression ${randomIndex + 1} of ${allCombinations.length} (randomly selected)...`;
1091
- progressBarFill.style.width = '50%';
1092
-
1093
- try {
1094
- const response = await fetch('/api/test-expression', {
1095
- method: 'POST',
1096
- headers: {
1097
- 'Content-Type': 'application/json',
1098
- 'Session-ID': sessionId
1099
- },
1100
- body: JSON.stringify(testExpression)
1101
- });
1102
-
1103
- const result = await response.json();
1104
-
1105
- // Store result
1106
- const testResult = {
1107
- expression: testExpression.regular,
1108
- settings: testExpression.settings,
1109
- success: result.success,
1110
- status: result.status || (result.success ? 'SUCCESS' : 'ERROR'),
1111
- message: result.message || result.error || 'Unknown error',
1112
- details: result,
1113
- totalPossible: allCombinations.length,
1114
- testedIndex: randomIndex + 1
1115
- };
1116
-
1117
- allTestResults.push(testResult);
1118
-
1119
- // Update progress
1120
- progressBarFill.style.width = '100%';
1121
- progressText.textContent = 'Test completed!';
1122
-
1123
- // Hide progress after a short delay
1124
- setTimeout(() => {
1125
- progressDiv.style.display = 'none';
1126
-
1127
- // Display the result
1128
- displaySingleTestResult(testResult);
1129
-
1130
- // Show download buttons
1131
- document.getElementById('downloadTestResultsBtn').style.display = 'inline-block';
1132
- document.getElementById('downloadExpressionWithSettingsBtn').style.display = 'inline-block';
1133
- }, 500);
1134
-
1135
- } catch (error) {
1136
- const testResult = {
1137
- expression: testExpression.regular,
1138
- settings: testExpression.settings,
1139
- success: false,
1140
- status: 'ERROR',
1141
- message: `Network error: ${error.message}`,
1142
- details: { error: error.message },
1143
- totalPossible: allCombinations.length,
1144
- testedIndex: randomIndex + 1
1145
- };
1146
- allTestResults.push(testResult);
1147
-
1148
- progressDiv.style.display = 'none';
1149
- displaySingleTestResult(testResult);
1150
- document.getElementById('downloadTestResultsBtn').style.display = 'inline-block';
1151
- document.getElementById('downloadExpressionWithSettingsBtn').style.display = 'inline-block';
1152
- }
1153
- }
1154
-
1155
- // Display single test result
1156
- function displaySingleTestResult(result) {
1157
- const resultsDiv = document.getElementById('brainTestResults');
1158
-
1159
- // Add summary info
1160
- const summaryDiv = document.createElement('div');
1161
- summaryDiv.className = 'test-summary';
1162
- summaryDiv.innerHTML = `
1163
- <h4>Random Test Result</h4>
1164
- <p>Randomly selected expression #${result.testedIndex} out of ${result.totalPossible} possible combinations</p>
1165
- `;
1166
- resultsDiv.appendChild(summaryDiv);
1167
-
1168
- // Add the test result
1169
- const resultItem = document.createElement('div');
1170
- resultItem.className = `test-result-item ${result.success && result.status !== 'ERROR' ? 'success' : 'error'}`;
1171
-
1172
- const expressionDiv = document.createElement('div');
1173
- expressionDiv.className = 'test-result-expression';
1174
- expressionDiv.innerHTML = `<strong>Expression:</strong> ${result.expression}`;
1175
-
1176
- // Display the message as it appears in the notebook
1177
- const messageDiv = document.createElement('div');
1178
- messageDiv.className = 'test-result-message';
1179
- messageDiv.style.whiteSpace = 'pre-wrap';
1180
- messageDiv.style.fontFamily = 'monospace';
1181
- messageDiv.style.backgroundColor = '#f5f5f5';
1182
- messageDiv.style.padding = '10px';
1183
- messageDiv.style.borderRadius = '4px';
1184
- messageDiv.style.marginTop = '10px';
1185
-
1186
- // Format the message - if it's the full response object, show it nicely
1187
- if (result.details && result.details.full_response) {
1188
- const fullResponse = result.details.full_response;
1189
-
1190
- // If it's an object with the expected structure, format it nicely
1191
- if (typeof fullResponse === 'object' && fullResponse.id && fullResponse.type && fullResponse.status) {
1192
- // Format like Python dict output
1193
- messageDiv.textContent = JSON.stringify(fullResponse, null, 2).replace(/"/g, "'");
1194
- } else if (typeof fullResponse === 'object') {
1195
- // For other objects, just stringify them
1196
- messageDiv.textContent = JSON.stringify(fullResponse, null, 2);
1197
- } else {
1198
- // For non-objects, show the message string
1199
- messageDiv.textContent = result.message;
1200
- }
1201
- } else {
1202
- // Fallback to simple message
1203
- messageDiv.textContent = result.message;
1204
- }
1205
-
1206
- resultItem.appendChild(expressionDiv);
1207
- resultItem.appendChild(messageDiv);
1208
-
1209
- // Add settings info
1210
- const settingsDiv = document.createElement('div');
1211
- settingsDiv.className = 'test-result-message';
1212
- settingsDiv.innerHTML = '<strong>Settings used:</strong>';
1213
- const settingsList = document.createElement('ul');
1214
- settingsList.style.margin = '5px 0';
1215
- settingsList.style.paddingLeft = '20px';
1216
-
1217
- for (const [key, value] of Object.entries(result.settings)) {
1218
- const li = document.createElement('li');
1219
- li.textContent = `${key}: ${value}`;
1220
- settingsList.appendChild(li);
1221
- }
1222
-
1223
- settingsDiv.appendChild(settingsList);
1224
- resultItem.appendChild(settingsDiv);
1225
-
1226
- resultsDiv.appendChild(resultItem);
1227
- }
1228
-
1229
- // Compatibility wrapper for old function name
1230
- function addTestResultToDisplay(result, index) {
1231
- // Add index info to result if not present
1232
- if (!result.testedIndex) {
1233
- result.testedIndex = index;
1234
- }
1235
- if (!result.totalPossible) {
1236
- result.totalPossible = allDecodedExpressions.length;
1237
- }
1238
- displaySingleTestResult(result);
1239
- }
1240
-
1241
- // Show test summary (kept for compatibility)
1242
- function showTestSummary(total, success, error) {
1243
- const resultsDiv = document.getElementById('brainTestResults');
1244
-
1245
- const summaryDiv = document.createElement('div');
1246
- summaryDiv.className = 'test-summary';
1247
- summaryDiv.innerHTML = `
1248
- <h4>Test Summary</h4>
1249
- <div class="test-summary-stats">
1250
- <div class="test-summary-stat">
1251
- <div class="test-summary-stat-value">${total}</div>
1252
- <div class="test-summary-stat-label">Total Tests</div>
1253
- </div>
1254
- <div class="test-summary-stat" style="color: #28a745;">
1255
- <div class="test-summary-stat-value">${success}</div>
1256
- <div class="test-summary-stat-label">Successful</div>
1257
- </div>
1258
- <div class="test-summary-stat" style="color: #dc3545;">
1259
- <div class="test-summary-stat-value">${error}</div>
1260
- <div class="test-summary-stat-label">Errors</div>
1261
- </div>
1262
- <div class="test-summary-stat">
1263
- <div class="test-summary-stat-value">${((success / total) * 100).toFixed(1)}%</div>
1264
- <div class="test-summary-stat-label">Success Rate</div>
1265
- </div>
1266
- </div>
1267
- `;
1268
-
1269
- resultsDiv.insertBefore(summaryDiv, resultsDiv.firstChild);
1270
- }
1271
-
1272
- // Open test results modal
1273
- function openBrainTestResultsModal() {
1274
- const modal = document.getElementById('brainTestResultsModal');
1275
- modal.style.display = 'block';
1276
-
1277
- // Hide buttons initially - they will be shown when test is completed
1278
- document.getElementById('downloadTestResultsBtn').style.display = 'none';
1279
- document.getElementById('downloadExpressionWithSettingsBtn').style.display = 'none';
1280
- }
1281
-
1282
- // Close test results modal
1283
- function closeBrainTestResultsModal() {
1284
- const modal = document.getElementById('brainTestResultsModal');
1285
- modal.style.display = 'none';
1286
-
1287
- // Hide buttons when modal is closed
1288
- document.getElementById('downloadTestResultsBtn').style.display = 'none';
1289
- document.getElementById('downloadExpressionWithSettingsBtn').style.display = 'none';
1290
- }
1291
-
1292
- // Download test results
1293
- function goToSimulator() {
1294
- // Navigate to the simulator page
1295
- window.location.href = '/simulator';
1296
- }
1297
-
1298
- function downloadTestResults() {
1299
- const results = allTestResults.map(result => ({
1300
- expression: result.expression,
1301
- settings: result.settings,
1302
- status: result.status,
1303
- message: result.message,
1304
- details: result.details
1305
- }));
1306
-
1307
- const jsonContent = JSON.stringify(results, null, 2);
1308
-
1309
- const blob = new Blob([jsonContent], { type: 'application/json' });
1310
- const url = URL.createObjectURL(blob);
1311
- const a = document.createElement('a');
1312
- a.href = url;
1313
- a.download = 'brain_test_results.json';
1314
- document.body.appendChild(a);
1315
- a.click();
1316
- document.body.removeChild(a);
1317
- URL.revokeObjectURL(url);
1318
-
1319
- const errorsDiv = document.getElementById('grammarErrors');
1320
- errorsDiv.innerHTML = `<div class="success-message">
1321
- ✓ Downloaded test results for ${allTestResults.length} expressions
1322
- </div>`;
1323
- }
1324
-
1325
- // Confirm and download expression with settings with shuffle option
1326
- function confirmAndDownloadExpressionWithSettings() {
1327
- const { settings, variations, types } = parseSettingValues();
1328
- const settingCombinations = generateSettingCombinations(settings, variations);
1329
- const totalCombinations = allDecodedExpressions.length * settingCombinations.length;
1330
-
1331
- if (totalCombinations > 1000) {
1332
- // Show confirmation dialog for large datasets
1333
- const shouldShuffle = confirm(`即将生成 ${totalCombinations.toLocaleString()} 个表达式配置。\n\n是否需要随机打乱表达式顺序?\n\n点击"确定"进行随机打乱\n点击"取消"保持原始顺序`);
1334
- downloadExpressionWithSettings(shouldShuffle);
1335
- } else {
1336
- // For small datasets, ask if user wants to shuffle
1337
- const shouldShuffle = confirm(`即将生成 ${totalCombinations.toLocaleString()} 个表达式配置。\n\n是否需要随机打乱表达式顺序?\n\n点击"确定"进行随机打乱\n点击"取消"保持原始顺序`);
1338
- downloadExpressionWithSettings(shouldShuffle);
1339
- }
1340
- }
1341
-
1342
- // Download expression with settings (same as Generate & Download)
1343
- async function downloadExpressionWithSettings(shouldShuffle = false) {
1344
- // Get current settings from the modal (same logic as applySettings)
1345
- const { settings, variations, types } = parseSettingValues();
1346
-
1347
- // Always include instrumentType and language
1348
- settings.instrumentType = settings.instrumentType || "EQUITY";
1349
- settings.language = settings.language || "FASTEXPR";
1350
-
1351
- // Generate all setting combinations
1352
- const settingCombinations = generateSettingCombinations(settings, variations);
1353
-
1354
- // Calculate total combinations for progress tracking
1355
- const totalCombinations = allDecodedExpressions.length * settingCombinations.length;
1356
-
1357
- // Get the button and show progress
1358
- const button = document.getElementById('downloadExpressionWithSettingsBtn');
1359
- const btnText = button.querySelector('.btn-text');
1360
- const btnProgress = button.querySelector('.btn-progress');
1361
- const progressBarFill = button.querySelector('.progress-bar-fill');
1362
- const progressText = button.querySelector('.progress-text');
1363
-
1364
- // Disable button and show progress
1365
- button.disabled = true;
1366
- btnText.style.display = 'none';
1367
- btnProgress.style.display = 'flex';
1368
-
1369
- // Show progress to user
1370
- const errorsDiv = document.getElementById('grammarErrors');
1371
- errorsDiv.innerHTML = `<div class="info-message">
1372
- ⏳ Generating ${totalCombinations.toLocaleString()} expression configurations...
1373
- </div>`;
1374
-
1375
- // Use streaming approach to handle large files
1376
- try {
1377
- // Create a writable stream for the file
1378
- const chunks = [];
1379
- let isFirst = true;
1380
-
1381
- // Start JSON array
1382
- chunks.push('[\n');
1383
-
1384
- let combinationCount = 0;
1385
-
1386
- // Create all combinations first
1387
- const allCombinations = [];
1388
- for (let exprIndex = 0; exprIndex < allDecodedExpressions.length; exprIndex++) {
1389
- const expr = allDecodedExpressions[exprIndex];
1390
-
1391
- for (let settingIndex = 0; settingIndex < settingCombinations.length; settingIndex++) {
1392
- const settingCombo = settingCombinations[settingIndex];
1393
-
1394
- const fullExpression = {
1395
- type: "REGULAR",
1396
- settings: settingCombo,
1397
- regular: expr.replace(/\n/g, '') // Remove newline characters
1398
- };
1399
-
1400
- allCombinations.push(fullExpression);
1401
- }
1402
- }
1403
-
1404
- // Shuffle if requested
1405
- if (shouldShuffle) {
1406
- // Fisher-Yates shuffle algorithm
1407
- for (let i = allCombinations.length - 1; i > 0; i--) {
1408
- const j = Math.floor(Math.random() * (i + 1));
1409
- [allCombinations[i], allCombinations[j]] = [allCombinations[j], allCombinations[i]];
1410
- }
1411
- }
1412
-
1413
- // Process combinations in order (original or shuffled)
1414
- for (let i = 0; i < allCombinations.length; i++) {
1415
- const fullExpression = allCombinations[i];
1416
-
1417
- // Add comma separator if not the first item
1418
- if (!isFirst) {
1419
- chunks.push(',\n');
1420
- } else {
1421
- isFirst = false;
1422
- }
1423
-
1424
- // Add the JSON stringified expression
1425
- chunks.push(JSON.stringify(fullExpression, null, 2));
1426
-
1427
- combinationCount++;
1428
-
1429
- // Update progress every 1000 combinations
1430
- if (combinationCount % 1000 === 0) {
1431
- const progress = Math.round((combinationCount / totalCombinations) * 100);
1432
- errorsDiv.innerHTML = `<div class="info-message">
1433
- ⏳ Generating ${totalCombinations.toLocaleString()} expression configurations... ${progress}%
1434
- </div>`;
1435
-
1436
- // Update button progress
1437
- progressBarFill.style.width = `${progress}%`;
1438
- progressText.textContent = `Generating... ${progress}%`;
1439
-
1440
- // Allow UI to update
1441
- await new Promise(resolve => setTimeout(resolve, 0));
1442
- }
1443
- }
1444
-
1445
- // End JSON array
1446
- chunks.push('\n]');
1447
-
1448
- // Create blob from chunks
1449
- const blob = new Blob(chunks, { type: 'application/json' });
1450
- const url = URL.createObjectURL(blob);
1451
- const a = document.createElement('a');
1452
- a.href = url;
1453
- a.download = 'expressions_with_settings.json';
1454
- document.body.appendChild(a);
1455
- a.click();
1456
- document.body.removeChild(a);
1457
- URL.revokeObjectURL(url);
1458
-
1459
- errorsDiv.innerHTML = `<div class="success-message">
1460
- ✓ Downloaded ${combinationCount.toLocaleString()} expression configurations as expressions_with_settings.json
1461
- </div>`;
1462
-
1463
- } catch (error) {
1464
- console.error('Error generating file:', error);
1465
- errorsDiv.innerHTML = `<div class="error-message">
1466
- ❌ Error generating file: ${error.message}
1467
- </div>`;
1468
- } finally {
1469
- // Restore button state
1470
- if (button) {
1471
- button.disabled = false;
1472
- btnText.style.display = 'inline';
1473
- btnProgress.style.display = 'none';
1474
- progressBarFill.style.width = '0%';
1475
- }
1476
- }
1477
- }
1478
-
1479
- /**
1480
- * Handle loading generated expressions from a JSON file
1481
- * Expected format: ["expr1", "expr2", ...]
1482
- */
1483
- function handleGeneratedExpressionsFileSelect(event) {
1484
- const file = event.target.files[0];
1485
- if (!file) return;
1486
-
1487
- const reader = new FileReader();
1488
- reader.onload = function(e) {
1489
- try {
1490
- const content = e.target.result;
1491
- const expressions = JSON.parse(content);
1492
-
1493
- if (!Array.isArray(expressions)) {
1494
- alert('Error: File content must be a JSON array of strings.');
1495
- return;
1496
- }
1497
-
1498
- // Validate that it's a list of strings (check first item)
1499
- if (expressions.length > 0 && typeof expressions[0] !== 'string') {
1500
- alert('Error: File content must be a JSON array of strings.');
1501
- return;
1502
- }
1503
-
1504
- // Set global variable
1505
- allDecodedExpressions = expressions;
1506
-
1507
- // Display results
1508
- displayDecodedResults(allDecodedExpressions.slice(0, MAX_DISPLAY_RESULTS), allDecodedExpressions.length);
1509
-
1510
- // Switch to results tab
1511
- const resultsTab = document.querySelector('[data-page="results"]');
1512
- if (resultsTab) {
1513
- resultsTab.style.display = 'flex'; // Ensure it's visible
1514
- resultsTab.click();
1515
- }
1516
-
1517
- // Update badge
1518
- const resultsBadge = document.getElementById('resultsBadge');
1519
- if (resultsBadge) {
1520
- resultsBadge.textContent = expressions.length.toLocaleString();
1521
- }
1522
-
1523
- // Reset file input
1524
- event.target.value = '';
1525
-
1526
- // Show success message in grammar errors div (if visible) or alert
1527
- const errorsDiv = document.getElementById('grammarErrors');
1528
- if (errorsDiv) {
1529
- errorsDiv.innerHTML = `<div class="success-message">
1530
- ✓ Successfully loaded ${expressions.length.toLocaleString()} expressions from file.
1531
- </div>`;
1532
- }
1533
-
1534
- } catch (error) {
1535
- console.error('Error parsing JSON:', error);
1536
- alert('Error parsing JSON file: ' + error.message);
1537
- }
1538
- };
1539
- reader.readAsText(file);
1540
- }