cnhkmcp 1.3.6__py3-none-any.whl → 1.3.8__py3-none-any.whl

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