703 lines
26 KiB
JavaScript
703 lines
26 KiB
JavaScript
// 提示词管理器核心模块,只包含业务逻辑和数据管理
|
||
import { PromptManagerUI } from './styles/PromptManagerUI.js';
|
||
import { globalTagColorManager } from './styles/components/TagColorManager.js';
|
||
|
||
export class PromptManager {
|
||
constructor() {
|
||
this.prompts = [];
|
||
this.loadPrompts();
|
||
} async loadPrompts() {
|
||
try {
|
||
// 从后端API加载最新数据
|
||
const response = await fetch('/dd_nodes/load_prompts');
|
||
if (response.ok) {
|
||
const result = await response.json();
|
||
if (result.success && result.data && result.data.prompts) {
|
||
this.prompts = result.data.prompts;
|
||
// 确保所有提示词都有标签属性(向后兼容)
|
||
this.prompts.forEach(prompt => {
|
||
if (!prompt.tags) {
|
||
prompt.tags = [];
|
||
}
|
||
});
|
||
|
||
// 加载标签颜色数据(如果存在)
|
||
if (result.data.tags && typeof result.data.tags === 'object') {
|
||
console.log('从后端加载标签颜色数据:', result.data.tags);
|
||
// 恢复标签颜色到全局标签颜色管理器
|
||
Object.entries(result.data.tags).forEach(([tagName, color]) => {
|
||
globalTagColorManager.setTagColor(tagName, color);
|
||
});
|
||
}
|
||
|
||
console.log(`从后端API加载了 ${this.prompts.length} 个提示词`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果API失败,使用空列表
|
||
this.prompts = [];
|
||
console.log('初始化空的提示词列表');
|
||
} catch (error) {
|
||
console.error('加载提示词失败:', error);
|
||
this.prompts = [];
|
||
}
|
||
}savePrompts() {
|
||
try {
|
||
// 直接同步到后端API
|
||
this.syncToBackend();
|
||
console.log('提示词已自动同步到后端');
|
||
} catch (error) {
|
||
console.error('保存提示词失败:', error);
|
||
}
|
||
} // 同步数据到后端API
|
||
async syncToBackend() {
|
||
try {
|
||
// 获取标签数据 - 直接从标签颜色管理器获取最新数据
|
||
const tagsData = Object.fromEntries(globalTagColorManager.colorMap);
|
||
|
||
console.log('同步标签数据到后端:', tagsData);
|
||
|
||
const response = await fetch('/dd_nodes/save_prompts', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
prompts: this.prompts,
|
||
tags: tagsData // 包含标签数据
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json();
|
||
if (result.success) {
|
||
console.log(`成功同步 ${result.count} 个提示词和 ${Object.keys(tagsData).length} 个标签到后端`);
|
||
} else {
|
||
console.error('后端同步失败:', result.error);
|
||
}
|
||
} else {
|
||
console.error('后端同步请求失败:', response.status);
|
||
}
|
||
} catch (error) {
|
||
console.error('后端同步失败:', error);
|
||
}
|
||
}// 已弃用:自动同步功能已转移到后端API
|
||
// 保留此方法仅为向后兼容
|
||
autoSyncToJsonFile() {
|
||
// 现在通过syncToBackend()方法同步到后端
|
||
console.log('已转为使用后端API同步');
|
||
}// 保存提示词到插件目录的JSON文件
|
||
async saveToJsonFile() {
|
||
try {
|
||
const promptsData = {
|
||
version: "2.4.0",
|
||
exportTime: new Date().toISOString(),
|
||
description: "ComfyUI-DD-Nodes 提示词管理器自动生成的提示词数据文件",
|
||
totalCount: this.prompts.length,
|
||
prompts: this.prompts
|
||
};
|
||
|
||
const dataStr = JSON.stringify(promptsData, null, 2);
|
||
|
||
// 由于浏览器安全限制,无法直接写入本地文件
|
||
// 但我们可以通过定期提醒用户备份的方式来实现数据持久化
|
||
|
||
// 检查是否需要提醒用户备份
|
||
this.checkBackupReminder();
|
||
|
||
// 将数据存储在内存中,供导出使用
|
||
this._lastSavedData = promptsData;
|
||
|
||
console.log('提示词数据已准备好,建议定期导出备份');
|
||
} catch (error) {
|
||
console.warn('准备备份数据失败:', error);
|
||
}
|
||
}
|
||
|
||
// 检查备份提醒
|
||
checkBackupReminder() {
|
||
const lastBackupTime = localStorage.getItem('comfyui_dd_last_backup_time');
|
||
const currentTime = Date.now();
|
||
const backupInterval = 24 * 60 * 60 * 1000; // 24小时
|
||
|
||
if (!lastBackupTime || (currentTime - parseInt(lastBackupTime)) > backupInterval) {
|
||
// 显示备份提醒
|
||
this.showBackupNotification();
|
||
localStorage.setItem('comfyui_dd_last_backup_time', currentTime.toString());
|
||
}
|
||
}
|
||
|
||
// 显示备份通知
|
||
showBackupNotification() {
|
||
if (this.prompts.length > 0) {
|
||
console.log(`🔔 备份提醒: 您有 ${this.prompts.length} 个提示词,建议导出备份以防数据丢失`);
|
||
|
||
// 如果有UI实例,可以显示更友好的提醒
|
||
if (typeof window !== 'undefined' && this.prompts.length >= 5) {
|
||
setTimeout(() => {
|
||
if (confirm(`您已创建了 ${this.prompts.length} 个提示词,建议现在导出备份文件吗?\n\n备份文件可以在更新插件或更换设备时恢复您的提示词。`)) {
|
||
this.exportPrompts();
|
||
}
|
||
}, 1000);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 自动创建本地备份文件(供开发者在插件更新时使用)
|
||
createLocalBackup() {
|
||
try {
|
||
const promptsData = {
|
||
version: "2.4.0",
|
||
exportTime: new Date().toISOString(),
|
||
description: "ComfyUI-DD-Nodes 提示词管理器自动备份",
|
||
totalCount: this.prompts.length,
|
||
prompts: this.prompts
|
||
};
|
||
|
||
// 将备份数据存储到特殊的localStorage键中
|
||
localStorage.setItem('comfyui_dd_prompts_backup', JSON.stringify(promptsData));
|
||
localStorage.setItem('comfyui_dd_backup_timestamp', Date.now().toString());
|
||
|
||
console.log('已创建本地备份');
|
||
} catch (error) {
|
||
console.error('创建本地备份失败:', error);
|
||
}
|
||
}
|
||
|
||
addPrompt(name, content, tags = []) {
|
||
const prompt = {
|
||
id: Date.now(),
|
||
name: name,
|
||
content: content,
|
||
tags: tags || [],
|
||
createdAt: new Date().toISOString()
|
||
};
|
||
this.prompts.push(prompt);
|
||
this.savePrompts();
|
||
return prompt;
|
||
}
|
||
|
||
updatePrompt(id, name, content, tags = []) {
|
||
const index = this.prompts.findIndex(p => p.id === id);
|
||
if (index !== -1) {
|
||
this.prompts[index].name = name;
|
||
this.prompts[index].content = content;
|
||
this.prompts[index].tags = tags || [];
|
||
this.prompts[index].updatedAt = new Date().toISOString();
|
||
this.savePrompts();
|
||
return this.prompts[index];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
deletePrompt(id) {
|
||
const index = this.prompts.findIndex(p => p.id === id);
|
||
if (index !== -1) {
|
||
this.prompts.splice(index, 1);
|
||
this.savePrompts();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
getPrompts() {
|
||
return this.prompts;
|
||
}
|
||
|
||
// 搜索提示词
|
||
searchPrompts(keyword) {
|
||
if (!keyword || keyword.trim() === '') {
|
||
return this.prompts;
|
||
}
|
||
|
||
const searchTerm = keyword.trim().toLowerCase();
|
||
|
||
// 检查是否是多标签搜索(以逗号分隔)
|
||
const isMultiTagSearch = searchTerm.includes(',');
|
||
const searchTags = isMultiTagSearch
|
||
? searchTerm.split(',').map(tag => tag.trim()).filter(tag => tag)
|
||
: [];
|
||
|
||
// 按相关性排序搜索结果
|
||
const searchResults = this.prompts.map(prompt => {
|
||
const name = prompt.name.toLowerCase();
|
||
const content = prompt.content.toLowerCase();
|
||
const tags = (prompt.tags || []).map(tag => tag.toLowerCase());
|
||
|
||
let score = 0;
|
||
|
||
// 如果是多标签搜索
|
||
if (isMultiTagSearch && searchTags.length > 0) {
|
||
const matchedTags = searchTags.filter(searchTag =>
|
||
tags.some(tag => tag.includes(searchTag))
|
||
);
|
||
|
||
if (matchedTags.length > 0) {
|
||
// 根据匹配的标签数量给分
|
||
score = 1000 + (matchedTags.length * 100);
|
||
|
||
// 如果所有搜索的标签都匹配,额外加分
|
||
if (matchedTags.length === searchTags.length) {
|
||
score += 500;
|
||
}
|
||
}
|
||
} else {
|
||
// 单关键词搜索
|
||
|
||
// 标签完全匹配得分最高
|
||
const exactTagMatch = tags.find(tag => tag === searchTerm);
|
||
if (exactTagMatch) {
|
||
score = 1100;
|
||
}
|
||
// 标签包含搜索词
|
||
else if (tags.some(tag => tag.includes(searchTerm))) {
|
||
score = 1000;
|
||
}
|
||
// 名称完全匹配得分次高
|
||
else if (name === searchTerm) {
|
||
score = 900;
|
||
}
|
||
// 名称开头匹配得分次高
|
||
else if (name.startsWith(searchTerm)) {
|
||
score = 800;
|
||
}
|
||
// 名称包含搜索词
|
||
else if (name.includes(searchTerm)) {
|
||
score = 700;
|
||
}
|
||
// 内容开头匹配
|
||
else if (content.startsWith(searchTerm)) {
|
||
score = 600;
|
||
}
|
||
// 内容包含搜索词
|
||
else if (content.includes(searchTerm)) {
|
||
score = 500;
|
||
}
|
||
|
||
// 根据匹配位置调整分数(越靠前分数越高)
|
||
if (score > 0 && score < 1000) { // 不调整标签匹配的分数
|
||
const nameIndex = name.indexOf(searchTerm);
|
||
const contentIndex = content.indexOf(searchTerm);
|
||
|
||
if (nameIndex >= 0) {
|
||
score += (100 - nameIndex); // 名称中越靠前分数越高
|
||
} else if (contentIndex >= 0) {
|
||
score += (50 - Math.min(contentIndex, 50)); // 内容中越靠前分数越高
|
||
}
|
||
}
|
||
}
|
||
|
||
return { prompt, score };
|
||
})
|
||
.filter(item => item.score > 0) // 只返回有匹配的结果
|
||
.sort((a, b) => b.score - a.score) // 按分数降序排列
|
||
.map(item => item.prompt); // 提取提示词对象
|
||
|
||
return searchResults;
|
||
} // 导出提示词到 JSON 格式
|
||
exportPrompts() {
|
||
// 获取标签颜色数据
|
||
const tagsData = Object.fromEntries(globalTagColorManager.colorMap);
|
||
|
||
// 使用当前最新的提示词数据
|
||
const promptsData = {
|
||
version: "2.4.0",
|
||
exportTime: new Date().toISOString(),
|
||
description: "ComfyUI-DD-Nodes 提示词管理器导出的提示词数据",
|
||
totalCount: this.prompts.length,
|
||
prompts: this.prompts,
|
||
tags: tagsData // 添加标签颜色数据
|
||
};
|
||
|
||
const dataStr = JSON.stringify(promptsData, null, 2);
|
||
const dataBlob = new Blob([dataStr], {type: 'application/json'});
|
||
const url = URL.createObjectURL(dataBlob);
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
|
||
// 使用时间戳生成文件名
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
||
link.download = `comfyui_prompts_${timestamp}.json`;
|
||
|
||
link.click();
|
||
URL.revokeObjectURL(url);
|
||
}// 从 JSON 文件导入提示词(兼容新旧格式)
|
||
importPrompts(file) {
|
||
return new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => { try {
|
||
const imported = JSON.parse(e.target.result);
|
||
let promptsToImport = [];
|
||
|
||
// 检查是否是新格式(包含metadata)
|
||
if (imported.prompts && Array.isArray(imported.prompts)) {
|
||
promptsToImport = imported.prompts;
|
||
|
||
// 导入标签颜色数据(如果存在)
|
||
if (imported.tags && typeof imported.tags === 'object') {
|
||
console.log('导入标签颜色数据:', imported.tags);
|
||
// 合并标签颜色到全局标签颜色管理器
|
||
Object.entries(imported.tags).forEach(([tagName, color]) => {
|
||
globalTagColorManager.setTagColor(tagName, color);
|
||
});
|
||
}
|
||
}
|
||
// 兼容旧格式(直接是数组)
|
||
else if (Array.isArray(imported)) {
|
||
promptsToImport = imported;
|
||
}
|
||
else {
|
||
reject('无效的JSON格式:文件必须包含提示词数组');
|
||
return;
|
||
}
|
||
|
||
if (promptsToImport.length === 0) {
|
||
reject('文件中没有找到提示词数据');
|
||
return;
|
||
}
|
||
|
||
// 合并导入的提示词,避免ID冲突
|
||
const maxId = this.prompts.length > 0 ? Math.max(...this.prompts.map(p => p.id)) : 0;
|
||
let importedCount = 0;
|
||
|
||
promptsToImport.forEach((prompt, index) => {
|
||
// 验证提示词数据完整性
|
||
if (!prompt.name || !prompt.content) {
|
||
console.warn(`跳过无效提示词 #${index + 1}:`, prompt);
|
||
return;
|
||
}
|
||
|
||
// 检查是否已存在相同名称的提示词
|
||
const existingPrompt = this.prompts.find(p => p.name === prompt.name);
|
||
if (existingPrompt) {
|
||
console.warn(`跳过重复提示词: ${prompt.name}`);
|
||
return;
|
||
}
|
||
|
||
// 分配新ID并添加到列表
|
||
prompt.id = maxId + importedCount + 1;
|
||
// 确保标签属性存在(向后兼容)
|
||
if (!prompt.tags) {
|
||
prompt.tags = [];
|
||
}
|
||
// 添加导入时间标记
|
||
prompt.importedAt = new Date().toISOString();
|
||
|
||
this.prompts.push(prompt);
|
||
importedCount++;
|
||
});
|
||
|
||
if (importedCount === 0) {
|
||
reject('没有导入任何新的提示词(可能都已存在)');
|
||
return;
|
||
}
|
||
|
||
// 导入完成后自动保存
|
||
this.savePrompts();
|
||
resolve(importedCount);
|
||
} catch (error) {
|
||
reject('解析JSON文件失败: ' + error.message);
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
});
|
||
}
|
||
}
|
||
|
||
export class PromptEmbedder {
|
||
constructor(node, promptManager) {
|
||
this.node = node;
|
||
this.promptManager = promptManager;
|
||
this.ui = new PromptManagerUI(this);
|
||
}
|
||
|
||
show() {
|
||
this.ui.show();
|
||
}
|
||
|
||
hide() {
|
||
this.ui.hide();
|
||
}
|
||
|
||
// 应用提示词到节点
|
||
applyPrompt(prompt) {
|
||
// 查找所有文本widget
|
||
const textWidgets = this.node.widgets.filter(w => this.isTextWidget(w));
|
||
|
||
if (textWidgets.length === 0) {
|
||
alert('未找到可用的文本输入框');
|
||
return;
|
||
}
|
||
|
||
// 如果有多个文本输入框,显示选择器
|
||
if (textWidgets.length > 1) {
|
||
// 首先检查是否有当前聚焦的widget
|
||
const focusedWidget = textWidgets.find(w =>
|
||
w.element && w.element === document.activeElement
|
||
);
|
||
|
||
if (focusedWidget) {
|
||
// 如果有聚焦的widget,直接应用到该widget
|
||
this.applyPromptToWidget(prompt, focusedWidget);
|
||
return;
|
||
}
|
||
|
||
// 否则显示选择器
|
||
this.ui.showTextWidgetSelector(prompt);
|
||
return;
|
||
}
|
||
|
||
// 如果只有一个文本输入框,直接应用
|
||
this.applyPromptToWidget(prompt, textWidgets[0]);
|
||
}
|
||
|
||
isTextWidget(widget) {
|
||
if (!widget) return false;
|
||
|
||
// 检查widget类型
|
||
if (widget.type === 'text' ||
|
||
widget.type === 'string' ||
|
||
widget.type === 'customtext') {
|
||
return true;
|
||
}
|
||
|
||
// 检查widget名称
|
||
const textWidgetNames = [
|
||
'text', 'prompt', 'positive', 'negative',
|
||
'text_g', 'text_l', 'clip_l', 'clip_g', 't5xxl',
|
||
'input', 'content', 'description'
|
||
];
|
||
|
||
if (textWidgetNames.includes(widget.name)) {
|
||
return true;
|
||
}
|
||
|
||
// 检查DOM元素
|
||
if (widget.element) {
|
||
const tagName = widget.element.tagName;
|
||
if (tagName === 'TEXTAREA') {
|
||
return true;
|
||
}
|
||
if (tagName === 'INPUT' &&
|
||
(widget.element.type === 'text' || widget.element.type === 'textarea')) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// 检查是否是ComfyUI的多行文本输入
|
||
if (widget.inputEl && widget.inputEl.tagName === 'TEXTAREA') {
|
||
return true;
|
||
}
|
||
|
||
// 检查ComfyUI特殊情况
|
||
if (widget.options && widget.options.multiline) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
} applyPromptToWidget(prompt, widget, insertMode = null) {
|
||
if (!widget) return;
|
||
|
||
const currentValue = widget.value || '';
|
||
const hasContent = currentValue.trim().length > 0;
|
||
|
||
// 如果指定了插入模式,直接使用
|
||
if (insertMode) {
|
||
let newValue;
|
||
if (insertMode === 'insert') {
|
||
// 插入到选中位置或开头
|
||
newValue = this.insertPromptAtCursor(widget, prompt.content);
|
||
} else if (insertMode === 'append') {
|
||
// 追加到内容末尾
|
||
newValue = currentValue + (currentValue ? ', ' : '') + prompt.content;
|
||
} else if (insertMode === 'replace') {
|
||
// 替换全部内容
|
||
newValue = prompt.content;
|
||
}
|
||
|
||
if (newValue !== undefined) {
|
||
this.setWidgetValue(widget, newValue);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 如果没有指定插入模式且有内容,显示对话框
|
||
if (hasContent) {
|
||
// 显示自定义确认对话框
|
||
this.ui.showInsertDialog(
|
||
'当前输入框已有内容',
|
||
'是否插入提示词?',
|
||
(action) => {
|
||
let newValue;
|
||
if (action === 'insert') {
|
||
// 插入到选中位置或开头
|
||
newValue = this.insertPromptAtCursor(widget, prompt.content);
|
||
} else if (action === 'append') {
|
||
// 追加到内容末尾
|
||
newValue = currentValue + (currentValue ? ', ' : '') + prompt.content;
|
||
} else if (action === 'replace') {
|
||
// 替换全部内容
|
||
newValue = prompt.content;
|
||
}
|
||
if (newValue !== undefined) {
|
||
this.setWidgetValue(widget, newValue);
|
||
}
|
||
}
|
||
);
|
||
} else {
|
||
this.setWidgetValue(widget, prompt.content);
|
||
}
|
||
}insertPromptAtCursor(widget, promptText) {
|
||
const currentValue = widget.value || '';
|
||
let cursorPosition = 0;
|
||
|
||
// 尝试获取文本框的选择位置
|
||
if (widget.inputEl && typeof widget.inputEl.selectionStart === 'number') {
|
||
// ComfyUI多行文本框
|
||
cursorPosition = widget.inputEl.selectionStart;
|
||
} else if (widget.element && typeof widget.element.selectionStart === 'number') {
|
||
// 普通输入框
|
||
cursorPosition = widget.element.selectionStart;
|
||
} else {
|
||
// 无法获取光标位置,默认插入到开头
|
||
cursorPosition = 0;
|
||
}
|
||
|
||
// 在光标位置插入提示词
|
||
const beforeCursor = currentValue.substring(0, cursorPosition);
|
||
const afterCursor = currentValue.substring(cursorPosition);
|
||
|
||
// 智能添加分隔符
|
||
let insertText = promptText;
|
||
|
||
// 检查光标前的情况
|
||
if (beforeCursor.length > 0) {
|
||
// 获取光标前最后一个字符
|
||
const lastChar = beforeCursor.charAt(beforeCursor.length - 1);
|
||
|
||
// 如果前面是换行符,不需要添加逗号
|
||
if (lastChar === '\n' || lastChar === '\r') {
|
||
// 在新行开始,不添加分隔符
|
||
} else {
|
||
// 检查是否需要添加逗号分隔符
|
||
const beforeTrim = beforeCursor.trimEnd();
|
||
if (beforeTrim.length > 0 && !beforeTrim.endsWith(',')) {
|
||
insertText = ', ' + insertText;
|
||
} else if (beforeCursor.endsWith(' ') && beforeTrim.endsWith(',')) {
|
||
// 如果前面已经有逗号和空格,直接插入
|
||
insertText = insertText;
|
||
} else if (beforeTrim.endsWith(',')) {
|
||
// 如果前面有逗号但没有空格
|
||
insertText = ' ' + insertText;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查光标后的情况
|
||
if (afterCursor.length > 0) {
|
||
// 获取光标后第一个字符
|
||
const firstChar = afterCursor.charAt(0);
|
||
|
||
// 如果后面是换行符,不需要添加逗号
|
||
if (firstChar === '\n' || firstChar === '\r') {
|
||
// 后面是新行,不添加分隔符
|
||
} else {
|
||
// 检查是否需要添加逗号分隔符
|
||
const afterTrim = afterCursor.trimStart();
|
||
if (afterTrim.length > 0 && !afterTrim.startsWith(',')) {
|
||
insertText = insertText + ', ';
|
||
}
|
||
}
|
||
}
|
||
|
||
const newValue = beforeCursor + insertText + afterCursor;
|
||
|
||
// 设置新的光标位置(在插入文本之后)
|
||
const newCursorPosition = beforeCursor.length + insertText.length;
|
||
|
||
// 延迟设置光标位置
|
||
setTimeout(() => {
|
||
if (widget.inputEl && typeof widget.inputEl.setSelectionRange === 'function') {
|
||
widget.inputEl.focus();
|
||
widget.inputEl.setSelectionRange(newCursorPosition, newCursorPosition);
|
||
} else if (widget.element && typeof widget.element.setSelectionRange === 'function') {
|
||
widget.element.focus();
|
||
widget.element.setSelectionRange(newCursorPosition, newCursorPosition);
|
||
}
|
||
}, 50);
|
||
|
||
return newValue;
|
||
}
|
||
|
||
setWidgetValue(widget, newValue) {
|
||
// 设置新值
|
||
widget.value = newValue;
|
||
|
||
// 尝试多种方式触发更新
|
||
|
||
// 1. 触发widget的回调
|
||
if (widget.callback) {
|
||
widget.callback(newValue);
|
||
}
|
||
|
||
// 2. 如果有DOM元素,更新DOM并触发事件
|
||
if (widget.element) {
|
||
widget.element.value = newValue;
|
||
widget.element.dispatchEvent(new Event('input', { bubbles: true }));
|
||
widget.element.dispatchEvent(new Event('change', { bubbles: true }));
|
||
}
|
||
|
||
// 3. 如果有inputEl(ComfyUI的多行文本输入)
|
||
if (widget.inputEl) {
|
||
widget.inputEl.value = newValue;
|
||
widget.inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
||
widget.inputEl.dispatchEvent(new Event('change', { bubbles: true }));
|
||
}
|
||
|
||
// 4. 强制触发ComfyUI的更新机制
|
||
if (this.node.onWidgetChanged) {
|
||
this.node.onWidgetChanged(widget.name, newValue, null, widget);
|
||
}
|
||
|
||
// 5. 标记节点为脏状态,强制重绘
|
||
if (this.node.setDirtyCanvas) {
|
||
this.node.setDirtyCanvas(true, true);
|
||
}
|
||
|
||
// 6. 触发图形更新
|
||
if (this.node.graph && this.node.graph.change) {
|
||
this.node.graph.change();
|
||
}
|
||
|
||
console.log(`已将提示词应用到 ${widget.name}: ${newValue}`);
|
||
this.hide();
|
||
}
|
||
|
||
getWidgetDisplayName(widget) {
|
||
const nameMap = {
|
||
'text': '文本',
|
||
'prompt': '提示词',
|
||
'positive': '正面提示词',
|
||
'negative': '负面提示词',
|
||
'text_g': '全局文本',
|
||
'text_l': '局部文本',
|
||
'clip_l': 'CLIP-L',
|
||
'clip_g': 'CLIP-G',
|
||
't5xxl': 'T5-XXL'
|
||
};
|
||
|
||
return nameMap[widget.name] || widget.name || '文本输入';
|
||
}
|
||
|
||
getAllTextWidgets() {
|
||
return this.node.widgets.filter(w => this.isTextWidget(w));
|
||
}
|
||
}
|
||
|
||
export const DEFAULT_CONFIG = {
|
||
autoSave: true,
|
||
maxPrompts: 1000,
|
||
searchDelay: 300
|
||
};
|