- 新增发布日期(publish_date)、热度(views)、置顶(is_pinned)字段 - 后台管理表格显示新字段和置顶操作按钮 - 前端默认排序:置顶优先 → 发布日期最新 - 新增多种排序选项:发布日期、热度、名称等 - 新增图片上传API(支持多图上传) - 后台管理表单添加图片上传组件(支持文件选择和粘贴) - 数据创建时自动初始化新字段
1251 lines
40 KiB
Python
1251 lines
40 KiB
Python
"""
|
||
ParamHub - 参数百科
|
||
AI大模型与硬件参数速查平台
|
||
v1.4.0 - 新增图片上传功能
|
||
"""
|
||
|
||
from flask import Flask, render_template, jsonify, request
|
||
from flask_cors import CORS
|
||
import json
|
||
import requests
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
import uuid
|
||
import time
|
||
import os
|
||
import base64
|
||
from werkzeug.utils import secure_filename
|
||
|
||
app = Flask(__name__, static_folder='static', static_url_path='/static')
|
||
CORS(app)
|
||
|
||
# 数据目录
|
||
DATA_DIR = Path(__file__).parent / 'data'
|
||
DATA_DIR.mkdir(exist_ok=True)
|
||
|
||
# 图片上传目录
|
||
IMAGES_DIR = Path(__file__).parent / 'static' / 'uploads'
|
||
IMAGES_DIR.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 允许的图片格式
|
||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||
|
||
def allowed_file(filename):
|
||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||
|
||
# 数据文件
|
||
MODELS_FILE = DATA_DIR / 'models.json'
|
||
GPUS_FILE = DATA_DIR / 'gpus.json'
|
||
CPUS_FILE = DATA_DIR / 'cpus.json'
|
||
CATEGORIES_FILE = DATA_DIR / 'categories.json'
|
||
KNOWLEDGE_FILE = DATA_DIR / 'knowledge.json'
|
||
CONFIG_FILE = DATA_DIR / 'config.json'
|
||
|
||
# 大模型配置
|
||
LLM_CONFIG = {
|
||
'base_url': 'http://192.168.2.17:19007/v1',
|
||
'api_key': 'xxxx',
|
||
'model': 'auto',
|
||
}
|
||
|
||
# 默认网站配置
|
||
DEFAULT_CONFIG = {
|
||
'site_name': 'ParamHub',
|
||
'site_subtitle': '参数百科',
|
||
'footer_text': 'ParamHub - AI大模型与硬件参数速查平台',
|
||
'icp_number': '',
|
||
'copyright_year': '2024',
|
||
'contact_email': '',
|
||
'github_url': '',
|
||
}
|
||
|
||
def load_config():
|
||
"""加载网站配置"""
|
||
if CONFIG_FILE.exists():
|
||
return json.loads(CONFIG_FILE.read_text(encoding='utf-8'))
|
||
return DEFAULT_CONFIG.copy()
|
||
|
||
def save_config(config):
|
||
"""保存网站配置"""
|
||
CONFIG_FILE.write_text(json.dumps(config, ensure_ascii=False, indent=2), encoding='utf-8')
|
||
|
||
def load_data(file_path):
|
||
"""加载JSON数据"""
|
||
if file_path.exists():
|
||
return json.loads(file_path.read_text(encoding='utf-8'))
|
||
return []
|
||
|
||
def save_data(file_path, data):
|
||
"""保存JSON数据"""
|
||
file_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8')
|
||
|
||
# ============ 大模型智能解析 ============
|
||
|
||
def parse_with_llm(text, category_type):
|
||
"""
|
||
使用大模型解析文本,提取结构化数据
|
||
"""
|
||
|
||
# 根据类型定义字段模板
|
||
field_templates = {
|
||
'model': {
|
||
'name': '模型名称',
|
||
'organization': '厂商/组织',
|
||
'parameters': '参数量(数字,单位B)',
|
||
'context_length': '上下文长度(数字)',
|
||
'architecture': '架构类型',
|
||
'is_open_source': '是否开源(true/false)',
|
||
'mmlu': 'MMLU分数(数字)',
|
||
'input_price': '输入价格(数字)',
|
||
'output_price': '输出价格(数字)',
|
||
'license': '许可证',
|
||
'description': '简介描述',
|
||
},
|
||
'gpu': {
|
||
'name': 'GPU名称',
|
||
'manufacturer': '厂商',
|
||
'architecture': '架构',
|
||
'memory_gb': '显存大小(数字,单位GB)',
|
||
'cuda_cores': 'CUDA核心数(数字)',
|
||
'tensor_cores': 'Tensor核心数(数字)',
|
||
'memory_bandwidth_gbs': '显存带宽(数字,单位GB/s)',
|
||
'fp16_tflops': 'FP16性能(数字,单位TF)',
|
||
'price_usd': '价格(数字)',
|
||
'release_year': '发布年份(数字)',
|
||
'description': '简介描述',
|
||
},
|
||
'cpu': {
|
||
'name': 'CPU名称',
|
||
'manufacturer': '厂商',
|
||
'architecture': '架构',
|
||
'cores': '核心数(数字)',
|
||
'threads': '线程数(数字)',
|
||
'base_clock_ghz': '基础频率(数字,单位GHz)',
|
||
'boost_clock_ghz': '加速频率(数字,单位GHz)',
|
||
'l3_cache_mb': 'L3缓存(数字,单位MB)',
|
||
'tdp_watts': 'TDP功耗(数字,单位W)',
|
||
'price_usd': '价格(数字)',
|
||
'description': '简介描述',
|
||
},
|
||
'dynamic': {
|
||
'name': '名称',
|
||
'brand': '品牌',
|
||
'price': '价格(数字)',
|
||
'year': '年份(数字)',
|
||
'specs': '规格参数',
|
||
'description': '简介描述',
|
||
},
|
||
}
|
||
|
||
fields = field_templates.get(category_type, field_templates['dynamic'])
|
||
|
||
prompt = f"""请解析以下文本,提取结构化数据。
|
||
|
||
文本内容:
|
||
{text}
|
||
|
||
需要提取的字段:
|
||
{json.dumps(fields, ensure_ascii=False, indent=2)}
|
||
|
||
要求:
|
||
1. 根据文本内容智能提取各个字段的值
|
||
2. 数字字段只返回数字,不带单位
|
||
3. 如果某字段在文本中没有提及,返回null
|
||
4. 返回JSON格式,不要包含任何其他内容
|
||
|
||
请直接返回JSON数据:"""
|
||
|
||
try:
|
||
response = requests.post(
|
||
f"{LLM_CONFIG['base_url']}/chat/completions",
|
||
headers={
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {LLM_CONFIG['api_key']}"
|
||
},
|
||
json={
|
||
"model": LLM_CONFIG['model'],
|
||
"messages": [
|
||
{"role": "system", "content": "你是一个数据提取助手,负责从文本中提取结构化数据。只返回JSON,不要其他内容。"},
|
||
{"role": "user", "content": prompt}
|
||
],
|
||
"max_tokens": 1000,
|
||
"temperature": 0.1
|
||
},
|
||
timeout=30
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
data = response.json()
|
||
content = data['choices'][0]['message']['content'].strip()
|
||
|
||
# 清理可能的markdown包裹
|
||
if content.startswith('```'):
|
||
content = content.split('\n', 1)[1] if '\n' in content else content[3:]
|
||
content = content.rsplit('```', 1)[0] if '```' in content else content
|
||
|
||
# 解析JSON
|
||
parsed = json.loads(content)
|
||
|
||
# 清理null值
|
||
cleaned = {}
|
||
for k, v in parsed.items():
|
||
if v is not None and v != '' and v != 'null':
|
||
# 尝试转换数字
|
||
if isinstance(v, str):
|
||
try:
|
||
if '.' in v:
|
||
cleaned[k] = float(v)
|
||
else:
|
||
cleaned[k] = int(v)
|
||
except:
|
||
cleaned[k] = v
|
||
else:
|
||
cleaned[k] = v
|
||
|
||
return cleaned
|
||
except Exception as e:
|
||
print(f"LLM解析失败: {e}")
|
||
|
||
# 降级处理:返回基本结构
|
||
return {'name': text[:50], 'description': text}
|
||
|
||
# ============ 页面路由 ============
|
||
|
||
@app.route('/')
|
||
def index():
|
||
"""首页"""
|
||
return render_template('index.html')
|
||
|
||
@app.route('/models')
|
||
def models_page():
|
||
"""模型数据库页面"""
|
||
return render_template('models.html')
|
||
|
||
@app.route('/gpus')
|
||
def gpus_page():
|
||
"""GPU数据库页面"""
|
||
return render_template('gpus.html')
|
||
|
||
@app.route('/cpus')
|
||
def cpus_page():
|
||
"""CPU数据库页面"""
|
||
return render_template('cpus.html')
|
||
|
||
@app.route('/tools')
|
||
def tools_page():
|
||
"""工具页面"""
|
||
return render_template('tools.html')
|
||
|
||
@app.route('/compare')
|
||
def compare_page():
|
||
"""对比页面"""
|
||
return render_template('compare.html')
|
||
|
||
@app.route('/knowledge')
|
||
def knowledge_page():
|
||
"""知识库页面"""
|
||
return render_template('knowledge.html')
|
||
|
||
@app.route('/admin')
|
||
def admin_page():
|
||
"""后台管理页面"""
|
||
return render_template('admin.html')
|
||
|
||
@app.route('/category/<category_id>')
|
||
def category_page(category_id):
|
||
"""动态分类页面"""
|
||
categories = load_data(CATEGORIES_FILE)
|
||
category = next((c for c in categories if c['id'] == category_id), None)
|
||
|
||
if not category:
|
||
return "分类不存在", 404
|
||
|
||
return render_template('category.html', category=category)
|
||
|
||
# ============ API路由 ============
|
||
|
||
@app.route('/api/models')
|
||
def api_models():
|
||
"""获取模型列表"""
|
||
models = load_data(MODELS_FILE)
|
||
|
||
# 过滤隐藏项(前台默认不显示visible=false)
|
||
hide_hidden = request.args.get('all', '0') == '0'
|
||
if hide_hidden:
|
||
models = [m for m in models if m.get('visible', True)]
|
||
|
||
# 搜索过滤
|
||
keyword = request.args.get('q', '').strip().lower()
|
||
if keyword:
|
||
models = [m for m in models if keyword in m.get('name', '').lower() or
|
||
keyword in m.get('organization', '').lower()]
|
||
|
||
# 排序
|
||
sort_by = request.args.get('sort', 'default')
|
||
reverse = request.args.get('order', 'desc') == 'desc'
|
||
|
||
# 安全排序:处理可能的None/缺失值
|
||
def safe_sort_key(x, key):
|
||
val = x.get(key)
|
||
if val is None:
|
||
if key in ['parameters', 'context_length', 'mmlu', 'views']:
|
||
return 0
|
||
elif key in ['publish_date', 'created_at', 'updated_at']:
|
||
return ''
|
||
return ''
|
||
return val
|
||
|
||
# 默认排序:置顶优先 → 发布日期最新
|
||
if sort_by == 'default':
|
||
# 先按置顶排序,再按发布日期排序
|
||
def default_sort_key(x):
|
||
is_pinned = x.get('is_pinned', False)
|
||
publish_date = x.get('publish_date', '')
|
||
created_at = x.get('created_at', '')
|
||
# 置顶的排在前面 (True > False)
|
||
# 发布日期按时间戳排序(越新的排在前面)
|
||
return (not is_pinned, -(parse_date_to_timestamp(publish_date) or parse_date_to_timestamp(created_at) or 0))
|
||
models = sorted(models, key=default_sort_key)
|
||
elif sort_by in ['name', 'parameters', 'context_length', 'mmlu', 'created_at', 'publish_date', 'views', 'updated_at']:
|
||
models = sorted(models, key=lambda x: safe_sort_key(x, sort_by), reverse=reverse)
|
||
|
||
return jsonify(models)
|
||
|
||
def parse_date_to_timestamp(date_str):
|
||
"""将日期字符串转换为时间戳"""
|
||
if not date_str:
|
||
return None
|
||
try:
|
||
# 支持多种格式
|
||
for fmt in ['%Y-%m-%d', '%Y-%m-%d %H:%M:%S', '%Y/%m/%d']:
|
||
try:
|
||
return datetime.strptime(date_str, fmt).timestamp()
|
||
except:
|
||
pass
|
||
return None
|
||
except:
|
||
return None
|
||
|
||
@app.route('/api/models/<model_id>')
|
||
def api_model_detail(model_id):
|
||
"""获取单个模型详情"""
|
||
models = load_data(MODELS_FILE)
|
||
model = next((m for m in models if m['id'] == model_id), None)
|
||
|
||
if not model:
|
||
return jsonify({'error': 'Model not found'}), 404
|
||
|
||
return jsonify(model)
|
||
|
||
@app.route('/api/models', methods=['POST'])
|
||
def api_create_model():
|
||
"""创建新模型"""
|
||
data = request.get_json()
|
||
models = load_data(MODELS_FILE)
|
||
|
||
data['id'] = uuid.uuid4().hex[:12]
|
||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
data['visible'] = data.get('visible', True) # 默认显示
|
||
data['publish_date'] = data.get('publish_date', '') # 发布日期
|
||
data['views'] = data.get('views', 0) # 热度/阅读数
|
||
data['is_pinned'] = data.get('is_pinned', False) # 置顶
|
||
|
||
models.append(data)
|
||
save_data(MODELS_FILE, models)
|
||
|
||
return jsonify(data)
|
||
|
||
@app.route('/api/models/<model_id>', methods=['PUT'])
|
||
def api_update_model(model_id):
|
||
"""更新模型"""
|
||
data = request.get_json()
|
||
models = load_data(MODELS_FILE)
|
||
|
||
model = next((m for m in models if m['id'] == model_id), None)
|
||
if not model:
|
||
return jsonify({'error': 'Model not found'}), 404
|
||
|
||
model.update(data)
|
||
model['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
save_data(MODELS_FILE, models)
|
||
|
||
return jsonify(model)
|
||
|
||
@app.route('/api/models/<model_id>', methods=['DELETE'])
|
||
def api_delete_model(model_id):
|
||
"""删除模型"""
|
||
models = load_data(MODELS_FILE)
|
||
models = [m for m in models if m['id'] != model_id]
|
||
save_data(MODELS_FILE, models)
|
||
|
||
return jsonify({'success': True})
|
||
|
||
@app.route('/api/models/<model_id>/visible', methods=['POST'])
|
||
def api_toggle_model_visible(model_id):
|
||
"""切换模型显示状态"""
|
||
models = load_data(MODELS_FILE)
|
||
model = next((m for m in models if m['id'] == model_id), None)
|
||
if not model:
|
||
return jsonify({'error': 'Model not found'}), 404
|
||
|
||
model['visible'] = not model.get('visible', True)
|
||
save_data(MODELS_FILE, models)
|
||
|
||
return jsonify({'success': True, 'visible': model['visible']})
|
||
|
||
# ============ 智能添加API ============
|
||
|
||
@app.route('/api/models/smart-add', methods=['POST'])
|
||
def api_smart_add_model():
|
||
"""智能添加模型(粘贴文本解析)"""
|
||
data = request.get_json()
|
||
text = data.get('text', '')
|
||
|
||
if not text:
|
||
return jsonify({'error': '文本不能为空'}), 400
|
||
|
||
# 大模型解析
|
||
parsed = parse_with_llm(text, 'model')
|
||
|
||
# 补充必要字段
|
||
parsed['id'] = uuid.uuid4().hex[:12]
|
||
parsed['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
parsed['visible'] = True
|
||
parsed['raw_text'] = text # 保存原始文本
|
||
parsed['publish_date'] = parsed.get('publish_date', '') # 发布日期
|
||
parsed['views'] = 0 # 热度初始化为0
|
||
parsed['is_pinned'] = False # 置顶初始化为False
|
||
|
||
# 保存
|
||
models = load_data(MODELS_FILE)
|
||
models.append(parsed)
|
||
save_data(MODELS_FILE, models)
|
||
|
||
return jsonify(parsed)
|
||
|
||
@app.route('/api/gpus/smart-add', methods=['POST'])
|
||
def api_smart_add_gpu():
|
||
"""智能添加GPU"""
|
||
data = request.get_json()
|
||
text = data.get('text', '')
|
||
|
||
if not text:
|
||
return jsonify({'error': '文本不能为空'}), 400
|
||
|
||
parsed = parse_with_llm(text, 'gpu')
|
||
parsed['id'] = uuid.uuid4().hex[:12]
|
||
parsed['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
parsed['visible'] = True
|
||
parsed['raw_text'] = text
|
||
parsed['publish_date'] = parsed.get('publish_date', '')
|
||
parsed['views'] = 0
|
||
parsed['is_pinned'] = False
|
||
|
||
gpus = load_data(GPUS_FILE)
|
||
gpus.append(parsed)
|
||
save_data(GPUS_FILE, gpus)
|
||
|
||
return jsonify(parsed)
|
||
|
||
@app.route('/api/cpus/smart-add', methods=['POST'])
|
||
def api_smart_add_cpu():
|
||
"""智能添加CPU"""
|
||
data = request.get_json()
|
||
text = data.get('text', '')
|
||
|
||
if not text:
|
||
return jsonify({'error': '文本不能为空'}), 400
|
||
|
||
parsed = parse_with_llm(text, 'cpu')
|
||
parsed['id'] = uuid.uuid4().hex[:12]
|
||
parsed['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
parsed['visible'] = True
|
||
parsed['raw_text'] = text
|
||
parsed['publish_date'] = parsed.get('publish_date', '')
|
||
parsed['views'] = 0
|
||
parsed['is_pinned'] = False
|
||
|
||
cpus = load_data(CPUS_FILE)
|
||
cpus.append(parsed)
|
||
save_data(CPUS_FILE, cpus)
|
||
|
||
return jsonify(parsed)
|
||
|
||
@app.route('/api/items/<category_id>/smart-add', methods=['POST'])
|
||
def api_smart_add_item(category_id):
|
||
"""智能添加动态分类数据"""
|
||
data = request.get_json()
|
||
text = data.get('text', '')
|
||
|
||
if not text:
|
||
return jsonify({'error': '文本不能为空'}), 400
|
||
|
||
parsed = parse_with_llm(text, 'dynamic')
|
||
parsed['id'] = uuid.uuid4().hex[:12]
|
||
parsed['category_id'] = category_id
|
||
parsed['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
parsed['visible'] = True
|
||
parsed['raw_text'] = text
|
||
parsed['publish_date'] = parsed.get('publish_date', '')
|
||
parsed['views'] = 0
|
||
parsed['is_pinned'] = False
|
||
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
items.append(parsed)
|
||
save_data(items_file, items)
|
||
|
||
return jsonify(parsed)
|
||
|
||
# ============ GPU API ============
|
||
|
||
@app.route('/api/gpus')
|
||
def api_gpus():
|
||
"""获取GPU列表"""
|
||
gpus = load_data(GPUS_FILE)
|
||
|
||
# 过滤隐藏项
|
||
hide_hidden = request.args.get('all', '0') == '0'
|
||
if hide_hidden:
|
||
gpus = [g for g in gpus if g.get('visible', True)]
|
||
|
||
keyword = request.args.get('q', '').strip().lower()
|
||
if keyword:
|
||
gpus = [g for g in gpus if keyword in g.get('name', '').lower() or
|
||
keyword in g.get('manufacturer', '').lower()]
|
||
|
||
# 排序
|
||
sort_by = request.args.get('sort', 'default')
|
||
reverse = request.args.get('order', 'desc') == 'desc'
|
||
|
||
def safe_sort_key(x, key):
|
||
val = x.get(key)
|
||
if val is None:
|
||
if key in ['memory_gb', 'cuda_cores', 'tensor_cores', 'views']:
|
||
return 0
|
||
elif key in ['publish_date', 'created_at', 'updated_at']:
|
||
return ''
|
||
return ''
|
||
return val
|
||
|
||
if sort_by == 'default':
|
||
def default_sort_key(x):
|
||
is_pinned = x.get('is_pinned', False)
|
||
publish_date = x.get('publish_date', '')
|
||
created_at = x.get('created_at', '')
|
||
return (not is_pinned, -(parse_date_to_timestamp(publish_date) or parse_date_to_timestamp(created_at) or 0))
|
||
gpus = sorted(gpus, key=default_sort_key)
|
||
elif sort_by in ['name', 'memory_gb', 'price_usd', 'created_at', 'publish_date', 'views', 'updated_at', 'release_year']:
|
||
gpus = sorted(gpus, key=lambda x: safe_sort_key(x, sort_by), reverse=reverse)
|
||
|
||
return jsonify(gpus)
|
||
|
||
@app.route('/api/gpus/<gpu_id>')
|
||
def api_gpu_detail(gpu_id):
|
||
"""获取单个GPU详情"""
|
||
gpus = load_data(GPUS_FILE)
|
||
gpu = next((g for g in gpus if g['id'] == gpu_id), None)
|
||
|
||
if not gpu:
|
||
return jsonify({'error': 'GPU not found'}), 404
|
||
|
||
return jsonify(gpu)
|
||
|
||
@app.route('/api/gpus', methods=['POST'])
|
||
def api_create_gpu():
|
||
"""创建新GPU"""
|
||
data = request.get_json()
|
||
gpus = load_data(GPUS_FILE)
|
||
|
||
data['id'] = uuid.uuid4().hex[:12]
|
||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
data['visible'] = data.get('visible', True)
|
||
data['publish_date'] = data.get('publish_date', '')
|
||
data['views'] = data.get('views', 0)
|
||
data['is_pinned'] = data.get('is_pinned', False)
|
||
|
||
gpus.append(data)
|
||
save_data(GPUS_FILE, gpus)
|
||
|
||
return jsonify(data)
|
||
|
||
@app.route('/api/gpus/<gpu_id>', methods=['PUT'])
|
||
def api_update_gpu(gpu_id):
|
||
"""更新GPU"""
|
||
data = request.get_json()
|
||
gpus = load_data(GPUS_FILE)
|
||
|
||
gpu = next((g for g in gpus if g['id'] == gpu_id), None)
|
||
if not gpu:
|
||
return jsonify({'error': 'GPU not found'}), 404
|
||
|
||
gpu.update(data)
|
||
gpu['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
save_data(GPUS_FILE, gpus)
|
||
|
||
return jsonify(gpu)
|
||
|
||
@app.route('/api/gpus/<gpu_id>', methods=['DELETE'])
|
||
def api_delete_gpu(gpu_id):
|
||
"""删除GPU"""
|
||
gpus = load_data(GPUS_FILE)
|
||
gpus = [g for g in gpus if g['id'] != gpu_id]
|
||
save_data(GPUS_FILE, gpus)
|
||
|
||
return jsonify({'success': True})
|
||
|
||
@app.route('/api/gpus/<gpu_id>/visible', methods=['POST'])
|
||
def api_toggle_gpu_visible(gpu_id):
|
||
"""切换GPU显示状态"""
|
||
gpus = load_data(GPUS_FILE)
|
||
gpu = next((g for g in gpus if g['id'] == gpu_id), None)
|
||
if not gpu:
|
||
return jsonify({'error': 'GPU not found'}), 404
|
||
|
||
gpu['visible'] = not gpu.get('visible', True)
|
||
save_data(GPUS_FILE, gpus)
|
||
|
||
return jsonify({'success': True, 'visible': gpu['visible']})
|
||
|
||
# ============ CPU API ============
|
||
|
||
@app.route('/api/cpus')
|
||
def api_cpus():
|
||
"""获取CPU列表"""
|
||
cpus = load_data(CPUS_FILE)
|
||
|
||
# 过滤隐藏项
|
||
hide_hidden = request.args.get('all', '0') == '0'
|
||
if hide_hidden:
|
||
cpus = [c for c in cpus if c.get('visible', True)]
|
||
|
||
keyword = request.args.get('q', '').strip().lower()
|
||
if keyword:
|
||
cpus = [c for c in cpus if keyword in c.get('name', '').lower() or
|
||
keyword in c.get('manufacturer', '').lower()]
|
||
|
||
# 排序
|
||
sort_by = request.args.get('sort', 'default')
|
||
reverse = request.args.get('order', 'desc') == 'desc'
|
||
|
||
def safe_sort_key(x, key):
|
||
val = x.get(key)
|
||
if val is None:
|
||
if key in ['cores', 'threads', 'views']:
|
||
return 0
|
||
elif key in ['publish_date', 'created_at', 'updated_at']:
|
||
return ''
|
||
return ''
|
||
return val
|
||
|
||
if sort_by == 'default':
|
||
def default_sort_key(x):
|
||
is_pinned = x.get('is_pinned', False)
|
||
publish_date = x.get('publish_date', '')
|
||
created_at = x.get('created_at', '')
|
||
return (not is_pinned, -(parse_date_to_timestamp(publish_date) or parse_date_to_timestamp(created_at) or 0))
|
||
cpus = sorted(cpus, key=default_sort_key)
|
||
elif sort_by in ['name', 'cores', 'threads', 'price_usd', 'created_at', 'publish_date', 'views', 'updated_at']:
|
||
cpus = sorted(cpus, key=lambda x: safe_sort_key(x, sort_by), reverse=reverse)
|
||
|
||
return jsonify(cpus)
|
||
|
||
@app.route('/api/cpus/<cpu_id>')
|
||
def api_cpu_detail(cpu_id):
|
||
"""获取单个CPU详情"""
|
||
cpus = load_data(CPUS_FILE)
|
||
cpu = next((c for c in cpus if c['id'] == cpu_id), None)
|
||
|
||
if not cpu:
|
||
return jsonify({'error': 'CPU not found'}), 404
|
||
|
||
return jsonify(cpu)
|
||
|
||
@app.route('/api/cpus', methods=['POST'])
|
||
def api_create_cpu():
|
||
"""创建新CPU"""
|
||
data = request.get_json()
|
||
cpus = load_data(CPUS_FILE)
|
||
|
||
data['id'] = uuid.uuid4().hex[:12]
|
||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
data['visible'] = data.get('visible', True)
|
||
data['publish_date'] = data.get('publish_date', '')
|
||
data['views'] = data.get('views', 0)
|
||
data['is_pinned'] = data.get('is_pinned', False)
|
||
|
||
cpus.append(data)
|
||
save_data(CPUS_FILE, cpus)
|
||
|
||
return jsonify(data)
|
||
|
||
@app.route('/api/cpus/<cpu_id>', methods=['PUT'])
|
||
def api_update_cpu(cpu_id):
|
||
"""更新CPU"""
|
||
data = request.get_json()
|
||
cpus = load_data(CPUS_FILE)
|
||
|
||
cpu = next((c for c in cpus if c['id'] == cpu_id), None)
|
||
if not cpu:
|
||
return jsonify({'error': 'CPU not found'}), 404
|
||
|
||
cpu.update(data)
|
||
cpu['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
save_data(CPUS_FILE, cpus)
|
||
|
||
return jsonify(cpu)
|
||
|
||
@app.route('/api/cpus/<cpu_id>', methods=['DELETE'])
|
||
def api_delete_cpu(cpu_id):
|
||
"""删除CPU"""
|
||
cpus = load_data(CPUS_FILE)
|
||
cpus = [c for c in cpus if c['id'] != cpu_id]
|
||
save_data(CPUS_FILE, cpus)
|
||
|
||
return jsonify({'success': True})
|
||
|
||
@app.route('/api/cpus/<cpu_id>/visible', methods=['POST'])
|
||
def api_toggle_cpu_visible(cpu_id):
|
||
"""切换CPU显示状态"""
|
||
cpus = load_data(CPUS_FILE)
|
||
cpu = next((c for c in cpus if c['id'] == cpu_id), None)
|
||
if not cpu:
|
||
return jsonify({'error': 'CPU not found'}), 404
|
||
|
||
cpu['visible'] = not cpu.get('visible', True)
|
||
save_data(CPUS_FILE, cpus)
|
||
|
||
return jsonify({'success': True, 'visible': cpu['visible']})
|
||
|
||
# ============ 搜索和其他API ============
|
||
|
||
@app.route('/api/search')
|
||
def api_search():
|
||
"""全局搜索"""
|
||
keyword = request.args.get('q', '').strip().lower()
|
||
|
||
if not keyword:
|
||
return jsonify({'models': [], 'gpus': [], 'cpus': []})
|
||
|
||
models = load_data(MODELS_FILE)
|
||
gpus = load_data(GPUS_FILE)
|
||
cpus = load_data(CPUS_FILE)
|
||
|
||
result = {
|
||
'models': [m for m in models if m.get('visible', True) and (keyword in m.get('name', '').lower() or keyword in m.get('organization', '').lower())],
|
||
'gpus': [g for g in gpus if g.get('visible', True) and (keyword in g.get('name', '').lower() or keyword in g.get('manufacturer', '').lower())],
|
||
'cpus': [c for c in cpus if c.get('visible', True) and (keyword in c.get('name', '').lower() or keyword in c.get('manufacturer', '').lower())]
|
||
}
|
||
|
||
return jsonify(result)
|
||
|
||
@app.route('/api/calculate/vram')
|
||
def api_calculate_vram():
|
||
"""显存计算"""
|
||
params = request.args.get('params', '7', type=float)
|
||
precision = request.args.get('precision', 'fp16', type=str)
|
||
|
||
bytes_per_param = {'fp32': 4, 'fp16': 2, 'int8': 1, 'int4': 0.5}
|
||
multiplier = bytes_per_param.get(precision, 2)
|
||
vram_gb = params * multiplier * 1e9 / (1024**3)
|
||
total_vram = vram_gb * 1.3
|
||
|
||
gpus = load_data(GPUS_FILE)
|
||
suitable_gpus = [g for g in gpus if g.get('visible', True) and g.get('memory_gb', 0) >= total_vram]
|
||
|
||
return jsonify({
|
||
'model_vram': round(vram_gb, 2),
|
||
'total_vram': round(total_vram, 2),
|
||
'suitable_gpus': suitable_gpus
|
||
})
|
||
|
||
@app.route('/api/stats')
|
||
def api_stats():
|
||
"""统计数据"""
|
||
models = load_data(MODELS_FILE)
|
||
gpus = load_data(GPUS_FILE)
|
||
cpus = load_data(CPUS_FILE)
|
||
categories = load_data(CATEGORIES_FILE)
|
||
knowledge = load_data(KNOWLEDGE_FILE)
|
||
|
||
return jsonify({
|
||
'models_count': len([m for m in models if m.get('visible', True)]),
|
||
'gpus_count': len([g for g in gpus if g.get('visible', True)]),
|
||
'cpus_count': len([c for c in cpus if c.get('visible', True)]),
|
||
'categories_count': len([c for c in categories if c.get('visible', True)]),
|
||
'knowledge_count': len(knowledge),
|
||
'latest_models': sorted([m for m in models if m.get('visible', True)], key=lambda x: x.get('created_at', ''), reverse=True)[:5]
|
||
})
|
||
|
||
# ============ 分类管理API ============
|
||
|
||
@app.route('/api/categories')
|
||
def api_categories():
|
||
"""获取分类列表"""
|
||
categories = load_data(CATEGORIES_FILE)
|
||
|
||
# 过滤隐藏项
|
||
hide_hidden = request.args.get('all', '0') == '0'
|
||
if hide_hidden:
|
||
categories = [c for c in categories if c.get('visible', True)]
|
||
|
||
return jsonify(sorted(categories, key=lambda x: x.get('order', 0)))
|
||
|
||
@app.route('/api/categories', methods=['POST'])
|
||
def api_create_category():
|
||
"""创建新分类"""
|
||
data = request.get_json()
|
||
categories = load_data(CATEGORIES_FILE)
|
||
|
||
data['id'] = uuid.uuid4().hex[:12]
|
||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
data['visible'] = data.get('visible', True)
|
||
|
||
categories.append(data)
|
||
save_data(CATEGORIES_FILE, categories)
|
||
|
||
return jsonify(data)
|
||
|
||
@app.route('/api/categories/<category_id>', methods=['PUT'])
|
||
def api_update_category(category_id):
|
||
"""更新分类"""
|
||
data = request.get_json()
|
||
categories = load_data(CATEGORIES_FILE)
|
||
|
||
category = next((c for c in categories if c['id'] == category_id), None)
|
||
if not category:
|
||
return jsonify({'error': 'Category not found'}), 404
|
||
|
||
category.update(data)
|
||
category['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
save_data(CATEGORIES_FILE, categories)
|
||
|
||
return jsonify(category)
|
||
|
||
@app.route('/api/categories/<category_id>', methods=['DELETE'])
|
||
def api_delete_category(category_id):
|
||
"""删除分类"""
|
||
categories = load_data(CATEGORIES_FILE)
|
||
categories = [c for c in categories if c['id'] != category_id]
|
||
save_data(CATEGORIES_FILE, categories)
|
||
|
||
return jsonify({'success': True})
|
||
|
||
@app.route('/api/categories/<category_id>/visible', methods=['POST'])
|
||
def api_toggle_category_visible(category_id):
|
||
"""切换分类显示状态"""
|
||
categories = load_data(CATEGORIES_FILE)
|
||
category = next((c for c in categories if c['id'] == category_id), None)
|
||
if not category:
|
||
return jsonify({'error': 'Category not found'}), 404
|
||
|
||
category['visible'] = not category.get('visible', True)
|
||
save_data(CATEGORIES_FILE, categories)
|
||
|
||
return jsonify({'success': True, 'visible': category['visible']})
|
||
|
||
# ============ 知识库管理API ============
|
||
|
||
@app.route('/api/knowledge')
|
||
def api_knowledge():
|
||
"""获取知识列表"""
|
||
knowledge = load_data(KNOWLEDGE_FILE)
|
||
|
||
# 过滤隐藏项(前台默认隐藏)
|
||
hide_hidden = request.args.get('all', '0') == '0'
|
||
if hide_hidden:
|
||
knowledge = [k for k in knowledge if k.get('visible', True)]
|
||
|
||
keyword = request.args.get('q', '').strip().lower()
|
||
if keyword:
|
||
knowledge = [k for k in knowledge if keyword in k.get('title', '').lower() or keyword in k.get('content', '').lower()]
|
||
|
||
category = request.args.get('category', '')
|
||
if category:
|
||
knowledge = [k for k in knowledge if k.get('category') == category]
|
||
|
||
return jsonify(sorted(knowledge, key=lambda x: x.get('order', 0)))
|
||
|
||
@app.route('/api/knowledge/<knowledge_id>')
|
||
def api_knowledge_detail(knowledge_id):
|
||
"""获取单个知识详情"""
|
||
knowledge = load_data(KNOWLEDGE_FILE)
|
||
item = next((k for k in knowledge if k['id'] == knowledge_id), None)
|
||
|
||
if not item:
|
||
return jsonify({'error': 'Knowledge not found'}), 404
|
||
|
||
return jsonify(item)
|
||
|
||
@app.route('/api/knowledge', methods=['POST'])
|
||
def api_create_knowledge():
|
||
"""创建新知识"""
|
||
data = request.get_json()
|
||
knowledge = load_data(KNOWLEDGE_FILE)
|
||
|
||
data['id'] = uuid.uuid4().hex[:12]
|
||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
data['visible'] = data.get('visible', True) # 默认显示
|
||
if 'order' not in data:
|
||
data['order'] = len(knowledge)
|
||
|
||
knowledge.append(data)
|
||
save_data(KNOWLEDGE_FILE, knowledge)
|
||
|
||
return jsonify(data)
|
||
|
||
@app.route('/api/knowledge/<knowledge_id>', methods=['PUT'])
|
||
def api_update_knowledge(knowledge_id):
|
||
"""更新知识"""
|
||
data = request.get_json()
|
||
knowledge = load_data(KNOWLEDGE_FILE)
|
||
|
||
item = next((k for k in knowledge if k['id'] == knowledge_id), None)
|
||
if not item:
|
||
return jsonify({'error': 'Knowledge not found'}), 404
|
||
|
||
item.update(data)
|
||
item['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
save_data(KNOWLEDGE_FILE, knowledge)
|
||
|
||
return jsonify(item)
|
||
|
||
@app.route('/api/knowledge/<knowledge_id>', methods=['DELETE'])
|
||
def api_delete_knowledge(knowledge_id):
|
||
"""删除知识"""
|
||
knowledge = load_data(KNOWLEDGE_FILE)
|
||
knowledge = [k for k in knowledge if k['id'] != knowledge_id]
|
||
save_data(KNOWLEDGE_FILE, knowledge)
|
||
|
||
return jsonify({'success': True})
|
||
|
||
@app.route('/api/knowledge/<knowledge_id>/visible', methods=['POST'])
|
||
def api_toggle_knowledge_visible(knowledge_id):
|
||
"""切换知识显示状态"""
|
||
knowledge = load_data(KNOWLEDGE_FILE)
|
||
item = next((k for k in knowledge if k['id'] == knowledge_id), None)
|
||
if not item:
|
||
return jsonify({'error': 'Knowledge not found'}), 404
|
||
|
||
item['visible'] = not item.get('visible', True)
|
||
save_data(KNOWLEDGE_FILE, knowledge)
|
||
|
||
return jsonify({'success': True, 'visible': item['visible']})
|
||
|
||
# ============ 动态分类数据API ============
|
||
|
||
@app.route('/api/items/<category_id>')
|
||
def api_items(category_id):
|
||
"""获取分类下的数据列表"""
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
|
||
# 过滤隐藏项
|
||
hide_hidden = request.args.get('all', '0') == '0'
|
||
if hide_hidden:
|
||
items = [i for i in items if i.get('visible', True)]
|
||
|
||
# 排序
|
||
sort_by = request.args.get('sort', 'default')
|
||
reverse = request.args.get('order', 'desc') == 'desc'
|
||
|
||
def safe_sort_key(x, key):
|
||
val = x.get(key)
|
||
if val is None:
|
||
if key in ['price', 'views', 'year']:
|
||
return 0
|
||
elif key in ['publish_date', 'created_at', 'updated_at']:
|
||
return ''
|
||
return ''
|
||
return val
|
||
|
||
if sort_by == 'default':
|
||
def default_sort_key(x):
|
||
is_pinned = x.get('is_pinned', False)
|
||
publish_date = x.get('publish_date', '')
|
||
created_at = x.get('created_at', '')
|
||
return (not is_pinned, -(parse_date_to_timestamp(publish_date) or parse_date_to_timestamp(created_at) or 0))
|
||
items = sorted(items, key=default_sort_key)
|
||
elif sort_by in ['name', 'price', 'year', 'created_at', 'publish_date', 'views', 'updated_at']:
|
||
items = sorted(items, key=lambda x: safe_sort_key(x, sort_by), reverse=reverse)
|
||
|
||
return jsonify(items)
|
||
|
||
@app.route('/api/items/<category_id>/<item_id>')
|
||
def api_item_detail(category_id, item_id):
|
||
"""获取单个数据详情"""
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
item = next((i for i in items if i['id'] == item_id), None)
|
||
|
||
if not item:
|
||
return jsonify({'error': 'Item not found'}), 404
|
||
|
||
return jsonify(item)
|
||
|
||
@app.route('/api/items/<category_id>', methods=['POST'])
|
||
def api_create_item(category_id):
|
||
"""创建新数据"""
|
||
data = request.get_json()
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
|
||
data['id'] = uuid.uuid4().hex[:12]
|
||
data['category_id'] = category_id
|
||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
data['visible'] = data.get('visible', True)
|
||
data['publish_date'] = data.get('publish_date', '')
|
||
data['views'] = data.get('views', 0)
|
||
data['is_pinned'] = data.get('is_pinned', False)
|
||
|
||
items.append(data)
|
||
save_data(items_file, items)
|
||
|
||
return jsonify(data)
|
||
|
||
@app.route('/api/items/<category_id>/<item_id>', methods=['PUT'])
|
||
def api_update_item(category_id, item_id):
|
||
"""更新数据"""
|
||
data = request.get_json()
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
|
||
item = next((i for i in items if i['id'] == item_id), None)
|
||
if not item:
|
||
return jsonify({'error': 'Item not found'}), 404
|
||
|
||
item.update(data)
|
||
item['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
save_data(items_file, items)
|
||
|
||
return jsonify(item)
|
||
|
||
@app.route('/api/items/<category_id>/<item_id>', methods=['DELETE'])
|
||
def api_delete_item(category_id, item_id):
|
||
"""删除数据"""
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
items = [i for i in items if i['id'] != item_id]
|
||
save_data(items_file, items)
|
||
|
||
return jsonify({'success': True})
|
||
|
||
@app.route('/api/items/<category_id>/<item_id>/visible', methods=['POST'])
|
||
def api_toggle_item_visible(category_id, item_id):
|
||
"""切换动态数据显示状态"""
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
item = next((i for i in items if i['id'] == item_id), None)
|
||
if not item:
|
||
return jsonify({'error': 'Item not found'}), 404
|
||
|
||
item['visible'] = not item.get('visible', True)
|
||
save_data(items_file, items)
|
||
|
||
return jsonify({'success': True, 'visible': item['visible']})
|
||
|
||
# ============ 置顶和热度API ============
|
||
|
||
@app.route('/api/models/<model_id>/pin', methods=['POST'])
|
||
def api_toggle_model_pin(model_id):
|
||
"""切换模型置顶状态"""
|
||
models = load_data(MODELS_FILE)
|
||
model = next((m for m in models if m['id'] == model_id), None)
|
||
if not model:
|
||
return jsonify({'error': 'Model not found'}), 404
|
||
|
||
model['is_pinned'] = not model.get('is_pinned', False)
|
||
save_data(MODELS_FILE, models)
|
||
|
||
return jsonify({'success': True, 'is_pinned': model['is_pinned']})
|
||
|
||
@app.route('/api/models/<model_id>/view', methods=['POST'])
|
||
def api_model_view(model_id):
|
||
"""增加模型阅读数"""
|
||
models = load_data(MODELS_FILE)
|
||
model = next((m for m in models if m['id'] == model_id), None)
|
||
if not model:
|
||
return jsonify({'error': 'Model not found'}), 404
|
||
|
||
model['views'] = model.get('views', 0) + 1
|
||
save_data(MODELS_FILE, models)
|
||
|
||
return jsonify({'success': True, 'views': model['views']})
|
||
|
||
@app.route('/api/gpus/<gpu_id>/pin', methods=['POST'])
|
||
def api_toggle_gpu_pin(gpu_id):
|
||
"""切换GPU置顶状态"""
|
||
gpus = load_data(GPUS_FILE)
|
||
gpu = next((g for g in gpus if g['id'] == gpu_id), None)
|
||
if not gpu:
|
||
return jsonify({'error': 'GPU not found'}), 404
|
||
|
||
gpu['is_pinned'] = not gpu.get('is_pinned', False)
|
||
save_data(GPUS_FILE, gpus)
|
||
|
||
return jsonify({'success': True, 'is_pinned': gpu['is_pinned']})
|
||
|
||
@app.route('/api/gpus/<gpu_id>/view', methods=['POST'])
|
||
def api_gpu_view(gpu_id):
|
||
"""增加GPU阅读数"""
|
||
gpus = load_data(GPUS_FILE)
|
||
gpu = next((g for g in gpus if g['id'] == gpu_id), None)
|
||
if not gpu:
|
||
return jsonify({'error': 'GPU not found'}), 404
|
||
|
||
gpu['views'] = gpu.get('views', 0) + 1
|
||
save_data(GPUS_FILE, gpus)
|
||
|
||
return jsonify({'success': True, 'views': gpu['views']})
|
||
|
||
@app.route('/api/cpus/<cpu_id>/pin', methods=['POST'])
|
||
def api_toggle_cpu_pin(cpu_id):
|
||
"""切换CPU置顶状态"""
|
||
cpus = load_data(CPUS_FILE)
|
||
cpu = next((c for c in cpus if c['id'] == cpu_id), None)
|
||
if not cpu:
|
||
return jsonify({'error': 'CPU not found'}), 404
|
||
|
||
cpu['is_pinned'] = not cpu.get('is_pinned', False)
|
||
save_data(CPUS_FILE, cpus)
|
||
|
||
return jsonify({'success': True, 'is_pinned': cpu['is_pinned']})
|
||
|
||
@app.route('/api/cpus/<cpu_id>/view', methods=['POST'])
|
||
def api_cpu_view(cpu_id):
|
||
"""增加CPU阅读数"""
|
||
cpus = load_data(CPUS_FILE)
|
||
cpu = next((c for c in cpus if c['id'] == cpu_id), None)
|
||
if not cpu:
|
||
return jsonify({'error': 'CPU not found'}), 404
|
||
|
||
cpu['views'] = cpu.get('views', 0) + 1
|
||
save_data(CPUS_FILE, cpus)
|
||
|
||
return jsonify({'success': True, 'views': cpu['views']})
|
||
|
||
@app.route('/api/items/<category_id>/<item_id>/pin', methods=['POST'])
|
||
def api_toggle_item_pin(category_id, item_id):
|
||
"""切换动态数据置顶状态"""
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
item = next((i for i in items if i['id'] == item_id), None)
|
||
if not item:
|
||
return jsonify({'error': 'Item not found'}), 404
|
||
|
||
item['is_pinned'] = not item.get('is_pinned', False)
|
||
save_data(items_file, items)
|
||
|
||
return jsonify({'success': True, 'is_pinned': item['is_pinned']})
|
||
|
||
@app.route('/api/items/<category_id>/<item_id>/view', methods=['POST'])
|
||
def api_item_view(category_id, item_id):
|
||
"""增加动态数据阅读数"""
|
||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||
items = load_data(items_file)
|
||
item = next((i for i in items if i['id'] == item_id), None)
|
||
if not item:
|
||
return jsonify({'error': 'Item not found'}), 404
|
||
|
||
item['views'] = item.get('views', 0) + 1
|
||
save_data(items_file, items)
|
||
|
||
return jsonify({'success': True, 'views': item['views']})
|
||
|
||
# ============ 网站配置API ============
|
||
|
||
@app.route('/api/config')
|
||
def api_get_config():
|
||
"""获取网站配置"""
|
||
return jsonify(load_config())
|
||
|
||
@app.route('/api/config', methods=['PUT'])
|
||
def api_update_config():
|
||
"""更新网站配置"""
|
||
data = request.get_json()
|
||
config = load_config()
|
||
config.update(data)
|
||
config['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
save_config(config)
|
||
return jsonify(config)
|
||
|
||
# ============ 图片上传API ============
|
||
|
||
@app.route('/api/upload/image', methods=['POST'])
|
||
def api_upload_image():
|
||
"""上传图片"""
|
||
if 'file' not in request.files:
|
||
return jsonify({'error': '没有文件'}), 400
|
||
|
||
file = request.files['file']
|
||
if file.filename == '':
|
||
return jsonify({'error': '没有选择文件'}), 400
|
||
|
||
if not allowed_file(file.filename):
|
||
return jsonify({'error': '不允许的文件格式'}), 400
|
||
|
||
# 生成唯一文件名
|
||
ext = file.filename.rsplit('.', 1)[1].lower()
|
||
filename = f"{uuid.uuid4().hex[:12]}_{int(time.time())}.{ext}"
|
||
|
||
# 保存文件
|
||
filepath = IMAGES_DIR / filename
|
||
file.save(filepath)
|
||
|
||
# 返回图片URL
|
||
return jsonify({
|
||
'success': True,
|
||
'filename': filename,
|
||
'url': f'/static/uploads/{filename}'
|
||
})
|
||
|
||
@app.route('/api/upload/image/base64', methods=['POST'])
|
||
def api_upload_image_base64():
|
||
"""上传Base64图片"""
|
||
data = request.get_json()
|
||
image_data = data.get('image', '')
|
||
|
||
if not image_data:
|
||
return jsonify({'error': '没有图片数据'}), 400
|
||
|
||
# 解析Base64数据
|
||
try:
|
||
# 移除data:image/xxx;base64,前缀
|
||
if 'base64,' in image_data:
|
||
image_data = image_data.split('base64,')[1]
|
||
|
||
# 生成文件名
|
||
ext = data.get('ext', 'png')
|
||
filename = f"{uuid.uuid4().hex[:12]}_{int(time.time())}.{ext}"
|
||
|
||
# 保存文件
|
||
filepath = IMAGES_DIR / filename
|
||
with open(filepath, 'wb') as f:
|
||
f.write(base64.b64decode(image_data))
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'filename': filename,
|
||
'url': f'/static/uploads/{filename}'
|
||
})
|
||
except Exception as e:
|
||
return jsonify({'error': str(e)}), 400
|
||
|
||
@app.route('/api/upload/image/delete/<filename>', methods=['DELETE'])
|
||
def api_delete_image(filename):
|
||
"""删除图片"""
|
||
filepath = IMAGES_DIR / filename
|
||
if filepath.exists():
|
||
filepath.unlink()
|
||
return jsonify({'success': True})
|
||
return jsonify({'error': '文件不存在'}), 404
|
||
|
||
if __name__ == '__main__':
|
||
print("=" * 50)
|
||
print("ParamHub - 参数百科 v1.4.0")
|
||
print("=" * 50)
|
||
print(f"访问地址: http://localhost:19010")
|
||
print(f"后台管理: http://localhost:19010/admin")
|
||
print("=" * 50)
|
||
|
||
app.run(host='0.0.0.0', port=19010, debug=True) |