✨ 更新:Base64上下文支持节点内置预览

This commit is contained in:
arlozen
2025-11-05 13:41:34 +08:00
parent 928335a1f8
commit 9ffbc5317b
3 changed files with 185 additions and 10 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
from .base64_downloader import *
from .base64_uploader import *
from .base64_context_previewer import *
from .base64_context_loader import *
@@ -7,7 +7,7 @@ from .base64_context import Base64Context
routes = PromptServer.instance.routes
@register_node
class Base64ContextPreviewer:
class Base64ContextLoader:
def __init__(self):
pass
@@ -17,17 +17,25 @@ class Base64ContextPreviewer:
"required": {},
"hidden": {
"key": ("STRING", {"default": "NONE"}),
"mode": (["Builtin", "Dialog"], {"default": "Builtin"}),
},
}
RETURN_TYPES = ()
RETURN_NAMES = ()
RETURN_TYPES = ("STRING", "STRING", "STRING")
RETURN_NAMES = ("base64", "basename", "suffix")
FUNCTION = "run"
CATEGORY = "EasyToolkit/Misc"
OUTPUT_NODE = True
def run(self, key):
return {}
def run(self, key, mode):
base64_context = get_persistent_context(key).get_value()
if not base64_context or not isinstance(base64_context, Base64Context):
raise Exception("There is no base64 data at all.")
base64 = base64_context.get_base64()
basename = base64_context.get_basename()
suffix = base64_context.get_suffix()
return {"result": (base64, basename, suffix,)}
@routes.post("/base64_context_previewer/get_keys")
async def handle_get_keys(request):
@@ -1,14 +1,27 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "EasyToolkit.Misc.Base64ContextPreviewer",
name: "EasyToolkit.Misc.Base64ContextLoader",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== "Base64ContextPreviewer") return;
if (nodeData.name !== "Base64ContextLoader") return;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = async function () {
if (onNodeCreated) onNodeCreated.apply(this, arguments);
// Add mode selector
let modeWidget = this.addWidget("combo", "mode", "Builtin", (value) => {
// Update preview visibility based on mode
if (value === "Builtin" && this.previewData) {
this.updateBuiltinPreview(this.previewData);
} else if (value === "Dialog") {
// Hide builtin preview
this.previewImage = null;
this.previewText = null;
this.setSize(this.computeSize());
}
}, { values: ["Builtin", "Dialog"] });
// Create key combo widget dynamically
// This widget name matches the hidden parameter "key" in backend
let keyWidget = this.addWidget("combo", "key", "NONE", (value) => {
@@ -20,6 +33,11 @@ app.registerExtension({
let previewWidget = this.addWidget("text", "preview_info", "等待预览...", () => {});
previewWidget.disabled = true;
// Store preview data
this.previewData = null;
this.previewImage = null;
this.previewText = null;
// Function to refresh keys from backend
const refreshKeys = async () => {
try {
@@ -80,8 +98,15 @@ app.registerExtension({
const fileInfo = `${data.filename}`;
previewWidget.value = fileInfo;
// Create preview dialog
this.showPreviewDialog(data);
// Store preview data
this.previewData = data;
// Show preview based on mode
if (modeWidget.value === "Builtin") {
this.updateBuiltinPreview(data);
} else {
this.showPreviewDialog(data);
}
app.extensionManager.toast.add({
severity: "info",
@@ -113,6 +138,148 @@ app.registerExtension({
});
};
// Add builtin preview update method
nodeType.prototype.updateBuiltinPreview = function(data) {
if (data.content_type === "image") {
// Load image
const img = new Image();
img.onload = () => {
this.previewImage = img;
this.previewText = null;
this.setSize(this.computeSize());
};
img.src = `data:${data.format};base64,${data.base64}`;
} else if (data.content_type === "text") {
// Decode text if needed
let textContent = data.base64;
if (!data.format.startsWith("text/")) {
try {
textContent = atob(data.base64);
} catch (e) {
textContent = data.base64;
}
}
this.previewText = textContent;
this.previewImage = null;
this.setSize(this.computeSize());
} else {
this.previewImage = null;
this.previewText = null;
this.setSize(this.computeSize());
}
};
// Override onDrawForeground to render preview on node
const onDrawForeground = nodeType.prototype.onDrawForeground;
nodeType.prototype.onDrawForeground = function(ctx) {
if (onDrawForeground) {
onDrawForeground.apply(this, arguments);
}
// Only draw builtin preview if mode is "Builtin"
const modeWidget = this.widgets?.find(w => w.name === "mode");
if (!modeWidget || modeWidget.value !== "Builtin") {
return;
}
if (this.previewImage) {
// Draw image preview
const [w, h] = this.size;
const previewHeight = 300;
const previewY = h - previewHeight - 10;
// Calculate image dimensions to fit in preview area
const maxWidth = w - 20;
const maxHeight = previewHeight;
let imgWidth = this.previewImage.width;
let imgHeight = this.previewImage.height;
const aspectRatio = imgWidth / imgHeight;
if (imgWidth > maxWidth) {
imgWidth = maxWidth;
imgHeight = imgWidth / aspectRatio;
}
if (imgHeight > maxHeight) {
imgHeight = maxHeight;
imgWidth = imgHeight * aspectRatio;
}
const imgX = (w - imgWidth) / 2;
const imgY = previewY + (maxHeight - imgHeight) / 2;
ctx.save();
ctx.drawImage(this.previewImage, imgX, imgY, imgWidth, imgHeight);
ctx.restore();
} else if (this.previewText) {
// Draw text preview
const [w, h] = this.size;
const previewHeight = 200;
const previewY = h - previewHeight - 10;
ctx.save();
ctx.fillStyle = "#2d2d2d";
ctx.fillRect(10, previewY, w - 20, previewHeight);
ctx.strokeStyle = "#444";
ctx.strokeRect(10, previewY, w - 20, previewHeight);
ctx.fillStyle = "#d4d4d4";
ctx.font = "12px monospace";
// Split text into lines and draw
const lines = this.previewText.split('\n');
const lineHeight = 14;
const maxLines = Math.floor((previewHeight - 20) / lineHeight);
const visibleLines = lines.slice(0, maxLines);
visibleLines.forEach((line, idx) => {
// Truncate long lines
const maxChars = Math.floor((w - 40) / 7);
const truncatedLine = line.length > maxChars ? line.substring(0, maxChars) + "..." : line;
ctx.fillText(truncatedLine, 15, previewY + 15 + idx * lineHeight);
});
if (lines.length > maxLines) {
ctx.fillText("...", 15, previewY + 15 + maxLines * lineHeight);
}
ctx.restore();
}
};
// Override computeSize to account for preview area
const computeSize = nodeType.prototype.computeSize;
nodeType.prototype.computeSize = function(out) {
const size = computeSize ? computeSize.apply(this, arguments) : [this.size[0], this.size[1]];
const modeWidget = this.widgets?.find(w => w.name === "mode");
if (modeWidget && modeWidget.value === "Builtin") {
let requiredExtraHeight = 0;
let requiredMinWidth = size[0];
if (this.previewImage) {
requiredExtraHeight = 310; // Space for image preview
// Calculate minimum width based on image aspect ratio
const imgAspect = this.previewImage.width / this.previewImage.height;
const previewHeight = 300;
requiredMinWidth = Math.max(requiredMinWidth, Math.min(this.previewImage.width, previewHeight * imgAspect) + 40);
} else if (this.previewText) {
requiredExtraHeight = 210; // Space for text preview
requiredMinWidth = Math.max(requiredMinWidth, 300); // Minimum width for text preview
}
// Calculate minimum required height (base height + preview area)
const minRequiredHeight = size[1] + requiredExtraHeight;
// Allow user to resize, but enforce minimum dimensions
// Use the larger of: computed minimum size or user's manual resize
size[0] = Math.max(size[0], requiredMinWidth);
size[1] = Math.max(size[1], minRequiredHeight);
}
return size;
};
// Add preview dialog method to node prototype
nodeType.prototype.showPreviewDialog = function(data) {
const dialog = document.createElement("div");