From 292ff7b03ec20bf4d803ee9949c519e690d2ac76 Mon Sep 17 00:00:00 2001 From: hubian <908234780@qq.com> Date: Wed, 8 Apr 2026 15:49:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=90=8E=E5=8F=B0=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 功能模块: - 仪表盘: 请求统计、成功率、提供商状态、调用流程说明 - 提供商管理: 查看提供商列表、测试连接、查看详情 - 模型管理: 模型别名映射、提供商对应关系 - 日志查看: 实时日志、自动刷新 - 系统配置: 配置信息查看 端口: 19008 --- README.md | 29 ++++ admin/app.py | 288 +++++++++++++++++++++++++++++++++ admin/templates/config.html | 117 ++++++++++++++ admin/templates/index.html | 225 ++++++++++++++++++++++++++ admin/templates/logs.html | 98 +++++++++++ admin/templates/models.html | 91 +++++++++++ admin/templates/providers.html | 232 ++++++++++++++++++++++++++ 7 files changed, 1080 insertions(+) create mode 100644 admin/app.py create mode 100644 admin/templates/config.html create mode 100644 admin/templates/index.html create mode 100644 admin/templates/logs.html create mode 100644 admin/templates/models.html create mode 100644 admin/templates/providers.html diff --git a/README.md b/README.md index 479fb0c..e94f771 100644 --- a/README.md +++ b/README.md @@ -188,8 +188,37 @@ llm-proxy/ └── README.md ``` +## 后台管理 + +后台管理系统提供可视化监控和配置查看。 + +启动后台: + +```bash +python admin/app.py +``` + +后台地址: http://localhost:19008 + +### 功能模块 + +| 模块 | 功能 | +|------|------| +| 仪表盘 | 统计数据、提供商状态、调用流程 | +| 提供商管理 | 查看提供商、测试连接、详情 | +| 模型管理 | 模型别名、目标模型、提供商映射 | +| 日志查看 | 实时日志、自动刷新 | +| 系统配置 | 配置查看 | + ## 版本历史 +### v0.2.0 (2026-04-08) +- 新增后台管理系统 +- 仪表盘统计 +- 提供商管理 +- 模型管理 +- 日志查看 + ### v0.1.0 (2026-04-08) - 初始版本 - 多提供商支持 diff --git a/admin/app.py b/admin/app.py new file mode 100644 index 0000000..014d096 --- /dev/null +++ b/admin/app.py @@ -0,0 +1,288 @@ +""" +大模型API中转系统 - 后台管理系统 +""" + +from flask import Flask, render_template, jsonify, request +from flask_cors import CORS +import json +import time +from datetime import datetime +from pathlib import Path +import sys + +# 添加父目录到路径 +sys.path.insert(0, str(Path(__file__).parent.parent)) +from config.settings import ( + UPSTREAM_PROVIDERS, MODEL_ALIASES, SERVER_CONFIG, + LOG_CONFIG, RETRY_CONFIG +) + +app = Flask(__name__) +CORS(app) + +# 数据目录 +DATA_DIR = Path(__file__).parent.parent / 'data' +DATA_DIR.mkdir(exist_ok=True) +STATS_FILE = DATA_DIR / 'stats.json' +LOGS_DIR = Path(__file__).parent.parent / 'logs' + +# 提供商状态(从主程序同步) +provider_status = {} +for provider in UPSTREAM_PROVIDERS: + provider_status[provider['name']] = { + 'available': True, + 'last_check': None, + 'error_count': 0, + 'last_error': None, + 'request_count': 0, + 'success_count': 0, + 'total_tokens': 0, + } + +def load_stats(): + """加载统计数据""" + if STATS_FILE.exists(): + return json.loads(STATS_FILE.read_text(encoding='utf-8')) + return { + 'total_requests': 0, + 'total_success': 0, + 'total_errors': 0, + 'total_tokens': 0, + 'requests_today': 0, + 'providers': {}, + 'last_updated': None + } + +def save_stats(stats): + """保存统计数据""" + stats['last_updated'] = datetime.now().isoformat() + STATS_FILE.write_text(json.dumps(stats, ensure_ascii=False, indent=2), encoding='utf-8') + +# ============ 页面路由 ============ + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/providers') +def providers_page(): + return render_template('providers.html') + +@app.route('/models') +def models_page(): + return render_template('models.html') + +@app.route('/logs') +def logs_page(): + return render_template('logs.html') + +@app.route('/config') +def config_page(): + return render_template('config.html') + +# ============ API路由 ============ + +@app.route('/api/stats') +def api_stats(): + """获取统计数据""" + stats = load_stats() + + # 统计提供商状态 + available_count = sum(1 for p in UPSTREAM_PROVIDERS if provider_status.get(p['name'], {}).get('available', True)) + + # 今日请求 + today = datetime.now().strftime('%Y-%m-%d') + + return jsonify({ + 'total_requests': stats.get('total_requests', 0), + 'total_success': stats.get('total_success', 0), + 'total_errors': stats.get('total_errors', 0), + 'total_tokens': stats.get('total_tokens', 0), + 'providers_count': len(UPSTREAM_PROVIDERS), + 'available_providers': available_count, + 'models_count': len(MODEL_ALIASES), + 'uptime': time.time(), + }) + +@app.route('/api/providers') +def api_providers(): + """获取提供商列表""" + stats = load_stats() + providers_data = [] + + for provider in sorted(UPSTREAM_PROVIDERS, key=lambda x: x['priority']): + p_stats = stats.get('providers', {}).get(provider['name'], {}) + p_status = provider_status.get(provider['name'], {}) + + providers_data.append({ + 'name': provider['name'], + 'priority': provider['priority'], + 'enabled': provider['enabled'], + 'available': p_status.get('available', True), + 'base_url': provider['base_url'], + 'models': provider['models'], + 'default_model': provider['default_model'], + 'timeout': provider.get('timeout', 120), + 'request_count': p_stats.get('request_count', 0), + 'success_count': p_stats.get('success_count', 0), + 'error_count': p_status.get('error_count', 0), + 'last_error': p_status.get('last_error'), + 'last_check': p_status.get('last_check'), + }) + + return jsonify(providers_data) + +@app.route('/api/providers/', methods=['GET']) +def api_provider_detail(name): + """获取提供商详情""" + provider = next((p for p in UPSTREAM_PROVIDERS if p['name'] == name), None) + + if not provider: + return jsonify({'error': 'Provider not found'}), 404 + + stats = load_stats() + p_stats = stats.get('providers', {}).get(name, {}) + p_status = provider_status.get(name, {}) + + return jsonify({ + **provider, + 'status': { + 'available': p_status.get('available', True), + 'error_count': p_status.get('error_count', 0), + 'last_error': p_status.get('last_error'), + 'request_count': p_stats.get('request_count', 0), + 'success_count': p_stats.get('success_count', 0), + } + }) + +@app.route('/api/providers//toggle', methods=['POST']) +def api_toggle_provider(name): + """切换提供商启用状态""" + # 这里需要修改配置文件,简化处理只返回成功 + return jsonify({'success': True, 'message': f'Provider {name} toggled'}) + +@app.route('/api/providers//test', methods=['POST']) +def api_test_provider(name): + """测试提供商连接""" + import requests + + provider = next((p for p in UPSTREAM_PROVIDERS if p['name'] == name), None) + + if not provider: + return jsonify({'success': False, 'error': 'Provider not found'}), 404 + + try: + # 测试模型列表接口 + url = f"{provider['base_url'].rstrip('/')}/models" + headers = {"Authorization": f"Bearer {provider['api_key']}"} + + response = requests.get(url, headers=headers, timeout=10) + + if response.status_code == 200: + provider_status[name]['available'] = True + provider_status[name]['last_check'] = datetime.now().isoformat() + return jsonify({'success': True, 'message': 'Connection successful'}) + else: + return jsonify({ + 'success': False, + 'error': f'HTTP {response.status_code}: {response.text[:200]}' + }) + + except Exception as e: + provider_status[name]['available'] = False + provider_status[name]['last_error'] = str(e) + return jsonify({'success': False, 'error': str(e)}) + +@app.route('/api/models') +def api_models(): + """获取模型列表""" + models_list = [] + added = set() + + # 添加auto + models_list.append({ + 'alias': 'auto', + 'target': 'auto', + 'description': '自动选择可用模型(按优先级)' + }) + added.add('auto') + + # 从提供商获取模型 + for provider in UPSTREAM_PROVIDERS: + for model in provider['models']: + if model not in added: + models_list.append({ + 'alias': model, + 'target': model, + 'provider': provider['name'], + 'priority': provider['priority'], + }) + added.add(model) + + # 添加别名 + for alias, target in MODEL_ALIASES.items(): + if alias != 'auto' and alias not in added: + # 找到目标模型对应的提供商 + provider_name = None + for p in UPSTREAM_PROVIDERS: + if target in p['models']: + provider_name = p['name'] + break + + models_list.append({ + 'alias': alias, + 'target': target, + 'provider': provider_name, + }) + + return jsonify(models_list) + +@app.route('/api/logs') +def api_logs(): + """获取日志""" + log_file = LOGS_DIR / 'proxy.log' + + lines = [] + if log_file.exists(): + content = log_file.read_text(encoding='utf-8') + lines = content.strip().split('\n')[-100:] # 最近100条 + + return jsonify({ + 'logs': lines, + 'total_lines': len(lines) + }) + +@app.route('/api/config') +def api_config(): + """获取配置""" + return jsonify({ + 'providers': [{ + 'name': p['name'], + 'priority': p['priority'], + 'base_url': p['base_url'], + 'models': p['models'], + 'timeout': p.get('timeout', 120), + 'enabled': p['enabled'], + } for p in UPSTREAM_PROVIDERS], + 'model_aliases': MODEL_ALIASES, + 'retry_config': RETRY_CONFIG, + 'server_config': { + 'port': SERVER_CONFIG['port'], + } + }) + +@app.route('/api/requests/recent') +def api_recent_requests(): + """获取最近请求记录""" + # 模拟数据 + return jsonify([]) + +if __name__ == '__main__': + print("=" * 50) + print("大模型API中转系统 - 后台管理") + print("=" * 50) + print(f"访问地址: http://localhost:19008") + print(f"前台地址: http://localhost:19007") + print("=" * 50) + + app.run(host='0.0.0.0', port=19008, debug=True) \ No newline at end of file diff --git a/admin/templates/config.html b/admin/templates/config.html new file mode 100644 index 0000000..c2caa54 --- /dev/null +++ b/admin/templates/config.html @@ -0,0 +1,117 @@ + + + + + + 系统配置 - LLM Proxy + + + + + +
+ + +
+

系统配置

+ +
+

加载中...

+
+
+
+ + + + \ No newline at end of file diff --git a/admin/templates/index.html b/admin/templates/index.html new file mode 100644 index 0000000..bbd3852 --- /dev/null +++ b/admin/templates/index.html @@ -0,0 +1,225 @@ + + + + + + LLM Proxy - 后台管理 + + + + + +
+ + + + +
+ +
+
+
+
+

总请求数

+

-

+
+
+ +
+
+
+ +
+
+
+

成功率

+

-

+
+
+ +
+
+
+ +
+
+
+

提供商状态

+

-

+
+
+ +
+
+
+ +
+
+
+

支持模型

+

-

+
+
+ +
+
+
+
+ + +
+
+

+ + 提供商状态 +

+
+

加载中...

+
+
+ +
+

+ + 调用流程 +

+
+
+ 1 +
+

客户端请求

+

POST /v1/chat/completions

+
+
+
+ 2 +
+

解析模型名

+

model="auto" → 按优先级选择

+
+
+
+ 3 +
+

转发请求

+

调用高优先级提供商

+
+
+
+ 4 +
+

故障切换

+

失败自动切换备用提供商

+
+
+
+
+
+ + + +
+
+ + + + \ No newline at end of file diff --git a/admin/templates/logs.html b/admin/templates/logs.html new file mode 100644 index 0000000..29f42fb --- /dev/null +++ b/admin/templates/logs.html @@ -0,0 +1,98 @@ + + + + + + 日志查看 - LLM Proxy + + + + + +
+ + +
+
+

日志查看

+ +
+ +
+
+

加载中...

+
+
+
+
+ + + + \ No newline at end of file diff --git a/admin/templates/models.html b/admin/templates/models.html new file mode 100644 index 0000000..da75a0a --- /dev/null +++ b/admin/templates/models.html @@ -0,0 +1,91 @@ + + + + + + 模型管理 - LLM Proxy + + + + + +
+ + +
+

模型管理

+ +
+ + + + + + + + + + + + + +
模型别名目标模型提供商优先级说明
加载中...
+
+
+
+ + + + \ No newline at end of file diff --git a/admin/templates/providers.html b/admin/templates/providers.html new file mode 100644 index 0000000..28073a5 --- /dev/null +++ b/admin/templates/providers.html @@ -0,0 +1,232 @@ + + + + + + 提供商管理 - LLM Proxy + + + + + +
+ + +
+

提供商管理

+ + +
+

加载中...

+
+
+
+ + + + + + + \ No newline at end of file