mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 04:35:51 +00:00
Initial commit with CC BY-NC-ND 4.0 license
This commit is contained in:
349
PreOptimizationViewer/index.js
Normal file
349
PreOptimizationViewer/index.js
Normal file
@@ -0,0 +1,349 @@
|
||||
import { renderExtensionTemplateAsync, extension_settings } from '/scripts/extensions.js';
|
||||
import { POPUP_TYPE, Popup } from '/scripts/popup.js';
|
||||
import { extensionName } from '../utils/settings.js';
|
||||
import { applyExclusionRules } from '../core/utils/rag-tag-extractor.js';
|
||||
|
||||
const preOptimizationViewerPath = `third-party/${extensionName}/PreOptimizationViewer`;
|
||||
let viewerOrb = null;
|
||||
function addViewerButton() {
|
||||
const button = document.createElement('div');
|
||||
button.id = 'pre-optimization-viewer-btn';
|
||||
button.classList.add('list-group-item', 'flex-container', 'flexGap5', 'interactable');
|
||||
button.innerHTML = `<i class="fa-solid fa-file-alt"></i><span>查看优化前文</span>`;
|
||||
button.title = '打开/关闭优化前文查看器';
|
||||
|
||||
const extensionsMenu = document.getElementById('extensionsMenu');
|
||||
if (extensionsMenu) {
|
||||
extensionsMenu.appendChild(button);
|
||||
$(button).on('click', toggleViewerOrb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function toggleViewerOrb() {
|
||||
if (viewerOrb && viewerOrb.length > 0) {
|
||||
viewerOrb.remove();
|
||||
viewerOrb = null;
|
||||
toastr.info('优化前文查看器已关闭。');
|
||||
} else {
|
||||
viewerOrb = $(`<div id="viewer-orb" title="点击查看优化前文 (可拖拽)"></div>`);
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
viewerOrb.css({
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: isMobile ? '56px' : '50px',
|
||||
height: isMobile ? '56px' : '50px',
|
||||
minWidth: '44px',
|
||||
minHeight: '44px',
|
||||
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)',
|
||||
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
||||
userSelect: 'none',
|
||||
webkitUserSelect: 'none',
|
||||
webkitTouchCallout: 'none',
|
||||
webkitTapHighlightColor: 'transparent',
|
||||
touchAction: 'none'
|
||||
});
|
||||
viewerOrb.html('<i class="fa-solid fa-file-alt fa-lg"></i>');
|
||||
$('body').append(viewerOrb);
|
||||
|
||||
makeDraggable(viewerOrb, showViewerPopup);
|
||||
|
||||
toastr.info('优化前文查看器已开启。');
|
||||
}
|
||||
}
|
||||
|
||||
function loadJsDiff() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.Diff) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jsdiff/5.1.0/diff.min.js';
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function renderDiffContent($contentContainer) {
|
||||
const snapshot = window.Amily2PreOptimizationSnapshot;
|
||||
|
||||
if (!snapshot || !snapshot.original) {
|
||||
$contentContainer.html('<p style="color: grey;">尚未捕获到优化前文。</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = extension_settings[extensionName];
|
||||
let originalText = snapshot.original;
|
||||
|
||||
if (settings.optimizationExclusionEnabled && settings.optimizationExclusionRules?.length > 0) {
|
||||
originalText = applyExclusionRules(originalText, settings.optimizationExclusionRules);
|
||||
}
|
||||
|
||||
const normalizeWhitespace = (text) => {
|
||||
|
||||
return text.replace(/\n{3,}/g, '\n\n').trim();
|
||||
};
|
||||
|
||||
originalText = normalizeWhitespace(originalText);
|
||||
|
||||
if (snapshot.optimized === null) {
|
||||
const fallbackHtml = `
|
||||
<div class="diff-fallback">
|
||||
<h4>正在等待优化结果...</h4>
|
||||
<p>这通常需要几秒钟的时间。以下是优化前的原始文本(已应用排除和规范化规则):</p>
|
||||
<hr>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">${originalText.replace(/</g, '<').replace(/>/g, '>')}</pre>
|
||||
</div>`;
|
||||
$contentContainer.html(fallbackHtml);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await loadJsDiff();
|
||||
const { optimized } = snapshot;
|
||||
|
||||
let cleanedOptimized = optimized.replace(/<!--[\s\S]*?-->/g, '');
|
||||
|
||||
cleanedOptimized = normalizeWhitespace(cleanedOptimized);
|
||||
|
||||
const diff = window.Diff.diffLines(originalText, cleanedOptimized, { newlineIsToken: true });
|
||||
|
||||
let diffHtml = '<pre style="white-space: pre-wrap; word-wrap: break-word;">';
|
||||
diff.forEach(part => {
|
||||
const color = part.added ? 'green' : part.removed ? 'red' : 'grey';
|
||||
const text = part.value.replace(/</g, '<').replace(/>/g, '>');
|
||||
if (part.removed) {
|
||||
diffHtml += `<del style="color: ${color}; background-color: rgba(255, 0, 0, 0.1); text-decoration: none;">${text}</del>`;
|
||||
} else if (part.added) {
|
||||
diffHtml += `<ins style="color: ${color}; background-color: rgba(0, 255, 0, 0.1); text-decoration: none;">${text}</ins>`;
|
||||
} else {
|
||||
diffHtml += `<span style="color: ${color};">${text}</span>`;
|
||||
}
|
||||
});
|
||||
diffHtml += '</pre>';
|
||||
$contentContainer.html(diffHtml);
|
||||
|
||||
} catch (error) {
|
||||
toastr.warning('加载差异对比库失败,将分别显示原文。');
|
||||
const fallbackHtml = `<div class="diff-fallback">
|
||||
<h4>未能加载差异对比视图</h4>
|
||||
<p>这通常是由于网络问题无法访问 cdnjs.cloudflare.com 导致的。以下是优化前后的文本:</p>
|
||||
<hr>
|
||||
<h5>优化前(已应用排除和规范化规则)</h5>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">${originalText.replace(/</g, '<').replace(/>/g, '>')}</pre>
|
||||
<hr>
|
||||
<h5>优化后</h5>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">${normalizeWhitespace(snapshot.optimized.replace(/<!--[\s\S]*?-->/g, '')).replace(/</g, '<').replace(/>/g, '>')}</pre>
|
||||
</div>`;
|
||||
$contentContainer.html(fallbackHtml);
|
||||
}
|
||||
}
|
||||
|
||||
async function showViewerPopup() {
|
||||
const snapshot = window.Amily2PreOptimizationSnapshot;
|
||||
if (!snapshot || !snapshot.original) {
|
||||
toastr.info('目前没有可供查看的优化前文。');
|
||||
return;
|
||||
}
|
||||
|
||||
const templateHtml = await renderExtensionTemplateAsync(preOptimizationViewerPath, 'template');
|
||||
const template = $(templateHtml);
|
||||
const contentDiv = template.find('#pre-optimization-content');
|
||||
|
||||
await renderDiffContent(contentDiv);
|
||||
|
||||
new Popup(template, POPUP_TYPE.OK, '优化前后对比', {
|
||||
wide: true,
|
||||
large: true,
|
||||
allowVerticalScrolling: true
|
||||
}).show();
|
||||
}
|
||||
|
||||
|
||||
function makeDraggable($element, onClick) {
|
||||
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'
|
||||
});
|
||||
|
||||
localStorage.setItem('preOptimizationViewer_buttonPos', 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',
|
||||
'user-select': 'none',
|
||||
'pointer-events': 'auto',
|
||||
'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',
|
||||
'user-select': 'auto',
|
||||
'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);
|
||||
|
||||
$(document).on('mousemove.draggable', dragMove);
|
||||
$(document).on('touchmove.draggable', dragMove);
|
||||
$(document).on('mouseup.draggable', dragEnd);
|
||||
$(document).on('touchend.draggable', dragEnd);
|
||||
|
||||
$element.on('click', (e) => {
|
||||
if (hasDragged) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('resize.draggable', () => {
|
||||
if ($element.length) {
|
||||
keepInBounds($element);
|
||||
}
|
||||
});
|
||||
|
||||
$element.css({
|
||||
'cursor': 'grab',
|
||||
'user-select': 'none',
|
||||
'-webkit-user-select': 'none'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function handleTextUpdate() {
|
||||
const $popup = $('.popup:visible').filter(function() {
|
||||
return $(this).find('.popup-header h4').text().trim() === '优化前后对比';
|
||||
});
|
||||
|
||||
if ($popup.length > 0) {
|
||||
const $contentDiv = $popup.find('#pre-optimization-content');
|
||||
renderDiffContent($contentDiv);
|
||||
toastr.success('优化对比已实时更新。', '【查看器】', { timeOut: 2000 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (document.getElementById('extensionsMenu')) {
|
||||
clearInterval(interval);
|
||||
addViewerButton();
|
||||
document.addEventListener('preOptimizationStateUpdated', handleTextUpdate);
|
||||
}
|
||||
}, 500);
|
||||
195
PreOptimizationViewer/style.css
Normal file
195
PreOptimizationViewer/style.css
Normal file
@@ -0,0 +1,195 @@
|
||||
|
||||
#viewer-orb {
|
||||
position: fixed !important;
|
||||
z-index: 9998;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: 3px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
touch-action: none;
|
||||
|
||||
box-shadow:
|
||||
0 8px 25px rgba(102, 126, 234, 0.4),
|
||||
0 4px 10px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
#viewer-orb:hover {
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
box-shadow:
|
||||
0 12px 35px rgba(102, 126, 234, 0.5),
|
||||
0 8px 15px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
#viewer-orb:active {
|
||||
transform: translateY(0) scale(0.95);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
#viewer-orb.dragging {
|
||||
cursor: grabbing !important;
|
||||
transform: scale(1.1);
|
||||
box-shadow:
|
||||
0 15px 40px rgba(102, 126, 234, 0.6),
|
||||
0 10px 20px rgba(0, 0, 0, 0.4);
|
||||
border-color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#viewer-orb {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.pre-optimization-viewer-content {
|
||||
padding: 20px;
|
||||
background: linear-gradient(145deg, #f8f9fa, #e9ecef);
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pre-optimization-viewer-content textarea {
|
||||
width: 100% !important;
|
||||
height: 450px !important;
|
||||
min-height: 300px;
|
||||
padding: 20px !important;
|
||||
border: 2px solid #e0e6ed !important;
|
||||
border-radius: 10px !important;
|
||||
background: #ffffff !important;
|
||||
color: #2c3e50 !important;
|
||||
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 1.6 !important;
|
||||
white-space: pre-wrap !important;
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0, 0, 0, 0.1),
|
||||
inset 0 1px 2px rgba(0, 0, 0, 0.05) !important;
|
||||
transition: all 0.3s ease !important;
|
||||
resize: vertical !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.pre-optimization-viewer-content textarea:focus {
|
||||
border-color: #667eea !important;
|
||||
box-shadow:
|
||||
0 4px 12px rgba(102, 126, 234, 0.2),
|
||||
inset 0 1px 2px rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
|
||||
.popup-body:has(.pov-container) {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
padding: 5px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.pov-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.pov-content-container {
|
||||
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
|
||||
padding: 15px;
|
||||
border: 2px solid #e0e6ed;
|
||||
border-radius: 10px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.pre-optimization-content-area {
|
||||
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
||||
line-height: 1.6;
|
||||
color: #2c3e50;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.diff-fallback {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.diff-fallback h4 {
|
||||
color: #d9534f;
|
||||
border-bottom: 1px solid #d9534f;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.diff-fallback p {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.diff-fallback pre {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
color: #333;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.pov-content-container::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.pov-content-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.pov-content-container::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.pov-content-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.pre-optimization-viewer-content textarea::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.pre-optimization-viewer-content textarea::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.pre-optimization-viewer-content textarea::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.pre-optimization-viewer-content textarea::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
7
PreOptimizationViewer/template.html
Normal file
7
PreOptimizationViewer/template.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="pov-container">
|
||||
<div id="pov-content-container" class="pov-content-container">
|
||||
<div id="pre-optimization-content" class="pre-optimization-content-area">
|
||||
正在加载内容...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user