Fixed RPG

This commit is contained in:
2026-06-01 07:44:38 +03:00
parent 600ad78f05
commit d4cd8f02f4
30 changed files with 1516 additions and 816 deletions
+154 -26
View File
@@ -1,10 +1,25 @@
import { currentPersona, setCurrentPersona, sessionId } from './state.js';
import { initChat } from './chat.js';
import { initWizard } from './utils.js';
import { initWizard, fillGreetingSelect, getSelectedGreeting } from './utils.js';
export let personaIndex = new Map();
function parseAlternateGreetings(p) {
if (Array.isArray(p?.alternate_greetings) && p.alternate_greetings.length) {
return p.alternate_greetings;
}
try {
const parsed = JSON.parse(p?.alternate_greetings_json || '[]');
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
let createWizard;
let cardImportWizard;
let cardPreview = null;
let cardImportFile = null;
export function highlightPersona(personaId) {
document.querySelectorAll('.persona-card').forEach(c => {
@@ -15,7 +30,10 @@ export function highlightPersona(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]));
personaIndex = new Map(personas.map(p => {
const alternate_greetings = parseAlternateGreetings(p);
return [p.persona_id, { ...p, alternate_greetings }];
}));
const bar = document.getElementById('personaBar');
bar.innerHTML = '';
@@ -55,6 +73,19 @@ export async function loadPersonas() {
document.getElementById('editAppearance').value = data.appearance_tags || '';
document.getElementById('editLora').value = data.lora_name || '';
document.getElementById('editLoraWeight').value = data.lora_weight ?? 0.8;
const alts = data.alternate_greetings || [];
const altBlock = document.getElementById('editCardAltBlock');
const altSelect = document.getElementById('editCardGreetingSelect');
if (alts.length) {
altBlock?.classList.remove('hidden');
fillGreetingSelect(altSelect, data.first_mes, alts);
altSelect.onchange = () => {
document.getElementById('editFirstMes').value =
getSelectedGreeting(altSelect, data.first_mes, alts);
};
} else {
altBlock?.classList.add('hidden');
}
document.getElementById('cardEditOverlay').classList.add('open');
});
@@ -95,7 +126,7 @@ export async function loadPersonas() {
importBtn.type = 'button';
importBtn.className = 'card-import-btn';
importBtn.innerHTML = '📥<span>Chub</span>';
importBtn.addEventListener('click', () => document.getElementById('cardModalOverlay').classList.add('open'));
importBtn.addEventListener('click', () => openCardImportModal());
bar.appendChild(importBtn);
}
@@ -112,6 +143,122 @@ export async function selectPersona(personaId) {
}
}
function fillImpCardForm(preview) {
document.getElementById('impCardName').value = preview.name || '';
document.getElementById('impCardDescription').value = preview.description || '';
document.getElementById('impCardPersonality').value = preview.personality || '';
document.getElementById('impCardScenario').value = preview.scenario || '';
document.getElementById('impCardMesExample').value = preview.mes_example || '';
document.getElementById('impCardAppearance').value = preview.appearance_tags || '';
const alts = preview.alternate_greetings || [];
const selectEl = document.getElementById('impCardGreetingSelect');
const firstMesEl = document.getElementById('impCardFirstMes');
fillGreetingSelect(selectEl, preview.first_mes, alts);
firstMesEl.value = preview.first_mes || '';
const altHint = document.getElementById('impCardAltHint');
if (alts.length) {
altHint.textContent = `В карточке ${alts.length} альтернативных приветствий — выбери в списке или отредактируй текст ниже`;
altHint.classList.remove('hidden');
} else {
altHint.classList.add('hidden');
}
selectEl.onchange = () => {
firstMesEl.value = getSelectedGreeting(selectEl, preview.first_mes, alts);
};
}
async function loadCardPreview() {
const fileInput = document.getElementById('cardFile');
if (!fileInput.files?.length) {
alert('Выберите файл карточки (JSON или PNG)');
return false;
}
const form = new FormData();
form.append('file', fileInput.files[0]);
const res = await fetch('/characters/preview', { method: 'POST', body: form });
const data = await res.json();
if (!res.ok) {
alert(data.detail || 'Ошибка чтения карточки');
return false;
}
cardPreview = data;
cardImportFile = fileInput.files[0];
fillImpCardForm(data);
const hint = document.getElementById('cardPreviewHint');
if (hint) {
hint.textContent = `${data.name} · ${data.alternate_count || 0} альт. приветствий`;
}
return true;
}
function openCardImportModal() {
cardPreview = null;
cardImportFile = null;
document.getElementById('cardFile').value = '';
document.getElementById('cardPreviewHint').textContent = '';
document.getElementById('cardLora').value = '';
document.getElementById('cardLoraWeight').value = '0.8';
cardImportWizard?.reset();
document.getElementById('cardModalOverlay').classList.add('open');
}
function closeCardImportModal() {
document.getElementById('cardModalOverlay').classList.remove('open');
cardImportWizard?.reset();
cardPreview = null;
cardImportFile = null;
}
function initCardImportWizard() {
const modal = document.getElementById('cardModalOverlay')?.querySelector('.modal-wizard');
if (!modal) return;
cardImportWizard = initWizard(modal, {
totalSteps: 2,
validateStep(step) {
if (step !== 1) return true;
return loadCardPreview();
},
});
}
async function submitCardImport() {
if (!cardImportFile || !cardPreview) {
alert('Сначала загрузите и проверьте карточку');
return;
}
const form = new FormData();
form.append('file', cardImportFile);
form.append('card_id', cardPreview.card_id || '');
form.append('lora_name', document.getElementById('cardLora').value.trim());
form.append('lora_weight', document.getElementById('cardLoraWeight').value || '0.8');
form.append('name', document.getElementById('impCardName').value.trim());
form.append('description', document.getElementById('impCardDescription').value.trim());
form.append('personality', document.getElementById('impCardPersonality').value.trim());
form.append('scenario', document.getElementById('impCardScenario').value.trim());
form.append('first_mes', document.getElementById('impCardFirstMes').value.trim());
form.append('mes_example', document.getElementById('impCardMesExample').value.trim());
form.append('appearance_tags', document.getElementById('impCardAppearance').value.trim());
form.append(
'alternate_greetings_json',
JSON.stringify(cardPreview.alternate_greetings || []),
);
const res = await fetch('/characters/import', { method: 'POST', body: form });
const data = await res.json();
if (!res.ok) {
alert(data.detail || 'Ошибка импорта');
return;
}
closeCardImportModal();
document.getElementById('cardFile').value = '';
await loadPersonas();
await selectPersona(data.persona_id);
}
export function initPersonaModals() {
const createModal = document.getElementById('modalOverlay');
createWizard = initWizard(createModal.querySelector('.modal-wizard'), {
@@ -132,8 +279,10 @@ export function initPersonaModals() {
createModal.classList.remove('open');
createWizard.reset();
});
initCardImportWizard();
document.getElementById('cardModalCancel').addEventListener('click', () => {
document.getElementById('cardModalOverlay').classList.remove('open');
closeCardImportModal();
});
document.getElementById('cardEditCancel').addEventListener('click', () => {
document.getElementById('cardEditOverlay').classList.remove('open');
@@ -210,28 +359,7 @@ export function initPersonaModals() {
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);
});
document.getElementById('cardModalImport').addEventListener('click', submitCardImport);
const personaEditSave = document.getElementById('personaEditSave');
if (personaEditSave) {