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.
- {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/METADATA +1 -1
- cnhkmcp-2.1.3.dist-info/RECORD +6 -0
- cnhkmcp-2.1.3.dist-info/top_level.txt +1 -0
- cnhkmcp/__init__.py +0 -125
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- cnhkmcp/untracked/APP/.gitignore +0 -32
- cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md +0 -112
- cnhkmcp/untracked/APP/README.md +0 -309
- cnhkmcp/untracked/APP/Tranformer/Transformer.py +0 -4985
- cnhkmcp/untracked/APP/Tranformer/ace.log +0 -0
- cnhkmcp/untracked/APP/Tranformer/ace_lib.py +0 -1510
- cnhkmcp/untracked/APP/Tranformer/helpful_functions.py +0 -180
- cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates.json +0 -2421
- cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates_/321/207/320/264/342/225/221/321/204/342/225/233/320/233.json +0 -654
- cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_error.json +0 -1034
- cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_success.json +0 -444
- 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
- cnhkmcp/untracked/APP/Tranformer/parsetab.py +0 -60
- cnhkmcp/untracked/APP/Tranformer/template_summary.txt +0 -3182
- cnhkmcp/untracked/APP/Tranformer/transformer_config.json +0 -7
- cnhkmcp/untracked/APP/Tranformer/validator.py +0 -889
- cnhkmcp/untracked/APP/ace.log +0 -69
- cnhkmcp/untracked/APP/ace_lib.py +0 -1510
- cnhkmcp/untracked/APP/blueprints/__init__.py +0 -6
- cnhkmcp/untracked/APP/blueprints/feature_engineering.py +0 -347
- cnhkmcp/untracked/APP/blueprints/idea_house.py +0 -221
- cnhkmcp/untracked/APP/blueprints/inspiration_house.py +0 -432
- cnhkmcp/untracked/APP/blueprints/paper_analysis.py +0 -570
- cnhkmcp/untracked/APP/custom_templates/templates.json +0 -1257
- cnhkmcp/untracked/APP/give_me_idea/BRAIN_Alpha_Template_Expert_SystemPrompt.md +0 -400
- cnhkmcp/untracked/APP/give_me_idea/ace_lib.py +0 -1510
- cnhkmcp/untracked/APP/give_me_idea/alpha_data_specific_template_master.py +0 -252
- cnhkmcp/untracked/APP/give_me_idea/fetch_all_datasets.py +0 -157
- cnhkmcp/untracked/APP/give_me_idea/fetch_all_operators.py +0 -99
- cnhkmcp/untracked/APP/give_me_idea/helpful_functions.py +0 -180
- cnhkmcp/untracked/APP/give_me_idea/what_is_Alpha_template.md +0 -11
- cnhkmcp/untracked/APP/helpful_functions.py +0 -180
- cnhkmcp/untracked/APP/hkSimulator/ace_lib.py +0 -1497
- cnhkmcp/untracked/APP/hkSimulator/autosimulator.py +0 -447
- cnhkmcp/untracked/APP/hkSimulator/helpful_functions.py +0 -180
- cnhkmcp/untracked/APP/mirror_config.txt +0 -20
- cnhkmcp/untracked/APP/operaters.csv +0 -129
- cnhkmcp/untracked/APP/requirements.txt +0 -53
- cnhkmcp/untracked/APP/run_app.bat +0 -28
- cnhkmcp/untracked/APP/run_app.sh +0 -34
- cnhkmcp/untracked/APP/setup_tsinghua.bat +0 -39
- cnhkmcp/untracked/APP/setup_tsinghua.sh +0 -43
- cnhkmcp/untracked/APP/simulator/alpha_submitter.py +0 -404
- cnhkmcp/untracked/APP/simulator/simulator_wqb.py +0 -618
- cnhkmcp/untracked/APP/ssrn-3332513.pdf +6 -109201
- cnhkmcp/untracked/APP/static/brain.js +0 -589
- cnhkmcp/untracked/APP/static/decoder.js +0 -1540
- cnhkmcp/untracked/APP/static/feature_engineering.js +0 -1729
- cnhkmcp/untracked/APP/static/idea_house.js +0 -937
- cnhkmcp/untracked/APP/static/inspiration.js +0 -465
- cnhkmcp/untracked/APP/static/inspiration_house.js +0 -868
- cnhkmcp/untracked/APP/static/paper_analysis.js +0 -390
- cnhkmcp/untracked/APP/static/script.js +0 -3082
- cnhkmcp/untracked/APP/static/simulator.js +0 -597
- cnhkmcp/untracked/APP/static/styles.css +0 -3127
- cnhkmcp/untracked/APP/static/usage_widget.js +0 -508
- cnhkmcp/untracked/APP/templates/alpha_inspector.html +0 -511
- cnhkmcp/untracked/APP/templates/feature_engineering.html +0 -960
- cnhkmcp/untracked/APP/templates/idea_house.html +0 -564
- cnhkmcp/untracked/APP/templates/index.html +0 -932
- cnhkmcp/untracked/APP/templates/inspiration_house.html +0 -861
- cnhkmcp/untracked/APP/templates/paper_analysis.html +0 -91
- cnhkmcp/untracked/APP/templates/simulator.html +0 -343
- cnhkmcp/untracked/APP/templates/transformer_web.html +0 -580
- cnhkmcp/untracked/APP/usage.md +0 -351
- 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
- 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
- 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
- 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
- cnhkmcp/untracked/arXiv_API_Tool_Manual.md +0 -490
- cnhkmcp/untracked/arxiv_api.py +0 -229
- cnhkmcp/untracked/forum_functions.py +0 -998
- 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
- 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
- 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
- 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
- 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
- cnhkmcp/untracked/platform_functions.py +0 -2886
- cnhkmcp/untracked/sample_mcp_config.json +0 -11
- cnhkmcp/untracked/user_config.json +0 -31
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- cnhkmcp-2.1.2.dist-info/RECORD +0 -111
- cnhkmcp-2.1.2.dist-info/top_level.txt +0 -1
- {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/WHEEL +0 -0
- {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/entry_points.txt +0 -0
- {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;"><${t}/></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;"><${t}/></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
|
-
}
|