Compare commits

...

219 Commits
v3.2 ... v3.9.3

Author SHA1 Message Date
archer
f56a339ad1 perf: index 2023-06-25 16:05:43 +08:00
archer
68eca25df4 fix: ssr close 2023-06-25 14:16:54 +08:00
archer
9eed321471 perf: docker-compose 2023-06-25 13:41:44 +08:00
archer
426176db47 fix: apikey 2023-06-25 13:20:00 +08:00
archer
cfb31afbd9 fix: select ui;perf: max link and compose 2023-06-25 10:52:58 +08:00
archer
5be57da407 fix: v1 api 2023-06-24 21:39:34 +08:00
archer
057c3411b9 perf: fetch error 2023-06-24 21:21:53 +08:00
archer
83d755ad0e feat: limit prompt 2023-06-24 18:55:46 +08:00
JustSong
ec9852fc63 docs: update README (#103) 2023-06-24 00:38:08 +08:00
archer
4e6f8aefe8 docs 2023-06-23 23:40:07 +08:00
archer
11352b754a fix: model 2023-06-23 23:21:59 +08:00
archer
965ad34283 docs 2023-06-23 23:16:49 +08:00
archer
986206b691 perf: sse response 2023-06-23 23:11:22 +08:00
archer
6787f19d78 feat: price 2023-06-23 18:05:53 +08:00
archer
64c35eaa3a docs 2023-06-23 17:43:14 +08:00
archer
41ada6ecda perf: keys 2023-06-23 17:12:52 +08:00
archer
ae1f7a888e perf: token count;feat: chunk size 2023-06-23 15:08:30 +08:00
archer
9aace871ff fix: ssr 2023-06-21 18:04:36 +08:00
moonrailgun
39739f9305 chore: fix admin build problem (#101) 2023-06-21 15:40:51 +08:00
archer
ce757d918b fix: ssr 2023-06-21 15:22:07 +08:00
archer
d592d4e99a markdown 2023-06-21 15:22:06 +08:00
moonrailgun
11ce10cd80 feat: add zh translation and change title (#100) 2023-06-21 15:21:24 +08:00
archer
6fb312ccfd link text 2023-06-20 10:41:17 +08:00
archer
3166376173 fix: template 2023-06-20 10:40:49 +08:00
archer
a02a528737 perf: my models 2023-06-19 21:08:32 +08:00
archer
dd4ca27dc7 perf: deploy 2023-06-19 20:00:54 +08:00
archer
f2d37c30a5 feat: baidu statistic 2023-06-19 17:28:25 +08:00
archer
1d236f87ae perf: markdown redraw 2023-06-19 16:50:14 +08:00
archer
3b515c3c2d fix: choices empty 2023-06-19 11:30:26 +08:00
archer
e95f83ec8e docs 2023-06-18 23:52:40 +08:00
archer
03793c66da README 2023-06-18 23:25:16 +08:00
archer
84daf85393 fix: base url 2023-06-18 22:38:55 +08:00
archer
6c62d80a4c fix: refresh page 2023-06-18 22:19:49 +08:00
archer
ff2043c0fb feat: maxToken setting 2023-06-18 21:23:36 +08:00
archer
ee9afa310a feat: openapi v2 chat 2023-06-18 21:06:07 +08:00
archer
2b93ae2d00 fix: time conf 2023-06-17 21:53:04 +08:00
archer
00c93a63cd perf: queue link 2023-06-17 21:27:44 +08:00
archer
61447c60ac feat: new app page 2023-06-17 17:31:38 +08:00
archer
df2fda6176 feat: auth key 2023-06-16 00:26:11 +08:00
archer
bc2504832f fix: nextjs version 2023-06-16 00:03:52 +08:00
archer
33ffd9d7dd loading 2023-06-15 22:36:09 +08:00
archer
80578a08c8 perf: app store 2023-06-15 22:17:54 +08:00
archer
2463e11cb9 feat: date picker 2023-06-15 21:44:31 +08:00
archer
4cbe4ebdc3 perf: image 2023-06-15 20:06:56 +08:00
archer
bb36e637e0 perf: code 2023-06-15 17:32:35 +08:00
archer
6f9e929298 perf: code 2023-06-15 17:32:12 +08:00
archer
bf1592d2c6 feat: admin set share 2023-06-15 00:21:27 +08:00
archer
c6259fca78 perf: export source 2023-06-14 23:14:26 +08:00
archer
cf3eb3b7b5 perf: upload img 2023-06-14 22:45:47 +08:00
archer
7c52cec0ea perf: binary avatar 2023-06-14 22:26:11 +08:00
archer
7c159d8aba fix: markdown 2023-06-14 20:58:11 +08:00
archer
07f8e18c10 fix: gpt35 4k 2023-06-14 20:54:34 +08:00
archer
e4aeee7be3 perf: token count 2023-06-14 20:02:43 +08:00
archer
8036ed6143 perf: qa 2023-06-14 14:33:26 +08:00
archer
85e6a0f38d fix: token limit 2023-06-14 10:01:00 +08:00
archer
dab70378bb feat: gpt35-16k 2023-06-14 09:45:49 +08:00
archer
0a0febd2e6 perf: admin 2023-06-14 00:24:50 +08:00
archer
391332c8dd perf: ssr 2023-06-13 20:07:32 +08:00
archer
89e7c1abca perf: admin 2023-06-13 11:49:26 +08:00
archer
fc3c360985 fix: context menu 2023-06-13 10:52:44 +08:00
archer
006ba3b877 fix: mermaid 2023-06-12 23:17:48 +08:00
archer
5a534aa630 perf: del loading 2023-06-12 22:12:29 +08:00
archer
98e3c0a41f perf: mermaid overflow 2023-06-12 22:02:06 +08:00
archer
99e47849f5 perf: kb test 2023-06-12 21:59:30 +08:00
archer
ca4cd8af9d fix: sse 2023-06-12 20:55:37 +08:00
archer
36a0ea7e43 fix: sensitive check 2023-06-12 18:29:22 +08:00
archer
71dd7f3e6c feat: search test 2023-06-12 18:18:08 +08:00
archer
6ac7119edf feat: kb UI 2023-06-12 15:11:29 +08:00
archer
daf1148bb1 fix: package 2023-06-12 10:27:59 +08:00
archer
82b05b3d94 perf: share message 2023-06-12 10:24:12 +08:00
archer
9ab5cef516 fix: tag theme 2023-06-11 21:40:43 +08:00
archer
1ac3edccab perf: ui 2023-06-11 19:41:19 +08:00
archer
d3959a918c docs 2023-06-11 19:20:52 +08:00
archer
623018f408 perf: ui 2023-06-11 19:18:40 +08:00
archer
6b6da76ac1 fix: git action 2023-06-11 18:02:59 +08:00
archer
3c9f12bb94 fix: render 2023-06-11 18:01:13 +08:00
archer
d0c3d60751 support mermaid 2023-06-11 16:32:06 +08:00
stakeswky
d057d20c17 添加mermaid图表接口 (#85)
* 添加mermaid图表接口

* 添加类型文件

* Update package.json

* Create next-env.d.ts
2023-06-10 23:27:11 +08:00
archer
9a831b5b8b fix: env conf 2023-06-10 23:22:37 +08:00
archer
7957f96d2c feat: admin set pgIvP 2023-06-10 23:10:35 +08:00
archer
fb1392565d docs 2023-06-10 22:59:40 +08:00
archer
b8a75921ed feat: admin set env 2023-06-10 22:56:57 +08:00
archer
7dd8e7bea1 feat: admin 2023-06-10 15:23:35 +08:00
stakeswky
7f9899f7f3 添加dashboard (#83)
* 后台

* 添加主界面

* 添加主界面

* Update server.js

* 修复bug

* 修复bug
2023-06-10 14:41:01 +08:00
archer
eef6d7518e perf: kb framwork 2023-06-10 14:31:20 +08:00
archer
6fd49b0955 perf: ui 2023-06-10 14:01:35 +08:00
archer
e19ac56fe5 feat: admin image 2023-06-10 00:42:55 +08:00
stakeswky
2378615887 后台 (#77) 2023-06-09 14:13:25 +08:00
archer
ba9d9c3d5f new framwork 2023-06-09 13:21:03 +08:00
archer
d9450bd7ee feat: sharechat message;fix: user balance 2023-06-09 10:52:01 +08:00
archer
69bb1f3fa5 docs 2023-06-06 23:27:44 +08:00
archer
7a38a81e12 feat: task and inform 2023-06-06 22:45:46 +08:00
archer
55d0ed9de6 feat: inform 2023-06-06 22:07:55 +08:00
archer
941549ff04 perf: user info framwork 2023-06-06 15:44:58 +08:00
archer
1f170e1cd2 docs 2023-06-06 11:45:33 +08:00
archer
b0d0a76a8e perf: paging data and image 2023-06-06 11:32:55 +08:00
archer
16018a7e0b perf: mongo index;fix: loading status and abort chat, system prompt empty 2023-06-06 11:12:13 +08:00
archer
707be3362c pg index 2023-06-06 10:20:50 +08:00
archer
942aeeac2e docs and embedding bill 2023-06-05 18:58:38 +08:00
archer
1111f07fa7 docs 2023-06-05 18:12:27 +08:00
archer
2f1506bf07 feat: maxlink and search sql 2023-06-05 14:03:46 +08:00
archer
48fbc74168 feat: embedding 2023-06-05 14:03:46 +08:00
archer
92b592dd98 feat: index type 2023-06-05 14:03:45 +08:00
archer
8cafebe26c fix: tiktoken memory 2023-06-05 14:03:44 +08:00
AJ's Life Journey
d69554575d Update mac.md (#71)
修改model_id为kb_id
2023-06-01 23:39:30 +08:00
archer
8817e4d2db feat: qa must pay 2023-06-01 11:51:00 +08:00
archer
c9ee6fabe4 fix: sql 2023-05-31 11:37:39 +08:00
archer
24319fe860 fix: quote len 2023-05-30 23:43:08 +08:00
archer
87c5cb6bca feat: quote source 2023-05-30 23:38:16 +08:00
archer
746b9af2de feat: kb data source 2023-05-30 23:26:29 +08:00
archer
176c5a4d79 fix: prompts filter 2023-05-30 21:27:09 +08:00
archer
0cde9a10a8 feat: use last quote 2023-05-30 21:18:08 +08:00
archer
59ddf09b94 fix: save chat 2023-05-30 00:40:03 +08:00
archer
cdee91bec1 fix: ts 2023-05-29 23:49:45 +08:00
archer
d36a7cb177 perf: avatar 2023-05-29 23:40:22 +08:00
archer
2fc31a706d feat: update model use time 2023-05-29 22:51:09 +08:00
archer
2fce76202a feat: custom title and set history top 2023-05-29 22:24:49 +08:00
Textcat
7fe39c2515 feat:自定义历史聊天标题 (#41)
* feat:自定义历史聊天标题

* Update chat.ts

* perf:自定义聊天标题

* feat: google auth

* perf:将修改标题移入右键菜单

* perf:updatetitle

---------

Co-authored-by: archer <545436317@qq.com>
2023-05-29 20:36:28 +08:00
archer
e818cb037f fix: openai error 2023-05-28 21:41:13 +08:00
archer
403e1f2d92 perf: export data 2023-05-28 21:13:17 +08:00
archer
d351a56e03 docs 2023-05-28 21:02:18 +08:00
archer
516618b0cd feat: insert data de-weight;perf: input queue 2023-05-28 20:13:19 +08:00
archer
7e99f905bc perf: random queue 2023-05-28 16:16:59 +08:00
archer
a287ace126 perf: code 2023-05-28 10:23:14 +08:00
archer
4f0bd677f2 docs 2023-05-27 15:10:19 +08:00
archer
741381ecb0 perf: generate queue 2023-05-27 15:05:30 +08:00
archer
f05b12975c perf: google message 2023-05-27 00:15:03 +08:00
archer
85e94966ac fix: kb delete and google auth 2023-05-26 23:37:21 +08:00
archer
dc1c1d1355 training queue 2023-05-26 23:08:25 +08:00
archer
69f32a0861 fix: search rate 2023-05-26 15:36:18 +08:00
archer
c99d6998ea feat: google auth 2023-05-26 13:59:41 +08:00
HDS
116e9c8d85 fix: 修改了init.sql重复的bug (#62) 2023-05-26 08:47:48 +08:00
archer
52920726d4 fix: default isEdit 2023-05-23 19:33:59 +08:00
archer
02caa57304 docs 2023-05-23 19:18:52 +08:00
archer
6014a56e54 feat: system prompt 2023-05-23 19:13:01 +08:00
archer
b8f08eb33e feat: quote change 2023-05-23 18:35:45 +08:00
archer
944e876aaa feat: chat quote 2023-05-23 15:09:57 +08:00
archer
ee2c259c3d perf: text and avatar 2023-05-22 16:47:41 +08:00
archer
1c8db69a5a feat: limit export kb 2023-05-22 14:14:06 +08:00
archer
5128bbcce4 perf: insert kb data 2023-05-22 13:16:34 +08:00
archer
51a5d450b7 feat: content check 2023-05-21 22:12:02 +08:00
archer
98444fd04b perf: bill framwork 2023-05-21 18:19:42 +08:00
archer
e45c1eb1e0 uniform authuser 2023-05-21 11:00:05 +08:00
archer
bd9d83e630 img and push data size 2023-05-21 11:00:00 +08:00
stakeswky
b66952ad98 Create useSearch.ts (#35)
联网功能
2023-05-20 17:04:12 +08:00
archer
242b21263a imgs 2023-05-19 14:11:48 +08:00
archer
2843178ede feat: openapi cors 2023-05-19 12:01:59 +08:00
archer
bb312441c6 fix: share prompts 2023-05-19 11:17:20 +08:00
archer
d07e5b8501 price 2023-05-19 10:31:25 +08:00
archer
246ee973ec feat: share unlogin.perf: link format and model ui 2023-05-19 10:26:30 +08:00
archer
a62a9c4067 perf: stream response 2023-05-19 00:00:56 +08:00
archer
7408db9cf6 docs 2023-05-18 22:31:18 +08:00
archer
5d4dd4a18c fix: ui bug 2023-05-18 20:54:01 +08:00
archer
5bf95bd846 feat: model related kb 2023-05-17 22:24:36 +08:00
archer
a79429fdcd feat: kb crud 2023-05-17 19:30:43 +08:00
archer
021add2af4 fix: safari reg error 2023-05-16 14:27:10 +08:00
archer
371e0e36c6 perf: error show 2023-05-15 22:33:37 +08:00
archer
e7d3a8e2e1 perf: http recognition and input textarea 2023-05-15 22:33:37 +08:00
archer
32a8d68c6c feat: docs and git 2023-05-15 22:33:36 +08:00
archer
06ab718e6e fix: share login. 2023-05-15 22:33:35 +08:00
archer
1d74095739 temp 2023-05-15 22:33:35 +08:00
archer
ca99837dab fix: ui;perf: docs 2023-05-15 22:33:34 +08:00
archer
d31bdf0ee0 feat: share chat page 2023-05-15 22:33:33 +08:00
ShengYan, Zhang
d3e7923040 fix: correct sql script. 2023-05-15 20:31:28 +08:00
archer
5f66f4523c perf: system model list 2023-05-13 14:38:43 +08:00
archer
4ec02c654b docs 2023-05-13 00:30:30 +08:00
archer
9a0c92629b perf: token params 2023-05-12 23:27:10 +08:00
archer
651eb1bf6b feat: fold sliderbar 2023-05-12 23:18:14 +08:00
archer
4e0c876154 perf: chat ui 2023-05-12 22:38:32 +08:00
archer
3e5118c4f7 fix: export 2023-05-12 18:30:52 +08:00
luiyezheng
bdd518fd3e 腾讯云函数代理方案 2023-05-12 11:25:57 +08:00
archer
340de071a9 fix: home;perf: phone adapt 2023-05-12 11:07:44 +08:00
archer
1226c3efb7 feat: chat ui 2023-05-12 00:09:10 +08:00
archer
39f9080eb2 perf: style 2023-05-11 21:19:43 +08:00
archer
3e4b165ed9 perf: ui 2023-05-11 17:29:46 +08:00
archer
451f234f68 perf: ssr and iphone daapt 2023-05-11 09:50:40 +08:00
archer
1ab45651e0 docs 2023-05-10 21:35:43 +08:00
archer
16f2ad7615 docs 2023-05-10 21:09:53 +08:00
archer
4ba4a99935 docs 2023-05-10 20:15:44 +08:00
archer
6f4471d2a0 merge 2023-05-10 19:59:19 +08:00
archer
250399a1aa docs 2023-05-10 18:28:20 +08:00
archer
1c8ce369b6 README 2023-05-10 15:29:17 +08:00
kense
875f78b42c fix: filter system prompt 字符串判空
修复 filterSystemPrompt为'\n'时, 判断有问题
2023-05-10 15:19:42 +08:00
archer
1e262a2198 docs 2023-05-10 15:18:16 +08:00
Archer
25067a14a6 Create LICENSE 2023-05-10 14:24:14 +08:00
archer
591cc21ff4 deploy docs 2023-05-10 14:14:36 +08:00
archer
5a21eb9bc1 perf: docs 2023-05-10 14:14:19 +08:00
archer
cdf4b9f324 perf: prompt and gpt4 2023-05-10 12:03:54 +08:00
archer
e3c9b8179e perf: message filter 2023-05-09 23:40:02 +08:00
archer
9b683884cc perf: overflow;load history 2023-05-09 22:54:39 +08:00
archer
4dc541e0a6 perf: search prompt 2023-05-09 21:54:07 +08:00
archer
ef2de489be perf: paging position 2023-05-09 21:30:51 +08:00
archer
cb3b9efc6e perf: filter prompt 2023-05-09 19:11:21 +08:00
archer
a745993829 perf: search prompt 2023-05-09 19:04:42 +08:00
archer
a837552b56 perf: search prompt 2023-05-09 16:32:02 +08:00
archer
f52f514f5f feat: callback training data 2023-05-09 13:20:52 +08:00
archer
fac53923dd perf: embedding price and img 2023-05-09 12:45:23 +08:00
archer
b200731d17 feat: cookie expired time 2023-05-09 11:26:11 +08:00
archer
de6ac0f589 perf: refresh page.img mode.collection filter 2023-05-09 11:13:33 +08:00
archer
18e0212d27 perf: ui show;fix: avatar 2023-05-08 18:44:10 +08:00
archer
f20a5fe9a6 perf: new chat modal 2023-05-08 14:19:28 +08:00
archer
d351084688 fix: openapi key unsave 2023-05-08 11:13:20 +08:00
archer
d807f9d097 deploy file 2023-05-08 10:46:33 +08:00
archer
3ef6d3fe63 deploy docs 2023-05-08 10:37:52 +08:00
archer
e2fccd391e feat: README 2023-05-08 09:52:40 +08:00
archer
aeef1fca0c intro 2023-05-08 09:49:13 +08:00
archer
b0402434e2 perf: text 2023-05-08 09:49:12 +08:00
archer
014fb504a4 feat: new ui 2023-05-08 09:49:11 +08:00
archer
4d043e0e46 fix: auth key code 2023-05-06 14:44:48 +08:00
archer
d1ee3913eb feat: deploy file 2023-05-05 15:00:52 +08:00
archer
a34a2b622c perf: token slice 2023-05-04 18:02:41 +08:00
archer
2d74fa8e10 fix: auth error.perf: prompt,process img 2023-05-04 15:08:09 +08:00
archer
c879905307 feat: openapi auth 2023-05-04 13:16:53 +08:00
archer
487ef670cd perf: prompt 2023-05-04 11:33:42 +08:00
archer
0d6897e180 feat: lafClaude 2023-05-04 10:53:55 +08:00
archer
3c8f38799c perf: ts type 2023-05-03 22:33:25 +08:00
archer
91b02bbfd9 feat: img compress;perf: color.prompt response 2023-05-03 21:42:23 +08:00
archer
17a42ac0cc perf: kb-add last question to search 2023-05-03 18:38:59 +08:00
archer
e384893ae0 feat: select chat model 2023-05-03 15:50:31 +08:00
450 changed files with 37497 additions and 20490 deletions

View File

@@ -1,27 +0,0 @@
# proxy
# AXIOS_PROXY_HOST=127.0.0.1
# AXIOS_PROXY_PORT=7890
# OPENAI_BASE_URL=https://api.openai.com/v1
# OPENAI_BASE_URL_AUTH=可选的安全凭证
# 是否开启队列任务。 1-开启0-关闭请求parentUrl去执行任务,单机时直接填1
queueTask=1
parentUrl=https://hostname/api/openapi/startEvents
# email
MY_MAIL=xxx@qq.com
MAILE_CODE=xxx
# ali ems
aliAccessKeyId=xxx
aliAccessKeySecret=xxx
aliSignName=xxx
aliTemplateCode=SMS_xxx
# token
TOKEN_KEY=xxx
# openai
OPENAIKEY=sk-xxx
# db
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin
PG_HOST=0.0.0.0
PG_PORT=8100
PG_USER=xxx
PG_PASSWORD=xxx
PG_DB_NAME=xxx

77
.github/workflows/image.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: Build images and copy image to docker
on:
workflow_dispatch:
push:
branches:
- 'main'
tags:
- 'v*.*.*'
jobs:
build-images:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Install Dependencies
run: |
sudo apt update && sudo apt install -y nodejs npm
- name: Set up QEMU (optional)
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: network=host
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
else
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
fi
- name: Build and publish image for main branch or tag push event
env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
run: |
cd client && \
docker buildx build \
--platform linux/amd64,linux/arm64 \
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt image" \
--label "org.opencontainers.image.licenses=MIT" \
--push \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.
push-to-docker-hub:
needs: build-images
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_NAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
else
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
fi
- name: Pull image from GitHub Container Registry
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}}
- name: Tag image with Docker Hub repository name and version tag
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
- name: Push image to Docker Hub
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}

View File

@@ -1,50 +0,0 @@
name: Release
on:
workflow_dispatch:
push:
branches:
- 'main'
jobs:
release:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Install Dependencies
run: |
sudo apt update && sudo apt install -y nodejs npm
- # Add support for more platforms with QEMU (optional)
# https://github.com/docker/setup-qemu-action
name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: network=host
- name: Login to gitbub
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: build and publish image
env:
# fork friendly ^^
DOCKER_REPO: ghcr.io/${{ github.repository_owner }}/fast-gpt
run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fast-gpt image" \
--label "org.opencontainers.image.licenses=MIT" \
--push \
-t ${DOCKER_REPO}:latest \
-f Dockerfile \
.

22
.gitignore vendored
View File

@@ -1,19 +1,10 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
node_modules/
# next.js
/.next/
/out/
.next/
out/
# production
/build
build/
# misc
.DS_Store
@@ -34,6 +25,7 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
/.vscode/
platform.json
testApi/
testApi/
local/
dist/

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"editor.formatOnSave": true, //每次保存自动格式化
"editor.mouseWheelZoom": true,
"typescript.tsdk": "./client/node_modules/typescript/lib",
"prettier.prettierPath": "./node_modules/prettier"
}

201
LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,47 +0,0 @@
SERVICE_NAME=fast-gpt
# Image URL to use all building/pushing image targets
IMG ?= $(SERVICE_NAME):latest
.PHONY: all
all: build
##@ General
# The help target prints out all targets with their descriptions organized
# beneath their categories. The categories are represented by '##@' and the
# target descriptions by '##'. The awk commands is responsible for reading the
# entire set of makefiles included in this invocation, looking for lines of the
# file as xyz: ## something, and then pretty-format the target and help. Then,
# if there's a line with ##@ something, that gets pretty-printed as a category.
# More info on the usage of ANSI control characters for terminal formatting:
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
# More info on the awk command:
# http://linuxcommand.org/lc3_adv_awk.php
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
##@ Build
.PHONY: build
build: ## Build desktop-frontend binary.
pnpm run build
.PHONY: run
run: ## Run a dev service from host.
pnpm run start
.PHONY: docker-build
docker-build: ## Build docker image with the desktop-frontend.
docker build -t c121914yu/fast-gpt:latest . --network host --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890
##@ Deployment
.PHONY: docker-run
docker-run: ## Push docker image.
docker run -d -p 8008:3000 --name fast-gpt -v /web_project/yjl/fast-gpt/logs:/app/.next/logs c121914yu/fast-gpt:latest
#TODO: add support of docker push
#TODO: add support of sealos apply

270
README.md
View File

@@ -1,256 +1,54 @@
# Fast GPT
# Fast GPT
Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接口,目前集成了 gpt35 和 embedding. 可构建自己的知识库。
Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接口,目前集成了 Gpt35, Gpt4 和 embedding. 可构建自己的知识库。并且 OpenAPI Chat 接口兼容 OpenAI 接口,意味着你只需修改 BaseUrl 和 Authorization 即可在已有项目基础上接入 FastGpt
## 知识库原理
![KBProcess](docs/imgs/KBProcess.jpg?raw=true "KBProcess")
## 🛸 在线体验
## 开发
**配置环境变量**
```bash
# proxy可选
AXIOS_PROXY_HOST=127.0.0.1
AXIOS_PROXY_PORT=7890
# openai 中转连接(可选)
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_BASE_URL_AUTH=可选的安全凭证
# 是否开启队列任务。 1-开启0-关闭(请求 parentUrl 去执行任务,单机时直接填1
queueTask=1
parentUrl=https://hostname/api/openapi/startEvents
# 发送邮箱验证码配置。参考 nodeMail 获取参数,自行百度。
MY_MAIL=xxx@qq.com
MAILE_CODE=xxx
# 阿里短信服务(邮箱和短信至少二选一)
aliAccessKeyId=xxx
aliAccessKeySecret=xxx
aliSignName=xxx
aliTemplateCode=SMS_xxx
# token随便填作为登录凭证
TOKEN_KEY=xxx
# openai key
OPENAIKEY=sk-xxx
# mongo连接地址
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin
# mongo数据库名称
MONGODB_NAME=xxx
# pg 数据库相关内容,和 docker-compose 对上
PG_HOST=0.0.0.0
PG_PORT=8102
PG_USER=xxx
PG_PASSWORD=xxx
PG_DB_NAME=xxx
```
**运行**
```
pnpm dev
```
🎉 [fastgpt.run](https://fastgpt.run/)
🎉 [ai.fastgpt.run](https://ai.fastgpt.run/)
## 部署
![Demo](docs/imgs/demo.png?raw=true 'demo')
### 代理环境(国外服务器可忽略)
1. [clash 方案](./docs/proxy/clash.md) - 仅需一台服务器(需要有 clash
2. [nginx 方案](./docs/proxy/nginx.md) - 需要一台国外服务器
3. [cloudflare 方案](./docs/proxy/cloudflare.md) - 需要有域名(每日免费 10w 次代理请求)
#### 知识库原理图
### docker 部署
#### 1. 安装 docker 和 docker-compose
这个不同系统略有区别,百度安装下。验证安装成功后进行下一步。下面给出一个例子:
```bash
# 安装docker
curl -L https://get.daocloud.io/docker | sh
sudo systemctl start docker
# 安装 docker-compose
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 验证安装
docker -v
docker-compose -v
```
![KBProcess](docs/imgs/KBProcess.jpg?raw=true 'KBProcess')
## 👨‍💻 开发
#### 2. 创建3个初始化文件
手动创建或者直接把 deploy 里内容复制过去
项目技术栈: NextJs + TS + ChakraUI + Mongo + PostgresVector 插件)
这是一个平台项目,非单机项目,除了模型调用外还涉及非常多用户的内容。
[本地开发 Quick Start](docs/dev/README.md)
**/root/fast-gpt/pg/init.sql**
```sql
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
## 🚀 私有化部署
CREATE EXTENSION vector;
-- init table
CREATE TABLE modelData (
id BIGSERIAL PRIMARY KEY,
vector VECTOR(1536),
status VARCHAR(50) NOT NULL,
user_id VARCHAR(50) NOT NULL,
model_id VARCHAR(50) NOT NULL,
q TEXT NOT NULL,
a TEXT NOT NULL
);
-- create index
CREATE INDEX modelData_status_index ON modelData USING HASH (status);
CREATE INDEX modelData_userId_index ON modelData USING HASH (user_id);
CREATE INDEX modelData_modelId_index ON modelData USING HASH (model_id);
EOSQL
```
- [Sealos 部署](https://sealos.io/docs/examples/ai-applications/install-fastgpt-on-desktop) 无需服务器,代理和域名。
- [docker-compose 部署](docs/deploy/docker.md)
- [由社区贡献的宝塔部署和本地运行教程](https://space.bilibili.com/431177525/channel/collectiondetail?sid=1370663)
**/root/fast-gpt/nginx/nginx.conf**
```conf
user nginx;
worker_processes auto;
worker_rlimit_nofile 51200;
## :point_right: RoadMap
events {
worker_connections 1024;
}
- [FastGpt RoadMap](https://kjqvjse66l.feishu.cn/docx/RVUxdqE2WolDYyxEKATcM0XXnte)
http {
access_log off;
server_names_hash_bucket_size 512;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
client_max_body_size 50M;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 8k;
gzip_http_version 1.1;
gzip_comp_level 6;
gzip_vary on;
gzip_types text/plain application/x-javascript text/css application/javascript application/json application/xml;
gzip_disable "MSIE [1-6]\.";
## 🏘️ 交流群
open_file_cache max=1000 inactive=1d;
open_file_cache_valid 30s;
open_file_cache_min_uses 8;
open_file_cache_errors off;
添加 wx 进入:
![Demo](https://otnvvf-imgs.oss.laf.run/wx300.jpg)
server {
listen 443 ssl;
server_name docgpt.ahapocket.cn;
ssl_certificate /ssl/docgpt.pem;
ssl_certificate_key /ssl/docgpt.key;
ssl_session_timeout 5m;
## 👀 其他
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 80;
server_name docgpt.ahapocket.cn;
rewrite ^(.*) https://$server_name$1 permanent;
}
}
```
- [FastGpt 常见问题](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [docker 部署教程](https://www.bilibili.com/video/BV1jo4y147fT/)
- [公众号接入](https://www.bilibili.com/video/BV1xh4y1t7fy/)
- [FastGpt V3.4 更新集合](https://www.bilibili.com/video/BV1Lo4y147Qh/?vd_source=92041a1a395f852f9d89158eaa3f61b4)
- [FastGpt 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
**/root/fast-gpt/docker-compose.yml**
```yml
version: "3.3"
services:
fast-gpt:
image: c121914yu/fast-gpt:latest
network_mode: host
restart: always
container_name: fast-gpt
environment:
# - AXIOS_PROXY_HOST=127.0.0.1
# - AXIOS_PROXY_PORT=7890
# - OPENAI_BASE_URL=https://api.openai.com/v1
# - OPENAI_BASE_URL_AUTH=可选的安全凭证
- MY_MAIL=xxxx@qq.com
- MAILE_CODE=xxxx
- aliAccessKeyId=xxxx
- aliAccessKeySecret=xxxx
- aliSignName=xxxxx
- aliTemplateCode=SMS_xxxx
- TOKEN_KEY=xxxx
- queueTask=1
- parentUrl=https://hostname/api/openapi/startEvents
- MONGODB_URI=mongodb://username:passsword@0.0.0.0:27017/?authSource=admin
- MONGODB_NAME=xxx
- PG_HOST=0.0.0.0
- PG_PORT=8100
- PG_USER=xxx
- PG_PASSWORD=xxx
- PG_DB_NAME=xxx
- OPENAIKEY=sk-xxxxx
nginx:
image: nginx:alpine3.17
container_name: nginx
restart: always
network_mode: host
volumes:
- /root/fast-gpt/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /root/fast-gpt/nginx/logs:/var/log/nginx
- /root/fast-gpt/nginx/ssl/docgpt.key:/ssl/docgpt.key
- /root/fast-gpt/nginx/ssl/docgpt.pem:/ssl/docgpt.pem
pg:
image: ankane/pgvector
container_name: pg
restart: always
ports:
- 8100:5432
environment:
- POSTGRES_USER=xxx
- POSTGRES_PASSWORD=xxx
- POSTGRES_DB=xxx
volumes:
- /root/fast-gpt/pg/data:/var/lib/postgresql/data
- /root/fast-gpt/pg/init.sql:/docker-entrypoint-initdb.d/init.sh
- /etc/localtime:/etc/localtime:ro
mongodb:
image: mongo:4.0.1
container_name: mongo
restart: always
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=username
- MONGO_INITDB_ROOT_PASSWORD=password
volumes:
- /root/fast-gpt/mongo/data:/data/db
- /root/fast-gpt/mongo/logs:/var/log/mongodb
- /etc/localtime:/etc/localtime:ro
```
## Powered by
#### 3. 运行 docker-compose
下面是一个辅助脚本,也可以直接 docker-compose up -d
**run.sh 运行文件**
```bash
#!/bin/bash
docker-compose pull
docker-compose up -d
- [TuShan: 5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan)
- [Laf: 3 分钟快速接入三方应用](https://github.com/labring/laf)
- [Sealos: 快速部署集群应用](https://github.com/labring/sealos)
- [One API: 令牌管理 & 二次分发,支持 Azure](https://github.com/songquanpeng/one-api)
echo "Docker Compose 重新拉取镜像完成!"
## 🌟 Star History
# 删除本地旧镜像
images=$(docker images --format "{{.ID}} {{.Repository}}" | grep fast-gpt)
# 将镜像 ID 和名称放入数组中
IFS=$'\n' read -rd '' -a image_array <<<"$images"
# 遍历数组并删除所有旧的镜像
for ((i=1; i<${#image_array[@]}; i++))
do
image=${image_array[$i]}
image_id=${image%% *}
docker rmi $image_id
done
```
## 其他优化点
### Git Action 自动打包镜像
.github里拥有一个 git 提交到 main 分支时自动打包 amd64 和 arm64 镜像的 actions。你仅需要提前在 git 配置好 session。
1. 创建账号 session: 头像 -> settings -> 最底部 Developer settings -> Personal access tokens -> tokens(classic) -> 创建新 session把一些看起来需要的权限勾上。
2. 添加 session 到仓库: 仓库 -> settings -> Secrets and variables -> Actions -> 创建secret
3. 填写 secret: Name-GH_PAT, Secret-第一步的tokens
## 其他问题
### Mac 可能的问题
> 因为教程有部分镜像不兼容arm64所以写个文档指导新手如何快速在mac上面搭建fast-gpt[如何在mac上面部署fastgpt](./docs/mac.md)
[![Star History Chart](https://api.star-history.com/svg?repos=c121914yu/FastGPT&type=Date)](https://star-history.com/#c121914yu/FastGPT&Date)

11
admin/.dockerignore Normal file
View File

@@ -0,0 +1,11 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.git
.yalc/
yalc.lock
testApi/
node_modules

8
admin/.env.template Normal file
View File

@@ -0,0 +1,8 @@
MONGODB_URI=mongodb://username:psw@0.0.0.0:27017/?authSource=admin
MONGODB_NAME=fastgpt
ADMIN_USER=username
ADMIN_PASS=password
ADMIN_SECRET=any
PARENT_URL=http://localhost:3000 # FastGpt服务的地址
PARENT_ROOT_KEY=rootkey # FastGpt 的rootkey
VITE_PUBLIC_SERVER_URL=http://localhost:3001 # 和server.js一致

1
admin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

48
admin/Dockerfile Normal file
View File

@@ -0,0 +1,48 @@
# Install dependencies only when needed
FROM node:current-alpine AS builder
RUN npm config set registry https://registry.npmmirror.com/
RUN apk add --no-cache libc6-compat && npm install -g pnpm
RUN pnpm config set registry https://registry.npmmirror.com/
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED 1
ENV VITE_PUBLIC_SERVER_URL ''
# Install dependencies based on the preferred package manager
COPY . .
RUN \
[ -f pnpm-lock.yaml ] && pnpm install || \
(echo "Lockfile not found." && exit 1)
RUN pnpm build
# Production image, copy all the files and run next
FROM node:current-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl \
&& apk add ca-certificates \
&& update-ca-certificates
COPY package.json pnpm-lock.yaml* ./
COPY --from=builder /app/server.js ./server.js
COPY --from=builder /app/service ./service
COPY --from=builder /app/dist ./dist
RUN npm config set registry https://registry.npmmirror.com/
RUN npm install -g pnpm
RUN pnpm config set registry https://registry.npmmirror.com/
RUN pnpm install --prod
RUN npm remove -g pnpm
ENV PORT=3001
EXPOSE 3001
CMD ["node", "server.js"]

201
admin/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

43
admin/README.md Normal file
View File

@@ -0,0 +1,43 @@
# FastGpt Admin
## 项目原理
使用 [Tushan](https://tushan.msgbyte.com/) 项目做前端,然后构造了一个与 mongodb 做沟通的 API 做后端,可以做到创建、修改和删除用户
## 开发
1. `cp .env.template .env.local`: 复制 .env.template 文件,添加环境变量
2. `pnpm i`
3. `pnpm dev`
4. 打开 `http://localhost:5173/` 访问前端页面
## 部署
1. 本地打包
`docker build -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-admin:latest . --network host --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890`
2. 直接拉镜像: `registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-admin:latest`
3. 部署时候填写环境变量: 数据库同 FastGpt 一致
```
MONGODB_URI=mongodb://username:psw@0.0.0.0:27017/?authSource=admin
MONGODB_NAME=fastgpt
ADMIN_USER=username
ADMIN_PASS=password
ADMIN_SECRET=any
PARENT_URL=http://localhost:3000
PARENT_ROOT_KEY=rootkey
```
## sealos 部署
1. 进入 sealos 官网: https://cloud.sealos.io/
2. 打开 App Launchpad(应用管理) 工具
3. 新建应用
1. 镜像名: `registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-admin:latest`
2. 容器端口: 3001
3. 环境变量: 参考上面
4. 打开外网访问开关
4. 点击部署。 完成后大约等待 1 分钟,
5. 点击 sealos 提供的外网访问地址,可以直接访问。

13
admin/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fast GPT</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

42
admin/package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "kbgpt-deafult",
"private": true,
"version": "0.0.0",
"type": "module",
"author": "anonymous",
"scripts": {
"dev": "concurrently \"vite\" \"npm run start:api\"",
"build": "tsc && vite build",
"preview": "vite preview",
"start:api": "nodemon server.js"
},
"dependencies": {
"@arco-design/web-react": "^2.49.1",
"concurrently": "^8.1.0",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dayjs": "^1.11.8",
"dotenv": "^16.1.4",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"mongoose": "^7.2.2",
"nodemon": "^2.0.22",
"react": "^18.2.0",
"react-admin": "^4.11.0",
"react-dom": "^18.2.0",
"react-i18next": "^12.3.1",
"tushan": "^0.2.30"
},
"devDependencies": {
"@types/jsonexport": "^3.0.2",
"@types/lodash-es": "^4.17.7",
"@types/node": "^20.2.5",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-helmet": "^6.1.6",
"@types/styled-components": "^5.1.26",
"@vitejs/plugin-react": "^3.1.0",
"typescript": "^4.9.2",
"vite": "^4.2.1"
}
}

6380
admin/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

10
admin/public/logo.svg Normal file
View File

@@ -0,0 +1,10 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28 88L49.5 57L118.5 29.5L248 51L323.5 122.5L360.5 324L301 421.5L164.5 412.5L118.5 324L127.5 225.5L143.5 184.5L151.5 130.5L127.5 95L82.5 80L49.5 95L28 88Z" fill="#DFDFDF"/>
<path d="M144.734 22.04C139.186 22.0047 133.638 22.1568 128.1 22.496C84.33 25.196 40.5 49 24.238 67.492C7.97598 85.984 4 91.601 4 91.601C4 91.601 34.922 98.392 57 97.5C79.078 96.608 111.355 88.82 127.692 104.564C144.032 120.309 151.428 146.017 135.232 175.709C116.062 210.852 102.516 271.862 115.086 332.235C127.656 392.609 168.054 451.995 254.814 478.007C288.29 488.043 333.639 494.757 376.459 485.673C420.966 476.885 472.309 450.915 483.351 422.563C474.101 431.448 463.911 437.703 453.149 442.353C471.455 421.433 484.884 392.621 489.939 354.179L492.469 334.939L476.147 345.435C465.644 352.19 455.562 358.838 446.054 363.831C448.692 357.959 451.092 350.611 453.784 341.054C442.687 356.244 430.054 366.409 415.186 372.526C405.952 372.023 396.833 367.659 385.976 356.429C374.618 344.682 367.856 324.334 363.513 298.763C359.169 273.191 357.053 242.836 352.845 211.886C344.425 149.984 326.933 84.013 263.105 50.851C226.15 31.651 184.013 22.274 144.733 22.038L144.734 22.04ZM144.611 40.05C181.073 40.305 220.721 49.115 254.808 66.824C311.201 96.124 326.802 153.964 335.011 214.312C339.115 244.487 341.197 274.866 345.769 301.777C347.085 309.53 348.604 317.019 350.462 324.162C335.014 324.202 323.208 315.855 308.758 299.445C316.143 329.855 320.748 335.979 334.463 354.995C306.243 346.76 273.823 320.255 253.513 290.932C250.239 330.979 273.736 362.506 286.788 374.862C261.612 360.666 226.075 333.326 202.165 286.207C201.149 327.633 214.095 373.939 238.615 402.672C204.1 391.136 173.645 303.2 153.195 275.039C140.155 308.256 150.247 364.124 169.267 405.161C149.639 382.323 138.38 355.786 132.712 328.565C121.188 273.223 134.462 214.718 151.037 184.327C170.587 148.485 161.952 112.577 140.187 91.601C118.419 70.625 66 81 53.633 83.286C41.266 85.572 31 83.286 31 83.286C31 83.286 41.3371 75.1684 48 70C74.6656 49.3155 88.786 42.954 129.211 40.461C134.263 40.149 139.406 40.011 144.614 40.047L144.611 40.05Z" fill="url(#paint0_linear_1104_3)"/>
<defs>
<linearGradient id="paint0_linear_1104_3" x1="384.5" y1="480" x2="256" y2="256" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF6011"/>
<stop offset="1" stop-color="#FF9411"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

30
admin/server.js Normal file
View File

@@ -0,0 +1,30 @@
import express from 'express';
import cors from 'cors';
import { useUserRoute } from './service/route/user.js';
import { useAppRoute } from './service/route/app.js';
import { useKbRoute } from './service/route/kb.js';
import { useSystemRoute } from './service/route/system.js';
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static('dist'));
useUserRoute(app);
useAppRoute(app);
useKbRoute(app);
useSystemRoute(app);
app.get('/*', (req, res) => {
res.sendFile(new URL('dist/index.html', import.meta.url).pathname);
});
app.use((err, req, res, next) => {
res.sendFile(new URL('dist/index.html', import.meta.url).pathname);
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

View File

@@ -0,0 +1,87 @@
import { Model, Kb } from '../schema.js';
import { auth } from './system.js';
export const useAppRoute = (app) => {
// 获取AI助手列表
app.get('/models', auth(), async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort;
const name = req.query.name || '';
const id = req.query.id || '';
const where = {
...(name && { name: { $regex: name, $options: 'i' } }),
...(id && { _id: id })
};
const modelsRaw = await Model.find(where)
.skip(start)
.limit(end - start)
.sort({ [sort]: order, 'share.isShare': -1, 'share.collection': -1 });
const models = [];
for (const modelRaw of modelsRaw) {
const model = modelRaw.toObject();
// 获取与模型关联的知识库名称
const kbNames = [];
for (const kbId of model.chat.relatedKbs) {
const kb = await Kb.findById(kbId);
kbNames.push(kb.name);
}
const orderedModel = {
id: model._id.toString(),
userId: model.userId,
name: model.name,
intro: model.intro,
model: model.chat?.chatModel,
relatedKbs: kbNames, // 将relatedKbs的id转换为相应的Kb名称
systemPrompt: model.chat?.systemPrompt || '',
temperature: model.chat?.temperature || 0,
'share.topNum': model.share?.topNum || 0,
'share.isShare': model.share?.isShare || false,
'share.collection': model.share?.collection || 0
};
models.push(orderedModel);
}
const totalCount = await Model.countDocuments(where);
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(models);
} catch (err) {
console.log(`Error fetching models: ${err}`);
res.status(500).json({ error: 'Error fetching models', details: err.message });
}
});
// 修改 app 信息
app.put('/models/:id', auth(), async (req, res) => {
try {
const _id = req.params.id;
let {
share: { isShare, topNum },
intro
} = req.body;
await Model.findByIdAndUpdate(_id, {
$set: {
intro: intro,
'share.topNum': Number(topNum),
'share.isShare': isShare === 'true' || isShare === true
}
});
res.json({});
} catch (err) {
console.log(`Error updating user: ${err}`);
res.status(500).json({ error: 'Error updating user' });
}
});
};

58
admin/service/route/kb.js Normal file
View File

@@ -0,0 +1,58 @@
import { Kb } from '../schema.js';
import { auth } from './system.js';
export const useKbRoute = (app) => {
// 获取用户知识库列表
app.get('/kbs', auth(), async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const tag = req.query.tag || '';
const name = req.query.name || '';
const where = {
...(name
? {
name: { $regex: name, $options: 'i' }
}
: {}),
...(tag
? {
tags: { $elemMatch: { $regex: tag, $options: 'i' } }
}
: {})
};
console.log(where);
const kbsRaw = await Kb.find(where)
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const kbs = [];
for (const kbRaw of kbsRaw) {
const kb = kbRaw.toObject();
const orderedKb = {
id: kb._id.toString(),
userId: kb.userId,
name: kb.name,
tags: kb.tags,
avatar: kb.avatar
};
kbs.push(orderedKb);
}
const totalCount = await Kb.countDocuments(where);
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(kbs);
} catch (err) {
console.log(`Error fetching kbs: ${err}`);
res.status(500).json({ error: 'Error fetching kbs', details: err.message });
}
});
};

View File

@@ -0,0 +1,133 @@
import jwt from 'jsonwebtoken';
import { System } from '../schema.js';
const adminAuth = {
username: process.env.ADMIN_USER,
password: process.env.ADMIN_PASS
};
const authSecret = process.env.ADMIN_SECRET;
const postParent = () => {
fetch(`${process.env.PARENT_URL}/api/system/updateEnv`, {
headers: {
rootkey: process.env.PARENT_ROOT_KEY
}
});
};
export const useSystemRoute = (app) => {
app.post('/api/login', (req, res) => {
if (!adminAuth.username || !adminAuth.password) {
res.status(401).end('Server not set env: ADMIN_USER, ADMIN_PASS');
return;
}
const { username, password } = req.body;
if (username === adminAuth.username && password === adminAuth.password) {
// 用户名和密码都正确返回token
const token = jwt.sign(
{
username,
platform: 'admin'
},
authSecret,
{
expiresIn: '2h'
}
);
res.json({
username,
token: token,
expiredAt: new Date().valueOf() + 2 * 60 * 60 * 1000
});
} else {
res.status(401).end('username or password incorrect');
}
});
app.get('/system', auth(), async (req, res) => {
try {
const data = await System.find();
const totalCount = await System.countDocuments();
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(
data.map((item) => {
const obj = item.toObject();
return {
...obj,
id: obj._id
};
})
);
} catch (error) {
console.log(error);
res.status(500).json({ error: 'Error creating system env' });
}
});
app.post('/system', auth(), async (req, res) => {
try {
await System.create({
...req.body,
sensitiveCheck: req.body.sensitiveCheck === 'true'
});
postParent();
res.json({});
} catch (error) {
res.status(500).json({ error: 'Error creating system env' });
}
});
app.put('/system/:id', auth(), async (req, res) => {
try {
const _id = req.params.id;
await System.findByIdAndUpdate(_id, {
...req.body,
sensitiveCheck: req.body.sensitiveCheck === 'true'
});
postParent();
res.json({});
} catch (error) {
res.status(500).json({ error: 'Error updating system env' });
}
});
app.delete('/system/:id', auth(), async (req, res) => {
try {
const _id = req.params.id;
await System.findByIdAndDelete(_id);
res.json({});
} catch (error) {
res.status(500).json({ error: 'Error updating system env' });
}
});
};
export const auth = () => {
return (req, res, next) => {
try {
const authorization = req.headers.authorization;
if (!authorization) {
return next(new Error("unAuthorization"))
}
const token = authorization.slice('Bearer '.length);
const payload = jwt.verify(token, authSecret);
if (typeof payload === 'string') {
res.status(401).end('payload type error');
return;
}
if (payload.platform !== 'admin') {
res.status(401).end('Payload invalid');
return;
}
next();
} catch (err) {
res.status(401).end(String(err));
}
};
};

182
admin/service/route/user.js Normal file
View File

@@ -0,0 +1,182 @@
import { User, Pay } from '../schema.js';
import dayjs from 'dayjs';
import { auth } from './system.js';
import crypto from 'crypto';
// 加密
const hashPassword = (psw) => {
return crypto.createHash('sha256').update(psw).digest('hex');
};
export const useUserRoute = (app) => {
// 统计近 30 天注册用户数量
app.get('/users/data', auth(), async (req, res) => {
try {
const day = 60;
let startCount = await User.countDocuments({
createTime: { $lt: new Date(Date.now() - day * 24 * 60 * 60 * 1000) }
});
const usersRaw = await User.aggregate([
{ $match: { createTime: { $gte: new Date(Date.now() - day * 24 * 60 * 60 * 1000) } } },
{
$group: {
_id: {
year: { $year: '$createTime' },
month: { $month: '$createTime' },
day: { $dayOfMonth: '$createTime' }
},
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day' } },
count: 1
}
},
{ $sort: { date: 1 } }
]);
const countResult = usersRaw.map((item) => {
const increaseRate = `${((item.count / startCount) * 100).toFixed(2)}%`;
startCount += item.count;
return {
date: item.date,
count: startCount,
increase: item.count,
increaseRate
};
});
res.json(countResult);
} catch (err) {
console.log(`Error fetching users: ${err}`);
res.status(500).json({ error: 'Error fetching users' });
}
});
// 获取用户列表
app.get('/users', auth(), async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || 'createTime';
const username = req.query.username || '';
const where = {
username: { $regex: username, $options: 'i' }
};
const usersRaw = await User.find(where)
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const users = usersRaw.map((user) => {
const obj = user.toObject();
return {
...obj,
id: obj._id,
createTime: dayjs(obj.createTime).format('YYYY/MM/DD HH:mm'),
password: ''
};
});
const totalCount = await User.countDocuments(where);
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(users);
} catch (err) {
console.log(`Error fetching users: ${err}`);
res.status(500).json({ error: 'Error fetching users' });
}
});
// 创建用户
app.post('/users', auth(), async (req, res) => {
try {
const { username, password, balance } = req.body;
if (!username || !password || !balance) {
return res.status(400).json({ error: 'Invalid user information' });
}
const existingUser = await User.findOne({ username });
if (existingUser) {
return res.status(400).json({ error: 'Username already exists' });
}
const result = await User.create({
username,
password,
balance
});
res.json(result);
} catch (err) {
console.log(`Error creating user: ${err}`);
res.status(500).json({ error: 'Error creating user' });
}
});
// 修改用户信息
app.put('/users/:id', auth(), async (req, res) => {
try {
const _id = req.params.id;
let { password, balance = 0 } = req.body;
const result = await User.findByIdAndUpdate(_id, {
...(password && { password: hashPassword(hashPassword(password)) }),
...(balance && { balance })
});
res.json(result);
} catch (err) {
console.log(`Error updating user: ${err}`);
res.status(500).json({ error: 'Error updating user' });
}
});
// 新增: 获取 pays 列表
app.get('/pays', auth(), async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const userId = req.query.userId || '';
const where = userId ? { userId: userId } : {};
const paysRaw = await Pay.find({
...where
})
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const pays = [];
for (const payRaw of paysRaw) {
const pay = payRaw.toObject();
const orderedPay = {
id: pay._id.toString(),
userId: pay.userId,
price: pay.price,
orderId: pay.orderId,
status: pay.status,
createTime: dayjs(pay.createTime).format('YYYY/MM/DD HH:mm')
};
pays.push(orderedPay);
}
const totalCount = await Pay.countDocuments({
...where
});
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(pays);
} catch (err) {
console.log(`Error fetching pays: ${err}`);
res.status(500).json({ error: 'Error fetching pays', details: err.message });
}
});
};

111
admin/service/schema.js Normal file
View File

@@ -0,0 +1,111 @@
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
const mongoUrl = process.env.MONGODB_URI;
const mongoDBName = process.env.MONGODB_NAME;
if (!mongoUrl || !mongoDBName) {
throw new Error('db error');
}
mongoose
.connect(mongoUrl, {
dbName: mongoDBName,
bufferCommands: true,
maxPoolSize: 5,
minPoolSize: 1,
maxConnecting: 5
})
.then(() => console.log('Connected to MongoDB successfully!'))
.catch((err) => console.log(`Error connecting to MongoDB: ${err}`));
const userSchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
username: String,
password: String,
balance: Number,
promotion: {
rate: Number
},
openaiKey: String,
avatar: String,
createTime: Date
});
// 新增: 定义 pays 模型
const paySchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
price: Number,
orderId: String,
status: String,
createTime: Date,
__v: Number
});
// 新增: 定义 kb 模型
const kbSchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
avatar: String,
name: String,
tags: [String],
updateTime: Date,
__v: Number
});
const modelSchema = new mongoose.Schema({
userId: mongoose.Schema.Types.ObjectId,
name: String,
avatar: String,
status: String,
intro: String,
chat: {
relatedKbs: [mongoose.Schema.Types.ObjectId],
systemPrompt: String,
temperature: Number,
chatModel: String
},
share: {
topNum: Number,
isShare: Boolean,
isShareDetail: Boolean,
intro: String,
collection: Number
},
security: {
domain: [String],
contextMaxLen: Number,
contentMaxLen: Number,
expiredTime: Number,
maxLoadAmount: Number
},
updateTime: Date
});
const SystemSchema = new mongoose.Schema({
vectorMaxProcess: {
type: Number,
default: 10
},
qaMaxProcess: {
type: Number,
default: 10
},
pgIvfflatProbe: {
type: Number,
default: 10
},
sensitiveCheck: {
type: Boolean,
default: false
}
});
export const Model = mongoose.models['model'] || mongoose.model('model', modelSchema);
export const Kb = mongoose.models['kb'] || mongoose.model('kb', kbSchema);
export const User = mongoose.models['user'] || mongoose.model('user', userSchema);
export const Pay = mongoose.models['pay'] || mongoose.model('pay', paySchema);
export const System = mongoose.models['system'] || mongoose.model('system', SystemSchema);

140
admin/src/App.tsx Normal file
View File

@@ -0,0 +1,140 @@
import {
createTextField,
jsonServerProvider,
ListTable,
Resource,
Tushan,
fetchJSON,
TushanContextProps,
HTTPClient
} from 'tushan';
import { authProvider } from './auth';
import { userFields, payFields, kbFields, ModelFields, SystemFields } from './fields';
import { Dashboard } from './Dashboard';
import { IconUser, IconApps, IconBook, IconStamp } from 'tushan/icon';
import { i18nZhTranslation } from 'tushan/client/i18n/resources/zh';
const authStorageKey = 'tushan:auth';
const httpClient: HTTPClient = (url, options = {}) => {
try {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
const { token } = JSON.parse(window.localStorage.getItem(authStorageKey) ?? '{}');
(options.headers as Headers).set('Authorization', `Bearer ${token}`);
return fetchJSON(url, options);
} catch (err) {
return Promise.reject();
}
};
const dataProvider = jsonServerProvider(import.meta.env.VITE_PUBLIC_SERVER_URL, httpClient);
const i18n: TushanContextProps['i18n'] = {
languages: [
{
key: 'zh',
label: '简体中文',
translation: i18nZhTranslation
}
]
};
function App() {
return (
<Tushan
basename="/"
header={'FastGpt-Admin'}
i18n={i18n}
dataProvider={dataProvider}
authProvider={authProvider}
dashboard={<Dashboard />}
>
<Resource
name="users"
label="用户信息"
icon={<IconUser />}
list={
<ListTable
filter={[
createTextField('username', {
label: 'username'
})
]}
fields={userFields}
action={{ detail: true, edit: true }}
/>
}
/>
<Resource
name="models"
icon={<IconApps />}
label="应用"
list={
<ListTable
filter={[
createTextField('id', {
label: 'id'
}),
createTextField('name', {
label: 'name'
})
]}
fields={ModelFields}
action={{ detail: true, edit: true }}
/>
}
/>
<Resource
name="pays"
label="支付记录"
icon={<IconStamp />}
list={
<ListTable
filter={[
createTextField('userId', {
label: 'userId'
})
]}
fields={payFields}
action={{ detail: true }}
/>
}
/>
<Resource
name="kbs"
label="知识库"
icon={<IconBook />}
list={
<ListTable
filter={[
createTextField('name', {
label: 'name'
}),
createTextField('tag', {
label: 'tag'
})
]}
fields={kbFields}
action={{ detail: true }}
/>
}
/>
<Resource
name="system"
label="系统"
list={
<ListTable
fields={SystemFields}
action={{ detail: true, edit: true, create: true, delete: true }}
/>
}
/>
</Tushan>
);
}
export default App;

224
admin/src/Dashboard.tsx Normal file
View File

@@ -0,0 +1,224 @@
import { Card, Link, Space, Grid, Divider, Typography } from '@arco-design/web-react';
import { IconApps, IconUser, IconUserGroup } from 'tushan/icon';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
AreaChart,
Area
} from 'tushan/chart';
import dayjs from 'dayjs';
const authStorageKey = 'tushan:auth';
type UsersChartDataType = { count: number; date: string; increase: number; increaseRate: string };
export const Dashboard: React.FC = React.memo(() => {
const [userCount, setUserCount] = useState(0); //用户数量
const [kbCount, setkbCount] = useState(0);
const [modelCount, setmodelCount] = useState(0);
const [usersData, setUsersData] = useState<UsersChartDataType[]>([]);
useEffect(() => {
const baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL;
const { token } = JSON.parse(window.localStorage.getItem(authStorageKey) ?? '{}');
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
};
const fetchCounts = async () => {
const userResponse = await fetch(`${baseUrl}/users?_end=1`, {
headers
});
const kbResponse = await fetch(`${baseUrl}/kbs?_end=1`, {
headers
});
const modelResponse = await fetch(`${baseUrl}/models?_end=1`, {
headers
});
const userTotalCount = userResponse.headers.get('X-Total-Count');
const kbTotalCount = kbResponse.headers.get('X-Total-Count');
const modelTotalCount = modelResponse.headers.get('X-Total-Count');
if (userTotalCount) {
setUserCount(Number(userTotalCount));
}
if (kbTotalCount) {
setkbCount(Number(kbTotalCount));
}
if (modelTotalCount) {
setmodelCount(Number(modelTotalCount));
}
};
const fetchUserData = async () => {
const userResponse: UsersChartDataType[] = await fetch(`${baseUrl}/users/data`, {
headers
}).then((res) => res.json());
setUsersData(
userResponse.map((item) => ({
...item,
date: dayjs(item.date).format('MM/DD')
}))
);
};
fetchCounts();
fetchUserData();
}, []);
return (
<div>
<div>
<Space direction="vertical" style={{ width: '100%' }}>
<Card bordered={false}>
<Typography.Title heading={5}>FastGpt Admin</Typography.Title>
<Divider />
<Grid.Row justify="center">
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
{/* 把 userCount 传递给 DataItem 组件 */}
<DataItem icon={<IconUser />} title={'用户'} count={userCount} />
</Grid.Col>
<Divider type="vertical" style={{ height: 40 }} />
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
<DataItem icon={<IconUserGroup />} title={'知识库'} count={kbCount} />
</Grid.Col>
<Divider type="vertical" style={{ height: 40 }} />
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
<DataItem icon={<IconApps />} title={'应用'} count={modelCount} />
</Grid.Col>
</Grid.Row>
<Divider />
<UserChart data={usersData} />
</Card>
</Space>
</div>
</div>
);
});
Dashboard.displayName = 'Dashboard';
const DashboardItem = React.memo(
(props: { title: string; href?: string; children: React.ReactNode }) => {
const { t } = useTranslation();
return (
<Card
title={props.title}
extra={
props.href && (
<Link target="_blank" href={props.href}>
{t('tushan.dashboard.more')}
</Link>
)
}
bordered={false}
style={{ overflow: 'hidden' }}
>
{props.children}
</Card>
);
}
);
DashboardItem.displayName = 'DashboardItem';
const DataItem = React.memo((props: { icon: React.ReactElement; title: string; count: number }) => {
return (
<Space>
<div
style={{
fontSize: 20,
padding: '0.5rem',
borderRadius: '9999px',
border: '1px solid #ccc',
width: 24,
height: 24,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
>
{props.icon}
</div>
<div>
<div style={{ fontWeight: 700 }}>{props.title}</div>
<div>{props.count}</div>
</div>
</Space>
);
});
DataItem.displayName = 'DataItem';
const CustomTooltip = ({ active, payload }: any) => {
const data = payload?.[0]?.payload as UsersChartDataType;
if (active && data) {
return (
<div
style={{
background: 'white',
padding: '5px 8px',
borderRadius: '8px',
boxShadow: '2px 2px 5px rgba(0,0,0,0.2)'
}}
>
<p className="label">
count: <strong>{data.count}</strong>
</p>
<p className="label">
increase: <strong>{data.increase}</strong>
</p>
<p className="label">
increaseRate: <strong>{data.increaseRate}</strong>
</p>
</div>
);
}
return null;
};
const UserChart = ({ data }: { data: UsersChartDataType[] }) => {
return (
<ResponsiveContainer width="100%" height={320}>
<AreaChart
width={730}
height={250}
data={data}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
</linearGradient>
<linearGradient id="colorPv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#82ca9d" stopOpacity={0.8} />
<stop offset="95%" stopColor="#82ca9d" stopOpacity={0} />
</linearGradient>
</defs>
<XAxis dataKey="date" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip content={<CustomTooltip />} />
<Area
type="monotone"
dataKey="count"
stroke="#82ca9d"
fillOpacity={1}
fill="url(#colorPv)"
/>
</AreaChart>
</ResponsiveContainer>
);
};

5
admin/src/auth.ts Normal file
View File

@@ -0,0 +1,5 @@
import { createAuthProvider, type AuthProvider } from 'tushan';
export const authProvider: AuthProvider = createAuthProvider({
loginUrl: `${import.meta.env.VITE_PUBLIC_SERVER_URL}/api/login`
});

52
admin/src/fields.ts Normal file
View File

@@ -0,0 +1,52 @@
import { createTextField, createNumberField } from 'tushan';
export const userFields = [
createTextField('id', { label: 'ID' }),
createTextField('username', { label: '用户名', edit: { hidden: true } }),
createNumberField('balance', { label: '余额', list: { sort: true } }),
createTextField('createTime', { label: 'Create Time', list: { sort: true } }),
createTextField('password', { label: '密码', list: { hidden: true } })
];
export const payFields = [
createTextField('id', { label: 'ID' }),
createTextField('userId', { label: '用户Id' }),
createNumberField('price', { label: '支付金额' }),
createTextField('orderId', { label: 'orderId' }),
createTextField('status', { label: '状态' }),
createTextField('createTime', { label: 'Create Time', list: { sort: true } })
];
export const kbFields = [
createTextField('id', { label: 'ID' }),
createTextField('userId', { label: '所属用户', edit: { hidden: true } }),
createTextField('name', { label: '知识库' }),
createTextField('tags', { label: 'Tags' })
];
export const ModelFields = [
createTextField('id', { label: 'ID' }),
createTextField('userId', { label: '所属用户', list: { hidden: true }, edit: { hidden: true } }),
createTextField('name', { label: '名字' }),
createTextField('model', { label: '模型', edit: { hidden: true } }),
createTextField('share.collection', { label: '收藏数', list: { sort: true } }),
createTextField('share.topNum', { label: '置顶等级', list: { sort: true } }),
createTextField('share.isShare', { label: '是否分享(true,false)' }),
createTextField('intro', { label: '介绍', list: { width: 400 } }),
createTextField('relatedKbs', { label: '引用的知识库', list: { hidden: true } }),
createTextField('temperature', { label: '温度' }),
createTextField('systemPrompt', {
label: '提示词',
list: {
width: 400,
hidden: true
}
})
];
export const SystemFields = [
createTextField('vectorMaxProcess', { label: '向量最大进程' }),
createTextField('qaMaxProcess', { label: 'qa最大进程' }),
createTextField('pgIvfflatProbe', { label: 'pg 探针数量' }),
createTextField('sensitiveCheck', { label: '敏感词校验(true,false)' })
];

5
admin/src/main.tsx Normal file
View File

@@ -0,0 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);

1
admin/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

21
admin/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
admin/tsconfig.node.json Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

7
admin/vite.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});

33
client/.env.template Normal file
View File

@@ -0,0 +1,33 @@
# 运行端口,如果不是 3000 口运行,需要改成其他的。注意:不是改了这个变量就会变成其他端口,而是因为改成其他端口,才用这个变量。
PORT=3000
# database max link
DB_MAX_LINK=5
# 代理
# AXIOS_PROXY_HOST=127.0.0.1
# AXIOS_PROXY_PORT=7890
# email
MY_MAIL=xxxx@qq.com
MAILE_CODE=xxxx
# ali ems
aliAccessKeyId=xxxx
aliAccessKeySecret=xxxx
aliSignName=xxxx
aliTemplateCode=xxxx
# token
TOKEN_KEY=dfdasfdas
# root key, 最高权限
ROOT_KEY=fdafasd
# 使用 oneapi
# ONEAPI_URL=https://xxxx.cloud.sealos.io/v1
# ONEAPI_KEY=sk-xxxx
# openai 的基本地址(国外的可以忽略,默认走 api.openai.com。不用 oneapi 的话需要下面 2 个参数,用户的 key 也会走下面的参数
OPENAI_BASE_URL=https://xxxx.cloud.sealos.io/openai/v1
OPENAIKEY=sk-xxxx
# db
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
MONGODB_NAME=fastgpt
PG_HOST=0.0.0.0
PG_PORT=8100
PG_USER=root
PG_PASSWORD=psw
PG_DB_NAME=dbname

31
client/.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
# dependencies
node_modules/
# next.js
.next/
out/
# production
build/
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
platform.json
testApi/
local/
.husky/

View File

@@ -6,6 +6,7 @@ WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json pnpm-lock.yaml* ./
RUN pnpm config set registry https://registry.npmmirror.com/
RUN \
[ -f pnpm-lock.yaml ] && pnpm install || \
(echo "Lockfile not found." && exit 1)
@@ -52,6 +53,8 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]

5
client/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

84
client/package.json Normal file
View File

@@ -0,0 +1,84 @@
{
"name": "fastgpt",
"version": "3.7",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@alicloud/dysmsapi20170525": "^2.0.23",
"@alicloud/openapi-client": "^0.4.5",
"@alicloud/tea-util": "^1.4.5",
"@chakra-ui/icons": "^2.0.17",
"@chakra-ui/react": "^2.7.0",
"@chakra-ui/system": "^2.5.8",
"@dqbd/tiktoken": "^1.0.7",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@next/font": "13.1.6",
"@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0",
"axios": "^1.3.3",
"cookie": "^0.5.0",
"crypto": "^1.0.1",
"date-fns": "^2.30.0",
"dayjs": "^1.11.7",
"formidable": "^2.1.1",
"framer-motion": "^9.0.6",
"hyperdown": "^2.4.29",
"immer": "^9.0.19",
"jsonwebtoken": "^9.0.0",
"lodash": "^4.17.21",
"mammoth": "^1.5.1",
"mermaid": "^10.2.3",
"mongoose": "^6.10.0",
"nanoid": "^4.0.1",
"next": "13.1.6",
"nextjs-cors": "^2.1.2",
"nodemailer": "^6.9.1",
"nprogress": "^0.2.0",
"openai": "^3.3.0",
"papaparse": "^5.4.1",
"pg": "^8.10.0",
"react": "18.2.0",
"react-day-picker": "^8.7.1",
"react-dom": "18.2.0",
"react-hook-form": "^7.43.1",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",
"rehype-katex": "^6.0.2",
"remark-breaks": "^3.0.3",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"request-ip": "^3.3.0",
"sass": "^1.58.3",
"tunnel": "^0.0.6",
"wxpay-v3": "^3.0.2",
"zustand": "^4.3.5"
},
"devDependencies": {
"@svgr/webpack": "^6.5.1",
"@types/cookie": "^0.5.1",
"@types/formidable": "^2.0.5",
"@types/jsonwebtoken": "^9.0.1",
"@types/lodash": "^4.14.191",
"@types/node": "18.14.0",
"@types/nodemailer": "^6.4.7",
"@types/papaparse": "^5.3.7",
"@types/pg": "^8.6.6",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@types/react-syntax-highlighter": "^15.5.6",
"@types/request-ip": "^0.0.37",
"@types/tunnel": "^0.0.3",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"typescript": "4.9.5"
},
"engines": {
"node": ">=18.0.0"
}
}

12210
client/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
### 常见问题
**Git 地址**: [项目地址,完全开源,随便用。](https://github.com/c121914yu/FastGPT)
**问题文档**: [先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
**价格表**
如果使用了自己的 Api Key网页上 openai 模型聊天不会计费。可以在账号页,看到详细账单。
| 计费项 | 价格: 元/ 1K tokens包含上下文|
| --- | --- |
| 知识库 - 索引 | 0.001 |
| chatgpt - 对话 | 0.015 |
| chatgpt16K - 对话 | 0.015 |
| gpt4 - 对话 | 0.1 |
| 文件拆分 | 0.015 |
**其他问题**
| 交流群 | 小助手 |
| ----------------------- | -------------------- |
| ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) |

View File

@@ -0,0 +1,9 @@
接受一个 csv 文件,表格头包含 question 和 answer。question 代表问题answer 代表答案。
导入前会进行去重,如果问题和答案完全相同,则不会被导入,所以最终导入的内容可能会比文件的内容少。但是,对于带有换行的内容,目前无法去重。
### 请保证 csv 文件为 utf-8 编码
| question | answer |
| ------------- | ------------------------------------------------------ |
| 什么是 laf | laf 是一个云函数开发平台…… |
| 什么是 sealos | Sealos 是以 kubernetes 为内核的云操作系统发行版,可以…… |

View File

@@ -0,0 +1,34 @@
## 欢迎使用 Fast GPT
### 项目开源
FastGpt 项目完全开源,可随意私有化部署,去除平台风险忧虑。项目地址:[Git 仓库](https://github.com/c121914yu/FastGPT)
### 开始使用知识库
1. AI 助手详情里,有一个模型效果。打开知识库搜索开关即可使用知识库搜索功能。
2. 导入知识库数据。可以手动输入或文件导入。
3. 开始对话。
4. 对话结束后,会看到聊天下方有一个“查看提示词”,可以看到搜索到了哪些内容。
注意使用知识库模型对话时tokens 消耗会加快。
### 价格表
如果使用了自己的 Api Key网页上 openai 模型聊天不会计费。可以在账号页,看到详细账单。
| 计费项 | 价格: 元/ 1K tokens包含上下文|
| --- | --- |
| 知识库 - 索引 | 0.001 |
| chatgpt - 对话 | 0.015 |
| chatgpt16K - 对话 | 0.015 |
| gpt4 - 对话 | 0.1 |
| 文件拆分 | 0.015 |
### 交流群/问题反馈
如果群满了,可加个小助手,定时拉
wx 号: YNyiqi
| 交流群 | 小助手 |
| ------------------------------------------------- | ---------------------------------------------- |
| ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) |

View File

@@ -0,0 +1,7 @@
### Fast GPT V3.9
1. 限时优惠活动,更低价的 tokens。
2. 新增 - 直接分段训练,可调节段落大小。
3. 优化 - tokens 计算性能。
4. 优化 - key 池管理,结合 one-api 项目,实现更方便的 key 池管理,具体参考[docker 部署 FastGpt](https://github.com/c121914yu/FastGPT/blob/main/docs/deploy/docker.md)
5. 新增 - V2 版 OpenAPI可以在任意第三方套壳 ChatGpt 项目中直接使用 FastGpt 的应用,注意!是直接,不需要改任何代码。具体参考[API 文档中《在第三方应用中使用 FastGpt》](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh)

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

BIN
client/public/icon/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,8 @@
var _hmt = _hmt || [];
(function () {
const hm = document.createElement('script');
hm.src = 'https://hm.baidu.com/hm.js?a5357e9dab086658bac0b6faf148882e';
const s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();

File diff suppressed because one or more lines are too long

80
client/src/api/chat.ts Normal file
View File

@@ -0,0 +1,80 @@
import { GET, POST, DELETE, PUT } from './request';
import type { HistoryItemType } from '@/types/chat';
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import { RequestPaging } from '../types/index';
import type { ShareChatSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/model';
import { Obj2Query } from '@/utils/tools';
import type { QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
/**
* 获取初始化聊天内容
*/
export const getInitChatSiteInfo = (modelId: '' | string, chatId: '' | string) =>
GET<InitChatResponse>(`/chat/init?modelId=${modelId}&chatId=${chatId}`);
/**
* 获取历史记录
*/
export const getChatHistory = (data: RequestPaging) =>
POST<HistoryItemType[]>('/chat/history/getHistory', data);
/**
* 删除一条历史记录
*/
export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${id}`);
/**
* get history quotes
*/
export const getHistoryQuote = (params: { chatId: string; historyId: string }) =>
GET<(QuoteItemType & { _id: string })[]>(`/chat/history/getHistoryQuote`, params);
/**
* update history quote status
*/
export const updateHistoryQuote = (params: {
chatId: string;
historyId: string;
quoteId: string;
sourceText: string;
}) => GET(`/chat/history/updateHistoryQuote`, params);
/**
* 删除一句对话
*/
export const delChatRecordByIndex = (chatId: string, contentId: string) =>
DELETE(`/chat/delChatRecordByContentId?chatId=${chatId}&contentId=${contentId}`);
/**
* 修改历史记录: 标题/置顶
*/
export const putChatHistory = (data: UpdateHistoryProps) =>
PUT('/chat/history/updateChatHistory', data);
/**
* create a shareChat
*/
export const createShareChat = (
data: ShareChatEditType & {
modelId: string;
}
) => POST<string>(`/chat/shareChat/create`, data);
/**
* get shareChat
*/
export const getShareChatList = (modelId: string) =>
GET<ShareChatSchema[]>(`/chat/shareChat/list?modelId=${modelId}`);
/**
* delete a shareChat
*/
export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`);
/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string; password: string }) =>
GET<InitShareChatResponse>(`/chat/shareChat/init?${Obj2Query(data)}`);

103
client/src/api/fetch.ts Normal file
View File

@@ -0,0 +1,103 @@
import { Props, ChatResponseType } from '@/pages/api/openapi/v1/chat/completions';
import { sseResponseEventEnum } from '@/constants/chat';
import { getErrText } from '@/utils/tools';
import { parseStreamChunk } from '@/utils/adapt';
interface StreamFetchProps {
data: Props;
onMessage: (text: string) => void;
abortSignal: AbortController;
}
export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) =>
new Promise<ChatResponseType & { responseText: string; errMsg: string }>(
async (resolve, reject) => {
try {
const response = await window.fetch('/api/openapi/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
signal: abortSignal.signal,
body: JSON.stringify({
...data,
stream: true
})
});
if (response.status !== 200) {
const err = await response.json();
return reject(err);
}
if (!response?.body) {
throw new Error('Request Error');
}
const reader = response.body?.getReader();
// response data
let responseText = '';
let newChatId = '';
let quoteLen = 0;
let errMsg = '';
const read = async () => {
try {
const { done, value } = await reader.read();
if (done) {
if (response.status === 200) {
return resolve({
responseText,
newChatId,
quoteLen,
errMsg
});
} else {
return reject('响应过程出现异常~');
}
}
const chunkResponse = parseStreamChunk(value);
chunkResponse.forEach((item) => {
// parse json data
const data = (() => {
try {
return JSON.parse(item.data);
} catch (error) {
return item.data;
}
})();
if (item.event === sseResponseEventEnum.answer && data !== '[DONE]') {
const answer: string = data?.choices?.[0].delta.content || '';
onMessage(answer);
responseText += answer;
} else if (item.event === sseResponseEventEnum.chatResponse) {
const chatResponse = data as ChatResponseType;
newChatId = chatResponse.newChatId;
quoteLen = chatResponse.quoteLen || 0;
} else if (item.event === sseResponseEventEnum.error) {
errMsg = getErrText(data, '流响应错误');
}
});
read();
} catch (err: any) {
if (err?.message === 'The user aborted a request.') {
return resolve({
responseText,
newChatId,
quoteLen,
errMsg
});
}
reject(getErrText(err, '请求异常'));
}
};
read();
} catch (err: any) {
console.log(err);
reject(getErrText(err, '请求异常'));
}
}
);

44
client/src/api/model.ts Normal file
View File

@@ -0,0 +1,44 @@
import { GET, POST, DELETE, PUT } from './request';
import type { ModelSchema } from '@/types/mongoSchema';
import type { ModelUpdateParams } from '@/types/model';
import { RequestPaging } from '../types/index';
import type { ModelListResponse } from './response/model';
/**
* 获取模型列表
*/
export const getMyModels = () => GET<ModelListResponse>('/model/list');
/**
* 创建一个模型
*/
export const postCreateModel = (data: { name: string }) => POST<string>('/model/create', data);
/**
* 根据 ID 删除模型
*/
export const delModelById = (id: string) => DELETE(`/model/del?modelId=${id}`);
/**
* 根据 ID 获取模型
*/
export const getModelById = (id: string) => GET<ModelSchema>(`/model/detail?modelId=${id}`);
/**
* 根据 ID 更新模型
*/
export const putModelById = (id: string, data: ModelUpdateParams) =>
PUT(`/model/update?modelId=${id}`, data);
/* 共享市场 */
/**
* 获取共享市场模型
*/
export const getShareModelList = (data: { searchText?: string } & RequestPaging) =>
POST(`/model/share/getModels`, data);
/**
* 收藏/取消收藏模型
*/
export const triggerModelCollection = (modelId: string) =>
POST<number>(`/model/share/collection?modelId=${modelId}`);

View File

@@ -0,0 +1,93 @@
import { GET, POST, PUT, DELETE } from '../request';
import type { KbItemType } from '@/types/plugin';
import { RequestPaging } from '@/types/index';
import { TrainingModeEnum } from '@/constants/plugin';
import { type QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
import {
Props as PushDataProps,
Response as PushDateResponse
} from '@/pages/api/openapi/kb/pushData';
import {
Props as SearchTestProps,
Response as SearchTestResponse
} from '@/pages/api/openapi/kb/searchTest';
export type KbUpdateParams = {
id: string;
name: string;
tags: string;
avatar: string;
};
/* knowledge base */
export const getKbList = () => GET<KbItemType[]>(`/plugins/kb/list`);
export const getKbById = (id: string) => GET<KbItemType>(`/plugins/kb/detail?id=${id}`);
export const postCreateKb = (data: { name: string }) => POST<string>(`/plugins/kb/create`, data);
export const putKbById = (data: KbUpdateParams) => PUT(`/plugins/kb/update`, data);
export const delKbById = (id: string) => DELETE(`/plugins/kb/delete?id=${id}`);
/* kb data */
type GetKbDataListProps = RequestPaging & {
kbId: string;
searchText: string;
};
export const getKbDataList = (data: GetKbDataListProps) =>
POST(`/plugins/kb/data/getDataList`, data);
/**
* 获取导出数据(不分页)
*/
export const getExportDataList = (kbId: string) =>
GET<[string, string, string][]>(
`/plugins/kb/data/exportModelData`,
{ kbId },
{
timeout: 600000
}
);
/**
* 获取模型正在拆分数据的数量
*/
export const getTrainingData = (data: { kbId: string; init: boolean }) =>
POST<{
qaListLen: number;
vectorListLen: number;
}>(`/plugins/kb/data/getTrainingData`, data);
export const getKbDataItemById = (dataId: string) =>
GET<QuoteItemType>(`/plugins/kb/data/getDataById`, { dataId });
/**
* 直接push数据
*/
export const postKbDataFromList = (data: PushDataProps) =>
POST<PushDateResponse>(`/openapi/kb/pushData`, data);
/**
* 更新一条数据
*/
export const putKbDataById = (data: { dataId: string; a: string; q?: string }) =>
PUT('/openapi/kb/updateData', data);
/**
* 删除一条知识库数据
*/
export const delOneKbDataByDataId = (dataId: string) =>
DELETE(`/openapi/kb/delDataById?dataId=${dataId}`);
/**
* 拆分数据
*/
export const postSplitData = (data: {
kbId: string;
chunks: string[];
prompt: string;
mode: `${TrainingModeEnum}`;
}) => POST(`/openapi/text/pushData`, data);
export const searchText = (data: SearchTestProps) =>
POST<SearchTestResponse>(`/openapi/kb/searchTest`, data);

View File

@@ -1,10 +1,11 @@
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { getToken, clearToken } from '@/utils/user';
import { clearCookie } from '@/utils/user';
import { TOKEN_ERROR_CODE } from '@/service/errorCode';
interface ConfigType {
headers?: { [key: string]: string };
hold?: boolean;
timeout?: number;
}
interface ResponseDataType {
code: number;
@@ -17,7 +18,7 @@ interface ResponseDataType {
*/
function requestStart(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig {
if (config.headers) {
config.headers.Authorization = getToken();
// config.headers.Authorization = getToken();
}
return config;
@@ -54,13 +55,13 @@ function responseError(err: any) {
if (typeof err === 'string') {
return Promise.reject({ message: err });
}
if (err.response) {
// 有报错响应
const res = err.response;
if (res.data.code in TOKEN_ERROR_CODE) {
clearToken();
return Promise.reject({ message: 'token过期重新登录' });
}
// 有报错响应
if (err?.code in TOKEN_ERROR_CODE) {
clearCookie();
window.location.replace(
`/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`
);
return Promise.reject({ message: 'token过期重新登录' });
}
return Promise.reject(err);
}

28
client/src/api/response/chat.d.ts vendored Normal file
View File

@@ -0,0 +1,28 @@
import type { ChatPopulate, ModelSchema } from '@/types/mongoSchema';
import type { ChatItemType } from '@/types/chat';
export interface InitChatResponse {
chatId: string;
modelId: string;
systemPrompt?: string;
limitPrompt?: string;
model: {
name: string;
avatar: string;
intro: string;
canUse: boolean;
};
chatModel: ModelSchema['chat']['chatModel']; // 对话模型名
history: ChatItemType[];
}
export interface InitShareChatResponse {
maxContext: number;
userAvatar: string;
model: {
name: string;
avatar: string;
intro: string;
};
chatModel: ModelSchema['chat']['chatModel']; // 对话模型名
}

6
client/src/api/response/model.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
import { ModelListItemType } from '@/types/model';
export type ModelListResponse = {
myModels: ModelListItemType[];
myCollectionModels: ModelListItemType[];
};

View File

@@ -1,7 +1,6 @@
import type { UserType } from '@/types/user';
import type { PromotionRecordSchema } from '@/types/mongoSchema';
export interface ResLogin {
token: string;
user: UserType;
}

9
client/src/api/system.ts Normal file
View File

@@ -0,0 +1,9 @@
import { GET, POST, PUT } from './request';
import type { ChatModelItemType } from '@/constants/model';
import type { InitDateResponse } from '@/pages/api/system/getInitData';
export const getInitData = () => GET<InitDateResponse>('/system/getInitData');
export const getSystemModelList = () => GET<ChatModelItemType[]>('/system/getModels');
export const uploadImg = (base64Img: string) => POST<string>('/system/uploadImage', { base64Img });

View File

@@ -2,18 +2,15 @@ import { GET, POST, PUT } from './request';
import { createHashPassword, Obj2Query } from '@/utils/tools';
import { ResLogin, PromotionRecordType } from './response/user';
import { UserAuthTypeEnum } from '@/constants/common';
import { UserType, UserUpdateParams } from '@/types/user';
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
import type { PagingData, RequestPaging } from '@/types';
import { BillSchema, PaySchema } from '@/types/mongoSchema';
import { adaptBill } from '@/utils/adapt';
import { informSchema, PaySchema } from '@/types/mongoSchema';
export const sendAuthCode = ({
username,
type
}: {
export const sendAuthCode = (data: {
username: string;
type: `${UserAuthTypeEnum}`;
}) => GET('/user/sendAuthCode', { username, type });
googleToken: string;
}) => POST('/user/sendAuthCode', data);
export const getTokenLogin = () => GET<UserType>('/user/tokenLogin');
@@ -64,13 +61,12 @@ export const postLogin = ({ username, password }: { username: string; password:
password: createHashPassword(password)
});
export const loginOut = () => GET('/user/loginout');
export const putUserInfo = (data: UserUpdateParams) => PUT('/user/update', data);
export const getUserBills = (data: RequestPaging) =>
GET<PagingData<BillSchema>>(`/user/getBill?${Obj2Query(data)}`).then((res) => ({
...res,
data: res.data.map((bill) => adaptBill(bill))
}));
POST<PagingData<UserBillType>>(`/user/getBill`, data);
export const getPayOrders = () => GET<PaySchema[]>(`/user/getPayOrders`);
@@ -85,3 +81,9 @@ export const checkPayResult = (payId: string) => GET<number>(`/user/checkPayResu
/* promotion records */
export const getPromotionRecords = (data: RequestPaging) =>
GET<PromotionRecordType>(`/user/promotion/getPromotions?${Obj2Query(data)}`);
export const getInforms = (data: RequestPaging) =>
POST<PagingData<informSchema>>(`/user/inform/list`, data);
export const getUnreadCount = () => GET<number>(`/user/inform/countUnread`);
export const readInform = (id: string) => GET(`/user/inform/read`, { id });

View File

@@ -0,0 +1,160 @@
import React, { useState } from 'react';
import {
Box,
Button,
Modal,
ModalOverlay,
ModalContent,
Flex,
ModalFooter,
ModalBody,
ModalCloseButton,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
IconButton
} from '@chakra-ui/react';
import { getOpenApiKeys, createAOpenApiKey, delOpenApiById } from '@/api/openapi';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading';
import dayjs from 'dayjs';
import { AddIcon, DeleteIcon } from '@chakra-ui/icons';
import { getErrText, useCopyData } from '@/utils/tools';
import { useToast } from '@/hooks/useToast';
import MyIcon from '../Icon';
const APIKeyModal = ({ onClose }: { onClose: () => void }) => {
const { Loading } = useLoading();
const { toast } = useToast();
const {
data: apiKeys = [],
isLoading: isGetting,
refetch
} = useQuery(['getOpenApiKeys'], getOpenApiKeys);
const [apiKey, setApiKey] = useState('');
const { copyData } = useCopyData();
const { mutate: onclickCreateApiKey, isLoading: isCreating } = useMutation({
mutationFn: () => createAOpenApiKey(),
onSuccess(res) {
setApiKey(res);
refetch();
},
onError(err) {
toast({
status: 'warning',
title: getErrText(err)
});
}
});
const { mutate: onclickRemove, isLoading: isDeleting } = useMutation({
mutationFn: async (id: string) => delOpenApiById(id),
onSuccess() {
refetch();
}
});
return (
<Modal isOpen onClose={onClose}>
<ModalOverlay />
<ModalContent w={'600px'} maxW={'90vw'} position={'relative'}>
<Box py={3} px={5}>
<Box fontWeight={'bold'} fontSize={'2xl'}>
API
</Box>
<Box fontSize={'sm'} color={'myGray.600'}>
API 使~
</Box>
</Box>
<ModalCloseButton />
<ModalBody minH={'300px'} maxH={['70vh', '500px']} overflow={'overlay'}>
<TableContainer mt={2} position={'relative'}>
<Table>
<Thead>
<Tr>
<Th>Api Key</Th>
<Th></Th>
<Th>使</Th>
<Th />
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{apiKeys.map(({ id, apiKey, createTime, lastUsedTime }) => (
<Tr key={id}>
<Td>{apiKey}</Td>
<Td>{dayjs(createTime).format('YYYY/MM/DD HH:mm:ss')}</Td>
<Td>
{lastUsedTime
? dayjs(lastUsedTime).format('YYYY/MM/DD HH:mm:ss')
: '没有使用过'}
</Td>
<Td>
<IconButton
icon={<DeleteIcon />}
size={'xs'}
aria-label={'delete'}
variant={'base'}
colorScheme={'gray'}
onClick={() => onclickRemove(id)}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</ModalBody>
<ModalFooter>
<Button
variant="base"
leftIcon={<AddIcon color={'myGray.600'} fontSize={'sm'} />}
onClick={() => onclickCreateApiKey()}
>
</Button>
</ModalFooter>
<Loading loading={isGetting || isCreating || isDeleting} fixed={false} />
</ModalContent>
<Modal isOpen={!!apiKey} onClose={() => setApiKey('')}>
<ModalOverlay />
<ModalContent w={'400px'} maxW={'90vw'}>
<Box py={3} px={5}>
<Box fontWeight={'bold'} fontSize={'2xl'}>
API
</Box>
<Box fontSize={'sm'} color={'myGray.600'}>
~
</Box>
</Box>
<ModalCloseButton />
<ModalBody>
<Flex
bg={'myGray.100'}
px={3}
py={2}
cursor={'pointer'}
onClick={() => copyData(apiKey)}
>
<Box flex={1}>{apiKey}</Box>
<MyIcon name={'copy'} w={'16px'}></MyIcon>
</Flex>
</ModalBody>
<ModalFooter>
<Button variant="base" onClick={() => setApiKey('')}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Modal>
);
};
export default APIKeyModal;

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { Image } from '@chakra-ui/react';
import type { ImageProps } from '@chakra-ui/react';
import { LOGO_ICON } from '@/constants/chat';
const Avatar = ({ w = '30px', ...props }: ImageProps) => {
return (
<Image
fallbackSrc={LOGO_ICON}
borderRadius={'50%'}
objectFit={'cover'}
alt=""
w={w}
h={w}
p={'1px'}
{...props}
/>
);
};
export default Avatar;

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
const Badge = ({
children,
isDot = false,
max = 99,
count = 0
}: {
children: React.ReactNode;
isDot?: boolean;
max?: number;
count?: number;
}) => {
return (
<Box position={'relative'}>
{children}
{count > 0 && (
<Box position={'absolute'} right={0} top={0} transform={'translate(70%,-50%)'}>
{isDot ? (
<Box w={'5px'} h={'5px'} bg={'myRead.600'} borderRadius={'20px'}></Box>
) : (
<Box
color={'white'}
bg={'myRead.600'}
lineHeight={0.9}
borderRadius={'100px'}
px={'4px'}
py={'2px'}
fontSize={'12px'}
border={'1px solid white'}
>
{count > max ? `${max}+` : count}
</Box>
)}
</Box>
)}
</Box>
);
};
export default Badge;

View File

@@ -0,0 +1,4 @@
.datePicker {
--rdp-background-color: #d6e8ff;
--rdp-accent-color: #0000ff;
}

View File

@@ -0,0 +1,121 @@
import React, { useState, useMemo, useRef } from 'react';
import { Box, Card, Flex, useTheme, useOutsideClick, Button } from '@chakra-ui/react';
import { addDays, format } from 'date-fns';
import { type DateRange, DayPicker } from 'react-day-picker';
import MyIcon from '../Icon';
import 'react-day-picker/dist/style.css';
import styles from './index.module.scss';
import zhCN from 'date-fns/locale/zh-CN';
const DateRangePicker = ({
onChange,
onSuccess,
position = 'bottom',
defaultDate = {
from: addDays(new Date(), -30),
to: new Date()
}
}: {
onChange?: (date: DateRange) => void;
onSuccess?: (date: DateRange) => void;
position?: 'bottom' | 'top';
defaultDate?: DateRange;
}) => {
const theme = useTheme();
const OutRangeRef = useRef(null);
const [range, setRange] = useState<DateRange | undefined>(defaultDate);
const [showSelected, setShowSelected] = useState(false);
const formatSelected = useMemo(() => {
if (range?.from && range.to) {
return `${format(range.from, 'y-MM-dd')} ~ ${format(range.to, 'y-MM-dd')}`;
}
return `${format(new Date(), 'y-MM-dd')} ~ ${format(new Date(), 'y-MM-dd')}`;
}, [range]);
useOutsideClick({
ref: OutRangeRef,
handler: () => {
setShowSelected(false);
}
});
return (
<Box position={'relative'} ref={OutRangeRef}>
<Flex
border={theme.borders.base}
px={3}
py={1}
borderRadius={'sm'}
cursor={'pointer'}
bg={'myWhite.600'}
fontSize={'sm'}
onClick={() => setShowSelected(true)}
>
<Box>{formatSelected}</Box>
<MyIcon ml={2} name={'date'} w={'16px'} color={'myGray.600'} />
</Flex>
{showSelected && (
<Card
position={'absolute'}
zIndex={1}
{...(position === 'top'
? {
bottom: '40px'
}
: {})}
>
<DayPicker
locale={zhCN}
id="test"
mode="range"
className={styles.datePicker}
defaultMonth={defaultDate.to}
selected={range}
disabled={[
{ from: new Date(2022, 3, 1), to: addDays(new Date(), -90) },
{ from: addDays(new Date(), 1), to: new Date(2099, 1, 1) }
]}
onSelect={(date) => {
if (date?.from === undefined) {
date = {
from: range?.from,
to: range?.from
};
}
if (date?.to === undefined) {
date.to = date.from;
}
setRange(date);
onChange && onChange(date);
}}
footer={
<Flex justifyContent={'flex-end'}>
<Button
variant={'outline'}
size={'sm'}
mr={2}
onClick={() => setShowSelected(false)}
>
</Button>
<Button
size={'sm'}
onClick={() => {
onSuccess && onSuccess(range || defaultDate);
setShowSelected(false);
}}
>
</Button>
</Flex>
}
/>
</Card>
)}
</Box>
);
};
export default DateRangePicker;
export type DateRangeType = DateRange;

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686969412308" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3481" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M517.864056 487.834624c-56.774051-54.213739-58.850339-144.187937-4.6366-200.960964 54.212716-56.773028 144.187937-58.849316 200.960964-4.6366 56.775074 54.213739 58.850339 144.186913 4.6366 200.960964C664.613328 539.972075 574.639131 542.048363 517.864056 487.834624zM687.194626 452.994118c37.533848-39.308261 36.09508-101.596909-3.210112-139.128711-39.304168-37.531801-101.593839-36.094056-139.127687 3.211135-37.532825 39.307238-36.093033 101.593839 3.212158 139.125641C587.374176 493.736031 649.660778 492.302379 687.194626 452.994118zM479.104287 670.917406l-101.495602 106.289792c26.206872 25.024953 27.167756 66.540486 2.14178 92.749404-25.028023 26.209942-66.543555 27.16571-92.750427 2.140757l-58.361199 53.027727c0 0-68.750827 11.100826-100.379175-19.101033-31.630395-30.205952-37.865399-112.721271-37.865399-112.721271l246.37427-258.302951c-63.173808-117.608581-47.24707-267.162736 49.939389-368.939747 36.517705-38.242999 80.346933-65.156976 127.165238-81.040734l1.084705 46.269813c-35.443233 14.07967-68.566632 35.596729-96.618525 64.973804-80.271208 84.064604-96.099708 205.865671-49.433876 305.083393l23.075555 39.163975L146.090774 798.015106c0 0 0.593518 49.77873 17.242709 65.677838 14.888082 14.216793 61.832254 9.828856 61.832254 9.828856l60.407812-63.260789 31.631418 30.203906c8.741082 8.346085 22.570042 8.030907 30.91715-0.711198 8.347109-8.742105 8.026814-22.571065-0.713244-30.91715l-31.632441-30.207999 156.456355-163.846672 39.009456 22.481014c101.259218 42.039465 222.201731 20.61041 302.474986-63.453171 104.251366-109.178585 100.260471-282.211477-8.91709-386.464889-33.591049-32.075533-73.260537-53.829999-115.093295-65.49262l-1.030469-45.153386c53.197596 12.471033 103.945397 38.547944 146.323577 79.015611 126.645398 120.931257 131.277906 321.649698 10.344602 448.296119C748.158093 705.787588 599.500355 728.598106 479.104287 670.917406z" p-id="3482"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684739031957" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4988" xmlns:xlink="http://www.w3.org/1999/xlink" width="64.125" height="64"><path d="M371.732817 94.172314q25.773475 0 44.112294 17.843175t18.338819 43.616651l0 247.821878q0 25.773475-18.338819 44.112294t-44.112294 18.338819l-247.821878 0q-25.773475 0-43.616651-18.338819t-17.843175-44.112294l0-247.821878q0-25.773475 17.843175-43.616651t43.616651-17.843175l247.821878 0zM371.732817 589.81607q25.773475 0 44.112294 17.843175t18.338819 43.616651l0 248.813166q0 25.773475-18.338819 43.616651t-44.112294 17.843175l-247.821878 0q-25.773475 0-43.616651-17.843175t-17.843175-43.616651l0-248.813166q0-25.773475 17.843175-43.616651t43.616651-17.843175l247.821878 0zM868.367861 589.81607q25.773475 0 43.616651 17.843175t17.843175 43.616651l0 248.813166q0 25.773475-17.843175 43.616651t-43.616651 17.843175l-247.821878 0q-25.773475 0-44.112294-17.843175t-18.338819-43.616651l0-248.813166q0-25.773475 18.338819-43.616651t44.112294-17.843175l247.821878 0zM1006.156825 203.21394q19.82575 19.82575 19.82575 46.590513t-19.82575 45.599226l-184.379477 184.379477q-19.82575 19.82575-46.094869 19.82575t-46.094869-19.82575l-184.379477-184.379477q-18.834463-18.834463-18.834463-45.599226t18.834463-46.590513l184.379477-184.379477q19.82575-18.834463 46.094869-18.834463t46.094869 18.834463z" p-id="4989"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683436459815" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1278" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M377.04830749 512.02677874l395.88826112-382.68981248c23.33363769-22.59430969 23.33363769-59.16132466 0-81.68344804-23.33247261-22.5582171-61.18836224-22.5582171-84.52083485 0L250.30081877 471.19378659c-23.29754397 22.5582171-23.29754397 59.14385977 0 81.63105451l438.11491385 423.52280349c11.70233003 11.27968995 26.99767353 16.91837099 42.29534607 16.91837098 15.29883648 0 30.59418112-5.63984498 42.22548878-16.95446471 23.33363769-22.5582171 23.33363769-59.07283854 0-81.63105451L377.04830749 512.02677874" p-id="1279"></path></svg>

After

Width:  |  Height:  |  Size: 866 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683254586102" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="940" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 34.13333333c263.91893333 0 477.86666667 213.94773333 477.86666667 477.86666667S775.91893333 989.86666667 512 989.86666667H79.6448A45.51146667 45.51146667 0 0 1 34.13333333 944.3552V512C34.13333333 248.08106667 248.08106667 34.13333333 512 34.13333333zM307.2 580.26666667a22.7552 22.7552 0 0 0-22.7552 22.7552V648.53333333A22.7552 22.7552 0 0 0 307.2 671.28853333h227.5552A22.7552 22.7552 0 0 0 557.51146667 648.53333333v-45.51146666A22.7552 22.7552 0 0 0 534.7552 580.26666667H307.2z m0-182.0448a22.7552 22.7552 0 0 0-22.7552 22.75626666v45.5104A22.7552 22.7552 0 0 0 307.2 489.2448h409.6a22.7552 22.7552 0 0 0 22.7552-22.75626667v-45.5104A22.7552 22.7552 0 0 0 716.8 398.22186667H307.2z" p-id="941"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683455612885" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1736" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M848.41813333 174.4896q69.90506667 69.90506667 104.31146667 157.2864t34.4064 179.13173333-34.4064 179.13173334-104.31146667 157.2864-157.2864 104.8576-179.13173333 34.95253333-179.67786667-34.95253333-157.83253333-104.8576-104.8576-157.2864-34.95253333-179.13173334 34.95253333-179.13173333 104.8576-157.2864 157.83253333-104.31146667 179.67786667-34.4064 179.13173333 34.4064 157.2864 104.31146667zM734.8224 729.36106667q22.9376-22.9376 20.20693333-52.4288t-25.66826666-52.4288l-114.688-114.688 114.688-114.688q22.9376-22.9376 25.66826666-52.4288t-20.20693333-52.4288-55.15946667-22.9376-55.15946666 22.9376l-114.688 113.59573333-111.4112-111.4112q-22.9376-21.84533333-52.4288-24.576t-52.4288 19.11466667q-22.9376 22.9376-22.9376 55.7056t22.9376 55.7056l111.4112 111.4112-111.4112 111.4112q-22.9376 22.9376-22.9376 55.15946666t22.9376 55.15946667 52.4288 20.20693333 52.4288-25.66826666l111.4112-111.4112 114.688 114.688q22.9376 22.9376 55.15946666 22.9376t55.15946667-22.9376z" p-id="1737"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 878 B

After

Width:  |  Height:  |  Size: 878 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686832863390" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4120" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M782.84 188.75h-43.15v-60.46c0-16.57-13.43-30-30-30s-30 13.43-30 30v60.46H371.88v-60.46c0-16.57-13.43-30-30-30s-30 13.43-30 30v60.46H250.5c-66.17 0-120 53.83-120 120v494.47c0 66.17 53.83 120 120 120h532.33c66.17 0 120-53.83 120-120V308.75c0.01-66.17-53.82-120-119.99-120z m-532.34 60h61.37v133.63c0 16.57 13.43 30 30 30s30-13.43 30-30V248.75h307.81v133.63c0 16.57 13.43 30 30 30s30-13.43 30-30V248.75h43.15c33.08 0 60 26.92 60 60V649.5H190.5V308.75c0-33.08 26.92-60 60-60z m532.34 614.47H250.5c-33.08 0-60-26.92-60-60V709.5h652.33v93.72c0.01 33.08-26.91 60-59.99 60z" p-id="4121"></path></svg>

After

Width:  |  Height:  |  Size: 924 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684826302600" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2244" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M904 512h-56c-4.4 0-8 3.6-8 8v320H184V184h320c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V520c0-4.4-3.6-8-8-8z" p-id="2245"></path><path d="M355.9 534.9L354 653.8c-0.1 8.9 7.1 16.2 16 16.2h0.4l118-2.9c2-0.1 4-0.9 5.4-2.3l415.9-415c3.1-3.1 3.1-8.2 0-11.3L785.4 114.3c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1 0.8-5.7 2.3l-415.8 415c-1.4 1.5-2.3 3.5-2.3 5.6z m63.5 23.6L779.7 199l45.2 45.1-360.5 359.7-45.7 1.1 0.7-46.4z" p-id="2246"></path></svg>

After

Width:  |  Height:  |  Size: 810 B

View File

@@ -0,0 +1,5 @@
<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="40" cy="40" r="39.5" stroke="#9CA2A8" stroke-dasharray="4 4"/>
<path d="M23.6876 55.8387L23.6407 55.8376C23.3701 55.8288 21.9635 55.7385 21.0265 54.8601C20.0656 53.959 19.9509 52.6633 19.9377 52.4125C19.9364 52.389 19.9358 52.3653 19.9359 52.3417V45.2852C19.9219 44.9945 19.9031 44.0125 20.2321 42.922C20.5474 41.8763 20.9369 41.1898 21.0484 41.0045L24.591 34.7371C24.6087 34.7056 24.6275 34.6752 24.6476 34.6457C24.758 34.4818 25.3518 33.6383 26.166 33.1317C27.0227 32.5982 28.0597 32.557 28.3572 32.557H50.7351C50.7639 32.557 50.7924 32.5578 50.8219 32.5596C51.0443 32.5739 52.1994 32.6714 53.0933 33.1828C53.9469 33.6716 54.634 34.5259 54.763 34.6919C54.7891 34.7259 54.8137 34.7611 54.8367 34.7974L59.0442 41.4646C59.0622 41.4939 59.0804 41.5246 59.0968 41.556C59.1774 41.7109 59.5933 42.529 59.8039 43.3247C60.0119 44.1119 60.0621 44.9819 60.0698 45.1481L60.0708 45.2048V52.2624C60.0756 52.6356 60.0069 53.9162 58.9446 54.8678C57.9386 55.7684 56.5842 55.8381 56.1931 55.8381H23.6876V55.8387ZM23.3271 42.4347C23.2497 42.5723 23.0089 43.0277 22.8065 43.6986C22.5927 44.4079 22.6181 45.0709 22.6215 45.1444C22.6234 45.1761 22.6245 45.208 22.625 45.2398V52.2952L22.6276 52.3147C22.6443 52.4416 22.7217 52.749 22.8551 52.8876L22.8731 52.9067L22.8943 52.9223C23.0811 53.06 23.5441 53.139 23.7058 53.1487L56.1952 53.1493C56.4648 53.1461 56.936 53.0563 57.1495 52.8649C57.3405 52.6937 57.3833 52.3998 57.3833 52.3528L57.3807 52.2712V45.2461C57.3662 44.9721 57.3131 44.4259 57.2031 44.0121C57.0786 43.5393 56.8001 42.973 56.7454 42.8646L56.7303 42.8382L52.6013 36.2947L52.5884 36.2791C52.4319 36.0906 52.0567 35.6879 51.757 35.5161C51.4975 35.3676 50.969 35.2725 50.6968 35.2471L50.6686 35.2458H28.362C28.1532 35.2476 27.7653 35.3029 27.5861 35.4149C27.3338 35.5722 27.0349 35.9316 26.912 36.0994L26.9014 36.1139L23.381 42.3419L23.3429 42.4059L23.3435 42.4043C23.3424 42.4074 23.3271 42.4347 23.3271 42.4347ZM39.8749 30.5851C39.5184 30.5847 39.1766 30.443 38.9245 30.1909C38.6724 29.9388 38.5306 29.5971 38.5301 29.2406V21.5056C38.5306 21.1492 38.6723 20.8075 38.9243 20.5555C39.1763 20.3034 39.518 20.1616 39.8744 20.1611C40.2309 20.1615 40.5728 20.3032 40.8249 20.5553C41.0771 20.8073 41.2189 21.1491 41.2194 21.5056V29.2405C41.2191 29.5969 41.0773 29.9387 40.8252 30.1908C40.5732 30.4429 40.2314 30.5846 39.8749 30.585V30.5851ZM48.8898 30.5341C48.5333 30.5336 48.1916 30.3917 47.9395 30.1396C47.6875 29.8875 47.5457 29.5458 47.5453 29.1893C47.5456 28.8809 47.6516 28.5819 47.8457 28.3423L50.9194 24.5515C51.0453 24.3956 51.2047 24.27 51.3856 24.1838C51.5666 24.0977 51.7646 24.0532 51.965 24.0537C52.2757 24.0537 52.5683 24.1575 52.8103 24.3539C52.948 24.4646 53.0623 24.6017 53.1465 24.757C53.2308 24.9123 53.2833 25.0829 53.3011 25.2587C53.32 25.4343 53.304 25.6119 53.2537 25.7813C53.2035 25.9506 53.1202 26.1083 53.0085 26.2452L49.935 30.036C49.8095 30.1918 49.6507 30.3175 49.4701 30.4036C49.2896 30.4898 49.092 30.5343 48.8919 30.5338H48.8898V30.5341ZM31.0112 30.4313C30.8092 30.4318 30.6097 30.3865 30.4278 30.2988C30.2458 30.2111 30.0861 30.0833 29.9607 29.925L26.9392 26.1344C26.7173 25.8554 26.6151 25.4998 26.6551 25.1455C26.695 24.7912 26.8739 24.4673 27.1525 24.2447C27.3898 24.0541 27.6853 23.9505 27.9897 23.9514C28.4019 23.9514 28.7853 24.1361 29.0419 24.458L32.0637 28.2485C32.2857 28.5274 32.388 28.883 32.3481 29.2372C32.3083 29.5914 32.1295 29.9154 31.851 30.138C31.6128 30.3274 31.3177 30.4307 31.0133 30.4313H31.0112Z" fill="#7B838B"/>
<path d="M39.9264 50.3068C36.9308 50.3068 34.3281 48.2985 33.5976 45.4236L33.5388 45.1918L21.5977 45.1847C21.2412 45.1839 20.8995 45.0419 20.6475 44.7897C20.3954 44.5375 20.2537 44.1957 20.2532 43.8391C20.2537 43.4827 20.3956 43.1411 20.6476 42.8892C20.8997 42.6373 21.2413 42.4955 21.5977 42.4951L34.7466 42.5035C35.103 42.5044 35.4444 42.6465 35.6963 42.8986C35.9481 43.1506 36.0899 43.4922 36.0904 43.8486C36.0904 45.9621 37.7753 47.6174 39.9264 47.6174C42.0698 47.6174 43.7489 45.9622 43.7489 43.8486C43.7496 43.4922 43.8915 43.1506 44.1435 42.8985C44.3954 42.6465 44.737 42.5044 45.0934 42.5035L58.1206 42.4953C58.8623 42.4953 59.4651 43.0976 59.4656 43.8388C59.4661 44.58 58.8634 45.1839 58.1221 45.185L46.3019 45.1921L46.243 45.4238C45.5134 48.2987 42.9162 50.3068 39.9264 50.3068Z" fill="#7B838B"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686468581713" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2951" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M512 640.64a42.666667 42.666667 0 0 0 42.666667-42.666667v-341.333333h130.986666a21.333333 21.333333 0 0 0 14.250667-5.461333l2.688-2.901334a21.333333 21.333333 0 0 0-4.010667-29.909333l-165.717333-126.464a32 32 0 0 0-38.912 0.042667L329.472 218.453333a21.333333 21.333333 0 0 0 12.970667 38.229334H469.333333v341.333333a42.666667 42.666667 0 0 0 42.666667 42.666667z m229.674667-298.368a42.666667 42.666667 0 0 0 4.992 85.034667H853.333333v426.666666H170.666667v-426.666666h106.666666a42.666667 42.666667 0 0 0 0-85.333334H170.666667a85.333333 85.333333 0 0 0-85.333334 85.333334v426.666666a85.333333 85.333333 0 0 0 85.333334 85.333334h682.666666a85.333333 85.333333 0 0 0 85.333334-85.333334v-426.666666a85.333333 85.333333 0 0 0-85.333334-85.333334h-106.666666z" fill="#000000" p-id="2952"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684122143852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2364" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z" p-id="2365"></path></svg>

After

Width:  |  Height:  |  Size: 1013 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686557412109" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2150" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M511.998 64C264.574 64 64 264.574 64 511.998S264.574 960 511.998 960 960 759.422 960 511.998 759.422 64 511.998 64z m353.851 597.438c-82.215 194.648-306.657 285.794-501.306 203.579S78.749 558.36 160.964 363.711 467.621 77.917 662.27 160.132c168.009 70.963 262.57 250.652 225.926 429.313a383.995 383.995 0 0 1-22.347 71.993z" p-id="2151"></path><path d="M543.311 498.639V256.121c0-17.657-14.314-31.97-31.97-31.97s-31.97 14.314-31.97 31.97v269.005l201.481 201.481c12.485 12.485 32.728 12.485 45.213 0s12.485-32.728 0-45.213L543.311 498.639z" p-id="2152"></path></svg>

After

Width:  |  Height:  |  Size: 875 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686042262954" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3245" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M510.1 928h5.5c52.6-0.7 96.7-38.4 103.3-88.5H406.8c6.6 50.1 50.7 87.9 103.3 88.5zM771.7 598.5V410.9c0.6-105.3-70.9-197-172.2-220.8v-4.5c0.8-31.7-15.5-61.4-42.5-77.6-27.1-16.1-60.6-16.1-87.7 0s-43.3 45.8-42.5 77.6v4.5C325.2 213.7 253.4 305.5 254 410.9v187.6c-51.9 41.3-83.2 103.5-85.9 170.2h689.5c-2.6-66.7-34-128.9-85.9-170.2z" p-id="3246"></path></svg>

After

Width:  |  Height:  |  Size: 663 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684163814302" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3451" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 384c-229.8 0-416-57.3-416-128v256c0 70.7 186.2 128 416 128s416-57.3 416-128V256c0 70.7-186.2 128-416 128z" p-id="3452"></path><path d="M512 704c-229.8 0-416-57.3-416-128v256c0 70.7 186.2 128 416 128s416-57.3 416-128V576c0 70.7-186.2 128-416 128zM512 320c229.8 0 416-57.3 416-128S741.8 64 512 64 96 121.3 96 192s186.2 128 416 128z" p-id="3453"></path></svg>

After

Width:  |  Height:  |  Size: 694 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686561811905" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2855" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M992 528c0 273.9-222.1 496-496 496S0 801.9 0 528 222.1 32 496 32c86.2 0 167.3 22 238 60.7 2.3 1.3 2.8 4.4 0.9 6.3l-37 37.3-4.2 4.3c-1.2 1.2-3.1 1.5-4.6 0.8-8.2-4.1-16.5-7.9-24.9-11.5C610.9 107.4 554.3 96 496 96s-114.9 11.4-168.1 33.9c-51.4 21.8-97.7 52.9-137.3 92.6-39.7 39.7-70.9 85.9-92.6 137.3C75.4 413.1 64 469.6 64 528c0 58.3 11.4 114.9 33.9 168.1 21.8 51.4 52.9 97.6 92.6 137.3 39.7 39.7 85.9 70.9 137.3 92.6 53.3 22.6 109.9 34 168.2 34s114.9-11.4 168.1-33.9c51.4-21.8 97.7-52.9 137.3-92.6 39.7-39.7 70.9-85.9 92.6-137.3 22.6-53.3 34-109.9 34-168.2 0-58.4-11.4-114.9-33.9-168.1-3.6-8.5-7.4-16.8-11.5-25-0.8-1.5-0.5-3.4 0.8-4.6l4.3-4.2 37.3-37c1.9-1.9 5-1.4 6.3 0.9C970 360.6 992 441.7 992 528z" p-id="2856"></path><path d="M781.4 397c-3.7-8-11.7-13.1-20.6-13.1H740c-6 0-11.8 2.4-16 6.6-7 7-8.6 17.6-4.1 26.4 2.6 5.1 5 10.3 7.3 15.7 13.2 31.2 19.9 64.3 19.9 98.5s-6.7 67.3-19.9 98.5c-12.7 30.1-31 57.2-54.2 80.4-23.3 23.3-50.3 41.5-80.4 54.2-31.3 13.1-64.4 19.8-98.6 19.8s-67.3-6.7-98.5-19.9c-30.1-12.7-57.2-31-80.4-54.2-23.3-23.3-41.5-50.3-54.2-80.4-13.2-31.2-19.9-64.3-19.9-98.5s6.7-67.3 19.9-98.5c12.7-30.1 31-57.2 54.2-80.4 23.3-23.3 50.3-41.5 80.4-54.2 31.2-13.2 64.3-19.9 98.5-19.9s67.3 6.7 98.5 19.9c4.9 2.1 9.8 4.3 14.6 6.7 8.8 4.4 19.4 2.6 26.3-4.4 4.3-4.3 6.7-10.1 6.7-16.2v-20.2c0-9-5.2-17.1-13.4-20.8-40.4-18.6-85.3-29-132.6-29-175.5 0-318 143.4-317 318.9C178 707.1 319.6 848 494 848c174.8 0 316.6-141.3 317-316.2 0.1-48.2-10.5-93.9-29.6-134.8z" p-id="2857"></path><path d="M634.5 488.5c-0.8-2.9-4.5-3.9-6.7-1.7l-34.7 34.7-1.8 1.8c-9 9-15.7 20.1-20.1 32.1-11.5 31.6-42.4 54-78.3 52.7-41.6-1.6-75.3-35.3-76.9-76.9-1.4-35.9 21-66.8 52.7-78.3 12-4.4 23-11.1 32.1-20.1l1.8-1.8 34.7-34.7c2.2-2.2 1.2-5.8-1.7-6.7-12.9-3.7-26.5-5.6-40.6-5.5-79.4 0.5-143 64.5-143 143.9 0 79.5 64.5 144 144 144 79.4 0 143.4-63.6 144-142.9 0.1-14.1-1.8-27.8-5.5-40.6z" p-id="2858"></path><path d="M1014.3 146H882c-2.2 0-4-1.8-4-4V9.8c0-2.4-2-4-4-4-1 0-2 0.4-2.8 1.2L766.8 112.4l-46.1 46.5-44 44.4c-3 3-4.6 7-4.6 11.3v85.5c0 4.3-1.7 8.3-4.7 11.3l-94.7 94.7-47.4 47.4-51.8 51.9c-12.5 12.5-12.5 32.8 0 45.3 6.3 6.3 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l51.8-51.9 123.2-123.2 19-19c3-3 7.1-4.7 11.3-4.7h85.5c4.2 0 8.3-1.7 11.3-4.6l44.3-43.9 46.5-46.1L1017 152.9c2.6-2.6 0.8-6.9-2.7-6.9zM864 214.3l-44 43.5-25.6 25.4c-3 3-7 4.6-11.3 4.6H744c-4.4 0-8-3.6-8-8v-39c0-4.2 1.7-8.3 4.6-11.3l25.5-25.7 43.5-43.9 1.6-1.6c1.6-1.7 4.5-0.7 4.8 1.6 4.8 25.8 23.5 41.6 48.6 47.7 2.1 0.5 2.9 3.2 1.3 4.7l-1.9 2z" p-id="2859"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684745011703" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1481" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M110.025 252.249c0 13.741 5.456 27.374 14.899 37.112s22.663 15.364 35.987 15.364 26.544-5.626 35.987-15.364c9.441-9.738 14.899-23.371 14.899-37.112s-5.456-27.375-14.899-37.111c-9.442-9.738-22.663-15.364-35.987-15.364s-26.544 5.626-35.987 15.364c-9.338 9.736-14.899 23.37-14.899 37.111m0 0zM103.625 512.575c0 13.741 5.455 27.482 14.899 37.22 9.442 9.738 22.663 15.364 36.091 15.364 13.324 0 26.649-5.626 36.091-15.364s14.899-23.371 14.899-37.22c0-13.741-5.455-27.482-14.899-37.22-9.442-9.738-22.662-15.364-36.091-15.364-13.324 0-26.649 5.626-36.091 15.364-9.444 9.737-14.899 23.37-14.899 37.22m0 0zM103.625 774.089c0 13.741 5.455 27.482 14.899 37.22 9.442 9.738 22.663 15.364 36.091 15.364 13.324 0 26.649-5.626 36.091-15.364s14.899-23.37 14.899-37.22c0-13.741-5.455-27.482-14.899-37.22-9.442-9.737-22.662-15.364-36.091-15.364-13.324 0-26.649 5.627-36.091 15.364-9.444 9.847-14.899 23.479-14.899 37.22m0 0zM919.041 249.869c0 27.699-19.935 50.095-44.59 50.095H345.88c-24.655 0-44.59-22.397-44.59-50.095 0-27.699 19.935-50.095 44.59-50.095h528.571c24.656-0.001 44.59 22.396 44.59 50.095m0 0zM919.041 510.195c0 27.59-19.935 50.095-44.59 50.095H345.88c-24.655 0-44.59-22.398-44.59-50.096 0-27.699 19.935-50.096 44.59-50.096h528.571c24.656-0.109 44.59 22.397 44.59 50.097m0 0zM919.041 771.601c0 27.699-19.935 50.096-44.59 50.096H345.88c-24.655 0-44.59-22.397-44.59-50.096 0-27.591 19.935-49.988 44.59-49.988h528.571c24.656-0.108 44.59 22.397 44.59 49.988m0 0z" p-id="1482"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683254592786" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1352" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M450.09164971 42.7357605a123.86965959 123.86965959 0 0 1 123.76374272 0L889.06369251 222.84722403a123.92261859 123.92261859 0 0 1 62.06722618 107.2407279v360.38180181c0 44.22025102-23.6194395 85.05116445-61.9613093 107.13480989l-0.10591688 0.10591687-315.20830008 180.11146353a123.86965959 123.86965959 0 0 1-123.76374272 0L134.93630749 797.7104805a123.92261859 123.92261859 0 0 1-62.06722618-107.24072676V330.08795193c0-44.22025102 23.67239737-85.05116445 61.9613093-107.13481102l0.10591688-0.10591688z m462.16781482 223.59029646a33.78744889 33.78744889 0 0 0-46.17971029-12.28634453l-353.81496263 204.57823687L158.44982898 254.09267029a33.78744889 33.78744889 0 0 0-33.89336463 58.46605597l353.6031289 204.47232v430.02207687c0 18.00585102 15.14609778 32.62236445 33.84040675 32.62236444a33.20490667 33.20490667 0 0 0 33.73449102-32.62236444V517.29583787l354.18567111-204.79006948a33.78744889 33.78744889 0 0 0 14.66947129-41.20162304z" p-id="1353"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

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