refactor(web): clarify App modes and scope workflow editor resources (#44171)

This commit is contained in:
yyh
2026-10-16 00:10:08 +00:00
committed by GitHub
parent 310ce87f9d
commit 8aa1fe96ab
504 changed files with 5736 additions and 4608 deletions
+5 -15
View File
@@ -2487,11 +2487,6 @@
"count": 1
}
},
"web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts": {
"typescript/no-explicit-any": {
"count": 1
}
},
"web/app/components/workflow/hooks/use-serial-async-callback.ts": {
"typescript/no-explicit-any": {
"count": 1
@@ -2787,7 +2782,7 @@
"count": 1
},
"typescript/no-explicit-any": {
"count": 21
"count": 20
}
},
"web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts": {
@@ -4250,11 +4245,6 @@
"count": 1
}
},
"web/service/use-flow.ts": {
"eslint-react/no-unnecessary-use-prefix": {
"count": 1
}
},
"web/service/use-log.ts": {
"@tanstack/query/prefer-query-options": {
"count": 7
@@ -4333,13 +4323,13 @@
},
"web/service/use-workflow.ts": {
"@tanstack/query/prefer-query-options": {
"count": 9
"count": 6
},
"no-restricted-imports": {
"count": 1
},
"typescript/no-explicit-any": {
"count": 3
"count": 1
}
},
"web/service/use-workspace.ts": {
@@ -4380,7 +4370,7 @@
},
"web/types/workflow.ts": {
"typescript/no-explicit-any": {
"count": 17
"count": 16
}
},
"web/utils/completion-params.spec.ts": {
@@ -4423,4 +4413,4 @@
"count": 2
}
}
}
}
@@ -9,7 +9,6 @@ import {
seedAppDetail,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppDetailLayout from '../layout-main'
@@ -63,7 +62,7 @@ const createAppDetail = (overrides: Partial<AppDetailWithSite> = {}) =>
createAppDetailFixture({
id: 'app-1',
name: 'Demo App',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.ViewLayout, AppACLPermission.Monitor],
...overrides,
})
@@ -148,15 +147,15 @@ describe('AppDetailLayout', () => {
describe('Document title', () => {
it.each([
['/app/app-1/workflow', 'common.appMenus.promptEng', AppModeEnum.WORKFLOW],
['/app/app-1/configuration', 'common.appMenus.promptEng', AppModeEnum.CHAT],
['/app/app-1/access-point', 'common.appMenus.accessPoint', AppModeEnum.WORKFLOW],
['/app/app-1/deploy', 'common.appMenus.deploy', AppModeEnum.WORKFLOW],
['/app/app-1/logs', 'common.appMenus.logs', AppModeEnum.WORKFLOW],
['/app/app-1/annotations', 'common.appMenus.annotations', AppModeEnum.CHAT],
['/app/app-1/overview', 'common.appMenus.overview', AppModeEnum.WORKFLOW],
['/app/app-1/access-config', 'navigation.settings.resourceAccess', AppModeEnum.WORKFLOW],
])('identifies the current detail page for %s', async (pathname, pageTitle, mode) => {
['/app/app-1/workflow', 'common.appMenus.promptEng', 'workflow'],
['/app/app-1/configuration', 'common.appMenus.promptEng', 'chat'],
['/app/app-1/access-point', 'common.appMenus.accessPoint', 'workflow'],
['/app/app-1/deploy', 'common.appMenus.deploy', 'workflow'],
['/app/app-1/logs', 'common.appMenus.logs', 'workflow'],
['/app/app-1/annotations', 'common.appMenus.annotations', 'chat'],
['/app/app-1/overview', 'common.appMenus.overview', 'workflow'],
['/app/app-1/access-config', 'navigation.settings.resourceAccess', 'workflow'],
] as const)('identifies the current detail page for %s', async (pathname, pageTitle, mode) => {
mockPathname = pathname
mockAppResponse.mockResolvedValue(
createAppDetail({
@@ -543,7 +542,7 @@ describe('AppDetailLayout', () => {
mockPathname = '/app/app-1/access-config'
mockAppResponse.mockResolvedValue(
createAppDetail({
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
bound_agent_id: 'agent-1',
permission_keys: [AppACLPermission.AccessConfig],
}),
@@ -570,7 +569,7 @@ describe('AppDetailLayout', () => {
</AppDetailLayout>,
{
cachedAppDetail: createAppDetail({
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
bound_agent_id: 'agent-1',
permission_keys: [AppACLPermission.AccessConfig],
}),
@@ -608,7 +607,7 @@ describe('AppDetailLayout', () => {
mockPathname = '/app/app-1/annotations'
mockAppResponse.mockResolvedValue(
createAppDetail({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
permission_keys: [AppACLPermission.Monitor],
}),
)
@@ -629,7 +628,7 @@ describe('AppDetailLayout', () => {
mockPathname = '/app/app-1/annotations'
mockAppResponse.mockResolvedValue(
createAppDetail({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
permission_keys: [AppACLPermission.LogAndAnnotation],
}),
)
@@ -20,7 +20,6 @@ import useDocumentTitle from '@/hooks/use-document-title'
import { redirect, usePathname } from '@/next/navigation'
import { consoleQuery } from '@/service/console'
import { isNotFoundError } from '@/service/http-error'
import { AppModeEnum } from '@/types/app'
import { getRedirectionPath } from '@/utils/app-redirection'
import { getAppACLCapabilities } from '@/utils/permission'
@@ -51,8 +50,7 @@ const getAppDetailRedirectPath = (
permissionOptions: ResourceMaintainerPermissionOptions,
) => {
const appACLCapabilities = getAppACLCapabilities(appDetail.permission_keys, permissionOptions)
const isWorkflowApp =
appDetail.mode === AppModeEnum.WORKFLOW || appDetail.mode === AppModeEnum.ADVANCED_CHAT
const isWorkflowApp = appDetail.mode === 'workflow' || appDetail.mode === 'advanced-chat'
const isLayoutPath = pathname.endsWith('configuration') || pathname.endsWith('workflow')
const isLogsPath = pathname.endsWith('logs')
const isAnnotationsPath = pathname.endsWith('annotations')
@@ -65,8 +63,7 @@ const getAppDetailRedirectPath = (
(isLogsPath && !appACLCapabilities.canAccessLogAndAnnotation) ||
(isAnnotationsPath && !appACLCapabilities.canAccessLogAndAnnotation) ||
(isOverviewPath && !appACLCapabilities.canMonitor) ||
(isAccessConfigPath &&
(appDetail.mode === AppModeEnum.AGENT || !appACLCapabilities.canAccessConfig)) ||
(isAccessConfigPath && (appDetail.mode === 'agent' || !appACLCapabilities.canAccessConfig)) ||
(isAccessPointPath && !appACLCapabilities.canViewAccessPoint) ||
(isDeployPath && (!isWorkflowApp || !appACLCapabilities.canDeploy))
)
@@ -3,7 +3,6 @@ import { screen } from '@testing-library/react'
import { consoleQuery } from '@/service/console'
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppDetailSection from '../app-detail-section'
@@ -221,18 +220,18 @@ describe('AppDetailSection', () => {
it.each([
{
label: 'the app is not a workflow app',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
permissionKeys: [AppACLPermission.Deploy],
},
{
label: 'app deploy ACL permission is missing',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permissionKeys: [AppACLPermission.Monitor],
},
])('should hide deploy navigation when $label', ({ mode, permissionKeys }) => {
] as const)('should hide deploy navigation when $label', ({ mode, permissionKeys }) => {
// Arrange
mockAppMode = mode
mockAppPermissionKeys = permissionKeys
mockAppPermissionKeys = [...permissionKeys]
// Act
render(<AppDetailSection />)
@@ -241,7 +240,7 @@ describe('AppDetailSection', () => {
expect(screen.queryByRole('link', { name: 'common.appMenus.deploy' })).not.toBeInTheDocument()
})
it.each([AppModeEnum.CHAT, AppModeEnum.AGENT_CHAT])(
it.each(['chat', 'agent-chat'] as const)(
'should render resource access navigation for %s apps when app access config permission is granted',
(mode) => {
// Arrange
@@ -263,7 +262,7 @@ describe('AppDetailSection', () => {
it('should hide resource access navigation for Agent apps', () => {
// Arrange
mockAppMode = AppModeEnum.AGENT
mockAppMode = 'agent'
mockAppPermissionKeys = [AppACLPermission.AccessConfig]
// Act
@@ -13,7 +13,6 @@ import { userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { usePathname } from '@/next/navigation'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getAppACLCapabilities } from '@/utils/permission'
import { AppInfoView } from './app-info'
import AppInfoHeader from './app-info/app-info-header'
@@ -71,11 +70,9 @@ function AppDetailContent({ appId, expand }: { appId: string; expand: boolean })
if (!appDetail || !systemFeatures || !currentUserId) return []
const appId = appDetail.id
const isWorkflowApp =
appDetail.mode === AppModeEnum.WORKFLOW || appDetail.mode === AppModeEnum.ADVANCED_CHAT
const supportsAnnotations =
appDetail.mode !== AppModeEnum.WORKFLOW && appDetail.mode !== AppModeEnum.COMPLETION
const supportsResourceAccess = appDetail.mode !== AppModeEnum.AGENT
const isWorkflowApp = appDetail.mode === 'workflow' || appDetail.mode === 'advanced-chat'
const supportsAnnotations = appDetail.mode !== 'workflow' && appDetail.mode !== 'completion'
const supportsResourceAccess = appDetail.mode !== 'agent'
const appACLCapabilities = getAppACLCapabilities(appDetail.permission_keys, {
currentUserId,
resourceMaintainer: appDetail.maintainer,
@@ -3,7 +3,6 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import AppInfoModals from '../app-info-modals'
vi.mock('next/dynamic', () => ({
@@ -112,7 +111,7 @@ const createAppDetail = (overrides: Partial<AppDetailWithSite> = {}) =>
createAppDetailFixture({
id: 'app-1',
name: 'Test App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon: '🤖',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -5,7 +5,6 @@ import * as React from 'react'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppInfoTrigger from '../app-info-trigger'
@@ -41,7 +40,7 @@ const createAppDetail = (overrides: Partial<AppDetailWithSite> = {}) =>
createAppDetailFixture({
id: 'app-1',
name: 'Test App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon: '🤖',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -74,7 +73,7 @@ describe('AppInfoTrigger', () => {
it('renders expanded app metadata without making the app info clickable', async () => {
const user = userEvent.setup()
const props = createProps({
appDetail: createAppDetail({ name: 'My Chatbot', mode: AppModeEnum.ADVANCED_CHAT }),
appDetail: createAppDetail({ name: 'My Chatbot', mode: 'advanced-chat' as const }),
})
render(<AppInfoTrigger {...props} />)
@@ -119,7 +118,7 @@ describe('AppInfoTrigger', () => {
it('shows import DSL for workflow apps without a workflow conversion operation', async () => {
const user = userEvent.setup()
const props = createProps({
appDetail: createAppDetail({ mode: AppModeEnum.WORKFLOW }),
appDetail: createAppDetail({ mode: 'workflow' as const }),
})
render(<AppInfoTrigger {...props} />)
@@ -1,6 +1,5 @@
import type { TFunction } from 'i18next'
import { withSelectorKey } from '@/test/i18n-mock'
import { AppModeEnum } from '@/types/app'
import { getAppModeLabel } from '../app-mode-labels'
describe('getAppModeLabel', () => {
@@ -10,19 +9,19 @@ describe('getAppModeLabel', () => {
}, 'app') as unknown as TFunction<['app']>
it('should return advanced chat label', () => {
expect(getAppModeLabel(AppModeEnum.ADVANCED_CHAT, t)).toBe('app.types.advanced')
expect(getAppModeLabel('advanced-chat', t)).toBe('app.types.advanced')
})
it('should return agent chat label', () => {
expect(getAppModeLabel(AppModeEnum.AGENT_CHAT, t)).toBe('app.types.agent')
expect(getAppModeLabel('agent-chat', t)).toBe('app.types.agent')
})
it('should return chatbot label', () => {
expect(getAppModeLabel(AppModeEnum.CHAT, t)).toBe('app.types.chatbot')
expect(getAppModeLabel('chat', t)).toBe('app.types.chatbot')
})
it('should return completion label', () => {
expect(getAppModeLabel(AppModeEnum.COMPLETION, t)).toBe('app.types.completion')
expect(getAppModeLabel('completion', t)).toBe('app.types.completion')
})
it('should return workflow label for unknown mode', () => {
@@ -30,6 +29,6 @@ describe('getAppModeLabel', () => {
})
it('should return workflow label for workflow mode', () => {
expect(getAppModeLabel(AppModeEnum.WORKFLOW, t)).toBe('app.types.workflow')
expect(getAppModeLabel('workflow', t)).toBe('app.types.workflow')
})
})
@@ -3,7 +3,6 @@ import { act, waitFor } from '@testing-library/react'
import { consoleQuery } from '@/service/console'
import { createConsoleQueryClient, renderHookWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import { useAppInfoActions } from '../use-app-info-actions'
@@ -140,7 +139,7 @@ describe('useAppInfoActions', () => {
mockAppDetail = createAppDetailFixture({
id: 'app-1',
name: 'Test App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon: '🤖',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -370,7 +369,7 @@ describe('useAppInfoActions', () => {
})
it('should open export warning modal for workflow mode', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.WORKFLOW })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'workflow' as const })
const { result } = renderActions()
@@ -382,7 +381,7 @@ describe('useAppInfoActions', () => {
})
it('should open export warning modal for advanced_chat mode', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.ADVANCED_CHAT })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'advanced-chat' as const })
const { result } = renderActions()
@@ -396,7 +395,7 @@ describe('useAppInfoActions', () => {
describe('handleConfirmExport', () => {
it('keeps the warning open after failure so the user can retry', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.WORKFLOW })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'workflow' as const })
mockExportWorkflowAppDsl
.mockResolvedValueOnce({ status: 'failed' })
.mockResolvedValueOnce({ status: 'downloaded' })
@@ -415,7 +414,7 @@ describe('useAppInfoActions', () => {
})
it('should export directly when no secret env variables', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.WORKFLOW })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'workflow' as const })
const { result } = renderActions()
await act(async () => {
@@ -429,7 +428,7 @@ describe('useAppInfoActions', () => {
})
it('should set secret env list when secret variables exist', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.WORKFLOW })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'workflow' as const })
const secretVars = [{ value_type: 'secret', name: 'API_KEY', value: 'secret' }]
mockExportWorkflowAppDsl.mockResolvedValue({
status: 'confirmation-required',
@@ -7,7 +7,6 @@ import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { AppModeEnum } from '@/types/app'
import { getAppACLCapabilities, hasPermission } from '@/utils/permission'
import AppInfoHeader from './app-info-header'
import { getAppModeLabel } from './app-mode-labels'
@@ -74,7 +73,7 @@ const AppInfoTrigger = ({
]
: []),
...(appACLCapabilities.canImportExportDSL &&
(appDetail.mode === AppModeEnum.ADVANCED_CHAT || appDetail.mode === AppModeEnum.WORKFLOW)
(appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow')
? [
{
id: 'import',
@@ -99,8 +98,7 @@ const AppInfoTrigger = ({
: []
const workflowConversionOperations: Operation[] =
appACLCapabilities.canEdit &&
(appDetail.mode === AppModeEnum.COMPLETION || appDetail.mode === AppModeEnum.CHAT)
appACLCapabilities.canEdit && (appDetail.mode === 'completion' || appDetail.mode === 'chat')
? [
{
id: 'switch',
@@ -1,15 +1,14 @@
import type { TFunction } from 'i18next'
import { AppModeEnum } from '@/types/app'
export function getAppModeLabel(mode: string, t: TFunction<['app']>): string {
switch (mode) {
case AppModeEnum.ADVANCED_CHAT:
case 'advanced-chat':
return t(($) => $['types.advanced'], { ns: 'app' })
case AppModeEnum.AGENT_CHAT:
case 'agent-chat':
return t(($) => $['types.agent'], { ns: 'app' })
case AppModeEnum.CHAT:
case 'chat':
return t(($) => $['types.chatbot'], { ns: 'app' })
case AppModeEnum.COMPLETION:
case 'completion':
return t(($) => $['types.completion'], { ns: 'app' })
default:
return t(($) => $['types.workflow'], { ns: 'app' })
@@ -17,7 +17,6 @@ import {
markAppDeletionSucceeded,
} from '@/service/app-deletion'
import { consoleClient, consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
export type AppInfoModalType =
@@ -234,7 +233,7 @@ export function useAppInfoActions({
const exportCheck = useCallback(async () => {
if (isExporting) return
if (appMode !== AppModeEnum.WORKFLOW && appMode !== AppModeEnum.ADVANCED_CHAT) {
if (appMode !== 'workflow' && appMode !== 'advanced-chat') {
onExport()
return
}
@@ -10,7 +10,6 @@ import {
import { consoleQuery } from '@/service/console'
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppAccessConfigPage from '../index'
@@ -375,7 +374,7 @@ describe('AppAccessConfigPage', () => {
it('should not mount access config data hooks for Agent apps', () => {
appDetail = createAppDetailFixture({
id: 'app-1',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
maintainer: 'account-1',
permission_keys: [AppACLPermission.AccessConfig],
})
@@ -23,7 +23,6 @@ import {
useUpdateAppUserAccessSettings,
} from '@/service/access-control/use-app-access-config'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getAppACLCapabilities } from '@/utils/permission'
type AppAccessConfigPageProps = {
@@ -265,7 +264,7 @@ const AppAccessConfigPage = ({ appId }: AppAccessConfigPageProps) => {
],
)
if (appDetail.mode === AppModeEnum.AGENT || !appACLCapabilities.canAccessConfig) return null
if (appDetail.mode === 'agent' || !appACLCapabilities.canAccessConfig) return null
return <AppAccessConfigContent appId={appId} maintainerId={appDetail.maintainer} />
}
@@ -1,5 +1,5 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { ReactElement } from 'react'
import type { AppModeEnum } from '@/types/app'
import { screen } from '@testing-library/react'
import { consoleQuery } from '@/service/console'
import { createHttpError } from '@/service/http-error'
@@ -43,7 +43,7 @@ function render(ui: ReactElement) {
queryClient.setQueryData(['system-features'], { webapp_auth: { enabled: true } })
queryClient.setQueryData(
consoleQuery.apps.byAppId.get.queryKey({ input: { params: { app_id: 'app-1' } } }),
createAppDetailFixture({ ...mocks.appInfo, mode: mocks.appInfo.mode as AppModeEnum }),
createAppDetailFixture({ ...mocks.appInfo, mode: mocks.appInfo.mode as AppMode }),
)
return renderWithConsole(ui, { wrapper: createQueryClientWrapper(queryClient) })
}
@@ -1,3 +1,4 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { ReactElement } from 'react'
import { screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
@@ -9,7 +10,6 @@ import { QueryClientTestProvider } from '@/test/console/query-provider'
import { render } from '@/test/console/render'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { EnvironmentServiceApiCard } from '../deployed-environment-access-points/environment-service-api-card'
import { EnvironmentWebAppCard } from '../deployed-environment-access-points/environment-web-app-card'
@@ -32,7 +32,7 @@ vi.mock('@/features/system-features/client', () => ({
}),
}))
let mockAppMode: AppModeEnum = AppModeEnum.WORKFLOW
let mockAppMode: AppMode = 'workflow'
vi.mock('@/context/i18n', () => ({
useDocLink: () => (path: string) => `https://docs.example.test/en${path}`,
@@ -149,7 +149,7 @@ describe('environment access point cards', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.saveSiteConfig.mockResolvedValue(true)
mockAppMode = AppModeEnum.WORKFLOW
mockAppMode = 'workflow'
vi.stubGlobal('fetch', async (input: RequestInfo | URL, init?: RequestInit) => {
const request = new Request(input, init)
const path = new URL(request.url).pathname
@@ -247,7 +247,7 @@ describe('environment access point cards', () => {
)
it('sends a chatflow app to the chat web app shell', async () => {
mockAppMode = AppModeEnum.ADVANCED_CHAT
mockAppMode = 'advanced-chat'
renderCard(<EnvironmentWebAppCard appId="app-1" environmentId="staging" canManageAccessPoint />)
@@ -1,3 +1,4 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { AppEnvironment } from '@dify/contracts/enterprise-app-deploy/types.gen'
import type { ReactNode } from 'react'
import type { AccessPoint as AccessPointType } from '@/app/components/app/deploy/utils/access-point'
@@ -11,11 +12,10 @@ import { QueryClientTestProvider } from '@/test/console/query-provider'
import { render } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AccessPoint from '..'
let appMode: AppModeEnum = AppModeEnum.WORKFLOW
let appMode: AppMode = 'workflow'
let appPermissionKeys: string[] = [AppACLPermission.AccessPointView]
const accessPointMocks = vi.hoisted(() => ({
builtIn: vi.fn(),
@@ -133,7 +133,7 @@ const renderAccessPoint = ({
describe('AccessPoint', () => {
beforeEach(() => {
vi.clearAllMocks()
appMode = AppModeEnum.WORKFLOW
appMode = 'workflow'
appPermissionKeys = [AppACLPermission.AccessPointView]
})
@@ -243,7 +243,7 @@ describe('AccessPoint', () => {
})
it('hides environment tabs for app types without multi-environment support', () => {
appMode = AppModeEnum.CHAT
appMode = 'chat'
renderAccessPoint()
@@ -8,7 +8,6 @@ import { toast } from '@/app/notifications'
import { render } from '@/test/console/render'
import { createAppDetailFixture, createAppModelConfigFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { MCPAccessPointCard } from '../built-in-access-points/mcp-card'
const mocks = vi.hoisted(() => ({
@@ -65,7 +64,7 @@ vi.mock('@/app/components/tools/mcp/mcp-server-modal', () => ({
const appInfo = createAppDetailFixture({
api_base_url: 'https://api.example.test/v1',
id: 'app-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: createAppModelConfigFixture({
updated_at: 1_710_000_000,
user_input_form: [
@@ -82,7 +81,7 @@ const appInfo = createAppDetailFixture({
const workflowAppInfo = createAppDetailFixture({
...appInfo,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
model_config: null,
})
@@ -1,4 +1,4 @@
import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen'
import type { AppDetailWithSite, AppMode } from '@dify/contracts/api/console/apps/types.gen'
import { QueryClientProvider, useSuspenseQuery } from '@tanstack/react-query'
import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
@@ -7,7 +7,6 @@ import { consoleQuery } from '@/service/console'
import { render } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { ServiceApiAccessPointCard } from '../built-in-access-points/service-api-card'
const mocks = vi.hoisted(() => ({
@@ -53,7 +52,7 @@ vi.mock('../built-in-access-points/api-secret-key-button', () => ({
}))
function createAppInfo(
mode: AppModeEnum,
mode: AppMode,
overrides: Partial<AppDetailWithSite> = {},
): AppDetailWithSite {
return createAppDetailFixture({
@@ -66,7 +65,7 @@ function createAppInfo(
}
function renderCard(
mode: AppModeEnum,
mode: AppMode,
availability: 'available' | 'loading' | 'unavailable' = 'available',
canManage = true,
overrides: Partial<AppDetailWithSite> = {},
@@ -124,12 +123,12 @@ describe('ServiceApiAccessPointCard', () => {
})
it.each([
[AppModeEnum.ADVANCED_CHAT, '/api-reference/guides/chatflow'],
[AppModeEnum.WORKFLOW, '/api-reference/guides/workflow'],
[AppModeEnum.CHAT, '/api-reference/guides/chat'],
[AppModeEnum.AGENT_CHAT, '/api-reference/guides/chat'],
[AppModeEnum.COMPLETION, '/api-reference/guides/completion'],
])('links %s apps to the matching external API reference', (mode, path) => {
['advanced-chat', '/api-reference/guides/chatflow'],
['workflow', '/api-reference/guides/workflow'],
['chat', '/api-reference/guides/chat'],
['agent-chat', '/api-reference/guides/chat'],
['completion', '/api-reference/guides/completion'],
] as const)('links %s apps to the matching external API reference', (mode, path) => {
renderCard(mode)
const apiReferenceLink = screen.getByRole('link', { name: /apiInfo\.doc/ })
@@ -141,7 +140,7 @@ describe('ServiceApiAccessPointCard', () => {
it('updates API status through the generated contract', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.WORKFLOW)
renderCard('workflow')
await user.click(screen.getByRole('switch'))
@@ -154,7 +153,7 @@ describe('ServiceApiAccessPointCard', () => {
})
it('shows loading without reporting an environment failure', () => {
renderCard(AppModeEnum.WORKFLOW, 'loading')
renderCard('workflow', 'loading')
const card = screen.getByRole('region', { name: /serviceApi\.title/ })
expect(card).toHaveAttribute('aria-busy', 'true')
@@ -165,7 +164,7 @@ describe('ServiceApiAccessPointCard', () => {
})
it('keeps API keys and external documentation available when the API is stopped', () => {
renderCard(AppModeEnum.WORKFLOW, 'available', true, { enable_api: false })
renderCard('workflow', 'available', true, { enable_api: false })
expect(screen.getByRole('button', { name: 'api-secret-keys' })).toBeEnabled()
const apiReferenceLink = screen.getByRole('link', { name: /apiInfo\.doc/ })
@@ -176,14 +175,14 @@ describe('ServiceApiAccessPointCard', () => {
})
it('disables API management without Access Point management permission', () => {
renderCard(AppModeEnum.WORKFLOW, 'available', false)
renderCard('workflow', 'available', false)
expect(screen.getByRole('button', { name: 'api-secret-keys' })).toBeDisabled()
expect(screen.getByRole('switch')).toHaveAttribute('aria-disabled', 'true')
})
it('disables API keys and external documentation when the access point is unavailable', () => {
renderCard(AppModeEnum.WORKFLOW, 'unavailable')
renderCard('workflow', 'unavailable')
expect(screen.getByRole('button', { name: 'api-secret-keys' })).toBeDisabled()
expect(screen.getByRole('button', { name: /apiInfo\.doc/ })).toBeDisabled()
@@ -193,7 +192,7 @@ describe('ServiceApiAccessPointCard', () => {
const user = userEvent.setup()
const toggle = createDeferredPromise<{ enable_api: boolean }>()
mocks.apiEnable.mockReturnValueOnce(toggle.promise)
renderCard(AppModeEnum.WORKFLOW)
renderCard('workflow')
const accessSwitch = screen.getByRole('switch')
await user.click(accessSwitch)
@@ -212,7 +211,7 @@ describe('ServiceApiAccessPointCard', () => {
it('keeps successful status changes silent', async () => {
const user = userEvent.setup()
mocks.apiEnable.mockResolvedValueOnce(createAppDetailFixture({ enable_api: false }))
renderCard(AppModeEnum.WORKFLOW)
renderCard('workflow')
const accessSwitch = screen.getByRole('switch')
await user.click(accessSwitch)
@@ -6,7 +6,6 @@ import { toast } from '@/app/notifications'
import { render } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { TriggerAccessPointCard } from '../built-in-access-points/trigger-card'
const mocks = vi.hoisted(() => ({
@@ -71,7 +70,7 @@ vi.mock('@/app/components/workflow/block-icon', () => ({
const appInfo = createAppDetailFixture({
id: 'app-1',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
})
function createTrigger(id: string, status: AppTrigger['status']): AppTrigger {
@@ -1,4 +1,8 @@
import type { AppDetailWithSite, AppSiteResponse } from '@dify/contracts/api/console/apps/types.gen'
import type {
AppDetailWithSite,
AppMode,
AppSiteResponse,
} from '@dify/contracts/api/console/apps/types.gen'
import type { PublishedWorkflow } from '../shared/utils'
import type { InputVar, Node } from '@/app/components/workflow/types'
import { QueryClientProvider, useSuspenseQuery } from '@tanstack/react-query'
@@ -12,7 +16,6 @@ import { seedAccountProfileQuery } from '@/test/console/account-profile'
import { render } from '@/test/console/render'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { basePath } from '@/utils/var'
import { WebAppAccessPointCard } from '../built-in-access-points/web-app-card'
@@ -92,7 +95,7 @@ vi.mock('@/context/i18n', () => ({
useDocLink: () => (path: string) => `https://docs.example.test/en${path}`,
}))
function createAppInfo(mode: AppModeEnum): AppDetailWithSite {
function createAppInfo(mode: AppMode): AppDetailWithSite {
return createAppDetailFixture({
access_mode: AccessMode.PUBLIC,
api_base_url: 'https://api.example.test/v1',
@@ -111,7 +114,7 @@ function createAppInfo(mode: AppModeEnum): AppDetailWithSite {
}
function renderCard(
mode: AppModeEnum,
mode: AppMode,
availability: 'available' | 'loading' | 'unavailable' = 'available',
workflow?: PublishedWorkflow,
{
@@ -259,7 +262,7 @@ describe('WebAppAccessPointCard', () => {
})
it('disables site actions when the app has no site', () => {
renderCard(AppModeEnum.CHAT, 'available', undefined, { appOverrides: { site: null } })
renderCard('chat', 'available', undefined, { appOverrides: { site: null } })
expect(screen.getByRole('button', { name: /settings\.settings/ })).toBeDisabled()
expect(
@@ -272,7 +275,7 @@ describe('WebAppAccessPointCard', () => {
})
it('does not present an unknown access mode as public or allow launching', () => {
renderCard(AppModeEnum.CHAT, 'available', undefined, { accessMode: null })
renderCard('chat', 'available', undefined, { accessMode: null })
expect(
screen.queryByRole('button', { name: /accessControlDialog\.accessItems\.anyone/ }),
@@ -287,7 +290,7 @@ describe('WebAppAccessPointCard', () => {
})
it('shows the current access mode without a redundant section label', () => {
renderCard(AppModeEnum.CHAT)
renderCard('chat')
expect(screen.queryByText(/publishApp\.title/)).not.toBeInTheDocument()
expect(
@@ -298,7 +301,7 @@ describe('WebAppAccessPointCard', () => {
it('disables Open and explains when the user cannot access the Web App', async () => {
const user = userEvent.setup()
mocks.getUserCanAccess.mockResolvedValue({ result: false })
renderCard(AppModeEnum.WORKFLOW, 'available', undefined, {
renderCard('workflow', 'available', undefined, {
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
})
@@ -319,7 +322,7 @@ describe('WebAppAccessPointCard', () => {
it('keeps Open disabled until access is granted', async () => {
const permission = createDeferredPromise<{ result: boolean }>()
mocks.getUserCanAccess.mockReturnValueOnce(permission.promise)
renderCard(AppModeEnum.WORKFLOW)
renderCard('workflow')
expect(screen.getByRole('button', { name: /studio\.accessPoint\.open/ })).toHaveAttribute(
'aria-disabled',
@@ -336,7 +339,7 @@ describe('WebAppAccessPointCard', () => {
it('allows opening external-member Web Apps independently of platform access', () => {
mocks.getUserCanAccess.mockResolvedValue({ result: false })
renderCard(AppModeEnum.WORKFLOW, 'available', undefined, {
renderCard('workflow', 'available', undefined, {
accessMode: AccessMode.EXTERNAL_MEMBERS,
})
@@ -347,7 +350,7 @@ describe('WebAppAccessPointCard', () => {
})
it('allows opening Web Apps without querying permissions when access control is disabled', () => {
renderCard(AppModeEnum.CHAT, 'available', undefined, { showAccessControl: false })
renderCard('chat', 'available', undefined, { showAccessControl: false })
expect(screen.getByRole('link', { name: /studio\.accessPoint\.open/ })).toHaveAttribute(
'href',
@@ -359,7 +362,7 @@ describe('WebAppAccessPointCard', () => {
it('updates Open after saving Web App access permissions', async () => {
const user = userEvent.setup()
mocks.getUserCanAccess.mockResolvedValue({ result: false })
renderCard(AppModeEnum.CHAT, 'available', undefined, {
renderCard('chat', 'available', undefined, {
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
})
@@ -380,7 +383,7 @@ describe('WebAppAccessPointCard', () => {
)
})
it.each([AppModeEnum.WORKFLOW, AppModeEnum.COMPLETION])(
it.each(['workflow', 'completion'] as const)(
'does not offer Embed into site for %s apps',
(mode) => {
renderCard(mode)
@@ -393,7 +396,7 @@ describe('WebAppAccessPointCard', () => {
it('keeps Embed into site for non-workflow Web apps', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.CHAT)
renderCard('chat')
await user.click(screen.getByRole('button', { name: /embedIntoSite/ }))
@@ -404,7 +407,7 @@ describe('WebAppAccessPointCard', () => {
it('updates site status through the generated contract', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.CHAT)
renderCard('chat')
await user.click(screen.getByRole('switch'))
@@ -418,7 +421,7 @@ describe('WebAppAccessPointCard', () => {
it('keeps site status changes behind Access Point management permission', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.CHAT, 'available', undefined, { canManageAccessPoint: false })
renderCard('chat', 'available', undefined, { canManageAccessPoint: false })
await user.click(screen.getByRole('switch'))
@@ -429,7 +432,7 @@ describe('WebAppAccessPointCard', () => {
const user = userEvent.setup()
const refresh = createDeferredPromise<AppDetailWithSite | null>()
mocks.getAppDetail.mockReturnValueOnce(refresh.promise)
renderCard(AppModeEnum.CHAT)
renderCard('chat')
await user.click(screen.getByRole('button', { name: /overview\.appInfo\.regenerate/ }))
await user.click(screen.getByRole('button', { name: /operation\.confirm/ }))
@@ -456,7 +459,7 @@ describe('WebAppAccessPointCard', () => {
const user = userEvent.setup()
const error = new Error('request failed')
mocks.siteEnable.mockRejectedValueOnce(error)
renderCard(AppModeEnum.CHAT)
renderCard('chat')
await user.click(screen.getByRole('switch'))
@@ -467,7 +470,7 @@ describe('WebAppAccessPointCard', () => {
it('passes hidden Chatflow inputs to the embed dialog', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.ADVANCED_CHAT, 'available', workflowWithHiddenInput)
renderCard('advanced-chat', 'available', workflowWithHiddenInput)
await user.click(screen.getByRole('button', { name: /embedIntoSite/ }))
@@ -478,7 +481,7 @@ describe('WebAppAccessPointCard', () => {
it('configures hidden workflow inputs before opening the Web App', async () => {
const user = userEvent.setup()
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
renderCard(AppModeEnum.WORKFLOW, 'available', workflowWithHiddenInput)
renderCard('workflow', 'available', workflowWithHiddenInput)
await user.click(screen.getByRole('button', { name: /operation\.config/ }))
await user.type(screen.getByLabelText('Secret'), 'top-secret')
@@ -496,7 +499,7 @@ describe('WebAppAccessPointCard', () => {
const user = userEvent.setup()
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
mocks.getUserCanAccess.mockResolvedValue({ result: false })
renderCard(AppModeEnum.WORKFLOW, 'available', workflowWithHiddenInput, {
renderCard('workflow', 'available', workflowWithHiddenInput, {
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
})
@@ -514,7 +517,7 @@ describe('WebAppAccessPointCard', () => {
})
it('shows loading without reporting an environment failure', () => {
renderCard(AppModeEnum.WORKFLOW, 'loading')
renderCard('workflow', 'loading')
const card = screen.getByRole('region', { name: /webApp\.title/ })
expect(card).toHaveAttribute('aria-busy', 'true')
@@ -525,7 +528,7 @@ describe('WebAppAccessPointCard', () => {
})
it('does not announce an unavailable access control entry as loading', () => {
renderCard(AppModeEnum.WORKFLOW, 'unavailable')
renderCard('workflow', 'unavailable')
const card = screen.getByRole('region', { name: /webApp\.title/ })
expect(within(card).queryByRole('status', { name: 'common.loading' })).not.toBeInTheDocument()
@@ -538,7 +541,7 @@ describe('WebAppAccessPointCard', () => {
mocks.siteEnable
.mockReturnValueOnce(firstToggle.promise)
.mockReturnValueOnce(secondToggle.promise)
renderCard(AppModeEnum.CHAT)
renderCard('chat')
const accessSwitch = screen.getByRole('switch')
await user.click(accessSwitch)
@@ -573,7 +576,7 @@ describe('WebAppAccessPointCard', () => {
it('disables Web App management actions without Access Point management', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.CHAT, 'available', undefined, { canManageAccessPoint: false })
renderCard('chat', 'available', undefined, { canManageAccessPoint: false })
expect(screen.getByRole('switch')).toHaveAttribute('aria-disabled', 'true')
expect(screen.getByRole('button', { name: /embedIntoSite/ })).toBeDisabled()
@@ -26,7 +26,6 @@ import {
useMCPServerDetail,
useRefreshMCPServerCode,
} from '@/service/use-tools'
import { AppModeEnum } from '@/types/app'
import { useAccessPointStatusLabel } from '../shared/use-access-point-status-label'
import { getPublishedWorkflowNodes, isAdvancedApp } from '../shared/utils'
@@ -50,7 +49,7 @@ export function MCPAccessPointCard({
const { t } = useTranslation(['appOverview', 'common', 'deployments', 'tools'])
const advancedApp = isAdvancedApp(appInfo)
const basicApp = !advancedApp
const workflowApp = appInfo.mode === AppModeEnum.WORKFLOW
const workflowApp = appInfo.mode === 'workflow'
const [showServerModal, setShowServerModal] = useState(false)
const [showRegenerate, setShowRegenerate] = useState(false)
const basicConfig = appInfo.model_config
@@ -37,7 +37,6 @@ import {
useGetUserCanAccessApp,
} from '@/service/access-control/use-app-access-control'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { useAccessPointStatusLabel } from '../shared/use-access-point-status-label'
import { getBuiltInAccessUrls, getHiddenStartInputs } from '../shared/utils'
import {
@@ -122,8 +121,7 @@ export function WebAppAccessPointCard({
: appInfo.enable_site
const running = siteAvailability === 'available' && optimisticEnabled
const actionsAvailable = running && Boolean(webAppUrl) && !toggleSiteMutation.isPending
const supportsEmbedded =
appInfo.mode !== AppModeEnum.COMPLETION && appInfo.mode !== AppModeEnum.WORKFLOW
const supportsEmbedded = appInfo.mode !== 'completion' && appInfo.mode !== 'workflow'
const hiddenLaunchVariables = getHiddenStartInputs(workflow)
const { data: accessSubjects } = useAppWhiteListSubjects(
appInfo.id,
@@ -220,9 +218,7 @@ export function WebAppAccessPointCard({
/>
<SettingsDialog
key={appInfo.id}
isChat={
appInfo.mode !== AppModeEnum.COMPLETION && appInfo.mode !== AppModeEnum.WORKFLOW
}
isChat={appInfo.mode !== 'completion' && appInfo.mode !== 'workflow'}
canDeploy={canDeploy}
appInfo={site ? { id: appInfo.id, mode: appInfo.mode, site } : undefined}
disabled={siteAvailability !== 'available' || !canManageAccessPoint}
@@ -1,10 +1,9 @@
import type { EnvironmentSite } from '@dify/contracts/enterprise-app-deploy/types.gen'
import { AppModeEnum } from '@/types/app'
import { basePath } from '@/utils/var'
export function getEnvironmentWebAppUrl(site?: EnvironmentSite, mode?: string) {
if (!site?.app_base_url || !site.code) return ''
const route = mode === AppModeEnum.ADVANCED_CHAT ? 'chat' : 'workflow'
const route = mode === 'advanced-chat' ? 'chat' : 'workflow'
return `${site.app_base_url.replace(/\/$/, '')}${basePath}/environment/${route}/${site.code}`
}
@@ -16,7 +16,6 @@ import { ACCESS_POINT_ORDER } from '@/app/components/app/deploy/utils/access-poi
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getAppACLCapabilities } from '@/utils/permission'
import { BuiltInAccessPoints } from './built-in-access-points'
import { DeployedEnvironmentAccessPoints } from './deployed-environment-access-points'
@@ -162,7 +161,7 @@ export default function AccessPoint({ appId }: AccessPointProps) {
workspacePermissionKeys,
})
const showEnvironmentTabs =
(appDetail?.mode === AppModeEnum.WORKFLOW || appDetail?.mode === AppModeEnum.ADVANCED_CHAT) &&
(appDetail?.mode === 'workflow' || appDetail?.mode === 'advanced-chat') &&
capabilities.canViewAccessPoint
return (
@@ -5,7 +5,6 @@ import type {
import type { InputVar, Node } from '@/app/components/workflow/types'
import type { DocPathWithoutLang } from '@/types/doc-paths'
import { BlockEnum, isTriggerNode } from '@/app/components/workflow/types'
import { AppModeEnum } from '@/types/app'
import { basePath } from '@/utils/var'
export type PublishedWorkflow = WorkflowResponse | null | undefined
@@ -25,7 +24,7 @@ export function getAppApiReferencePath(appMode: AppDetailWithSite['mode']) {
}
export function getPublishedWorkflowState(appInfo: AppDetailWithSite, workflow: PublishedWorkflow) {
const isWorkflowApp = appInfo.mode === AppModeEnum.WORKFLOW
const isWorkflowApp = appInfo.mode === 'workflow'
const nodes = getPublishedWorkflowNodes(workflow)
const hasStartNode = nodes.some((node) => node.data.type === BlockEnum.Start)
const hasTriggerNode = nodes.some((node) => isTriggerNode(node.data.type))
@@ -46,9 +45,7 @@ export function getPublishedWorkflowNodes(workflow: PublishedWorkflow) {
export function getBuiltInAccessUrls(appInfo: AppDetailWithSite) {
const appMode =
appInfo.mode === AppModeEnum.COMPLETION || appInfo.mode === AppModeEnum.WORKFLOW
? appInfo.mode
: AppModeEnum.CHAT
appInfo.mode === 'completion' || appInfo.mode === 'workflow' ? appInfo.mode : 'chat'
return {
api: appInfo.api_base_url ?? '',
@@ -69,5 +66,5 @@ export function getHiddenStartInputs(workflow: PublishedWorkflow) {
}
export function isAdvancedApp(appInfo: AppDetailWithSite) {
return appInfo.mode === AppModeEnum.WORKFLOW || appInfo.mode === AppModeEnum.ADVANCED_CHAT
return appInfo.mode === 'workflow' || appInfo.mode === 'advanced-chat'
}
@@ -17,7 +17,6 @@ import {
} from '@/service/annotation'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import Annotation from '../index'
import { AnnotationEnableStatus, JobStatus } from '../type'
@@ -181,7 +180,7 @@ const updateAnnotationStatusMock = updateAnnotationStatus as Mock
const appDetail = createAppDetailFixture({
id: 'app-id',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
})
const createAnnotation = (overrides: Partial<AnnotationItem> = {}): AnnotationItem => ({
+1 -2
View File
@@ -35,7 +35,6 @@ import {
updateAnnotationStatus,
} from '@/service/annotation'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { sleep } from '@/utils'
import PageTitle from '../log-annotation/page-title'
import EmptyElement from './empty-element'
@@ -55,7 +54,7 @@ const Annotation: FC<Props> = (props) => {
const docLink = useDocLink()
const [isShowEdit, setIsShowEdit] = useState(false)
const [annotationConfig, setAnnotationConfig] = useState<AnnotationReplyConfig | null>(null)
const [isChatApp] = useState(appDetail.mode !== AppModeEnum.COMPLETION)
const [isChatApp] = useState(appDetail.mode !== 'completion')
const [controlRefreshSwitch, setControlRefreshSwitch] = useState(() => Date.now())
const deploymentEdition = useAtomValue(deploymentEditionAtom)
const { data: annotationQuota } = useQuery(
@@ -11,7 +11,6 @@ import {
renderWithConsoleQuery as renderWithQuery,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import { basePath } from '@/utils/var'
import { AppPublisher } from '../index'
@@ -269,7 +268,7 @@ describe('AppPublisher', () => {
mockAppDetail = {
id: 'app-1',
name: 'Demo App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
maintainer: 'user-2',
permission_keys: [],
access_mode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
@@ -346,7 +345,7 @@ describe('AppPublisher', () => {
const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries')
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.Deploy],
}
mockOnPublish.mockResolvedValue(undefined)
@@ -398,7 +397,7 @@ describe('AppPublisher', () => {
const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries')
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [],
}
mockOnPublish.mockResolvedValue(undefined)
@@ -423,7 +422,7 @@ describe('AppPublisher', () => {
const user = userEvent.setup()
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockPublishedWorkflow = {
created_at: Math.floor(Date.now() / 1000),
@@ -458,7 +457,7 @@ describe('AppPublisher', () => {
expect(mockUpdateWorkflow).toHaveBeenCalledWith(
{
appId: 'app-1',
appMode: AppModeEnum.WORKFLOW,
appMode: 'workflow' as const,
url: '/apps/app-1/workflows/workflow-version-5',
title: 'Release 6',
releaseNotes: 'Updated notes',
@@ -480,7 +479,7 @@ describe('AppPublisher', () => {
const queryClient = createConsoleQueryClient()
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.Deploy],
}
const environmentsQuery =
@@ -513,7 +512,7 @@ describe('AppPublisher', () => {
it('should keep the workflow publisher single-environment without app deploy ACL', () => {
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [],
}
@@ -581,7 +580,7 @@ describe('AppPublisher', () => {
}
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.Deploy],
}
const environmentsQuery =
@@ -684,7 +683,7 @@ describe('AppPublisher', () => {
const detailRequests: Request[] = []
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.Deploy],
}
const environmentsQuery =
@@ -832,7 +831,7 @@ describe('AppPublisher', () => {
it('should keep workflow tool drawer mounted after closing the publish popover', () => {
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
render(<AppPublisher appId="app-1" publishedAt={Date.now()} toolPublished />)
@@ -853,7 +852,7 @@ describe('AppPublisher', () => {
it('should show one success toast when automatically publishing a workflow tool', async () => {
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockOnPublish.mockImplementation(async (_params, options?: { showSuccessToast?: boolean }) => {
if (options?.showSuccessToast !== false) mockToastSuccess('common.api.actionSuccess')
@@ -876,7 +875,7 @@ describe('AppPublisher', () => {
it('should not show a success toast when workflow tool creation fails after publishing', async () => {
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockOnPublish.mockImplementation(async (_params, options?: { showSuccessToast?: boolean }) => {
if (options?.showSuccessToast !== false) mockToastSuccess('common.api.actionSuccess')
@@ -900,7 +899,7 @@ describe('AppPublisher', () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockOnPublish.mockRejectedValueOnce(new Error('publish failed'))
@@ -922,7 +921,7 @@ describe('AppPublisher', () => {
mockWorkspacePermissionKeys = []
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
render(<AppPublisher appId="app-1" publishedAt={Date.now()} />)
@@ -1066,7 +1065,7 @@ describe('AppPublisher', () => {
const user = userEvent.setup()
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockPublishedWorkflow = {
created_at: 1_710_000_100,
@@ -1102,7 +1101,7 @@ describe('AppPublisher', () => {
}
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockFetchPublishedWorkflow.mockResolvedValue({
created_at: 1_710_000_300,
@@ -2,7 +2,6 @@ import { detectPlatform } from '@tanstack/react-hotkeys'
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useState } from 'react'
import { AppModeEnum } from '@/types/app'
import { PublisherPanel } from '../publisher-content/publisher-panel'
vi.mock('../environment-deployment-flow', () => ({
@@ -38,7 +37,7 @@ function PublisherPanelHarness({
builtInPublisher={{
actions: {
appId: 'app-1',
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
appURL: 'https://example.com/app',
canViewAccessPoint: false,
disabledFunctionButton: false,
@@ -8,7 +8,6 @@ import { useRef } from 'react'
import { expectLoadingButton } from '@/test/button'
import { createConsoleQueryWrapper } from '@/test/console/query-data'
import { render as renderWithConsoleState } from '@/test/console/render'
import { AppModeEnum } from '@/types/app'
import { PublisherActionsSection } from '../built-in-publisher/actions-section'
import { PublisherSummarySection } from '../built-in-publisher/summary-section'
import { usePublishController } from '../publisher-content/use-publish-controller'
@@ -82,7 +81,7 @@ function PublisherPopup({
function PublishingPopup({ onPublish }: { onPublish: () => Promise<void> }) {
const publish = usePublishController({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
supportsMultiEnvironment: false,
onClose: () => {},
onPublish,
@@ -535,7 +534,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton={false}
@@ -595,7 +594,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton={false}
@@ -626,7 +625,7 @@ describe('app-publisher sections', () => {
it('should show the disabled reason below setup and configured workflow tool actions', () => {
const commonProps = {
appId: 'workflow-app',
appMode: AppModeEnum.WORKFLOW,
appMode: 'workflow' as const,
appURL: 'https://example.com/app',
canViewAccessPoint: true,
disabledFunctionButton: false,
@@ -666,7 +665,7 @@ describe('app-publisher sections', () => {
const onConfigureWorkflowTool = vi.fn()
const commonProps = {
appId: 'workflow-app',
appMode: AppModeEnum.WORKFLOW,
appMode: 'workflow' as const,
appURL: 'https://example.com/app',
canViewAccessPoint: true,
disabledFunctionButton: false,
@@ -710,7 +709,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'trigger-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton={false}
@@ -740,7 +739,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint={false}
disabledFunctionButton={false}
@@ -765,7 +764,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton
@@ -795,7 +794,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton
@@ -822,7 +821,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton
@@ -1,17 +1,16 @@
import type { TFunction } from 'i18next'
import { withSelectorKey } from '@/test/i18n-mock'
import { AppModeEnum } from '@/types/app'
import { basePath } from '@/utils/var'
import { getDisabledFunctionTooltip, getPublisherAppMode, getPublisherAppUrl } from '../utils'
describe('app-publisher utils', () => {
describe('getPublisherAppMode', () => {
it('should normalize chat-like apps to chat mode', () => {
expect(getPublisherAppMode(AppModeEnum.AGENT_CHAT)).toBe(AppModeEnum.CHAT)
expect(getPublisherAppMode('agent-chat')).toBe('chat')
})
it('should keep completion mode unchanged', () => {
expect(getPublisherAppMode(AppModeEnum.COMPLETION)).toBe(AppModeEnum.COMPLETION)
expect(getPublisherAppMode('completion')).toBe('completion')
})
})
@@ -21,7 +20,7 @@ describe('app-publisher utils', () => {
getPublisherAppUrl({
appBaseUrl: 'https://example.com',
accessToken: 'token-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
}),
).toBe(`https://example.com${basePath}/chat/token-1`)
})
@@ -3,7 +3,6 @@ import type { AppPublisherProps } from '../types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useTranslation } from 'react-i18next'
import { AppModeEnum } from '@/types/app'
import SuggestedAction from '../suggested-action'
import WorkflowToolAction from '../workflow-tool-action'
@@ -58,7 +57,7 @@ export function PublisherActionsSection({
const hasPublishedVersion = Boolean(publishedAt)
const showOpenWebApp = !hasTriggerNode
const showWorkflowTool = appMode === AppModeEnum.WORKFLOW && !hasHumanInputNode && !hasTriggerNode
const showWorkflowTool = appMode === 'workflow' && !hasHumanInputNode && !hasTriggerNode
const navigationDisabled = !hasPublishedVersion
const workflowToolDisabled =
!hasPublishedVersion || !workflowToolAvailable || (toolPublished && workflowToolIsLoading)
@@ -7,13 +7,12 @@ import { useTranslation } from 'react-i18next'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getAppACLCapabilities } from '@/utils/permission'
import { PublisherContent } from './publisher-content'
import { appPublisherOpenAtom, AppPublisherStateBoundary } from './state'
export function AppPublisher(props: AppPublisherProps) {
const { t } = useTranslation(['workflow'])
const { t } = useTranslation(['workflow'] as const)
return (
<Suspense
fallback={
@@ -45,8 +44,7 @@ function AppPublisherContent(props: AppPublisherProps) {
workspacePermissionKeys,
})
const supportsMultiEnvironment =
(appDetail.mode === AppModeEnum.WORKFLOW || appDetail.mode === AppModeEnum.ADVANCED_CHAT) &&
canDeploy
(appDetail.mode === 'workflow' || appDetail.mode === 'advanced-chat') && canDeploy
return (
<AppPublisherStateBoundary
@@ -18,7 +18,6 @@ import {
appWorkflowQueryOptions,
appWorkflowVersionsInfiniteQueryOptions,
} from '@/service/workflow-queries'
import { AppModeEnum } from '@/types/app'
type UsePublishControllerParams = Pick<
AppPublisherProps,
@@ -66,11 +65,8 @@ export function usePublishController({
const queryClient = useQueryClient()
const workflowStore = use(WorkflowContext)
const invalidateAppWorkflow = useInvalidateAppWorkflow()
const isWorkflowApp = appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT
const isChatApp =
appMode === AppModeEnum.CHAT ||
appMode === AppModeEnum.AGENT_CHAT ||
appMode === AppModeEnum.COMPLETION
const isWorkflowApp = appMode === 'workflow' || appMode === 'advanced-chat'
const isChatApp = appMode === 'chat' || appMode === 'agent-chat' || appMode === 'completion'
const {
data: publishedWorkflow,
isError: isPublishedWorkflowError,
@@ -7,7 +7,6 @@ import { useTranslation } from 'react-i18next'
import { useCanManageTools } from '@/app/components/tools/hooks/use-tool-permissions'
import { useConfigureButton } from '@/app/components/tools/workflow-tool/hooks/use-configure-button'
import { appDefaultIconBackground } from '@/config'
import { AppModeEnum } from '@/types/app'
type UseWorkflowToolParams = {
appDescription?: string
@@ -50,10 +49,10 @@ export function useWorkflowTool({
toolPublished,
workflowToolAvailable,
}: UseWorkflowToolParams) {
const { t } = useTranslation(['workflow'])
const { t } = useTranslation(['workflow'] as const)
const [drawerOpen, setDrawerOpen] = useState(false)
const canManageTools = useCanManageTools()
const visible = appMode === AppModeEnum.WORKFLOW && !hasHumanInputNode && !hasTriggerNode
const visible = appMode === 'workflow' && !hasHumanInputNode && !hasTriggerNode
const published = Boolean(toolPublished)
const message =
!hasPublishedVersion || !workflowToolAvailable
@@ -1,10 +1,9 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { TFunction } from 'i18next'
import { AppModeEnum } from '@/types/app'
import { basePath } from '@/utils/var'
export const getPublisherAppMode = (mode?: AppMode) => {
if (mode !== AppModeEnum.COMPLETION && mode !== AppModeEnum.WORKFLOW) return AppModeEnum.CHAT
if (mode !== 'completion' && mode !== 'workflow') return 'chat'
return mode
}
@@ -9,7 +9,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { CollectionType } from '@/app/components/tools/types'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import ConfigurationView from '../configuration-view'
const mockIsAgentV2Enabled = vi.hoisted(() => vi.fn(() => false))
@@ -96,7 +96,7 @@ const createContextValue = (): ComponentProps<typeof ConfigContext.Provider>['va
appId: 'app-1',
onOpenFeatures: vi.fn(),
isTrailFinished: false,
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.chat,
promptMode: 'simple' as never,
setPromptMode: vi.fn(),
@@ -351,7 +351,7 @@ describe('ConfigurationView', () => {
it('should show the legacy Agent badge for legacy Agent apps when Agent v2 is enabled', async () => {
mockIsAgentV2Enabled.mockReturnValue(true)
const contextValue = createContextValue()
contextValue.mode = AppModeEnum.AGENT_CHAT
contextValue.mode = 'agent-chat'
render(<ConfigurationView {...createViewModel({ contextValue })} />)
@@ -375,7 +375,7 @@ describe('ConfigurationView', () => {
it('should not show the legacy Agent badge when Agent v2 is disabled', () => {
const contextValue = createContextValue()
contextValue.mode = AppModeEnum.AGENT_CHAT
contextValue.mode = 'agent-chat'
render(<ConfigurationView {...createViewModel({ contextValue })} />)
@@ -1,5 +1,5 @@
import type { ModelConfig } from '@/models/debug'
import { AppModeEnum, ModelModeType, Resolution, TransferMethod } from '@/types/app'
import { ModelModeType, Resolution, TransferMethod } from '@/types/app'
import {
buildConfigurationFeaturesData,
getConfigurationPublishingState,
@@ -129,7 +129,7 @@ describe('configuration utils', () => {
hasSetContextVar: false,
hasSelectedDataSets: false,
isAdvancedMode: true,
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.completion,
promptTemplate: 'ignored',
})
@@ -158,7 +158,7 @@ describe('configuration utils', () => {
hasSetContextVar: false,
hasSelectedDataSets: true,
isAdvancedMode: false,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelModeType: ModelModeType.completion,
promptTemplate: 'Prompt',
})
@@ -188,7 +188,7 @@ describe('configuration utils', () => {
hasSetContextVar: true,
hasSelectedDataSets: false,
isAdvancedMode: true,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelModeType: ModelModeType.chat,
promptTemplate: 'ignored',
})
@@ -217,7 +217,7 @@ describe('configuration utils', () => {
hasSetContextVar: true,
hasSelectedDataSets: false,
isAdvancedMode: true,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelModeType: ModelModeType.completion,
promptTemplate: 'ignored',
})
@@ -4,7 +4,6 @@ import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum } from '@/types/app'
import AdvancedPromptInput from '../advanced-prompt-input'
const mockEmit = vi.fn()
@@ -82,7 +81,7 @@ vi.mock('../prompt-editor-height-resize-wrap', () => ({
const createContextValue = () =>
({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
hasSetBlockStatus: {
context: false,
history: false,
@@ -5,7 +5,7 @@ import * as React from 'react'
import { MAX_PROMPT_MESSAGE_LENGTH } from '@/config'
import ConfigContext from '@/context/debug-configuration'
import { PromptRole } from '@/models/debug'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import Prompt from '../index'
type DebugConfiguration = {
@@ -113,7 +113,7 @@ const renderComponent = (
contextOverrides: Partial<DebugConfiguration> = {},
) => {
const mergedProps: IPromptProps = {
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
promptTemplate: 'initial template',
promptVariables: defaultPromptVariables,
onChange: vi.fn(),
@@ -144,7 +144,7 @@ describe('Prompt config component', () => {
const simplePrompt = screen.getByTestId('simple-prompt-input')
expect(simplePrompt)!.toBeInTheDocument()
expect(simplePrompt)!.toHaveAttribute('data-mode', AppModeEnum.CHAT)
expect(simplePrompt)!.toHaveAttribute('data-mode', 'chat')
expect(mockSimplePromptInputProps?.promptTemplate).toBe('initial template')
fireEvent.click(simplePrompt)
expect(onChange).toHaveBeenCalledWith('mocked prompt', defaultPromptVariables)
@@ -1,5 +1,4 @@
import { render, screen } from '@testing-library/react'
import { AppModeEnum } from '@/types/app'
import Prompt from '../simple-prompt-input'
vi.mock('@/app/components/base/features/hooks', () => ({
@@ -9,7 +8,7 @@ vi.mock('@/app/components/base/features/hooks', () => ({
}))
describe('SimplePromptInput accessibility', () => {
it.each([AppModeEnum.CHAT, AppModeEnum.COMPLETION])(
it.each(['chat', 'completion'] as const)(
'names the real editor from the visible section heading in %s mode',
(mode) => {
render(<Prompt mode={mode} promptTemplate="" promptVariables={[]} noResize />)
@@ -22,9 +21,7 @@ describe('SimplePromptInput accessibility', () => {
)
it('keeps an accessible editor name when embedded without a section heading', () => {
render(
<Prompt mode={AppModeEnum.CHAT} promptTemplate="" promptVariables={[]} noTitle noResize />,
)
render(<Prompt mode={'chat'} promptTemplate="" promptVariables={[]} noTitle noResize />)
expect(screen.getByRole('textbox', { name: 'appDebug.chatSubTitle' })).toBeInTheDocument()
expect(screen.queryByRole('heading')).not.toBeInTheDocument()
})
@@ -2,7 +2,6 @@ import type { ReactNode } from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum } from '@/types/app'
import Prompt from '../simple-prompt-input'
const mockEmit = vi.fn()
@@ -144,7 +143,7 @@ describe('SimplePromptInput', () => {
render(
<ConfigContext.Provider value={createContextValue()}>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello {{new_var}}"
promptVariables={[]}
onChange={mockOnChange}
@@ -170,7 +169,7 @@ describe('SimplePromptInput', () => {
render(
<ConfigContext.Provider value={createContextValue()}>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello"
promptVariables={[
{ key: 'existing_var', name: 'Existing', type: 'string', required: true },
@@ -215,12 +214,7 @@ describe('SimplePromptInput', () => {
it('should apply automatic generation results to prompt and opening statement', () => {
render(
<ConfigContext.Provider value={createContextValue()}>
<Prompt
mode={AppModeEnum.CHAT}
promptTemplate="Hello"
promptVariables={[]}
onChange={mockOnChange}
/>
<Prompt mode={'chat'} promptTemplate="Hello" promptVariables={[]} onChange={mockOnChange} />
</ConfigContext.Provider>,
)
@@ -275,7 +269,7 @@ describe('SimplePromptInput', () => {
})}
>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello {{existing_var}}"
promptVariables={[
{ key: 'existing_var', name: 'Existing', type: 'string', required: true },
@@ -300,7 +294,7 @@ describe('SimplePromptInput', () => {
})}
>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello {{search_api}} {{existing_var}}"
promptVariables={[
{ key: 'existing_var', name: 'Existing', type: 'string', required: true },
@@ -321,7 +315,7 @@ describe('SimplePromptInput', () => {
render(
<ConfigContext.Provider value={createContextValue()}>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello {{existing_var}}"
promptVariables={[{ key: 'existing_var', name: '', type: 'string', required: true }]}
onChange={mockOnChange}
@@ -1,4 +1,5 @@
'use client'
import type { FC } from 'react'
import type { ExternalDataTool } from '@/models/common'
import type { PromptRole, PromptVariable } from '@/models/debug'
@@ -20,7 +21,6 @@ import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/promp
import ConfigContext from '@/context/debug-configuration'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useModalContext } from '@/context/modal-context'
import { AppModeEnum } from '@/types/app'
import { getNewVar, getVars } from '@/utils/var'
import ConfirmAddVar from './confirm-add-var'
import MessageTypeSelector from './message-type-selector'
@@ -102,7 +102,7 @@ const AdvancedPromptInput: FC<Props> = ({
},
})
}
const isChatApp = mode !== AppModeEnum.COMPLETION
const isChatApp = mode !== 'completion'
const [isCopied, setIsCopied] = React.useState(false)
const promptVariablesObj = (() => {
@@ -1,7 +1,8 @@
'use client'
import type { FC } from 'react'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { PromptItem, PromptVariable } from '@/models/debug'
import type { AppModeEnum } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { RiAddLine } from '@remixicon/react'
import { produce } from 'immer'
@@ -16,7 +17,7 @@ import { ModelModeType } from '@/types/app'
import SimplePromptInput from './simple-prompt-input'
export type IPromptProps = {
mode: AppModeEnum
mode: ConfigurationAppMode
promptTemplate: string
promptVariables: PromptVariable[]
readonly?: boolean
@@ -1,5 +1,7 @@
'use client'
import type { FC } from 'react'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { ExternalDataTool } from '@/models/common'
import type { PromptVariable } from '@/models/debug'
import type { GenRes } from '@/service/debug'
@@ -24,13 +26,12 @@ import ConfigContext from '@/context/debug-configuration'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useModalContext } from '@/context/modal-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { AppModeEnum } from '@/types/app'
import { getNewVar, getVars } from '@/utils/var'
import ConfirmAddVar from './confirm-add-var'
import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap'
type ISimplePromptInput = {
mode: AppModeEnum
mode: ConfigurationAppMode
promptTemplate: string
promptVariables: PromptVariable[]
readonly?: boolean
@@ -54,7 +55,7 @@ const Prompt: FC<ISimplePromptInput> = ({
const { t } = useTranslation(['appDebug'])
const titleId = useId()
const title =
mode !== AppModeEnum.COMPLETION
mode !== 'completion'
? t(($) => $.chatSubTitle, { ns: 'appDebug' })
: t(($) => $.completionSubTitle, { ns: 'appDebug' })
const media = useBreakpoints()
@@ -66,6 +67,7 @@ const Prompt: FC<ISimplePromptInput> = ({
const {
appId,
modelConfig,
modelModeType,
dataSets,
setModelConfig,
setPrevPromptConfig,
@@ -176,7 +178,7 @@ const Prompt: FC<ISimplePromptInput> = ({
setModelConfig(newModelConfig)
setPrevPromptConfig(modelConfig.configs)
if (mode !== AppModeEnum.COMPLETION) {
if (mode !== 'completion') {
setIntroduction(res.opening_statement || '')
const newFeatures = produce(features, (draft) => {
draft.opening = {
@@ -314,7 +316,7 @@ const Prompt: FC<ISimplePromptInput> = ({
{showAutomatic && (
<GetAutomaticResModal
flowId={appId}
mode={mode as AppModeEnum}
modelMode={modelModeType}
isShow={showAutomatic}
onClose={showAutomaticFalse}
onFinished={handleAutomaticRes}
@@ -9,7 +9,6 @@ import { vi } from 'vite-plus/test'
import { toast } from '@/app/components/app/configuration/toast'
import DebugConfigurationContext from '@/context/debug-configuration'
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
import { AppModeEnum } from '@/types/app'
import ConfigVar, { ADD_EXTERNAL_DATA_TOOL } from '../index'
const toastErrorSpy = vi.spyOn(toast, 'error').mockReturnValue('toast-error')
@@ -69,7 +68,7 @@ type DebugConfigurationState = React.ComponentProps<
>['value']
const defaultDebugConfigValue = {
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
dataSets: [],
modelConfig: {
model_id: 'test-model',
@@ -399,7 +398,7 @@ describe('ConfigVar', () => {
onPromptVariablesChange,
},
{
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [{ id: 'dataset-1' } as DebugConfigurationState['dataSets'][number]],
},
)
@@ -10,7 +10,6 @@ import {
seedAppDetail,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import ConfigModal from '../index'
vi.mock('@/next/navigation', async (importOriginal) => ({
@@ -116,12 +115,12 @@ const createPayload = (overrides: Partial<InputVar> = {}): InputVar => ({
const renderConfigModal = (payload: InputVar = createPayload()) => {
const queryClient = createConsoleQueryClient()
seedAppDetail(queryClient, createAppDetailFixture({ id: 'app-1', mode: AppModeEnum.CHAT }))
seedAppDetail(queryClient, createAppDetailFixture({ id: 'app-1', mode: 'chat' as const }))
return renderWithConsoleQuery(
<DebugConfigurationContext.Provider
value={
{
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
dataSets: [],
modelConfig: { model_id: 'model-1' },
} as any
@@ -11,7 +11,7 @@ import {
seedAppDetail,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum, TransferMethod } from '@/types/app'
import { TransferMethod } from '@/types/app'
import ConfigModal from '../index'
vi.mock('@/next/navigation', async (importOriginal) => ({
@@ -37,7 +37,7 @@ vi.mock('@monaco-editor/react', async () => ({
const render = (ui: React.ReactElement) => {
const queryClient = createConsoleQueryClient()
seedAppDetail(queryClient, createAppDetailFixture({ id: 'app-1', mode: AppModeEnum.CHAT }))
seedAppDetail(queryClient, createAppDetailFixture({ id: 'app-1', mode: 'chat' as const }))
return renderWithConsoleQuery(ui, { queryClient })
}
@@ -1,4 +1,5 @@
'use client'
import type { ChangeEvent, FC } from 'react'
import type { Item as SelectItem } from './type-select'
import type { ConfigModalValidationError } from './utils'
@@ -15,7 +16,6 @@ import ConfigContext from '@/context/debug-configuration'
import { useParams } from '@/next/navigation'
import { consoleQuery } from '@/service/console'
import { commonQueryKeys } from '@/service/use-common'
import { AppModeEnum } from '@/types/app'
import {
checkKeys,
getNewVarInWorkflow,
@@ -69,8 +69,7 @@ const ConfigModal: FC<IConfigModalProps> = ({
input: appId ? { params: { app_id: appId } } : skipToken,
}),
)
const isBasicApp =
appDetail?.mode !== AppModeEnum.ADVANCED_CHAT && appDetail?.mode !== AppModeEnum.WORKFLOW
const isBasicApp = appDetail?.mode !== 'advanced-chat' && appDetail?.mode !== 'workflow'
const jsonSchemaStr = useMemo(
() => getJsonSchemaEditorValue(type, tempPayload.json_schema),
[tempPayload.json_schema, type],
@@ -1,4 +1,5 @@
'use client'
import type { FC } from 'react'
import type { InputVar } from '@/app/components/workflow/types'
import type { ExternalDataTool } from '@/models/common'
@@ -27,7 +28,6 @@ import { InputVarType } from '@/app/components/workflow/types'
import ConfigContext from '@/context/debug-configuration'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useModalContext } from '@/context/modal-context'
import { AppModeEnum } from '@/types/app'
import { getNewVar, hasDuplicateStr } from '@/utils/var'
import Panel from '../base/feature-panel'
import EditModal from './config-modal'
@@ -244,7 +244,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
(index: number) => {
const removeVar = promptVariables[index]
if (mode === AppModeEnum.COMPLETION && dataSets.length > 0 && removeVar!.is_context_var) {
if (mode === 'completion' && dataSets.length > 0 && removeVar!.is_context_var) {
showDeleteContextVarModal()
setRemoveIndex(index)
return
@@ -1,9 +1,10 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { ModelConfig, PromptVariable } from '@/models/debug'
import type { ToolItem } from '@/types/app'
import { render, screen } from '@testing-library/react'
import * as React from 'react'
import * as useContextSelector from 'use-context-selector'
import { AgentStrategy, AppModeEnum, ModelModeType } from '@/types/app'
import { AgentStrategy, ModelModeType } from '@/types/app'
import Config from '../index'
vi.mock('use-context-selector', async (importOriginal) => {
@@ -64,7 +65,7 @@ vi.mock('../../config-prompt/conversation-history/history-panel', () => ({
}))
type MockContext = {
mode: AppModeEnum
mode: AppMode
isAdvancedMode: boolean
modelModeType: ModelModeType
isAgent: boolean
@@ -118,7 +119,7 @@ const createModelConfig = (overrides: Partial<ModelConfig> = {}): ModelConfig =>
})
const createContextValue = (overrides: Partial<MockContext> = {}): MockContext => ({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
isAdvancedMode: false,
modelModeType: ModelModeType.chat,
isAgent: false,
@@ -173,11 +174,11 @@ describe('Config - Rendering', () => {
expect(screen.getByTestId('agent-tools')).toBeInTheDocument()
})
it('should display HistoryPanel only when advanced chat completion values apply', () => {
it('should display HistoryPanel for a chat app using an advanced completion prompt', () => {
const showHistoryModal = vi.fn()
renderConfig({
isAdvancedMode: true,
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.completion,
hasSetBlockStatus: {
context: false,
@@ -1,7 +1,6 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { page } from 'vite-plus/test/browser'
import { render } from 'vitest-browser-react'
import { AppModeEnum } from '@/types/app'
import GetAutomaticRes from '../get-automatic-res'
vi.mock('react-i18next', async () => {
@@ -71,7 +70,7 @@ describe('Prompt generator reflow', () => {
const screen = await render(
<QueryClientProvider client={queryClient}>
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={vi.fn()}
onFinished={vi.fn()}
@@ -1,5 +1,4 @@
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { AppModeEnum } from '@/types/app'
import GetAutomaticRes from '../get-automatic-res'
const mockGenerateBasicAppFirstTimeRule = vi.fn()
@@ -53,7 +52,9 @@ vi.mock(
}) => (
<div>
<button
onClick={() => setModel({ modelId: 'gpt-4o-mini', provider: 'openai', mode: 'chat' })}
onClick={() =>
setModel({ modelId: 'gpt-4o-mini', provider: 'openai', mode: 'chat' as const })
}
>
change-model
</button>
@@ -121,7 +122,7 @@ describe('GetAutomaticRes', () => {
it('should apply a basic suggestion and persist model updates', async () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -152,7 +153,7 @@ describe('GetAutomaticRes', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -176,7 +177,7 @@ describe('GetAutomaticRes', () => {
it('should block generation when instruction is empty', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -203,7 +204,7 @@ describe('GetAutomaticRes', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -254,7 +255,7 @@ describe('GetAutomaticRes', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -291,7 +292,7 @@ describe('GetAutomaticRes', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.ADVANCED_CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -315,6 +316,7 @@ describe('GetAutomaticRes', () => {
current: 'current prompt',
instruction: 'workflow instruction',
ideal_output: 'ideal output',
model_config: expect.objectContaining({ mode: 'chat' }),
}),
)
})
@@ -1,9 +1,10 @@
'use client'
import type { FC } from 'react'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
// type
import type { GenRes } from '@/service/debug'
import type { AppModeEnum, CompletionParams, Model, ModelModeType } from '@/types/app'
import type { CompletionParams, Model, ModelModeType } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { useQuery } from '@tanstack/react-query'
@@ -31,7 +32,7 @@ import useGenData from './use-gen-data'
const i18nPrefix = 'generate'
type IGetAutomaticResProps = {
mode: AppModeEnum
modelMode: ModelModeType
isShow: boolean
onClose: () => void
onFinished: (res: GenRes) => void
@@ -60,7 +61,7 @@ const TryLabel: FC<{
}
const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
mode,
modelMode,
isShow,
onClose,
flowId,
@@ -84,10 +85,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return {
name: defaultModel?.model ?? '',
provider: defaultModel?.provider.provider ?? '',
mode: mode as unknown as ModelModeType,
mode: modelMode,
completion_params: {} as CompletionParams,
}
}, [defaultModel, mode, selectedModel, storedModel])
}, [defaultModel, modelMode, selectedModel, storedModel])
const tryList = [
{
iconClassName: 'i-ri-terminal-box-line',
@@ -1,6 +1,5 @@
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { AppModeEnum } from '@/types/app'
import GetCodeGeneratorResModal from '../get-code-generator-res'
const mockGenerateRule = vi.fn()
@@ -52,7 +51,9 @@ vi.mock(
}) => (
<div>
<button
onClick={() => setModel({ modelId: 'gpt-4o-mini', provider: 'openai', mode: 'chat' })}
onClick={() =>
setModel({ modelId: 'gpt-4o-mini', provider: 'openai', mode: 'chat' as const })
}
>
change-model
</button>
@@ -119,7 +120,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -146,7 +147,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -173,7 +174,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -200,7 +201,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="completion"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -221,6 +222,7 @@ describe('GetCodeGeneratorResModal', () => {
instruction: 'code instruction',
ideal_output: 'code output',
language: 'python',
model_config: expect.objectContaining({ mode: 'completion' }),
}),
)
})
@@ -255,7 +257,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -295,7 +297,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.javascript}
onClose={mockOnClose}
@@ -2,7 +2,7 @@ import type { FC } from 'react'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import type { GenRes } from '@/service/debug'
import type { AppModeEnum, CompletionParams, Model, ModelModeType } from '@/types/app'
import type { CompletionParams, Model, ModelModeType } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { useQuery } from '@tanstack/react-query'
@@ -42,7 +42,7 @@ type IGetCodeGeneratorResProps = {
flowId: string
nodeId: string
currentCode?: string
mode: AppModeEnum
modelMode: ModelModeType
isShow: boolean
codeLanguages: CodeLanguage
onClose: () => void
@@ -53,7 +53,7 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = ({
flowId,
nodeId,
currentCode,
mode,
modelMode,
isShow,
codeLanguages,
onClose,
@@ -80,10 +80,10 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = ({
return {
name: defaultModel?.model ?? '',
provider: defaultModel?.provider.provider ?? '',
mode: mode as unknown as ModelModeType,
mode: modelMode,
completion_params: defaultCompletionParams,
}
}, [defaultModel, mode, selectedModel, storedModel])
}, [defaultModel, modelMode, selectedModel, storedModel])
const [instructionFromSessionStorage, setInstructionFromSessionStorage] =
useSessionStorageState<string>(`improve-instruction-${flowId}-${nodeId}`)
@@ -1,4 +1,5 @@
'use client'
import type { FC } from 'react'
import type { ModelConfig, PromptVariable } from '@/models/debug'
import { produce } from 'immer'
@@ -7,7 +8,7 @@ import { useContext } from 'use-context-selector'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import ConfigVar from '@/app/components/app/configuration/config-var'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import HistoryPanel from '../config-prompt/conversation-history/history-panel'
import ConfigVision from '../config-vision'
import DatasetConfig from '../dataset-config'
@@ -30,9 +31,7 @@ const Config: FC = () => {
setPrevPromptConfig,
dataSets,
} = useContext(ConfigContext)
const isChatApp = [AppModeEnum.ADVANCED_CHAT, AppModeEnum.AGENT_CHAT, AppModeEnum.CHAT].includes(
mode,
)
const isChatApp = mode === 'agent-chat' || mode === 'chat'
const formattingChangedDispatcher = useFormattingChangedDispatcher()
const promptTemplate = modelConfig.configs.prompt_template
@@ -1,4 +1,5 @@
'use client'
import type { FC } from 'react'
import type { ConfigurationViewModel } from '../hooks/configuration-view-model'
import type { InstallBundleCompleteCallback } from '@/app/components/plugins/install-plugin/install-bundle'
@@ -40,7 +41,7 @@ import ModelParameterModal from '@/app/components/header/account-setting/model-p
import PluginDependency from '@/app/components/workflow/plugin-dependency'
import ConfigContext from '@/context/debug-configuration'
import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import { LegacyAgentBadge } from './legacy-agent-badge'
const ConfigurationView: FC<ConfigurationViewModel> = ({
@@ -80,7 +81,7 @@ const ConfigurationView: FC<ConfigurationViewModel> = ({
}) => {
const { t } = useTranslation(['appDebug', 'common'])
const debugWithMultipleModel = appPublisherProps.debugWithMultipleModel
const showLegacyAgentBadge = isAgentV2Enabled() && contextValue.mode === AppModeEnum.AGENT_CHAT
const showLegacyAgentBadge = isAgentV2Enabled() && contextValue.mode === 'agent-chat'
const handlePluginInstallComplete: InstallBundleCompleteCallback = (plugins, installStatus) => {
if (!installStatus.some((status) => status.success)) return
@@ -289,7 +290,7 @@ const ConfigurationView: FC<ConfigurationViewModel> = ({
show
inWorkflow={false}
showFileUpload={false}
isChatMode={contextValue.mode !== AppModeEnum.COMPLETION}
isChatMode={contextValue.mode !== 'completion'}
disabled={!!contextValue.readonly}
onChange={onFeaturesChange}
onClose={onCloseFeaturePanel}
@@ -11,7 +11,7 @@ import { getSelectedDatasetsMode } from '@/app/components/workflow/nodes/knowled
import { DatasetPermission, DataSourceType } from '@/models/datasets'
import { createConsoleQueryWrapper } from '@/test/console/query-data'
import { render as renderWithConsoleState } from '@/test/console/render'
import { AppModeEnum, ModelModeType, RETRIEVE_TYPE } from '@/types/app'
import { ModelModeType, RETRIEVE_TYPE } from '@/types/app'
import { DatasetACLPermission, getDatasetACLCapabilities } from '@/utils/permission'
import DatasetConfig from '../index'
@@ -185,7 +185,7 @@ vi.mock(
// Mock context
const mockConfigContext: any = {
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.chat,
isAgent: false,
dataSets: [],
@@ -516,7 +516,7 @@ describe('DatasetConfig', () => {
it('should show context variable selector in completion mode with datasets', () => {
const dataset = createMockDataset()
renderDatasetConfig({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [dataset],
modelConfig: {
configs: {
@@ -537,7 +537,7 @@ describe('DatasetConfig', () => {
it('should not show context variable selector in chat mode', () => {
const dataset = createMockDataset()
renderDatasetConfig({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
dataSets: [dataset],
modelConfig: {
configs: {
@@ -555,7 +555,7 @@ describe('DatasetConfig', () => {
const user = userEvent.setup()
const dataset = createMockDataset()
renderDatasetConfig({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [dataset],
modelConfig: {
configs: {
@@ -877,7 +877,7 @@ describe('DatasetConfig', () => {
// Context var is only shown when datasets exist AND there are prompt_variables
// Test with no datasets to ensure context var is not shown
renderDatasetConfig({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [],
modelConfig: {
configs: {
@@ -960,7 +960,7 @@ describe('DatasetConfig', () => {
metadata_model_config: {
provider: 'openai',
name: 'gpt-3.5-turbo',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
completion_params: { temperature: 0.7 },
},
},
@@ -976,7 +976,7 @@ describe('DatasetConfig', () => {
metadata_model_config: {
provider: 'openai',
name: 'gpt-4o-mini',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
completion_params: { temperature: 0.7 },
},
}),
@@ -993,7 +993,7 @@ describe('DatasetConfig', () => {
metadata_model_config: {
provider: 'openai',
name: 'gpt-3.5-turbo',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
completion_params: { temperature: 0.5, max_tokens: 100 },
},
},
@@ -34,7 +34,6 @@ import {
import ConfigContext from '@/context/debug-configuration'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { AppModeEnum } from '@/types/app'
import { getDatasetACLCapabilities } from '@/utils/permission'
import FeaturePanel from '../base/feature-panel'
import { OperationButton } from '../base/operation-button'
@@ -269,7 +268,7 @@ const DatasetConfig: FC<Props> = ({ readonly, hideMetadataFilter }) => {
draft.metadata_model_config = {
provider: model.provider,
name: model.modelId,
mode: model.mode || AppModeEnum.CHAT,
mode: model.mode || 'chat',
completion_params: draft.metadata_model_config?.completion_params || { temperature: 0.7 },
}
})
@@ -355,7 +354,7 @@ const DatasetConfig: FC<Props> = ({ readonly, hideMetadataFilter }) => {
</div>
)}
{!readonly && mode === AppModeEnum.COMPLETION && dataSet.length > 0 && (
{!readonly && mode === 'completion' && dataSet.length > 0 && (
<ContextVar
value={selectedContextVar?.key}
options={promptVariablesToSelect}
@@ -6,7 +6,7 @@ import * as React from 'react'
import Log from '@/app/components/base/chat/chat/log'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType, TransferMethod } from '@/types/app'
import { ModelModeType, TransferMethod } from '@/types/app'
import Debug from '../index'
import { APP_CHAT_WITH_MULTIPLE_MODEL, APP_CHAT_WITH_MULTIPLE_MODEL_RESTART } from '../types'
@@ -345,7 +345,7 @@ const createContextValue = (overrides: Partial<DebugContextValue> = {}): DebugCo
appId: 'app-id',
onOpenFeatures: vi.fn(),
isTrailFinished: false,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelModeType: ModelModeType.chat,
promptMode: 'simple' as DebugContextValue['promptMode'],
setPromptMode: vi.fn(),
@@ -676,7 +676,7 @@ describe('Debug', () => {
const { notify } = renderDebug({
contextValue: {
isAdvancedMode: true,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelModeType: ModelModeType.completion,
hasSetBlockStatus: { context: false, history: false, query: true },
},
@@ -693,7 +693,7 @@ describe('Debug', () => {
const { notify } = renderDebug({
contextValue: {
isAdvancedMode: true,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelModeType: ModelModeType.completion,
hasSetBlockStatus: { context: false, history: true, query: false },
},
@@ -711,7 +711,7 @@ describe('Debug', () => {
it('should render prompt value panel and no-result placeholder', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
},
})
@@ -722,7 +722,7 @@ describe('Debug', () => {
it('should notify when required input is missing', () => {
const { notify } = renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
inputs: {},
modelConfig: {
...createContextValue().modelConfig,
@@ -752,7 +752,7 @@ describe('Debug', () => {
it('should notify when local file upload is still pending', () => {
const { notify } = renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
...createContextValue().modelConfig,
configs: {
@@ -776,7 +776,7 @@ describe('Debug', () => {
it('should show cannot-query-dataset warning when dataset context variable is missing', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [{ id: 'dataset-1' }] as DebugContextValue['dataSets'],
hasSetContextVar: false,
modelConfig: {
@@ -800,7 +800,7 @@ describe('Debug', () => {
const user = userEvent.setup()
const { notify } = renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
isAdvancedMode: true,
chatPromptConfig: { prompt: [{ text: 'Missing role' }] },
},
@@ -817,7 +817,7 @@ describe('Debug', () => {
const user = userEvent.setup()
const file = { ...mockState.mockFeaturesState.file, enabled: true, number_limits: 1.5 }
mockState.mockFeaturesState.file = file
const { notify } = renderDebug({ contextValue: { mode: AppModeEnum.COMPLETION } })
const { notify } = renderDebug({ contextValue: { mode: 'completion' as const } })
await user.click(screen.getByRole('button', { name: 'Send' }))
@@ -870,7 +870,7 @@ describe('Debug', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
promptMode: 'simple' as DebugContextValue['promptMode'],
textToSpeechConfig: { enabled: true, voice: 'alloy', language: 'en' },
modelConfig: {
@@ -924,7 +924,7 @@ describe('Debug', () => {
mockState.mockSendCompletionMessage.mockImplementation(() => undefined)
const { notify } = renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
...createContextValue().modelConfig,
configs: {
@@ -975,7 +975,7 @@ describe('Debug', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
...createContextValue().modelConfig,
configs: {
@@ -999,7 +999,7 @@ describe('Debug', () => {
handlers.onData('Completion answer', true, { messageId: 'completion-1' })
handlers.onCompleted()
})
renderDebug({ contextValue: { mode: AppModeEnum.COMPLETION } })
renderDebug({ contextValue: { mode: 'completion' as const } })
await user.click(screen.getByRole('button', { name: 'Send' }))
await user.click(
within(screen.getByRole('region', { name: 'First answer' })).getByRole('button', {
@@ -1080,7 +1080,7 @@ describe('Debug', () => {
it('should emit completion event in multiple-model completion mode', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
...createContextValue().modelConfig,
configs: {
@@ -1166,7 +1166,7 @@ describe('Debug', () => {
it('should update feature store according to multiple-model vision support', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.CHAT,
mode: 'chat',
},
props: {
debugWithMultipleModel: true,
@@ -3,7 +3,6 @@ import type { ModelAndParameter } from '../../types'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { ModelStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { AppModeEnum } from '@/types/app'
import DebugItem from '../debug-item'
const mockUseDebugConfigurationContext = vi.fn()
@@ -98,7 +97,7 @@ describe('DebugItem', () => {
capturedModelParameterTriggerProps = null
mockUseDebugConfigurationContext.mockReturnValue({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
})
mockUseDebugWithMultipleModelContext.mockReturnValue({
@@ -156,7 +155,7 @@ describe('DebugItem', () => {
describe('ChatItem rendering', () => {
it('should render ChatItem in CHAT mode with active model', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active },
@@ -170,7 +169,7 @@ describe('DebugItem', () => {
})
it('should render ChatItem in AGENT_CHAT mode with active model', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.AGENT_CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'agent-chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active },
@@ -183,7 +182,7 @@ describe('DebugItem', () => {
})
it('should not render ChatItem when model is not active', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.disabled },
@@ -196,7 +195,7 @@ describe('DebugItem', () => {
})
it('should not render ChatItem when provider not found', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'anthropic', model: 'claude-3', status: ModelStatusEnum.active },
@@ -209,7 +208,7 @@ describe('DebugItem', () => {
})
it('should not render ChatItem when model not found', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-4', status: ModelStatusEnum.active },
@@ -224,7 +223,7 @@ describe('DebugItem', () => {
describe('TextGenerationItem rendering', () => {
it('should render TextGenerationItem in COMPLETION mode with active model', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.COMPLETION })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'completion' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active },
@@ -238,7 +237,7 @@ describe('DebugItem', () => {
})
it('should not render TextGenerationItem when provider is not found', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.COMPLETION })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'completion' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'anthropic', model: 'claude-3', status: ModelStatusEnum.active },
@@ -503,7 +502,7 @@ describe('DebugItem', () => {
})
it('should handle model with quotaExceeded status', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'anthropic', model: 'not-matching', status: ModelStatusEnum.quotaExceeded },
@@ -1,3 +1,4 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { CSSProperties } from 'react'
import type { ModelAndParameter } from '../../types'
import type { DebugWithMultipleModelContextType } from '../context'
@@ -12,7 +13,7 @@ import {
DEFAULT_CHAT_PROMPT_CONFIG,
DEFAULT_COMPLETION_PROMPT_CONFIG,
} from '@/config'
import { AppModeEnum, ModelModeType, Resolution, TransferMethod } from '@/types/app'
import { ModelModeType, Resolution, TransferMethod } from '@/types/app'
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../../types'
import DebugWithMultipleModel from '../index'
@@ -161,7 +162,7 @@ const createModelConfig = (promptVariables: PromptVariableWithMeta[] = []): Mode
type DebugConfiguration = {
onOpenFeatures: () => void
mode: AppModeEnum
mode: AppMode
inputs: Inputs
modelConfig: ModelConfig
readonly: boolean
@@ -172,7 +173,7 @@ const createDebugConfiguration = (
overrides: Partial<DebugConfiguration> = {},
): DebugConfiguration => ({
onOpenFeatures: vi.fn(),
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
inputs: {},
modelConfig: createModelConfig(),
readonly: false,
@@ -536,7 +537,7 @@ describe('DebugWithMultipleModel', () => {
// Arrange
mockUseDebugConfigurationContext.mockReturnValue(
createDebugConfiguration({
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
}),
)
@@ -552,7 +553,7 @@ describe('DebugWithMultipleModel', () => {
// Arrange
mockUseDebugConfigurationContext.mockReturnValue(
createDebugConfiguration({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
}),
)
const multipleModelConfigs = [createModelAndParameter()]
@@ -842,7 +843,7 @@ describe('DebugWithMultipleModel', () => {
it('should set full height when chat input is hidden', () => {
mockUseDebugConfigurationContext.mockReturnValue(
createDebugConfiguration({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
}),
)
@@ -17,7 +17,6 @@ import {
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import ChatItem from './chat-item'
import { useDebugWithMultipleModelContext } from './context'
import ModelParameterTrigger from './model-parameter-trigger'
@@ -124,13 +123,13 @@ const DebugItem: FC<DebugItemProps> = ({ modelAndParameter, className, style })
</DropdownMenu>
</div>
<div style={{ height: 'calc(100% - 40px)' }}>
{(mode === AppModeEnum.CHAT || mode === AppModeEnum.AGENT_CHAT) &&
{(mode === 'chat' || mode === 'agent-chat') &&
currentProvider &&
currentModel &&
currentModel.status === ModelStatusEnum.active && (
<ChatItem modelAndParameter={modelAndParameter} />
)}
{mode === AppModeEnum.COMPLETION &&
{mode === 'completion' &&
currentProvider &&
currentModel &&
currentModel.status === ModelStatusEnum.active && (
@@ -7,7 +7,6 @@ import ChatInputArea from '@/app/components/base/chat/chat/chat-input-area'
import { useFeatures } from '@/app/components/base/features/hooks'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { AppModeEnum } from '@/types/app'
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types'
import { useDebugWithMultipleModelContext } from './context'
import { DebugWithMultipleModelContextProvider } from './context-provider'
@@ -28,7 +27,7 @@ const DebugWithMultipleModel = () => {
const { multipleModelConfigs, checkCanSend } = useDebugWithMultipleModelContext()
const { eventEmitter } = useEventEmitterContextContext()
const isChatMode = mode === AppModeEnum.CHAT || mode === AppModeEnum.AGENT_CHAT
const isChatMode = mode === 'chat' || mode === 'agent-chat'
const handleSend = useCallback(
(message: string, files?: FileEntity[]) => {
@@ -10,7 +10,7 @@ import { CollectionType } from '@/app/components/tools/types'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { PromptMode } from '@/models/debug'
import { renderWithAccountProfile as render } from '@/test/console/account-profile'
import { AgentStrategy, AppModeEnum, ModelModeType, Resolution, TransferMethod } from '@/types/app'
import { AgentStrategy, ModelModeType, Resolution, TransferMethod } from '@/types/app'
import DebugWithSingleModel from '../index'
// ============================================================================
@@ -135,7 +135,7 @@ const mockDebugConfigContext = {
appId: 'test-app-id',
onOpenFeatures: vi.fn(),
isTrailFinished: false,
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.chat,
promptMode: PromptMode.simple,
setPromptMode: vi.fn(),
@@ -1,4 +1,5 @@
'use client'
import type { AppModelConfigPayload } from '@dify/contracts/api/console/apps/types.gen'
import type { FC } from 'react'
import type { DebugWithSingleModelRefType } from './debug-with-single-model'
@@ -44,7 +45,7 @@ import { useEventEmitterContextContext } from '@/context/event-emitter'
import { consoleQuery } from '@/service/console'
import { sendCompletionMessage } from '@/service/debug'
import { AppSourceType } from '@/service/share'
import { AppModeEnum, ModelModeType, TransferMethod } from '@/types/app'
import { ModelModeType, TransferMethod } from '@/types/app'
import { formatBooleanInputs, promptVariablesToUserInputsForm } from '@/utils/model-config'
import GroupName from '../base/group-name'
import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset'
@@ -147,7 +148,7 @@ const Debug: FC<IDebug> = ({
const [completionFiles, setCompletionFiles] = useState<VisionFile[]>([])
const checkCanSend = useCallback(() => {
if (isAdvancedMode && mode !== AppModeEnum.COMPLETION) {
if (isAdvancedMode && mode !== 'completion') {
if (modelModeType === ModelModeType.completion) {
if (!hasSetBlockStatus.history) {
toast.error(t(($) => $['otherError.historyNoBeEmpty'], { ns: 'appDebug' }))
@@ -413,9 +414,7 @@ const Debug: FC<IDebug> = ({
const handleOpenLog = (item: IChatItem) => {
const container =
debugWithMultipleModel || mode === AppModeEnum.COMPLETION
? ref.current
: singleChatContainerRef.current
debugWithMultipleModel || mode === 'completion' ? ref.current : singleChatContainerRef.current
if (container) setLogModalWidth(document.body.clientWidth - (container.clientWidth + 16) - 8)
setSelectedLogItem(item)
}
@@ -423,9 +422,7 @@ const Debug: FC<IDebug> = ({
useEffect(() => {
if (!selectedLogItem) return
const container =
debugWithMultipleModel || mode === AppModeEnum.COMPLETION
? ref.current
: singleChatContainerRef.current
debugWithMultipleModel || mode === 'completion' ? ref.current : singleChatContainerRef.current
if (!container) return
const adjustModalWidth = () => {
@@ -471,7 +468,7 @@ const Debug: FC<IDebug> = ({
<div className="mx-2 h-3.5 w-px bg-divider-regular" />
</>
) : null}
{mode !== AppModeEnum.COMPLETION && (
{mode !== 'completion' && (
<>
{canTestAndRun && (
<Tooltip>
@@ -525,14 +522,14 @@ const Debug: FC<IDebug> = ({
)}
</div>
</div>
{mode !== AppModeEnum.COMPLETION && (
{mode !== 'completion' && (
<CollapsiblePanel render={<div className="mx-3" />}>
<ChatUserInput inputs={inputs} />
</CollapsiblePanel>
)}
{mode === AppModeEnum.COMPLETION && (
{mode === 'completion' && (
<PromptValuePanel
appType={mode as AppModeEnum}
appType={mode}
onSend={handleSendTextCompletion}
inputs={inputs}
visionConfig={{
@@ -582,7 +579,7 @@ const Debug: FC<IDebug> = ({
</div>
)}
{/* Chat */}
{mode !== AppModeEnum.COMPLETION && (
{mode !== 'completion' && (
<div className="h-0 grow overflow-hidden">
<DebugWithSingleModel
ref={debugWithSingleModelRef}
@@ -593,7 +590,7 @@ const Debug: FC<IDebug> = ({
</div>
)}
{/* Text Generation */}
{mode === AppModeEnum.COMPLETION && (
{mode === 'completion' && (
<>
{(completionRes || isResponding) && (
<>
@@ -7,7 +7,7 @@ import {
} from '@/app/components/base/prompt-editor/constants'
import { PromptMode, PromptRole } from '@/models/debug'
import { fetchPromptTemplate } from '@/service/debug'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import useAdvancedPromptConfig from '../use-advanced-prompt-config'
vi.mock('@/service/debug', () => ({
@@ -25,7 +25,7 @@ describe('useAdvancedPromptConfig', () => {
const handleUserChangedPrompt = vi.fn()
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
modelModeType: ModelModeType.chat,
modelName: 'gpt-4o',
promptMode: PromptMode.advanced,
@@ -55,7 +55,7 @@ describe('useAdvancedPromptConfig', () => {
it('should derive simple prompt block status from the pre-prompt', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.COMPLETION,
appMode: 'completion' as const,
modelModeType: ModelModeType.completion,
modelName: 'gpt-4o',
promptMode: PromptMode.simple,
@@ -79,7 +79,7 @@ describe('useAdvancedPromptConfig', () => {
const handleUserChangedPrompt = vi.fn()
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
modelModeType: ModelModeType.chat,
modelName: 'gpt-4o',
promptMode: PromptMode.simple,
@@ -118,7 +118,7 @@ describe('useAdvancedPromptConfig', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.COMPLETION,
appMode: 'completion' as const,
modelModeType: ModelModeType.completion,
modelName: 'gpt-4o',
promptMode: PromptMode.simple,
@@ -137,7 +137,7 @@ describe('useAdvancedPromptConfig', () => {
await waitFor(() => {
expect(mockFetchPromptTemplate).toHaveBeenCalledWith({
appMode: AppModeEnum.COMPLETION,
appMode: 'completion' as const,
mode: ModelModeType.completion,
modelName: 'gpt-4o',
hasSetDataSet: true,
@@ -168,7 +168,7 @@ describe('useAdvancedPromptConfig', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
modelModeType: ModelModeType.chat,
modelName: 'gpt-4o',
promptMode: PromptMode.simple,
@@ -211,7 +211,7 @@ describe('useAdvancedPromptConfig', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
modelModeType: ModelModeType.completion,
modelName: 'gpt-4o',
promptMode: PromptMode.advanced,
@@ -268,7 +268,7 @@ describe('useAdvancedPromptConfig', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.COMPLETION,
appMode: 'completion' as const,
modelModeType: ModelModeType.completion,
modelName: 'gpt-4o',
promptMode: PromptMode.advanced,
@@ -6,7 +6,6 @@ import { createAppDetailFixture, createAppModelConfigFixture } from '@/test/fixt
import { withSelectorKey } from '@/test/i18n-mock'
import {
AgentStrategy,
AppModeEnum,
ModelModeType,
Resolution,
RETRIEVE_TYPE,
@@ -133,7 +132,7 @@ describe('useConfiguration utils', () => {
mockFetchCollectionList.mockResolvedValue([])
mockFetchAppDetailDirect.mockResolvedValue(
createAppDetailFixture({
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
model_config: createAppModelConfigFixture({
model: {
provider: 'langgenius/openai/openai',
@@ -277,7 +276,7 @@ describe('useConfiguration utils', () => {
top_k: 4,
} as any,
deletedTools: [{ type: 'tool', provider_id: 'tool-1', tool_name: 'search' }],
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
nextDataSets: [{ id: 'dataset-1' }] as any,
})
@@ -336,7 +335,7 @@ describe('useConfiguration utils', () => {
datasets: { datasets: [] },
retrieval_model: RETRIEVE_TYPE.multiWay,
} as any,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
nextDataSets: [],
})
@@ -369,7 +368,7 @@ describe('useConfiguration utils', () => {
datasets: { datasets: [] },
retrieval_model: RETRIEVE_TYPE.multiWay,
} as any,
mode: AppModeEnum.CHAT,
mode: 'chat',
nextDataSets: [],
})
@@ -496,7 +495,7 @@ describe('useConfiguration utils', () => {
])
mockFetchAppDetailDirect.mockResolvedValue({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat',
model_config: createAppModelConfigFixture({
prompt_type: 'advanced',
chat_prompt_config: {
@@ -581,7 +580,7 @@ describe('useConfiguration utils', () => {
})
mockFetchAppDetailDirect.mockResolvedValue({
deleted_tools: [],
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
model_config: createAppModelConfigFixture({
prompt_type: 'simple',
chat_prompt_config: { prompt: [] },
@@ -642,7 +641,7 @@ describe('useConfiguration utils', () => {
mockFetchCollectionList.mockResolvedValue([])
mockFetchAppDetailDirect.mockResolvedValue({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat',
model_config: createAppModelConfigFixture({
prompt_type: 'simple',
chat_prompt_config: { prompt: [] },
@@ -839,7 +838,7 @@ describe('useConfiguration utils', () => {
backendModelConfig,
collectionList: [],
datasetConfigs: buildConfigurationDatasetConfigs({ backendModelConfig, nextDataSets: [] }),
mode: AppModeEnum.CHAT,
mode: 'chat',
nextDataSets: [],
})
const setPublishedConfig = vi.fn()
@@ -862,7 +861,7 @@ describe('useConfiguration utils', () => {
hasSetBlockStatus: { history: true, query: true },
isAdvancedMode: true,
isFunctionCall: false,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelConfig: config.modelConfig,
promptEmpty: false,
promptMode: 'advanced',
@@ -911,7 +910,7 @@ describe('useConfiguration utils', () => {
},
isAdvancedMode: true,
isFunctionCall: true,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelConfig: {
agentConfig: {
enabled: true,
@@ -1019,7 +1018,7 @@ describe('useConfiguration utils', () => {
},
isAdvancedMode: true,
isFunctionCall: false,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelConfig: {
configs: {
prompt_template: 'hello',
@@ -1080,7 +1079,7 @@ describe('useConfiguration utils', () => {
handleSetVisionConfig,
isAdvancedMode: true,
migrateToDefaultPrompt,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelConfig: {
model_id: 'gpt-4o-mini',
provider: 'langgenius/openai/openai',
@@ -1137,7 +1136,7 @@ describe('useConfiguration utils', () => {
handleSetVisionConfig,
isAdvancedMode: true,
migrateToDefaultPrompt,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
model_id: 'gpt-4o-mini',
provider: 'langgenius/openai/openai',
@@ -12,7 +12,7 @@ import { seedAppDetail } from '@/test/console/query-data'
import { renderHook as renderHookWithConsoleState } from '@/test/console/render'
import { createAppDetailFixture, createAppModelConfigFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import { useConfiguration } from '../use-configuration'
@@ -24,7 +24,7 @@ const createRouteAppDetail = (overrides: Partial<AppDetailWithSite> = {}) =>
model_config: createAppModelConfigFixture({
updated_at: 1710000000,
}),
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
permission_keys: mockAppPermissionKeys,
...overrides,
})
@@ -252,7 +252,7 @@ describe('useConfiguration', () => {
vi.mocked(consoleClient.apps.byAppId.modelConfig.post).mockResolvedValue(undefined as never)
mockFetchAppDetailDirect.mockResolvedValue({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: createAppModelConfigFixture({
prompt_type: 'advanced',
chat_prompt_config: {
@@ -675,7 +675,7 @@ describe('useConfiguration', () => {
mockCurrentModelFeatures = ['vision', 'document', 'audio', 'video']
mockFetchAppDetailDirect.mockResolvedValueOnce({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: createAppModelConfigFixture({
prompt_type: 'simple',
chat_prompt_config: { prompt: [] },
@@ -788,7 +788,7 @@ describe('useConfiguration', () => {
})
mockFetchAppDetailDirect.mockResolvedValueOnce({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: createAppModelConfigFixture({
prompt_type: 'simple',
chat_prompt_config: { prompt: [] },
@@ -4,7 +4,6 @@ import { PromptMode } from '@/models/debug'
import { consoleClient } from '@/service/console'
import { fetchDatasets } from '@/service/datasets'
import { fetchCollectionList } from '@/service/tools'
import { AppModeEnum } from '@/types/app'
import { withCollectionIconBasePath } from '../../utils'
import { buildConfigurationDatasetConfigs } from './dataset'
import { normalizeChatPromptConfig, normalizeCompletionPromptConfig } from './prompt-config'
@@ -24,11 +23,7 @@ export async function loadConfigurationState({
const collectionList = withCollectionIconBasePath(await fetchCollectionList(), basePath)
const response = await consoleClient.apps.byAppId.get({ params: { app_id: appId } })
const mode = response.mode
if (
mode !== AppModeEnum.CHAT &&
mode !== AppModeEnum.AGENT_CHAT &&
mode !== AppModeEnum.COMPLETION
)
if (mode !== 'chat' && mode !== 'agent-chat' && mode !== 'completion')
throw new Error(`App mode ${mode} does not use model configuration`)
const backendModelConfig = response.model_config
if (!backendModelConfig) throw new Error(`App ${appId} has no model configuration`)
@@ -1,11 +1,12 @@
import type { TFunction } from 'i18next'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { ModelConfig } from '@/models/debug'
import type { VisionSettings } from '@/types/app'
import { produce } from 'immer'
import { useMemo } from 'react'
import { toast } from '@/app/components/app/configuration/toast'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
type ModelChangeHandlerOptions = {
@@ -23,7 +24,7 @@ type ModelChangeHandlerOptions = {
handleSetVisionConfig: (config: VisionSettings, notNoticeFormattingChanged?: boolean) => void
isAdvancedMode: boolean
migrateToDefaultPrompt: (force?: boolean, modelModeType?: ModelModeType) => Promise<void>
mode: AppModeEnum
mode: ConfigurationAppMode
modelConfig: ModelConfig
resolvedModelModeType: ModelModeType
setCompletionParams: (value: FormValue) => void
@@ -61,7 +62,7 @@ export const createModelChangeHandler =
}) => {
if (isAdvancedMode) {
if (nextModelMode === ModelModeType.completion) {
if (mode !== AppModeEnum.COMPLETION) {
if (mode !== 'completion') {
if (
!completionPromptConfig.prompt?.text ||
!completionPromptConfig.conversation_histories_role.assistant_prefix ||
@@ -1,6 +1,7 @@
import type { AppModelConfigPayload } from '@dify/contracts/api/console/apps/types.gen'
import type { TFunction } from 'i18next'
import type { ConfigurationPublishConfig } from './types'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { Features as FeaturesData } from '@/app/components/base/features/types'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { DataSet } from '@/models/datasets'
@@ -21,7 +22,7 @@ import { produce } from 'immer'
import { toast } from '@/app/components/app/configuration/toast'
import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
import { PromptMode } from '@/models/debug'
import { AgentStrategy, AppModeEnum, ModelModeType } from '@/types/app'
import { AgentStrategy, ModelModeType } from '@/types/app'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
import { normalizeChatPromptConfig, normalizeCompletionPromptConfig } from './prompt-config'
@@ -152,7 +153,7 @@ export const createPublishHandler =
hasSetBlockStatus: { history: boolean; query: boolean }
isAdvancedMode: boolean
isFunctionCall: boolean
mode: AppModeEnum
mode: ConfigurationAppMode
modelConfig: ModelConfig
promptEmpty: boolean
promptMode: PromptMode
@@ -178,7 +179,7 @@ export const createPublishHandler =
}
if (
isAdvancedMode &&
mode !== AppModeEnum.COMPLETION &&
mode !== 'completion' &&
resolvedModelModeType === ModelModeType.completion
) {
if (!hasSetBlockStatus.history) {
@@ -3,6 +3,7 @@ import type {
DeletedTool,
} from '@dify/contracts/api/console/apps/types.gen'
import type { ConfigurationPublishConfig } from './types'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { Collection } from '@/app/components/tools/types'
import type { DataSet } from '@/models/datasets'
import type { AnnotationReplyConfig, DatasetConfigs, ModelConfig } from '@/models/debug'
@@ -12,7 +13,7 @@ import {
} from '@dify/contracts/api/console/apps/zod.gen'
import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING } from '@/config'
import { PromptMode } from '@/models/debug'
import { AgentStrategy, AppModeEnum } from '@/types/app'
import { AgentStrategy } from '@/types/app'
import { correctModelProvider, correctToolProvider } from '@/utils'
import { userInputsFormToPromptVariables } from '@/utils/model-config'
import { matchesProviderReference } from '@/utils/provider-reference'
@@ -50,7 +51,7 @@ function buildPublishedModelConfig({
backendModelConfig: AppModelConfigResponse
collectionList: Collection[]
deletedTools?: DeletedTool[]
mode: AppModeEnum
mode: ConfigurationAppMode
nextDataSets: DataSet[]
}): ModelConfig {
const model = backendModelConfig.model
@@ -107,7 +108,7 @@ function buildPublishedModelConfig({
external_data_tools: externalDataTools,
dataSets: nextDataSets,
agentConfig:
mode === AppModeEnum.AGENT_CHAT
mode === 'agent-chat'
? {
...backendModelConfig.agent_mode,
enabled: true,
@@ -170,7 +171,7 @@ export function buildPublishedConfig({
collectionList: Collection[]
datasetConfigs: DatasetConfigs
deletedTools?: DeletedTool[]
mode: AppModeEnum
mode: ConfigurationAppMode
nextDataSets: DataSet[]
}): ConfigurationPublishConfig {
const modelConfig = buildPublishedModelConfig({
@@ -1,6 +1,9 @@
import type { AppType } from '@/app/components/apps/app-type'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { DatasetConfigs, ModelConfig, PromptMode } from '@/models/debug'
export type ConfigurationAppMode = Extract<AppType, 'chat' | 'agent-chat' | 'completion'>
export type ConfigurationPublishConfig = {
modelConfig: ModelConfig
completionParams: FormValue
@@ -1,8 +1,8 @@
import type { Dispatch, SetStateAction } from 'react'
import type { ConfigurationPublishConfig } from './types'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { Collection } from '@/app/components/tools/types'
import type { AnnotationReplyConfig } from '@/models/debug'
import type { AppModeEnum } from '@/types/app'
import { useEffect, useRef, useState } from 'react'
import { basePath } from '@/utils/var'
import { loadConfigurationState } from './load'
@@ -13,7 +13,7 @@ type ConfigurationLoaderOperations = {
setAnnotationConfig: (config: AnnotationReplyConfig, notSetFormatChanged?: boolean) => void
setCollectionList: Dispatch<SetStateAction<Collection[]>>
setHasFetchedDetail: Dispatch<SetStateAction<boolean>>
setMode: Dispatch<SetStateAction<AppModeEnum>>
setMode: Dispatch<SetStateAction<ConfigurationAppMode>>
setPublishedConfig: Dispatch<SetStateAction<ConfigurationPublishConfig | null>>
syncToPublishedConfig: (config: ConfigurationPublishConfig) => void
}
@@ -2,12 +2,13 @@ import type { TFunction } from 'i18next'
import type { Dispatch, SetStateAction } from 'react'
import type { ConfigurationPublishConfig } from './types'
import type { AppPublisherPublishParams } from '@/app/components/app/app-publisher/types'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { Features as FeaturesData } from '@/app/components/base/features/types'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { DataSet } from '@/models/datasets'
import type { DatasetConfigs, ModelConfig, PromptMode } from '@/models/debug'
import type { ConsoleClient } from '@/service/console'
import type { AppModeEnum, ModelModeType } from '@/types/app'
import type { ModelModeType } from '@/types/app'
import { useCallback } from 'react'
import { createPublishHandler } from './publish'
@@ -25,7 +26,7 @@ type UseConfigurationPublishParams = {
hasSetBlockStatus: { history: boolean; query: boolean }
isAdvancedMode: boolean
isFunctionCall: boolean
mode: AppModeEnum
mode: ConfigurationAppMode
modelConfig: ModelConfig
promptEmpty: boolean
promptMode: PromptMode
@@ -1,3 +1,4 @@
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type {
ChatPromptConfig,
@@ -17,10 +18,10 @@ import {
import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
import { PromptMode } from '@/models/debug'
import { fetchPromptTemplate } from '@/service/debug'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
type Param = {
appMode?: AppModeEnum
appMode?: ConfigurationAppMode
modelModeType: ModelModeType
modelName: string
promptMode: PromptMode
@@ -175,9 +176,7 @@ const useAdvancedPromptConfig = ({
)
if (
[AppModeEnum.ADVANCED_CHAT, AppModeEnum.AGENT_CHAT, AppModeEnum.CHAT].includes(
appMode,
) &&
(appMode === 'agent-chat' || appMode === 'chat') &&
completionPromptConfig.conversation_histories_role.assistant_prefix &&
completionPromptConfig.conversation_histories_role.user_prefix
)
@@ -1,6 +1,8 @@
'use client'
import type { ConfigurationPublishConfig } from './configuration-lifecycle/types'
import type { ConfigurationViewModel } from './configuration-view-model'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { OnFeaturesChange } from '@/app/components/base/features/types'
import type { Collection } from '@/app/components/tools/types'
import type { Inputs, ModelConfig, PromptConfig, PromptVariable } from '@/models/debug'
@@ -25,7 +27,7 @@ import {
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { PromptMode } from '@/models/debug'
import { useFileUploadConfig } from '@/service/use-common'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import { supportFunctionCall } from '@/utils/tool-call'
import { buildConfigurationFeaturesData, getConfigurationPublishingState } from '../utils'
import { buildConfigurationContextValue } from './build-configuration-context'
@@ -61,7 +63,7 @@ export const useConfiguration = (): ConfigurationViewModel => {
const [hasFetchedDetail, setHasFetchedDetail] = useState(false)
// oxlint-disable-next-line eslint-react/use-state -- This custom hook returns a state object.
const featureConfiguration = useFeatureConfigurationState()
const [mode, setMode] = useState<AppModeEnum>(AppModeEnum.CHAT)
const [mode, setMode] = useState<ConfigurationAppMode>('chat')
const [publishedConfig, setPublishedConfig] = useState<ConfigurationPublishConfig | null>(null)
const [conversationId, setConversationId] = useState<string | null>('')
@@ -106,7 +108,7 @@ export const useConfiguration = (): ConfigurationViewModel => {
visionConfig,
} = modelConfiguration
const modelModeType = modelConfig.mode
const isAgent = mode === AppModeEnum.AGENT_CHAT
const isAgent = mode === 'agent-chat'
const [collectionList, setCollectionList] = useState<Collection[]>([])
// oxlint-disable-next-line eslint-react/use-state -- This custom hook returns a state object.
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType, Resolution } from '@/types/app'
import { ModelModeType, Resolution } from '@/types/app'
import PromptValuePanel from '../index'
const mockOnOpenFeatures = vi.fn()
@@ -78,7 +78,7 @@ const baseContextValue: any = {
},
setInputs: mockSetInputs,
canTestAndRun: true,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
isAdvancedMode: false,
completionPromptConfig: {
prompt: { text: 'completion' },
@@ -88,7 +88,7 @@ const baseContextValue: any = {
} as any
const defaultProps: IPromptValuePanelProps = {
appType: AppModeEnum.COMPLETION,
appType: 'completion',
onSend: mockOnSend,
inputs: { textVar: 'initial', boolVar: false },
visionConfig: { enabled: false, number_limits: 0, detail: Resolution.low, transfer_methods: [] },
@@ -142,10 +142,10 @@ describe('PromptValuePanel', () => {
it('disables run when mode is not completion', () => {
renderPanel({
context: {
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
},
props: {
appType: AppModeEnum.CHAT,
appType: 'chat',
},
})
@@ -1,5 +1,7 @@
'use client'
import type { FC } from 'react'
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { Inputs } from '@/models/debug'
import type { VisionFile, VisionSettings } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
@@ -25,10 +27,10 @@ import FeatureBar from '@/app/components/base/features/new-feature-panel/feature
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
export type IPromptValuePanelProps = {
appType: AppModeEnum
appType: ConfigurationAppMode
onSend?: () => void
inputs: Inputs
visionConfig: VisionSettings
@@ -93,7 +95,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
}, [promptVariables, inputs, setInputs])
const canNotRun = useMemo(() => {
if (mode !== AppModeEnum.COMPLETION) return true
if (mode !== 'completion') return true
if (isAdvancedMode) {
if (modelModeType === ModelModeType.chat)
@@ -320,7 +322,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
<div className="mx-3">
<FeatureBar
showFileUpload={false}
isChatMode={appType !== AppModeEnum.COMPLETION}
isChatMode={appType !== 'completion'}
onFeatureBarClick={onOpenFeatures}
disabled={readonly}
hideEditEntrance={readonly}
@@ -1,3 +1,4 @@
import type { ConfigurationAppMode } from '@/app/components/app/configuration/hooks/configuration-lifecycle/types'
import type { Features as FeaturesData, FileUpload } from '@/app/components/base/features/types'
import type { Collection } from '@/app/components/tools/types'
import type {
@@ -8,7 +9,7 @@ import type {
} from '@/models/debug'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { AppModeEnum, ModelModeType, Resolution } from '@/types/app'
import { ModelModeType, Resolution } from '@/types/app'
export const withCollectionIconBasePath = (collectionList: Collection[], prefix?: string) => {
if (!prefix) return collectionList
@@ -84,12 +85,12 @@ export const getConfigurationPublishingState = ({
hasSetContextVar: boolean
hasSelectedDataSets: boolean
isAdvancedMode: boolean
mode: AppModeEnum
mode: ConfigurationAppMode
modelModeType: ModelModeType
promptTemplate: string
}) => {
const promptEmpty = (() => {
if (mode !== AppModeEnum.COMPLETION) return false
if (mode !== 'completion') return false
if (isAdvancedMode) {
if (modelModeType === ModelModeType.chat)
@@ -102,7 +103,7 @@ export const getConfigurationPublishingState = ({
})()
const cannotPublish = (() => {
if (mode !== AppModeEnum.COMPLETION) {
if (mode !== 'completion') {
if (!isAdvancedMode) return false
if (modelModeType === ModelModeType.completion) {
@@ -118,6 +119,6 @@ export const getConfigurationPublishingState = ({
return {
promptEmpty,
cannotPublish,
contextVarEmpty: mode === AppModeEnum.COMPLETION && hasSelectedDataSets && !hasSetContextVar,
contextVarEmpty: mode === 'completion' && hasSelectedDataSets && !hasSetContextVar,
}
}
@@ -4,7 +4,6 @@ import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { trackEvent } from '@/app/components/base/amplitude'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { AppModeEnum } from '@/types/app'
import AppCard from '../index'
vi.mock('@/app/components/base/amplitude', () => ({ trackEvent: vi.fn() }))
@@ -16,7 +15,7 @@ const app = {
can_trial: true,
app: {
id: 'app-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon_type: 'emoji',
icon: '🤖',
icon_background: '#FFEAD5',
@@ -1,6 +1,7 @@
'use client'
import type { RecommendedAppResponse } from '@dify/contracts/api/console/explore/types.gen'
import type { AppType } from '@/app/components/apps/app-type'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group'
@@ -23,7 +24,6 @@ import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { useCanImportAgents } from '@/features/agent-v2/permissions'
import { useImportDSL } from '@/hooks/use-import-dsl'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { trackCreateApp } from '@/utils/create-app-tracking'
import { hasPermission } from '@/utils/permission'
import AppCard from '../app-card'
@@ -62,7 +62,7 @@ const Apps = ({ onClose, onCreateFromBlank, templateMode }: AppsProps) => {
searchInputRef.current?.focus()
}
const [currentType, setCurrentType] = useState<AppModeEnum[]>([])
const [currentType, setCurrentType] = useState<AppType[]>([])
const [currCategory, setCurrCategory] = useState<AppCategories | string>(allCategoriesEn)
const { data, isLoading } = useQuery(
@@ -74,7 +74,7 @@ const Apps = ({ onClose, onCreateFromBlank, templateMode }: AppsProps) => {
() =>
[...(data?.recommended_apps ?? [])]
.filter((item) =>
templateMode ? item.app?.mode === templateMode : item.app?.mode !== AppModeEnum.AGENT,
templateMode ? item.app?.mode === templateMode : item.app?.mode !== 'agent',
)
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0)),
[data?.recommended_apps, templateMode],
@@ -101,18 +101,11 @@ const Apps = ({ onClose, onCreateFromBlank, templateMode }: AppsProps) => {
})
if (currentType.length === 0) return filteredByCategory
return filteredByCategory.filter((item) => {
if (currentType.includes(AppModeEnum.CHAT) && item.app?.mode === AppModeEnum.CHAT) return true
if (
currentType.includes(AppModeEnum.ADVANCED_CHAT) &&
item.app?.mode === AppModeEnum.ADVANCED_CHAT
)
return true
if (currentType.includes(AppModeEnum.AGENT_CHAT) && item.app?.mode === AppModeEnum.AGENT_CHAT)
return true
if (currentType.includes(AppModeEnum.COMPLETION) && item.app?.mode === AppModeEnum.COMPLETION)
return true
if (currentType.includes(AppModeEnum.WORKFLOW) && item.app?.mode === AppModeEnum.WORKFLOW)
return true
if (currentType.includes('chat') && item.app?.mode === 'chat') return true
if (currentType.includes('advanced-chat') && item.app?.mode === 'advanced-chat') return true
if (currentType.includes('agent-chat') && item.app?.mode === 'agent-chat') return true
if (currentType.includes('completion') && item.app?.mode === 'completion') return true
if (currentType.includes('workflow') && item.app?.mode === 'workflow') return true
return false
})
}, [currentType, activeCategory, allCategoriesEn, data, allList])
@@ -152,7 +145,7 @@ const Apps = ({ onClose, onCreateFromBlank, templateMode }: AppsProps) => {
})
if (
!canCreateTemplate(detail.mode) ||
(templateMode ? detail.mode !== templateMode : detail.mode === AppModeEnum.AGENT)
(templateMode ? detail.mode !== templateMode : detail.mode === 'agent')
)
throw new Error('Template mode does not match this picker')
await handleImportDSL(
@@ -8,7 +8,6 @@ import { useRouter } from '@/next/navigation'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { mockEmojiData } from '@/test/emoji-picker'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import { trackCreateApp } from '@/utils/create-app-tracking'
import CreateAppModal from '../index'
@@ -109,7 +108,7 @@ const renderModal = () => {
show
onClose={onClose}
onCreateFromTemplate={onCreateFromTemplate}
defaultAppMode={AppModeEnum.ADVANCED_CHAT}
defaultAppMode={'advanced-chat'}
/>,
)
return { onClose, onCreateFromTemplate }
@@ -138,7 +137,7 @@ describe('CreateAppModal', () => {
it('creates an app and fires callbacks', async () => {
const mockApp = createAppDetailFixture({
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'advanced-chat' as const,
maintainer: 'user-1',
})
mockCreateApp.mockResolvedValue(mockApp)
@@ -155,13 +154,13 @@ describe('CreateAppModal', () => {
icon_type: 'emoji',
icon: '🤖',
icon_background: '#FFEAD5',
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'advanced-chat' as const,
}),
)
expect(mockTrackCreateApp).toHaveBeenCalledWith({
source: 'studio_blank',
appMode: AppModeEnum.ADVANCED_CHAT,
appMode: 'advanced-chat' as const,
})
expect(onClose).toHaveBeenCalledTimes(1)
await waitFor(() =>
@@ -176,7 +175,7 @@ describe('CreateAppModal', () => {
it('waits for create_app tracking before redirecting after blank app creation', async () => {
const mockApp = createAppDetailFixture({
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'advanced-chat' as const,
maintainer: 'user-1',
})
let resolveTracking: (() => void) | undefined
@@ -196,7 +195,7 @@ describe('CreateAppModal', () => {
await waitFor(() => {
expect(mockTrackCreateApp).toHaveBeenCalledWith({
source: 'studio_blank',
appMode: AppModeEnum.ADVANCED_CHAT,
appMode: 'advanced-chat' as const,
})
})
const createButton = screen.getByRole('button', { name: /app\.newApp\.Create/ })
@@ -251,7 +250,7 @@ describe('CreateAppModal', () => {
async ({ platform, modifier }) => {
vi.spyOn(navigator, 'platform', 'get').mockReturnValue(platform)
mockCreateApp.mockResolvedValue(
createAppDetailFixture({ id: 'chat-app', mode: AppModeEnum.CHAT }),
createAppDetailFixture({ id: 'chat-app', mode: 'chat' as const }),
)
renderModal()
@@ -288,7 +287,7 @@ describe('CreateAppModal', () => {
icon_type: 'emoji',
icon: '🤖',
icon_background: '#F3FEE7',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
})
})
},
@@ -302,7 +301,7 @@ describe('CreateAppModal', () => {
async ({ platform, modifier }) => {
vi.spyOn(navigator, 'platform', 'get').mockReturnValue(platform)
mockCreateApp.mockResolvedValue(
createAppDetailFixture({ id: 'reopened-app', mode: AppModeEnum.ADVANCED_CHAT }),
createAppDetailFixture({ id: 'reopened-app', mode: 'advanced-chat' as const }),
)
const onClose = vi.fn()
const modal = (show: boolean) => (
@@ -310,7 +309,7 @@ describe('CreateAppModal', () => {
show={show}
onClose={onClose}
onCreateFromTemplate={vi.fn()}
defaultAppMode={AppModeEnum.ADVANCED_CHAT}
defaultAppMode={'advanced-chat'}
/>
)
const { rerender } = render(modal(true))
@@ -366,7 +365,7 @@ describe('CreateAppModal', () => {
it('should switch between app types before creating a completion app', async () => {
mockCreateApp.mockResolvedValue(
createAppDetailFixture({ id: 'completion-app', mode: AppModeEnum.COMPLETION }),
createAppDetailFixture({ id: 'completion-app', mode: 'completion' as const }),
)
renderModal()
@@ -385,7 +384,7 @@ describe('CreateAppModal', () => {
expect(mockCreateApp).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Completion App',
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
}),
)
})
@@ -416,7 +415,7 @@ describe('CreateAppModal', () => {
expect(mockCreateApp).toHaveBeenCalledTimes(1)
resolveCreate?.(createAppDetailFixture({ id: 'slow-app', mode: AppModeEnum.ADVANCED_CHAT }))
resolveCreate?.(createAppDetailFixture({ id: 'slow-app', mode: 'advanced-chat' as const }))
await waitFor(() => {
expect(mockGetRedirection).toHaveBeenCalledTimes(1)
})
@@ -1,6 +1,7 @@
'use client'
import type { Hotkey } from '@tanstack/react-hotkeys'
import type { AppType } from '@/app/components/apps/app-type'
import type { IconPickerValue } from '@/app/components/base/icon-picker'
import { zPostAppsBody } from '@dify/contracts/api/console/apps/zod.gen'
import { Button } from '@langgenius/dify-ui/button'
@@ -29,7 +30,6 @@ import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useTheme from '@/hooks/use-theme'
import { useRouter } from '@/next/navigation'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import { trackCreateApp } from '@/utils/create-app-tracking'
import { hasPermission } from '@/utils/permission'
@@ -39,17 +39,13 @@ import { CreateAppDialogShell } from '../create-app-dialog-shell'
type CreateAppProps = {
onClose: () => void
onCreateFromTemplate?: () => void
defaultAppMode?: AppModeEnum
defaultAppMode?: AppType
}
const CREATE_APP_HOTKEY = 'Mod+Enter' satisfies Hotkey
const shouldExpandBeginnerAppTypes = (appMode?: AppModeEnum) => {
return (
appMode === AppModeEnum.CHAT ||
appMode === AppModeEnum.AGENT_CHAT ||
appMode === AppModeEnum.COMPLETION
)
const shouldExpandBeginnerAppTypes = (appMode?: AppType) => {
return appMode === 'chat' || appMode === 'agent-chat' || appMode === 'completion'
}
function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppProps) {
@@ -58,7 +54,7 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
const nameInputId = useId()
const contentRef = useRef<HTMLDivElement>(null)
const [appMode, setAppMode] = useState<AppModeEnum>(defaultAppMode || AppModeEnum.ADVANCED_CHAT)
const [appMode, setAppMode] = useState<AppType>(defaultAppMode || 'advanced-chat')
const [appIcon, setAppIcon] = useState<IconPickerValue>({
type: 'emoji',
icon: '🤖',
@@ -209,7 +205,7 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
<div>
<div className="flex flex-row gap-2">
<AppTypeCard
active={appMode === AppModeEnum.WORKFLOW}
active={appMode === 'workflow'}
title={t(($) => $['types.workflow'], { ns: 'app' })}
description={t(($) => $['newApp.workflowShortDescription'], { ns: 'app' })}
icon={
@@ -221,11 +217,11 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
</div>
}
onClick={() => {
setAppMode(AppModeEnum.WORKFLOW)
setAppMode('workflow')
}}
/>
<AppTypeCard
active={appMode === AppModeEnum.ADVANCED_CHAT}
active={appMode === 'advanced-chat'}
title={t(($) => $['types.advanced'], { ns: 'app' })}
description={t(($) => $['newApp.advancedShortDescription'], { ns: 'app' })}
icon={
@@ -237,7 +233,7 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
</div>
}
onClick={() => {
setAppMode(AppModeEnum.ADVANCED_CHAT)
setAppMode('advanced-chat')
}}
/>
</div>
@@ -261,7 +257,7 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
{isAppTypeExpanded && (
<div className="flex flex-row gap-2">
<AppTypeCard
active={appMode === AppModeEnum.CHAT}
active={appMode === 'chat'}
title={t(($) => $['types.chatbot'], { ns: 'app' })}
description={t(($) => $['newApp.chatbotShortDescription'], { ns: 'app' })}
icon={
@@ -273,11 +269,11 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
</div>
}
onClick={() => {
setAppMode(AppModeEnum.CHAT)
setAppMode('chat')
}}
/>
<AppTypeCard
active={appMode === AppModeEnum.AGENT_CHAT}
active={appMode === 'agent-chat'}
title={t(($) => $['types.agent'], { ns: 'app' })}
description={t(($) => $['newApp.agentShortDescription'], { ns: 'app' })}
icon={
@@ -289,11 +285,11 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
</div>
}
onClick={() => {
setAppMode(AppModeEnum.AGENT_CHAT)
setAppMode('agent-chat')
}}
/>
<AppTypeCard
active={appMode === AppModeEnum.COMPLETION}
active={appMode === 'completion'}
title={t(($) => $['newApp.completeApp'], { ns: 'app' })}
description={t(($) => $['newApp.completionShortDescription'], { ns: 'app' })}
icon={
@@ -305,7 +301,7 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
</div>
}
onClick={() => {
setAppMode(AppModeEnum.COMPLETION)
setAppMode('completion')
}}
/>
</div>
@@ -400,20 +396,11 @@ function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppP
'repeating-linear-gradient(135deg, transparent, transparent 2px, rgba(16,24,40,0.04) 4px,transparent 3px, transparent 6px)',
}}
>
<AppScreenShot show={appMode === AppModeEnum.CHAT} mode={AppModeEnum.CHAT} />
<AppScreenShot
show={appMode === AppModeEnum.ADVANCED_CHAT}
mode={AppModeEnum.ADVANCED_CHAT}
/>
<AppScreenShot
show={appMode === AppModeEnum.AGENT_CHAT}
mode={AppModeEnum.AGENT_CHAT}
/>
<AppScreenShot
show={appMode === AppModeEnum.COMPLETION}
mode={AppModeEnum.COMPLETION}
/>
<AppScreenShot show={appMode === AppModeEnum.WORKFLOW} mode={AppModeEnum.WORKFLOW} />
<AppScreenShot show={appMode === 'chat'} mode={'chat'} />
<AppScreenShot show={appMode === 'advanced-chat'} mode={'advanced-chat'} />
<AppScreenShot show={appMode === 'agent-chat'} mode={'agent-chat'} />
<AppScreenShot show={appMode === 'completion'} mode={'completion'} />
<AppScreenShot show={appMode === 'workflow'} mode={'workflow'} />
</div>
<div className="absolute inset-x-0 border-b border-b-divider-subtle"></div>
</div>
@@ -479,31 +466,31 @@ function AppTypeCard({ icon, title, description, active, onClick }: AppTypeCardP
)
}
function AppPreview({ mode }: { mode: AppModeEnum }) {
function AppPreview({ mode }: { mode: AppType }) {
const { t } = useTranslation(['app'])
const previewInfo = (() => {
switch (mode) {
case AppModeEnum.CHAT:
case 'chat':
return {
title: t(($) => $['types.chatbot'], { ns: 'app' }),
description: t(($) => $['newApp.chatbotUserDescription'], { ns: 'app' }),
}
case AppModeEnum.ADVANCED_CHAT:
case 'advanced-chat':
return {
title: t(($) => $['types.advanced'], { ns: 'app' }),
description: t(($) => $['newApp.advancedUserDescription'], { ns: 'app' }),
}
case AppModeEnum.AGENT_CHAT:
case 'agent-chat':
return {
title: t(($) => $['types.agent'], { ns: 'app' }),
description: t(($) => $['newApp.agentUserDescription'], { ns: 'app' }),
}
case AppModeEnum.COMPLETION:
case 'completion':
return {
title: t(($) => $['newApp.completeApp'], { ns: 'app' }),
description: t(($) => $['newApp.completionUserDescription'], { ns: 'app' }),
}
case AppModeEnum.WORKFLOW:
case 'workflow':
return {
title: t(($) => $['types.workflow'], { ns: 'app' }),
description: t(($) => $['newApp.workflowUserDescription'], { ns: 'app' }),
@@ -525,14 +512,14 @@ function AppPreview({ mode }: { mode: AppModeEnum }) {
)
}
function AppScreenShot({ mode, show }: { mode: AppModeEnum; show: boolean }) {
function AppScreenShot({ mode, show }: { mode: AppType; show: boolean }) {
const { theme } = useTheme()
const modeToImageMap = {
[AppModeEnum.CHAT]: 'Chatbot',
[AppModeEnum.ADVANCED_CHAT]: 'Chatflow',
[AppModeEnum.AGENT_CHAT]: 'Agent',
[AppModeEnum.COMPLETION]: 'TextGenerator',
[AppModeEnum.WORKFLOW]: 'Workflow',
chat: 'Chatbot',
'advanced-chat': 'Chatflow',
'agent-chat': 'Agent',
completion: 'TextGenerator',
workflow: 'Workflow',
}
return (
<picture>
@@ -4,7 +4,6 @@ import userEvent from '@testing-library/user-event'
import { DSLImportMode, DSLImportStatus } from '@/models/app'
import { httpErrorFromResponse } from '@/service/http-error'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { AppModeEnum } from '@/types/app'
import CreateFromDSLModal from '../index'
import { CreateFromDSLModalTab } from '../types'
@@ -163,7 +162,7 @@ describe('CreateFromDSLModal', () => {
id: 'package-import',
status: 'completed-with-warnings',
app_id: 'agent-app',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
warnings: [
{ code: 'agent_skill_missing', path: 'skills.s_000001', message: 'Missing Skill' },
],
@@ -180,7 +179,7 @@ describe('CreateFromDSLModal', () => {
})
expect(mockResolveImportedAppRedirectionTarget).toHaveBeenCalledWith({
id: 'agent-app',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
permission_keys: undefined,
})
},
@@ -193,7 +192,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSL.mockResolvedValue({
id: 'import',
status: 'completed',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
})
render(<CreateFromDSLModal show onClose={vi.fn()} />)
const input = document.querySelector<HTMLInputElement>('input[type="file"]')
@@ -309,7 +308,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-1',
status: DSLImportStatus.COMPLETED,
app_id: 'app-1',
app_mode: AppModeEnum.CHAT,
app_mode: 'chat',
permission_keys: ['app.acl.view_layout'],
})
@@ -336,7 +335,7 @@ describe('CreateFromDSLModal', () => {
})
expect(mockTrackCreateApp).toHaveBeenCalledWith({
source: 'studio_upload',
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
})
expect(handleSuccess).toHaveBeenCalledTimes(1)
expect(handleClose).toHaveBeenCalledTimes(1)
@@ -358,7 +357,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-no-permissions',
status: DSLImportStatus.COMPLETED,
app_id: 'app-no-permissions',
app_mode: AppModeEnum.WORKFLOW,
app_mode: 'workflow',
})
render(
@@ -375,7 +374,7 @@ describe('CreateFromDSLModal', () => {
})
expect(mockGetRedirection).toHaveBeenCalledWith(
{ id: 'app-no-permissions', mode: AppModeEnum.WORKFLOW, permission_keys: undefined },
{ id: 'app-no-permissions', mode: 'workflow' as const, permission_keys: undefined },
mockPush,
{
currentUserId: 'user-1',
@@ -391,7 +390,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-2',
status: DSLImportStatus.COMPLETED_WITH_WARNINGS,
app_id: 'app-2',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
permission_keys: ['app.acl.view_layout'],
warnings: [
{
@@ -446,7 +445,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-file-in-flight',
status: DSLImportStatus.COMPLETED,
app_id: 'app-1',
app_mode: AppModeEnum.CHAT,
app_mode: 'chat',
permission_keys: ['app.acl.view_layout'],
})
@@ -517,7 +516,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSLConfirm.mockResolvedValue({
status: DSLImportStatus.COMPLETED,
app_id: 'app-3',
app_mode: AppModeEnum.WORKFLOW,
app_mode: 'workflow',
permission_keys: ['app.acl.view_layout'],
})
@@ -548,10 +547,10 @@ describe('CreateFromDSLModal', () => {
})
expect(mockTrackCreateApp).toHaveBeenCalledWith({
source: 'studio_upload',
appMode: AppModeEnum.WORKFLOW,
appMode: 'workflow' as const,
})
expect(mockGetRedirection).toHaveBeenCalledWith(
{ id: 'app-3', mode: AppModeEnum.WORKFLOW, permission_keys: ['app.acl.view_layout'] },
{ id: 'app-3', mode: 'workflow' as const, permission_keys: ['app.acl.view_layout'] },
mockPush,
{
currentUserId: 'user-1',
@@ -569,7 +568,7 @@ describe('CreateFromDSLModal', () => {
const completed = {
status: DSLImportStatus.COMPLETED,
app_id: 'created-app',
app_mode: AppModeEnum.WORKFLOW,
app_mode: 'workflow',
}
mockImportDSL.mockResolvedValue(
confirmed ? { id: 'pending-import', status: DSLImportStatus.PENDING } : completed,
@@ -606,7 +605,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSLConfirm.mockResolvedValue({
status: DSLImportStatus.COMPLETED_WITH_WARNINGS,
app_id: 'agent-app-1',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
warnings: [
{
code: 'agent_tool_authorization_required',
@@ -618,7 +617,7 @@ describe('CreateFromDSLModal', () => {
})
mockResolveImportedAppRedirectionTarget.mockResolvedValueOnce({
id: 'agent-app-1',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
permission_keys: undefined,
bound_agent_id: 'agent-1',
})
@@ -645,13 +644,13 @@ describe('CreateFromDSLModal', () => {
})
expect(mockResolveImportedAppRedirectionTarget).toHaveBeenCalledWith({
id: 'agent-app-1',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
permission_keys: undefined,
})
expect(mockGetRedirection).toHaveBeenCalledWith(
expect.objectContaining({
id: 'agent-app-1',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
bound_agent_id: 'agent-1',
}),
mockPush,
@@ -771,7 +770,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-in-flight',
status: DSLImportStatus.COMPLETED,
app_id: 'app-1',
app_mode: AppModeEnum.CHAT,
app_mode: 'chat',
permission_keys: ['app.acl.view_layout'],
})
})
@@ -834,7 +833,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-confirming',
status: DSLImportStatus.COMPLETED,
app_id: 'app-confirming',
app_mode: AppModeEnum.WORKFLOW,
app_mode: 'workflow',
})
})
})
@@ -882,7 +881,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSL.mockResolvedValue({
id: 'package-import',
status: 'completed',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
})
render(<CreateFromDSLModal show onClose={vi.fn()} />)
expect(screen.getByText('apps-full')).toBeInTheDocument()
@@ -918,7 +917,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSL.mockResolvedValue({
id: 'package-import',
status: 'completed',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
})
render(<CreateFromDSLModal show onClose={vi.fn()} />)
expect(
@@ -1,8 +1,7 @@
'use client'
import type { AppImportPayload, Import } from '@dify/contracts/api/console/apps/types.gen'
import type { AppImportPayload, AppMode, Import } from '@dify/contracts/api/console/apps/types.gen'
import type { Hotkey } from '@tanstack/react-hotkeys'
import type { AppModeEnum } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import {
Dialog,
@@ -30,7 +29,6 @@ import { userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useRouter } from '@/next/navigation'
import { consoleQuery } from '@/service/console'
import { AppModeEnum as AppMode } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import { trackCreateApp } from '@/utils/create-app-tracking'
import { getErrorMessage } from '@/utils/error-message'
@@ -65,20 +63,20 @@ type ImportSource =
const CREATE_FROM_DSL_HOTKEY = 'Mod+Enter' satisfies Hotkey
function getImportedAppMode(mode?: string | null): AppModeEnum | undefined {
function getImportedAppMode(mode?: string | null): AppMode | undefined {
switch (mode) {
case AppMode.COMPLETION:
return AppMode.COMPLETION
case AppMode.WORKFLOW:
return AppMode.WORKFLOW
case AppMode.CHAT:
return AppMode.CHAT
case AppMode.ADVANCED_CHAT:
return AppMode.ADVANCED_CHAT
case AppMode.AGENT_CHAT:
return AppMode.AGENT_CHAT
case AppMode.AGENT:
return AppMode.AGENT
case 'completion':
return 'completion'
case 'workflow':
return 'workflow'
case 'chat':
return 'chat'
case 'advanced-chat':
return 'advanced-chat'
case 'agent-chat':
return 'agent-chat'
case 'agent':
return 'agent'
default:
return undefined
}
@@ -33,7 +33,6 @@ import {
seedAppDetail,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppDeploy from '..'
import { EnvironmentTable } from '../environment-table'
@@ -745,7 +744,7 @@ function render(
queryClient,
createAppDetailFixture({
...mockBuiltInEnvironment.appDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow',
permission_keys: appPermissionKeys,
}),
)
+1 -5
View File
@@ -18,7 +18,6 @@ import { userProfileQueryOptions } from '@/features/account-profile/client'
import { getDocLanguage } from '@/i18n/language'
import { useParams } from '@/next/navigation'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getAppACLCapabilities } from '@/utils/permission'
import { BuiltInEnvironmentCard } from './built-in-environment-card'
import { EnvironmentTable } from './environment-table'
@@ -170,10 +169,7 @@ export default function AppDeploy() {
workspacePermissionKeys,
})
if (
(appDetail.mode !== AppModeEnum.WORKFLOW && appDetail.mode !== AppModeEnum.ADVANCED_CHAT) ||
!canDeploy
)
if ((appDetail.mode !== 'workflow' && appDetail.mode !== 'advanced-chat') || !canDeploy)
return null
return (
@@ -6,7 +6,6 @@ import { PageType } from '@/app/components/base/features/new-feature-panel/annot
import { consoleQuery } from '@/service/console'
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import LogAnnotation from '../index'
vi.mock('@/app/components/app/annotation', () => ({
@@ -75,7 +74,7 @@ describe('LogAnnotation', () => {
it('should render log content without the old page tabs', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.CHAT })
appDetail = createMockApp({ mode: 'chat' })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.log} />)
@@ -88,7 +87,7 @@ describe('LogAnnotation', () => {
it('should render completion logs without the old page tabs', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.COMPLETION })
appDetail = createMockApp({ mode: 'completion' as const })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.log} />)
@@ -101,7 +100,7 @@ describe('LogAnnotation', () => {
it('should hide tabs and render workflow log in workflow mode', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.WORKFLOW })
appDetail = createMockApp({ mode: 'workflow' as const })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.log} />)
@@ -116,7 +115,7 @@ describe('LogAnnotation', () => {
describe('Props', () => {
it('should render log content when page type is log', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.CHAT })
appDetail = createMockApp({ mode: 'chat' })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.log} />)
@@ -128,7 +127,7 @@ describe('LogAnnotation', () => {
it('should render annotation content when page type is annotation', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.CHAT })
appDetail = createMockApp({ mode: 'chat' })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.annotation} />)
@@ -1,4 +1,5 @@
'use client'
import type { FC } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'
import * as React from 'react'
@@ -7,7 +8,6 @@ import Log from '@/app/components/app/log'
import WorkflowLog from '@/app/components/app/workflow-log'
import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
type Props = Readonly<{
appId: string
@@ -24,11 +24,11 @@ const LogAnnotation: FC<Props> = ({ appId, pageType }) => {
return (
<div className="flex h-full flex-col px-6 pt-3">
<div className="h-0 grow">
{pageType === PageType.log && appDetail.mode !== AppModeEnum.WORKFLOW && (
{pageType === PageType.log && appDetail.mode !== 'workflow' && (
<Log appDetail={appDetail} />
)}
{pageType === PageType.annotation && <Annotation appDetail={appDetail} />}
{pageType === PageType.log && appDetail.mode === AppModeEnum.WORKFLOW && (
{pageType === PageType.log && appDetail.mode === 'workflow' && (
<WorkflowLog appDetail={appDetail} />
)}
</div>
@@ -1,8 +1,7 @@
import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen'
import type { AppDetailWithSite, AppMode } from '@dify/contracts/api/console/apps/types.gen'
import { screen } from '@testing-library/react'
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import EmptyElement from '../empty-element'
vi.mock('react-i18next', async () => {
@@ -39,7 +38,7 @@ vi.mock('@/utils/var', () => ({
}))
describe('EmptyElement', () => {
const createMockAppDetail = (mode: AppModeEnum) =>
const createMockAppDetail = (mode: AppMode) =>
createAppDetailFixture({
id: 'test-app-id',
name: 'Test App',
@@ -59,14 +58,14 @@ describe('EmptyElement', () => {
describe('Rendering', () => {
it('should render empty element with title', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
expect(screen.getByText('table.empty.element.title')).toBeInTheDocument()
})
it('should render Trans component with i18n key', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
expect(screen.getByText('table.empty.element.content', { exact: false })).toBeInTheDocument()
@@ -75,7 +74,7 @@ describe('EmptyElement', () => {
describe('App Mode Handling', () => {
it('should use CHAT mode for chat apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -83,7 +82,7 @@ describe('EmptyElement', () => {
})
it('should use COMPLETION mode for completion apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.COMPLETION)
const appDetail = createMockAppDetail('completion')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -91,7 +90,7 @@ describe('EmptyElement', () => {
})
it('should use WORKFLOW mode for workflow apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.WORKFLOW)
const appDetail = createMockAppDetail('workflow')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -99,7 +98,7 @@ describe('EmptyElement', () => {
})
it('should use CHAT mode for advanced-chat apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.ADVANCED_CHAT)
const appDetail = createMockAppDetail('advanced-chat')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -107,7 +106,7 @@ describe('EmptyElement', () => {
})
it('should use CHAT mode for agent-chat apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.AGENT_CHAT)
const appDetail = createMockAppDetail('agent-chat')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -128,7 +127,7 @@ describe('EmptyElement', () => {
describe('Links', () => {
it('should render share link with correct attributes', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
const links = screen.getAllByRole('link')
@@ -139,7 +138,7 @@ describe('EmptyElement', () => {
})
it('should render test link with redirection path', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
const links = screen.getAllByRole('link')
@@ -3,7 +3,6 @@ import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import dayjs from 'dayjs'
import { APP_PAGE_LIMIT } from '@/config'
import { AppModeEnum } from '@/types/app'
import Logs from '../index'
vi.mock('@/context/i18n', () => ({
@@ -94,7 +93,7 @@ describe('Logs', () => {
appDetail={
{
id: 'app-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as any
}
/>,
@@ -125,7 +124,7 @@ describe('Logs', () => {
appDetail={
{
id: 'app-2',
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
} as any
}
/>,
@@ -150,7 +149,7 @@ describe('Logs', () => {
appDetail={
{
id: 'app-3',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as any
}
/>,
@@ -174,7 +173,7 @@ describe('Logs', () => {
appDetail={
{
id: 'app-sandbox-last-30-days',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as any
}
/>,
@@ -200,7 +199,7 @@ describe('Logs', () => {
const user = userEvent.setup()
const appDetail = {
id: 'app-period-transition',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as any
mockUseChatConversations.mockReturnValue({
data: { total: 0 },
@@ -8,7 +8,6 @@ import { QueryClientTestProvider } from '@/test/console/query-provider'
import { render } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { renderWithNuqs } from '@/test/nuqs-testing'
import { AppModeEnum } from '@/types/app'
import ConversationList from '../list'
const mockFetchChatMessages = vi.fn()
@@ -304,7 +303,7 @@ const createChatMessage = (id: string, overrides: Record<string, unknown> = {})
})
const renderConversationList = ({
appDetail = { id: 'app-1', mode: AppModeEnum.CHAT } as any,
appDetail = { id: 'app-1', mode: 'chat' as const } as any,
logs = createLogs() as any,
searchParams = '?page=2',
}: {
@@ -518,36 +517,39 @@ describe('ConversationList', () => {
)
it.each([
['chatbot', AppModeEnum.CHAT, 'false'],
['agent', AppModeEnum.AGENT_CHAT, 'false'],
['chatflow', AppModeEnum.ADVANCED_CHAT, 'true'],
])('should expose run details only for %s conversation answers', async (_, mode, expected) => {
mockChatConversationDetail = {
id: 'conversation-1',
created_at: 1710000000,
model_config: {
model: 'gpt-4o',
configs: {
introduction: 'Hello there',
['chatbot', 'chat', 'false'],
['agent', 'agent-chat', 'false'],
['chatflow', 'advanced-chat', 'true'],
] as const)(
'should expose run details only for %s conversation answers',
async (_, mode, expected) => {
mockChatConversationDetail = {
id: 'conversation-1',
created_at: 1710000000,
model_config: {
model: 'gpt-4o',
configs: {
introduction: 'Hello there',
},
user_input_form: [],
},
user_input_form: [],
},
message: {
inputs: {},
},
}
mockFetchChatMessages.mockResolvedValue({
data: [createChatMessage('message-1')],
has_more: false,
})
message: {
inputs: {},
},
}
mockFetchChatMessages.mockResolvedValue({
data: [createChatMessage('message-1')],
has_more: false,
})
renderConversationList({
appDetail: { id: 'app-1', mode } as any,
searchParams: '?conversation_id=conversation-1',
})
renderConversationList({
appDetail: { id: 'app-1', mode } as any,
searchParams: '?conversation_id=conversation-1',
})
expect(await screen.findByTestId('chat-panel')).toHaveAttribute('data-log-enabled', expected)
})
expect(await screen.findByTestId('chat-panel')).toHaveAttribute('data-log-enabled', expected)
},
)
it('should render completion details and refetch after feedback updates', async () => {
mockCompletionConversationDetail = {
@@ -575,7 +577,7 @@ describe('ConversationList', () => {
}
renderConversationList({
appDetail: { id: 'app-1', mode: AppModeEnum.COMPLETION } as any,
appDetail: { id: 'app-1', mode: 'completion' as const } as any,
logs: createCompletionLogs() as any,
searchParams: '?page=2&conversation_id=conversation-1',
})
@@ -605,7 +607,7 @@ describe('ConversationList', () => {
it('should render chatflow status cells and feedback counters for advanced chat logs', () => {
renderConversationList({
appDetail: { id: 'app-1', mode: AppModeEnum.ADVANCED_CHAT } as any,
appDetail: { id: 'app-1', mode: 'advanced-chat' as const } as any,
logs: {
data: [
{
@@ -744,7 +746,7 @@ describe('ConversationList', () => {
})
})
it.each(['workflow', 'agent', 'prompt'])(
it.each(['workflow', 'agent', 'prompt'] as const)(
'should open and close the selected %s log within the conversation',
async (kind) => {
mockChatConversationDetail = {
@@ -753,7 +755,7 @@ describe('ConversationList', () => {
message: { inputs: {} },
}
renderConversationList({
appDetail: { id: 'app-1', mode: AppModeEnum.ADVANCED_CHAT },
appDetail: { id: 'app-1', mode: 'advanced-chat' as const },
searchParams: '?conversation_id=conversation-1',
})
const user = userEvent.setup()
@@ -776,7 +778,7 @@ describe('ConversationList', () => {
message: { inputs: {} },
}
renderConversationList({
appDetail: { id: 'app-1', mode: AppModeEnum.ADVANCED_CHAT },
appDetail: { id: 'app-1', mode: 'advanced-chat' as const },
searchParams: '?conversation_id=conversation-1',
})
const user = userEvent.setup()
@@ -791,7 +793,7 @@ describe('ConversationList', () => {
it('should clear a selected log when URL navigation changes or closes the conversation', async () => {
const user = userEvent.setup()
const queryClient = createAccountProfileQueryClient({ timezone: 'Asia/Shanghai' })
const appDetail = createAppDetailFixture({ mode: AppModeEnum.ADVANCED_CHAT })
const appDetail = createAppDetailFixture({ mode: 'advanced-chat' as const })
const view = (searchParams: string) => (
<NuqsTestingAdapter searchParams={searchParams} hasMemory>
<QueryClientTestProvider queryClient={queryClient}>
+1 -3
View File
@@ -10,7 +10,6 @@ import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Link from '@/next/link'
import { AppModeEnum } from '@/types/app'
import { getRedirectionPath } from '@/utils/app-redirection'
import { basePath } from '@/utils/var'
@@ -46,8 +45,7 @@ const EmptyElement: FC<{ appDetail: AppDetailWithSite }> = ({ appDetail }) => {
const isRbacEnabled = systemFeatures.rbac_enabled
const getWebAppType = (appType: AppDetailWithSite['mode']) => {
if (appType !== AppModeEnum.COMPLETION && appType !== AppModeEnum.WORKFLOW)
return AppModeEnum.CHAT
if (appType !== 'completion' && appType !== 'workflow') return 'chat'
return appType
}
+1 -2
View File
@@ -14,7 +14,6 @@ import { APP_PAGE_LIMIT } from '@/config'
import { useDocLink } from '@/context/i18n'
import { usePathname, useRouter, useSearchParams } from '@/next/navigation'
import { useChatConversations, useCompletionConversations } from '@/service/use-log'
import { AppModeEnum } from '@/types/app'
import PageTitle from '../log-annotation/page-title'
import {
resolveLogTimePeriod,
@@ -96,7 +95,7 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
}, [appDetail.id, currPage, limit, queryParams])
// Get the app type first
const isChatMode = appDetail.mode !== AppModeEnum.COMPLETION
const isChatMode = appDetail.mode !== 'completion'
const query = {
page: currPage + 1,
+5 -11
View File
@@ -61,7 +61,6 @@ import {
} from '@/service/log'
import { AppSourceType } from '@/service/share'
import { useChatConversationDetail, useCompletionConversationDetail } from '@/service/use-log'
import { AppModeEnum } from '@/types/app'
import PromptLogModal from '../../base/prompt-log-modal'
import {
applyAnnotationAdded,
@@ -301,12 +300,7 @@ function DetailPanel({ appDetail, detail, onClose, onFeedback }: IDetailPanel) {
// Only load initial messages, don't auto-load more
useEffect(() => {
if (
appDetail.id &&
detail.id &&
appDetail.mode !== AppModeEnum.COMPLETION &&
!fetchInitiated.current
) {
if (appDetail.id && detail.id && appDetail.mode !== 'completion' && !fetchInitiated.current) {
// Mark as initialized, but don't auto-load more messages
fetchInitiated.current = true
// Still call fetchData to get initial messages
@@ -400,8 +394,8 @@ function DetailPanel({ appDetail, detail, onClose, onFeedback }: IDetailPanel) {
}
}, [hasMore, isLoading, loadMoreMessages])
const isChatMode = appDetail.mode !== AppModeEnum.COMPLETION
const isAdvanced = appDetail.mode === AppModeEnum.ADVANCED_CHAT
const isChatMode = appDetail.mode !== 'completion'
const isAdvanced = appDetail.mode === 'advanced-chat'
const logKind = selectedLogItem?.workflow_run_id
? 'workflow'
: selectedLogItem?.agent_thoughts?.length
@@ -752,8 +746,8 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
const closingConversationIdRef = useRef<string | null>(null)
const pendingConversationIdRef = useRef<string | null>(null)
const pendingConversationCacheRef = useRef<ConversationSelection | undefined>(undefined)
const isChatMode = appDetail.mode !== AppModeEnum.COMPLETION // Whether the app is a chat app
const isChatflow = appDetail.mode === AppModeEnum.ADVANCED_CHAT // Whether the app is a chatflow app
const isChatMode = appDetail.mode !== 'completion' // Whether the app is a chat app
const isChatflow = appDetail.mode === 'advanced-chat' // Whether the app is a chatflow app
const activeConversationId =
conversationIdInUrl ?? pendingConversationIdRef.current ?? currentConversation?.id
@@ -1,4 +1,5 @@
'use client'
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { ReactNode } from 'react'
import { Button, buttonVariants } from '@langgenius/dify-ui/button'
@@ -14,7 +15,6 @@ import {
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { useTranslation } from 'react-i18next'
import { useDocLink } from '@/context/i18n'
import { AppModeEnum } from '@/types/app'
type CustomizeDialogProps = {
disabled?: boolean
@@ -62,7 +62,7 @@ export function CustomizeDialog({
}: CustomizeDialogProps) {
const { t } = useTranslation(['appOverview', 'common'])
const docLink = useDocLink()
const isChatApp = mode === AppModeEnum.CHAT || mode === AppModeEnum.ADVANCED_CHAT
const isChatApp = mode === 'chat' || mode === 'advanced-chat'
const repository =
sourceCodeRepository ?? (isChatApp ? 'webapp-conversation' : 'webapp-text-generator')
const apiDocLink = docLink('/api-reference/guides/get-started')

Some files were not shown because too many files have changed in this diff Show More