mirror of
https://github.com/langgenius/dify.git
synced 2026-05-20 16:57:01 +08:00
Compare commits
1 Commits
codex/upgr
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 848c15a265 |
32
pnpm-lock.yaml
generated
32
pnpm-lock.yaml
generated
@ -16,8 +16,8 @@ catalogs:
|
||||
specifier: 9.0.0
|
||||
version: 9.0.0
|
||||
'@base-ui/react':
|
||||
specifier: 1.5.0
|
||||
version: 1.5.0
|
||||
specifier: 1.4.1
|
||||
version: 1.4.1
|
||||
'@chromatic-com/storybook':
|
||||
specifier: 5.2.1
|
||||
version: 5.2.1
|
||||
@ -741,7 +741,7 @@ importers:
|
||||
devDependencies:
|
||||
'@base-ui/react':
|
||||
specifier: 'catalog:'
|
||||
version: 1.5.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
version: 1.4.1(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
'@chromatic-com/storybook':
|
||||
specifier: 'catalog:'
|
||||
version: 5.2.1(storybook@10.4.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vite-plus@0.1.21(@types/node@25.9.0)(@vitest/coverage-v8@4.1.6(@types/node@25.9.0)(@voidzero-dev/vite-plus-core@0.1.21(@types/node@25.9.0)(esbuild@0.27.2)(jiti@2.7.0)(tsx@4.22.2)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.7.0)(tsx@4.22.2)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.21(@types/node@25.9.0)(esbuild@0.27.2)(jiti@2.7.0)(tsx@4.22.2)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.7.0)(tsx@4.22.2)(typescript@6.0.3)(yaml@2.8.3)))
|
||||
@ -900,7 +900,7 @@ importers:
|
||||
version: 1.30.4(@amplitude/rrweb@2.0.0-alpha.40)
|
||||
'@base-ui/react':
|
||||
specifier: 'catalog:'
|
||||
version: 1.5.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
version: 1.4.1(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
'@emoji-mart/data':
|
||||
specifier: 'catalog:'
|
||||
version: 1.2.1
|
||||
@ -1652,8 +1652,8 @@ packages:
|
||||
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@base-ui/react@1.5.0':
|
||||
resolution: {integrity: sha512-z1gSAlced1yY+iM+mHDEtIkD8UI3Ebs52MuBPxvV6f5hRutk+xvCH/wuB7hDqDzK9JG5FoMz5nhrqtSs1wjt1A==}
|
||||
'@base-ui/react@1.4.1':
|
||||
resolution: {integrity: sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@date-fns/tz': ^1.2.0
|
||||
@ -1669,8 +1669,8 @@ packages:
|
||||
date-fns:
|
||||
optional: true
|
||||
|
||||
'@base-ui/utils@0.2.9':
|
||||
resolution: {integrity: sha512-x/PDDCYzoqPpjrdyb3VcyylTI2IjUXEtYDGi5foh7KsnmNJIIaVwA2GLgDH1dps1GgXiJbA60hM+AyuTfQzIvw==}
|
||||
'@base-ui/utils@0.2.8':
|
||||
resolution: {integrity: sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
@ -7926,8 +7926,8 @@ packages:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
reselect@5.2.0:
|
||||
resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==}
|
||||
reselect@5.1.1:
|
||||
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||
|
||||
reserved-identifiers@1.2.0:
|
||||
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
|
||||
@ -9239,10 +9239,10 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@base-ui/react@1.5.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
|
||||
'@base-ui/react@1.4.1(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@base-ui/utils': 0.2.9(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
'@base-ui/utils': 0.2.8(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
'@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
'@floating-ui/utils': 0.2.11
|
||||
react: 19.2.6
|
||||
@ -9251,13 +9251,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@base-ui/utils@0.2.9(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
|
||||
'@base-ui/utils@0.2.8(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@floating-ui/utils': 0.2.11
|
||||
react: 19.2.6
|
||||
react-dom: 19.2.6(react@19.2.6)
|
||||
reselect: 5.2.0
|
||||
reselect: 5.1.1
|
||||
use-sync-external-store: 1.6.0(react@19.2.6)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
@ -16002,7 +16002,7 @@ snapshots:
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
reselect@5.2.0: {}
|
||||
reselect@5.1.1: {}
|
||||
|
||||
reserved-identifiers@1.2.0: {}
|
||||
|
||||
@ -17028,7 +17028,7 @@ time:
|
||||
'@amplitude/analytics-browser@2.42.3': '2026-05-13T17:32:46.705Z'
|
||||
'@amplitude/plugin-session-replay-browser@1.30.4': '2026-05-14T00:11:02.360Z'
|
||||
'@antfu/eslint-config@9.0.0': '2026-05-11T06:18:58.474Z'
|
||||
'@base-ui/react@1.5.0': '2026-05-19T13:22:48.843Z'
|
||||
'@base-ui/react@1.4.1': '2026-04-20T12:24:35.520Z'
|
||||
'@chromatic-com/storybook@5.2.1': '2026-05-14T07:49:29.364Z'
|
||||
'@cucumber/cucumber@12.9.0': '2026-05-15T16:02:12.674Z'
|
||||
'@egoist/tailwindcss-icons@1.9.2': '2026-01-31T10:48:44.594Z'
|
||||
|
||||
@ -63,7 +63,7 @@ catalog:
|
||||
'@amplitude/analytics-browser': 2.42.3
|
||||
'@amplitude/plugin-session-replay-browser': 1.30.4
|
||||
'@antfu/eslint-config': 9.0.0
|
||||
'@base-ui/react': 1.5.0
|
||||
'@base-ui/react': 1.4.1
|
||||
'@chromatic-com/storybook': 5.2.1
|
||||
'@cucumber/cucumber': 12.9.0
|
||||
'@egoist/tailwindcss-icons': 1.9.2
|
||||
|
||||
@ -17,6 +17,20 @@ vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: vi.fn(),
|
||||
}))
|
||||
|
||||
const mockConfig = vi.hoisted(() => ({
|
||||
isCloudEdition: true,
|
||||
}))
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
get IS_CLOUD_EDITION() {
|
||||
return mockConfig.isCloudEdition
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const mockApp: App = {
|
||||
can_trial: true,
|
||||
app: {
|
||||
@ -70,6 +84,7 @@ describe('AppCard', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfig.isCloudEdition = true
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
@ -261,6 +276,13 @@ describe('AppCard', () => {
|
||||
app: mockApp,
|
||||
})
|
||||
})
|
||||
|
||||
it('should hide try button outside cloud edition', () => {
|
||||
mockConfig.isCloudEdition = false
|
||||
renderWithProvider(<AppCard {...defaultProps} />)
|
||||
|
||||
expect(screen.queryByRole('button', { name: /explore\.appCard\.try/ })).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Keyboard Accessibility', () => {
|
||||
|
||||
@ -4,14 +4,13 @@ import { PlusIcon } from '@heroicons/react/20/solid'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiInformation2Line } from '@remixicon/react'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import AppListContext from '@/context/app-list-context'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
import { AppTypeIcon, AppTypeLabel } from '../../type-selector'
|
||||
|
||||
type AppCardProps = {
|
||||
@ -27,8 +26,7 @@ const AppCard = ({
|
||||
}: AppCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { app: appBasicInfo } = app
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
|
||||
const canViewApp = IS_CLOUD_EDITION
|
||||
const setShowTryAppPanel = useContextSelector(AppListContext, ctx => ctx.setShowTryAppPanel)
|
||||
const handleShowTryAppPanel = useCallback(() => {
|
||||
trackEvent('preview_template', {
|
||||
@ -69,19 +67,21 @@ const AppCard = ({
|
||||
{app.description}
|
||||
</div>
|
||||
</div>
|
||||
{(canCreate || isTrialApp) && (
|
||||
{(canCreate || canViewApp) && (
|
||||
<div className={cn('absolute right-0 bottom-0 left-0 hidden bg-linear-to-t from-components-panel-gradient-2 from-[60.27%] to-transparent p-4 pt-8 group-hover:flex')}>
|
||||
<div className={cn('grid h-8 w-full grid-cols-1 items-center space-x-2', canCreate && 'grid-cols-2')}>
|
||||
<div className={cn('grid h-8 w-full grid-cols-1 items-center space-x-2', canCreate && canViewApp && 'grid-cols-2')}>
|
||||
{canCreate && (
|
||||
<Button variant="primary" onClick={() => onCreate()}>
|
||||
<PlusIcon className="mr-1 size-4" />
|
||||
<span className="text-xs">{t('newApp.useTemplate', { ns: 'app' })}</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleShowTryAppPanel}>
|
||||
<RiInformation2Line className="mr-1 size-4" />
|
||||
<span>{t('appCard.try', { ns: 'explore' })}</span>
|
||||
</Button>
|
||||
{canViewApp && (
|
||||
<Button onClick={handleShowTryAppPanel}>
|
||||
<RiInformation2Line className="mr-1 size-4" />
|
||||
<span>{t('appCard.try', { ns: 'explore' })}</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -15,6 +15,20 @@ vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: vi.fn(),
|
||||
}))
|
||||
|
||||
const mockConfig = vi.hoisted(() => ({
|
||||
isCloudEdition: true,
|
||||
}))
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
get IS_CLOUD_EDITION() {
|
||||
return mockConfig.isCloudEdition
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const createApp = (overrides?: Partial<App>): App => ({
|
||||
can_trial: true,
|
||||
app_id: 'app-id',
|
||||
@ -62,6 +76,7 @@ describe('AppCard', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfig.isCloudEdition = true
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
@ -113,6 +128,13 @@ describe('AppCard', () => {
|
||||
|
||||
expect(screen.getByText('explore.appCard.try')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide try button outside cloud edition', () => {
|
||||
mockConfig.isCloudEdition = false
|
||||
renderComponent({ canCreate: true, isExplore: true })
|
||||
|
||||
expect(screen.queryByText('explore.appCard.try')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props', () => {
|
||||
|
||||
@ -5,11 +5,10 @@ import { PlusIcon } from '@heroicons/react/20/solid'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiInformation2Line } from '@remixicon/react'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { AppTypeIcon } from '../../app/type-selector'
|
||||
|
||||
@ -30,8 +29,7 @@ const AppCard = ({
|
||||
}: AppCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { app: appBasicInfo } = app
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
|
||||
const canViewApp = IS_CLOUD_EDITION
|
||||
const handleTryApp = () => {
|
||||
trackEvent('preview_template', {
|
||||
template_id: app.app_id,
|
||||
@ -78,9 +76,9 @@ const AppCard = ({
|
||||
{app.description}
|
||||
</div>
|
||||
</div>
|
||||
{isExplore && (canCreate || isTrialApp) && (
|
||||
{isExplore && (canCreate || canViewApp) && (
|
||||
<div className={cn('absolute right-0 bottom-0 left-0 hidden bg-linear-to-t from-components-panel-gradient-2 from-[60.27%] to-transparent p-4 pt-8 group-hover:flex')}>
|
||||
<div className={cn('grid h-8 w-full grid-cols-1 space-x-2', canCreate && 'grid-cols-2')}>
|
||||
<div className={cn('grid h-8 w-full grid-cols-1 space-x-2', canCreate && canViewApp && 'grid-cols-2')}>
|
||||
{
|
||||
canCreate && (
|
||||
<Button variant="primary" className="h-7" onClick={() => onCreate()}>
|
||||
@ -89,10 +87,12 @@ const AppCard = ({
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<Button className="h-7" onClick={handleTryApp}>
|
||||
<RiInformation2Line className="mr-1 size-4" />
|
||||
<span>{t('appCard.try', { ns: 'explore' })}</span>
|
||||
</Button>
|
||||
{canViewApp && (
|
||||
<Button className="h-7" onClick={handleTryApp}>
|
||||
<RiInformation2Line className="mr-1 size-4" />
|
||||
<span>{t('appCard.try', { ns: 'explore' })}</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -51,6 +51,20 @@ vi.mock('@/utils/create-app-tracking', () => ({
|
||||
trackCreateApp: (...args: unknown[]) => mockTrackCreateApp(...args),
|
||||
}))
|
||||
|
||||
const mockConfig = vi.hoisted(() => ({
|
||||
isCloudEdition: false,
|
||||
}))
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
get IS_CLOUD_EDITION() {
|
||||
return mockConfig.isCloudEdition
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/explore/create-app-modal', () => ({
|
||||
default: (props: CreateAppModalProps) => {
|
||||
if (!props.show)
|
||||
@ -137,6 +151,7 @@ const mockMemberRole = (hasEditPermission: boolean) => {
|
||||
|
||||
type RenderOptions = {
|
||||
enableExploreBanner?: boolean
|
||||
isCloudEdition?: boolean
|
||||
}
|
||||
|
||||
const renderAppList = (
|
||||
@ -145,6 +160,7 @@ const renderAppList = (
|
||||
searchParams?: Record<string, string>,
|
||||
options: RenderOptions = {},
|
||||
) => {
|
||||
mockConfig.isCloudEdition = options.isCloudEdition ?? false
|
||||
mockMemberRole(hasEditPermission)
|
||||
const { wrapper: SystemFeaturesWrapper, queryClient } = createSystemFeaturesWrapper({
|
||||
systemFeatures: { enable_explore_banner: options.enableExploreBanner ?? false },
|
||||
@ -166,6 +182,7 @@ describe('AppList', () => {
|
||||
mockExploreData = { categories: [], allList: [] }
|
||||
mockIsLoading = false
|
||||
mockIsError = false
|
||||
mockConfig.isCloudEdition = false
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@ -400,7 +417,7 @@ describe('AppList', () => {
|
||||
allList: [createApp()],
|
||||
}
|
||||
|
||||
renderAppList(true)
|
||||
renderAppList(true, undefined, undefined, { isCloudEdition: true })
|
||||
|
||||
fireEvent.click(screen.getByText('explore.appCard.try'))
|
||||
expect(screen.getByTestId('try-app-panel')).toBeInTheDocument()
|
||||
@ -423,7 +440,7 @@ describe('AppList', () => {
|
||||
options.onSuccess?.({ app_mode: AppModeEnum.CHAT })
|
||||
})
|
||||
|
||||
renderAppList(true)
|
||||
renderAppList(true, undefined, undefined, { isCloudEdition: true })
|
||||
|
||||
fireEvent.click(screen.getByText('explore.appCard.try'))
|
||||
fireEvent.click(screen.getByTestId('try-app-create'))
|
||||
@ -444,7 +461,7 @@ describe('AppList', () => {
|
||||
allList: [createApp()],
|
||||
}
|
||||
|
||||
renderAppList(true)
|
||||
renderAppList(true, undefined, undefined, { isCloudEdition: true })
|
||||
|
||||
fireEvent.click(screen.getByText('explore.appCard.try'))
|
||||
expect(screen.getByTestId('try-app-panel')).toBeInTheDocument()
|
||||
|
||||
Reference in New Issue
Block a user