diff options
Diffstat (limited to 'src')
3 files changed, 87 insertions, 76 deletions
diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index c3d37fd0e..8516f054b 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -113,65 +113,62 @@ export class Agent { } /** - * Loads existing dynamic tools by checking the current registry and ensuring all stored tools are available + * Loads every dynamic tool that the server reports via /getDynamicTools. + * • Uses dynamic `import()` so webpack/vite will code-split each tool automatically. + * • Registers the tool in `dynamicToolRegistry` under the name it advertises via + * `toolInfo.name`; also registers the legacy camel-case key if different. */ private async loadExistingDynamicTools(): Promise<void> { try { - console.log('Loading dynamic tools...'); + console.log('Loading dynamic tools from server…'); + const toolFiles = await this.fetchDynamicToolList(); - // Since we're in a browser environment, we can't use filesystem operations - // Instead, we'll maintain tools in the registry and try to load known tools + let loaded = 0; + for (const { name: className, path } of toolFiles) { + // Legacy key (e.g., CharacterCountTool → characterCountTool) + const legacyKey = className.replace(/^[A-Z]/, m => m.toLowerCase()); - // Try to manually load the known dynamic tools that exist - const knownDynamicTools = [ - { name: 'CharacterCountTool', actionName: 'charactercount' }, - { name: 'WordCountTool', actionName: 'wordcount' }, - { name: 'TestTool', actionName: 'test' }, - ]; + // Skip if we already have the legacy key + if (this.dynamicToolRegistry.has(legacyKey)) continue; - let loadedCount = 0; - for (const toolInfo of knownDynamicTools) { try { - // Check if tool is already in registry - if (this.dynamicToolRegistry.has(toolInfo.actionName)) { - console.log(`✓ Tool ${toolInfo.actionName} already loaded`); - loadedCount++; + // @vite-ignore keeps Vite/Webpack from trying to statically analyse the variable part + const ToolClass = require(`../tools/${path}`)[className]; + + if (!ToolClass || !(ToolClass.prototype instanceof BaseTool)) { + console.warn(`File ${path} does not export a valid BaseTool subclass`); continue; } - // Try to load the tool using require (works better in webpack environment) - let toolInstance = null; - try { - // Use require with the relative path - const toolModule = require(`../tools/dynamic/${toolInfo.name}`); - const ToolClass = toolModule[toolInfo.name]; + const instance: BaseTool<ReadonlyArray<Parameter>> = new ToolClass(); - if (ToolClass && typeof ToolClass === 'function') { - toolInstance = new ToolClass(); + // Prefer the tool’s self-declared name (matches <action> tag) + const key = (instance.name || '').trim() || legacyKey; - if (toolInstance instanceof BaseTool) { - this.dynamicToolRegistry.set(toolInfo.actionName, toolInstance); - loadedCount++; - console.log(`✓ Loaded dynamic tool: ${toolInfo.actionName} (from ${toolInfo.name})`); - } - } - } catch (requireError) { - // Tool file doesn't exist or can't be loaded, which is fine - console.log(`Tool ${toolInfo.name} not available:`, (requireError as Error).message); + // Check for duplicates + if (this.dynamicToolRegistry.has(key)) { + console.warn(`Dynamic tool key '${key}' already registered – keeping existing instance`); + continue; } - } catch (error) { - console.warn(`⚠ Failed to load ${toolInfo.name}:`, error); - } - } - console.log(`Successfully loaded ${loadedCount} dynamic tools`); + // ✅ register under the preferred key + this.dynamicToolRegistry.set(key, instance); - // Log all currently registered dynamic tools - if (this.dynamicToolRegistry.size > 0) { - console.log('Currently registered dynamic tools:', Array.from(this.dynamicToolRegistry.keys())); + // optional: also register the legacy key for safety + if (key !== legacyKey && !this.dynamicToolRegistry.has(legacyKey)) { + this.dynamicToolRegistry.set(legacyKey, instance); + } + + loaded++; + console.info(`✓ Loaded dynamic tool '${key}' from '${path}'`); + } catch (err) { + console.error(`✗ Failed to load '${path}':`, err); + } } - } catch (error) { - console.error('Error loading dynamic tools:', error); + + console.log(`Dynamic-tool load complete – ${loaded}/${toolFiles.length} added`); + } catch (err) { + console.error('Dynamic-tool bootstrap failed:', err); } } @@ -246,6 +243,14 @@ export class Agent { this.loadExistingDynamicTools(); } + private async fetchDynamicToolList(): Promise<{ name: string; path: string }[]> { + const res = await fetch('/getDynamicTools'); + if (!res.ok) throw new Error(`Failed to fetch dynamic tool list – ${res.statusText}`); + const json = await res.json(); + console.log('Dynamic tools fetched:', json.tools); + return json.tools ?? []; + } + /** * This method handles the conversation flow with the assistant, processes user queries, * and manages the assistant's decision-making process, including tool actions. diff --git a/src/client/views/nodes/chatbot/tools/dynamic/CharacterCountTool.ts b/src/client/views/nodes/chatbot/tools/dynamic/CharacterCountTool.ts deleted file mode 100644 index 38fed231c..000000000 --- a/src/client/views/nodes/chatbot/tools/dynamic/CharacterCountTool.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Observation } from '../../types/types'; -import { ParametersType, ToolInfo } from '../../types/tool_types'; -import { BaseTool } from '../BaseTool'; - -const characterCountParams = [ - { - name: 'text', - type: 'string', - description: 'The text to count characters in', - required: true - } - ] as const; - - type CharacterCountParamsType = typeof characterCountParams; - - const characterCountInfo: ToolInfo<CharacterCountParamsType> = { - name: 'charactercount', - description: 'Counts characters in text, excluding spaces', - citationRules: 'No citation needed.', - parameterRules: characterCountParams - }; - - export class CharacterCountTool extends BaseTool<CharacterCountParamsType> { - constructor() { - super(characterCountInfo); - } - - async execute(args: ParametersType<CharacterCountParamsType>): Promise<Observation[]> { - const { text } = args; - const count = text ? text.replace(/\s/g, '').length : 0; - return [{ type: 'text', text: `Character count (excluding spaces): ${count}` }]; - } - }
\ No newline at end of file diff --git a/src/client/views/nodes/chatbot/tools/dynamic/InspirationalQuotesTool.ts b/src/client/views/nodes/chatbot/tools/dynamic/InspirationalQuotesTool.ts new file mode 100644 index 000000000..23bbe1d76 --- /dev/null +++ b/src/client/views/nodes/chatbot/tools/dynamic/InspirationalQuotesTool.ts @@ -0,0 +1,39 @@ +import { Observation } from '../../types/types'; +import { ParametersType, ToolInfo } from '../../types/tool_types'; +import { BaseTool } from '../BaseTool'; + +const inspirationalQuotesParams = [ + { + name: 'category', + type: 'string', + description: 'The category of inspirational quotes to retrieve', + required: false + } + ] as const; + + type InspirationalQuotesParamsType = typeof inspirationalQuotesParams; + + const inspirationalQuotesInfo: ToolInfo<InspirationalQuotesParamsType> = { + name: 'inspirationalquotestool', + description: 'Provides a random inspirational quote from a predefined list.', + citationRules: 'No citation needed.', + parameterRules: inspirationalQuotesParams + }; + + export class InspirationalQuotesTool extends BaseTool<InspirationalQuotesParamsType> { + constructor() { + super(inspirationalQuotesInfo); + } + + async execute(args: ParametersType<InspirationalQuotesParamsType>): Promise<Observation[]> { + const quotes = [ + "The only way to do great work is to love what you do. - Steve Jobs", + "The best time to plant a tree was 20 years ago. The second best time is now. - Chinese Proverb", + "Your time is limited, so don’t waste it living someone else’s life. - Steve Jobs", + "Not everything that is faced can be changed, but nothing can be changed until it is faced. - James Baldwin", + "The purpose of our lives is to be happy. - Dalai Lama" + ]; + const randomQuote = quotes[Math.floor(Math.random() * quotes.length)]; + return [{ type: 'text', text: randomQuote }]; + } + }
\ No newline at end of file |