mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 18:27:19 +08:00
Merge branch 'main' into jzh
This commit is contained in:
commit
f5955489ec
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@ -6,6 +6,9 @@
|
||||
|
||||
* @crazywoola @laipz8200 @Yeuoly
|
||||
|
||||
# ESLint suppression file is maintained by autofix.ci pruning.
|
||||
/eslint-suppressions.json
|
||||
|
||||
# CODEOWNERS file
|
||||
/.github/CODEOWNERS @laipz8200 @crazywoola
|
||||
|
||||
|
||||
2
.github/actions/setup-web/action.yml
vendored
2
.github/actions/setup-web/action.yml
vendored
@ -4,7 +4,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup Vite+
|
||||
uses: voidzero-dev/setup-vp@20553a7a7429c429a74894104a2835d7fed28a72 # v1.3.0
|
||||
uses: voidzero-dev/setup-vp@4f5aa3e38c781f1b01e78fb9255527cee8a6efa6 # v1.8.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: true
|
||||
|
||||
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
@ -6,5 +6,4 @@ web:
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- '.npmrc'
|
||||
- '.nvmrc'
|
||||
|
||||
1
.github/workflows/autofix.yml
vendored
1
.github/workflows/autofix.yml
vendored
@ -43,7 +43,6 @@ jobs:
|
||||
package.json
|
||||
pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
.npmrc
|
||||
.nvmrc
|
||||
- name: Check api inputs
|
||||
if: github.event_name != 'merge_group'
|
||||
|
||||
8
.github/workflows/build-push.yml
vendored
8
.github/workflows/build-push.yml
vendored
@ -74,7 +74,7 @@ jobs:
|
||||
password: ${{ env.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
@ -84,7 +84,7 @@ jobs:
|
||||
|
||||
- name: Build Docker image
|
||||
id: build
|
||||
uses: depot/build-push-action@v1
|
||||
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
|
||||
with:
|
||||
project: ${{ vars.DEPOT_PROJECT_ID }}
|
||||
context: ${{ matrix.build_context }}
|
||||
@ -124,10 +124,10 @@ jobs:
|
||||
file: "web/Dockerfile"
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@98e3b2c9eab4f4f98a95c0c0a3ea5e5e672fd2a8 # v3.10.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Validate Docker image
|
||||
uses: docker/build-push-action@5cd29d66b4a8d8e6f4d5dfe2e9329f0b1d446289 # v6.18.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
push: false
|
||||
context: ${{ matrix.build_context }}
|
||||
|
||||
8
.github/workflows/docker-build.yml
vendored
8
.github/workflows/docker-build.yml
vendored
@ -44,10 +44,10 @@ jobs:
|
||||
file: "web/Dockerfile"
|
||||
steps:
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: depot/build-push-action@v1
|
||||
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
|
||||
with:
|
||||
project: ${{ vars.DEPOT_PROJECT_ID }}
|
||||
push: false
|
||||
@ -71,10 +71,10 @@ jobs:
|
||||
file: "web/Dockerfile"
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@98e3b2c9eab4f4f98a95c0c0a3ea5e5e672fd2a8 # v3.10.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@5cd29d66b4a8d8e6f4d5dfe2e9329f0b1d446289 # v6.18.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
push: false
|
||||
context: ${{ matrix.context }}
|
||||
|
||||
2
.github/workflows/main-ci.yml
vendored
2
.github/workflows/main-ci.yml
vendored
@ -69,7 +69,6 @@ jobs:
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- '.npmrc'
|
||||
- '.nvmrc'
|
||||
- '.github/workflows/web-tests.yml'
|
||||
- '.github/actions/setup-web/**'
|
||||
@ -83,7 +82,6 @@ jobs:
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- '.npmrc'
|
||||
- '.nvmrc'
|
||||
- 'docker/docker-compose.middleware.yaml'
|
||||
- 'docker/middleware.env.example'
|
||||
|
||||
3
.github/workflows/style.yml
vendored
3
.github/workflows/style.yml
vendored
@ -83,7 +83,6 @@ jobs:
|
||||
package.json
|
||||
pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
.npmrc
|
||||
.nvmrc
|
||||
.github/workflows/style.yml
|
||||
.github/actions/setup-web/**
|
||||
@ -110,8 +109,6 @@ jobs:
|
||||
- name: Web tsslint
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
working-directory: ./web
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
run: vp run lint:tss
|
||||
|
||||
- name: Web type check
|
||||
|
||||
1
.github/workflows/tool-test-sdks.yaml
vendored
1
.github/workflows/tool-test-sdks.yaml
vendored
@ -9,7 +9,6 @@ on:
|
||||
- package.json
|
||||
- pnpm-lock.yaml
|
||||
- pnpm-workspace.yaml
|
||||
- .npmrc
|
||||
|
||||
concurrency:
|
||||
group: sdk-tests-${{ github.head_ref || github.run_id }}
|
||||
|
||||
2
.github/workflows/translate-i18n-claude.yml
vendored
2
.github/workflows/translate-i18n-claude.yml
vendored
@ -158,7 +158,7 @@ jobs:
|
||||
|
||||
- name: Run Claude Code for Translation Sync
|
||||
if: steps.context.outputs.CHANGED_FILES != ''
|
||||
uses: anthropics/claude-code-action@567fe954a4527e81f132d87d1bdbcc94f7737434 # v1.0.107
|
||||
uses: anthropics/claude-code-action@ef50f123a3a9be95b60040d042717517407c7256 # v1.0.110
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@ -113,8 +113,18 @@ def create_tenant(email: str, language: str | None = None, name: str | None = No
|
||||
# Validates name encoding for non-Latin characters.
|
||||
name = name.strip().encode("utf-8").decode("utf-8") if name else None
|
||||
|
||||
# generate random password
|
||||
new_password = secrets.token_urlsafe(16)
|
||||
# Generate a random password that satisfies the password policy.
|
||||
# The iteration limit guards against infinite loops caused by unexpected bugs in valid_password.
|
||||
for _ in range(100):
|
||||
new_password = secrets.token_urlsafe(16)
|
||||
try:
|
||||
valid_password(new_password)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
click.echo(click.style("Failed to generate a valid password. Please try again.", fg="red"))
|
||||
return
|
||||
|
||||
# register account
|
||||
account = RegisterService.register(
|
||||
|
||||
@ -385,9 +385,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/config/agent/agent-tools/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 9
|
||||
}
|
||||
@ -641,11 +638,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/app/overview/app-card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/overview/customize/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -2607,14 +2599,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/external-api/external-api-modal/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -2625,11 +2609,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/extra-info/statistics.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/formatted-text/flavours/type.ts": {
|
||||
"ts/no-empty-object-type": {
|
||||
"count": 1
|
||||
@ -3299,9 +3278,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@ -3764,11 +3740,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/detail/tool-item.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/mcp-server-modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -3782,11 +3753,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/mcp-service-card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -4241,9 +4207,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/prompt/editor.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
@ -5711,9 +5674,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/signin/one-more-step.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
|
||||
@ -2,11 +2,12 @@
|
||||
"name": "dify",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.33.2",
|
||||
"packageManager": "pnpm@11.0.0",
|
||||
"engines": {
|
||||
"node": "^22.22.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "concurrently -k -n vinext,proxy \"vp run dify-web#dev:vinext\" \"vp run dify-web#dev:proxy\"",
|
||||
"prepare": "vp config",
|
||||
"type-check": "vp run -r type-check",
|
||||
"lint": "eslint --cache --concurrency=auto",
|
||||
@ -16,6 +17,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "catalog:",
|
||||
"concurrently": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"eslint-markdown": "catalog:",
|
||||
"eslint-plugin-markdown-preferences": "catalog:",
|
||||
|
||||
2112
pnpm-lock.yaml
generated
2112
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,11 @@
|
||||
saveExact: true
|
||||
catalogMode: prefer
|
||||
dedupeDirectDeps: true
|
||||
engineStrict: true
|
||||
minimumReleaseAge: 1440
|
||||
optimisticRepeatInstall: true
|
||||
verifyDepsBeforeRun: install
|
||||
resolutionMode: time-based
|
||||
allowBuilds:
|
||||
'@parcel/watcher': false
|
||||
canvas: false
|
||||
@ -5,7 +13,6 @@ allowBuilds:
|
||||
sharp: false
|
||||
autoInstallPeers: false
|
||||
blockExoticSubdeps: true
|
||||
catalogMode: prefer
|
||||
shellEmulator: true
|
||||
strictDepBuilds: true
|
||||
trustPolicy: no-downgrade
|
||||
@ -42,13 +49,13 @@ overrides:
|
||||
svgo@>=3.0.0 <3.3.3: 3.3.3
|
||||
tar@<=7.5.10: 7.5.11
|
||||
undici@>=7.0.0 <7.24.0: 7.24.0
|
||||
vite: npm:@voidzero-dev/vite-plus-core@0.1.19
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@0.1.19
|
||||
vite: npm:@voidzero-dev/vite-plus-core@0.1.20
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@0.1.20
|
||||
yaml@>=2.0.0 <2.8.3: 2.8.3
|
||||
yauzl@<3.2.1: 3.2.1
|
||||
catalog:
|
||||
'@amplitude/analytics-browser': 2.41.1
|
||||
'@amplitude/plugin-session-replay-browser': 1.28.0
|
||||
'@amplitude/analytics-browser': 2.42.0
|
||||
'@amplitude/plugin-session-replay-browser': 1.28.1
|
||||
'@antfu/eslint-config': 8.2.0
|
||||
'@base-ui/react': 1.4.1
|
||||
'@chromatic-com/storybook': 5.1.2
|
||||
@ -61,16 +68,16 @@ catalog:
|
||||
'@formatjs/intl-localematcher': 0.8.4
|
||||
'@headlessui/react': 2.2.10
|
||||
'@heroicons/react': 2.2.0
|
||||
'@hono/node-server': 1.19.14
|
||||
'@hono/node-server': 2.0.0
|
||||
'@iconify-json/heroicons': 1.2.3
|
||||
'@iconify-json/ri': 1.2.10
|
||||
'@lexical/code': 0.43.0
|
||||
'@lexical/link': 0.43.0
|
||||
'@lexical/list': 0.43.0
|
||||
'@lexical/react': 0.43.0
|
||||
'@lexical/selection': 0.43.0
|
||||
'@lexical/text': 0.43.0
|
||||
'@lexical/utils': 0.43.0
|
||||
'@lexical/code': 0.44.0
|
||||
'@lexical/link': 0.44.0
|
||||
'@lexical/list': 0.44.0
|
||||
'@lexical/react': 0.44.0
|
||||
'@lexical/selection': 0.44.0
|
||||
'@lexical/text': 0.44.0
|
||||
'@lexical/utils': 0.44.0
|
||||
'@mdx-js/loader': 3.1.1
|
||||
'@mdx-js/react': 3.1.1
|
||||
'@mdx-js/rollup': 3.1.1
|
||||
@ -98,20 +105,20 @@ catalog:
|
||||
'@tailwindcss/postcss': 4.2.4
|
||||
'@tailwindcss/typography': 0.5.19
|
||||
'@tailwindcss/vite': 4.2.4
|
||||
'@tanstack/eslint-plugin-query': 5.100.5
|
||||
'@tanstack/eslint-plugin-query': 5.100.6
|
||||
'@tanstack/react-devtools': 0.10.2
|
||||
'@tanstack/react-form': 1.29.1
|
||||
'@tanstack/react-form-devtools': 0.2.22
|
||||
'@tanstack/react-query': 5.100.5
|
||||
'@tanstack/react-query-devtools': 5.100.5
|
||||
'@tanstack/react-query': 5.100.6
|
||||
'@tanstack/react-query-devtools': 5.100.6
|
||||
'@tanstack/react-virtual': 3.13.24
|
||||
'@testing-library/dom': 10.4.1
|
||||
'@testing-library/jest-dom': 6.9.1
|
||||
'@testing-library/react': 16.3.2
|
||||
'@testing-library/user-event': 14.6.1
|
||||
'@tsslint/cli': 3.0.4
|
||||
'@tsslint/compat-eslint': 3.0.4
|
||||
'@tsslint/config': 3.0.4
|
||||
'@tsslint/cli': 3.1.0
|
||||
'@tsslint/compat-eslint': 3.1.0
|
||||
'@tsslint/config': 3.1.0
|
||||
'@types/js-cookie': 3.0.6
|
||||
'@types/js-yaml': 4.0.9
|
||||
'@types/negotiator': 0.6.4
|
||||
@ -120,9 +127,9 @@ catalog:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3
|
||||
'@types/sortablejs': 1.15.9
|
||||
'@typescript-eslint/eslint-plugin': 8.59.0
|
||||
'@typescript-eslint/parser': 8.59.0
|
||||
'@typescript/native-preview': 7.0.0-dev.20260426.1
|
||||
'@typescript-eslint/eslint-plugin': 8.59.1
|
||||
'@typescript-eslint/parser': 8.59.1
|
||||
'@typescript/native-preview': 7.0.0-dev.20260428.1
|
||||
'@vitejs/plugin-react': 6.0.1
|
||||
'@vitejs/plugin-rsc': 0.5.25
|
||||
'@vitest/coverage-v8': 4.1.5
|
||||
@ -134,7 +141,8 @@ catalog:
|
||||
clsx: 2.1.1
|
||||
cmdk: 1.1.1
|
||||
code-inspector-plugin: 1.5.1
|
||||
copy-to-clipboard: 3.3.3
|
||||
concurrently: ^9.2.1
|
||||
copy-to-clipboard: 4.0.2
|
||||
cron-parser: 5.5.0
|
||||
dayjs: 1.11.20
|
||||
decimal.js: 10.6.0
|
||||
@ -147,8 +155,8 @@ catalog:
|
||||
emoji-mart: 5.6.0
|
||||
es-toolkit: 1.46.0
|
||||
eslint: 10.2.1
|
||||
eslint-markdown: 0.6.1
|
||||
eslint-plugin-better-tailwindcss: 4.4.1
|
||||
eslint-markdown: 0.7.0
|
||||
eslint-plugin-better-tailwindcss: 4.5.0
|
||||
eslint-plugin-hyoban: 0.14.1
|
||||
eslint-plugin-markdown-preferences: 0.41.1
|
||||
eslint-plugin-no-barrel-files: 1.3.1
|
||||
@ -161,7 +169,7 @@ catalog:
|
||||
hono: 4.12.15
|
||||
html-entities: 2.6.0
|
||||
html-to-image: 1.11.13
|
||||
i18next: 26.0.6
|
||||
i18next: 26.0.8
|
||||
i18next-resources-to-backend: 1.2.1
|
||||
iconify-import-svg: 0.2.0
|
||||
immer: 11.1.4
|
||||
@ -174,7 +182,7 @@ catalog:
|
||||
knip: 6.7.0
|
||||
ky: 2.0.2
|
||||
lamejs: 1.2.1
|
||||
lexical: 0.43.0
|
||||
lexical: 0.44.0
|
||||
loro-crdt: 1.12.0
|
||||
mermaid: 11.14.0
|
||||
mime: 4.1.0
|
||||
@ -214,18 +222,18 @@ catalog:
|
||||
string-ts: 2.3.1
|
||||
tailwind-merge: 3.5.0
|
||||
tailwindcss: 4.2.4
|
||||
tldts: 7.0.28
|
||||
tldts: 7.0.29
|
||||
tsx: 4.21.0
|
||||
typescript: 6.0.3
|
||||
uglify-js: 3.19.3
|
||||
unist-util-visit: 5.1.0
|
||||
use-context-selector: 2.0.0
|
||||
uuid: 13.0.0
|
||||
vinext: 0.0.41
|
||||
vite: npm:@voidzero-dev/vite-plus-core@0.1.19
|
||||
uuid: 14.0.0
|
||||
vinext: 0.0.45
|
||||
vite: npm:@voidzero-dev/vite-plus-core@0.1.20
|
||||
vite-plugin-inspect: 12.0.0-beta.1
|
||||
vite-plus: 0.1.19
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@0.1.19
|
||||
vite-plus: 0.1.20
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@0.1.20
|
||||
vitest-browser-react: 2.2.0
|
||||
vitest-canvas-mock: 1.1.4
|
||||
zod: 4.3.6
|
||||
|
||||
@ -1 +0,0 @@
|
||||
save-exact=true
|
||||
@ -195,9 +195,19 @@ describe('Header Nav Flow', () => {
|
||||
renderNav()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Alpha/i }))
|
||||
fireEvent.click(await screen.findByText('menus.newApp'))
|
||||
|
||||
const openCreateMenu = async () => {
|
||||
fireEvent.click(await screen.findByText('menus.newApp'))
|
||||
return screen.findByText('newApp.startFromBlank')
|
||||
}
|
||||
|
||||
await openCreateMenu()
|
||||
fireEvent.click(await screen.findByText('newApp.startFromBlank'))
|
||||
|
||||
await openCreateMenu()
|
||||
fireEvent.click(await screen.findByText('newApp.startFromTemplate'))
|
||||
|
||||
await openCreateMenu()
|
||||
fireEvent.click(await screen.findByText('importDSL'))
|
||||
|
||||
expect(mockOnCreate).toHaveBeenNthCalledWith(1, 'blank')
|
||||
|
||||
@ -6,7 +6,9 @@ import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import type { AgentTool } from '@/types/app'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiEqualizer2Line,
|
||||
@ -23,7 +25,6 @@ import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
@ -154,13 +155,23 @@ const AgentTools: FC = () => {
|
||||
title={(
|
||||
<div className="flex items-center">
|
||||
<div className="mr-1">{t('agent.tools.name', { ns: 'appDebug' })}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[180px]">
|
||||
{t('agent.tools.description', { ns: 'appDebug' })}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={t('agent.tools.description', { ns: 'appDebug' })}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-4 w-4 shrink-0 items-center justify-center rounded-sm p-px outline-hidden hover:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
>
|
||||
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent popupClassName="w-[180px] px-3 py-2 system-xs-regular text-text-tertiary">
|
||||
{t('agent.tools.description', { ns: 'appDebug' })}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
headerRight={(
|
||||
@ -216,34 +227,59 @@ const AgentTools: FC = () => {
|
||||
<span className="pr-1.5 system-xs-medium text-text-secondary">{getProviderShowName(item)}</span>
|
||||
<span className="text-text-tertiary">{item.tool_label}</span>
|
||||
{!item.isDeleted && !readonly && (
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<Popover>
|
||||
<span className="h-4 w-4">
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={item.tool_name}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="ml-0.5 hidden h-4 w-4 items-center justify-center rounded-sm outline-hidden group-hover:inline-flex hover:bg-state-base-hover focus-visible:inline-flex focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
data-testid="tool-info-tooltip"
|
||||
>
|
||||
<RiInformation2Line className="h-4 w-4 text-text-tertiary" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
<PopoverContent popupClassName="w-[180px] px-3 py-2 system-xs-regular">
|
||||
<div className="w-[180px]">
|
||||
<div className="mb-1.5 text-text-secondary">{item.tool_name}</div>
|
||||
<div className="mb-1.5 text-text-tertiary">{t('toolNameUsageTip', { ns: 'tools' })}</div>
|
||||
<div className="cursor-pointer text-text-accent" onClick={() => copy(item.tool_name)}>{t('copyToolName', { ns: 'tools' })}</div>
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer rounded-sm text-text-accent outline-hidden hover:underline focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
onClick={() => copy(item.tool_name)}
|
||||
>
|
||||
{t('copyToolName', { ns: 'tools' })}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div className="h-4 w-4">
|
||||
<div className="ml-0.5 hidden group-hover:inline-block" data-testid="tool-info-tooltip">
|
||||
<RiInformation2Line className="h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-1 flex shrink-0 items-center">
|
||||
{item.isDeleted && (
|
||||
<div className="mr-2 flex items-center">
|
||||
<Tooltip
|
||||
popupContent={t('toolRemoved', { ns: 'tools' })}
|
||||
>
|
||||
<div className="mr-1 cursor-pointer rounded-md p-1 hover:bg-black/5">
|
||||
<AlertTriangle className="h-4 w-4 text-[#F79009]" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={t('toolRemoved', { ns: 'tools' })}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="mr-1 cursor-pointer rounded-md p-1 outline-hidden hover:bg-black/5 focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
>
|
||||
<AlertTriangle className="h-4 w-4 text-[#F79009]" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent popupClassName="px-3 py-2 system-xs-regular text-text-tertiary">
|
||||
{t('toolRemoved', { ns: 'tools' })}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div
|
||||
className="cursor-pointer rounded-md p-1 text-text-tertiary hover:text-text-destructive"
|
||||
onClick={() => {
|
||||
@ -263,19 +299,25 @@ const AgentTools: FC = () => {
|
||||
{!item.isDeleted && !readonly && (
|
||||
<div className="mr-2 hidden items-center gap-1 group-hover:flex">
|
||||
{!item.notAuthor && (
|
||||
<Tooltip
|
||||
popupContent={t('setBuiltInTools.infoAndSetting', { ns: 'tools' })}
|
||||
needsDelay={false}
|
||||
>
|
||||
<div
|
||||
className="cursor-pointer rounded-md p-1 hover:bg-black/5"
|
||||
onClick={() => {
|
||||
setCurrentTool(item)
|
||||
setIsShowSettingTool(true)
|
||||
}}
|
||||
>
|
||||
<RiEqualizer2Line className="h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer rounded-md p-1 outline-hidden hover:bg-black/5 focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
aria-label={t('setBuiltInTools.infoAndSetting', { ns: 'tools' })}
|
||||
onClick={() => {
|
||||
setCurrentTool(item)
|
||||
setIsShowSettingTool(true)
|
||||
}}
|
||||
>
|
||||
<RiEqualizer2Line className="h-4 w-4 text-text-tertiary" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t('setBuiltInTools.infoAndSetting', { ns: 'tools' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div
|
||||
|
||||
@ -270,6 +270,7 @@ describe('AppCard', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'overview.appInfo.enableTooltip.description' }))
|
||||
fireEvent.click(screen.getByText('overview.appInfo.enableTooltip.learnMore'))
|
||||
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith('https://docs.example.com/use-dify/nodes/user-input', '_blank')
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import type { ConfigParams } from './settings'
|
||||
import type { AppDetailResponse } from '@/models/app'
|
||||
import type { AppSSO } from '@/types/app'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
@ -9,7 +10,6 @@ import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppBasic from '@/app/components/app-sidebar/basic'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
@ -179,6 +179,31 @@ function AppCard({
|
||||
triggerModeDisabled,
|
||||
])
|
||||
|
||||
const missingStartNodeContent = cardState.appUnpublished || cardState.missingStartNode
|
||||
? (
|
||||
<>
|
||||
<div className="mb-1 text-xs font-normal text-text-secondary">
|
||||
{t('overview.appInfo.enableTooltip.description', { ns: 'appOverview' })}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer rounded-sm text-xs font-normal text-text-accent outline-hidden hover:underline focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
onClick={() => window.open(docLink('/use-dify/nodes/user-input'), '_blank')}
|
||||
>
|
||||
{t('overview.appInfo.enableTooltip.learnMore', { ns: 'appOverview' })}
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
: ''
|
||||
|
||||
const statusPopoverContent = cardState.toggleDisabled
|
||||
? (
|
||||
triggerModeDisabled && triggerModeMessage
|
||||
? triggerModeMessage
|
||||
: missingStartNodeContent
|
||||
)
|
||||
: ''
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${isInPanel ? 'border-t border-l-[0.5px]' : 'border-[0.5px] shadow-xs'} w-full max-w-full rounded-xl border-effects-highlight ${className ?? ''} ${cardState.isMinimalState ? 'h-12' : ''}`}
|
||||
@ -187,13 +212,19 @@ function AppCard({
|
||||
{triggerModeDisabled && (
|
||||
triggerModeMessage
|
||||
? (
|
||||
<Tooltip
|
||||
popupContent={triggerModeMessage}
|
||||
popupClassName="max-w-64 rounded-xl bg-components-panel-bg px-3 py-2 text-xs text-text-secondary shadow-lg"
|
||||
position="right"
|
||||
>
|
||||
<div className="absolute inset-0 z-10 cursor-not-allowed rounded-xl" aria-hidden="true" />
|
||||
</Tooltip>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={typeof triggerModeMessage === 'string' ? triggerModeMessage : basicName}
|
||||
render={<button type="button" className="absolute inset-0 z-10 cursor-not-allowed rounded-xl outline-hidden focus-visible:ring-1 focus-visible:ring-components-input-border-hover" />}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="right"
|
||||
popupClassName="max-w-64 rounded-xl bg-components-panel-bg px-3 py-2 text-xs text-text-secondary shadow-lg"
|
||||
>
|
||||
{triggerModeMessage}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
: <div className="absolute inset-0 z-10 cursor-not-allowed rounded-xl" aria-hidden="true" />
|
||||
)}
|
||||
@ -219,38 +250,31 @@ function AppCard({
|
||||
: t('overview.status.disable', { ns: 'appOverview' })}
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
cardState.toggleDisabled
|
||||
? (
|
||||
triggerModeDisabled && triggerModeMessage
|
||||
? triggerModeMessage
|
||||
: (cardState.appUnpublished || cardState.missingStartNode)
|
||||
? (
|
||||
<>
|
||||
<div className="mb-1 text-xs font-normal text-text-secondary">
|
||||
{t('overview.appInfo.enableTooltip.description', { ns: 'appOverview' })}
|
||||
</div>
|
||||
<div
|
||||
className="cursor-pointer text-xs font-normal text-text-accent hover:underline"
|
||||
onClick={() => window.open(docLink('/use-dify/nodes/user-input'), '_blank')}
|
||||
>
|
||||
{t('overview.appInfo.enableTooltip.learnMore', { ns: 'appOverview' })}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
: ''
|
||||
)
|
||||
: ''
|
||||
}
|
||||
position="right"
|
||||
popupClassName="w-58 max-w-60 rounded-xl bg-components-panel-bg px-3.5 py-3 shadow-lg"
|
||||
offset={24}
|
||||
>
|
||||
<div>
|
||||
<Switch checked={cardState.runningStatus} onCheckedChange={onChangeStatus} disabled={cardState.toggleDisabled} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{cardState.toggleDisabled && statusPopoverContent
|
||||
? (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
nativeButton={false}
|
||||
aria-label={typeof statusPopoverContent === 'string' ? statusPopoverContent : t('overview.appInfo.enableTooltip.description', { ns: 'appOverview' })}
|
||||
render={(
|
||||
<div>
|
||||
<Switch checked={cardState.runningStatus} onCheckedChange={onChangeStatus} disabled={cardState.toggleDisabled} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="right"
|
||||
sideOffset={24}
|
||||
popupClassName="w-58 max-w-60 rounded-xl bg-components-panel-bg px-3.5 py-3 shadow-lg"
|
||||
>
|
||||
{statusPopoverContent}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
: (
|
||||
<Switch checked={cardState.runningStatus} onCheckedChange={onChangeStatus} disabled={cardState.toggleDisabled} />
|
||||
)}
|
||||
</div>
|
||||
{!cardState.isMinimalState && (
|
||||
<AppCardUrlSection
|
||||
|
||||
@ -10,13 +10,13 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiBook2Line, RiCloseLine, RiInformation2Line, RiLock2Fill } from '@remixicon/react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { memo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent } from '@/app/components/base/portal-to-follow-elem'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { createExternalAPI } from '@/service/datasets'
|
||||
import Form from './Form'
|
||||
|
||||
@ -57,15 +57,20 @@ const formSchemas: FormSchema[] = [
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
const emptyExternalAPIFormData: CreateExternalAPIReq = {
|
||||
name: '',
|
||||
settings: {
|
||||
endpoint: '',
|
||||
api_key: '',
|
||||
},
|
||||
}
|
||||
|
||||
const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({ data, onSave, onCancel, datasetBindings, isEditMode, onEdit }) => {
|
||||
const { t } = useTranslation()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
const [formData, setFormData] = useState<CreateExternalAPIReq>({ name: '', settings: { endpoint: '', api_key: '' } })
|
||||
useEffect(() => {
|
||||
if (isEditMode && data)
|
||||
setFormData(data)
|
||||
}, [isEditMode, data])
|
||||
const [formData, setFormData] = useState<CreateExternalAPIReq>(() => isEditMode && data ? data : emptyExternalAPIFormData)
|
||||
const hasEmptyInputs = Object.values(formData).some(value => typeof value === 'string' ? value.trim() === '' : Object.values(value).some(v => v.trim() === ''))
|
||||
const handleDataChange = (val: CreateExternalAPIReq) => {
|
||||
setFormData(val)
|
||||
@ -108,106 +113,121 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({ data, onSave, onCan
|
||||
}
|
||||
}
|
||||
return (
|
||||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent className="z-60 h-full w-full">
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black/25">
|
||||
<div className="shadows-shadow-xl relative flex w-[480px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg">
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="grow self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{isEditMode ? t('editExternalAPIFormTitle', { ns: 'dataset' }) : t('createExternalAPI', { ns: 'dataset' })}
|
||||
</div>
|
||||
{isEditMode && (datasetBindings?.length ?? 0) > 0 && (
|
||||
<div className="flex items-center system-xs-regular text-text-tertiary">
|
||||
{t('editExternalAPIFormWarning.front', { ns: 'dataset' })}
|
||||
<span className="flex cursor-pointer items-center text-text-accent">
|
||||
|
||||
{datasetBindings?.length}
|
||||
{' '}
|
||||
{t('editExternalAPIFormWarning.end', { ns: 'dataset' })}
|
||||
<Dialog
|
||||
open
|
||||
disablePointerDismissal
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<DialogContent className="w-[480px]! max-w-none! overflow-visible! rounded-2xl! border-[0.5px]! border-components-panel-border! bg-components-panel-bg! p-0! shadow-xl!">
|
||||
<div className="relative flex w-full flex-col items-start">
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<DialogTitle className="grow self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{isEditMode ? t('editExternalAPIFormTitle', { ns: 'dataset' }) : t('createExternalAPI', { ns: 'dataset' })}
|
||||
</DialogTitle>
|
||||
{isEditMode && (datasetBindings?.length ?? 0) > 0 && (
|
||||
<div className="flex items-center system-xs-regular text-text-tertiary">
|
||||
{t('editExternalAPIFormWarning.front', { ns: 'dataset' })}
|
||||
<span className="flex cursor-pointer items-center text-text-accent">
|
||||
|
||||
{datasetBindings?.length}
|
||||
{' '}
|
||||
{t('editExternalAPIFormWarning.end', { ns: 'dataset' })}
|
||||
|
||||
<Tooltip
|
||||
popupClassName="flex items-center self-stretch w-[320px]"
|
||||
popupContent={(
|
||||
<div className="p-1">
|
||||
<div className="flex items-start self-stretch pt-1 pr-3 pb-0.5 pl-2">
|
||||
<div className="system-xs-medium-uppercase text-text-tertiary">{`${datasetBindings?.length} ${t('editExternalAPITooltipTitle', { ns: 'dataset' })}`}</div>
|
||||
</div>
|
||||
{datasetBindings?.map(binding => (
|
||||
<div key={binding.id} className="flex items-center gap-1 self-stretch px-2 py-1">
|
||||
<RiBook2Line className="h-4 w-4 text-text-secondary" />
|
||||
<div className="system-sm-medium text-text-secondary">{binding.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={t('editExternalAPIFormWarning.end', { ns: 'dataset' })}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-3.5 w-3.5 items-center justify-center rounded-sm outline-hidden hover:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
>
|
||||
<RiInformation2Line className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
asChild={false}
|
||||
position="bottom"
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="bottom"
|
||||
popupClassName="flex w-[320px] items-center self-stretch px-3 py-2"
|
||||
>
|
||||
<RiInformation2Line className="h-3.5 w-3.5" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ActionButton className="absolute top-5 right-5" onClick={onCancel}>
|
||||
<RiCloseLine className="h-[18px] w-[18px] shrink-0 text-text-tertiary" />
|
||||
</ActionButton>
|
||||
<Form value={formData} onChange={handleDataChange} formSchemas={formSchemas} className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3" />
|
||||
<div className="flex items-center justify-end gap-2 self-stretch p-6 pt-5">
|
||||
<Button type="button" variant="secondary" onClick={onCancel}>
|
||||
{t('externalAPIForm.cancel', { ns: 'dataset' })}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
if (isEditMode && (datasetBindings?.length ?? 0) > 0)
|
||||
setShowConfirm(true)
|
||||
else if (isEditMode && onEdit)
|
||||
onEdit(formData)
|
||||
else
|
||||
handleSave()
|
||||
}}
|
||||
disabled={hasEmptyInputs || loading}
|
||||
>
|
||||
{t('externalAPIForm.save', { ns: 'dataset' })}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-1 self-stretch rounded-b-2xl border-t-[0.5px] border-divider-subtle
|
||||
bg-background-soft px-2 py-3 system-xs-regular text-text-tertiary"
|
||||
>
|
||||
<RiLock2Fill className="h-3 w-3 text-text-quaternary" />
|
||||
{t('externalAPIForm.encrypted.front', { ns: 'dataset' })}
|
||||
<a className="text-text-accent" target="_blank" rel="noopener noreferrer" href="https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html">
|
||||
PKCS1_OAEP
|
||||
</a>
|
||||
{t('externalAPIForm.encrypted.end', { ns: 'dataset' })}
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialog
|
||||
open={showConfirm && (datasetBindings?.length ?? 0) > 0}
|
||||
onOpenChange={open => !open && setShowConfirm(false)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
Warning
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{`${t('editExternalAPIConfirmWarningContent.front', { ns: 'dataset' })} ${datasetBindings?.length} ${t('editExternalAPIConfirmWarningContent.end', { ns: 'dataset' })}`}
|
||||
</AlertDialogDescription>
|
||||
<div className="p-1">
|
||||
<div className="flex items-start self-stretch pt-1 pr-3 pb-0.5 pl-2">
|
||||
<div className="system-xs-medium-uppercase text-text-tertiary">{`${datasetBindings?.length} ${t('editExternalAPITooltipTitle', { ns: 'dataset' })}`}</div>
|
||||
</div>
|
||||
{datasetBindings?.map(binding => (
|
||||
<div key={binding.id} className="flex items-center gap-1 self-stretch px-2 py-1">
|
||||
<RiBook2Line className="h-4 w-4 text-text-secondary" />
|
||||
<div className="system-sm-medium text-text-secondary">{binding.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</span>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleSave}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
</div>
|
||||
<ActionButton className="absolute top-5 right-5" onClick={onCancel}>
|
||||
<RiCloseLine className="h-[18px] w-[18px] shrink-0 text-text-tertiary" />
|
||||
</ActionButton>
|
||||
<Form value={formData} onChange={handleDataChange} formSchemas={formSchemas} className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3" />
|
||||
<div className="flex items-center justify-end gap-2 self-stretch p-6 pt-5">
|
||||
<Button type="button" variant="secondary" onClick={onCancel}>
|
||||
{t('externalAPIForm.cancel', { ns: 'dataset' })}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
if (isEditMode && (datasetBindings?.length ?? 0) > 0)
|
||||
setShowConfirm(true)
|
||||
else if (isEditMode && onEdit)
|
||||
onEdit(formData)
|
||||
else
|
||||
handleSave()
|
||||
}}
|
||||
disabled={hasEmptyInputs || loading}
|
||||
>
|
||||
{t('externalAPIForm.save', { ns: 'dataset' })}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-1 self-stretch rounded-b-2xl border-t-[0.5px] border-divider-subtle
|
||||
bg-background-soft px-2 py-3 system-xs-regular text-text-tertiary"
|
||||
>
|
||||
<RiLock2Fill className="h-3 w-3 text-text-quaternary" />
|
||||
{t('externalAPIForm.encrypted.front', { ns: 'dataset' })}
|
||||
<a className="text-text-accent" target="_blank" rel="noopener noreferrer" href="https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html">
|
||||
PKCS1_OAEP
|
||||
</a>
|
||||
{t('externalAPIForm.encrypted.end', { ns: 'dataset' })}
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
<AlertDialog
|
||||
open={showConfirm && (datasetBindings?.length ?? 0) > 0}
|
||||
onOpenChange={open => !open && setShowConfirm(false)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||
Warning
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{`${t('editExternalAPIConfirmWarningContent.front', { ns: 'dataset' })} ${datasetBindings?.length} ${t('editExternalAPIConfirmWarningContent.end', { ns: 'dataset' })}`}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={handleSave}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default memo(AddExternalAPIModal)
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import type { RelatedAppResponse } from '@/models/datasets'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { RiInformation2Line } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import NoLinkedAppsPanel from '../no-linked-apps-panel'
|
||||
|
||||
type StatisticsProps = {
|
||||
@ -40,26 +40,34 @@ const Statistics = ({
|
||||
<div className="system-md-semibold-uppercase text-text-secondary">
|
||||
{relatedAppsTotal ?? '--'}
|
||||
</div>
|
||||
<Tooltip
|
||||
position="top-start"
|
||||
noDecoration
|
||||
needsDelay
|
||||
popupContent={
|
||||
hasRelatedApps
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={t('datasetMenus.relatedApp', { ns: 'common' })}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="flex cursor-pointer items-center gap-x-0.5 rounded-sm system-2xs-medium-uppercase text-text-tertiary outline-hidden hover:text-text-secondary focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
>
|
||||
<span>{t('datasetMenus.relatedApp', { ns: 'common' })}</span>
|
||||
<RiInformation2Line className="size-3" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="top-start"
|
||||
popupClassName="border-0 bg-transparent p-0 shadow-none"
|
||||
>
|
||||
{hasRelatedApps
|
||||
? (
|
||||
<LinkedAppsPanel
|
||||
relatedApps={relatedApps.data}
|
||||
isMobile={!expand}
|
||||
/>
|
||||
)
|
||||
: <NoLinkedAppsPanel />
|
||||
}
|
||||
>
|
||||
<div className="flex cursor-pointer items-center gap-x-0.5 system-2xs-medium-uppercase text-text-tertiary">
|
||||
<span>{t('datasetMenus.relatedApp', { ns: 'common' })}</span>
|
||||
<RiInformation2Line className="size-3" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
: <NoLinkedAppsPanel />}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -307,11 +307,15 @@ describe('Nav Component', () => {
|
||||
fireEvent.click(selectorButton)
|
||||
})
|
||||
|
||||
const createButton = await screen.findByText('Create New')
|
||||
await act(async () => {
|
||||
fireEvent.click(createButton)
|
||||
})
|
||||
const openCreateMenu = async () => {
|
||||
const createButton = await screen.findByText('Create New')
|
||||
await act(async () => {
|
||||
fireEvent.click(createButton)
|
||||
})
|
||||
return screen.findByText(/app\.newApp\.startFromBlank/i)
|
||||
}
|
||||
|
||||
await openCreateMenu()
|
||||
const blankOption = await screen.findByText(
|
||||
/app\.newApp\.startFromBlank/i,
|
||||
)
|
||||
@ -320,6 +324,7 @@ describe('Nav Component', () => {
|
||||
})
|
||||
expect(mockOnCreate).toHaveBeenCalledWith('blank')
|
||||
|
||||
await openCreateMenu()
|
||||
const templateOption = await screen.findByText(
|
||||
/app\.newApp\.startFromTemplate/i,
|
||||
)
|
||||
@ -328,6 +333,7 @@ describe('Nav Component', () => {
|
||||
})
|
||||
expect(mockOnCreate).toHaveBeenCalledWith('template')
|
||||
|
||||
await openCreateMenu()
|
||||
const dslOption = await screen.findByText(/app\.importDSL/i)
|
||||
await act(async () => {
|
||||
fireEvent.click(dslOption)
|
||||
|
||||
@ -203,23 +203,30 @@ describe('NavSelector Component', () => {
|
||||
await act(async () => {
|
||||
fireEvent.click(button)
|
||||
})
|
||||
const createBtn = screen.getByText('Create New')
|
||||
await act(async () => {
|
||||
fireEvent.click(createBtn)
|
||||
})
|
||||
|
||||
const openCreateMenu = async () => {
|
||||
const createBtn = screen.getByText('Create New')
|
||||
await act(async () => {
|
||||
fireEvent.click(createBtn)
|
||||
})
|
||||
return screen.findByText(/app\.newApp\.startFromBlank/i)
|
||||
}
|
||||
|
||||
await openCreateMenu()
|
||||
const blank = await screen.findByText(/app\.newApp\.startFromBlank/i)
|
||||
await act(async () => {
|
||||
fireEvent.click(blank)
|
||||
})
|
||||
expect(mockOnCreate).toHaveBeenCalledWith('blank')
|
||||
|
||||
await openCreateMenu()
|
||||
const template = await screen.findByText(/app\.newApp\.startFromTemplate/i)
|
||||
await act(async () => {
|
||||
fireEvent.click(template)
|
||||
})
|
||||
expect(mockOnCreate).toHaveBeenCalledWith('template')
|
||||
|
||||
await openCreateMenu()
|
||||
const dsl = await screen.findByText(/app\.importDSL/i)
|
||||
await act(async () => {
|
||||
fireEvent.click(dsl)
|
||||
@ -227,6 +234,21 @@ describe('NavSelector Component', () => {
|
||||
expect(mockOnCreate).toHaveBeenCalledWith('dsl')
|
||||
})
|
||||
|
||||
it('should open extended create menu on hover in app mode', async () => {
|
||||
render(<NavSelector {...defaultProps} isApp />)
|
||||
const button = screen.getByRole('button')
|
||||
await act(async () => {
|
||||
fireEvent.click(button)
|
||||
})
|
||||
|
||||
const createBtn = screen.getByText('Create New')
|
||||
await act(async () => {
|
||||
fireEvent.mouseEnter(createBtn)
|
||||
})
|
||||
|
||||
expect(await screen.findByText(/app\.newApp\.startFromBlank/i))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show create button for non-editors', async () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
isCurrentWorkspaceEditor: false,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { AppIconType, AppModeEnum } from '@/types/app'
|
||||
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
|
||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
RiAddLine,
|
||||
@ -8,7 +8,7 @@ import {
|
||||
RiArrowRightSLine,
|
||||
} from '@remixicon/react'
|
||||
import { debounce } from 'es-toolkit/compat'
|
||||
import { Fragment, useCallback } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { AppTypeIcon } from '@/app/components/app/type-selector'
|
||||
@ -38,6 +38,75 @@ export type INavSelectorProps = {
|
||||
isLoadingMore?: boolean
|
||||
}
|
||||
|
||||
type AppCreateMenuProps = {
|
||||
createText: string
|
||||
startFromBlankText: string
|
||||
startFromTemplateText: string
|
||||
importDSLText: string
|
||||
onCreate: (state: string) => void
|
||||
}
|
||||
|
||||
const AppCreateMenu = ({
|
||||
createText,
|
||||
startFromBlankText,
|
||||
startFromTemplateText,
|
||||
importDSLText,
|
||||
onCreate,
|
||||
}: AppCreateMenuProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleCreate = (state: string) => {
|
||||
setOpen(false)
|
||||
onCreate(state)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full" onMouseLeave={() => setOpen(false)}>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full p-1 text-left"
|
||||
onClick={() => setOpen(value => !value)}
|
||||
onMouseEnter={() => setOpen(true)}
|
||||
>
|
||||
<div className={cn(
|
||||
'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover',
|
||||
open && 'bg-state-base-hover!',
|
||||
)}
|
||||
>
|
||||
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-background-default">
|
||||
<RiAddLine className="h-4 w-4 text-text-primary" />
|
||||
</div>
|
||||
<div className="grow text-left text-[14px] font-normal text-text-secondary">{createText}</div>
|
||||
<RiArrowRightSLine className="h-3.5 w-3.5 shrink-0 text-text-primary" />
|
||||
</div>
|
||||
</button>
|
||||
{open && (
|
||||
<div
|
||||
className="absolute top-[3px] right-[-198px] z-10 min-w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"
|
||||
onMouseEnter={() => setOpen(true)}
|
||||
>
|
||||
<div className="p-1">
|
||||
<button type="button" className={cn('flex w-full cursor-pointer items-center rounded-lg px-3 py-[6px] text-left font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => handleCreate('blank')}>
|
||||
<FilePlus01 className="mr-2 h-4 w-4 shrink-0 text-text-secondary" />
|
||||
{startFromBlankText}
|
||||
</button>
|
||||
<button type="button" className={cn('flex w-full cursor-pointer items-center rounded-lg px-3 py-[6px] text-left font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => handleCreate('template')}>
|
||||
<FilePlus02 className="mr-2 h-4 w-4 shrink-0 text-text-secondary" />
|
||||
{startFromTemplateText}
|
||||
</button>
|
||||
</div>
|
||||
<div className="border-t border-divider-regular p-1">
|
||||
<button type="button" className={cn('flex w-full cursor-pointer items-center rounded-lg px-3 py-[6px] text-left font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => handleCreate('dsl')}>
|
||||
<FileArrow01 className="mr-2 h-4 w-4 shrink-0 text-text-secondary" />
|
||||
{importDSLText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onLoadMore, isLoadingMore }: INavSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
@ -72,7 +141,7 @@ const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onL
|
||||
className="
|
||||
absolute right-0 -left-11 mt-1.5 w-60 max-w-80
|
||||
origin-top-right divide-y divide-divider-regular rounded-lg bg-components-panel-bg-blur
|
||||
shadow-lg
|
||||
shadow-lg outline-none
|
||||
"
|
||||
>
|
||||
<div className="overflow-auto px-1 py-1" style={{ maxHeight: '50vh' }} onScroll={handleScroll}>
|
||||
@ -130,56 +199,13 @@ const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onL
|
||||
</MenuItem>
|
||||
)}
|
||||
{isApp && isCurrentWorkspaceEditor && (
|
||||
<Menu as="div" className="relative h-full w-full">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<MenuButton className="w-full p-1">
|
||||
<div className={cn(
|
||||
'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover',
|
||||
open && 'bg-state-base-hover!',
|
||||
)}
|
||||
>
|
||||
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-background-default">
|
||||
<RiAddLine className="h-4 w-4 text-text-primary" />
|
||||
</div>
|
||||
<div className="grow text-left text-[14px] font-normal text-text-secondary">{createText}</div>
|
||||
<RiArrowRightSLine className="h-3.5 w-3.5 shrink-0 text-text-primary" />
|
||||
</div>
|
||||
</MenuButton>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems className={cn(
|
||||
'absolute top-[3px] right-[-198px] z-10 min-w-[200px] rounded-lg bg-components-panel-bg-blur shadow-lg',
|
||||
)}
|
||||
>
|
||||
<div className="p-1">
|
||||
<div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('blank')}>
|
||||
<FilePlus01 className="mr-2 h-4 w-4 shrink-0 text-text-secondary" />
|
||||
{t('newApp.startFromBlank', { ns: 'app' })}
|
||||
</div>
|
||||
<div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('template')}>
|
||||
<FilePlus02 className="mr-2 h-4 w-4 shrink-0 text-text-secondary" />
|
||||
{t('newApp.startFromTemplate', { ns: 'app' })}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-divider-regular p-1">
|
||||
<div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('dsl')}>
|
||||
<FileArrow01 className="mr-2 h-4 w-4 shrink-0 text-text-secondary" />
|
||||
{t('importDSL', { ns: 'app' })}
|
||||
</div>
|
||||
</div>
|
||||
</MenuItems>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
<AppCreateMenu
|
||||
createText={createText}
|
||||
startFromBlankText={t('newApp.startFromBlank', { ns: 'app' })}
|
||||
startFromTemplateText={t('newApp.startFromTemplate', { ns: 'app' })}
|
||||
importDSLText={t('importDSL', { ns: 'app' })}
|
||||
onCreate={onCreate}
|
||||
/>
|
||||
)}
|
||||
</MenuItems>
|
||||
</>
|
||||
|
||||
@ -76,6 +76,8 @@ const createPluginDetail = (): PluginDetail => ({
|
||||
})
|
||||
|
||||
describe('EndpointList', () => {
|
||||
const getAddButton = () => screen.getByRole('button', { name: 'plugin.detailPanel.endpointModalTitle' })
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockEndpointListData = { endpoints: mockEndpoints }
|
||||
@ -112,7 +114,7 @@ describe('EndpointList', () => {
|
||||
it('should render add button', () => {
|
||||
render(<EndpointList detail={createPluginDetail()} />)
|
||||
|
||||
expect(screen.getAllByRole('button').length).toBeGreaterThan(0)
|
||||
expect(getAddButton()).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -120,8 +122,7 @@ describe('EndpointList', () => {
|
||||
it('should show modal when add button clicked', () => {
|
||||
render(<EndpointList detail={createPluginDetail()} />)
|
||||
|
||||
const addButton = screen.getAllByRole('button')[0]
|
||||
fireEvent.click(addButton!)
|
||||
fireEvent.click(getAddButton())
|
||||
|
||||
expect(screen.getByTestId('endpoint-modal'))!.toBeInTheDocument()
|
||||
})
|
||||
@ -129,8 +130,7 @@ describe('EndpointList', () => {
|
||||
it('should hide modal when cancel clicked', () => {
|
||||
render(<EndpointList detail={createPluginDetail()} />)
|
||||
|
||||
const addButton = screen.getAllByRole('button')[0]
|
||||
fireEvent.click(addButton!)
|
||||
fireEvent.click(getAddButton())
|
||||
expect(screen.getByTestId('endpoint-modal'))!.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-cancel'))
|
||||
@ -140,8 +140,7 @@ describe('EndpointList', () => {
|
||||
it('should call createEndpoint when save clicked', () => {
|
||||
render(<EndpointList detail={createPluginDetail()} />)
|
||||
|
||||
const addButton = screen.getAllByRole('button')[0]
|
||||
fireEvent.click(addButton!)
|
||||
fireEvent.click(getAddButton())
|
||||
fireEvent.click(screen.getByTestId('modal-save'))
|
||||
|
||||
expect(mockCreateEndpoint).toHaveBeenCalled()
|
||||
@ -176,8 +175,7 @@ describe('EndpointList', () => {
|
||||
it('should invalidate endpoint list after successful create', () => {
|
||||
render(<EndpointList detail={createPluginDetail()} />)
|
||||
|
||||
const addButton = screen.getAllByRole('button')[0]
|
||||
fireEvent.click(addButton!)
|
||||
fireEvent.click(getAddButton())
|
||||
fireEvent.click(screen.getByTestId('modal-save'))
|
||||
|
||||
expect(mockInvalidateEndpointList).toHaveBeenCalledWith('test-plugin')
|
||||
@ -186,8 +184,7 @@ describe('EndpointList', () => {
|
||||
it('should pass correct params to createEndpoint', () => {
|
||||
render(<EndpointList detail={createPluginDetail()} />)
|
||||
|
||||
const addButton = screen.getAllByRole('button')[0]
|
||||
fireEvent.click(addButton!)
|
||||
fireEvent.click(getAddButton())
|
||||
fireEvent.click(screen.getByTestId('modal-save'))
|
||||
|
||||
expect(mockCreateEndpoint).toHaveBeenCalledWith({
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import {
|
||||
RiAddLine,
|
||||
@ -11,7 +12,6 @@ import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import {
|
||||
@ -67,10 +67,23 @@ const EndpointList = ({ detail }: Props) => {
|
||||
<div className="mb-1 flex h-6 items-center justify-between system-sm-semibold-uppercase text-text-secondary">
|
||||
<div className="flex items-center gap-0.5">
|
||||
{t('detailPanel.endpoints', { ns: 'plugin' })}
|
||||
<Tooltip
|
||||
position="right"
|
||||
popupClassName="w-[240px] p-4 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border"
|
||||
popupContent={(
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={t('detailPanel.endpointsTip', { ns: 'plugin' })}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-4 w-4 shrink-0 items-center justify-center rounded-sm p-px outline-hidden hover:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
>
|
||||
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="right"
|
||||
popupClassName="w-[240px] p-4 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border"
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle">
|
||||
<RiApps2AddLine className="h-4 w-4 text-text-tertiary" />
|
||||
@ -80,17 +93,19 @@ const EndpointList = ({ detail }: Props) => {
|
||||
href={docLink('/develop-plugin/getting-started/getting-started-dify-plugin')}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex cursor-pointer items-center gap-1 system-xs-regular text-text-accent"
|
||||
>
|
||||
<div className="inline-flex cursor-pointer items-center gap-1 system-xs-regular text-text-accent">
|
||||
<RiBookOpenLine className="h-3 w-3" />
|
||||
{t('detailPanel.endpointsDocLink', { ns: 'plugin' })}
|
||||
</div>
|
||||
<RiBookOpenLine className="h-3 w-3" />
|
||||
{t('detailPanel.endpointsDocLink', { ns: 'plugin' })}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<ActionButton onClick={showEndpointModal}>
|
||||
<ActionButton
|
||||
aria-label={t('detailPanel.endpointModalTitle', { ns: 'plugin' })}
|
||||
onClick={showEndpointModal}
|
||||
>
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
import type { Tool } from '@/app/components/tools/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n-config/language'
|
||||
|
||||
@ -51,25 +51,31 @@ const MCPToolItem = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={tool.name}
|
||||
position="left"
|
||||
popupClassName="p-0! px-4! py-3.5! w-[360px]! border-[0.5px]! border-components-panel-border! rounded-xl! shadow-lg!"
|
||||
popupContent={(
|
||||
<Popover key={tool.name}>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={tool.label[language]}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className={cn('bg-components-panel-item-bg w-full cursor-pointer rounded-xl border-[0.5px] border-components-panel-border-subtle px-4 py-3 text-left shadow-xs outline-hidden hover:bg-components-panel-on-panel-item-bg-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover')}
|
||||
>
|
||||
<div className="pb-0.5 system-md-semibold text-text-secondary">{tool.label[language]}</div>
|
||||
<div className="line-clamp-2 system-xs-regular text-text-tertiary" title={tool.description[language]}>{tool.description[language]}</div>
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="left"
|
||||
popupClassName="w-[360px]! rounded-xl! border-[0.5px]! border-components-panel-border! px-4! py-3.5! shadow-lg!"
|
||||
>
|
||||
<div>
|
||||
<div className="mb-1 title-xs-semi-bold text-text-primary">{tool.label[language]}</div>
|
||||
<div className="body-xs-regular text-text-secondary">{tool.description[language]}</div>
|
||||
{renderParameters()}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn('bg-components-panel-item-bg cursor-pointer rounded-xl border-[0.5px] border-components-panel-border-subtle px-4 py-3 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover')}
|
||||
>
|
||||
<div className="pb-0.5 system-md-semibold text-text-secondary">{tool.label[language]}</div>
|
||||
<div className="line-clamp-2 system-xs-regular text-text-tertiary" title={tool.description[language]}>{tool.description[language]}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
export default MCPToolItem
|
||||
|
||||
@ -15,14 +15,15 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { RiEditLine, RiLoopLeftLine } from '@remixicon/react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal'
|
||||
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
|
||||
@ -81,13 +82,22 @@ const ServerURLSection: FC<ServerURLSectionProps> = ({
|
||||
<CopyFeedback content={serverURL} className="size-6!" />
|
||||
<Divider type="vertical" className="mx-0.5! h-3.5! shrink-0" />
|
||||
{isCurrentWorkspaceManager && (
|
||||
<Tooltip popupContent={t('overview.appInfo.regenerate', { ns: 'appOverview' }) || ''}>
|
||||
<div
|
||||
className="cursor-pointer rounded-md p-1 hover:bg-state-base-hover"
|
||||
onClick={onRegenerate}
|
||||
>
|
||||
<RiLoopLeftLine className={cn('h-4 w-4 text-text-tertiary hover:text-text-secondary', genLoading && 'animate-spin')} />
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer rounded-md p-1 outline-hidden hover:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
aria-label={t('overview.appInfo.regenerate', { ns: 'appOverview' }) || ''}
|
||||
onClick={onRegenerate}
|
||||
>
|
||||
<RiLoopLeftLine className={cn('h-4 w-4 text-text-tertiary hover:text-text-secondary', genLoading && 'animate-spin')} />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t('overview.appInfo.regenerate', { ns: 'appOverview' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
@ -104,13 +114,19 @@ type TriggerModeOverlayProps = {
|
||||
const TriggerModeOverlay: FC<TriggerModeOverlayProps> = ({ triggerModeMessage }) => {
|
||||
if (triggerModeMessage) {
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={triggerModeMessage}
|
||||
popupClassName="max-w-64 rounded-xl bg-components-panel-bg px-3 py-2 text-xs text-text-secondary shadow-lg"
|
||||
position="right"
|
||||
>
|
||||
<div className="absolute inset-0 z-10 cursor-not-allowed rounded-xl" aria-hidden="true"></div>
|
||||
</Tooltip>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={typeof triggerModeMessage === 'string' ? triggerModeMessage : 'Disabled'}
|
||||
render={<button type="button" className="absolute inset-0 z-10 cursor-not-allowed rounded-xl outline-hidden focus-visible:ring-1 focus-visible:ring-components-input-border-hover" />}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="right"
|
||||
popupClassName="max-w-64 rounded-xl bg-components-panel-bg px-3 py-2 text-xs text-text-secondary shadow-lg"
|
||||
>
|
||||
{triggerModeMessage}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
return <div className="absolute inset-0 z-10 cursor-not-allowed rounded-xl" aria-hidden="true"></div>
|
||||
@ -146,12 +162,13 @@ function getTooltipContent({
|
||||
<div className="mb-1 text-xs font-normal text-text-secondary">
|
||||
{t('overview.appInfo.enableTooltip.description', { ns: 'appOverview' })}
|
||||
</div>
|
||||
<div
|
||||
className="cursor-pointer text-xs font-normal text-text-accent hover:underline"
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer rounded-sm text-xs font-normal text-text-accent outline-hidden hover:underline focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
onClick={() => window.open(docLink('/use-dify/nodes/user-input'), '_blank')}
|
||||
>
|
||||
{t('overview.appInfo.enableTooltip.learnMore', { ns: 'appOverview' })}
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -316,16 +333,31 @@ const MCPServiceCard: FC<IAppCardProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<StatusIndicator serverActivated={serverActivated} />
|
||||
<Tooltip
|
||||
popupContent={tooltipContent}
|
||||
position="right"
|
||||
popupClassName="w-58 max-w-60 rounded-xl bg-components-panel-bg px-3.5 py-3 shadow-lg"
|
||||
offset={24}
|
||||
>
|
||||
<div>
|
||||
<Switch checked={activated} onCheckedChange={onChangeStatus} disabled={toggleDisabled} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{toggleDisabled && tooltipContent
|
||||
? (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
nativeButton={false}
|
||||
aria-label={typeof tooltipContent === 'string' ? tooltipContent : t('overview.appInfo.enableTooltip.description', { ns: 'appOverview' })}
|
||||
render={(
|
||||
<div>
|
||||
<Switch checked={activated} onCheckedChange={onChangeStatus} disabled={toggleDisabled} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="right"
|
||||
sideOffset={24}
|
||||
popupClassName="w-58 max-w-60 rounded-xl bg-components-panel-bg px-3.5 py-3 shadow-lg"
|
||||
>
|
||||
{tooltipContent}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
: (
|
||||
<Switch checked={activated} onCheckedChange={onChangeStatus} disabled={toggleDisabled} />
|
||||
)}
|
||||
</div>
|
||||
{!isMinimalState && (
|
||||
<ServerURLSection
|
||||
|
||||
@ -7,7 +7,9 @@ import type {
|
||||
Variable,
|
||||
} from '../../../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
} from '@remixicon/react'
|
||||
@ -26,7 +28,6 @@ import { Variable02 } from '@/app/components/base/icons/src/vender/solid/develop
|
||||
import { Jinja } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import { PROMPT_EDITOR_INSERT_QUICKLY } from '@/app/components/base/prompt-editor/plugins/update-block'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useWorkflowVariableType } from '@/app/components/workflow/hooks'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars'
|
||||
import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn'
|
||||
@ -165,7 +166,25 @@ const Editor: FC<Props> = ({
|
||||
{' '}
|
||||
{required && <span className="text-text-destructive">*</span>}
|
||||
</div>
|
||||
{!!titleTooltip && <Tooltip popupContent={titleTooltip} />}
|
||||
{!!titleTooltip && (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={typeof titleTooltip === 'string' ? titleTooltip : typeof title === 'string' ? title : 'Help'}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-4 w-4 shrink-0 items-center justify-center rounded-sm p-px outline-hidden hover:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
>
|
||||
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent popupClassName="max-w-[300px] px-3 py-2 system-xs-regular text-text-tertiary">
|
||||
{titleTooltip}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xs leading-[18px] font-medium text-text-tertiary">{value?.length || 0}</div>
|
||||
@ -184,34 +203,48 @@ const Editor: FC<Props> = ({
|
||||
{/* Operations */}
|
||||
<div className="flex items-center space-x-[2px]">
|
||||
{isSupportJinja && (
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div>
|
||||
<div>{t('common.enableJinja', { ns: 'workflow' })}</div>
|
||||
<a className="text-text-accent" target="_blank" href="https://jinja.palletsprojects.com/en/2.10.x/">{t('common.learnMore', { ns: 'workflow' })}</a>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div className={cn(editionType === EditionType.jinja2 && 'border-components-button-ghost-bg-hover bg-components-button-ghost-bg-hover', 'flex h-[22px] items-center space-x-0.5 rounded-[5px] border border-transparent px-1.5 hover:border-components-button-ghost-bg-hover')}>
|
||||
<Jinja className="h-3 w-6 text-text-quaternary" />
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={editionType === EditionType.jinja2}
|
||||
onCheckedChange={(checked) => {
|
||||
onEditionTypeChange?.(checked ? EditionType.jinja2 : EditionType.basic)
|
||||
}}
|
||||
<div className={cn(editionType === EditionType.jinja2 && 'border-components-button-ghost-bg-hover bg-components-button-ghost-bg-hover', 'flex h-[22px] items-center space-x-0.5 rounded-[5px] border border-transparent px-1.5 hover:border-components-button-ghost-bg-hover')}>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
aria-label={t('common.enableJinja', { ns: 'workflow' })}
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-4 w-7 items-center justify-center rounded-sm outline-hidden hover:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
>
|
||||
<Jinja className="h-3 w-6 text-text-quaternary" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<PopoverContent popupClassName="px-3 py-2 system-xs-regular text-text-tertiary">
|
||||
<div>
|
||||
<div>{t('common.enableJinja', { ns: 'workflow' })}</div>
|
||||
<a className="text-text-accent hover:underline" target="_blank" rel="noopener noreferrer" href="https://jinja.palletsprojects.com/en/2.10.x/">{t('common.learnMore', { ns: 'workflow' })}</a>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={editionType === EditionType.jinja2}
|
||||
onCheckedChange={(checked) => {
|
||||
onEditionTypeChange?.(checked ? EditionType.jinja2 : EditionType.basic)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!readOnly && (
|
||||
<Tooltip
|
||||
popupContent={`${t('common.insertVarTip', { ns: 'workflow' })}`}
|
||||
>
|
||||
<ActionButton onClick={handleInsertVariable}>
|
||||
<Variable02 className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<ActionButton onClick={handleInsertVariable}>
|
||||
<Variable02 className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t('common.insertVarTip', { ns: 'workflow' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showRemove && (
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
import type { Reducer } from 'react'
|
||||
import type { LanguagesSupported } from '@/i18n-config/language'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useReducer } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { LICENSE_LINK } from '@/constants/link'
|
||||
import { languages } from '@/i18n-config/language'
|
||||
import Link from '@/next/link'
|
||||
@ -94,21 +94,35 @@ const OneMoreStep = () => {
|
||||
<div className="mx-auto mt-6 w-full">
|
||||
<div className="relative">
|
||||
<div className="mb-5">
|
||||
<label className="my-2 flex items-center justify-between system-md-semibold text-text-secondary">
|
||||
{t('invitationCode', { ns: 'login' })}
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[256px] text-xs font-medium">
|
||||
<div className="my-2 flex items-center justify-between system-md-semibold text-text-secondary">
|
||||
<label htmlFor="invitation_code">
|
||||
{t('invitationCode', { ns: 'login' })}
|
||||
</label>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
openOnHover
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer rounded-sm text-text-accent-secondary outline-hidden focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
|
||||
>
|
||||
{t('dontHave', { ns: 'login' })}
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="top"
|
||||
popupClassName="w-[256px] px-3 py-2 text-xs font-medium text-text-tertiary"
|
||||
>
|
||||
<div>
|
||||
<div className="font-medium">{t('sendUsMail', { ns: 'login' })}</div>
|
||||
<div className="cursor-pointer text-xs font-medium text-text-accent-secondary">
|
||||
<a href="mailto:request-invitation@langgenius.ai">request-invitation@langgenius.ai</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<span className="cursor-pointer text-text-accent-secondary">{t('dontHave', { ns: 'login' })}</span>
|
||||
</Tooltip>
|
||||
</label>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
<Input
|
||||
id="invitation_code"
|
||||
|
||||
@ -21,10 +21,18 @@ const config: KnipConfig = {
|
||||
],
|
||||
/// keep-sorted
|
||||
rules: {
|
||||
binaries: 'error',
|
||||
// TODO: fix these warnings
|
||||
// Unused devDependencies (3)
|
||||
// @eslint-react/eslint-plugin package.json:160:6
|
||||
// @next/eslint-plugin-next package.json:168:6
|
||||
// eslint-plugin-react-refresh package.json:211:6
|
||||
// Unlisted binaries (2)
|
||||
// eslint package.json
|
||||
// vp package.json
|
||||
binaries: 'warn',
|
||||
catalog: 'error',
|
||||
dependencies: 'error',
|
||||
devDependencies: 'error',
|
||||
devDependencies: 'warn',
|
||||
duplicates: 'error',
|
||||
enumMembers: 'error',
|
||||
exports: 'error',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user