Compare commits
142 Commits
main
...
build/esli
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2fe28279c8 | ||
![]() |
3d165ec7d9 | ||
![]() |
0b8c896481 | ||
![]() |
15fe635465 | ||
![]() |
f8c3189f4d | ||
![]() |
f215db87e3 | ||
![]() |
67d02212b4 | ||
![]() |
cff9adaf8e | ||
![]() |
cdd2a40086 | ||
![]() |
024028bc52 | ||
![]() |
0ae085b48a | ||
![]() |
2094c54951 | ||
![]() |
f4f11135d3 | ||
![]() |
8e9d7a229d | ||
![]() |
8f49572f85 | ||
![]() |
5aa7696cc3 | ||
![]() |
15dd79e822 | ||
![]() |
4651ab4195 | ||
![]() |
5e3160e6f6 | ||
![]() |
973cd126bb | ||
![]() |
ebaf8766ef | ||
![]() |
d2190e9c3a | ||
![]() |
37f55098fe | ||
![]() |
b1771194cc | ||
![]() |
0279bd8c75 | ||
![]() |
5e077e4ce8 | ||
![]() |
64067e1f20 | ||
![]() |
5295c72ca1 | ||
![]() |
1ecea62052 | ||
![]() |
307af29b65 | ||
![]() |
10190a9aa5 | ||
![]() |
7c5c35600c | ||
![]() |
63b333cdb1 | ||
![]() |
a6776190bd | ||
![]() |
9577cbac27 | ||
![]() |
f6ae13abad | ||
![]() |
f3d501e7d5 | ||
![]() |
2eab8fcc33 | ||
![]() |
bdb81fe20d | ||
![]() |
0f60fe7f2a | ||
![]() |
425f624de5 | ||
![]() |
b1919745e2 | ||
![]() |
9a242bcac9 | ||
![]() |
a6109a60b8 | ||
![]() |
28f7bbf83a | ||
![]() |
cac04c5f3c | ||
![]() |
18f5f9cc37 | ||
![]() |
1787c5c93f | ||
![]() |
f981494613 | ||
![]() |
fbc853af92 | ||
![]() |
1a64c660ba | ||
![]() |
846555af1b | ||
![]() |
bca94854f7 | ||
![]() |
1bd70bd8bf | ||
![]() |
d1dcd39191 | ||
![]() |
35384bda41 | ||
![]() |
89fb6eb648 | ||
![]() |
aa61a890b2 | ||
![]() |
31ece363c3 | ||
![]() |
70a5d78cc5 | ||
![]() |
57f4dfdb6f | ||
![]() |
aa9028a607 | ||
![]() |
d83f94c55c | ||
![]() |
a8c5e0b0b0 | ||
![]() |
177e8cbf73 | ||
![]() |
23828fd15a | ||
![]() |
2cc37ac8e5 | ||
![]() |
c9ee1e9ff2 | ||
![]() |
4f10f5d5f4 | ||
![]() |
c48c84674e | ||
![]() |
1e9fbbf41b | ||
![]() |
4dd144ce43 | ||
![]() |
a387ff1c38 | ||
![]() |
a9e367e6de | ||
![]() |
e2fec587f8 | ||
![]() |
39a6f0943d | ||
![]() |
684896d100 | ||
![]() |
54f911f6cd | ||
![]() |
0e5c16d0c2 | ||
![]() |
b8cd6ea478 | ||
![]() |
fc61fd0f50 | ||
![]() |
2fbfc988c4 | ||
![]() |
99f5fea001 | ||
![]() |
ecd2a1be9f | ||
![]() |
49ee9ca5f1 | ||
![]() |
6d0eef12b1 | ||
![]() |
c1e0a939b0 | ||
![]() |
060a894bd1 | ||
![]() |
c75e02b5b2 | ||
![]() |
fcf43ee845 | ||
![]() |
466f61d044 | ||
![]() |
27ae74af50 | ||
![]() |
8dd941e3d2 | ||
![]() |
dec4bf6b98 | ||
![]() |
e2c33fc40f | ||
![]() |
c74e59d1f4 | ||
![]() |
1fcb902715 | ||
![]() |
c08f98218c | ||
![]() |
c6377f6e38 | ||
![]() |
3cb0a5bd68 | ||
![]() |
95777d23e0 | ||
![]() |
e7dc16fd08 | ||
![]() |
495dec143c | ||
![]() |
4cc6dfa232 | ||
![]() |
d1452d4af4 | ||
![]() |
d68ca56b3a | ||
![]() |
49856d8d17 | ||
![]() |
902de72cc0 | ||
![]() |
76f6b8d104 | ||
![]() |
f111e605c4 | ||
![]() |
b358ed3a5b | ||
![]() |
88dbf639e0 | ||
![]() |
aa8b525b48 | ||
![]() |
c990bc61db | ||
![]() |
6d7588f236 | ||
![]() |
8257c7bf02 | ||
![]() |
946068967b | ||
![]() |
19f5684960 | ||
![]() |
6d62840aff | ||
![]() |
ab868ac979 | ||
![]() |
1d74e693ea | ||
![]() |
fa43d4202f | ||
![]() |
6b29860788 | ||
![]() |
36800eeaba | ||
![]() |
67acd174ac | ||
![]() |
b5edc64b2a | ||
![]() |
d00b2724cc | ||
![]() |
43f87c0b86 | ||
![]() |
7a43f48c95 | ||
![]() |
58a913b09d | ||
![]() |
cd03795f2c | ||
![]() |
36f8b5711d | ||
![]() |
f9c48e9ea9 | ||
![]() |
3b48f8c98e | ||
![]() |
cef1010cb5 | ||
![]() |
cb4875a3a7 | ||
![]() |
bbca708832 | ||
![]() |
05aec43ee3 | ||
![]() |
e8127756e0 | ||
![]() |
792595a46f | ||
![]() |
d7d7281c93 | ||
![]() |
21193c2fbf |
@ -1,11 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd web && npm install
|
npm add -g pnpm@9.12.2
|
||||||
|
cd web && pnpm install
|
||||||
pipx install poetry
|
pipx install poetry
|
||||||
|
|
||||||
echo 'alias start-api="cd /workspaces/dify/api && poetry run python -m flask run --host 0.0.0.0 --port=5001 --debug"' >> ~/.bashrc
|
echo 'alias start-api="cd /workspaces/dify/api && poetry run python -m flask run --host 0.0.0.0 --port=5001 --debug"' >> ~/.bashrc
|
||||||
echo 'alias start-worker="cd /workspaces/dify/api && poetry run python -m celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion"' >> ~/.bashrc
|
echo 'alias start-worker="cd /workspaces/dify/api && poetry run python -m celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion"' >> ~/.bashrc
|
||||||
echo 'alias start-web="cd /workspaces/dify/web && npm run dev"' >> ~/.bashrc
|
echo 'alias start-web="cd /workspaces/dify/web && pnpm dev"' >> ~/.bashrc
|
||||||
echo 'alias start-containers="cd /workspaces/dify/docker && docker-compose -f docker-compose.middleware.yaml -p dify up -d"' >> ~/.bashrc
|
echo 'alias start-containers="cd /workspaces/dify/docker && docker-compose -f docker-compose.middleware.yaml -p dify up -d"' >> ~/.bashrc
|
||||||
|
|
||||||
source /home/vscode/.bashrc
|
source /home/vscode/.bashrc
|
6
.github/workflows/style.yml
vendored
@ -76,16 +76,16 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: yarn
|
cache: pnpm
|
||||||
cache-dependency-path: ./web/package.json
|
cache-dependency-path: ./web/package.json
|
||||||
|
|
||||||
- name: Web dependencies
|
- name: Web dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: yarn install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Web style check
|
- name: Web style check
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: yarn run lint
|
run: pnpm run lint
|
||||||
|
|
||||||
|
|
||||||
superlinter:
|
superlinter:
|
||||||
|
6
.github/workflows/tool-test-sdks.yaml
vendored
@ -32,10 +32,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: ''
|
cache: ''
|
||||||
cache-dependency-path: 'yarn.lock'
|
cache-dependency-path: 'pnpm-lock.yaml'
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: yarn install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: yarn test
|
run: pnpm test
|
||||||
|
@ -38,11 +38,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: env.FILES_CHANGED == 'true'
|
if: env.FILES_CHANGED == 'true'
|
||||||
run: yarn install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run npm script
|
- name: Run npm script
|
||||||
if: env.FILES_CHANGED == 'true'
|
if: env.FILES_CHANGED == 'true'
|
||||||
run: npm run auto-gen-i18n
|
run: pnpm run auto-gen-i18n
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: env.FILES_CHANGED == 'true'
|
if: env.FILES_CHANGED == 'true'
|
||||||
|
6
.github/workflows/web-tests.yml
vendored
@ -34,13 +34,13 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: yarn
|
cache: pnpm
|
||||||
cache-dependency-path: ./web/package.json
|
cache-dependency-path: ./web/package.json
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: yarn install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: yarn test
|
run: pnpm test
|
||||||
|
3
.gitignore
vendored
@ -188,3 +188,6 @@ api/.vscode
|
|||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
/.pnpm-store
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
/**/node_modules/*
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
dist/
|
|
||||||
build/
|
|
||||||
out/
|
|
||||||
.next/
|
|
@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"next",
|
|
||||||
"@antfu",
|
|
||||||
"plugin:storybook/recommended"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/consistent-type-definitions": [
|
|
||||||
"error",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"no-console": "off",
|
|
||||||
"indent": "off",
|
|
||||||
"@typescript-eslint/indent": [
|
|
||||||
"error",
|
|
||||||
2,
|
|
||||||
{
|
|
||||||
"SwitchCase": 1,
|
|
||||||
"flatTernaryExpressions": false,
|
|
||||||
"ignoredNodes": [
|
|
||||||
"PropertyDefinition[decorators]",
|
|
||||||
"TSUnionType",
|
|
||||||
"FunctionExpression[params]:has(Identifier[decorators])"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react-hooks/exhaustive-deps": "warn",
|
|
||||||
"react/display-name": "warn"
|
|
||||||
}
|
|
||||||
}
|
|
7
web/.gitignore
vendored
@ -44,10 +44,9 @@ package-lock.json
|
|||||||
.pnp.cjs
|
.pnp.cjs
|
||||||
.pnp.loader.mjs
|
.pnp.loader.mjs
|
||||||
.yarn/
|
.yarn/
|
||||||
.yarnrc.yml
|
|
||||||
|
|
||||||
# pmpm
|
|
||||||
pnpm-lock.yaml
|
|
||||||
|
|
||||||
.favorites.json
|
.favorites.json
|
||||||
|
|
||||||
|
# storybook
|
||||||
|
/storybook-static
|
||||||
*storybook.log
|
*storybook.log
|
@ -63,7 +63,7 @@ if $web_modified; then
|
|||||||
# check if the test file exists
|
# check if the test file exists
|
||||||
if [ -f "../$test_file" ]; then
|
if [ -f "../$test_file" ]; then
|
||||||
echo "Detected changes in $file, running corresponding unit tests..."
|
echo "Detected changes in $file, running corresponding unit tests..."
|
||||||
npm run test "../$test_file"
|
pnpm run test "../$test_file"
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Unit tests failed. Please fix the errors before committing."
|
echo "Unit tests failed. Please fix the errors before committing."
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { Preview } from '@storybook/react'
|
import type { Preview } from '@storybook/react'
|
||||||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
import { withThemeByDataAttribute } from '@storybook/addon-themes'
|
||||||
import I18nServer from '../app/components/i18n-server'
|
import I18nServer from '../app/components/i18n-server'
|
||||||
|
|
||||||
import '../app/styles/globals.css'
|
import '../app/styles/globals.css'
|
||||||
@ -16,12 +16,12 @@ export const decorators = [
|
|||||||
defaultTheme: 'light',
|
defaultTheme: 'light',
|
||||||
attributeName: 'data-theme',
|
attributeName: 'data-theme',
|
||||||
}),
|
}),
|
||||||
Story => {
|
(Story) => {
|
||||||
return <I18nServer>
|
return <I18nServer>
|
||||||
<Story />
|
<Story />
|
||||||
</I18nServer>
|
</I18nServer>
|
||||||
}
|
}
|
||||||
];
|
]
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
parameters: {
|
parameters: {
|
||||||
|
@ -6,6 +6,9 @@ LABEL maintainer="takatost@gmail.com"
|
|||||||
# RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
# RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||||
|
|
||||||
RUN apk add --no-cache tzdata
|
RUN apk add --no-cache tzdata
|
||||||
|
RUN npm install -g pnpm@9.12.2
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|
||||||
|
|
||||||
# install packages
|
# install packages
|
||||||
@ -14,12 +17,12 @@ FROM base AS packages
|
|||||||
WORKDIR /app/web
|
WORKDIR /app/web
|
||||||
|
|
||||||
COPY package.json .
|
COPY package.json .
|
||||||
COPY yarn.lock .
|
COPY pnpm-lock.yaml .
|
||||||
|
|
||||||
# if you located in China, you can use taobao registry to speed up
|
# if you located in China, you can use taobao registry to speed up
|
||||||
# RUN yarn install --frozen-lockfile --registry https://registry.npmmirror.com/
|
# RUN pnpm install --frozen-lockfile --registry https://registry.npmmirror.com/
|
||||||
|
|
||||||
RUN yarn install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# build resources
|
# build resources
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
@ -27,7 +30,7 @@ WORKDIR /app/web
|
|||||||
COPY --from=packages /app/web/ .
|
COPY --from=packages /app/web/ .
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN yarn build
|
RUN pnpm build
|
||||||
|
|
||||||
|
|
||||||
# production stage
|
# production stage
|
||||||
@ -57,8 +60,7 @@ COPY docker/entrypoint.sh ./entrypoint.sh
|
|||||||
|
|
||||||
|
|
||||||
# global runtime packages
|
# global runtime packages
|
||||||
RUN yarn global add pm2 \
|
RUN pnpm add -g pm2 \
|
||||||
&& yarn cache clean \
|
|
||||||
&& mkdir /.pm2 \
|
&& mkdir /.pm2 \
|
||||||
&& chown -R 1001:0 /.pm2 /app/web \
|
&& chown -R 1001:0 /.pm2 /app/web \
|
||||||
&& chmod -R g=u /.pm2 /app/web
|
&& chmod -R g=u /.pm2 /app/web
|
||||||
|
@ -6,14 +6,12 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
|
|||||||
|
|
||||||
### Run by source code
|
### Run by source code
|
||||||
|
|
||||||
To start the web frontend service, you will need [Node.js v18.x (LTS)](https://nodejs.org/en) and [NPM version 8.x.x](https://www.npmjs.com/) or [Yarn](https://yarnpkg.com/).
|
To start the web frontend service, you will need [Node.js v18.x (LTS)](https://nodejs.org/en) and [pnpm version 9.12.2](https://pnpm.io).
|
||||||
|
|
||||||
First, install the dependencies:
|
First, install the dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
pnpm install
|
||||||
# or
|
|
||||||
yarn install --frozen-lockfile
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, configure the environment variables. Create a file named `.env.local` in the current directory and copy the contents from `.env.example`. Modify the values of these environment variables according to your requirements:
|
Then, configure the environment variables. Create a file named `.env.local` in the current directory and copy the contents from `.env.example`. Modify the values of these environment variables according to your requirements:
|
||||||
@ -43,9 +41,7 @@ NEXT_PUBLIC_SENTRY_DSN=
|
|||||||
Finally, run the development server:
|
Finally, run the development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
pnpm run dev
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
@ -59,19 +55,19 @@ You can start editing the file under folder `app`. The page auto-updates as you
|
|||||||
First, build the app for production:
|
First, build the app for production:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, start the server:
|
Then, start the server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run start
|
pnpm run start
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to customize the host and port:
|
If you want to customize the host and port:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run start --port=3001 --host=0.0.0.0
|
pnpm run start --port=3001 --host=0.0.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Storybook
|
## Storybook
|
||||||
@ -81,7 +77,7 @@ This project uses [Storybook](https://storybook.js.org/) for UI component develo
|
|||||||
To start the storybook server, run:
|
To start the storybook server, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn storybook
|
pnpm storybook
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:6006](http://localhost:6006) with your browser to see the result.
|
Open [http://localhost:6006](http://localhost:6006) with your browser to see the result.
|
||||||
@ -99,7 +95,7 @@ You can create a test file with a suffix of `.spec` beside the file that to be t
|
|||||||
Run test:
|
Run test:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run test
|
pnpm run test
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are not familiar with writing tests, here is some code to refer to:
|
If you are not familiar with writing tests, here is some code to refer to:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { type Locale } from '@/i18n'
|
import type { Locale } from '@/i18n'
|
||||||
import DevelopMain from '@/app/components/develop'
|
import DevelopMain from '@/app/components/develop'
|
||||||
|
|
||||||
export type IDevelopProps = {
|
export type IDevelopProps = {
|
||||||
|
@ -8,12 +8,11 @@ import { useBoolean } from 'ahooks'
|
|||||||
import {
|
import {
|
||||||
Cog8ToothIcon,
|
Cog8ToothIcon,
|
||||||
// CommandLineIcon,
|
// CommandLineIcon,
|
||||||
Squares2X2Icon,
|
|
||||||
// eslint-disable-next-line sort-imports
|
|
||||||
PuzzlePieceIcon,
|
|
||||||
DocumentTextIcon,
|
DocumentTextIcon,
|
||||||
PaperClipIcon,
|
PaperClipIcon,
|
||||||
|
PuzzlePieceIcon,
|
||||||
QuestionMarkCircleIcon,
|
QuestionMarkCircleIcon,
|
||||||
|
Squares2X2Icon,
|
||||||
} from '@heroicons/react/24/outline'
|
} from '@heroicons/react/24/outline'
|
||||||
import {
|
import {
|
||||||
Cog8ToothIcon as Cog8ToothSolidIcon,
|
Cog8ToothIcon as Cog8ToothSolidIcon,
|
||||||
|
18
web/app/(commonLayout)/plugins/page.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import PluginPage from '@/app/components/plugins/plugin-page'
|
||||||
|
import PluginsPanel from '@/app/components/plugins/plugin-page/plugins-panel'
|
||||||
|
import Marketplace from '@/app/components/plugins/marketplace'
|
||||||
|
|
||||||
|
const PluginList = async () => {
|
||||||
|
return (
|
||||||
|
<PluginPage
|
||||||
|
plugins={<PluginsPanel />}
|
||||||
|
marketplace={<Marketplace />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: 'Plugins - Dify',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginList
|
14
web/app/(commonLayout)/plugins/test/card/actions.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { revalidatePath } from 'next/cache'
|
||||||
|
|
||||||
|
// Server Actions
|
||||||
|
export async function handleDelete() {
|
||||||
|
// revalidatePath only invalidates the cache when the included path is next visited.
|
||||||
|
revalidatePath('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchPluginDetail(org: string, name: string) {
|
||||||
|
// Fetch plugin detail TODO
|
||||||
|
return { org, name }
|
||||||
|
}
|
74
web/app/(commonLayout)/plugins/test/card/page.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { handleDelete } from './actions'
|
||||||
|
import Card from '@/app/components/plugins/card'
|
||||||
|
import { customTool, extensionDallE, modelGPT4, toolNotion } from '@/app/components/plugins/card/card-mock'
|
||||||
|
import PluginItem from '@/app/components/plugins/plugin-item'
|
||||||
|
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||||
|
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||||
|
import Badge from '@/app/components/base/badge'
|
||||||
|
|
||||||
|
const PluginList = async () => {
|
||||||
|
const pluginList = [toolNotion, extensionDallE, modelGPT4, customTool]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='pb-3 bg-white'>
|
||||||
|
<div className='mx-3 '>
|
||||||
|
<h2 className='my-3'>Dify Plugin list</h2>
|
||||||
|
<div className='grid grid-cols-2 gap-3'>
|
||||||
|
{pluginList.map((plugin, index) => (
|
||||||
|
<PluginItem
|
||||||
|
key={index}
|
||||||
|
payload={plugin as any}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className='my-3'>Install Plugin / Package under bundle</h2>
|
||||||
|
<div className='w-[512px] rounded-2xl bg-background-section-burn p-2'>
|
||||||
|
<Card
|
||||||
|
payload={toolNotion as any}
|
||||||
|
descriptionLineRows={1}
|
||||||
|
titleLeft={
|
||||||
|
<Badge className='ml-1' text={toolNotion.version} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3 className='my-1'>Installed</h3>
|
||||||
|
<div className='w-[512px] rounded-2xl bg-background-section-burn p-2'>
|
||||||
|
<Card
|
||||||
|
payload={toolNotion as any}
|
||||||
|
descriptionLineRows={1}
|
||||||
|
installed
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className='my-1'>Install model provide</h3>
|
||||||
|
<div className='grid grid-cols-2 gap-3'>
|
||||||
|
{pluginList.map((plugin, index) => (
|
||||||
|
<ProviderCard key={index} payload={plugin as any} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='my-3 h-[px] bg-gray-50'></div>
|
||||||
|
<h2 className='my-3'>Marketplace Plugin list</h2>
|
||||||
|
<div className='grid grid-cols-4 gap-3'>
|
||||||
|
{pluginList.map((plugin, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
payload={plugin as any}
|
||||||
|
footer={
|
||||||
|
<CardMoreInfo downloadCount={index % 2 === 0 ? 1234 : 6} tags={index % 2 === 0 ? ['Search', 'Tag that has very very long name', 'Productivity', 'Tag2'] : []} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: 'Plugins - Card',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginList
|
20
web/app/(commonLayout)/plugins/test/other/page.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
'use client'
|
||||||
|
import { useBoolean } from 'ahooks'
|
||||||
|
import UpdatePlugin from '@/app/components/plugins/update-plugin'
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
const [isShowUpdateModal, {
|
||||||
|
setTrue: showUpdateModal,
|
||||||
|
setFalse: hideUpdateModal,
|
||||||
|
}] = useBoolean(false)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div onClick={showUpdateModal}>Show Upgrade</div>
|
||||||
|
{isShowUpdateModal && (
|
||||||
|
<UpdatePlugin onHide={hideUpdateModal} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page
|
39
web/app/(commonLayout)/plugins/test/tools-picker/page.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
'use client'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import AllTools from '@/app/components/workflow/block-selector/all-tools'
|
||||||
|
import {
|
||||||
|
fetchAllBuiltInTools,
|
||||||
|
fetchAllCustomTools,
|
||||||
|
fetchAllWorkflowTools,
|
||||||
|
} from '@/service/tools'
|
||||||
|
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
|
const ToolsPicker = () => {
|
||||||
|
const [buildInTools, setBuildInTools] = useState<ToolWithProvider[]>([])
|
||||||
|
const [customTools, setCustomTools] = useState<ToolWithProvider[]>([])
|
||||||
|
const [workflowTools, setWorkflowTools] = useState<ToolWithProvider[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const buildInTools = await fetchAllBuiltInTools()
|
||||||
|
const customTools = await fetchAllCustomTools()
|
||||||
|
const workflowTools = await fetchAllWorkflowTools()
|
||||||
|
setBuildInTools(buildInTools)
|
||||||
|
setCustomTools(customTools)
|
||||||
|
setWorkflowTools(workflowTools)
|
||||||
|
})()
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className="relative mt-5 mx-auto w-[320px] bg-white">
|
||||||
|
<AllTools
|
||||||
|
searchText=""
|
||||||
|
onSelect={() => { }}
|
||||||
|
buildInTools={buildInTools}
|
||||||
|
customTools={customTools}
|
||||||
|
workflowTools={workflowTools}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolsPicker
|
@ -9,7 +9,7 @@ import ConfirmAddVar from './confirm-add-var'
|
|||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap'
|
import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { type PromptVariable } from '@/models/debug'
|
import type { PromptVariable } from '@/models/debug'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import type { CompletionParams } from '@/types/app'
|
import type { CompletionParams } from '@/types/app'
|
||||||
import { AppType } from '@/types/app'
|
import { AppType } from '@/types/app'
|
||||||
|
@ -106,23 +106,22 @@ const SettingBuiltInTool: FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{infoSchemas.length > 0 && (
|
{infoSchemas.length > 0 && (
|
||||||
<div className='mt-6'>
|
<div className='my-2'>
|
||||||
<div className='flex items-center mb-4 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>
|
<div className='pt-3 text-text-secondary system-sm-semibold-uppercase'>
|
||||||
<div className='mr-3'>{t('tools.setBuiltInTools.parameters')}</div>
|
{t('tools.setBuiltInTools.parameters')}
|
||||||
<div className='grow w-0 h-px bg-[#f3f4f6]'></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='space-y-4'>
|
<div className='py-2 space-y-3'>
|
||||||
{infoSchemas.map((item: any, index) => (
|
{infoSchemas.map((item: any, index) => (
|
||||||
<div key={index}>
|
<div key={index} className='py-1'>
|
||||||
<div className='flex items-center space-x-2 leading-[18px]'>
|
<div className='flex items-center gap-2'>
|
||||||
<div className='text-[13px] font-semibold text-gray-900'>{item.label[language]}</div>
|
<div className='text-text-secondary code-sm-semibold'>{item.label[language]}</div>
|
||||||
<div className='text-xs font-medium text-gray-500'>{item.type === 'number-input' ? t('tools.setBuiltInTools.number') : t('tools.setBuiltInTools.string')}</div>
|
<div className='text-text-tertiary system-xs-regular'>{item.type === 'number-input' ? t('tools.setBuiltInTools.number') : t('tools.setBuiltInTools.string')}</div>
|
||||||
{item.required && (
|
{item.required && (
|
||||||
<div className='text-xs font-medium text-[#EC4A0A]'>{t('tools.setBuiltInTools.required')}</div>
|
<div className='text-text-warning-secondary system-xs-medium'>{t('tools.setBuiltInTools.required')}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{item.human_description && (
|
{item.human_description && (
|
||||||
<div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'>
|
<div className='mt-0.5 text-text-tertiary system-xs-regular'>
|
||||||
{item.human_description?.[language]}
|
{item.human_description?.[language]}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -192,9 +191,9 @@ const SettingBuiltInTool: FC<Props> = ({
|
|||||||
</>)}
|
</>)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
panelClassName='mt-[65px] !w-[405px]'
|
panelClassName='mt-[64px] mb-2 !w-[420px]'
|
||||||
maxWidthClassName='!max-w-[405px]'
|
maxWidthClassName='!max-w-[420px]'
|
||||||
height='calc(100vh - 65px)'
|
height='calc(100vh - 64px)'
|
||||||
headerClassName='!border-b-black/5'
|
headerClassName='!border-b-black/5'
|
||||||
body={
|
body={
|
||||||
<div className='h-full pt-3'>
|
<div className='h-full pt-3'>
|
||||||
@ -203,7 +202,7 @@ const SettingBuiltInTool: FC<Props> = ({
|
|||||||
<Loading type='app' />
|
<Loading type='app' />
|
||||||
</div>
|
</div>
|
||||||
: (<div className='flex flex-col h-full'>
|
: (<div className='flex flex-col h-full'>
|
||||||
<div className='grow h-0 overflow-y-auto px-6'>
|
<div className='grow h-0 overflow-y-auto px-4'>
|
||||||
{isInfoActive ? infoUI : settingUI}
|
{isInfoActive ? infoUI : settingUI}
|
||||||
</div>
|
</div>
|
||||||
{!readonly && !isInfoActive && (
|
{!readonly && !isInfoActive && (
|
||||||
|
@ -19,7 +19,7 @@ import AgentTools from './agent/agent-tools'
|
|||||||
import ConfigContext from '@/context/debug-configuration'
|
import ConfigContext from '@/context/debug-configuration'
|
||||||
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
||||||
import ConfigVar from '@/app/components/app/configuration/config-var'
|
import ConfigVar from '@/app/components/app/configuration/config-var'
|
||||||
import { type CitationConfig, type ModelConfig, type ModerationConfig, type MoreLikeThisConfig, type PromptVariable, type SpeechToTextConfig, type SuggestedQuestionsAfterAnswerConfig, type TextToSpeechConfig } from '@/models/debug'
|
import type { CitationConfig, ModelConfig, ModerationConfig, MoreLikeThisConfig, PromptVariable, SpeechToTextConfig, SuggestedQuestionsAfterAnswerConfig, TextToSpeechConfig } from '@/models/debug'
|
||||||
import type { AppType } from '@/types/app'
|
import type { AppType } from '@/types/app'
|
||||||
import { ModelModeType } from '@/types/app'
|
import { ModelModeType } from '@/types/app'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
@ -134,11 +134,11 @@ const Config: FC = () => {
|
|||||||
annotation: annotationConfig.enabled,
|
annotation: annotationConfig.enabled,
|
||||||
setAnnotation: async (value) => {
|
setAnnotation: async (value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
setIsShowAnnotationConfigInit(true)
|
setIsShowAnnotationConfigInit(true)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
await handleDisableAnnotation(annotationConfig.embedding_model)
|
await handleDisableAnnotation(annotationConfig.embedding_model)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -55,7 +55,7 @@ import ModelParameterModal from '@/app/components/header/account-setting/model-p
|
|||||||
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import { fetchCollectionList } from '@/service/tools'
|
import { fetchCollectionList } from '@/service/tools'
|
||||||
import { type Collection } from '@/app/components/tools/types'
|
import type { Collection } from '@/app/components/tools/types'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import {
|
import {
|
||||||
getMultipleRetrievalConfig,
|
getMultipleRetrievalConfig,
|
||||||
@ -142,7 +142,7 @@ const Configuration: FC = () => {
|
|||||||
const setCompletionParams = (value: FormValue) => {
|
const setCompletionParams = (value: FormValue) => {
|
||||||
const params = { ...value }
|
const params = { ...value }
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
if ((!params.stop || params.stop.length === 0) && (modeModeTypeRef.current === ModelModeType.completion)) {
|
if ((!params.stop || params.stop.length === 0) && (modeModeTypeRef.current === ModelModeType.completion)) {
|
||||||
params.stop = getTempStop()
|
params.stop = getTempStop()
|
||||||
setTempStop([])
|
setTempStop([])
|
||||||
@ -331,7 +331,7 @@ const Configuration: FC = () => {
|
|||||||
const [canReturnToSimpleMode, setCanReturnToSimpleMode] = useState(true)
|
const [canReturnToSimpleMode, setCanReturnToSimpleMode] = useState(true)
|
||||||
const setPromptMode = async (mode: PromptMode) => {
|
const setPromptMode = async (mode: PromptMode) => {
|
||||||
if (mode === PromptMode.advanced) {
|
if (mode === PromptMode.advanced) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
await migrateToDefaultPrompt()
|
await migrateToDefaultPrompt()
|
||||||
setCanReturnToSimpleMode(true)
|
setCanReturnToSimpleMode(true)
|
||||||
}
|
}
|
||||||
@ -523,7 +523,6 @@ const Configuration: FC = () => {
|
|||||||
sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
|
sensitive_word_avoidance: modelConfig.sensitive_word_avoidance,
|
||||||
external_data_tools: modelConfig.external_data_tools,
|
external_data_tools: modelConfig.external_data_tools,
|
||||||
dataSets: datasets || [],
|
dataSets: datasets || [],
|
||||||
// eslint-disable-next-line multiline-ternary
|
|
||||||
agentConfig: res.mode === 'agent-chat' ? {
|
agentConfig: res.mode === 'agent-chat' ? {
|
||||||
max_iteration: DEFAULT_AGENT_SETTING.max_iteration,
|
max_iteration: DEFAULT_AGENT_SETTING.max_iteration,
|
||||||
...modelConfig.agent_mode,
|
...modelConfig.agent_mode,
|
||||||
|
@ -20,7 +20,7 @@ const Progress: FC<IProgressProps> = ({
|
|||||||
className={cn(s.bar, exhausted && s['bar-error'], 'absolute top-0 left-0 right-0 bottom-0')}
|
className={cn(s.bar, exhausted && s['bar-error'], 'absolute top-0 left-0 right-0 bottom-0')}
|
||||||
style={{ width: `${value}%` }}
|
style={{ width: `${value}%` }}
|
||||||
/>
|
/>
|
||||||
{Array(10).fill(0).map((i, k) => (
|
{Array.from({ length: 10 }).fill(0).map((i, k) => (
|
||||||
<div key={k} className={s['bar-item']} />
|
<div key={k} className={s['bar-item']} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.action-btn {
|
.action-btn {
|
||||||
@apply inline-flex justify-center items-center cursor-pointer text-text-tertiary
|
@apply inline-flex justify-center items-center cursor-pointer text-text-tertiary hover:text-text-secondary hover:bg-state-base-hover
|
||||||
hover:text-text-secondary
|
|
||||||
hover:bg-state-base-hover
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn-disabled {
|
.action-btn-disabled {
|
||||||
@ -29,21 +27,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-btn.action-btn-active {
|
.action-btn.action-btn-active {
|
||||||
@apply
|
@apply text-text-accent bg-state-accent-active hover:bg-state-accent-active-alt
|
||||||
text-text-accent
|
|
||||||
bg-state-accent-active
|
|
||||||
hover:bg-state-accent-active-alt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn.action-btn-disabled {
|
.action-btn.action-btn-disabled {
|
||||||
@apply
|
@apply text-text-disabled
|
||||||
text-text-disabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn.action-btn-destructive {
|
.action-btn.action-btn-destructive {
|
||||||
@apply
|
@apply text-text-destructive bg-state-destructive-hover
|
||||||
text-text-destructive
|
|
||||||
bg-state-destructive-hover
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -28,7 +28,7 @@ const actionButtonVariants = cva(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export type ActionButtonProps = {
|
export type ActionButtonProps = {
|
||||||
size?: 'xs' | 'm' | 'l' | 'xl'
|
size?: 'xs' | 's' | 'm' | 'l' | 'xl'
|
||||||
state?: ActionButtonState
|
state?: ActionButtonState
|
||||||
styleCss?: CSSProperties
|
styleCss?: CSSProperties
|
||||||
} & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof actionButtonVariants>
|
} & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof actionButtonVariants>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import AudioPlayer from '@/app/components/base/audio-btn/audio'
|
import AudioPlayer from '@/app/components/base/audio-btn/audio'
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
// eslint-disable-next-line ts/consistent-type-definitions
|
||||||
interface AudioPlayerManager {
|
interface AudioPlayerManager {
|
||||||
instance: AudioPlayerManager
|
instance: AudioPlayerManager
|
||||||
}
|
}
|
||||||
@ -12,6 +12,7 @@ export class AudioPlayerManager {
|
|||||||
private audioPlayers: AudioPlayer | null = null
|
private audioPlayers: AudioPlayer | null = null
|
||||||
private msgId: string | undefined
|
private msgId: string | undefined
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
private constructor() {
|
private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import Toast from '@/app/components/base/toast'
|
|||||||
import { textToAudioStream } from '@/service/share'
|
import { textToAudioStream } from '@/service/share'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
// eslint-disable-next-line ts/consistent-type-definitions
|
||||||
interface Window {
|
interface Window {
|
||||||
ManagedMediaSource: any
|
ManagedMediaSource: any
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({ src }) => {
|
|||||||
audio.load()
|
audio.load()
|
||||||
|
|
||||||
// Delayed generation of waveform data
|
// Delayed generation of waveform data
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
const timer = setTimeout(() => generateWaveformData(src), 1000)
|
const timer = setTimeout(() => generateWaveformData(src), 1000)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -49,4 +49,6 @@ const AutoHeightTextarea = forwardRef<HTMLTextAreaElement, AutoHeightTextareaPro
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AutoHeightTextarea.displayName = 'AutoHeightTextarea'
|
||||||
|
|
||||||
export default AutoHeightTextarea
|
export default AutoHeightTextarea
|
||||||
|
@ -5,22 +5,28 @@ type BadgeProps = {
|
|||||||
className?: string
|
className?: string
|
||||||
text: string
|
text: string
|
||||||
uppercase?: boolean
|
uppercase?: boolean
|
||||||
|
hasRedCornerMark?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Badge = ({
|
const Badge = ({
|
||||||
className,
|
className,
|
||||||
text,
|
text,
|
||||||
uppercase = true,
|
uppercase = true,
|
||||||
|
hasRedCornerMark,
|
||||||
}: BadgeProps) => {
|
}: BadgeProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center px-[5px] h-5 rounded-[5px] border border-divider-deep leading-3 text-text-tertiary',
|
'relative inline-flex items-center px-[5px] h-5 rounded-[5px] border border-divider-deep leading-3 text-text-tertiary',
|
||||||
uppercase ? 'system-2xs-medium-uppercase' : 'system-xs-medium',
|
uppercase ? 'system-2xs-medium-uppercase' : 'system-xs-medium',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
|
{hasRedCornerMark && (
|
||||||
|
<div className='absolute top-[-2px] right-[-2px] w-1.5 h-1.5 border border-components-badge-status-light-error-border-inner bg-components-badge-status-light-error-bg rounded-[2px] shadow-sm'>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
28
web/app/components/base/badge/index.css
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@tailwind components;
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.badge {
|
||||||
|
@apply inline-flex justify-center items-center text-text-tertiary border border-divider-deep
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-l {
|
||||||
|
@apply rounded-md gap-1 min-w-6
|
||||||
|
}
|
||||||
|
|
||||||
|
/* m is for the regular button */
|
||||||
|
.badge-m {
|
||||||
|
@apply rounded-md gap-[3px] min-w-5
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-s {
|
||||||
|
@apply rounded-[5px] gap-0.5 min-w-[18px]
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.badge-warning {
|
||||||
|
@apply text-text-warning border border-text-warning
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.badge-accent {
|
||||||
|
@apply text-text-accent-secondary border border-text-accent-secondary
|
||||||
|
}
|
||||||
|
}
|
81
web/app/components/base/badge/index.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import type { CSSProperties, ReactNode } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { type VariantProps, cva } from 'class-variance-authority'
|
||||||
|
import classNames from '@/utils/classnames'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
enum BadgeState {
|
||||||
|
Warning = 'warning',
|
||||||
|
Accent = 'accent',
|
||||||
|
Default = '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const BadgeVariants = cva(
|
||||||
|
'badge',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
s: 'badge-s',
|
||||||
|
m: 'badge-m',
|
||||||
|
l: 'badge-l',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
type BadgeProps = {
|
||||||
|
size?: 's' | 'm' | 'l'
|
||||||
|
iconOnly?: boolean
|
||||||
|
uppercase?: boolean
|
||||||
|
state?: BadgeState
|
||||||
|
styleCss?: CSSProperties
|
||||||
|
children?: ReactNode
|
||||||
|
} & React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof BadgeVariants>
|
||||||
|
|
||||||
|
function getBadgeState(state: BadgeState) {
|
||||||
|
switch (state) {
|
||||||
|
case BadgeState.Warning:
|
||||||
|
return 'badge-warning'
|
||||||
|
case BadgeState.Accent:
|
||||||
|
return 'badge-accent'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Badge: React.FC<BadgeProps> = ({
|
||||||
|
className,
|
||||||
|
size,
|
||||||
|
state = BadgeState.Default,
|
||||||
|
iconOnly = false,
|
||||||
|
uppercase = false,
|
||||||
|
styleCss,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
BadgeVariants({ size, className }),
|
||||||
|
getBadgeState(state),
|
||||||
|
size === 's'
|
||||||
|
? (iconOnly ? 'p-[3px]' : 'px-[5px] py-[3px]')
|
||||||
|
: size === 'l'
|
||||||
|
? (iconOnly ? 'p-1.5' : 'px-2 py-1')
|
||||||
|
: (iconOnly ? 'p-1' : 'px-[5px] py-[2px]'),
|
||||||
|
uppercase ? 'system-2xs-medium-uppercase' : 'system-2xs-medium',
|
||||||
|
)}
|
||||||
|
style={styleCss}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Badge.displayName = 'Badge'
|
||||||
|
|
||||||
|
export default Badge
|
||||||
|
export { Badge, BadgeState, BadgeVariants }
|
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
|
import { debounce } from 'lodash-es'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
@ -18,22 +19,29 @@ const CopyBtn = ({
|
|||||||
}: ICopyBtnProps) => {
|
}: ICopyBtnProps) => {
|
||||||
const [isCopied, setIsCopied] = useState(false)
|
const [isCopied, setIsCopied] = useState(false)
|
||||||
|
|
||||||
|
const onClickCopy = debounce(() => {
|
||||||
|
copy(value)
|
||||||
|
setIsCopied(true)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
const onMouseLeave = debounce(() => {
|
||||||
|
setIsCopied(false)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${className}`}>
|
<div className={`${className}`}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
popupContent={(isCopied ? t('appApi.copied') : t('appApi.copy'))}
|
popupContent={(isCopied ? t('appApi.copied') : t('appApi.copy'))}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
className={'box-border p-0.5 flex items-center justify-center rounded-md bg-white cursor-pointer'}
|
className={'box-border p-0.5 flex items-center justify-center rounded-md bg-white cursor-pointer'}
|
||||||
style={!isPlain
|
style={!isPlain
|
||||||
? {
|
? {
|
||||||
boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)',
|
boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)',
|
||||||
}
|
}
|
||||||
: {}}
|
: {}}
|
||||||
onClick={() => {
|
onClick={onClickCopy}
|
||||||
copy(value)
|
|
||||||
setIsCopied(true)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className={`w-6 h-6 rounded-md hover:bg-gray-50 ${s.copyIcon} ${isCopied ? s.copied : ''}`}></div>
|
<div className={`w-6 h-6 rounded-md hover:bg-gray-50 ${s.copyIcon} ${isCopied ? s.copied : ''}`}></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,8 +12,9 @@ import Divider from '@/app/components/base/divider'
|
|||||||
import { searchEmoji } from '@/utils/emoji'
|
import { searchEmoji } from '@/utils/emoji'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
// eslint-disable-next-line ts/no-namespace
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
// eslint-disable-next-line ts/consistent-type-definitions
|
||||||
interface IntrinsicElements {
|
interface IntrinsicElements {
|
||||||
'em-emoji': React.DetailedHTMLProps< React.HTMLAttributes<HTMLElement>, HTMLElement >
|
'em-emoji': React.DetailedHTMLProps< React.HTMLAttributes<HTMLElement>, HTMLElement >
|
||||||
}
|
}
|
||||||
|
@ -28,4 +28,6 @@ const IconBase = forwardRef<React.MutableRefObject<HTMLOrSVGElement>, IconBasePr
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
IconBase.displayName = 'IconBase'
|
||||||
|
|
||||||
export default IconBase
|
export default IconBase
|
||||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
@ -0,0 +1,8 @@
|
|||||||
|
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Group">
|
||||||
|
<path id="Vector" d="M5.6475 8.0115L0.333496 5.05884V11.3335C0.333491 11.4524 0.365258 11.569 0.425506 11.6715C0.485754 11.7739 0.572294 11.8584 0.676163 11.9162L6.3335 15.0588V9.17684C6.33344 8.93907 6.26981 8.70565 6.14919 8.50075C6.02857 8.29586 5.85536 8.12694 5.6475 8.0115Z" fill="#354052"/>
|
||||||
|
<path id="Vector_2" d="M7.66699 9.17684V15.0588L13.3243 11.9162C13.4282 11.8584 13.5147 11.7739 13.575 11.6715C13.6352 11.569 13.667 11.4524 13.667 11.3335V5.05884L8.35299 8.0115C8.14513 8.12694 7.97192 8.29586 7.8513 8.50075C7.73068 8.70565 7.66705 8.93907 7.66699 9.17684Z" fill="#676F83"/>
|
||||||
|
<path id="Vector_3" d="M10.1913 2.34351C9.804 3.33351 8.588 4.00017 7 4.00017C5.412 4.00017 4.196 3.33351 3.80867 2.34351L1 3.90417L6.35267 6.87817C6.5507 6.98815 6.77348 7.04586 7 7.04586C7.22652 7.04586 7.4493 6.98815 7.64733 6.87817L13 3.90417L10.1913 2.34351Z" fill="#676F83"/>
|
||||||
|
<path id="Vector_4" d="M7 2.66675C8.10457 2.66675 9 2.21903 9 1.66675C9 1.11446 8.10457 0.666748 7 0.666748C5.89543 0.666748 5 1.11446 5 1.66675C5 2.21903 5.89543 2.66675 7 2.66675Z" fill="#354052"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,9 @@
|
|||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Icon">
|
||||||
|
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M11.3891 2.41987C11.6635 2.58871 11.749 2.94802 11.5802 3.22239L10.3324 5.25H11.0833C11.4055 5.25 11.6667 5.51117 11.6667 5.83334V11.6667C11.6667 12.311 11.1444 12.8333 10.5 12.8333H3.50001C2.85568 12.8333 2.33334 12.311 2.33334 11.6667V5.83334C2.33334 5.51117 2.59451 5.25 2.91668 5.25H8.96252L10.5865 2.61094C10.7554 2.33657 11.1147 2.25102 11.3891 2.41987ZM5.83334 7.58334C5.51118 7.58334 5.25001 7.84449 5.25001 8.16667C5.25001 8.48884 5.51118 8.75 5.83334 8.75H8.16668C8.48885 8.75 8.75001 8.48884 8.75001 8.16667C8.75001 7.84449 8.48885 7.58334 8.16668 7.58334H5.83334Z" fill="#676F83"/>
|
||||||
|
<g id="Vector_2" opacity="0.5">
|
||||||
|
<path d="M6.91257 1.79347C6.96898 1.76525 7.01477 1.71948 7.043 1.66303L7.32195 1.10508C7.42946 0.890105 7.73623 0.890105 7.84374 1.10508L8.12269 1.66303C8.15093 1.71948 8.19672 1.76525 8.25313 1.79347L8.81108 2.07245C9.0261 2.17994 9.0261 2.48672 8.81108 2.5942L8.25313 2.87318C8.19672 2.9014 8.15093 2.94717 8.12269 3.00362L7.84374 3.56158C7.73623 3.77655 7.42946 3.77655 7.32195 3.56158L7.043 3.00362C7.01477 2.94717 6.96898 2.9014 6.91257 2.87318L6.35461 2.5942C6.13965 2.48672 6.13965 2.17994 6.35461 2.07245L6.91257 1.79347Z" fill="#676F83"/>
|
||||||
|
<path d="M3.80145 2.7657C3.85789 2.73748 3.90366 2.69171 3.93189 2.63526L4.11364 2.27174C4.22113 2.05677 4.5279 2.05677 4.63539 2.27174L4.81715 2.63526C4.84537 2.6917 4.89114 2.73748 4.94759 2.7657L5.3111 2.94745C5.52607 3.05494 5.52607 3.36172 5.3111 3.4692L4.94759 3.65096C4.89114 3.67919 4.84537 3.72495 4.81715 3.7814L4.63539 4.14491C4.5279 4.35988 4.22113 4.35988 4.11364 4.14491L3.93189 3.7814C3.90366 3.72495 3.85789 3.67919 3.80145 3.65096L3.43793 3.4692C3.22296 3.36172 3.22296 3.05494 3.43793 2.94745L3.80145 2.7657Z" fill="#676F83"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="13" height="20" viewBox="0 0 13 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="Shape" d="M0 0H13V20C9.98017 20 7.26458 18.1615 6.14305 15.3576L0 0Z" fill="#F9FAFB"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 200 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,6 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Icon">
|
||||||
|
<path id="Vector" d="M3.99999 1.33325H7.99999V5.33325C7.99999 6.06963 8.59692 6.66659 9.33332 6.66659H13.3333V13.3333C13.3333 14.0697 12.7364 14.6666 12 14.6666H6.66666V13.3333H7.99999V11.9999H6.66666V10.6666H7.99999V9.33325H6.66666V7.99992H5.33332V9.33325H6.66666V10.6666H5.33332V11.9999H6.66666V13.3333H5.33332V14.6666H3.99999C3.26361 14.6666 2.66666 14.0697 2.66666 13.3333V2.66659C2.66666 1.93021 3.26361 1.33325 3.99999 1.33325Z" fill="#676F83"/>
|
||||||
|
<path id="Vector_2" opacity="0.5" d="M12.9428 4.99993C13.0415 5.09868 13.1232 5.21133 13.1859 5.33327H9.33334V1.48071C9.45528 1.54338 9.56794 1.62504 9.66668 1.72379L12.9428 4.99993Z" fill="#676F83"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 775 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Icon">
|
||||||
|
<path id="Vector" d="M8 1C4.1325 1 1 4.1325 1 8C1 11.0975 3.00375 13.7137 5.78625 14.6413C6.13625 14.7025 6.2675 14.4925 6.2675 14.3088C6.2675 14.1425 6.25875 13.5913 6.25875 13.005C4.5 13.3288 4.045 12.5763 3.905 12.1825C3.82625 11.9812 3.485 11.36 3.1875 11.1937C2.9425 11.0625 2.5925 10.7387 3.17875 10.73C3.73 10.7212 4.12375 11.2375 4.255 11.4475C4.885 12.5062 5.89125 12.2088 6.29375 12.025C6.355 11.57 6.53875 11.2638 6.74 11.0887C5.1825 10.9137 3.555 10.31 3.555 7.6325C3.555 6.87125 3.82625 6.24125 4.2725 5.75125C4.2025 5.57625 3.9575 4.85875 4.3425 3.89625C4.3425 3.89625 4.92875 3.7125 6.2675 4.61375C6.8275 4.45625 7.4225 4.3775 8.0175 4.3775C8.6125 4.3775 9.2075 4.45625 9.7675 4.61375C11.1063 3.70375 11.6925 3.89625 11.6925 3.89625C12.0775 4.85875 11.8325 5.57625 11.7625 5.75125C12.2087 6.24125 12.48 6.8625 12.48 7.6325C12.48 10.3187 10.8438 10.9137 9.28625 11.0887C9.54 11.3075 9.75875 11.7275 9.75875 12.3837C9.75875 13.32 9.75 14.0725 9.75 14.3088C9.75 14.4925 9.88125 14.7113 10.2312 14.6413C11.6209 14.1721 12.8284 13.279 13.6839 12.0877C14.5393 10.8963 14.9996 9.46668 15 8C15 4.1325 11.8675 1 8 1Z" fill="#676F83"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,8 +1,9 @@
|
|||||||
const path = require('node:path')
|
import path from 'node:path'
|
||||||
const { open, readdir, access, mkdir, writeFile, appendFile, rm } = require('node:fs/promises')
|
import { access, appendFile, mkdir, open, readdir, rm, writeFile } from 'node:fs/promises'
|
||||||
const { parseXml } = require('@rgrove/parse-xml')
|
import { parseXml } from '@rgrove/parse-xml'
|
||||||
const camelCase = require('lodash/camelCase')
|
import { camelCase, template } from 'lodash-es'
|
||||||
const template = require('lodash/template')
|
|
||||||
|
const __dirname = path.dirname(new URL(import.meta.url).pathname)
|
||||||
|
|
||||||
const generateDir = async (currentPath) => {
|
const generateDir = async (currentPath) => {
|
||||||
try {
|
try {
|
66
web/app/components/base/icons/src/vender/other/Group.json
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "14",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 14 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Group"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector",
|
||||||
|
"d": "M5.6475 8.0115L0.333496 5.05884V11.3335C0.333491 11.4524 0.365258 11.569 0.425506 11.6715C0.485754 11.7739 0.572294 11.8584 0.676163 11.9162L6.3335 15.0588V9.17684C6.33344 8.93907 6.26981 8.70565 6.14919 8.50075C6.02857 8.29586 5.85536 8.12694 5.6475 8.0115Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector_2",
|
||||||
|
"d": "M7.66699 9.17684V15.0588L13.3243 11.9162C13.4282 11.8584 13.5147 11.7739 13.575 11.6715C13.6352 11.569 13.667 11.4524 13.667 11.3335V5.05884L8.35299 8.0115C8.14513 8.12694 7.97192 8.29586 7.8513 8.50075C7.73068 8.70565 7.66705 8.93907 7.66699 9.17684Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector_3",
|
||||||
|
"d": "M10.1913 2.34351C9.804 3.33351 8.588 4.00017 7 4.00017C5.412 4.00017 4.196 3.33351 3.80867 2.34351L1 3.90417L6.35267 6.87817C6.5507 6.98815 6.77348 7.04586 7 7.04586C7.22652 7.04586 7.4493 6.98815 7.64733 6.87817L13 3.90417L10.1913 2.34351Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector_4",
|
||||||
|
"d": "M7 2.66675C8.10457 2.66675 9 2.21903 9 1.66675C9 1.11446 8.10457 0.666748 7 0.666748C5.89543 0.666748 5 1.11446 5 1.66675C5 2.21903 5.89543 2.66675 7 2.66675Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Group"
|
||||||
|
}
|
16
web/app/components/base/icons/src/vender/other/Group.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './Group.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'Group'
|
||||||
|
|
||||||
|
export default Icon
|
@ -1 +1,2 @@
|
|||||||
export { default as Generator } from './Generator'
|
export { default as Generator } from './Generator'
|
||||||
|
export { default as Group } from './Group'
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "14",
|
||||||
|
"height": "14",
|
||||||
|
"viewBox": "0 0 14 14",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector",
|
||||||
|
"fill-rule": "evenodd",
|
||||||
|
"clip-rule": "evenodd",
|
||||||
|
"d": "M11.3891 2.41987C11.6635 2.58871 11.749 2.94802 11.5802 3.22239L10.3324 5.25H11.0833C11.4055 5.25 11.6667 5.51117 11.6667 5.83334V11.6667C11.6667 12.311 11.1444 12.8333 10.5 12.8333H3.50001C2.85568 12.8333 2.33334 12.311 2.33334 11.6667V5.83334C2.33334 5.51117 2.59451 5.25 2.91668 5.25H8.96252L10.5865 2.61094C10.7554 2.33657 11.1147 2.25102 11.3891 2.41987ZM5.83334 7.58334C5.51118 7.58334 5.25001 7.84449 5.25001 8.16667C5.25001 8.48884 5.51118 8.75 5.83334 8.75H8.16668C8.48885 8.75 8.75001 8.48884 8.75001 8.16667C8.75001 7.84449 8.48885 7.58334 8.16668 7.58334H5.83334Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector_2",
|
||||||
|
"opacity": "0.5"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M6.91257 1.79347C6.96898 1.76525 7.01477 1.71948 7.043 1.66303L7.32195 1.10508C7.42946 0.890105 7.73623 0.890105 7.84374 1.10508L8.12269 1.66303C8.15093 1.71948 8.19672 1.76525 8.25313 1.79347L8.81108 2.07245C9.0261 2.17994 9.0261 2.48672 8.81108 2.5942L8.25313 2.87318C8.19672 2.9014 8.15093 2.94717 8.12269 3.00362L7.84374 3.56158C7.73623 3.77655 7.42946 3.77655 7.32195 3.56158L7.043 3.00362C7.01477 2.94717 6.96898 2.9014 6.91257 2.87318L6.35461 2.5942C6.13965 2.48672 6.13965 2.17994 6.35461 2.07245L6.91257 1.79347Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M3.80145 2.7657C3.85789 2.73748 3.90366 2.69171 3.93189 2.63526L4.11364 2.27174C4.22113 2.05677 4.5279 2.05677 4.63539 2.27174L4.81715 2.63526C4.84537 2.6917 4.89114 2.73748 4.94759 2.7657L5.3111 2.94745C5.52607 3.05494 5.52607 3.36172 5.3111 3.4692L4.94759 3.65096C4.89114 3.67919 4.84537 3.72495 4.81715 3.7814L4.63539 4.14491C4.5279 4.35988 4.22113 4.35988 4.11364 4.14491L3.93189 3.7814C3.90366 3.72495 3.85789 3.67919 3.80145 3.65096L3.43793 3.4692C3.22296 3.36172 3.22296 3.05494 3.43793 2.94745L3.80145 2.7657Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "BoxSparkleFill"
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './BoxSparkleFill.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'BoxSparkleFill'
|
||||||
|
|
||||||
|
export default Icon
|
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "13",
|
||||||
|
"height": "20",
|
||||||
|
"viewBox": "0 0 13 20",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Shape",
|
||||||
|
"d": "M0 0H13V20C9.98017 20 7.26458 18.1615 6.14305 15.3576L0 0Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "LeftCorner"
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './LeftCorner.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'LeftCorner'
|
||||||
|
|
||||||
|
export default Icon
|
2
web/app/components/base/icons/src/vender/plugin/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as BoxSparkleFill } from './BoxSparkleFill'
|
||||||
|
export { default as LeftCorner } from './LeftCorner'
|
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector",
|
||||||
|
"d": "M3.99999 1.33325H7.99999V5.33325C7.99999 6.06963 8.59692 6.66659 9.33332 6.66659H13.3333V13.3333C13.3333 14.0697 12.7364 14.6666 12 14.6666H6.66666V13.3333H7.99999V11.9999H6.66666V10.6666H7.99999V9.33325H6.66666V7.99992H5.33332V9.33325H6.66666V10.6666H5.33332V11.9999H6.66666V13.3333H5.33332V14.6666H3.99999C3.26361 14.6666 2.66666 14.0697 2.66666 13.3333V2.66659C2.66666 1.93021 3.26361 1.33325 3.99999 1.33325Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector_2",
|
||||||
|
"opacity": "0.5",
|
||||||
|
"d": "M12.9428 4.99993C13.0415 5.09868 13.1232 5.21133 13.1859 5.33327H9.33334V1.48071C9.45528 1.54338 9.56794 1.62504 9.66668 1.72379L12.9428 4.99993Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "FileZip"
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './FileZip.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'FileZip'
|
||||||
|
|
||||||
|
export default Icon
|
@ -1,3 +1,4 @@
|
|||||||
export { default as File05 } from './File05'
|
export { default as File05 } from './File05'
|
||||||
export { default as FileSearch02 } from './FileSearch02'
|
export { default as FileSearch02 } from './FileSearch02'
|
||||||
|
export { default as FileZip } from './FileZip'
|
||||||
export { default as Folder } from './Folder'
|
export { default as Folder } from './Folder'
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Vector",
|
||||||
|
"d": "M8 1C4.1325 1 1 4.1325 1 8C1 11.0975 3.00375 13.7137 5.78625 14.6413C6.13625 14.7025 6.2675 14.4925 6.2675 14.3088C6.2675 14.1425 6.25875 13.5913 6.25875 13.005C4.5 13.3288 4.045 12.5763 3.905 12.1825C3.82625 11.9812 3.485 11.36 3.1875 11.1937C2.9425 11.0625 2.5925 10.7387 3.17875 10.73C3.73 10.7212 4.12375 11.2375 4.255 11.4475C4.885 12.5062 5.89125 12.2088 6.29375 12.025C6.355 11.57 6.53875 11.2638 6.74 11.0887C5.1825 10.9137 3.555 10.31 3.555 7.6325C3.555 6.87125 3.82625 6.24125 4.2725 5.75125C4.2025 5.57625 3.9575 4.85875 4.3425 3.89625C4.3425 3.89625 4.92875 3.7125 6.2675 4.61375C6.8275 4.45625 7.4225 4.3775 8.0175 4.3775C8.6125 4.3775 9.2075 4.45625 9.7675 4.61375C11.1063 3.70375 11.6925 3.89625 11.6925 3.89625C12.0775 4.85875 11.8325 5.57625 11.7625 5.75125C12.2087 6.24125 12.48 6.8625 12.48 7.6325C12.48 10.3187 10.8438 10.9137 9.28625 11.0887C9.54 11.3075 9.75875 11.7275 9.75875 12.3837C9.75875 13.32 9.75 14.0725 9.75 14.3088C9.75 14.4925 9.88125 14.7113 10.2312 14.6413C11.6209 14.1721 12.8284 13.279 13.6839 12.0877C14.5393 10.8963 14.9996 9.46668 15 8C15 4.1325 11.8675 1 8 1Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Github"
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './Github.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'Github'
|
||||||
|
|
||||||
|
export default Icon
|
@ -5,6 +5,7 @@ export { default as Download02 } from './Download02'
|
|||||||
export { default as Edit03 } from './Edit03'
|
export { default as Edit03 } from './Edit03'
|
||||||
export { default as Edit04 } from './Edit04'
|
export { default as Edit04 } from './Edit04'
|
||||||
export { default as Eye } from './Eye'
|
export { default as Eye } from './Eye'
|
||||||
|
export { default as Github } from './Github'
|
||||||
export { default as MessageClockCircle } from './MessageClockCircle'
|
export { default as MessageClockCircle } from './MessageClockCircle'
|
||||||
export { default as PlusCircle } from './PlusCircle'
|
export { default as PlusCircle } from './PlusCircle'
|
||||||
export { default as QuestionTriangle } from './QuestionTriangle'
|
export { default as QuestionTriangle } from './QuestionTriangle'
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
import { useSelector } from '@/context/app-context'
|
|
||||||
|
|
||||||
type LogoSiteProps = {
|
type LogoSiteProps = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -10,17 +9,10 @@ type LogoSiteProps = {
|
|||||||
const LogoSite: FC<LogoSiteProps> = ({
|
const LogoSite: FC<LogoSiteProps> = ({
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useSelector((s) => {
|
|
||||||
return {
|
|
||||||
theme: s.theme,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const src = theme === 'light' ? '/logo/logo-site.png' : `/logo/logo-site-${theme}.png`
|
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
src={src}
|
src={'/logo/logo.png'}
|
||||||
className={classNames('block w-auto h-10', className)}
|
className={classNames('block w-[22.651px] h-[24.5px]', className)}
|
||||||
alt='logo'
|
alt='logo'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -75,7 +75,6 @@ export function PreCode(props: { children: any }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
||||||
const useLazyLoad = (ref: RefObject<Element>): boolean => {
|
const useLazyLoad = (ref: RefObject<Element>): boolean => {
|
||||||
const [isIntersecting, setIntersecting] = useState<boolean>(false)
|
const [isIntersecting, setIntersecting] = useState<boolean>(false)
|
||||||
|
|
||||||
@ -297,11 +296,11 @@ export default class ErrorBoundary extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line ts/ban-ts-comment
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
if (this.state.hasError)
|
if (this.state.hasError)
|
||||||
return <div>Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content. <br />(see the browser console for more information)</div>
|
return <div>Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content. <br />(see the browser console for more information)</div>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line ts/ban-ts-comment
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
return this.props.children
|
return this.props.children
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`)
|
const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`)
|
||||||
const prevPrimitiveCode = usePrevious(props.PrimitiveCode)
|
const prevPrimitiveCode = usePrevious(props.PrimitiveCode)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const timeRef = useRef<NodeJS.Timeout>()
|
const timeRef = useRef<number>()
|
||||||
const [errMsg, setErrMsg] = useState('')
|
const [errMsg, setErrMsg] = useState('')
|
||||||
|
|
||||||
const renderFlowchart = async (PrimitiveCode: string) => {
|
const renderFlowchart = async (PrimitiveCode: string) => {
|
||||||
@ -74,15 +74,15 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (timeRef.current)
|
if (timeRef.current)
|
||||||
clearTimeout(timeRef.current)
|
window.clearTimeout(timeRef.current)
|
||||||
|
|
||||||
timeRef.current = setTimeout(() => {
|
timeRef.current = window.setTimeout(() => {
|
||||||
renderFlowchart(props.PrimitiveCode)
|
renderFlowchart(props.PrimitiveCode)
|
||||||
}, 300)
|
}, 300)
|
||||||
}, [props.PrimitiveCode])
|
}, [props.PrimitiveCode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line ts/ban-ts-comment
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
{
|
{
|
||||||
@ -108,4 +108,6 @@ const Flowchart = React.forwardRef((props: {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Flowchart.displayName = 'Flowchart'
|
||||||
|
|
||||||
export default Flowchart
|
export default Flowchart
|
||||||
|
@ -34,15 +34,15 @@ export default function CustomPopover({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
}: IPopover) {
|
}: IPopover) {
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||||
const timeOutRef = useRef<NodeJS.Timeout | null>(null)
|
const timeOutRef = useRef<number | null>(null)
|
||||||
|
|
||||||
const onMouseEnter = (isOpen: boolean) => {
|
const onMouseEnter = (isOpen: boolean) => {
|
||||||
timeOutRef.current && clearTimeout(timeOutRef.current)
|
timeOutRef.current && window.clearTimeout(timeOutRef.current)
|
||||||
!isOpen && buttonRef.current?.click()
|
!isOpen && buttonRef.current?.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMouseLeave = (isOpen: boolean) => {
|
const onMouseLeave = (isOpen: boolean) => {
|
||||||
timeOutRef.current = setTimeout(() => {
|
timeOutRef.current = window.setTimeout(() => {
|
||||||
isOpen && buttonRef.current?.click()
|
isOpen && buttonRef.current?.click()
|
||||||
}, timeoutDuration)
|
}, timeoutDuration)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { EditorConfig, NodeKey, SerializedTextNode } from 'lexical'
|
import type { EditorConfig, SerializedTextNode } from 'lexical'
|
||||||
import { $createTextNode, TextNode } from 'lexical'
|
import { $createTextNode, TextNode } from 'lexical'
|
||||||
|
|
||||||
export class CustomTextNode extends TextNode {
|
export class CustomTextNode extends TextNode {
|
||||||
@ -10,9 +10,9 @@ export class CustomTextNode extends TextNode {
|
|||||||
return new CustomTextNode(node.__text, node.__key)
|
return new CustomTextNode(node.__text, node.__key)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(text: string, key?: NodeKey) {
|
// constructor(text: string, key?: NodeKey) {
|
||||||
super(text, key)
|
// super(text, key)
|
||||||
}
|
// }
|
||||||
|
|
||||||
createDOM(config: EditorConfig) {
|
createDOM(config: EditorConfig) {
|
||||||
const dom = super.createDOM(config)
|
const dom = super.createDOM(config)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
EditorConfig,
|
EditorConfig,
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
NodeKey,
|
|
||||||
SerializedTextNode,
|
SerializedTextNode,
|
||||||
} from 'lexical'
|
} from 'lexical'
|
||||||
import {
|
import {
|
||||||
@ -18,9 +17,9 @@ export class VariableValueBlockNode extends TextNode {
|
|||||||
return new VariableValueBlockNode(node.__text, node.__key)
|
return new VariableValueBlockNode(node.__text, node.__key)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(text: string, key?: NodeKey) {
|
// constructor(text: string, key?: NodeKey) {
|
||||||
super(text, key)
|
// super(text, key)
|
||||||
}
|
// }
|
||||||
|
|
||||||
createDOM(config: EditorConfig): HTMLElement {
|
createDOM(config: EditorConfig): HTMLElement {
|
||||||
const element = super.createDOM(config)
|
const element = super.createDOM(config)
|
||||||
|
@ -1,65 +1,82 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import Badge, { BadgeState } from '@/app/components/base/badge/index'
|
||||||
type Option = {
|
type Option = {
|
||||||
value: string
|
value: string
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TabSliderProps = {
|
type TabSliderProps = {
|
||||||
className?: string
|
className?: string
|
||||||
itemWidth?: number
|
|
||||||
value: string
|
value: string
|
||||||
onChange: (v: string) => void
|
onChange: (v: string) => void
|
||||||
options: Option[]
|
options: Option[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const TabSlider: FC<TabSliderProps> = ({
|
const TabSlider: FC<TabSliderProps> = ({
|
||||||
className,
|
className,
|
||||||
itemWidth = 118,
|
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
options,
|
options,
|
||||||
}) => {
|
}) => {
|
||||||
const currentIndex = options.findIndex(option => option.value === value)
|
const [activeIndex, setActiveIndex] = useState(options.findIndex(option => option.value === value))
|
||||||
const current = options[currentIndex]
|
const [sliderStyle, setSliderStyle] = useState({})
|
||||||
|
|
||||||
|
const updateSliderStyle = (index: number) => {
|
||||||
|
const tabElement = document.getElementById(`tab-${index}`)
|
||||||
|
if (tabElement) {
|
||||||
|
const { offsetLeft, offsetWidth } = tabElement
|
||||||
|
setSliderStyle({
|
||||||
|
transform: `translateX(${offsetLeft}px)`,
|
||||||
|
width: `${offsetWidth}px`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newIndex = options.findIndex(option => option.value === value)
|
||||||
|
setActiveIndex(newIndex)
|
||||||
|
updateSliderStyle(newIndex)
|
||||||
|
}, [value, options])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(className, 'relative flex p-0.5 rounded-lg bg-gray-200')}>
|
<div className={cn(className, 'inline-flex p-0.5 rounded-[10px] bg-components-segmented-control-bg-normal relative items-center justify-center')}>
|
||||||
{
|
|
||||||
options.map((option, index) => (
|
|
||||||
<div
|
<div
|
||||||
|
className="absolute top-0.5 bottom-0.5 left-0 right-0 bg-components-panel-bg rounded-[10px] transition-transform duration-300 ease-in-out shadows-shadow-xs"
|
||||||
|
style={sliderStyle}
|
||||||
|
/>
|
||||||
|
{options.map((option, index) => (
|
||||||
|
<div
|
||||||
|
id={`tab-${index}`}
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={`
|
className={cn(
|
||||||
flex justify-center items-center h-7 text-[13px]
|
'relative flex justify-center items-center px-2.5 py-1.5 gap-1 rounded-[10px] transition-colors duration-300 ease-in-out cursor-pointer z-10',
|
||||||
font-semibold text-gray-600 rounded-[7px] cursor-pointer
|
'system-md-semibold',
|
||||||
hover:bg-gray-50
|
index === activeIndex
|
||||||
${index !== options.length - 1 && 'mr-[1px]'}
|
? 'text-text-primary'
|
||||||
`}
|
: 'text-text-tertiary',
|
||||||
style={{
|
)}
|
||||||
width: itemWidth,
|
onClick={() => {
|
||||||
|
if (index !== activeIndex) {
|
||||||
|
onChange(option.value)
|
||||||
|
updateSliderStyle(index)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onClick={() => onChange(option.value)}
|
|
||||||
>
|
>
|
||||||
{option.text}
|
{option.text}
|
||||||
</div>
|
{option.value === 'plugins'
|
||||||
))
|
&& <Badge
|
||||||
}
|
size='s'
|
||||||
{
|
uppercase={true}
|
||||||
current && (
|
state={BadgeState.Default}
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
absolute flex justify-center items-center h-7 bg-white text-[13px] font-semibold text-primary-600
|
|
||||||
border-[0.5px] border-gray-200 rounded-[7px] shadow-xs transition-transform
|
|
||||||
`}
|
|
||||||
style={{
|
|
||||||
width: itemWidth,
|
|
||||||
transform: `translateX(${currentIndex * itemWidth + 1}px)`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{current.text}
|
6
|
||||||
</div>
|
</Badge>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ const TagManagementModal = ({ show, type }: TagManagementModalProps) => {
|
|||||||
setTagList(res)
|
setTagList(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [pending, setPending] = useState<Boolean>(false)
|
const [pending, setPending] = useState<boolean>(false)
|
||||||
const [name, setName] = useState<string>('')
|
const [name, setName] = useState<string>('')
|
||||||
const createNewTag = async () => {
|
const createNewTag = async () => {
|
||||||
if (!name)
|
if (!name)
|
||||||
|
@ -54,7 +54,7 @@ const Panel = (props: PanelProps) => {
|
|||||||
return tagList.filter(tag => tag.type === type && !value.includes(tag.id) && tag.name.includes(keywords))
|
return tagList.filter(tag => tag.type === type && !value.includes(tag.id) && tag.name.includes(keywords))
|
||||||
}, [type, tagList, value, keywords])
|
}, [type, tagList, value, keywords])
|
||||||
|
|
||||||
const [creating, setCreating] = useState<Boolean>(false)
|
const [creating, setCreating] = useState<boolean>(false)
|
||||||
const createNewTag = async () => {
|
const createNewTag = async () => {
|
||||||
if (!keywords)
|
if (!keywords)
|
||||||
return
|
return
|
||||||
|
@ -78,7 +78,7 @@ const TagItemEditor: FC<TagItemEditorProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [showRemoveModal, setShowRemoveModal] = useState(false)
|
const [showRemoveModal, setShowRemoveModal] = useState(false)
|
||||||
const [pending, setPending] = useState<Boolean>(false)
|
const [pending, setPending] = useState<boolean>(false)
|
||||||
const removeTag = async (tagID: string) => {
|
const removeTag = async (tagID: string) => {
|
||||||
if (pending)
|
if (pending)
|
||||||
return
|
return
|
||||||
|
@ -7,11 +7,13 @@ import cn from '@/utils/classnames'
|
|||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClick: () => void
|
onClick?: () => void
|
||||||
|
isDisplayOnly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderBillingBtn: FC<Props> = ({
|
const HeaderBillingBtn: FC<Props> = ({
|
||||||
onClick,
|
onClick,
|
||||||
|
isDisplayOnly = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { plan, enableBilling, isFetchedPlan } = useProviderContext()
|
const { plan, enableBilling, isFetchedPlan } = useProviderContext()
|
||||||
const {
|
const {
|
||||||
@ -25,9 +27,9 @@ const HeaderBillingBtn: FC<Props> = ({
|
|||||||
})()
|
})()
|
||||||
const classNames = (() => {
|
const classNames = (() => {
|
||||||
if (type === Plan.professional)
|
if (type === Plan.professional)
|
||||||
return 'border-[#E0F2FE] hover:border-[#B9E6FE] bg-[#E0F2FE] text-[#026AA2]'
|
return `border-[#E0F2FE] ${!isDisplayOnly ? 'hover:border-[#B9E6FE]' : ''} bg-[#E0F2FE] text-[#026AA2]`
|
||||||
if (type === Plan.team)
|
if (type === Plan.team)
|
||||||
return 'border-[#E0EAFF] hover:border-[#C7D7FE] bg-[#E0EAFF] text-[#3538CD]'
|
return `border-[#E0EAFF] ${!isDisplayOnly ? 'hover:border-[#C7D7FE]' : ''} bg-[#E0EAFF] text-[#3538CD]`
|
||||||
return ''
|
return ''
|
||||||
})()
|
})()
|
||||||
|
|
||||||
@ -35,10 +37,22 @@ const HeaderBillingBtn: FC<Props> = ({
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
if (type === Plan.sandbox)
|
if (type === Plan.sandbox)
|
||||||
return <UpgradeBtn onClick={onClick} isShort />
|
return <UpgradeBtn onClick={isDisplayOnly ? undefined : onClick} isShort />
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (!isDisplayOnly && onClick)
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={onClick} className={cn(classNames, 'flex items-center h-[22px] px-2 rounded-md border text-xs font-semibold uppercase cursor-pointer')}>
|
<div
|
||||||
|
onClick={handleClick}
|
||||||
|
className={cn(
|
||||||
|
classNames,
|
||||||
|
'flex items-center h-[22px] px-2 rounded-md border text-xs font-semibold uppercase',
|
||||||
|
isDisplayOnly ? 'cursor-default' : 'cursor-pointer',
|
||||||
|
)}
|
||||||
|
>
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,7 @@ import VectorSpaceFull from '@/app/components/billing/vector-space-full'
|
|||||||
type IStepOneProps = {
|
type IStepOneProps = {
|
||||||
datasetId?: string
|
datasetId?: string
|
||||||
dataSourceType?: DataSourceType
|
dataSourceType?: DataSourceType
|
||||||
dataSourceTypeDisable: Boolean
|
dataSourceTypeDisable: boolean
|
||||||
hasConnection: boolean
|
hasConnection: boolean
|
||||||
onSetting: () => void
|
onSetting: () => void
|
||||||
files: FileItem[]
|
files: FileItem[]
|
||||||
|
@ -28,7 +28,7 @@ import Loading from '@/app/components/base/loading'
|
|||||||
import FloatRightContainer from '@/app/components/base/float-right-container'
|
import FloatRightContainer from '@/app/components/base/float-right-container'
|
||||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||||
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||||
import { type RetrievalConfig } from '@/types/app'
|
import type { RetrievalConfig } from '@/types/app'
|
||||||
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import { formatNumber } from '@/utils/format'
|
import { formatNumber } from '@/utils/format'
|
||||||
@ -202,7 +202,7 @@ const StepTwo = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fetchFileIndexingEstimate = async (docForm = DocForm.TEXT, language?: string) => {
|
const fetchFileIndexingEstimate = async (docForm = DocForm.TEXT, language?: string) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams(docForm, language)!)
|
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams(docForm, language)!)
|
||||||
if (segmentationType === SegmentType.CUSTOM)
|
if (segmentationType === SegmentType.CUSTOM)
|
||||||
setCustomFileIndexingEstimate(res)
|
setCustomFileIndexingEstimate(res)
|
||||||
@ -344,7 +344,7 @@ const StepTwo = ({
|
|||||||
doc_form: docForm,
|
doc_form: docForm,
|
||||||
doc_language: docLanguage,
|
doc_language: docLanguage,
|
||||||
process_rule: getProcessRule(),
|
process_rule: getProcessRule(),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
retrieval_model: retrievalConfig, // Readonly. If want to changed, just go to settings page.
|
retrieval_model: retrievalConfig, // Readonly. If want to changed, just go to settings page.
|
||||||
embedding_model: embeddingModel.model, // Readonly
|
embedding_model: embeddingModel.model, // Readonly
|
||||||
embedding_model_provider: embeddingModel.provider, // Readonly
|
embedding_model_provider: embeddingModel.provider, // Readonly
|
||||||
@ -357,7 +357,7 @@ const StepTwo = ({
|
|||||||
rerankDefaultModel,
|
rerankDefaultModel,
|
||||||
isRerankDefaultModelValid: !!isRerankDefaultModelValid,
|
isRerankDefaultModelValid: !!isRerankDefaultModelValid,
|
||||||
rerankModelList,
|
rerankModelList,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
retrievalConfig,
|
retrievalConfig,
|
||||||
indexMethod: indexMethod as string,
|
indexMethod: indexMethod as string,
|
||||||
})
|
})
|
||||||
@ -367,7 +367,7 @@ const StepTwo = ({
|
|||||||
}
|
}
|
||||||
const postRetrievalConfig = ensureRerankModelSelected({
|
const postRetrievalConfig = ensureRerankModelSelected({
|
||||||
rerankDefaultModel: rerankDefaultModel!,
|
rerankDefaultModel: rerankDefaultModel!,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
retrievalConfig,
|
retrievalConfig,
|
||||||
indexMethod: indexMethod as string,
|
indexMethod: indexMethod as string,
|
||||||
})
|
})
|
||||||
|
@ -87,4 +87,6 @@ const Form: FC<FormProps> = React.memo(({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Form.displayName = 'Form'
|
||||||
|
|
||||||
export default Form
|
export default Form
|
||||||
|
@ -18,7 +18,7 @@ import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/d
|
|||||||
import { updateDatasetSetting } from '@/service/datasets'
|
import { updateDatasetSetting } from '@/service/datasets'
|
||||||
import type { DataSetListResponse } from '@/models/datasets'
|
import type { DataSetListResponse } from '@/models/datasets'
|
||||||
import DatasetDetailContext from '@/context/dataset-detail'
|
import DatasetDetailContext from '@/context/dataset-detail'
|
||||||
import { type RetrievalConfig } from '@/types/app'
|
import type { RetrievalConfig } from '@/types/app'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||||
|
@ -9,7 +9,6 @@ import { Menu, Transition } from '@headlessui/react'
|
|||||||
import Indicator from '../indicator'
|
import Indicator from '../indicator'
|
||||||
import AccountAbout from '../account-about'
|
import AccountAbout from '../account-about'
|
||||||
import { mailToSupport } from '../utils/util'
|
import { mailToSupport } from '../utils/util'
|
||||||
import WorkplaceSelector from './workplace-selector'
|
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
import I18n from '@/context/i18n'
|
import I18n from '@/context/i18n'
|
||||||
import Avatar from '@/app/components/base/avatar'
|
import Avatar from '@/app/components/base/avatar'
|
||||||
@ -101,10 +100,6 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<div className='px-1 py-1'>
|
|
||||||
<div className='mt-2 px-3 text-xs font-medium text-gray-500'>{t('common.userProfile.workspace')}</div>
|
|
||||||
<WorkplaceSelector />
|
|
||||||
</div>
|
|
||||||
<div className="px-1 py-1">
|
<div className="px-1 py-1">
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<Link
|
<Link
|
||||||
|
@ -2,33 +2,20 @@ import { Fragment } from 'react'
|
|||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Menu, Transition } from '@headlessui/react'
|
import { Menu, Transition } from '@headlessui/react'
|
||||||
import s from './index.module.css'
|
import { RiArrowDownSLine } from '@remixicon/react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { switchWorkspace } from '@/service/common'
|
import { switchWorkspace } from '@/service/common'
|
||||||
import { useWorkspacesContext } from '@/context/workspace-context'
|
import { useWorkspacesContext } from '@/context/workspace-context'
|
||||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
import HeaderBillingBtn from '@/app/components/billing/header-billing-btn'
|
||||||
import { Check } from '@/app/components/base/icons/src/vender/line/general'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
|
|
||||||
const itemClassName = `
|
|
||||||
flex items-center px-3 py-2 h-10 cursor-pointer
|
|
||||||
`
|
|
||||||
const itemIconClassName = `
|
|
||||||
shrink-0 mr-2 flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600
|
|
||||||
`
|
|
||||||
const itemNameClassName = `
|
|
||||||
grow mr-2 text-sm text-gray-700 text-left
|
|
||||||
`
|
|
||||||
const itemCheckClassName = `
|
|
||||||
shrink-0 w-4 h-4 text-primary-600
|
|
||||||
`
|
|
||||||
|
|
||||||
const WorkplaceSelector = () => {
|
const WorkplaceSelector = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
const { workspaces } = useWorkspacesContext()
|
const { workspaces } = useWorkspacesContext()
|
||||||
|
const { enableBilling } = useProviderContext()
|
||||||
const currentWorkspace = workspaces.find(v => v.current)
|
const currentWorkspace = workspaces.find(v => v.current)
|
||||||
|
|
||||||
const handleSwitchWorkspace = async (tenant_id: string) => {
|
const handleSwitchWorkspace = async (tenant_id: string) => {
|
||||||
try {
|
try {
|
||||||
if (currentWorkspace?.id === tenant_id)
|
if (currentWorkspace?.id === tenant_id)
|
||||||
@ -49,13 +36,13 @@ const WorkplaceSelector = () => {
|
|||||||
<>
|
<>
|
||||||
<Menu.Button className={cn(
|
<Menu.Button className={cn(
|
||||||
`
|
`
|
||||||
${itemClassName} w-full
|
flex items-center p-0.5 gap-1.5 w-full
|
||||||
group hover:bg-gray-50 cursor-pointer ${open && 'bg-gray-50'} rounded-lg
|
group hover:bg-state-base-hover cursor-pointer ${open && 'bg-state-base-hover'} rounded-[10px]
|
||||||
`,
|
`,
|
||||||
)}>
|
)}>
|
||||||
<div className={itemIconClassName}>{currentWorkspace?.name[0].toLocaleUpperCase()}</div>
|
<div className='flex items-center justify-center w-7 h-7 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600'>{currentWorkspace?.name[0].toLocaleUpperCase()}</div>
|
||||||
<div className={`${itemNameClassName} truncate`}>{currentWorkspace?.name}</div>
|
<div className={'truncate max-w-[80px] text-text-secondary system-sm-medium'}>{currentWorkspace?.name}</div>
|
||||||
<ChevronRight className='shrink-0 w-[14px] h-[14px] text-gray-500' />
|
<RiArrowDownSLine className='w-4 h-4 text-text-secondary' />
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
@ -69,19 +56,24 @@ const WorkplaceSelector = () => {
|
|||||||
<Menu.Items
|
<Menu.Items
|
||||||
className={cn(
|
className={cn(
|
||||||
`
|
`
|
||||||
absolute top-[1px] min-w-[200px] max-h-[70vh] overflow-y-scroll z-10 bg-white border-[0.5px] border-gray-200
|
flex w-[280px] flex-col items-start absolute left-[-15px] mt-1 rounded-xl shadows-shadow-lg
|
||||||
divide-y divide-gray-100 origin-top-right rounded-xl
|
|
||||||
`,
|
`,
|
||||||
s.popup,
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="px-1 py-1">
|
<div className="flex flex-col p-1 pb-2 items-start self-stretch w-full rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadows-shadow-lg ">
|
||||||
|
<div className='flex px-3 pt-1 pb-0.5 items-start self-stretch'>
|
||||||
|
<span className='flex-1 text-text-tertiary system-xs-medium-uppercase'>{t('common.userProfile.workspace')}</span>
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
workspaces.map(workspace => (
|
workspaces.map(workspace => (
|
||||||
<div className={itemClassName} key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
|
<div className='flex py-1 pl-3 pr-2 items-center gap-2 self-stretch hover:bg-state-base-hover rounded-lg' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
|
||||||
<div className={itemIconClassName}>{workspace.name[0].toLocaleUpperCase()}</div>
|
<div className='flex items-center justify-center w-7 h-7 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600'>{workspace.name[0].toLocaleUpperCase()}</div>
|
||||||
<div className={itemNameClassName}>{workspace.name}</div>
|
<div className='line-clamp-1 flex-grow overflow-hidden text-text-secondary text-ellipsis system-md-regular'>{workspace.name}</div>
|
||||||
{workspace.current && <Check className={itemCheckClassName} />}
|
{enableBilling && (
|
||||||
|
<div className='select-none'>
|
||||||
|
<HeaderBillingBtn isDisplayOnly={true} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
.modal {
|
|
||||||
max-width: 1024px !important;
|
|
||||||
border-radius: 12px !important;
|
|
||||||
margin-top: 60px;
|
|
||||||
margin-bottom: 60px;
|
|
||||||
padding: 0 !important;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
@ -2,9 +2,8 @@
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
RiBox3Fill,
|
RiBrain2Fill,
|
||||||
RiBox3Line,
|
RiBrain2Line,
|
||||||
RiCloseLine,
|
|
||||||
RiColorFilterFill,
|
RiColorFilterFill,
|
||||||
RiColorFilterLine,
|
RiColorFilterLine,
|
||||||
RiDatabase2Fill,
|
RiDatabase2Fill,
|
||||||
@ -22,21 +21,17 @@ import LanguagePage from './language-page'
|
|||||||
import ApiBasedExtensionPage from './api-based-extension-page'
|
import ApiBasedExtensionPage from './api-based-extension-page'
|
||||||
import DataSourcePage from './data-source-page'
|
import DataSourcePage from './data-source-page'
|
||||||
import ModelProviderPage from './model-provider-page'
|
import ModelProviderPage from './model-provider-page'
|
||||||
import s from './index.module.css'
|
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import BillingPage from '@/app/components/billing/billing-page'
|
import BillingPage from '@/app/components/billing/billing-page'
|
||||||
import CustomPage from '@/app/components/custom/custom-page'
|
import CustomPage from '@/app/components/custom/custom-page'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import MenuDialog from '@/app/components/header/account-setting/menu-dialog'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
|
||||||
const iconClassName = `
|
const iconClassName = `
|
||||||
w-4 h-4 ml-3 mr-2
|
w-5 h-5 mr-2
|
||||||
`
|
|
||||||
|
|
||||||
const scrolledClassName = `
|
|
||||||
border-b shadow-xs bg-white/[.98]
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type IAccountSettingProps = {
|
type IAccountSettingProps = {
|
||||||
@ -68,8 +63,8 @@ export default function AccountSetting({
|
|||||||
{
|
{
|
||||||
key: 'provider',
|
key: 'provider',
|
||||||
name: t('common.settings.provider'),
|
name: t('common.settings.provider'),
|
||||||
icon: <RiBox3Line className={iconClassName} />,
|
icon: <RiBrain2Line className={iconClassName} />,
|
||||||
activeIcon: <RiBox3Fill className={iconClassName} />,
|
activeIcon: <RiBrain2Fill className={iconClassName} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'members',
|
key: 'members',
|
||||||
@ -117,7 +112,7 @@ export default function AccountSetting({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'account-group',
|
key: 'account-group',
|
||||||
name: t('common.settings.accountGroup'),
|
name: t('common.settings.generalGroup'),
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'language',
|
key: 'language',
|
||||||
@ -144,32 +139,31 @@ export default function AccountSetting({
|
|||||||
|
|
||||||
const activeItem = [...menuItems[0].items, ...menuItems[1].items].find(item => item.key === activeMenu)
|
const activeItem = [...menuItems[0].items, ...menuItems[1].items].find(item => item.key === activeMenu)
|
||||||
|
|
||||||
|
const [searchValue, setSearchValue] = useState<string>('')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<MenuDialog
|
||||||
isShow
|
show
|
||||||
onClose={() => { }}
|
onClose={onCancel}
|
||||||
className={s.modal}
|
|
||||||
wrapperClassName='pt-[60px]'
|
|
||||||
>
|
>
|
||||||
<div className='flex'>
|
<div className='mx-auto max-w-[1048px] h-[100vh] flex'>
|
||||||
<div className='w-[44px] sm:w-[200px] px-[1px] py-4 sm:p-4 border border-gray-100 shrink-0 sm:shrink-1 flex flex-col items-center sm:items-start'>
|
<div className='w-[44px] sm:w-[224px] pl-4 pr-6 border-r border-divider-burn flex flex-col'>
|
||||||
<div className='mb-8 ml-0 sm:ml-2 text-sm sm:text-base font-medium leading-6 text-gray-900'>{t('common.userProfile.settings')}</div>
|
<div className='mt-6 mb-8 px-3 py-2 text-text-primary title-2xl-semi-bold'>{t('common.userProfile.settings')}</div>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
{
|
{
|
||||||
menuItems.map(menuItem => (
|
menuItems.map(menuItem => (
|
||||||
<div key={menuItem.key} className='mb-4'>
|
<div key={menuItem.key} className='mb-2'>
|
||||||
{!isCurrentWorkspaceDatasetOperator && (
|
{!isCurrentWorkspaceDatasetOperator && (
|
||||||
<div className='px-2 mb-[6px] text-[10px] sm:text-xs font-medium text-gray-500'>{menuItem.name}</div>
|
<div className='py-2 pl-3 pb-1 mb-0.5 text-text-tertiary system-xs-medium-uppercase'>{menuItem.name}</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
menuItem.items.map(item => (
|
menuItem.items.map(item => (
|
||||||
<div
|
<div
|
||||||
key={item.key}
|
key={item.key}
|
||||||
className={`
|
className={cn(
|
||||||
flex items-center h-[37px] mb-[2px] text-sm cursor-pointer rounded-lg
|
'flex items-center mb-0.5 p-1 pl-3 h-[37px] text-sm cursor-pointer rounded-lg',
|
||||||
${activeMenu === item.key ? 'font-semibold text-primary-600 bg-primary-50' : 'font-light text-gray-700'}
|
activeMenu === item.key ? 'bg-state-base-active text-components-menu-item-text-active system-sm-semibold' : 'text-components-menu-item-text system-sm-medium')}
|
||||||
`}
|
|
||||||
title={item.name}
|
title={item.name}
|
||||||
onClick={() => setActiveMenu(item.key)}
|
onClick={() => setActiveMenu(item.key)}
|
||||||
>
|
>
|
||||||
@ -184,19 +178,25 @@ export default function AccountSetting({
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref={scrollRef} className='relative w-[824px] h-[720px] pb-4 overflow-y-auto'>
|
<div ref={scrollRef} className='relative w-[824px] pb-4 bg-components-panel-bg overflow-y-auto'>
|
||||||
<div className={cn('sticky top-0 px-6 py-4 flex items-center h-14 mb-4 bg-white text-base font-medium text-gray-900 z-20', scrolled && scrolledClassName)}>
|
<div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b')}>
|
||||||
<div className='shrink-0'>{activeItem?.name}</div>
|
<div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div>
|
||||||
{
|
{
|
||||||
activeItem?.description && (
|
activeItem?.description && (
|
||||||
<div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
|
<div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{activeItem?.key === 'provider' && (
|
||||||
<div className='grow flex justify-end'>
|
<div className='grow flex justify-end'>
|
||||||
<div className='flex items-center justify-center -mr-4 w-6 h-6 cursor-pointer' onClick={onCancel}>
|
<Input
|
||||||
<RiCloseLine className='w-4 h-4 text-gray-400' />
|
showLeftIcon
|
||||||
</div>
|
wrapperClassName='!w-[200px]'
|
||||||
|
className='!h-8 !text-[13px]'
|
||||||
|
onChange={e => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='px-4 sm:px-8 pt-2'>
|
<div className='px-4 sm:px-8 pt-2'>
|
||||||
{activeMenu === 'members' && <MembersPage />}
|
{activeMenu === 'members' && <MembersPage />}
|
||||||
@ -209,6 +209,6 @@ export default function AccountSetting({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</MenuDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
72
web/app/components/header/account-setting/menu-dialog.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Fragment, useCallback, useEffect } from 'react'
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
|
import { Dialog, Transition } from '@headlessui/react'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type DialogProps = {
|
||||||
|
className?: string
|
||||||
|
children: ReactNode
|
||||||
|
show: boolean
|
||||||
|
onClose?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuDialog = ({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
show,
|
||||||
|
onClose,
|
||||||
|
}: DialogProps) => {
|
||||||
|
const close = useCallback(() => onClose?.(), [onClose])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape')
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleKeyDown)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleKeyDown)
|
||||||
|
}
|
||||||
|
}, [close])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition appear show={show} as={Fragment}>
|
||||||
|
<Dialog as="div" className="relative z-40" onClose={() => {}}>
|
||||||
|
<div className="fixed inset-0">
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-full">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 scale-100"
|
||||||
|
leaveTo="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className={cn('grow relative w-full h-full p-0 overflow-hidden text-left align-middle transition-all transform bg-background-sidenav-bg backdrop-blur-md', className)}>
|
||||||
|
<div className='absolute right-0 top-0 h-full w-1/2 bg-components-panel-bg'/>
|
||||||
|
<div className='absolute top-6 right-6 flex flex-col items-center'>
|
||||||
|
<Button
|
||||||
|
variant='tertiary'
|
||||||
|
size='large'
|
||||||
|
className='px-2'
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
<RiCloseLine className='w-5 h-5' />
|
||||||
|
</Button>
|
||||||
|
<div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>ESC</div>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuDialog
|
@ -1,8 +1,16 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import {
|
||||||
|
RiAlertFill,
|
||||||
|
RiArrowDownSLine,
|
||||||
|
RiArrowRightUpLine,
|
||||||
|
RiBrainLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import { useContext } from 'use-context-selector'
|
||||||
import SystemModelSelector from './system-model-selector'
|
import SystemModelSelector from './system-model-selector'
|
||||||
import ProviderAddedCard, { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card'
|
import ProviderAddedCard, { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card'
|
||||||
import ProviderCard from './provider-card'
|
// import ProviderCard from './provider-card'
|
||||||
import type {
|
import type {
|
||||||
CustomConfigurationModelFixedFields,
|
CustomConfigurationModelFixedFields,
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
@ -17,10 +25,15 @@ import {
|
|||||||
useUpdateModelList,
|
useUpdateModelList,
|
||||||
useUpdateModelProviders,
|
useUpdateModelProviders,
|
||||||
} from './hooks'
|
} from './hooks'
|
||||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
import Divider from '@/app/components/base/divider'
|
||||||
|
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||||
|
import I18n from '@/context/i18n'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { useModalContextSelector } from '@/context/modal-context'
|
import { useModalContextSelector } from '@/context/modal-context'
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
import { extensionDallE, modelGPT4, toolNotion } from '@/app/components/plugins/card/card-mock'
|
||||||
|
|
||||||
const ModelProviderPage = () => {
|
const ModelProviderPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -57,25 +70,25 @@ const ModelProviderPage = () => {
|
|||||||
|
|
||||||
const handleOpenModal = (
|
const handleOpenModal = (
|
||||||
provider: ModelProvider,
|
provider: ModelProvider,
|
||||||
configurateMethod: ConfigurationMethodEnum,
|
configurationMethod: ConfigurationMethodEnum,
|
||||||
CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields,
|
CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields,
|
||||||
) => {
|
) => {
|
||||||
setShowModelModal({
|
setShowModelModal({
|
||||||
payload: {
|
payload: {
|
||||||
currentProvider: provider,
|
currentProvider: provider,
|
||||||
currentConfigurationMethod: configurateMethod,
|
currentConfigurationMethod: configurationMethod,
|
||||||
currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields,
|
currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields,
|
||||||
},
|
},
|
||||||
onSaveCallback: () => {
|
onSaveCallback: () => {
|
||||||
updateModelProviders()
|
updateModelProviders()
|
||||||
|
|
||||||
if (configurateMethod === ConfigurationMethodEnum.predefinedModel) {
|
if (configurationMethod === ConfigurationMethodEnum.predefinedModel) {
|
||||||
provider.supported_model_types.forEach((type) => {
|
provider.supported_model_types.forEach((type) => {
|
||||||
updateModelList(type)
|
updateModelList(type)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configurateMethod === ConfigurationMethodEnum.customizableModel && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) {
|
if (configurationMethod === ConfigurationMethodEnum.customizableModel && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) {
|
||||||
eventEmitter?.emit({
|
eventEmitter?.emit({
|
||||||
type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST,
|
type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST,
|
||||||
payload: provider.provider,
|
payload: provider.provider,
|
||||||
@ -88,20 +101,29 @@ const ModelProviderPage = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [collapse, setCollapse] = useState(false)
|
||||||
|
const { locale } = useContext(I18n)
|
||||||
|
|
||||||
|
// TODO #Plugin list API#
|
||||||
|
const pluginList = [toolNotion, extensionDallE, modelGPT4]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative pt-1 -mt-2'>
|
<div className='relative pt-1 -mt-2'>
|
||||||
<div className={`flex items-center justify-between mb-2 h-8 ${defaultModelNotConfigured && 'px-3 bg-[#FFFAEB] rounded-lg border border-[#FEF0C7]'}`}>
|
<div className={cn('flex items-center mb-2')}>
|
||||||
{
|
<div className='grow text-text-primary system-md-semibold'>{t('common.modelProvider.models')}</div>
|
||||||
defaultModelNotConfigured
|
<div className={cn(
|
||||||
? (
|
'shrink-0 relative flex items-center justify-end gap-2 p-0.5 rounded-lg border border-transparent',
|
||||||
<div className='flex items-center text-xs font-medium text-gray-700'>
|
defaultModelNotConfigured && 'pl-2 bg-components-panel-bg-blur border-components-panel-border shadow-xs',
|
||||||
<AlertTriangle className='mr-1 w-3 h-3 text-[#F79009]' />
|
)}>
|
||||||
|
{defaultModelNotConfigured && <div className='absolute top-0 bottom-0 right-0 left-0 opacity-40' style={{ background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)' }} />}
|
||||||
|
{defaultModelNotConfigured && (
|
||||||
|
<div className='flex items-center gap-1 text-text-primary system-xs-medium'>
|
||||||
|
<RiAlertFill className='w-4 h-4 text-text-warning-secondary' />
|
||||||
{t('common.modelProvider.notConfigured')}
|
{t('common.modelProvider.notConfigured')}
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
: <div className='text-sm font-medium text-gray-800'>{t('common.modelProvider.models')}</div>
|
|
||||||
}
|
|
||||||
<SystemModelSelector
|
<SystemModelSelector
|
||||||
|
notConfigured={defaultModelNotConfigured}
|
||||||
textGenerationDefaultModel={textGenerationDefaultModel}
|
textGenerationDefaultModel={textGenerationDefaultModel}
|
||||||
embeddingsDefaultModel={embeddingsDefaultModel}
|
embeddingsDefaultModel={embeddingsDefaultModel}
|
||||||
rerankDefaultModel={rerankDefaultModel}
|
rerankDefaultModel={rerankDefaultModel}
|
||||||
@ -109,42 +131,65 @@ const ModelProviderPage = () => {
|
|||||||
ttsDefaultModel={ttsDefaultModel}
|
ttsDefaultModel={ttsDefaultModel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{
|
</div>
|
||||||
!!configuredProviders?.length && (
|
{!configuredProviders?.length && (
|
||||||
<div className='pb-3'>
|
<div className='mb-2 p-4 rounded-[10px]' style={{ background: 'linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%)' }}>
|
||||||
{
|
<div className='w-10 h-10 flex items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur'>
|
||||||
configuredProviders?.map(provider => (
|
<RiBrainLine className='w-5 h-5 text-text-primary' />
|
||||||
|
</div>
|
||||||
|
<div className='mt-2 text-text-secondary system-sm-medium'>{t('common.modelProvider.emptyProviderTitle')}</div>
|
||||||
|
<div className='mt-1 text-text-tertiary system-xs-regular'>{t('common.modelProvider.emptyProviderTip')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!!configuredProviders?.length && (
|
||||||
|
<div className='relative'>
|
||||||
|
{configuredProviders?.map(provider => (
|
||||||
<ProviderAddedCard
|
<ProviderAddedCard
|
||||||
key={provider.provider}
|
key={provider.provider}
|
||||||
provider={provider}
|
provider={provider}
|
||||||
onOpenModal={(configurateMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurateMethod, currentCustomConfigurationModelFixedFields)}
|
onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
|
||||||
/>
|
/>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
{false && !!notConfiguredProviders?.length && (
|
||||||
{
|
|
||||||
!!notConfiguredProviders?.length && (
|
|
||||||
<>
|
<>
|
||||||
<div className='flex items-center mb-2 text-xs font-semibold text-gray-500'>
|
<div className='flex items-center mb-2 pt-2 text-text-primary system-md-semibold'>{t('common.modelProvider.configureRequired')}</div>
|
||||||
+ {t('common.modelProvider.addMoreModelProvider')}
|
<div className='relative'>
|
||||||
<span className='grow ml-3 h-[1px] bg-gradient-to-r from-[#f3f4f6]' />
|
{notConfiguredProviders?.map(provider => (
|
||||||
</div>
|
<ProviderAddedCard
|
||||||
<div className='grid grid-cols-3 gap-2'>
|
notConfigured
|
||||||
{
|
|
||||||
notConfiguredProviders?.map(provider => (
|
|
||||||
<ProviderCard
|
|
||||||
key={provider.provider}
|
key={provider.provider}
|
||||||
provider={provider}
|
provider={provider}
|
||||||
onOpenModal={(configurateMethod: ConfigurationMethodEnum) => handleOpenModal(provider, configurateMethod)}
|
onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
|
||||||
/>
|
/>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)}
|
||||||
}
|
<div className='mb-2'>
|
||||||
|
<Divider className='!mt-4 h-px' />
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='flex items-center gap-1 text-text-primary system-md-semibold cursor-pointer' onClick={() => setCollapse(!collapse)}>
|
||||||
|
<RiArrowDownSLine className={cn('w-4 h-4', collapse && '-rotate-90')} />
|
||||||
|
{t('common.modelProvider.installProvider')}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center mb-2 pt-2'>
|
||||||
|
<span className='pr-1 text-text-tertiary system-sm-regular'>{t('common.modelProvider.discoverMore')}</span>
|
||||||
|
<Link target="_blank" href="/plugins" className='inline-flex items-center system-sm-medium text-text-accent'>
|
||||||
|
Dify Marketplace
|
||||||
|
<RiArrowRightUpLine className='w-4 h-4' />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!collapse && (
|
||||||
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
|
{pluginList.map((plugin, index) => (
|
||||||
|
<ProviderCard key={index} installed={false} payload={plugin as any} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { RiEqualizer2Line } from '@remixicon/react'
|
||||||
import type { ModelProvider } from '../declarations'
|
import type { ModelProvider } from '../declarations'
|
||||||
import {
|
import {
|
||||||
ConfigurationMethodEnum,
|
ConfigurationMethodEnum,
|
||||||
@ -14,7 +15,6 @@ import PrioritySelector from './priority-selector'
|
|||||||
import PriorityUseTip from './priority-use-tip'
|
import PriorityUseTip from './priority-use-tip'
|
||||||
import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './index'
|
import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './index'
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { changeModelProviderPriority } from '@/service/common'
|
import { changeModelProviderPriority } from '@/service/common'
|
||||||
import { useToastContext } from '@/app/components/base/toast'
|
import { useToastContext } from '@/app/components/base/toast'
|
||||||
@ -69,7 +69,7 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
|
|||||||
<div className='shrink-0 relative ml-1 p-1 w-[112px] rounded-lg bg-white/[0.3] border-[0.5px] border-black/5'>
|
<div className='shrink-0 relative ml-1 p-1 w-[112px] rounded-lg bg-white/[0.3] border-[0.5px] border-black/5'>
|
||||||
<div className='flex items-center justify-between mb-1 pt-1 pl-2 pr-[7px] h-5 text-xs font-medium text-gray-500'>
|
<div className='flex items-center justify-between mb-1 pt-1 pl-2 pr-[7px] h-5 text-xs font-medium text-gray-500'>
|
||||||
API-KEY
|
API-KEY
|
||||||
<Indicator color={isCustomConfigured ? 'green' : 'gray'} />
|
<Indicator color={isCustomConfigured ? 'green' : 'red'} />
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-0.5'>
|
<div className='flex items-center gap-0.5'>
|
||||||
<Button
|
<Button
|
||||||
@ -77,7 +77,7 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
|
|||||||
size='small'
|
size='small'
|
||||||
onClick={onSetup}
|
onClick={onSetup}
|
||||||
>
|
>
|
||||||
<Settings01 className='mr-1 w-3 h-3' />
|
<RiEqualizer2Line className='mr-1 w-3.5 h-3.5' />
|
||||||
{t('common.operation.setup')}
|
{t('common.operation.setup')}
|
||||||
</Button>
|
</Button>
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,8 @@ import type { FC } from 'react'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
|
RiArrowRightSLine,
|
||||||
|
RiInformation2Fill,
|
||||||
RiLoader2Line,
|
RiLoader2Line,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import type {
|
import type {
|
||||||
@ -21,7 +23,6 @@ import CredentialPanel from './credential-panel'
|
|||||||
import QuotaPanel from './quota-panel'
|
import QuotaPanel from './quota-panel'
|
||||||
import ModelList from './model-list'
|
import ModelList from './model-list'
|
||||||
import AddModelButton from './add-model-button'
|
import AddModelButton from './add-model-button'
|
||||||
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
|
|
||||||
import { fetchModelProviderModelList } from '@/service/common'
|
import { fetchModelProviderModelList } from '@/service/common'
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import { IS_CE_EDITION } from '@/config'
|
import { IS_CE_EDITION } from '@/config'
|
||||||
@ -29,10 +30,12 @@ import { useAppContext } from '@/context/app-context'
|
|||||||
|
|
||||||
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
|
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
|
||||||
type ProviderAddedCardProps = {
|
type ProviderAddedCardProps = {
|
||||||
|
notConfigured?: boolean
|
||||||
provider: ModelProvider
|
provider: ModelProvider
|
||||||
onOpenModal: (configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void
|
onOpenModal: (configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void
|
||||||
}
|
}
|
||||||
const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||||
|
notConfigured,
|
||||||
provider,
|
provider,
|
||||||
onOpenModal,
|
onOpenModal,
|
||||||
}) => {
|
}) => {
|
||||||
@ -47,6 +50,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||||||
const hasModelList = fetched && !!modelList.length
|
const hasModelList = fetched && !!modelList.length
|
||||||
const { isCurrentWorkspaceManager } = useAppContext()
|
const { isCurrentWorkspaceManager } = useAppContext()
|
||||||
const showQuota = systemConfig.enabled && [...MODEL_PROVIDER_QUOTA_GET_PAID].includes(provider.provider) && !IS_CE_EDITION
|
const showQuota = systemConfig.enabled && [...MODEL_PROVIDER_QUOTA_GET_PAID].includes(provider.provider) && !IS_CE_EDITION
|
||||||
|
const showCredential = configurationMethods.includes(ConfigurationMethodEnum.predefinedModel) && isCurrentWorkspaceManager
|
||||||
|
|
||||||
const getModelList = async (providerName: string) => {
|
const getModelList = async (providerName: string) => {
|
||||||
if (loading)
|
if (loading)
|
||||||
@ -105,7 +109,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
configurationMethods.includes(ConfigurationMethodEnum.predefinedModel) && isCurrentWorkspaceManager && (
|
showCredential && (
|
||||||
<CredentialPanel
|
<CredentialPanel
|
||||||
onSetup={() => onOpenModal(ConfigurationMethodEnum.predefinedModel)}
|
onSetup={() => onOpenModal(ConfigurationMethodEnum.predefinedModel)}
|
||||||
provider={provider}
|
provider={provider}
|
||||||
@ -116,34 +120,45 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||||||
{
|
{
|
||||||
collapsed && (
|
collapsed && (
|
||||||
<div className='group flex items-center justify-between pl-2 py-1.5 pr-[11px] border-t border-t-black/5 bg-white/30 text-xs font-medium text-gray-500'>
|
<div className='group flex items-center justify-between pl-2 py-1.5 pr-[11px] border-t border-t-black/5 bg-white/30 text-xs font-medium text-gray-500'>
|
||||||
<div className='group-hover:hidden pl-1 pr-1.5 h-6 leading-6'>
|
{(showQuota || !notConfigured) && (
|
||||||
|
<>
|
||||||
|
<div className='group-hover:hidden flex items-center pl-1 pr-1.5 h-6 leading-6'>
|
||||||
{
|
{
|
||||||
hasModelList
|
hasModelList
|
||||||
? t('common.modelProvider.modelsNum', { num: modelList.length })
|
? t('common.modelProvider.modelsNum', { num: modelList.length })
|
||||||
: t('common.modelProvider.showModels')
|
: t('common.modelProvider.showModels')
|
||||||
}
|
}
|
||||||
|
{!loading && <RiArrowRightSLine className='w-4 h-4' />}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='hidden group-hover:flex items-center pl-1 pr-1.5 h-6 rounded-lg hover:bg-white cursor-pointer'
|
className='hidden group-hover:flex items-center pl-1 pr-1.5 h-6 rounded-lg hover:bg-white cursor-pointer'
|
||||||
onClick={handleOpenModelList}
|
onClick={handleOpenModelList}
|
||||||
>
|
>
|
||||||
<ChevronDownDouble className='mr-0.5 w-3 h-3' />
|
|
||||||
{
|
{
|
||||||
hasModelList
|
hasModelList
|
||||||
? t('common.modelProvider.showModelsNum', { num: modelList.length })
|
? t('common.modelProvider.showModelsNum', { num: modelList.length })
|
||||||
: t('common.modelProvider.showModels')
|
: t('common.modelProvider.showModels')
|
||||||
}
|
}
|
||||||
|
{!loading && <RiArrowRightSLine className='w-4 h-4' />}
|
||||||
{
|
{
|
||||||
loading && (
|
loading && (
|
||||||
<RiLoader2Line className='ml-0.5 animate-spin w-3 h-3' />
|
<RiLoader2Line className='ml-0.5 animate-spin w-3 h-3' />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!showQuota && notConfigured && (
|
||||||
|
<div className='flex items-center pl-1 pr-1.5 h-6'>
|
||||||
|
<RiInformation2Fill className='mr-1 w-4 h-4 text-text-accent' />
|
||||||
|
<span>{t('common.modelProvider.configureTip')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{
|
{
|
||||||
configurationMethods.includes(ConfigurationMethodEnum.customizableModel) && isCurrentWorkspaceManager && (
|
configurationMethods.includes(ConfigurationMethodEnum.customizableModel) && isCurrentWorkspaceManager && (
|
||||||
<AddModelButton
|
<AddModelButton
|
||||||
onClick={() => onOpenModal(ConfigurationMethodEnum.customizableModel)}
|
onClick={() => onOpenModal(ConfigurationMethodEnum.customizableModel)}
|
||||||
className='hidden group-hover:flex group-hover:text-primary-600'
|
className='flex'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
RiArrowRightSLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
import type {
|
import type {
|
||||||
CustomConfigurationModelFixedFields,
|
CustomConfigurationModelFixedFields,
|
||||||
ModelItem,
|
ModelItem,
|
||||||
@ -12,7 +15,6 @@ import {
|
|||||||
// import Tab from './tab'
|
// import Tab from './tab'
|
||||||
import AddModelButton from './add-model-button'
|
import AddModelButton from './add-model-button'
|
||||||
import ModelListItem from './model-list-item'
|
import ModelListItem from './model-list-item'
|
||||||
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
|
|
||||||
import { useModalContextSelector } from '@/context/modal-context'
|
import { useModalContextSelector } from '@/context/modal-context'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
|
||||||
@ -51,15 +53,16 @@ const ModelList: FC<ModelListProps> = ({
|
|||||||
<div className='py-1 bg-white rounded-lg'>
|
<div className='py-1 bg-white rounded-lg'>
|
||||||
<div className='flex items-center pl-1 pr-[3px]'>
|
<div className='flex items-center pl-1 pr-[3px]'>
|
||||||
<span className='group shrink-0 flex items-center mr-2'>
|
<span className='group shrink-0 flex items-center mr-2'>
|
||||||
<span className='group-hover:hidden pl-1 pr-1.5 h-6 leading-6 text-xs font-medium text-gray-500'>
|
<span className='group-hover:hidden inline-flex pl-1 pr-1.5 h-6 leading-6 text-xs font-medium text-gray-500'>
|
||||||
{t('common.modelProvider.modelsNum', { num: models.length })}
|
{t('common.modelProvider.modelsNum', { num: models.length })}
|
||||||
|
<RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className='hidden group-hover:inline-flex items-center pl-1 pr-1.5 h-6 text-xs font-medium text-gray-500 bg-gray-50 cursor-pointer rounded-lg'
|
className='hidden group-hover:inline-flex items-center pl-1 pr-1.5 h-6 text-xs font-medium text-gray-500 bg-gray-50 cursor-pointer rounded-lg'
|
||||||
onClick={() => onCollapse()}
|
onClick={() => onCollapse()}
|
||||||
>
|
>
|
||||||
<ChevronDownDouble className='mr-0.5 w-3 h-3 rotate-180' />
|
{t('common.modelProvider.modelsNum', { num: models.length })}
|
||||||
{t('common.modelProvider.collapse')}
|
<RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{/* {
|
{/* {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { RiEqualizer2Line } from '@remixicon/react'
|
||||||
import ModelSelector from '../model-selector'
|
import ModelSelector from '../model-selector'
|
||||||
import {
|
import {
|
||||||
useModelList,
|
useModelList,
|
||||||
@ -13,7 +14,6 @@ import type {
|
|||||||
} from '../declarations'
|
} from '../declarations'
|
||||||
import { ModelTypeEnum } from '../declarations'
|
import { ModelTypeEnum } from '../declarations'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
@ -31,6 +31,7 @@ type SystemModelSelectorProps = {
|
|||||||
rerankDefaultModel: DefaultModelResponse | undefined
|
rerankDefaultModel: DefaultModelResponse | undefined
|
||||||
speech2textDefaultModel: DefaultModelResponse | undefined
|
speech2textDefaultModel: DefaultModelResponse | undefined
|
||||||
ttsDefaultModel: DefaultModelResponse | undefined
|
ttsDefaultModel: DefaultModelResponse | undefined
|
||||||
|
notConfigured: boolean
|
||||||
}
|
}
|
||||||
const SystemModel: FC<SystemModelSelectorProps> = ({
|
const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||||
textGenerationDefaultModel,
|
textGenerationDefaultModel,
|
||||||
@ -38,6 +39,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
|||||||
rerankDefaultModel,
|
rerankDefaultModel,
|
||||||
speech2textDefaultModel,
|
speech2textDefaultModel,
|
||||||
ttsDefaultModel,
|
ttsDefaultModel,
|
||||||
|
notConfigured,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useToastContext()
|
const { notify } = useToastContext()
|
||||||
@ -128,14 +130,13 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||||
<div className={`
|
<Button
|
||||||
flex items-center px-2 h-6 text-xs text-gray-700 cursor-pointer bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs
|
variant={notConfigured ? 'primary' : 'secondary'}
|
||||||
hover:bg-gray-100 hover:shadow-none
|
size='small'
|
||||||
${open && 'bg-gray-100 shadow-none'}
|
>
|
||||||
`}>
|
<RiEqualizer2Line className='mr-1 w-3.5 h-3.5' />
|
||||||
<Settings01 className='mr-1 w-3 h-3 text-gray-500' />
|
|
||||||
{t('common.modelProvider.systemModelSettings')}
|
{t('common.modelProvider.systemModelSettings')}
|
||||||
</div>
|
</Button>
|
||||||
</PortalToFollowElemTrigger>
|
</PortalToFollowElemTrigger>
|
||||||
<PortalToFollowElemContent className='z-50'>
|
<PortalToFollowElemContent className='z-50'>
|
||||||
<div className='pt-4 w-[360px] rounded-xl border-[0.5px] border-black/5 bg-white shadow-xl'>
|
<div className='pt-4 w-[360px] rounded-xl border-[0.5px] border-black/5 bg-white shadow-xl'>
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
RiRobot2Line,
|
RiRobot2Line,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import Nav from '../nav'
|
import Nav from '../nav'
|
||||||
import { type NavItem } from '../nav/nav-selector'
|
import type { NavItem } from '../nav/nav-selector'
|
||||||
import { fetchAppList } from '@/service/apps'
|
import { fetchAppList } from '@/service/apps'
|
||||||
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
||||||
import CreateAppModal from '@/app/components/app/create-app-modal'
|
import CreateAppModal from '@/app/components/app/create-app-modal'
|
||||||
|
@ -9,12 +9,14 @@ import AccountDropdown from './account-dropdown'
|
|||||||
import AppNav from './app-nav'
|
import AppNav from './app-nav'
|
||||||
import DatasetNav from './dataset-nav'
|
import DatasetNav from './dataset-nav'
|
||||||
import EnvNav from './env-nav'
|
import EnvNav from './env-nav'
|
||||||
|
import PluginsNav from './plugins-nav'
|
||||||
import ExploreNav from './explore-nav'
|
import ExploreNav from './explore-nav'
|
||||||
import ToolsNav from './tools-nav'
|
import ToolsNav from './tools-nav'
|
||||||
import GithubStar from './github-star'
|
import GithubStar from './github-star'
|
||||||
import { WorkspaceProvider } from '@/context/workspace-context'
|
import { WorkspaceProvider } from '@/context/workspace-context'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import LogoSite from '@/app/components/base/logo/logo-site'
|
import LogoSite from '@/app/components/base/logo/logo-site'
|
||||||
|
import WorkplaceSelector from '@/app/components/header/account-dropdown/workplace-selector'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
@ -47,7 +49,7 @@ const Header = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [selectedSegment])
|
}, [selectedSegment])
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-1 items-center justify-between px-4'>
|
<div className='flex flex-1 items-center justify-between pr-3'>
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
{isMobile && <div
|
{isMobile && <div
|
||||||
className='flex items-center justify-center h-8 w-8 cursor-pointer'
|
className='flex items-center justify-center h-8 w-8 cursor-pointer'
|
||||||
@ -55,18 +57,36 @@ const Header = () => {
|
|||||||
>
|
>
|
||||||
<Bars3Icon className="h-4 w-4 text-gray-500" />
|
<Bars3Icon className="h-4 w-4 text-gray-500" />
|
||||||
</div>}
|
</div>}
|
||||||
{!isMobile && <>
|
{!isMobile
|
||||||
<Link href="/apps" className='flex items-center mr-4'>
|
&& <div className='flex w-64 p-2 pl-3 gap-1.5 items-center shrink-0 self-stretch'>
|
||||||
|
<Link href="/apps" className='flex w-8 h-8 items-center justify-center gap-2 shrink-0'>
|
||||||
<LogoSite className='object-contain' />
|
<LogoSite className='object-contain' />
|
||||||
</Link>
|
</Link>
|
||||||
<GithubStar />
|
<div className='font-light text-divider-deep'>/</div>
|
||||||
</>}
|
<div className='flex items-center gap-0.5'>
|
||||||
|
<WorkspaceProvider>
|
||||||
|
<WorkplaceSelector />
|
||||||
|
</WorkspaceProvider>
|
||||||
|
{enableBilling && (
|
||||||
|
<div className='select-none'>
|
||||||
|
<HeaderBillingBtn onClick={handlePlanClick} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<Link href="/apps" className='flex items-center mr-4'>
|
<Link href="/apps" className='flex items-center mr-4'>
|
||||||
<LogoSite />
|
<LogoSite />
|
||||||
</Link>
|
</Link>
|
||||||
|
<div className='font-light text-divider-deep'>/</div>
|
||||||
|
{enableBilling && (
|
||||||
|
<div className='select-none'>
|
||||||
|
<HeaderBillingBtn onClick={handlePlanClick} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<GithubStar />
|
<GithubStar />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -80,14 +100,10 @@ const Header = () => {
|
|||||||
)}
|
)}
|
||||||
<div className='flex items-center flex-shrink-0'>
|
<div className='flex items-center flex-shrink-0'>
|
||||||
<EnvNav />
|
<EnvNav />
|
||||||
{enableBilling && (
|
<div className='mr-3'>
|
||||||
<div className='mr-3 select-none'>
|
<PluginsNav />
|
||||||
<HeaderBillingBtn onClick={handlePlanClick} />
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<WorkspaceProvider>
|
|
||||||
<AccountDropdown isMobile={isMobile} />
|
<AccountDropdown isMobile={isMobile} />
|
||||||
</WorkspaceProvider>
|
|
||||||
</div>
|
</div>
|
||||||
{(isMobile && isShowNavMenu) && (
|
{(isMobile && isShowNavMenu) && (
|
||||||
<div className='w-full flex flex-col p-2 gap-y-1'>
|
<div className='w-full flex flex-col p-2 gap-y-1'>
|
||||||
|
@ -68,7 +68,7 @@ const Nav = ({
|
|||||||
{
|
{
|
||||||
curNav && isActivated && (
|
curNav && isActivated && (
|
||||||
<>
|
<>
|
||||||
<div className='font-light text-gray-300 '>/</div>
|
<div className='font-light text-divider-deep'>/</div>
|
||||||
<NavSelector
|
<NavSelector
|
||||||
isApp={isApp}
|
isApp={isApp}
|
||||||
curNav={curNav}
|
curNav={curNav}
|
||||||
|
30
web/app/components/header/plugins-nav/index.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import classNames from '@/utils/classnames'
|
||||||
|
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||||
|
type PluginsNavProps = {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const PluginsNav = ({
|
||||||
|
className,
|
||||||
|
}: PluginsNavProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href="/plugins" className={classNames(
|
||||||
|
className, 'group',
|
||||||
|
)}>
|
||||||
|
<div className='flex flex-row p-1.5 gap-0.5 items-center justify-center rounded-xl system-xs-medium-uppercase hover:bg-state-base-hover text-text-tertiary hover:text-text-secondary'>
|
||||||
|
<div className='flex w-4 h-4 justify-center items-center'>
|
||||||
|
<Group />
|
||||||
|
</div>
|
||||||
|
<span className='px-0.5'>{t('common.menus.plugins')}</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginsNav
|
62
web/app/components/plugins/base/key-value-item.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
|
||||||
|
import {
|
||||||
|
RiClipboardLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ClipboardCheck } from '../../base/icons/src/vender/line/files'
|
||||||
|
import Tooltip from '../../base/tooltip'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
|
type Props = {
|
||||||
|
label: string
|
||||||
|
labelWidthClassName?: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyValueItem: FC<Props> = ({
|
||||||
|
label,
|
||||||
|
labelWidthClassName = 'w-10',
|
||||||
|
value,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [isCopied, setIsCopied] = useState(false)
|
||||||
|
const handleCopy = useCallback(() => {
|
||||||
|
copy(value)
|
||||||
|
setIsCopied(true)
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isCopied) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setIsCopied(false)
|
||||||
|
}, 2000)
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isCopied])
|
||||||
|
|
||||||
|
const CopyIcon = isCopied ? ClipboardCheck : RiClipboardLine
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-center gap-1 self-stretch'>
|
||||||
|
<span className={cn('flex flex-col justify-center items-start text-text-tertiary system-xs-medium', labelWidthClassName)}>{label}</span>
|
||||||
|
<div className='flex justify-center items-center gap-0.5'>
|
||||||
|
<span className='system-xs-medium text-text-secondary'>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
<Tooltip popupContent={t(`common.operation.${isCopied ? 'copied' : 'copy'}`)} position='top'>
|
||||||
|
<ActionButton onClick={handleCopy}>
|
||||||
|
<CopyIcon className='w-3.5 h-3.5 text-text-tertiary' />
|
||||||
|
</ActionButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(KeyValueItem)
|
46
web/app/components/plugins/card/base/card-icon.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { RiCheckLine } from '@remixicon/react'
|
||||||
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
const Icon = ({
|
||||||
|
className,
|
||||||
|
src,
|
||||||
|
installed = false,
|
||||||
|
}: {
|
||||||
|
className?: string
|
||||||
|
src: string | {
|
||||||
|
'content': string
|
||||||
|
'background': string
|
||||||
|
}
|
||||||
|
installed?: boolean
|
||||||
|
}) => {
|
||||||
|
if (typeof src === 'object') {
|
||||||
|
return (
|
||||||
|
<div className={cn('relative', className)}>
|
||||||
|
<AppIcon
|
||||||
|
size='large'
|
||||||
|
iconType={'emoji'}
|
||||||
|
icon={src.content}
|
||||||
|
background={src.background}
|
||||||
|
className='rounded-md'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn('shrink-0 relative w-10 h-10 rounded-md bg-center bg-no-repeat bg-contain', className)}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${src})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{installed
|
||||||
|
&& <div className='flex justify-center items-center gap-2 absolute bottom-[-4px] right-[-4px] w-[18px] h-[18px] rounded-full border-2 border-components-panel-bg bg-state-success-solid'>
|
||||||
|
<RiCheckLine className='w-3 h-3 text-text-primary-on-surface' />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Icon
|
12
web/app/components/plugins/card/base/corner-mark.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { LeftCorner } from '../../../base/icons/src/vender/plugin'
|
||||||
|
|
||||||
|
const CornerMark = ({ text }: { text: string }) => {
|
||||||
|
return (
|
||||||
|
<div className='absolute top-0 right-0 flex pl-[13px] '>
|
||||||
|
<LeftCorner className="text-background-section" />
|
||||||
|
<div className="h-5 leading-5 rounded-tr-xl pr-2 bg-background-section text-text-tertiary system-2xs-medium-uppercase">{text}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CornerMark
|
31
web/app/components/plugins/card/base/description.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
text: string
|
||||||
|
descriptionLineRows: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const Description: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
text,
|
||||||
|
descriptionLineRows,
|
||||||
|
}) => {
|
||||||
|
const lineClassName = useMemo(() => {
|
||||||
|
if (descriptionLineRows === 1)
|
||||||
|
return 'h-4 truncate'
|
||||||
|
else if (descriptionLineRows === 2)
|
||||||
|
return 'h-8 line-clamp-2'
|
||||||
|
else
|
||||||
|
return 'h-12 line-clamp-3'
|
||||||
|
}, [descriptionLineRows])
|
||||||
|
return (
|
||||||
|
<div className={cn('text-text-tertiary system-xs-regular', lineClassName, className)}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Description
|
19
web/app/components/plugins/card/base/download-count.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { RiInstallLine } from '@remixicon/react'
|
||||||
|
import { formatNumber } from '@/utils/format'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
downloadCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DownloadCount = ({
|
||||||
|
downloadCount,
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-1 text-text-tertiary">
|
||||||
|
<RiInstallLine className="shrink-0 w-3 h-3" />
|
||||||
|
<div className="system-xs-regular">{formatNumber(downloadCount)}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DownloadCount
|
30
web/app/components/plugins/card/base/org-info.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import cn from '@/utils/classnames'
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
orgName?: string
|
||||||
|
packageName: string
|
||||||
|
packageNameClassName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgInfo = ({
|
||||||
|
className,
|
||||||
|
orgName,
|
||||||
|
packageName,
|
||||||
|
packageNameClassName,
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('flex items-center h-4 space-x-0.5', className)}>
|
||||||
|
{orgName && (
|
||||||
|
<>
|
||||||
|
<span className='shrink-0 text-text-tertiary system-xs-regular'>{orgName}</span>
|
||||||
|
<span className='shrink-0 text-text-quaternary system-xs-regular'>/</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<span className={cn('shrink-0 w-0 grow truncate text-text-tertiary system-xs-regular', packageNameClassName)}>
|
||||||
|
{packageName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgInfo
|
46
web/app/components/plugins/card/base/placeholder.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Group } from '../../../base/icons/src/vender/other'
|
||||||
|
import Title from './title'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
wrapClassName: string
|
||||||
|
loadingFileName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadingPlaceholder = ({ className }: { className?: string }) => (
|
||||||
|
<div className={cn('h-2 rounded-sm opacity-20 bg-text-quaternary', className)} />
|
||||||
|
)
|
||||||
|
|
||||||
|
const Placeholder = ({
|
||||||
|
wrapClassName,
|
||||||
|
loadingFileName,
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<div className={wrapClassName}>
|
||||||
|
<div className="flex">
|
||||||
|
<div
|
||||||
|
className='flex w-10 h-10 p-1 justify-center items-center gap-2 rounded-[10px]
|
||||||
|
border-[0.5px] border-components-panel-border bg-background-default backdrop-blur-sm'>
|
||||||
|
<div className='flex w-5 h-5 justify-center items-center'>
|
||||||
|
<Group className='text-text-tertiary' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 grow">
|
||||||
|
<div className="flex items-center h-5">
|
||||||
|
<Title title={loadingFileName} />
|
||||||
|
</div>
|
||||||
|
<div className={cn('flex items-center h-4 space-x-0.5')}>
|
||||||
|
<LoadingPlaceholder className="w-[41px]" />
|
||||||
|
<span className='shrink-0 text-text-quaternary system-xs-regular'>
|
||||||
|
·
|
||||||
|
</span>
|
||||||
|
<LoadingPlaceholder className="w-[180px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<LoadingPlaceholder className="mt-3 w-[420px]" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Placeholder
|
13
web/app/components/plugins/card/base/title.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const Title = ({
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className='max-w-[150px] truncate text-text-secondary system-md-semibold'>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Title
|
73
web/app/components/plugins/card/card-mock.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { PluginType } from '../types'
|
||||||
|
|
||||||
|
export const toolNotion = {
|
||||||
|
type: PluginType.tool,
|
||||||
|
org: 'Notion',
|
||||||
|
name: 'notion page search',
|
||||||
|
version: '1.2.0',
|
||||||
|
latest_version: '1.3.0',
|
||||||
|
icon: 'https://via.placeholder.com/150',
|
||||||
|
label: {
|
||||||
|
'en-US': 'Notion Page Search',
|
||||||
|
'zh-Hans': 'Notion 页面搜索',
|
||||||
|
},
|
||||||
|
brief: {
|
||||||
|
'en-US': 'Description: Search Notion pages and open visited ones faster. No admin access required.More and more info...More and more info...More and more info...',
|
||||||
|
'zh-Hans': '搜索 Notion 页面并更快地打开已访问的页面。无需管理员访问权限。More and more info...More and more info...More and more info...',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extensionDallE = {
|
||||||
|
type: PluginType.extension,
|
||||||
|
org: 'OpenAI',
|
||||||
|
name: 'DALL-E',
|
||||||
|
version: '1.1.0',
|
||||||
|
latest_version: '1.2.0',
|
||||||
|
install_count: 1234,
|
||||||
|
icon: 'https://via.placeholder.com/150',
|
||||||
|
label: {
|
||||||
|
'en-US': 'DALL-E',
|
||||||
|
'zh-Hans': 'DALL-E',
|
||||||
|
},
|
||||||
|
brief: {
|
||||||
|
'en-US': 'Description: A simple plugin to use OpenAI DALL-E model.',
|
||||||
|
'zh-Hans': '一个使用 OpenAI DALL-E 模型的简单插件。',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const modelGPT4 = {
|
||||||
|
type: PluginType.model,
|
||||||
|
org: 'OpenAI',
|
||||||
|
name: 'GPT-4',
|
||||||
|
version: '1.0.0',
|
||||||
|
latest_version: '1.0.0',
|
||||||
|
install_count: 99999,
|
||||||
|
icon: 'https://via.placeholder.com/150',
|
||||||
|
label: {
|
||||||
|
'en-US': 'GPT-4',
|
||||||
|
'zh-Hans': 'GPT-4',
|
||||||
|
},
|
||||||
|
brief: {
|
||||||
|
'en-US': 'Description: A simple plugin to use OpenAI GPT-4 model.',
|
||||||
|
'zh-Hans': '一个使用 OpenAI GPT-4 模型的简单插件。',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const customTool = {
|
||||||
|
type: PluginType.tool,
|
||||||
|
name: 'notion page search',
|
||||||
|
version: '1.2.0',
|
||||||
|
latest_version: '1.3.0',
|
||||||
|
icon: {
|
||||||
|
content: '🕵️',
|
||||||
|
background: '#FEF7C3',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
'en-US': 'Notion Page Search',
|
||||||
|
'zh-Hans': 'Notion 页面搜索',
|
||||||
|
},
|
||||||
|
brief: {
|
||||||
|
'en-US': 'Description: Search Notion pages and open visited ones faster. No admin access required.More and more info...More and more info...More and more info...',
|
||||||
|
'zh-Hans': '搜索 Notion 页面并更快地打开已访问的页面。无需管理员访问权限。More and more info...More and more info...More and more info...',
|
||||||
|
},
|
||||||
|
}
|
36
web/app/components/plugins/card/card-more-info.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import DownloadCount from './base/download-count'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
downloadCount?: number
|
||||||
|
tags: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const CardMoreInfo = ({
|
||||||
|
downloadCount,
|
||||||
|
tags,
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center h-5">
|
||||||
|
{downloadCount !== undefined && <DownloadCount downloadCount={downloadCount} />}
|
||||||
|
{downloadCount !== undefined && tags && tags.length > 0 && <div className="mx-2 text-text-quaternary system-xs-regular">·</div>}
|
||||||
|
{tags && tags.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-wrap space-x-2 h-4 overflow-hidden">
|
||||||
|
{tags.map(tag => (
|
||||||
|
<div
|
||||||
|
key={tag}
|
||||||
|
className="flex space-x-1 system-xs-regular max-w-[120px] overflow-hidden"
|
||||||
|
title={`# ${tag}`}
|
||||||
|
>
|
||||||
|
<span className="text-text-quaternary">#</span>
|
||||||
|
<span className="truncate text-text-tertiary">{tag}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CardMoreInfo
|
83
web/app/components/plugins/card/index.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
import { RiVerifiedBadgeLine } from '@remixicon/react'
|
||||||
|
import type { Plugin } from '../types'
|
||||||
|
import Icon from '../card/base/card-icon'
|
||||||
|
import CornerMark from './base/corner-mark'
|
||||||
|
import Title from './base/title'
|
||||||
|
import OrgInfo from './base/org-info'
|
||||||
|
import Description from './base/description'
|
||||||
|
import Placeholder from './base/placeholder'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import { useGetLanguage } from '@/context/i18n'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
payload: Plugin
|
||||||
|
titleLeft?: React.ReactNode
|
||||||
|
installed?: boolean
|
||||||
|
hideCornerMark?: boolean
|
||||||
|
descriptionLineRows?: number
|
||||||
|
footer?: React.ReactNode
|
||||||
|
isLoading?: boolean
|
||||||
|
loadingFileName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Card = ({
|
||||||
|
className,
|
||||||
|
payload,
|
||||||
|
titleLeft,
|
||||||
|
installed,
|
||||||
|
hideCornerMark,
|
||||||
|
descriptionLineRows = 2,
|
||||||
|
footer,
|
||||||
|
isLoading = false,
|
||||||
|
loadingFileName,
|
||||||
|
}: Props) => {
|
||||||
|
const locale = useGetLanguage()
|
||||||
|
|
||||||
|
const { type, name, org, label, brief, icon } = payload
|
||||||
|
|
||||||
|
const getLocalizedText = (obj: Record<string, string> | undefined) =>
|
||||||
|
obj?.[locale] || obj?.['en-US'] || ''
|
||||||
|
|
||||||
|
const wrapClassName = cn('relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className)
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Placeholder
|
||||||
|
wrapClassName={wrapClassName}
|
||||||
|
loadingFileName={loadingFileName!}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={wrapClassName}>
|
||||||
|
{!hideCornerMark && <CornerMark text={type} />}
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex">
|
||||||
|
<Icon src={icon} installed={installed} />
|
||||||
|
<div className="ml-3 grow">
|
||||||
|
<div className="flex items-center h-5">
|
||||||
|
<Title title={getLocalizedText(label)} />
|
||||||
|
<RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />
|
||||||
|
{titleLeft} {/* This can be version badge */}
|
||||||
|
</div>
|
||||||
|
<OrgInfo
|
||||||
|
className="mt-0.5"
|
||||||
|
orgName={org}
|
||||||
|
packageName={name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Description
|
||||||
|
className="mt-3"
|
||||||
|
text={getLocalizedText(brief)}
|
||||||
|
descriptionLineRows={descriptionLineRows}
|
||||||
|
/>
|
||||||
|
{footer && <div>{footer}</div>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(Card)
|
@ -0,0 +1,270 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import Modal from '@/app/components/base/modal'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import type { Item } from '@/app/components/base/select'
|
||||||
|
import { PortalSelect } from '@/app/components/base/select'
|
||||||
|
import type { GitHubRepoReleaseResponse } from '@/app/components/plugins/types'
|
||||||
|
import { installPackageFromGitHub } from '@/service/plugins'
|
||||||
|
import Toast from '@/app/components/base/toast'
|
||||||
|
|
||||||
|
type InstallFromGitHubProps = {
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstallStep = 'url' | 'version' | 'package' | 'installed'
|
||||||
|
|
||||||
|
type GitHubUrlInfo = {
|
||||||
|
isValid: boolean
|
||||||
|
owner?: string
|
||||||
|
repo?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ onClose }) => {
|
||||||
|
const [step, setStep] = useState<InstallStep>('url')
|
||||||
|
const [repoUrl, setRepoUrl] = useState('')
|
||||||
|
const [selectedVersion, setSelectedVersion] = useState('')
|
||||||
|
const [selectedPackage, setSelectedPackage] = useState('')
|
||||||
|
const [releases, setReleases] = useState<GitHubRepoReleaseResponse[]>([])
|
||||||
|
|
||||||
|
const versions: Item[] = releases.map(release => ({
|
||||||
|
value: release.tag_name,
|
||||||
|
name: release.tag_name,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const packages: Item[] = selectedVersion
|
||||||
|
? (releases
|
||||||
|
.find(release => release.tag_name === selectedVersion)
|
||||||
|
?.assets.map(asset => ({
|
||||||
|
value: asset.browser_download_url,
|
||||||
|
name: asset.name,
|
||||||
|
})) || [])
|
||||||
|
: []
|
||||||
|
|
||||||
|
const parseGitHubUrl = (url: string): GitHubUrlInfo => {
|
||||||
|
const githubUrlRegex = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/
|
||||||
|
const match = url.match(githubUrlRegex)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
return {
|
||||||
|
isValid: true,
|
||||||
|
owner: match[1],
|
||||||
|
repo: match[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isValid: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInstall = async () => {
|
||||||
|
try {
|
||||||
|
const response = await installPackageFromGitHub({ repo: repoUrl, version: selectedVersion, package: selectedPackage })
|
||||||
|
if (response.plugin_unique_identifier) {
|
||||||
|
setStep('installed')
|
||||||
|
console.log('Package installed:')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Failed to install package:')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Error installing package:')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNext = async () => {
|
||||||
|
switch (step) {
|
||||||
|
case 'url': {
|
||||||
|
const { isValid, owner, repo } = parseGitHubUrl(repoUrl)
|
||||||
|
if (!isValid || !owner || !repo) {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: 'Invalid GitHub URL. Please enter a valid URL in the format: https://github.com/owner/repo',
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`)
|
||||||
|
if (!res.ok)
|
||||||
|
throw new Error('Failed to fetch releases')
|
||||||
|
const data = await res.json()
|
||||||
|
const formattedReleases = data.map((release: any) => ({
|
||||||
|
tag_name: release.tag_name,
|
||||||
|
assets: release.assets.map((asset: any) => ({
|
||||||
|
browser_download_url: asset.browser_download_url,
|
||||||
|
id: asset.id,
|
||||||
|
name: asset.name,
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
setReleases(formattedReleases)
|
||||||
|
setStep('version')
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: 'Failed to fetch repository release',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'version':
|
||||||
|
setStep('package')
|
||||||
|
break
|
||||||
|
case 'package':
|
||||||
|
handleInstall()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isInputValid = () => {
|
||||||
|
switch (step) {
|
||||||
|
case 'url':
|
||||||
|
return !!repoUrl.trim()
|
||||||
|
case 'version':
|
||||||
|
return !!selectedVersion
|
||||||
|
case 'package':
|
||||||
|
return !!selectedPackage
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const InfoRow = ({ label, value }: { label: string; value: string }) => (
|
||||||
|
<div className='flex items-center gap-3'>
|
||||||
|
<div className='flex w-[72px] items-center gap-2'>
|
||||||
|
<div className='text-text-tertiary system-sm-medium'>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex-grow overflow-hidden text-text-secondary text-ellipsis system-sm-medium'>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isShow={true}
|
||||||
|
onClose={onClose}
|
||||||
|
className='flex min-w-[480px] p-0 flex-col items-start rounded-2xl border-[0.5px]
|
||||||
|
border-components-panel-border bg-components-panel-bg shadows-shadow-xl'
|
||||||
|
closable
|
||||||
|
>
|
||||||
|
<div className='flex pt-6 pl-6 pb-3 pr-14 items-start gap-2 self-stretch'>
|
||||||
|
<div className='flex flex-col items-start gap-1 flex-grow'>
|
||||||
|
<div className='self-stretch text-text-primary title-2xl-semi-bold'>
|
||||||
|
Install plugin from GitHub
|
||||||
|
</div>
|
||||||
|
<div className='self-stretch text-text-tertiary system-xs-regular'>
|
||||||
|
{step !== 'installed' && 'Please make sure that you only install plugins from a trusted source.'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`flex px-6 py-3 flex-col justify-center items-start self-stretch ${step === 'installed' ? 'gap-2' : 'gap-4'}`}>
|
||||||
|
{step === 'url' && (
|
||||||
|
<>
|
||||||
|
<label
|
||||||
|
htmlFor='repoUrl'
|
||||||
|
className='flex flex-col justify-center items-start self-stretch text-text-secondary'
|
||||||
|
>
|
||||||
|
<span className='system-sm-semibold'>GitHub repository</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type='url'
|
||||||
|
id='repoUrl'
|
||||||
|
name='repoUrl'
|
||||||
|
value={repoUrl}
|
||||||
|
onChange={e => setRepoUrl(e.target.value)} // TODO: needs to verify the url
|
||||||
|
className='flex items-center self-stretch rounded-lg border border-components-input-border-active
|
||||||
|
bg-components-input-bg-active shadows-shadow-xs p-2 gap-[2px] flex-grow overflow-hidden
|
||||||
|
text-components-input-text-filled text-ellipsis system-sm-regular'
|
||||||
|
placeholder='Please enter GitHub repo URL'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{step === 'version' && (
|
||||||
|
<>
|
||||||
|
<label
|
||||||
|
htmlFor='version'
|
||||||
|
className='flex flex-col justify-center items-start self-stretch text-text-secondary'
|
||||||
|
>
|
||||||
|
<span className='system-sm-semibold'>Select version</span>
|
||||||
|
</label>
|
||||||
|
<PortalSelect
|
||||||
|
value={selectedVersion}
|
||||||
|
onSelect={item => setSelectedVersion(item.value as string)}
|
||||||
|
items={versions}
|
||||||
|
placeholder="Please select a version"
|
||||||
|
popupClassName='w-[432px] z-[1001]'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{step === 'package' && (
|
||||||
|
<>
|
||||||
|
<label
|
||||||
|
htmlFor='package'
|
||||||
|
className='flex flex-col justify-center items-start self-stretch text-text-secondary'
|
||||||
|
>
|
||||||
|
<span className='system-sm-semibold'>Select package</span>
|
||||||
|
</label>
|
||||||
|
<PortalSelect
|
||||||
|
value={selectedPackage}
|
||||||
|
onSelect={item => setSelectedPackage(item.value as string)}
|
||||||
|
items={packages}
|
||||||
|
placeholder="Please select a package"
|
||||||
|
popupClassName='w-[432px] z-[1001]'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{step === 'installed' && (
|
||||||
|
<>
|
||||||
|
<div className='text-text-secondary system-md-regular'>The plugin has been installed successfully.</div>
|
||||||
|
<div className='flex w-full p-4 flex-col justify-center items-start gap-2 rounded-2xl bg-background-section-burn'>
|
||||||
|
{[
|
||||||
|
{ label: 'Repository', value: repoUrl },
|
||||||
|
{ label: 'Version', value: selectedVersion },
|
||||||
|
{ label: 'Package', value: selectedPackage },
|
||||||
|
].map(({ label, value }) => (
|
||||||
|
<InfoRow key={label} label={label} value={value} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='flex p-6 pt-5 justify-end items-center gap-2 self-stretch'>
|
||||||
|
{step === 'installed'
|
||||||
|
? (
|
||||||
|
<Button
|
||||||
|
variant='primary'
|
||||||
|
className='min-w-[72px]'
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant='secondary'
|
||||||
|
className='min-w-[72px]'
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{step === 'url' ? 'Cancel' : 'Back'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='primary'
|
||||||
|
className='min-w-[72px]'
|
||||||
|
onClick={handleNext}
|
||||||
|
disabled={!isInputValid()}
|
||||||
|
>
|
||||||
|
{step === 'package' ? 'Install' : 'Next'}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InstallFromGitHub
|