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