You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
424 lines
15 KiB
424 lines
15 KiB
document.addEventListener('DOMContentLoaded', async () => {
|
|
try {
|
|
const response = await fetch('/api/config/defaults');
|
|
const result = await response.json();
|
|
|
|
if (result.success && result.config) {
|
|
const config = result.config;
|
|
|
|
if (config.brain) {
|
|
if (config.brain.username) {
|
|
document.getElementById('brainUsername').value = config.brain.username;
|
|
}
|
|
if (config.brain.password) {
|
|
document.getElementById('brainPassword').value = config.brain.password;
|
|
}
|
|
}
|
|
|
|
if (config.llm) {
|
|
if (config.llm.api_key) {
|
|
document.getElementById('llmApiKey').value = config.llm.api_key;
|
|
}
|
|
if (config.llm.base_url) {
|
|
document.getElementById('llmBaseUrl').value = config.llm.base_url;
|
|
}
|
|
if (config.llm.model) {
|
|
document.getElementById('llmModel').value = config.llm.model;
|
|
}
|
|
}
|
|
|
|
if (config.transformer) {
|
|
if (config.transformer.top_n_datafield) {
|
|
document.getElementById('topNDatafield').value = config.transformer.top_n_datafield;
|
|
}
|
|
if (config.transformer.data_type) {
|
|
document.getElementById('dataType').value = config.transformer.data_type;
|
|
}
|
|
if (config.transformer.alpha_id) {
|
|
document.getElementById('alphaId').value = config.transformer.alpha_id;
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('加载默认配置失败:', error);
|
|
}
|
|
});
|
|
|
|
const form = document.getElementById('transformerForm');
|
|
const submitBtn = document.getElementById('submitBtn');
|
|
const downloadBtn = document.getElementById('downloadBtn');
|
|
const loginAndFetchBtn = document.getElementById('loginAndFetchBtn');
|
|
const testLLMBtn = document.getElementById('testLLMBtn');
|
|
const downloadDatafieldsBtn = document.getElementById('downloadDatafieldsBtn');
|
|
const regionSelect = document.getElementById('region');
|
|
const delaySelect = document.getElementById('delay');
|
|
const universeSelect = document.getElementById('universe');
|
|
const dataTypeSelect = document.getElementById('dataType');
|
|
const categoryButtons = document.getElementById('category-buttons');
|
|
|
|
let optionsData = {};
|
|
|
|
dataTypeSelect.addEventListener('change', function() {
|
|
if (this.value === 'VECTOR') {
|
|
if (!confirm("请确保您输入的原型Alpha中正确地使用了vector operator,否则极容易造成数据类型错误!")) {
|
|
this.value = 'MATRIX';
|
|
}
|
|
}
|
|
});
|
|
|
|
loginAndFetchBtn.addEventListener('click', async () => {
|
|
const username = document.getElementById('brainUsername').value.trim();
|
|
const password = document.getElementById('brainPassword').value;
|
|
|
|
if (!username || !password) {
|
|
alert('请先填写BRAIN用户名和密码');
|
|
return;
|
|
}
|
|
|
|
loginAndFetchBtn.disabled = true;
|
|
loginAndFetchBtn.textContent = '正在登录...';
|
|
|
|
try {
|
|
const response = await fetch('/api/transformer/login-and-fetch-options', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
optionsData = result.options;
|
|
|
|
populateRegionSelect();
|
|
regionSelect.disabled = false;
|
|
|
|
if (result.categories) {
|
|
populateCategoryButtons(result.categories);
|
|
}
|
|
|
|
loginAndFetchBtn.textContent = '登录成功';
|
|
} else {
|
|
alert('登录失败: ' + result.error);
|
|
loginAndFetchBtn.disabled = false;
|
|
loginAndFetchBtn.textContent = '登录BRAIN并获取选项';
|
|
}
|
|
} catch (error) {
|
|
alert('登录出错: ' + error.message);
|
|
loginAndFetchBtn.disabled = false;
|
|
loginAndFetchBtn.textContent = '登录BRAIN并获取选项';
|
|
}
|
|
});
|
|
|
|
testLLMBtn.addEventListener('click', async () => {
|
|
const apiKey = document.getElementById('llmApiKey').value.trim();
|
|
const baseUrl = document.getElementById('llmBaseUrl').value.trim();
|
|
const model = document.getElementById('llmModel').value.trim();
|
|
|
|
if (!apiKey || !baseUrl || !model) {
|
|
alert('请先填写完整的 LLM 配置');
|
|
return;
|
|
}
|
|
|
|
testLLMBtn.disabled = true;
|
|
testLLMBtn.textContent = '测试中...';
|
|
testLLMBtn.classList.remove('btn-success', 'btn-error');
|
|
|
|
try {
|
|
const response = await fetch('/api/test-llm', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
llm_api_key: apiKey,
|
|
llm_base_url: baseUrl,
|
|
llm_model: model
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
testLLMBtn.textContent = '连接成功';
|
|
testLLMBtn.classList.add('btn-success');
|
|
} else {
|
|
testLLMBtn.textContent = '连接失败';
|
|
testLLMBtn.classList.add('btn-error');
|
|
}
|
|
} catch (error) {
|
|
testLLMBtn.textContent = '连接失败';
|
|
testLLMBtn.classList.add('btn-error');
|
|
} finally {
|
|
testLLMBtn.disabled = false;
|
|
}
|
|
});
|
|
|
|
function populateRegionSelect() {
|
|
while (regionSelect.options.length > 1) {
|
|
regionSelect.remove(1);
|
|
}
|
|
|
|
const regions = Object.keys(optionsData);
|
|
regions.forEach(region => {
|
|
const option = document.createElement('option');
|
|
option.value = region;
|
|
option.textContent = region;
|
|
regionSelect.appendChild(option);
|
|
});
|
|
}
|
|
|
|
function populateCategoryButtons(categories) {
|
|
categories.forEach(category => {
|
|
const btn = document.createElement('button');
|
|
btn.type = 'button';
|
|
btn.dataset.value = category.id || category;
|
|
btn.textContent = category.name || category;
|
|
btn.onclick = function() { toggleCategory(this); };
|
|
btn.className = 'btn';
|
|
btn.style.cssText = 'padding: 4px 12px; font-size: 11px;';
|
|
categoryButtons.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
// 全选/反选功能
|
|
function toggleAllCategories() {
|
|
const otherBtns = categoryButtons.querySelectorAll('button:not(#cat-all)');
|
|
|
|
// 检查是否全部选中
|
|
let allSelected = true;
|
|
otherBtns.forEach(b => {
|
|
if (!isCategoryButtonSelected(b)) {
|
|
allSelected = false;
|
|
}
|
|
});
|
|
|
|
if (allSelected) {
|
|
// 全部选中 -> 全不选
|
|
otherBtns.forEach(b => {
|
|
b.style.backgroundColor = 'white';
|
|
b.style.color = 'black';
|
|
});
|
|
} else {
|
|
// 不是全部选中 -> 全选
|
|
otherBtns.forEach(b => {
|
|
b.style.backgroundColor = '#87CEEB'; // 浅蓝色
|
|
b.style.color = 'black';
|
|
});
|
|
}
|
|
}
|
|
|
|
// 点击具体类别按钮
|
|
function toggleCategory(btn) {
|
|
if (isCategoryButtonSelected(btn)) {
|
|
btn.style.backgroundColor = 'white';
|
|
btn.style.color = 'black';
|
|
} else {
|
|
btn.style.backgroundColor = '#87CEEB'; // 浅蓝色
|
|
btn.style.color = 'black';
|
|
}
|
|
}
|
|
|
|
// 检查类别按钮是否被选中的辅助函数
|
|
function isCategoryButtonSelected(btn) {
|
|
const bg = btn.style.backgroundColor;
|
|
// 浅蓝色或 rgb(135, 206, 235) 都表示选中
|
|
return bg === '#87CEEB' || bg === 'rgb(135, 206, 235)' || bg === 'lightblue';
|
|
}
|
|
|
|
// 获取选中的类别列表
|
|
function getSelectedCategories() {
|
|
const otherBtns = categoryButtons.querySelectorAll('button:not(#cat-all)');
|
|
|
|
// 获取选中的具体类别(使用新的选中判断)
|
|
const selected = [];
|
|
otherBtns.forEach(b => {
|
|
if (isCategoryButtonSelected(b)) {
|
|
selected.push(b.dataset.value);
|
|
}
|
|
});
|
|
|
|
// 如果没有选中任何具体类别,返回所有类别(默认全选)
|
|
if (selected.length === 0) {
|
|
const allCategories = [];
|
|
otherBtns.forEach(b => {
|
|
allCategories.push(b.dataset.value);
|
|
});
|
|
return allCategories;
|
|
}
|
|
|
|
return selected;
|
|
}
|
|
|
|
regionSelect.addEventListener('change', () => {
|
|
const selectedRegion = regionSelect.value;
|
|
|
|
universeSelect.innerHTML = '<option value="">-- 先选择Delay --</option>';
|
|
universeSelect.disabled = true;
|
|
|
|
if (selectedRegion && optionsData[selectedRegion]) {
|
|
const delays = Object.keys(optionsData[selectedRegion]);
|
|
// 保留 delaySelect 的当前值(如果有效)
|
|
const currentDelay = delaySelect.value;
|
|
delaySelect.innerHTML = '';
|
|
delays.forEach(delay => {
|
|
const option = document.createElement('option');
|
|
option.value = delay;
|
|
option.textContent = delay;
|
|
if (delay === currentDelay) {
|
|
option.selected = true;
|
|
}
|
|
delaySelect.appendChild(option);
|
|
});
|
|
delaySelect.disabled = false;
|
|
// 触发 delay change 事件来更新 universe
|
|
if (currentDelay && optionsData[selectedRegion][currentDelay]) {
|
|
delaySelect.dispatchEvent(new Event('change'));
|
|
}
|
|
}
|
|
});
|
|
|
|
delaySelect.addEventListener('change', () => {
|
|
const selectedRegion = regionSelect.value;
|
|
const selectedDelay = delaySelect.value;
|
|
|
|
universeSelect.innerHTML = '<option value="">-- 先选择Delay --</option>';
|
|
universeSelect.disabled = true;
|
|
|
|
if (selectedRegion && selectedDelay && optionsData[selectedRegion][selectedDelay]) {
|
|
const universes = optionsData[selectedRegion][selectedDelay];
|
|
universes.forEach(universe => {
|
|
const option = document.createElement('option');
|
|
option.value = universe;
|
|
option.textContent = universe;
|
|
universeSelect.appendChild(option);
|
|
});
|
|
universeSelect.disabled = false;
|
|
}
|
|
});
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const formData = {
|
|
alpha_id: document.getElementById('alphaId').value.trim(),
|
|
llm_api_key: document.getElementById('llmApiKey').value.trim(),
|
|
llm_base_url: document.getElementById('llmBaseUrl').value.trim(),
|
|
llm_model: document.getElementById('llmModel').value.trim(),
|
|
brain_username: document.getElementById('brainUsername').value.trim(),
|
|
brain_password: document.getElementById('brainPassword').value.trim(),
|
|
top_n_datafield: parseInt(document.getElementById('topNDatafield').value) || 50,
|
|
max_retries: parseInt(document.getElementById('maxRetries').value) || 20,
|
|
data_type: document.getElementById('dataType').value || 'MATRIX'
|
|
};
|
|
|
|
const region = document.getElementById('region').value;
|
|
const delay = document.getElementById('delay').value;
|
|
const universe = document.getElementById('universe').value;
|
|
|
|
if (region) formData.user_region = region;
|
|
if (delay) formData.user_delay = parseInt(delay);
|
|
if (universe) formData.user_universe = universe;
|
|
|
|
// 使用 getSelectedCategories 获取选中的类别
|
|
const selectedCategories = getSelectedCategories();
|
|
if (selectedCategories.length > 0) {
|
|
formData.user_category = selectedCategories;
|
|
}
|
|
|
|
submitBtn.disabled = true;
|
|
submitBtn.textContent = '处理中...';
|
|
|
|
try {
|
|
const response = await fetch('/api/generate', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(formData)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
const successCount = result.expressions_success ? result.expressions_success.length : 0;
|
|
const candidatesData = result.candidates || {};
|
|
const candidateTemplatesCount = Object.keys(candidatesData).length;
|
|
const errorCount = result.expressions_error ? result.expressions_error.length : 0;
|
|
|
|
// 显示下载按钮
|
|
downloadBtn.style.display = 'block';
|
|
downloadBtn.textContent = '下载结果 (' + successCount + '成功, ' + candidateTemplatesCount + '模板)';
|
|
downloadBtn.onclick = function() {
|
|
const alphaId = document.getElementById('alphaId').value.trim();
|
|
window.location.href = '/api/download/' + alphaId;
|
|
};
|
|
} else {
|
|
downloadBtn.style.display = 'none';
|
|
downloadBtn.textContent = '下载结果 (ZIP)';
|
|
}
|
|
|
|
} catch (error) {
|
|
downloadBtn.style.display = 'none';
|
|
downloadBtn.textContent = '下载结果 (ZIP)';
|
|
} finally {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = '生成变种';
|
|
}
|
|
});
|
|
|
|
// 下载数据字段缓存按钮
|
|
downloadDatafieldsBtn.addEventListener('click', async () => {
|
|
const region = regionSelect.value;
|
|
const delay = delaySelect.value;
|
|
const universe = universeSelect.value;
|
|
|
|
if (!region || !delay || !universe) {
|
|
alert('请先选择地区、Delay和股票池');
|
|
return;
|
|
}
|
|
|
|
const brainUsername = document.getElementById('brainUsername').value.trim();
|
|
const brainPassword = document.getElementById('brainPassword').value.trim();
|
|
|
|
if (!brainUsername || !brainPassword) {
|
|
alert('请先输入BRAIN用户名和密码');
|
|
return;
|
|
}
|
|
|
|
// 获取选中的类别(使用新函数)
|
|
const selectedCategories = getSelectedCategories();
|
|
console.log('选中的类别:', selectedCategories);
|
|
|
|
downloadDatafieldsBtn.disabled = true;
|
|
downloadDatafieldsBtn.textContent = '正在下载...';
|
|
|
|
try {
|
|
const requestBody = {
|
|
username: brainUsername,
|
|
password: brainPassword,
|
|
region: region,
|
|
delay: parseInt(delay),
|
|
universe: universe,
|
|
data_type: dataTypeSelect.value,
|
|
category: selectedCategories // 总是传递类别列表
|
|
};
|
|
|
|
const response = await fetch('/api/download-datafields', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(requestBody)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
alert('下载失败: ' + (result.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
alert('下载出错: ' + error.message);
|
|
} finally {
|
|
downloadDatafieldsBtn.disabled = false;
|
|
downloadDatafieldsBtn.textContent = '下载数据字段缓存';
|
|
}
|
|
});
|
|
|