feat(web): enhance Go to Anything navigation and discovery (#41160)

This commit is contained in:
Crazywoola
2026-08-24 08:48:53 +00:00
committed by GitHub
parent fef28097cc
commit 21d540f760
49 changed files with 975 additions and 537 deletions
-13
View File
@@ -1,13 +0,0 @@
have_fun: false
memory_config:
disabled: false
code_review:
disable: true
comment_severity_threshold: MEDIUM
max_review_comments: -1
pull_request_opened:
help: false
summary: false
code_review: false
include_drafts: false
ignore_patterns: []
-5
View File
@@ -2466,11 +2466,6 @@
"count": 2
}
},
"web/app/components/goto-anything/actions/recent-store.ts": {
"no-restricted-globals": {
"count": 2
}
},
"web/app/components/header/account-setting/data-source-page-new/__tests__/item.spec.tsx": {
"jsx-a11y/click-events-have-key-events": {
"count": 1
@@ -50,16 +50,27 @@ const emptyRemoteQueryState = (): RemoteQueryState => ({
error: null,
})
let remoteQueryStates: Record<'app' | 'knowledge' | 'plugin', RemoteQueryState> = {
let remoteQueryStates: Record<
'app' | 'knowledge' | 'plugin' | 'skill' | 'agent',
RemoteQueryState
> = {
app: emptyRemoteQueryState(),
knowledge: emptyRemoteQueryState(),
plugin: emptyRemoteQueryState(),
skill: emptyRemoteQueryState(),
agent: emptyRemoteQueryState(),
}
let enabledRemoteQueryKeys: string[] = []
function setRemoteResults(results: TestSearchResult[]) {
results.forEach((result) => {
if (result.type === 'app' || result.type === 'knowledge' || result.type === 'plugin')
if (
result.type === 'app' ||
result.type === 'knowledge' ||
result.type === 'plugin' ||
result.type === 'skill' ||
result.type === 'agent'
)
remoteQueryStates[result.type].data.push(result)
})
}
@@ -82,6 +93,36 @@ vi.mock('../actions/knowledge', () => ({
vi.mock('../actions/plugin', () => ({
pluginSearchQueryOptions: () => ({ queryKey: ['plugin'] }),
}))
vi.mock('../actions/skill', () => ({
skillSearchQueryOptions: () => ({ queryKey: ['skill'] }),
}))
vi.mock('../actions/agent', () => ({
agentSearchQueryOptions: () => ({ queryKey: ['agent'] }),
}))
const visibilityState = vi.hoisted(() => ({
agentEnabled: true,
canManageAgents: true,
datasetOperator: false,
}))
vi.mock('jotai', async (importOriginal) => {
const actual = await importOriginal<typeof import('jotai')>()
return {
...actual,
useAtomValue: () => visibilityState.datasetOperator,
}
})
vi.mock('@/features/agent-v2/feature-flag', () => ({
isAgentV2Enabled: () => visibilityState.agentEnabled,
}))
vi.mock('@/features/agent-v2/permissions', () => ({
useCanManageAgents: () => visibilityState.canManageAgents,
}))
vi.mock(
'@/app/components/plugins/install-plugin/hooks/use-workspace-plugin-install-permission',
() => ({
@@ -113,14 +154,22 @@ const actionsMock = {
app: createRemoteAction('@app', '@app'),
knowledge: createRemoteAction('@knowledge', '@kb'),
plugin: createRemoteAction('@plugin', '@plugin'),
skill: createRemoteAction('@skill', '@skill'),
agent: createRemoteAction('@agents', '@agents'),
}
const createActionsMock = vi.fn(() => actionsMock)
const createActionsMock = vi.fn(
(
_isWorkflowPage?: boolean,
_isRagPipelinePage?: boolean,
_availability?: { agents: boolean; skills: boolean },
) => actionsMock,
)
const matchActionMock = vi.fn<
(query: string, actions: Record<string, ActionItem>) => ActionItem | undefined
>(() => undefined)
vi.mock('../actions', () => ({
createActions: () => createActionsMock(),
createActions: (...args: Parameters<typeof createActionsMock>) => createActionsMock(...args),
getActionSearchTerm: (_query: string, action: ActionItem) => action.key,
matchAction: (query: string, actions: Record<string, ActionItem>) =>
matchActionMock(query, actions),
@@ -178,10 +227,15 @@ describe('GotoAnything', () => {
app: emptyRemoteQueryState(),
knowledge: emptyRemoteQueryState(),
plugin: emptyRemoteQueryState(),
skill: emptyRemoteQueryState(),
agent: emptyRemoteQueryState(),
}
debouncedSearchQuery = undefined
enabledRemoteQueryKeys = []
matchActionMock.mockReset()
visibilityState.agentEnabled = true
visibilityState.canManageAgents = true
visibilityState.datasetOperator = false
mockFindCommand = null
mockAvailableCommands = []
})
@@ -322,6 +376,21 @@ describe('GotoAnything', () => {
})
describe('search functionality', () => {
it.each([
[{ agentEnabled: true, canManageAgents: true, datasetOperator: false }, true, true],
[{ agentEnabled: false, canManageAgents: true, datasetOperator: false }, false, true],
[{ agentEnabled: true, canManageAgents: false, datasetOperator: true }, false, false],
] as const)(
'matches scope visibility to workspace capabilities',
(visibility, agents, skills) => {
Object.assign(visibilityState, visibility)
renderGotoAnything(<GotoAnything />)
expect(createActionsMock).toHaveBeenCalledWith(false, false, { agents, skills })
},
)
it('should navigate to selected result', async () => {
const user = userEvent.setup()
setRemoteResults([
@@ -460,6 +529,88 @@ describe('GotoAnything', () => {
).toBeInTheDocument()
})
it('shows the localized system model description for /models', async () => {
const user = userEvent.setup()
mockAvailableCommands = [{ name: 'models', description: 'Fallback description' }]
renderGotoAnything(<GotoAnything />)
triggerSearchShortcut()
const input = await screen.findByRole('combobox', {
name: 'app.gotoAnything.searchTitle',
})
await user.type(input, '/')
expect(screen.getByText('common.modelProvider.systemModelSettingsDesc')).toBeInTheDocument()
expect(screen.queryByText('Fallback description')).not.toBeInTheDocument()
})
it('queries skills and agents during ordinary search', async () => {
const user = userEvent.setup()
renderGotoAnything(<GotoAnything />)
triggerSearchShortcut()
const input = await screen.findByRole('combobox', {
name: 'app.gotoAnything.searchTitle',
})
await user.type(input, 'research')
expect(enabledRemoteQueryKeys).toEqual(
expect.arrayContaining(['app', 'knowledge', 'plugin', 'skill', 'agent']),
)
})
it.each([
['@skill', actionsMock.skill, 'skill'],
['@agents', actionsMock.agent, 'agent'],
] as const)('limits %s searches to the matching provider', async (scope, action, provider) => {
const user = userEvent.setup()
matchActionMock.mockImplementation((query: string) =>
query.startsWith(scope) ? action : undefined,
)
renderGotoAnything(<GotoAnything />)
triggerSearchShortcut()
const input = await screen.findByRole('combobox', {
name: 'app.gotoAnything.searchTitle',
})
await user.type(input, `${scope} research`)
expect(enabledRemoteQueryKeys).toContain(provider)
expect(enabledRemoteQueryKeys).not.toEqual(
expect.arrayContaining(
['app', 'knowledge', 'plugin', 'skill', 'agent'].filter((key) => key !== provider),
),
)
})
it.each([
['skill', '/skills/skill-1'],
['agent', '/agents/agent-1/configure'],
] as const)('navigates to a selected %s result', async (type, path) => {
const user = userEvent.setup()
setRemoteResults([
{
id: `${type}-1`,
type,
title: `${type} result`,
path,
data: {},
},
])
renderGotoAnything(<GotoAnything />)
triggerSearchShortcut()
const input = await screen.findByRole('combobox', {
name: 'app.gotoAnything.searchTitle',
})
await user.type(input, type)
await user.click(await screen.findByText(`${type} result`))
expect(routerPush).toHaveBeenCalledWith(path)
})
it('should not search providers with a stale scope prefix', async () => {
const user = userEvent.setup()
matchActionMock.mockImplementation((query: string) =>
@@ -512,6 +663,8 @@ describe('GotoAnything', () => {
app: { data: [], isLoading: false, isError: true, error: testError },
knowledge: { data: [], isLoading: false, isError: true, error: testError },
plugin: { data: [], isLoading: false, isError: true, error: testError },
skill: { data: [], isLoading: false, isError: true, error: testError },
agent: { data: [], isLoading: false, isError: true, error: testError },
}
renderGotoAnything(<GotoAnything />)
@@ -564,17 +717,24 @@ describe('GotoAnything', () => {
expect(screen.queryByText('app.gotoAnything.searchFailed')).not.toBeInTheDocument()
})
it('should show default state when no query', async () => {
it('should show scope cards with the matching sidebar icons when opened', async () => {
renderGotoAnything(<GotoAnything />)
triggerSearchShortcut()
await waitFor(() => {
expect(
screen.getByPlaceholderText('app.gotoAnything.searchPlaceholder'),
).toBeInTheDocument()
})
const expectedScopeIcons = [
['@app', 'i-custom-vender-main-nav-studio'],
['@kb', 'i-custom-vender-main-nav-knowledge'],
['@plugin', 'i-custom-vender-main-nav-marketplace'],
['@skill', 'i-custom-vender-main-nav-skill'],
['@agents', 'i-custom-vender-main-nav-roster'],
] as const
expect(screen.getAllByText('app.gotoAnything.searchTitle')).toHaveLength(2)
for (const [scope, icon] of expectedScopeIcons) {
const option = await screen.findByRole('option', { name: new RegExp(scope) })
expect(option.querySelector(`.${icon}`)).toBeInTheDocument()
}
expect(screen.getByText('app.gotoAnything.selectSearchType')).toBeInTheDocument()
})
it('should show no results state when search returns empty', async () => {
@@ -0,0 +1,81 @@
import type { AgentAppPartial } from '@dify/contracts/api/console/agent/types.gen'
import { agentAction, agentSearchQueryOptions } from '../agent'
const serviceMocks = vi.hoisted(() => ({ queryOptions: vi.fn((options) => options) }))
vi.mock('@/service/client', () => ({
consoleQuery: { agent: { get: { queryOptions: serviceMocks.queryOptions } } },
}))
vi.mock('react-i18next', async () => {
const { createReactI18nextMock } = await import('@/test/i18n-mock')
return createReactI18nextMock({
'roster.title': 'Agents',
'roster.searchLabel': 'Search agents',
})
})
vi.mock('../../../base/app-icon', () => ({ default: () => null }))
function agent(overrides: Partial<AgentAppPartial> = {}): AgentAppPartial {
return {
id: 'agent-1',
name: 'Researcher',
description: 'Investigates a topic',
mode: 'agent-chat',
icon_url: null,
...overrides,
}
}
describe('agent search query', () => {
beforeEach(() => vi.clearAllMocks())
it('exposes the @agents scope', () => {
expect(agentAction).toMatchObject({
key: '@agents',
shortcut: '@agents',
title: 'Agents',
description: 'Search agents',
source: 'remote',
})
})
it('queries the generated agent endpoint by name', () => {
agentSearchQueryOptions('research')
expect(serviceMocks.queryOptions).toHaveBeenCalledWith(
expect.objectContaining({
input: {
query: {
page: 1,
limit: 10,
name: 'research',
sort_by: 'last_modified',
},
},
retry: false,
select: expect.any(Function),
}),
)
})
it('maps agents to their configure pages', () => {
const options = agentSearchQueryOptions('research')
const results = options.select!({
data: [agent()] as never,
has_more: false,
limit: 10,
page: 1,
total: 1,
})
expect(results[0]).toMatchObject({
id: 'agent-1',
title: 'Researcher',
description: 'Investigates a topic',
type: 'agent',
path: '/agents/agent-1/configure',
})
})
})
@@ -32,6 +32,26 @@ vi.mock('../plugin', () => ({
} satisfies ActionItem,
}))
vi.mock('../skill', () => ({
skillAction: {
key: '@skill',
shortcut: '@skill',
title: 'Skills',
description: 'Search skills',
source: 'remote',
} satisfies ActionItem,
}))
vi.mock('../agent', () => ({
agentAction: {
key: '@agents',
shortcut: '@agents',
title: 'Agents',
description: 'Search agents',
source: 'remote',
} satisfies ActionItem,
}))
vi.mock('../commands/slash', () => ({
slashAction: {
key: '/',
@@ -73,6 +93,15 @@ describe('createActions', () => {
expect.objectContaining({ slash: expect.any(Object), app: expect.any(Object) }),
)
expect(createActions(false, false)).not.toHaveProperty('node')
expect(createActions(false, false)).toHaveProperty('skill')
expect(createActions(false, false)).not.toHaveProperty('agent')
})
it('applies workspace availability to skill and agent scopes', () => {
const actions = createActions(false, false, { agents: true, skills: false })
expect(actions).toHaveProperty('agent')
expect(actions).not.toHaveProperty('skill')
})
it('uses the workflow-owned node action on workflow pages', () => {
@@ -98,7 +127,7 @@ describe('getActionSearchTerm', () => {
})
describe('matchAction', () => {
const actions = createActions(false, false)
const actions = createActions(false, false, { agents: true, skills: true })
beforeEach(() => {
vi.mocked(slashCommandRegistry.getAllCommands).mockReturnValue([])
@@ -108,6 +137,8 @@ describe('matchAction', () => {
['@app query', '@app'],
['@kb query', '@knowledge'],
['@plugin query', '@plugin'],
['@skill query', '@skill'],
['@agents query', '@agents'],
])('matches %s', (query, key) => {
expect(matchAction(query, actions)?.key).toBe(key)
})
@@ -1,76 +0,0 @@
import { addRecentItem, getRecentItems } from '../recent-store'
describe('recent-store', () => {
beforeEach(() => {
localStorage.clear()
})
describe('getRecentItems', () => {
it('returns an empty array when nothing is stored', () => {
expect(getRecentItems()).toEqual([])
})
it('parses stored items from localStorage', () => {
const items = [{ id: 'app-1', title: 'App 1', path: '/app/1', originalType: 'app' as const }]
localStorage.setItem('goto-anything:recent', JSON.stringify(items))
expect(getRecentItems()).toEqual(items)
})
it('returns an empty array when stored JSON is invalid', () => {
localStorage.setItem('goto-anything:recent', 'not-json')
expect(getRecentItems()).toEqual([])
})
it('returns an empty array when localStorage throws', () => {
const spy = vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
throw new Error('boom')
})
expect(getRecentItems()).toEqual([])
spy.mockRestore()
})
})
describe('addRecentItem', () => {
it('prepends a new item to the stored list', () => {
addRecentItem({ id: 'a', title: 'A', path: '/a', originalType: 'app' })
addRecentItem({ id: 'b', title: 'B', path: '/b', originalType: 'knowledge' })
const stored = getRecentItems()
expect(stored.map((i) => i.id)).toEqual(['b', 'a'])
})
it('deduplicates by id, moving the existing entry to the front', () => {
addRecentItem({ id: 'a', title: 'A', path: '/a', originalType: 'app' })
addRecentItem({ id: 'b', title: 'B', path: '/b', originalType: 'app' })
addRecentItem({ id: 'a', title: 'A updated', path: '/a', originalType: 'app' })
const stored = getRecentItems()
expect(stored.map((i) => i.id)).toEqual(['a', 'b'])
expect(stored[0]!.title).toBe('A updated')
})
it('caps the list at 8 items, evicting the oldest', () => {
for (let i = 0; i < 10; i++)
addRecentItem({ id: `item-${i}`, title: `Item ${i}`, path: `/i/${i}`, originalType: 'app' })
const stored = getRecentItems()
expect(stored).toHaveLength(8)
expect(stored[0]!.id).toBe('item-9')
expect(stored[7]!.id).toBe('item-2')
})
it('silently swallows storage errors', () => {
const spy = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
throw new Error('quota')
})
expect(() =>
addRecentItem({ id: 'x', title: 'X', path: '/x', originalType: 'app' }),
).not.toThrow()
spy.mockRestore()
})
})
})
@@ -0,0 +1,77 @@
import type { SkillResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import { skillAction, skillSearchQueryOptions } from '../skill'
const serviceMocks = vi.hoisted(() => ({ queryOptions: vi.fn((options) => options) }))
vi.mock('@/service/client', () => ({
consoleQuery: {
workspaces: { current: { skills: { get: { queryOptions: serviceMocks.queryOptions } } } },
},
}))
vi.mock('react-i18next', async () => {
const { createReactI18nextMock } = await import('@/test/i18n-mock')
return createReactI18nextMock({
'skillManagement.title': 'Skills',
'skillManagement.searchLabel': 'Search skills',
})
})
function skill(overrides: Partial<SkillResponse> = {}): SkillResponse {
return {
id: 'skill-1',
name: 'summarizer',
display_name: 'Summarizer',
description: 'Summarizes long documents',
icon: '',
visibility: 'private',
created_at: 1,
updated_at: 1,
...overrides,
}
}
describe('skill search query', () => {
beforeEach(() => vi.clearAllMocks())
it('exposes the @skill scope', () => {
expect(skillAction).toMatchObject({
key: '@skill',
shortcut: '@skill',
title: 'Skills',
description: 'Search skills',
source: 'remote',
})
})
it('queries the generated skill endpoint by keyword', () => {
skillSearchQueryOptions('summary')
expect(serviceMocks.queryOptions).toHaveBeenCalledWith(
expect.objectContaining({
input: { query: { page: 1, limit: 10, keyword: 'summary' } },
retry: false,
select: expect.any(Function),
}),
)
})
it('maps skills to their detail pages', () => {
const options = skillSearchQueryOptions('summary')
const results = options.select!({
data: [skill()] as never,
page: 1,
limit: 10,
total: 1,
has_more: false,
})
expect(results[0]).toMatchObject({
id: 'skill-1',
title: 'Summarizer',
description: 'Summarizes long documents',
type: 'skill',
path: '/skills/skill-1',
})
})
})
@@ -0,0 +1,59 @@
import type { AgentAppPartial, AgentIconType } from '@dify/contracts/api/console/agent/types.gen'
import type { ActionItem, AgentSearchResult } from './types'
import { getI18n } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import AppIcon from '../../base/app-icon'
function getAgentResults(agents: AgentAppPartial[]): AgentSearchResult[] {
return agents.map((agent) => {
const imageUrl =
agent.icon_type === 'image' || agent.icon_type === 'link' ? agent.icon : undefined
const iconType = (imageUrl ? 'image' : agent.icon_type) as AgentIconType | null | undefined
return {
id: agent.id,
title: agent.name,
description: agent.description || agent.role || undefined,
type: 'agent' as const,
path: `/agents/${agent.id}/configure`,
icon: (
<AppIcon
size="large"
rounded
iconType={iconType}
icon={agent.icon ?? undefined}
background={agent.icon_background}
imageUrl={imageUrl}
/>
),
data: agent,
}
})
}
export const agentAction: ActionItem = {
key: '@agents',
shortcut: '@agents',
get title() {
return getI18n().t(($) => $['roster.title'], { ns: 'agentV2' })
},
get description() {
return getI18n().t(($) => $['roster.searchLabel'], { ns: 'agentV2' })
},
source: 'remote',
}
export function agentSearchQueryOptions(searchTerm: string) {
return consoleQuery.agent.get.queryOptions({
input: {
query: {
page: 1,
limit: 10,
name: searchTerm,
sort_by: 'last_modified',
},
},
retry: false,
select: (response) => getAgentResults(response.data),
})
}
@@ -1,14 +1,14 @@
/**
* Tests for direct-mode commands that share similar patterns:
* docs, account, community, forum
* docs, account, discord, models
*
* Each command: opens a URL or navigates, has direct mode, and registers a navigation command.
*/
import { accountCommand } from '../account'
import { registerCommands, unregisterCommands } from '../command-bus'
import { communityCommand } from '../community'
import { discordCommand } from '../discord'
import { docsCommand } from '../docs'
import { forumCommand } from '../forum'
import { modelsCommand, SYSTEM_MODELS_PATH } from '../models'
vi.mock('../command-bus')
@@ -161,21 +161,21 @@ describe('accountCommand', () => {
})
})
describe('communityCommand', () => {
describe('discordCommand', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('has correct metadata', () => {
expect(communityCommand.name).toBe('community')
expect(communityCommand.mode).toBe('direct')
expect(communityCommand.execute).toBeDefined()
expect(discordCommand.name).toBe('discord')
expect(discordCommand.mode).toBe('direct')
expect(discordCommand.execute).toBeDefined()
})
it('execute opens Discord URL', () => {
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
communityCommand.execute?.()
discordCommand.execute?.()
expect(openSpy).toHaveBeenCalledWith(
'https://discord.gg/5AEfbxcd9k',
@@ -185,46 +185,46 @@ describe('communityCommand', () => {
openSpy.mockRestore()
})
it('search returns community result', async () => {
const results = await communityCommand.search('', 'en')
it('search returns Discord result', async () => {
const results = await discordCommand.search('', 'en')
expect(results).toHaveLength(1)
expect(results[0]).toMatchObject({
id: 'community',
id: 'discord',
type: 'command',
data: { command: 'navigation.community' },
data: { command: 'navigation.discord' },
})
})
it('search uses fallback description when i18n returns empty', async () => {
mockT.mockImplementation((key: string) => (key.includes('communityDesc') ? '' : key))
mockT.mockImplementation((key: string) => (key.includes('discordDesc') ? '' : key))
const results = await communityCommand.search('', 'en')
const results = await discordCommand.search('', 'en')
expect(results[0]!.description).toBe('Open Discord community')
mockT.mockImplementation((key: string) => key)
})
it('registers navigation.community command', () => {
communityCommand.register?.({} as Record<string, never>)
expect(registerCommands).toHaveBeenCalledWith({ 'navigation.community': expect.any(Function) })
it('registers navigation.discord command', () => {
discordCommand.register?.({} as Record<string, never>)
expect(registerCommands).toHaveBeenCalledWith({ 'navigation.discord': expect.any(Function) })
})
it('registered handler opens URL from args', async () => {
communityCommand.register?.({} as Record<string, never>)
discordCommand.register?.({} as Record<string, never>)
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
const handlers = vi.mocked(registerCommands).mock.calls[0]![0]
await handlers['navigation.community']!({ url: 'https://custom-url.com' })
await handlers['navigation.discord']!({ url: 'https://custom-url.com' })
expect(openSpy).toHaveBeenCalledWith('https://custom-url.com', '_blank', 'noopener,noreferrer')
openSpy.mockRestore()
})
it('registered handler falls back to default URL when no args', async () => {
communityCommand.register?.({} as Record<string, never>)
discordCommand.register?.({} as Record<string, never>)
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
const handlers = vi.mocked(registerCommands).mock.calls[0]![0]
await handlers['navigation.community']!()
await handlers['navigation.discord']!()
expect(openSpy).toHaveBeenCalledWith(
'https://discord.gg/5AEfbxcd9k',
@@ -234,83 +234,61 @@ describe('communityCommand', () => {
openSpy.mockRestore()
})
it('unregisters navigation.community command', () => {
communityCommand.unregister?.()
expect(unregisterCommands).toHaveBeenCalledWith(['navigation.community'])
it('unregisters navigation.discord command', () => {
discordCommand.unregister?.()
expect(unregisterCommands).toHaveBeenCalledWith(['navigation.discord'])
})
})
describe('forumCommand', () => {
describe('modelsCommand', () => {
let originalHref: string
beforeEach(() => {
vi.clearAllMocks()
originalHref = window.location.href
})
it('has correct metadata', () => {
expect(forumCommand.name).toBe('forum')
expect(forumCommand.mode).toBe('direct')
expect(forumCommand.execute).toBeDefined()
afterEach(() => {
Object.defineProperty(window, 'location', { value: { href: originalHref }, writable: true })
})
it('execute opens forum URL', () => {
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
forumCommand.execute?.()
expect(openSpy).toHaveBeenCalledWith('https://forum.dify.ai', '_blank', 'noopener,noreferrer')
openSpy.mockRestore()
it('has direct command metadata', () => {
expect(modelsCommand.name).toBe('models')
expect(modelsCommand.mode).toBe('direct')
expect(modelsCommand.execute).toBeDefined()
})
it('search returns forum result', async () => {
const results = await forumCommand.search('', 'en')
it('navigates to model provider with the system model dialog URL state', () => {
Object.defineProperty(window, 'location', { value: { href: '' }, writable: true })
modelsCommand.execute?.()
expect(window.location.href).toBe(SYSTEM_MODELS_PATH)
})
it('search returns the system models result', async () => {
const results = await modelsCommand.search('', 'en')
expect(results).toHaveLength(1)
expect(results[0]).toMatchObject({
id: 'forum',
id: 'models',
type: 'command',
data: { command: 'navigation.forum' },
data: { command: 'navigation.models' },
})
})
it('search uses fallback description when i18n returns empty', async () => {
mockT.mockImplementation((key: string) => (key.includes('feedbackDesc') ? '' : key))
const results = await forumCommand.search('', 'en')
expect(results[0]!.description).toBe('Open community feedback discussions')
mockT.mockImplementation((key: string) => key)
})
it('registers navigation.forum command', () => {
forumCommand.register?.({} as Record<string, never>)
expect(registerCommands).toHaveBeenCalledWith({ 'navigation.forum': expect.any(Function) })
})
it('registered handler opens URL from args', async () => {
forumCommand.register?.({} as Record<string, never>)
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
it('registers a navigation command that opens the system model route', async () => {
Object.defineProperty(window, 'location', { value: { href: '' }, writable: true })
modelsCommand.register?.({} as Record<string, never>)
const handlers = vi.mocked(registerCommands).mock.calls[0]![0]
await handlers['navigation.forum']!({ url: 'https://custom-forum.com' })
expect(openSpy).toHaveBeenCalledWith(
'https://custom-forum.com',
'_blank',
'noopener,noreferrer',
)
openSpy.mockRestore()
await handlers['navigation.models']!()
expect(window.location.href).toBe(SYSTEM_MODELS_PATH)
})
it('registered handler falls back to default URL when no args', async () => {
forumCommand.register?.({} as Record<string, never>)
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
const handlers = vi.mocked(registerCommands).mock.calls[0]![0]
await handlers['navigation.forum']!()
expect(openSpy).toHaveBeenCalledWith('https://forum.dify.ai', '_blank', 'noopener,noreferrer')
openSpy.mockRestore()
})
it('unregisters navigation.forum command', () => {
forumCommand.unregister?.()
expect(unregisterCommands).toHaveBeenCalledWith(['navigation.forum'])
it('unregisters navigation.models command', () => {
modelsCommand.unregister?.()
expect(unregisterCommands).toHaveBeenCalledWith(['navigation.models'])
})
})
@@ -24,18 +24,42 @@ describe('goCommand', () => {
describe('search', () => {
it('returns all navigation items when query is empty', async () => {
goCommand.register?.({ agentsAvailable: true, skillsAvailable: true })
const results = await goCommand.search('', 'en')
expect(results.map((r) => r.id)).toEqual([
'go-apps',
'go-datasets',
'go-agents',
'go-skills',
'go-plugins',
'go-tools',
'go-explore',
'go-home',
'go-account',
])
})
it('hides agent and skill destinations when their navigation is unavailable', async () => {
goCommand.register?.({ agentsAvailable: false, skillsAvailable: false })
const results = await goCommand.search('', 'en')
expect(results.map((result) => result.id)).not.toEqual(
expect.arrayContaining(['go-agents', 'go-skills']),
)
})
it('routes Home to the consolidated home page', async () => {
const results = await goCommand.search('home', 'en')
expect(results[0]).toMatchObject({
id: 'go-home',
title: 'Home',
description: '/',
data: { command: 'navigation.go', args: { path: '/' } },
})
})
it('filters by id match', async () => {
const results = await goCommand.search('plugins', 'en')
@@ -71,7 +95,7 @@ describe('goCommand', () => {
describe('register / unregister', () => {
it('registers navigation.go command', () => {
goCommand.register?.({} as Record<string, never>)
goCommand.register?.({ agentsAvailable: false, skillsAvailable: true })
expect(registerCommands).toHaveBeenCalledWith({ 'navigation.go': expect.any(Function) })
})
@@ -84,7 +108,7 @@ describe('goCommand', () => {
it('registered handler navigates to the provided path', async () => {
Object.defineProperty(window, 'location', { value: { href: '' }, writable: true })
goCommand.register?.({} as Record<string, never>)
goCommand.register?.({ agentsAvailable: false, skillsAvailable: true })
const handlers = vi.mocked(registerCommands).mock.calls[0]![0]
await handlers['navigation.go']!({ path: '/datasets' })
@@ -94,7 +118,7 @@ describe('goCommand', () => {
it('registered handler does nothing when path is missing', async () => {
Object.defineProperty(window, 'location', { value: { href: '/current' }, writable: true })
goCommand.register?.({} as Record<string, never>)
goCommand.register?.({ agentsAvailable: false, skillsAvailable: true })
const handlers = vi.mocked(registerCommands).mock.calls[0]![0]
await handlers['navigation.go']!()
@@ -46,6 +46,12 @@ vi.mock('next-themes', () => ({
vi.mock('@/i18n-config', () => ({
setLocaleOnClient: mockSetLocale,
}))
vi.mock('@/features/agent-v2/feature-flag', () => ({
isAgentV2Enabled: () => true,
}))
vi.mock('@/features/agent-v2/permissions', () => ({
useCanManageAgents: () => true,
}))
vi.mock('../command-bus', () => ({
executeCommand: (...args: unknown[]) => mockExecuteCommand(...args),
@@ -120,9 +126,9 @@ describe('SlashCommandProvider', () => {
expect(mockRegister.mock.calls.map((call) => call[0].name)).toEqual([
'theme',
'language',
'forum',
'docs',
'community',
'discord',
'models',
'account',
'go',
])
@@ -132,6 +138,10 @@ describe('SlashCommandProvider', () => {
expect(mockRegister).toHaveBeenCalledWith(expect.objectContaining({ name: 'language' }), {
setLocale: mockSetLocale,
})
expect(mockRegister).toHaveBeenCalledWith(expect.objectContaining({ name: 'go' }), {
agentsAvailable: true,
skillsAvailable: true,
})
unmount()
@@ -140,9 +150,9 @@ describe('SlashCommandProvider', () => {
expect(mockUnregister.mock.calls.map((call) => call[0])).toEqual([
'theme',
'language',
'forum',
'docs',
'community',
'discord',
'models',
'account',
'go',
'create',
@@ -158,9 +168,9 @@ describe('SlashCommandProvider', () => {
expect(mockRegister.mock.calls.map((call) => call[0].name)).toEqual([
'theme',
'language',
'forum',
'docs',
'community',
'discord',
'models',
'account',
'go',
'create',
@@ -172,9 +182,9 @@ describe('SlashCommandProvider', () => {
expect(mockUnregister.mock.calls.map((call) => call[0])).toEqual([
'theme',
'language',
'forum',
'docs',
'community',
'discord',
'models',
'account',
'go',
'create',
@@ -1,54 +0,0 @@
import type { SlashCommandHandler } from './types'
import { getI18n } from 'react-i18next'
import { registerCommands, unregisterCommands } from './command-bus'
// Community command dependency types
type CommunityDeps = Record<string, never>
/**
* Community command - Opens Discord community
*/
export const communityCommand: SlashCommandHandler<CommunityDeps> = {
name: 'community',
description: 'Open community Discord',
mode: 'direct',
// Direct execution function
execute: () => {
const url = 'https://discord.gg/5AEfbxcd9k'
window.open(url, '_blank', 'noopener,noreferrer')
},
search(args: string, locale: string = 'en') {
const i18n = getI18n()
return [
{
id: 'community',
title: i18n.t(($) => $['userProfile.community'], { ns: 'common', lng: locale }),
description:
i18n.t(($) => $['gotoAnything.actions.communityDesc'], { ns: 'app', lng: locale }) ||
'Open Discord community',
type: 'command' as const,
icon: (
<div className="flex h-6 w-6 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-components-panel-bg">
<span aria-hidden className="i-ri-discord-line size-4 text-text-tertiary" />
</div>
),
data: { command: 'navigation.community', args: { url: 'https://discord.gg/5AEfbxcd9k' } },
},
]
},
register(_deps: CommunityDeps) {
registerCommands({
'navigation.community': async (args) => {
const url = args?.url || 'https://discord.gg/5AEfbxcd9k'
window.open(url, '_blank', 'noopener,noreferrer')
},
})
},
unregister() {
unregisterCommands(['navigation.community'])
},
}
@@ -0,0 +1,49 @@
import type { SlashCommandHandler } from './types'
import { getI18n } from 'react-i18next'
import { registerCommands, unregisterCommands } from './command-bus'
type DiscordDeps = Record<string, never>
const DISCORD_URL = 'https://discord.gg/5AEfbxcd9k'
const openDiscord = (url = DISCORD_URL) => {
window.open(url, '_blank', 'noopener,noreferrer')
}
export const discordCommand: SlashCommandHandler<DiscordDeps> = {
name: 'discord',
description: 'Open Discord community',
mode: 'direct',
execute: () => openDiscord(),
search(_args: string, locale: string = 'en') {
const i18n = getI18n()
return [
{
id: 'discord',
title: 'Discord',
description:
i18n.t(($) => $['gotoAnything.actions.discordDesc'], { ns: 'app', lng: locale }) ||
'Open Discord community',
type: 'command' as const,
icon: (
<div className="flex h-6 w-6 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-components-panel-bg">
<span aria-hidden className="i-ri-discord-line size-4 text-text-tertiary" />
</div>
),
data: { command: 'navigation.discord', args: { url: DISCORD_URL } },
},
]
},
register(_deps: DiscordDeps) {
registerCommands({
'navigation.discord': async (args) => openDiscord(args?.url),
})
},
unregister() {
unregisterCommands(['navigation.discord'])
},
}
@@ -1,54 +0,0 @@
import type { SlashCommandHandler } from './types'
import { getI18n } from 'react-i18next'
import { registerCommands, unregisterCommands } from './command-bus'
// Forum command dependency types
type ForumDeps = Record<string, never>
/**
* Forum command - Opens Dify community forum
*/
export const forumCommand: SlashCommandHandler<ForumDeps> = {
name: 'forum',
description: 'Open Dify community forum',
mode: 'direct',
// Direct execution function
execute: () => {
const url = 'https://forum.dify.ai'
window.open(url, '_blank', 'noopener,noreferrer')
},
search(args: string, locale: string = 'en') {
const i18n = getI18n()
return [
{
id: 'forum',
title: i18n.t(($) => $['userProfile.forum'], { ns: 'common', lng: locale }),
description:
i18n.t(($) => $['gotoAnything.actions.feedbackDesc'], { ns: 'app', lng: locale }) ||
'Open community feedback discussions',
type: 'command' as const,
icon: (
<div className="flex h-6 w-6 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-components-panel-bg">
<span aria-hidden className="i-ri-feedback-line size-4 text-text-tertiary" />
</div>
),
data: { command: 'navigation.forum', args: { url: 'https://forum.dify.ai' } },
},
]
},
register(_deps: ForumDeps) {
registerCommands({
'navigation.forum': async (args) => {
const url = args?.url || 'https://forum.dify.ai'
window.open(url, '_blank', 'noopener,noreferrer')
},
})
},
unregister() {
unregisterCommands(['navigation.forum'])
},
}
@@ -4,16 +4,40 @@ import { registerCommands, unregisterCommands } from './command-bus'
const NAV_ITEMS = [
{ id: 'apps', label: 'Apps', path: '/apps', iconClassName: 'i-ri-apps-2-line' },
{ id: 'datasets', label: 'Knowledge', path: '/datasets', iconClassName: 'i-ri-book-open-line' },
{
id: 'agents',
label: 'Agents',
path: '/agents',
iconClassName: 'i-custom-vender-main-nav-roster',
availability: 'agents',
},
{
id: 'skills',
label: 'Skills',
path: '/skills',
iconClassName: 'i-custom-vender-main-nav-skill',
availability: 'skills',
},
{ id: 'plugins', label: 'Plugins', path: '/plugins', iconClassName: 'i-ri-plug-line' },
{ id: 'tools', label: 'Tools', path: '/tools', iconClassName: 'i-ri-tools-line' },
{ id: 'explore', label: 'Explore', path: '/explore', iconClassName: 'i-ri-compass-line' },
{ id: 'home', label: 'Home', path: '/', iconClassName: 'i-ri-compass-line' },
{ id: 'account', label: 'Account', path: '/account', iconClassName: 'i-ri-user-line' },
]
] as const
type GoDeps = {
agentsAvailable: boolean
skillsAvailable: boolean
}
let availability: GoDeps = {
agentsAvailable: false,
skillsAvailable: true,
}
/**
* Go command - Navigate to a top-level section of the app
*/
export const goCommand: SlashCommandHandler = {
export const goCommand: SlashCommandHandler<GoDeps> = {
name: 'go',
aliases: ['navigate', 'nav'],
description: 'Navigate to a section',
@@ -21,9 +45,14 @@ export const goCommand: SlashCommandHandler = {
search(args: string, _locale: string = 'en') {
const query = args.trim().toLowerCase()
const items = NAV_ITEMS.filter(
(item) => !query || item.id.includes(query) || item.label.toLowerCase().includes(query),
)
const items = NAV_ITEMS.filter((item) => {
if ('availability' in item) {
if (item.availability === 'agents' && !availability.agentsAvailable) return false
if (item.availability === 'skills' && !availability.skillsAvailable) return false
}
return !query || item.id.includes(query) || item.label.toLowerCase().includes(query)
})
return items.map((item) => ({
id: `go-${item.id}`,
title: item.label,
@@ -38,7 +67,8 @@ export const goCommand: SlashCommandHandler = {
}))
},
register() {
register(deps: GoDeps) {
availability = deps
registerCommands({
'navigation.go': async (args) => {
if (args?.path) window.location.href = args.path
@@ -47,6 +77,10 @@ export const goCommand: SlashCommandHandler = {
},
unregister() {
availability = {
agentsAvailable: false,
skillsAvailable: true,
}
unregisterCommands(['navigation.go'])
},
}
@@ -0,0 +1,53 @@
import type { SlashCommandHandler } from './types'
import { getI18n } from 'react-i18next'
import { registerCommands, unregisterCommands } from './command-bus'
type ModelsDeps = Record<string, never>
export const SYSTEM_MODELS_PATH = '/integrations/model-provider?dialog=system-models'
const openSystemModels = () => {
window.location.href = SYSTEM_MODELS_PATH
}
export const modelsCommand: SlashCommandHandler<ModelsDeps> = {
name: 'models',
description: 'Configure default workspace models',
mode: 'direct',
execute: openSystemModels,
search(_args: string, locale: string = 'en') {
const i18n = getI18n()
return [
{
id: 'models',
title: i18n.t(($) => $['modelProvider.systemModelSettings'], {
ns: 'common',
lng: locale,
}),
description: i18n.t(($) => $['modelProvider.systemModelSettingsDesc'], {
ns: 'common',
lng: locale,
}),
type: 'command' as const,
icon: (
<div className="flex h-6 w-6 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-components-panel-bg">
<span aria-hidden className="i-ri-brain-2-line size-4 text-text-tertiary" />
</div>
),
data: { command: 'navigation.models' },
},
]
},
register(_deps: ModelsDeps) {
registerCommands({
'navigation.models': async () => openSystemModels(),
})
},
unregister() {
unregisterCommands(['navigation.models'])
},
}
@@ -1,24 +1,30 @@
'use client'
import { useAtomValue } from 'jotai'
import { useTheme } from 'next-themes'
import { useEffect } from 'react'
import { ENABLE_FEATURE_PREVIEW } from '@/config'
import { useDocLink } from '@/context/i18n'
import { isCurrentWorkspaceDatasetOperatorAtom } from '@/context/workspace-state'
import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag'
import { useCanManageAgents } from '@/features/agent-v2/permissions'
import { setLocaleOnClient } from '@/i18n-config'
import { accountCommand } from './account'
import { communityCommand } from './community'
import { createCommand } from './create'
import { discordCommand } from './discord'
import { docsCommand } from './docs'
import { forumCommand } from './forum'
import { goCommand } from './go'
import { languageCommand } from './language'
import { modelsCommand } from './models'
import { refineCommand } from './refine'
import { slashCommandRegistry } from './registry'
import { themeCommand } from './theme'
type SlashCommandDeps = {
agentsAvailable: boolean
getDocsHomeUrl: () => string
setTheme: (theme: string) => void
setLocale: typeof setLocaleOnClient
skillsAvailable: boolean
}
const registerSlashCommands = (deps: SlashCommandDeps) => {
@@ -26,11 +32,14 @@ const registerSlashCommands = (deps: SlashCommandDeps) => {
slashCommandRegistry.register(languageCommand, {
setLocale: deps.setLocale as (locale: string) => Promise<void>,
})
slashCommandRegistry.register(forumCommand, {})
slashCommandRegistry.register(docsCommand, { getDocsHomeUrl: deps.getDocsHomeUrl })
slashCommandRegistry.register(communityCommand, {})
slashCommandRegistry.register(discordCommand, {})
slashCommandRegistry.register(modelsCommand, {})
slashCommandRegistry.register(accountCommand, {})
slashCommandRegistry.register(goCommand, {})
slashCommandRegistry.register(goCommand, {
agentsAvailable: deps.agentsAvailable,
skillsAvailable: deps.skillsAvailable,
})
if (ENABLE_FEATURE_PREVIEW) {
slashCommandRegistry.register(createCommand, {})
slashCommandRegistry.register(refineCommand, {})
@@ -40,9 +49,9 @@ const registerSlashCommands = (deps: SlashCommandDeps) => {
const unregisterSlashCommands = () => {
slashCommandRegistry.unregister('theme')
slashCommandRegistry.unregister('language')
slashCommandRegistry.unregister('forum')
slashCommandRegistry.unregister('docs')
slashCommandRegistry.unregister('community')
slashCommandRegistry.unregister('discord')
slashCommandRegistry.unregister('models')
slashCommandRegistry.unregister('account')
slashCommandRegistry.unregister('go')
slashCommandRegistry.unregister('create')
@@ -52,14 +61,20 @@ const unregisterSlashCommands = () => {
export const SlashCommandProvider = () => {
const theme = useTheme()
const getDocsHomeUrl = useDocLink()
const canManageAgents = useCanManageAgents()
const isCurrentWorkspaceDatasetOperator = useAtomValue(isCurrentWorkspaceDatasetOperatorAtom)
const agentsAvailable = isAgentV2Enabled() && canManageAgents
const skillsAvailable = !isCurrentWorkspaceDatasetOperator
useEffect(() => {
registerSlashCommands({
agentsAvailable,
getDocsHomeUrl,
setTheme: theme.setTheme,
setLocale: setLocaleOnClient,
skillsAvailable,
})
return () => unregisterSlashCommands()
}, [getDocsHomeUrl, theme.setTheme])
}, [agentsAvailable, getDocsHomeUrl, skillsAvailable, theme.setTheme])
return null
}
@@ -16,7 +16,7 @@ export type SlashCommandHandler<TDeps = unknown> = {
/**
* Command mode:
* - 'direct': Execute immediately when selected (e.g., /docs, /community)
* - 'direct': Execute immediately when selected (e.g., /docs, /discord)
* - 'submenu': Show submenu options (e.g., /theme, /language)
*/
mode?: 'direct' | 'submenu'
@@ -1,10 +1,12 @@
import type { ActionItem } from './types'
import { agentAction } from './agent'
import { appAction } from './app'
import { slashCommandRegistry } from './commands/registry'
import { slashAction } from './commands/slash'
import { knowledgeAction } from './knowledge'
import { pluginAction } from './plugin'
import { ragPipelineNodesAction } from './rag-pipeline-nodes'
import { skillAction } from './skill'
import { workflowNodesAction } from './workflow-nodes'
const defaultActions = {
@@ -14,10 +16,30 @@ const defaultActions = {
plugin: pluginAction,
} satisfies Record<string, ActionItem>
export function createActions(isWorkflowPage: boolean, isRagPipelinePage: boolean) {
if (isRagPipelinePage) return { ...defaultActions, node: ragPipelineNodesAction }
if (isWorkflowPage) return { ...defaultActions, node: workflowNodesAction }
return defaultActions
type ActionAvailability = {
agents: boolean
skills: boolean
}
const defaultAvailability: ActionAvailability = {
agents: false,
skills: true,
}
export function createActions(
isWorkflowPage: boolean,
isRagPipelinePage: boolean,
availability: ActionAvailability = defaultAvailability,
) {
const availableActions = {
...defaultActions,
...(availability.skills ? { skill: skillAction } : {}),
...(availability.agents ? { agent: agentAction } : {}),
}
if (isRagPipelinePage) return { ...availableActions, node: ragPipelineNodesAction }
if (isWorkflowPage) return { ...availableActions, node: workflowNodesAction }
return availableActions
}
export function getActionSearchTerm(query: string, action: ActionItem) {
@@ -1,27 +0,0 @@
const RECENT_ITEMS_KEY = 'goto-anything:recent'
const MAX_RECENT_ITEMS = 8
export function getRecentItems() {
try {
const stored = localStorage.getItem(RECENT_ITEMS_KEY)
if (!stored) return []
return JSON.parse(stored) as Array<{
id: string
title: string
description?: string
path: string
originalType: 'app' | 'knowledge'
}>
} catch {
return []
}
}
export function addRecentItem(item: ReturnType<typeof getRecentItems>[number]): void {
try {
const recent = getRecentItems()
const filtered = recent.filter((r) => r.id !== item.id)
const updated = [item, ...filtered].slice(0, MAX_RECENT_ITEMS)
localStorage.setItem(RECENT_ITEMS_KEY, JSON.stringify(updated))
} catch {}
}
@@ -0,0 +1,46 @@
import type { SkillResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import type { ActionItem, SkillSearchResult } from './types'
import { getI18n } from 'react-i18next'
import { consoleQuery } from '@/service/client'
function getSkillResults(skills: SkillResponse[]): SkillSearchResult[] {
return skills.map((skill) => ({
id: skill.id,
title: skill.display_name || skill.name,
description: skill.description || undefined,
type: 'skill' as const,
path: `/skills/${skill.id}`,
icon: (
<div className="flex size-9 shrink-0 items-center justify-center rounded-[10px] border-[0.5px] border-divider-regular bg-background-default">
<span aria-hidden className="i-custom-vender-main-nav-skill size-4.5 text-text-secondary" />
</div>
),
data: skill,
}))
}
export const skillAction: ActionItem = {
key: '@skill',
shortcut: '@skill',
get title() {
return getI18n().t(($) => $['skillManagement.title'], { ns: 'skill' })
},
get description() {
return getI18n().t(($) => $['skillManagement.searchLabel'], { ns: 'skill' })
},
source: 'remote',
}
export function skillSearchQueryOptions(searchTerm: string) {
return consoleQuery.workspaces.current.skills.get.queryOptions({
input: {
query: {
page: 1,
limit: 10,
keyword: searchTerm,
},
},
retry: false,
select: (response) => getSkillResults(response.data ?? []),
})
}
@@ -1,11 +1,20 @@
import type { AgentAppPartial } from '@dify/contracts/api/console/agent/types.gen'
import type { AppPartial } from '@dify/contracts/api/console/apps/types.gen'
import type { DatasetListItemResponse } from '@dify/contracts/api/console/datasets/types.gen'
import type { SkillResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import type { ReactNode } from 'react'
import type { TypeWithI18N } from '../../base/form/types'
import type { Plugin } from '../../plugins/types'
import type { CommonNodeType } from '../../workflow/types'
type SearchResultType = 'app' | 'knowledge' | 'plugin' | 'workflow-node' | 'command' | 'recent'
type SearchResultType =
| 'app'
| 'knowledge'
| 'plugin'
| 'skill'
| 'agent'
| 'workflow-node'
| 'command'
type BaseSearchResult<T> = {
id: string
@@ -29,6 +38,14 @@ export type KnowledgeSearchResult = {
type: 'knowledge'
} & BaseSearchResult<DatasetListItemResponse>
export type SkillSearchResult = {
type: 'skill'
} & BaseSearchResult<SkillResponse>
export type AgentSearchResult = {
type: 'agent'
} & BaseSearchResult<AgentAppPartial>
type WorkflowNodeSearchResult = {
type: 'workflow-node'
metadata?: {
@@ -41,21 +58,17 @@ export type CommandSearchResult = {
type: 'command'
} & BaseSearchResult<{ command: string; args?: Record<string, unknown> }>
export type RecentSearchResult = {
type: 'recent'
originalType: 'app' | 'knowledge'
} & BaseSearchResult<{ path: string }>
export type SearchResult =
| AppSearchResult
| PluginSearchResult
| KnowledgeSearchResult
| SkillSearchResult
| AgentSearchResult
| WorkflowNodeSearchResult
| CommandSearchResult
| RecentSearchResult
type ActionItemBase = {
key: '@app' | '@knowledge' | '@plugin' | '@node' | '/'
key: '@app' | '@knowledge' | '@plugin' | '@skill' | '@agents' | '@node' | '/'
shortcut: string
title: string | TypeWithI18N
description: string
+118 -87
View File
@@ -2,7 +2,7 @@
import type { AutocompleteChangeEventDetails } from '@langgenius/dify-ui/autocomplete'
import type { Plugin } from '../plugins/types'
import type { ActionItem, RecentSearchResult, SearchResult } from './actions/types'
import type { ActionItem, SearchResult } from './actions/types'
import {
Autocomplete,
AutocompleteCollection,
@@ -33,21 +33,27 @@ import {
import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys'
import { useQuery } from '@tanstack/react-query'
import { useDebounce } from 'ahooks'
import { useAtomValue } from 'jotai'
import { useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { MAIN_NAV_ROUTES } from '@/app/components/main-nav/routes'
import { selectWorkflowNode } from '@/app/components/workflow/utils/node-navigation'
import { useGetLanguage } from '@/context/i18n'
import { isCurrentWorkspaceDatasetOperatorAtom } from '@/context/workspace-state'
import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag'
import { useCanManageAgents } from '@/features/agent-v2/permissions'
import { usePathname, useRouter } from '@/next/navigation'
import { PluginInstallPermissionProvider } from '../plugins/install-plugin/components/plugin-install-permission-provider'
import useWorkspacePluginInstallPermission from '../plugins/install-plugin/hooks/use-workspace-plugin-install-permission'
import InstallFromMarketplace from '../plugins/install-plugin/install-from-marketplace'
import { createActions, getActionSearchTerm, matchAction } from './actions'
import { agentSearchQueryOptions } from './actions/agent'
import { appSearchQueryOptions } from './actions/app'
import { slashCommandRegistry } from './actions/commands/registry'
import { SlashCommandProvider } from './actions/commands/slash-provider'
import { knowledgeSearchQueryOptions } from './actions/knowledge'
import { pluginSearchQueryOptions } from './actions/plugin'
import { addRecentItem, getRecentItems } from './actions/recent-store'
import { skillSearchQueryOptions } from './actions/skill'
import { EmptyState } from './components/empty-state'
import { Footer } from './components/footer'
import { gotoAnythingDialogHandle } from './dialog-handle'
@@ -61,6 +67,7 @@ type CommandOption = {
kind: 'command-option'
shortcut: string
description: string
icon: string
}
type GotoAnythingOption = CommandOption | SearchResult
@@ -71,9 +78,8 @@ const slashCommandDescriptionKeys = {
'/theme': 'gotoAnything.actions.themeCategoryDesc',
'/language': 'gotoAnything.actions.languageChangeDesc',
'/account': 'gotoAnything.actions.accountDesc',
'/feedback': 'gotoAnything.actions.feedbackDesc',
'/docs': 'gotoAnything.actions.docDesc',
'/community': 'gotoAnything.actions.communityDesc',
'/discord': 'gotoAnything.actions.discordDesc',
} as const
const actionDescriptionKeys = {
@@ -89,9 +95,25 @@ const groupLabelKeys = {
knowledge: 'gotoAnything.groups.knowledgeBases',
'workflow-node': 'gotoAnything.groups.workflowNodes',
command: 'gotoAnything.groups.commands',
recent: 'gotoAnything.groups.recent',
} as const
type MainNavRouteKey = (typeof MAIN_NAV_ROUTES)[number]['key']
const scopeMainNavRouteKeys = {
'@app': 'apps',
'@knowledge': 'datasets',
'@plugin': 'marketplace',
'@skill': 'skills',
'@agents': 'roster',
} as const satisfies Partial<Record<ActionItem['key'], MainNavRouteKey>>
function getScopeCardIcon(action: ActionItem) {
const routeKey = scopeMainNavRouteKeys[action.key as keyof typeof scopeMainNavRouteKeys]
if (!routeKey) return 'i-ri-node-tree'
return MAIN_NAV_ROUTES.find((route) => route.key === routeKey)?.icon ?? 'i-ri-node-tree'
}
function getCommandOptions(actions: Record<string, ActionItem>, query: string): CommandOption[] {
const trimmedQuery = query.trim()
const filter = trimmedQuery.slice(1).toLowerCase()
@@ -104,6 +126,7 @@ function getCommandOptions(actions: Record<string, ActionItem>, query: string):
kind: 'command-option',
shortcut: `/${command.name}`,
description: command.description,
icon: 'i-ri-terminal-box-line',
}))
}
@@ -114,6 +137,7 @@ function getCommandOptions(actions: Record<string, ActionItem>, query: string):
kind: 'command-option',
shortcut: action.shortcut,
description: action.description,
icon: getScopeCardIcon(action),
}))
}
@@ -142,7 +166,7 @@ function getSearchMode(
isCommandsMode: boolean,
actions: Record<string, ActionItem>,
) {
if (isCommandsMode) return searchQuery.trim().startsWith('@') ? 'scopes' : 'commands'
if (isCommandsMode) return searchQuery.trim().startsWith('/') ? 'commands' : 'scopes'
const action = matchAction(searchQuery.trim().toLowerCase(), actions)
if (!action) return 'general'
@@ -152,7 +176,7 @@ function getSearchMode(
function isCommandSelectionQuery(query: string, actions: Record<string, ActionItem>) {
const trimmedQuery = query.trim()
if (trimmedQuery === '@' || trimmedQuery === '/') return true
if (!trimmedQuery || trimmedQuery === '@' || trimmedQuery === '/') return true
return (
(trimmedQuery.startsWith('@') || trimmedQuery.startsWith('/')) &&
@@ -160,23 +184,6 @@ function isCommandSelectionQuery(query: string, actions: Record<string, ActionIt
)
}
function getRecentSearchResults(): RecentSearchResult[] {
return getRecentItems().map((item) => ({
id: `recent-${item.id}`,
title: item.title,
description: item.description,
type: 'recent',
originalType: item.originalType,
path: item.path,
icon: (
<div className="flex h-6 w-6 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-components-panel-bg">
<span aria-hidden className="i-ri-time-line size-4 text-text-tertiary" />
</div>
),
data: { path: item.path },
}))
}
function dedupeSearchResults(results: SearchResult[]) {
const seen = new Set<string>()
return results.filter((result) => {
@@ -202,6 +209,10 @@ function GotoAnythingDialog() {
const pathname = usePathname()
const router = useRouter()
const defaultLocale = useGetLanguage()
const canManageAgents = useCanManageAgents()
const isCurrentWorkspaceDatasetOperator = useAtomValue(isCurrentWorkspaceDatasetOperatorAtom)
const agentsAvailable = isAgentV2Enabled() && canManageAgents
const skillsAvailable = !isCurrentWorkspaceDatasetOperator
const isWorkflowPage =
appWorkflowPathPattern.test(pathname) || sharedWorkflowPathPattern.test(pathname)
const isRagPipelinePage = ragPipelinePathPattern.test(pathname)
@@ -210,8 +221,12 @@ function GotoAnythingDialog() {
const [activePlugin, setActivePlugin] = useState<Plugin>()
const inputRef = useRef<HTMLInputElement>(null)
const actions = useMemo(
() => createActions(isWorkflowPage, isRagPipelinePage),
[isWorkflowPage, isRagPipelinePage],
() =>
createActions(isWorkflowPage, isRagPipelinePage, {
agents: agentsAvailable,
skills: skillsAvailable,
}),
[agentsAvailable, isWorkflowPage, isRagPipelinePage, skillsAvailable],
)
const trimmedSearchQuery = searchQuery.trim()
const isCommandsMode = isCommandSelectionQuery(searchQuery, actions)
@@ -230,6 +245,12 @@ function GotoAnythingDialog() {
remoteSearchEnabled && (!debouncedAction || debouncedAction.key === '@knowledge')
const pluginSearchEnabled =
remoteSearchEnabled && (!debouncedAction || debouncedAction.key === '@plugin')
const skillSearchEnabled =
remoteSearchEnabled && skillsAvailable && (!debouncedAction || debouncedAction.key === '@skill')
const agentSearchEnabled =
remoteSearchEnabled &&
agentsAvailable &&
(!debouncedAction || debouncedAction.key === '@agents')
const appSearchQuery = useQuery({
...appSearchQueryOptions(debouncedSearchTerm, debouncedAction?.key === '@app'),
enabled: appSearchEnabled,
@@ -242,6 +263,14 @@ function GotoAnythingDialog() {
...pluginSearchQueryOptions(debouncedSearchTerm, defaultLocale),
enabled: pluginSearchEnabled,
})
const skillSearchQuery = useQuery({
...skillSearchQueryOptions(debouncedSearchTerm),
enabled: skillSearchEnabled,
})
const agentSearchQuery = useQuery({
...agentSearchQueryOptions(debouncedSearchTerm),
enabled: agentSearchEnabled,
})
const localSearchResults = useMemo(() => {
if (!trimmedSearchQuery || isCommandsMode) return []
@@ -265,6 +294,8 @@ function GotoAnythingDialog() {
appSearchEnabled ? appSearchQuery : undefined,
knowledgeSearchEnabled ? knowledgeSearchQuery : undefined,
pluginSearchEnabled ? pluginSearchQuery : undefined,
skillSearchEnabled ? skillSearchQuery : undefined,
agentSearchEnabled ? agentSearchQuery : undefined,
].filter((query) => query !== undefined)
const isDebouncing = remoteSearchEnabled && searchQuery.trim() !== debouncedSearchQuery.trim()
const isLoading = isDebouncing || activeRemoteQueries.some((query) => query.isLoading)
@@ -278,8 +309,7 @@ function GotoAnythingDialog() {
? []
: activeRemoteQueries.flatMap((query) => query.data ?? [])
const searchResults = [...localSearchResults, ...remoteSearchResults]
const recentResults = trimmedSearchQuery || isCommandsMode ? [] : getRecentSearchResults()
const dedupedResults = dedupeSearchResults(recentResults.length ? recentResults : searchResults)
const dedupedResults = dedupeSearchResults(searchResults)
const groupedResults = groupSearchResults(dedupedResults)
function resetSearch() {
@@ -330,19 +360,7 @@ function GotoAnythingDialog() {
case 'workflow-node':
if (result.metadata?.nodeId) selectWorkflowNode(result.metadata.nodeId, true)
break
case 'recent':
if (result.path) router.push(result.path)
break
default:
if ((result.type === 'app' || result.type === 'knowledge') && result.path) {
addRecentItem({
id: result.id,
title: result.title,
description: result.description,
path: result.path,
originalType: result.type,
})
}
if (result.path) router.push(result.path)
}
}
@@ -366,11 +384,34 @@ function GotoAnythingDialog() {
else handleNavigate(option)
}
const isSlashMode = searchQuery.trim().startsWith('/')
function getCommandOptionDescription(option: CommandOption) {
if (option.shortcut === '/models')
return t(($) => $['modelProvider.systemModelSettingsDesc'], { ns: 'common' })
const descriptionKey = isSlashMode
? slashCommandDescriptionKeys[option.shortcut as keyof typeof slashCommandDescriptionKeys]
: actionDescriptionKeys[option.shortcut as keyof typeof actionDescriptionKeys]
if (!descriptionKey) return option.description
return t(($) => $[descriptionKey], { ns: 'app' })
}
function getGroupLabel(type: string) {
if (type === 'skill') return t(($) => $['skillManagement.title'], { ns: 'skill' })
if (type === 'agent') return t(($) => $['roster.title'], { ns: 'agentV2' })
return t(($) => $[groupLabelKeys[type as keyof typeof groupLabelKeys] || `${type}s`], {
ns: 'app',
})
}
const commandOptions = getCommandOptions(actions, searchQuery)
const autocompleteOptions: GotoAnythingOption[] = isCommandsMode ? commandOptions : dedupedResults
const visibleOptions = isLoading || isError ? [] : autocompleteOptions
const autocompleteResultCount = visibleOptions.length
const isSlashMode = searchQuery.trim().startsWith('/')
let autocompleteStatus: string | null = null
if (isLoading) autocompleteStatus = t(($) => $['gotoAnything.searching'], { ns: 'app' })
@@ -397,8 +438,16 @@ function GotoAnythingDialog() {
<DialogBackdrop />
<DialogPopup
initialFocus={inputRef}
className="fixed top-1/2 left-1/2 max-h-[80dvh] w-120! max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-hidden p-0!"
className="fixed top-1/2 left-1/2 isolate max-h-[80dvh] w-160! max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-hidden p-0!"
>
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 top-0 h-64 bg-[url('/marketplace/hero-gradient-noise.svg')] bg-cover bg-center opacity-18 dark:opacity-28"
/>
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 top-0 h-64 bg-linear-to-b from-components-panel-bg/20 via-components-panel-bg/70 to-components-panel-bg"
/>
<DialogTitle className="sr-only">
{t(($) => $['gotoAnything.searchTitle'], { ns: 'app' })}
</DialogTitle>
@@ -443,7 +492,7 @@ function GotoAnythingDialog() {
<AutocompleteStatus className="sr-only">{autocompleteStatus}</AutocompleteStatus>
<ScrollArea className="relative h-60 min-h-0 overflow-hidden">
<ScrollArea className="relative h-88 min-h-0 overflow-hidden">
<ScrollAreaViewport
aria-busy={isLoading || undefined}
className="scroll-py-1 overscroll-contain"
@@ -472,46 +521,35 @@ function GotoAnythingDialog() {
{!isLoading && !isError && isCommandsMode && autocompleteResultCount > 0 && (
<AutocompleteList className="max-h-none overflow-visible p-0">
<AutocompleteGroup items={commandOptions}>
<AutocompleteGroupLabel className="px-4 pt-3 pb-2 text-left text-sm font-medium text-text-secondary">
<AutocompleteGroupLabel className="px-4 pt-4 pb-2 text-left font-mono text-[11px] font-medium tracking-[0.12em] text-text-tertiary uppercase">
{isSlashMode
? t(($) => $['gotoAnything.groups.commands'], { ns: 'app' })
: t(($) => $['gotoAnything.selectSearchType'], { ns: 'app' })}
</AutocompleteGroupLabel>
<AutocompleteCollection<CommandOption>>
{(option) => (
<AutocompleteItem
key={option.shortcut}
value={option}
className="mx-4 p-2"
onClick={() => selectOption(option)}
>
<span className="min-w-18 text-left font-mono text-xs text-text-tertiary">
{option.shortcut}
</span>
<span className="ml-3 text-sm text-text-secondary">
{isSlashMode
? t(
($) =>
$[
slashCommandDescriptionKeys[
option.shortcut as keyof typeof slashCommandDescriptionKeys
] || option.description
],
{ ns: 'app' },
)
: t(
($) =>
$[
actionDescriptionKeys[
option.shortcut as keyof typeof actionDescriptionKeys
]
],
{ ns: 'app' },
)}
</span>
</AutocompleteItem>
)}
</AutocompleteCollection>
<div className="grid grid-cols-1 gap-2 px-4 pb-4 sm:grid-cols-2">
<AutocompleteCollection<CommandOption>>
{(option) => (
<AutocompleteItem
key={option.shortcut}
value={option}
className="group m-0 min-h-18 items-start gap-3 rounded-xl border-[0.5px] border-components-card-border bg-components-card-bg/90 p-3 shadow-xs shadow-shadow-shadow-3 backdrop-blur-sm hover:border-divider-regular hover:bg-state-base-hover-alt data-highlighted:border-state-accent-solid data-highlighted:bg-state-base-hover"
onClick={() => selectOption(option)}
>
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-background-default text-text-tertiary group-data-highlighted:text-text-accent">
<span aria-hidden className={`${option.icon} size-4`} />
</span>
<span className="min-w-0 flex-1 text-left">
<span className="block truncate font-mono text-xs font-semibold tracking-[-0.01em] text-text-primary">
{option.shortcut}
</span>
<span className="mt-1 line-clamp-2 block text-xs leading-4 text-text-tertiary">
{getCommandOptionDescription(option)}
</span>
</span>
</AutocompleteItem>
)}
</AutocompleteCollection>
</div>
</AutocompleteGroup>
</AutocompleteList>
)}
@@ -533,14 +571,7 @@ function GotoAnythingDialog() {
{Object.entries(groupedResults).map(([type, results]) => (
<AutocompleteGroup key={type} items={results}>
<AutocompleteGroupLabel className="px-4 pt-3 pb-2 text-text-secondary capitalize">
{t(
($) =>
$[
groupLabelKeys[type as keyof typeof groupLabelKeys] ||
`${type}s`
],
{ ns: 'app' },
)}
{getGroupLabel(type)}
</AutocompleteGroupLabel>
<AutocompleteCollection<SearchResult>>
{(result) => (
@@ -2,7 +2,7 @@ import type { DefaultModelResponse } from '../../declarations'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { vi } from 'vite-plus/test'
import { render } from '@/test/console/render'
import { renderWithNuqs as render } from '@/test/nuqs-testing'
import { ModelTypeEnum } from '../../declarations'
import SystemModel from '../index'
@@ -148,6 +148,26 @@ describe('SystemModel', () => {
})
})
it('opens the dialog from URL state', async () => {
render(<SystemModel {...defaultProps} />, { searchParams: '?dialog=system-models' })
expect(await screen.findByRole('button', { name: /save/i })).toBeInTheDocument()
expect(mockUseModelList).toHaveBeenCalledWith(ModelTypeEnum.textEmbedding, { enabled: true })
})
it('clears only the dialog URL state when closed', async () => {
const user = userEvent.setup()
const { onUrlUpdate } = render(<SystemModel {...defaultProps} />, {
searchParams: '?dialog=system-models&source=goto-anything',
})
await user.click(await screen.findByRole('button', { name: /cancel/i }))
expect(onUrlUpdate).toHaveBeenCalledWith(
expect.objectContaining({ queryString: '?source=goto-anything' }),
)
})
it('loads non-text model lists only after the dialog opens', async () => {
const user = userEvent.setup()
render(<SystemModel {...defaultProps} />)
@@ -274,18 +294,21 @@ describe('SystemModel', () => {
expect(mockModelSelectorProps.every((props) => props.showModelMeta === false)).toBe(true)
})
it('should close the dialog from the empty selector configure action', async () => {
it('should close the dialog from every empty selector configure action', async () => {
const user = userEvent.setup()
render(<SystemModel {...defaultProps} />)
fireEvent.click(screen.getByRole('button', { name: /system model settings/i }))
await waitFor(() => {
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument()
})
for (let index = 0; index < 5; index++) {
await user.click(screen.getByRole('button', { name: /system model settings/i }))
const configureActions = await screen.findAllByRole('button', {
name: 'Mock Configure Empty State',
})
fireEvent.click(screen.getAllByRole('button', { name: 'Mock Configure Empty State' })[0]!)
await user.click(configureActions[index]!)
await waitFor(() => {
expect(screen.queryByRole('button', { name: /save/i })).not.toBeInTheDocument()
})
await waitFor(() => {
expect(screen.queryByRole('button', { name: /save/i })).not.toBeInTheDocument()
})
}
})
})
@@ -6,6 +6,7 @@ import { Dialog, DialogClose, DialogContent, DialogTitle } from '@langgenius/dif
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { useAtomValue } from 'jotai'
import { parseAsStringLiteral, useQueryState } from 'nuqs'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
@@ -49,6 +50,8 @@ type SystemModelTipKey =
| 'modelProvider.speechToTextModel.tip'
| 'modelProvider.ttsModel.tip'
const systemModelDialogQueryParser = parseAsStringLiteral(['system-models'] as const)
const SystemModel: FC<SystemModelSelectorProps> = ({
className,
textGenerationDefaultModel,
@@ -67,7 +70,13 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
const canManageSystemDefaultModel = hasPermission(workspacePermissionKeys, 'plugin.model_config')
const updateModelList = useUpdateModelList()
const invalidateDefaultModel = useInvalidateDefaultModel()
const [open, setOpen] = useState(false)
const [activeDialog, setActiveDialog] = useQueryState('dialog', systemModelDialogQueryParser)
const [manuallyOpen, setManuallyOpen] = useState(false)
const open = manuallyOpen || activeDialog === 'system-models'
const handleOpenChange = (nextOpen: boolean) => {
setManuallyOpen(nextOpen)
if (!nextOpen && activeDialog === 'system-models') void setActiveDialog(null)
}
const { data: embeddingModelList, isLoading: isEmbeddingModelListLoading } = useModelList(
ModelTypeEnum.textEmbedding,
{ enabled: open },
@@ -145,7 +154,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
})
if (res.result === 'success') {
toast.success(t(($) => $['actionMsg.modifiedSuccessfully'], { ns: 'common' }))
setOpen(false)
handleOpenChange(false)
const allModelTypes = [
ModelTypeEnum.textGeneration,
@@ -183,7 +192,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
variant={notConfigured ? 'primary' : 'secondary'}
size="small"
disabled={isLoading}
onClick={() => setOpen(true)}
onClick={() => setManuallyOpen(true)}
>
{isLoading ? (
<span className="i-ri-loader-2-line size-3.5 animate-spin" />
@@ -192,7 +201,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
)}
{t(($) => $['modelProvider.systemModelSettings'], { ns: 'common' })}
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent
backdropProps={{ forceRender: true }}
className="flex max-h-[calc(100dvh-2rem)] w-120 max-w-120 flex-col overflow-hidden rounded-2xl p-0"
@@ -241,7 +250,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
models={textGenerationModelList}
hideProviderSettingsFooter={hideProviderSettingsFooter}
onOpenMarketplace={onOpenMarketplace}
onConfigureEmptyState={() => setOpen(false)}
onConfigureEmptyState={() => handleOpenChange(false)}
showModelMeta={false}
onValueChange={(model) =>
handleChangeDefaultModel(ModelTypeEnum.textGeneration, model)
@@ -260,7 +269,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
models={embeddingModelList}
hideProviderSettingsFooter={hideProviderSettingsFooter}
onOpenMarketplace={onOpenMarketplace}
onConfigureEmptyState={() => setOpen(false)}
onConfigureEmptyState={() => handleOpenChange(false)}
showModelMeta={false}
onValueChange={(model) =>
handleChangeDefaultModel(ModelTypeEnum.textEmbedding, model)
@@ -279,7 +288,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
models={rerankModelList}
hideProviderSettingsFooter={hideProviderSettingsFooter}
onOpenMarketplace={onOpenMarketplace}
onConfigureEmptyState={() => setOpen(false)}
onConfigureEmptyState={() => handleOpenChange(false)}
showModelMeta={false}
onValueChange={(model) =>
handleChangeDefaultModel(ModelTypeEnum.rerank, model)
@@ -298,7 +307,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
models={speech2textModelList}
hideProviderSettingsFooter={hideProviderSettingsFooter}
onOpenMarketplace={onOpenMarketplace}
onConfigureEmptyState={() => setOpen(false)}
onConfigureEmptyState={() => handleOpenChange(false)}
showModelMeta={false}
onValueChange={(model) =>
handleChangeDefaultModel(ModelTypeEnum.speech2text, model)
@@ -314,7 +323,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
models={ttsModelList}
hideProviderSettingsFooter={hideProviderSettingsFooter}
onOpenMarketplace={onOpenMarketplace}
onConfigureEmptyState={() => setOpen(false)}
onConfigureEmptyState={() => handleOpenChange(false)}
showModelMeta={false}
onValueChange={(model) => handleChangeDefaultModel(ModelTypeEnum.tts, model)}
/>
@@ -324,7 +333,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
)}
</div>
<div className="flex h-19 shrink-0 items-center justify-end gap-2 px-6 pt-5 pb-6">
<Button className="min-w-18" onClick={() => setOpen(false)}>
<Button className="min-w-18" onClick={() => handleOpenChange(false)}>
{t(($) => $['operation.cancel'], { ns: 'common' })}
</Button>
<Button
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "ابدأ من سير عمل مُعد مسبقًا. مناسب لتعلم Dify والأنماط الشائعة.",
"firstEmpty.title": "لا يوجد تطبيق بعد",
"gotoAnything.actions.accountDesc": "الانتقال إلى صفحة الحساب",
"gotoAnything.actions.communityDesc": "فتح مجتمع Discord",
"gotoAnything.actions.createAuto": "تلقائي",
"gotoAnything.actions.createAutoDesc": "دع الذكاء الاصطناعي يختار Workflow أو Chatflow من وصفك",
"gotoAnything.actions.createCategoryDesc": "قم بإنشاء سير عمل أو سير دردشة تم إنشاؤه بواسطة الذكاء الاصطناعي",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "أنشئ تطبيق تدفق الدردشة (الدردشة المتقدمة) من الوصف",
"gotoAnything.actions.createWorkflow": "سير العمل",
"gotoAnything.actions.createWorkflowDesc": "قم بإنشاء تطبيق سير عمل من الوصف",
"gotoAnything.actions.discordDesc": "فتح مجتمع Discord",
"gotoAnything.actions.docDesc": "فتح وثائق المساعدة",
"gotoAnything.actions.feedbackDesc": "فتح مناقشات ملاحظات المجتمع",
"gotoAnything.actions.languageChangeDesc": "تغيير لغة واجهة المستخدم",
"gotoAnything.actions.refineCategoryDesc": "قم بتحسين سير العمل الحالي أو الرسم البياني لتدفق الدردشة",
"gotoAnything.actions.refineDesc": "قم بوصف التغيير الذي سيتم تطبيقه على المسودة الحالية",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "أوامر",
"gotoAnything.groups.knowledgeBases": "قواعد المعرفة",
"gotoAnything.groups.plugins": "إضافات",
"gotoAnything.groups.recent": "الأخيرة",
"gotoAnything.groups.workflowNodes": "عقد سير العمل",
"gotoAnything.inScope": "في {{scope}}",
"gotoAnything.noMatchingCommands": "لم يتم العثور على أوامر مطابقة",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Starte mit einem vorgefertigten Workflow. Ideal, um Dify und gängige Muster zu lernen.",
"firstEmpty.title": "Noch keine App",
"gotoAnything.actions.accountDesc": "Gehe zur Kontoseite",
"gotoAnything.actions.communityDesc": "Offene Discord-Community",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Lassen Sie die KI anhand Ihrer Beschreibung Workflow oder Chatflow auswählen",
"gotoAnything.actions.createCategoryDesc": "Erstellen Sie einen KI-generierten Workflow oder Chatflow",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Generieren Sie eine Chatflow-App (erweiterter Chat) aus einer Beschreibung",
"gotoAnything.actions.createWorkflow": "Arbeitsablauf",
"gotoAnything.actions.createWorkflowDesc": "Generieren Sie eine Workflow-App aus einer Beschreibung",
"gotoAnything.actions.discordDesc": "Offene Discord-Community",
"gotoAnything.actions.docDesc": "Öffnen Sie die Hilfedokumentation",
"gotoAnything.actions.feedbackDesc": "Offene Diskussionen zum Feedback der Gemeinschaft",
"gotoAnything.actions.languageChangeDesc": "UI-Sprache ändern",
"gotoAnything.actions.refineCategoryDesc": "Verfeinern Sie den aktuellen Workflow oder das Chatflow-Diagramm",
"gotoAnything.actions.refineDesc": "Beschreiben Sie eine Änderung, die auf den aktuellen Entwurf angewendet werden soll",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Befehle",
"gotoAnything.groups.knowledgeBases": "Wissensdatenbanken",
"gotoAnything.groups.plugins": "Integrationen",
"gotoAnything.groups.recent": "Zuletzt",
"gotoAnything.groups.workflowNodes": "Workflow-Knoten",
"gotoAnything.inScope": "in {{scope}}s",
"gotoAnything.noMatchingCommands": "Keine übereinstimmenden Befehle gefunden",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Pick a ready-made app and customize it. The fastest way to see Dify in action.",
"firstEmpty.title": "Build your first App",
"gotoAnything.actions.accountDesc": "Navigate to account page",
"gotoAnything.actions.communityDesc": "Open Discord community",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Create an AI-generated workflow or chatflow",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Generate a chatflow (advanced chat) app from a description",
"gotoAnything.actions.createWorkflow": "Workflow",
"gotoAnything.actions.createWorkflowDesc": "Generate a workflow app from a description",
"gotoAnything.actions.discordDesc": "Open Discord community",
"gotoAnything.actions.docDesc": "Open help documentation",
"gotoAnything.actions.feedbackDesc": "Open community feedback discussions",
"gotoAnything.actions.languageChangeDesc": "Change UI language",
"gotoAnything.actions.refineCategoryDesc": "Refine the current workflow or chatflow graph",
"gotoAnything.actions.refineDesc": "Describe a change to apply to the current draft",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Commands",
"gotoAnything.groups.knowledgeBases": "Knowledge Bases",
"gotoAnything.groups.plugins": "Integrations",
"gotoAnything.groups.recent": "Recent",
"gotoAnything.groups.workflowNodes": "Workflow Nodes",
"gotoAnything.inScope": "in {{scope}}s",
"gotoAnything.noMatchingCommands": "No matching commands found",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Empieza con un flujo de trabajo prediseñado. Ideal para aprender Dify y patrones comunes.",
"firstEmpty.title": "Aún no hay aplicaciones",
"gotoAnything.actions.accountDesc": "Navegar a la página de cuenta",
"gotoAnything.actions.communityDesc": "Abrir comunidad de Discord",
"gotoAnything.actions.createAuto": "Automático",
"gotoAnything.actions.createAutoDesc": "Deja que la IA elija Workflow o Chatflow a partir de tu descripción",
"gotoAnything.actions.createCategoryDesc": "Cree un flujo de trabajo o flujo de chat generado por IA",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Generar una aplicación de chatflow (chat avanzado) a partir de una descripción",
"gotoAnything.actions.createWorkflow": "Flujo de trabajo",
"gotoAnything.actions.createWorkflowDesc": "Generar una aplicación de flujo de trabajo a partir de una descripción",
"gotoAnything.actions.discordDesc": "Abrir comunidad de Discord",
"gotoAnything.actions.docDesc": "Abrir la documentación de ayuda",
"gotoAnything.actions.feedbackDesc": "Discusiones de retroalimentación de la comunidad abierta",
"gotoAnything.actions.languageChangeDesc": "Cambiar el idioma de la interfaz",
"gotoAnything.actions.refineCategoryDesc": "Refinar el flujo de trabajo actual o el gráfico de flujo de chat",
"gotoAnything.actions.refineDesc": "Describir un cambio para aplicar al borrador actual.",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Comandos",
"gotoAnything.groups.knowledgeBases": "Bases de conocimiento",
"gotoAnything.groups.plugins": "Complementos",
"gotoAnything.groups.recent": "Reciente",
"gotoAnything.groups.workflowNodes": "Nodos de flujo de trabajo",
"gotoAnything.inScope": "en {{scope}}s",
"gotoAnything.noMatchingCommands": "No se encontraron comandos coincidentes",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "از یک گردش‌کار از پیش ساخته شروع کنید. برای یادگیری Dify و الگوهای رایج مناسب است.",
"firstEmpty.title": "هنوز برنامه‌ای وجود ندارد",
"gotoAnything.actions.accountDesc": "به صفحه حساب کاربری بروید",
"gotoAnything.actions.communityDesc": "جامعه دیسکورد باز",
"gotoAnything.actions.createAuto": "خودکار",
"gotoAnything.actions.createAutoDesc": "به هوش مصنوعی اجازه دهید بر اساس توضیحات شما Workflow یا Chatflow را انتخاب کند",
"gotoAnything.actions.createCategoryDesc": "یک گردش کار یا جریان گفتگو ایجاد شده توسط هوش مصنوعی ایجاد کنید",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "یک برنامه chatflow (چت پیشرفته) از توضیحات ایجاد کنید",
"gotoAnything.actions.createWorkflow": "گردش کار",
"gotoAnything.actions.createWorkflowDesc": "یک برنامه گردش کار را از توضیحات ایجاد کنید",
"gotoAnything.actions.discordDesc": "جامعه دیسکورد باز",
"gotoAnything.actions.docDesc": "مستندات کمک را باز کنید",
"gotoAnything.actions.feedbackDesc": "بحث‌های باز بازخورد جامعه",
"gotoAnything.actions.languageChangeDesc": "زبان رابط کاربری را تغییر دهید",
"gotoAnything.actions.refineCategoryDesc": "جریان کار یا نمودار جریان گفتگو را اصلاح کنید",
"gotoAnything.actions.refineDesc": "تغییری را برای اعمال در پیش‌نویس فعلی توضیح دهید",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "دستورات",
"gotoAnything.groups.knowledgeBases": "پایگاه های دانش",
"gotoAnything.groups.plugins": "یکپارچه‌سازی",
"gotoAnything.groups.recent": "اخیر",
"gotoAnything.groups.workflowNodes": "گره های گردش کار",
"gotoAnything.inScope": "در {{scope}}s",
"gotoAnything.noMatchingCommands": "هیچ دستوری منطبق یافت نشد",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Commencez avec un workflow prédéfini. Idéal pour apprendre Dify et les modèles courants.",
"firstEmpty.title": "Aucune application pour le moment",
"gotoAnything.actions.accountDesc": "Accédez à la page de compte",
"gotoAnything.actions.communityDesc": "Ouvrir la communauté Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Laissez l'IA choisir Workflow ou Chatflow à partir de votre description",
"gotoAnything.actions.createCategoryDesc": "Créez un flux de travail ou un chatflow généré par l'IA",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Générer une application chatflow (chat avancé) à partir d'une description",
"gotoAnything.actions.createWorkflow": "Flux de travail",
"gotoAnything.actions.createWorkflowDesc": "Générer une application de workflow à partir d'une description",
"gotoAnything.actions.discordDesc": "Ouvrir la communauté Discord",
"gotoAnything.actions.docDesc": "Ouvrir la documentation d'aide",
"gotoAnything.actions.feedbackDesc": "Discussions de rétroaction de la communauté ouverte",
"gotoAnything.actions.languageChangeDesc": "Changer la langue de l'interface",
"gotoAnything.actions.refineCategoryDesc": "Affiner le flux de travail ou le graphique de chatflow actuel",
"gotoAnything.actions.refineDesc": "Décrire un changement à appliquer au brouillon actuel",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Commandes",
"gotoAnything.groups.knowledgeBases": "Bases de connaissances",
"gotoAnything.groups.plugins": "Plug-ins",
"gotoAnything.groups.recent": "Récent",
"gotoAnything.groups.workflowNodes": "Nœuds de flux de travail",
"gotoAnything.inScope": "dans {{scope}}s",
"gotoAnything.noMatchingCommands": "Aucune commande correspondante na été trouvée",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "पहले से बने workflow से शुरू करें। Dify और सामान्य patterns सीखने के लिए बढ़िया।",
"firstEmpty.title": "अभी कोई ऐप नहीं है",
"gotoAnything.actions.accountDesc": "खाता पृष्ठ पर जाएं",
"gotoAnything.actions.communityDesc": "ओपन डिस्कॉर्ड समुदाय",
"gotoAnything.actions.createAuto": "स्वचालित",
"gotoAnything.actions.createAutoDesc": "AI को आपके विवरण से Workflow या Chatflow चुनने दें",
"gotoAnything.actions.createCategoryDesc": "एआई-जनरेटेड वर्कफ़्लो या चैटफ़्लो बनाएं",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "विवरण से एक चैटफ़्लो (उन्नत चैट) ऐप बनाएं",
"gotoAnything.actions.createWorkflow": "कार्यप्रवाह",
"gotoAnything.actions.createWorkflowDesc": "विवरण से वर्कफ़्लो ऐप बनाएं",
"gotoAnything.actions.discordDesc": "ओपन डिस्कॉर्ड समुदाय",
"gotoAnything.actions.docDesc": "सहायता दस्तावेज़ खोलें",
"gotoAnything.actions.feedbackDesc": "खुले समुदाय की फीडबैक चर्चाएँ",
"gotoAnything.actions.languageChangeDesc": "इंटरफेस भाषा बदलें",
"gotoAnything.actions.refineCategoryDesc": "वर्तमान वर्कफ़्लो या चैटफ़्लो ग्राफ़ को परिष्कृत करें",
"gotoAnything.actions.refineDesc": "वर्मन मसौदे पर लागू हो रहा है",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "आदेश",
"gotoAnything.groups.knowledgeBases": "ज्ञान आधार",
"gotoAnything.groups.plugins": "एकीकरण",
"gotoAnything.groups.recent": "हाल ही में",
"gotoAnything.groups.workflowNodes": "कार्यप्रवाह नोड्स",
"gotoAnything.inScope": "{{scope}}s में",
"gotoAnything.noMatchingCommands": "कोई मिलती-जुलती कमांड्स नहीं मिलीं",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Mulai dari workflow siap pakai. Cocok untuk mempelajari Dify dan pola umum.",
"firstEmpty.title": "Belum ada aplikasi",
"gotoAnything.actions.accountDesc": "Arahkan ke halaman akun",
"gotoAnything.actions.communityDesc": "Buka komunitas Discord",
"gotoAnything.actions.createAuto": "Otomatis",
"gotoAnything.actions.createAutoDesc": "Biarkan AI memilih Workflow atau Chatflow dari deskripsi Anda",
"gotoAnything.actions.createCategoryDesc": "Buat alur kerja atau alur obrolan yang dihasilkan AI",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Hasilkan aplikasi chatflow (obrolan lanjutan) dari deskripsi",
"gotoAnything.actions.createWorkflow": "Alur kerja",
"gotoAnything.actions.createWorkflowDesc": "Hasilkan aplikasi alur kerja dari deskripsi",
"gotoAnything.actions.discordDesc": "Buka komunitas Discord",
"gotoAnything.actions.docDesc": "Buka dokumentasi bantuan",
"gotoAnything.actions.feedbackDesc": "Buka diskusi umpan balik komunitas",
"gotoAnything.actions.languageChangeDesc": "Mengubah bahasa UI",
"gotoAnything.actions.refineCategoryDesc": "Sempurnakan alur kerja atau grafik alur obrolan saat ini",
"gotoAnything.actions.refineDesc": "Jelaskan perubahan yang akan diterapkan pada draf saat ini",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Perintah",
"gotoAnything.groups.knowledgeBases": "Basis Pengetahuan",
"gotoAnything.groups.plugins": "Integrasi",
"gotoAnything.groups.recent": "Terbaru",
"gotoAnything.groups.workflowNodes": "Node Alur Kerja",
"gotoAnything.inScope": "di {{scope}}s",
"gotoAnything.noMatchingCommands": "Tidak ada perintah yang cocok ditemukan",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Inizia da un workflow preconfigurato. Ideale per imparare Dify e i pattern comuni.",
"firstEmpty.title": "Ancora nessuna app",
"gotoAnything.actions.accountDesc": "Vai alla pagina dell'account",
"gotoAnything.actions.communityDesc": "Apri la community di Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Lascia che l'IA scelga Workflow o Chatflow dalla tua descrizione",
"gotoAnything.actions.createCategoryDesc": "Crea un flusso di lavoro o un flusso di chat generato dall'intelligenza artificiale",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Genera un'app del flusso di chat (chat avanzata) da una descrizione",
"gotoAnything.actions.createWorkflow": "Flusso di lavoro",
"gotoAnything.actions.createWorkflowDesc": "Genera un'app del flusso di lavoro da una descrizione",
"gotoAnything.actions.discordDesc": "Apri la community di Discord",
"gotoAnything.actions.docDesc": "Apri la documentazione di aiuto",
"gotoAnything.actions.feedbackDesc": "Discussioni di feedback della comunità aperta",
"gotoAnything.actions.languageChangeDesc": "Cambia lingua dell'interfaccia",
"gotoAnything.actions.refineCategoryDesc": "Perfeziona il flusso di lavoro corrente o il grafico del flusso di chat",
"gotoAnything.actions.refineDesc": "Descrivi una modifica da applicare alla bozza corrente",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Comandi",
"gotoAnything.groups.knowledgeBases": "Basi di conoscenza",
"gotoAnything.groups.plugins": "Integrazione",
"gotoAnything.groups.recent": "Recente",
"gotoAnything.groups.workflowNodes": "Nodi del flusso di lavoro",
"gotoAnything.inScope": "in {{scope}}s",
"gotoAnything.noMatchingCommands": "Nessun comando corrispondente trovato",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "用意されたアプリを選んでカスタマイズ。Dify を最速で体感できる方法です。",
"firstEmpty.title": "最初のアプリを作成",
"gotoAnything.actions.accountDesc": "アカウントページに移動する",
"gotoAnything.actions.communityDesc": "オープンDiscordコミュニティ",
"gotoAnything.actions.createAuto": "自動",
"gotoAnything.actions.createAutoDesc": "説明に基づいて AI が Workflow か Chatflow を選択します",
"gotoAnything.actions.createCategoryDesc": "AI が生成したワークフローまたはチャットフローを作成する",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "説明からチャットフロー (高度なチャット) アプリを生成する",
"gotoAnything.actions.createWorkflow": "ワークフロー",
"gotoAnything.actions.createWorkflowDesc": "説明からワークフロー アプリを生成する",
"gotoAnything.actions.discordDesc": "オープンDiscordコミュニティ",
"gotoAnything.actions.docDesc": "ヘルプドキュメントを開く",
"gotoAnything.actions.feedbackDesc": "オープンなコミュニティフィードバックディスカッション",
"gotoAnything.actions.languageChangeDesc": "UI言語を変更する",
"gotoAnything.actions.refineCategoryDesc": "現在のワークフローまたはチャットフローグラフを改良する",
"gotoAnything.actions.refineDesc": "現在のドラフトに適用する変更について説明します",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "コマンド",
"gotoAnything.groups.knowledgeBases": "ナレッジベース",
"gotoAnything.groups.plugins": "インテグレーション",
"gotoAnything.groups.recent": "最近",
"gotoAnything.groups.workflowNodes": "ワークフローノード",
"gotoAnything.inScope": "{{scope}}s 内",
"gotoAnything.noMatchingCommands": "一致するコマンドが見つかりません",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "미리 만들어진 workflow에서 시작하세요. Dify와 일반적인 패턴을 배우기에 좋습니다.",
"firstEmpty.title": "아직 앱이 없습니다",
"gotoAnything.actions.accountDesc": "계정 페이지로 이동",
"gotoAnything.actions.communityDesc": "오픈 디스코드 커뮤니티",
"gotoAnything.actions.createAuto": "자동",
"gotoAnything.actions.createAutoDesc": "설명을 바탕으로 AI가 Workflow 또는 Chatflow를 선택하도록 합니다",
"gotoAnything.actions.createCategoryDesc": "AI 생성 워크플로 또는 채팅 흐름 만들기",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "설명에서 chatflow (№ 급 채팅) 앱 생성",
"gotoAnything.actions.createWorkflow": "작업 흐름",
"gotoAnything.actions.createWorkflowDesc": "설명에서 워크플로 앱 생성",
"gotoAnything.actions.discordDesc": "오픈 디스코드 커뮤니티",
"gotoAnything.actions.docDesc": "도움 문서 열기",
"gotoAnything.actions.feedbackDesc": "공개 커뮤니티 피드백 토론",
"gotoAnything.actions.languageChangeDesc": "UI 언어 변경",
"gotoAnything.actions.refineCategoryDesc": "현재 워크플로우 또는 챗플로우 그래프를 구체화합니다.",
"gotoAnything.actions.refineDesc": "현재 초안에 적용할 변경 사항 설명",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "명령어",
"gotoAnything.groups.knowledgeBases": "기술 자료",
"gotoAnything.groups.plugins": "통합",
"gotoAnything.groups.recent": "최근",
"gotoAnything.groups.workflowNodes": "워크플로 노드",
"gotoAnything.inScope": "{{scope}}s 내에서",
"gotoAnything.noMatchingCommands": "일치하는 명령을 찾을 수 없습니다.",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "ເລືອກແອັບທີ່ພ້ອມໃຊ້ງານ ແລະ ປັບແຕ່ງຕາມໃຈ. ວິທີທີ່ໄວທີ່ສຸດໃນການເບິ່ງການເຮັດວຽກຂອງ Dify.",
"firstEmpty.title": "ສ້າງແອັບທຳອິດຂອງທ່ານ",
"gotoAnything.actions.accountDesc": "ໄປທີ່ໜ້າບັນຊີ",
"gotoAnything.actions.communityDesc": "ເປີດຊຸມຊົນ Discord",
"gotoAnything.actions.createAuto": "ອັດຕະໂນມັດ",
"gotoAnything.actions.createAutoDesc": "ໃຫ້ AI ເລືອກ Workflow ຫຼື Chatflow ຈາກຄຳອະທິບາຍຂອງທ່ານ",
"gotoAnything.actions.createCategoryDesc": "ສ້າງ Workflow ຫຼື Chatflow ທີ່ສ້າງໂດຍ AI",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "ສ້າງແອັບ Chatflow (ການສົນທະນາຂັ້ນສູງ) ຈາກຄຳອະທິບາຍ",
"gotoAnything.actions.createWorkflow": "Workflow",
"gotoAnything.actions.createWorkflowDesc": "ສ້າງແອັບ Workflow ຈາກຄຳອະທິບາຍ",
"gotoAnything.actions.discordDesc": "ເປີດຊຸມຊົນ Discord",
"gotoAnything.actions.docDesc": "ເປີດເອກະສານຊ່ວຍເຫຼືອ",
"gotoAnything.actions.feedbackDesc": "ເປີດການສົນທະນາເພື່ອແລກປ່ຽນຄຳຄິດເຫັນກັບຊຸມຊົນ",
"gotoAnything.actions.languageChangeDesc": "ປ່ຽນພາສາຂອງລະບົບ",
"gotoAnything.actions.refineCategoryDesc": "ປັບປຸງກຣາຟ Workflow ຫຼື Chatflow ປະຈຸບັນ",
"gotoAnything.actions.refineDesc": "ອະທິບາຍການປ່ຽນແປງທີ່ຈະນຳໃຊ້ກັບຮ່າງປະຈຸບັນ",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "ຄຳສັ່ງ",
"gotoAnything.groups.knowledgeBases": "ຄັງຄວາມຮູ້",
"gotoAnything.groups.plugins": "ການເຊື່ອມຕໍ່",
"gotoAnything.groups.recent": "ຫວ່າງໆນີ້",
"gotoAnything.groups.workflowNodes": "ໂນດໃນ Workflow",
"gotoAnything.inScope": "ໃນ {{scope}}",
"gotoAnything.noMatchingCommands": "ບໍ່ພົບຄຳສັ່ງທີ່ກົງກັນ",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Begin met een vooraf gebouwde workflow. Handig om Dify en veelgebruikte patronen te leren.",
"firstEmpty.title": "Nog geen app",
"gotoAnything.actions.accountDesc": "Navigate to account page",
"gotoAnything.actions.communityDesc": "Open Discord community",
"gotoAnything.actions.createAuto": "Automatisch",
"gotoAnything.actions.createAutoDesc": "Laat AI op basis van je beschrijving Workflow of Chatflow kiezen",
"gotoAnything.actions.createCategoryDesc": "Creëer een door AI gegenereerde workflow of chatflow",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Genereer een chatflow-app (geavanceerde chat) op basis van een beschrijving",
"gotoAnything.actions.createWorkflow": "Werkstroom",
"gotoAnything.actions.createWorkflowDesc": "Genereer een workflow-app op basis van een beschrijving",
"gotoAnything.actions.discordDesc": "Open Discord community",
"gotoAnything.actions.docDesc": "Open help documentation",
"gotoAnything.actions.feedbackDesc": "Open community feedback discussions",
"gotoAnything.actions.languageChangeDesc": "Change UI language",
"gotoAnything.actions.refineCategoryDesc": "Verfijn de huidige workflow of chatflowgrafiek",
"gotoAnything.actions.refineDesc": "Beschrijf een wijziging die moet worden toegepast op het huidige concept",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Commands",
"gotoAnything.groups.knowledgeBases": "Knowledge Bases",
"gotoAnything.groups.plugins": "Integraties",
"gotoAnything.groups.recent": "Recent",
"gotoAnything.groups.workflowNodes": "Workflow Nodes",
"gotoAnything.inScope": "in {{scope}}s",
"gotoAnything.noMatchingCommands": "No matching commands found",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Zacznij od gotowego workflow. Dobre do nauki Dify i typowych wzorców.",
"firstEmpty.title": "Nie ma jeszcze aplikacji",
"gotoAnything.actions.accountDesc": "Przejdź do strony konta",
"gotoAnything.actions.communityDesc": "Otwarta społeczność Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Pozwól AI wybrać Workflow lub Chatflow na podstawie Twojego opisu",
"gotoAnything.actions.createCategoryDesc": "Utwórz przepływ pracy lub czat generowany przez sztuczną inteligencję",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Wygeneruj aplikację Chatflow (czat zaawansowany) na podstawie opisu",
"gotoAnything.actions.createWorkflow": "Przepływ pracy",
"gotoAnything.actions.createWorkflowDesc": "Wygeneruj aplikację przepływu pracy na podstawie opisu",
"gotoAnything.actions.discordDesc": "Otwarta społeczność Discord",
"gotoAnything.actions.docDesc": "Otwórz dokumentację pomocy",
"gotoAnything.actions.feedbackDesc": "Otwarte dyskusje na temat opinii społeczności",
"gotoAnything.actions.languageChangeDesc": "Zmień język interfejsu",
"gotoAnything.actions.refineCategoryDesc": "Udoskonal bieżący przepływ pracy lub wykres przepływu rozmów",
"gotoAnything.actions.refineDesc": "Opisz zmianę, którą chcesz zastosować w bieżącej wersji roboczej",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Polecenia",
"gotoAnything.groups.knowledgeBases": "Bazy wiedzy",
"gotoAnything.groups.plugins": "Integracje",
"gotoAnything.groups.recent": "Ostatnie",
"gotoAnything.groups.workflowNodes": "Węzły przepływu pracy",
"gotoAnything.inScope": "w {{scope}}s",
"gotoAnything.noMatchingCommands": "Nie znaleziono pasujących poleceń",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Comece com um workflow pré-criado. Ótimo para aprender Dify e padrões comuns.",
"firstEmpty.title": "Ainda não há apps",
"gotoAnything.actions.accountDesc": "Navegue até a página da conta",
"gotoAnything.actions.communityDesc": "Comunidade do Discord aberta",
"gotoAnything.actions.createAuto": "Automático",
"gotoAnything.actions.createAutoDesc": "Deixe a IA escolher Workflow ou Chatflow a partir da sua descrição",
"gotoAnything.actions.createCategoryDesc": "Crie um fluxo de trabalho ou fluxo de chat gerado por IA",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Gere um aplicativo chatflow (chat avançado) a partir de uma descrição",
"gotoAnything.actions.createWorkflow": "Fluxo de trabalho",
"gotoAnything.actions.createWorkflowDesc": "Gere um aplicativo de fluxo de trabalho a partir de uma descrição",
"gotoAnything.actions.discordDesc": "Comunidade do Discord aberta",
"gotoAnything.actions.docDesc": "Abra a documentação de ajuda",
"gotoAnything.actions.feedbackDesc": "Discussões de feedback da comunidade aberta",
"gotoAnything.actions.languageChangeDesc": "Mudar o idioma da interface",
"gotoAnything.actions.refineCategoryDesc": "Refinar o fluxo de trabalho atual ou gráfico de fluxo de chat",
"gotoAnything.actions.refineDesc": "Descreva uma alteração a ser aplicada ao rascunho atual",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Comandos",
"gotoAnything.groups.knowledgeBases": "Bases de conhecimento",
"gotoAnything.groups.plugins": "Integrações",
"gotoAnything.groups.recent": "Recente",
"gotoAnything.groups.workflowNodes": "Nós de fluxo de trabalho",
"gotoAnything.inScope": "em {{scope}}s",
"gotoAnything.noMatchingCommands": "Nenhum comando correspondente encontrado",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Începe cu un workflow preconfigurat. Bun pentru a învăța Dify și tiparele comune.",
"firstEmpty.title": "Încă nu există aplicații",
"gotoAnything.actions.accountDesc": "Navigați la pagina de cont",
"gotoAnything.actions.communityDesc": "Deschide comunitatea Discord",
"gotoAnything.actions.createAuto": "Automat",
"gotoAnything.actions.createAutoDesc": "Lasă AI să aleagă Workflow sau Chatflow pe baza descrierii tale",
"gotoAnything.actions.createCategoryDesc": "Creați un flux de lucru sau un flux de chat generat de AI",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Generați o aplicație chatflow (chat avansat) dintr-o descriere",
"gotoAnything.actions.createWorkflow": "Fluxul de lucru",
"gotoAnything.actions.createWorkflowDesc": "Generați o aplicație de flux de lucru dintr-o descriere",
"gotoAnything.actions.discordDesc": "Deschide comunitatea Discord",
"gotoAnything.actions.docDesc": "Deschide documentația de ajutor",
"gotoAnything.actions.feedbackDesc": "Discuții de feedback deschis pentru comunitate",
"gotoAnything.actions.languageChangeDesc": "Schimbați limba interfeței",
"gotoAnything.actions.refineCategoryDesc": "Rafinați fluxul de lucru curent sau graficul fluxului de chat",
"gotoAnything.actions.refineDesc": "Descrieți o modificare de aplicat schiței actuale",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Comenzi",
"gotoAnything.groups.knowledgeBases": "Baze de cunoștințe",
"gotoAnything.groups.plugins": "Integrări",
"gotoAnything.groups.recent": "Recent",
"gotoAnything.groups.workflowNodes": "Noduri de flux de lucru",
"gotoAnything.inScope": "în {{scope}}s",
"gotoAnything.noMatchingCommands": "Nu s-au găsit comenzi potrivite",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Начните с готового workflow. Подходит для изучения Dify и распространенных шаблонов.",
"firstEmpty.title": "Приложений пока нет",
"gotoAnything.actions.accountDesc": "Перейдите на страницу учетной записи",
"gotoAnything.actions.communityDesc": "Открытое сообщество Discord",
"gotoAnything.actions.createAuto": "Авто",
"gotoAnything.actions.createAutoDesc": "Позвольте ИИ выбрать Workflow или Chatflow на основе вашего описания",
"gotoAnything.actions.createCategoryDesc": "Создайте рабочий процесс или поток чата, созданный искусственным интеллектом.",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Создайте приложение чата (расширенный чат) из описания.",
"gotoAnything.actions.createWorkflow": "? абочий процесс",
"gotoAnything.actions.createWorkflowDesc": "Создание приложения рабочего процесса из описания",
"gotoAnything.actions.discordDesc": "Открытое сообщество Discord",
"gotoAnything.actions.docDesc": "Откройте справочную документацию",
"gotoAnything.actions.feedbackDesc": "Обсуждения обратной связи с открытым сообществом",
"gotoAnything.actions.languageChangeDesc": "Измените язык интерфейса",
"gotoAnything.actions.refineCategoryDesc": "Уточните текущий рабочий процесс или график потока чата",
"gotoAnything.actions.refineDesc": "Опишите изменение, которое будет применено к текущему проекту.",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Команды",
"gotoAnything.groups.knowledgeBases": "Базы знаний",
"gotoAnything.groups.plugins": "Интеграции",
"gotoAnything.groups.recent": "Недавние",
"gotoAnything.groups.workflowNodes": "Узлы рабочих процессов",
"gotoAnything.inScope": "в {{scope}}s",
"gotoAnything.noMatchingCommands": "Соответствующие команды не найдены",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Začnite z vnaprej pripravljenim workflowom. Primerno za učenje Dify in pogostih vzorcev.",
"firstEmpty.title": "Aplikacij še ni",
"gotoAnything.actions.accountDesc": "Pojdite na stran računa",
"gotoAnything.actions.communityDesc": "Odpri Discord skupnost",
"gotoAnything.actions.createAuto": "Samodejno",
"gotoAnything.actions.createAutoDesc": "Naj AI na podlagi vašega opisa izbere Workflow ali Chatflow",
"gotoAnything.actions.createCategoryDesc": "Ustvarite potek dela ali potek klepeta, ki ga ustvari AI",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Iz opisa ustvarite aplikacijo chatflow (napredni klepet).",
"gotoAnything.actions.createWorkflow": "Potek dela",
"gotoAnything.actions.createWorkflowDesc": "Iz opisa ustvarite aplikacijo za potek dela",
"gotoAnything.actions.discordDesc": "Odpri Discord skupnost",
"gotoAnything.actions.docDesc": "Odprite pomoč dokumentacijo",
"gotoAnything.actions.feedbackDesc": "Razprave o povratnih informacijah odprte skupnosti",
"gotoAnything.actions.languageChangeDesc": "Spremeni jezik vmesnika",
"gotoAnything.actions.refineCategoryDesc": "Izboljšajte trenutni tok dela ali graf toka klepeta",
"gotoAnything.actions.refineDesc": "Opišite spremembo za trenutni osnutek",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Ukazi",
"gotoAnything.groups.knowledgeBases": "Baze znanja",
"gotoAnything.groups.plugins": "Integracije",
"gotoAnything.groups.recent": "Nedavno",
"gotoAnything.groups.workflowNodes": "Vozlišča poteka dela",
"gotoAnything.inScope": "v {{scope}}s",
"gotoAnything.noMatchingCommands": "Ujemajoči se ukazi niso našli",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "เริ่มจาก workflow ที่สร้างไว้แล้ว เหมาะสำหรับเรียนรู้ Dify และรูปแบบที่พบบ่อย",
"firstEmpty.title": "ยังไม่มีแอป",
"gotoAnything.actions.accountDesc": "ไปที่หน้าบัญชี",
"gotoAnything.actions.communityDesc": "เปิดชุมชน Discord",
"gotoAnything.actions.createAuto": "อัตโนมัติ",
"gotoAnything.actions.createAutoDesc": "ให้ AI เลือก Workflow หรือ Chatflow จากคำอธิบายของคุณ",
"gotoAnything.actions.createCategoryDesc": "สร้างเวิร์กโฟลว์หรือแชทที่สร้างโดย AI",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "สร้างแอป Chatflow (แชทขั้นสูง) จากคำอธิบาย",
"gotoAnything.actions.createWorkflow": "ขั้นตอนการทำงาน",
"gotoAnything.actions.createWorkflowDesc": "สร้างแอปเวิร์กโฟลว์จากคำอธิบาย",
"gotoAnything.actions.discordDesc": "เปิดชุมชน Discord",
"gotoAnything.actions.docDesc": "เปิดเอกสารช่วยเหลือ",
"gotoAnything.actions.feedbackDesc": "การอภิปรายข้อเสนอแนะแบบเปิดในชุมชน",
"gotoAnything.actions.languageChangeDesc": "เปลี่ยนภาษา UI",
"gotoAnything.actions.refineCategoryDesc": "ปรับแต่งเวิร์กโฟลว์ปัจจุบันหรือกราฟโฟลว์แชท",
"gotoAnything.actions.refineDesc": "อธิบายการเปลี่ยนแปลงที่จะนำไปใช้กับร่างปัจจุบัน",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "คำสั่ง",
"gotoAnything.groups.knowledgeBases": "ฐานความรู้",
"gotoAnything.groups.plugins": "การผสานรวม",
"gotoAnything.groups.recent": "ล่าสุด",
"gotoAnything.groups.workflowNodes": "โหนดเวิร์กโฟลว์",
"gotoAnything.inScope": "ใน {{scope}}s",
"gotoAnything.noMatchingCommands": "ไม่พบคำสั่งที่ตรงกัน",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Önceden hazırlanmış bir workflow ile başlayın. Dify ve yaygın kalıpları öğrenmek için uygundur.",
"firstEmpty.title": "Henüz uygulama yok",
"gotoAnything.actions.accountDesc": "Hesap sayfasına gidin",
"gotoAnything.actions.communityDesc": "Açık Discord topluluğu",
"gotoAnything.actions.createAuto": "Otomatik",
"gotoAnything.actions.createAutoDesc": "Yapay zekanın açıklamanıza göre Workflow veya Chatflow seçmesine izin verin",
"gotoAnything.actions.createCategoryDesc": "Yapay zeka tarafından oluşturulan bir iş akışı veya sohbet akışı oluşturun",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Açıklamadan bir sohbet akışı (gelişmiş sohbet) uygulaması oluşturun",
"gotoAnything.actions.createWorkflow": "İş akışı",
"gotoAnything.actions.createWorkflowDesc": "Açıklamadan iş akışı uygulaması oluşturma",
"gotoAnything.actions.discordDesc": "Açık Discord topluluğu",
"gotoAnything.actions.docDesc": "Yardım belgelerini aç",
"gotoAnything.actions.feedbackDesc": "Açık topluluk geri bildirim tartışmaları",
"gotoAnything.actions.languageChangeDesc": "UI dilini değiştir",
"gotoAnything.actions.refineCategoryDesc": "Mevcut iş akışını veya sohbet akışı grafiğini hassaslaştırın",
"gotoAnything.actions.refineDesc": "Mevcut taslağa uygulanacak bir değişikliği açıklayın",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Komutlar",
"gotoAnything.groups.knowledgeBases": "Bilgi Tabanları",
"gotoAnything.groups.plugins": "Entegrasyonlar",
"gotoAnything.groups.recent": "Son",
"gotoAnything.groups.workflowNodes": "İş Akışı Düğümleri",
"gotoAnything.inScope": "{{scope}}s içinde",
"gotoAnything.noMatchingCommands": "Eşleşen komut bulunamadı",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Почніть із готового workflow. Добре підходить для вивчення Dify і поширених шаблонів.",
"firstEmpty.title": "Застосунків ще немає",
"gotoAnything.actions.accountDesc": "Перейдіть на сторінку облікового запису",
"gotoAnything.actions.communityDesc": "Відкрита Discord-спільнота",
"gotoAnything.actions.createAuto": "Авто",
"gotoAnything.actions.createAutoDesc": "Дозвольте ШІ вибрати Workflow або Chatflow на основі вашого опису",
"gotoAnything.actions.createCategoryDesc": "Створіть створений штучним інтелектом робочий процес або процес чату",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Створіть програму chatflow (розширений чат) з опису",
"gotoAnything.actions.createWorkflow": "робочий процес",
"gotoAnything.actions.createWorkflowDesc": "Створіть програму робочого процесу з опису",
"gotoAnything.actions.discordDesc": "Відкрита Discord-спільнота",
"gotoAnything.actions.docDesc": "Відкрийте документацію допомоги",
"gotoAnything.actions.feedbackDesc": "Відкриті обговорення відгуків громади",
"gotoAnything.actions.languageChangeDesc": "Змінити мову інтерфейсу",
"gotoAnything.actions.refineCategoryDesc": "Уточніть поточний робочий процес або графік чату",
"gotoAnything.actions.refineDesc": "Опишіть зміну, яку слід застосувати до поточної чернетки",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Команди",
"gotoAnything.groups.knowledgeBases": "Бази знань",
"gotoAnything.groups.plugins": "Інтеграції",
"gotoAnything.groups.recent": "Нещодавні",
"gotoAnything.groups.workflowNodes": "Вузли документообігу",
"gotoAnything.inScope": "у {{scope}}s",
"gotoAnything.noMatchingCommands": "Відповідних команд не знайдено",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "Bắt đầu từ workflow dựng sẵn. Phù hợp để học Dify và các mẫu phổ biến.",
"firstEmpty.title": "Chưa có ứng dụng",
"gotoAnything.actions.accountDesc": "Đi đến trang tài khoản",
"gotoAnything.actions.communityDesc": "Mở cộng đồng Discord",
"gotoAnything.actions.createAuto": "Tự động",
"gotoAnything.actions.createAutoDesc": "Để AI chọn Workflow hoặc Chatflow từ mô tả của bạn",
"gotoAnything.actions.createCategoryDesc": "Tạo quy trình làm việc hoặc luồng trò chuyện do AI tạo",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "Tạo ứng dụng luồng trò chuyện (trò chuyện nâng cao) từ mô tả",
"gotoAnything.actions.createWorkflow": "Quy trình làm việc",
"gotoAnything.actions.createWorkflowDesc": "Tạo ứng dụng quy trình làm việc từ mô tả",
"gotoAnything.actions.discordDesc": "Mở cộng đồng Discord",
"gotoAnything.actions.docDesc": "Mở tài liệu trợ giúp",
"gotoAnything.actions.feedbackDesc": "Thảo luận phản hồi cộng đồng mở",
"gotoAnything.actions.languageChangeDesc": "Thay đổi ngôn ngữ giao diện",
"gotoAnything.actions.refineCategoryDesc": "Tinh chỉnh quy trình làm việc hiện tại hoặc biểu đồ luồng trò chuyện",
"gotoAnything.actions.refineDesc": "Mô tả một thay đổi để áp dụng cho dự thảo hiện tại",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "Lệnh",
"gotoAnything.groups.knowledgeBases": "Cơ sở kiến thức",
"gotoAnything.groups.plugins": "Tích hợp",
"gotoAnything.groups.recent": "Gần đây",
"gotoAnything.groups.workflowNodes": "Nút quy trình làm việc",
"gotoAnything.inScope": "trong {{scope}}s",
"gotoAnything.noMatchingCommands": "Không tìm thấy lệnh phù hợp",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "选择现成的应用进行定制。最快体验 Dify 的方式。",
"firstEmpty.title": "创建你的第一个应用",
"gotoAnything.actions.accountDesc": "导航到账户页面",
"gotoAnything.actions.communityDesc": "打开 Discord 社区",
"gotoAnything.actions.createAuto": "自动",
"gotoAnything.actions.createAutoDesc": "让 AI 根据你的描述自动选择 Workflow 或 Chatflow",
"gotoAnything.actions.createCategoryDesc": "创建由 AI 生成的工作流或 Chatflow",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "根据描述生成一个 Chatflow(高级聊天)应用",
"gotoAnything.actions.createWorkflow": "Workflow",
"gotoAnything.actions.createWorkflowDesc": "根据描述生成一个工作流应用",
"gotoAnything.actions.discordDesc": "打开 Discord 社区",
"gotoAnything.actions.docDesc": "打开帮助文档",
"gotoAnything.actions.feedbackDesc": "打开社区反馈讨论",
"gotoAnything.actions.languageChangeDesc": "更改界面语言",
"gotoAnything.actions.refineCategoryDesc": "优化当前的工作流或 Chatflow 图",
"gotoAnything.actions.refineDesc": "描述要应用到当前草稿的修改",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "命令",
"gotoAnything.groups.knowledgeBases": "知识库",
"gotoAnything.groups.plugins": "集成",
"gotoAnything.groups.recent": "最近",
"gotoAnything.groups.workflowNodes": "工作流节点",
"gotoAnything.inScope": "在 {{scope}}s 中",
"gotoAnything.noMatchingCommands": "未找到匹配的命令",
+1 -3
View File
@@ -55,7 +55,6 @@
"firstEmpty.templateDescription": "從預先建置的 workflow 開始。適合學習 Dify 和常見模式。",
"firstEmpty.title": "尚無應用程式",
"gotoAnything.actions.accountDesc": "導航到帳戶頁面",
"gotoAnything.actions.communityDesc": "開放的 Discord 社區",
"gotoAnything.actions.createAuto": "自動",
"gotoAnything.actions.createAutoDesc": "讓 AI 根據你的描述自動選擇 Workflow 或 Chatflow",
"gotoAnything.actions.createCategoryDesc": "建立 AI 產生的工作流程或聊天流程",
@@ -63,8 +62,8 @@
"gotoAnything.actions.createChatflowDesc": "根据描述生成聊天流(高级聊天)应用程序",
"gotoAnything.actions.createWorkflow": "工作流程",
"gotoAnything.actions.createWorkflowDesc": "根据描述生成工作流应用程序",
"gotoAnything.actions.discordDesc": "開放的 Discord 社區",
"gotoAnything.actions.docDesc": "開啟幫助文件",
"gotoAnything.actions.feedbackDesc": "開放社區反饋討論",
"gotoAnything.actions.languageChangeDesc": "更改 UI 語言",
"gotoAnything.actions.refineCategoryDesc": "優化目前工作流程或聊天流程圖",
"gotoAnything.actions.refineDesc": "描述適用於當前草案的更改",
@@ -95,7 +94,6 @@
"gotoAnything.groups.commands": "指令",
"gotoAnything.groups.knowledgeBases": "知識庫",
"gotoAnything.groups.plugins": "集成",
"gotoAnything.groups.recent": "最近",
"gotoAnything.groups.workflowNodes": "工作流節點",
"gotoAnything.inScope": "在 {{scope}}s 中",
"gotoAnything.noMatchingCommands": "未找到匹配的命令",