import { currentPersona, setCurrentPersona, sessionId } from './state.js'; import { initChat } from './chat.js'; import { initWizard } from './utils.js'; export let personaIndex = new Map(); let createWizard; export function highlightPersona(personaId) { document.querySelectorAll('.persona-card').forEach(c => { c.classList.toggle('active', c.dataset.id === personaId); }); } export async function loadPersonas() { const res = await fetch('/personas/'); const personas = await res.json(); personaIndex = new Map(personas.map(p => [p.persona_id, p])); const bar = document.getElementById('personaBar'); bar.innerHTML = ''; personas.forEach(p => { const card = document.createElement('div'); card.className = 'persona-card' + (p.persona_id === currentPersona ? ' active' : ''); card.dataset.id = p.persona_id; const isCard = p.persona_id.startsWith('card_'); const isCustomPersona = p.custom && !isCard; const avatar = p.avatar_path ? `/static/${p.avatar_path}` : ''; card.innerHTML = ` ${avatar ? `` : `${p.emoji}`} ${p.name} ${p.custom ? `` : ''} ${isCard ? `` : ''} ${isCustomPersona ? `` : ''} `; card.addEventListener('click', () => selectPersona(p.persona_id)); card.querySelector('.del-btn')?.addEventListener('click', async (e) => { e.stopPropagation(); await fetch(`/personas/${p.persona_id}`, { method: 'DELETE' }); if (currentPersona === p.persona_id) await selectPersona('default'); loadPersonas(); }); card.querySelector('.edit-btn')?.addEventListener('click', async (e) => { e.stopPropagation(); const cardId = p.persona_id.slice(5); const r = await fetch(`/characters/${cardId}`); const data = await r.json(); document.getElementById('editCardId').value = cardId; document.getElementById('editName').value = data.name || ''; document.getElementById('editDescription').value = data.description || ''; document.getElementById('editPersonality').value = data.personality || ''; document.getElementById('editScenario').value = data.scenario || ''; document.getElementById('editFirstMes').value = data.first_mes || ''; document.getElementById('editMesExample').value = data.mes_example || ''; document.getElementById('editAppearance').value = data.appearance_tags || ''; document.getElementById('editLora').value = data.lora_name || ''; document.getElementById('editLoraWeight').value = data.lora_weight ?? 0.8; document.getElementById('cardEditOverlay').classList.add('open'); }); card.querySelector('.edit-persona-btn')?.addEventListener('click', async (e) => { e.stopPropagation(); const r = await fetch(`/personas/${p.persona_id}`); const data = await r.json(); document.getElementById('editPersonaId').value = p.persona_id; document.getElementById('editPName').value = data.name || ''; document.getElementById('editPEmoji').value = data.emoji || ''; document.getElementById('editPDesc').value = data.description || ''; document.getElementById('editPPersonality').value = data.personality || ''; document.getElementById('editPScenario').value = data.scenario || ''; document.getElementById('editPFirstMes').value = data.first_mes || ''; document.getElementById('editPMesExample').value = data.mes_example || ''; document.getElementById('editPLorebook').value = data.lorebook_json || '[]'; document.getElementById('editPPrompt').value = data.prompt || ''; document.getElementById('editPSdEnabled').checked = !!data.sd_enabled; document.getElementById('editPLora').value = data.lora_name || ''; document.getElementById('editPLoraWeight').value = data.lora_weight ?? 0.8; document.getElementById('editPAppearance').value = data.appearance_tags || ''; document.getElementById('personaEditOverlay').classList.add('open'); }); bar.appendChild(card); }); const addBtn = document.createElement('button'); addBtn.type = 'button'; addBtn.className = 'persona-add'; addBtn.innerHTML = '➕Создать'; addBtn.addEventListener('click', () => { document.getElementById('modalOverlay').classList.add('open'); createWizard?.reset(); }); bar.appendChild(addBtn); const importBtn = document.createElement('button'); importBtn.type = 'button'; importBtn.className = 'card-import-btn'; importBtn.innerHTML = '📥Chub'; importBtn.addEventListener('click', () => document.getElementById('cardModalOverlay').classList.add('open')); bar.appendChild(importBtn); } export async function selectPersona(personaId) { setCurrentPersona(personaId); highlightPersona(personaId); if (sessionId) { await fetch(`/sessions/${sessionId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ persona_id: personaId }), }); await initChat(); } } export function initPersonaModals() { const createModal = document.getElementById('modalOverlay'); createWizard = initWizard(createModal.querySelector('.modal-wizard'), { totalSteps: 3, validateStep(step) { if (step !== 1) return true; const id = document.getElementById('pId').value.trim(); const name = document.getElementById('pName').value.trim(); if (!id || !name) { alert('Заполни ID и имя'); return false; } return true; }, }); document.getElementById('modalCancel').addEventListener('click', () => { createModal.classList.remove('open'); createWizard.reset(); }); document.getElementById('cardModalCancel').addEventListener('click', () => { document.getElementById('cardModalOverlay').classList.remove('open'); }); document.getElementById('cardEditCancel').addEventListener('click', () => { document.getElementById('cardEditOverlay').classList.remove('open'); }); // custom persona editor (reuses create modal fields) const personaEditCancel = document.getElementById('personaEditCancel'); if (personaEditCancel) { personaEditCancel.addEventListener('click', () => { document.getElementById('personaEditOverlay').classList.remove('open'); }); } document.getElementById('modalSave').addEventListener('click', async () => { const data = { persona_id: document.getElementById('pId').value.trim(), name: document.getElementById('pName').value.trim(), emoji: document.getElementById('pEmoji').value.trim() || '🤖', description: document.getElementById('pDesc').value.trim(), prompt: document.getElementById('pPrompt').value.trim(), sd_enabled: document.getElementById('pSdEnabled').checked, lora_name: document.getElementById('pLora').value.trim(), appearance_tags: document.getElementById('pAppearance').value.trim(), personality: document.getElementById('pPersonality').value.trim(), scenario: document.getElementById('pScenario').value.trim(), first_mes: document.getElementById('pFirstMes').value.trim(), mes_example: document.getElementById('pMesExample').value.trim(), lorebook_json: document.getElementById('pLorebook').value.trim() || '[]', }; if (!data.persona_id || !data.name) { alert('Заполни ID и имя'); return; } await fetch('/personas/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); document.getElementById('modalOverlay').classList.remove('open'); createWizard.reset(); await loadPersonas(); await selectPersona(data.persona_id); }); document.getElementById('cardEditSave').addEventListener('click', async () => { const cardId = document.getElementById('editCardId').value; const body = { name: document.getElementById('editName').value.trim() || undefined, description: document.getElementById('editDescription').value.trim() || undefined, personality: document.getElementById('editPersonality').value.trim() || undefined, scenario: document.getElementById('editScenario').value.trim() || undefined, first_mes: document.getElementById('editFirstMes').value.trim() || undefined, mes_example: document.getElementById('editMesExample').value.trim() || undefined, appearance_tags: document.getElementById('editAppearance').value.trim() || undefined, lora_name: document.getElementById('editLora').value.trim() || undefined, lora_weight: parseFloat(document.getElementById('editLoraWeight').value) || undefined, }; const res = await fetch(`/characters/${cardId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) { alert('Ошибка сохранения'); return; } const avatarFile = document.getElementById('editCardAvatar')?.files?.[0]; if (avatarFile) { const form = new FormData(); form.append('file', avatarFile); await fetch(`/characters/${cardId}/avatar`, { method: 'POST', body: form }); document.getElementById('editCardAvatar').value = ''; } document.getElementById('cardEditOverlay').classList.remove('open'); await loadPersonas(); }); document.getElementById('cardModalImport').addEventListener('click', async () => { const fileInput = document.getElementById('cardFile'); if (!fileInput.files?.length) { alert('Выберите файл карточки (JSON или PNG)'); return; } const form = new FormData(); form.append('file', fileInput.files[0]); form.append('lora_name', document.getElementById('cardLora').value.trim()); form.append('lora_weight', document.getElementById('cardLoraWeight').value || '0.8'); const res = await fetch('/characters/import', { method: 'POST', body: form }); const data = await res.json(); if (!res.ok) { alert(data.detail || 'Ошибка импорта'); return; } document.getElementById('cardModalOverlay').classList.remove('open'); fileInput.value = ''; await loadPersonas(); await selectPersona(data.persona_id); }); const personaEditSave = document.getElementById('personaEditSave'); if (personaEditSave) { personaEditSave.addEventListener('click', async () => { const personaId = document.getElementById('editPersonaId').value; const body = { name: document.getElementById('editPName').value.trim() || undefined, emoji: document.getElementById('editPEmoji').value.trim() || undefined, description: document.getElementById('editPDesc').value.trim() || undefined, personality: document.getElementById('editPPersonality').value.trim() || undefined, scenario: document.getElementById('editPScenario').value.trim() || undefined, first_mes: document.getElementById('editPFirstMes').value.trim() || undefined, mes_example: document.getElementById('editPMesExample').value.trim() || undefined, lorebook_json: document.getElementById('editPLorebook').value.trim() || undefined, prompt: document.getElementById('editPPrompt').value.trim() || undefined, sd_enabled: document.getElementById('editPSdEnabled').checked, lora_name: document.getElementById('editPLora').value.trim() || undefined, lora_weight: parseFloat(document.getElementById('editPLoraWeight').value) || undefined, appearance_tags: document.getElementById('editPAppearance').value.trim() || undefined, }; const res = await fetch(`/personas/${personaId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) { alert('Ошибка сохранения'); return; } const avatarFile = document.getElementById('editPAvatar')?.files?.[0]; if (avatarFile) { const form = new FormData(); form.append('file', avatarFile); await fetch(`/personas/${personaId}/avatar`, { method: 'POST', body: form }); document.getElementById('editPAvatar').value = ''; } document.getElementById('personaEditOverlay').classList.remove('open'); await loadPersonas(); }); } }