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.

299 lines
11 KiB

const API_BASE = '/api/ai-config';
let currentConfig = null;
let selectedProvider = 'openai';
document.addEventListener('DOMContentLoaded', function() {
loadProviders();
loadConfig();
initEventListeners();
});
function initEventListeners() {
document.getElementById('api-provider').addEventListener('change', function() {
selectedProvider = this.value;
updateProviderModels();
});
document.getElementById('temperature').addEventListener('input', function() {
document.getElementById('temp-value').textContent = this.value;
});
}
async function loadProviders() {
try {
const response = await fetch(`${API_BASE}/providers`);
const data = await response.json();
if (data.success) {
renderProviders(data.data);
}
} catch (error) {
console.error('加载提供商失败:', error);
renderProviders(getDefaultProviders());
}
}
function getDefaultProviders() {
return [
{ id: 'openai', name: 'OpenAI', icon: 'fas fa-brain' },
{ id: 'anthropic', name: 'Claude', icon: 'fas fa-robot' },
{ id: 'google', name: 'Gemini', icon: 'fas fa-gem' },
{ id: 'aliyun', name: '通义千问', icon: 'fas fa-cloud' },
{ id: 'baidu', name: '文心一言', icon: 'fas fa-comments' },
{ id: 'zhipu', name: '智谱清言', icon: 'fas fa-lightbulb' }
];
}
function renderProviders(providers) {
const grid = document.getElementById('provider-grid');
const iconMap = {
'openai': 'fas fa-brain',
'anthropic': 'fas fa-robot',
'google': 'fas fa-gem',
'aliyun': 'fas fa-cloud',
'baidu': 'fas fa-comments',
'zhipu': 'fas fa-lightbulb',
'custom': 'fas fa-cog'
};
grid.innerHTML = providers.map(p => `
<div class="provider-card ${p.id === selectedProvider ? 'active' : ''}" data-provider="${p.id}">
<i class="${iconMap[p.id] || 'fas fa-cog'}"></i>
<div class="provider-name">${p.name}</div>
</div>
`).join('');
grid.querySelectorAll('.provider-card').forEach(card => {
card.addEventListener('click', function() {
grid.querySelectorAll('.provider-card').forEach(c => c.classList.remove('active'));
this.classList.add('active');
selectedProvider = this.dataset.provider;
document.getElementById('api-provider').value = selectedProvider;
updateProviderModels();
});
});
}
function updateProviderModels() {
const modelSelect = document.getElementById('model-id');
const modelMap = {
'openai': ['gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'],
'anthropic': ['claude-3-opus', 'claude-3-sonnet', 'claude-3-haiku'],
'google': ['gemini-pro', 'gemini-pro-vision'],
'aliyun': ['qwen-max', 'qwen-plus', 'qwen-turbo'],
'baidu': ['ernie-4.0', 'ernie-3.5', 'ernie-speed'],
'zhipu': ['glm-4', 'glm-3-turbo'],
'custom': ['custom-model']
};
const apiBaseMap = {
'openai': 'https://api.openai.com/v1',
'anthropic': 'https://api.anthropic.com/v1',
'google': 'https://generativelanguage.googleapis.com/v1beta',
'aliyun': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
'baidu': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop',
'zhipu': 'https://open.bigmodel.cn/api/paas/v4',
'custom': ''
};
const models = modelMap[selectedProvider] || ['custom-model'];
modelSelect.innerHTML = models.map(m => `<option value="${m}">${m}</option>`).join('');
document.getElementById('api-base').value = apiBaseMap[selectedProvider] || '';
}
async function loadConfig() {
try {
const response = await fetch(API_BASE);
const result = await response.json();
if (result.success && result.data) {
currentConfig = result.data;
populateForm(currentConfig);
renderModelsList(currentConfig.models || []);
}
} catch (error) {
console.error('加载配置失败:', error);
}
}
function populateForm(config) {
if (config.models && config.models.length > 0) {
const activeModel = config.models.find(m => m.enabled) || config.models[0];
document.getElementById('api-provider').value = activeModel.provider || 'openai';
document.getElementById('api-key').value = activeModel.api_key || '';
document.getElementById('api-base').value = activeModel.api_base || '';
document.getElementById('model-id').value = activeModel.model_id || 'gpt-4o';
document.getElementById('temperature').value = activeModel.temperature || 0.7;
document.getElementById('temp-value').textContent = activeModel.temperature || 0.7;
document.getElementById('max-tokens').value = activeModel.max_tokens || 2000;
}
if (config.analysis_settings) {
document.getElementById('enable-technical').checked = config.analysis_settings.enable_technical_analysis !== false;
document.getElementById('enable-fundamental').checked = config.analysis_settings.enable_fundamental_analysis === true;
document.getElementById('enable-sentiment').checked = config.analysis_settings.enable_sentiment_analysis === true;
document.getElementById('risk-tolerance').value = config.analysis_settings.risk_tolerance || 'medium';
document.getElementById('max-position').value = config.analysis_settings.max_position_pct || 10;
}
}
function renderModelsList(models) {
const list = document.getElementById('models-list');
if (!models || models.length === 0) {
list.innerHTML = '<div class="empty-state">暂无已保存的模型</div>';
return;
}
list.innerHTML = models.map((model, index) => `
<div class="model-item">
<div class="model-info">
<div class="model-status ${model.enabled ? 'active' : 'inactive'}"></div>
<div>
<div class="model-name">${model.model_name || model.model_id}</div>
<div class="model-provider">${getProviderName(model.provider || model.api_base)}</div>
</div>
</div>
<div class="model-actions">
${!model.enabled ? `<button class="btn-set-active" onclick="setActiveModel(${index})">设为默认</button>` : '<span class="active-badge">默认</span>'}
<button class="btn-delete" onclick="deleteModel(${index})"><i class="fas fa-trash"></i></button>
</div>
</div>
`).join('');
}
function getProviderName(apiBase) {
const map = {
'openai': 'OpenAI',
'anthropic': 'Anthropic',
'google': 'Google',
'aliyun': '阿里云',
'baidu': '百度',
'zhipu': '智谱'
};
return map[apiBase] || apiBase;
}
function toggleApiKeyVisibility() {
const input = document.getElementById('api-key');
const icon = document.querySelector('.toggle-visibility i');
if (input.type === 'password') {
input.type = 'text';
icon.className = 'fas fa-eye-slash';
} else {
input.type = 'password';
icon.className = 'fas fa-eye';
}
}
async function testConnection() {
const resultEl = document.getElementById('test-result');
resultEl.textContent = '测试中...';
resultEl.className = 'test-result';
const config = {
model_name: document.getElementById('model-id').value,
api_key: document.getElementById('api-key').value,
api_base: document.getElementById('api-base').value,
model_id: document.getElementById('model-id').value,
temperature: parseFloat(document.getElementById('temperature').value),
max_tokens: parseInt(document.getElementById('max-tokens').value),
enabled: true
};
try {
const response = await fetch(`${API_BASE}/test`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const data = await response.json();
if (data.success) {
resultEl.textContent = '✓ 连接成功';
resultEl.className = 'test-result success';
} else {
resultEl.textContent = '✗ ' + data.message;
resultEl.className = 'test-result error';
}
} catch (error) {
resultEl.textContent = '✗ 连接失败: ' + error.message;
resultEl.className = 'test-result error';
}
}
async function saveConfig() {
const models = currentConfig?.models || [];
const existingIndex = models.findIndex(m => m.provider === selectedProvider);
const newModel = {
model_name: document.getElementById('model-id').value,
provider: selectedProvider,
api_key: document.getElementById('api-key').value,
api_base: document.getElementById('api-base').value,
model_id: document.getElementById('model-id').value,
temperature: parseFloat(document.getElementById('temperature').value),
max_tokens: parseInt(document.getElementById('max-tokens').value),
enabled: true
};
if (existingIndex >= 0) {
models[existingIndex] = { ...models[existingIndex], ...newModel };
} else {
models.push(newModel);
}
const config = {
models: models,
active_model: selectedProvider,
analysis_settings: {
enable_technical_analysis: document.getElementById('enable-technical').checked,
enable_fundamental_analysis: document.getElementById('enable-fundamental').checked,
enable_sentiment_analysis: document.getElementById('enable-sentiment').checked,
risk_tolerance: document.getElementById('risk-tolerance').value,
max_position_pct: parseInt(document.getElementById('max-position').value)
}
};
try {
const response = await fetch(API_BASE, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const data = await response.json();
if (data.success) {
alert('配置保存成功!');
currentConfig = config;
renderModelsList(models);
} else {
alert('保存失败: ' + data.message);
}
} catch (error) {
alert('保存失败: ' + error.message);
}
}
function setActiveModel(index) {
if (!currentConfig || !currentConfig.models) return;
currentConfig.models.forEach((m, i) => {
m.enabled = i === index;
});
saveConfig();
}
function deleteModel(index) {
if (!confirm('确定要删除这个模型吗?')) return;
if (!currentConfig || !currentConfig.models) return;
currentConfig.models.splice(index, 1);
saveConfig();
}
function addNewModel() {
document.getElementById('api-key').value = '';
document.getElementById('api-key').focus();
}