579 lines
30 KiB
HTML
579 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>股票分析系统</title>
|
||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||
</head>
|
||
<body class="bg-gray-100">
|
||
<div class="container mx-auto px-4 py-8">
|
||
<h1 class="text-3xl font-bold mb-8 text-center">股票分析系统</h1>
|
||
|
||
<!-- 添加公告栏 -->
|
||
{% if announcement %}
|
||
<div class="max-w-4xl mx-auto mb-8">
|
||
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded">
|
||
<div class="flex">
|
||
<div class="flex-shrink-0">
|
||
<svg class="h-5 w-5 text-blue-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||
</svg>
|
||
</div>
|
||
<div class="ml-3">
|
||
<p class="text-sm text-blue-700">
|
||
{{ announcement }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="max-w-4xl mx-auto">
|
||
<!-- 批量分析 -->
|
||
<div class="bg-white p-6 rounded-lg shadow-md">
|
||
<h2 class="text-xl font-semibold mb-4">股票批量分析</h2>
|
||
|
||
<!-- API配置部分 -->
|
||
<div class="mb-6 border-b pb-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-medium text-gray-700">API配置</h3>
|
||
<button id="toggleApiConfig" class="text-blue-600 hover:text-blue-800 text-sm flex items-center">
|
||
<span id="toggleApiConfigText">显示配置</span>
|
||
<svg id="toggleApiConfigIcon" class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div id="apiConfigPanel" class="hidden space-y-4">
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label for="apiUrl" class="block text-sm font-medium text-gray-700 mb-1">API URL</label>
|
||
<input type="text" id="apiUrl"
|
||
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="例如: https://api.openai.com"
|
||
value="{{ default_api_url }}">
|
||
</div>
|
||
<div>
|
||
<label for="apiModel" class="block text-sm font-medium text-gray-700 mb-1">API 模型</label>
|
||
<input type="text" id="apiModel"
|
||
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="例如: gpt-3.5-turbo"
|
||
value="{{ default_api_model }}">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label for="apiKey" class="block text-sm font-medium text-gray-700 mb-1">API Key</label>
|
||
<input type="password" id="apiKey"
|
||
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="输入您的API Key">
|
||
<p class="mt-1 text-sm text-gray-500">如不填写,将使用系统默认配置</p>
|
||
</div>
|
||
<div class="flex justify-end">
|
||
<button id="resetApiConfig" class="text-gray-600 hover:text-gray-800 text-sm mr-3">
|
||
重置为默认
|
||
</button>
|
||
<button id="testApiConfig" class="bg-blue-100 text-blue-700 px-3 py-1 rounded hover:bg-blue-200 text-sm">
|
||
测试连接
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加市场类型选择 -->
|
||
<div class="mb-4">
|
||
<label for="marketType" class="block text-sm font-medium text-gray-700 mb-2">
|
||
选择市场类型
|
||
</label>
|
||
<select id="marketType" onchange="handleMarketTypeChange()"
|
||
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||
<option value="A">A股</option>
|
||
<option value="HK">港股</option>
|
||
<option value="US">美股</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 美股搜索框 -->
|
||
<div id="usStockSearch" class="mb-4 hidden">
|
||
<div class="relative">
|
||
<input type="text"
|
||
id="searchInput"
|
||
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="输入美股名称搜索(中文和英文都试下)"
|
||
oninput="debounceSearch(event)">
|
||
<!-- 添加搜索 loading 图标 -->
|
||
<div id="searchLoading" class="absolute right-3 top-2.5 hidden">
|
||
<svg class="animate-spin h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||
</svg>
|
||
</div>
|
||
<!-- 搜索结果下拉框 -->
|
||
<div id="searchResults"
|
||
class="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg hidden">
|
||
</div>
|
||
<!-- 添加错误提示 -->
|
||
<div id="searchError" class="hidden absolute z-10 w-full mt-1 p-3 bg-red-50 text-red-600 rounded-md border border-red-200">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 修改搜索相关函数 -->
|
||
<script>
|
||
async function searchUsStocks(keyword) {
|
||
if (!keyword) {
|
||
document.getElementById('searchResults').classList.add('hidden');
|
||
document.getElementById('searchError').classList.add('hidden');
|
||
return;
|
||
}
|
||
|
||
// 显示 loading
|
||
document.getElementById('searchLoading').classList.remove('hidden');
|
||
// 隐藏之前的错误信息
|
||
document.getElementById('searchError').classList.add('hidden');
|
||
|
||
try {
|
||
const response = await fetch(`/search_us_stocks?keyword=${encodeURIComponent(keyword)}`);
|
||
const data = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(data.error || '搜索失败');
|
||
}
|
||
|
||
displaySearchResults(data.results);
|
||
} catch (error) {
|
||
console.error('搜索出错:', error);
|
||
// 显示错误信息
|
||
const errorDiv = document.getElementById('searchError');
|
||
errorDiv.textContent = `搜索出错: ${error.message}`;
|
||
errorDiv.classList.remove('hidden');
|
||
// 隐藏搜索结果
|
||
document.getElementById('searchResults').classList.add('hidden');
|
||
} finally {
|
||
// 隐藏 loading
|
||
document.getElementById('searchLoading').classList.add('hidden');
|
||
}
|
||
}
|
||
</script>
|
||
<div class="mb-4">
|
||
<label for="batchStocks" class="block text-sm font-medium text-gray-700 mb-2">
|
||
输入代码
|
||
</label>
|
||
<textarea id="batchStocks"
|
||
class="w-full p-2 border rounded h-32"
|
||
placeholder="输入代码,支持多个代码(用回车或逗号分隔)"></textarea>
|
||
</div>
|
||
|
||
<!-- 添加新的 JavaScript 代码 -->
|
||
<script>
|
||
let searchTimeout;
|
||
|
||
function handleMarketTypeChange() {
|
||
const marketType = document.getElementById('marketType').value;
|
||
const searchDiv = document.getElementById('usStockSearch');
|
||
searchDiv.classList.toggle('hidden', marketType !== 'US');
|
||
|
||
// 清空搜索结果
|
||
document.getElementById('searchResults').innerHTML = '';
|
||
document.getElementById('searchInput').value = '';
|
||
}
|
||
|
||
function debounceSearch(event) {
|
||
clearTimeout(searchTimeout);
|
||
searchTimeout = setTimeout(() => {
|
||
searchUsStocks(event.target.value);
|
||
}, 300);
|
||
}
|
||
|
||
function displaySearchResults(results) {
|
||
const resultsDiv = document.getElementById('searchResults');
|
||
|
||
if (!results || results.length === 0) {
|
||
resultsDiv.classList.add('hidden');
|
||
return;
|
||
}
|
||
|
||
let html = '<div class="py-1">';
|
||
results.forEach(stock => {
|
||
html += `
|
||
<div class="px-4 py-2 hover:bg-gray-100 cursor-pointer flex justify-between items-center"
|
||
onclick="selectStock('${stock.symbol}')">
|
||
<div>
|
||
<div class="font-medium">${stock.name}</div>
|
||
<div class="text-sm text-gray-500">${stock.symbol}</div>
|
||
</div>
|
||
<div class="text-right">
|
||
<div class="font-medium">$${stock.price}</div>
|
||
<div class="text-sm text-gray-500">市值: ${formatMarketValue(stock.market_value)}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
html += '</div>';
|
||
|
||
resultsDiv.innerHTML = html;
|
||
resultsDiv.classList.remove('hidden');
|
||
}
|
||
|
||
function formatMarketValue(value) {
|
||
if (value >= 1e12) {
|
||
return (value / 1e12).toFixed(2) + '万亿';
|
||
} else if (value >= 1e8) {
|
||
return (value / 1e8).toFixed(2) + '亿';
|
||
} else if (value >= 1e4) {
|
||
return (value / 1e4).toFixed(2) + '万';
|
||
}
|
||
return value.toString();
|
||
}
|
||
|
||
function selectStock(symbol) {
|
||
const textarea = document.getElementById('batchStocks');
|
||
const currentValue = textarea.value.trim();
|
||
|
||
|
||
// 添加到 textarea
|
||
textarea.value = currentValue
|
||
? currentValue + '\n' + symbol
|
||
: symbol;
|
||
|
||
// 清空并隐藏搜索结果
|
||
document.getElementById('searchInput').value = '';
|
||
document.getElementById('searchResults').classList.add('hidden');
|
||
}
|
||
|
||
// 点击外部时隐藏搜索结果
|
||
document.addEventListener('click', function(event) {
|
||
const searchResults = document.getElementById('searchResults');
|
||
const searchInput = document.getElementById('searchInput');
|
||
|
||
if (!searchResults.contains(event.target) &&
|
||
!searchInput.contains(event.target)) {
|
||
searchResults.classList.add('hidden');
|
||
}
|
||
});
|
||
</script>
|
||
<button id="analyzeBtn" onclick="analyzeStocks()"
|
||
class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 flex items-center justify-center">
|
||
<span>开始分析</span>
|
||
<div id="loadingSpinner" class="hidden ml-2">
|
||
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||
</svg>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 结果展示 -->
|
||
<div id="results" class="mt-8">
|
||
<div class="flex justify-between items-center mb-6">
|
||
<h2 class="text-2xl font-bold text-gray-800">分析结果</h2>
|
||
<button onclick="copyAnalysisResults()"
|
||
class="flex items-center text-blue-600 hover:text-blue-700">
|
||
<svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/>
|
||
</svg>
|
||
复制分析结果
|
||
</button>
|
||
</div>
|
||
<div id="resultContent" class="space-y-8"></div>
|
||
</div>
|
||
|
||
<script>
|
||
function copyAnalysisResults() {
|
||
const resultContent = document.getElementById('resultContent');
|
||
if (!resultContent.textContent.trim()) {
|
||
alert('暂无分析结果可复制');
|
||
return;
|
||
}
|
||
|
||
// 提取需要复制的文本
|
||
let copyText = '';
|
||
const results = resultContent.querySelectorAll('.bg-white');
|
||
|
||
results.forEach(result => {
|
||
// 获取股票代码
|
||
const stockCode = result.querySelector('h3').textContent.trim();
|
||
copyText += `股票代码:${stockCode}\n`;
|
||
|
||
// 获取主要指标
|
||
const indicators = result.querySelectorAll('.flex.justify-between');
|
||
indicators.forEach(indicator => {
|
||
const label = indicator.querySelector('.text-gray-600').textContent;
|
||
const value = indicator.querySelector('.font-medium').textContent;
|
||
copyText += `${label}:${value}\n`;
|
||
});
|
||
|
||
// 获取 AI 分析内容
|
||
const aiAnalysis = result.querySelector('.prose').textContent;
|
||
copyText += `\nAI分析:\n${aiAnalysis}\n`;
|
||
|
||
copyText += '\n----------------------------------------\n\n';
|
||
});
|
||
|
||
// 复制到剪贴板
|
||
const textarea = document.createElement('textarea');
|
||
textarea.value = copyText;
|
||
document.body.appendChild(textarea);
|
||
textarea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textarea);
|
||
|
||
// 显示提示
|
||
alert('分析结果已复制到剪贴板');
|
||
}
|
||
</script>
|
||
<script>
|
||
let isAnalyzing = false;
|
||
|
||
async function analyzeStocks() {
|
||
if (isAnalyzing) return; // 防止重复点击
|
||
|
||
const stockInput = document.getElementById('batchStocks').value.trim();
|
||
const marketType = document.getElementById('marketType').value;
|
||
const analyzeBtn = document.getElementById('analyzeBtn');
|
||
const loadingSpinner = document.getElementById('loadingSpinner');
|
||
|
||
// 获取API配置
|
||
const apiUrl = document.getElementById('apiUrl').value.trim();
|
||
const apiKey = document.getElementById('apiKey').value.trim();
|
||
const apiModel = document.getElementById('apiModel').value.trim();
|
||
|
||
if (!stockInput) {
|
||
alert('请输入代码');
|
||
return;
|
||
}
|
||
|
||
const stockCodes = stockInput.split(/[\n,]/)
|
||
.map(code => code.trim())
|
||
.filter(code => code.length > 0);
|
||
|
||
try {
|
||
isAnalyzing = true;
|
||
analyzeBtn.disabled = true;
|
||
loadingSpinner.classList.remove('hidden');
|
||
analyzeBtn.querySelector('span').textContent = '分析中...';
|
||
|
||
const response = await fetch('/analyze', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
stock_codes: stockCodes,
|
||
market_type: marketType, // 添加市场类型参数
|
||
api_url: apiUrl,
|
||
api_key: apiKey,
|
||
api_model: apiModel
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(data.error || '分析失败');
|
||
}
|
||
|
||
const results = Array.isArray(data.results) ? data.results : [data];
|
||
displayResults(results);
|
||
} catch (error) {
|
||
alert('请求失败: ' + error.message);
|
||
document.getElementById('resultContent').innerHTML = `
|
||
<div class="p-4 bg-red-50 text-red-600 rounded">
|
||
分析出错:${error.message}
|
||
</div>
|
||
`;
|
||
} finally {
|
||
isAnalyzing = false;
|
||
analyzeBtn.disabled = false;
|
||
loadingSpinner.classList.add('hidden');
|
||
analyzeBtn.querySelector('span').textContent = '开始分析';
|
||
}
|
||
}
|
||
|
||
function displayResults(results) {
|
||
const resultContent = document.getElementById('resultContent');
|
||
if (!results || results.length === 0) {
|
||
resultContent.innerHTML = '<div class="p-6 bg-yellow-50 text-yellow-600 rounded-lg text-center">没有分析结果</div>';
|
||
return;
|
||
}
|
||
|
||
let html = '';
|
||
results.forEach(result => {
|
||
// 根据市场类型设置货币符号
|
||
const currencySymbol = (() => {
|
||
switch(document.getElementById('marketType').value) {
|
||
case 'US':
|
||
return '$';
|
||
case 'HK':
|
||
return 'HK$';
|
||
case 'A':
|
||
default:
|
||
return '¥';
|
||
}
|
||
})();
|
||
|
||
html += `
|
||
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
|
||
<!-- 头部信息 -->
|
||
<div class="bg-gradient-to-r from-blue-600 to-blue-700 px-6 py-4">
|
||
<h3 class="text-xl font-bold text-white">
|
||
${result.stock_code}
|
||
</h3>
|
||
</div>
|
||
|
||
<!-- 主要指标 -->
|
||
<div class="p-6">
|
||
<div class="grid grid-cols-2 gap-6 mb-6">
|
||
<div class="space-y-3">
|
||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||
<span class="text-gray-600">分析时间</span>
|
||
<span class="font-medium">${result.analysis_date}</span>
|
||
</div>
|
||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||
<span class="text-gray-600">当前价格</span>
|
||
<span class="font-medium">${currencySymbol}${result.price.toFixed(2)}</span>
|
||
</div>
|
||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||
<span class="text-gray-600">价格变动</span>
|
||
<span class="font-medium ${result.price_change >= 0 ? 'text-red-500' : 'text-green-500'}">
|
||
${result.price_change.toFixed(2)}%
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="space-y-3">
|
||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||
<span class="text-gray-600">综合评分</span>
|
||
<span class="font-medium text-blue-600">${result.score}分</span>
|
||
</div>
|
||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||
<span class="text-gray-600">投资建议</span>
|
||
<span class="font-medium text-purple-600">${result.recommendation}</span>
|
||
</div>
|
||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||
<span class="text-gray-600">RSI指标</span>
|
||
<span class="font-medium">${result.rsi.toFixed(2)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AI分析部分 -->
|
||
<div class="mt-6">
|
||
<h4 class="text-lg font-semibold text-gray-800 mb-3">AI分析</h4>
|
||
<div class="prose prose-blue max-w-none bg-gray-50 p-4 rounded-lg">
|
||
${marked.parse(result.ai_analysis)}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 免责声明 -->
|
||
<div class="mt-6 border-t border-gray-100 pt-4">
|
||
<div class="bg-blue-50 p-4 rounded-lg">
|
||
<p class="text-sm text-blue-800 font-semibold mb-1">声明:</p>
|
||
<p class="text-sm text-blue-600">本分析仅基于技术指标和历史数据,不构成投资建议。股市有风险,投资需谨慎。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
resultContent.innerHTML = html;
|
||
|
||
// 添加 Markdown 样式
|
||
const style = document.createElement('style');
|
||
style.textContent = `
|
||
.prose h1 { font-size: 1.5em; margin-top: 1em; margin-bottom: 0.5em; font-weight: bold; }
|
||
.prose h2 { font-size: 1.3em; margin-top: 1em; margin-bottom: 0.5em; font-weight: bold; }
|
||
.prose h3 { font-size: 1.1em; margin-top: 1em; margin-bottom: 0.5em; font-weight: bold; }
|
||
.prose p { margin-bottom: 1em; line-height: 1.6; }
|
||
.prose ul { list-style-type: disc; padding-left: 1.5em; margin-bottom: 1em; }
|
||
.prose ol { list-style-type: decimal; padding-left: 1.5em; margin-bottom: 1em; }
|
||
.prose li { margin-bottom: 0.5em; }
|
||
.prose strong { font-weight: 600; color: #1a56db; }
|
||
.prose em { font-style: italic; }
|
||
.prose blockquote { border-left: 4px solid #e5e7eb; padding-left: 1em; margin: 1em 0; color: #4b5563; }
|
||
.prose code { background-color: #f3f4f6; padding: 0.2em 0.4em; border-radius: 0.25em; font-size: 0.9em; }
|
||
`;
|
||
document.head.appendChild(style);
|
||
}
|
||
</script>
|
||
<!-- 添加 marked.js 用于解析 Markdown -->
|
||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||
|
||
<!-- API配置相关脚本 -->
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// API配置面板切换
|
||
const toggleBtn = document.getElementById('toggleApiConfig');
|
||
const configPanel = document.getElementById('apiConfigPanel');
|
||
const toggleText = document.getElementById('toggleApiConfigText');
|
||
const toggleIcon = document.getElementById('toggleApiConfigIcon');
|
||
|
||
toggleBtn.addEventListener('click', function() {
|
||
const isHidden = configPanel.classList.contains('hidden');
|
||
configPanel.classList.toggle('hidden', !isHidden);
|
||
toggleText.textContent = isHidden ? '隐藏配置' : '显示配置';
|
||
toggleIcon.style.transform = isHidden ? 'rotate(180deg)' : '';
|
||
});
|
||
|
||
// 重置API配置
|
||
document.getElementById('resetApiConfig').addEventListener('click', function() {
|
||
document.getElementById('apiUrl').value = '{{ default_api_url }}';
|
||
document.getElementById('apiModel').value = '{{ default_api_model }}';
|
||
document.getElementById('apiKey').value = '';
|
||
});
|
||
|
||
// 测试API连接
|
||
document.getElementById('testApiConfig').addEventListener('click', async function() {
|
||
const apiUrl = document.getElementById('apiUrl').value.trim();
|
||
const apiKey = document.getElementById('apiKey').value.trim();
|
||
const apiModel = document.getElementById('apiModel').value.trim();
|
||
|
||
if (!apiUrl) {
|
||
alert('请输入API URL');
|
||
return;
|
||
}
|
||
|
||
if (!apiKey) {
|
||
alert('请输入API Key');
|
||
return;
|
||
}
|
||
|
||
this.textContent = '测试中...';
|
||
this.disabled = true;
|
||
|
||
try {
|
||
// 使用后端代理进行API测试
|
||
const response = await fetch('/test_api_connection', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
api_url: apiUrl,
|
||
api_key: apiKey,
|
||
api_model: apiModel
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (response.ok && data.success) {
|
||
alert('API连接成功!');
|
||
} else {
|
||
alert(`API连接失败: ${data.message || '未知错误'}`);
|
||
}
|
||
} catch (error) {
|
||
alert(`API连接测试失败: ${error.message}`);
|
||
} finally {
|
||
this.textContent = '测试连接';
|
||
this.disabled = false;
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |