2 Commits

Author SHA1 Message Date
8086d76e93 feat: 添加浏览器标签图标 favicon
- 创建 SVG 格式 favicon(深色背景+大脑/网络节点设计)
- 在所有后台管理页面添加 favicon
2026-04-11 11:31:17 +08:00
247f9e2165 fix: 修复高优先级提供商失败时自动切换到备用提供商
- 任何非200响应(400、429、500等)都会触发切换
- 增加 exclude_providers 参数排除已尝试的提供商
- 避免重复尝试失败的提供商
- 添加切换日志便于调试
2026-04-10 01:51:36 +08:00
9 changed files with 42 additions and 13 deletions

View File

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect x="2" y="2" width="28" height="28" rx="4" fill="#1a1a2e"/>
<circle cx="16" cy="16" r="10" fill="#4a90d9" opacity="0.3"/>
<path d="M16 6 L16 10 M16 22 L16 26 M6 16 L10 16 M22 16 L26 16" stroke="#4a90d9" stroke-width="2"/>
<circle cx="16" cy="16" r="6" fill="#4a90d9"/>
<path d="M12 14 L16 16 L20 14 M12 18 L16 16 L20 18" stroke="#fff" stroke-width="1.5" fill="none"/>
<circle cx="10" cy="10" r="2" fill="#28a745"/>
<circle cx="22" cy="10" r="2" fill="#28a745"/>
<circle cx="10" cy="22" r="2" fill="#28a745"/>
<circle cx="22" cy="22" r="2" fill="#28a745"/>
<path d="M10 10 L16 16 M22 10 L16 16 M10 22 L16 16 M22 22 L16 16" stroke="#28a745" stroke-width="1" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 764 B

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Auto配置管理 - LLM Proxy</title> <title>Auto配置管理 - LLM Proxy</title>
<link rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style> <style>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>对话 - LLM Proxy</title> <title>对话 - LLM Proxy</title>
<link rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style> <style>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统配置 - LLM Proxy</title> <title>系统配置 - LLM Proxy</title>
<link rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style> <style>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LLM Proxy - 后台管理</title> <title>LLM Proxy - 后台管理</title>
<link rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style> <style>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>日志查看 - LLM Proxy</title> <title>日志查看 - LLM Proxy</title>
<link rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style> <style>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模型管理 - LLM Proxy</title> <title>模型管理 - LLM Proxy</title>
<link rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style> <style>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>提供商管理 - LLM Proxy</title> <title>提供商管理 - LLM Proxy</title>
<link rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style> <style>

36
app.py
View File

@@ -177,10 +177,17 @@ def get_provider_for_model(model_name):
return None, None return None, None
def get_available_provider_for_auto(auto_name='auto'): def get_available_provider_for_auto(auto_name='auto', exclude_providers=None):
"""获取auto模式下的可用提供商支持自定义auto配置""" """获取auto模式下的可用提供商支持自定义auto配置
Args:
auto_name: auto配置名称
exclude_providers: 要排除的提供商名称列表(已尝试过的)
"""
refresh_config() refresh_config()
exclude_providers = exclude_providers or []
# 获取auto配置 # 获取auto配置
profile = _cached_auto_profiles.get(auto_name, _cached_auto_profiles.get('auto', {})) profile = _cached_auto_profiles.get(auto_name, _cached_auto_profiles.get('auto', {}))
@@ -194,6 +201,8 @@ def get_available_provider_for_auto(auto_name='auto'):
for provider in sorted_providers: for provider in sorted_providers:
if not provider['enabled']: if not provider['enabled']:
continue continue
if provider['name'] in exclude_providers:
continue
if not provider_status.get(provider['name'], {}).get('available', True): if not provider_status.get(provider['name'], {}).get('available', True):
continue continue
@@ -448,26 +457,27 @@ def chat_completions():
increment_stats(model, provider['name'], success=True, tokens=request_tokens) increment_stats(model, provider['name'], success=True, tokens=request_tokens)
return jsonify(result) return jsonify(result)
elif response.status_code == 429: else:
# 速率限制,尝试下一个提供商 # 任何非200响应都尝试下一个提供商
mark_provider_error(provider['name'], "Rate limit") error_info = response.json() if response.headers.get('content-type', '').startswith('application/json') else {"error": response.text}
last_error = error_info
logger.warning(f"Provider {provider['name']} returned {response.status_code}: {error_info}")
mark_provider_error(provider['name'], f"HTTP {response.status_code}")
tried_providers.append(provider['name']) tried_providers.append(provider['name'])
# 尝试下一个提供商 # 尝试下一个提供商
next_provider, next_model = get_available_provider_for_auto('auto') next_provider, next_model = get_available_provider_for_auto('auto', exclude_providers=tried_providers)
if next_provider and next_provider['name'] not in tried_providers: if next_provider and next_provider['name'] not in tried_providers:
logger.info(f"Switching to next provider: {next_provider['name']}")
provider = next_provider provider = next_provider
resolved_model = next_model resolved_model = next_model
request_provider = provider['name'] request_provider = provider['name']
time.sleep(RETRY_CONFIG['retry_delay'])
continue continue
increment_stats(model, provider['name'], success=False, error='Rate limit') # 所有提供商都尝试过了,返回最后一个错误
return jsonify(response.json()), response.status_code
else:
last_error = response.json() if response.headers.get('content-type', '').startswith('application/json') else {"error": response.text}
increment_stats(model, provider['name'], success=False, error=str(last_error)) increment_stats(model, provider['name'], success=False, error=str(last_error))
return jsonify(last_error), response.status_code return jsonify(error_info), response.status_code
except Exception as e: except Exception as e:
last_error = str(e) last_error = str(e)
@@ -475,7 +485,7 @@ def chat_completions():
tried_providers.append(provider['name']) tried_providers.append(provider['name'])
# 尝试下一个提供商 # 尝试下一个提供商
next_provider, next_model = get_available_provider_for_auto('auto') next_provider, next_model = get_available_provider_for_auto('auto', exclude_providers=tried_providers)
if next_provider and next_provider['name'] not in tried_providers: if next_provider and next_provider['name'] not in tried_providers:
provider = next_provider provider = next_provider
resolved_model = next_model resolved_model = next_model