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