diff --git a/web/app/components/plugins/marketplace/description/index.tsx b/web/app/components/plugins/marketplace/description/index.tsx
index d1be3c9806..8cab8373f3 100644
--- a/web/app/components/plugins/marketplace/description/index.tsx
+++ b/web/app/components/plugins/marketplace/description/index.tsx
@@ -11,6 +11,7 @@ const Description = async ({
}: DescriptionProps) => {
const localeDefault = getLocaleOnServer()
const { t } = await translate(localeFromProps || localeDefault, 'plugin')
+ const { t: tCommon } = await translate(localeFromProps || localeDefault, 'common')
return (
<>
@@ -34,7 +35,8 @@ const Description = async ({
{t('category.bundles')}
- {t('marketplace.inDifyMarketplace')}
+ {tCommon('operation.in')}
+ {t('marketplace.difyMarketplace')}
>
)
diff --git a/web/app/components/tools/marketplace/index.tsx b/web/app/components/tools/marketplace/index.tsx
index e7c1aaec9e..2c56cdd322 100644
--- a/web/app/components/tools/marketplace/index.tsx
+++ b/web/app/components/tools/marketplace/index.tsx
@@ -1,9 +1,13 @@
-import { RiArrowUpDoubleLine } from '@remixicon/react'
+import {
+ RiArrowRightUpLine,
+ RiArrowUpDoubleLine,
+} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useMarketplace } from './hooks'
import List from '@/app/components/plugins/marketplace/list'
import Loading from '@/app/components/base/loading'
import { getLocaleOnClient } from '@/i18n'
+import { MARKETPLACE_URL_PREFIX } from '@/config'
type MarketplaceProps = {
searchPluginText: string
@@ -51,7 +55,15 @@ const Marketplace = ({
{t('plugin.category.bundles')}
- {t('plugin.marketplace.inDifyMarketplace')}
+ {t('common.operation.in')}
+
+ {t('plugin.marketplace.difyMarketplace')}
+
+
{
diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx
index acec3dad7e..364c5e00b9 100644
--- a/web/app/components/tools/provider-list.tsx
+++ b/web/app/components/tools/provider-list.tsx
@@ -10,10 +10,10 @@ import LabelFilter from '@/app/components/tools/labels/filter'
import Input from '@/app/components/base/input'
import ProviderDetail from '@/app/components/tools/provider/detail'
import Empty from '@/app/components/tools/add-tool-modal/empty'
-import { fetchCollectionList } from '@/service/tools'
import Card from '@/app/components/plugins/card'
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
import { useSelector as useAppContextSelector } from '@/context/app-context'
+import { useAllToolProviders } from '@/service/use-tools'
const ProviderList = () => {
const { t } = useTranslation()
@@ -36,8 +36,7 @@ const ProviderList = () => {
const handleKeywordsChange = (value: string) => {
setKeywords(value)
}
-
- const [collectionList, setCollectionList] = useState([])
+ const { data: collectionList, refetch } = useAllToolProviders()
const filteredCollectionList = useMemo(() => {
return collectionList.filter((collection) => {
if (collection.type !== activeTab)
@@ -49,13 +48,6 @@ const ProviderList = () => {
return true
})
}, [activeTab, tagFilterValue, keywords, collectionList])
- const getProviderList = async () => {
- const list = await fetchCollectionList()
- setCollectionList([...list])
- }
- useEffect(() => {
- getProviderList()
- }, [])
const [currentProvider, setCurrentProvider] = useState()
useEffect(() => {
@@ -106,7 +98,7 @@ const ProviderList = () => {
>
{
setCurrentProvider(undefined)}
- onRefreshData={getProviderList}
+ onRefreshData={refetch}
/>
)}
diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs
index 2f51cfaca3..58dec4999e 100644
--- a/web/eslint.config.mjs
+++ b/web/eslint.config.mjs
@@ -9,6 +9,7 @@ import { FlatCompat } from '@eslint/eslintrc'
import globals from 'globals'
import storybook from 'eslint-plugin-storybook'
import { fixupConfigRules } from '@eslint/compat'
+import tailwind from 'eslint-plugin-tailwindcss'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
@@ -76,6 +77,12 @@ export default combine(
? []
// TODO: remove this when upgrade to nextjs 15
: fixupConfigRules(compat.extends('next')),
+ {
+ rules: {
+ // performance issue, and not used.
+ '@next/next/no-html-link-for-pages': 'off',
+ },
+ },
{
ignores: [
'**/node_modules/*',
@@ -160,4 +167,17 @@ export default combine(
},
},
},
+ tailwind.configs['flat/recommended'],
+ {
+ rules: {
+ // due to 1k lines of tailwind config, these rule have performance issue
+ 'tailwindcss/no-contradicting-classname': 'off',
+ 'tailwindcss/no-unnecessary-arbitrary-value': 'off',
+ 'tailwindcss/enforces-shorthand': 'off',
+ 'tailwindcss/no-custom-classname': 'off',
+
+ // in the future
+ 'tailwindcss/classnames-order': 'off',
+ },
+ },
)
diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts
index b9d4f80265..97b158904b 100644
--- a/web/i18n/en-US/common.ts
+++ b/web/i18n/en-US/common.ts
@@ -44,6 +44,7 @@ const translation = {
zoomOut: 'Zoom Out',
zoomIn: 'Zoom In',
openInNewTab: 'Open in new tab',
+ in: 'in',
},
errorMsg: {
fieldRequired: '{{field}} is required',
diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts
index 91c2ac8cba..5c38a3a3ab 100644
--- a/web/i18n/en-US/plugin.ts
+++ b/web/i18n/en-US/plugin.ts
@@ -142,7 +142,7 @@ const translation = {
empower: 'Empower your AI development',
discover: 'Discover',
and: 'and',
- inDifyMarketplace: 'in Dify Marketplace',
+ difyMarketplace: 'Dify Marketplace',
moreFrom: 'More from Marketplace',
noPluginFound: 'No plugin found',
pluginsResult: '{{num}} results',
diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts
index 170713b95c..28be0bf3e9 100644
--- a/web/i18n/zh-Hans/common.ts
+++ b/web/i18n/zh-Hans/common.ts
@@ -44,6 +44,7 @@ const translation = {
zoomOut: '缩小',
zoomIn: '放大',
openInNewTab: '在新标签页打开',
+ in: '在',
},
errorMsg: {
fieldRequired: '{{field}} 为必填项',
diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts
index a6e8bceae8..f3c34a8219 100644
--- a/web/i18n/zh-Hans/plugin.ts
+++ b/web/i18n/zh-Hans/plugin.ts
@@ -142,7 +142,7 @@ const translation = {
empower: '助力您的 AI 开发',
discover: '探索',
and: '和',
- inDifyMarketplace: '在 Dify 市场中',
+ difyMarketplace: 'Dify 市场',
moreFrom: '更多来自市场',
noPluginFound: '未找到插件',
pluginsResult: '{{num}} 个插件结果',
diff --git a/web/package.json b/web/package.json
index 22a704bdcb..ba619d45f7 100644
--- a/web/package.json
+++ b/web/package.json
@@ -162,6 +162,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.13",
"eslint-plugin-storybook": "^0.10.1",
+ "eslint-plugin-tailwindcss": "^3.17.5",
"husky": "^9.1.6",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 9426a81c49..2c43d1fe44 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -422,6 +422,9 @@ importers:
eslint-plugin-storybook:
specifier: ^0.10.1
version: 0.10.1(eslint@9.13.0(jiti@1.21.6))(typescript@4.9.5)
+ eslint-plugin-tailwindcss:
+ specifier: ^3.17.5
+ version: 3.17.5(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@18.15.0)(typescript@4.9.5)))
husky:
specifier: ^9.1.6
version: 9.1.6
@@ -4444,6 +4447,12 @@ packages:
peerDependencies:
eslint: '>=6'
+ eslint-plugin-tailwindcss@3.17.5:
+ resolution: {integrity: sha512-8Mi7p7dm+mO1dHgRHHFdPu4RDTBk69Cn4P0B40vRQR+MrguUpwmKwhZy1kqYe3Km8/4nb+cyrCF+5SodOEmaow==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ tailwindcss: ^3.4.0
+
eslint-plugin-toml@0.11.1:
resolution: {integrity: sha512-Y1WuMSzfZpeMIrmlP1nUh3kT8p96mThIq4NnHrYUhg10IKQgGfBZjAWnrg9fBqguiX4iFps/x/3Hb5TxBisfdw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -13174,6 +13183,12 @@ snapshots:
- supports-color
- typescript
+ eslint-plugin-tailwindcss@3.17.5(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@18.15.0)(typescript@4.9.5))):
+ dependencies:
+ fast-glob: 3.3.2
+ postcss: 8.4.47
+ tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@18.15.0)(typescript@4.9.5))
+
eslint-plugin-toml@0.11.1(eslint@9.13.0(jiti@1.21.6)):
dependencies:
debug: 4.3.7
diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts
index 8b06d47342..36b8c3d120 100644
--- a/web/service/use-tools.ts
+++ b/web/service/use-tools.ts
@@ -13,6 +13,15 @@ import {
const NAME_SPACE = 'tools'
+const useAllToolProvidersKey = [NAME_SPACE, 'allToolProviders']
+export const useAllToolProviders = () => {
+ return useQuery({
+ queryKey: useAllToolProvidersKey,
+ queryFn: () => get('/workspaces/current/tool-providers'),
+ initialData: [],
+ })
+}
+
const useAllBuiltInToolsKey = [NAME_SPACE, 'builtIn']
export const useAllBuiltInTools = () => {
return useQuery({