- Add multiple registration methods to ensure function availability - Try registering at multiple times during initialization lifecycle - Create special function object formats for different AI models - Add detailed instructions in UI for adding necessary system prompt - Provide alternative direct markdown format as fallback - Add more robust error handling and diagnostics 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
267 lines
9.4 KiB
JavaScript
267 lines
9.4 KiB
JavaScript
// 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 = `
|
|
<div class="sillybubble-test">
|
|
<p>Generated Markdown:</p>
|
|
<pre>${markdown.replace(/</g, '<').replace(/>/g, '>')}</pre>
|
|
<p>Preview (if connected to image service):</p>
|
|
<div>${markdown}</div>
|
|
</div>
|
|
`;
|
|
|
|
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);
|
|
}
|
|
}); |