[PR #24091] Feature/improve goto anything commands #30500

Closed
opened 2026-02-21 20:47:37 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/langgenius/dify/pull/24091

State: closed
Merged: Yes


Important

  1. Make sure you have read our contribution guidelines
  2. Ensure there is an associated issue and you have been assigned to it
  3. Use the correct syntax to link this PR: Fixes #<issue number>.

Summary

fix #24088

/**
 * Goto Anything - Action System
 *
 * This file defines the action registry for the goto-anything search system.
 * Actions handle different types of searches: apps, knowledge bases, plugins, workflow nodes, and commands.
 *
 * ## How to Add a New Slash Command
 *
 * 1. **Create Command Handler File** (in `./commands/` directory):
 *    ```typescript
 *    // commands/my-command.ts
 *    import type { SlashCommandHandler } from './types'
 *    import type { CommandSearchResult } from '../types'
 *    import { registerCommands, unregisterCommands } from './command-bus'
 *
 *    interface MyCommandDeps {
 *      myService?: (data: any) => Promise<void>
 *    }
 *
 *    export const myCommand: SlashCommandHandler<MyCommandDeps> = {
 *      name: 'mycommand',
 *      aliases: ['mc'], // Optional aliases
 *      description: 'My custom command description',
 *
 *      async search(args: string, locale: string = 'en') {
 *        // Return search results based on args
 *        return [{
 *          id: 'my-result',
 *          title: 'My Command Result',
 *          description: 'Description of the result',
 *          type: 'command' as const,
 *          data: { command: 'my.action', args: { value: args } }
 *        }]
 *      },
 *
 *      register(deps: MyCommandDeps) {
 *        registerCommands({
 *          'my.action': async (args) => {
 *            await deps.myService?.(args?.value)
 *          }
 *        })
 *      },
 *
 *      unregister() {
 *        unregisterCommands(['my.action'])
 *      }
 *    }
 *    ```
 *
 * **Example for Self-Contained Command (no external dependencies):**
 *    ```typescript
 *    // commands/calculator-command.ts
 *    export const calculatorCommand: SlashCommandHandler = {
 *      name: 'calc',
 *      aliases: ['calculator'],
 *      description: 'Simple calculator',
 *
 *      async search(args: string) {
 *        if (!args.trim()) return []
 *        try {
 *          // Safe math evaluation (implement proper parser in real use)
 *          const result = Function('"use strict"; return (' + args + ')')()
 *          return [{
 *            id: 'calc-result',
 *            title: `${args} = ${result}`,
 *            description: 'Calculator result',
 *            type: 'command' as const,
 *            data: { command: 'calc.copy', args: { result: result.toString() } }
 *          }]
 *        } catch {
 *          return [{
 *            id: 'calc-error',
 *            title: 'Invalid expression',
 *            description: 'Please enter a valid math expression',
 *            type: 'command' as const,
 *            data: { command: 'calc.noop', args: {} }
 *          }]
 *        }
 *      },
 *
 *      register() {
 *        registerCommands({
 *          'calc.copy': (args) => navigator.clipboard.writeText(args.result),
 *          'calc.noop': () => {} // No operation
 *        })
 *      },
 *
 *      unregister() {
 *        unregisterCommands(['calc.copy', 'calc.noop'])
 *      }
 *    }
 *    ```
 *
 * 2. **Register Command** (in `./commands/slash.tsx`):
 *    ```typescript
 *    import { myCommand } from './my-command'
 *    import { calculatorCommand } from './calculator-command' // For self-contained commands
 *
 *    export const registerSlashCommands = (deps: Record<string, any>) => {
 *      slashCommandRegistry.register(themeCommand, { setTheme: deps.setTheme })
 *      slashCommandRegistry.register(languageCommand, { setLocale: deps.setLocale })
 *      slashCommandRegistry.register(myCommand, { myService: deps.myService }) // With dependencies
 *      slashCommandRegistry.register(calculatorCommand) // Self-contained, no dependencies
 *    }
 *
 *    export const unregisterSlashCommands = () => {
 *      slashCommandRegistry.unregister('theme')
 *      slashCommandRegistry.unregister('language')
 *      slashCommandRegistry.unregister('mycommand')
 *      slashCommandRegistry.unregister('calc') // Add this line
 *    }
 *    ```
 *
 *
 * 3. **Update SlashCommandProvider** (in `./commands/slash.tsx`):
 *    ```typescript
 *    export const SlashCommandProvider = () => {
 *      const theme = useTheme()
 *      const myService = useMyService() // Add external dependency if needed
 *
 *      useEffect(() => {
 *        registerSlashCommands({
 *          setTheme: theme.setTheme,          // Required for theme command
 *          setLocale: setLocaleOnClient,      // Required for language command
 *          myService: myService,              // Required for your custom command
 *          // Note: calculatorCommand doesn't need dependencies, so not listed here
 *        })
 *        return () => unregisterSlashCommands()
 *      }, [theme.setTheme, myService]) // Update dependency array for all dynamic deps
 *
 *      return null
 *    }
 *    ```
 *
 *    **Note:** Self-contained commands (like calculator) don't require dependencies but are
 *    still registered through the same system for consistent lifecycle management.
 *
 * 4. **Usage**: Users can now type `/mycommand` or `/mc` to use your command
 *
 * ## Command System Architecture
 * - Commands are registered via `SlashCommandRegistry`
 * - Each command is self-contained with its own dependencies
 * - Commands support aliases for easier access
 * - Command execution is handled by the command bus system
 * - All commands should be registered through `SlashCommandProvider` for consistent lifecycle management
 *
 * ## Command Types
 * **Commands with External Dependencies:**
 * - Require external services, APIs, or React hooks
 * - Must provide dependencies in `SlashCommandProvider`
 * - Example: theme commands (needs useTheme), API commands (needs service)
 *
 * **Self-Contained Commands:**
 * - Pure logic operations, no external dependencies
 * - Still recommended to register through `SlashCommandProvider` for consistency
 * - Example: calculator, text manipulation commands
 *
 * ## Available Actions
 * - `@app` - Search applications
 * - `@knowledge` / `@kb` - Search knowledge bases
 * - `@plugin` - Search plugins
 * - `@node` - Search workflow nodes (workflow pages only)
 * - `/` - Execute slash commands (theme, language, etc.)
 */

Screenshots

Before After
... ...

https://github.com/user-attachments/assets/d049bb23-6b00-4069-9e07-a1c2df8d46ec

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran dev/reformat(backend) and cd web && npx lint-staged(frontend) to appease the lint gods
**Original Pull Request:** https://github.com/langgenius/dify/pull/24091 **State:** closed **Merged:** Yes --- > [!IMPORTANT] > > 1. Make sure you have read our [contribution guidelines](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) > 2. Ensure there is an associated issue and you have been assigned to it > 3. Use the correct syntax to link this PR: `Fixes #<issue number>`. ## Summary fix #24088 <!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. --> ``` /** * Goto Anything - Action System * * This file defines the action registry for the goto-anything search system. * Actions handle different types of searches: apps, knowledge bases, plugins, workflow nodes, and commands. * * ## How to Add a New Slash Command * * 1. **Create Command Handler File** (in `./commands/` directory): * ```typescript * // commands/my-command.ts * import type { SlashCommandHandler } from './types' * import type { CommandSearchResult } from '../types' * import { registerCommands, unregisterCommands } from './command-bus' * * interface MyCommandDeps { * myService?: (data: any) => Promise<void> * } * * export const myCommand: SlashCommandHandler<MyCommandDeps> = { * name: 'mycommand', * aliases: ['mc'], // Optional aliases * description: 'My custom command description', * * async search(args: string, locale: string = 'en') { * // Return search results based on args * return [{ * id: 'my-result', * title: 'My Command Result', * description: 'Description of the result', * type: 'command' as const, * data: { command: 'my.action', args: { value: args } } * }] * }, * * register(deps: MyCommandDeps) { * registerCommands({ * 'my.action': async (args) => { * await deps.myService?.(args?.value) * } * }) * }, * * unregister() { * unregisterCommands(['my.action']) * } * } * ``` * * **Example for Self-Contained Command (no external dependencies):** * ```typescript * // commands/calculator-command.ts * export const calculatorCommand: SlashCommandHandler = { * name: 'calc', * aliases: ['calculator'], * description: 'Simple calculator', * * async search(args: string) { * if (!args.trim()) return [] * try { * // Safe math evaluation (implement proper parser in real use) * const result = Function('"use strict"; return (' + args + ')')() * return [{ * id: 'calc-result', * title: `${args} = ${result}`, * description: 'Calculator result', * type: 'command' as const, * data: { command: 'calc.copy', args: { result: result.toString() } } * }] * } catch { * return [{ * id: 'calc-error', * title: 'Invalid expression', * description: 'Please enter a valid math expression', * type: 'command' as const, * data: { command: 'calc.noop', args: {} } * }] * } * }, * * register() { * registerCommands({ * 'calc.copy': (args) => navigator.clipboard.writeText(args.result), * 'calc.noop': () => {} // No operation * }) * }, * * unregister() { * unregisterCommands(['calc.copy', 'calc.noop']) * } * } * ``` * * 2. **Register Command** (in `./commands/slash.tsx`): * ```typescript * import { myCommand } from './my-command' * import { calculatorCommand } from './calculator-command' // For self-contained commands * * export const registerSlashCommands = (deps: Record<string, any>) => { * slashCommandRegistry.register(themeCommand, { setTheme: deps.setTheme }) * slashCommandRegistry.register(languageCommand, { setLocale: deps.setLocale }) * slashCommandRegistry.register(myCommand, { myService: deps.myService }) // With dependencies * slashCommandRegistry.register(calculatorCommand) // Self-contained, no dependencies * } * * export const unregisterSlashCommands = () => { * slashCommandRegistry.unregister('theme') * slashCommandRegistry.unregister('language') * slashCommandRegistry.unregister('mycommand') * slashCommandRegistry.unregister('calc') // Add this line * } * ``` * * * 3. **Update SlashCommandProvider** (in `./commands/slash.tsx`): * ```typescript * export const SlashCommandProvider = () => { * const theme = useTheme() * const myService = useMyService() // Add external dependency if needed * * useEffect(() => { * registerSlashCommands({ * setTheme: theme.setTheme, // Required for theme command * setLocale: setLocaleOnClient, // Required for language command * myService: myService, // Required for your custom command * // Note: calculatorCommand doesn't need dependencies, so not listed here * }) * return () => unregisterSlashCommands() * }, [theme.setTheme, myService]) // Update dependency array for all dynamic deps * * return null * } * ``` * * **Note:** Self-contained commands (like calculator) don't require dependencies but are * still registered through the same system for consistent lifecycle management. * * 4. **Usage**: Users can now type `/mycommand` or `/mc` to use your command * * ## Command System Architecture * - Commands are registered via `SlashCommandRegistry` * - Each command is self-contained with its own dependencies * - Commands support aliases for easier access * - Command execution is handled by the command bus system * - All commands should be registered through `SlashCommandProvider` for consistent lifecycle management * * ## Command Types * **Commands with External Dependencies:** * - Require external services, APIs, or React hooks * - Must provide dependencies in `SlashCommandProvider` * - Example: theme commands (needs useTheme), API commands (needs service) * * **Self-Contained Commands:** * - Pure logic operations, no external dependencies * - Still recommended to register through `SlashCommandProvider` for consistency * - Example: calculator, text manipulation commands * * ## Available Actions * - `@app` - Search applications * - `@knowledge` / `@kb` - Search knowledge bases * - `@plugin` - Search plugins * - `@node` - Search workflow nodes (workflow pages only) * - `/` - Execute slash commands (theme, language, etc.) */ ``` ## Screenshots | Before | After | |--------|-------| | ... | ... | https://github.com/user-attachments/assets/d049bb23-6b00-4069-9e07-a1c2df8d46ec ## Checklist - [ ] This change requires a documentation update, included: [Dify Document](https://github.com/langgenius/dify-docs) - [x] I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!) - [x] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change. - [x] I've updated the documentation accordingly. - [x] I ran `dev/reformat`(backend) and `cd web && npx lint-staged`(frontend) to appease the lint gods
yindo added the pull-request label 2026-02-21 20:47:37 -05:00
yindo closed this issue 2026-02-21 20:47:37 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#30500