// SillyBubble - Dynamic Chat Bubble Image Generation Extension
import { extension_settings, getContext } from "../../../extensions.js";
import { saveSettingsDebounced, callPopup } from "../../../../script.js";
// Extension configuration
const extensionName = "SillyBubble";
const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`;
// Default settings
const defaultSettings = {
image_service_url: "image.php",
default_style: "default",
enabled: true
};
// Make sure settings exist
if (!extension_settings[extensionName]) {
extension_settings[extensionName] = {};
}
// Apply defaults for any missing settings
if (Object.keys(extension_settings[extensionName]).length === 0) {
Object.assign(extension_settings[extensionName], defaultSettings);
}
// Function for AI to call - generates markdown image with URL-encoded text
function generateChatBubbleImage(text, style) {
if (!extension_settings[extensionName].enabled) {
return text;
}
// Use default style if not specified
const bubbleStyle = style || extension_settings[extensionName].default_style;
// URL encode the text parameter
const encodedText = encodeURIComponent(text);
// Construct the URL with the encoded text
const imageUrl = `${extension_settings[extensionName].image_service_url}?q=${encodedText}`;
// Add style parameter if specified
const fullUrl = bubbleStyle !== "default"
? `${imageUrl}&style=${bubbleStyle}`
: imageUrl;
// Return markdown image format
return ``;
}
// Load extension settings into UI
function loadSettings() {
// Update UI with current settings
$("#sillybubble_enabled").prop("checked", extension_settings[extensionName].enabled).trigger("input");
$("#sillybubble_image_url").val(extension_settings[extensionName].image_service_url).trigger("input");
$("#sillybubble_default_style").val(extension_settings[extensionName].default_style).trigger("input");
}
// Handle enable/disable toggle
function onEnabledInput(event) {
const value = Boolean($(event.target).prop("checked"));
extension_settings[extensionName].enabled = value;
saveSettingsDebounced();
}
// Handle image service URL changes
function onImageUrlInput(event) {
const value = $(event.target).val();
extension_settings[extensionName].image_service_url = value;
saveSettingsDebounced();
}
// Handle default style changes
function onDefaultStyleInput(event) {
const value = $(event.target).val();
extension_settings[extensionName].default_style = value;
saveSettingsDebounced();
}
// Test function to visualize a bubble
function onTestButtonClick() {
const testText = "This is a test chat bubble generated by SillyBubble!";
const markdown = generateChatBubbleImage(testText);
const testPopup = `
Generated Markdown:
${markdown.replace(//g, '>')}
Preview (if connected to image service):
${markdown}
`;
callPopup(testPopup, 'text');
}
// Register function tool
function registerFunctionTool() {
try {
// Add function directly to context
const context = getContext();
if (context) {
context.generateChatBubbleImage = generateChatBubbleImage;
console.log(`[${extensionName}] Function added to context: generateChatBubbleImage`);
// Check if registerFunctionTool exists in context
if (typeof context.registerFunctionTool === 'function') {
// Define parameter schema following JSON schema format
const bubbleSchema = Object.freeze({
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
text: {
type: 'string',
description: 'The text to display in the chat bubble'
},
style: {
type: 'string',
description: 'The visual style of the chat bubble (default, modern, retro, minimal)'
},
},
required: ['text']
});
// Register the function tool using SillyTavern's own API
context.registerFunctionTool({
name: 'generateChatBubbleImage',
displayName: 'Chat Bubble Image',
description: 'Creates a markdown image link with the text displayed as a styled chat bubble. Use when you want to visually style messages or display text in a speech bubble.',
parameters: bubbleSchema,
action: async (args) => {
if (!args?.text) return '';
return generateChatBubbleImage(args.text, args.style);
},
formatMessage: () => '',
});
console.log(`[${extensionName}] Function tool registered via context.registerFunctionTool`);
} else {
console.warn(`[${extensionName}] registerFunctionTool not available in context`);
}
} else {
console.warn(`[${extensionName}] Unable to get context for function registration`);
}
} catch (error) {
console.error(`[${extensionName}] Error registering function tool:`, error);
}
}
// Initialize extension
jQuery(async () => {
console.log(`[${extensionName}] Initializing...`);
try {
// Load settings HTML
const settingsHtml = await $.get(`${extensionFolderPath}/example.html`);
// Visual extensions go in the right column (extensions_settings2)
$("#extensions_settings2").append(settingsHtml);
// Register event listeners
$("#sillybubble_enabled").on("input", onEnabledInput);
$("#sillybubble_image_url").on("input", onImageUrlInput);
$("#sillybubble_default_style").on("input", onDefaultStyleInput);
$("#sillybubble_test_button").on("click", onTestButtonClick);
// Initial attempt to register the function tool
registerFunctionTool();
// Make function globally accessible as fallback
window.generateChatBubbleImage = generateChatBubbleImage;
// Wait for SillyTavern to fully initialize, then register again
$(document).ready(() => {
// Try again after a short delay
setTimeout(() => registerFunctionTool(), 2000);
// Final attempt after SillyTavern is fully loaded
setTimeout(() => registerFunctionTool(), 10000);
});
// Load settings
loadSettings();
console.log(`[${extensionName}] Initialization complete`);
} catch (error) {
console.error(`[${extensionName}] Initialization error:`, error);
}
});