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, "'"); } _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 `
`; }); // 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 = `