mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 06:55:51 +00:00
ci: auto build & obfuscate [2026-04-06 19:02:37] (Jenkins #10)
This commit is contained in:
542
SL/module/SfiGenModule.js
Normal file
542
SL/module/SfiGenModule.js
Normal file
@@ -0,0 +1,542 @@
|
||||
import { Module, ModuleBuilder } from './Module.js';
|
||||
import { extension_settings, getContext } from '../../../../../extensions.js';
|
||||
import { saveSettingsDebounced, saveChat, reloadCurrentChat, eventSource, event_types } from '../../../../../../script.js';
|
||||
import { registerSlashCommand } from '../../../../../slash-commands.js';
|
||||
|
||||
const extensionName = 'ST-Amily2-Chat-Optimisation-Dev'; // Use main extension name for settings
|
||||
const sfigenSettingsKey = 'sfigen_settings';
|
||||
|
||||
const defaultSettings = {
|
||||
api_key: '',
|
||||
model: 'Qwen/Qwen-Image',
|
||||
negative_prompt: '模糊, 低分辨率, 水印, 文字',
|
||||
image_size: '1664x928',
|
||||
steps: 50,
|
||||
cfg: 4.0,
|
||||
regex_tag: 'sfigen',
|
||||
prefix_prompt: ''
|
||||
};
|
||||
|
||||
const builder = new ModuleBuilder()
|
||||
.name('SfiGen')
|
||||
.view('assets/siliconflow-image-gen.html')
|
||||
.strict(true)
|
||||
.required(['mount']);
|
||||
|
||||
export default class SfiGenModule extends Module {
|
||||
constructor() {
|
||||
super(builder);
|
||||
this.settings = {};
|
||||
}
|
||||
|
||||
async init(ctx = {}) {
|
||||
await super.init(ctx);
|
||||
this._loadSettings();
|
||||
return this;
|
||||
}
|
||||
|
||||
_loadSettings() {
|
||||
if (!extension_settings[extensionName]) {
|
||||
extension_settings[extensionName] = {};
|
||||
}
|
||||
if (!extension_settings[extensionName][sfigenSettingsKey]) {
|
||||
extension_settings[extensionName][sfigenSettingsKey] = { ...defaultSettings };
|
||||
}
|
||||
this.settings = extension_settings[extensionName][sfigenSettingsKey];
|
||||
|
||||
// Ensure all default keys exist
|
||||
for (const key in defaultSettings) {
|
||||
if (!(key in this.settings)) {
|
||||
this.settings[key] = defaultSettings[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_saveSettings() {
|
||||
extension_settings[extensionName][sfigenSettingsKey] = this.settings;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async mount() {
|
||||
if (this.el) {
|
||||
this.el.id = 'amily2_sfigen_panel';
|
||||
this.el.style.display = 'none';
|
||||
}
|
||||
|
||||
this._bindUI();
|
||||
this._registerSlashCommand();
|
||||
this._bindEvents();
|
||||
this._bindButtonsGlobal();
|
||||
}
|
||||
|
||||
_bindUI() {
|
||||
const $el = $(this.el);
|
||||
|
||||
// Bind inputs
|
||||
$el.find('#sfigen_api_key').val(this.settings.api_key).on('input', (e) => {
|
||||
this.settings.api_key = $(e.target).val();
|
||||
this._saveSettings();
|
||||
});
|
||||
$el.find('#sfigen_model').val(this.settings.model).on('input', (e) => {
|
||||
this.settings.model = $(e.target).val();
|
||||
this._saveSettings();
|
||||
});
|
||||
$el.find('#sfigen_negative_prompt').val(this.settings.negative_prompt).on('input', (e) => {
|
||||
this.settings.negative_prompt = $(e.target).val();
|
||||
this._saveSettings();
|
||||
});
|
||||
$el.find('#sfigen_image_size').val(this.settings.image_size).on('change', (e) => {
|
||||
this.settings.image_size = $(e.target).val();
|
||||
this._saveSettings();
|
||||
});
|
||||
$el.find('#sfigen_steps').val(this.settings.steps).on('input', (e) => {
|
||||
this.settings.steps = $(e.target).val();
|
||||
this._saveSettings();
|
||||
});
|
||||
$el.find('#sfigen_cfg').val(this.settings.cfg).on('input', (e) => {
|
||||
this.settings.cfg = $(e.target).val();
|
||||
this._saveSettings();
|
||||
});
|
||||
$el.find('#sfigen_regex_tag').val(this.settings.regex_tag).on('input', (e) => {
|
||||
this.settings.regex_tag = $(e.target).val();
|
||||
this._saveSettings();
|
||||
});
|
||||
$el.find('#sfigen_prefix_prompt').val(this.settings.prefix_prompt).on('input', (e) => {
|
||||
this.settings.prefix_prompt = $(e.target).val();
|
||||
this._saveSettings();
|
||||
});
|
||||
|
||||
// Bind style tags
|
||||
$el.find('.sfigen-style-tag').on('click', (e) => {
|
||||
const promptToAdd = $(e.target).data('prompt');
|
||||
const textarea = $el.find('#sfigen_prefix_prompt');
|
||||
let currentVal = textarea.val().trim();
|
||||
|
||||
if (currentVal) {
|
||||
if (!currentVal.endsWith(',')) {
|
||||
currentVal += ', ';
|
||||
} else {
|
||||
currentVal += ' ';
|
||||
}
|
||||
textarea.val(currentVal + promptToAdd);
|
||||
} else {
|
||||
textarea.val(promptToAdd);
|
||||
}
|
||||
|
||||
textarea.trigger('input');
|
||||
|
||||
$(e.target).css('opacity', '0.5');
|
||||
setTimeout(() => $(e.target).css('opacity', '1'), 200);
|
||||
});
|
||||
|
||||
// Bind back button
|
||||
$el.find('#amily2_sfigen_back_to_main').on('click', () => {
|
||||
$el.hide();
|
||||
$('#amily2_chat_optimiser > .plugin-features').show();
|
||||
});
|
||||
}
|
||||
|
||||
async _generateImage(prompt) {
|
||||
let finalPrompt = prompt;
|
||||
if (this.settings.prefix_prompt && this.settings.prefix_prompt.trim() !== '') {
|
||||
finalPrompt = `${this.settings.prefix_prompt.trim()}, ${prompt}`;
|
||||
}
|
||||
|
||||
console.log(`[SfiGen] 开始生成图片,最终提示词:`, finalPrompt);
|
||||
|
||||
if (!this.settings.api_key) {
|
||||
console.warn(`[SfiGen] 未配置 API Key`);
|
||||
toastr.error('请先在扩展设置中配置 SiliconFlow API Key');
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = 'https://api.siliconflow.cn/v1/images/generations';
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${this.settings.api_key}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const body = {
|
||||
model: this.settings.model,
|
||||
prompt: finalPrompt,
|
||||
negative_prompt: this.settings.negative_prompt,
|
||||
image_size: this.settings.image_size,
|
||||
seed: Math.floor(Math.random() * 1000000000),
|
||||
num_inference_steps: parseInt(this.settings.steps),
|
||||
cfg: parseFloat(this.settings.cfg)
|
||||
};
|
||||
|
||||
try {
|
||||
toastr.info('正在生成图片,请稍候...');
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.images && data.images.length > 0) {
|
||||
toastr.success('图片生成成功!');
|
||||
return data.images[0].url;
|
||||
} else {
|
||||
throw new Error('API 返回数据中没有图片 URL');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[SfiGen] 生成图片失败:`, error);
|
||||
toastr.error(`生成图片失败: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_escapeHtml(unsafe) {
|
||||
return (unsafe || '').replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
|
||||
_processMessageDOM(messageId) {
|
||||
const messageElement = $(`.mes[mesid="${messageId}"] .mes_text`);
|
||||
if (!messageElement.length) return;
|
||||
|
||||
// 检查是否已经处理过,如果已经有容器,说明已经处理过了,直接返回
|
||||
if (messageElement.find('.sfigen-image-container').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let html = messageElement.html();
|
||||
const tag = this.settings.regex_tag || 'sfigen';
|
||||
|
||||
let newHtml = html;
|
||||
let hasMatch = false;
|
||||
|
||||
// 1. 匹配 [tag: prompt]
|
||||
const regexPrompt = new RegExp(`\\[${tag}:\\s*([^\\]]+)\\]`, 'gi');
|
||||
newHtml = newHtml.replace(regexPrompt, (match, prompt) => {
|
||||
hasMatch = true;
|
||||
const buttonId = `sfigen-btn-${messageId}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const safePrompt = this._escapeHtml(prompt);
|
||||
const safeMatch = this._escapeHtml(match);
|
||||
return `<div class="sfigen-image-container" data-message-id="${messageId}" data-prompt="${safePrompt}" data-original-tag="${safeMatch}" style="width: 96%; max-width: 600px; background: var(--SmartThemeBlurTintColor); border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden; margin: 20px auto; padding: 15px; text-align: center; position: relative; z-index: 10;"><button id="${buttonId}" class="sfigen-generate-btn" style="background-color: var(--SmartThemeQuoteColor); color: var(--SmartThemeBodyColor); border: 1px solid var(--SmartThemeBorderColor); padding: 8px 20px; border-radius: 8px; cursor: pointer; pointer-events: auto; display: inline-block; font-weight: bold; transition: all 0.2s;"><i class="fa-solid fa-image"></i> 生成图片</button></div>`;
|
||||
});
|
||||
|
||||
// 2. 匹配 [tag_img: prompt | url1,url2]
|
||||
const regexImg = new RegExp(`\\[${tag}_img:\\s*([^\\]]+)\\]`, 'gi');
|
||||
newHtml = newHtml.replace(regexImg, (match, content) => {
|
||||
hasMatch = true;
|
||||
|
||||
let prompt = "未知提示词";
|
||||
let imageList = [];
|
||||
|
||||
if (content.includes('|')) {
|
||||
const parts = content.split('|');
|
||||
prompt = parts[0].trim();
|
||||
imageList = parts[1].split(',').map(u => u.trim());
|
||||
} else {
|
||||
imageList = content.split(',').map(u => u.trim());
|
||||
}
|
||||
|
||||
const displayUrl = imageList[imageList.length - 1];
|
||||
const buttonId = `sfigen-btn-${messageId}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const safePrompt = this._escapeHtml(prompt);
|
||||
const safeMatch = this._escapeHtml(match);
|
||||
|
||||
let navHtml = '';
|
||||
if (imageList.length > 1) {
|
||||
navHtml = `<div style="display: flex; justify-content: center; gap: 10px; margin-top: 10px;">`;
|
||||
imageList.forEach((url, index) => {
|
||||
const isActive = index === imageList.length - 1;
|
||||
navHtml += `<button class="sfigen-nav-btn" data-url="${this._escapeHtml(url)}" style="width: 12px; height: 12px; border-radius: 50%; border: none; background-color: ${isActive ? 'var(--SmartThemeQuoteColor)' : 'var(--SmartThemeBorderColor)'}; cursor: pointer; padding: 0;"></button>`;
|
||||
});
|
||||
navHtml += `</div>`;
|
||||
}
|
||||
|
||||
return `<div class="sfigen-image-container" data-message-id="${messageId}" data-prompt="${safePrompt}" data-original-tag="${safeMatch}" data-urls="${this._escapeHtml(imageList.join(','))}" style="width: 96%; max-width: 600px; background: var(--SmartThemeBlurTintColor); border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden; margin: 20px auto; padding: 15px; text-align: center; position: relative; z-index: 10;">
|
||||
<div style="width: calc(100% - 4px); margin: 2px auto 15px auto; border: 2px solid rgba(0,0,0,0.15); border-radius: 8px; overflow: hidden; position: relative; cursor: pointer;" class="sfigen-img-wrapper">
|
||||
<img src="${this._escapeHtml(displayUrl)}" class="sfigen-display-img" style="width: 100%; display: block; transition: transform 0.3s;" alt="CG" title="点击放大">
|
||||
<div class="sfigen-img-overlay" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.3); display: flex; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.2s;">
|
||||
<i class="fa-solid fa-magnifying-glass-plus" style="color: white; font-size: 2em;"></i>
|
||||
</div>
|
||||
</div>
|
||||
${navHtml}
|
||||
<div style="display: flex; justify-content: center; gap: 10px; margin-top: 15px;">
|
||||
<button id="${buttonId}" class="sfigen-generate-btn" style="background-color: var(--SmartThemeQuoteColor); color: var(--SmartThemeBodyColor); border: 1px solid var(--SmartThemeBorderColor); padding: 8px 20px; border-radius: 8px; cursor: pointer; pointer-events: auto; display: inline-block; font-weight: bold; transition: all 0.2s;"><i class="fa-solid fa-rotate-right"></i> 再次生成</button>
|
||||
<button class="sfigen-save-btn" data-url="${this._escapeHtml(displayUrl)}" style="background-color: var(--SmartThemeQuoteColor); color: var(--SmartThemeBodyColor); border: 1px solid var(--SmartThemeBorderColor); padding: 8px 20px; border-radius: 8px; cursor: pointer; pointer-events: auto; display: inline-block; font-weight: bold; transition: all 0.2s;"><i class="fa-solid fa-download"></i> 保存图片</button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
if (hasMatch) {
|
||||
messageElement.html(newHtml);
|
||||
}
|
||||
}
|
||||
|
||||
_bindEvents() {
|
||||
const handleMessageRender = (messageId) => {
|
||||
setTimeout(() => this._processMessageDOM(messageId), 50);
|
||||
};
|
||||
|
||||
eventSource.on(event_types.USER_MESSAGE_RENDERED, handleMessageRender);
|
||||
eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, handleMessageRender);
|
||||
eventSource.on(event_types.MESSAGE_UPDATED, handleMessageRender);
|
||||
eventSource.on(event_types.MESSAGE_EDITED, handleMessageRender);
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, handleMessageRender);
|
||||
|
||||
eventSource.on(event_types.CHAT_CHANGED, () => {
|
||||
setTimeout(() => {
|
||||
$('.mes').each((_, el) => {
|
||||
const messageId = $(el).attr('mesid');
|
||||
if (messageId) {
|
||||
this._processMessageDOM(messageId);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Initial processing
|
||||
setTimeout(() => {
|
||||
$('.mes').each((_, el) => {
|
||||
const messageId = $(el).attr('mesid');
|
||||
if (messageId) {
|
||||
this._processMessageDOM(messageId);
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
_bindButtonsGlobal() {
|
||||
$(document).off('click', '.sfigen-generate-btn');
|
||||
|
||||
$(document).on('click', '.sfigen-generate-btn', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
const btn = $(e.currentTarget);
|
||||
const container = btn.closest('.sfigen-image-container');
|
||||
const prompt = container.data('prompt');
|
||||
const messageId = container.data('message-id');
|
||||
const originalTag = container.data('original-tag');
|
||||
|
||||
btn.prop('disabled', true);
|
||||
btn.html('<i class="fa-solid fa-spinner fa-spin"></i> 生成中...');
|
||||
|
||||
const imageUrl = await this._generateImage(prompt);
|
||||
|
||||
if (imageUrl) {
|
||||
const tag = this.settings.regex_tag || 'sfigen';
|
||||
|
||||
let existingUrls = container.data('urls') ? String(container.data('urls')).split(',') : [];
|
||||
existingUrls.push(imageUrl);
|
||||
const urlsString = existingUrls.join(',');
|
||||
|
||||
const newTag = `[${tag}_img: ${prompt} | ${urlsString}]`;
|
||||
|
||||
const context = getContext();
|
||||
const chat = context.chat;
|
||||
|
||||
if (chat && chat[messageId]) {
|
||||
const message = chat[messageId];
|
||||
|
||||
// Fix: Use a more robust replacement strategy
|
||||
// Sometimes originalTag might have been modified by markdown parser
|
||||
// So we replace the whole tag block in the original message
|
||||
const regexPrompt = new RegExp(`\\[${tag}:\\s*([^\\]]+)\\]`, 'gi');
|
||||
const regexImg = new RegExp(`\\[${tag}_img:\\s*([^\\]]+)\\]`, 'gi');
|
||||
|
||||
let replaced = false;
|
||||
|
||||
// Try exact match first
|
||||
if (message.mes.includes(originalTag)) {
|
||||
message.mes = message.mes.replace(originalTag, newTag);
|
||||
replaced = true;
|
||||
}
|
||||
// If not found, try regex replacement
|
||||
else {
|
||||
message.mes = message.mes.replace(regexImg, (match, content) => {
|
||||
if (content.includes(prompt)) {
|
||||
replaced = true;
|
||||
return newTag;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
if (!replaced) {
|
||||
message.mes = message.mes.replace(regexPrompt, (match, p) => {
|
||||
if (p.trim() === prompt.trim()) {
|
||||
replaced = true;
|
||||
return newTag;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (replaced) {
|
||||
await saveChat();
|
||||
|
||||
// 立即在前端替换 DOM,显示生成的图片
|
||||
let navHtml = '';
|
||||
if (existingUrls.length > 1) {
|
||||
navHtml = `<div style="display: flex; justify-content: center; gap: 10px; margin-top: 10px;">`;
|
||||
existingUrls.forEach((url, index) => {
|
||||
const isActive = index === existingUrls.length - 1;
|
||||
navHtml += `<button class="sfigen-nav-btn" data-url="${this._escapeHtml(url)}" style="width: 12px; height: 12px; border-radius: 50%; border: none; background-color: ${isActive ? 'var(--SmartThemeQuoteColor)' : 'var(--SmartThemeBorderColor)'}; cursor: pointer; padding: 0;"></button>`;
|
||||
});
|
||||
navHtml += `</div>`;
|
||||
}
|
||||
|
||||
const newButtonId = `sfigen-btn-${messageId}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const safePrompt = this._escapeHtml(prompt);
|
||||
const safeNewTag = this._escapeHtml(newTag);
|
||||
const safeUrlsString = this._escapeHtml(urlsString);
|
||||
const safeImageUrl = this._escapeHtml(imageUrl);
|
||||
|
||||
const finalHtml = `<div class="sfigen-image-container" data-message-id="${messageId}" data-prompt="${safePrompt}" data-original-tag="${safeNewTag}" data-urls="${safeUrlsString}" style="width: 96%; max-width: 600px; background: var(--SmartThemeBlurTintColor); border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden; margin: 20px auto; padding: 15px; text-align: center; position: relative; z-index: 10;">
|
||||
<div style="width: calc(100% - 4px); margin: 2px auto 15px auto; border: 2px solid rgba(0,0,0,0.15); border-radius: 8px; overflow: hidden; position: relative; cursor: pointer;" class="sfigen-img-wrapper">
|
||||
<img src="${safeImageUrl}" class="sfigen-display-img" style="width: 100%; display: block; transition: transform 0.3s;" alt="CG" title="点击放大">
|
||||
<div class="sfigen-img-overlay" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.3); display: flex; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.2s;">
|
||||
<i class="fa-solid fa-magnifying-glass-plus" style="color: white; font-size: 2em;"></i>
|
||||
</div>
|
||||
</div>
|
||||
${navHtml}
|
||||
<div style="display: flex; justify-content: center; gap: 10px; margin-top: 15px;">
|
||||
<button id="${newButtonId}" class="sfigen-generate-btn" style="background-color: var(--SmartThemeQuoteColor); color: var(--SmartThemeBodyColor); border: 1px solid var(--SmartThemeBorderColor); padding: 8px 20px; border-radius: 8px; cursor: pointer; pointer-events: auto; display: inline-block; font-weight: bold; transition: all 0.2s;"><i class="fa-solid fa-rotate-right"></i> 再次生成</button>
|
||||
<button class="sfigen-save-btn" data-url="${safeImageUrl}" style="background-color: var(--SmartThemeQuoteColor); color: var(--SmartThemeBodyColor); border: 1px solid var(--SmartThemeBorderColor); padding: 8px 20px; border-radius: 8px; cursor: pointer; pointer-events: auto; display: inline-block; font-weight: bold; transition: all 0.2s;"><i class="fa-solid fa-download"></i> 保存图片</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
container.replaceWith(finalHtml);
|
||||
|
||||
} else {
|
||||
console.warn(`[SfiGen] Could not find tag to replace in message ${messageId}`);
|
||||
toastr.warning('图片已生成,但无法保存到聊天记录中。');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
btn.prop('disabled', false);
|
||||
btn.html('<i class="fa-solid fa-image"></i> 重新生成');
|
||||
}
|
||||
});
|
||||
|
||||
// Image hover and zoom
|
||||
$(document).on('mouseenter', '.sfigen-img-wrapper', function() {
|
||||
$(this).find('.sfigen-img-overlay').css('opacity', '1');
|
||||
$(this).find('.sfigen-display-img').css('transform', 'scale(1.02)');
|
||||
}).on('mouseleave', '.sfigen-img-wrapper', function() {
|
||||
$(this).find('.sfigen-img-overlay').css('opacity', '0');
|
||||
$(this).find('.sfigen-display-img').css('transform', 'scale(1)');
|
||||
});
|
||||
|
||||
$(document).on('click', '.sfigen-img-wrapper', function(e) {
|
||||
e.stopPropagation();
|
||||
const imgUrl = $(this).find('img').attr('src');
|
||||
|
||||
const overlay = $(`
|
||||
<div id="sfigen-zoom-overlay" style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.9); z-index: 9999; display: flex; justify-content: center; align-items: center; cursor: zoom-out; opacity: 0; transition: opacity 0.3s;">
|
||||
<img src="${imgUrl}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 8px; box-shadow: 0 0 20px rgba(0,0,0,0.5); transform: scale(0.9); transition: transform 0.3s;">
|
||||
<div style="position: absolute; top: 20px; right: 20px; color: white; font-size: 24px; cursor: pointer;"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$('body').append(overlay);
|
||||
|
||||
setTimeout(() => {
|
||||
overlay.css('opacity', '1');
|
||||
overlay.find('img').css('transform', 'scale(1)');
|
||||
}, 10);
|
||||
|
||||
overlay.on('click', function() {
|
||||
overlay.css('opacity', '0');
|
||||
overlay.find('img').css('transform', 'scale(0.9)');
|
||||
setTimeout(() => overlay.remove(), 300);
|
||||
});
|
||||
});
|
||||
|
||||
// Save image
|
||||
$(document).on('click', '.sfigen-save-btn', async function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const url = $(this).data('url');
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
a.download = `sfigen_${timestamp}.png`;
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
document.body.removeChild(a);
|
||||
|
||||
toastr.success('图片已保存到默认下载目录');
|
||||
} catch (error) {
|
||||
console.error(`[SfiGen] 保存图片失败:`, error);
|
||||
toastr.error('保存图片失败');
|
||||
}
|
||||
});
|
||||
|
||||
// Nav buttons
|
||||
$(document).on('click', '.sfigen-nav-btn', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const btn = $(this);
|
||||
const container = btn.closest('.sfigen-image-container');
|
||||
const targetUrl = btn.data('url');
|
||||
|
||||
container.find('.sfigen-display-img').attr('src', targetUrl);
|
||||
container.find('.sfigen-save-btn').data('url', targetUrl);
|
||||
|
||||
container.find('.sfigen-nav-btn').css('background-color', 'var(--SmartThemeBorderColor)');
|
||||
btn.css('background-color', 'var(--SmartThemeQuoteColor)');
|
||||
});
|
||||
}
|
||||
|
||||
_registerSlashCommand() {
|
||||
registerSlashCommand('sfigen', async (args, value) => {
|
||||
if (!value) {
|
||||
toastr.warning('请提供提示词。例如: /sfigen 一个可爱的猫咪');
|
||||
return;
|
||||
}
|
||||
const imageUrl = await this._generateImage(value);
|
||||
if (imageUrl) {
|
||||
const context = getContext();
|
||||
const message = `<img src="${imageUrl}" alt="Generated Image" style="max-width: 100%; border-radius: 8px;" />`;
|
||||
|
||||
context.chat.push({
|
||||
name: 'System',
|
||||
is_user: false,
|
||||
is_system: true,
|
||||
mes: message,
|
||||
send_date: Date.now(),
|
||||
});
|
||||
await saveChat();
|
||||
|
||||
if (typeof window.updateChat === 'function') {
|
||||
window.updateChat();
|
||||
} else if (typeof window.updateMessageBlock === 'function') {
|
||||
window.updateMessageBlock(context.chat.length - 1, context.chat[context.chat.length - 1]);
|
||||
} else {
|
||||
await reloadCurrentChat();
|
||||
}
|
||||
}
|
||||
}, [], '使用 SiliconFlow 生成图片', true, true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user