diff --git a/web/app/components/goto-anything/actions/commands/banana.spec.tsx b/web/app/components/goto-anything/actions/commands/banana.spec.tsx deleted file mode 100644 index 420cf40af6..0000000000 --- a/web/app/components/goto-anything/actions/commands/banana.spec.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import { isInWorkflowPage, VIBE_COMMAND_EVENT } from '@/app/components/workflow/constants' -import { bananaCommand } from './banana' -import { registerCommands, unregisterCommands } from './command-bus' - -// Mock i18n for testing -const mockI18n = { - t: vi.fn((key: string, options?: Record) => { - if (!options) - return key - return `${key}:${JSON.stringify(options)}` - }), -} - -vi.mock('react-i18next', () => ({ - getI18n: () => mockI18n, -})) - -vi.mock('@/app/components/workflow/constants', async () => { - const actual = await vi.importActual( - '@/app/components/workflow/constants', - ) - return { - ...actual, - isInWorkflowPage: vi.fn(), - } -}) - -vi.mock('./command-bus', () => ({ - registerCommands: vi.fn(), - unregisterCommands: vi.fn(), -})) - -const mockedIsInWorkflowPage = vi.mocked(isInWorkflowPage) -const mockedRegisterCommands = vi.mocked(registerCommands) -const mockedUnregisterCommands = vi.mocked(unregisterCommands) -const mockedT = mockI18n.t - -type CommandArgs = { dsl?: string } -type CommandMap = Record void | Promise> - -beforeEach(() => { - vi.clearAllMocks() -}) - -// Command availability, search, and registration behavior for banana command. -describe('bananaCommand', () => { - // Command metadata mirrors the static definition. - describe('metadata', () => { - it('should expose name, mode, and description', () => { - // Assert - expect(bananaCommand.name).toBe('banana') - expect(bananaCommand.mode).toBe('submenu') - expect(bananaCommand.description).toContain('app.gotoAnything.actions.vibeDesc') - }) - }) - - // Availability mirrors workflow page detection. - describe('availability', () => { - it('should return true when on workflow page', () => { - // Arrange - mockedIsInWorkflowPage.mockReturnValue(true) - - // Act - const available = bananaCommand.isAvailable?.() - - // Assert - expect(available).toBe(true) - expect(mockedIsInWorkflowPage).toHaveBeenCalledTimes(1) - }) - - it('should return false when not on workflow page', () => { - // Arrange - mockedIsInWorkflowPage.mockReturnValue(false) - - // Act - const available = bananaCommand.isAvailable?.() - - // Assert - expect(available).toBe(false) - expect(mockedIsInWorkflowPage).toHaveBeenCalledTimes(1) - }) - }) - - // Search results depend on provided arguments. - describe('search', () => { - it('should return hint description when args are empty', async () => { - // Arrange - mockedIsInWorkflowPage.mockReturnValue(true) - - // Act - const result = await bananaCommand.search(' ') - - // Assert - expect(result).toHaveLength(1) - const [item] = result - expect(item.description).toContain('app.gotoAnything.actions.vibeHint') - expect(item.data?.args?.dsl).toBe('') - expect(item.data?.command).toBe('workflow.vibe') - expect(mockedT).toHaveBeenCalledWith( - 'app.gotoAnything.actions.vibeTitle', - expect.objectContaining({ lng: 'en' }), - ) - expect(mockedT).toHaveBeenCalledWith( - 'app.gotoAnything.actions.vibeHint', - expect.objectContaining({ prompt: expect.any(String), lng: 'en' }), - ) - }) - - it('should return default description when args are provided', async () => { - // Arrange - mockedIsInWorkflowPage.mockReturnValue(true) - - // Act - const result = await bananaCommand.search(' make a flow ', 'fr') - - // Assert - expect(result).toHaveLength(1) - const [item] = result - expect(item.description).toContain('app.gotoAnything.actions.vibeDesc') - expect(item.data?.args?.dsl).toBe('make a flow') - expect(item.data?.command).toBe('workflow.vibe') - expect(mockedT).toHaveBeenCalledWith( - 'app.gotoAnything.actions.vibeTitle', - expect.objectContaining({ lng: 'fr' }), - ) - expect(mockedT).toHaveBeenCalledWith( - 'app.gotoAnything.actions.vibeDesc', - expect.objectContaining({ lng: 'fr' }), - ) - }) - - it('should fall back to Banana when title translation is empty', async () => { - // Arrange - mockedIsInWorkflowPage.mockReturnValue(true) - mockedT.mockImplementationOnce(() => '') - - // Act - const result = await bananaCommand.search('make a plan') - - // Assert - expect(result).toHaveLength(1) - expect(result[0]?.title).toBe('Banana') - }) - }) - - // Command registration and event dispatching. - describe('registration', () => { - it('should register the workflow vibe command', () => { - // Act - expect(bananaCommand.register).toBeDefined() - bananaCommand.register?.({}) - - // Assert - expect(mockedRegisterCommands).toHaveBeenCalledTimes(1) - const commands = mockedRegisterCommands.mock.calls[0]?.[0] as CommandMap - expect(commands['workflow.vibe']).toEqual(expect.any(Function)) - }) - - it('should dispatch vibe event when command handler runs', async () => { - // Arrange - const dispatchSpy = vi.spyOn(document, 'dispatchEvent') - expect(bananaCommand.register).toBeDefined() - bananaCommand.register?.({}) - expect(mockedRegisterCommands).toHaveBeenCalledTimes(1) - const commands = mockedRegisterCommands.mock.calls[0]?.[0] as CommandMap - - try { - // Act - await commands['workflow.vibe']?.({ dsl: 'hello' }) - - // Assert - expect(dispatchSpy).toHaveBeenCalledTimes(1) - const event = dispatchSpy.mock.calls[0][0] as CustomEvent - expect(event.type).toBe(VIBE_COMMAND_EVENT) - expect(event.detail).toEqual({ dsl: 'hello' }) - } - finally { - dispatchSpy.mockRestore() - } - }) - - it('should unregister workflow vibe command', () => { - // Act - expect(bananaCommand.unregister).toBeDefined() - bananaCommand.unregister?.() - - // Assert - expect(mockedUnregisterCommands).toHaveBeenCalledWith(['workflow.vibe']) - }) - }) -}) diff --git a/web/app/components/goto-anything/actions/commands/banana.tsx b/web/app/components/goto-anything/actions/commands/generate.tsx similarity index 61% rename from web/app/components/goto-anything/actions/commands/banana.tsx rename to web/app/components/goto-anything/actions/commands/generate.tsx index e071d84767..ccd8c0310d 100644 --- a/web/app/components/goto-anything/actions/commands/banana.tsx +++ b/web/app/components/goto-anything/actions/commands/generate.tsx @@ -5,20 +5,20 @@ import { getI18n } from 'react-i18next' import { isInWorkflowPage, VIBE_COMMAND_EVENT } from '@/app/components/workflow/constants' import { registerCommands, unregisterCommands } from './command-bus' -type BananaDeps = Record +type GenerateDeps = Record -const BANANA_PROMPT_EXAMPLE = 'Summarize a document, classify sentiment, then notify Slack' +const GENERATE_PROMPT_EXAMPLE = 'Summarize a document, classify sentiment, then notify Slack' -const dispatchVibeCommand = (input?: string) => { +const dispatchGenerateCommand = (input?: string) => { if (typeof document === 'undefined') return document.dispatchEvent(new CustomEvent(VIBE_COMMAND_EVENT, { detail: { dsl: input } })) } -export const bananaCommand: SlashCommandHandler = { - name: 'banana', - description: getI18n().t('gotoAnything.actions.vibeDesc', { ns: 'app' }), +export const generateCommand: SlashCommandHandler = { + name: 'generate', + description: getI18n().t('gotoAnything.actions.generationDesc', { ns: 'app' }), mode: 'submenu', isAvailable: () => isInWorkflowPage(), @@ -27,11 +27,11 @@ export const bananaCommand: SlashCommandHandler = { const hasInput = !!trimmed return [{ - id: 'banana-vibe', - title: getI18n().t('gotoAnything.actions.vibeTitle', { ns: 'app', lng: locale }) || 'Banana', + id: 'generate', + title: getI18n().t('gotoAnything.actions.vibeTitle', { ns: 'app', lng: locale }) || 'Generate', description: hasInput - ? getI18n().t('gotoAnything.actions.vibeDesc', { ns: 'app', lng: locale }) - : getI18n().t('gotoAnything.actions.vibeHint', { ns: 'app', lng: locale, prompt: BANANA_PROMPT_EXAMPLE }), + ? getI18n().t('gotoAnything.actions.generationDesc', { ns: 'app', lng: locale }) + : getI18n().t('gotoAnything.actions.vibeHint', { ns: 'app', lng: locale, prompt: GENERATE_PROMPT_EXAMPLE }), type: 'command' as const, icon: (
@@ -39,21 +39,21 @@ export const bananaCommand: SlashCommandHandler = {
), data: { - command: 'workflow.vibe', + command: 'workflow.generate', args: { dsl: trimmed }, }, }] }, - register(_deps: BananaDeps) { + register(_deps: GenerateDeps) { registerCommands({ - 'workflow.vibe': async (args) => { - dispatchVibeCommand(args?.dsl) + 'workflow.generate': async (args) => { + dispatchGenerateCommand(args?.dsl) }, }) }, unregister() { - unregisterCommands(['workflow.vibe']) + unregisterCommands(['workflow.generate']) }, } diff --git a/web/app/components/goto-anything/actions/commands/slash.tsx b/web/app/components/goto-anything/actions/commands/slash.tsx index 2d6019dac1..5d20c0a938 100644 --- a/web/app/components/goto-anything/actions/commands/slash.tsx +++ b/web/app/components/goto-anything/actions/commands/slash.tsx @@ -7,10 +7,10 @@ import { getI18n } from 'react-i18next' import { setLocaleOnClient } from '@/i18n-config' import { ACTION_KEYS } from '../../constants' import { accountCommand } from './account' -import { bananaCommand } from './banana' import { communityCommand } from './community' import { docsCommand } from './docs' import { forumCommand } from './forum' +import { generateCommand } from './generate' import { languageCommand } from './language' import { slashCommandRegistry } from './registry' import { themeCommand } from './theme' @@ -39,7 +39,7 @@ export const registerSlashCommands = (deps: SlashCommandDependencies) => { slashCommandRegistry.register(communityCommand, {}) slashCommandRegistry.register(accountCommand, {}) slashCommandRegistry.register(zenCommand, {}) - slashCommandRegistry.register(bananaCommand, {}) + slashCommandRegistry.register(generateCommand, {}) } export const unregisterSlashCommands = () => { @@ -51,7 +51,7 @@ export const unregisterSlashCommands = () => { slashCommandRegistry.unregister('community') slashCommandRegistry.unregister('account') slashCommandRegistry.unregister('zen') - slashCommandRegistry.unregister('banana') + slashCommandRegistry.unregister('generate') } export const SlashCommandProvider = () => { diff --git a/web/app/components/goto-anything/actions/commands/vibe.tsx b/web/app/components/goto-anything/actions/commands/vibe.tsx deleted file mode 100644 index 76a371f7cc..0000000000 --- a/web/app/components/goto-anything/actions/commands/vibe.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { SlashCommandHandler } from './types' -import { RiSparklingFill } from '@remixicon/react' -import * as React from 'react' -import { getI18n } from 'react-i18next' -import { isInWorkflowPage, VIBE_COMMAND_EVENT } from '@/app/components/workflow/constants' -import { registerCommands, unregisterCommands } from './command-bus' - -type VibeDeps = Record - -const VIBE_PROMPT_EXAMPLE = 'Summarize a document, classify sentiment, then notify Slack' - -const dispatchVibeCommand = (input?: string) => { - if (typeof document === 'undefined') - return - - document.dispatchEvent(new CustomEvent(VIBE_COMMAND_EVENT, { detail: { dsl: input } })) -} - -export const vibeCommand: SlashCommandHandler = { - name: 'vibe', - description: getI18n().t('gotoAnything.actions.vibeDesc', { ns: 'app' }), - mode: 'submenu', - isAvailable: () => isInWorkflowPage(), - - async search(args: string, locale: string = 'en') { - const trimmed = args.trim() - const hasInput = !!trimmed - - return [{ - id: 'vibe', - title: getI18n().t('gotoAnything.actions.vibeTitle', { ns: 'app', lng: locale }) || 'Vibe', - description: hasInput - ? getI18n().t('gotoAnything.actions.vibeDesc', { ns: 'app', lng: locale }) - : getI18n().t('gotoAnything.actions.vibeHint', { ns: 'app', lng: locale, prompt: VIBE_PROMPT_EXAMPLE }), - type: 'command' as const, - icon: ( -
- -
- ), - data: { - command: 'workflow.vibe', - args: { dsl: trimmed }, - }, - }] - }, - - register(_deps: VibeDeps) { - registerCommands({ - 'workflow.vibe': async (args) => { - dispatchVibeCommand(args?.dsl) - }, - }) - }, - - unregister() { - unregisterCommands(['workflow.vibe']) - }, -} diff --git a/web/app/components/goto-anything/command-selector.tsx b/web/app/components/goto-anything/command-selector.tsx index 731796a320..48b47e945d 100644 --- a/web/app/components/goto-anything/command-selector.tsx +++ b/web/app/components/goto-anything/command-selector.tsx @@ -126,7 +126,7 @@ const CommandSelector: FC = ({ scopes, onCommandSelect, searchFilter, com '/docs': 'gotoAnything.actions.docDesc', '/community': 'gotoAnything.actions.communityDesc', '/zen': 'gotoAnything.actions.zenDesc', - '/banana': 'gotoAnything.actions.vibeDesc', + '/generate': 'gotoAnything.actions.generationDesc', } as const return t(slashKeyMap[item.key as keyof typeof slashKeyMap] || item.description, { ns: 'app' }) })() diff --git a/web/app/components/goto-anything/hooks/use-goto-anything-navigation.ts b/web/app/components/goto-anything/hooks/use-goto-anything-navigation.ts index 8b90e597c5..77d613bedf 100644 --- a/web/app/components/goto-anything/hooks/use-goto-anything-navigation.ts +++ b/web/app/components/goto-anything/hooks/use-goto-anything-navigation.ts @@ -67,7 +67,7 @@ export const useGotoAnythingNavigation = ( switch (result.type) { case 'command': { - if (result.data.command === 'workflow.vibe') { + if (result.data.command === 'workflow.generate') { if (typeof document !== 'undefined') { document.dispatchEvent(new CustomEvent(VIBE_COMMAND_EVENT, { detail: { dsl: result.data.args?.dsl } })) } diff --git a/web/app/components/workflow/panel/vibe-panel/index.tsx b/web/app/components/workflow/panel/vibe-panel/index.tsx index f2f9440d99..75f1e1425b 100644 --- a/web/app/components/workflow/panel/vibe-panel/index.tsx +++ b/web/app/components/workflow/panel/vibe-panel/index.tsx @@ -234,7 +234,7 @@ const VibePanel: FC = () => {
{t('gotoAnything.actions.vibeTitle', { ns: 'app' })}
-
{t('gotoAnything.actions.vibeDesc', { ns: 'app' })}
+
{t('gotoAnything.actions.generationDesc', { ns: 'app' })}