Developer Guide: Creating Compatible TellMeATale Plugins

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.

Core Plugin Structure

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
    }
});
        

The pluginDefinition Object

Property 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.

1. renderConfigUI(configContainer, currentConfig, instanceId, updateCallback)

This function populates the provided configContainer (a DOM element) with HTML elements for configuring the plugin.

Security: Always sanitize user-provided values before inserting them into HTML, especially when setting 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'; } } }

2. execute(taskType, payload, config, context)

This asynchronous function is where your plugin performs its main task.

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.

Expected Return Structures by taskType:

Error Handling: If an error occurs during execution (API call fails, data processing error), your execute function should throw new Error("Descriptive error message");. This error will be caught by the calling system.

Working with ComfyUI

The provided ComfyUIAPI object offers helper functions for interacting with a ComfyUI server. Plugins for ComfyUI typically involve these steps:

  1. Configuration:
    • 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).
  2. Execution Flow:
    1. Parse the workflow string from config.otherConfig.workflow into a JavaScript object.
    2. Use ComfyUIAPI.modifyWorkflowInput(workflow, promptNodeId, promptInputName, payload) to inject the user's input into the workflow.
    3. For image generation, randomize seeds: Iterate through workflow nodes. If a node has a seed input (e.g., KSampler), update it to Math.floor(Math.random() * 1e15) to prevent caching and ensure unique outputs.
    4. Get a clientId using ComfyUIAPI.getClientId().
    5. Queue the prompt: await ComfyUIAPI.queuePrompt(serverAddress, workflow, clientId).
    6. Poll for completion: await ComfyUIAPI.pollUntilComplete(serverAddress, promptId).
    7. Extract output:
      • Access history.outputs[outputNodeId].
      • For images: The data under outputValueKey (e.g., history.outputs[outputNodeId].images[0]) will contain filename, type, etc. Use ComfyUIAPI.fetchOutputData(serverAddress, imageFileInfo) to get the image blob/base64.
      • For text/chat: The generated text/JSON string is often found under a specific key like 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.
    8. Format the result according to the expected return structure for the taskType.
The 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.

Working with External APIs (OpenAI, Gemini, Anthropic)

Plugins for direct API providers typically involve:

  1. Configuration:
    • API Key: Accessed via config.apiKey.
    • Model ID: Accessed via config.model.
    • Endpoint URL: Either from config.endpoint or hardcoded in the plugin if fixed.
    • API-specific parameters (temperature, max_tokens, etc.): From config.otherConfig.
  2. Execution Flow:
    1. Construct the request headers (e.g., Content-Type, Authorization Bearer token).
    2. Construct the request body according to the API's specification, using payload and parameters from config.
    3. Make the HTTP request (e.g., using fetch).
      The 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.
    4. Handle the API response:
      • Check for errors (!response.ok). Parse error messages if available.
      • Parse the successful response JSON.
      • Extract the relevant data (e.g., generated text, image data). For chat plugins, this often involves parsing an inner JSON string if the LLM is instructed to return structured data.
    5. Format the result according to the expected return structure for the taskType. Implement robust parsing for LLM JSON output, with fallbacks (see Gemini and Anthropic examples).

Plugin Best Practices

Helper Utilities

The environment provides a few useful global helper functions:

Study Existing Plugins: The built-in plugins (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.