mirror of
https://github.com/langgenius/dify.git
synced 2026-05-02 00:18:03 +08:00
Merge branch 'main' into feat/model-total-credits
This commit is contained in:
@ -22,6 +22,7 @@ import {
|
||||
createContext,
|
||||
useContextSelector,
|
||||
} from 'use-context-selector'
|
||||
import { useMarketplaceFilters } from '@/hooks/use-query-params'
|
||||
import { useInstalledPluginList } from '@/service/use-plugins'
|
||||
import {
|
||||
getValidCategoryKeys,
|
||||
@ -37,7 +38,6 @@ import { PLUGIN_TYPE_SEARCH_MAP } from './plugin-type-switch'
|
||||
import {
|
||||
getMarketplaceListCondition,
|
||||
getMarketplaceListFilterType,
|
||||
updateSearchParams,
|
||||
} from './utils'
|
||||
|
||||
export type MarketplaceContextValue = {
|
||||
@ -107,16 +107,22 @@ export const MarketplaceContextProvider = ({
|
||||
scrollContainerId,
|
||||
showSearchParams,
|
||||
}: MarketplaceContextProviderProps) => {
|
||||
// Use nuqs hook for URL-based filter state
|
||||
const [urlFilters, setUrlFilters] = useMarketplaceFilters()
|
||||
|
||||
const { data, isSuccess } = useInstalledPluginList(!shouldExclude)
|
||||
const exclude = useMemo(() => {
|
||||
if (shouldExclude)
|
||||
return data?.plugins.map(plugin => plugin.plugin_id)
|
||||
}, [data?.plugins, shouldExclude])
|
||||
const queryFromSearchParams = searchParams?.q || ''
|
||||
const tagsFromSearchParams = searchParams?.tags ? getValidTagKeys(searchParams.tags.split(',')) : []
|
||||
|
||||
// Initialize from URL params (legacy support) or use nuqs state
|
||||
const queryFromSearchParams = searchParams?.q || urlFilters.q
|
||||
const tagsFromSearchParams = getValidTagKeys(urlFilters.tags)
|
||||
const hasValidTags = !!tagsFromSearchParams.length
|
||||
const hasValidCategory = getValidCategoryKeys(searchParams?.category)
|
||||
const hasValidCategory = getValidCategoryKeys(urlFilters.category)
|
||||
const categoryFromSearchParams = hasValidCategory || PLUGIN_TYPE_SEARCH_MAP.all
|
||||
|
||||
const [searchPluginText, setSearchPluginText] = useState(queryFromSearchParams)
|
||||
const searchPluginTextRef = useRef(searchPluginText)
|
||||
const [filterPluginTags, setFilterPluginTags] = useState<string[]>(tagsFromSearchParams)
|
||||
@ -158,10 +164,6 @@ export const MarketplaceContextProvider = ({
|
||||
sortOrder: sortRef.current.sortOrder,
|
||||
type: getMarketplaceListFilterType(activePluginTypeRef.current),
|
||||
})
|
||||
const url = new URL(window.location.href)
|
||||
if (searchParams?.language)
|
||||
url.searchParams.set('language', searchParams?.language)
|
||||
history.replaceState({}, '', url)
|
||||
}
|
||||
else {
|
||||
if (shouldExclude && isSuccess) {
|
||||
@ -183,28 +185,32 @@ export const MarketplaceContextProvider = ({
|
||||
resetPlugins()
|
||||
}, [exclude, queryMarketplaceCollectionsAndPlugins, resetPlugins])
|
||||
|
||||
const debouncedUpdateSearchParams = useMemo(() => debounce(() => {
|
||||
updateSearchParams({
|
||||
query: searchPluginTextRef.current,
|
||||
category: activePluginTypeRef.current,
|
||||
tags: filterPluginTagsRef.current,
|
||||
})
|
||||
}, 500), [])
|
||||
|
||||
const handleUpdateSearchParams = useCallback((debounced?: boolean) => {
|
||||
const applyUrlFilters = useCallback(() => {
|
||||
if (!showSearchParams)
|
||||
return
|
||||
const nextFilters = {
|
||||
q: searchPluginTextRef.current,
|
||||
category: activePluginTypeRef.current,
|
||||
tags: filterPluginTagsRef.current,
|
||||
}
|
||||
const categoryChanged = urlFilters.category !== nextFilters.category
|
||||
setUrlFilters(nextFilters, {
|
||||
history: categoryChanged ? 'push' : 'replace',
|
||||
})
|
||||
}, [setUrlFilters, showSearchParams, urlFilters.category])
|
||||
|
||||
const debouncedUpdateSearchParams = useMemo(() => debounce(() => {
|
||||
applyUrlFilters()
|
||||
}, 500), [applyUrlFilters])
|
||||
|
||||
const handleUpdateSearchParams = useCallback((debounced?: boolean) => {
|
||||
if (debounced) {
|
||||
debouncedUpdateSearchParams()
|
||||
}
|
||||
else {
|
||||
updateSearchParams({
|
||||
query: searchPluginTextRef.current,
|
||||
category: activePluginTypeRef.current,
|
||||
tags: filterPluginTagsRef.current,
|
||||
})
|
||||
applyUrlFilters()
|
||||
}
|
||||
}, [debouncedUpdateSearchParams, showSearchParams])
|
||||
}, [applyUrlFilters, debouncedUpdateSearchParams])
|
||||
|
||||
const handleQueryPlugins = useCallback((debounced?: boolean) => {
|
||||
handleUpdateSearchParams(debounced)
|
||||
|
||||
@ -84,12 +84,14 @@ const PluginTypeSwitch = ({
|
||||
const handlePopState = useCallback(() => {
|
||||
if (!showSearchParams)
|
||||
return
|
||||
// nuqs handles popstate automatically
|
||||
const url = new URL(window.location.href)
|
||||
const category = url.searchParams.get('category') || PLUGIN_TYPE_SEARCH_MAP.all
|
||||
handleActivePluginTypeChange(category)
|
||||
}, [showSearchParams, handleActivePluginTypeChange])
|
||||
|
||||
useEffect(() => {
|
||||
// nuqs manages popstate internally, but we keep this for URL sync
|
||||
window.addEventListener('popstate', handlePopState)
|
||||
return () => {
|
||||
window.removeEventListener('popstate', handlePopState)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import type {
|
||||
CollectionsAndPluginsSearchParams,
|
||||
MarketplaceCollection,
|
||||
PluginsSearchParams,
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
@ -152,22 +151,3 @@ export const getMarketplaceListFilterType = (category: string) => {
|
||||
|
||||
return 'plugin'
|
||||
}
|
||||
|
||||
export const updateSearchParams = (pluginsSearchParams: PluginsSearchParams) => {
|
||||
const { query, category, tags } = pluginsSearchParams
|
||||
const url = new URL(window.location.href)
|
||||
const categoryChanged = url.searchParams.get('category') !== category
|
||||
if (query)
|
||||
url.searchParams.set('q', query)
|
||||
else
|
||||
url.searchParams.delete('q')
|
||||
if (category)
|
||||
url.searchParams.set('category', category)
|
||||
else
|
||||
url.searchParams.delete('category')
|
||||
if (tags && tags.length)
|
||||
url.searchParams.set('tags', tags.join(','))
|
||||
else
|
||||
url.searchParams.delete('tags')
|
||||
history[`${categoryChanged ? 'pushState' : 'replaceState'}`]({}, '', url)
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import type { ReactNode, RefObject } from 'react'
|
||||
import type { FilterState } from './filter-management'
|
||||
import { noop } from 'es-toolkit/compat'
|
||||
import { useQueryState } from 'nuqs'
|
||||
import {
|
||||
useMemo,
|
||||
useRef,
|
||||
@ -13,7 +14,6 @@ import {
|
||||
useContextSelector,
|
||||
} from 'use-context-selector'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
||||
import { PLUGIN_PAGE_TABS_MAP, usePluginPageTabs } from '../hooks'
|
||||
|
||||
export type PluginPageContextValue = {
|
||||
@ -68,8 +68,8 @@ export const PluginPageContextProvider = ({
|
||||
const options = useMemo(() => {
|
||||
return enable_marketplace ? tabs : tabs.filter(tab => tab.value !== PLUGIN_PAGE_TABS_MAP.marketplace)
|
||||
}, [tabs, enable_marketplace])
|
||||
const [activeTab, setActiveTab] = useTabSearchParams({
|
||||
defaultTab: options[0].value,
|
||||
const [activeTab, setActiveTab] = useQueryState('category', {
|
||||
defaultValue: options[0].value,
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@ -9,10 +9,6 @@ import {
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { noop } from 'es-toolkit/compat'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
useRouter,
|
||||
useSearchParams,
|
||||
} from 'next/navigation'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
@ -25,6 +21,7 @@ import { MARKETPLACE_API_PREFIX, SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import I18n from '@/context/i18n'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { usePluginInstallation } from '@/hooks/use-query-params'
|
||||
import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins'
|
||||
import { sleep } from '@/utils'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -42,9 +39,6 @@ import PluginTasks from './plugin-tasks'
|
||||
import useReferenceSetting from './use-reference-setting'
|
||||
import { useUploader } from './use-uploader'
|
||||
|
||||
const PACKAGE_IDS_KEY = 'package-ids'
|
||||
const BUNDLE_INFO_KEY = 'bundle-info'
|
||||
|
||||
export type PluginPageProps = {
|
||||
plugins: React.ReactNode
|
||||
marketplace: React.ReactNode
|
||||
@ -55,33 +49,13 @@ const PluginPage = ({
|
||||
}: PluginPageProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const searchParams = useSearchParams()
|
||||
const { replace } = useRouter()
|
||||
useDocumentTitle(t('plugin.metadata.title'))
|
||||
|
||||
// just support install one package now
|
||||
const packageId = useMemo(() => {
|
||||
const idStrings = searchParams.get(PACKAGE_IDS_KEY)
|
||||
try {
|
||||
return idStrings ? JSON.parse(idStrings)[0] : ''
|
||||
}
|
||||
catch {
|
||||
return ''
|
||||
}
|
||||
}, [searchParams])
|
||||
// Use nuqs hook for installation state
|
||||
const [{ packageId, bundleInfo }, setInstallState] = usePluginInstallation()
|
||||
|
||||
const [uniqueIdentifier, setUniqueIdentifier] = useState<string | null>(null)
|
||||
|
||||
const [dependencies, setDependencies] = useState<Dependency[]>([])
|
||||
const bundleInfo = useMemo(() => {
|
||||
const info = searchParams.get(BUNDLE_INFO_KEY)
|
||||
try {
|
||||
return info ? JSON.parse(info) : undefined
|
||||
}
|
||||
catch {
|
||||
return undefined
|
||||
}
|
||||
}, [searchParams])
|
||||
|
||||
const [isShowInstallFromMarketplace, {
|
||||
setTrue: showInstallFromMarketplace,
|
||||
@ -90,11 +64,9 @@ const PluginPage = ({
|
||||
|
||||
const hideInstallFromMarketplace = () => {
|
||||
doHideInstallFromMarketplace()
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.delete(PACKAGE_IDS_KEY)
|
||||
url.searchParams.delete(BUNDLE_INFO_KEY)
|
||||
replace(url.toString())
|
||||
setInstallState(null)
|
||||
}
|
||||
|
||||
const [manifest, setManifest] = useState<PluginDeclaration | PluginManifestInMarket | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@ -114,12 +86,17 @@ const PluginPage = ({
|
||||
return
|
||||
}
|
||||
if (bundleInfo) {
|
||||
const { data } = await fetchBundleInfoFromMarketPlace(bundleInfo)
|
||||
setDependencies(data.version.dependencies)
|
||||
showInstallFromMarketplace()
|
||||
try {
|
||||
const { data } = await fetchBundleInfoFromMarketPlace(bundleInfo)
|
||||
setDependencies(data.version.dependencies)
|
||||
showInstallFromMarketplace()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to load bundle info:', error)
|
||||
}
|
||||
}
|
||||
})()
|
||||
}, [packageId, bundleInfo])
|
||||
}, [packageId, bundleInfo, showInstallFromMarketplace])
|
||||
|
||||
const {
|
||||
referenceSetting,
|
||||
|
||||
Reference in New Issue
Block a user