This commit is contained in:
jack
2026-04-22 11:31:11 +08:00
parent 2f420e3348
commit 61633b21b4
25 changed files with 14763 additions and 0 deletions
+290
View File
@@ -0,0 +1,290 @@
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;
}
}
}
} 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 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并获取选项';
}
});
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 toggleCategory(btn) {
const allBtn = document.getElementById('cat-all');
const isAllBtn = (btn === allBtn);
if (isAllBtn) {
allBtn.style.backgroundColor = '#000080';
allBtn.style.color = 'white';
const otherBtns = categoryButtons.querySelectorAll('button:not(#cat-all)');
otherBtns.forEach(b => {
b.style.backgroundColor = '#c0c0c0';
b.style.color = 'black';
});
} else {
if (btn.style.backgroundColor === 'rgb(0, 0, 128)') {
btn.style.backgroundColor = '#c0c0c0';
btn.style.color = 'black';
} else {
btn.style.backgroundColor = '#000080';
btn.style.color = 'white';
}
const anySelected = categoryButtons.querySelectorAll('button:not(#cat-all)');
let hasSelected = false;
anySelected.forEach(b => {
if (b.style.backgroundColor === 'rgb(0, 0, 128)') {
hasSelected = true;
}
});
if (hasSelected) {
allBtn.style.backgroundColor = '#c0c0c0';
allBtn.style.color = 'black';
} else {
allBtn.style.backgroundColor = '#000080';
allBtn.style.color = 'white';
}
}
}
regionSelect.addEventListener('change', () => {
const selectedRegion = regionSelect.value;
delaySelect.innerHTML = '<option value="">-- 先选择地区 --</option>';
universeSelect.innerHTML = '<option value="">-- 先选择Delay --</option>';
delaySelect.disabled = true;
universeSelect.disabled = true;
if (selectedRegion && optionsData[selectedRegion]) {
const delays = Object.keys(optionsData[selectedRegion]);
delays.forEach(delay => {
const option = document.createElement('option');
option.value = delay;
option.textContent = delay;
delaySelect.appendChild(option);
});
delaySelect.disabled = false;
}
});
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,
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;
const allBtn = document.getElementById('cat-all');
let selectedCategories = [];
if (allBtn.style.backgroundColor !== 'rgb(0, 0, 128)') {
const categoryBtns = categoryButtons.querySelectorAll('button:not(#cat-all)');
categoryBtns.forEach(btn => {
if (btn.style.backgroundColor === 'rgb(0, 0, 128)') {
selectedCategories.push(btn.dataset.value);
}
});
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 candidateCount = result.candidates ? result.candidates.length : 0;
const errorCount = result.expressions_error ? result.expressions_error.length : 0;
alert('生成完成!成功: ' + successCount + ' 个, 候选: ' + candidateCount + ' 个, 错误: ' + errorCount + ' 个');
// 显示下载按钮
downloadBtn.style.display = 'block';
downloadBtn.onclick = function() {
const alphaId = document.getElementById('alphaId').value.trim();
window.location.href = '/api/download/' + alphaId;
};
} else {
alert('生成失败: ' + (result.error || '未知错误'));
downloadBtn.style.display = 'none';
}
} catch (error) {
alert('请求失败: ' + error.message);
downloadBtn.style.display = 'none';
} finally {
submitBtn.disabled = false;
submitBtn.textContent = '生成变种';
}
});
+148
View File
@@ -0,0 +1,148 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpha Transformer - </title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<div class="header">
<h1>Alpha Transformer</h1>
<p> - AI驱动的Alpha变种生成器</p>
</div>
<div class="content">
<div class="instructions">
<h3>使用说明</h3>
<ul>
<li>输入您的种子Alpha ID,系统将基于BRAIN平台生成多个变种</li>
<li>配置您的LLM(支持OpenAI兼容接口)和BRAIN凭证</li>
<li>点击生成按钮,系统将自动完成所有处理并返回结果</li>
</ul>
</div>
<form id="transformerForm">
<div class="form-section">
<h2>BRAIN 凭证</h2>
<div class="form-row">
<div class="form-group">
<label for="brainUsername">用户名 *</label>
<input type="text" id="brainUsername" name="brain_username" required
placeholder="BRAIN平台用户名">
</div>
<div class="form-group">
<label for="brainPassword">密码 *</label>
<input type="password" id="brainPassword" name="brain_password" required
placeholder="BRAIN平台密码">
</div>
</div>
<button type="button" class="btn" id="loginAndFetchBtn" style="margin-top: 10px;">
登录BRAIN并获取选项
</button>
</div>
<div class="form-section">
<h2>基本信息</h2>
<div class="form-group">
<label for="alphaId">Alpha ID *</label>
<input type="text" id="alphaId" name="alpha_id" required
placeholder="例如: ak2YPVxv">
</div>
</div>
<div class="form-section">
<h2>LLM 配置</h2>
<div class="form-row">
<div class="form-group">
<label for="llmApiKey">API Key *</label>
<input type="password" id="llmApiKey" name="llm_api_key" required
placeholder="您的LLM API密钥">
</div>
<div class="form-group">
<label for="llmBaseUrl">Base URL *</label>
<input type="text" id="llmBaseUrl" name="llm_base_url" required
value="https://api.moonshot.cn/v1"
placeholder="例如: https://api.moonshot.cn/v1">
</div>
</div>
<div class="form-group">
<label for="llmModel">模型名称 *</label>
<input type="text" id="llmModel" name="llm_model" required
value="kimi-k2.5"
placeholder="例如: kimi-k2.5, gpt-4">
</div>
</div>
<div class="form-section">
<h2>高级选项(可选)</h2>
<div class="form-group">
<label for="topNDatafield">Datafield Top N (候选数量,建议低于50)</label>
<input type="number" id="topNDatafield" name="top_n_datafield"
value="50" min="1" max="100">
</div>
<div class="form-group">
<label for="dataType">数据类型 (Data Type)</label>
<select id="dataType" name="data_type">
<option value="MATRIX" selected>MATRIX</option>
<option value="VECTOR">VECTOR</option>
</select>
</div>
<div class="form-group">
<label>目标地区 (可选 - 留空则使用种子Alpha的默认值)</label>
<select id="region" name="region" disabled>
<option value="">-- 登录后获取选项 --</option>
</select>
</div>
<div class="form-group">
<label for="delay">目标Delay (可选 - 留空则使用种子Alpha的默认值)</label>
<select id="delay" name="delay" disabled>
<option value="">-- 先选择地区 --</option>
</select>
</div>
<div class="form-group">
<label for="universe">目标股票池 (可选 - 留空则使用种子Alpha的默认值)</label>
<select id="universe" name="universe" disabled>
<option value="">-- 先选择Delay --</option>
</select>
</div>
<div class="form-group">
<label>目标数据类别 (可多选)</label>
<div id="category-buttons">
<button type="button" class="btn" id="cat-all" data-value="" onclick="toggleCategory(this)">
不筛选 (默认)
</button>
</div>
</div>
</div>
<button type="submit" class="btn submit-btn" id="submitBtn">
生成变种
</button>
<button type="button" class="btn download-btn" id="downloadBtn" style="display: none; margin-top: 10px; background: linear-gradient(135deg, var(--monokai-cyan) 0%, #4fc3f7 100%);">
下载结果 (ZIP)
</button>
</form>
</div>
</div>
<script src="/app.js"></script>
</body>
</html>
+343
View File
@@ -0,0 +1,343 @@
/* Monokai Theme for Alpha Transformer */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Monokai Color Palette */
:root {
--monokai-bg: #272822;
--monokai-fg: #f8f8f2;
--monokai-comment: #75715e;
--monokai-red: #f92672;
--monokai-orange: #fd971f;
--monokai-yellow: #e6db74;
--monokai-green: #a6e22e;
--monokai-cyan: #66d9ef;
--monokai-blue: #268bd2;
--monokai-purple: #ae81ff;
--monokai-dark: #1e1f1c;
--monokai-border: #3e3d32;
}
body {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
background: var(--monokai-dark);
min-height: 100vh;
padding: 20px;
color: var(--monokai-fg);
}
.container {
max-width: 900px;
margin: 0 auto;
background: var(--monokai-bg);
border: 2px solid var(--monokai-border);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
.header {
background: linear-gradient(135deg, var(--monokai-dark) 0%, var(--monokai-bg) 100%);
color: var(--monokai-green);
padding: 20px;
font-weight: bold;
font-size: 18px;
border-bottom: 2px solid var(--monokai-border);
border-radius: 8px 8px 0 0;
}
.header h1 {
color: var(--monokai-green);
text-shadow: 0 0 10px rgba(166, 226, 46, 0.3);
}
.header p {
color: var(--monokai-comment);
font-size: 14px;
margin-top: 5px;
}
.content {
padding: 20px;
}
.form-section {
margin-bottom: 20px;
background: var(--monokai-dark);
border: 1px solid var(--monokai-border);
border-radius: 6px;
padding: 20px;
}
.form-section h2 {
color: var(--monokai-cyan);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--monokai-border);
font-size: 16px;
font-weight: bold;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: var(--monokai-orange);
font-weight: normal;
font-size: 13px;
}
.form-group input,
.form-group select {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--monokai-border);
border-radius: 4px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 13px;
background: var(--monokai-bg);
color: var(--monokai-fg);
transition: all 0.3s ease;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: var(--monokai-cyan);
box-shadow: 0 0 0 2px rgba(102, 217, 239, 0.2);
}
.form-group input::placeholder {
color: var(--monokai-comment);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.btn {
padding: 10px 20px;
background: var(--monokai-purple);
color: var(--monokai-bg);
border: none;
border-radius: 4px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 13px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.btn:hover {
background: #c5a3ff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(174, 129, 255, 0.3);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
background: var(--monokai-comment);
color: var(--monokai-dark);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.submit-btn {
width: 100%;
padding: 14px 28px;
background: linear-gradient(135deg, var(--monokai-green) 0%, #8bc34a 100%);
color: var(--monokai-bg);
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: bold;
margin-top: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.submit-btn:hover {
background: linear-gradient(135deg, #b8e068 0%, #9ccc65 100%);
box-shadow: 0 4px 15px rgba(166, 226, 46, 0.4);
}
.instructions {
background: var(--monokai-dark);
border: 1px solid var(--monokai-border);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
.instructions h3 {
color: var(--monokai-yellow);
margin-bottom: 12px;
font-size: 15px;
font-weight: bold;
}
.instructions ul {
color: var(--monokai-fg);
padding-left: 25px;
}
.instructions li {
margin-bottom: 8px;
line-height: 1.6;
}
.instructions li::marker {
color: var(--monokai-red);
}
#category-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px;
border: 1px solid var(--monokai-border);
border-radius: 4px;
background: var(--monokai-bg);
}
/* Loading animation */
.loading {
display: none;
text-align: center;
padding: 40px;
background: var(--monokai-dark);
border: 1px solid var(--monokai-border);
border-radius: 6px;
margin-top: 20px;
}
.loading.active {
display: block;
}
.spinner {
border: 3px solid var(--monokai-border);
border-top: 3px solid var(--monokai-cyan);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Error message */
.error-message {
background: rgba(249, 38, 114, 0.1);
color: var(--monokai-red);
padding: 15px;
margin-top: 15px;
border: 1px solid var(--monokai-red);
border-radius: 4px;
display: none;
font-weight: bold;
}
.error-message.active {
display: block;
}
/* Result section */
.result-section {
margin-top: 20px;
display: none;
}
.result-section.active {
display: block;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 12px;
background: var(--monokai-dark);
color: var(--monokai-green);
font-weight: bold;
border: 1px solid var(--monokai-border);
border-radius: 4px 4px 0 0;
}
.result-content {
background: var(--monokai-bg);
border: 1px solid var(--monokai-border);
border-radius: 0 0 4px 4px;
padding: 15px;
max-height: 400px;
overflow-y: auto;
}
.result-content pre {
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
color: var(--monokai-fg);
line-height: 1.5;
}
.btn-copy {
padding: 6px 14px;
background: var(--monokai-cyan);
color: var(--monokai-bg);
border: none;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-copy:hover {
background: #87e8f5;
box-shadow: 0 2px 8px rgba(102, 217, 239, 0.3);
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--monokai-dark);
}
::-webkit-scrollbar-thumb {
background: var(--monokai-comment);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--monokai-purple);
}
/* Selection styling */
::selection {
background: var(--monokai-purple);
color: var(--monokai-bg);
}