Spaces:
Running
Running
| /* ===== Character Prompts Module ===== */ | |
| window.NAICharacter = { | |
| MAX_CHARS: 6, | |
| characters: [], // [{prompt, uc, row, col}] | |
| addCharacter() { | |
| if (this.characters.length >= this.MAX_CHARS) return; | |
| this.characters.push({ | |
| prompt: '', | |
| uc: '', | |
| row: 2, // 0-indexed, default 3rd row | |
| col: 2, // 0-indexed, default 3rd col | |
| }); | |
| this.render(); | |
| }, | |
| removeCharacter(idx) { | |
| this.characters.splice(idx, 1); | |
| this.render(); | |
| }, | |
| isAutoCoords() { | |
| const cb = $('#char-auto-coords'); | |
| return cb ? cb.checked : true; | |
| }, | |
| render() { | |
| const container = $('#char-list'); | |
| container.innerHTML = ''; | |
| const autoCoords = this.isAutoCoords(); | |
| this.characters.forEach((char, idx) => { | |
| const item = document.createElement('div'); | |
| item.className = 'char-item'; | |
| // Header with remove button | |
| const header = document.createElement('div'); | |
| header.className = 'char-item-header'; | |
| header.innerHTML = ` | |
| <span class="char-item-title">角色 ${idx + 1}</span> | |
| <button class="char-remove-btn" title="删除此角色">✕</button> | |
| `; | |
| header.querySelector('.char-remove-btn').addEventListener('click', () => this.removeCharacter(idx)); | |
| item.appendChild(header); | |
| // Prompt textarea | |
| const promptGroup = document.createElement('div'); | |
| promptGroup.className = 'form-group'; | |
| promptGroup.innerHTML = ` | |
| <label>角色${idx + 1} 提示词</label> | |
| <textarea class="form-textarea char-prompt" rows="1" placeholder="角色${idx + 1}的提示词...">${char.prompt}</textarea> | |
| `; | |
| const promptTA = promptGroup.querySelector('textarea'); | |
| promptTA.addEventListener('input', () => { | |
| char.prompt = promptTA.value; | |
| NAIUtils.autoResize(promptTA); | |
| }); | |
| item.appendChild(promptGroup); | |
| setTimeout(() => NAIUtils.autoResize(promptTA), 0); | |
| // UC textarea | |
| const ucGroup = document.createElement('div'); | |
| ucGroup.className = 'form-group'; | |
| ucGroup.innerHTML = ` | |
| <label>角色${idx + 1} 负面提示词</label> | |
| <textarea class="form-textarea char-uc" rows="1" placeholder="角色${idx + 1}的负面提示词...">${char.uc}</textarea> | |
| `; | |
| const ucTA = ucGroup.querySelector('textarea'); | |
| ucTA.addEventListener('input', () => { | |
| char.uc = ucTA.value; | |
| NAIUtils.autoResize(ucTA); | |
| }); | |
| item.appendChild(ucGroup); | |
| setTimeout(() => NAIUtils.autoResize(ucTA), 0); | |
| // 5x5 Grid (only interactive when auto coords is off) | |
| const gridGroup = document.createElement('div'); | |
| gridGroup.className = 'form-group char-grid-group'; | |
| if (autoCoords) gridGroup.classList.add('disabled'); | |
| const gridLabel = document.createElement('label'); | |
| gridLabel.textContent = `角色${idx + 1} 位置`; | |
| gridGroup.appendChild(gridLabel); | |
| const grid = document.createElement('div'); | |
| grid.className = 'char-grid'; | |
| for (let r = 0; r < 5; r++) { | |
| for (let c = 0; c < 5; c++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'char-grid-cell'; | |
| if (r === char.row && c === char.col) { | |
| cell.classList.add('selected'); | |
| } | |
| if (!autoCoords) { | |
| cell.addEventListener('click', () => { | |
| char.row = r; | |
| char.col = c; | |
| // Update grid selection | |
| grid.querySelectorAll('.char-grid-cell').forEach(cl => cl.classList.remove('selected')); | |
| cell.classList.add('selected'); | |
| }); | |
| } | |
| grid.appendChild(cell); | |
| } | |
| } | |
| gridGroup.appendChild(grid); | |
| item.appendChild(gridGroup); | |
| container.appendChild(item); | |
| }); | |
| // Update add button state | |
| const addBtn = $('#btn-char-add'); | |
| if (addBtn) { | |
| addBtn.disabled = this.characters.length >= this.MAX_CHARS; | |
| } | |
| }, | |
| collectParams() { | |
| if (this.characters.length === 0) return { characters: [], useCoords: !this.isAutoCoords() }; | |
| const useCoords = !this.isAutoCoords(); | |
| const characters = this.characters.map(char => { | |
| // x = col * 0.2 + 0.1, y = row * 0.2 + 0.1 (centered in cell) | |
| const x = parseFloat((char.col * 0.2 + 0.1).toFixed(2)); | |
| const y = parseFloat((char.row * 0.2 + 0.1).toFixed(2)); | |
| return { | |
| prompt: char.prompt, | |
| uc: char.uc, | |
| center: { x, y }, | |
| }; | |
| }); | |
| return { characters, useCoords }; | |
| }, | |
| init() { | |
| // Toggle | |
| $('#char-toggle').addEventListener('click', () => { | |
| const c = $('#char-content'); | |
| const a = $('#char-arrow'); | |
| const open = c.classList.toggle('open'); | |
| a.textContent = open ? '▼' : '▶'; | |
| }); | |
| // Auto coords checkbox | |
| $('#char-auto-coords').addEventListener('change', () => { | |
| this.render(); | |
| }); | |
| // Add button | |
| $('#btn-char-add').addEventListener('click', () => this.addCharacter()); | |
| }, | |
| }; |