mirror of
https://github.com/langgenius/dify.git
synced 2026-08-29 03:45:08 +08:00
feat(web): enhance Go to Anything navigation and discovery (#41160)
This commit is contained in:
@@ -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: []
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
+55
-77
@@ -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
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
+33
-10
@@ -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()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
+19
-10
@@ -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
|
||||
|
||||
@@ -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": "لم يتم العثور على أوامر مطابقة",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "هیچ دستوری منطبق یافت نشد",
|
||||
|
||||
@@ -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 n’a été trouvée",
|
||||
|
||||
@@ -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": "कोई मिलती-जुलती कमांड्स नहीं मिलीं",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "一致するコマンドが見つかりません",
|
||||
|
||||
@@ -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": "일치하는 명령을 찾을 수 없습니다.",
|
||||
|
||||
@@ -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": "ບໍ່ພົບຄຳສັ່ງທີ່ກົງກັນ",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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ń",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Соответствующие команды не найдены",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "ไม่พบคำสั่งที่ตรงกัน",
|
||||
|
||||
@@ -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ı",
|
||||
|
||||
@@ -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": "Відповідних команд не знайдено",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "未找到匹配的命令",
|
||||
|
||||
@@ -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": "未找到匹配的命令",
|
||||
|
||||
Reference in New Issue
Block a user