2 Commits
Author SHA1 Message Date
hz4th_coder 5169d368bf fix: v1.4.2
1. 填了模型路径后红色线框变橙色(已更改标记)
   - setParam时实时更新线框样式,保留焦点不重建DOM
2. 二进制程序区域宽度缩小一半,运行模式区域扩大
2026-07-20 00:30:21 +08:00
hz4th_coder c12c2240a2 fix: v1.4.1 - 三项优化
1. 选模型后参数自适应调整:
   - n_gpu_layers 设为模型实际层数(而非'all')
   - 多卡时 split_mode 设为 tensor
   - tensor_split 按各卡显存比例自动计算
   - 增减/切换显卡时也自动更新 tensor_split

2. 模型路径为空时红色线框标记

3. 参数值非默认时用橙色线框标记(非红非绿)
2026-07-20 00:21:09 +08:00
3 changed files with 47 additions and 10 deletions
+4
View File
@@ -209,6 +209,8 @@ header h1 {
.top-controls { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 20px; }
.control-group { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 200px; }
.control-group.control-group-small { flex: 0.5; min-width: 140px; }
.control-group.control-group-large { flex: 2; min-width: 300px; }
.control-group label { font-size: 0.85em; color: var(--text-dim); font-weight: 500; }
@@ -378,6 +380,8 @@ header h1 {
transition: border-color 0.15s;
}
.param-item:hover { border-color: var(--accent2); }
.param-item.param-empty { border-color: var(--accent); border-width: 2px; }
.param-item.param-changed { border-color: #e9c46a; }
.param-item.modified { border-color: var(--accent2); }
/* keep old .modified for backwards compat but remove red styling */
+2 -2
View File
@@ -36,14 +36,14 @@
<label>llama.cpp 版本</label>
<select id="version-select" onchange="onVersionChange()"></select>
</div>
<div class="control-group">
<div class="control-group control-group-large">
<label>运行模式</label>
<div class="mode-switch">
<button class="mode-btn active" data-mode="gpu" onclick="switchMode('gpu')">🎮 GPU 模式</button>
<button class="mode-btn" data-mode="gpu_cpu" onclick="switchMode('gpu_cpu')">🎮+💻 GPU+CPU 模式</button>
</div>
</div>
<div class="control-group">
<div class="control-group control-group-small">
<label>二进制程序</label>
<select id="binary-select">
<option value="llama-server">llama-server</option>
+41 -8
View File
@@ -79,9 +79,9 @@ function renderGpuSlots() {
document.getElementById('btn-add-gpu').style.display = state.gpuSlots.length >= 4 ? 'none' : 'block';
updateEstimate();
}
function addGpuSlot() { if (state.gpuSlots.length >= 4) return; const dg = state.gpus.find(g => g.name === 'RTX 3090') || state.gpus[0]; state.gpuSlots.push({ ...dg }); renderGpuSlots(); generateCommand(); }
function removeGpuSlot(i) { state.gpuSlots.splice(i, 1); renderGpuSlots(); generateCommand(); }
function updateGpuSlot(i, name) { const g = state.gpus.find(g => g.name === name); if (g) state.gpuSlots[i] = { ...g }; renderGpuSlots(); generateCommand(); }
function addGpuSlot() { if (state.gpuSlots.length >= 4) return; const dg = state.gpus.find(g => g.name === 'RTX 3090') || state.gpus[0]; state.gpuSlots.push({ ...dg }); renderGpuSlots(); if (state.selectedModel) applyModelDefaults(state.selectedModel); generateCommand(); }
function removeGpuSlot(i) { state.gpuSlots.splice(i, 1); renderGpuSlots(); if (state.gpuSlots.length > 1) { if (state.selectedModel) applyModelDefaults(state.selectedModel); } else { state.paramValues['split_mode'] = state.params.find(p=>p.param_key==='split_mode')?.default_value || 'layer'; state.paramValues['tensor_split'] = state.params.find(p=>p.param_key==='tensor_split')?.default_value || ''; } generateCommand(); }
function updateGpuSlot(i, name) { const g = state.gpus.find(g => g.name === name); if (g) state.gpuSlots[i] = { ...g }; renderGpuSlots(); if (state.gpuSlots.length > 1 && state.selectedModel) applyModelDefaults(state.selectedModel); generateCommand(); }
// ===== Model Selection =====
function filterBaseModels() {
@@ -153,15 +153,29 @@ function applyModelDefaults(m) {
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';
// Set n_gpu_layers to model's layer count (not 'all', use actual number)
if (m.layers && m.layers > 0) {
state.paramValues['n_gpu_layers'] = String(m.layers);
}
// Multi-GPU: set split_mode to tensor, compute tensor_split by VRAM ratio
if (state.gpuSlots.length > 1) {
state.paramValues['split_mode'] = 'tensor';
updateTensorSplit();
}
// Update param UI if currently visible
renderParams();
generateCommand();
}
function updateTensorSplit() {
if (state.gpuSlots.length <= 1) return;
const totalVram = state.gpuSlots.reduce((s, g) => s + (g.vram_mb || 0), 0);
if (totalVram > 0) {
const ratios = state.gpuSlots.map(g => (g.vram_mb / totalVram).toFixed(2));
state.paramValues['tensor_split'] = ratios.join(',');
}
}
// ===== Parameter Rendering =====
function switchTab(cat) {
state.currentTab = cat;
@@ -230,7 +244,13 @@ function renderParams() {
function renderParamItem(p) {
const val = state.paramValues[p.param_key];
const mc = '';
// Determine item class: empty-required (red), modified (orange), or normal
let itemClass = '';
if (p.param_key === 'model' && (!val || val.trim() === '')) {
itemClass = 'param-empty';
} else if (isParamModified(p, val)) {
itemClass = 'param-changed';
}
const vb = p.affects_vram ? '<span class="vram-badge">⚡显存</span>' : '';
// Show both short and long flag
const shortFlag = p.short_flag || '';
@@ -256,7 +276,7 @@ function renderParamItem(p) {
inp = `<input type="text" value="${val}" onchange="setParam('${p.param_key}', this.value)" data-key="${p.param_key}">`;
}
const uh = p.unit ? `<span class="param-unit">${p.unit}</span>` : '';
return `<div class="param-item ${mc}" data-key="${p.param_key}">${flagHtml}${vb}<span class="param-desc-text">${p.description}</span>${inp}${uh}</div>`;
return `<div class="param-item ${itemClass}" data-key="${p.param_key}">${flagHtml}${vb}<span class="param-desc-text">${p.description}</span>${inp}${uh}</div>`;
}
function isParamModified(p, val) {
@@ -267,6 +287,19 @@ function isParamModified(p, val) {
function setParam(key, value) {
state.paramValues[key] = value;
// Update this item's visual state without full re-render (preserves focus)
const item = document.querySelector(`.param-item[data-key="${key}"]`);
if (item) {
const p = state.params.find(p => p.param_key === key);
if (p) {
item.classList.remove('param-empty', 'param-changed');
if (p.param_key === 'model' && (!value || String(value).trim() === '')) {
item.classList.add('param-empty');
} else if (isParamModified(p, value)) {
item.classList.add('param-changed');
}
}
}
// If on 'modified' tab, re-render to update the list
if (state.currentTab === 'modified') renderParams();
generateCommand(); updateEstimate();