Compare commits

..

2 Commits

Author SHA1 Message Date
archer
c7f0db0811 fix: ui;perf: docs 2023-05-14 22:04:19 +08:00
archer
c16d59671f feat: share chat page 2023-05-14 21:37:26 +08:00
695 changed files with 23974 additions and 57585 deletions

31
.env.template Normal file
View File

@@ -0,0 +1,31 @@
# proxy
# AXIOS_PROXY_HOST=127.0.0.1
# AXIOS_PROXY_PORT=7890
# 是否开启队列任务。 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
# OPENAI_BASE_URL=https://api.openai.com/v1
# OPENAI_BASE_URL_AUTH=可选的安全凭证
OPENAIKEY=sk-xxx
GPT4KEY=sk-xxx
# claude
CLAUDE_BASE_URL=calude模型请求地址
CLAUDE_KEY=CLAUDE_KEY
# 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

View File

@@ -1,79 +0,0 @@
name: Build fastgpt-admin images and copy image to docker hub
on:
workflow_dispatch:
push:
paths:
- 'admin/**'
branches:
- 'main'
tags:
- 'v*.*.*'
jobs:
build-admin-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-admin:latest" >> $GITHUB_ENV
else
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-admin:${{ 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 admin && \
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-admin image" \
--label "org.opencontainers.image.licenses=MIT" \
--push \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.
push-to-docker-hub:
needs: build-admin-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-admin:${{env.IMAGE_TAG}}
- name: Tag image with Docker Hub repository name and version tag
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt-admin:${{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,79 +0,0 @@
name: Build fastgpt images and copy image to docker hub
on:
workflow_dispatch:
push:
paths:
- 'client/**'
branches:
- 'main'
tags:
- 'v*.*.*'
jobs:
build-fastgpt-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-fastgpt-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}}

50
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
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 }}/fastgpt
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=fastgpt image" \
--label "org.opencontainers.image.licenses=MIT" \
--push \
-t ${DOCKER_REPO}:latest \
-f Dockerfile \
.

23
.gitignore vendored
View File

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

15
.vscode/settings.json vendored
View File

@@ -1,15 +0,0 @@
{
"editor.formatOnSave": true,
"editor.mouseWheelZoom": true,
"typescript.tsdk": "client/node_modules/typescript/lib",
"prettier.prettierPath": "./node_modules/prettier",
"i18n-ally.localesPaths": [
"client/public/locales"
],
"i18n-ally.enabledParsers": ["json"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.keepFulfilled": true,
"i18n-ally.sourceLanguage": "zh", // 根据此语言文件翻译其他语言文件的变量和内容
"i18n-ally.displayLanguage": "en", // 显示语言
}

View File

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

47
Makefile Normal file
View File

@@ -0,0 +1,47 @@
SERVICE_NAME=fastgpt
# 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 registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt: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 fastgpt -v /web_project/yjl/fastgpt/logs:/app/.next/logs registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest
#TODO: add support of docker push
#TODO: add support of sealos apply

View File

@@ -1,29 +1,27 @@
# Fast GPT
Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接口,目前集成了 Gpt35, Gpt4 和 embedding. 可构建自己的知识库。并且 OpenAPI Chat 接口兼容 OpenAI 接口,意味着你只需修改 BaseUrl 和 Authorization 即可在已有项目基础上接入 FastGpt
Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接口,目前集成了 gpt35 和 embedding. 可构建自己的知识库。
## 🛸 在线体验
🎉 [fastgpt.run](https://fastgpt.run/)
🎉 [ai.fastgpt.run](https://ai.fastgpt.run/)
🎉 [fastgpt.run](https://fastgpt.run/) (国内版)
🎉 [ai.fastgpt.run](https://ai.fastgpt.run/) (海外版)
![Demo](.github/imgs/demo.png?raw=true 'demo')
![Demo](docs/imgs/demo.png?raw=true 'demo')
#### 知识库原理图
![KBProcess](.github/imgs/KBProcess.jpg?raw=true 'KBProcess')
![KBProcess](docs/imgs/KBProcess.jpg?raw=true 'KBProcess')
## 👨‍💻 开发
项目技术栈: NextJs + TS + ChakraUI + Mongo + PostgresVector 插件)
这是一个平台项目,非单机项目,除了模型调用外还涉及非常多用户的内容。
[本地开发 Quick Start](docSite/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quick-start/dev.md)
[本地开发 Quick Start](docs/dev/README.md)
## 🚀 私有化部署
- [官方推荐 Sealos 部署](https://sealos.io/docs/examples/ai-applications/install-fastgpt-on-desktop) 无需服务器,代理和域名,高可用。
- [docker-compose 部署](docSite/i18n/zh-Hans/docusaurus-plugin-content-docs/current/deploy/docker.md) 单机版。
- [由社区贡献的宝塔部署和本地运行教程](https://www.bilibili.com/video/BV1tV4y1y7Mj/?vd_source=92041a1a395f852f9d89158eaa3f61b4) 单机版。
[docker-compose 部署教程](docs/deploy/docker.md)
## :point_right: RoadMap
@@ -31,27 +29,16 @@ Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接
## 🏘️ 交流群
添加 wx 进入:
![Demo](https://otnvvf-imgs.oss.laf.run/wx300.jpg)
## Powered by
- [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)
wx: fastgpt123
![Demo](docs/imgs/wx300.jpg?raw=true 'wx')
## 👀 其他
- [FastGpt 常见问题](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [docker 部署教程视频](https://www.bilibili.com/video/BV1jo4y147fT/)
- [公众号接入视频教程](https://www.bilibili.com/video/BV1xh4y1t7fy/)
- [FastGpt + Laf 最佳实践,将知识库装入公众号,点击去 Laf 公众号体验效果](https://b4jky7-fastgpt.oss.laf.run/lafercode.png)
- [FastGpt V3.4 更新集合](https://www.bilibili.com/video/BV1Lo4y147Qh/?vd_source=92041a1a395f852f9d89158eaa3f61b4)
- [FastGpt 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
## 第三方生态
- [luolinAI: 企微机器人,开箱即用](https://github.com/luolin-ai/FastGPT-Enterprise-WeChatbot)
## 🌟 Star History
[![Star History Chart](https://api.star-history.com/svg?repos=labring/FastGPT&type=Date)](https://star-history.com/#labring/FastGPT&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=c121914yu/FastGPT&type=Date)](https://star-history.com/#c121914yu/FastGPT&Date)

View File

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

View File

@@ -1,8 +0,0 @@
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
View File

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

View File

@@ -1,48 +0,0 @@
# 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"]

View File

@@ -1,201 +0,0 @@
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,43 +0,0 @@
# 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 提供的外网访问地址,可以直接访问。

View File

@@ -1,13 +0,0 @@
<!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>

View File

@@ -1,42 +0,0 @@
{
"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.3.1"
},
"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"
}
}

5553
admin/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,37 +0,0 @@
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) => {
try {
res.sendFile(new URL('dist/index.html', import.meta.url).pathname);
} catch (error) {
res.end();
}
});
app.use((err, req, res, next) => {
try {
res.sendFile(new URL('dist/index.html', import.meta.url).pathname);
} catch (error) {
res.end();
}
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

View File

@@ -1,86 +0,0 @@
import { App, Kb } from '../schema.js';
import { auth } from './system.js';
export const useAppRoute = (app) => {
// 获取AI助手列表
app.get('/apps', 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 App.find(where)
.skip(start)
.limit(end - start)
.sort({ [sort]: order, 'share.isShare': -1, 'share.collection': -1 });
const models = [];
for (const modelRaw of modelsRaw) {
const app = modelRaw.toObject();
// 获取与模型关联的知识库名称
const kbNames = [];
for (const kbId of app.chat.relatedKbs) {
const kb = await Kb.findById(kbId);
kbNames.push(kb.name);
}
const orderedModel = {
id: app._id.toString(),
userId: app.userId,
name: app.name,
intro: app.intro,
relatedKbs: kbNames, // 将relatedKbs的id转换为相应的Kb名称
systemPrompt: app.chat?.systemPrompt || '',
temperature: app.chat?.temperature || 0,
'share.topNum': app.share?.topNum || 0,
'share.isShare': app.share?.isShare || false,
'share.collection': app.share?.collection || 0
};
models.push(orderedModel);
}
const totalCount = await App.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('/apps/:id', auth(), async (req, res) => {
try {
const _id = req.params.id;
let {
share: { isShare, topNum },
intro
} = req.body;
await App.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' });
}
});
};

View File

@@ -1,57 +0,0 @@
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' } }
}
: {})
};
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

@@ -1,76 +0,0 @@
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');
}
});
};
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));
}
};
};

View File

@@ -1,242 +0,0 @@
import { User, Pay } from '../schema.js';
import dayjs from 'dayjs';
import { auth } from './system.js';
import crypto from 'crypto';
export const PRICE_SCALE = 100000;
export const formatPrice = (val = 0, multiple = 1) => {
return Number(((val / PRICE_SCALE) * multiple).toFixed(10));
};
// 加密
const hashPassword = (psw) => {
return crypto.createHash('sha256').update(psw).digest('hex');
};
const day = 60;
export const useUserRoute = (app) => {
// 统计近 30 天注册用户数量
app.get('/users/data', auth(), async (req, res) => {
try {
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,
balance: formatPrice(obj.balance),
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: hashPassword(hashPassword(password)),
balance: balance * PRICE_SCALE
});
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 { username, password, balance = 0 } = req.body;
const result = await User.findByIdAndUpdate(_id, {
...(username && { username }),
...(password && { password: hashPassword(hashPassword(password)) }),
...(balance && { balance: balance * PRICE_SCALE })
});
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 });
}
});
// 获取本月账单
app.get('/pays/data', auth(), async (req, res) => {
try {
let startCount = 0;
const paysRaw = await Pay.aggregate([
{
$match: {
status: 'SUCCESS',
createTime: {
$gte: new Date(Date.now() - day * 24 * 60 * 60 * 1000 + 8 * 60 * 60 * 1000) // 补时差
}
}
},
{
$addFields: {
adjustedCreateTime: { $add: ['$createTime', 8 * 60 * 60 * 1000] }
}
},
{
$group: {
_id: {
year: { $year: '$adjustedCreateTime' },
month: { $month: '$adjustedCreateTime' },
day: { $dayOfMonth: '$adjustedCreateTime' }
},
count: { $sum: '$price' }
}
},
{
$project: {
_id: 0,
date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day' } },
count: 1
}
},
{ $sort: { date: 1 } }
]);
const countResult = paysRaw.map((item) => {
startCount += item.count;
return {
date: item.date,
total: startCount,
count: item.count
};
});
res.json(countResult);
} catch (err) {
console.log(`Error fetching users: ${err}`);
res.status(500).json({ error: 'Error fetching users' });
}
});
};

View File

@@ -1,121 +0,0 @@
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({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true,
select: false
},
createTime: {
type: Date,
default: () => new Date()
},
avatar: {
type: String,
default: '/icon/human.png'
},
balance: {
type: Number,
default: 0
},
limit: {
exportKbTime: {
// Every half hour
type: Date
}
}
});
// 新增: 定义 pays 模型
const paySchema = new mongoose.Schema({
userId: mongoose.Schema.Types.ObjectId,
price: Number,
orderId: String,
status: String,
createTime: Date,
__v: Number
});
// 新增: 定义 kb 模型
const kbSchema = new mongoose.Schema({
userId: mongoose.Schema.Types.ObjectId,
avatar: String,
name: String,
tags: [String],
updateTime: Date,
__v: Number
});
const appSchema = new mongoose.Schema({
userId: mongoose.Schema.Types.ObjectId,
name: String,
avatar: String,
status: String,
intro: 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 App = mongoose.models['model'] || mongoose.model('model', appSchema);
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);

View File

@@ -1,129 +0,0 @@
import {
createTextField,
jsonServerProvider,
ListTable,
Resource,
Tushan,
fetchJSON,
TushanContextProps,
HTTPClient
} from 'tushan';
import { authProvider } from './auth';
import { userFields, payFields, kbFields, AppFields } 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={{ create: true, detail: true, edit: true }}
/>
}
/>
<Resource
name="apps"
icon={<IconApps />}
label="应用"
list={
<ListTable
filter={[
createTextField('id', {
label: 'id'
}),
createTextField('name', {
label: 'name'
})
]}
fields={AppFields}
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 }}
/>
}
/>
</Tushan>
);
}
export default App;

View File

@@ -1,273 +0,0 @@
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';
const PRICE_SCALE = 100000;
type fetchChatData = {
count: number;
total?: number;
date: string;
increase?: number;
increaseRate?: string;
};
type chatDataType = {
date: string;
userCount: number;
userIncrease?: number;
userIncreaseRate?: string;
payTotal: number;
payCount: number;
};
export const Dashboard: React.FC = React.memo(() => {
const [userCount, setUserCount] = useState(0); //用户数量
const [kbCount, setkbCount] = useState(0);
const [modelCount, setmodelCount] = useState(0);
const [chatData, setChatData] = useState<chatDataType[]>([]);
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 fetchChatData = async () => {
const [userResponse, payResponse]: fetchChatData[][] = await Promise.all([
fetch(`${baseUrl}/users/data`, {
headers
}).then((res) => res.json()),
fetch(`${baseUrl}/pays/data`, {
headers
}).then((res) => res.json())
]);
const data = userResponse.map((item, i) => {
const pay = payResponse.find((pay) => item.date === pay.date);
return {
date: dayjs(item.date).format('MM/DD'),
userCount: item.count,
userIncrease: item.increase,
userIncreaseRate: item.increaseRate,
payCount: pay ? pay.count / PRICE_SCALE : 0,
payTotal: pay?.total ? pay.total / PRICE_SCALE : 0
};
});
setChatData(data);
};
fetchCounts();
fetchChatData();
}, []);
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 />
<div>
<strong> & </strong>
<UserChart data={chatData} />
</div>
<Divider />
</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 chatDataType;
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">
: <strong>{data.date}</strong>
</p>
<p className="label">
: <strong>{data.userCount}</strong>
</p>
<p className="label">
: <strong>{data.userIncrease}</strong>
</p>
<p className="label">
: <strong>{data.payCount}</strong>
</p>
<p className="label">
60: <strong>{data.payTotal}</strong>
</p>
</div>
);
}
return null;
};
const UserChart = ({ data }: { data: chatDataType[] }) => {
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="userCount" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#82ca9d" stopOpacity={0.8} />
<stop offset="95%" stopColor="#82ca9d" stopOpacity={0} />
</linearGradient>
<linearGradient id="payTotal" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
</linearGradient>
</defs>
<XAxis dataKey="date" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip content={<CustomTooltip />} />
<Area
type="monotone"
dataKey="userCount"
stroke="#82ca9d"
fillOpacity={1}
fill="url(#userCount)"
/>
<Area
type="monotone"
dataKey="payTotal"
stroke="#8884d8"
fillOpacity={1}
fill="url(#payTotal)"
/>
</AreaChart>
</ResponsiveContainer>
);
};

View File

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

View File

@@ -1,49 +0,0 @@
import { createTextField, createNumberField } from 'tushan';
export const userFields = [
createTextField('id', { label: 'ID' }),
createTextField('username', { label: '用户名' }),
createNumberField('balance', { label: '余额(元)', list: { sort: true } }),
createTextField('createTime', {
label: '创建时间',
list: { sort: true },
edit: { hidden: 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: '创建时间', 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 AppFields = [
createTextField('id', { label: 'ID' }),
createTextField('userId', { label: '所属用户', list: { hidden: true }, edit: { hidden: true } }),
createTextField('name', { label: '名字' }),
createTextField('app', { 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
}
})
];

View File

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

View File

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

View File

@@ -1,21 +0,0 @@
{
"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" }]
}

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
# 运行端口,如果不是 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

32
client/.gitignore vendored
View File

@@ -1,32 +0,0 @@
# 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/
data/config.json.local

View File

@@ -1,70 +0,0 @@
{
"FeConfig": {
"show_emptyChat": true,
"show_register": true,
"show_appStore": false,
"show_userDetail": true,
"show_git": true,
"systemTitle": "FastAI",
"authorText": "Made by FastAI Team."
},
"SystemParams": {
"beianText": "",
"baiduTongji": "",
"googleClientVerKey": "",
"googleServiceVerKey": "",
"vectorMaxProcess": 15,
"qaMaxProcess": 15,
"pgIvfflatProbe": 20,
"sensitiveCheck": false
},
"ChatModels": [
{
"model": "gpt-3.5-turbo",
"name": "FastAI-4k",
"contextMaxToken": 4000,
"quoteMaxToken": 2000,
"maxTemperature": 1.2,
"price": 1.5
},
{
"model": "gpt-3.5-turbo-16k",
"name": "FastAI-16k",
"contextMaxToken": 16000,
"quoteMaxToken": 8000,
"maxTemperature": 1.2,
"price": 3
},
{
"model": "ERNIE-Bot",
"name": "文心一言",
"contextMaxToken": 3000,
"quoteMaxToken": 1500,
"maxTemperature": 1,
"price": 1.2
},
{
"model": "gpt-4",
"name": "FastAI-Plus",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 45
}
],
"QAModels": [
{
"model": "gpt-3.5-turbo-16k",
"name": "FastAI-16k",
"maxToken": 16000,
"price": 3
}
],
"VectorModels": [
{
"model": "text-embedding-ada-002",
"name": "Embedding-2",
"price": 0.2
}
]
}

View File

@@ -1,5 +0,0 @@
/// <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.

View File

@@ -1,12 +0,0 @@
//next-i18next.config.js
/**
* @type {import('next-i18next').UserConfig}
*/
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh', 'zh-Hans'],
localeDetection: false
}
};

View File

@@ -1,91 +0,0 @@
{
"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",
"echarts": "^5.4.1",
"formidable": "^2.1.1",
"framer-motion": "^9.0.6",
"hyperdown": "^2.4.29",
"i18next": "^22.5.1",
"immer": "^9.0.19",
"js-cookie": "^3.0.5",
"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",
"next-i18next": "^13.3.0",
"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-i18next": "^12.3.1",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",
"reactflow": "^11.7.4",
"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/js-cookie": "^3.0.3",
"@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"
}
}

12752
client/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +0,0 @@
### 常见问题
**一键部署**V4 版本未正式开源,目前仅提供一键部署方案:
[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
**Git 地址**: [项目地址。V4-beta 暂未开源,在正式版发布后会开源。](https://github.com/labring/FastGPT)
**反馈问卷**: 如果你遇到任何使用问题或有期望的功能,可以[填写该问卷](https://www.wjx.cn/vm/rLIw1uD.aspx#)
**问题文档**: [先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
**价格表**
| 计费项 | 价格: 元/ 1K tokens包含上下文|
| --- | --- |
| 知识库 - 索引 | 0.002 |
| FastAI4k - 对话 | 0.015 |
| FastAI16k - 对话 | 0.03 |
| FastAI-Plus - 对话 | 0.45 |
| 文件拆分 | 0.03 |
**其他问题**
| 交流群 | 小助手 |
| ----------------------- | -------------------- |
| ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) |

View File

@@ -1,8 +0,0 @@
### Fast GPT V4.0-beta
1. 全新交互,增加采用模块组合的方式构建知识库,同时保留表单的简易模式。
2. 问题分类 - 可以对用户的问题进行分类,再执行不同的操作。
3. 对话引导 - 增加开场引导和变量,提高对话可玩性。
4. 新增 - 每轮对话均可展示完整的上下文状态。
5. 新增 - 网页嵌入(简单版),可直接接入现有网页。在 【应用详情-外部使用-创建新链接-嵌入网页】 中获取嵌入脚本。
6. 新增 - 个人 openai 账号可以使用网页上 OpenAI 对话模型。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1 +0,0 @@
<?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="1689493855879" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3538" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M145.6 0C100.8 0 64 36.8 64 81.6v860.8C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L656 0H145.6z" fill="#45B058" p-id="3539"></path><path d="M388.8 691.2c1.6 1.6 3.2 4.8 3.2 8 0 6.4-4.8 11.2-11.2 11.2-3.2 0-6.4 0-8-3.2-11.2-12.8-30.4-22.4-48-22.4-41.6 0-73.6 32-73.6 78.4 0 44.8 32 78.4 73.6 78.4 17.6 0 35.2-8 48-22.4 1.6-1.6 4.8-3.2 8-3.2 6.4 0 11.2 4.8 11.2 11.2 0 3.2-1.6 6.4-3.2 8-14.4 16-35.2 27.2-64 27.2-56 0-99.2-40-99.2-99.2s43.2-99.2 99.2-99.2c28.8 0 49.6 11.2 64 27.2z m108.8 171.2c-28.8 0-51.2-9.6-67.2-24-3.2-1.6-3.2-4.8-3.2-8 0-6.4 3.2-12.8 11.2-12.8 1.6 0 4.8 1.6 6.4 3.2 12.8 11.2 32 20.8 54.4 20.8 33.6 0 44.8-19.2 44.8-33.6 0-49.6-113.6-22.4-113.6-91.2 0-30.4 27.2-52.8 65.6-52.8 24 0 46.4 8 62.4 20.8 1.6 1.6 3.2 4.8 3.2 8 0 6.4-4.8 11.2-11.2 11.2-1.6 0-4.8 0-6.4-1.6-14.4-11.2-32-17.6-49.6-17.6-24 0-40 12.8-40 30.4 0 43.2 113.6 19.2 113.6 91.2 0 27.2-19.2 56-70.4 56z m272-179.2L702.4 848c-3.2 8-9.6 12.8-17.6 12.8h-1.6c-8 0-16-4.8-19.2-12.8l-65.6-164.8c-1.6-1.6-1.6-3.2-1.6-4.8 0-6.4 4.8-12.8 12.8-12.8 4.8 0 9.6 3.2 11.2 8l62.4 160 62.4-160c1.6-4.8 6.4-8 11.2-8 8 0 14.4 6.4 14.4 12.8 0 1.6-1.6 3.2-1.6 4.8z" fill="#FFFFFF" p-id="3540"></path><path d="M960 326.4v16H755.2s-100.8-20.8-97.6-108.8c0 0 3.2 92.8 96 92.8H960z" fill="#349C42" p-id="3541"></path><path d="M657.6 0v233.6c0 25.6 17.6 92.8 97.6 92.8H960L657.6 0z" fill="#FFFFFF" p-id="3542"></path></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1 +0,0 @@
<?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="1689494897388" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="958" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M146.56 0C101.76 0 66.56 35.2 66.56 80V940.8C66.56 985.6 101.76 1024 146.56 1024H879.36c44.8 0 80-35.2 80-80V323.2L658.56 0h-512z" fill="#F7622C" p-id="959"></path><path d="M962.56 326.4v16h-204.8s-99.2-19.2-99.2-108.8c0 0 3.2 92.8 96 92.8H962.56z" fill="#F54921" p-id="960"></path><path d="M658.56 0v233.6c0 25.6 16 92.8 96 92.8H962.56L658.56 0zM367.36 812.8H360.96l-121.6-54.4c-6.4-3.2-12.8-9.6-12.8-19.2 0-6.4 6.4-16 12.8-19.2L360.96 665.6h3.2c6.4 0 12.8 6.4 12.8 16 0 6.4-3.2 12.8-6.4 16L258.56 745.6 370.56 793.6c3.2 3.2 6.4 6.4 6.4 16 3.2 0-3.2 3.2-9.6 3.2zM501.76 636.8l-70.4 211.2c-3.2 6.4-6.4 9.6-12.8 9.6-9.6 0-16-6.4-16-16l3.2-3.2L476.16 627.2c3.2-6.4 6.4-12.8 12.8-12.8 9.6 0 12.8 6.4 12.8 16v6.4z m166.4 121.6l-121.6 54.4h-3.2c-6.4 0-16-3.2-16-12.8 0-6.4 3.2-12.8 6.4-16l112-48-112-48c-3.2-3.2-6.4-6.4-6.4-16 0-6.4 6.4-16 16-16h3.2l121.6 54.4c6.4 3.2 12.8 12.8 12.8 19.2-3.2 16-6.4 25.6-12.8 28.8z" fill="#FFFFFF" p-id="961"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<?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="1689494956529" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3178" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M903.542857 256.8c6.857143 6.857143 10.742857 16.114286 10.742857 25.828571V987.428571c0 20.228571-16.342857 36.571429-36.571428 36.571429H146.285714c-20.228571 0-36.571429-16.342857-36.571428-36.571429V36.571429c0-20.228571 16.342857-36.571429 36.571428-36.571429h485.371429c9.714286 0 19.085714 3.885714 25.942857 10.742857l245.942857 246.057143zM829.942857 299.428571L614.857143 84.342857V299.428571h215.085714zM413.862857 613.634286l67.554286 151.965714a18.285714 18.285714 0 0 0 16.708571 10.857143h27.497143a18.285714 18.285714 0 0 0 16.72-10.868572l67.542857-152.4V793.142857a18.285714 18.285714 0 0 0 18.297143 18.285714H659.428571a18.285714 18.285714 0 0 0 18.285715-18.285714V482.285714a18.285714 18.285714 0 0 0-18.285715-18.285714h-39.714285a18.285714 18.285714 0 0 0-16.765715 10.994286L512.114286 683.657143l-90.834286-208.674286a18.285714 18.285714 0 0 0-16.765714-10.982857H364.571429a18.285714 18.285714 0 0 0-18.285715 18.285714v310.857143a18.285714 18.285714 0 0 0 18.285715 18.285714h31.005714a18.285714 18.285714 0 0 0 18.285714-18.285714V613.634286z" p-id="3179" data-spm-anchor-id="a313x.7781069.0.i3" class="selected" fill="#515151"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +0,0 @@
<?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="1689494925506" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1120" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M149.76 0C104.96 0 66.56 35.2 66.56 80V940.8c0 48 38.4 83.2 83.2 83.2h732.8c44.8 0 80-35.2 80-80V323.2L658.56 0H149.76z" fill="#DD5154" p-id="1121"></path><path d="M962.56 326.4v16h-204.8s-99.2-19.2-96-105.6c0 0 3.2 89.6 96 89.6h204.8z" fill="#6B0D12" p-id="1122"></path><path d="M329.91232 652.3904l2.31424 0.02048c21.02272 0.33792 36.81792 5.3504 47.29856 15.15008 10.9056 10.19904 16.29184 26.112 16.29184 47.60064 0 41.71776-21.63712 63.2832-64.01536 63.90272l-1.90976 0.01536h-36.43904v63.45216l-0.03072 1.2544c-0.19968 3.59424-1.23392 6.5536-3.15904 8.8064-2.2784 2.65216-5.85216 3.90144-10.5472 3.90144-4.92544 0-8.63232-1.37728-10.9056-4.28032-1.85344-2.37056-2.87744-5.2224-3.08736-8.4992l-0.03584-1.24928v-173.78816l0.02048-1.28512c0.15872-5.4784 1.34656-9.3696 3.83488-11.61728 2.44736-2.21696 6.91712-3.24608 13.58336-3.36896l1.46432-0.01536h45.32224z m407.21408 0l1.0752 0.02048c8.66304 0.33792 13.47584 4.8384 13.47584 12.86144 0 4.57216-1.30048 7.98208-4.08064 9.96864-2.26304 1.61792-5.40672 2.45248-9.43616 2.60096l-1.37216 0.0256H659.3536v59.38688l69.74464 0.00512 1.17248 0.0256c6.43072 0.26112 10.69056 2.75456 12.00128 7.20896 1.28 2.80576 1.152 5.9392-0.128 8.82688-1.37216 5.7088-5.60128 8.8064-12.0576 9.10336l-1.03936 0.0256H659.3536v81.23904l-0.03584 1.13664c-0.2048 3.31776-1.28 6.0672-3.24608 8.1408-2.29376 2.42688-6.00064 3.52768-11.0336 3.52768-4.88448 0-8.448-1.1264-10.55744-3.6352-1.7408-2.06336-2.67776-4.75136-2.8672-8.00256l-0.03072-1.24416v-176.37888l0.02048-1.20832c0.16896-4.77184 1.3312-8.2688 3.69664-10.3936 2.28864-2.06848 5.88288-3.07712 10.78272-3.2256l1.24928-0.01536h89.79456z m-233.00096 0l2.65728 0.03072c13.3888 0.3072 25.0624 2.90816 35.00544 7.8336 10.57792 5.23776 19.34336 12.42112 26.27584 21.5296 6.89664 9.0624 12.0064 19.73248 15.33952 31.98976 3.31264 12.17024 4.9664 25.15456 4.9664 38.94784 0 13.98272-1.65376 27.06432-4.9664 39.23456-3.328 12.24704-8.38656 22.95296-15.17568 32.1024a72.28416 72.28416 0 0 1-25.85088 21.69344c-9.79968 4.95104-21.26336 7.55712-34.37056 7.84896l-2.47808 0.0256H451.59936l-1.14176-0.01024c-5.84704-0.11776-9.53856-1.09056-11.24352-3.51232-1.38752-1.96096-2.06336-4.92544-2.176-8.97536l-0.01536-1.24416v-173.78816l0.02048-1.25952c0.11264-4.01408 0.78336-6.9632 2.17088-8.92416 1.6896-2.39616 5.32992-3.38944 11.136-3.5072l1.27488-0.01536h52.50048z m-5.76512 24.33024h-33.85856v152.576h41.02656c17.3568 0 30.73536-6.5024 40.3712-19.59936 9.76896-13.27104 14.69952-32.53248 14.69952-57.83552 0-24.51456-5.06368-43.18208-15.08864-56.05888-9.56928-12.288-24.51968-18.6624-45.07648-19.06176l-2.0736-0.02048z m-168.46848 0.86016h-36.43904v76.88192h36.43904c14.08 0 23.92576-3.13344 29.67552-9.23136 5.79584-6.144 8.76544-16.04096 8.76544-29.78304 0-13.6704-3.29728-23.296-9.7536-29.056-6.22592-5.5552-15.13472-8.50944-26.81856-8.79104l-1.8688-0.02048zM658.56 0L962.56 326.4h-208c-80 0-96-64-96-92.8V0z" fill="#FFFFFF" p-id="1123"></path></svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -1 +0,0 @@
<?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="1689495026020" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="958" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M146.56 0C104.96 0 66.56 35.2 66.56 80v864C66.56 988.8 101.76 1024 146.56 1024H879.36c44.8 0 80-35.2 80-80V323.2L658.56 0h-512z" fill="#99B7D2" p-id="959"></path><path d="M962.56 323.2v16h-204.8s-99.2-19.2-96-105.6c0 0 3.2 89.6 96 89.6h204.8z" fill="#527EA7" p-id="960"></path><path d="M449.07008 650.25024c3.00032 0.12288 5.84192 2.09408 8.72448 5.7344l0.7168 0.94208 47.79008 68.11136 45.7216-65.42848 0.64512-1.01376c2.82112-4.30592 5.89824-6.89664 9.33376-7.5776 3.56352-0.7168 7.1168 0.11264 10.54208 2.39616 6.32832 4.29568 7.2704 10.58304 2.944 17.8688l-0.5632 0.91136-52.87424 75.3664 62.83776 89.51808 0.65024 1.3824c1.07008 2.41152 1.7408 4.74624 2.01216 7.00416 0.41984 3.49184-1.56672 6.64064-5.55008 9.4464-7.39328 5.00736-14.1312 3.92192-19.03104-2.97984l-0.55296-0.8192L506.0096 770.6624l-56.43264 80.50688-0.73728 0.98816c-2.47296 3.13856-5.30432 5.04832-8.47872 5.59616-3.20512 0.55296-6.34368-0.24576-9.216-2.2272l-0.84992-0.62976c-3.16416-2.19136-4.85376-5.43744-4.9664-9.48224-0.1024-3.49184 0.8704-6.66112 2.7904-9.28768l0.67584-0.85504 61.19424-87.424-52.83328-75.58656-0.60416-0.8192c-1.67424-2.4576-2.39616-5.40672-2.19136-8.7552a11.42784 11.42784 0 0 1 5.43744-9.28768c3.2768-2.18624 6.36416-3.27168 9.2672-3.1488z m-62.98112 3.584l1.14688 0.03584c3.31264 0.21504 6.1696 1.31584 8.4736 3.30752 2.70336 2.33472 4.03968 5.67808 4.03968 9.82016 0 4.65408-1.58208 8.13568-4.82816 10.0864a17.16736 17.16736 0 0 1-7.74144 2.47296l-1.17248 0.03584h-43.60192v164.37248l-0.03072 1.30048c-0.17408 3.70688-1.08544 6.67136-2.82624 8.84224-2.10944 2.64192-5.7856 3.82464-10.88512 3.82464-5.12 0-8.81664-1.2544-10.92608-4.0192-1.6896-2.22208-2.60096-5.12-2.78016-8.6528l-0.03584-1.3568-0.00512-164.31104h-43.66336l-1.18784-0.03072c-3.39968-0.17408-6.2464-1.09056-8.46848-2.78528-2.73408-2.07872-4.0192-5.51424-4.0192-10.06592 0-4.36224 1.35168-7.75168 4.1472-9.9072 2.29376-1.77152 5.09952-2.74432 8.35072-2.93888l1.24416-0.03584h114.76992z m365.60896 0l1.14688 0.03584c3.31264 0.21504 6.1696 1.31584 8.4736 3.30752 2.70336 2.33472 4.03968 5.67808 4.03968 9.82016 0 4.65408-1.58208 8.13568-4.82816 10.0864a17.16736 17.16736 0 0 1-7.74144 2.47296l-1.17248 0.03584h-43.60192v164.37248l-0.03072 1.30048c-0.17408 3.70688-1.08544 6.67136-2.82624 8.84224-2.10944 2.64192-5.7856 3.82464-10.88512 3.82464-5.12 0-8.81664-1.2544-10.92608-4.0192-1.6896-2.22208-2.60096-5.12-2.78016-8.6528l-0.03584-1.3568v-164.31104h-43.66848l-1.18784-0.03072c-3.39968-0.17408-6.2464-1.09056-8.46848-2.78528-2.73408-2.07872-4.0192-5.51424-4.0192-10.06592 0-4.36224 1.35168-7.75168 4.1472-9.9072 2.29376-1.77152 5.09952-2.74432 8.35072-2.93888l1.24416-0.03584h114.76992zM658.56 0L962.56 323.2h-208c-80 0-96-64-96-92.8V0z" fill="#FFFFFF" p-id="961"></path></svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -1,61 +0,0 @@
async function embedChatbot() {
const chatBtnId = 'fastgpt-chatbot-button';
const chatWindowId = 'fastgpt-chatbot-window';
const script = document.getElementById('fastgpt-iframe');
const botSrc = script?.getAttribute('data-src');
const primaryColor = script?.getAttribute('data-color') || '#4e83fd';
if (!botSrc) {
console.error(`Can't find appid`);
return;
}
if (document.getElementById(chatBtnId)) {
return;
}
const MessageIcon = `<?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="1690532785664" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4132" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M512 32C247.04 32 32 224 32 464A410.24 410.24 0 0 0 172.48 768L160 965.12a25.28 25.28 0 0 0 39.04 22.4l168-112A528.64 528.64 0 0 0 512 896c264.96 0 480-192 480-432S776.96 32 512 32z m244.8 416l-361.6 301.76a12.48 12.48 0 0 1-19.84-12.48l59.2-233.92h-160a12.48 12.48 0 0 1-7.36-23.36l361.6-301.76a12.48 12.48 0 0 1 19.84 12.48l-59.2 233.92h160a12.48 12.48 0 0 1 8 22.08z" fill=${primaryColor} p-id="4133"></path></svg>`;
const CloseIcon = `<?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="1690535441526" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6367" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M512 1024A512 512 0 1 1 512 0a512 512 0 0 1 0 1024zM305.956571 370.395429L447.488 512 305.956571 653.604571a45.568 45.568 0 1 0 64.438858 64.438858L512 576.512l141.604571 141.531429a45.568 45.568 0 0 0 64.438858-64.438858L576.512 512l141.531429-141.604571a45.568 45.568 0 1 0-64.438858-64.438858L512 447.488 370.395429 305.956571a45.568 45.568 0 0 0-64.438858 64.438858z" fill=${primaryColor} p-id="6368"></path></svg>`;
const ChatBtn = document.createElement('div');
ChatBtn.id = chatBtnId;
ChatBtn.style.cssText =
'position: fixed; bottom: 1rem; right: 1rem; width: 40px; height: 40px; cursor: pointer; z-index: 2147483647; ';
const ChatBtnDiv = document.createElement('div');
ChatBtnDiv.style.cssText =
'transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);} display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 9999;';
ChatBtnDiv.innerHTML = MessageIcon;
ChatBtn.appendChild(ChatBtnDiv);
const iframe = document.createElement('iframe');
iframe.allow = 'fullscreen;microphone';
iframe.title = 'FastGpt Chat Window';
iframe.id = chatWindowId;
iframe.src = botSrc;
iframe.style.cssText =
'visibility: hidden; border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 4rem; right: 1rem; width: 24rem; height: 40rem; max-width: 90vw; max-height: 85vh; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;';
document.body.appendChild(iframe);
iframe.onload = () => {
document.body.appendChild(ChatBtn);
};
ChatBtn.addEventListener('click', function () {
const chatWindow = document.getElementById(chatWindowId);
if (!chatWindow) return;
const visibilityVal = chatWindow.style.visibility;
if (visibilityVal === 'hidden') {
chatWindow.style.visibility = 'unset';
ChatBtnDiv.innerHTML = CloseIcon;
} else {
chatWindow.style.visibility = 'hidden';
ChatBtnDiv.innerHTML = MessageIcon;
}
});
}
document.body.onload = embedChatbot;

View File

@@ -1,44 +0,0 @@
{
"App": "App",
"Cancel": "No",
"Confirm": "Yes",
"Warning": "Warning",
"app": {
"App Detail": "App Detail",
"Confirm Del App Tip": "Confirm to delete the app and all its chats",
"Confirm Save App Tip": "After saving, the advanced orchestration configuration will be overwritten. Make sure that the application does not use advanced orchestration.",
"Connection is invalid": "Connecting is invalid",
"Connection type is different": "Connection type is different",
"My Apps": "My Apps"
},
"chat": {
"Confirm to clear history": "Confirm to clear history?",
"Exit Chat": "Exit",
"History": "History",
"New Chat": "New Chat",
"You need to a chat app": "You need to a chat app"
},
"home": {
"Quickly build AI question and answer library": "Quickly build AI question and answer library",
"Start Now": "Start Now",
"Visual AI orchestration": "Visual AI orchestration"
},
"navbar": {
"Account": "Account",
"Apps": "Apps",
"Chat": "Chat",
"Datasets": "DataSets",
"Store": "Store",
"Tools": "Tools"
},
"user": {
"Bill Detail": "Bill Detail",
"Old password is error": "Old password is error",
"OpenAI Account Setting": "OpenAI Account Setting",
"Pay": "Pay",
"Set OpenAI Account Failed": "Set OpenAI account failed",
"Update Password": "Update Password",
"Update password failed": "Update password failed",
"Update password succseful": "Update password succseful"
}
}

View File

@@ -1,44 +0,0 @@
{
"App": "应用",
"Cancel": "取消",
"Confirm": "确认",
"Warning": "提示",
"app": {
"App Detail": "应用详情",
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",
"Confirm Save App Tip": "保存后将会覆盖高级编排配置,请确保该应用未使用高级编排功能。",
"Connection is invalid": "连接无效",
"Connection type is different": "连接的类型不一致",
"My Apps": "我的应用"
},
"chat": {
"Confirm to clear history": "确认清空该应用的聊天记录?",
"Exit Chat": "退出聊天",
"History": "记录",
"New Chat": "新对话",
"You need to a chat app": "你需要创建一个应用"
},
"home": {
"Quickly build AI question and answer library": "快速搭建 AI 问答系统",
"Start Now": "立即开始",
"Visual AI orchestration": "可视化 AI 编排"
},
"navbar": {
"Account": "账号",
"Apps": "应用",
"Chat": "聊天",
"Datasets": "知识库",
"Store": "应用市场",
"Tools": "工具"
},
"user": {
"Bill Detail": "账单详情",
"Old password is error": "旧密码错误",
"OpenAI Account Setting": "OpenAI 账号配置",
"Pay": "充值",
"Set OpenAI Account Failed": "设置 OpenAI 账号异常",
"Update Password": "修改密码",
"Update password failed": "修改密码异常",
"Update password succseful": "修改密码成功"
}
}

View File

@@ -1,53 +0,0 @@
import { GET, POST, DELETE, PUT } from './request';
import type { AppSchema } from '@/types/mongoSchema';
import type { AppListItemType, AppUpdateParams } from '@/types/app';
import { RequestPaging } from '../types/index';
import type { Props as CreateAppProps } from '@/pages/api/app/create';
import { addDays } from 'date-fns';
/**
* 获取模型列表
*/
export const getMyModels = () => GET<AppListItemType[]>('/app/myApps');
/**
* 创建一个模型
*/
export const postCreateApp = (data: CreateAppProps) => POST<string>('/app/create', data);
/**
* 根据 ID 删除模型
*/
export const delModelById = (id: string) => DELETE(`/app/del?appId=${id}`);
/**
* 根据 ID 获取模型
*/
export const getModelById = (id: string) => GET<AppSchema>(`/app/detail?appId=${id}`);
/**
* 根据 ID 更新模型
*/
export const putAppById = (id: string, data: AppUpdateParams) =>
PUT(`/app/update?appId=${id}`, data);
/* 共享市场 */
/**
* 获取共享市场模型
*/
export const getShareModelList = (data: { searchText?: string } & RequestPaging) =>
POST(`/app/share/getModels`, data);
/**
* 收藏/取消收藏模型
*/
export const triggerModelCollection = (appId: string) =>
POST<number>(`/app/share/collection?appId=${appId}`);
// ====================== data
export const getAppTotalUsage = (data: { appId: string }) =>
POST<{ date: String; total: number }[]>(`/app/data/totalUsage`, {
...data,
start: addDays(new Date(), -13),
end: addDays(new Date(), 1)
}).then((res) => (res.length === 0 ? [{ date: new Date(), total: 0 }] : res));

View File

@@ -1,76 +0,0 @@
import { GET, POST, DELETE, PUT } from './request';
import type { ChatHistoryItemType } from '@/types/chat';
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import { RequestPaging } from '../types/index';
import type { OutLinkSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/app';
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
/**
* 获取初始化聊天内容
*/
export const getInitChatSiteInfo = (data: { appId: string; chatId?: string }) =>
GET<InitChatResponse>(`/chat/init`, data);
/**
* 获取历史记录
*/
export const getChatHistory = (data: RequestPaging & { appId?: string }) =>
POST<ChatHistoryItemType[]>('/chat/history/getHistory', data);
/**
* 删除一条历史记录
*/
export const delChatHistoryById = (chatId: string) => DELETE(`/chat/removeHistory`, { chatId });
/**
* clear all history by appid
*/
export const clearChatHistoryByAppId = (appId: string) => DELETE(`/chat/removeHistory`, { appId });
/**
* update history quote status
*/
export const updateHistoryQuote = (params: {
chatId: string;
contentId: string;
quoteId: string;
sourceText: string;
}) => PUT(`/chat/history/updateHistoryQuote`, params);
/**
* 删除一句对话
*/
export const delChatRecordByIndex = (data: { chatId: string; contentId: string }) =>
DELETE(`/chat/delChatRecordByContentId`, data);
/**
* 修改历史记录: 标题/置顶
*/
export const putChatHistory = (data: UpdateHistoryProps) =>
PUT('/chat/history/updateChatHistory', data);
/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string }) =>
GET<InitShareChatResponse>(`/chat/shareChat/init`, data);
/**
* create a shareChat
*/
export const createShareChat = (
data: ShareChatEditType & {
appId: string;
}
) => POST<string>(`/chat/shareChat/create`, data);
/**
* get shareChat
*/
export const getShareChatList = (appId: string) =>
GET<OutLinkSchema[]>(`/chat/shareChat/list`, { appId });
/**
* delete a shareChat
*/
export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`);

View File

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

View File

@@ -1,93 +0,0 @@
import { GET, POST, PUT, DELETE } from '../request';
import type { KbItemType, KbListItemType } from '@/types/plugin';
import { RequestPaging } from '@/types/index';
import { TrainingModeEnum } from '@/constants/plugin';
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';
import { Response as KbDataItemType } from '@/pages/api/plugins/kb/data/getDataById';
import { Props as UpdateDataProps } from '@/pages/api/openapi/kb/updateData';
export type KbUpdateParams = {
id: string;
name: string;
tags: string;
avatar: string;
};
/* knowledge base */
export const getKbList = () => GET<KbListItemType[]>(`/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<KbDataItemType>(`/plugins/kb/data/getDataById`, { dataId });
/**
* 直接push数据
*/
export const postKbDataFromList = (data: PushDataProps) =>
POST<PushDateResponse>(`/openapi/kb/pushData`, data);
/**
* 更新一条数据
*/
export const putKbDataById = (data: UpdateDataProps) => 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,6 +0,0 @@
import { AppListItemType } from '@/types/app';
export type AppListResponse = {
myApps: AppListItemType[];
myCollectionApps: AppListItemType[];
};

View File

@@ -1,25 +0,0 @@
import type { AppSchema } from '@/types/mongoSchema';
import type { ChatItemType } from '@/types/chat';
import { VariableItemType } from '@/types/app';
export interface InitChatResponse {
chatId: string;
appId: string;
app: {
variableModules?: VariableItemType[];
welcomeText?: string;
chatModels?: string[];
name: string;
avatar: string;
intro: string;
canUse?: boolean;
};
title: string;
variables: Record<string, any>;
history: ChatItemType[];
}
export interface InitShareChatResponse {
userAvatar: string;
app: InitChatResponse['app'];
}

View File

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

View File

@@ -1,22 +0,0 @@
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}
fallbackStrategy={'onError'}
borderRadius={'50%'}
objectFit={'cover'}
alt=""
w={w}
h={w}
p={'1px'}
{...props}
/>
);
};
export default Avatar;

View File

@@ -1,42 +0,0 @@
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

@@ -1,43 +0,0 @@
import React from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import { ChatItemType } from '@/types/chat';
import MyModal from '../MyModal';
const ContextModal = ({
context = [],
onClose
}: {
context: ChatItemType[];
onClose: () => void;
}) => {
const theme = useTheme();
return (
<MyModal
isOpen={true}
onClose={onClose}
title={`完整对话记录(${context.length}条)`}
h={['90vh', '80vh']}
minW={['90vw', '600px']}
isCentered
>
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} fontSize={'sm'}>
{context.map((item, i) => (
<Box
key={i}
p={2}
borderRadius={'lg'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
position={'relative'}
>
<Box fontWeight={'bold'}>{item.obj}</Box>
<Box>{item.value}</Box>
</Box>
))}
</ModalBody>
</MyModal>
);
};
export default ContextModal;

View File

@@ -1,137 +0,0 @@
import React, { useCallback, useState } from 'react';
import { ModalBody, ModalCloseButton, ModalHeader, Box, useTheme } from '@chakra-ui/react';
import { getKbDataItemById } from '@/api/plugins/kb';
import { useLoading } from '@/hooks/useLoading';
import { useToast } from '@/hooks/useToast';
import { getErrText } from '@/utils/tools';
import { QuoteItemType } from '@/types/chat';
import MyIcon from '@/components/Icon';
import InputDataModal from '@/pages/kb/detail/components/InputDataModal';
import MyModal from '../MyModal';
const QuoteModal = ({
onUpdateQuote,
rawSearch = [],
onClose
}: {
onUpdateQuote: (quoteId: string, sourceText: string) => Promise<void>;
rawSearch: QuoteItemType[];
onClose: () => void;
}) => {
const theme = useTheme();
const { toast } = useToast();
const { setIsLoading, Loading } = useLoading();
const [editDataItem, setEditDataItem] = useState<{
kbId: string;
dataId: string;
a: string;
q: string;
}>();
/**
* click edit, get new kbDataItem
*/
const onclickEdit = useCallback(
async (item: QuoteItemType) => {
try {
setIsLoading(true);
const data = (await getKbDataItemById(item.id)) as QuoteItemType;
if (!data) {
onUpdateQuote(item.id, '已删除');
throw new Error('该数据已被删除');
}
setEditDataItem({
kbId: data.kb_id,
dataId: data.id,
q: data.q,
a: data.a
});
} catch (err) {
toast({
status: 'warning',
title: getErrText(err)
});
}
setIsLoading(false);
},
[setIsLoading, toast, onUpdateQuote]
);
return (
<>
<MyModal
isOpen={true}
onClose={onClose}
h={['90vh', '80vh']}
isCentered
minW={['90vw', '600px']}
title={
<>
({rawSearch.length})
<Box fontSize={['xs', 'sm']} fontWeight={'normal'}>
注意: 修改知识库内容成功后
</Box>
</>
}
>
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} fontSize={'sm'}>
{rawSearch.map((item) => (
<Box
key={item.id}
flex={'1 0 0'}
p={2}
borderRadius={'lg'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
position={'relative'}
_hover={{ '& .edit': { display: 'flex' } }}
>
{item.source && <Box color={'myGray.600'}>({item.source})</Box>}
<Box>{item.q}</Box>
<Box>{item.a}</Box>
<Box
className="edit"
display={'none'}
position={'absolute'}
right={0}
top={0}
bottom={0}
w={'40px'}
bg={'rgba(255,255,255,0.9)'}
alignItems={'center'}
justifyContent={'center'}
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
>
<MyIcon
name={'edit'}
w={'18px'}
h={'18px'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{
color: 'myBlue.700'
}}
onClick={() => onclickEdit(item)}
/>
</Box>
</Box>
))}
</ModalBody>
<Loading fixed={false} />
</MyModal>
{editDataItem && (
<InputDataModal
onClose={() => setEditDataItem(undefined)}
onSuccess={() => onUpdateQuote(editDataItem.dataId, '手动修改')}
onDelete={() => onUpdateQuote(editDataItem.dataId, '已删除')}
kbId={editDataItem.kbId}
defaultValues={editDataItem}
/>
)}
</>
);
};
export default QuoteModal;

View File

@@ -1,94 +0,0 @@
import React, { useCallback, useMemo, useState } from 'react';
import { ChatModuleEnum } from '@/constants/chat';
import { ChatHistoryItemResType, ChatItemType, QuoteItemType } from '@/types/chat';
import { Flex, BoxProps } from '@chakra-ui/react';
import dynamic from 'next/dynamic';
import Tag from '../Tag';
import MyTooltip from '../MyTooltip';
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
const ContextModal = dynamic(() => import('./ContextModal'), { ssr: false });
const ResponseDetailModal = ({
chatId,
contentId,
responseData = []
}: {
chatId?: string;
contentId?: string;
responseData?: ChatHistoryItemResType[];
}) => {
const [quoteModalData, setQuoteModalData] = useState<QuoteItemType[]>();
const [contextModalData, setContextModalData] = useState<ChatItemType[]>();
const {
tokens = 0,
quoteList = [],
completeMessages = []
} = useMemo(() => {
const chatData = responseData.find((item) => item.moduleName === ChatModuleEnum.AIChat);
if (!chatData) return {};
return {
tokens: chatData.tokens,
quoteList: chatData.quoteList,
completeMessages: chatData.completeMessages
};
}, [responseData]);
const isEmpty = useMemo(
() => quoteList.length === 0 && completeMessages.length === 0 && tokens === 0,
[completeMessages.length, quoteList.length, tokens]
);
const updateQuote = useCallback(async (quoteId: string, sourceText: string) => {}, []);
const TagStyles: BoxProps = {
mr: 2,
bg: 'transparent'
};
return isEmpty ? null : (
<Flex alignItems={'center'} mt={2} flexWrap={'wrap'}>
{quoteList.length > 0 && (
<MyTooltip label="查看引用">
<Tag
colorSchema="blue"
cursor={'pointer'}
{...TagStyles}
onClick={() => setQuoteModalData(quoteList)}
>
{quoteList.length}
</Tag>
</MyTooltip>
)}
{completeMessages.length > 0 && (
<MyTooltip label={'点击查看完整对话记录'}>
<Tag
colorSchema="green"
cursor={'pointer'}
{...TagStyles}
onClick={() => setContextModalData(completeMessages)}
>
{completeMessages.length}
</Tag>
</MyTooltip>
)}
{tokens > 0 && (
<Tag colorSchema="gray" cursor={'default'} {...TagStyles}>
{tokens}tokens
</Tag>
)}
{!!quoteModalData && (
<QuoteModal
rawSearch={quoteModalData}
onUpdateQuote={updateQuote}
onClose={() => setQuoteModalData(undefined)}
/>
)}
{!!contextModalData && (
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
)}
</Flex>
);
};
export default ResponseDetailModal;

View File

@@ -1,818 +0,0 @@
import React, {
useCallback,
useRef,
useState,
useMemo,
forwardRef,
useImperativeHandle,
ForwardedRef,
useEffect
} from 'react';
import { throttle } from 'lodash';
import {
ChatHistoryItemResType,
ChatItemType,
ChatSiteItemType,
ExportChatType
} from '@/types/chat';
import { useToast } from '@/hooks/useToast';
import {
useCopyData,
voiceBroadcast,
cancelBroadcast,
hasVoiceApi,
getErrText
} from '@/utils/tools';
import { Box, Card, Flex, Input, Textarea, Button, useTheme, BoxProps } from '@chakra-ui/react';
import { feConfigs } from '@/store/static';
import { Types } from 'mongoose';
import { EventNameEnum } from '../Markdown/constant';
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { useMarkdown } from '@/hooks/useMarkdown';
import { VariableItemType } from '@/types/app';
import { VariableInputEnum } from '@/constants/app';
import { useForm } from 'react-hook-form';
import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
import { fileDownload } from '@/utils/file';
import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global';
import { TaskResponseKeyEnum } from '@/constants/chat';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
import Markdown from '@/components/Markdown';
import MySelect from '@/components/Select';
import MyTooltip from '../MyTooltip';
import dynamic from 'next/dynamic';
const ResponseDetailModal = dynamic(() => import('./ResponseDetailModal'));
import styles from './index.module.scss';
const textareaMinH = '22px';
export type StartChatFnProps = {
messages: MessageItemType[];
controller: AbortController;
variables: Record<string, any>;
generatingMessage: (text: string) => void;
};
export type ComponentRef = {
getChatHistory: () => ChatSiteItemType[];
resetVariables: (data?: Record<string, any>) => void;
resetHistory: (chatId: ChatSiteItemType[]) => void;
scrollToBottom: (behavior?: 'smooth' | 'auto') => void;
};
const VariableLabel = ({
required = false,
children
}: {
required?: boolean;
children: React.ReactNode | string;
}) => (
<Box as={'label'} display={'inline-block'} position={'relative'} mb={1}>
{children}
{required && (
<Box position={'absolute'} top={'-2px'} right={'-10px'} color={'red.500'} fontWeight={'bold'}>
*
</Box>
)}
</Box>
);
const Empty = () => {
const { data: chatProblem } = useMarkdown({ url: '/chatProblem.md' });
const { data: versionIntro } = useMarkdown({ url: '/versionIntro.md' });
return (
<Box
pt={[6, 0]}
w={'85%'}
maxW={'600px'}
m={'auto'}
alignItems={'center'}
justifyContent={'center'}
>
{/* version intro */}
<Card p={4} mb={10}>
<Markdown source={versionIntro} />
</Card>
<Card p={4}>
<Markdown source={chatProblem} />
</Card>
</Box>
);
};
const ChatAvatar = ({ src, type }: { src?: string; type: 'Human' | 'AI' }) => {
const theme = useTheme();
return (
<Box
w={['28px', '34px']}
h={['28px', '34px']}
p={'2px'}
borderRadius={'lg'}
border={theme.borders.base}
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
bg={type === 'Human' ? 'white' : 'myBlue.100'}
>
<Avatar src={src} w={'100%'} h={'100%'} />
</Box>
);
};
const ChatBox = (
{
showEmptyIntro = false,
chatId,
appAvatar,
userAvatar,
variableModules,
welcomeText,
onUpdateVariable,
onStartChat,
onDelMessage
}: {
showEmptyIntro?: boolean;
chatId?: string;
appAvatar?: string;
userAvatar?: string;
variableModules?: VariableItemType[];
welcomeText?: string;
onUpdateVariable?: (e: Record<string, any>) => void;
onStartChat: (e: StartChatFnProps) => Promise<{
responseText: string;
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType[];
}>;
onDelMessage?: (e: { contentId?: string; index: number }) => void;
},
ref: ForwardedRef<ComponentRef>
) => {
const ChatBoxRef = useRef<HTMLDivElement>(null);
const theme = useTheme();
const router = useRouter();
const { copyData } = useCopyData();
const { toast } = useToast();
const { isPc } = useGlobalStore();
const TextareaDom = useRef<HTMLTextAreaElement>(null);
const controller = useRef(new AbortController());
const [refresh, setRefresh] = useState(false);
const [variables, setVariables] = useState<Record<string, any>>({});
const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]);
const isChatting = useMemo(
() => chatHistory[chatHistory.length - 1]?.status === 'loading',
[chatHistory]
);
const variableIsFinish = useMemo(() => {
if (!variableModules || chatHistory.length > 0) return true;
for (let i = 0; i < variableModules.length; i++) {
const item = variableModules[i];
if (item.required && !variables[item.key]) {
return false;
}
}
return true;
}, [chatHistory.length, variableModules, variables]);
const { register, reset, getValues, setValue, handleSubmit } = useForm<Record<string, any>>({
defaultValues: variables
});
// 滚动到底部
const scrollToBottom = useCallback(
(behavior: 'smooth' | 'auto' = 'smooth') => {
if (!ChatBoxRef.current) return;
ChatBoxRef.current.scrollTo({
top: ChatBoxRef.current.scrollHeight,
behavior
});
},
[ChatBoxRef]
);
// 聊天信息生成中……获取当前滚动条位置,判断是否需要滚动到底部
const generatingScroll = useCallback(
throttle(() => {
if (!ChatBoxRef.current) return;
const isBottom =
ChatBoxRef.current.scrollTop + ChatBoxRef.current.clientHeight + 150 >=
ChatBoxRef.current.scrollHeight;
isBottom && scrollToBottom('auto');
}, 100),
[]
);
// eslint-disable-next-line react-hooks/exhaustive-deps
const generatingMessage = useCallback(
(text: string) => {
setChatHistory((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
value: item.value + text
};
})
);
generatingScroll();
},
[generatingScroll, setChatHistory]
);
// 复制内容
const onclickCopy = useCallback(
(value: string) => {
const val = value.replace(/\n+/g, '\n');
copyData(val);
},
[copyData]
);
// 重置输入内容
const resetInputVal = useCallback((val: string) => {
if (!TextareaDom.current) return;
setTimeout(() => {
/* 回到最小高度 */
if (TextareaDom.current) {
TextareaDom.current.value = val;
TextareaDom.current.style.height =
val === '' ? textareaMinH : `${TextareaDom.current.scrollHeight}px`;
}
}, 100);
}, []);
/**
* user confirm send prompt
*/
const sendPrompt = useCallback(
async (data: Record<string, any> = {}, inputVal = '') => {
if (isChatting) {
toast({
title: '正在聊天中...请等待结束',
status: 'warning'
});
return;
}
// get input value
const val = inputVal.trim().replace(/\n\s*/g, '\n');
if (!val) {
toast({
title: '内容为空',
status: 'warning'
});
return;
}
const newChatList: ChatSiteItemType[] = [
...chatHistory,
{
_id: String(new Types.ObjectId()),
obj: 'Human',
value: val,
status: 'finish'
},
{
_id: String(new Types.ObjectId()),
obj: 'AI',
value: '',
status: 'loading'
}
];
// 插入内容
setChatHistory(newChatList);
// 清空输入内容
resetInputVal('');
setTimeout(() => {
scrollToBottom();
}, 100);
try {
// create abort obj
const abortSignal = new AbortController();
controller.current = abortSignal;
const messages = adaptChatItem_openAI({ messages: newChatList, reserveId: true });
const { responseData } = await onStartChat({
messages,
controller: abortSignal,
generatingMessage,
variables: data
});
// set finish status
setChatHistory((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
status: 'finish',
responseData
};
})
);
setTimeout(() => {
generatingScroll();
isPc && TextareaDom.current?.focus();
}, 100);
} catch (err: any) {
toast({
title: getErrText(err, '聊天出错了~'),
status: 'error',
duration: 5000,
isClosable: true
});
if (!err?.responseText) {
resetInputVal(inputVal);
setChatHistory(newChatList.slice(0, newChatList.length - 2));
}
// set finish status
setChatHistory((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
status: 'finish'
};
})
);
}
},
[
isChatting,
chatHistory,
resetInputVal,
toast,
scrollToBottom,
onStartChat,
generatingMessage,
generatingScroll,
isPc
]
);
useImperativeHandle(ref, () => ({
getChatHistory: () => chatHistory,
resetVariables(e) {
const defaultVal: Record<string, any> = {};
variableModules?.forEach((item) => {
defaultVal[item.key] = '';
});
reset(e || defaultVal);
setVariables(e || defaultVal);
},
resetHistory(e) {
setChatHistory(e);
},
scrollToBottom
}));
const controlIconStyle = {
w: '14px',
cursor: 'pointer',
p: 1,
bg: 'white',
borderRadius: 'lg',
boxShadow: '0 0 5px rgba(0,0,0,0.1)',
border: theme.borders.base,
mr: 3
};
const controlContainerStyle = {
className: 'control',
color: 'myGray.400',
display: ['flex', 'none'],
pl: 1,
mt: 2
};
const MessageCardStyle: BoxProps = {
px: 4,
py: 3,
borderRadius: '0 8px 8px 8px',
boxShadow: '0 0 8px rgba(0,0,0,0.15)'
};
const messageCardMaxW = ['calc(100% - 25px)', 'calc(100% - 40px)'];
const showEmpty = useMemo(
() =>
feConfigs?.show_emptyChat &&
showEmptyIntro &&
chatHistory.length === 0 &&
!variableModules?.length &&
!welcomeText,
[chatHistory.length, showEmptyIntro, variableModules, welcomeText]
);
useEffect(() => {
return () => {
controller.current?.abort();
// close voice
cancelBroadcast();
};
}, [router.query]);
useEffect(() => {
const listen = () => {
cancelBroadcast();
};
window.addEventListener('beforeunload', listen);
return () => {
window.removeEventListener('beforeunload', listen);
};
}, []);
return (
<Flex flexDirection={'column'} h={'100%'}>
<Box
ref={ChatBoxRef}
flex={'1 0 0'}
h={0}
w={'100%'}
overflow={'overlay'}
px={[4, 0]}
mt={[0, 5]}
pb={3}
>
<Box maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
{showEmpty && <Empty />}
{!!welcomeText && (
<Flex flexDirection={'column'} alignItems={'flex-start'} py={2}>
{/* avatar */}
<ChatAvatar src={appAvatar} type={'AI'} />
{/* message */}
<Card order={2} mt={2} {...MessageCardStyle} bg={'white'} maxW={messageCardMaxW}>
<Markdown
source={`~~~guide \n${welcomeText}`}
isChatting={false}
onClick={(e) => {
const val = e?.data;
if (e?.event !== EventNameEnum.guideClick || !val) return;
handleSubmit((data) => sendPrompt(data, val))();
}}
/>
</Card>
</Flex>
)}
{/* variable input */}
{!!variableModules?.length && (
<Flex flexDirection={'column'} alignItems={'flex-start'} py={2}>
{/* avatar */}
<ChatAvatar src={appAvatar} type={'AI'} />
{/* message */}
<Card
order={2}
mt={2}
bg={'white'}
w={'400px'}
maxW={messageCardMaxW}
{...MessageCardStyle}
>
{variableModules.map((item) => (
<Box key={item.id} mb={4}>
<VariableLabel required={item.required}>{item.label}</VariableLabel>
{item.type === VariableInputEnum.input && (
<Input
isDisabled={variableIsFinish}
{...register(item.key, {
required: item.required
})}
/>
)}
{item.type === VariableInputEnum.select && (
<MySelect
width={'100%'}
isDisabled={variableIsFinish}
list={(item.enums || []).map((item) => ({
label: item.value,
value: item.value
}))}
value={getValues(item.key)}
onchange={(e) => {
setValue(item.key, e);
setRefresh(!refresh);
}}
/>
)}
</Box>
))}
{!variableIsFinish && (
<Button
leftIcon={<MyIcon name={'chatFill'} w={'16px'} />}
size={'sm'}
maxW={'100px'}
borderRadius={'lg'}
onClick={handleSubmit((data) => {
onUpdateVariable?.(data);
setVariables(data);
})}
>
{'开始对话'}
</Button>
)}
</Card>
</Flex>
)}
{/* chat history */}
<Box id={'history'}>
{chatHistory.map((item, index) => (
<Flex
position={'relative'}
key={item._id}
flexDirection={'column'}
alignItems={item.obj === 'Human' ? 'flex-end' : 'flex-start'}
py={5}
_hover={{
'& .control': {
display: item.status === 'finish' ? 'flex' : 'none'
}
}}
>
{item.obj === 'Human' && (
<>
<Flex w={'100%'} alignItems={'center'} justifyContent={'flex-end'}>
<Flex {...controlContainerStyle} justifyContent={'flex-end'} mr={3}>
<MyTooltip label={'复制'}>
<MyIcon
{...controlIconStyle}
name={'copy'}
_hover={{ color: 'myBlue.700' }}
onClick={() => onclickCopy(item.value)}
/>
</MyTooltip>
{onDelMessage && (
<MyTooltip label={'删除'}>
<MyIcon
{...controlIconStyle}
mr={0}
name={'delete'}
_hover={{ color: 'red.600' }}
onClick={() => {
setChatHistory((state) =>
state.filter((chat) => chat._id !== item._id)
);
onDelMessage({
contentId: item._id,
index
});
}}
/>
</MyTooltip>
)}
</Flex>
<ChatAvatar src={userAvatar} type={'Human'} />
</Flex>
<Box position={'relative'} maxW={messageCardMaxW} mt={['6px', 2]}>
<Card
className="markdown"
whiteSpace={'pre-wrap'}
{...MessageCardStyle}
bg={'myBlue.300'}
borderRadius={'8px 0 8px 8px'}
>
<Box as={'p'}>{item.value}</Box>
</Card>
</Box>
</>
)}
{item.obj === 'AI' && (
<>
<Flex w={'100%'} alignItems={'center'}>
<ChatAvatar src={appAvatar} type={'AI'} />
<Flex {...controlContainerStyle} ml={3}>
<MyTooltip label={'复制'}>
<MyIcon
{...controlIconStyle}
name={'copy'}
_hover={{ color: 'myBlue.700' }}
onClick={() => onclickCopy(item.value)}
/>
</MyTooltip>
{onDelMessage && (
<MyTooltip label={'删除'}>
<MyIcon
{...controlIconStyle}
name={'delete'}
_hover={{ color: 'red.600' }}
onClick={() => {
setChatHistory((state) =>
state.filter((chat) => chat._id !== item._id)
);
onDelMessage({
contentId: item._id,
index
});
}}
/>
</MyTooltip>
)}
{hasVoiceApi && (
<MyTooltip label={'语音播报'}>
<MyIcon
{...controlIconStyle}
name={'voice'}
_hover={{ color: '#E74694' }}
onClick={() => voiceBroadcast({ text: item.value })}
/>
</MyTooltip>
)}
</Flex>
</Flex>
<Box position={'relative'} maxW={messageCardMaxW} mt={['6px', 2]}>
<Card bg={'white'} {...MessageCardStyle}>
<Markdown
source={item.value}
isChatting={index === chatHistory.length - 1 && isChatting}
/>
<ResponseDetailModal
chatId={chatId}
contentId={item._id}
responseData={item.responseData}
/>
</Card>
</Box>
</>
)}
</Flex>
))}
</Box>
</Box>
</Box>
{/* input */}
{variableIsFinish ? (
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(750px, 100%)']} px={[0, 5]}>
<Box
py={'18px'}
position={'relative'}
boxShadow={`0 0 10px rgba(0,0,0,0.2)`}
borderTop={['1px solid', 0]}
borderTopColor={'myGray.200'}
borderRadius={['none', 'md']}
backgroundColor={'white'}
>
{/* 输入框 */}
<Textarea
ref={TextareaDom}
py={0}
pr={['45px', '55px']}
border={'none'}
_focusVisible={{
border: 'none'
}}
placeholder="提问"
resize={'none'}
rows={1}
height={'22px'}
lineHeight={'22px'}
maxHeight={'150px'}
maxLength={-1}
overflowY={'auto'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
boxShadow={'none !important'}
color={'myGray.900'}
onChange={(e) => {
const textarea = e.target;
textarea.style.height = textareaMinH;
textarea.style.height = `${textarea.scrollHeight}px`;
}}
onKeyDown={(e) => {
// 触发快捷发送
if (e.keyCode === 13 && !e.shiftKey) {
handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))();
e.preventDefault();
}
// 全选内容
// @ts-ignore
e.key === 'a' && e.ctrlKey && e.target?.select();
}}
/>
{/* 发送和等待按键 */}
<Flex
alignItems={'center'}
justifyContent={'center'}
h={'25px'}
w={'25px'}
position={'absolute'}
right={['12px', '20px']}
bottom={'15px'}
>
{isChatting ? (
<MyIcon
className={styles.stopIcon}
width={['22px', '25px']}
height={['22px', '25px']}
cursor={'pointer'}
name={'stop'}
color={'gray.500'}
onClick={() => controller.current?.abort()}
/>
) : (
<MyIcon
name={'chatSend'}
width={['18px', '20px']}
height={['18px', '20px']}
cursor={'pointer'}
color={'gray.500'}
onClick={() => {
handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))();
}}
/>
)}
</Flex>
</Box>
</Box>
) : null}
</Flex>
);
};
export default React.memo(forwardRef(ChatBox));
export const useChatBox = () => {
const onExportChat = useCallback(
({ type, history }: { type: ExportChatType; history: ChatItemType[] }) => {
const getHistoryHtml = () => {
const historyDom = document.getElementById('history');
if (!historyDom) return;
const dom = Array.from(historyDom.children).map((child, i) => {
const avatar = `<img src="${
child.querySelector<HTMLImageElement>('.avatar')?.src
}" alt="" />`;
const chatContent = child.querySelector<HTMLDivElement>('.markdown');
if (!chatContent) {
return '';
}
const chatContentClone = chatContent.cloneNode(true) as HTMLDivElement;
const codeHeader = chatContentClone.querySelectorAll('.code-header');
codeHeader.forEach((childElement: any) => {
childElement.remove();
});
return `<div class="chat-item">
${avatar}
${chatContentClone.outerHTML}
</div>`;
});
const html = htmlTemplate.replace('{{CHAT_CONTENT}}', dom.join('\n'));
return html;
};
const map: Record<ExportChatType, () => void> = {
md: () => {
fileDownload({
text: history.map((item) => item.value).join('\n\n'),
type: 'text/markdown',
filename: 'chat.md'
});
},
html: () => {
const html = getHistoryHtml();
html &&
fileDownload({
text: html,
type: 'text/html',
filename: '聊天记录.html'
});
},
pdf: () => {
const html = getHistoryHtml();
html &&
// @ts-ignore
html2pdf(html, {
margin: 0,
filename: `聊天记录.pdf`
});
}
};
map[type]();
},
[]
);
return {
onExportChat
};
};

View File

@@ -1,29 +0,0 @@
import { SystemInputEnum } from '@/constants/app';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { getChatModel } from '@/service/utils/data';
import { AppModuleItemType, VariableItemType } from '@/types/app';
export const getSpecialModule = (modules: AppModuleItemType[]) => {
const welcomeText: string =
modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
const variableModules: VariableItemType[] =
modules
.find((item) => item.flowType === FlowModuleTypeEnum.variable)
?.inputs.find((item) => item.key === SystemInputEnum.variables)?.value || [];
return {
welcomeText,
variableModules
};
};
export const getChatModelNameList = (modules: AppModuleItemType[]): string[] => {
const chatModules = modules.filter((item) => item.flowType === FlowModuleTypeEnum.chatNode);
return chatModules
.map(
(item) => getChatModel(item.inputs.find((input) => input.key === 'model')?.value)?.name || ''
)
.filter((item) => item);
};

View File

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

View File

@@ -1,121 +0,0 @@
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

@@ -1,22 +0,0 @@
import React from 'react';
import { Flex, type FlexProps } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
const CloseIcon = (props: FlexProps) => {
return (
<Flex
cursor={'pointer'}
w={'22px'}
h={'22px'}
alignItems={'center'}
justifyContent={'center'}
borderRadius={'50%'}
_hover={{ bg: 'myGray.200' }}
{...props}
>
<MyIcon name={'closeLight'} w={'12px'} color={'myGray.500'} />
</Flex>
);
};
export default CloseIcon;

View File

@@ -1,25 +0,0 @@
import React from 'react';
import MyIcon from '@/components/Icon';
import { IconProps } from '@chakra-ui/react';
const DeleteIcon = (props: IconProps) => {
return (
<MyIcon
className="delete"
name={'delete' as any}
w={'14px'}
_hover={{ color: 'red.600' }}
display={['block', 'none']}
cursor={'pointer'}
{...props}
/>
);
};
export default DeleteIcon;
export const hoverDeleteStyles = {
'& .delete': {
display: 'block'
}
};

View File

@@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1 +0,0 @@
<?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" ><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>

Before

Width:  |  Height:  |  Size: 844 B

View File

@@ -1 +0,0 @@
<?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="1689057990782" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1770" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M878.5 255.1H770V146.7c0-43.6-35.5-79.1-79.1-79.1H145.2c-43.6 0-79.1 35.5-79.1 79.1v545.8c0 43.6 35.5 79.1 79.1 79.1h108.4V880c0 43.6 35.5 79.1 79.1 79.1h545.8c43.6 0 79.1-35.5 79.1-79.1V334.2c-0.1-43.6-35.6-79.1-79.1-79.1zM145.2 707.5c-8.3 0-15.1-6.8-15.1-15.1V146.7c0-8.3 6.8-15.1 15.1-15.1H691c8.3 0 15.1 6.8 15.1 15.1v545.8c0 8.3-6.8 15.1-15.1 15.1H145.2zM893.5 880c0 8.3-6.8 15.1-15.1 15.1H332.7c-8.3 0-15.1-6.8-15.1-15.1V771.5H691c43.6 0 79.1-35.5 79.1-79.1V319.1h108.4c8.3 0 15.1 6.8 15.1 15.1V880z" p-id="1771"></path></svg>

Before

Width:  |  Height:  |  Size: 840 B

View File

@@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 924 B

View File

@@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 810 B

View File

@@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1 +0,0 @@
<?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="1689489239258" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23019" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M145.6 0C100.8 0 64 36.8 64 81.6v860.8C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L656 0H145.6z" fill="#45B058" p-id="23020"></path><path d="M388.8 691.2c1.6 1.6 3.2 4.8 3.2 8 0 6.4-4.8 11.2-11.2 11.2-3.2 0-6.4 0-8-3.2-11.2-12.8-30.4-22.4-48-22.4-41.6 0-73.6 32-73.6 78.4 0 44.8 32 78.4 73.6 78.4 17.6 0 35.2-8 48-22.4 1.6-1.6 4.8-3.2 8-3.2 6.4 0 11.2 4.8 11.2 11.2 0 3.2-1.6 6.4-3.2 8-14.4 16-35.2 27.2-64 27.2-56 0-99.2-40-99.2-99.2s43.2-99.2 99.2-99.2c28.8 0 49.6 11.2 64 27.2z m108.8 171.2c-28.8 0-51.2-9.6-67.2-24-3.2-1.6-3.2-4.8-3.2-8 0-6.4 3.2-12.8 11.2-12.8 1.6 0 4.8 1.6 6.4 3.2 12.8 11.2 32 20.8 54.4 20.8 33.6 0 44.8-19.2 44.8-33.6 0-49.6-113.6-22.4-113.6-91.2 0-30.4 27.2-52.8 65.6-52.8 24 0 46.4 8 62.4 20.8 1.6 1.6 3.2 4.8 3.2 8 0 6.4-4.8 11.2-11.2 11.2-1.6 0-4.8 0-6.4-1.6-14.4-11.2-32-17.6-49.6-17.6-24 0-40 12.8-40 30.4 0 43.2 113.6 19.2 113.6 91.2 0 27.2-19.2 56-70.4 56z m272-179.2L702.4 848c-3.2 8-9.6 12.8-17.6 12.8h-1.6c-8 0-16-4.8-19.2-12.8l-65.6-164.8c-1.6-1.6-1.6-3.2-1.6-4.8 0-6.4 4.8-12.8 12.8-12.8 4.8 0 9.6 3.2 11.2 8l62.4 160 62.4-160c1.6-4.8 6.4-8 11.2-8 8 0 14.4 6.4 14.4 12.8 0 1.6-1.6 3.2-1.6 4.8z" fill="#FFFFFF" p-id="23021"></path><path d="M960 326.4v16H755.2s-100.8-20.8-97.6-108.8c0 0 3.2 92.8 96 92.8H960z" fill="#349C42" p-id="23022"></path><path d="M657.6 0v233.6c0 25.6 17.6 92.8 97.6 92.8H960L657.6 0z" fill="#FFFFFF" p-id="23023"></path></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1 +0,0 @@
<?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="1689324646567" class="icon" viewBox="0 0 1110 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3584" data-spm-anchor-id="a313x.7781069.0.i4" xmlns:xlink="http://www.w3.org/1999/xlink" width="69.375" height="64"><path d="M919.75593219 424.16813563a21.72203344 21.72203344 0 0 0 21.65694937-21.78711844V140.93559313c0-60.04067812-48.545085-108.93559312-108.26847469-108.93559313H140.26033906C80.545085 32 32 80.894915 32 140.93559313v740.77017c0 60.04067812 48.545085 108.93559312 108.26033906 108.93559312h692.88406781c59.71525406 0 108.26033906-48.894915 108.26033907-108.93559312V750.98305063a21.72203344 21.72203344 0 0 0-21.64881375-21.78711844 21.72203344 21.72203344 0 0 0-21.65694938 21.78711843v130.72271251c0 36.04067812-29.14169531 65.36135625-64.95457594 65.36135531H140.26033906c-35.81288156 0-64.95457594-29.32881375-64.95457593-65.36135531V249.87118625h822.80135531v152.50983094a21.72203344 21.72203344 0 0 0 21.64881375 21.78711843zM75.30576312 206.29694937V140.93559313c0-36.04067812 29.14169531-65.36135625 64.95457594-65.36135625h692.88406781c35.81288156 0 64.96271156 29.32881375 64.96271157 65.36135625v65.36135625H75.30576312z m64.95457594-43.57423781a21.72203344 21.72203344 0 0 0 21.65694938-21.78711843 21.72203344 21.72203344 0 0 0-21.65694938-21.78711844 21.72203344 21.72203344 0 0 0-21.64881375 21.78711844 21.72203344 21.72203344 0 0 0 21.64881375 21.78711843z m86.61152531 0a21.72203344 21.72203344 0 0 0 21.65694938-21.78711843 21.72203344 21.72203344 0 0 0-21.65694938-21.78711844 21.72203344 21.72203344 0 0 0-21.64881375 21.78711844 21.72203344 21.72203344 0 0 0 21.64881375 21.78711843z m86.61152532 0a21.72203344 21.72203344 0 0 0 21.65694937-21.78711843 21.72203344 21.72203344 0 0 0-21.65694937-21.78711844 21.72203344 21.72203344 0 0 0-21.64881375 21.78711844 21.72203344 21.72203344 0 0 0 21.64881375 21.78711843zM486.70644031 467.7423725a21.72203344 21.72203344 0 0 1 21.64881375 21.795255v174.29694844a21.72203344 21.72203344 0 0 1-21.64881375 21.78711937 21.72203344 21.72203344 0 0 1-21.65694844-21.78711937v-65.36135531H378.43796656v65.36135531a21.72203344 21.72203344 0 0 1-21.64881375 21.78711937 21.72203344 21.72203344 0 0 1-21.65694937-21.78711937V489.5376275a21.72203344 21.72203344 0 0 1 21.65694937-21.795255 21.72203344 21.72203344 0 0 1 21.64881375 21.795255v65.36135531H465.0576275v-65.36135531a21.72203344 21.72203344 0 0 1 21.65694844-21.795255z m173.21491594 0c11.95932188 0 21.665085 9.76271156 21.66508406 21.78711937a21.72203344 21.72203344 0 0 1-21.65694844 21.78711844h-21.65694937v152.51796563a21.72203344 21.72203344 0 0 1-21.64881375 21.78711937 21.72203344 21.72203344 0 0 1-21.65694938-21.78711937V511.31661031h-21.64881281a21.72203344 21.72203344 0 0 1-21.65694937-21.77898281 21.72203344 21.72203344 0 0 1 21.65694937-21.795255h86.60338969z m224.80271156 1.70847469a21.79525406 21.79525406 0 0 1 13.38305063 20.1274575v174.29694937a21.72203344 21.72203344 0 0 1-21.64881375 21.74644125 21.72203344 21.72203344 0 0 1-21.65694844-21.78711937v-121.70847469l-27.97830562 28.14915281a21.57559313 21.57559313 0 0 1-30.61423688 0l-28.01898281-28.14915281v121.70847469a21.72203344 21.72203344 0 0 1-21.65694938 21.78711937 21.72203344 21.72203344 0 0 1-21.64881375-21.78711937V489.5376275c0-8.80271156 5.28813563-16.77559313 13.38305063-20.13559406a21.68135625 21.68135625 0 0 1 23.59322062 4.71050906l49.62711844 49.97694937 49.62711844-49.93627125a21.38847469 21.38847469 0 0 1 23.60949187-4.71050906z m164.94915281 172.58847468a21.72203344 21.72203344 0 0 1 21.65694844 21.78711844 21.72203344 21.72203344 0 0 1-21.65694843 21.78711938h-86.61152532a21.72203344 21.72203344 0 0 1-21.64881375-21.78711938V489.52949188a21.72203344 21.72203344 0 0 1 21.64881375-21.78711938 21.72203344 21.72203344 0 0 1 21.65694844 21.78711938v152.50982999h64.95457688z" p-id="3585"></path></svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1 +0,0 @@
<?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="1689488775813" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6900" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M448 768h448V256H448v512z" fill="#7839ee" opacity=".1" p-id="6901"></path><path d="M960 928H384v-64h576v64z m0-768H384v-64h576v64zM128 736H64v-64h64v64z m0-128H64v-64h64v64z m0-128H64v-64h64v64z m0-128H64v-64h64v64z m0-128H64v-64h64v64zM320 800H256v-64h64v64z m0-128H256v-64h64v64z m0-128H256v-64h64v64z m0-128H256v-64h64v64z m0-128H256v-64h64v64zM640 256h64v512h-64z" fill="#7839ee" p-id="6902"></path><path d="M672 160l96 128H576l96-128zM672 864l96-128H576l96 128z" fill="#7839ee" p-id="6903"></path></svg>

Before

Width:  |  Height:  |  Size: 839 B

View File

@@ -1 +0,0 @@
<?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="1689489413405" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2128" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M753 521.1c-23.2-60.8-116.5-56.6-143.1-4.8-15.9-52.8-121.7-70.4-146.8-6.4 0-51.1-0.8-271.7-0.8-271.7-0.1-32.3-26.3-58.3-58.5-58.3s-58.4 26-58.5 58.3l-1.9 433.7s-23.1-53.6-81.8-83.9c-94.5-48.6-137.3 17.9-92.8 62.6C266.4 748.7 322.2 830.3 356 922c28.6 77.5 87.4 94.2 87.4 94.2h366c48.2-31 62.3-134.1 62.3-134.1V582.2c-0.3-111.6-120.1-93.8-118.7-61.1z" p-id="2129" fill="#4e83fd"></path><path d="M286.3 442.9v-72.4c-36.3-32.1-59.6-78.5-59.6-130.8 0-96.7 78.5-175.2 175.2-175.2 96.7 0 175.2 78.5 175.2 175.2 0 51.2-22.4 96.9-57.4 128.9v74.1c69.9-40.6 117.6-115.4 117.6-202.1 0-129.4-105-234.4-234.4-234.4s-234.4 105-234.4 234.4c-0.1 86.9 47.7 161.8 117.8 202.3z" p-id="2130" fill="#4e83fd"></path></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1 +0,0 @@
<?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="1689324655973" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3734" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 601.09184948l-213.36752354-331.89890023A41.3976083 41.3976083 0 0 0 222.02342717 291.06546788v428.06065559H208.2150185a41.42522469 41.42522469 0 0 0 0 82.85044936h110.46726671a41.42522469 41.42522469 0 0 0 0-82.85044936h-13.80840868V432.10455041l172.27370066 267.99358607a41.42522469 41.42522469 0 0 0 69.70484562 0l172.27370066-267.99358607V719.12612347H705.31771479a41.42522469 41.42522469 0 0 0 0 82.85044936h110.46726671a41.42522469 41.42522469 0 0 0 0-82.85044936h-13.80840867V291.06546788a41.42522469 41.42522469 0 0 0-76.69189974-21.76205051L512 601.09184948z" p-id="3735"></path><path d="M14.8973037 180.59820247a165.70089876 165.70089876 0 0 1 165.70089877-165.70089877h662.80359506a165.70089876 165.70089876 0 0 1 165.70089877 165.70089877v662.80359506a165.70089876 165.70089876 0 0 1-165.70089877 165.70089877H180.59820247a165.70089876 165.70089876 0 0 1-165.70089877-165.70089877V180.59820247z m165.70089877-82.85044938a82.85044939 82.85044939 0 0 0-82.85044938 82.85044938v662.80359506a82.85044939 82.85044939 0 0 0 82.85044938 82.85044938h662.80359506a82.85044939 82.85044939 0 0 0 82.85044938-82.85044938V180.59820247a82.85044939 82.85044939 0 0 0-82.85044938-82.85044938H180.59820247z" p-id="3736"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +0,0 @@
<?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="1689325086843" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1918" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M75.09333333 220.72888889h-36.40888888H75.09333333z m0 582.54222222h0z m194.18074112 194.18074112v-36.40888889 36.40888889z m485.4518511 0v0z m194.18074112-194.18074112h-36.40888889 36.40888889z m0-307.92211001h0zM480.10581333 26.54814777v-36.40888889V26.54814777zM269.27407445 26.54814777v0z m622.73763556 331.46652444l-25.72894891 25.72894891 25.72894891-25.72894891z m-274.57156779-274.57156664l25.72894777-25.72894891-25.72894777 25.72894891zM560.54518557 220.72888889h-36.4088889 36.4088889z m194.18073998 194.18074112v0zM38.68444445 220.72888889v582.54222222h72.81777777V220.72888889h-72.81777777zM269.27407445 1033.86074112h485.4518511v-72.81777778H269.27407445v72.81777778zM985.31555555 803.27111111v-307.92211001h-72.81777777V803.27111111h72.81777777zM480.10581333-9.86074112H269.27407445v72.81777778h210.83173888v-72.81777778z m437.68339001 342.14646557l-274.62011335-274.62011222-51.45789554 51.45789554 274.57156665 274.66865778 51.45789668-51.45789554zM480.10581333 62.95703666a157.77185223 157.77185223 0 0 1 111.55683556 46.21501667l51.45789668-51.45789667a230.58963001 230.58963001 0 0 0-163.01473224-67.57489778v72.81777778z m505.20974222 432.39196444c0-61.16693333-24.27259221-119.80951666-67.52635221-163.06327665l-51.45789667 51.45789667c29.56401778 29.61256334 46.16647111 69.75943111 46.16647111 111.60537998h72.81777777zM38.68444445 803.27111111A230.58963001 230.58963001 0 0 0 269.27407445 1033.86074112v-72.81777778A157.77185223 157.77185223 0 0 1 111.50222222 803.27111111h-72.81777777z m72.81777777-582.54222222A157.77185223 157.77185223 0 0 1 269.27407445 62.95703666v-72.81777778A230.58963001 230.58963001 0 0 0 38.68444445 220.72888889h72.81777777zM754.72592555 1033.86074112A230.58963001 230.58963001 0 0 0 985.31555555 803.27111111h-72.81777777A157.77185223 157.77185223 0 0 1 754.72592555 961.04296334v72.81777778zM524.13629667 26.54814777v194.18074112h72.81777778V26.54814777h-72.81777778zM754.72592555 451.3185189h194.18074112v-72.81777778h-194.18074112v72.81777778zM524.13629667 220.72888889A230.58963001 230.58963001 0 0 0 754.72592555 451.3185189v-72.81777778A157.77185223 157.77185223 0 0 1 596.95407445 220.72888889h-72.81777778z" p-id="1919"></path><path d="M241.94313443 594.76954112V803.27111111h42.08867556v-78.6432h27.42803001c19.07825778 0 33.64181333-0.97090333 43.69066667-3.0098011 7.37886777-1.60199111 14.61210112-4.8545189 21.74824334-9.80612779 7.18468779-5.00015445 13.1072-11.89356999 17.76753778-20.63170332 4.66033778-8.73813333 6.99050667-19.46661888 6.99050666-32.28254891 0-16.55390777-4.02924999-30.09801443-12.13629667-40.53522887a54.56478777 54.56478777 0 0 0-30.00092445-20.43752334c-7.76723001-2.08744334-24.46677333-3.15543666-50.00154112-3.15543666H241.89459001z m271.07631446 0h-76.9441189V803.27111111h79.1771978c15.53445888 0 28.01057223-1.45635555 37.28270222-4.36906666 12.42756779-4.02924999 22.28224-9.5634011 29.6125622-16.69954333 9.66049223-9.36922112 17.08790557-21.65115221 22.33078557-36.79725113 4.22343111-12.42756779 6.35941888-27.18530333 6.35941888-44.37029888 0-19.56371001-2.28162333-35.92343666-6.79632555-49.22481778a91.02222222 91.02222222 0 0 0-19.90352554-33.83599445 69.71088555 69.71088555 0 0 0-31.45728-19.22389333c-9.12649443-2.66998557-22.33078557-3.98070557-39.66141668-3.98070443z m133.98471111 0V803.27111111h42.04012999v-88.59496334h87.0415178v-35.29234887h-87.0415178v-49.32190891h100.87689558v-35.29234887H646.95561443zM283.98326557 689.18992555v-59.12803556h20.34043221q22.71914667 0 30.29219555 1.40781113a29.12711111 29.12711111 0 0 1 16.89372445 9.22358443c4.46615666 4.95160889 6.69923555 11.16539221 6.69923555 18.78698666a27.71930112 27.71930112 0 0 1-17.9617189 26.45712669c-5.53415111 2.18453333-16.6509989 3.25252779-33.25345109 3.25252665h-23.01041777z m194.13219555 78.93447111v-138.06250667h18.93262222c17.13644999 0 28.64165888 0.63108779 34.51562667 1.94180779q11.79648 2.57289443 19.51516444 9.85467221c5.09724445 4.8545189 9.07795001 11.55375445 11.94211556 20.1947978 2.81562112 8.64104334 4.27197667 20.97152 4.27197554 37.0885211s-1.45635555 28.88438557-4.27197554 38.30215111c-2.86416555 9.27212999-6.5536 15.97136555-11.11684779 20.0491611q-6.65068999 6.11669333-16.89372444 8.64104335a114.66372779 114.66372779 0 0 1-25.48622223 1.99035221h-31.40873443z" p-id="1920"></path></svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1 +0,0 @@
<?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="1689489324421" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="28738" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M166.32 808.98l-99.28 99.28c-15.31 15.32-39.71 15.32-55.03 0-15.32-15.31-15.32-39.71 0-55.03l98.71-99.28c15.32-15.31 39.71-15.31 55.03 0 15.32 15.32 15.32 39.72 0.57 55.03z m48.22 48.79l-99.28 99.27c-15.31 15.32-15.31 39.71 0 55.03 15.32 15.32 39.71 15.32 55.03 0l99.28-99.28c15.32-15.32 15.32-39.71 0-55.03-15.32-15.31-39.72-15.31-55.03 0.01z m625.14-545.16c0 71.47-57.86 129.34-129.34 129.34S581 384.08 581 312.61c0-71.48 57.86-129.34 129.34-129.34 72.05 0 129.91 57.86 129.34 129.34z m-77.71 0.57c0-28.36-23.26-51.63-51.62-51.63-28.36 0-51.62 23.26-51.62 51.63 0 28.36 23.26 51.62 51.62 51.62 28.93 0 52.19-23.26 51.62-51.62z m236.56 68.07c-27.8 104.39-83.39 203.65-164.51 285.35h-3.97l-5.11 5.67c10.21 38.57 11.35 78.28 2.84 116.86-9.07 46.52-32.33 90.2-66.94 124.8l-2.27 2.27c-38.57 36.87-85.09 59.57-134.45 67.51-51.62 7.94-105.51 0-152.6-23.26-17.58-10.21-25.53-32.9-16.45-51.63 1.7-2.84 3.41-5.67 5.67-8.5 14.18-15.32 24.96-32.9 32.9-51.63l3.4-7.94c-10.21 1.7-20.42 4.53-30.63 5.1-29.5 4.54-60.14 6.24-90.2 5.67-10.21 0-19.86-3.97-26.1-11.35L181.63 671.14c-8.51-7.95-11.35-18.72-10.78-29.5 0-28.37 2.27-58.43 5.67-86.79 1.7-10.78 2.84-20.99 5.1-31.2l-8.51 3.4c-17.58 7.95-35.17 18.72-51.06 33.47-14.75 14.18-39.14 12.48-53.32-2.27-2.27-2.84-3.97-7.37-6.24-10.21-23.26-47.09-30.63-99.28-22.69-150.9 8.22-51.8 32.65-99.66 69.78-136.71 34.61-34.61 78.85-57.86 123.67-66.94 39.14-8.51 79.42-7.94 116.86 2.27l7.95-7.95C439.75 106.69 538.45 51.66 642.83 23.3c108.35-28.36 222.38-28.36 329.59 0 13.62 4.54 23.82 15.32 27.23 27.23l0.57 0.57v0.56c28.37 107.78 28.37 221.81-1.69 329.59zM936.12 92.51c-89.06-19.86-182.67-18.16-270.03 5.1-91.34 24.96-178.7 73.18-250.74 145.22l-10.78 10.78-10.21 11.35c-10.21 11.35-26.66 16.45-42.55 10.78-31.2-12.48-65.81-14.75-98.71-7.38-31.2 6.81-61.27 22.13-86.23 46.52-26.66 27.8-43.12 60.7-48.79 95.3-3.4 20.99-3.4 42.55 0.57 62.97 9.64-5.67 18.72-10.78 28.93-14.18 26.66-11.34 55.6-18.15 84.53-18.72 3.97-0.57 7.94 0 11.91 0.56 19.86 5.67 31.77 26.66 26.1 46.52-7.95 26.1-13.05 52.2-16.45 78.29-1.7 22.12-3.4 43.12-3.97 64.67l146.36 146.93c22.12-1.14 43.11-2.27 64.67-5.11 27.23-3.97 52.76-8.51 78.29-15.89 3.97-0.56 7.94-1.13 11.91-1.13 20.42 0.57 36.31 18.72 36.31 39.14-1.7 29.5-7.38 58.43-19.29 85.66-3.97 9.08-9.07 19.29-14.18 28.36 20.42 3.97 41.98 3.97 62.4 1.13 33.47-5.1 66.94-20.99 93.03-47.09l1.13-1.13c24.39-24.96 39.71-55.6 47.09-86.79 6.81-32.34 3.97-66.94-7.94-99.28-4.54-14.18-1.7-30.64 10.21-41.41l11.91-11.35 8.51-8.51 2.27-1.13c72.04-72.61 119.7-159.41 145.79-250.17 23.27-89.05 25.54-180.95 7.95-270.01z" p-id="28739" fill="#FB7C3C"></path></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1 +0,0 @@
<?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="1689491332665" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5892" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M393.2 701.7c-8.2 0-16.4-3.1-22.6-9.4-12.5-12.5-12.5-32.8 0-45.3l115.3-115.3c14-14 36.9-14 50.9 0L652.1 647c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-95.5-95.5-95.5 95.5c-6.2 6.2-14.4 9.4-22.6 9.4z" fill="" p-id="5893"></path><path d="M511.3 921.1c-17.7 0-32-14.3-32-32v-276c0-17.7 14.3-32 32-32s32 14.3 32 32v276c0 17.6-14.3 32-32 32z" fill="" p-id="5894"></path><path d="M732.7 784.9c-17.7 0-32-14.3-32-32s14.3-32 32-32c90.6 0 164.3-73.7 164.3-164.3 0-82.9-61.9-153-144-163.1l-22.7-2.8-4.7-22.4c-20.8-99.9-110.1-172.4-212.4-172.4-102.2 0-191.5 72.5-212.4 172.4l-4.7 22.4-22.7 2.8c-82.1 10.1-144 80.2-144 163.1 0 90.6 73.7 164.3 164.3 164.3 17.7 0 32 14.3 32 32s-14.3 32-32 32c-61 0-118.3-23.8-161.5-66.9-43.1-43.1-66.9-100.5-66.9-161.5 0-107.6 75.2-199.7 178.2-222.8 15.8-53.8 47.7-102.2 91.3-138.1 50.1-41.2 113.4-63.9 178.3-63.9s128.3 22.7 178.3 63.9c43.6 35.9 75.5 84.3 91.3 138.1C885.9 356.8 961 448.9 961 556.5c0 61-23.8 118.3-66.9 161.5-43.1 43.1-100.4 66.9-161.4 66.9z" fill="" p-id="5895"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<?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="1689598169213" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3429" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M254.80971969 32C131.70542094 32 32 131.82654219 32 254.83663531c0 123.11775656 99.81308438 222.83214938 222.80971969 222.83214938h222.92186906V254.83663531C477.72710281 131.71439281 377.91401844 32 254.80971969 32z m514.38056062 0c-122.99214938 0-222.92186906 99.82654219-222.92186906 222.83663531v222.83214938h222.92186906C892.18691562 477.66878469 992 377.95887875 992 254.83663531 992 131.71439281 892.18691562 32 769.19028031 32zM32 769.16336469C32 892.28560719 131.81308438 992 254.80971969 992c123.10429875 0 222.80971969-99.71439281 222.80971968-222.83663531v-222.83214938H254.80971969C131.81308438 546.33121531 32 646.04112125 32 769.16336469z m737.07813094-222.83214938h-222.91738313v222.83214938c0 123.12224344 99.81308438 222.83663531 222.91738313 222.83663531 123.10878469 0 222.80971969-99.82654219 222.80971968-222.83663531 0.11214937-123.11775656-99.700935-222.83214938-222.80971968-222.83214938z" p-id="3430"></path></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1 +0,0 @@
<?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="1688892007547" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2705" xmlns:xlink="http://www.w3.org/1999/xlink" width="64.0625" height="64"><path d="M533.353412 682.285176c0-0.030118 0.060235-0.060235 0.060235-0.090353C533.172706 682.194824 533.142588 682.224941 533.353412 682.285176zM40.176941 555.008l314.398118 314.398118c27.045647 27.045647 70.927059 27.045647 97.942588 0 27.045647-26.985412 27.045647-70.866824 0-97.912471l-196.186353-196.216471 665.630118 0c38.249412 0 69.240471-31.021176 69.240471-69.240471 0-38.279529-31.021176-69.270588-69.240471-69.270588L256.361412 436.766118l196.186353-196.186353c27.045647-27.015529 27.045647-70.927059 0-97.942588C439.024941 129.114353 421.285647 122.337882 403.546353 122.337882c-17.709176 0-35.448471 6.776471-48.941176 20.299294L40.176941 457.065412C13.101176 484.111059 13.101176 527.932235 40.176941 555.008z" p-id="2706"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

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