feat: v1.1.0 新增智能添加和展示开关功能
功能更新: - 新增智能添加功能:粘贴文本自动解析为结构化数据 - 新增展示开关:各分类和产品支持显示/隐藏控制 - 保留原始数据:智能添加的产品保留raw_text字段 - 优化价格显示:支持多币种、价格区间、单位 - 修复图标问题:CPU图标改为ri-cpu-line - 新增favicon:所有页面添加浏览器标签图标 技术改进: - 新增大模型API集成(LLM Proxy) - 新增smart-add API接口 - 新增visible字段和toggle API - 优化前端表格显示
This commit is contained in:
444
app.py
444
app.py
@@ -1,13 +1,16 @@
|
||||
"""
|
||||
ParamHub - 参数百科
|
||||
AI大模型与硬件参数速查平台
|
||||
v1.1.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
|
||||
|
||||
app = Flask(__name__, static_folder='static', static_url_path='/static')
|
||||
CORS(app)
|
||||
@@ -23,6 +26,13 @@ CPUS_FILE = DATA_DIR / 'cpus.json'
|
||||
CATEGORIES_FILE = DATA_DIR / 'categories.json'
|
||||
KNOWLEDGE_FILE = DATA_DIR / 'knowledge.json'
|
||||
|
||||
# 大模型配置
|
||||
LLM_CONFIG = {
|
||||
'base_url': 'http://192.168.2.17:19007/v1',
|
||||
'api_key': 'xxxx',
|
||||
'model': 'auto',
|
||||
}
|
||||
|
||||
def load_data(file_path):
|
||||
"""加载JSON数据"""
|
||||
if file_path.exists():
|
||||
@@ -33,6 +43,136 @@ 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('/')
|
||||
@@ -93,6 +233,11 @@ 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:
|
||||
@@ -125,10 +270,9 @@ def api_create_model():
|
||||
data = request.get_json()
|
||||
models = load_data(MODELS_FILE)
|
||||
|
||||
# 生成ID
|
||||
import uuid
|
||||
data['id'] = uuid.uuid4().hex[:12]
|
||||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
data['visible'] = data.get('visible', True) # 默认显示
|
||||
|
||||
models.append(data)
|
||||
save_data(MODELS_FILE, models)
|
||||
@@ -160,11 +304,123 @@ def api_delete_model(model_id):
|
||||
|
||||
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 # 保存原始文本
|
||||
|
||||
# 保存
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
@@ -183,38 +439,15 @@ def api_gpu_detail(gpu_id):
|
||||
|
||||
return jsonify(gpu)
|
||||
|
||||
@app.route('/api/cpus')
|
||||
def api_cpus():
|
||||
"""获取CPU列表"""
|
||||
cpus = load_data(CPUS_FILE)
|
||||
|
||||
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()]
|
||||
|
||||
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/gpus', methods=['POST'])
|
||||
def api_create_gpu():
|
||||
"""创建新GPU"""
|
||||
data = request.get_json()
|
||||
gpus = load_data(GPUS_FILE)
|
||||
|
||||
import uuid
|
||||
data['id'] = uuid.uuid4().hex[:12]
|
||||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
data['visible'] = data.get('visible', True)
|
||||
|
||||
gpus.append(data)
|
||||
save_data(GPUS_FILE, gpus)
|
||||
@@ -246,15 +479,58 @@ def api_delete_gpu(gpu_id):
|
||||
|
||||
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()]
|
||||
|
||||
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)
|
||||
|
||||
import uuid
|
||||
data['id'] = uuid.uuid4().hex[:12]
|
||||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
data['visible'] = data.get('visible', True)
|
||||
|
||||
cpus.append(data)
|
||||
save_data(CPUS_FILE, cpus)
|
||||
@@ -286,6 +562,21 @@ def api_delete_cpu(cpu_id):
|
||||
|
||||
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():
|
||||
"""全局搜索"""
|
||||
@@ -299,12 +590,9 @@ def api_search():
|
||||
cpus = load_data(CPUS_FILE)
|
||||
|
||||
result = {
|
||||
'models': [m for m in models if keyword in m.get('name', '').lower() or
|
||||
keyword in m.get('organization', '').lower()],
|
||||
'gpus': [g for g in gpus if keyword in g.get('name', '').lower() or
|
||||
keyword in g.get('manufacturer', '').lower()],
|
||||
'cpus': [c for c in cpus if keyword in c.get('name', '').lower() or
|
||||
keyword in c.get('manufacturer', '').lower()]
|
||||
'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)
|
||||
@@ -312,31 +600,16 @@ def api_search():
|
||||
@app.route('/api/calculate/vram')
|
||||
def api_calculate_vram():
|
||||
"""显存计算"""
|
||||
params = request.args.get('params', '7', type=float) # 参数量(B)
|
||||
precision = request.args.get('precision', 'fp16', type=str) # 精度
|
||||
|
||||
# 计算公式
|
||||
# FP32: 参数 * 4字节
|
||||
# FP16: 参数 * 2字节
|
||||
# INT8: 参数 * 1字节
|
||||
# INT4: 参数 * 0.5字节
|
||||
|
||||
bytes_per_param = {
|
||||
'fp32': 4,
|
||||
'fp16': 2,
|
||||
'int8': 1,
|
||||
'int4': 0.5
|
||||
}
|
||||
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) # 转换为GB
|
||||
|
||||
# 加上KV cache和激活值估算(约30%额外开销)
|
||||
vram_gb = params * multiplier * 1e9 / (1024**3)
|
||||
total_vram = vram_gb * 1.3
|
||||
|
||||
# 推荐GPU
|
||||
gpus = load_data(GPUS_FILE)
|
||||
suitable_gpus = [g for g in gpus if g.get('memory_gb', 0) >= total_vram]
|
||||
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),
|
||||
@@ -354,12 +627,12 @@ def api_stats():
|
||||
knowledge = load_data(KNOWLEDGE_FILE)
|
||||
|
||||
return jsonify({
|
||||
'models_count': len(models),
|
||||
'gpus_count': len(gpus),
|
||||
'cpus_count': len(cpus),
|
||||
'categories_count': len(categories),
|
||||
'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(models, key=lambda x: x.get('created_at', ''), reverse=True)[:5]
|
||||
'latest_models': sorted([m for m in models if m.get('visible', True)], key=lambda x: x.get('created_at', ''), reverse=True)[:5]
|
||||
})
|
||||
|
||||
# ============ 分类管理API ============
|
||||
@@ -368,7 +641,13 @@ def api_stats():
|
||||
def api_categories():
|
||||
"""获取分类列表"""
|
||||
categories = load_data(CATEGORIES_FILE)
|
||||
return jsonify(categories)
|
||||
|
||||
# 过滤隐藏项
|
||||
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():
|
||||
@@ -376,9 +655,9 @@ def api_create_category():
|
||||
data = request.get_json()
|
||||
categories = load_data(CATEGORIES_FILE)
|
||||
|
||||
import uuid
|
||||
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)
|
||||
@@ -410,6 +689,19 @@ def api_delete_category(category_id):
|
||||
|
||||
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')
|
||||
@@ -417,13 +709,10 @@ def api_knowledge():
|
||||
"""获取知识列表"""
|
||||
knowledge = load_data(KNOWLEDGE_FILE)
|
||||
|
||||
# 搜索过滤
|
||||
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()]
|
||||
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]
|
||||
@@ -447,10 +736,8 @@ def api_create_knowledge():
|
||||
data = request.get_json()
|
||||
knowledge = load_data(KNOWLEDGE_FILE)
|
||||
|
||||
import uuid
|
||||
data['id'] = uuid.uuid4().hex[:12]
|
||||
data['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
if 'order' not in data:
|
||||
data['order'] = len(knowledge)
|
||||
|
||||
@@ -491,6 +778,12 @@ 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)]
|
||||
|
||||
return jsonify(items)
|
||||
|
||||
@app.route('/api/items/<category_id>/<item_id>')
|
||||
@@ -512,10 +805,10 @@ def api_create_item(category_id):
|
||||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||||
items = load_data(items_file)
|
||||
|
||||
import uuid
|
||||
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)
|
||||
|
||||
items.append(data)
|
||||
save_data(items_file, items)
|
||||
@@ -549,11 +842,26 @@ def api_delete_item(category_id, item_id):
|
||||
|
||||
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']})
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("=" * 50)
|
||||
print("ParamHub - 参数百科")
|
||||
print("ParamHub - 参数百科 v1.1.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)
|
||||
@@ -18,7 +18,7 @@
|
||||
{
|
||||
"id": "cpus",
|
||||
"name": "CPU处理器",
|
||||
"icon": "ri-memory-line",
|
||||
"icon": "ri-cpu-line",
|
||||
"color": "purple",
|
||||
"description": "Intel、AMD等CPU处理器参数",
|
||||
"order": 3
|
||||
@@ -42,8 +42,8 @@
|
||||
{
|
||||
"id": "021dc76d36be",
|
||||
"name": "汽车",
|
||||
"icon": "ri-folder-line",
|
||||
"color": "blue",
|
||||
"icon": "ri-car-line",
|
||||
"color": "red",
|
||||
"order": 6,
|
||||
"description": "汽车方面",
|
||||
"created_at": "2026-04-09 10:09:01"
|
||||
|
||||
210
data/models.json
210
data/models.json
@@ -1,14 +1,200 @@
|
||||
[
|
||||
{"id": "gpt4", "name": "GPT-4", "organization": "OpenAI", "parameters": 1760, "architecture": "Transformer", "context_length": 8192, "input_price": 0.03, "output_price": 0.06, "mmlu": 86.4, "humaneval": 67.0, "is_open_source": false, "license": "Proprietary", "description": "OpenAI最强大的多模态大模型", "created_at": "2024-01-01"},
|
||||
{"id": "gpt4turbo", "name": "GPT-4 Turbo", "organization": "OpenAI", "parameters": 1760, "architecture": "Transformer", "context_length": 128000, "input_price": 0.01, "output_price": 0.03, "mmlu": 86.4, "humaneval": 67.0, "is_open_source": false, "license": "Proprietary", "description": "GPT-4增强版,128K上下文", "created_at": "2024-01-01"},
|
||||
{"id": "gpt35", "name": "GPT-3.5 Turbo", "organization": "OpenAI", "parameters": 175, "architecture": "Transformer", "context_length": 16385, "input_price": 0.0005, "output_price": 0.0015, "mmlu": 70.0, "humaneval": 48.1, "is_open_source": false, "license": "Proprietary", "description": "性价比高的通用模型", "created_at": "2024-01-01"},
|
||||
{"id": "claude3opus", "name": "Claude 3 Opus", "organization": "Anthropic", "parameters": 400, "architecture": "Transformer", "context_length": 200000, "input_price": 0.015, "output_price": 0.075, "mmlu": 86.8, "humaneval": 84.9, "is_open_source": false, "license": "Proprietary", "description": "Anthropic最强模型,200K上下文", "created_at": "2024-01-01"},
|
||||
{"id": "claude3sonnet", "name": "Claude 3 Sonnet", "organization": "Anthropic", "parameters": 175, "architecture": "Transformer", "context_length": 200000, "input_price": 0.003, "output_price": 0.015, "mmlu": 79.0, "humaneval": 73.0, "is_open_source": false, "license": "Proprietary", "description": "平衡性能与成本", "created_at": "2024-01-01"},
|
||||
{"id": "llama270b", "name": "Llama 2 70B", "organization": "Meta", "parameters": 70, "architecture": "Transformer", "context_length": 4096, "input_price": 0, "output_price": 0, "mmlu": 69.8, "humaneval": 29.9, "is_open_source": true, "license": "Llama 2 Community", "description": "Meta开源大模型,70B参数", "created_at": "2024-01-01"},
|
||||
{"id": "llama3", "name": "Llama 3 70B", "organization": "Meta", "parameters": 70, "architecture": "Transformer", "context_length": 8192, "input_price": 0, "output_price": 0, "mmlu": 82.0, "humaneval": 81.7, "is_open_source": true, "license": "Llama 3 Community", "description": "Meta最新开源模型,性能接近GPT-4", "created_at": "2024-01-01"},
|
||||
{"id": "mistral7b", "name": "Mistral 7B", "organization": "Mistral AI", "parameters": 7, "architecture": "Transformer", "context_length": 32768, "input_price": 0, "output_price": 0, "mmlu": 62.5, "humaneval": 26.8, "is_open_source": true, "license": "Apache 2.0", "description": "小巧高效的开源模型", "created_at": "2024-01-01"},
|
||||
{"id": "mixtral8x7b", "name": "Mixtral 8x7B", "organization": "Mistral AI", "parameters": 47, "architecture": "MoE", "context_length": 32768, "input_price": 0, "output_price": 0, "mmlu": 70.6, "humaneval": 40.2, "is_open_source": true, "license": "Apache 2.0", "description": "MoE架构,高效推理", "created_at": "2024-01-01"},
|
||||
{"id": "qwen72b", "name": "Qwen 72B", "organization": "Alibaba", "parameters": 72, "architecture": "Transformer", "context_length": 32768, "input_price": 0, "output_price": 0, "mmlu": 83.1, "humaneval": 65.4, "is_open_source": true, "license": "Apache 2.0", "description": "阿里开源大模型,中文能力强", "created_at": "2024-01-01"},
|
||||
{"id": "deepseekv3", "name": "DeepSeek V3", "organization": "DeepSeek", "parameters": 685, "architecture": "MoE", "context_length": 128000, "input_price": 0.00014, "output_price": 0.00028, "mmlu": 88.5, "humaneval": 86.2, "is_open_source": true, "license": "MIT", "description": "DeepSeek最新模型,性价比极高", "created_at": "2024-01-01"},
|
||||
{"id": "glm4", "name": "GLM-4", "organization": "Zhipu AI", "parameters": 130, "architecture": "Transformer", "context_length": 128000, "input_price": 0.014, "output_price": 0.014, "mmlu": 81.0, "humaneval": 70.0, "is_open_source": false, "license": "Proprietary", "description": "智谱AI大模型,中文能力强", "created_at": "2024-01-01"}
|
||||
{
|
||||
"id": "gpt4",
|
||||
"name": "GPT-4",
|
||||
"organization": "OpenAI",
|
||||
"parameters": 1760,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 8192,
|
||||
"input_price": 0.03,
|
||||
"output_price": 0.06,
|
||||
"mmlu": 86.4,
|
||||
"humaneval": 67.0,
|
||||
"is_open_source": false,
|
||||
"license": "Proprietary",
|
||||
"description": "OpenAI最强大的多模态大模型",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "gpt4turbo",
|
||||
"name": "GPT-4 Turbo",
|
||||
"organization": "OpenAI",
|
||||
"parameters": 1760,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 128000,
|
||||
"input_price": 0.01,
|
||||
"output_price": 0.03,
|
||||
"mmlu": 86.4,
|
||||
"humaneval": 67.0,
|
||||
"is_open_source": false,
|
||||
"license": "Proprietary",
|
||||
"description": "GPT-4增强版,128K上下文",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "gpt35",
|
||||
"name": "GPT-3.5 Turbo",
|
||||
"organization": "OpenAI",
|
||||
"parameters": 175,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 16385,
|
||||
"input_price": 0.0005,
|
||||
"output_price": 0.0015,
|
||||
"mmlu": 70.0,
|
||||
"humaneval": 48.1,
|
||||
"is_open_source": false,
|
||||
"license": "Proprietary",
|
||||
"description": "性价比高的通用模型",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "claude3opus",
|
||||
"name": "Claude 3 Opus",
|
||||
"organization": "Anthropic",
|
||||
"parameters": 400,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 200000,
|
||||
"input_price": 0.015,
|
||||
"output_price": 0.075,
|
||||
"mmlu": 86.8,
|
||||
"humaneval": 84.9,
|
||||
"is_open_source": false,
|
||||
"license": "Proprietary",
|
||||
"description": "Anthropic最强模型,200K上下文",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "claude3sonnet",
|
||||
"name": "Claude 3 Sonnet",
|
||||
"organization": "Anthropic",
|
||||
"parameters": 175,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 200000,
|
||||
"input_price": 0.003,
|
||||
"output_price": 0.015,
|
||||
"mmlu": 79.0,
|
||||
"humaneval": 73.0,
|
||||
"is_open_source": false,
|
||||
"license": "Proprietary",
|
||||
"description": "平衡性能与成本",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "llama270b",
|
||||
"name": "Llama 2 70B",
|
||||
"organization": "Meta",
|
||||
"parameters": 70,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 4096,
|
||||
"input_price": 0,
|
||||
"output_price": 0,
|
||||
"mmlu": 69.8,
|
||||
"humaneval": 29.9,
|
||||
"is_open_source": true,
|
||||
"license": "Llama 2 Community",
|
||||
"description": "Meta开源大模型,70B参数",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "llama3",
|
||||
"name": "Llama 3 70B",
|
||||
"organization": "Meta",
|
||||
"parameters": 70,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 8192,
|
||||
"input_price": 0,
|
||||
"output_price": 0,
|
||||
"mmlu": 82.0,
|
||||
"humaneval": 81.7,
|
||||
"is_open_source": true,
|
||||
"license": "Llama 3 Community",
|
||||
"description": "Meta最新开源模型,性能接近GPT-4",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "mistral7b",
|
||||
"name": "Mistral 7B",
|
||||
"organization": "Mistral AI",
|
||||
"parameters": 7,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 32768,
|
||||
"input_price": 0,
|
||||
"output_price": 0,
|
||||
"mmlu": 62.5,
|
||||
"humaneval": 26.8,
|
||||
"is_open_source": true,
|
||||
"license": "Apache 2.0",
|
||||
"description": "小巧高效的开源模型",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "mixtral8x7b",
|
||||
"name": "Mixtral 8x7B",
|
||||
"organization": "Mistral AI",
|
||||
"parameters": 47,
|
||||
"architecture": "MoE",
|
||||
"context_length": 32768,
|
||||
"input_price": 0,
|
||||
"output_price": 0,
|
||||
"mmlu": 70.6,
|
||||
"humaneval": 40.2,
|
||||
"is_open_source": true,
|
||||
"license": "Apache 2.0",
|
||||
"description": "MoE架构,高效推理",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "qwen72b",
|
||||
"name": "Qwen 72B",
|
||||
"organization": "Alibaba",
|
||||
"parameters": 72,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 32768,
|
||||
"input_price": 0,
|
||||
"output_price": 0,
|
||||
"mmlu": 83.1,
|
||||
"humaneval": 65.4,
|
||||
"is_open_source": true,
|
||||
"license": "Apache 2.0",
|
||||
"description": "阿里开源大模型,中文能力强",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "deepseekv3",
|
||||
"name": "DeepSeek V3",
|
||||
"organization": "DeepSeek",
|
||||
"parameters": 685,
|
||||
"architecture": "MoE",
|
||||
"context_length": 128000,
|
||||
"input_price": 0.00014,
|
||||
"output_price": 0.00028,
|
||||
"mmlu": 88.5,
|
||||
"humaneval": 86.2,
|
||||
"is_open_source": true,
|
||||
"license": "MIT",
|
||||
"description": "DeepSeek最新模型,性价比极高",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "glm4",
|
||||
"name": "GLM-4",
|
||||
"organization": "Zhipu AI",
|
||||
"parameters": 130,
|
||||
"architecture": "Transformer",
|
||||
"context_length": 128000,
|
||||
"input_price": 0.014,
|
||||
"output_price": 0.014,
|
||||
"mmlu": 81.0,
|
||||
"humaneval": 70.0,
|
||||
"is_open_source": false,
|
||||
"license": "Proprietary",
|
||||
"description": "智谱AI大模型,中文能力强",
|
||||
"created_at": "2024-01-01"
|
||||
},
|
||||
{
|
||||
"id": "15246152c91e",
|
||||
"created_at": "2026-04-11 01:54:26",
|
||||
"visible": true,
|
||||
"raw_text": "test"
|
||||
}
|
||||
]
|
||||
14
static/favicon.svg
Normal file
14
static/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<!-- 背景 -->
|
||||
<rect x="2" y="2" width="28" height="28" rx="6" fill="#4f46e5"/>
|
||||
<!-- 数据网格 -->
|
||||
<rect x="6" y="6" width="8" height="8" rx="2" fill="white"/>
|
||||
<rect x="18" y="6" width="8" height="8" rx="2" fill="white"/>
|
||||
<rect x="6" y="18" width="8" height="8" rx="2" fill="white"/>
|
||||
<rect x="18" y="18" width="8" height="8" rx="2" fill="white"/>
|
||||
<!-- 连接线 -->
|
||||
<line x1="14" y1="10" x2="18" y2="10" stroke="#4f46e5" stroke-width="2"/>
|
||||
<line x1="10" y1="14" x2="10" y2="18" stroke="#4f46e5" stroke-width="2"/>
|
||||
<line x1="22" y1="14" x2="22" y2="18" stroke="#4f46e5" stroke-width="2"/>
|
||||
<line x1="14" y1="22" x2="18" y2="22" stroke="#4f46e5" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 757 B |
@@ -3,7 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>后台管理 - ParamHub</title>
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
<style>
|
||||
@@ -65,7 +67,7 @@
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">ID</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">名称</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">描述</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">排序</th>
|
||||
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">显示</th>
|
||||
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -78,7 +80,10 @@
|
||||
<section id="section-dynamic" class="hidden">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800" id="dynamic-title">数据管理</h1>
|
||||
<button onclick="openAddModal('dynamic')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>添加数据</button>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="openSmartAddModal('dynamic')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
|
||||
<button onclick="openAddModal('dynamic')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>手动添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
||||
<table class="w-full"><tbody id="admin-dynamic-table"><tr><td class="text-center text-gray-400 py-8">加载中...</td></tr></tbody></table>
|
||||
@@ -89,7 +94,10 @@
|
||||
<section id="section-models" class="hidden">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">模型管理</h1>
|
||||
<button onclick="openAddModal('model')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>添加模型</button>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="openSmartAddModal('model')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
|
||||
<button onclick="openAddModal('model')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>手动添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
||||
<table class="w-full">
|
||||
@@ -100,10 +108,11 @@
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">参数量</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">上下文</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">类型</th>
|
||||
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">显示</th>
|
||||
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="admin-models-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
<tbody id="admin-models-table"><tr><td colspan="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
@@ -112,7 +121,10 @@
|
||||
<section id="section-gpus" class="hidden">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">GPU管理</h1>
|
||||
<button onclick="openAddModal('gpu')" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"><i class="ri-add-line mr-2"></i>添加GPU</button>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="openSmartAddModal('gpu')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
|
||||
<button onclick="openAddModal('gpu')" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"><i class="ri-add-line mr-2"></i>手动添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
||||
<table class="w-full">
|
||||
@@ -123,10 +135,11 @@
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">显存</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">架构</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">价格</th>
|
||||
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">显示</th>
|
||||
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="admin-gpus-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
<tbody id="admin-gpus-table"><tr><td colspan="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
@@ -135,7 +148,10 @@
|
||||
<section id="section-cpus" class="hidden">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">CPU管理</h1>
|
||||
<button onclick="openAddModal('cpu')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-add-line mr-2"></i>添加CPU</button>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="openSmartAddModal('cpu')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
|
||||
<button onclick="openAddModal('cpu')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-add-line mr-2"></i>手动添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
||||
<table class="w-full">
|
||||
@@ -146,10 +162,11 @@
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">核心/线程</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">主频</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-600">价格</th>
|
||||
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">显示</th>
|
||||
<th class="px-4 py-3 text-center text-sm font-medium text-gray-600">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="admin-cpus-table"><tr><td colspan="6" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
<tbody id="admin-cpus-table"><tr><td colspan="7" class="text-center text-gray-400 py-8">加载中...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
@@ -200,6 +217,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 智能添加弹窗 -->
|
||||
<div id="smartAddModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
|
||||
<div class="bg-white rounded-xl max-w-3xl w-full mx-4 max-h-[90vh] overflow-auto">
|
||||
<div class="p-6 border-b flex justify-between items-center sticky top-0 bg-white z-10">
|
||||
<h2 class="text-xl font-bold text-gray-800"><i class="ri-magic-line mr-2 text-orange-600"></i>智能添加</h2>
|
||||
<button onclick="closeSmartAddModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p class="text-sm text-gray-500 mb-4">粘贴产品信息文本,AI将自动解析并提取结构化数据。支持各种格式的产品介绍、规格参数、价格信息等。</p>
|
||||
<textarea id="smartAddText" rows="8" class="w-full p-4 border border-gray-200 rounded-lg focus:outline-none focus:border-orange-400 text-gray-700" placeholder="粘贴产品信息文本...
|
||||
|
||||
示例:
|
||||
GPT-4是OpenAI发布的大语言模型,参数量约1.8万亿,支持128K上下文,MMLU分数86.4,输入价格$0.03/1K tokens,输出价格$0.06/1K tokens,商业许可。"></textarea>
|
||||
<div id="smartAddPreview" class="mt-4 hidden">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-2">解析结果预览:</h3>
|
||||
<div class="bg-gray-50 rounded-lg p-4 text-sm text-gray-600" id="smartAddResult"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 border-t flex justify-end gap-4 sticky bottom-0 bg-white">
|
||||
<button onclick="closeSmartAddModal()" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">取消</button>
|
||||
<button onclick="smartAddSubmit()" id="smartAddBtn" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-1"></i>智能解析并添加</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 原始数据查看弹窗 -->
|
||||
<div id="rawDataModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
|
||||
<div class="bg-white rounded-xl max-w-3xl w-full mx-4 max-h-[90vh] overflow-auto">
|
||||
<div class="p-6 border-b flex justify-between items-center sticky top-0 bg-white z-10">
|
||||
<h2 class="text-xl font-bold text-gray-800"><i class="ri-file-text-line mr-2"></i>原始数据</h2>
|
||||
<button onclick="closeRawDataModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
|
||||
</div>
|
||||
<div id="rawDataContent" class="p-6">
|
||||
<div class="bg-gray-50 rounded-lg p-4 text-sm text-gray-700 whitespace-pre-wrap" id="rawDataText"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentType = '';
|
||||
let currentId = '';
|
||||
@@ -217,6 +272,43 @@
|
||||
red: 'bg-red-100 text-red-600'
|
||||
};
|
||||
|
||||
// 价格格式化函数
|
||||
function formatPrice(item) {
|
||||
// 支持多种价格格式
|
||||
const currency = item.currency || 'USD';
|
||||
const symbols = { USD: '$', CNY: '¥', EUR: '€', JPY: '¥', GBP: '£' };
|
||||
const symbol = symbols[currency] || currency;
|
||||
|
||||
// 价格区间支持
|
||||
const minPrice = item.min_price || item.price_usd_min || item.price_min;
|
||||
const maxPrice = item.max_price || item.price_usd_max || item.price_max;
|
||||
const singlePrice = item.price_usd || item.price_cny || item.price;
|
||||
|
||||
// 单位处理
|
||||
const unit = item.price_unit || '';
|
||||
|
||||
if (minPrice && maxPrice) {
|
||||
// 价格区间
|
||||
return `${symbol}${formatNumber(minPrice)}-${formatNumber(maxPrice)}${unit ? ' ' + unit : ''}`;
|
||||
} else if (singlePrice) {
|
||||
return `${symbol}${formatNumber(singlePrice)}${unit ? ' ' + unit : ''}`;
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
|
||||
// 数字格式化
|
||||
function formatNumber(num) {
|
||||
if (!num) return '0';
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + '万';
|
||||
}
|
||||
if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toLocaleString();
|
||||
}
|
||||
|
||||
// 初始化
|
||||
async function init() {
|
||||
await loadCategories();
|
||||
@@ -242,7 +334,7 @@
|
||||
const builtinMap = {
|
||||
'ai-models': {id: 'models', name: '模型管理', icon: 'ri-robot-line'},
|
||||
'gpus': {id: 'gpus', name: 'GPU管理', icon: 'ri-cpu-line'},
|
||||
'cpus': {id: 'cpus', name: 'CPU管理', icon: 'ri-memory-line'}
|
||||
'cpus': {id: 'cpus', name: 'CPU管理', icon: 'ri-cpu-line'}
|
||||
};
|
||||
|
||||
let html = fixedItems.map(item => `
|
||||
@@ -377,7 +469,7 @@
|
||||
</div>
|
||||
<div class="bg-white rounded-xl p-5 shadow-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center"><i class="ri-memory-line text-xl text-purple-600"></i></div>
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center"><i class="ri-cpu-line text-xl text-purple-600"></i></div>
|
||||
<div><div class="text-2xl font-bold text-gray-800">${data.cpus_count}</div><div class="text-xs text-gray-500">CPU数量</div></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -417,12 +509,16 @@
|
||||
}
|
||||
|
||||
document.getElementById('admin-categories-table').innerHTML = categories.map(c => `
|
||||
<tr class="border-b hover:bg-gray-50">
|
||||
<tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''}">
|
||||
<td class="px-4 py-3"><div class="w-10 h-10 rounded-lg ${colorMap[c.color] || 'bg-gray-100 text-gray-600'} flex items-center justify-center"><i class="${c.icon} text-xl"></i></div></td>
|
||||
<td class="px-4 py-3 text-gray-500 text-sm font-mono">${c.id}</td>
|
||||
<td class="px-4 py-3 font-medium text-gray-800">${c.name}</td>
|
||||
<td class="px-4 py-3 text-gray-600 text-sm">${c.description || '-'}</td>
|
||||
<td class="px-4 py-3">${c.order || 0}</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="toggleVisible('category', '${c.id}')" class="${c.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80">
|
||||
<i class="${c.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="editItem('category', '${c.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
|
||||
<button onclick="deleteItem('category', '${c.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
|
||||
@@ -433,16 +529,22 @@
|
||||
|
||||
// 加载模型列表
|
||||
async function loadAdminModels() {
|
||||
const res = await fetch('/api/models');
|
||||
const res = await fetch('/api/models?all=1');
|
||||
const models = await res.json();
|
||||
if (models.length === 0) { document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
|
||||
if (models.length === 0) { document.getElementById('admin-models-table').innerHTML = '<tr><td colspan="7" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
|
||||
document.getElementById('admin-models-table').innerHTML = models.map(m => `
|
||||
<tr class="border-b hover:bg-gray-50">
|
||||
<tr class="border-b hover:bg-gray-50 ${m.visible === false ? 'bg-gray-100 opacity-60' : ''}">
|
||||
<td class="px-4 py-3 font-medium text-gray-800">${m.name}</td>
|
||||
<td class="px-4 py-3 text-gray-600">${m.organization}</td>
|
||||
<td class="px-4 py-3">${m.parameters}B</td>
|
||||
<td class="px-4 py-3 text-gray-600">${m.context_length || '-'}</td>
|
||||
<td class="px-4 py-3">${m.is_open_source ? '<span class="text-green-600">开源</span>' : '<span class="text-gray-600">商业</span>'}</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="toggleVisible('model', '${m.id}')" class="${m.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80">
|
||||
<i class="${m.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
|
||||
</button>
|
||||
${m.raw_text ? `<button onclick="showRawData('${m.id}', 'model')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="editItem('model', '${m.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
|
||||
<button onclick="deleteItem('model', '${m.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
|
||||
@@ -453,16 +555,22 @@
|
||||
|
||||
// 加载GPU列表
|
||||
async function loadAdminGpus() {
|
||||
const res = await fetch('/api/gpus');
|
||||
const res = await fetch('/api/gpus?all=1');
|
||||
const gpus = await res.json();
|
||||
if (gpus.length === 0) { document.getElementById('admin-gpus-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
|
||||
if (gpus.length === 0) { document.getElementById('admin-gpus-table').innerHTML = '<tr><td colspan="7" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
|
||||
document.getElementById('admin-gpus-table').innerHTML = gpus.map(g => `
|
||||
<tr class="border-b hover:bg-gray-50">
|
||||
<tr class="border-b hover:bg-gray-50 ${g.visible === false ? 'bg-gray-100 opacity-60' : ''}">
|
||||
<td class="px-4 py-3 font-medium text-gray-800">${g.name}</td>
|
||||
<td class="px-4 py-3 text-gray-600">${g.manufacturer}</td>
|
||||
<td class="px-4 py-3">${g.memory_gb}GB</td>
|
||||
<td class="px-4 py-3 text-gray-600">${g.architecture || '-'}</td>
|
||||
<td class="px-4 py-3 text-gray-600">$${g.price_usd || '-'}</td>
|
||||
<td class="px-4 py-3 text-gray-600">${formatPrice(g)}</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="toggleVisible('gpu', '${g.id}')" class="${g.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80">
|
||||
<i class="${g.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
|
||||
</button>
|
||||
${g.raw_text ? `<button onclick="showRawData('${g.id}', 'gpu')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="editItem('gpu', '${g.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
|
||||
<button onclick="deleteItem('gpu', '${g.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
|
||||
@@ -473,16 +581,22 @@
|
||||
|
||||
// 加载CPU列表
|
||||
async function loadAdminCpus() {
|
||||
const res = await fetch('/api/cpus');
|
||||
const res = await fetch('/api/cpus?all=1');
|
||||
const cpus = await res.json();
|
||||
if (cpus.length === 0) { document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="6" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
|
||||
if (cpus.length === 0) { document.getElementById('admin-cpus-table').innerHTML = '<tr><td colspan="7" class="text-center text-gray-400 py-8">暂无数据</td></tr>'; return; }
|
||||
document.getElementById('admin-cpus-table').innerHTML = cpus.map(c => `
|
||||
<tr class="border-b hover:bg-gray-50">
|
||||
<tr class="border-b hover:bg-gray-50 ${c.visible === false ? 'bg-gray-100 opacity-60' : ''}">
|
||||
<td class="px-4 py-3 font-medium text-gray-800">${c.name}</td>
|
||||
<td class="px-4 py-3 text-gray-600">${c.manufacturer}</td>
|
||||
<td class="px-4 py-3">${c.cores}/${c.threads}</td>
|
||||
<td class="px-4 py-3 text-gray-600">${c.base_clock_ghz || '-'}-${c.boost_clock_ghz || '-'}GHz</td>
|
||||
<td class="px-4 py-3 text-gray-600">$${c.price_usd || '-'}</td>
|
||||
<td class="px-4 py-3 text-gray-600">${formatPrice(c)}</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="toggleVisible('cpu', '${c.id}')" class="${c.visible === false ? 'text-gray-400' : 'text-green-600'} hover:opacity-80">
|
||||
<i class="${c.visible === false ? 'ri-eye-off-line' : 'ri-eye-line'}"></i>
|
||||
</button>
|
||||
${c.raw_text ? `<button onclick="showRawData('${c.id}', 'cpu')" class="text-gray-400 hover:text-gray-600 ml-1" title="查看原始数据"><i class="ri-file-text-line"></i></button>` : ''}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="editItem('cpu', '${c.id}')" class="text-blue-600 hover:text-blue-800 mr-2"><i class="ri-edit-line"></i></button>
|
||||
<button onclick="deleteItem('cpu', '${c.id}')" class="text-red-600 hover:text-red-800"><i class="ri-delete-bin-line"></i></button>
|
||||
@@ -583,9 +697,9 @@
|
||||
const data = {};
|
||||
formData.forEach((value, key) => {
|
||||
if (value) {
|
||||
const numFields = ['parameters', 'context_length', 'mmlu', 'humaneval', 'input_price', 'output_price', 'memory_gb', 'cuda_cores', 'tensor_cores', 'memory_bandwidth_gbs', 'fp32_tflops', 'fp16_tflops', 'int8_perf_tops', 'price_usd', 'release_year', 'cores', 'threads', 'base_clock_ghz', 'boost_clock_ghz', 'l3_cache_mb', 'tdp_watts', 'order'];
|
||||
const numFields = ['parameters', 'context_length', 'mmlu', 'humaneval', 'input_price', 'output_price', 'memory_gb', 'cuda_cores', 'tensor_cores', 'memory_bandwidth_gbs', 'fp32_tflops', 'fp16_tflops', 'int8_perf_tops', 'price_usd', 'min_price', 'max_price', 'release_year', 'cores', 'threads', 'base_clock_ghz', 'boost_clock_ghz', 'l3_cache_mb', 'tdp_watts', 'order', 'price'];
|
||||
if (numFields.includes(key)) data[key] = parseFloat(value);
|
||||
else if (key === 'is_open_source') data[key] = value === 'true';
|
||||
else if (key === 'is_open_source' || key === 'visible') data[key] = value === 'true';
|
||||
else data[key] = value;
|
||||
}
|
||||
});
|
||||
@@ -635,6 +749,16 @@
|
||||
<option value="red" ${data.color === 'red' ? 'selected' : ''}>红色</option>
|
||||
</select></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">排序</label><input type="number" name="order" value="${data.order || 0}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">是否显示</label><select name="visible" class="w-full px-3 py-2 border rounded-lg">
|
||||
<option value="true" ${data.visible !== false ? 'selected' : ''}>显示</option>
|
||||
<option value="false" ${data.visible === false ? 'selected' : ''}>隐藏</option>
|
||||
</select></div>
|
||||
</div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="2" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
|
||||
</form>`;
|
||||
}
|
||||
</select></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">排序</label><input type="number" name="order" value="${data.order || 0}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
</div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="2" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
|
||||
</form>`;
|
||||
@@ -697,7 +821,15 @@
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">Tensor核心</label><input type="number" name="tensor_cores" value="${data.tensor_cores || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">显存带宽(GB/s)</label><input type="number" name="memory_bandwidth_gbs" value="${data.memory_bandwidth_gbs || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">FP16性能(TF)</label><input type="number" name="fp16_tflops" value="${data.fp16_tflops || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">价格($)</label><input type="number" name="price_usd" value="${data.price_usd || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">币种</label><select name="currency" class="w-full px-3 py-2 border rounded-lg">
|
||||
<option value="USD" ${data.currency === 'USD' || !data.currency ? 'selected' : ''}>美元 (USD)</option>
|
||||
<option value="CNY" ${data.currency === 'CNY' ? 'selected' : ''}>人民币 (CNY)</option>
|
||||
<option value="EUR" ${data.currency === 'EUR' ? 'selected' : ''}>欧元 (EUR)</option>
|
||||
</select></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">价格</label><input type="number" name="price_usd" value="${data.price_usd || ''}" step="0.01" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">最低价</label><input type="number" name="min_price" value="${data.min_price || ''}" step="0.01" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">最高价</label><input type="number" name="max_price" value="${data.max_price || ''}" step="0.01" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">价格单位</label><input type="text" name="price_unit" value="${data.price_unit || ''}" placeholder="如: 万" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">发布年份</label><input type="number" name="release_year" value="${data.release_year || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
</div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
|
||||
@@ -716,13 +848,145 @@
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">加速频率(GHz)</label><input type="number" name="boost_clock_ghz" value="${data.boost_clock_ghz || ''}" step="0.1" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">L3缓存(MB)</label><input type="number" name="l3_cache_mb" value="${data.l3_cache_mb || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">TDP功耗(W)</label><input type="number" name="tdp_watts" value="${data.tdp_watts || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">价格($)</label><input type="number" name="price_usd" value="${data.price_usd || ''}" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">币种</label><select name="currency" class="w-full px-3 py-2 border rounded-lg">
|
||||
<option value="USD" ${data.currency === 'USD' || !data.currency ? 'selected' : ''}>美元 (USD)</option>
|
||||
<option value="CNY" ${data.currency === 'CNY' ? 'selected' : ''}>人民币 (CNY)</option>
|
||||
<option value="EUR" ${data.currency === 'EUR' ? 'selected' : ''}>欧元 (EUR)</option>
|
||||
</select></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">价格</label><input type="number" name="price_usd" value="${data.price_usd || ''}" step="0.01" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">最低价</label><input type="number" name="min_price" value="${data.min_price || ''}" step="0.01" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">最高价</label><input type="number" name="max_price" value="${data.max_price || ''}" step="0.01" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">价格单位</label><input type="text" name="price_unit" value="${data.price_unit || ''}" placeholder="如: 万" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
</div>
|
||||
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="3" class="w-full px-3 py-2 border rounded-lg">${data.description || ''}</textarea></div>
|
||||
</form>`;
|
||||
}
|
||||
|
||||
document.getElementById('editModal').addEventListener('click', function(e) { if (e.target === this) closeModal(); });
|
||||
document.getElementById('smartAddModal').addEventListener('click', function(e) { if (e.target === this) closeSmartAddModal(); });
|
||||
document.getElementById('rawDataModal').addEventListener('click', function(e) { if (e.target === this) closeRawDataModal(); });
|
||||
|
||||
// ============ 智能添加功能 ============
|
||||
|
||||
let smartAddType = '';
|
||||
|
||||
function openSmartAddModal(type) {
|
||||
smartAddType = type;
|
||||
document.getElementById('smartAddText').value = '';
|
||||
document.getElementById('smartAddPreview').classList.add('hidden');
|
||||
document.getElementById('smartAddModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeSmartAddModal() {
|
||||
document.getElementById('smartAddModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
async function smartAddSubmit() {
|
||||
const text = document.getElementById('smartAddText').value.trim();
|
||||
if (!text) {
|
||||
alert('请粘贴产品信息文本');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('smartAddBtn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i>解析中...';
|
||||
|
||||
try {
|
||||
let endpoint;
|
||||
if (smartAddType === 'model') endpoint = '/api/models/smart-add';
|
||||
else if (smartAddType === 'gpu') endpoint = '/api/gpus/smart-add';
|
||||
else if (smartAddType === 'cpu') endpoint = '/api/cpus/smart-add';
|
||||
else if (smartAddType === 'dynamic') endpoint = `/api/items/${dynamicCategoryId}/smart-add`;
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
alert('解析失败: ' + data.error);
|
||||
} else {
|
||||
// 显示解析结果
|
||||
document.getElementById('smartAddPreview').classList.remove('hidden');
|
||||
document.getElementById('smartAddResult').innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
||||
|
||||
// 关闭弹窗并刷新列表
|
||||
closeSmartAddModal();
|
||||
|
||||
if (smartAddType === 'dynamic') showDynamicCategory(dynamicCategoryId);
|
||||
else {
|
||||
const loaders = {model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus};
|
||||
loaders[smartAddType]();
|
||||
}
|
||||
loadOverview();
|
||||
|
||||
alert('智能添加成功!数据已自动解析并保存。');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('请求失败: ' + e.message);
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="ri-magic-line mr-1"></i>智能解析并添加';
|
||||
}
|
||||
|
||||
// ============ 显示切换功能 ============
|
||||
|
||||
async function toggleVisible(type, id) {
|
||||
let endpoint;
|
||||
if (type === 'category') endpoint = `/api/categories/${id}/visible`;
|
||||
else if (type === 'model') endpoint = `/api/models/${id}/visible`;
|
||||
else if (type === 'gpu') endpoint = `/api/gpus/${id}/visible`;
|
||||
else if (type === 'cpu') endpoint = `/api/cpus/${id}/visible`;
|
||||
else if (type === 'dynamic') endpoint = `/api/items/${dynamicCategoryId}/${id}/visible`;
|
||||
|
||||
try {
|
||||
await fetch(endpoint, { method: 'POST' });
|
||||
|
||||
// 刷新列表
|
||||
if (type === 'dynamic') showDynamicCategory(dynamicCategoryId);
|
||||
else {
|
||||
const loaders = {category: loadAdminCategories, model: loadAdminModels, gpu: loadAdminGpus, cpu: loadAdminCpus};
|
||||
loaders[type]();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('切换失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 原始数据查看 ============
|
||||
|
||||
async function showRawData(id, type) {
|
||||
let endpoint;
|
||||
if (type === 'model') endpoint = `/api/models/${id}`;
|
||||
else if (type === 'gpu') endpoint = `/api/gpus/${id}`;
|
||||
else if (type === 'cpu') endpoint = `/api/cpus/${id}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(endpoint);
|
||||
const data = await res.json();
|
||||
|
||||
let content = '';
|
||||
if (data.raw_text) {
|
||||
content = `【原始文本】\n${data.raw_text}\n\n【解析数据】\n${JSON.stringify(data, null, 2)}`;
|
||||
} else {
|
||||
content = JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
document.getElementById('rawDataText').textContent = content;
|
||||
document.getElementById('rawDataModal').classList.remove('hidden');
|
||||
} catch (e) {
|
||||
alert('获取数据失败');
|
||||
}
|
||||
}
|
||||
|
||||
function closeRawDataModal() {
|
||||
document.getElementById('rawDataModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ category.name }} - ParamHub</title>
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>对比工具 - ParamHub</title>
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
</head>
|
||||
@@ -40,7 +41,7 @@
|
||||
<i class="ri-cpu-line mr-2"></i>GPU对比
|
||||
</button>
|
||||
<button onclick="setCompareType('cpu')" id="btnCpu" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">
|
||||
<i class="ri-memory-line mr-2"></i>CPU对比
|
||||
<i class="ri-cpu-line mr-2"></i>CPU对比
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CPU数据库 - ParamHub</title>
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
</head>
|
||||
@@ -24,7 +25,7 @@
|
||||
<main class="max-w-7xl mx-auto px-4 py-8">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-memory-line text-purple-600"></i>
|
||||
<i class="ri-cpu-line text-purple-600"></i>
|
||||
CPU数据库
|
||||
</h1>
|
||||
<p class="text-gray-500 mt-1">处理器规格参数一览</p>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GPU数据库 - ParamHub</title>
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
</head>
|
||||
@@ -58,7 +59,7 @@
|
||||
<!-- 热门模型 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="ri-flashlight-line text-indigo-600"></i>
|
||||
<i class="ri-cpu-line text-indigo-600"></i>
|
||||
热门模型
|
||||
</h2>
|
||||
<div id="latestModels" class="grid grid-cols-2 gap-4">
|
||||
@@ -146,7 +147,7 @@
|
||||
const statItems = [
|
||||
{key: 'models_count', label: 'AI模型', icon: 'ri-robot-line', color: 'blue'},
|
||||
{key: 'gpus_count', label: 'GPU显卡', icon: 'ri-cpu-line', color: 'green'},
|
||||
{key: 'cpus_count', label: 'CPU处理器', icon: 'ri-memory-line', color: 'purple'},
|
||||
{key: 'cpus_count', label: 'CPU处理器', icon: 'ri-cpu-line', color: 'purple'},
|
||||
{key: 'knowledge_count', label: '知识条目', icon: 'ri-book-open-line', color: 'teal'}
|
||||
];
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>知识库 - ParamHub</title>
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
</head>
|
||||
@@ -75,7 +76,7 @@
|
||||
<!-- 显存计算 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="ri-memory-line text-orange-600"></i>
|
||||
<i class="ri-cpu-line text-orange-600"></i>
|
||||
如何计算显存需求?
|
||||
</h2>
|
||||
<p class="text-gray-600 leading-relaxed">
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>模型数据库 - ParamHub</title>
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>实用工具 - ParamHub</title>
|
||||
<title>ParamHub - 参数百科</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||
</head>
|
||||
@@ -33,7 +34,7 @@
|
||||
<!-- 显存计算器 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="ri-memory-line text-green-600"></i>
|
||||
<i class="ri-cpu-line text-green-600"></i>
|
||||
显存计算器
|
||||
</h2>
|
||||
<p class="text-gray-500 mb-4">计算大模型所需的显存大小,并推荐合适的GPU</p>
|
||||
|
||||
Reference in New Issue
Block a user