Компактный архив промтов с круглой кнопкой и поиском
// ==UserScript==
// @name NovelAI Floating Manager
// @namespace SweetAi
// @version 2.0
// @description Компактный архив промтов с круглой кнопкой и поиском
// @match https://novelai.net/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Создаем стили
const style = document.createElement('style');
style.innerHTML = `
#nai-floating-btn {
position: fixed; top: 80%; left: 10px; z-index: 10001;
width: 50px; height: 50px; background: #4e4ecf;
border-radius: 50%; display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.5); cursor: move; font-size: 24px;
color: white; border: 2px solid #ffffff33; user-select: none; touch-action: none;
}
#nai-main-panel {
position: fixed; top: 80%; left: 70px; z-index: 10000;
background: #1c1c21; border: 1px solid #4a4a55; border-radius: 12px;
width: 280px; color: #eee; font-family: sans-serif;
box-shadow: 0 10px 30px rgba(0,0,0,0.7); display: none; overflow: hidden;
}
.nai-header { padding: 12px; background: #2d2d38; display: flex; justify-content: space-between; font-weight: bold; border-bottom: 1px solid #333; }
.nai-body { padding: 12px; max-height: 80vh; overflow-y: auto; }
.nai-input, .nai-select {
width: 100%; background: #0b0b0e; color: #fff; border: 1px solid #444;
margin-bottom: 8px; padding: 8px; border-radius: 6px; box-sizing: border-box; font-size: 13px;
}
.nai-btn-save {
width: 100%; background: #4e4ecf; color: #fff; border: none;
padding: 10px; border-radius: 6px; cursor: pointer; font-weight: bold; margin-bottom: 10px;
}
.prompt-card {
background: #2a2a33; padding: 10px; border-radius: 8px; margin-bottom: 8px;
border-left: 4px solid #4e4ecf; position: relative;
}
.prompt-actions { display: flex; gap: 5px; margin-top: 8px; }
.act-btn { flex: 1; font-size: 11px; padding: 5px; cursor: pointer; border-radius: 4px; border: none; color: white; font-weight: bold; }
.copy-btn { background: #28a745; }
.del-btn { background: #dc3545; }
#pListScroll { max-height: 250px; overflow-y: auto; margin-top: 5px; }
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-thumb { background: #444; border-radius: 10px; }
`;
document.head.appendChild(style);
// Основные элементы
const floatBtn = document.createElement('div');
floatBtn.id = 'nai-floating-btn';
floatBtn.innerHTML = '🎨';
document.body.appendChild(floatBtn);
const panel = document.createElement('div');
panel.id = 'nai-main-panel';
panel.innerHTML = `
<div class="nai-header">
<span>Archive</span>
<span id="closePanel" style="cursor:pointer; padding: 0 5px;">✕</span>
</div>
<div class="nai-body">
<input type="text" id="pName" class="nai-input" placeholder="Название (напр. Химата)">
<textarea id="pText" class="nai-input" placeholder="Промт..." style="height:50px; resize:none;"></textarea>
<select id="pCatAdd" class="nai-select">
<option>Персонажи</option><option>Одежда</option><option>Фон</option><option>Позы</option><option>Эффекты</option>
</select>
<button id="addBtn" class="nai-btn-save">Сохранить в базу</button>
<div style="font-size:10px; color:#777; margin: 10px 0 5px; text-transform: uppercase; letter-spacing: 1px;">Фильтры</div>
<select id="pCatFilter" class="nai-select">
<option value="all">Все категории</option>
<option>Персонажи</option><option>Одежда</option><option>Фон</option><option>Позы</option><option>Эффекты</option>
</select>
<input type="text" id="pSearch" class="nai-input" placeholder="Поиск по названию...">
<div id="pListScroll"></div>
</div>
`;
document.body.appendChild(panel);
let archive = JSON.parse(localStorage.getItem('nai_pro_v2')) || [];
const render = () => {
const list = document.getElementById('pListScroll');
const filter = document.getElementById('pCatFilter').value;
const search = document.getElementById('pSearch').value.toLowerCase();
list.innerHTML = '';
archive.filter(item => {
const matchCat = filter === 'all' || item.cat === filter;
const matchSearch = item.name.toLowerCase().includes(search);
return matchCat && matchSearch;
}).forEach((item) => {
const card = document.createElement('div');
card.className = 'prompt-card';
card.innerHTML = `
<div style="font-weight:bold; font-size:13px; color: #fff;">${item.name}</div>
<div style="font-size:10px; color:#888;">${item.cat}</div>
<div class="prompt-actions">
<button class="act-btn copy-btn" onclick="window.copyPrompt(${archive.indexOf(item)})">Копировать</button>
<button class="act-btn del-btn" onclick="window.delPrompt(${archive.indexOf(item)})">×</button>
</div>
`;
list.appendChild(card);
});
};
// События
window.copyPrompt = (i) => {
navigator.clipboard.writeText(archive[i].text);
const b = event.target; b.innerText = 'Скопировано!';
setTimeout(() => b.innerText = 'Копировать', 1000);
};
window.delPrompt = (i) => {
if(confirm('Удалить?')) {
archive.splice(i, 1);
localStorage.setItem('nai_pro_v2', JSON.stringify(archive));
render();
}
};
document.getElementById('addBtn').onclick = () => {
const name = document.getElementById('pName').value.trim() || 'Без названия';
const text = document.getElementById('pText').value.trim();
const cat = document.getElementById('pCatAdd').value;
if(text) {
archive.push({name, text, cat});
localStorage.setItem('nai_pro_v2', JSON.stringify(archive));
document.getElementById('pName').value = '';
document.getElementById('pText').value = '';
render();
}
};
document.getElementById('pCatFilter').onchange = render;
document.getElementById('pSearch').oninput = render;
// Логика открытия/закрытия
floatBtn.onclick = () => {
if (panel.style.display === 'none' || panel.style.display === '') {
panel.style.display = 'block';
panel.style.top = (floatBtn.offsetTop - 100) + 'px'; // Открываем рядом с кнопкой
panel.style.left = (floatBtn.offsetLeft + 60) + 'px';
} else {
panel.style.display = 'none';
}
};
document.getElementById('closePanel').onclick = () => { panel.style.display = 'none'; };
// Перетаскивание круглой кнопки
function dragElement(el) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
el.onmousedown = dragMouseDown;
el.ontouchstart = dragMouseDown;
function dragMouseDown(e) {
e.preventDefault();
const clientX = e.clientX || (e.touches ? e.touches[0].clientX : 0);
const clientY = e.clientY || (e.touches ? e.touches[0].clientY : 0);
pos3 = clientX; pos4 = clientY;
document.onmouseup = closeDragElement;
document.ontouchend = closeDragElement;
document.onmousemove = elementDrag;
document.ontouchmove = elementDrag;
}
function elementDrag(e) {
const clientX = e.clientX || (e.touches ? e.touches[0].clientX : 0);
const clientY = e.clientY || (e.touches ? e.touches[0].clientY : 0);
pos1 = pos3 - clientX; pos2 = pos4 - clientY;
pos3 = clientX; pos4 = clientY;
el.style.top = (el.offsetTop - pos2) + "px";
el.style.left = (el.offsetLeft - pos1) + "px";
// Панель следует за кнопкой, если открыта
panel.style.top = (el.offsetTop - 100) + "px";
panel.style.left = (el.offsetLeft + 60) + "px";
}
function closeDragElement() {
document.onmouseup = null; document.onmousemove = null;
document.ontouchend = null; document.ontouchmove = null;
}
}
dragElement(floatBtn);
render();
})();