This guide outlines how to create plugins for the TellMeATalePlugins
system. Following these guidelines will also help in developing integrations for ComfyUI or other AI service providers in a structured manner.
Plugins are JavaScript objects registered with the TellMeATalePlugins.register()
method. Each plugin must define a specific set of properties and methods.
TellMeATalePlugins.register({
id: "unique-plugin-id-v1", // string, unique identifier (recommend versioning)
name: "My Awesome Plugin", // string, user-friendly display name
description: "This plugin does amazing things with AI.", // string, brief explanation
taskTypes: ["chat", "image"], // array of strings, e.g., "chat", "image", "tts", "video"
defaultConfig: { // object, default values for configuration options
someOption: "defaultValue",
anotherOption: 123
},
renderConfigUI: function(configContainer, currentConfig, instanceId, updateCallback) {
// Renders HTML UI for plugin configuration
},
execute: async function(taskType, payload, config, context) {
// Executes the plugin's core logic
}
});
pluginDefinition
ObjectProperty | Type | Required | Description |
---|---|---|---|
id |
String | Yes | A unique identifier for the plugin. It's good practice to include a version number (e.g., my-plugin-v1 ). This ID is used internally to retrieve and manage the plugin. |
name |
String | Yes | A user-friendly name displayed in the UI. |
description |
String | Optional (but Recommended) | A short description of what the plugin does. |
taskTypes |
Array |
Yes (must not be empty) | An array of task types this plugin can handle. Examples: "chat" , "image" , "tts" , "video" . A plugin must support at least one task type. |
defaultConfig |
Object | Optional | An object containing default values for any configuration options your plugin might need. These values are accessible within renderConfigUI (via currentConfig.otherConfig ) and execute (via config.otherConfig ). |
renderConfigUI |
Function | Yes | function(configContainer, currentConfig, instanceId, updateCallback) . This function is responsible for rendering the HTML configuration interface for the plugin instance. See details below. |
execute |
Function | Yes | async function(taskType, payload, config, context) . This asynchronous function contains the core logic of the plugin. See details below. |
renderConfigUI(configContainer, currentConfig, instanceId, updateCallback)
This function populates the provided configContainer
(a DOM element) with HTML elements for configuring the plugin.
configContainer
(HTMLElement): The DOM element where you should inject your configuration UI.currentConfig
(Object): An object containing the current persisted configuration for this plugin instance.
currentConfig.apiKey
(String): The API key, if applicable for the provider.currentConfig.model
(String): The model ID, if applicable for the provider.currentConfig.otherConfig
(Object): An object containing custom settings specific to this plugin, initially populated from defaultConfig
and then updated by user input.instanceId
(String): A unique ID for this particular instance of the plugin's configuration. Use this when calling updateCallback
.updateCallback
(Function): A global function updateProviderInstanceConfig(instanceId, key, value, isOtherConfig)
that you must call when a configuration value changes.
instanceId
: The ID passed to renderConfigUI
.key
: The configuration key being updated (e.g., "serverAddress"
, "apiKey"
, "model"
).value
: The new value for the key.isOtherConfig
(Boolean): Set to true
if the key belongs to the plugin-specific otherConfig
object (most common for custom fields). Set to false
for top-level keys like apiKey
or model
.value
attributes or innerHTML
. Use the provided sanitizeHTML(string)
utility.
// Example renderConfigUI
renderConfigUI: function (configContainer, currentConfig, instanceId, updateCallback) {
const otherConf = currentConfig.otherConfig || this.defaultConfig || {}; // Ensure otherConf is an object
// sanitizeHTML is a utility function you should use.
// Assume sanitizeHTML and updateProviderInstanceConfig are globally available.
configContainer.innerHTML = \`
\`;
// Optional: Add validation for JSON textareas
const textarea = configContainer.querySelector('textarea[data-otherconfig-json="true"]');
if (textarea) {
textarea.addEventListener('input', () => {
try {
if (textarea.value.trim()) JSON.parse(textarea.value);
textarea.style.border = '1px solid #ccc';
} catch (e) {
textarea.style.border = '1px solid red';
}
});
// Initial validation
try { if (textarea.value.trim()) JSON.parse(textarea.value); } catch (e) { textarea.style.border = '1px solid red'; }
}
}
execute(taskType, payload, config, context)
This asynchronous function is where your plugin performs its main task.
taskType
(String): The specific task being requested (e.g., "chat"
, "image"
). Your plugin should verify this matches one of its supported taskTypes
.payload
(Any): The primary input for the task. For "chat"
or "image"
prompts, this is usually a string. For TTS, it's the text to synthesize. Structure may vary based on taskType
or specific plugin needs.config
(Object): The configuration for this plugin instance.
config.providerId
(String): The ID of the provider instance.config.name
(String): The name of the provider instance.config.apiKey
(String): The API key, if configured for the provider.config.model
(String): The model ID, if configured.config.endpoint
(String): An API endpoint, if configured.config.otherConfig
(Object): Your plugin-specific settings, as defined in defaultConfig
and modified via renderConfigUI
.context
(Any): An optional piece of data that can be passed from a previous execution of this plugin or another part of the application. Useful for maintaining state, like conversation history or session IDs (e.g., OpenAI Responses API's previous_response_id
). Your plugin should define how it uses this.Return Value: The execute
function must return a Promise that resolves to an object specific to the taskType
, or reject with an Error if something goes wrong.
taskType
:"chat"
:
// For structured story content (preferred)
{
page_content: "The story text for the current page...",
choices: ["Choice 1 description", "Choice 2 text"],
summary_short: "A brief summary of this page.",
key_elements: ["element1", "item2"],
plot_significance_score: 0.7, // Number 0-1
vonnegut_shape_phase_assessment: "Rising action",
// Optional, for passing state to the next call if needed
newContext: "some_session_id_or_context_data",
// ... any other structured fields your LLM provides and your app uses
}
// For simpler premise generation or if structured output fails
{
premise: "A one-sentence premise for a story.",
newContext: null // or some data
}
// Fallback if JSON parsing fails but text is available
{
page_content: "Raw text output from LLM.",
choices: ["Parsed choice 1", "Parsed choice 2"], // Best effort parsing
summary_short: "Summary not available.",
// ... other fields with default/fallback values
newContext: null
}
The comfyui-chat-v1
, gemini-chat-v1
, anthropic-messages-v1
, and OpenAI plugins show examples of attempting to parse JSON and falling back gracefully.
"image"
:
{
base64Data: "iVBORw0KGgoAAAANSUhEUg...", // Base64 encoded image data (without 'data:image/png;base64,')
type: "image",
format: "png" // or "jpg", "webp", etc.
}
// Or if returning a URL (less common for direct generation)
{
url: "http://example.com/image.png",
type: "image",
format: "png"
}
"tts"
:
// For direct audio data
{
blob: new Blob(...), // The audio data as a Blob
base64Data: "UklGRiYAAABXQVZFZm10IBAAAA...", // Optional: Base64 encoded audio data
type: "audio",
format: "wav" // or "mp3", "ogg"
}
// For browser-based TTS fallback
{
useBrowserFallback: true,
providerName: "Browser Built-in TTS", // Name of the TTS provider for UI
settings: { lang: "en-US", rate: 1.0, pitch: 1.0, voice: "Voice Name" },
type: "audio"
}
"video"
:
{
url: "http://example.com/video.mp4",
type: "video",
format: "mp4" // or "webm" etc.
}
execute
function should throw new Error("Descriptive error message");
. This error will be caught by the calling system.
The provided ComfyUIAPI
object offers helper functions for interacting with a ComfyUI server. Plugins for ComfyUI typically involve these steps:
serverAddress
: URL of the ComfyUI server (e.g., http://127.0.0.1:8188
- note default is 8188 not 8000 as in example).workflow
: The ComfyUI workflow in API JSON format. This can be obtained by clicking "Save (API Format)" in the ComfyUI interface.promptNodeId
: The ID of the node in the workflow that accepts the main input (e.g., text prompt).promptInputName
: The name of the input field within the promptNodeId
(e.g., "text"
for CLIPTextEncode, "speech"
for F5TTS).outputNodeId
: The ID of the node that produces the desired output (e.g., SaveImage, a custom text output node).outputValueKey
: (For chat/text) The key within the output node's data in the ComfyUI history that holds the generated text/JSON string. (For images) The key that holds the array of image file information (usually "images"
for SaveImage).workflow
string from config.otherConfig.workflow
into a JavaScript object.ComfyUIAPI.modifyWorkflowInput(workflow, promptNodeId, promptInputName, payload)
to inject the user's input into the workflow.seed
input (e.g., KSampler), update it to Math.floor(Math.random() * 1e15)
to prevent caching and ensure unique outputs.clientId
using ComfyUIAPI.getClientId()
.await ComfyUIAPI.queuePrompt(serverAddress, workflow, clientId)
.await ComfyUIAPI.pollUntilComplete(serverAddress, promptId)
.history.outputs[outputNodeId]
.outputValueKey
(e.g., history.outputs[outputNodeId].images[0]
) will contain filename, type, etc. Use ComfyUIAPI.fetchOutputData(serverAddress, imageFileInfo)
to get the image blob/base64.history.outputs[outputNodeId][outputValueKey]
or history.outputs[outputNodeId].text[0]
. Careful inspection of ComfyUI's history output for your specific workflow is needed to find the correct path.taskType
.comfyui-f5tts-v1
plugin demonstrates advanced ComfyUI interaction, including copying files to ComfyUI's input directory via a window.externalJsBridge
. This bridge is necessary for operations that a browser cannot perform directly due to security restrictions (like local file system access).
ComfyUIAPI.modifyWorkflowInput
Limitations: This helper primarily targets simple input fields. Modifying widgets_values
(often used for text inputs in nodes like CLIPTextEncode if not directly linked) is more complex and has limited support in the current helper. Ensure your workflow nodes expose direct inputs for prompts, or be prepared to manipulate the workflow JSON more deeply. The current helper has a heuristic for CLIPTextEncode's first widget value.
Plugins for direct API providers typically involve:
config.apiKey
.config.model
.config.endpoint
or hardcoded in the plugin if fixed.config.otherConfig
.Content-Type
, Authorization
Bearer token).payload
and parameters from config
.fetch
).
anthropic-messages-v1
plugin uses window.externalJsBridge.MakeHttpRequest(...)
. This is a pattern for when direct fetch
calls from the browser might be problematic (CORS) or if requests need to be routed through a backend component that the bridge represents.
!response.ok
). Parse error messages if available.taskType
. Implement robust parsing for LLM JSON output, with fallbacks (see Gemini and Anthropic examples).id
and name
. Version your id
.execute
.Error
objects.gemini-chat-v1
). Check LLM finishReason
if available (e.g., safety blocks).renderConfigUI
.sanitizeHTML
for all dynamic content in the UI.context
): If your plugin's API calls are part of a sequence or session, use the context
parameter to pass state (e.g., conversation history, previous response ID) and return newContext
.execute
to be idempotent, especially if retries are possible. For ComfyUI image/TTS generation, randomizing seeds is crucial to avoid cached results on retries or subsequent identical prompts.console.log
, console.warn
, and console.error
judiciously for debugging. Provide enough information to trace execution flow and identify issues.The environment provides a few useful global helper functions:
generateUUID()
: Creates a standard UUID.generateFilenameSafeUUID()
: Creates a UUID with hyphens replaced by underscores.sanitizeHTML(string)
: Escapes HTML special characters in a string to prevent XSS. Crucial for UI rendering.updateProviderInstanceConfig(instanceId, key, value, isOtherConfig)
: Used in renderConfigUI
to persist configuration changes.comfyui-chat-v1
, comfyui-image-v1
, comfyui-f5tts-v1
, gemini-chat-v1
, anthropic-messages-v1
, openai-chat-v1
, etc.) are excellent examples to learn from. They demonstrate various patterns for configuration, execution, and error handling.