|
|
/**
|
|
|
* 模板解码模块 - 完整列表迭代方法
|
|
|
*
|
|
|
* 此模块使用笛卡尔积方法处理模板表达式的解码。
|
|
|
* 它依赖于 script.js 中定义的全局 'templates' 变量
|
|
|
*
|
|
|
* 函数列表:
|
|
|
* - decodeTemplates(): 解码模板的主函数
|
|
|
* - generateCombinations(): 使用笛卡尔积生成所有可能的组合
|
|
|
* - displayDecodedResults(): 显示解码结果
|
|
|
* - searchResults(): 在所有解码结果中搜索
|
|
|
* - copySingleResult(): 复制单个结果到剪贴板
|
|
|
* - copyAllResults(): 复制所有结果到剪贴板
|
|
|
* - downloadResults(): 将结果下载为文本文件
|
|
|
*/
|
|
|
|
|
|
// 全局变量,用于存储所有解码后的表达式以供搜索
|
|
|
let allDecodedExpressions = [];
|
|
|
let displayedExpressions = [];
|
|
|
const MAX_DISPLAY_RESULTS = 999;
|
|
|
|
|
|
// 使用完整列表方法解码模板
|
|
|
function decodeTemplates() {
|
|
|
const editor = document.getElementById('expressionEditor');
|
|
|
const expression = editor.value.trim();
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
|
|
|
// 检查表达式是否为空
|
|
|
if (!expression) {
|
|
|
errorsDiv.innerHTML = '<div class="error-item"><strong>错误:</strong> 请输入要解码的表达式</div>';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 首先,检测所有模板
|
|
|
const templateRegex = /<(\w+)\/>/g;
|
|
|
const matches = [...expression.matchAll(templateRegex)];
|
|
|
const uniqueTemplates = [...new Set(matches.map(match => match[1]))];
|
|
|
|
|
|
// 检查是否有要解码的模板
|
|
|
if (uniqueTemplates.length === 0) {
|
|
|
errorsDiv.innerHTML = '<div class="error-item"><strong>错误:</strong> 在要解码的表达式中未找到模板</div>';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 检查是否所有模板都已配置
|
|
|
const unconfigured = [];
|
|
|
const templateValues = new Map();
|
|
|
|
|
|
uniqueTemplates.forEach(templateName => {
|
|
|
const template = templates.get(templateName);
|
|
|
if (!template || !template.variables || template.variables.length === 0) {
|
|
|
unconfigured.push(templateName);
|
|
|
} else {
|
|
|
templateValues.set(templateName, template.variables);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 如果有任何模板未配置,显示错误
|
|
|
if (unconfigured.length > 0) {
|
|
|
errorsDiv.innerHTML = `<div class="error-item">
|
|
|
<strong>错误:</strong> 以下模板在解码前需要配置:
|
|
|
${unconfigured.map(t => `<span class="template-name" style="font-family: monospace;"><${t}/></span>`).join(', ')}
|
|
|
</div>`;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 计算总组合数
|
|
|
let totalCombinations = 1;
|
|
|
templateValues.forEach(values => {
|
|
|
totalCombinations *= values.length;
|
|
|
});
|
|
|
|
|
|
// 如果组合数过多,发出警告
|
|
|
if (totalCombinations > 1000) {
|
|
|
if (!confirm(`这将生成 ${totalCombinations} 个表达式。可能需要一些时间。继续吗?`)) {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 生成所有组合(笛卡尔积)
|
|
|
const combinations = generateCombinations(templateValues);
|
|
|
|
|
|
// 生成解码后的表达式
|
|
|
const decodedExpressions = combinations.map(combination => {
|
|
|
let decodedExpression = expression;
|
|
|
combination.forEach(({template, value}) => {
|
|
|
const regex = new RegExp(`<${template}/>`, 'g');
|
|
|
decodedExpression = decodedExpression.replace(regex, value);
|
|
|
});
|
|
|
return decodedExpression;
|
|
|
});
|
|
|
|
|
|
// 全局存储所有表达式
|
|
|
allDecodedExpressions = decodedExpressions;
|
|
|
|
|
|
// 显示结果(限制为 MAX_DISPLAY_RESULTS)
|
|
|
displayDecodedResults(decodedExpressions.slice(0, MAX_DISPLAY_RESULTS), decodedExpressions.length);
|
|
|
|
|
|
// 清除错误并显示成功信息
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 使用完整列表方法成功解码 ${decodedExpressions.length} 个表达式
|
|
|
${decodedExpressions.length > MAX_DISPLAY_RESULTS ?
|
|
|
`<br>⚠️ 显示前 ${MAX_DISPLAY_RESULTS} 个结果。使用搜索查找特定表达式。` : ''}
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
// 生成模板值的所有组合(笛卡尔积)
|
|
|
function generateCombinations(templateValues) {
|
|
|
const templates = Array.from(templateValues.keys());
|
|
|
if (templates.length === 0) return [];
|
|
|
|
|
|
const combinations = [];
|
|
|
|
|
|
function generate(index, current) {
|
|
|
if (index === templates.length) {
|
|
|
combinations.push([...current]);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const template = templates[index];
|
|
|
const values = templateValues.get(template);
|
|
|
|
|
|
for (const value of values) {
|
|
|
current.push({template, value});
|
|
|
generate(index + 1, current);
|
|
|
current.pop();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
generate(0, []);
|
|
|
return combinations;
|
|
|
}
|
|
|
|
|
|
// 显示解码结果
|
|
|
function displayDecodedResults(expressions, totalCount = null, isRandom = false) {
|
|
|
const resultsList = document.getElementById('resultsList');
|
|
|
|
|
|
// 清除之前的结果
|
|
|
resultsList.innerHTML = '';
|
|
|
|
|
|
// 如果结果数量超过显示数量,添加搜索框(仅用于完整迭代)
|
|
|
if (!isRandom && totalCount && totalCount > MAX_DISPLAY_RESULTS) {
|
|
|
const searchContainer = document.createElement('div');
|
|
|
searchContainer.className = 'results-search-container';
|
|
|
searchContainer.innerHTML = `
|
|
|
<input type="text" id="resultsSearchInput" class="results-search-input"
|
|
|
placeholder="在所有 ${totalCount} 个表达式中搜索...">
|
|
|
<button id="resultsSearchBtn" class="btn btn-secondary btn-small">搜索</button>
|
|
|
<button id="resultsClearSearchBtn" class="btn btn-outline btn-small" style="display: none;">清除搜索</button>
|
|
|
`;
|
|
|
resultsList.appendChild(searchContainer);
|
|
|
|
|
|
// 为搜索添加事件监听器
|
|
|
document.getElementById('resultsSearchBtn').addEventListener('click', searchResults);
|
|
|
document.getElementById('resultsSearchInput').addEventListener('keypress', (e) => {
|
|
|
if (e.key === 'Enter') searchResults();
|
|
|
});
|
|
|
document.getElementById('resultsClearSearchBtn').addEventListener('click', clearSearch);
|
|
|
}
|
|
|
|
|
|
// 添加关于结果数量的信息
|
|
|
if (expressions.length > 0) {
|
|
|
const infoDiv = document.createElement('div');
|
|
|
infoDiv.className = 'results-info';
|
|
|
if (isRandom) {
|
|
|
// 对于随机结果,显示实际选择的数量与总组合数的对比
|
|
|
const actualSelectedCount = allDecodedExpressions.length;
|
|
|
if (actualSelectedCount > expressions.length) {
|
|
|
infoDiv.innerHTML = `从 <strong>${totalCount}</strong> 个总组合中随机选择了 <strong>${actualSelectedCount}</strong> 个表达式<br>
|
|
|
<small>显示前 <strong>${expressions.length}</strong> 个结果。下载将包含所有 <strong>${actualSelectedCount}</strong> 个表达式。</small>`;
|
|
|
} else {
|
|
|
infoDiv.innerHTML = `从 <strong>${totalCount}</strong> 个总组合中随机选择了 <strong>${expressions.length}</strong> 个表达式`;
|
|
|
}
|
|
|
} else if (totalCount && totalCount > expressions.length) {
|
|
|
infoDiv.innerHTML = `总共生成了 <strong>${totalCount}</strong> 个表达式。
|
|
|
显示 <strong>${expressions.length}</strong> 个结果
|
|
|
${expressions.length === MAX_DISPLAY_RESULTS ? '(前999个)' : '(已过滤)'}。`;
|
|
|
} else {
|
|
|
infoDiv.textContent = `使用完整列表迭代生成了 ${expressions.length} 个表达式`;
|
|
|
}
|
|
|
resultsList.appendChild(infoDiv);
|
|
|
}
|
|
|
|
|
|
// 全局存储显示的表达式
|
|
|
displayedExpressions = expressions;
|
|
|
|
|
|
// 添加每个表达式
|
|
|
expressions.forEach((expr, index) => {
|
|
|
const resultItem = document.createElement('div');
|
|
|
resultItem.className = 'result-item';
|
|
|
|
|
|
const number = document.createElement('span');
|
|
|
number.className = 'result-number';
|
|
|
number.textContent = `${index + 1}.`;
|
|
|
|
|
|
const expression = document.createElement('span');
|
|
|
expression.className = 'result-expression';
|
|
|
expression.textContent = expr;
|
|
|
|
|
|
resultItem.appendChild(number);
|
|
|
resultItem.appendChild(expression);
|
|
|
// 复制按钮已禁用
|
|
|
// resultItem.appendChild(copyBtn);
|
|
|
resultsList.appendChild(resultItem);
|
|
|
});
|
|
|
|
|
|
// 显示结果标签页并更新徽章
|
|
|
const resultsTab = document.getElementById('resultsTab');
|
|
|
const resultsBadge = document.getElementById('resultsBadge');
|
|
|
resultsTab.style.display = 'flex';
|
|
|
resultsBadge.textContent = totalCount || expressions.length;
|
|
|
|
|
|
// 导航到结果页面
|
|
|
navigateToPage('results');
|
|
|
}
|
|
|
|
|
|
// 在所有结果中搜索
|
|
|
function searchResults() {
|
|
|
const searchInput = document.getElementById('resultsSearchInput');
|
|
|
const searchTerm = searchInput.value.trim().toLowerCase();
|
|
|
|
|
|
if (!searchTerm) {
|
|
|
// 如果搜索为空,再次显示前1000个
|
|
|
displayDecodedResults(allDecodedExpressions.slice(0, MAX_DISPLAY_RESULTS), allDecodedExpressions.length);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 根据搜索词过滤所有表达式
|
|
|
const filteredExpressions = allDecodedExpressions.filter(expr =>
|
|
|
expr.toLowerCase().includes(searchTerm)
|
|
|
);
|
|
|
|
|
|
// 显示过滤后的结果(仍然限制为 MAX_DISPLAY_RESULTS)
|
|
|
displayDecodedResults(filteredExpressions.slice(0, MAX_DISPLAY_RESULTS), allDecodedExpressions.length);
|
|
|
|
|
|
// 显示清除按钮
|
|
|
document.getElementById('resultsClearSearchBtn').style.display = 'inline-block';
|
|
|
|
|
|
// 更新信息消息
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
if (filteredExpressions.length === 0) {
|
|
|
errorsDiv.innerHTML = `<div class="warning-message">
|
|
|
未找到匹配 "${searchTerm}" 的表达式
|
|
|
</div>`;
|
|
|
} else if (filteredExpressions.length > MAX_DISPLAY_RESULTS) {
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
找到 ${filteredExpressions.length} 个表达式匹配 "${searchTerm}"。
|
|
|
显示前 ${MAX_DISPLAY_RESULTS} 个结果。
|
|
|
</div>`;
|
|
|
} else {
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
找到 ${filteredExpressions.length} 个表达式匹配 "${searchTerm}"
|
|
|
</div>`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 清除搜索并显示原始结果
|
|
|
function clearSearch() {
|
|
|
document.getElementById('resultsSearchInput').value = '';
|
|
|
document.getElementById('resultsClearSearchBtn').style.display = 'none';
|
|
|
displayDecodedResults(allDecodedExpressions.slice(0, MAX_DISPLAY_RESULTS), allDecodedExpressions.length);
|
|
|
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 显示总共 ${allDecodedExpressions.length} 个表达式中的前 ${MAX_DISPLAY_RESULTS} 个
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
// 复制单个结果
|
|
|
function copySingleResult(expression) {
|
|
|
navigator.clipboard.writeText(expression).then(() => {
|
|
|
// 显示临时成功消息
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
const prevContent = errorsDiv.innerHTML;
|
|
|
errorsDiv.innerHTML = '<div class="success-message">✓ 已复制到剪贴板</div>';
|
|
|
setTimeout(() => {
|
|
|
errorsDiv.innerHTML = prevContent;
|
|
|
}, 2000);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 复制显示的结果
|
|
|
function copyDisplayedResults() {
|
|
|
// 复制所有当前显示的表达式
|
|
|
const expressions = displayedExpressions.join('\n');
|
|
|
|
|
|
navigator.clipboard.writeText(expressions).then(() => {
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 已复制 ${displayedExpressions.length} 个显示的表达式到剪贴板
|
|
|
</div>`;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 复制所有结果
|
|
|
function copyAllResults() {
|
|
|
// 复制所有生成的表达式
|
|
|
const expressions = allDecodedExpressions.join('\n');
|
|
|
|
|
|
navigator.clipboard.writeText(expressions).then(() => {
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 所有 ${allDecodedExpressions.length} 个表达式已复制到剪贴板
|
|
|
</div>`;
|
|
|
}).catch(err => {
|
|
|
// 处理大剪贴板操作可能出现的错误
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
errorsDiv.innerHTML = `<div class="error-item">
|
|
|
<strong>错误:</strong> 复制到剪贴板失败。数据可能过大。
|
|
|
请改用"下载全部"按钮。
|
|
|
</div>`;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 将结果下载为文本文件
|
|
|
function downloadResults() {
|
|
|
// 下载表达式(全部或随机选择)
|
|
|
const expressions = allDecodedExpressions.join('\n');
|
|
|
|
|
|
const blob = new Blob([expressions], { type: 'text/plain' });
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
a.href = url;
|
|
|
a.download = 'decoded_expressions.txt';
|
|
|
document.body.appendChild(a);
|
|
|
a.click();
|
|
|
document.body.removeChild(a);
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 已将 ${allDecodedExpressions.length} 个表达式下载为 decoded_expressions.txt
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
// 随机迭代 - 生成全部然后随机选择
|
|
|
function randomIteration() {
|
|
|
const editor = document.getElementById('expressionEditor');
|
|
|
const expression = editor.value.trim();
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
const randomCountInput = document.getElementById('randomCount');
|
|
|
const randomCount = parseInt(randomCountInput.value) || 10;
|
|
|
|
|
|
// 检查表达式是否为空
|
|
|
if (!expression) {
|
|
|
errorsDiv.innerHTML = '<div class="error-item"><strong>错误:</strong> 请输入要解码的表达式</div>';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 首先,检测所有模板
|
|
|
const templateRegex = /<(\w+)\/>/g;
|
|
|
const matches = [...expression.matchAll(templateRegex)];
|
|
|
const uniqueTemplates = [...new Set(matches.map(match => match[1]))];
|
|
|
|
|
|
// 检查是否有要解码的模板
|
|
|
if (uniqueTemplates.length === 0) {
|
|
|
errorsDiv.innerHTML = '<div class="error-item"><strong>错误:</strong> 在要解码的表达式中未找到模板</div>';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 检查是否所有模板都已配置
|
|
|
const unconfigured = [];
|
|
|
const templateValues = new Map();
|
|
|
|
|
|
uniqueTemplates.forEach(templateName => {
|
|
|
const template = templates.get(templateName);
|
|
|
if (!template || !template.variables || template.variables.length === 0) {
|
|
|
unconfigured.push(templateName);
|
|
|
} else {
|
|
|
templateValues.set(templateName, template.variables);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 如果有任何模板未配置,显示错误
|
|
|
if (unconfigured.length > 0) {
|
|
|
errorsDiv.innerHTML = `<div class="error-item">
|
|
|
<strong>错误:</strong> 以下模板在解码前需要配置:
|
|
|
${unconfigured.map(t => `<span class="template-name" style="font-family: monospace;"><${t}/></span>`).join(', ')}
|
|
|
</div>`;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 计算总组合数
|
|
|
let totalCombinations = 1;
|
|
|
templateValues.forEach(values => {
|
|
|
totalCombinations *= values.length;
|
|
|
});
|
|
|
|
|
|
// 验证随机数量
|
|
|
if (randomCount > totalCombinations) {
|
|
|
errorsDiv.innerHTML = `<div class="warning-message">
|
|
|
⚠️ 请求了 ${randomCount} 个随机表达式,但只有 ${totalCombinations} 个唯一组合存在。
|
|
|
改为生成所有 ${totalCombinations} 个表达式。
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
// 生成所有组合(笛卡尔积)
|
|
|
const combinations = generateCombinations(templateValues);
|
|
|
|
|
|
// 生成所有解码后的表达式
|
|
|
const allExpressions = combinations.map(combination => {
|
|
|
let decodedExpression = expression;
|
|
|
combination.forEach(({template, value}) => {
|
|
|
const regex = new RegExp(`<${template}/>`, 'g');
|
|
|
decodedExpression = decodedExpression.replace(regex, value);
|
|
|
});
|
|
|
return decodedExpression;
|
|
|
});
|
|
|
|
|
|
// 随机选择请求数量的表达式
|
|
|
const selectedExpressions = [];
|
|
|
const actualCount = Math.min(randomCount, allExpressions.length);
|
|
|
|
|
|
if (actualCount === allExpressions.length) {
|
|
|
// 如果请求全部或更多,直接返回全部
|
|
|
selectedExpressions.push(...allExpressions);
|
|
|
} else {
|
|
|
// 无放回随机选择
|
|
|
const indices = new Set();
|
|
|
while (indices.size < actualCount) {
|
|
|
indices.add(Math.floor(Math.random() * allExpressions.length));
|
|
|
}
|
|
|
indices.forEach(index => {
|
|
|
selectedExpressions.push(allExpressions[index]);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 全局存储所有选择的表达式以供下载(不受显示限制)
|
|
|
allDecodedExpressions = selectedExpressions;
|
|
|
|
|
|
// 对于显示,限制为 MAX_DISPLAY_RESULTS,但保留完整集合以供下载
|
|
|
const displayExpressions = selectedExpressions.slice(0, MAX_DISPLAY_RESULTS);
|
|
|
|
|
|
// 显示结果(显示有限,但下载计数完整)
|
|
|
displayDecodedResults(displayExpressions, allExpressions.length, true);
|
|
|
|
|
|
// 清除错误并显示成功信息,明确说明显示与下载的区别
|
|
|
if (selectedExpressions.length > MAX_DISPLAY_RESULTS) {
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 从 ${allExpressions.length} 个总组合中随机选择了 ${selectedExpressions.length} 个表达式<br>
|
|
|
📺 显示前 ${MAX_DISPLAY_RESULTS} 个结果。下载将包含所有 ${selectedExpressions.length} 个表达式。
|
|
|
</div>`;
|
|
|
} else {
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 从 ${allExpressions.length} 个总组合中随机选择了 ${selectedExpressions.length} 个表达式
|
|
|
</div>`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 为 Next Move 打开设置模态框
|
|
|
function openSettingsModal() {
|
|
|
const modal = document.getElementById('settingsModal');
|
|
|
modal.style.display = 'block';
|
|
|
updateTotalCombinations();
|
|
|
|
|
|
// 为设置输入添加事件监听器
|
|
|
document.querySelectorAll('.setting-value-input').forEach(input => {
|
|
|
input.addEventListener('input', updateTotalCombinations);
|
|
|
});
|
|
|
|
|
|
// 为添加设置按钮添加事件监听器
|
|
|
document.getElementById('addSettingBtn').addEventListener('click', addCustomSetting);
|
|
|
|
|
|
// 为测试周期滑块添加事件监听器
|
|
|
const testPeriodSlider = document.querySelector('.test-period-slider');
|
|
|
if (testPeriodSlider) {
|
|
|
testPeriodSlider.addEventListener('input', updateTestPeriodValue);
|
|
|
// 初始化显示值
|
|
|
updateTestPeriodValue();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 关闭设置模态框
|
|
|
function closeSettingsModal() {
|
|
|
const modal = document.getElementById('settingsModal');
|
|
|
modal.style.display = 'none';
|
|
|
}
|
|
|
|
|
|
// 更新测试周期值显示
|
|
|
function updateTestPeriodValue() {
|
|
|
const slider = document.querySelector('.test-period-slider');
|
|
|
const valueDisplay = document.getElementById('testPeriodValue');
|
|
|
|
|
|
if (slider && valueDisplay) {
|
|
|
const totalMonths = parseInt(slider.value);
|
|
|
const years = Math.floor(totalMonths / 12);
|
|
|
const months = totalMonths % 12;
|
|
|
const periodValue = `P${years}Y${months}M`;
|
|
|
valueDisplay.textContent = periodValue;
|
|
|
|
|
|
// 更新滑块的 value 属性,以便 parseSettingValues 可以读取
|
|
|
slider.setAttribute('data-period-value', periodValue);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 添加自定义设置行
|
|
|
function addCustomSetting() {
|
|
|
const tbody = document.getElementById('settingsTableBody');
|
|
|
const row = document.createElement('tr');
|
|
|
row.className = 'custom-setting-row';
|
|
|
|
|
|
row.innerHTML = `
|
|
|
<td><input type="text" class="setting-name-input form-input" placeholder="设置名称"></td>
|
|
|
<td><input type="text" class="setting-value-input" data-setting="custom" placeholder="值"></td>
|
|
|
<td><select class="setting-type-select"><option>string</option><option>number</option><option>boolean</option></select></td>
|
|
|
<td><button class="remove-setting-btn" onclick="removeCustomSetting(this)">移除</button></td>
|
|
|
`;
|
|
|
|
|
|
tbody.appendChild(row);
|
|
|
|
|
|
// 为新输入添加事件监听器
|
|
|
row.querySelector('.setting-value-input').addEventListener('input', updateTotalCombinations);
|
|
|
row.querySelector('.setting-name-input').addEventListener('input', updateTotalCombinations);
|
|
|
}
|
|
|
|
|
|
// 移除自定义设置行
|
|
|
function removeCustomSetting(button) {
|
|
|
button.closest('tr').remove();
|
|
|
updateTotalCombinations();
|
|
|
}
|
|
|
|
|
|
// 计算总组合数
|
|
|
function updateTotalCombinations() {
|
|
|
let totalCombinations = allDecodedExpressions.length;
|
|
|
|
|
|
// 获取所有设置及其值
|
|
|
const settingInputs = document.querySelectorAll('.setting-value-input');
|
|
|
settingInputs.forEach(input => {
|
|
|
const values = input.value.split(',').map(v => v.trim()).filter(v => v !== '');
|
|
|
if (values.length > 1) {
|
|
|
totalCombinations *= values.length;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
document.getElementById('totalCombinations').textContent = totalCombinations.toLocaleString();
|
|
|
}
|
|
|
|
|
|
// 解析设置值(处理逗号分隔的值)
|
|
|
function parseSettingValues() {
|
|
|
const settings = {};
|
|
|
const variations = {};
|
|
|
const types = {};
|
|
|
|
|
|
// 获取预定义设置
|
|
|
const settingRows = document.querySelectorAll('#settingsTableBody tr');
|
|
|
settingRows.forEach(row => {
|
|
|
const nameCell = row.cells[0];
|
|
|
// 使用选择器或输入框获取值
|
|
|
let input = row.querySelector('.setting-value-input');
|
|
|
if (input) {
|
|
|
let settingName;
|
|
|
// 检查是否为自定义设置
|
|
|
const nameInput = row.querySelector('.setting-name-input');
|
|
|
if (nameInput) {
|
|
|
settingName = nameInput.value.trim();
|
|
|
if (!settingName) return; // 如果没有名称,跳过
|
|
|
} else {
|
|
|
settingName = nameCell.textContent.trim();
|
|
|
}
|
|
|
// 获取类型
|
|
|
const typeSelect = row.querySelector('.setting-type-select');
|
|
|
const type = typeSelect ? typeSelect.value : 'string';
|
|
|
types[settingName] = type;
|
|
|
// 对于选择下拉框,以不同方式获取值
|
|
|
let values = [];
|
|
|
if (input.tagName === 'SELECT') {
|
|
|
values = [input.value];
|
|
|
} else if (input.type === 'range' && settingName === 'testPeriod') {
|
|
|
// 测试周期滑块的特别处理
|
|
|
const periodValue = input.getAttribute('data-period-value') || 'P0Y0M';
|
|
|
values = [periodValue];
|
|
|
} else {
|
|
|
values = input.value.split(',').map(v => v.trim()).filter(v => v !== '');
|
|
|
}
|
|
|
// 根据类型转换值
|
|
|
const convertedValues = values.map(v => {
|
|
|
if (type === 'number') {
|
|
|
const num = parseFloat(v);
|
|
|
return isNaN(num) ? v : num;
|
|
|
} else if (type === 'boolean') {
|
|
|
if (typeof v === 'boolean') return v;
|
|
|
if (typeof v === 'string') {
|
|
|
if (v.toLowerCase() === 'true') return true;
|
|
|
if (v.toLowerCase() === 'false') return false;
|
|
|
}
|
|
|
return false;
|
|
|
} else {
|
|
|
return v;
|
|
|
}
|
|
|
});
|
|
|
if (convertedValues.length === 0) {
|
|
|
// 如果没有值,使用空字符串
|
|
|
settings[settingName] = '';
|
|
|
} else if (convertedValues.length === 1) {
|
|
|
// 单个值
|
|
|
settings[settingName] = convertedValues[0];
|
|
|
} else {
|
|
|
// 多个值 - 存储以供迭代
|
|
|
variations[settingName] = convertedValues;
|
|
|
settings[settingName] = convertedValues[0]; // 默认为第一个值
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
return { settings, variations, types };
|
|
|
}
|
|
|
|
|
|
// 生成所有设置组合
|
|
|
function generateSettingCombinations(baseSettings, variations) {
|
|
|
const variationKeys = Object.keys(variations);
|
|
|
if (variationKeys.length === 0) {
|
|
|
return [baseSettings];
|
|
|
}
|
|
|
|
|
|
const combinations = [];
|
|
|
|
|
|
function generate(index, current) {
|
|
|
if (index === variationKeys.length) {
|
|
|
combinations.push({...current});
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const key = variationKeys[index];
|
|
|
const values = variations[key];
|
|
|
|
|
|
for (const value of values) {
|
|
|
current[key] = value;
|
|
|
generate(index + 1, current);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
generate(0, {...baseSettings});
|
|
|
return combinations;
|
|
|
}
|
|
|
|
|
|
// 将设置应用到表达式
|
|
|
function applySettings() {
|
|
|
const { settings, variations, types } = parseSettingValues();
|
|
|
|
|
|
// 始终包含 instrumentType 和 language
|
|
|
settings.instrumentType = settings.instrumentType || "EQUITY";
|
|
|
settings.language = settings.language || "FASTEXPR";
|
|
|
|
|
|
// 生成所有设置组合
|
|
|
const settingCombinations = generateSettingCombinations(settings, variations);
|
|
|
|
|
|
// 创建所有表达式-设置组合
|
|
|
const allCombinations = [];
|
|
|
|
|
|
allDecodedExpressions.forEach(expr => {
|
|
|
settingCombinations.forEach(settingCombo => {
|
|
|
const fullExpression = {
|
|
|
type: "REGULAR",
|
|
|
settings: settingCombo,
|
|
|
regular: expr.replace(/\n/g, '') // 移除换行符
|
|
|
};
|
|
|
allCombinations.push(JSON.stringify(fullExpression, null, 2));
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 创建 JSON 数组
|
|
|
const jsonContent = '[\n' + allCombinations.join(',\n') + '\n]';
|
|
|
|
|
|
// 下载带设置的表达式
|
|
|
const blob = new Blob([jsonContent], { type: 'application/json' });
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
a.href = url;
|
|
|
a.download = 'expressions_with_settings.json';
|
|
|
document.body.appendChild(a);
|
|
|
a.click();
|
|
|
document.body.removeChild(a);
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
|
// 关闭模态框并显示成功信息
|
|
|
closeSettingsModal();
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 已将 ${allCombinations.length} 个表达式配置下载为 expressions_with_settings.json
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
// 全局存储测试结果
|
|
|
let allTestResults = [];
|
|
|
|
|
|
// 使用 BRAIN API 生成并测试表达式
|
|
|
async function generateAndTest() {
|
|
|
const { settings, variations, types } = parseSettingValues();
|
|
|
|
|
|
// 检查用户是否使用正确方法登录到 BRAIN
|
|
|
if (!window.brainAPI || !window.brainAPI.isConnectedToBrain()) {
|
|
|
alert('在测试表达式之前,请先连接到 BRAIN。');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 从全局变量获取会话 ID
|
|
|
const sessionId = brainSessionId;
|
|
|
if (!sessionId) {
|
|
|
alert('未找到 BRAIN 会话。请重新连接到 BRAIN。');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 始终包含 instrumentType 和 language
|
|
|
settings.instrumentType = settings.instrumentType || "EQUITY";
|
|
|
settings.language = settings.language || "FASTEXPR";
|
|
|
|
|
|
// 生成所有设置组合
|
|
|
const settingCombinations = generateSettingCombinations(settings, variations);
|
|
|
|
|
|
// 创建所有表达式-设置组合
|
|
|
const allCombinations = [];
|
|
|
|
|
|
allDecodedExpressions.forEach(expr => {
|
|
|
settingCombinations.forEach(settingCombo => {
|
|
|
const fullExpression = {
|
|
|
type: "REGULAR",
|
|
|
settings: settingCombo,
|
|
|
regular: expr.replace(/\n/g, '') // 移除换行符
|
|
|
};
|
|
|
allCombinations.push(fullExpression);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 随机选择一个表达式进行测试
|
|
|
const randomIndex = Math.floor(Math.random() * allCombinations.length);
|
|
|
const testExpression = allCombinations[randomIndex];
|
|
|
|
|
|
// 关闭设置模态框并打开测试结果模态框
|
|
|
closeSettingsModal();
|
|
|
openBrainTestResultsModal();
|
|
|
|
|
|
// 显示进度
|
|
|
const progressDiv = document.getElementById('brainTestProgress');
|
|
|
const progressBarFill = document.getElementById('progressBarFill');
|
|
|
const progressText = document.getElementById('progressText');
|
|
|
const resultsDiv = document.getElementById('brainTestResults');
|
|
|
|
|
|
progressDiv.style.display = 'block';
|
|
|
resultsDiv.innerHTML = '';
|
|
|
allTestResults = [];
|
|
|
|
|
|
// 测试单个随机选择的表达式
|
|
|
progressText.textContent = `正在测试表达式 ${randomIndex + 1},共 ${allCombinations.length} 个(随机选择)...`;
|
|
|
progressBarFill.style.width = '50%';
|
|
|
|
|
|
try {
|
|
|
const response = await fetch('/api/test-expression', {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'Content-Type': 'application/json',
|
|
|
'Session-ID': sessionId
|
|
|
},
|
|
|
body: JSON.stringify(testExpression)
|
|
|
});
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
// 存储结果
|
|
|
const testResult = {
|
|
|
expression: testExpression.regular,
|
|
|
settings: testExpression.settings,
|
|
|
success: result.success,
|
|
|
status: result.status || (result.success ? 'SUCCESS' : 'ERROR'),
|
|
|
message: result.message || result.error || '未知错误',
|
|
|
details: result,
|
|
|
totalPossible: allCombinations.length,
|
|
|
testedIndex: randomIndex + 1
|
|
|
};
|
|
|
|
|
|
allTestResults.push(testResult);
|
|
|
|
|
|
// 更新进度
|
|
|
progressBarFill.style.width = '100%';
|
|
|
progressText.textContent = '测试完成!';
|
|
|
|
|
|
// 短暂延迟后隐藏进度
|
|
|
setTimeout(() => {
|
|
|
progressDiv.style.display = 'none';
|
|
|
|
|
|
// 显示结果
|
|
|
displaySingleTestResult(testResult);
|
|
|
|
|
|
// 显示下载按钮
|
|
|
document.getElementById('downloadTestResultsBtn').style.display = 'inline-block';
|
|
|
document.getElementById('downloadExpressionWithSettingsBtn').style.display = 'inline-block';
|
|
|
}, 500);
|
|
|
|
|
|
} catch (error) {
|
|
|
const testResult = {
|
|
|
expression: testExpression.regular,
|
|
|
settings: testExpression.settings,
|
|
|
success: false,
|
|
|
status: 'ERROR',
|
|
|
message: `网络错误: ${error.message}`,
|
|
|
details: { error: error.message },
|
|
|
totalPossible: allCombinations.length,
|
|
|
testedIndex: randomIndex + 1
|
|
|
};
|
|
|
allTestResults.push(testResult);
|
|
|
|
|
|
progressDiv.style.display = 'none';
|
|
|
displaySingleTestResult(testResult);
|
|
|
document.getElementById('downloadTestResultsBtn').style.display = 'inline-block';
|
|
|
document.getElementById('downloadExpressionWithSettingsBtn').style.display = 'inline-block';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 显示单个测试结果
|
|
|
function displaySingleTestResult(result) {
|
|
|
const resultsDiv = document.getElementById('brainTestResults');
|
|
|
|
|
|
// 添加摘要信息
|
|
|
const summaryDiv = document.createElement('div');
|
|
|
summaryDiv.className = 'test-summary';
|
|
|
summaryDiv.innerHTML = `
|
|
|
<h4>随机测试结果</h4>
|
|
|
<p>从 ${result.totalPossible} 个可能组合中随机选择了表达式 #${result.testedIndex}</p>
|
|
|
`;
|
|
|
resultsDiv.appendChild(summaryDiv);
|
|
|
|
|
|
// 添加测试结果
|
|
|
const resultItem = document.createElement('div');
|
|
|
resultItem.className = `test-result-item ${result.success && result.status !== 'ERROR' ? 'success' : 'error'}`;
|
|
|
|
|
|
const expressionDiv = document.createElement('div');
|
|
|
expressionDiv.className = 'test-result-expression';
|
|
|
expressionDiv.innerHTML = `<strong>表达式:</strong> ${result.expression}`;
|
|
|
|
|
|
// 按照笔记本中的样子显示消息
|
|
|
const messageDiv = document.createElement('div');
|
|
|
messageDiv.className = 'test-result-message';
|
|
|
messageDiv.style.whiteSpace = 'pre-wrap';
|
|
|
messageDiv.style.fontFamily = 'monospace';
|
|
|
messageDiv.style.backgroundColor = '#f5f5f5';
|
|
|
messageDiv.style.padding = '10px';
|
|
|
messageDiv.style.borderRadius = '4px';
|
|
|
messageDiv.style.marginTop = '10px';
|
|
|
|
|
|
// 格式化消息 - 如果是完整的响应对象,美观地显示它
|
|
|
if (result.details && result.details.full_response) {
|
|
|
const fullResponse = result.details.full_response;
|
|
|
|
|
|
// 如果是具有预期结构的对象,美观地格式化它
|
|
|
if (typeof fullResponse === 'object' && fullResponse.id && fullResponse.type && fullResponse.status) {
|
|
|
// 格式化为 Python 字典输出
|
|
|
messageDiv.textContent = JSON.stringify(fullResponse, null, 2).replace(/"/g, "'");
|
|
|
} else if (typeof fullResponse === 'object') {
|
|
|
// 对于其他对象,直接字符串化它们
|
|
|
messageDiv.textContent = JSON.stringify(fullResponse, null, 2);
|
|
|
} else {
|
|
|
// 对于非对象,显示消息字符串
|
|
|
messageDiv.textContent = result.message;
|
|
|
}
|
|
|
} else {
|
|
|
// 回退到简单消息
|
|
|
messageDiv.textContent = result.message;
|
|
|
}
|
|
|
|
|
|
resultItem.appendChild(expressionDiv);
|
|
|
resultItem.appendChild(messageDiv);
|
|
|
|
|
|
// 添加设置信息
|
|
|
const settingsDiv = document.createElement('div');
|
|
|
settingsDiv.className = 'test-result-message';
|
|
|
settingsDiv.innerHTML = '<strong>使用的设置:</strong>';
|
|
|
const settingsList = document.createElement('ul');
|
|
|
settingsList.style.margin = '5px 0';
|
|
|
settingsList.style.paddingLeft = '20px';
|
|
|
|
|
|
for (const [key, value] of Object.entries(result.settings)) {
|
|
|
const li = document.createElement('li');
|
|
|
li.textContent = `${key}: ${value}`;
|
|
|
settingsList.appendChild(li);
|
|
|
}
|
|
|
|
|
|
settingsDiv.appendChild(settingsList);
|
|
|
resultItem.appendChild(settingsDiv);
|
|
|
|
|
|
resultsDiv.appendChild(resultItem);
|
|
|
}
|
|
|
|
|
|
// 旧函数名的兼容性包装器
|
|
|
function addTestResultToDisplay(result, index) {
|
|
|
// 如果不存在,向结果添加索引信息
|
|
|
if (!result.testedIndex) {
|
|
|
result.testedIndex = index;
|
|
|
}
|
|
|
if (!result.totalPossible) {
|
|
|
result.totalPossible = allDecodedExpressions.length;
|
|
|
}
|
|
|
displaySingleTestResult(result);
|
|
|
}
|
|
|
|
|
|
// 显示测试摘要(保持兼容性)
|
|
|
function showTestSummary(total, success, error) {
|
|
|
const resultsDiv = document.getElementById('brainTestResults');
|
|
|
|
|
|
const summaryDiv = document.createElement('div');
|
|
|
summaryDiv.className = 'test-summary';
|
|
|
summaryDiv.innerHTML = `
|
|
|
<h4>测试摘要</h4>
|
|
|
<div class="test-summary-stats">
|
|
|
<div class="test-summary-stat">
|
|
|
<div class="test-summary-stat-value">${total}</div>
|
|
|
<div class="test-summary-stat-label">总测试数</div>
|
|
|
</div>
|
|
|
<div class="test-summary-stat" style="color: #28a745;">
|
|
|
<div class="test-summary-stat-value">${success}</div>
|
|
|
<div class="test-summary-stat-label">成功</div>
|
|
|
</div>
|
|
|
<div class="test-summary-stat" style="color: #dc3545;">
|
|
|
<div class="test-summary-stat-value">${error}</div>
|
|
|
<div class="test-summary-stat-label">错误</div>
|
|
|
</div>
|
|
|
<div class="test-summary-stat">
|
|
|
<div class="test-summary-stat-value">${((success / total) * 100).toFixed(1)}%</div>
|
|
|
<div class="test-summary-stat-label">成功率</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
resultsDiv.insertBefore(summaryDiv, resultsDiv.firstChild);
|
|
|
}
|
|
|
|
|
|
// 打开测试结果模态框
|
|
|
function openBrainTestResultsModal() {
|
|
|
const modal = document.getElementById('brainTestResultsModal');
|
|
|
modal.style.display = 'block';
|
|
|
}
|
|
|
|
|
|
// 关闭测试结果模态框
|
|
|
function closeBrainTestResultsModal() {
|
|
|
const modal = document.getElementById('brainTestResultsModal');
|
|
|
modal.style.display = 'none';
|
|
|
}
|
|
|
|
|
|
// 下载测试结果
|
|
|
function downloadTestResults() {
|
|
|
const results = allTestResults.map(result => ({
|
|
|
expression: result.expression,
|
|
|
settings: result.settings,
|
|
|
status: result.status,
|
|
|
message: result.message,
|
|
|
details: result.details
|
|
|
}));
|
|
|
|
|
|
const jsonContent = JSON.stringify(results, null, 2);
|
|
|
|
|
|
const blob = new Blob([jsonContent], { type: 'application/json' });
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
a.href = url;
|
|
|
a.download = 'brain_test_results.json';
|
|
|
document.body.appendChild(a);
|
|
|
a.click();
|
|
|
document.body.removeChild(a);
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 已下载 ${allTestResults.length} 个表达式的测试结果
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
// 下载带设置的表达式(与"生成并下载"相同)
|
|
|
function downloadExpressionWithSettings() {
|
|
|
// 从模态框获取当前设置(与 applySettings 相同的逻辑)
|
|
|
const { settings, variations, types } = parseSettingValues();
|
|
|
|
|
|
// 始终包含 instrumentType 和 language
|
|
|
settings.instrumentType = settings.instrumentType || "EQUITY";
|
|
|
settings.language = settings.language || "FASTEXPR";
|
|
|
|
|
|
// 生成所有设置组合
|
|
|
const settingCombinations = generateSettingCombinations(settings, variations);
|
|
|
|
|
|
// 创建所有表达式-设置组合
|
|
|
const allCombinations = [];
|
|
|
|
|
|
allDecodedExpressions.forEach(expr => {
|
|
|
settingCombinations.forEach(settingCombo => {
|
|
|
const fullExpression = {
|
|
|
type: "REGULAR",
|
|
|
settings: settingCombo,
|
|
|
regular: expr.replace(/\n/g, '') // 移除换行符
|
|
|
};
|
|
|
allCombinations.push(JSON.stringify(fullExpression, null, 2));
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 创建 JSON 数组
|
|
|
const jsonContent = '[\n' + allCombinations.join(',\n') + '\n]';
|
|
|
|
|
|
// 下载带设置的表达式
|
|
|
const blob = new Blob([jsonContent], { type: 'application/json' });
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
a.href = url;
|
|
|
a.download = 'expressions_with_settings.json';
|
|
|
document.body.appendChild(a);
|
|
|
a.click();
|
|
|
document.body.removeChild(a);
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
|
const errorsDiv = document.getElementById('grammarErrors');
|
|
|
errorsDiv.innerHTML = `<div class="success-message">
|
|
|
✓ 已将 ${allCombinations.length} 个表达式配置下载为 expressions_with_settings.json
|
|
|
</div>`;
|
|
|
} |