diff --git a/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx index 957befb15a8..33acbde4813 100644 --- a/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx @@ -93,6 +93,33 @@ const mockBuiltInTools = vi.hoisted(() => [ }, ]) +const wikipediaProvider = { + id: 'wikipedia', + name: 'Wikipedia', + author: 'Dify', + description: { en_US: 'Wikipedia tools' }, + icon: 'wikipedia.svg', + icon_dark: 'wikipedia-dark.svg', + label: { en_US: 'Wikipedia' }, + type: 'builtin', + team_credentials: {}, + is_team_authorization: true, + allow_delete: false, + labels: [], + meta: {}, + tools: [ + { + name: 'wikipedia_search', + author: 'Dify', + label: { en_US: 'Wikipedia Search' }, + description: { en_US: 'Search Wikipedia.' }, + parameters: [], + labels: [], + output_schema: {}, + }, + ], +} + vi.mock('@/app/components/base/prompt-editor', () => ({ __esModule: true, default: (props: PromptEditorProps) => { @@ -929,6 +956,60 @@ describe('AgentPromptEditor', () => { ]) }) + it('should show configured providers and actions before other available tools', () => { + mockBuiltInTools.unshift(wikipediaProvider) + const configuredDuckDuckGoTranslateTool: AgentTool = { + ...duckDuckGoProviderTool, + actions: [ + { + id: 'duckduckgo-translate', + name: 'DuckDuckGo Translate', + toolName: 'ddg_translate', + description: 'Translate search results.', + }, + ], + } + + const view = render( + , + ) + + try { + const providerButtons = screen + .getAllByRole('button') + .filter( + (button) => + button.textContent?.includes('DuckDuckGo') || + button.textContent?.includes('Wikipedia'), + ) + + expect(providerButtons).toHaveLength(2) + expect(providerButtons[0]).toHaveTextContent('DuckDuckGo') + expect(providerButtons[1]).toHaveTextContent('Wikipedia') + + fireEvent.click(screen.getByRole('button', { name: 'DuckDuckGo' })) + const actionButtons = screen.getAllByRole('button', { + name: /DuckDuckGo (Search|Translate)/, + }) + expect(actionButtons[0]).toHaveTextContent('DuckDuckGo Translate') + expect(actionButtons[1]).toHaveTextContent('DuckDuckGo Search') + } finally { + view.unmount() + mockBuiltInTools.shift() + } + }) + it('should close the slash menu when the trailing slash is deleted', async () => { const { setPromptValue } = renderAgentPromptEditor('Review /') diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/slash.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/slash.tsx index 48482025ceb..6767aa8ed9f 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/slash.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/slash.tsx @@ -329,20 +329,23 @@ function AgentPromptToolRows({ const configuredCliTools = ENABLE_AGENT_CLI_TOOLS ? configuredTools.filter((tool) => tool.kind === 'cli') : [] - const availableProviders = useMemo(() => { - if (activeTab === 'all') return [...builtInTools, ...workflowTools, ...customTools, ...mcpTools] - if (activeTab === ToolType.BuiltIn) return builtInTools - if (activeTab === ToolType.Workflow) return workflowTools - if (activeTab === ToolType.Custom) return customTools - if (activeTab === ToolType.MCP) return mcpTools - - return [] - }, [activeTab, builtInTools, customTools, mcpTools, workflowTools]) - const selectedTools = useMemo( () => configuredTools.flatMap(toSelectedToolValue), [configuredTools], ) + const availableProviders = useMemo(() => { + let providers: ToolWithProvider[] = [] + if (activeTab === 'all') + providers = [...builtInTools, ...workflowTools, ...customTools, ...mcpTools] + if (activeTab === ToolType.BuiltIn) providers = builtInTools + if (activeTab === ToolType.Workflow) providers = workflowTools + if (activeTab === ToolType.Custom) providers = customTools + if (activeTab === ToolType.MCP) providers = mcpTools + + return prioritizeItems(providers, (provider) => + provider.tools.some((tool) => isToolSelected(selectedTools, provider, tool)), + ) + }, [activeTab, builtInTools, customTools, mcpTools, selectedTools, workflowTools]) const tabs = [ { key: 'all' as const, label: t(($) => $['agentDetail.configure.tools.toolTabs.all']) }, { @@ -435,7 +438,9 @@ function AgentPromptToolRows({ onToggle={() => toggleProvider(provider.id)} /> {expandedProviderIds.has(provider.id) && - provider.tools.map((tool) => ( + prioritizeItems(provider.tools, (tool) => + isToolSelected(selectedTools, provider, tool), + ).map((tool) => ( (items: T[], isPriority: (item: T) => boolean) { + const priorityItems: T[] = [] + const remainingItems: T[] = [] + + items.forEach((item) => { + if (isPriority(item)) priorityItems.push(item) + else remainingItems.push(item) + }) + + return [...priorityItems, ...remainingItems] +} + function getLocalizedText(text: Record | undefined | null, language: string) { if (!text) return ''