// 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 `![](${fullUrl})`; } // 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); } });