Files
llama-cmd-gen/static/js/admin.js
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

395 lines
16 KiB
JavaScript

// ===== Admin State =====
let adminState = {
gpus: [],
versions: [],
params: [],
currentVersionId: null,
settings: {},
};
// ===== Init =====
async function adminInit() {
await loadAdminGpus();
await loadAdminVersions();
await loadAdminSettings();
}
// ===== Tab Switching =====
function adminSwitchTab(tab) {
document.querySelectorAll('.admin-tab').forEach(t => {
t.classList.toggle('active', t.dataset.tab === tab);
});
document.querySelectorAll('.admin-section').forEach(s => {
s.classList.toggle('active', s.id === 'admin-' + tab);
});
}
// ===== GPU Management =====
async function loadAdminGpus() {
const res = await fetch('/api/admin/gpus');
adminState.gpus = await res.json();
renderGpuTable();
}
function renderGpuTable() {
const tbody = document.getElementById('gpu-table-body');
tbody.innerHTML = adminState.gpus.map(g => `
<tr>
<td>${g.id}</td>
<td><input type="text" value="${g.name}" onchange="updateGpu(${g.id}, 'name', this.value)"></td>
<td><input type="number" value="${g.vram_mb}" onchange="updateGpu(${g.id}, 'vram_mb', this.value)"></td>
<td><input type="text" value="${g.compute_capability || ''}" onchange="updateGpu(${g.id}, 'compute_capability', this.value)"></td>
<td><input type="text" value="${g.description || ''}" onchange="updateGpu(${g.id}, 'description', this.value)"></td>
<td><input type="number" value="${g.sort_order}" onchange="updateGpu(${g.id}, 'sort_order', this.value)" style="width:60px"></td>
<td><button class="btn-action btn-delete" onclick="deleteGpu(${g.id})">删除</button></td>
</tr>
`).join('');
}
async function addGpu() {
const data = {
name: document.getElementById('gpu-name').value,
vram_mb: parseInt(document.getElementById('gpu-vram').value) || 0,
compute_capability: document.getElementById('gpu-cc').value,
description: document.getElementById('gpu-desc').value,
sort_order: parseInt(document.getElementById('gpu-order').value) || 0,
};
if (!data.name || !data.vram_mb) {
alert('请填写GPU名称和显存大小');
return;
}
await fetch('/api/admin/gpus', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
// Clear inputs
document.getElementById('gpu-name').value = '';
document.getElementById('gpu-vram').value = '';
document.getElementById('gpu-cc').value = '';
document.getElementById('gpu-desc').value = '';
document.getElementById('gpu-order').value = '0';
await loadAdminGpus();
}
async function updateGpu(id, field, value) {
const gpu = adminState.gpus.find(g => g.id === id);
if (!gpu) return;
const data = {
name: gpu.name,
vram_mb: gpu.vram_mb,
compute_capability: gpu.compute_capability,
description: gpu.description,
sort_order: gpu.sort_order,
};
if (field === 'vram_mb' || field === 'sort_order') {
data[field] = parseInt(value) || 0;
} else {
data[field] = value;
}
await fetch(`/api/admin/gpus/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
await loadAdminGpus();
}
async function deleteGpu(id) {
if (!confirm('确定删除这个GPU吗?')) return;
await fetch(`/api/admin/gpus/${id}`, { method: 'DELETE' });
await loadAdminGpus();
}
// ===== Version Management =====
async function loadAdminVersions() {
const res = await fetch('/api/admin/versions');
adminState.versions = await res.json();
renderVersionTable();
// Populate version select in params tab
const sel = document.getElementById('admin-param-version');
sel.innerHTML = adminState.versions.map(v => `<option value="${v.id}">${v.version_tag}</option>`).join('');
if (adminState.versions.length > 0) {
adminState.currentVersionId = adminState.versions[0].id;
sel.value = adminState.currentVersionId;
await loadAdminParams();
}
}
function renderVersionTable() {
const tbody = document.getElementById('version-table-body');
tbody.innerHTML = adminState.versions.map(v => `
<tr>
<td>${v.id}</td>
<td><input type="text" value="${v.version_tag}" onchange="updateVersion(${v.id}, 'version_tag', this.value)"></td>
<td><input type="text" value="${v.description || ''}" onchange="updateVersion(${v.id}, 'description', this.value)"></td>
<td><input type="date" value="${v.release_date || ''}" onchange="updateVersion(${v.id}, 'release_date', this.value)"></td>
<td>
<select onchange="updateVersion(${v.id}, 'is_active', this.value)">
<option value="1" ${v.is_active ? 'selected' : ''}>是</option>
<option value="0" ${!v.is_active ? 'selected' : ''}>否</option>
</select>
</td>
<td><input type="number" value="${v.sort_order}" onchange="updateVersion(${v.id}, 'sort_order', this.value)" style="width:60px"></td>
<td><button class="btn-action btn-delete" onclick="deleteVersion(${v.id})">删除</button></td>
</tr>
`).join('');
}
async function addVersion() {
const data = {
version_tag: document.getElementById('ver-tag').value,
description: document.getElementById('ver-desc').value,
release_date: document.getElementById('ver-date').value,
sort_order: parseInt(document.getElementById('ver-order').value) || 0,
};
if (!data.version_tag) {
alert('请填写版本标签');
return;
}
await fetch('/api/admin/versions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
document.getElementById('ver-tag').value = '';
document.getElementById('ver-desc').value = '';
document.getElementById('ver-date').value = '';
document.getElementById('ver-order').value = '0';
await loadAdminVersions();
}
async function updateVersion(id, field, value) {
const ver = adminState.versions.find(v => v.id === id);
if (!ver) return;
const data = {
version_tag: ver.version_tag,
description: ver.description,
release_date: ver.release_date,
is_active: ver.is_active,
sort_order: ver.sort_order,
};
if (field === 'is_active' || field === 'sort_order') {
data[field] = parseInt(value);
} else {
data[field] = value;
}
await fetch(`/api/admin/versions/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
await loadAdminVersions();
}
async function deleteVersion(id) {
if (!confirm('删除版本将同时删除该版本的所有参数,确定继续吗?')) return;
await fetch(`/api/admin/versions/${id}`, { method: 'DELETE' });
await loadAdminVersions();
}
// ===== Parameter Management =====
async function loadAdminParams() {
const sel = document.getElementById('admin-param-version');
adminState.currentVersionId = parseInt(sel.value);
if (!adminState.currentVersionId) return;
const res = await fetch(`/api/admin/versions/${adminState.currentVersionId}/params`);
adminState.params = await res.json();
renderParamTable();
}
function renderParamTable() {
const tbody = document.getElementById('param-table-body');
tbody.innerHTML = adminState.params.map(p => {
const options = Array.isArray(p.options) ? p.options.join(', ') : (p.options || '');
const flag = p.short_flag ? `${p.short_flag}/${p.long_flag}` : p.long_flag;
return `
<tr>
<td>${p.id}</td>
<td><input type="text" value="${p.param_key}" onchange="updateParam(${p.id}, 'param_key', this.value)"></td>
<td><input type="text" value="${flag}" style="width:150px" onchange="updateParamFlag(${p.id}, this.value)"></td>
<td><input type="text" value="${p.description || ''}" onchange="updateParam(${p.id}, 'description', this.value)"></td>
<td>
<select onchange="updateParam(${p.id}, 'category', this.value)">
<option value="common" ${p.category === 'common' ? 'selected' : ''}>通用</option>
<option value="sampling" ${p.category === 'sampling' ? 'selected' : ''}>采样</option>
<option value="server" ${p.category === 'server' ? 'selected' : ''}>服务器</option>
<option value="model_source" ${p.category === 'model_source' ? 'selected' : ''}>模型来源</option>
<option value="lora" ${p.category === 'lora' ? 'selected' : ''}>LoRA</option>
<option value="logging" ${p.category === 'logging' ? 'selected' : ''}>日志</option>
<option value="advanced" ${p.category === 'advanced' ? 'selected' : ''}>高级</option>
</select>
</td>
<td>
<select onchange="updateParam(${p.id}, 'param_type', this.value)">
<option value="string" ${p.param_type === 'string' ? 'selected' : ''}>字符串</option>
<option value="number" ${p.param_type === 'number' ? 'selected' : ''}>数字</option>
<option value="boolean" ${p.param_type === 'boolean' ? 'selected' : ''}>布尔</option>
<option value="select" ${p.param_type === 'select' ? 'selected' : ''}>选择</option>
</select>
</td>
<td><input type="text" value="${p.default_value || ''}" onchange="updateParam(${p.id}, 'default_value', this.value)"></td>
<td>
<select onchange="updateParam(${p.id}, 'is_important', this.value)">
<option value="0" ${!p.is_important ? 'selected' : ''}>否</option>
<option value="1" ${p.is_important ? 'selected' : ''}>是</option>
</select>
</td>
<td>
<select onchange="updateParam(${p.id}, 'affects_vram', this.value)">
<option value="0" ${!p.affects_vram ? 'selected' : ''}>否</option>
<option value="1" ${p.affects_vram ? 'selected' : ''}>是</option>
</select>
</td>
<td><button class="btn-action btn-delete" onclick="deleteParam(${p.id})">删除</button></td>
</tr>
`;
}).join('');
}
async function addParam() {
const optionsStr = document.getElementById('param-options').value;
const options = optionsStr ? optionsStr.split(',').map(s => s.trim()).filter(s => s) : null;
const data = {
param_key: document.getElementById('param-key').value,
short_flag: document.getElementById('param-short').value,
long_flag: document.getElementById('param-long').value,
description: document.getElementById('param-desc').value,
category: document.getElementById('param-category').value,
param_type: document.getElementById('param-type').value,
default_value: document.getElementById('param-default').value,
options: options,
min_value: document.getElementById('param-min').value ? parseFloat(document.getElementById('param-min').value) : null,
max_value: document.getElementById('param-max').value ? parseFloat(document.getElementById('param-max').value) : null,
step: document.getElementById('param-step').value ? parseFloat(document.getElementById('param-step').value) : null,
unit: document.getElementById('param-unit').value,
is_important: parseInt(document.getElementById('param-important').value) || 0,
affects_vram: parseInt(document.getElementById('param-vram').value) || 0,
sort_order: parseInt(document.getElementById('param-order').value) || 0,
};
if (!data.param_key || !data.long_flag) {
alert('请填写参数键和长标志');
return;
}
await fetch(`/api/admin/versions/${adminState.currentVersionId}/params`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
// Clear inputs
['param-key', 'param-short', 'param-long', 'param-desc', 'param-default',
'param-options', 'param-min', 'param-max', 'param-step', 'param-unit'].forEach(id => {
document.getElementById(id).value = '';
});
document.getElementById('param-important').value = '0';
document.getElementById('param-vram').value = '0';
document.getElementById('param-order').value = '0';
await loadAdminParams();
}
async function updateParam(id, field, value) {
const p = adminState.params.find(p => p.id === id);
if (!p) return;
const data = {
param_key: p.param_key,
short_flag: p.short_flag,
long_flag: p.long_flag,
description: p.description,
category: p.category,
param_type: p.param_type,
default_value: p.default_value,
options: p.options,
min_value: p.min_value,
max_value: p.max_value,
step: p.step,
unit: p.unit,
is_important: p.is_important,
affects_vram: p.affects_vram,
sort_order: p.sort_order,
};
if (field === 'is_important' || field === 'affects_vram' || field === 'sort_order') {
data[field] = parseInt(value);
} else {
data[field] = value;
}
await fetch(`/api/admin/params/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
await loadAdminParams();
}
async function updateParamFlag(id, value) {
const p = adminState.params.find(p => p.id === id);
if (!p) return;
// Parse "short/long" format
const parts = value.split('/').map(s => s.trim());
const data = {
param_key: p.param_key,
short_flag: parts[0] || '',
long_flag: parts[1] || parts[0] || '',
description: p.description,
category: p.category,
param_type: p.param_type,
default_value: p.default_value,
options: p.options,
min_value: p.min_value,
max_value: p.max_value,
step: p.step,
unit: p.unit,
is_important: p.is_important,
affects_vram: p.affects_vram,
sort_order: p.sort_order,
};
await fetch(`/api/admin/params/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
await loadAdminParams();
}
async function deleteParam(id) {
if (!confirm('确定删除这个参数吗?')) return;
await fetch(`/api/admin/params/${id}`, { method: 'DELETE' });
await loadAdminParams();
}
// ===== Settings =====
async function loadAdminSettings() {
const res = await fetch('/api/admin/settings');
adminState.settings = await res.json();
renderSettings();
}
function renderSettings() {
const container = document.getElementById('settings-form');
container.innerHTML = Object.entries(adminState.settings).map(([key, value]) => `
<div class="setting-item">
<label>${key}</label>
<input type="text" value="${value}" onchange="adminState.settings['${key}'] = this.value">
</div>
`).join('');
}
async function saveSettings() {
await fetch('/api/admin/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(adminState.settings)
});
alert('设置已保存');
}
// ===== Init =====
window.addEventListener('DOMContentLoaded', adminInit);