mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 03:25:51 +00:00
ci: auto build & obfuscate [2026-04-06 00:50:28] (Jenkins #7)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,158 +1,158 @@
|
||||
export function makeDraggable($element, onClick, storageKey) {
|
||||
let isDragging = false;
|
||||
let hasDragged = false;
|
||||
let startPos = { x: 0, y: 0 };
|
||||
let elementStartPos = { x: 0, y: 0 };
|
||||
|
||||
const getEventCoords = (e) => {
|
||||
if (e.touches && e.touches.length > 0) {
|
||||
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
||||
} else if (e.changedTouches && e.changedTouches.length > 0) {
|
||||
return { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY };
|
||||
}
|
||||
return { x: e.clientX, y: e.clientY };
|
||||
};
|
||||
|
||||
const keepInBounds = ($elem) => {
|
||||
const windowWidth = $(window).width();
|
||||
const windowHeight = $(window).height();
|
||||
const elemWidth = $elem.outerWidth();
|
||||
const elemHeight = $elem.outerHeight();
|
||||
|
||||
let currentPos = $elem.offset();
|
||||
let newLeft = Math.max(0, Math.min(currentPos.left, windowWidth - elemWidth));
|
||||
let newTop = Math.max(0, Math.min(currentPos.top, windowHeight - elemHeight));
|
||||
|
||||
$elem.css({
|
||||
left: newLeft + 'px',
|
||||
top: newTop + 'px',
|
||||
transform: 'none'
|
||||
});
|
||||
|
||||
if (storageKey) {
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
left: newLeft + 'px',
|
||||
top: newTop + 'px'
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const dragStart = (e) => {
|
||||
e.preventDefault();
|
||||
isDragging = true;
|
||||
hasDragged = false;
|
||||
|
||||
const coords = getEventCoords(e.originalEvent || e);
|
||||
startPos = { x: coords.x, y: coords.y };
|
||||
|
||||
const offset = $element.offset();
|
||||
elementStartPos = { x: offset.left, y: offset.top };
|
||||
|
||||
$element.css({
|
||||
'cursor': 'grabbing',
|
||||
'transition': 'none'
|
||||
});
|
||||
|
||||
$('body').css({
|
||||
'user-select': 'none',
|
||||
'-webkit-user-select': 'none',
|
||||
'overflow': 'hidden'
|
||||
});
|
||||
};
|
||||
|
||||
const dragMove = (e) => {
|
||||
if (!isDragging) return;
|
||||
e.preventDefault();
|
||||
hasDragged = true;
|
||||
|
||||
const coords = getEventCoords(e.originalEvent || e);
|
||||
const deltaX = coords.x - startPos.x;
|
||||
const deltaY = coords.y - startPos.y;
|
||||
|
||||
let newLeft = elementStartPos.x + deltaX;
|
||||
let newTop = elementStartPos.y + deltaY;
|
||||
|
||||
const windowWidth = $(window).width();
|
||||
const windowHeight = $(window).height();
|
||||
const elemWidth = $element.outerWidth();
|
||||
const elemHeight = $element.outerHeight();
|
||||
|
||||
newLeft = Math.max(0, Math.min(newLeft, windowWidth - elemWidth));
|
||||
newTop = Math.max(0, Math.min(newTop, windowHeight - elemHeight));
|
||||
|
||||
$element.css({
|
||||
left: newLeft + 'px',
|
||||
top: newTop + 'px',
|
||||
transform: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const dragEnd = (e) => {
|
||||
if (!isDragging) return;
|
||||
isDragging = false;
|
||||
|
||||
$element.css({
|
||||
'cursor': 'grab',
|
||||
'transition': 'transform 0.2s ease, box-shadow 0.2s ease'
|
||||
});
|
||||
|
||||
$('body').css({
|
||||
'user-select': 'auto',
|
||||
'-webkit-user-select': 'auto',
|
||||
'overflow': 'auto'
|
||||
});
|
||||
|
||||
keepInBounds($element);
|
||||
|
||||
if (!hasDragged && onClick) {
|
||||
if (e.type === 'touchend') {
|
||||
e.preventDefault();
|
||||
setTimeout(onClick, 10);
|
||||
} else {
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$element.on('mousedown', dragStart);
|
||||
$element.on('touchstart', dragStart);
|
||||
|
||||
const namespace = '.draggable' + Date.now();
|
||||
$(document).on(`mousemove${namespace}`, dragMove);
|
||||
$(document).on(`touchmove${namespace}`, dragMove);
|
||||
$(document).on(`mouseup${namespace}`, dragEnd);
|
||||
$(document).on(`touchend${namespace}`, dragEnd);
|
||||
|
||||
$element.on('click', (e) => {
|
||||
if (hasDragged) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on(`resize${namespace}`, () => {
|
||||
if ($element.length) {
|
||||
keepInBounds($element);
|
||||
}
|
||||
});
|
||||
|
||||
$element.css({
|
||||
'cursor': 'grab',
|
||||
'user-select': 'none',
|
||||
'-webkit-user-select': 'none'
|
||||
});
|
||||
|
||||
if (storageKey) {
|
||||
const savedPos = localStorage.getItem(storageKey);
|
||||
if (savedPos) {
|
||||
$element.css(JSON.parse(savedPos));
|
||||
setTimeout(() => keepInBounds($element), 0);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
$element.off('mousedown touchstart click');
|
||||
$(document).off(namespace);
|
||||
$(window).off(namespace);
|
||||
};
|
||||
}
|
||||
export function makeDraggable($element, onClick, storageKey) {
|
||||
let isDragging = false;
|
||||
let hasDragged = false;
|
||||
let startPos = { x: 0, y: 0 };
|
||||
let elementStartPos = { x: 0, y: 0 };
|
||||
|
||||
const getEventCoords = (e) => {
|
||||
if (e.touches && e.touches.length > 0) {
|
||||
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
||||
} else if (e.changedTouches && e.changedTouches.length > 0) {
|
||||
return { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY };
|
||||
}
|
||||
return { x: e.clientX, y: e.clientY };
|
||||
};
|
||||
|
||||
const keepInBounds = ($elem) => {
|
||||
const windowWidth = $(window).width();
|
||||
const windowHeight = $(window).height();
|
||||
const elemWidth = $elem.outerWidth();
|
||||
const elemHeight = $elem.outerHeight();
|
||||
|
||||
let currentPos = $elem.offset();
|
||||
let newLeft = Math.max(0, Math.min(currentPos.left, windowWidth - elemWidth));
|
||||
let newTop = Math.max(0, Math.min(currentPos.top, windowHeight - elemHeight));
|
||||
|
||||
$elem.css({
|
||||
left: newLeft + 'px',
|
||||
top: newTop + 'px',
|
||||
transform: 'none'
|
||||
});
|
||||
|
||||
if (storageKey) {
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
left: newLeft + 'px',
|
||||
top: newTop + 'px'
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const dragStart = (e) => {
|
||||
e.preventDefault();
|
||||
isDragging = true;
|
||||
hasDragged = false;
|
||||
|
||||
const coords = getEventCoords(e.originalEvent || e);
|
||||
startPos = { x: coords.x, y: coords.y };
|
||||
|
||||
const offset = $element.offset();
|
||||
elementStartPos = { x: offset.left, y: offset.top };
|
||||
|
||||
$element.css({
|
||||
'cursor': 'grabbing',
|
||||
'transition': 'none'
|
||||
});
|
||||
|
||||
$('body').css({
|
||||
'user-select': 'none',
|
||||
'-webkit-user-select': 'none',
|
||||
'overflow': 'hidden'
|
||||
});
|
||||
};
|
||||
|
||||
const dragMove = (e) => {
|
||||
if (!isDragging) return;
|
||||
e.preventDefault();
|
||||
hasDragged = true;
|
||||
|
||||
const coords = getEventCoords(e.originalEvent || e);
|
||||
const deltaX = coords.x - startPos.x;
|
||||
const deltaY = coords.y - startPos.y;
|
||||
|
||||
let newLeft = elementStartPos.x + deltaX;
|
||||
let newTop = elementStartPos.y + deltaY;
|
||||
|
||||
const windowWidth = $(window).width();
|
||||
const windowHeight = $(window).height();
|
||||
const elemWidth = $element.outerWidth();
|
||||
const elemHeight = $element.outerHeight();
|
||||
|
||||
newLeft = Math.max(0, Math.min(newLeft, windowWidth - elemWidth));
|
||||
newTop = Math.max(0, Math.min(newTop, windowHeight - elemHeight));
|
||||
|
||||
$element.css({
|
||||
left: newLeft + 'px',
|
||||
top: newTop + 'px',
|
||||
transform: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const dragEnd = (e) => {
|
||||
if (!isDragging) return;
|
||||
isDragging = false;
|
||||
|
||||
$element.css({
|
||||
'cursor': 'grab',
|
||||
'transition': 'transform 0.2s ease, box-shadow 0.2s ease'
|
||||
});
|
||||
|
||||
$('body').css({
|
||||
'user-select': 'auto',
|
||||
'-webkit-user-select': 'auto',
|
||||
'overflow': 'auto'
|
||||
});
|
||||
|
||||
keepInBounds($element);
|
||||
|
||||
if (!hasDragged && onClick) {
|
||||
if (e.type === 'touchend') {
|
||||
e.preventDefault();
|
||||
setTimeout(onClick, 10);
|
||||
} else {
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$element.on('mousedown', dragStart);
|
||||
$element.on('touchstart', dragStart);
|
||||
|
||||
const namespace = '.draggable' + Date.now();
|
||||
$(document).on(`mousemove${namespace}`, dragMove);
|
||||
$(document).on(`touchmove${namespace}`, dragMove);
|
||||
$(document).on(`mouseup${namespace}`, dragEnd);
|
||||
$(document).on(`touchend${namespace}`, dragEnd);
|
||||
|
||||
$element.on('click', (e) => {
|
||||
if (hasDragged) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on(`resize${namespace}`, () => {
|
||||
if ($element.length) {
|
||||
keepInBounds($element);
|
||||
}
|
||||
});
|
||||
|
||||
$element.css({
|
||||
'cursor': 'grab',
|
||||
'user-select': 'none',
|
||||
'-webkit-user-select': 'none'
|
||||
});
|
||||
|
||||
if (storageKey) {
|
||||
const savedPos = localStorage.getItem(storageKey);
|
||||
if (savedPos) {
|
||||
$element.css(JSON.parse(savedPos));
|
||||
setTimeout(() => keepInBounds($element), 0);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
$element.off('mousedown touchstart click');
|
||||
$(document).off(namespace);
|
||||
$(window).off(namespace);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as state from './prese_state.js';
|
||||
import * as ui from './prese_ui.js';
|
||||
|
||||
// Public API for other modules
|
||||
export { getPresetPrompts, getMixedOrder } from './prese_state.js';
|
||||
|
||||
// Initialize the application
|
||||
$(document).ready(function() {
|
||||
state.loadPresets();
|
||||
ui.addPresetSettingsButton();
|
||||
});
|
||||
import * as state from './prese_state.js';
|
||||
import * as ui from './prese_ui.js';
|
||||
|
||||
// Public API for other modules
|
||||
export { getPresetPrompts, getMixedOrder } from './prese_state.js';
|
||||
|
||||
// Initialize the application
|
||||
$(document).ready(function() {
|
||||
state.loadPresets();
|
||||
ui.addPresetSettingsButton();
|
||||
});
|
||||
|
||||
@@ -1,408 +1,408 @@
|
||||
<div id="amily2-preset-settings-popup">
|
||||
<style>
|
||||
#amily2-preset-settings-popup {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 确保编辑器容器有更大的高度和滚动能力 */
|
||||
#prompt-editor-container {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
padding: 12px 12px 150px 12px;
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
#prompt-editor-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#prompt-editor-container::-webkit-scrollbar-track {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
#prompt-editor-container::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#prompt-editor-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
/* 紧凑的区块样式 */
|
||||
#amily2-preset-settings-popup .prompt-section {
|
||||
border: 1px solid #555;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
background: linear-gradient(135deg, #2a2a2a 0%, #1e1e1e 100%);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .prompt-section h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .prompt-section .text-muted {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
/* 混合列表样式 */
|
||||
#amily2-preset-settings-popup .mixed-list {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* 混合项目样式 */
|
||||
#amily2-preset-settings-popup .mixed-item {
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
background: #333;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .mixed-item:hover {
|
||||
border-color: #666;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* 项目头部 */
|
||||
#amily2-preset-settings-popup .item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #3a3a3a;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-type-badge {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .badge-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .badge-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 项目控制按钮 */
|
||||
#amily2-preset-settings-popup .item-controls {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-controls .btn {
|
||||
padding: 2px 6px;
|
||||
font-size: 11px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 项目内容 */
|
||||
#amily2-preset-settings-popup .item-content {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content select {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content textarea {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
border: 1px solid #555;
|
||||
background: #2a2a2a;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content textarea:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content strong {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content .small {
|
||||
font-size: 11px;
|
||||
margin: 4px 0 0 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 条件块水平线格式样式 */
|
||||
#amily2-preset-settings-popup .conditional-line-format {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #3a3a3a;
|
||||
border-bottom: 1px solid #444;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-prefix {
|
||||
color: #6c757d;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-dashes {
|
||||
color: #555;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-name {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-controls {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-controls .btn {
|
||||
padding: 2px 6px;
|
||||
font-size: 11px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-description {
|
||||
padding: 8px 12px;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-description code {
|
||||
background: transparent;
|
||||
color: #aaa;
|
||||
font-size: 11px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 区块控制按钮 - 更紧凑 */
|
||||
#amily2-preset-settings-popup .section-controls {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .section-controls .btn {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* 区块操作按钮组 - 更紧凑 */
|
||||
#amily2-preset-settings-popup .section-action-buttons {
|
||||
margin-top: 6px !important;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .section-action-buttons .btn {
|
||||
font-size: 10px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
/* 顶部按钮组 - 居中布局 */
|
||||
#amily2-preset-settings-popup .button-group {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 按钮样式 - 更紧凑 */
|
||||
#amily2-preset-settings-popup .btn {
|
||||
color: #fff;
|
||||
border: 1px solid transparent;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-success {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-info {
|
||||
background: linear-gradient(135deg, #17a2b8 0%, #20c997 100%);
|
||||
border-color: #17a2b8;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-warning {
|
||||
background: linear-gradient(135deg, #ffc107 0%, #fd7e14 100%);
|
||||
border-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-danger {
|
||||
background: linear-gradient(135deg, #dc3545 0%, #e83e8c 100%);
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-primary {
|
||||
background: linear-gradient(135deg, #007bff 0%, #6610f2 100%);
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-secondary {
|
||||
background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-sm {
|
||||
background: #6c757d;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-sm.btn-danger {
|
||||
background: #dc3545;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-sm:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 表单控件样式 */
|
||||
#amily2-preset-settings-popup .form-control {
|
||||
background: #2a2a2a;
|
||||
border: 1px solid #555;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .form-control:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 拖拽手柄样式 */
|
||||
#amily2-preset-settings-popup .drag-handle {
|
||||
cursor: grab;
|
||||
color: #888;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
padding: 0 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .drag-handle:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* 拖拽状态样式 */
|
||||
#amily2-preset-settings-popup .mixed-item.dragging {
|
||||
opacity: 0.5;
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .mixed-item.drag-over {
|
||||
border-color: #007bff;
|
||||
background: #1a4480;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .mixed-item[draggable="true"] {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup::-webkit-scrollbar-track {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup::-webkit-scrollbar-thumb:hover {
|
||||
background: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h3 style="margin: 0 0 16px 0; color: #fff; font-weight: 600;">Amily2 提示词链编辑器</h3>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="save-all-presets" class="btn btn-success">全部保存</button>
|
||||
<button id="import-all-presets" class="btn btn-info">导入配置</button>
|
||||
<button id="export-all-presets" class="btn btn-warning">导出配置</button>
|
||||
<button id="reset-all-presets" class="btn btn-danger">恢复全部</button>
|
||||
</div>
|
||||
|
||||
<div id="preset-manager-container">
|
||||
<!-- Preset manager UI will be injected here by JS -->
|
||||
</div>
|
||||
|
||||
<div id="prompt-editor-container">
|
||||
<!-- JS will dynamically populate this -->
|
||||
</div>
|
||||
<div id="amily2-preset-settings-popup">
|
||||
<style>
|
||||
#amily2-preset-settings-popup {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 确保编辑器容器有更大的高度和滚动能力 */
|
||||
#prompt-editor-container {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
padding: 12px 12px 150px 12px;
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
#prompt-editor-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#prompt-editor-container::-webkit-scrollbar-track {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
#prompt-editor-container::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#prompt-editor-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
/* 紧凑的区块样式 */
|
||||
#amily2-preset-settings-popup .prompt-section {
|
||||
border: 1px solid #555;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
background: linear-gradient(135deg, #2a2a2a 0%, #1e1e1e 100%);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .prompt-section h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .prompt-section .text-muted {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
/* 混合列表样式 */
|
||||
#amily2-preset-settings-popup .mixed-list {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* 混合项目样式 */
|
||||
#amily2-preset-settings-popup .mixed-item {
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
background: #333;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .mixed-item:hover {
|
||||
border-color: #666;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* 项目头部 */
|
||||
#amily2-preset-settings-popup .item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #3a3a3a;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-type-badge {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .badge-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .badge-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 项目控制按钮 */
|
||||
#amily2-preset-settings-popup .item-controls {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-controls .btn {
|
||||
padding: 2px 6px;
|
||||
font-size: 11px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 项目内容 */
|
||||
#amily2-preset-settings-popup .item-content {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content select {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content textarea {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
border: 1px solid #555;
|
||||
background: #2a2a2a;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content textarea:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content strong {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .item-content .small {
|
||||
font-size: 11px;
|
||||
margin: 4px 0 0 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 条件块水平线格式样式 */
|
||||
#amily2-preset-settings-popup .conditional-line-format {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #3a3a3a;
|
||||
border-bottom: 1px solid #444;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-prefix {
|
||||
color: #6c757d;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-dashes {
|
||||
color: #555;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-name {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-controls {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-controls .btn {
|
||||
padding: 2px 6px;
|
||||
font-size: 11px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-description {
|
||||
padding: 8px 12px;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .conditional-description code {
|
||||
background: transparent;
|
||||
color: #aaa;
|
||||
font-size: 11px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 区块控制按钮 - 更紧凑 */
|
||||
#amily2-preset-settings-popup .section-controls {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .section-controls .btn {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* 区块操作按钮组 - 更紧凑 */
|
||||
#amily2-preset-settings-popup .section-action-buttons {
|
||||
margin-top: 6px !important;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .section-action-buttons .btn {
|
||||
font-size: 10px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
/* 顶部按钮组 - 居中布局 */
|
||||
#amily2-preset-settings-popup .button-group {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 按钮样式 - 更紧凑 */
|
||||
#amily2-preset-settings-popup .btn {
|
||||
color: #fff;
|
||||
border: 1px solid transparent;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-success {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-info {
|
||||
background: linear-gradient(135deg, #17a2b8 0%, #20c997 100%);
|
||||
border-color: #17a2b8;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-warning {
|
||||
background: linear-gradient(135deg, #ffc107 0%, #fd7e14 100%);
|
||||
border-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-danger {
|
||||
background: linear-gradient(135deg, #dc3545 0%, #e83e8c 100%);
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-primary {
|
||||
background: linear-gradient(135deg, #007bff 0%, #6610f2 100%);
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-secondary {
|
||||
background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-sm {
|
||||
background: #6c757d;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-sm.btn-danger {
|
||||
background: #dc3545;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .btn-sm:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 表单控件样式 */
|
||||
#amily2-preset-settings-popup .form-control {
|
||||
background: #2a2a2a;
|
||||
border: 1px solid #555;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .form-control:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 拖拽手柄样式 */
|
||||
#amily2-preset-settings-popup .drag-handle {
|
||||
cursor: grab;
|
||||
color: #888;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
padding: 0 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .drag-handle:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* 拖拽状态样式 */
|
||||
#amily2-preset-settings-popup .mixed-item.dragging {
|
||||
opacity: 0.5;
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .mixed-item.drag-over {
|
||||
border-color: #007bff;
|
||||
background: #1a4480;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup .mixed-item[draggable="true"] {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup::-webkit-scrollbar-track {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#amily2-preset-settings-popup::-webkit-scrollbar-thumb:hover {
|
||||
background: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h3 style="margin: 0 0 16px 0; color: #fff; font-weight: 600;">Amily2 提示词链编辑器</h3>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="save-all-presets" class="btn btn-success">全部保存</button>
|
||||
<button id="import-all-presets" class="btn btn-info">导入配置</button>
|
||||
<button id="export-all-presets" class="btn btn-warning">导出配置</button>
|
||||
<button id="reset-all-presets" class="btn btn-danger">恢复全部</button>
|
||||
</div>
|
||||
|
||||
<div id="preset-manager-container">
|
||||
<!-- Preset manager UI will be injected here by JS -->
|
||||
</div>
|
||||
|
||||
<div id="prompt-editor-container">
|
||||
<!-- JS will dynamically populate this -->
|
||||
</div>
|
||||
|
||||
@@ -1,189 +1,189 @@
|
||||
import * as state from './prese_state.js';
|
||||
|
||||
let draggedItem = null;
|
||||
let draggedSection = null;
|
||||
let draggedOrderIndex = null;
|
||||
let isDragging = false;
|
||||
let startY = 0;
|
||||
let startX = 0;
|
||||
let dragThreshold = 5;
|
||||
let dragPlaceholder = null;
|
||||
let scrollInterval = null;
|
||||
let scrollContainer = null;
|
||||
|
||||
function createDragPlaceholder() {
|
||||
return $('<div class="drag-placeholder" style="height: 2px; background-color: #007bff; margin: 2px 0; opacity: 0.8;"></div>');
|
||||
}
|
||||
|
||||
function getEventPosition(e) {
|
||||
if (e.type.includes('touch')) {
|
||||
const touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
|
||||
return { x: touch.clientX, y: touch.clientY };
|
||||
}
|
||||
return { x: e.clientX, y: e.clientY };
|
||||
}
|
||||
|
||||
function findTargetItem(x, y) {
|
||||
const elements = document.elementsFromPoint(x, y);
|
||||
for (let element of elements) {
|
||||
const $element = $(element);
|
||||
const $mixedItem = $element.closest('.mixed-item');
|
||||
if ($mixedItem.length && !$mixedItem.is(draggedItem)) {
|
||||
return $mixedItem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function onDragStart(e, item) {
|
||||
e.preventDefault();
|
||||
draggedItem = item;
|
||||
draggedSection = draggedItem.data('section');
|
||||
draggedOrderIndex = draggedItem.data('order-index');
|
||||
|
||||
// 修复:直接查找固定的滚动容器
|
||||
scrollContainer = $('#amily2-preset-settings-popup').find('#prompt-editor-container');
|
||||
|
||||
const pos = getEventPosition(e);
|
||||
startX = pos.x;
|
||||
startY = pos.y;
|
||||
isDragging = false;
|
||||
|
||||
$(document).on('mousemove touchmove', onDragMove);
|
||||
$(document).on('mouseup touchend', onDragEnd);
|
||||
}
|
||||
|
||||
function onDragMove(e) {
|
||||
const pos = getEventPosition(e);
|
||||
const deltaX = Math.abs(pos.x - startX);
|
||||
const deltaY = Math.abs(pos.y - startY);
|
||||
|
||||
if (!isDragging && (deltaX > dragThreshold || deltaY > dragThreshold)) {
|
||||
isDragging = true;
|
||||
draggedItem.addClass('dragging');
|
||||
draggedItem.css({
|
||||
'opacity': '0.5',
|
||||
'transform': 'rotate(2deg)'
|
||||
});
|
||||
|
||||
dragPlaceholder = createDragPlaceholder();
|
||||
draggedItem.after(dragPlaceholder);
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
const targetItem = findTargetItem(pos.x, pos.y);
|
||||
|
||||
if (targetItem && targetItem.data('section') === draggedSection) {
|
||||
const targetRect = targetItem[0].getBoundingClientRect();
|
||||
const targetMiddle = targetRect.top + targetRect.height / 2;
|
||||
|
||||
if (pos.y < targetMiddle) {
|
||||
targetItem.before(dragPlaceholder);
|
||||
} else {
|
||||
targetItem.after(dragPlaceholder);
|
||||
}
|
||||
}
|
||||
|
||||
handleAutoScroll(pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
function onDragEnd(e) {
|
||||
$(document).off('mousemove touchmove', onDragMove);
|
||||
$(document).off('mouseup touchend', onDragEnd);
|
||||
|
||||
if (isDragging) {
|
||||
completeDrag();
|
||||
}
|
||||
|
||||
resetDragState();
|
||||
stopAutoScroll();
|
||||
}
|
||||
|
||||
function completeDrag() {
|
||||
if (!draggedItem || !dragPlaceholder) return;
|
||||
|
||||
const sectionContainer = dragPlaceholder.closest('.mixed-list');
|
||||
dragPlaceholder.before(draggedItem);
|
||||
|
||||
const newOrder = [];
|
||||
sectionContainer.find('.mixed-item').each(function(index) {
|
||||
const $item = $(this);
|
||||
$item.attr('data-order-index', index); // 更新UI索引属性
|
||||
|
||||
const type = $item.data('type');
|
||||
if (type === 'prompt') {
|
||||
newOrder.push({
|
||||
type: 'prompt',
|
||||
index: parseInt($item.data('prompt-index'), 10)
|
||||
});
|
||||
} else if (type === 'conditional') {
|
||||
newOrder.push({
|
||||
type: 'conditional',
|
||||
id: $item.data('conditional-id')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const allOrders = state.getCurrentMixedOrder();
|
||||
allOrders[draggedSection] = newOrder;
|
||||
state.setCurrentMixedOrder(allOrders);
|
||||
|
||||
toastr.info('顺序已调整,请点击保存按钮以生效。', '', { timeOut: 3000 });
|
||||
}
|
||||
|
||||
function resetDragState() {
|
||||
if (draggedItem) {
|
||||
draggedItem.removeClass('dragging');
|
||||
draggedItem.css({
|
||||
'opacity': '',
|
||||
'transform': ''
|
||||
});
|
||||
}
|
||||
|
||||
if (dragPlaceholder) {
|
||||
dragPlaceholder.remove();
|
||||
dragPlaceholder = null;
|
||||
}
|
||||
|
||||
draggedItem = null;
|
||||
draggedSection = null;
|
||||
draggedOrderIndex = null;
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
function handleAutoScroll(clientY) {
|
||||
let containerElement = scrollContainer ? scrollContainer[0] : null;
|
||||
if (!containerElement) return;
|
||||
|
||||
const containerRect = containerElement.getBoundingClientRect();
|
||||
const scrollZone = 120;
|
||||
const scrollSpeed = 15;
|
||||
|
||||
stopAutoScroll();
|
||||
|
||||
if (clientY < containerRect.top + scrollZone) {
|
||||
scrollInterval = setInterval(() => {
|
||||
containerElement.scrollTop -= scrollSpeed;
|
||||
if (containerElement.scrollTop <= 0) stopAutoScroll();
|
||||
}, 50);
|
||||
} else if (clientY > containerRect.bottom - scrollZone) {
|
||||
scrollInterval = setInterval(() => {
|
||||
containerElement.scrollTop += scrollSpeed;
|
||||
if (containerElement.scrollTop >= containerElement.scrollHeight - containerElement.clientHeight) stopAutoScroll();
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
function stopAutoScroll() {
|
||||
if (scrollInterval) {
|
||||
clearInterval(scrollInterval);
|
||||
scrollInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function bindDragEvents(context) {
|
||||
context.find('.drag-handle').off('mousedown.amily2 touchstart.amily2').on('mousedown.amily2 touchstart.amily2', function(e) {
|
||||
onDragStart(e, $(this).closest('.mixed-item'));
|
||||
});
|
||||
}
|
||||
import * as state from './prese_state.js';
|
||||
|
||||
let draggedItem = null;
|
||||
let draggedSection = null;
|
||||
let draggedOrderIndex = null;
|
||||
let isDragging = false;
|
||||
let startY = 0;
|
||||
let startX = 0;
|
||||
let dragThreshold = 5;
|
||||
let dragPlaceholder = null;
|
||||
let scrollInterval = null;
|
||||
let scrollContainer = null;
|
||||
|
||||
function createDragPlaceholder() {
|
||||
return $('<div class="drag-placeholder" style="height: 2px; background-color: #007bff; margin: 2px 0; opacity: 0.8;"></div>');
|
||||
}
|
||||
|
||||
function getEventPosition(e) {
|
||||
if (e.type.includes('touch')) {
|
||||
const touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
|
||||
return { x: touch.clientX, y: touch.clientY };
|
||||
}
|
||||
return { x: e.clientX, y: e.clientY };
|
||||
}
|
||||
|
||||
function findTargetItem(x, y) {
|
||||
const elements = document.elementsFromPoint(x, y);
|
||||
for (let element of elements) {
|
||||
const $element = $(element);
|
||||
const $mixedItem = $element.closest('.mixed-item');
|
||||
if ($mixedItem.length && !$mixedItem.is(draggedItem)) {
|
||||
return $mixedItem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function onDragStart(e, item) {
|
||||
e.preventDefault();
|
||||
draggedItem = item;
|
||||
draggedSection = draggedItem.data('section');
|
||||
draggedOrderIndex = draggedItem.data('order-index');
|
||||
|
||||
// 修复:直接查找固定的滚动容器
|
||||
scrollContainer = $('#amily2-preset-settings-popup').find('#prompt-editor-container');
|
||||
|
||||
const pos = getEventPosition(e);
|
||||
startX = pos.x;
|
||||
startY = pos.y;
|
||||
isDragging = false;
|
||||
|
||||
$(document).on('mousemove touchmove', onDragMove);
|
||||
$(document).on('mouseup touchend', onDragEnd);
|
||||
}
|
||||
|
||||
function onDragMove(e) {
|
||||
const pos = getEventPosition(e);
|
||||
const deltaX = Math.abs(pos.x - startX);
|
||||
const deltaY = Math.abs(pos.y - startY);
|
||||
|
||||
if (!isDragging && (deltaX > dragThreshold || deltaY > dragThreshold)) {
|
||||
isDragging = true;
|
||||
draggedItem.addClass('dragging');
|
||||
draggedItem.css({
|
||||
'opacity': '0.5',
|
||||
'transform': 'rotate(2deg)'
|
||||
});
|
||||
|
||||
dragPlaceholder = createDragPlaceholder();
|
||||
draggedItem.after(dragPlaceholder);
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
const targetItem = findTargetItem(pos.x, pos.y);
|
||||
|
||||
if (targetItem && targetItem.data('section') === draggedSection) {
|
||||
const targetRect = targetItem[0].getBoundingClientRect();
|
||||
const targetMiddle = targetRect.top + targetRect.height / 2;
|
||||
|
||||
if (pos.y < targetMiddle) {
|
||||
targetItem.before(dragPlaceholder);
|
||||
} else {
|
||||
targetItem.after(dragPlaceholder);
|
||||
}
|
||||
}
|
||||
|
||||
handleAutoScroll(pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
function onDragEnd(e) {
|
||||
$(document).off('mousemove touchmove', onDragMove);
|
||||
$(document).off('mouseup touchend', onDragEnd);
|
||||
|
||||
if (isDragging) {
|
||||
completeDrag();
|
||||
}
|
||||
|
||||
resetDragState();
|
||||
stopAutoScroll();
|
||||
}
|
||||
|
||||
function completeDrag() {
|
||||
if (!draggedItem || !dragPlaceholder) return;
|
||||
|
||||
const sectionContainer = dragPlaceholder.closest('.mixed-list');
|
||||
dragPlaceholder.before(draggedItem);
|
||||
|
||||
const newOrder = [];
|
||||
sectionContainer.find('.mixed-item').each(function(index) {
|
||||
const $item = $(this);
|
||||
$item.attr('data-order-index', index); // 更新UI索引属性
|
||||
|
||||
const type = $item.data('type');
|
||||
if (type === 'prompt') {
|
||||
newOrder.push({
|
||||
type: 'prompt',
|
||||
index: parseInt($item.data('prompt-index'), 10)
|
||||
});
|
||||
} else if (type === 'conditional') {
|
||||
newOrder.push({
|
||||
type: 'conditional',
|
||||
id: $item.data('conditional-id')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const allOrders = state.getCurrentMixedOrder();
|
||||
allOrders[draggedSection] = newOrder;
|
||||
state.setCurrentMixedOrder(allOrders);
|
||||
|
||||
toastr.info('顺序已调整,请点击保存按钮以生效。', '', { timeOut: 3000 });
|
||||
}
|
||||
|
||||
function resetDragState() {
|
||||
if (draggedItem) {
|
||||
draggedItem.removeClass('dragging');
|
||||
draggedItem.css({
|
||||
'opacity': '',
|
||||
'transform': ''
|
||||
});
|
||||
}
|
||||
|
||||
if (dragPlaceholder) {
|
||||
dragPlaceholder.remove();
|
||||
dragPlaceholder = null;
|
||||
}
|
||||
|
||||
draggedItem = null;
|
||||
draggedSection = null;
|
||||
draggedOrderIndex = null;
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
function handleAutoScroll(clientY) {
|
||||
let containerElement = scrollContainer ? scrollContainer[0] : null;
|
||||
if (!containerElement) return;
|
||||
|
||||
const containerRect = containerElement.getBoundingClientRect();
|
||||
const scrollZone = 120;
|
||||
const scrollSpeed = 15;
|
||||
|
||||
stopAutoScroll();
|
||||
|
||||
if (clientY < containerRect.top + scrollZone) {
|
||||
scrollInterval = setInterval(() => {
|
||||
containerElement.scrollTop -= scrollSpeed;
|
||||
if (containerElement.scrollTop <= 0) stopAutoScroll();
|
||||
}, 50);
|
||||
} else if (clientY > containerRect.bottom - scrollZone) {
|
||||
scrollInterval = setInterval(() => {
|
||||
containerElement.scrollTop += scrollSpeed;
|
||||
if (containerElement.scrollTop >= containerElement.scrollHeight - containerElement.clientHeight) stopAutoScroll();
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
function stopAutoScroll() {
|
||||
if (scrollInterval) {
|
||||
clearInterval(scrollInterval);
|
||||
scrollInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function bindDragEvents(context) {
|
||||
context.find('.drag-handle').off('mousedown.amily2 touchstart.amily2').on('mousedown.amily2 touchstart.amily2', function(e) {
|
||||
onDragStart(e, $(this).closest('.mixed-item'));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,220 +1,297 @@
|
||||
import * as state from './prese_state.js';
|
||||
import * as ui from './prese_ui.js';
|
||||
import { bindDragEvents } from './prese_dragdrop.js';
|
||||
import { sectionTitles } from './config.js';
|
||||
|
||||
function updatePresetsFromUI(context) {
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
context.find('.prompt-section').each(function() {
|
||||
const sectionKey = $(this).data('section');
|
||||
if (sectionKey && currentPresets[sectionKey]) {
|
||||
$(this).find('.mixed-list .mixed-item[data-type="prompt"]').each(function() {
|
||||
const promptIndex = $(this).data('prompt-index');
|
||||
const role = $(this).find('.role-select').val();
|
||||
const content = $(this).find('.content-textarea').val();
|
||||
|
||||
if (currentPresets[sectionKey][promptIndex]) {
|
||||
currentPresets[sectionKey][promptIndex] = { role, content };
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
state.setCurrentPresets(currentPresets);
|
||||
}
|
||||
|
||||
function exportSectionPreset(sectionKey) {
|
||||
const sectionConfig = {
|
||||
presets: { [sectionKey]: state.getCurrentPresets()[sectionKey] },
|
||||
mixedOrder: { [sectionKey]: state.getCurrentMixedOrder()[sectionKey] },
|
||||
version: 'v2.1_section',
|
||||
sectionName: sectionTitles[sectionKey],
|
||||
exportTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(sectionConfig, null, 2)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `amily2_${sectionKey}_preset.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toastr.success(`${sectionTitles[sectionKey]} 已导出!`);
|
||||
}
|
||||
|
||||
function importSectionPreset(sectionKey, context) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const imported = JSON.parse(e.target.result);
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
const currentMixedOrder = state.getCurrentMixedOrder();
|
||||
|
||||
if (imported.version === 'v2.1_section' && imported.presets && imported.mixedOrder) {
|
||||
if (imported.presets[sectionKey] && imported.mixedOrder[sectionKey]) {
|
||||
currentPresets[sectionKey] = imported.presets[sectionKey];
|
||||
currentMixedOrder[sectionKey] = imported.mixedOrder[sectionKey];
|
||||
toastr.success(`${sectionTitles[sectionKey]} 已成功导入!`);
|
||||
} else {
|
||||
throw new Error("文件中不包含对应的section数据");
|
||||
}
|
||||
} else if (imported.version === 'v2.1' && imported.presets && imported.mixedOrder) {
|
||||
if (imported.presets[sectionKey] && imported.mixedOrder[sectionKey]) {
|
||||
currentPresets[sectionKey] = imported.presets[sectionKey];
|
||||
currentMixedOrder[sectionKey] = imported.mixedOrder[sectionKey];
|
||||
toastr.success(`${sectionTitles[sectionKey]} 已成功导入!`);
|
||||
} else {
|
||||
throw new Error("文件中不包含对应的section数据");
|
||||
}
|
||||
} else if (imported[sectionKey]) {
|
||||
currentPresets[sectionKey] = imported[sectionKey];
|
||||
toastr.success(`${sectionTitles[sectionKey]} 已成功导入(使用默认条件块顺序)!`);
|
||||
} else {
|
||||
throw new Error("无法识别的文件格式或不包含对应section数据");
|
||||
}
|
||||
|
||||
state.setCurrentPresets(currentPresets);
|
||||
state.setCurrentMixedOrder(currentMixedOrder);
|
||||
state.savePresets();
|
||||
if (context && context.length) {
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Import section error:", error);
|
||||
toastr.error(`导入失败:${error.message}`);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
export function bindEvents(context) {
|
||||
context.find('.add-prompt-item').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
const currentMixedOrder = state.getCurrentMixedOrder();
|
||||
|
||||
currentPresets[sectionKey].push({ role: 'system', content: '' });
|
||||
currentMixedOrder[sectionKey].push({ type: 'prompt', index: currentPresets[sectionKey].length - 1 });
|
||||
|
||||
state.setCurrentPresets(currentPresets);
|
||||
state.setCurrentMixedOrder(currentMixedOrder);
|
||||
|
||||
ui.renderEditor(context);
|
||||
toastr.info('新提示词已添加,点击保存按钮完成操作');
|
||||
});
|
||||
|
||||
context.find('.delete-mixed-item').off('click.amily2').on('click.amily2', function() {
|
||||
const item = $(this).closest('.mixed-item');
|
||||
const sectionKey = item.data('section');
|
||||
const orderIndex = item.data('order-index');
|
||||
const itemType = item.data('type');
|
||||
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
const currentMixedOrder = state.getCurrentMixedOrder();
|
||||
|
||||
if (itemType === 'prompt') {
|
||||
const promptIndex = item.data('prompt-index');
|
||||
currentPresets[sectionKey].splice(promptIndex, 1);
|
||||
currentMixedOrder[sectionKey].forEach(orderItem => {
|
||||
if (orderItem.type === 'prompt' && orderItem.index > promptIndex) {
|
||||
orderItem.index--;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
currentMixedOrder[sectionKey].splice(orderIndex, 1);
|
||||
|
||||
state.setCurrentPresets(currentPresets);
|
||||
state.setCurrentMixedOrder(currentMixedOrder);
|
||||
|
||||
ui.renderEditor(context);
|
||||
toastr.info('项目已删除,点击保存按钮完成操作');
|
||||
});
|
||||
|
||||
context.off('change.amily2', '.role-select').on('change.amily2', '.role-select', function() {
|
||||
updatePresetsFromUI(context);
|
||||
});
|
||||
|
||||
context.off('input.amily2 paste.amily2 keyup.amily2', '.content-textarea').on('input.amily2 paste.amily2 keyup.amily2', function() {
|
||||
updatePresetsFromUI(context);
|
||||
});
|
||||
|
||||
context.find('#preset-select').off('change.amily2').on('change.amily2', function() {
|
||||
const selectedPreset = $(this).val();
|
||||
if (state.switchPreset(selectedPreset)) {
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('#new-preset').off('click.amily2').on('click.amily2', () => {
|
||||
if (state.createNewPreset()) {
|
||||
ui.renderPresetManager(context);
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('#rename-preset').off('click.amily2').on('click.amily2', () => {
|
||||
if (state.renamePreset()) {
|
||||
ui.renderPresetManager(context);
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('#delete-preset').off('click.amily2').on('click.amily2', () => {
|
||||
if (state.deletePreset()) {
|
||||
ui.renderPresetManager(context);
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('.save-section-preset').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
updatePresetsFromUI(context);
|
||||
state.savePresets();
|
||||
toastr.success(`${sectionTitles[sectionKey]} in preset "${state.getPresetManager().activePreset}" has been saved!`);
|
||||
});
|
||||
|
||||
context.find('.import-section-preset').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
importSectionPreset(sectionKey, context);
|
||||
});
|
||||
|
||||
context.find('.export-section-preset').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
exportSectionPreset(sectionKey);
|
||||
});
|
||||
|
||||
context.find('.reset-section-preset').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
if (confirm(`您确定要将 ${sectionTitles[sectionKey]} 恢复为默认设置吗?`)) {
|
||||
state.resetSectionPreset(sectionKey);
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('.collapsible-header').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
const content = $(this).next('.collapsible-content');
|
||||
const icon = $(this).find('.collapse-icon');
|
||||
const globalCollapseState = ui.getGlobalCollapseState();
|
||||
|
||||
content.slideToggle(200, function() {
|
||||
const isVisible = content.is(':visible');
|
||||
icon.text(isVisible ? '▼' : '▶');
|
||||
globalCollapseState[sectionKey] = isVisible;
|
||||
});
|
||||
});
|
||||
|
||||
bindDragEvents(context);
|
||||
}
|
||||
import * as state from './prese_state.js';
|
||||
import * as ui from './prese_ui.js';
|
||||
import { bindDragEvents } from './prese_dragdrop.js';
|
||||
import { sectionTitles } from './config.js';
|
||||
|
||||
function updatePresetsFromUI(context) {
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
context.find('.prompt-section').each(function() {
|
||||
const sectionKey = $(this).data('section');
|
||||
if (sectionKey && currentPresets[sectionKey]) {
|
||||
$(this).find('.mixed-list .mixed-item[data-type="prompt"]').each(function() {
|
||||
const promptIndex = $(this).data('prompt-index');
|
||||
const role = $(this).find('.role-select').val();
|
||||
const content = $(this).find('.content-textarea').val();
|
||||
|
||||
if (currentPresets[sectionKey][promptIndex]) {
|
||||
currentPresets[sectionKey][promptIndex] = { role, content };
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
state.setCurrentPresets(currentPresets);
|
||||
}
|
||||
|
||||
function exportSectionPreset(sectionKey) {
|
||||
const sectionConfig = {
|
||||
presets: { [sectionKey]: state.getCurrentPresets()[sectionKey] },
|
||||
mixedOrder: { [sectionKey]: state.getCurrentMixedOrder()[sectionKey] },
|
||||
version: 'v2.1_section',
|
||||
sectionName: sectionTitles[sectionKey],
|
||||
exportTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(sectionConfig, null, 2)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `amily2_${sectionKey}_preset.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toastr.success(`${sectionTitles[sectionKey]} 已导出!`);
|
||||
}
|
||||
|
||||
function importSectionPreset(sectionKey, context) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const imported = JSON.parse(e.target.result);
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
const currentMixedOrder = state.getCurrentMixedOrder();
|
||||
|
||||
if (imported.version === 'v2.1_section' && imported.presets && imported.mixedOrder) {
|
||||
if (imported.presets[sectionKey] && imported.mixedOrder[sectionKey]) {
|
||||
currentPresets[sectionKey] = imported.presets[sectionKey];
|
||||
currentMixedOrder[sectionKey] = imported.mixedOrder[sectionKey];
|
||||
toastr.success(`${sectionTitles[sectionKey]} 已成功导入!`);
|
||||
} else {
|
||||
throw new Error("文件中不包含对应的section数据");
|
||||
}
|
||||
} else if (imported.version === 'v2.1' && imported.presets && imported.mixedOrder) {
|
||||
if (imported.presets[sectionKey] && imported.mixedOrder[sectionKey]) {
|
||||
currentPresets[sectionKey] = imported.presets[sectionKey];
|
||||
currentMixedOrder[sectionKey] = imported.mixedOrder[sectionKey];
|
||||
toastr.success(`${sectionTitles[sectionKey]} 已成功导入!`);
|
||||
} else {
|
||||
throw new Error("文件中不包含对应的section数据");
|
||||
}
|
||||
} else if (imported[sectionKey]) {
|
||||
currentPresets[sectionKey] = imported[sectionKey];
|
||||
toastr.success(`${sectionTitles[sectionKey]} 已成功导入(使用默认条件块顺序)!`);
|
||||
} else {
|
||||
throw new Error("无法识别的文件格式或不包含对应section数据");
|
||||
}
|
||||
|
||||
state.setCurrentPresets(currentPresets);
|
||||
state.setCurrentMixedOrder(currentMixedOrder);
|
||||
state.savePresets();
|
||||
if (context && context.length) {
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Import section error:", error);
|
||||
toastr.error(`导入失败:${error.message}`);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
function exportAllPresets() {
|
||||
const activePresetName = state.getPresetManager().activePreset;
|
||||
const exportData = {
|
||||
version: 'v2.1',
|
||||
presets: state.getCurrentPresets(),
|
||||
mixedOrder: state.getCurrentMixedOrder(),
|
||||
presetName: activePresetName,
|
||||
exportTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `amily2_all_presets_${activePresetName}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toastr.success(`预设 "${activePresetName}" 的所有配置已导出!`);
|
||||
}
|
||||
|
||||
function importAllPresets(context) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const imported = JSON.parse(e.target.result);
|
||||
|
||||
if (imported.version === 'v2.1' && imported.presets && imported.mixedOrder) {
|
||||
state.setCurrentPresets(imported.presets);
|
||||
state.setCurrentMixedOrder(imported.mixedOrder);
|
||||
state.savePresets();
|
||||
toastr.success(`所有配置已成功导入!`);
|
||||
if (context && context.length) {
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
} else {
|
||||
throw new Error("无法识别的文件格式或不是完整的预设配置");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Import all presets error:", error);
|
||||
toastr.error(`导入失败:${error.message}`);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
export function bindEvents(context) {
|
||||
context.find('.add-prompt-item').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
const currentMixedOrder = state.getCurrentMixedOrder();
|
||||
|
||||
currentPresets[sectionKey].push({ role: 'system', content: '' });
|
||||
currentMixedOrder[sectionKey].push({ type: 'prompt', index: currentPresets[sectionKey].length - 1 });
|
||||
|
||||
state.setCurrentPresets(currentPresets);
|
||||
state.setCurrentMixedOrder(currentMixedOrder);
|
||||
|
||||
ui.renderEditor(context);
|
||||
toastr.info('新提示词已添加,点击保存按钮完成操作');
|
||||
});
|
||||
|
||||
context.find('.delete-mixed-item').off('click.amily2').on('click.amily2', function() {
|
||||
const item = $(this).closest('.mixed-item');
|
||||
const sectionKey = item.data('section');
|
||||
const orderIndex = item.data('order-index');
|
||||
const itemType = item.data('type');
|
||||
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
const currentMixedOrder = state.getCurrentMixedOrder();
|
||||
|
||||
if (itemType === 'prompt') {
|
||||
const promptIndex = item.data('prompt-index');
|
||||
currentPresets[sectionKey].splice(promptIndex, 1);
|
||||
currentMixedOrder[sectionKey].forEach(orderItem => {
|
||||
if (orderItem.type === 'prompt' && orderItem.index > promptIndex) {
|
||||
orderItem.index--;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
currentMixedOrder[sectionKey].splice(orderIndex, 1);
|
||||
|
||||
state.setCurrentPresets(currentPresets);
|
||||
state.setCurrentMixedOrder(currentMixedOrder);
|
||||
|
||||
ui.renderEditor(context);
|
||||
toastr.info('项目已删除,点击保存按钮完成操作');
|
||||
});
|
||||
|
||||
context.off('change.amily2', '.role-select').on('change.amily2', '.role-select', function() {
|
||||
updatePresetsFromUI(context);
|
||||
});
|
||||
|
||||
context.off('input.amily2 paste.amily2 keyup.amily2', '.content-textarea').on('input.amily2 paste.amily2 keyup.amily2', function() {
|
||||
updatePresetsFromUI(context);
|
||||
});
|
||||
|
||||
context.find('#preset-select').off('change.amily2').on('change.amily2', function() {
|
||||
const selectedPreset = $(this).val();
|
||||
if (state.switchPreset(selectedPreset)) {
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('#new-preset').off('click.amily2').on('click.amily2', () => {
|
||||
if (state.createNewPreset()) {
|
||||
ui.renderPresetManager(context);
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('#rename-preset').off('click.amily2').on('click.amily2', () => {
|
||||
if (state.renamePreset()) {
|
||||
ui.renderPresetManager(context);
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('#delete-preset').off('click.amily2').on('click.amily2', () => {
|
||||
if (state.deletePreset()) {
|
||||
ui.renderPresetManager(context);
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('.save-section-preset').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
updatePresetsFromUI(context);
|
||||
state.savePresets();
|
||||
toastr.success(`${sectionTitles[sectionKey]} in preset "${state.getPresetManager().activePreset}" has been saved!`);
|
||||
});
|
||||
|
||||
context.find('.import-section-preset').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
importSectionPreset(sectionKey, context);
|
||||
});
|
||||
|
||||
context.find('.export-section-preset').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
exportSectionPreset(sectionKey);
|
||||
});
|
||||
|
||||
context.find('.reset-section-preset').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
if (confirm(`您确定要将 ${sectionTitles[sectionKey]} 恢复为默认设置吗?`)) {
|
||||
state.resetSectionPreset(sectionKey);
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
// 全局按钮事件绑定
|
||||
context.find('#save-all-presets').off('click.amily2').on('click.amily2', function() {
|
||||
updatePresetsFromUI(context);
|
||||
state.savePresets();
|
||||
toastr.success(`预设 "${state.getPresetManager().activePreset}" 的所有配置已保存!`);
|
||||
});
|
||||
|
||||
context.find('#export-all-presets').off('click.amily2').on('click.amily2', function() {
|
||||
exportAllPresets();
|
||||
});
|
||||
|
||||
context.find('#import-all-presets').off('click.amily2').on('click.amily2', function() {
|
||||
importAllPresets(context);
|
||||
});
|
||||
|
||||
context.find('#reset-all-presets').off('click.amily2').on('click.amily2', function() {
|
||||
if (confirm("您确定要将当前预设的所有配置恢复为默认状态吗?此操作无法撤销。")) {
|
||||
state.resetPresets();
|
||||
ui.renderEditor(context);
|
||||
}
|
||||
});
|
||||
|
||||
context.find('.collapsible-header').off('click.amily2').on('click.amily2', function() {
|
||||
const sectionKey = $(this).closest('.prompt-section').data('section');
|
||||
const content = $(this).next('.collapsible-content');
|
||||
const icon = $(this).find('.collapse-icon');
|
||||
const globalCollapseState = ui.getGlobalCollapseState();
|
||||
|
||||
content.slideToggle(200, function() {
|
||||
const isVisible = content.is(':visible');
|
||||
icon.text(isVisible ? '▼' : '▶');
|
||||
globalCollapseState[sectionKey] = isVisible;
|
||||
});
|
||||
});
|
||||
|
||||
bindDragEvents(context);
|
||||
}
|
||||
|
||||
@@ -1,406 +1,444 @@
|
||||
import { SETTINGS_KEY, defaultPrompts, defaultMixedOrder } from './config.js';
|
||||
import { compatibleTriggerSlash } from '../core/tavernhelper-compatibility.js';
|
||||
|
||||
let presetManager = {
|
||||
activePreset: '默认预设',
|
||||
presets: {
|
||||
'默认预设': {
|
||||
prompts: JSON.parse(JSON.stringify(defaultPrompts)),
|
||||
mixedOrder: JSON.parse(JSON.stringify(defaultMixedOrder))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let currentPresets = {};
|
||||
let currentMixedOrder = {};
|
||||
|
||||
export function getPresetManager() {
|
||||
return presetManager;
|
||||
}
|
||||
|
||||
export function setPresetManager(newManager) {
|
||||
presetManager = newManager;
|
||||
}
|
||||
|
||||
export function getCurrentPresets() {
|
||||
return currentPresets;
|
||||
}
|
||||
|
||||
export function setCurrentPresets(newPresets) {
|
||||
currentPresets = newPresets;
|
||||
}
|
||||
|
||||
export function getCurrentMixedOrder() {
|
||||
return currentMixedOrder;
|
||||
}
|
||||
|
||||
export function setCurrentMixedOrder(newOrder) {
|
||||
currentMixedOrder = newOrder;
|
||||
}
|
||||
|
||||
export function loadPresets() {
|
||||
const saved = localStorage.getItem(SETTINGS_KEY);
|
||||
if (saved) {
|
||||
try {
|
||||
presetManager = JSON.parse(saved);
|
||||
if (!presetManager.presets || !presetManager.activePreset) {
|
||||
throw new Error("Invalid preset data structure");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load Amily2 presets, resetting to default.", e);
|
||||
toastr.error("加载预设失败,已重置为默认设置。");
|
||||
resetToDefaultManager();
|
||||
}
|
||||
} else {
|
||||
migrateFromOldVersion();
|
||||
}
|
||||
|
||||
loadActivePreset();
|
||||
}
|
||||
|
||||
function migrateFromOldVersion() {
|
||||
const oldSettingsKey = 'amily2_prompt_presets_v2';
|
||||
const oldSaved = localStorage.getItem(oldSettingsKey);
|
||||
const oldSavedMixedOrder = localStorage.getItem(oldSettingsKey + '_mixed_order');
|
||||
|
||||
if (oldSaved) {
|
||||
try {
|
||||
const oldPrompts = JSON.parse(oldSaved);
|
||||
const oldMixedOrder = oldSavedMixedOrder ? JSON.parse(oldSavedMixedOrder) : defaultMixedOrder;
|
||||
|
||||
presetManager.presets['默认预设'] = {
|
||||
prompts: oldPrompts,
|
||||
mixedOrder: oldMixedOrder
|
||||
};
|
||||
|
||||
toastr.info("旧版本设置已成功迁移!");
|
||||
|
||||
localStorage.removeItem(oldSettingsKey);
|
||||
localStorage.removeItem(oldSettingsKey + '_mixed_order');
|
||||
} catch (e) {
|
||||
console.error("Failed to migrate old presets", e);
|
||||
resetToDefaultManager();
|
||||
}
|
||||
} else {
|
||||
toastr.success("未检测到 Amily2 预设,已为您初始化默认设置。");
|
||||
resetToDefaultManager();
|
||||
loadActivePreset();
|
||||
savePresets();
|
||||
}
|
||||
}
|
||||
|
||||
function resetToDefaultManager() {
|
||||
presetManager = {
|
||||
activePreset: '默认预设',
|
||||
presets: {
|
||||
'默认预设': {
|
||||
prompts: JSON.parse(JSON.stringify(defaultPrompts)),
|
||||
mixedOrder: JSON.parse(JSON.stringify(defaultMixedOrder))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function loadActivePreset() {
|
||||
const activePresetName = presetManager.activePreset;
|
||||
const activePresetData = presetManager.presets[activePresetName];
|
||||
|
||||
if (activePresetData) {
|
||||
currentPresets = JSON.parse(JSON.stringify(activePresetData.prompts));
|
||||
currentMixedOrder = JSON.parse(JSON.stringify(activePresetData.mixedOrder));
|
||||
let isMigrated = false;
|
||||
|
||||
const cwbMigrationChecks = {
|
||||
'cwb_summarizer': ['cwb_break_armor_prompt', 'cwb_char_card_prompt', 'newContext'],
|
||||
'cwb_summarizer_incremental': ['cwb_break_armor_prompt', 'cwb_char_card_prompt', 'cwb_incremental_char_card_prompt', 'oldFiles', 'newContext']
|
||||
};
|
||||
|
||||
for (const sectionKey in cwbMigrationChecks) {
|
||||
const requiredBlocks = cwbMigrationChecks[sectionKey];
|
||||
const order = currentMixedOrder[sectionKey] || [];
|
||||
|
||||
const isMissingBlocks = !requiredBlocks.every(blockId =>
|
||||
order.some(item => item.type === 'conditional' && item.id === blockId)
|
||||
);
|
||||
|
||||
if (isMissingBlocks) {
|
||||
console.log(`Amily2: 检测到 CWB 模块 [${sectionKey}] 缺少必要的条件块,正在执行迁移...`);
|
||||
currentPresets[sectionKey] = JSON.parse(JSON.stringify(defaultPrompts[sectionKey]));
|
||||
currentMixedOrder[sectionKey] = JSON.parse(JSON.stringify(defaultMixedOrder[sectionKey]));
|
||||
isMigrated = true;
|
||||
}
|
||||
}
|
||||
|
||||
const sectionsToMigrate = ['batch_filler', 'secondary_filler', 'reorganizer'];
|
||||
|
||||
sectionsToMigrate.forEach(sectionKey => {
|
||||
if (!currentPresets[sectionKey]) {
|
||||
currentPresets[sectionKey] = JSON.parse(JSON.stringify(defaultPrompts[sectionKey]));
|
||||
isMigrated = true;
|
||||
}
|
||||
if (!currentMixedOrder[sectionKey]) {
|
||||
currentMixedOrder[sectionKey] = JSON.parse(JSON.stringify(defaultMixedOrder[sectionKey]));
|
||||
isMigrated = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentMixedOrder.reorganizer && currentMixedOrder.reorganizer.some(item => item.id === 'thinkingFramework')) {
|
||||
console.log("Amily2: 检测到旧版 reorganizer 配置,正在执行一次性迁移...");
|
||||
currentPresets.reorganizer = JSON.parse(JSON.stringify(defaultPrompts.reorganizer));
|
||||
currentMixedOrder.reorganizer = JSON.parse(JSON.stringify(defaultMixedOrder.reorganizer));
|
||||
isMigrated = true;
|
||||
}
|
||||
|
||||
sectionsToMigrate.forEach(sectionKey => {
|
||||
const order = currentMixedOrder[sectionKey] || [];
|
||||
let sectionMigrated = false;
|
||||
|
||||
if (!order.some(item => item.type === 'conditional' && item.id === 'worldbook')) {
|
||||
const worldBookBlock = { type: 'conditional', id: 'worldbook' };
|
||||
let ruleTemplateIndex = order.findIndex(item => item.type === 'conditional' && item.id === 'ruleTemplate');
|
||||
if (ruleTemplateIndex !== -1) {
|
||||
order.splice(ruleTemplateIndex, 0, worldBookBlock);
|
||||
} else {
|
||||
let lastPromptIndex = -1;
|
||||
order.forEach((item, index) => {
|
||||
if (item.type === 'prompt') {
|
||||
lastPromptIndex = index;
|
||||
}
|
||||
});
|
||||
order.splice(lastPromptIndex + 1, 0, worldBookBlock);
|
||||
}
|
||||
sectionMigrated = true;
|
||||
}
|
||||
|
||||
if (sectionKey === 'secondary_filler' && !order.some(item => item.type === 'conditional' && item.id === 'contextHistory')) {
|
||||
const contextHistoryBlock = { type: 'conditional', id: 'contextHistory' };
|
||||
let worldbookIndex = order.findIndex(item => item.type === 'conditional' && item.id === 'worldbook');
|
||||
if (worldbookIndex !== -1) {
|
||||
order.splice(worldbookIndex + 1, 0, contextHistoryBlock);
|
||||
} else {
|
||||
let lastPromptIndex = -1;
|
||||
order.forEach((item, index) => {
|
||||
if (item.type === 'prompt') {
|
||||
lastPromptIndex = index;
|
||||
}
|
||||
});
|
||||
order.splice(lastPromptIndex + 1, 0, contextHistoryBlock);
|
||||
}
|
||||
sectionMigrated = true;
|
||||
}
|
||||
|
||||
if (sectionMigrated) {
|
||||
currentMixedOrder[sectionKey] = order;
|
||||
isMigrated = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (isMigrated) {
|
||||
console.log("Amily2: 自动迁移预设,更新到最新版本。");
|
||||
presetManager.presets[activePresetName].prompts = JSON.parse(JSON.stringify(currentPresets));
|
||||
presetManager.presets[activePresetName].mixedOrder = JSON.parse(JSON.stringify(currentMixedOrder));
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
|
||||
toastr.info("Amily2 提示词预设已自动更新以支持最新功能。");
|
||||
}
|
||||
const novelProcessorOrder = currentMixedOrder.novel_processor || [];
|
||||
const hasChapterContent = novelProcessorOrder.some(item => item.type === 'conditional' && item.id === 'chapterContent');
|
||||
|
||||
if (!hasChapterContent) {
|
||||
console.log("Amily2: 检测到 novel_processor 缺少 chapterContent 条件块,正在执行迁移...");
|
||||
currentPresets.novel_processor = JSON.parse(JSON.stringify(defaultPrompts.novel_processor));
|
||||
currentMixedOrder.novel_processor = JSON.parse(JSON.stringify(defaultMixedOrder.novel_processor));
|
||||
isMigrated = true;
|
||||
}
|
||||
} else {
|
||||
const firstPresetName = Object.keys(presetManager.presets)[0];
|
||||
if (firstPresetName) {
|
||||
presetManager.activePreset = firstPresetName;
|
||||
loadActivePreset();
|
||||
} else {
|
||||
resetToDefaultManager();
|
||||
loadActivePreset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function savePresets() {
|
||||
const activePresetName = presetManager.activePreset;
|
||||
if (presetManager.presets[activePresetName]) {
|
||||
presetManager.presets[activePresetName].prompts = currentPresets;
|
||||
presetManager.presets[activePresetName].mixedOrder = currentMixedOrder;
|
||||
}
|
||||
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
|
||||
toastr.success(`预设 "${presetManager.activePreset}" 已保存!`);
|
||||
}
|
||||
|
||||
export async function getPresetPrompts(sectionKey) {
|
||||
const presets = currentPresets[sectionKey];
|
||||
const order = currentMixedOrder[sectionKey];
|
||||
|
||||
if (!presets || presets.length === 0 || !order) {
|
||||
console.warn(`Amily2: getPresetPrompts - 没有找到 ${sectionKey} 的数据`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const orderedPrompts = [];
|
||||
|
||||
console.log(`Amily2: getPresetPrompts - ${sectionKey} 顺序:`, order);
|
||||
|
||||
const originalToastr = window.toastr;
|
||||
const dummyToastr = {
|
||||
success: () => {},
|
||||
info: () => {},
|
||||
warning: () => {},
|
||||
error: () => {},
|
||||
clear: () => {}
|
||||
};
|
||||
|
||||
try {
|
||||
window.toastr = dummyToastr;
|
||||
|
||||
for (const [index, item] of order.entries()) {
|
||||
if (item.type === 'prompt' && presets[item.index] !== undefined) {
|
||||
const prompt = JSON.parse(JSON.stringify(presets[item.index]));
|
||||
|
||||
if (prompt.content) {
|
||||
try {
|
||||
const command = `/echo ${prompt.content}`;
|
||||
const replacedContent = await compatibleTriggerSlash(command);
|
||||
prompt.content = replacedContent;
|
||||
} catch (error) {
|
||||
console.error(`[Amily2] 宏替换失败 for prompt at index ${index}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
orderedPrompts.push(prompt);
|
||||
console.log(`Amily2: 添加提示词 ${index}:`, { role: prompt.role, content: prompt.content.substring(0, 50) + '...' });
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
window.toastr = originalToastr;
|
||||
}
|
||||
|
||||
console.log(`Amily2: getPresetPrompts - ${sectionKey} 返回 ${orderedPrompts.length} 个提示词`);
|
||||
return orderedPrompts.length > 0 ? orderedPrompts : null;
|
||||
}
|
||||
|
||||
export function getMixedOrder(sectionKey) {
|
||||
const order = currentMixedOrder[sectionKey] || null;
|
||||
console.log(`Amily2: getMixedOrder - ${sectionKey}:`, order);
|
||||
return order;
|
||||
}
|
||||
|
||||
export function createNewPreset() {
|
||||
const newName = prompt("请输入新预设的名称:");
|
||||
|
||||
if (newName === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const trimmedNewName = newName.trim();
|
||||
|
||||
if (trimmedNewName === "") {
|
||||
toastr.warning("预设名称不能为空!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (presetManager.presets[trimmedNewName]) {
|
||||
toastr.error("该名称的预设已存在!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentPresetData = presetManager.presets[presetManager.activePreset];
|
||||
presetManager.presets[trimmedNewName] = JSON.parse(JSON.stringify(currentPresetData));
|
||||
presetManager.activePreset = trimmedNewName;
|
||||
|
||||
savePresets();
|
||||
loadActivePreset();
|
||||
toastr.success(`新预设 "${trimmedNewName}" 已创建并激活!`);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function renamePreset() {
|
||||
const oldName = presetManager.activePreset;
|
||||
const newName = prompt(`请输入 "${oldName}" 的新名称:`, oldName);
|
||||
|
||||
if (newName === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const trimmedNewName = newName.trim();
|
||||
|
||||
if (trimmedNewName === oldName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trimmedNewName === "") {
|
||||
toastr.warning("预设名称不能为空!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (presetManager.presets[trimmedNewName]) {
|
||||
toastr.error("该名称的预设已存在!");
|
||||
return false;
|
||||
}
|
||||
|
||||
presetManager.presets[trimmedNewName] = presetManager.presets[oldName];
|
||||
delete presetManager.presets[oldName];
|
||||
presetManager.activePreset = trimmedNewName;
|
||||
|
||||
savePresets();
|
||||
toastr.success(`预设已重命名为 "${trimmedNewName}"!`);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function deletePreset() {
|
||||
const nameToDelete = presetManager.activePreset;
|
||||
if (Object.keys(presetManager.presets).length <= 1) {
|
||||
toastr.error("不能删除唯一的预设!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (confirm(`您确定要删除预设 "${nameToDelete}" 吗?此操作无法撤销。`)) {
|
||||
delete presetManager.presets[nameToDelete];
|
||||
|
||||
presetManager.activePreset = Object.keys(presetManager.presets)[0];
|
||||
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
|
||||
|
||||
loadActivePreset();
|
||||
toastr.success(`预设 "${nameToDelete}" 已删除!`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function switchPreset(presetName) {
|
||||
if (presetManager.presets[presetName]) {
|
||||
presetManager.activePreset = presetName;
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
|
||||
loadActivePreset();
|
||||
toastr.clear();
|
||||
toastr.info(`已切换到预设 "${presetName}"`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function resetSectionPreset(sectionKey) {
|
||||
currentPresets[sectionKey] = JSON.parse(JSON.stringify(defaultPrompts[sectionKey]));
|
||||
currentMixedOrder[sectionKey] = JSON.parse(JSON.stringify(defaultMixedOrder[sectionKey]));
|
||||
savePresets();
|
||||
toastr.success(`${sectionKey} 已恢复为默认设置!`);
|
||||
}
|
||||
|
||||
export function resetPresets() {
|
||||
const activePresetName = presetManager.activePreset;
|
||||
presetManager.presets[activePresetName] = {
|
||||
prompts: JSON.parse(JSON.stringify(defaultPrompts)),
|
||||
mixedOrder: JSON.parse(JSON.stringify(defaultMixedOrder))
|
||||
};
|
||||
|
||||
loadActivePreset();
|
||||
savePresets();
|
||||
toastr.success(`预设 "${activePresetName}" 已恢复为默认设置!`);
|
||||
}
|
||||
import { SETTINGS_KEY, defaultPrompts, defaultMixedOrder } from './config.js';
|
||||
import { compatibleTriggerSlash } from '../core/tavernhelper-compatibility.js';
|
||||
import { showHtmlModal } from '../ui/page-window.js';
|
||||
|
||||
let presetManager = {
|
||||
activePreset: '默认预设',
|
||||
presets: {
|
||||
'默认预设': {
|
||||
prompts: JSON.parse(JSON.stringify(defaultPrompts)),
|
||||
mixedOrder: JSON.parse(JSON.stringify(defaultMixedOrder))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let currentPresets = {};
|
||||
let currentMixedOrder = {};
|
||||
|
||||
export function getPresetManager() {
|
||||
return presetManager;
|
||||
}
|
||||
|
||||
export function setPresetManager(newManager) {
|
||||
presetManager = newManager;
|
||||
}
|
||||
|
||||
export function getCurrentPresets() {
|
||||
return currentPresets;
|
||||
}
|
||||
|
||||
export function setCurrentPresets(newPresets) {
|
||||
currentPresets = newPresets;
|
||||
}
|
||||
|
||||
export function getCurrentMixedOrder() {
|
||||
return currentMixedOrder;
|
||||
}
|
||||
|
||||
export function setCurrentMixedOrder(newOrder) {
|
||||
currentMixedOrder = newOrder;
|
||||
}
|
||||
|
||||
const CURRENT_PROMPT_VERSION = 'v3.1_soft_prompt';
|
||||
|
||||
function checkPromptVersion() {
|
||||
const savedVersion = localStorage.getItem('amily2_prompt_version');
|
||||
if (savedVersion !== CURRENT_PROMPT_VERSION) {
|
||||
setTimeout(() => {
|
||||
showUpdateDialog();
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
function showUpdateDialog() {
|
||||
const htmlContent = `
|
||||
<div style="text-align: left; line-height: 1.6; font-size: 15px; padding: 10px;">
|
||||
<p>检测到当前提示词版本为旧版本。</p>
|
||||
<p>为更好的体验,请点击 <strong>一键更新</strong>,会将提示词恢复成最新版本提示词链默认状态。</p>
|
||||
<p>或者点击 <strong>保留自定义</strong> 按钮,则保留您之前的提示词。</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
showHtmlModal('Amily2 提示词更新', htmlContent, {
|
||||
okText: '一键更新',
|
||||
cancelText: '保留自定义',
|
||||
showCancel: true,
|
||||
onOk: () => {
|
||||
resetPresets();
|
||||
localStorage.setItem('amily2_prompt_version', CURRENT_PROMPT_VERSION);
|
||||
toastr.success("已更新为最新版本提示词!");
|
||||
},
|
||||
onCancel: () => {
|
||||
localStorage.setItem('amily2_prompt_version', CURRENT_PROMPT_VERSION);
|
||||
toastr.info("已保留您的自定义提示词。");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function loadPresets() {
|
||||
const saved = localStorage.getItem(SETTINGS_KEY);
|
||||
if (saved) {
|
||||
try {
|
||||
presetManager = JSON.parse(saved);
|
||||
if (!presetManager.presets || !presetManager.activePreset) {
|
||||
throw new Error("Invalid preset data structure");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load Amily2 presets, resetting to default.", e);
|
||||
toastr.error("加载预设失败,已重置为默认设置。");
|
||||
resetToDefaultManager();
|
||||
}
|
||||
} else {
|
||||
migrateFromOldVersion();
|
||||
}
|
||||
|
||||
loadActivePreset();
|
||||
checkPromptVersion();
|
||||
}
|
||||
|
||||
function migrateFromOldVersion() {
|
||||
const oldSettingsKey = 'amily2_prompt_presets_v2';
|
||||
const oldSaved = localStorage.getItem(oldSettingsKey);
|
||||
const oldSavedMixedOrder = localStorage.getItem(oldSettingsKey + '_mixed_order');
|
||||
|
||||
if (oldSaved) {
|
||||
try {
|
||||
const oldPrompts = JSON.parse(oldSaved);
|
||||
const oldMixedOrder = oldSavedMixedOrder ? JSON.parse(oldSavedMixedOrder) : defaultMixedOrder;
|
||||
|
||||
presetManager.presets['默认预设'] = {
|
||||
prompts: oldPrompts,
|
||||
mixedOrder: oldMixedOrder
|
||||
};
|
||||
|
||||
toastr.info("旧版本设置已成功迁移!");
|
||||
|
||||
localStorage.removeItem(oldSettingsKey);
|
||||
localStorage.removeItem(oldSettingsKey + '_mixed_order');
|
||||
} catch (e) {
|
||||
console.error("Failed to migrate old presets", e);
|
||||
resetToDefaultManager();
|
||||
}
|
||||
} else {
|
||||
toastr.success("未检测到 Amily2 预设,已为您初始化默认设置。");
|
||||
resetToDefaultManager();
|
||||
loadActivePreset();
|
||||
savePresets();
|
||||
}
|
||||
}
|
||||
|
||||
function resetToDefaultManager() {
|
||||
presetManager = {
|
||||
activePreset: '默认预设',
|
||||
presets: {
|
||||
'默认预设': {
|
||||
prompts: JSON.parse(JSON.stringify(defaultPrompts)),
|
||||
mixedOrder: JSON.parse(JSON.stringify(defaultMixedOrder))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function loadActivePreset() {
|
||||
const activePresetName = presetManager.activePreset;
|
||||
const activePresetData = presetManager.presets[activePresetName];
|
||||
|
||||
if (activePresetData) {
|
||||
currentPresets = JSON.parse(JSON.stringify(activePresetData.prompts));
|
||||
currentMixedOrder = JSON.parse(JSON.stringify(activePresetData.mixedOrder));
|
||||
let isMigrated = false;
|
||||
|
||||
const cwbMigrationChecks = {
|
||||
'cwb_summarizer': ['cwb_break_armor_prompt', 'cwb_char_card_prompt', 'newContext'],
|
||||
'cwb_summarizer_incremental': ['cwb_break_armor_prompt', 'cwb_char_card_prompt', 'cwb_incremental_char_card_prompt', 'oldFiles', 'newContext']
|
||||
};
|
||||
|
||||
for (const sectionKey in cwbMigrationChecks) {
|
||||
const requiredBlocks = cwbMigrationChecks[sectionKey];
|
||||
const order = currentMixedOrder[sectionKey] || [];
|
||||
|
||||
const isMissingBlocks = !requiredBlocks.every(blockId =>
|
||||
order.some(item => item.type === 'conditional' && item.id === blockId)
|
||||
);
|
||||
|
||||
if (isMissingBlocks) {
|
||||
console.log(`Amily2: 检测到 CWB 模块 [${sectionKey}] 缺少必要的条件块,正在执行迁移...`);
|
||||
currentPresets[sectionKey] = JSON.parse(JSON.stringify(defaultPrompts[sectionKey]));
|
||||
currentMixedOrder[sectionKey] = JSON.parse(JSON.stringify(defaultMixedOrder[sectionKey]));
|
||||
isMigrated = true;
|
||||
}
|
||||
}
|
||||
|
||||
const sectionsToMigrate = ['batch_filler', 'secondary_filler', 'reorganizer'];
|
||||
|
||||
sectionsToMigrate.forEach(sectionKey => {
|
||||
if (!currentPresets[sectionKey]) {
|
||||
currentPresets[sectionKey] = JSON.parse(JSON.stringify(defaultPrompts[sectionKey]));
|
||||
isMigrated = true;
|
||||
}
|
||||
if (!currentMixedOrder[sectionKey]) {
|
||||
currentMixedOrder[sectionKey] = JSON.parse(JSON.stringify(defaultMixedOrder[sectionKey]));
|
||||
isMigrated = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentMixedOrder.reorganizer && currentMixedOrder.reorganizer.some(item => item.id === 'thinkingFramework')) {
|
||||
console.log("Amily2: 检测到旧版 reorganizer 配置,正在执行一次性迁移...");
|
||||
currentPresets.reorganizer = JSON.parse(JSON.stringify(defaultPrompts.reorganizer));
|
||||
currentMixedOrder.reorganizer = JSON.parse(JSON.stringify(defaultMixedOrder.reorganizer));
|
||||
isMigrated = true;
|
||||
}
|
||||
|
||||
sectionsToMigrate.forEach(sectionKey => {
|
||||
const order = currentMixedOrder[sectionKey] || [];
|
||||
let sectionMigrated = false;
|
||||
|
||||
if (!order.some(item => item.type === 'conditional' && item.id === 'worldbook')) {
|
||||
const worldBookBlock = { type: 'conditional', id: 'worldbook' };
|
||||
let ruleTemplateIndex = order.findIndex(item => item.type === 'conditional' && item.id === 'ruleTemplate');
|
||||
if (ruleTemplateIndex !== -1) {
|
||||
order.splice(ruleTemplateIndex, 0, worldBookBlock);
|
||||
} else {
|
||||
let lastPromptIndex = -1;
|
||||
order.forEach((item, index) => {
|
||||
if (item.type === 'prompt') {
|
||||
lastPromptIndex = index;
|
||||
}
|
||||
});
|
||||
order.splice(lastPromptIndex + 1, 0, worldBookBlock);
|
||||
}
|
||||
sectionMigrated = true;
|
||||
}
|
||||
|
||||
if (sectionKey === 'secondary_filler' && !order.some(item => item.type === 'conditional' && item.id === 'contextHistory')) {
|
||||
const contextHistoryBlock = { type: 'conditional', id: 'contextHistory' };
|
||||
let worldbookIndex = order.findIndex(item => item.type === 'conditional' && item.id === 'worldbook');
|
||||
if (worldbookIndex !== -1) {
|
||||
order.splice(worldbookIndex + 1, 0, contextHistoryBlock);
|
||||
} else {
|
||||
let lastPromptIndex = -1;
|
||||
order.forEach((item, index) => {
|
||||
if (item.type === 'prompt') {
|
||||
lastPromptIndex = index;
|
||||
}
|
||||
});
|
||||
order.splice(lastPromptIndex + 1, 0, contextHistoryBlock);
|
||||
}
|
||||
sectionMigrated = true;
|
||||
}
|
||||
|
||||
if (sectionMigrated) {
|
||||
currentMixedOrder[sectionKey] = order;
|
||||
isMigrated = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (isMigrated) {
|
||||
console.log("Amily2: 自动迁移预设,更新到最新版本。");
|
||||
presetManager.presets[activePresetName].prompts = JSON.parse(JSON.stringify(currentPresets));
|
||||
presetManager.presets[activePresetName].mixedOrder = JSON.parse(JSON.stringify(currentMixedOrder));
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
|
||||
toastr.info("Amily2 提示词预设已自动更新以支持最新功能。");
|
||||
}
|
||||
const novelProcessorOrder = currentMixedOrder.novel_processor || [];
|
||||
const hasChapterContent = novelProcessorOrder.some(item => item.type === 'conditional' && item.id === 'chapterContent');
|
||||
|
||||
if (!hasChapterContent) {
|
||||
console.log("Amily2: 检测到 novel_processor 缺少 chapterContent 条件块,正在执行迁移...");
|
||||
currentPresets.novel_processor = JSON.parse(JSON.stringify(defaultPrompts.novel_processor));
|
||||
currentMixedOrder.novel_processor = JSON.parse(JSON.stringify(defaultMixedOrder.novel_processor));
|
||||
isMigrated = true;
|
||||
}
|
||||
} else {
|
||||
const firstPresetName = Object.keys(presetManager.presets)[0];
|
||||
if (firstPresetName) {
|
||||
presetManager.activePreset = firstPresetName;
|
||||
loadActivePreset();
|
||||
} else {
|
||||
resetToDefaultManager();
|
||||
loadActivePreset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function savePresets() {
|
||||
const activePresetName = presetManager.activePreset;
|
||||
if (presetManager.presets[activePresetName]) {
|
||||
presetManager.presets[activePresetName].prompts = currentPresets;
|
||||
presetManager.presets[activePresetName].mixedOrder = currentMixedOrder;
|
||||
}
|
||||
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
|
||||
toastr.success(`预设 "${presetManager.activePreset}" 已保存!`);
|
||||
}
|
||||
|
||||
export async function getPresetPrompts(sectionKey) {
|
||||
const presets = currentPresets[sectionKey];
|
||||
const order = currentMixedOrder[sectionKey];
|
||||
|
||||
if (!presets || presets.length === 0 || !order) {
|
||||
console.warn(`Amily2: getPresetPrompts - 没有找到 ${sectionKey} 的数据`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const orderedPrompts = [];
|
||||
|
||||
console.log(`Amily2: getPresetPrompts - ${sectionKey} 顺序:`, order);
|
||||
|
||||
const originalToastr = window.toastr;
|
||||
const dummyToastr = {
|
||||
success: () => {},
|
||||
info: () => {},
|
||||
warning: () => {},
|
||||
error: () => {},
|
||||
clear: () => {}
|
||||
};
|
||||
|
||||
try {
|
||||
window.toastr = dummyToastr;
|
||||
|
||||
for (const [index, item] of order.entries()) {
|
||||
if (item.type === 'prompt' && presets[item.index] !== undefined) {
|
||||
const prompt = JSON.parse(JSON.stringify(presets[item.index]));
|
||||
|
||||
if (prompt.content) {
|
||||
try {
|
||||
const command = `/echo ${prompt.content}`;
|
||||
const replacedContent = await compatibleTriggerSlash(command);
|
||||
prompt.content = replacedContent;
|
||||
} catch (error) {
|
||||
console.error(`[Amily2] 宏替换失败 for prompt at index ${index}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
orderedPrompts.push(prompt);
|
||||
console.log(`Amily2: 添加提示词 ${index}:`, { role: prompt.role, content: prompt.content.substring(0, 50) + '...' });
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
window.toastr = originalToastr;
|
||||
}
|
||||
|
||||
console.log(`Amily2: getPresetPrompts - ${sectionKey} 返回 ${orderedPrompts.length} 个提示词`);
|
||||
return orderedPrompts.length > 0 ? orderedPrompts : null;
|
||||
}
|
||||
|
||||
export function getMixedOrder(sectionKey) {
|
||||
const order = currentMixedOrder[sectionKey] || null;
|
||||
console.log(`Amily2: getMixedOrder - ${sectionKey}:`, order);
|
||||
return order;
|
||||
}
|
||||
|
||||
export function createNewPreset() {
|
||||
const newName = prompt("请输入新预设的名称:");
|
||||
|
||||
if (newName === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const trimmedNewName = newName.trim();
|
||||
|
||||
if (trimmedNewName === "") {
|
||||
toastr.warning("预设名称不能为空!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (presetManager.presets[trimmedNewName]) {
|
||||
toastr.error("该名称的预设已存在!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentPresetData = presetManager.presets[presetManager.activePreset];
|
||||
presetManager.presets[trimmedNewName] = JSON.parse(JSON.stringify(currentPresetData));
|
||||
presetManager.activePreset = trimmedNewName;
|
||||
|
||||
savePresets();
|
||||
loadActivePreset();
|
||||
toastr.success(`新预设 "${trimmedNewName}" 已创建并激活!`);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function renamePreset() {
|
||||
const oldName = presetManager.activePreset;
|
||||
const newName = prompt(`请输入 "${oldName}" 的新名称:`, oldName);
|
||||
|
||||
if (newName === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const trimmedNewName = newName.trim();
|
||||
|
||||
if (trimmedNewName === oldName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trimmedNewName === "") {
|
||||
toastr.warning("预设名称不能为空!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (presetManager.presets[trimmedNewName]) {
|
||||
toastr.error("该名称的预设已存在!");
|
||||
return false;
|
||||
}
|
||||
|
||||
presetManager.presets[trimmedNewName] = presetManager.presets[oldName];
|
||||
delete presetManager.presets[oldName];
|
||||
presetManager.activePreset = trimmedNewName;
|
||||
|
||||
savePresets();
|
||||
toastr.success(`预设已重命名为 "${trimmedNewName}"!`);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function deletePreset() {
|
||||
const nameToDelete = presetManager.activePreset;
|
||||
if (Object.keys(presetManager.presets).length <= 1) {
|
||||
toastr.error("不能删除唯一的预设!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (confirm(`您确定要删除预设 "${nameToDelete}" 吗?此操作无法撤销。`)) {
|
||||
delete presetManager.presets[nameToDelete];
|
||||
|
||||
presetManager.activePreset = Object.keys(presetManager.presets)[0];
|
||||
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
|
||||
|
||||
loadActivePreset();
|
||||
toastr.success(`预设 "${nameToDelete}" 已删除!`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function switchPreset(presetName) {
|
||||
if (presetManager.presets[presetName]) {
|
||||
presetManager.activePreset = presetName;
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
|
||||
loadActivePreset();
|
||||
toastr.clear();
|
||||
toastr.info(`已切换到预设 "${presetName}"`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function resetSectionPreset(sectionKey) {
|
||||
currentPresets[sectionKey] = JSON.parse(JSON.stringify(defaultPrompts[sectionKey]));
|
||||
currentMixedOrder[sectionKey] = JSON.parse(JSON.stringify(defaultMixedOrder[sectionKey]));
|
||||
savePresets();
|
||||
toastr.success(`${sectionKey} 已恢复为默认设置!`);
|
||||
}
|
||||
|
||||
export function resetPresets() {
|
||||
const activePresetName = presetManager.activePreset;
|
||||
presetManager.presets[activePresetName] = {
|
||||
prompts: JSON.parse(JSON.stringify(defaultPrompts)),
|
||||
mixedOrder: JSON.parse(JSON.stringify(defaultMixedOrder))
|
||||
};
|
||||
|
||||
loadActivePreset();
|
||||
savePresets();
|
||||
toastr.success(`预设 "${activePresetName}" 已恢复为默认设置!`);
|
||||
}
|
||||
|
||||
@@ -1,228 +1,228 @@
|
||||
import { renderExtensionTemplateAsync } from "/scripts/extensions.js";
|
||||
import { POPUP_TYPE, Popup } from "/scripts/popup.js";
|
||||
import { makeDraggable } from './draggable.js';
|
||||
import { sectionTitles, conditionalBlocks, presetSettingsPath } from './config.js';
|
||||
import * as state from './prese_state.js';
|
||||
import { bindEvents } from './prese_events.js';
|
||||
|
||||
let settingsOrb = null;
|
||||
let globalCollapseState = {};
|
||||
|
||||
export function renderPresetManager(context) {
|
||||
const presetManager = state.getPresetManager();
|
||||
const managerHtml = `
|
||||
<div id="preset-manager" style="padding: 8px; border-bottom: 1px solid #ccc; margin-bottom: 8px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap;">
|
||||
<label for="preset-select" style="margin-bottom: 0; font-size: 12px; white-space: nowrap;">选择预设:</label>
|
||||
<select id="preset-select" class="form-control" style="display: inline-block; width: auto; font-size: 12px; padding: 4px 8px; min-width: 120px;"></select>
|
||||
<button id="new-preset" class="btn btn-primary btn-sm" style="font-size: 11px; padding: 4px 8px;">新建</button>
|
||||
<button id="rename-preset" class="btn btn-secondary btn-sm" style="font-size: 11px; padding: 4px 8px;">重命名</button>
|
||||
<button id="delete-preset" class="btn btn-danger btn-sm" style="font-size: 11px; padding: 4px 8px;">删除</button>
|
||||
</div>
|
||||
`;
|
||||
context.find('#preset-manager-container').html(managerHtml);
|
||||
|
||||
const select = context.find('#preset-select');
|
||||
select.empty();
|
||||
for (const presetName in presetManager.presets) {
|
||||
const option = $('<option></option>').val(presetName).text(presetName);
|
||||
if (presetName === presetManager.activePreset) {
|
||||
option.prop('selected', true);
|
||||
}
|
||||
select.append(option);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderEditor(context) {
|
||||
const container = context.find('#prompt-editor-container');
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
const currentMixedOrder = state.getCurrentMixedOrder();
|
||||
|
||||
if (!container.length) {
|
||||
console.error("Amily2 [renderEditor]: Could not find #prompt-editor-container.");
|
||||
return;
|
||||
}
|
||||
|
||||
const openSections = new Set();
|
||||
container.find('.prompt-section').each(function() {
|
||||
const sectionKey = $(this).data('section');
|
||||
const content = $(this).find('.collapsible-content');
|
||||
if (content.is(':visible')) {
|
||||
openSections.add(sectionKey);
|
||||
}
|
||||
});
|
||||
|
||||
container.empty();
|
||||
|
||||
for (const sectionKey in sectionTitles) {
|
||||
const sectionTitle = sectionTitles[sectionKey];
|
||||
const prompts = currentPresets[sectionKey] || [];
|
||||
const order = currentMixedOrder[sectionKey] || [];
|
||||
|
||||
const sectionHtml = $(`
|
||||
<div class="prompt-section" data-section="${sectionKey}">
|
||||
<h3 class="collapsible-header" style="cursor: pointer; user-select: none;">${sectionTitle} <span class="collapse-icon">▶</span></h3>
|
||||
<div class="collapsible-content" style="display: none;">
|
||||
<p class="text-muted">拖拽排序:普通提示词和条件块可自由调整顺序</p>
|
||||
<div class="mixed-list"></div>
|
||||
<div class="section-controls">
|
||||
<button class="add-prompt-item btn btn-primary">+ 提示词</button>
|
||||
<div class="section-action-buttons" style="margin-top: 10px;">
|
||||
<button class="save-section-preset btn btn-success btn-sm">保存</button>
|
||||
<button class="import-section-preset btn btn-info btn-sm">导入</button>
|
||||
<button class="export-section-preset btn btn-warning btn-sm">导出</button>
|
||||
<button class="reset-section-preset btn btn-danger btn-sm">恢复默认</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const listContainer = sectionHtml.find('.mixed-list');
|
||||
|
||||
order.forEach((item, orderIndex) => {
|
||||
let itemHtml;
|
||||
if (item.type === 'prompt') {
|
||||
const prompt = prompts[item.index];
|
||||
if (prompt) {
|
||||
itemHtml = createMixedPromptItemHtml(prompt, item.index, orderIndex, sectionKey);
|
||||
}
|
||||
} else if (item.type === 'conditional') {
|
||||
const block = conditionalBlocks[sectionKey]?.find(b => b.id === item.id);
|
||||
if (block) {
|
||||
itemHtml = createMixedConditionalItemHtml(block, orderIndex, sectionKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemHtml) {
|
||||
listContainer.append(itemHtml);
|
||||
}
|
||||
});
|
||||
|
||||
container.append(sectionHtml);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
container.find('.prompt-section').each(function() {
|
||||
const sectionKey = $(this).data('section');
|
||||
const contentElement = $(this).find('.collapsible-content');
|
||||
const iconElement = $(this).find('.collapse-icon');
|
||||
|
||||
const isExpanded = globalCollapseState[sectionKey] === true || openSections.has(sectionKey);
|
||||
|
||||
if (isExpanded) {
|
||||
contentElement.show();
|
||||
iconElement.text('▼');
|
||||
} else {
|
||||
contentElement.hide();
|
||||
iconElement.text('▶');
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
|
||||
bindEvents(context);
|
||||
}
|
||||
|
||||
function createMixedPromptItemHtml(prompt, promptIndex, orderIndex, sectionKey) {
|
||||
return `
|
||||
<div class="mixed-item prompt-item" data-type="prompt" data-prompt-index="${promptIndex}" data-order-index="${orderIndex}" data-section="${sectionKey}" draggable="false">
|
||||
<div class="item-header">
|
||||
<span class="drag-handle" draggable="true">⋮⋮</span>
|
||||
<div class="role-selector-group">
|
||||
<select class="role-select form-control" style="width: 80px; font-size: 11px; padding: 2px 4px; margin-right: 4px;">
|
||||
<option value="system" ${prompt.role === 'system' ? 'selected' : ''}>系统</option>
|
||||
<option value="user" ${prompt.role === 'user' ? 'selected' : ''}>用户</option>
|
||||
<option value="assistant" ${prompt.role === 'assistant' ? 'selected' : ''}>AI</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="item-controls">
|
||||
<button class="delete-mixed-item btn btn-sm btn-danger">X</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<textarea class="content-textarea form-control">${prompt.content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function createMixedConditionalItemHtml(block, orderIndex, sectionKey) {
|
||||
return `
|
||||
<div class="mixed-item conditional-item" data-type="conditional" data-conditional-id="${block.id}" data-order-index="${orderIndex}" data-section="${sectionKey}" draggable="false">
|
||||
<div class="conditional-line-format">
|
||||
<span class="drag-handle" draggable="true">⋮⋮</span>
|
||||
<span class="conditional-prefix">条件块</span>
|
||||
<span class="conditional-dashes">---</span>
|
||||
<span class="conditional-name">${block.name}</span>
|
||||
<span class="conditional-dashes">---</span>
|
||||
</div>
|
||||
<div class="conditional-description">
|
||||
<code class="text-muted small">${block.description}</code>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export function toggleSettingsOrb() {
|
||||
if (settingsOrb && settingsOrb.length > 0) {
|
||||
settingsOrb.remove();
|
||||
settingsOrb = null;
|
||||
toastr.info('提示词链编辑器已关闭。');
|
||||
} else {
|
||||
settingsOrb = $(`<div id="amily2-settings-orb" title="点击打开提示词链编辑器 (可拖拽)"></div>`);
|
||||
settingsOrb.css({
|
||||
position: 'fixed',
|
||||
top: '85%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: '50px',
|
||||
height: '50px',
|
||||
backgroundColor: 'var(--primary-color)',
|
||||
color: 'white',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'grab',
|
||||
zIndex: '9998',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)'
|
||||
});
|
||||
settingsOrb.html('<i class="fa-solid fa-scroll fa-lg"></i>');
|
||||
$('body').append(settingsOrb);
|
||||
|
||||
makeDraggable(settingsOrb, showPresetSettings, 'amily2_settingsOrb_pos');
|
||||
toastr.info('提示词链编辑器已开启。');
|
||||
}
|
||||
}
|
||||
|
||||
async function showPresetSettings() {
|
||||
const template = $(await renderExtensionTemplateAsync(presetSettingsPath, 'prese-settings'));
|
||||
|
||||
renderPresetManager(template);
|
||||
renderEditor(template);
|
||||
|
||||
const popup = new Popup(template, POPUP_TYPE.TEXT, 'Amily2 提示词链编辑器', {
|
||||
wide: true,
|
||||
large: true,
|
||||
okButton: '关闭',
|
||||
cancelButton: false,
|
||||
});
|
||||
|
||||
await popup.show();
|
||||
}
|
||||
|
||||
export function addPresetSettingsButton() {
|
||||
const button = document.createElement('div');
|
||||
button.id = 'amily2-preset-settings-button';
|
||||
button.classList.add('list-group-item', 'flex-container', 'flexGap5', 'interactable');
|
||||
button.innerHTML = `<i class="fa-solid fa-scroll"></i><span>Amily2 提示词链</span>`;
|
||||
button.addEventListener('click', toggleSettingsOrb);
|
||||
|
||||
const extensionsMenu = document.getElementById('extensionsMenu');
|
||||
if (extensionsMenu && !document.getElementById(button.id)) {
|
||||
extensionsMenu.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
export function getGlobalCollapseState() {
|
||||
return globalCollapseState;
|
||||
}
|
||||
import { renderExtensionTemplateAsync } from "/scripts/extensions.js";
|
||||
import { POPUP_TYPE, Popup } from "/scripts/popup.js";
|
||||
import { makeDraggable } from './draggable.js';
|
||||
import { sectionTitles, conditionalBlocks, presetSettingsPath } from './config.js';
|
||||
import * as state from './prese_state.js';
|
||||
import { bindEvents } from './prese_events.js';
|
||||
|
||||
let settingsOrb = null;
|
||||
let globalCollapseState = {};
|
||||
|
||||
export function renderPresetManager(context) {
|
||||
const presetManager = state.getPresetManager();
|
||||
const managerHtml = `
|
||||
<div id="preset-manager" style="padding: 8px; border-bottom: 1px solid #ccc; margin-bottom: 8px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap;">
|
||||
<label for="preset-select" style="margin-bottom: 0; font-size: 12px; white-space: nowrap;">选择预设:</label>
|
||||
<select id="preset-select" class="form-control" style="display: inline-block; width: auto; font-size: 12px; padding: 4px 8px; min-width: 120px;"></select>
|
||||
<button id="new-preset" class="btn btn-primary btn-sm" style="font-size: 11px; padding: 4px 8px;">新建</button>
|
||||
<button id="rename-preset" class="btn btn-secondary btn-sm" style="font-size: 11px; padding: 4px 8px;">重命名</button>
|
||||
<button id="delete-preset" class="btn btn-danger btn-sm" style="font-size: 11px; padding: 4px 8px;">删除</button>
|
||||
</div>
|
||||
`;
|
||||
context.find('#preset-manager-container').html(managerHtml);
|
||||
|
||||
const select = context.find('#preset-select');
|
||||
select.empty();
|
||||
for (const presetName in presetManager.presets) {
|
||||
const option = $('<option></option>').val(presetName).text(presetName);
|
||||
if (presetName === presetManager.activePreset) {
|
||||
option.prop('selected', true);
|
||||
}
|
||||
select.append(option);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderEditor(context) {
|
||||
const container = context.find('#prompt-editor-container');
|
||||
const currentPresets = state.getCurrentPresets();
|
||||
const currentMixedOrder = state.getCurrentMixedOrder();
|
||||
|
||||
if (!container.length) {
|
||||
console.error("Amily2 [renderEditor]: Could not find #prompt-editor-container.");
|
||||
return;
|
||||
}
|
||||
|
||||
const openSections = new Set();
|
||||
container.find('.prompt-section').each(function() {
|
||||
const sectionKey = $(this).data('section');
|
||||
const content = $(this).find('.collapsible-content');
|
||||
if (content.is(':visible')) {
|
||||
openSections.add(sectionKey);
|
||||
}
|
||||
});
|
||||
|
||||
container.empty();
|
||||
|
||||
for (const sectionKey in sectionTitles) {
|
||||
const sectionTitle = sectionTitles[sectionKey];
|
||||
const prompts = currentPresets[sectionKey] || [];
|
||||
const order = currentMixedOrder[sectionKey] || [];
|
||||
|
||||
const sectionHtml = $(`
|
||||
<div class="prompt-section" data-section="${sectionKey}">
|
||||
<h3 class="collapsible-header" style="cursor: pointer; user-select: none;">${sectionTitle} <span class="collapse-icon">▶</span></h3>
|
||||
<div class="collapsible-content" style="display: none;">
|
||||
<p class="text-muted">拖拽排序:普通提示词和条件块可自由调整顺序</p>
|
||||
<div class="mixed-list"></div>
|
||||
<div class="section-controls">
|
||||
<button class="add-prompt-item btn btn-primary">+ 提示词</button>
|
||||
<div class="section-action-buttons" style="margin-top: 10px;">
|
||||
<button class="save-section-preset btn btn-success btn-sm">保存</button>
|
||||
<button class="import-section-preset btn btn-info btn-sm">导入</button>
|
||||
<button class="export-section-preset btn btn-warning btn-sm">导出</button>
|
||||
<button class="reset-section-preset btn btn-danger btn-sm">恢复默认</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const listContainer = sectionHtml.find('.mixed-list');
|
||||
|
||||
order.forEach((item, orderIndex) => {
|
||||
let itemHtml;
|
||||
if (item.type === 'prompt') {
|
||||
const prompt = prompts[item.index];
|
||||
if (prompt) {
|
||||
itemHtml = createMixedPromptItemHtml(prompt, item.index, orderIndex, sectionKey);
|
||||
}
|
||||
} else if (item.type === 'conditional') {
|
||||
const block = conditionalBlocks[sectionKey]?.find(b => b.id === item.id);
|
||||
if (block) {
|
||||
itemHtml = createMixedConditionalItemHtml(block, orderIndex, sectionKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemHtml) {
|
||||
listContainer.append(itemHtml);
|
||||
}
|
||||
});
|
||||
|
||||
container.append(sectionHtml);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
container.find('.prompt-section').each(function() {
|
||||
const sectionKey = $(this).data('section');
|
||||
const contentElement = $(this).find('.collapsible-content');
|
||||
const iconElement = $(this).find('.collapse-icon');
|
||||
|
||||
const isExpanded = globalCollapseState[sectionKey] === true || openSections.has(sectionKey);
|
||||
|
||||
if (isExpanded) {
|
||||
contentElement.show();
|
||||
iconElement.text('▼');
|
||||
} else {
|
||||
contentElement.hide();
|
||||
iconElement.text('▶');
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
|
||||
bindEvents(context);
|
||||
}
|
||||
|
||||
function createMixedPromptItemHtml(prompt, promptIndex, orderIndex, sectionKey) {
|
||||
return `
|
||||
<div class="mixed-item prompt-item" data-type="prompt" data-prompt-index="${promptIndex}" data-order-index="${orderIndex}" data-section="${sectionKey}" draggable="false">
|
||||
<div class="item-header">
|
||||
<span class="drag-handle" draggable="true">⋮⋮</span>
|
||||
<div class="role-selector-group">
|
||||
<select class="role-select form-control" style="width: 80px; font-size: 11px; padding: 2px 4px; margin-right: 4px;">
|
||||
<option value="system" ${prompt.role === 'system' ? 'selected' : ''}>系统</option>
|
||||
<option value="user" ${prompt.role === 'user' ? 'selected' : ''}>用户</option>
|
||||
<option value="assistant" ${prompt.role === 'assistant' ? 'selected' : ''}>AI</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="item-controls">
|
||||
<button class="delete-mixed-item btn btn-sm btn-danger">X</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<textarea class="content-textarea form-control">${prompt.content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function createMixedConditionalItemHtml(block, orderIndex, sectionKey) {
|
||||
return `
|
||||
<div class="mixed-item conditional-item" data-type="conditional" data-conditional-id="${block.id}" data-order-index="${orderIndex}" data-section="${sectionKey}" draggable="false">
|
||||
<div class="conditional-line-format">
|
||||
<span class="drag-handle" draggable="true">⋮⋮</span>
|
||||
<span class="conditional-prefix">条件块</span>
|
||||
<span class="conditional-dashes">---</span>
|
||||
<span class="conditional-name">${block.name}</span>
|
||||
<span class="conditional-dashes">---</span>
|
||||
</div>
|
||||
<div class="conditional-description">
|
||||
<code class="text-muted small">${block.description}</code>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export function toggleSettingsOrb() {
|
||||
if (settingsOrb && settingsOrb.length > 0) {
|
||||
settingsOrb.remove();
|
||||
settingsOrb = null;
|
||||
toastr.info('提示词链编辑器已关闭。');
|
||||
} else {
|
||||
settingsOrb = $(`<div id="amily2-settings-orb" title="点击打开提示词链编辑器 (可拖拽)"></div>`);
|
||||
settingsOrb.css({
|
||||
position: 'fixed',
|
||||
top: '85%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: '50px',
|
||||
height: '50px',
|
||||
backgroundColor: 'var(--primary-color)',
|
||||
color: 'white',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
cursor: 'grab',
|
||||
zIndex: '9998',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)'
|
||||
});
|
||||
settingsOrb.html('<i class="fa-solid fa-scroll fa-lg"></i>');
|
||||
$('body').append(settingsOrb);
|
||||
|
||||
makeDraggable(settingsOrb, showPresetSettings, 'amily2_settingsOrb_pos');
|
||||
toastr.info('提示词链编辑器已开启。');
|
||||
}
|
||||
}
|
||||
|
||||
async function showPresetSettings() {
|
||||
const template = $(await renderExtensionTemplateAsync(presetSettingsPath, 'prese-settings'));
|
||||
|
||||
renderPresetManager(template);
|
||||
renderEditor(template);
|
||||
|
||||
const popup = new Popup(template, POPUP_TYPE.TEXT, 'Amily2 提示词链编辑器', {
|
||||
wide: true,
|
||||
large: true,
|
||||
okButton: '关闭',
|
||||
cancelButton: false,
|
||||
});
|
||||
|
||||
await popup.show();
|
||||
}
|
||||
|
||||
export function addPresetSettingsButton() {
|
||||
const button = document.createElement('div');
|
||||
button.id = 'amily2-preset-settings-button';
|
||||
button.classList.add('list-group-item', 'flex-container', 'flexGap5', 'interactable');
|
||||
button.innerHTML = `<i class="fa-solid fa-scroll"></i><span>Amily2 提示词链</span>`;
|
||||
button.addEventListener('click', toggleSettingsOrb);
|
||||
|
||||
const extensionsMenu = document.getElementById('extensionsMenu');
|
||||
if (extensionsMenu && !document.getElementById(button.id)) {
|
||||
extensionsMenu.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
export function getGlobalCollapseState() {
|
||||
return globalCollapseState;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user