Files
metona-ollama-desktop/src/renderer/components/preset-bar.ts
T
thzxx 7ca0e33d77 feat: TypeScript + Electron v2 重构 - 纯桌面版
- 全面迁移到 TypeScript,严格类型定义
- 放弃 Web 版,专注 Electron 桌面应用
- 主进程模块化:main.ts, preload.ts, menu.ts, tray.ts, ipc.ts, utils.ts
- 渲染进程完整迁移所有功能组件
- 删除 PWA 相关文件 (sw.js, manifest.json)
- 删除 Web 版降级逻辑
- 保留所有核心功能:流式对话、多模型、Think推理、多模态、RAG知识库、Agent预设、历史管理
- 保留 Windows 11 Fluent Design 暗色主题样式
2026-04-06 03:04:20 +08:00

188 lines
6.7 KiB
TypeScript
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.
/**
* PresetBar - 预设栏组件
*/
import { state, KEYS } from '../state/state.js';
import {
getPresets, getActivePresetId, activatePreset,
createPreset, updatePreset, deletePreset
} from '../services/preset-manager.js';
import { showToast } from './toast.js';
import type { Preset } from '../types.js';
let presetBarEl: HTMLElement;
let presetListEl: HTMLElement;
let editingPresetId: string | null = null;
function escapeHtml(str: string): string {
const map: Record<string, string> = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
return String(str).replace(/[&<>"']/g, c => map[c]);
}
export function initPresetBar(): void {
const modelBar = document.querySelector('.model-bar');
if (!modelBar) return;
const bar = document.createElement('div');
bar.className = 'preset-bar';
bar.innerHTML = `
<div class="preset-list" id="presetList"></div>
<button class="preset-add-btn" id="btnAddPreset" title="管理预设">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
</button>
`;
modelBar.after(bar);
presetBarEl = bar;
presetListEl = bar.querySelector('#presetList')!;
bar.querySelector('#btnAddPreset')!.addEventListener('click', () => openPresetModal());
state.on('activePresetId', renderPresetBar);
state.on('presets', renderPresetBar);
renderPresetBar();
}
function renderPresetBar(): void {
if (!presetListEl) return;
const presets = getPresets();
const activeId = getActivePresetId();
presetListEl.innerHTML = '';
for (const preset of presets) {
const chip = document.createElement('button');
chip.className = 'preset-chip' + (preset.id === activeId ? ' active' : '');
chip.title = preset.systemPrompt ? preset.systemPrompt.slice(0, 100) : '无系统提示词';
chip.innerHTML = `<span class="preset-chip-icon">${preset.icon}</span><span class="preset-chip-name">${escapeHtml(preset.name)}</span>`;
chip.addEventListener('click', () => handlePresetClick(preset.id));
chip.addEventListener('contextmenu', (e) => {
if (!preset.builtIn) {
e.preventDefault();
editingPresetId = preset.id;
openPresetModal(preset);
}
});
presetListEl.appendChild(chip);
}
}
async function handlePresetClick(presetId: string): Promise<void> {
const currentId = getActivePresetId();
if (presetId === currentId) return;
await activatePreset(presetId);
const preset = getPresets().find(p => p.id === presetId);
if (preset) {
showToast(`已切换到 ${preset.icon} ${preset.name}`, 'success', 2000);
}
}
function openPresetModal(editPreset?: Preset): void {
const modal = document.querySelector('#presetModal') as HTMLElement;
const titleEl = modal.querySelector('#presetModalTitle')!;
const nameInput = modal.querySelector('#inputPresetName') as HTMLInputElement;
const iconInput = modal.querySelector('#inputPresetIcon') as HTMLInputElement;
const spInput = modal.querySelector('#inputPresetSystemPrompt') as HTMLTextAreaElement;
const tempInput = modal.querySelector('#inputPresetTemp') as HTMLInputElement;
const tempVal = modal.querySelector('#presetTempValue')!;
const ctxInput = modal.querySelector('#inputPresetNumCtx') as HTMLInputElement;
const thinkCheck = modal.querySelector('#togglePresetThink') as HTMLInputElement;
const btnSave = modal.querySelector('#btnSavePreset') as HTMLButtonElement;
const btnDelete = modal.querySelector('#btnDeletePreset') as HTMLElement;
if (editPreset && editPreset.id) {
editingPresetId = editPreset.id;
titleEl.textContent = '✏️ 编辑预设';
nameInput.value = editPreset.name;
iconInput.value = editPreset.icon;
spInput.value = editPreset.systemPrompt;
tempInput.value = String(editPreset.temperature ?? 0.7);
tempVal.textContent = (editPreset.temperature ?? 0.7).toFixed(1);
ctxInput.value = String(editPreset.numCtx || 24576);
thinkCheck.checked = editPreset.think ?? false;
btnDelete.style.display = editPreset.builtIn ? 'none' : '';
btnSave.textContent = '保存';
} else {
editingPresetId = null;
titleEl.textContent = '✨ 新建预设';
nameInput.value = '';
iconInput.value = '🤖';
spInput.value = '';
tempInput.value = '0.7';
tempVal.textContent = '0.7';
ctxInput.value = '24576';
thinkCheck.checked = false;
btnDelete.style.display = 'none';
btnSave.textContent = '创建';
}
modal.style.display = '';
nameInput.focus();
tempInput.oninput = () => {
tempVal.textContent = parseFloat(tempInput.value).toFixed(1);
};
}
export function initPresetModal(): void {
const modal = document.querySelector('#presetModal') as HTMLElement;
if (!modal) return;
modal.querySelector('#btnClosePresetModal')!.addEventListener('click', () => {
modal.style.display = 'none';
});
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.style.display = 'none';
});
modal.querySelector('#btnSavePreset')!.addEventListener('click', async () => {
const name = (modal.querySelector('#inputPresetName') as HTMLInputElement).value.trim();
if (!name) { showToast('请输入预设名称', 'warning'); return; }
const data = {
name,
icon: (modal.querySelector('#inputPresetIcon') as HTMLInputElement).value.trim() || '🤖',
systemPrompt: (modal.querySelector('#inputPresetSystemPrompt') as HTMLTextAreaElement).value.trim(),
temperature: parseFloat((modal.querySelector('#inputPresetTemp') as HTMLInputElement).value) || 0.7,
numCtx: parseInt((modal.querySelector('#inputPresetNumCtx') as HTMLInputElement).value) || 24576,
think: (modal.querySelector('#togglePresetThink') as HTMLInputElement).checked
};
if (editingPresetId) {
await updatePreset(editingPresetId, data);
showToast('预设已更新', 'success', 2000);
if (editingPresetId === getActivePresetId()) {
await activatePreset(editingPresetId);
}
} else {
const created = await createPreset(data);
await activatePreset(created.id);
showToast(`已创建并激活 ${data.icon} ${data.name}`, 'success', 2000);
}
modal.style.display = 'none';
editingPresetId = null;
});
modal.querySelector('#btnDeletePreset')!.addEventListener('click', async () => {
if (!editingPresetId) return;
const preset = getPresets().find(p => p.id === editingPresetId);
if (!preset) return;
if (confirm(`确定删除预设 "${preset.icon} ${preset.name}" `)) {
await deletePreset(editingPresetId);
showToast('预设已删除', 'info', 2000);
modal.style.display = 'none';
editingPresetId = null;
}
});
}