Improve function registration for SillyBubble extension

- Use the proper ToolManager API when available
- Add fallback mechanisms when ToolManager is not available
- Fix extension settings handling to use the standard pattern
- Implement proper JSON schema for function parameters
- Add delayed initialization to ensure SillyTavern is fully loaded
- Add better error handling and logging

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sven Olderaan 2025-03-14 21:20:23 +01:00
parent 5ccb1d3984
commit 247abbe91a

158
index.js
View File

@ -1,31 +1,42 @@
// SillyBubble - Dynamic Chat Bubble Image Generation Extension // SillyBubble - Dynamic Chat Bubble Image Generation Extension
import { extension_settings, getContext, loadExtensionSettings, registerCustomFunction } from "../../../extensions.js"; import { extension_settings, getContext } from "../../../extensions.js";
import { saveSettingsDebounced, callPopup, substituteParams } from "../../../../script.js"; import { saveSettingsDebounced, callPopup } from "../../../../script.js";
// Extension configuration // Extension configuration
const extensionName = "SillyBubble"; const extensionName = "SillyBubble";
const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`; const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`;
const extensionSettings = extension_settings[extensionName];
// Default settings
const defaultSettings = { const defaultSettings = {
image_service_url: "image.php", image_service_url: "image.php",
default_style: "default", default_style: "default",
enabled: true 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 for AI to call - generates markdown image with URL-encoded text
function generateChatBubbleImage(text, style) { function generateChatBubbleImage(text, style) {
if (!extensionSettings.enabled) { if (!extension_settings[extensionName].enabled) {
return text; return text;
} }
// Use default style if not specified // Use default style if not specified
const bubbleStyle = style || extensionSettings.default_style; const bubbleStyle = style || extension_settings[extensionName].default_style;
// URL encode the text parameter // URL encode the text parameter
const encodedText = encodeURIComponent(text); const encodedText = encodeURIComponent(text);
// Construct the URL with the encoded text // Construct the URL with the encoded text
const imageUrl = `${extensionSettings.image_service_url}?q=${encodedText}`; const imageUrl = `${extension_settings[extensionName].image_service_url}?q=${encodedText}`;
// Add style parameter if specified // Add style parameter if specified
const fullUrl = bubbleStyle !== "default" const fullUrl = bubbleStyle !== "default"
@ -36,37 +47,32 @@ function generateChatBubbleImage(text, style) {
return `![](${fullUrl})`; return `![](${fullUrl})`;
} }
// Load extension settings // Load extension settings into UI
async function loadSettings() { function loadSettings() {
extension_settings[extensionName] = extension_settings[extensionName] || {};
if (Object.keys(extension_settings[extensionName]).length === 0) {
Object.assign(extension_settings[extensionName], defaultSettings);
}
// Update UI with current settings // Update UI with current settings
$("#sillybubble_enabled").prop("checked", extensionSettings.enabled).trigger("input"); $("#sillybubble_enabled").prop("checked", extension_settings[extensionName].enabled).trigger("input");
$("#sillybubble_image_url").val(extensionSettings.image_service_url).trigger("input"); $("#sillybubble_image_url").val(extension_settings[extensionName].image_service_url).trigger("input");
$("#sillybubble_default_style").val(extensionSettings.default_style).trigger("input"); $("#sillybubble_default_style").val(extension_settings[extensionName].default_style).trigger("input");
} }
// Handle enable/disable toggle // Handle enable/disable toggle
function onEnabledInput(event) { function onEnabledInput(event) {
const value = Boolean($(event.target).prop("checked")); const value = Boolean($(event.target).prop("checked"));
extensionSettings.enabled = value; extension_settings[extensionName].enabled = value;
saveSettingsDebounced(); saveSettingsDebounced();
} }
// Handle image service URL changes // Handle image service URL changes
function onImageUrlInput(event) { function onImageUrlInput(event) {
const value = $(event.target).val(); const value = $(event.target).val();
extensionSettings.image_service_url = value; extension_settings[extensionName].image_service_url = value;
saveSettingsDebounced(); saveSettingsDebounced();
} }
// Handle default style changes // Handle default style changes
function onDefaultStyleInput(event) { function onDefaultStyleInput(event) {
const value = $(event.target).val(); const value = $(event.target).val();
extensionSettings.default_style = value; extension_settings[extensionName].default_style = value;
saveSettingsDebounced(); saveSettingsDebounced();
} }
@ -82,11 +88,77 @@ function onTestButtonClick() {
<div>${markdown}</div> <div>${markdown}</div>
</div> </div>
`; `;
callPopup(testPopup, 'text'); callPopup(testPopup, 'text');
} }
// Register function tool
function registerFunctionTool() {
try {
// Check if ToolManager exists in global scope
if (!window.ToolManager) {
console.warn(`[${extensionName}] ToolManager not found. Will try alternative registration methods.`);
// Method 1: Register directly to window
window.generateChatBubbleImage = generateChatBubbleImage;
console.log(`[${extensionName}] Registered function to window object`);
// Method 2: Add to extensions_functions if it exists
if (window.extensions_functions) {
window.extensions_functions['generateChatBubbleImage'] = generateChatBubbleImage;
console.log(`[${extensionName}] Added to extensions_functions`);
}
return;
}
// Check if tool calling is supported
if (typeof window.ToolManager.isToolCallingSupported !== 'function' || !window.ToolManager.isToolCallingSupported()) {
console.warn(`[${extensionName}] Tool calling is not supported in the current configuration`);
return;
}
// 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`);
} catch (error) {
console.error(`[${extensionName}] Error registering function tool:`, error);
}
}
// Initialize extension // Initialize extension
jQuery(async () => { jQuery(async () => {
console.log(`[${extensionName}] Initializing...`);
try {
// Load settings HTML // Load settings HTML
const settingsHtml = await $.get(`${extensionFolderPath}/example.html`); const settingsHtml = await $.get(`${extensionFolderPath}/example.html`);
// Visual extensions go in the right column (extensions_settings2) // Visual extensions go in the right column (extensions_settings2)
@ -98,47 +170,31 @@ jQuery(async () => {
$("#sillybubble_default_style").on("input", onDefaultStyleInput); $("#sillybubble_default_style").on("input", onDefaultStyleInput);
$("#sillybubble_test_button").on("click", onTestButtonClick); $("#sillybubble_test_button").on("click", onTestButtonClick);
// Register the custom function for AI to call // Wait for the document to fully load before trying to register tools
registerCustomFunction({ $(document).ready(() => {
name: 'generateChatBubbleImage', setTimeout(() => {
displayName: 'Generate Chat Bubble Image', // Try to register the function tool after a short delay
description: 'Creates a markdown image link with URL-encoded text parameter for dynamic chat bubbles', // to ensure all SillyTavern systems are initialized
parameters: [ registerFunctionTool();
{
name: 'text',
displayName: 'Text',
description: 'The text to display in the chat bubble',
type: 'string',
required: true,
},
{
name: 'style',
displayName: 'Style',
description: 'The visual style of the chat bubble (optional)',
type: 'string',
required: false,
}
],
isPublic: true, // Make sure function is exposed to the AI
run: async function(text, style) {
return generateChatBubbleImage(text, style);
}
});
// Log to console to verify registration // Also add the function to extension context
console.log(`[${extensionName}] Function registered: generateChatBubbleImage`);
// Add to context if needed - this might be required to expose to AI
try { try {
const context = getContext(); const context = getContext();
if (context && typeof context.addExtensionFunction === 'function') { if (context) {
context.addExtensionFunction('generateChatBubbleImage', generateChatBubbleImage); // Make the function available to the AI
context.generateChatBubbleImage = generateChatBubbleImage;
console.log(`[${extensionName}] Function added to context: generateChatBubbleImage`); console.log(`[${extensionName}] Function added to context: generateChatBubbleImage`);
} }
} catch (error) { } catch (error) {
console.error(`[${extensionName}] Error adding function to context:`, error); console.error(`[${extensionName}] Error adding function to context:`, error);
} }
}, 2000); // Wait 2 seconds for SillyTavern to initialize
});
// Load settings // Load settings
loadSettings(); loadSettings();
console.log(`[${extensionName}] Initialization complete`);
} catch (error) {
console.error(`[${extensionName}] Initialization error:`, error);
}
}); });