fix: 统一前后端API_URL处理规则: /结尾忽略v1版本,#结尾强制使用输入地址。 优化公告区域位置
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
2. 增加港股、美股支持。
|
2. 增加港股、美股支持。
|
||||||
3. 完善Dockerfile、GitHub Actions 支持docker一键部署使用。
|
3. 完善Dockerfile、GitHub Actions 支持docker一键部署使用。
|
||||||
4. 支持x86_64 和 ARM64架构镜像
|
4. 支持x86_64 和 ARM64架构镜像
|
||||||
|
5. 支持流式输出,支持前端传入Key(仅作为本地用户使用,日志等内容不会输出) 感谢@Cassianvale
|
||||||
|
|
||||||
## docker一键部署
|
## docker一键部署
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from typing import Dict, List, Optional, Tuple, Generator
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import json
|
import json
|
||||||
from logger import get_logger
|
from logger import get_logger
|
||||||
|
from utils.api_utils import APIUtils
|
||||||
|
|
||||||
# 获取日志器
|
# 获取日志器
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
@@ -259,12 +260,8 @@ class StockAnalyzer:
|
|||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return error_msg if not stream else (yield json.dumps({"error": error_msg}))
|
return error_msg if not stream else (yield json.dumps({"error": error_msg}))
|
||||||
|
|
||||||
# 标准化API URL 使用Cherry Studio相同逻辑处理
|
# 标准化API URL
|
||||||
if self.API_URL.endswith('/'):
|
api_url = APIUtils.format_api_url(self.API_URL)
|
||||||
api_url = f"{self.API_URL}chat/completions"
|
|
||||||
else:
|
|
||||||
api_url = f"{self.API_URL}/v1/chat/completions"
|
|
||||||
|
|
||||||
|
|
||||||
logger.debug(f"标准化后的API URL: {api_url}")
|
logger.debug(f"标准化后的API URL: {api_url}")
|
||||||
|
|
||||||
|
|||||||
@@ -7,29 +7,203 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100">
|
<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 %}
|
{% if announcement %}
|
||||||
<div class="max-w-4xl mx-auto mb-8">
|
<div id="announcement-container" class="fixed top-4 right-4 max-w-md z-50 animate-fade-in-down">
|
||||||
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded">
|
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-lg shadow-lg">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3 pr-8">
|
||||||
<p class="text-sm text-blue-700">
|
<p class="text-sm text-blue-700 whitespace-pre-line" id="announcement-text"></p>
|
||||||
{{ announcement }}
|
<p class="text-xs text-gray-500 mt-1" id="announcement-timer"></p>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
<button onclick="closeAnnouncement()" class="absolute top-2 right-2 text-gray-400 hover:text-gray-600">
|
||||||
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 处理公告文本中的URL
|
||||||
|
const announcementText = `{{ announcement }}`;
|
||||||
|
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||||
|
document.getElementById('announcement-text').innerHTML = announcementText.replace(
|
||||||
|
urlRegex,
|
||||||
|
'<a href="$1" target="_blank" class="text-blue-600 hover:text-blue-800 underline">$1</a>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置自动关闭时间(5秒)
|
||||||
|
const autoCloseTime = 5;
|
||||||
|
let remainingTime = autoCloseTime;
|
||||||
|
const timerElement = document.getElementById('announcement-timer');
|
||||||
|
|
||||||
|
// 更新倒计时显示
|
||||||
|
function updateTimer() {
|
||||||
|
timerElement.textContent = `${remainingTime}秒后自动关闭`;
|
||||||
|
if (remainingTime <= 0) {
|
||||||
|
closeAnnouncement();
|
||||||
|
} else {
|
||||||
|
remainingTime--;
|
||||||
|
setTimeout(updateTimer, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动倒计时
|
||||||
|
updateTimer();
|
||||||
|
|
||||||
|
// 关闭公告的函数
|
||||||
|
function closeAnnouncement() {
|
||||||
|
const container = document.getElementById('announcement-container');
|
||||||
|
container.style.opacity = '0';
|
||||||
|
container.style.transform = 'translateY(-10px)';
|
||||||
|
container.style.transition = 'all 0.3s ease-out';
|
||||||
|
setTimeout(() => {
|
||||||
|
container.remove();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<h1 class="text-3xl font-bold mb-8 text-center">股票分析系统</h1>
|
||||||
|
|
||||||
|
<!-- 添加市场时间显示 -->
|
||||||
|
<div class="max-w-4xl mx-auto mb-8">
|
||||||
|
<div class="bg-white rounded-lg shadow-md p-6">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
|
<!-- 当前时间 -->
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-gray-600 mb-2">当前时间</p>
|
||||||
|
<p id="currentTime" class="text-2xl font-bold text-gray-800"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- A股状态 -->
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-gray-600 mb-2">A股市场</p>
|
||||||
|
<p id="cnMarketStatus" class="text-lg font-medium"></p>
|
||||||
|
<p id="cnMarketTimer" class="text-sm text-gray-500 mt-1"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 港股状态 -->
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-gray-600 mb-2">港股市场</p>
|
||||||
|
<p id="hkMarketStatus" class="text-lg font-medium"></p>
|
||||||
|
<p id="hkMarketTimer" class="text-sm text-gray-500 mt-1"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 美股状态 -->
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-gray-600 mb-2">美股市场</p>
|
||||||
|
<p id="usMarketStatus" class="text-lg font-medium"></p>
|
||||||
|
<p id="usMarketTimer" class="text-sm text-gray-500 mt-1"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 更新时间显示
|
||||||
|
function updateTime() {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// 更新当前时间
|
||||||
|
document.getElementById('currentTime').textContent =
|
||||||
|
now.toLocaleTimeString('zh-CN', { hour12: false });
|
||||||
|
|
||||||
|
// 获取中国时间
|
||||||
|
const cnOptions = { timeZone: 'Asia/Shanghai', hour12: false };
|
||||||
|
const cnTime = now.toLocaleString('zh-CN', cnOptions);
|
||||||
|
const cnHour = new Date(cnTime).getHours();
|
||||||
|
const cnMinute = new Date(cnTime).getMinutes();
|
||||||
|
|
||||||
|
// A股市场状态
|
||||||
|
const cnMarketOpen = (cnHour === 9 && cnMinute >= 30) || (cnHour === 10) ||
|
||||||
|
(cnHour === 11 && cnMinute <= 30) ||
|
||||||
|
(cnHour >= 13 && cnHour < 15);
|
||||||
|
|
||||||
|
updateMarketStatus('cn', cnMarketOpen, cnHour, cnMinute, 9, 30, 15, 0);
|
||||||
|
|
||||||
|
// 港股市场状态(与A股相同时区)
|
||||||
|
const hkMarketOpen = (cnHour === 9 && cnMinute >= 30) ||
|
||||||
|
(cnHour === 10) || (cnHour === 11) ||
|
||||||
|
(cnHour >= 13 && cnHour < 16);
|
||||||
|
|
||||||
|
updateMarketStatus('hk', hkMarketOpen, cnHour, cnMinute, 9, 30, 16, 0);
|
||||||
|
|
||||||
|
// 获取美国东部时间
|
||||||
|
const usOptions = { timeZone: 'America/New_York', hour12: false };
|
||||||
|
const usTime = now.toLocaleString('zh-CN', usOptions);
|
||||||
|
const usHour = new Date(usTime).getHours();
|
||||||
|
const usMinute = new Date(usTime).getMinutes();
|
||||||
|
|
||||||
|
// 美股市场状态
|
||||||
|
const usMarketOpen = (usHour >= 9 && usHour < 16) ||
|
||||||
|
(usHour === 16 && usMinute === 0);
|
||||||
|
|
||||||
|
updateMarketStatus('us', usMarketOpen, usHour, usMinute, 9, 30, 16, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新市场状态的通用函数
|
||||||
|
function updateMarketStatus(market, isOpen, currentHour, currentMinute, openHour, openMinute, closeHour, closeMinute) {
|
||||||
|
const element = document.getElementById(`${market}MarketStatus`);
|
||||||
|
const timer = document.getElementById(`${market}MarketTimer`);
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
// ... 现有的开市逻辑保持不变 ...
|
||||||
|
} else {
|
||||||
|
element.textContent = '已休市';
|
||||||
|
element.className = 'text-lg font-medium text-gray-600';
|
||||||
|
|
||||||
|
// 计算距离下一个开盘时间
|
||||||
|
const now = new Date();
|
||||||
|
const nextOpen = new Date(now);
|
||||||
|
|
||||||
|
if (market === 'us') {
|
||||||
|
// 获取美东时间的下一个开盘时间
|
||||||
|
const usTime = new Date(now.toLocaleString('en-US', { timeZone: 'America/New_York' }));
|
||||||
|
const usHour = usTime.getHours();
|
||||||
|
|
||||||
|
// 如果当前美东时间已过收盘时间,设置为下一个交易日
|
||||||
|
if (usHour >= closeHour) {
|
||||||
|
nextOpen.setDate(nextOpen.getDate() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置为美东时间的开盘时间
|
||||||
|
nextOpen.setHours(openHour + 12, openMinute, 0); // 加12小时转换为北京时间
|
||||||
|
} else {
|
||||||
|
// 其他市场的逻辑保持不变
|
||||||
|
if (currentHour >= closeHour) {
|
||||||
|
nextOpen.setDate(nextOpen.getDate() + 1);
|
||||||
|
}
|
||||||
|
nextOpen.setHours(openHour, openMinute, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeToOpen = nextOpen - now;
|
||||||
|
const hours = Math.floor(timeToOpen / 3600000);
|
||||||
|
const minutes = Math.floor((timeToOpen % 3600000) / 60000);
|
||||||
|
|
||||||
|
// 确保显示的时间始终为正数
|
||||||
|
if (hours >= 0 && minutes >= 0) {
|
||||||
|
timer.textContent = `距离开盘还有 ${hours}小时${minutes}分钟`;
|
||||||
|
} else {
|
||||||
|
timer.textContent = '计算开盘时间中...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每秒更新一次时间
|
||||||
|
setInterval(updateTime, 1000);
|
||||||
|
updateTime(); // 立即执行一次
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<!-- 批量分析 -->
|
<!-- 批量分析 -->
|
||||||
<div class="bg-white p-6 rounded-lg shadow-md">
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
||||||
@@ -54,7 +228,9 @@
|
|||||||
<input type="text" id="apiUrl"
|
<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"
|
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"
|
placeholder="例如: https://api.openai.com"
|
||||||
value="{{ default_api_url }}">
|
value="{{ default_api_url }}"
|
||||||
|
oninput="updateFormattedUrl(this.value)">
|
||||||
|
<p id="formattedUrl" class="mt-1 text-sm text-gray-500 break-all"></p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="apiModel" class="block text-sm font-medium text-gray-700 mb-1">API 模型</label>
|
<label for="apiModel" class="block text-sm font-medium text-gray-700 mb-1">API 模型</label>
|
||||||
@@ -118,7 +294,7 @@
|
|||||||
id="searchInput"
|
id="searchInput"
|
||||||
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
placeholder="输入美股名称搜索(中文和英文都试下)"
|
placeholder="输入美股名称搜索(中文和英文都试下)"
|
||||||
oninput="debounceSearch(event)">
|
oninput="handleSearchInput(event)">
|
||||||
<!-- 添加搜索 loading 图标 -->
|
<!-- 添加搜索 loading 图标 -->
|
||||||
<div id="searchLoading" class="absolute right-3 top-2.5 hidden">
|
<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">
|
<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">
|
||||||
@@ -138,7 +314,19 @@
|
|||||||
|
|
||||||
<!-- 修改搜索相关函数 -->
|
<!-- 修改搜索相关函数 -->
|
||||||
<script>
|
<script>
|
||||||
async function searchUsStocks(keyword) {
|
// 创建防抖函数
|
||||||
|
function debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
func.apply(this, args);
|
||||||
|
}, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用防抖包装搜索函数
|
||||||
|
const debouncedSearch = debounce(async (keyword) => {
|
||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
document.getElementById('searchResults').classList.add('hidden');
|
document.getElementById('searchResults').classList.add('hidden');
|
||||||
document.getElementById('searchError').classList.add('hidden');
|
document.getElementById('searchError').classList.add('hidden');
|
||||||
@@ -161,16 +349,18 @@
|
|||||||
displaySearchResults(data.results);
|
displaySearchResults(data.results);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('搜索出错:', error);
|
console.error('搜索出错:', error);
|
||||||
// 显示错误信息
|
|
||||||
const errorDiv = document.getElementById('searchError');
|
const errorDiv = document.getElementById('searchError');
|
||||||
errorDiv.textContent = `搜索出错: ${error.message}`;
|
errorDiv.textContent = `搜索出错: ${error.message}`;
|
||||||
errorDiv.classList.remove('hidden');
|
errorDiv.classList.remove('hidden');
|
||||||
// 隐藏搜索结果
|
|
||||||
document.getElementById('searchResults').classList.add('hidden');
|
document.getElementById('searchResults').classList.add('hidden');
|
||||||
} finally {
|
} finally {
|
||||||
// 隐藏 loading
|
|
||||||
document.getElementById('searchLoading').classList.add('hidden');
|
document.getElementById('searchLoading').classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
}, 500); // 设置500ms的防抖延迟
|
||||||
|
|
||||||
|
// 修改输入事件处理函数
|
||||||
|
function handleSearchInput(event) {
|
||||||
|
debouncedSearch(event.target.value);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
@@ -962,3 +1152,35 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function updateFormattedUrl(url) {
|
||||||
|
const formattedUrlElement = document.getElementById('formattedUrl');
|
||||||
|
if (!url) {
|
||||||
|
formattedUrlElement.textContent = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let formattedUrl;
|
||||||
|
if (url.endsWith('/')) {
|
||||||
|
formattedUrl = `${url}chat/completions`;
|
||||||
|
} else if (url.endsWith('#')) {
|
||||||
|
formattedUrl = url.replace("#", "");
|
||||||
|
} else {
|
||||||
|
formattedUrl = `${url}/v1/chat/completions`;
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedUrlElement.innerHTML = `
|
||||||
|
<span class="font-medium">/结尾忽略v1版本,#结尾强制使用输入地址:</span><br>
|
||||||
|
<span class="text-blue-600">${formattedUrl}</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时初始化显示
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const apiUrl = document.getElementById('apiUrl');
|
||||||
|
if (apiUrl.value) {
|
||||||
|
updateFormattedUrl(apiUrl.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -3,6 +3,7 @@ import requests
|
|||||||
import json
|
import json
|
||||||
from logger import get_logger, get_stream_logger
|
from logger import get_logger, get_stream_logger
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
from utils.api_utils import APIUtils
|
||||||
|
|
||||||
# 获取日志器
|
# 获取日志器
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
@@ -50,11 +51,8 @@ def test_api_stream():
|
|||||||
logger.error("API Key未配置,无法进行测试")
|
logger.error("API Key未配置,无法进行测试")
|
||||||
return
|
return
|
||||||
|
|
||||||
if api_url.endswith('/'):
|
# 标准化API URL
|
||||||
api_url = f"{api_url}chat/completions"
|
api_url = APIUtils.format_api_url(api_url)
|
||||||
else:
|
|
||||||
api_url = f"{api_url}/v1/chat/completions"
|
|
||||||
|
|
||||||
logger.debug(f"标准化后的API URL: {api_url}")
|
logger.debug(f"标准化后的API URL: {api_url}")
|
||||||
|
|
||||||
# 构建简单的测试提示
|
# 构建简单的测试提示
|
||||||
|
|||||||
23
utils/api_utils.py
Normal file
23
utils/api_utils.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
class APIUtils:
|
||||||
|
@staticmethod
|
||||||
|
def format_api_url(base_url: str) -> str:
|
||||||
|
"""
|
||||||
|
格式化 API URL
|
||||||
|
|
||||||
|
/结尾忽略v1版本,#结尾强制使用输入地址
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: 基础 API URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 格式化后的完整 API URL
|
||||||
|
"""
|
||||||
|
if not base_url:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if base_url.endswith('/'):
|
||||||
|
return f"{base_url}chat/completions"
|
||||||
|
elif base_url.endswith('#'):
|
||||||
|
return base_url.replace('#', '')
|
||||||
|
else:
|
||||||
|
return f"{base_url}/v1/chat/completions"
|
||||||
@@ -6,6 +6,11 @@ import os
|
|||||||
import traceback
|
import traceback
|
||||||
import requests
|
import requests
|
||||||
from logger import get_logger
|
from logger import get_logger
|
||||||
|
from utils.api_utils import APIUtils
|
||||||
|
# 加载环境变量
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
# 获取日志器
|
# 获取日志器
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
@@ -138,13 +143,7 @@ def test_api_connection():
|
|||||||
return jsonify({'error': '请提供API Key'}), 400
|
return jsonify({'error': '请提供API Key'}), 400
|
||||||
|
|
||||||
# 构建API URL
|
# 构建API URL
|
||||||
if api_url.endswith('/'):
|
test_url = APIUtils.format_api_url(api_url)
|
||||||
test_url = f"{api_url}chat/completions"
|
|
||||||
else:
|
|
||||||
test_url = f"{api_url}/v1/chat/completions"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
logger.debug(f"完整API测试URL: {test_url}")
|
logger.debug(f"完整API测试URL: {test_url}")
|
||||||
|
|
||||||
# 发送测试请求
|
# 发送测试请求
|
||||||
|
|||||||
Reference in New Issue
Block a user