Files
llama-cmd-gen/app.py
T
hz4th_coder a407032b4d feat: llama.cpp 命令生成器 v1.0.0
- 支持多版本 llama.cpp 参数 (b6310, b10068)
- GPU 模式 / GPU+CPU 模式
- 多 GPU 支持 (最多4张)
- 实时显存/内存估算
- 自然语言解析生成命令
- 参数分级显示 (重要/隐藏)
- 仅输出非默认值参数
- 后台管理 (GPU/版本/参数 CRUD)
- 模型预设 (7B-70B)
2026-07-19 18:08:36 +08:00

742 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""llama.cpp command generator - main Flask application."""
import os
import sys
import json
import re
import math
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from db import get_db, init_db, DB_PATH
app = Flask(__name__, static_folder='static', static_url_path='')
CORS(app)
# ==================== Helper Functions ====================
def parse_param_value(param_type, value):
"""Parse parameter value based on its type."""
if value is None or value == '':
return None
if param_type == 'number':
try:
if '.' in str(value):
return float(value)
return int(value)
except (ValueError, TypeError):
return value
elif param_type == 'boolean':
return str(value).lower() in ('true', '1', 'yes', 'on')
return value
def format_param_value(param_type, value):
"""Format parameter value for display."""
if param_type == 'number':
try:
f = float(value)
if f == int(f):
return str(int(f))
return str(f)
except (ValueError, TypeError):
return str(value)
return str(value)
def get_flag_for_param(param):
"""Get the flag string for a parameter (prefers short flag)."""
if param['short_flag']:
return param['short_flag']
return param['long_flag']
def get_kv_cache_bytes_per_element(cache_type):
"""Get bytes per element for KV cache type."""
cache_type_map = {
'f32': 4,
'f16': 2,
'bf16': 2,
'q8_0': 1,
'q4_0': 0.5,
'q4_1': 0.5625,
'iq4_nl': 0.5,
'q5_0': 0.625,
'q5_1': 0.6875,
}
return cache_type_map.get(cache_type, 2)
def estimate_vram(params_dict, gpus, version_params):
"""
Estimate VRAM usage based on parameters.
Returns dict with breakdown.
"""
# Model parameters (user configurable hints)
model_size_gb = float(params_dict.get('_model_size_gb', 0) or 0)
model_layers = int(params_dict.get('_model_layers', 0) or 0)
model_embd = int(params_dict.get('_model_embd', 0) or 0)
model_heads = int(params_dict.get('_model_heads', 0) or 0)
model_kv_heads = int(params_dict.get('_model_kv_heads', 0) or 0)
model_head_dim = int(params_dict.get('_model_head_dim', 0) or 0)
# If model_layers not specified, try to estimate from model size
if model_layers == 0 and model_size_gb > 0:
# Rough estimate: ~0.3GB per layer for 7B, scale accordingly
model_layers = max(1, int(model_size_gb / 0.4))
# n_gpu_layers
ngl_raw = params_dict.get('n_gpu_layers', 'auto')
if ngl_raw in ('auto', 'all', '-1'):
ngl = model_layers if model_layers > 0 else 32
if ngl_raw == '0':
ngl = 0
else:
try:
ngl = int(ngl_raw)
except (ValueError, TypeError):
ngl = 0
# Context size
ctx_size = int(params_dict.get('ctx_size', 0) or 0)
if ctx_size == 0:
ctx_size = 4096 # default assumption
# Batch size
batch_size = int(params_dict.get('batch_size', 2048) or 2048)
ubatch_size = int(params_dict.get('ubatch_size', 512) or 512)
# KV cache types
ctk = params_dict.get('cache_type_k', 'f16')
ctv = params_dict.get('cache_type_v', 'f16')
k_bytes = get_kv_cache_bytes_per_element(ctk)
v_bytes = get_kv_cache_bytes_per_element(ctv)
# Parallel slots
parallel = int(params_dict.get('parallel', 1) or 1)
if parallel < 1:
parallel = 1
# Flash attention
fa = params_dict.get('flash_attn', 'auto')
fa_enabled = fa in ('on', 'auto')
# === Calculate VRAM components ===
total_vram = sum(g['vram_mb'] for g in gpus) if gpus else 0
max_single_vram = max((g['vram_mb'] for g in gpus), default=0)
# 1. Model weights in VRAM
if model_size_gb > 0 and model_layers > 0:
ratio = min(1.0, ngl / model_layers) if ngl > 0 else 0
weights_vram_mb = model_size_gb * 1024 * ratio
else:
# Fallback estimate: ~1GB per 1B params at Q4
weights_vram_mb = 0
# 2. KV cache
# KV cache per layer = 2 (K and V) * n_kv_heads * head_dim * ctx_size * bytes_per_element
if model_kv_heads > 0 and model_head_dim > 0:
kv_per_layer_bytes = 2 * model_kv_heads * model_head_dim * ctx_size
kv_total_bytes = kv_per_layer_bytes * model_layers * parallel
elif model_embd > 0:
# Fallback: use embedding dim
kv_per_layer_bytes = 2 * model_embd * ctx_size
kv_total_bytes = kv_per_layer_bytes * model_layers * parallel
else:
# Very rough estimate: ~0.5MB per layer per 1K context at f16
kv_total_bytes = 0
if model_layers > 0:
kv_total_bytes = int(0.5 * 1024 * 1024 * model_layers * (ctx_size / 1024) * parallel)
kv_vram_mb = (kv_total_bytes * (k_bytes + v_bytes) / 2) / (1024 * 1024)
# If ngl < model_layers, only ngl layers' KV is on GPU
if model_layers > 0 and ngl < model_layers:
kv_vram_mb = kv_vram_mb * (ngl / model_layers)
# 3. Compute buffer / overhead
# Rough: batch_size * model_embd * 4 bytes * some factor
compute_mb = 0
if model_embd > 0:
compute_mb = (batch_size * model_embd * 4 * 2) / (1024 * 1024) # logits buffer
compute_mb = max(compute_mb, 100) # minimum overhead
# CUDA context overhead (~300-500MB per GPU)
cuda_overhead_mb = len(gpus) * 400 if gpus else 0
# Total
total_estimate = weights_vram_mb + kv_vram_mb + compute_mb + cuda_overhead_mb
return {
'total_mb': round(total_estimate, 1),
'total_gb': round(total_estimate / 1024, 2),
'weights_mb': round(weights_vram_mb, 1),
'weights_gb': round(weights_vram_mb / 1024, 2),
'kv_cache_mb': round(kv_vram_mb, 1),
'kv_cache_gb': round(kv_vram_mb / 1024, 2),
'compute_mb': round(compute_mb, 1),
'cuda_overhead_mb': round(cuda_overhead_mb, 1),
'total_vram_available_mb': total_vram,
'total_vram_available_gb': round(total_vram / 1024, 2) if total_vram else 0,
'max_single_vram_mb': max_single_vram,
'fits': total_estimate <= total_vram if total_vram > 0 else None,
'usage_percent': round(total_estimate / total_vram * 100, 1) if total_vram > 0 else None,
# For CPU+GPU mode
'cpu_weights_mb': round(model_size_gb * 1024 * max(0, 1 - (ngl / model_layers if model_layers > 0 else 0)), 1) if model_size_gb > 0 else 0,
}
def parse_natural_language(text):
"""
Parse natural language description into parameter values.
Supports Chinese and English keywords.
"""
result = {}
text_lower = text.lower()
# GPU selection
gpu_patterns = [
(r'(?:rtx\s*)?3090', 'RTX 3090'),
(r'(?:rtx\s*)?4090', 'RTX 4090'),
(r'(?:rtx\s*)?4080', 'RTX 4080'),
(r'(?:rtx\s*)?3080', 'RTX 3080'),
(r'(?:rtx\s*)?5090', 'RTX 5090'),
(r'(?:rtx\s*)?4070\s*ti', 'RTX 4070 Ti'),
(r'(?:rtx\s*)?4060', 'RTX 4060'),
(r'a100\s*80', 'A100 80GB'),
(r'a100\s*40', 'A100 40GB'),
(r'h100', 'H100 80GB'),
(r'v100', 'V100 32GB'),
(r'a6000', 'RTX A6000'),
(r'a5000', 'RTX A5000'),
(r'7900\s*xtx', 'RX 7900 XTX'),
]
for pattern, gpu_name in gpu_patterns:
if re.search(pattern, text_lower):
result['_gpu_name'] = gpu_name
break
# GPU count
gpu_count_match = re.search(r'(\d+)\s*(?:张|块|个)?\s*(?:gpu|显卡|卡)', text_lower)
if gpu_count_match:
result['_gpu_count'] = int(gpu_count_match.group(1))
# Context size
ctx_patterns = [
r'(?:上下文|context|ctx)[\s大小为]*[:\s]*(\d+)',
r'(\d+)\s*(?:k|K)\s*(?:上下文|context|ctx)',
]
for pattern in ctx_patterns:
m = re.search(pattern, text_lower)
if m:
val = int(m.group(1))
if val < 100: # like "8k context"
val = val * 1024
result['ctx_size'] = str(val)
break
# GPU layers
ngl_patterns = [
r'(?:gpu\s*层|gpu\s*layers?|ngl|offload)[\s:]*(\d+)',
r'(\d+)\s*(?:层|layers?)\s*(?:gpu|offload)',
r'全部(?:offload|卸载|gpu)|all\s*(?:gpu|layers?)',
]
for pattern in ngl_patterns:
m = re.search(pattern, text_lower)
if m:
val = m.group(1) if m.lastindex else '-1'
result['n_gpu_layers'] = val
break
# Temperature
temp_match = re.search(r'(?:温度|temp|temperature)[\s:]*(\d+(?:\.\d+)?)', text_lower)
if temp_match:
result['temperature'] = temp_match.group(1)
# Top-k
topk_match = re.search(r'(?:top[\s-]*k)[\s:]*(\d+)', text_lower)
if topk_match:
result['top_k'] = topk_match.group(1)
# Top-p
topp_match = re.search(r'(?:top[\s-]*p)[\s:]*(\d+(?:\.\d+)?)', text_lower)
if topp_match:
result['top_p'] = topp_match.group(1)
# Threads
threads_match = re.search(r'(?:线程|threads?|cpu\s*线程)[\s:]*(\d+)', text_lower)
if threads_match:
result['threads'] = threads_match.group(1)
# Batch size
batch_match = re.search(r'(?:batch|批处理|批次)[\s大小:]*(\d+)', text_lower)
if batch_match:
result['batch_size'] = batch_match.group(1)
# Port
port_match = re.search(r'(?:端口|port)[\s:]*(\d+)', text_lower)
if port_match:
result['port'] = port_match.group(1)
# Model path
model_match = re.search(r'(?:模型|model)[\s路径]*[:\s]+([^\s,]+\.gguf)', text_lower)
if model_match:
result['model'] = model_match.group(1)
# HF repo
hf_match = re.search(r'(?:hf|hugging\s*face|仓库)[\s:]+([^\s,]+)', text_lower)
if hf_match and not model_match:
result['hf_repo'] = hf_match.group(1)
# Flash attention
if re.search(r'flash\s*atten|flash\s*attn|fa', text_lower):
result['flash_attn'] = 'on'
# Mode
if re.search(r'cpu\s*\+\s*gpu|gpu\s*\+\s*cpu|混合', text_lower):
result['_mode'] = 'gpu_cpu'
elif re.search(r'纯\s*gpu|gpu\s*only|仅\s*gpu', text_lower):
result['_mode'] = 'gpu'
# Parallel
parallel_match = re.search(r'(?:并行|parallel|slots?)[\s:]*(\d+)', text_lower)
if parallel_match:
result['parallel'] = parallel_match.group(1)
# Split mode
if re.search(r'row\s*split|行分割|按行分割', text_lower):
result['split_mode'] = 'row'
elif re.search(r'tensor\s*split|张量分割|按张量分割', text_lower):
result['split_mode'] = 'tensor'
# mlock
if re.search(r'mlock|锁[\s定]*内存|内存锁', text_lower):
result['mlock'] = 'true'
# numa
numa_match = re.search(r'numa[\s:]*(distribute|isolate|numactl)', text_lower)
if numa_match:
result['numa'] = numa_match.group(1)
# Reasoning budget
reasoning_match = re.search(r'(?:reasoning|推理|thinking)[\s预算budget]*[:\s]*(\d+)', text_lower)
if reasoning_match:
result['reasoning_budget'] = reasoning_match.group(1)
return result
# ==================== API Routes ====================
@app.route('/')
def index():
return send_from_directory('static', 'index.html')
@app.route('/admin')
def admin():
return send_from_directory('static', 'admin.html')
# ----- Versions -----
@app.route('/api/versions')
def get_versions():
db = get_db()
versions = db.execute('SELECT * FROM llama_versions WHERE is_active = 1 ORDER BY sort_order').fetchall()
result = [dict(v) for v in versions]
db.close()
return jsonify(result)
@app.route('/api/versions/<int:vid>/params')
def get_version_params(vid):
db = get_db()
params = db.execute(
'SELECT * FROM params WHERE version_id = ? ORDER BY is_important DESC, sort_order',
(vid,)
).fetchall()
result = []
for p in params:
d = dict(p)
if d.get('options'):
try:
d['options'] = json.loads(d['options'])
except (json.JSONDecodeError, TypeError):
pass
result.append(d)
db.close()
return jsonify(result)
# ----- GPUs -----
@app.route('/api/gpus')
def get_gpus():
db = get_db()
gpus = db.execute('SELECT * FROM gpus ORDER BY sort_order, name').fetchall()
result = [dict(g) for g in gpus]
db.close()
return jsonify(result)
# ----- Generate Command -----
@app.route('/api/generate', methods=['POST'])
def generate_command():
data = request.json
version_id = data.get('version_id')
params = data.get('params', {})
mode = data.get('mode', 'gpu')
gpu_selections = data.get('gpu_selections', [])
binary = data.get('binary', 'llama-server')
system_memory_gb = data.get('system_memory_gb', 0)
db = get_db()
# Get param definitions
param_defs = db.execute(
'SELECT * FROM params WHERE version_id = ?',
(version_id,)
).fetchall()
param_def_map = {p['param_key']: dict(p) for p in param_defs}
db.close()
# Build command
cmd_parts = [binary]
# GPU layers
if mode == 'gpu':
# Add GPU-related args
if gpu_selections:
gpu_names = [g.get('name', '') for g in gpu_selections]
device_str = ','.join(str(i) for i in range(len(gpu_selections)))
# n_gpu_layers
ngl = params.get('n_gpu_layers', 'auto')
if ngl and ngl != param_def_map.get('n_gpu_layers', {}).get('default_value', 'auto'):
flag = param_def_map.get('n_gpu_layers', {}).get('short_flag') or param_def_map.get('n_gpu_layers', {}).get('long_flag', '-ngl')
cmd_parts.append(f'{flag} {ngl}')
# tensor_split for multiple GPUs
if len(gpu_selections) > 1:
ts = params.get('tensor_split', '')
if ts and ts != param_def_map.get('tensor_split', {}).get('default_value', ''):
flag = param_def_map.get('tensor_split', {}).get('short_flag', '-ts')
cmd_parts.append(f'{flag} {ts}')
elif not ts:
# Auto-generate tensor split based on VRAM ratio
total_vram = sum(g.get('vram_mb', 0) for g in gpu_selections)
if total_vram > 0:
ratios = [str(round(g.get('vram_mb', 0) / total_vram, 2)) for g in gpu_selections]
cmd_parts.append(f'-ts {",".join(ratios)}')
elif mode == 'gpu_cpu':
# GPU+CPU mode
if gpu_selections:
ngl = params.get('n_gpu_layers', 'auto')
if ngl and ngl != param_def_map.get('n_gpu_layers', {}).get('default_value', 'auto'):
flag = param_def_map.get('n_gpu_layers', {}).get('short_flag') or param_def_map.get('n_gpu_layers', {}).get('long_flag', '-ngl')
cmd_parts.append(f'{flag} {ngl}')
# Iterate through params and add non-default ones
skip_params = {'n_gpu_layers', 'tensor_split'}
for key, value in params.items():
if key.startswith('_'):
continue
if key in skip_params:
continue
p_def = param_def_map.get(key)
if not p_def:
continue
default_val = p_def['default_value']
# Skip if value equals default
if str(value) == str(default_val):
continue
# Skip empty values
if value is None or value == '' or value == 'false':
if str(default_val).lower() == 'false' and str(value).lower() == 'false':
continue
if value == '' or value is None:
continue
# Boolean params: only add if true (and default is false)
if p_def['param_type'] == 'boolean':
if str(value).lower() == 'true' and str(default_val).lower() != 'true':
flag = p_def['short_flag'] or p_def['long_flag']
cmd_parts.append(flag)
elif str(value).lower() == 'false' and str(default_val).lower() == 'true':
# Add --no- variant
flag = p_def['long_flag']
cmd_parts.append(f'--no-{flag.lstrip("--")}')
continue
flag = p_def['short_flag'] or p_def['long_flag']
cmd_parts.append(f'{flag} {value}')
command = ' '.join(cmd_parts)
return jsonify({'command': command, 'mode': mode, 'binary': binary})
# ----- Estimate VRAM -----
@app.route('/api/estimate', methods=['POST'])
def estimate():
data = request.json
params = data.get('params', {})
gpu_selections = data.get('gpu_selections', [])
mode = data.get('mode', 'gpu')
system_memory_gb = data.get('system_memory_gb', 0)
# Prepare GPU list with VRAM
gpus = []
for gs in gpu_selections:
gpus.append({'vram_mb': gs.get('vram_mb', 0), 'name': gs.get('name', '')})
result = estimate_vram(params, gpus, [])
# Add system memory estimation for GPU+CPU mode
if mode == 'gpu_cpu':
model_size_gb = float(params.get('_model_size_gb', 0) or 0)
model_layers = int(params.get('_model_layers', 0) or 0)
ngl_raw = params.get('n_gpu_layers', 'auto')
if ngl_raw in ('auto', 'all', '-1'):
ngl = model_layers if model_layers > 0 else 32
else:
try:
ngl = int(ngl_raw)
except (ValueError, TypeError):
ngl = 0
# CPU portion of weights
if model_layers > 0 and ngl < model_layers:
cpu_weights_gb = model_size_gb * (1 - ngl / model_layers)
else:
cpu_weights_gb = 0
# KV cache on CPU
ctx_size = int(params.get('ctx_size', 0) or 0)
if ctx_size == 0:
ctx_size = 4096
parallel = int(params.get('parallel', 1) or 1)
if parallel < 1:
parallel = 1
# Estimate KV cache on CPU (layers not on GPU)
model_kv_heads = int(params.get('_model_kv_heads', 0) or 0)
model_head_dim = int(params.get('_model_head_dim', 0) or 0)
model_embd = int(params.get('_model_embd', 0) or 0)
if model_layers > 0 and ngl < model_layers:
remaining_layers = model_layers - ngl
if model_kv_heads > 0 and model_head_dim > 0:
kv_cpu_bytes = 2 * model_kv_heads * model_head_dim * ctx_size * remaining_layers * parallel
elif model_embd > 0:
kv_cpu_bytes = 2 * model_embd * ctx_size * remaining_layers * parallel
else:
kv_cpu_bytes = int(0.5 * 1024 * 1024 * remaining_layers * (ctx_size / 1024) * parallel) * 2
kv_cpu_mb = kv_cpu_bytes * 2 / (1024 * 1024) # Assuming f16
else:
kv_cpu_mb = 0
total_cpu_mb = cpu_weights_gb * 1024 + kv_cpu_mb + 500 # overhead
result['cpu_total_mb'] = round(total_cpu_mb, 1)
result['cpu_total_gb'] = round(total_cpu_mb / 1024, 2)
result['cpu_weights_gb'] = round(cpu_weights_gb, 2)
result['cpu_kv_cache_mb'] = round(kv_cpu_mb, 1)
result['system_memory_gb'] = system_memory_gb
if system_memory_gb > 0:
result['cpu_usage_percent'] = round(total_cpu_mb / (system_memory_gb * 1024) * 100, 1)
return jsonify(result)
# ----- Parse Natural Language -----
@app.route('/api/parse-nl', methods=['POST'])
def parse_nl():
data = request.json
text = data.get('text', '')
result = parse_natural_language(text)
return jsonify(result)
# ==================== Admin API ====================
@app.route('/api/admin/gpus', methods=['GET', 'POST'])
def admin_gpus():
db = get_db()
if request.method == 'GET':
gpus = db.execute('SELECT * FROM gpus ORDER BY sort_order, name').fetchall()
result = [dict(g) for g in gpus]
db.close()
return jsonify(result)
elif request.method == 'POST':
data = request.json
db.execute(
'INSERT INTO gpus (name, vram_mb, compute_capability, description, sort_order) VALUES (?, ?, ?, ?, ?)',
(data['name'], data['vram_mb'], data.get('compute_capability', ''),
data.get('description', ''), data.get('sort_order', 0))
)
db.commit()
db.close()
return jsonify({'status': 'ok'})
@app.route('/api/admin/gpus/<int:gid>', methods=['PUT', 'DELETE'])
def admin_gpu_edit(gid):
db = get_db()
if request.method == 'PUT':
data = request.json
db.execute(
'UPDATE gpus SET name=?, vram_mb=?, compute_capability=?, description=?, sort_order=? WHERE id=?',
(data['name'], data['vram_mb'], data.get('compute_capability', ''),
data.get('description', ''), data.get('sort_order', 0), gid)
)
db.commit()
db.close()
return jsonify({'status': 'ok'})
elif request.method == 'DELETE':
db.execute('DELETE FROM gpus WHERE id=?', (gid,))
db.commit()
db.close()
return jsonify({'status': 'ok'})
@app.route('/api/admin/versions', methods=['GET', 'POST'])
def admin_versions():
db = get_db()
if request.method == 'GET':
versions = db.execute('SELECT * FROM llama_versions ORDER BY sort_order').fetchall()
result = [dict(v) for v in versions]
db.close()
return jsonify(result)
elif request.method == 'POST':
data = request.json
db.execute(
'INSERT INTO llama_versions (version_tag, description, release_date, is_active, sort_order) VALUES (?, ?, ?, ?, ?)',
(data['version_tag'], data.get('description', ''), data.get('release_date', ''),
data.get('is_active', 1), data.get('sort_order', 0))
)
db.commit()
db.close()
return jsonify({'status': 'ok'})
@app.route('/api/admin/versions/<int:vid>', methods=['PUT', 'DELETE'])
def admin_version_edit(vid):
db = get_db()
if request.method == 'PUT':
data = request.json
db.execute(
'UPDATE llama_versions SET version_tag=?, description=?, release_date=?, is_active=?, sort_order=? WHERE id=?',
(data['version_tag'], data.get('description', ''), data.get('release_date', ''),
data.get('is_active', 1), data.get('sort_order', 0), vid)
)
db.commit()
db.close()
return jsonify({'status': 'ok'})
elif request.method == 'DELETE':
db.execute('DELETE FROM llama_versions WHERE id=?', (vid,))
db.commit()
db.close()
return jsonify({'status': 'ok'})
@app.route('/api/admin/versions/<int:vid>/params', methods=['GET', 'POST'])
def admin_params(vid):
db = get_db()
if request.method == 'GET':
params = db.execute(
'SELECT * FROM params WHERE version_id = ? ORDER BY is_important DESC, sort_order',
(vid,)
).fetchall()
result = []
for p in params:
d = dict(p)
if d.get('options'):
try:
d['options'] = json.loads(d['options'])
except (json.JSONDecodeError, TypeError):
pass
result.append(d)
db.close()
return jsonify(result)
elif request.method == 'POST':
data = request.json
options = data.get('options')
if isinstance(options, list):
options = json.dumps(options)
db.execute(
'''INSERT INTO params
(version_id, param_key, short_flag, long_flag, description, category, param_type,
default_value, options, min_value, max_value, step, unit, is_important, affects_vram, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
(vid, data['param_key'], data.get('short_flag', ''), data['long_flag'],
data.get('description', ''), data.get('category', 'common'),
data.get('param_type', 'string'), data.get('default_value', ''),
options, data.get('min_value'), data.get('max_value'),
data.get('step'), data.get('unit'), data.get('is_important', 0),
data.get('affects_vram', 0), data.get('sort_order', 0))
)
db.commit()
db.close()
return jsonify({'status': 'ok'})
@app.route('/api/admin/params/<int:pid>', methods=['PUT', 'DELETE'])
def admin_param_edit(pid):
db = get_db()
if request.method == 'PUT':
data = request.json
options = data.get('options')
if isinstance(options, list):
options = json.dumps(options)
db.execute(
'''UPDATE params SET
param_key=?, short_flag=?, long_flag=?, description=?, category=?, param_type=?,
default_value=?, options=?, min_value=?, max_value=?, step=?, unit=?,
is_important=?, affects_vram=?, sort_order=? WHERE id=?''',
(data['param_key'], data.get('short_flag', ''), data['long_flag'],
data.get('description', ''), data.get('category', 'common'),
data.get('param_type', 'string'), data.get('default_value', ''),
options, data.get('min_value'), data.get('max_value'),
data.get('step'), data.get('unit'), data.get('is_important', 0),
data.get('affects_vram', 0), data.get('sort_order', 0), pid)
)
db.commit()
db.close()
return jsonify({'status': 'ok'})
elif request.method == 'DELETE':
db.execute('DELETE FROM params WHERE id=?', (pid,))
db.commit()
db.close()
return jsonify({'status': 'ok'})
@app.route('/api/admin/settings', methods=['GET', 'PUT'])
def admin_settings():
db = get_db()
if request.method == 'GET':
settings = db.execute('SELECT * FROM settings').fetchall()
result = {s['key']: s['value'] for s in settings}
db.close()
return jsonify(result)
elif request.method == 'PUT':
data = request.json
for key, value in data.items():
db.execute(
'INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)',
(key, str(value))
)
db.commit()
db.close()
return jsonify({'status': 'ok'})
if __name__ == '__main__':
init_db()
app.run(host='0.0.0.0', port=16052, debug=False)