// 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 { // Register the function in the most direct way to the window window.generateChatBubbleImage = generateChatBubbleImage; console.log(`[${extensionName}] Registered function to window object`); // Method 1: Try through window.SillyTavern API if it exists if (window.SillyTavern?.getContext) { try { const api = window.SillyTavern; const context = api.getContext(); // Add to context directly context.generateChatBubbleImage = generateChatBubbleImage; console.log(`[${extensionName}] Function added to SillyTavern context API`); // If registerCustomFunction exists, use it if (typeof api.registerCustomFunction === 'function') { api.registerCustomFunction('generateChatBubbleImage', generateChatBubbleImage); console.log(`[${extensionName}] Function registered via SillyTavern API`); } } catch (error) { console.warn(`[${extensionName}] Error registering with SillyTavern API:`, error); } } // Method 2: Try through extensions_functions if it exists if (window.extensions_functions) { window.extensions_functions['generateChatBubbleImage'] = generateChatBubbleImage; console.log(`[${extensionName}] Added to extensions_functions`); } // Method 3: Try with ToolManager if it exists if (window.ToolManager) { try { // Check if tool calling is supported if (typeof window.ToolManager.isToolCallingSupported === 'function' && window.ToolManager.isToolCallingSupported()) { // Define the function parameters schema const parameters = { "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 tool window.ToolManager.registerFunctionTool({ name: 'generateChatBubbleImage', displayName: 'Generate Chat Bubble', description: 'Creates a markdown image link with the text displayed as a chat bubble', parameters: parameters, action: async (params) => { return generateChatBubbleImage(params.text, params.style); }, formatMessage: async (params) => { return `Generating chat bubble with text: "${params.text?.substring(0, 30)}${params.text?.length > 30 ? '...' : ''}"`; } }); console.log(`[${extensionName}] Function tool registered via ToolManager`); } else { console.warn(`[${extensionName}] Tool calling is not supported in the current configuration`); } } catch (error) { console.error(`[${extensionName}] Error registering with ToolManager:`, error); } } // Method 4: Try to register globally as a function call format // This creates a special object that some AI models might recognize window.AI_FUNCTION_generateChatBubbleImage = { name: 'generateChatBubbleImage', description: 'Creates a markdown image link with the text displayed as a chat bubble', parameters: { 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 (optional)' } }, required: ['text'] }, function: generateChatBubbleImage }; console.log(`[${extensionName}] Created special AI function format object`); } 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); // First registration attempt immediately registerFunctionTool(); // Try to add to context immediately try { const context = getContext(); if (context) { // Make the function available to the AI context.generateChatBubbleImage = generateChatBubbleImage; console.log(`[${extensionName}] Function added to context: generateChatBubbleImage`); } } catch (error) { console.error(`[${extensionName}] Error adding function to context:`, error); } // Wait for the document to fully load before trying again $(document).ready(() => { // Try registration again after a short delay setTimeout(() => { registerFunctionTool(); // Add function to global window for direct accessibility window.generateChatBubbleImage = generateChatBubbleImage; // Also add explicit API function definition for direct calling window.SILLYBUBBLE_FUNCTION = { name: 'generateChatBubbleImage', call: function(text, style) { return generateChatBubbleImage(text, style); } }; console.log(`[${extensionName}] Function registration and context setup completed`); }, 2000); // Wait 2 seconds for SillyTavern to initialize // One final attempt after 10 seconds setTimeout(() => { registerFunctionTool(); console.log(`[${extensionName}] Final function registration attempt completed`); }, 10000); }); // Load settings loadSettings(); console.log(`[${extensionName}] Initialization complete`); } catch (error) { console.error(`[${extensionName}] Initialization error:`, error); } });