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.
 
 
 
 
 
 
wqb-server/static/brain.js

381 lines
12 KiB

/**
* BRAIN API 集成模块
* 处理 WorldQuant BRAIN 的身份验证、运算符和数据字段
* 现在使用本地 Python 代理服务器来绕过 CORS 限制
*/
// BRAIN 会话和数据存储
let brainSession = null;
let brainOperators = null;
let brainDataFields = null;
let brainSessionId = localStorage.getItem('brain_session_id');
// Flask 应用 API 端点
const PROXY_BASE = '';
// 打开 BRAIN 登录模态框
function openBrainLoginModal() {
const modal = document.getElementById('brainLoginModal');
const statusDiv = document.getElementById('brainLoginStatus');
statusDiv.innerHTML = '';
statusDiv.className = 'login-status';
// 清除之前的输入
document.getElementById('brainUsername').value = '';
document.getElementById('brainPassword').value = '';
modal.style.display = 'block';
document.getElementById('brainUsername').focus();
}
// 关闭 BRAIN 登录模态框
function closeBrainLoginModal() {
const modal = document.getElementById('brainLoginModal');
const loginBtn = document.getElementById('loginBtn');
// 如果登录正在进行中,不允许关闭
if (loginBtn.disabled) {
return;
}
modal.style.display = 'none';
}
// 通过代理服务器与 BRAIN 进行身份验证
async function authenticateBrain() {
const username = document.getElementById('brainUsername').value.trim();
const password = document.getElementById('brainPassword').value;
const statusDiv = document.getElementById('brainLoginStatus');
const loginBtn = document.getElementById('loginBtn');
const spinner = document.getElementById('loginSpinner');
const modal = document.getElementById('brainLoginModal');
if (!username || !password) {
showLoginStatus('请输入用户名和密码。', 'error');
return;
}
// 禁用所有输入框和按钮
document.getElementById('brainUsername').disabled = true;
document.getElementById('brainPassword').disabled = true;
document.getElementById('cancelBtn').disabled = true;
loginBtn.disabled = true;
loginBtn.textContent = '连接中...';
// 显示加载指示器
spinner.style.display = 'block';
// 禁用模态框关闭
modal.querySelector('.close').style.display = 'none';
// 显示加载状态
showLoginStatus('正在连接到代理服务器...', 'loading');
try {
showLoginStatus('正在与 BRAIN 进行身份验证...', 'loading');
// 通过代理服务器进行身份验证
const authResponse = await fetch(`${PROXY_BASE}/api/authenticate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
password: password
})
});
if (!authResponse.ok) {
const errorData = await authResponse.json();
throw new Error(errorData.error || '身份验证失败');
}
const authData = await authResponse.json();
brainSessionId = authData.session_id;
brainSession = { authenticated: true, username: username };
// 将会话 ID 存储在 localStorage 中供其他页面使用
localStorage.setItem('brain_session_id', brainSessionId);
// 立即获取运算符以支持 "Op" 按钮功能
showLoginStatus('正在加载运算符...', 'loading');
brainOperators = await getUserOperators();
// 更新 UI 显示已连接状态
updateConnectedState();
showLoginStatus(`连接成功!已加载 ${brainOperators.length} 个运算符。`, 'success');
// 禁用按钮以防止进一步点击
loginBtn.disabled = true;
document.getElementById('brainUsername').disabled = true;
document.getElementById('brainPassword').disabled = true;
// 短暂延迟后关闭模态框
setTimeout(() => {
// 在关闭前重新启用所有控件
document.getElementById('brainUsername').disabled = false;
document.getElementById('brainPassword').disabled = false;
document.getElementById('cancelBtn').disabled = false;
loginBtn.disabled = false;
loginBtn.textContent = '连接';
spinner.style.display = 'none';
modal.querySelector('.close').style.display = 'block';
closeBrainLoginModal();
}, 1500);
} catch (error) {
console.error('BRAIN 身份验证失败:', error);
showLoginStatus(`连接失败: ${error.message}`, 'error');
brainSession = null;
brainSessionId = null;
} finally {
// 重新启用所有控件
document.getElementById('brainUsername').disabled = false;
document.getElementById('brainPassword').disabled = false;
document.getElementById('cancelBtn').disabled = false;
loginBtn.disabled = false;
loginBtn.textContent = '连接';
spinner.style.display = 'none';
modal.querySelector('.close').style.display = 'block';
}
}
// 通过代理服务器获取用户运算符
async function getUserOperators() {
if (!brainSession || !brainSessionId) {
throw new Error('未通过 BRAIN 身份验证');
}
try {
const response = await fetch(`${PROXY_BASE}/api/operators`, {
method: 'GET',
headers: {
'Session-ID': brainSessionId
}
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '获取运算符失败');
}
const operators = await response.json();
console.log(`从 BRAIN API 接收到 ${operators.length} 个运算符`);
// 记录类别以验证我们拥有所有运算符类型
const categories = [...new Set(operators.map(op => op.category))].sort();
console.log(`运算符类别: ${categories.join(', ')}`);
return operators;
} catch (error) {
console.error('获取运算符失败:', error);
throw error;
}
}
// 通过代理服务器获取数据字段
async function getDataFields(region = 'USA', delay = 1, universe = 'TOP3000', datasetId = 'fundamental6') {
if (!brainSession || !brainSessionId) {
throw new Error('未通过 BRAIN 身份验证');
}
try {
const params = new URLSearchParams({
region: region,
delay: delay.toString(),
universe: universe,
dataset_id: datasetId
});
const response = await fetch(`${PROXY_BASE}/api/datafields?${params}`, {
method: 'GET',
headers: {
'Session-ID': brainSessionId
}
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '获取数据字段失败');
}
return await response.json();
} catch (error) {
console.error('获取数据字段失败:', error);
throw error;
}
}
// 更新 UI 显示已连接状态
function updateConnectedState() {
const connectBtn = document.getElementById('connectToBrain');
connectBtn.textContent = '已连接到 BRAIN';
connectBtn.className = 'btn btn-brain connected';
// 在语法错误区域显示连接信息
const errorsDiv = document.getElementById('grammarErrors');
errorsDiv.innerHTML = `<div class="success-message">
✓ 已成功连接到 WorldQuant BRAIN<br>
<strong>用户名:</strong> ${brainSession.username}<br>
<strong>已加载运算符:</strong> ${brainOperators ? brainOperators.length : 0}<br>
<em>数据字段将在需要时加载。</em>
</div>`;
// 5 秒后自动隐藏消息
setTimeout(() => {
if (errorsDiv.innerHTML.includes('已成功连接')) {
errorsDiv.innerHTML = '';
}
}, 5000);
}
// 显示登录状态消息
function showLoginStatus(message, type) {
const statusDiv = document.getElementById('brainLoginStatus');
statusDiv.textContent = message;
statusDiv.className = `login-status ${type}`;
}
// 检查是否已连接到 BRAIN
function isConnectedToBrain() {
return brainSession !== null && brainSessionId !== null;
}
// 获取所有可用运算符(按需获取)
async function getAllOperators() {
if (!brainOperators && isConnectedToBrain()) {
try {
brainOperators = await getUserOperators();
} catch (error) {
console.error('按需获取运算符失败:', error);
return [];
}
}
return brainOperators || [];
}
// 同步获取已加载的运算符(用于 UI 组件)
function getLoadedOperators() {
return brainOperators || [];
}
// 获取所有可用数据字段(按需获取)
async function getAllDataFields() {
if (!brainDataFields && isConnectedToBrain()) {
try {
brainDataFields = await getDataFields();
} catch (error) {
console.error('按需获取数据字段失败:', error);
return [];
}
}
return brainDataFields || [];
}
// 按类别获取运算符(支持按需加载)
async function getOperatorsByCategory(category) {
const operators = await getAllOperators();
return operators.filter(op => op.category === category);
}
// 搜索运算符(支持按需加载)
async function searchOperators(searchTerm) {
const operators = await getAllOperators();
const term = searchTerm.toLowerCase();
return operators.filter(op =>
op.name.toLowerCase().includes(term) ||
op.category.toLowerCase().includes(term)
);
}
// 搜索数据字段(支持按需加载)
async function searchDataFields(searchTerm) {
const dataFields = await getAllDataFields();
const term = searchTerm.toLowerCase();
return dataFields.filter(field =>
field.id.toLowerCase().includes(term) ||
field.description.toLowerCase().includes(term)
);
}
// 从 BRAIN 注销
async function logoutFromBrain() {
if (brainSessionId) {
try {
await fetch(`${PROXY_BASE}/api/logout`, {
method: 'POST',
headers: {
'Session-ID': brainSessionId
}
});
} catch (error) {
console.warn('从代理服务器注销失败:', error);
}
}
// 清除本地会话数据
brainSession = null;
brainSessionId = null;
brainOperators = null;
brainDataFields = null;
// 清除 localStorage
localStorage.removeItem('brain_session_id');
// 更新 UI
const connectBtn = document.getElementById('connectToBrain');
connectBtn.textContent = '连接到 BRAIN';
connectBtn.className = 'btn btn-brain';
}
// 在页面加载时检查会话有效性
async function checkSessionValidity() {
if (brainSessionId) {
try {
const response = await fetch(`${PROXY_BASE}/api/status`, {
method: 'GET',
headers: {
'Session-ID': brainSessionId
}
});
if (response.ok) {
const data = await response.json();
if (data.valid) {
brainSession = { authenticated: true, username: data.username };
// 更新 UI 显示已连接状态
updateConnectedState();
} else {
// 会话已过期,清除它
localStorage.removeItem('brain_session_id');
brainSessionId = null;
}
}
} catch (error) {
console.warn('检查会话有效性失败:', error);
}
}
}
// 在页面加载时初始化
document.addEventListener('DOMContentLoaded', checkSessionValidity);
// 导出函数供其他模块使用
window.brainAPI = {
openBrainLoginModal,
closeBrainLoginModal,
authenticateBrain,
isConnectedToBrain,
getAllOperators,
getAllDataFields,
getDataFields,
getOperatorsByCategory,
searchOperators,
searchDataFields,
logoutFromBrain,
getLoadedOperators
};