Files
piano-plan/app/templates/api_settings.html
T

237 lines
8.5 KiB
HTML

{% extends "base.html" %}
{% block title %}API设置 - 钢琴练习方案系统{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-key"></i> AI API 配置</h5>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> 配置AI生成练习报告所需的API参数。
</div>
<form id="apiConfigForm">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">提供商</label>
<select class="form-select" id="apiProvider" onchange="onProviderChange()">
<option value="minimax">MiniMax (Token Plan)</option>
<option value="volcengine">火山引擎 (Volcengine)</option>
<option value="deepseek">DeepSeek</option>
<option value="openai">OpenAI</option>
<option value="openrouter">OpenRouter</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">模型</label>
<select class="form-select" id="apiModel">
<option value="MiniMax-M2.7-highspeed">MiniMax-M2.7-highspeed</option>
<option value="doubao-seed-2.0-pro">doubao-seed-2.0-pro</option>
<option value="doubao-seed-code">doubao-seed-code</option>
<option value="doubao-seed-2.0-lite">doubao-seed-2.0-lite</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">API Key</label>
<div class="input-group">
<input type="password" class="form-control" id="apiKey" placeholder="请输入API Key">
<button class="btn btn-outline-secondary" type="button" onclick="toggleApiKey()">
<i class="bi bi-eye" id="apiKeyToggleIcon"></i>
</button>
</div>
<small class="text-muted" id="apiKeyPreview"></small>
</div>
<div class="mb-3">
<label class="form-label">API Endpoint</label>
<input type="text" class="form-control" id="apiEndpoint" placeholder="https://ark.cn-beijing.volces.com/api/coding/v3">
</div>
<div class="mb-3">
<label class="form-label">Temperature</label>
<input type="number" class="form-control" id="apiTemperature" min="0" max="2" step="0.1" value="0.7">
<small class="text-muted">控制输出的随机性,值越大越有创造性</small>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-primary" onclick="saveApiConfig()">
<i class="bi bi-save"></i> 保存配置
</button>
<button type="button" class="btn btn-outline-secondary" onclick="testApiConnection()">
<i class="bi bi-plug"></i> 测试连接
</button>
</div>
<div class="mt-3" id="apiTestResult"></div>
</form>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h6 class="mb-0"><i class="bi bi-lightbulb"></i> 推荐配置</h6>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>提供商</th>
<th>推荐模型</th>
<th>Endpoint</th>
</tr>
</thead>
<tbody>
<tr>
<td>MiniMax</td>
<td>MiniMax-M2.7-highspeed</td>
<td><code>https://api.minimaxi.com/anthropic/v1</code></td>
</tr>
<tr>
<td>火山方舟</td>
<td>doubao-seed-2.0-pro</td>
<td><code>https://ark.cn-beijing.volces.com/api/coding/v3</code></td>
</tr>
<tr>
<td>DeepSeek</td>
<td>deepseek-chat</td>
<td><code>https://api.deepseek.com</code></td>
</tr>
<tr>
<td>OpenRouter</td>
<td>deepseek/deepseek-r1</td>
<td><code>https://openrouter.ai/api/v1</code></td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
window.pageInit = function(data) {
if (data.role !== 'admin') {
window.location.href = '/';
return;
}
loadApiConfig();
};
const providerDefaults = {
'minimax': {
endpoint: 'https://api.minimaxi.com/anthropic/v1',
model: 'MiniMax-M2.7-highspeed',
temperature: 0.7,
api_key: ''
},
'volcengine': {
endpoint: 'https://ark.cn-beijing.volces.com/api/coding/v3',
model: 'doubao-seed-2.0-pro',
temperature: 0.7,
api_key: ''
},
'deepseek': {
endpoint: 'https://api.deepseek.com',
model: 'deepseek-chat',
temperature: 0.7,
api_key: ''
},
'openai': {
endpoint: 'https://api.openai.com/v1',
model: 'gpt-4o-mini',
temperature: 0.7,
api_key: ''
},
'openrouter': {
endpoint: 'https://openrouter.ai/api/v1',
model: 'anthropic/claude-3-haiku',
temperature: 0.7,
api_key: ''
}
};
async function onProviderChange() {
const provider = document.getElementById('apiProvider').value;
const defaults = providerDefaults[provider] || providerDefaults['volcengine'];
document.getElementById('apiModel').value = defaults.model;
document.getElementById('apiEndpoint').value = defaults.endpoint;
document.getElementById('apiTemperature').value = defaults.temperature;
document.getElementById('apiKey').value = defaults.api_key || '';
}
async function saveApiConfig(silent = false, overrideProvider = null) {
const provider = overrideProvider || document.getElementById('apiProvider').value;
const config = {
provider: provider,
model: document.getElementById('apiModel').value,
api_key: document.getElementById('apiKey').value,
base_url: document.getElementById('apiEndpoint').value,
temperature: parseFloat(document.getElementById('apiTemperature').value)
};
const r = await fetch('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const data = await r.json();
if (!silent) {
if (data.error) {
alert(data.error);
} else {
alert('保存成功');
}
}
return data;
}
async function loadApiConfig() {
try {
const r = await fetch('/api/config');
const config = await r.json();
document.getElementById('apiProvider').value = config.provider || 'volcengine';
document.getElementById('apiModel').value = config.model || 'doubao-seed-2.0-pro';
document.getElementById('apiKey').value = config.api_key || '';
document.getElementById('apiEndpoint').value = config.base_url || '';
document.getElementById('apiTemperature').value = config.temperature || 0.7;
if (config.api_key_preview) {
document.getElementById('apiKeyPreview').textContent = '当前: ' + config.api_key_preview;
}
} catch (e) {
console.error(e);
}
}
async function testApiConnection() {
const resultDiv = document.getElementById('apiTestResult');
resultDiv.innerHTML = '<div class="alert alert-info">测试中...</div>';
const r = await fetch('/api/config/test', { method: 'POST' });
const data = await r.json();
if (data.success) {
resultDiv.innerHTML = '<div class="alert alert-success"><i class="bi bi-check-circle"></i> 连接成功</div>';
} else {
resultDiv.innerHTML = '<div class="alert alert-danger"><i class="bi bi-x-circle"></i> 连接失败: ' + (data.error || '未知错误') + '</div>';
}
}
function toggleApiKey() {
const input = document.getElementById('apiKey');
const icon = document.getElementById('apiKeyToggleIcon');
if (input.type === 'password') {
input.type = 'text';
icon.className = 'bi bi-eye-slash';
} else {
input.type = 'password';
icon.className = 'bi bi-eye';
}
}
</script>
{% endblock %}