++
This commit is contained in:
@@ -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 = '生成变种';
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user