feat: v1.4.0 - 五项优化

1. 模型选择: 点击搜索框弹出top5模型,支持搜索过滤
2. 选模型后自动调整参数: ctx_size设为模型默认上下文,n_gpu_layers设为all
   - models表新增 default_ctx 字段,各模型预设上下文值
3. 参数配置新增「已改参数」标签页
   - 放在最右边,绿色样式区别于其他标签
   - 显示所有被修改过(非默认值)的参数
4. 复制按钮移到命令文本框左上角悬空
5. 自然语言默认提示文本可在后台管理中编辑
   - 新增 /api/settings/public 接口
   - 后台系统设置新增 nl_default_text 配置项
This commit is contained in:
2026-07-20 00:05:18 +08:00
parent fb3d7290bf
commit 26d7ee0b66
7 changed files with 150 additions and 83 deletions
+14 -5
View File
@@ -621,6 +621,15 @@ def get_models_grouped():
return jsonify({'models': grouped, 'default_quant': default_quant})
# ----- Public Settings (only nl_default_text) -----
@app.route('/api/settings/public')
def get_public_settings():
db = get_db()
nl = db.execute("SELECT value FROM settings WHERE key = 'nl_default_text'").fetchone()
db.close()
return jsonify({'nl_default_text': nl['value'] if nl else ''})
# ----- Parse Natural Language -----
@app.route('/api/parse-nl', methods=['POST'])
def parse_nl():
@@ -871,11 +880,11 @@ def admin_add_model():
data = request.json
db = get_db()
db.execute(
'''INSERT INTO models (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, quant, description, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
'''INSERT INTO models (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, default_ctx, quant, description, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
(data['base_model'], data['name'], data['size_gb'], data['layers'], data['embd'],
data['kv_heads'], data['head_dim'], data['attention_heads'],
data.get('quant', ''), data.get('description', ''), data.get('sort_order', 0))
data.get('default_ctx', 0), data.get('quant', ''), data.get('description', ''), data.get('sort_order', 0))
)
db.commit()
db.close()
@@ -890,10 +899,10 @@ def admin_model_edit(mid):
data = request.json
db.execute(
'''UPDATE models SET base_model=?, name=?, size_gb=?, layers=?, embd=?, kv_heads=?,
head_dim=?, attention_heads=?, quant=?, description=?, sort_order=? WHERE id=?''',
head_dim=?, attention_heads=?, default_ctx=?, quant=?, description=?, sort_order=? WHERE id=?''',
(data['base_model'], data['name'], data['size_gb'], data['layers'], data['embd'],
data['kv_heads'], data['head_dim'], data['attention_heads'],
data.get('quant', ''), data.get('description', ''), data.get('sort_order', 0), mid)
data.get('default_ctx', 0), data.get('quant', ''), data.get('description', ''), data.get('sort_order', 0), mid)
)
db.commit()
db.close()
+58 -57
View File
@@ -88,6 +88,7 @@ def init_db():
kv_heads INTEGER NOT NULL,
head_dim INTEGER NOT NULL,
attention_heads INTEGER NOT NULL,
default_ctx INTEGER DEFAULT 0,
quant TEXT DEFAULT '',
description TEXT DEFAULT '',
sort_order INTEGER DEFAULT 0
@@ -313,66 +314,66 @@ def insert_default_data(conn):
(v2_id,) + p)
# ===== Default models =====
# (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, quant, description, sort_order)
# (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, default_ctx, quant, description, sort_order)
default_models = [
# Llama-3-8B-Instruct
("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (Q4_K_M)", 4.9, 32, 4096, 8, 128, 32, "Q4_K_M", "Meta Llama 3 8B Instruct, Q4_K_M 量化", 1),
("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (Q8_0)", 8.5, 32, 4096, 8, 128, 32, "Q8_0", "Meta Llama 3 8B Instruct, Q8_0 量化", 2),
("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (FP16)", 15.5, 32, 4096, 8, 128, 32, "FP16", "Meta Llama 3 8B Instruct, FP16", 3),
# Llama-3-70B-Instruct
("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (Q4_K_M)", 38.5, 80, 8192, 8, 128, 64, "Q4_K_M", "Meta Llama 3 70B Instruct, Q4_K_M 量化", 4),
("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (Q8_0)", 74.0, 80, 8192, 8, 128, 64, "Q8_0", "Meta Llama 3 70B Instruct, Q8_0 量化", 5),
("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (FP16)", 138.0, 80, 8192, 8, 128, 64, "FP16", "Meta Llama 3 70B Instruct, FP16", 6),
# Llama-3.1-8B-Instruct
("Llama-3.1-8B-Instruct", "Llama-3.1-8B-Instruct (Q4_K_M)", 4.9, 32, 4096, 8, 128, 32, "Q4_K_M", "Meta Llama 3.1 8B Instruct, Q4_K_M 量化", 7),
("Llama-3.1-8B-Instruct", "Llama-3.1-8B-Instruct (Q8_0)", 8.5, 32, 4096, 8, 128, 32, "Q8_0", "Meta Llama 3.1 8B Instruct, Q8_0 量化", 8),
# Llama-3.1-70B-Instruct
("Llama-3.1-70B-Instruct", "Llama-3.1-70B-Instruct (Q4_K_M)", 38.5, 80, 8192, 8, 128, 64, "Q4_K_M", "Meta Llama 3.1 70B Instruct, Q4_K_M 量化", 9),
("Llama-3.1-70B-Instruct", "Llama-3.1-70B-Instruct (Q8_0)", 74.0, 80, 8192, 8, 128, 64, "Q8_0", "Meta Llama 3.1 70B Instruct, Q8_0 量化", 10),
# Llama-3.1-405B-Instruct
("Llama-3.1-405B-Instruct", "Llama-3.1-405B-Instruct (Q4_K_M)", 226.0, 126, 16384, 8, 128, 128, "Q4_K_M", "Meta Llama 3.1 405B Instruct, Q4_K_M 量化", 11),
# Qwen2.5-7B-Instruct
("Qwen2.5-7B-Instruct", "Qwen2.5-7B-Instruct (Q4_K_M)", 4.7, 28, 3584, 4, 128, 28, "Q4_K_M", "Qwen2.5 7B Instruct, Q4_K_M 量化", 12),
("Qwen2.5-7B-Instruct", "Qwen2.5-7B-Instruct (Q8_0)", 7.6, 28, 3584, 4, 128, 28, "Q8_0", "Qwen2.5 7B Instruct, Q8_0 量化", 13),
# Qwen2.5-14B-Instruct
("Qwen2.5-14B-Instruct", "Qwen2.5-14B-Instruct (Q4_K_M)", 8.7, 40, 5120, 8, 128, 40, "Q4_K_M", "Qwen2.5 14B Instruct, Q4_K_M 量化", 14),
# Qwen2.5-32B-Instruct
("Qwen2.5-32B-Instruct", "Qwen2.5-32B-Instruct (Q4_K_M)", 19.5, 64, 5120, 8, 128, 64, "Q4_K_M", "Qwen2.5 32B Instruct, Q4_K_M 量化", 15),
("Qwen2.5-32B-Instruct", "Qwen2.5-32B-Instruct (Q8_0)", 32.0, 64, 5120, 8, 128, 64, "Q8_0", "Qwen2.5 32B Instruct, Q8_0 量化", 16),
# Qwen2.5-72B-Instruct
("Qwen2.5-72B-Instruct", "Qwen2.5-72B-Instruct (Q4_K_M)", 42.0, 80, 8192, 8, 128, 64, "Q4_K_M", "Qwen2.5 72B Instruct, Q4_K_M 量化", 17),
("Qwen2.5-72B-Instruct", "Qwen2.5-72B-Instruct (Q8_0)", 75.0, 80, 8192, 8, 128, 64, "Q8_0", "Qwen2.5 72B Instruct, Q8_0 量化", 18),
# DeepSeek-V2-Chat
("DeepSeek-V2-Chat", "DeepSeek-V2-Chat (Q4_K_M)", 23.0, 60, 5120, 8, 128, 60, "Q4_K_M", "DeepSeek V2 Chat, Q4_K_M 量化", 19),
# DeepSeek-V2.5-Chat
("DeepSeek-V2.5-Chat", "DeepSeek-V2.5-Chat (Q4_K_M)", 23.0, 60, 5120, 8, 128, 60, "Q4_K_M", "DeepSeek V2.5 Chat, Q4_K_M 量化", 20),
# DeepSeek-R1-Distill-Qwen-32B
("DeepSeek-R1-Distill-Qwen-32B", "DeepSeek-R1-Distill-Qwen-32B (Q4_K_M)", 19.5, 64, 5120, 8, 128, 64, "Q4_K_M", "DeepSeek R1 Distill Qwen 32B, Q4_K_M 量化", 21),
("DeepSeek-R1-Distill-Qwen-32B", "DeepSeek-R1-Distill-Qwen-32B (Q8_0)", 32.0, 64, 5120, 8, 128, 64, "Q8_0", "DeepSeek R1 Distill Qwen 32B, Q8_0 量化", 22),
# DeepSeek-R1-Distill-Llama-70B
("DeepSeek-R1-Distill-Llama-70B", "DeepSeek-R1-Distill-Llama-70B (Q4_K_M)", 42.0, 80, 8192, 8, 128, 64, "Q4_K_M", "DeepSeek R1 Distill Llama 70B, Q4_K_M 量化", 23),
("DeepSeek-R1-Distill-Llama-70B", "DeepSeek-R1-Distill-Llama-70B (Q8_0)", 75.0, 80, 8192, 8, 128, 64, "Q8_0", "DeepSeek R1 Distill Llama 70B, Q8_0 量化", 24),
# Mistral-7B-Instruct-v0.3
("Mistral-7B-Instruct-v0.3", "Mistral-7B-Instruct-v0.3 (Q4_K_M)", 4.4, 32, 4096, 8, 128, 32, "Q4_K_M", "Mistral 7B Instruct v0.3, Q4_K_M 量化", 25),
("Mistral-7B-Instruct-v0.3", "Mistral-7B-Instruct-v0.3 (Q8_0)", 7.5, 32, 4096, 8, 128, 32, "Q8_0", "Mistral 7B Instruct v0.3, Q8_0 量化", 26),
# Mixtral-8x7B-Instruct
("Mixtral-8x7B-Instruct", "Mixtral-8x7B-Instruct (Q4_K_M)", 26.0, 32, 4096, 8, 128, 32, "Q4_K_M", "Mixtral 8x7B Instruct, Q4_K_M 量化", 27),
# Gemma-2-9B-It
("Gemma-2-9B-It", "Gemma-2-9B-It (Q4_K_M)", 5.4, 42, 3584, 4, 256, 14, "Q4_K_M", "Google Gemma 2 9B It, Q4_K_M 量化", 28),
# Gemma-2-27B-It
("Gemma-2-27B-It", "Gemma-2-27B-It (Q4_K_M)", 16.5, 46, 4608, 4, 128, 36, "Q4_K_M", "Google Gemma 2 27B It, Q4_K_M 量化", 29),
# Phi-3-Mini-4K-Instruct
("Phi-3-Mini-4K-Instruct", "Phi-3-Mini-4K-Instruct (Q4_K_M)", 2.5, 32, 3072, 32, 96, 32, "Q4_K_M", "Microsoft Phi-3 Mini 4K Instruct, Q4_K_M 量化", 30),
# Phi-3-Medium-14B-Instruct
("Phi-3-Medium-14B-Instruct", "Phi-3-Medium-14B-Instruct (Q4_K_M)", 8.4, 40, 5120, 10, 128, 40, "Q4_K_M", "Microsoft Phi-3 Medium 14B Instruct, Q4_K_M 量化", 31),
# GLM-4-9B-Chat
("GLM-4-9B-Chat", "GLM-4-9B-Chat (Q4_K_M)", 5.5, 40, 4096, 4, 128, 40, "Q4_K_M", "Zhipu GLM-4 9B Chat, Q4_K_M 量化", 32),
("GLM-4-9B-Chat", "GLM-4-9B-Chat (Q8_0)", 9.0, 40, 4096, 4, 128, 40, "Q8_0", "Zhipu GLM-4 9B Chat, Q8_0 量化", 33),
# Llama-3-8B-Instruct (ctx 8192)
("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (Q4_K_M)", 4.9, 32, 4096, 8, 128, 32, 8192, "Q4_K_M", "Meta Llama 3 8B Instruct, Q4_K_M 量化", 1),
("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (Q8_0)", 8.5, 32, 4096, 8, 128, 32, 8192, "Q8_0", "Meta Llama 3 8B Instruct, Q8_0 量化", 2),
("Llama-3-8B-Instruct", "Llama-3-8B-Instruct (FP16)", 15.5, 32, 4096, 8, 128, 32, 8192, "FP16", "Meta Llama 3 8B Instruct, FP16", 3),
# Llama-3-70B-Instruct (ctx 8192)
("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (Q4_K_M)", 38.5, 80, 8192, 8, 128, 64, 8192, "Q4_K_M", "Meta Llama 3 70B Instruct, Q4_K_M 量化", 4),
("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (Q8_0)", 74.0, 80, 8192, 8, 128, 64, 8192, "Q8_0", "Meta Llama 3 70B Instruct, Q8_0 量化", 5),
("Llama-3-70B-Instruct", "Llama-3-70B-Instruct (FP16)", 138.0, 80, 8192, 8, 128, 64, 8192, "FP16", "Meta Llama 3 70B Instruct, FP16", 6),
# Llama-3.1-8B-Instruct (ctx 131072)
("Llama-3.1-8B-Instruct", "Llama-3.1-8B-Instruct (Q4_K_M)", 4.9, 32, 4096, 8, 128, 32, 131072, "Q4_K_M", "Meta Llama 3.1 8B Instruct, Q4_K_M 量化", 7),
("Llama-3.1-8B-Instruct", "Llama-3.1-8B-Instruct (Q8_0)", 8.5, 32, 4096, 8, 128, 32, 131072, "Q8_0", "Meta Llama 3.1 8B Instruct, Q8_0 量化", 8),
# Llama-3.1-70B-Instruct (ctx 131072)
("Llama-3.1-70B-Instruct", "Llama-3.1-70B-Instruct (Q4_K_M)", 38.5, 80, 8192, 8, 128, 64, 131072, "Q4_K_M", "Meta Llama 3.1 70B Instruct, Q4_K_M 量化", 9),
("Llama-3.1-70B-Instruct", "Llama-3.1-70B-Instruct (Q8_0)", 74.0, 80, 8192, 8, 128, 64, 131072, "Q8_0", "Meta Llama 3.1 70B Instruct, Q8_0 量化", 10),
# Llama-3.1-405B-Instruct (ctx 131072)
("Llama-3.1-405B-Instruct", "Llama-3.1-405B-Instruct (Q4_K_M)", 226.0, 126, 16384, 8, 128, 128, 131072, "Q4_K_M", "Meta Llama 3.1 405B Instruct, Q4_K_M 量化", 11),
# Qwen2.5-7B-Instruct (ctx 32768)
("Qwen2.5-7B-Instruct", "Qwen2.5-7B-Instruct (Q4_K_M)", 4.7, 28, 3584, 4, 128, 28, 32768, "Q4_K_M", "Qwen2.5 7B Instruct, Q4_K_M 量化", 12),
("Qwen2.5-7B-Instruct", "Qwen2.5-7B-Instruct (Q8_0)", 7.6, 28, 3584, 4, 128, 28, 32768, "Q8_0", "Qwen2.5 7B Instruct, Q8_0 量化", 13),
# Qwen2.5-14B-Instruct (ctx 32768)
("Qwen2.5-14B-Instruct", "Qwen2.5-14B-Instruct (Q4_K_M)", 8.7, 40, 5120, 8, 128, 40, 32768, "Q4_K_M", "Qwen2.5 14B Instruct, Q4_K_M 量化", 14),
# Qwen2.5-32B-Instruct (ctx 32768)
("Qwen2.5-32B-Instruct", "Qwen2.5-32B-Instruct (Q4_K_M)", 19.5, 64, 5120, 8, 128, 64, 32768, "Q4_K_M", "Qwen2.5 32B Instruct, Q4_K_M 量化", 15),
("Qwen2.5-32B-Instruct", "Qwen2.5-32B-Instruct (Q8_0)", 32.0, 64, 5120, 8, 128, 64, 32768, "Q8_0", "Qwen2.5 32B Instruct, Q8_0 量化", 16),
# Qwen2.5-72B-Instruct (ctx 32768)
("Qwen2.5-72B-Instruct", "Qwen2.5-72B-Instruct (Q4_K_M)", 42.0, 80, 8192, 8, 128, 64, 32768, "Q4_K_M", "Qwen2.5 72B Instruct, Q4_K_M 量化", 17),
("Qwen2.5-72B-Instruct", "Qwen2.5-72B-Instruct (Q8_0)", 75.0, 80, 8192, 8, 128, 64, 32768, "Q8_0", "Qwen2.5 72B Instruct, Q8_0 量化", 18),
# DeepSeek-V2-Chat (ctx 4096)
("DeepSeek-V2-Chat", "DeepSeek-V2-Chat (Q4_K_M)", 23.0, 60, 5120, 8, 128, 60, 4096, "Q4_K_M", "DeepSeek V2 Chat, Q4_K_M 量化", 19),
# DeepSeek-V2.5-Chat (ctx 4096)
("DeepSeek-V2.5-Chat", "DeepSeek-V2.5-Chat (Q4_K_M)", 23.0, 60, 5120, 8, 128, 60, 4096, "Q4_K_M", "DeepSeek V2.5 Chat, Q4_K_M 量化", 20),
# DeepSeek-R1-Distill-Qwen-32B (ctx 131072)
("DeepSeek-R1-Distill-Qwen-32B", "DeepSeek-R1-Distill-Qwen-32B (Q4_K_M)", 19.5, 64, 5120, 8, 128, 64, 131072, "Q4_K_M", "DeepSeek R1 Distill Qwen 32B, Q4_K_M 量化", 21),
("DeepSeek-R1-Distill-Qwen-32B", "DeepSeek-R1-Distill-Qwen-32B (Q8_0)", 32.0, 64, 5120, 8, 128, 64, 131072, "Q8_0", "DeepSeek R1 Distill Qwen 32B, Q8_0 量化", 22),
# DeepSeek-R1-Distill-Llama-70B (ctx 131072)
("DeepSeek-R1-Distill-Llama-70B", "DeepSeek-R1-Distill-Llama-70B (Q4_K_M)", 42.0, 80, 8192, 8, 128, 64, 131072, "Q4_K_M", "DeepSeek R1 Distill Llama 70B, Q4_K_M 量化", 23),
("DeepSeek-R1-Distill-Llama-70B", "DeepSeek-R1-Distill-Llama-70B (Q8_0)", 75.0, 80, 8192, 8, 128, 64, 131072, "Q8_0", "DeepSeek R1 Distill Llama 70B, Q8_0 量化", 24),
# Mistral-7B-Instruct-v0.3 (ctx 32768)
("Mistral-7B-Instruct-v0.3", "Mistral-7B-Instruct-v0.3 (Q4_K_M)", 4.4, 32, 4096, 8, 128, 32, 32768, "Q4_K_M", "Mistral 7B Instruct v0.3, Q4_K_M 量化", 25),
("Mistral-7B-Instruct-v0.3", "Mistral-7B-Instruct-v0.3 (Q8_0)", 7.5, 32, 4096, 8, 128, 32, 32768, "Q8_0", "Mistral 7B Instruct v0.3, Q8_0 量化", 26),
# Mixtral-8x7B-Instruct (ctx 32768)
("Mixtral-8x7B-Instruct", "Mixtral-8x7B-Instruct (Q4_K_M)", 26.0, 32, 4096, 8, 128, 32, 32768, "Q4_K_M", "Mixtral 8x7B Instruct, Q4_K_M 量化", 27),
# Gemma-2-9B-It (ctx 8192)
("Gemma-2-9B-It", "Gemma-2-9B-It (Q4_K_M)", 5.4, 42, 3584, 4, 256, 14, 8192, "Q4_K_M", "Google Gemma 2 9B It, Q4_K_M 量化", 28),
# Gemma-2-27B-It (ctx 8192)
("Gemma-2-27B-It", "Gemma-2-27B-It (Q4_K_M)", 16.5, 46, 4608, 4, 128, 36, 8192, "Q4_K_M", "Google Gemma 2 27B It, Q4_K_M 量化", 29),
# Phi-3-Mini-4K-Instruct (ctx 4096)
("Phi-3-Mini-4K-Instruct", "Phi-3-Mini-4K-Instruct (Q4_K_M)", 2.5, 32, 3072, 32, 96, 32, 4096, "Q4_K_M", "Microsoft Phi-3 Mini 4K Instruct, Q4_K_M 量化", 30),
# Phi-3-Medium-14B-Instruct (ctx 14336)
("Phi-3-Medium-14B-Instruct", "Phi-3-Medium-14B-Instruct (Q4_K_M)", 8.4, 40, 5120, 10, 128, 40, 14336, "Q4_K_M", "Microsoft Phi-3 Medium 14B Instruct, Q4_K_M 量化", 31),
# GLM-4-9B-Chat (ctx 131072)
("GLM-4-9B-Chat", "GLM-4-9B-Chat (Q4_K_M)", 5.5, 40, 4096, 4, 128, 40, 131072, "Q4_K_M", "Zhipu GLM-4 9B Chat, Q4_K_M 量化", 32),
("GLM-4-9B-Chat", "GLM-4-9B-Chat (Q8_0)", 9.0, 40, 4096, 4, 128, 40, 131072, "Q8_0", "Zhipu GLM-4 9B Chat, Q8_0 量化", 33),
]
for m in default_models:
c.execute('''INSERT INTO models (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, quant, description, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', m)
c.execute('''INSERT INTO models (base_model, name, size_gb, layers, embd, kv_heads, head_dim, attention_heads, default_ctx, quant, description, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', m)
# ===== Default settings =====
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("admin_password", "admin123"))
@@ -385,7 +386,7 @@ def insert_default_data(conn):
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_api_url", ""))
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_api_key", ""))
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_api_model", ""))
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("llm_system_prompt", "你是一个llama.cpp命令行参数解析助手。用户会用自然语言描述他想运行的模型和参数配置,你需要将其解析为JSON格式的参数。\n\n可用的参数键包括: model, ctx_size, n_gpu_layers, threads, batch_size, temperature, top_k, top_p, flash_attn, port, host, parallel, split_mode, mlock, numa, repeat_penalty, presence_penalty, frequency_penalty, seed, min_p, typical, mirostat, mirostat_lr, mirostat_ent。\n\nGPU型号会通过 _gpu_name 字段返回,GPU数量通过 _gpu_count 返回。\n\n只返回JSON,不要其他文本。"))
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("nl_default_text", "用自然语言描述你想要的配置,支持多行输入。\n例如:\n用RTX 4090跑Llama-3-70B,上下文8192\n温度0.7,开启flash attention\n端口设为8080"))
conn.commit()
+2 -1
View File
@@ -131,6 +131,7 @@
<input type="number" id="model-kv" placeholder="KV Heads">
<input type="number" id="model-hdim" placeholder="Head Dim">
<input type="number" id="model-heads" placeholder="Attention Heads">
<input type="number" id="model-ctx" placeholder="默认上下文" value="0">
<input type="text" id="model-quant" placeholder="量化 (如: Q4_K_M)">
<input type="text" id="model-desc" placeholder="描述">
<input type="number" id="model-order" placeholder="排序" value="0">
@@ -138,7 +139,7 @@
</div>
</div>
<table class="admin-table">
<thead><tr><th>ID</th><th>基模型</th><th>名称</th><th>大小(GB)</th><th>层数</th><th>EMBD</th><th>KV</th><th>HD</th><th>Heads</th><th>量化</th><th>排序</th><th>操作</th></tr></thead>
<thead><tr><th>ID</th><th>基模型</th><th>名称</th><th>大小</th><th>层数</th><th>EMBD</th><th>KV</th><th>HD</th><th>Heads</th><th>默认CTX</th><th>量化</th><th>排序</th><th>操作</th></tr></thead>
<tbody id="model-table-body"></tbody>
</table>
</section>
+7 -3
View File
@@ -340,6 +340,8 @@ header h1 {
.param-tabs { display: flex; gap: 4px; margin-bottom: 12px; flex-wrap: wrap; }
.tab-btn { padding: 6px 14px; background: var(--bg-input); border: 1px solid var(--border); color: var(--text-dim); border-radius: var(--radius); cursor: pointer; font-size: 0.85em; }
.tab-btn.active { background: var(--accent); color: white; border-color: var(--accent); }
.tab-btn.tab-modified { border-color: var(--accent2); color: var(--accent2); margin-left: auto; }
.tab-btn.tab-modified.active { background: var(--accent2); color: var(--bg); border-color: var(--accent2); }
/* ===== Param Search ===== */
.param-search-container { margin-bottom: 12px; position: relative; }
@@ -437,11 +439,12 @@ header h1 {
/* ===== Command Output ===== */
.command-panel { position: sticky; bottom: 20px; }
.command-output { position: relative; background: #0d1117; border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 0.9em; color: #4ecca3; word-break: break-all; min-height: 60px; white-space: pre-wrap; }
.command-output-wrap { position: relative; }
.command-output { position: relative; background: #0d1117; border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 16px 16px 70px; font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 0.9em; color: #4ecca3; word-break: break-all; min-height: 60px; white-space: pre-wrap; }
.command-copy-btn {
position: absolute;
bottom: 6px;
right: 6px;
top: 6px;
left: 6px;
background: var(--bg-input);
border: 1px solid var(--border);
color: var(--text-dim);
@@ -451,6 +454,7 @@ header h1 {
font-size: 0.75em;
transition: all 0.15s;
opacity: 0.7;
z-index: 5;
}
.command-copy-btn:hover { opacity: 1; border-color: var(--accent2); color: var(--accent2); }
.command-copy-btn.copied { background: var(--accent2); color: var(--bg); border-color: var(--accent2); opacity: 1; }
+4 -3
View File
@@ -90,7 +90,7 @@
<div class="model-step">
<label class="model-step-label">第1步:选择模型</label>
<div class="model-select-container">
<input type="text" id="model-search" placeholder="搜索模型名称..." oninput="filterBaseModels()" autocomplete="off" onfocus="this.select()">
<input type="text" id="model-search" placeholder="搜索模型名称..." oninput="filterBaseModels()" onfocus="onModelSearchFocus()" onblur="onModelSearchBlur()" autocomplete="off">
<div class="model-dropdown" id="model-dropdown"></div>
</div>
</div>
@@ -105,7 +105,7 @@
<section class="panel">
<h2>💬 自然语言生成</h2>
<div class="nl-input-container">
<textarea id="nl-input" rows="3" placeholder="用自然语言描述你想要的配置,支持多行输入。&#10;例如:&#10;用RTX 4090跑Llama-3-70B,上下文8192&#10;温度0.7,开启flash attention&#10;端口设为8080"></textarea>
<textarea id="nl-input" rows="3" placeholder="加载中..."></textarea>
<button onclick="parseNaturalLanguage()">解析</button>
</div>
<div id="nl-result" class="nl-result"></div>
@@ -126,6 +126,7 @@
<button class="tab-btn" data-cat="lora" onclick="switchTab('lora')">LoRA</button>
<button class="tab-btn" data-cat="logging" onclick="switchTab('logging')">日志</button>
<button class="tab-btn" data-cat="advanced" onclick="switchTab('advanced')">高级</button>
<button class="tab-btn tab-modified" data-cat="modified" onclick="switchTab('modified')">已改参数</button>
</div>
<div id="param-container"></div>
<button class="btn-toggle-advanced" onclick="toggleHiddenParams()" id="toggle-advanced-btn">▼ 显示更多参数</button>
@@ -135,8 +136,8 @@
<section class="panel command-panel">
<h2>📋 生成命令</h2>
<div class="command-output-wrap">
<div class="command-output" id="command-output">请在上方配置参数...</div>
<button class="command-copy-btn" onclick="copyCommand()" id="copy-btn">📋 复制</button>
<div class="command-output" id="command-output">请在上方配置参数...</div>
</div>
<div class="command-hint" id="command-hint"></div>
</section>
+14 -11
View File
@@ -241,14 +241,15 @@ function renderModelTable() {
<td>${m.id}</td>
<td><input type="text" value="${m.base_model}" onchange="updateModel(${m.id}, 'base_model', this.value)" style="width:140px"></td>
<td><input type="text" value="${m.name}" onchange="updateModel(${m.id}, 'name', this.value)" style="width:200px"></td>
<td><input type="number" value="${m.size_gb}" step="0.1" onchange="updateModel(${m.id}, 'size_gb', this.value)" style="width:70px"></td>
<td><input type="number" value="${m.layers}" onchange="updateModel(${m.id}, 'layers', this.value)" style="width:60px"></td>
<td><input type="number" value="${m.embd}" onchange="updateModel(${m.id}, 'embd', this.value)" style="width:70px"></td>
<td><input type="number" value="${m.kv_heads}" onchange="updateModel(${m.id}, 'kv_heads', this.value)" style="width:60px"></td>
<td><input type="number" value="${m.head_dim}" onchange="updateModel(${m.id}, 'head_dim', this.value)" style="width:60px"></td>
<td><input type="number" value="${m.attention_heads}" onchange="updateModel(${m.id}, 'attention_heads', this.value)" style="width:60px"></td>
<td><input type="text" value="${m.quant || ''}" onchange="updateModel(${m.id}, 'quant', this.value)" style="width:80px"></td>
<td><input type="number" value="${m.sort_order}" onchange="updateModel(${m.id}, 'sort_order', this.value)" style="width:50px"></td>
<td><input type="number" value="${m.size_gb}" step="0.1" onchange="updateModel(${m.id}, 'size_gb', this.value)" style="width:60px"></td>
<td><input type="number" value="${m.layers}" onchange="updateModel(${m.id}, 'layers', this.value)" style="width:50px"></td>
<td><input type="number" value="${m.embd}" onchange="updateModel(${m.id}, 'embd', this.value)" style="width:60px"></td>
<td><input type="number" value="${m.kv_heads}" onchange="updateModel(${m.id}, 'kv_heads', this.value)" style="width:50px"></td>
<td><input type="number" value="${m.head_dim}" onchange="updateModel(${m.id}, 'head_dim', this.value)" style="width:50px"></td>
<td><input type="number" value="${m.attention_heads}" onchange="updateModel(${m.id}, 'attention_heads', this.value)" style="width:50px"></td>
<td><input type="number" value="${m.default_ctx || 0}" onchange="updateModel(${m.id}, 'default_ctx', this.value)" style="width:70px"></td>
<td><input type="text" value="${m.quant || ''}" onchange="updateModel(${m.id}, 'quant', this.value)" style="width:70px"></td>
<td><input type="number" value="${m.sort_order}" onchange="updateModel(${m.id}, 'sort_order', this.value)" style="width:40px"></td>
<td><button class="btn-action btn-delete" onclick="deleteModel(${m.id})">删除</button></td>
</tr>`).join('');
}
@@ -263,22 +264,23 @@ async function addModel() {
kv_heads: parseInt(document.getElementById('model-kv').value) || 0,
head_dim: parseInt(document.getElementById('model-hdim').value) || 0,
attention_heads: parseInt(document.getElementById('model-heads').value) || 0,
default_ctx: parseInt(document.getElementById('model-ctx').value) || 0,
quant: document.getElementById('model-quant').value,
description: document.getElementById('model-desc').value,
sort_order: parseInt(document.getElementById('model-order').value) || 0,
};
if (!data.base_model || !data.name || !data.size_gb || !data.layers) { alert('请填写基模型、名称、大小和层数'); return; }
await fetch('/api/admin/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
['model-basemodel','model-name','model-size','model-layers','model-embd','model-kv','model-hdim','model-heads','model-quant','model-desc','model-order'].forEach(id => document.getElementById(id).value = id === 'model-order' ? '0' : '');
['model-basemodel','model-name','model-size','model-layers','model-embd','model-kv','model-hdim','model-heads','model-ctx','model-quant','model-desc','model-order'].forEach(id => document.getElementById(id).value = id === 'model-order' ? '0' : '');
await loadAdminModels();
}
async function updateModel(id, field, value) {
const m = adminState.models.find(m => m.id === id);
if (!m) return;
const data = { base_model: m.base_model, name: m.name, size_gb: m.size_gb, layers: m.layers, embd: m.embd, kv_heads: m.kv_heads, head_dim: m.head_dim, attention_heads: m.attention_heads, quant: m.quant, description: m.description, sort_order: m.sort_order };
const data = { base_model: m.base_model, name: m.name, size_gb: m.size_gb, layers: m.layers, embd: m.embd, kv_heads: m.kv_heads, head_dim: m.head_dim, attention_heads: m.attention_heads, default_ctx: m.default_ctx || 0, quant: m.quant, description: m.description, sort_order: m.sort_order };
if (['size_gb'].includes(field)) data[field] = parseFloat(value) || 0;
else if (['layers','embd','kv_heads','head_dim','attention_heads','sort_order'].includes(field)) data[field] = parseInt(value) || 0;
else if (['layers','embd','kv_heads','head_dim','attention_heads','default_ctx','sort_order'].includes(field)) data[field] = parseInt(value) || 0;
else data[field] = value;
await fetch(`/api/admin/models/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
await loadAdminModels();
@@ -305,6 +307,7 @@ function renderSettings() {
'default_version': '默认版本',
'default_mode': '默认模式',
'default_quant': '默认量化版本',
'nl_default_text': '自然语言默认提示文本',
'llm_enabled': '启用LLM解析 (true/false)',
'llm_api_url': 'LLM API URL',
'llm_api_key': 'LLM API Key',
+50 -2
View File
@@ -14,6 +14,7 @@ async function init() {
await loadVersions();
await loadGpus();
await loadModelsGrouped();
await loadNlDefaultText();
const dv = state.versions.find(v => v.version_tag === 'b10068') || state.versions[0];
if (dv) { state.currentVersionId = dv.id; document.getElementById('version-select').value = dv.id; await loadParams(dv.id); }
const dg = state.gpus.find(g => g.name === 'RTX 3090') || state.gpus[0];
@@ -21,6 +22,15 @@ async function init() {
renderParams(); generateCommand(); updateEstimate();
}
async function loadNlDefaultText() {
const res = await fetch('/api/settings/public');
const data = await res.json();
const ta = document.getElementById('nl-input');
if (data.nl_default_text) {
ta.placeholder = data.nl_default_text;
}
}
// ===== Load Data =====
async function loadVersions() {
const res = await fetch('/api/versions'); state.versions = await res.json();
@@ -76,10 +86,26 @@ function updateGpuSlot(i, name) { const g = state.gpus.find(g => g.name === name
// ===== Model Selection =====
function filterBaseModels() {
const text = document.getElementById('model-search').value.toLowerCase();
if (!text) {
// Show top 5 when input is empty (on focus)
state.filteredBaseModels = state.baseModelList.slice(0, 5);
} else {
state.filteredBaseModels = state.baseModelList.filter(n => n.toLowerCase().includes(text));
}
renderBaseModelDropdown();
document.getElementById('model-dropdown').style.display = state.filteredBaseModels.length > 0 ? 'block' : 'none';
}
function onModelSearchFocus() {
if (!document.getElementById('model-search').value) {
state.filteredBaseModels = state.baseModelList.slice(0, 5);
renderBaseModelDropdown();
document.getElementById('model-dropdown').style.display = 'block';
}
}
function onModelSearchBlur() {
// Delay to allow click on option
setTimeout(() => { document.getElementById('model-dropdown').style.display = 'none'; }, 200);
}
function renderBaseModelDropdown() {
document.getElementById('model-dropdown').innerHTML = state.filteredBaseModels.map(n => {
const quants = state.modelsGrouped[n] || [];
@@ -117,9 +143,25 @@ function selectModel(id) {
<span class="detail-item"><b>Head Dim:</b> ${m.head_dim}</span>
<span class="detail-item"><b>量化:</b> ${m.quant || 'N/A'}</span>
</div>`;
// Auto-adjust params based on selected model
applyModelDefaults(m);
updateEstimate();
}
function applyModelDefaults(m) {
// Set ctx_size to model's default context if available
if (m.default_ctx && m.default_ctx > 0) {
state.paramValues['ctx_size'] = String(m.default_ctx);
}
// Set n_gpu_layers to 'all' for GPU mode
if (state.mode === 'gpu') {
state.paramValues['n_gpu_layers'] = 'all';
}
// Update param UI if currently visible
renderParams();
generateCommand();
}
// ===== Parameter Rendering =====
function switchTab(cat) {
state.currentTab = cat;
@@ -151,6 +193,10 @@ function onParamSearch() {
}
function getFilteredParams() {
// 'modified' is a special tab showing all modified params
if (state.currentTab === 'modified') {
return state.params.filter(p => isParamModified(p, state.paramValues[p.param_key]));
}
let params = state.params.filter(p => p.category === state.currentTab);
if (state.paramSearchText) {
params = state.params.filter(p =>
@@ -166,7 +212,8 @@ function getFilteredParams() {
function renderParams() {
const container = document.getElementById('param-container');
const all = getFilteredParams();
if (state.paramSearchText) {
// For 'modified' tab, show all (no important/hidden distinction)
if (state.currentTab === 'modified' || state.paramSearchText) {
container.innerHTML = all.map(p => renderParamItem(p)).join('');
document.getElementById('toggle-advanced-btn').style.display = 'none';
return;
@@ -220,7 +267,8 @@ function isParamModified(p, val) {
function setParam(key, value) {
state.paramValues[key] = value;
// No visual modification marker per user request
// If on 'modified' tab, re-render to update the list
if (state.currentTab === 'modified') renderParams();
generateCommand(); updateEstimate();
}