diff --git a/README.md b/README.md
index c9fb729..223b85c 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,15 @@
# ComfyInject
+** This is just a minor modification of https://github.com/Spadic21/ComfyInject to introduce:
+
+- Placeholders for CHAR_AVATAR and USER_AVATAR - I just didn't get what I was looking for, so now optional placeholders can be used (note you need to include those properly in your workflow).
+- A minor change so the ComfyInject model can be chosen via select, instead of a textfield.
+
+And that's it **
+
+*************************************************************************************************************
+
A SillyTavern extension that automatically generates images from `[[IMG: ... ]]` markers in bot messages using your local ComfyUI instance.
When your LLM outputs a marker, ComfyInject intercepts it, sends the prompt to ComfyUI, and replaces the marker with the generated image, all without leaving the chat. Multiple images per message are supported. Images are saved permanently into the chat history and survive page reloads. Outbound prompts sent to the LLM replace injected images with a compact token so the model maintains visual continuity across the conversation.
diff --git a/scripts/generate-workflow-index.cjs b/scripts/generate-workflow-index.cjs
new file mode 100644
index 0000000..c044070
--- /dev/null
+++ b/scripts/generate-workflow-index.cjs
@@ -0,0 +1,10 @@
+const fs = require('fs');
+const path = require('path');
+
+const workflowsDir = path.join(__dirname, '..', 'workflows');
+const files = fs.readdirSync(workflowsDir)
+ .filter(f => f.endsWith('.json') && f !== 'index.json')
+ .sort();
+
+fs.writeFileSync(path.join(workflowsDir, 'index.json'), JSON.stringify(files, null, 2) + '\n');
+console.log(`Generated workflows/index.json with ${files.length} workflow(s):\n ${files.join('\n ')}`);
diff --git a/settings.html b/settings.html
index 9fbc60d..c85c8bd 100644
--- a/settings.html
+++ b/settings.html
@@ -13,26 +13,19 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
+
- Filename of the workflow JSON in the workflows folder.
-
+
diff --git a/src/comfy.js b/src/comfy.js
index 9bf58a5..cb12836 100644
--- a/src/comfy.js
+++ b/src/comfy.js
@@ -1,5 +1,12 @@
import { MODULE_NAME } from "../settings.js";
import { resolveSeed } from "./state.js";
+import {
+ formatCharacterAvatar,
+ getCharacterAvatar,
+ user_avatar,
+ getUserAvatar,
+} from '../../../../../script.js';
+import { getBase64Async } from '../../../../utils.js';
const EXTENSION_FOLDER = `scripts/extensions/third-party/ComfyInject`;
@@ -121,6 +128,69 @@ function buildImageUrl(filename, subfolder, host) {
return `${host}/view?${params.toString()}`;
}
+/**
+ * Gets the active character's avatar URL (or group fallback).
+ */
+function getCharacterAvatarUrl() {
+ const context = SillyTavern.getContext();
+ if (context.groupId) {
+ const groupMembers = context.groups.find(x => x.id === context.groupId)?.members;
+ const lastMessageAvatar = context.chat?.filter(x => !x.is_system && !x.is_user)?.slice(-1)[0]?.original_avatar;
+ const randomMemberAvatar = Array.isArray(groupMembers) ? groupMembers[Math.floor(Math.random() * groupMembers.length)] : null;
+ const avatarToUse = lastMessageAvatar || randomMemberAvatar;
+ return formatCharacterAvatar(avatarToUse);
+ } else {
+ return getCharacterAvatar(context.characterId);
+ }
+}
+
+/**
+ * Gets the active user's avatar URL.
+ */
+function getUserAvatarUrl() {
+ return getUserAvatar(user_avatar);
+}
+
+/**
+ * Fetch avatar image and return as base64 string.
+ * @param {string} url - URL of the avatar to fetch
+ * @returns {Promise} Base64-encoded image data
+ */
+async function fetchAvatarAsBase64(url) {
+ if (!url) return "";
+ const response = await fetch(url);
+ if (!response.ok) return "";
+ const blob = await response.blob();
+ const dataUrl = await getBase64Async(blob);
+ return dataUrl.split(",")[1] || "";
+}
+
+/**
+ * Fetch character avatar for {{CHAR_AVATAR}} placeholder.
+ * @returns {Promise}
+ */
+async function fetchCharacterAvatar() {
+ try {
+ return await fetchAvatarAsBase64(getCharacterAvatarUrl());
+ } catch (e) {
+ console.warn("[ComfyInject] Error fetching character avatar:", e);
+ return "";
+ }
+}
+
+/**
+ * Fetch user avatar for {{USER_AVATAR}} placeholder.
+ * @returns {Promise}
+ */
+async function fetchUserAvatar() {
+ try {
+ return await fetchAvatarAsBase64(getUserAvatarUrl());
+ } catch (e) {
+ console.warn("[ComfyInject] Error fetching user avatar:", e);
+ return "";
+ }
+}
+
/**
* Main entry point. Takes parsed marker data and returns a usable image URL.
* @param {object} params
@@ -159,8 +229,13 @@ export async function generateImage({ prompt, ar, shot, seed, messageIndex, bypa
? resolveSeed(settings.seed_lock_mode === "CUSTOM" ? settings.seed_lock_value : settings.seed_lock_mode, messageIndex)
: seed;
- // Load and fill the workflow
- const workflow = await loadWorkflow();
+ // Load workflow and avatars in parallel
+ const [workflow, charAvatar, userAvatar] = await Promise.all([
+ loadWorkflow(),
+ fetchCharacterAvatar().catch(() => ""),
+ fetchUserAvatar().catch(() => ""),
+ ]);
+
const filled = fillWorkflow(workflow, {
CHECKPOINT: settings.checkpoint,
POSITIVE_PROMPT: positivePrompt,
@@ -173,6 +248,8 @@ export async function generateImage({ prompt, ar, shot, seed, messageIndex, bypa
SAMPLER: settings.sampler,
SCHEDULER: settings.scheduler,
DENOISE: settings.denoise,
+ CHAR_AVATAR: charAvatar,
+ USER_AVATAR: userAvatar,
});
// Submit to ComfyUI and wait for the result
diff --git a/src/ui.js b/src/ui.js
index 81f6df3..3cb8543 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -31,52 +31,55 @@ async function fetchCheckpoints() {
if (!response.ok) return [];
const data = await response.json();
return data?.CheckpointLoaderSimple?.input?.required?.ckpt_name?.[0] ?? [];
- } catch (err) {
+ } catch {
return [];
}
}
/**
- * Validates that a workflow file exists in the workflows folder.
- * Shows a toastr error if the file doesn't exist, success if it does.
- * @param {string} filename - The workflow filename to validate
+ * Populates the checkpoint