Compare commits

...

142 Commits

Author SHA1 Message Date
AkaraChen
2fe28279c8 build: update react refresh ignore 2024-10-22 14:09:31 +08:00
AkaraChen
3d165ec7d9 build: eslint plugin react-refresh 2024-10-22 13:49:39 +08:00
Yi
0b8c896481 Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-22 13:43:15 +08:00
Yi
15fe635465 chore: install package from GitHub 2024-10-22 13:43:01 +08:00
AkaraChen
f8c3189f4d build: fix eslint undef 2024-10-22 11:43:23 +08:00
AkaraChen
f215db87e3 build: fix eslint undef 2024-10-22 11:36:42 +08:00
Joel
67d02212b4 chore: pnpm 2024-10-22 11:18:30 +08:00
Joel
cff9adaf8e chore: tools ts problems 2024-10-22 11:06:28 +08:00
AkaraChen
cdd2a40086 style: minimium codemod 2024-10-22 02:24:59 +00:00
AkaraChen
024028bc52 build: sync eslint rule 2024-10-22 10:24:10 +08:00
AkaraChen
0ae085b48a build: add eslint common rule 2024-10-22 10:24:10 +08:00
AkaraChen
2094c54951 build: update eslint config antfu 2024-10-22 10:24:10 +08:00
AkaraChen
f4f11135d3 build: using eslint flat config 2024-10-22 10:24:10 +08:00
Joel
8e9d7a229d feat: scroll to view and fix action hidden 2024-10-21 18:21:45 +08:00
Joel
8f49572f85 chore: from marketplace tilte ui 2024-10-21 15:07:18 +08:00
JzoNg
5aa7696cc3 update style of action list 2024-10-21 11:34:21 +08:00
JzoNg
15dd79e822 provider detail data binding 2024-10-21 11:34:21 +08:00
JzoNg
4651ab4195 new style of provider detail 2024-10-21 11:34:21 +08:00
JzoNg
5e3160e6f6 fix title & description of tool provider 2024-10-21 11:34:21 +08:00
JzoNg
973cd126bb create & update endpoint 2024-10-21 11:34:21 +08:00
JzoNg
ebaf8766ef endpoint form 2024-10-21 11:34:21 +08:00
JzoNg
d2190e9c3a remove endpoint 2024-10-21 11:34:21 +08:00
JzoNg
37f55098fe switch endpoint service state 2024-10-21 11:34:21 +08:00
JzoNg
b1771194cc servise of endpoints 2024-10-21 11:34:21 +08:00
JzoNg
0279bd8c75 endpoint card databing 2024-10-21 11:34:21 +08:00
JzoNg
5e077e4ce8 endpoints data binding 2024-10-21 11:34:21 +08:00
JzoNg
64067e1f20 plugin detail header operations 2024-10-21 11:34:21 +08:00
JzoNg
5295c72ca1 endpoints mock data 2024-10-21 11:34:21 +08:00
JzoNg
1ecea62052 add verified tag 2024-10-21 11:34:21 +08:00
JzoNg
307af29b65 add plugin description 2024-10-21 11:34:21 +08:00
JzoNg
10190a9aa5 plugin detail header data binding 2024-10-21 11:34:21 +08:00
JzoNg
7c5c35600c plugin detail type 2024-10-21 11:34:21 +08:00
JzoNg
63b333cdb1 modify plugin detail panel 2024-10-21 11:34:21 +08:00
JzoNg
a6776190bd chore: update remix icon 2024-10-21 11:34:19 +08:00
AkaraChen
9577cbac27 build: docker use pnpm 2024-10-21 11:27:01 +08:00
AkaraChen
f6ae13abad ci: migrate to pnpm 2024-10-21 11:14:30 +08:00
AkaraChen
f3d501e7d5 fix: gen-icon script phantom deps 2024-10-21 10:43:52 +08:00
AkaraChen
2eab8fcc33 build: switch to pnpm 2024-10-21 10:43:52 +08:00
Joel
bdb81fe20d feat: choose tool sticky 2024-10-18 18:18:59 +08:00
Yi
0f60fe7f2a chore: update workspace name (truncated for long name) 2024-10-18 14:17:53 +08:00
Yi
425f624de5 chore: add plugin panel 2024-10-18 14:02:40 +08:00
Yi
b1919745e2 Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-18 14:01:34 +08:00
twwu
9a242bcac9 Merge branch 'main' into feat/plugins 2024-10-18 09:59:43 +08:00
Yi
a6109a60b8 Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-17 15:22:08 +08:00
Yi
28f7bbf83a chore: installation progress bar 2024-10-17 15:21:56 +08:00
Joel
cac04c5f3c refactor: chagne card to client component 2024-10-17 15:06:06 +08:00
Joel
18f5f9cc37 feat: plugin upgrade 2024-10-16 18:05:55 +08:00
Joel
1787c5c93f chore: handle tag name too long 2024-10-16 16:39:47 +08:00
Joel
f981494613 feat: plugin support emoji icon 2024-10-16 16:28:19 +08:00
Joel
fbc853af92 feat: remove config 2024-10-16 16:28:18 +08:00
StyleZhang
1a64c660ba enable marketplace 2024-10-16 15:50:18 +08:00
Joel
846555af1b fix: action buttion ui 2024-10-16 11:45:25 +08:00
Joel
bca94854f7 feat: plugin info 2024-10-16 11:30:04 +08:00
Joel
1bd70bd8bf chore: copy button 2024-10-16 11:05:51 +08:00
Joel
d1dcd39191 feat: add debug info i18n and extract common to components 2024-10-16 10:52:16 +08:00
Joel
35384bda41 chore: refactor card loading 2024-10-15 22:52:57 +08:00
Joel
89fb6eb648 chore: set hideCornerMark to optional 2024-10-15 21:45:30 +08:00
Joel
aa61a890b2 chore: change downloadCount to optional 2024-10-15 21:43:20 +08:00
Joel
31ece363c3 chore: chagne mangament attr name 2024-10-15 21:35:56 +08:00
Joel
70a5d78cc5 chore: priviege i18n 2024-10-15 21:31:06 +08:00
Joel
57f4dfdb6f feat: add permission data logic 2024-10-15 19:20:59 +08:00
Yi
aa9028a607 Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-15 15:48:05 +08:00
Yi
d83f94c55c fix: set constrain for uploading packages only works in the Plugins tab 2024-10-15 15:47:54 +08:00
StyleZhang
a8c5e0b0b0 tool list item click 2024-10-15 15:13:00 +08:00
Yi
177e8cbf73 Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-15 14:57:37 +08:00
Yi
23828fd15a Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-15 14:57:11 +08:00
StyleZhang
2cc37ac8e5 tool list 2024-10-15 14:57:00 +08:00
Yi
c9ee1e9ff2 feat: install difypkg ui 2024-10-15 14:56:59 +08:00
Joel
4f10f5d5f4 chore: hover show action 2024-10-15 11:57:44 +08:00
Yi
c48c84674e Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-15 11:02:45 +08:00
Yi
1e9fbbf41b fix: dynamic sub header color 2024-10-15 11:02:25 +08:00
StyleZhang
4dd144ce43 tools list 2024-10-15 10:41:10 +08:00
Yi
a387ff1c38 Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-14 18:43:24 +08:00
Yi
a9e367e6de feat: use-uploader hook 2024-10-14 18:43:08 +08:00
Joel
e2fec587f8 feat: from marketplace 2024-10-14 18:35:13 +08:00
StyleZhang
39a6f0943d marketplace 2024-10-14 12:38:53 +08:00
JzoNg
684896d100 merge main 2024-10-14 10:29:52 +08:00
JzoNg
54f911f6cd endpoints 2024-10-13 10:49:55 +08:00
Joel
0e5c16d0c2 feat: view to ui and fix some ui promblem 2024-10-12 18:15:11 +08:00
Joel
b8cd6ea478 feat: support view choose 2024-10-12 17:36:35 +08:00
JzoNg
fc61fd0f50 action list 2024-10-12 17:08:45 +08:00
JzoNg
2fbfc988c4 plugin panel detail 2024-10-12 16:37:57 +08:00
JzoNg
99f5fea001 plugin detail panel header 2024-10-12 16:37:57 +08:00
StyleZhang
ecd2a1be9f marketplace 2024-10-12 16:34:18 +08:00
Joel
49ee9ca5f1 feat: tool item support action 2024-10-12 16:04:16 +08:00
Joel
6d0eef12b1 feat: split tools data to out and add demo 2024-10-12 14:30:46 +08:00
StyleZhang
c1e0a939b0 marketplace 2024-10-12 12:46:49 +08:00
JzoNg
060a894bd1 interaction of plugin detail panel 2024-10-12 12:36:29 +08:00
JzoNg
c75e02b5b2 update provider card 2024-10-12 12:36:29 +08:00
StyleZhang
fcf43ee845 plugin page context 2024-10-12 11:33:12 +08:00
StyleZhang
466f61d044 relocate file 2024-10-12 11:05:03 +08:00
StyleZhang
27ae74af50 hook 2024-10-12 11:03:00 +08:00
Joel
8dd941e3d2 chore: instal plug add tag 2024-10-11 18:18:32 +08:00
Joel
dec4bf6b98 fix: install modal item server 2024-10-11 18:06:23 +08:00
Joel
e2c33fc40f fix: plugin item i18n 2024-10-11 18:05:45 +08:00
Yi
c74e59d1f4 Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-11 17:28:27 +08:00
Yi
1fcb902715 feat: add cards to "install from marketplace" 2024-10-11 17:25:33 +08:00
JzoNg
c08f98218c hide search in other pages 2024-10-11 17:13:28 +08:00
Yi
c6377f6e38 fix: naming styles 2024-10-11 16:29:07 +08:00
Yi
3cb0a5bd68 Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-11 16:28:04 +08:00
Yi
95777d23e0 fix: naming styles 2024-10-11 16:27:31 +08:00
JzoNg
e7dc16fd08 provider card 2024-10-11 16:21:19 +08:00
JzoNg
495dec143c unconfigured provider 2024-10-11 16:21:19 +08:00
JzoNg
4cc6dfa232 new style of provider card 2024-10-11 16:21:18 +08:00
JzoNg
d1452d4af4 no provider installed 2024-10-11 16:21:18 +08:00
JzoNg
d68ca56b3a system default model 2024-10-11 16:21:18 +08:00
JzoNg
49856d8d17 setting content header & close button 2024-10-11 16:21:18 +08:00
JzoNg
902de72cc0 new style of settings 2024-10-11 16:21:18 +08:00
StyleZhang
76f6b8d104 marketplace 2024-10-11 16:15:24 +08:00
StyleZhang
f111e605c4 marketplace 2024-10-11 15:27:14 +08:00
Joel
b358ed3a5b fix: init workflow image crash 2024-10-11 14:31:14 +08:00
Joel
88dbf639e0 chore: enchance locale props 2024-10-11 14:24:43 +08:00
Joel
aa8b525b48 fix: icon tsx to router problem 2024-10-11 14:10:24 +08:00
Yi
c990bc61db feat: install plugins (partial) 2024-10-11 12:39:27 +08:00
Joel
6d7588f236 chore: fix ui 2024-10-10 17:53:13 +08:00
Joel
8257c7bf02 chore: remove useless data 2024-10-10 17:49:23 +08:00
Joel
946068967b feat: finish card components 2024-10-10 17:47:04 +08:00
Joel
19f5684960 feat: add label and fix some ui problem 2024-10-10 11:29:11 +08:00
Joel
6d62840aff chore: split card component 2024-10-10 10:40:26 +08:00
Joel
ab868ac979 fix: error igm 2024-10-10 10:25:25 +08:00
Joel
1d74e693ea chore: fix imge name 2024-10-10 10:22:18 +08:00
Joel
fa43d4202f feat: plugin item 2024-10-09 18:36:15 +08:00
Joel
6b29860788 feat: add installed 2024-10-09 18:12:14 +08:00
Joel
36800eeaba feat: base card component 2024-10-09 17:51:54 +08:00
Yi
67acd174ac add different styles to plugins and discover 2024-10-09 15:55:33 +08:00
StyleZhang
b5edc64b2a plugin type switch 2024-10-09 15:06:09 +08:00
Yi
d00b2724cc Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins 2024-10-09 12:53:56 +08:00
Yi
43f87c0b86 make the drop plugin only appears when the user selects "plugins" 2024-10-09 12:53:43 +08:00
Joel
7a43f48c95 chore: add test page 2024-10-09 11:35:16 +08:00
StyleZhang
58a913b09d marketplace 2024-10-08 17:58:05 +08:00
Joel
cd03795f2c chore: add endpoint types 2024-10-08 15:44:52 +08:00
Joel
36f8b5711d feat: plugin types 2024-09-30 15:29:53 +08:00
Joel
f9c48e9ea9 fix: eslint to find top dir 2024-09-29 18:27:36 +08:00
JzoNg
3b48f8c98e Merge branch 'main' into tp 2024-09-27 16:47:57 +08:00
JzoNg
cef1010cb5 style update 2024-09-27 16:47:01 +08:00
Joel
cb4875a3a7 chore: split the common tailwind config 2024-09-26 15:06:36 +08:00
StyleZhang
bbca708832 add marketplace card 2024-09-24 11:18:34 +08:00
JzoNg
05aec43ee3 Merge branch 'main' into tp 2024-09-23 11:39:46 +08:00
Yi
e8127756e0 Merge branch 'main' of github.com:langgenius/dify into feat/plugins 2024-09-18 20:57:52 +08:00
Yi
792595a46f update page header 2024-09-18 18:32:33 +08:00
Yi
d7d7281c93 feat: plugin homepage 2024-09-16 18:58:39 +08:00
Yi
21193c2fbf update the plugins page 2024-09-14 17:09:25 +08:00
212 changed files with 24910 additions and 807 deletions

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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'

View File

@ -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
View File

@ -188,3 +188,6 @@ api/.vscode
.idea/ .idea/
.vscode .vscode
# pnpm
/.pnpm-store

View File

@ -1,7 +0,0 @@
/**/node_modules/*
node_modules/
dist/
build/
out/
.next/

View File

@ -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
View File

@ -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

View File

@ -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."

View File

@ -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: {

View File

@ -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

View File

@ -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:

View File

@ -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 = {

View File

@ -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,

View 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

View 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 }
}

View 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

View 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

View 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

View File

@ -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'

View File

@ -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 && (

View File

@ -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)
} }
}, },

View File

@ -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,

View File

@ -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>

View File

@ -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
} }
} }

View File

@ -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>

View File

@ -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() {
} }

View File

@ -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
} }

View File

@ -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 () => {

View File

@ -49,4 +49,6 @@ const AutoHeightTextarea = forwardRef<HTMLTextAreaElement, AutoHeightTextareaPro
}, },
) )
AutoHeightTextarea.displayName = 'AutoHeightTextarea'
export default AutoHeightTextarea export default AutoHeightTextarea

View File

@ -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>
) )
} }

View 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
}
}

View 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 }

View File

@ -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>

View File

@ -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 >
} }

View File

@ -28,4 +28,6 @@ const IconBase = forwardRef<React.MutableRefObject<HTMLOrSVGElement>, IconBasePr
}) })
}) })
IconBase.displayName = 'IconBase'
export default IconBase export default IconBase

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View 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"
}

View 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

View File

@ -1 +1,2 @@
export { default as Generator } from './Generator' export { default as Generator } from './Generator'
export { default as Group } from './Group'

View File

@ -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"
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -0,0 +1,2 @@
export { default as BoxSparkleFill } from './BoxSparkleFill'
export { default as LeftCorner } from './LeftCorner'

View File

@ -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"
}

View File

@ -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

View File

@ -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'

View File

@ -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"
}

View File

@ -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

View File

@ -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'

View File

@ -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'
/> />
) )

View File

@ -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
} }

View File

@ -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

View File

@ -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)
} }

View File

@ -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)

View File

@ -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)

View File

@ -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>
) )
} }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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>
) )

View File

@ -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[]

View File

@ -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,
}) })

View File

@ -87,4 +87,6 @@ const Form: FC<FormProps> = React.memo(({
) )
}) })
Form.displayName = 'Form'
export default Form export default Form

View File

@ -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'

View File

@ -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

View File

@ -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>
)) ))
} }

View File

@ -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;
}

View File

@ -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>
) )
} }

View 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

View File

@ -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>
) )
} }

View File

@ -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>
{ {

View File

@ -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'
/> />
) )
} }

View File

@ -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>
{/* { {/* {

View File

@ -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'>

View File

@ -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'

View File

@ -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'>

View File

@ -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}

View 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

View 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)

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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...',
},
}

View 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

View 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)

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More