Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
bf172fab
authored
Oct 02, 2023
by
Archer
Committed by
GitHub
Oct 02, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
perf: markdown more wrap (#365)
parent
36f5648c
Show whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
638 additions
and
537 deletions
+638
-537
.github/workflows/preview-image.yml
+4
-2
README.md
+8
-9
docSite/assets/imgs/fastgptonsealos1.png
+0
-0
docSite/assets/jsconfig.json
+2
-2
docSite/content/docs/development/configuration.md
+12
-28
docSite/content/docs/development/intro.md
+32
-25
docSite/content/docs/development/openApi.md
+9
-7
docSite/content/docs/installation/docker.md
+46
-101
docSite/content/docs/installation/one-api.md
+3
-4
docSite/content/docs/installation/sealos.md
+17
-0
docSite/content/docs/installation/upgrading/445.md
+3
-2
files/deploy/fastgpt/config.json
+0
-63
projects/app/.env.template
+2
-1
projects/app/data/config.json
+0
-12
projects/app/public/locales/zh/common.json
+1
-1
projects/app/src/components/ChatBox/SelectDataset.tsx
+1
-1
projects/app/src/components/ChatBox/index.tsx
+292
-240
projects/app/src/components/Icon/icons/common/pause.svg
+2
-0
projects/app/src/components/Icon/icons/light/loading.svg
+53
-0
projects/app/src/components/Icon/index.tsx
+3
-1
projects/app/src/components/Markdown/chat/Guide.tsx
+6
-3
projects/app/src/components/Markdown/index.tsx
+4
-2
projects/app/src/components/PageContainer/index.tsx
+1
-1
projects/app/src/components/core/dataset/SelectModal.tsx
+2
-8
projects/app/src/constants/flow/ModuleTemplate.ts
+1
-1
projects/app/src/pages/_app.tsx
+6
-0
projects/app/src/pages/api/core/dataset/data/exportAll.ts
+4
-5
projects/app/src/pages/chat/share.tsx
+7
-2
projects/app/src/service/common/stream.ts
+1
-1
projects/app/src/service/moduleDispatch/tools/answer.ts
+1
-1
projects/app/src/utils/web/voice.ts
+115
-14
No files found.
.github/workflows/preview-image.yml
View file @
bf172fab
...
@@ -17,7 +17,9 @@ jobs:
...
@@ -17,7 +17,9 @@ jobs:
with
:
with
:
fetch-depth
:
1
fetch-depth
:
1
-
name
:
Set up Docker Buildx
-
name
:
Set up Docker Buildx
uses
:
docker/setup-buildx-action@v1
uses
:
docker/setup-buildx-action@v2
with
:
driver-opts
:
network=host
-
name
:
Cache Docker layers
-
name
:
Cache Docker layers
uses
:
actions/cache@v2
uses
:
actions/cache@v2
with
:
with
:
...
@@ -34,7 +36,7 @@ jobs:
...
@@ -34,7 +36,7 @@ jobs:
-
name
:
Set DOCKER_REPO_TAGGED based on branch or tag
-
name
:
Set DOCKER_REPO_TAGGED based on branch or tag
run
:
|
run
:
|
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.number }}" >> $GITHUB_ENV
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.number }}" >> $GITHUB_ENV
-
name
:
Build
and publish
image for PR
-
name
:
Build image for PR
env
:
env
:
DOCKER_REPO_TAGGED
:
${{ env.DOCKER_REPO_TAGGED }}
DOCKER_REPO_TAGGED
:
${{ env.DOCKER_REPO_TAGGED }}
run
:
|
run
:
|
...
...
README.md
View file @
bf172fab
...
@@ -80,8 +80,8 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
...
@@ -80,8 +80,8 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
*
[
部署 FastGPT
](
https://doc.fastgpt.run/docs/installation
)
*
[
部署 FastGPT
](
https://doc.fastgpt.run/docs/installation
)
*
[
系统配置文件说明
](
https://doc.fastgpt.run/docs/development/configuration/
)
*
[
系统配置文件说明
](
https://doc.fastgpt.run/docs/development/configuration/
)
*
[
多模型配置
](
https://doc.fastgpt.run/docs/installation/one-api/
)
*
[
多模型配置
](
https://doc.fastgpt.run/docs/installation/one-api/
)
*
[
版本
升级
](
https://doc.fastgpt.run/docs/installation/upgrading
)
*
[
版本
更新/升级介绍
](
https://doc.fastgpt.run/docs/installation/upgrading
)
*
[
API 文档
](
https://doc.fastgpt.run/docs/development/openapi
?pre_pathname=%2Fdrive%2Fhome%2F
)
*
[
API 文档
](
https://doc.fastgpt.run/docs/development/openapi
/
)
## 🏘️ 社区交流群
## 🏘️ 社区交流群
...
@@ -89,12 +89,6 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
...
@@ -89,12 +89,6 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
!
[
](https://otnvvf-imgs.oss.laf.run/wx300.jpg)
!
[
](https://otnvvf-imgs.oss.laf.run/wx300.jpg)
## 👀 其他
-
[
FastGPT 常见问题
](
https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh
)
-
[
docker 部署教程视频
](
https://www.bilibili.com/video/BV1jo4y147fT/
)
-
[
FastGPT 知识库演示
](
https://www.bilibili.com/video/BV1Wo4y1p7i1/
)
## 💪 相关项目
## 💪 相关项目
-
[
Laf: 3 分钟快速接入三方应用
](
https://github.com/labring/laf
)
-
[
Laf: 3 分钟快速接入三方应用
](
https://github.com/labring/laf
)
...
@@ -102,10 +96,15 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
...
@@ -102,10 +96,15 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
-
[
One API: 多模型管理,支持 Azure、文心一言等
](
https://github.com/songquanpeng/one-api
)
-
[
One API: 多模型管理,支持 Azure、文心一言等
](
https://github.com/songquanpeng/one-api
)
-
[
TuShan: 5 分钟搭建后台管理系统
](
https://github.com/msgbyte/tushan
)
-
[
TuShan: 5 分钟搭建后台管理系统
](
https://github.com/msgbyte/tushan
)
## 👀 其他
-
[
保姆级 FastGPT 教程
](
https://www.bilibili.com/video/BV1n34y1A7Bo/?spm_id_from=333.999.0.0
)
-
[
接入飞书
](
https://www.bilibili.com/video/BV1Su4y1r7R3/?spm_id_from=333.999.0.0
)
-
[
接入企微
](
https://www.bilibili.com/video/BV1Tp4y1n72T/?spm_id_from=333.999.0.0
)
## 🤝 第三方生态
## 🤝 第三方生态
-
[
OnWeChat 个人微信/企微机器人
](
https://doc.fastgpt.run/docs/use-cases/onwechat/
)
-
[
OnWeChat 个人微信/企微机器人
](
https://doc.fastgpt.run/docs/use-cases/onwechat/
)
-
[
luolinAI: 企微机器人,开箱即用
](
https://github.com/luolin-ai/FastGPT-Enterprise-WeChatbot
)
## 🌟 Star History
## 🌟 Star History
...
...
docSite/assets/imgs/fastgptonsealos1.png
0 → 100644
View file @
bf172fab
212 KB
docSite/assets/jsconfig.json
View file @
bf172fab
...
@@ -3,8 +3,8 @@
...
@@ -3,8 +3,8 @@
"baseUrl"
:
"."
,
"baseUrl"
:
"."
,
"paths"
:
{
"paths"
:
{
"*"
:
[
"*"
:
[
"../../../../.
./.
cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2@v2.21100.20000/package/dist/cjs/popper.js/*"
,
"../../../../.cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2@v2.21100.20000/package/dist/cjs/popper.js/*"
,
"../../../../.
./.
cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/twbs/bootstrap@v5.3.0+incompatible/js/*"
"../../../../.cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/twbs/bootstrap@v5.3.0+incompatible/js/*"
]
]
}
}
}
}
...
...
docSite/content/docs/development/configuration.md
View file @
bf172fab
...
@@ -11,35 +11,19 @@ weight: 520
...
@@ -11,35 +11,19 @@ weight: 520
**开发环境下**
,你需要将示例配置文件
`config.json`
复制成
`config.local.json`
文件才会生效。
**开发环境下**
,你需要将示例配置文件
`config.json`
复制成
`config.local.json`
文件才会生效。
这个配置文件中包含了
前端页面定制、系统级参数、AI 对话的
模型等……
这个配置文件中包含了
系统级参数、AI 对话的模型、function
模型等……
{{% alert context="warning" %}}
注意:下面的配置介绍仅是局部介绍,你需要完整挂载整个
`config.json`
,不能仅挂载一部分。你可以直接在默认的 config.json 基础上根据下面的介绍进行修改。挂载上去的配置文件不能包含注释。
{{% /alert %}}
##
基础字段粗略说明
##
完整配置参数
这里介绍一些基础的配置字段:
**使用时,请务必去除注释!**
```
json
```
json
...
{
//
这个配置文件是系统级参数
"SystemParams"
:
{
"SystemParams"
:
{
"vectorMaxProcess"
:
15
,
//
向量生成最大进程,结合数据库性能和
key
来设置
"vectorMaxProcess"
:
15
,
//
向量生成最大进程,结合数据库性能和
key
来设置
"qaMaxProcess"
:
15
,
//
QA
生成最大进程,结合数据库性能和
key
来设置
"qaMaxProcess"
:
15
,
//
QA
生成最大进程,结合数据库性能和
key
来设置
"pgIvfflatProbe"
:
20
//
pg
vector
搜索探针。没有设置索引前可忽略,通常
50
w
组以上才需要设置。
"pgIvfflatProbe"
:
20
//
pg
vector
搜索探针。没有设置索引前可忽略,通常
50
w
组以上才需要设置。
},
...
```
## 完整配置参数
```
json
{
"SystemParams"
:
{
"vectorMaxProcess"
:
15
,
"qaMaxProcess"
:
15
,
"pgIvfflatProbe"
:
20
},
},
"ChatModels"
:
[
"ChatModels"
:
[
{
{
...
@@ -79,25 +63,25 @@ weight: 520
...
@@ -79,25 +63,25 @@ weight: 520
"maxToken"
:
3000
"maxToken"
:
3000
}
}
],
],
"QAModel"
:
{
"QAModel"
:
{
//
QA
拆分模型
"model"
:
"gpt-3.5-turbo-16k"
,
"model"
:
"gpt-3.5-turbo-16k"
,
"name"
:
"GPT35-16k"
,
"name"
:
"GPT35-16k"
,
"maxToken"
:
0
,
"maxToken"
:
1600
0
,
"price"
:
0
"price"
:
0
},
},
"ExtractModel"
:
{
"ExtractModel"
:
{
//
内容提取模型
"model"
:
"gpt-3.5-turbo-16k"
,
"model"
:
"gpt-3.5-turbo-16k"
,
"functionCall"
:
true
,
"functionCall"
:
true
,
//
是否使用
functionCall
"name"
:
"GPT35-16k"
,
"name"
:
"GPT35-16k"
,
"maxToken"
:
0
,
"maxToken"
:
1600
0
,
"price"
:
0
,
"price"
:
0
,
"prompt"
:
""
"prompt"
:
""
},
},
"CQModel"
:
{
"CQModel"
:
{
//
问题分类模型
"model"
:
"gpt-3.5-turbo-16k"
,
"model"
:
"gpt-3.5-turbo-16k"
,
"functionCall"
:
true
,
"functionCall"
:
true
,
"name"
:
"GPT35-16k"
,
"name"
:
"GPT35-16k"
,
"maxToken"
:
0
,
"maxToken"
:
1600
0
,
"price"
:
0
,
"price"
:
0
,
"prompt"
:
""
"prompt"
:
""
}
}
...
...
docSite/content/docs/development/intro.md
View file @
bf172fab
...
@@ -10,30 +10,27 @@ weight: 510
...
@@ -10,30 +10,27 @@ weight: 510
本文档介绍了如何设置开发环境以构建和测试
[
FastGPT
](
https://fastgpt.run
)
。
本文档介绍了如何设置开发环境以构建和测试
[
FastGPT
](
https://fastgpt.run
)
。
## Tips
1.
用户默认的时区为
`Asia/Shanghai`
,非 linux 环境时候,获取系统时间会异常,本地开发时候,可以将用户的时区调整成 UTC(+0)。
## 前置依赖项
## 前置依赖项
您需要在计算机上安装和配置以下依赖项才能构建
[
FastGPT
](
https://fastgpt.run
)
:
您需要在计算机上安装和配置以下依赖项才能构建
[
FastGPT
](
https://fastgpt.run
)
:
-
[
Git
](
http://git-scm.com/
)
-
[
Git
](
http://git-scm.com/
)
-
[
Docker
](
https://www.docker.com/
)
-
[
Docker
](
https://www.docker.com/
)
(构建镜像)
-
[
Docker Compose
](
https://docs.docker.com/compose/install/
)
-
[
Node.js v18.x (LTS)
](
http://nodejs.org
)
-
[
Node.js v18.x (LTS)
](
http://nodejs.org
)
-
[
npm
](
https://www.npmjs.com/
)
版本 8.x.x 或
[
Yarn
](
https://yarnpkg.com/
)
-
[
pnpm
](
https://pnpm.io/
)
版本 8.x.x
## 本地开发
##
开始
本地开发
要设置一个可工作的开发环境,只需 Fork 项目的 Git 存储库,并部署一个数据库,然后开始进行开发测试。
**Tips**
### Fork 存储库
1.
用户默认的时区为
`Asia/Shanghai`
,非 linux 环境时候,获取系统时间会异常,本地开发时候,可以将用户的时区调整成 UTC(+0)。
2.
建议先服务器装好数据库在进行本地开发。
### 1. Fork 存储库
您需要 Fork
[
存储库
](
https://github.com/labring/FastGPT
)
。
您需要 Fork
[
存储库
](
https://github.com/labring/FastGPT
)
。
### 克隆存储库
###
2.
克隆存储库
克隆您在 GitHub 上 Fork 的存储库:
克隆您在 GitHub 上 Fork 的存储库:
...
@@ -41,23 +38,27 @@ weight: 510
...
@@ -41,23 +38,27 @@ weight: 510
git clone git@github.com:<github_username>/FastGPT.git
git clone git@github.com:<github_username>/FastGPT.git
```
```
**projects 目录下为 FastGPT 应用代码。NextJS 框架前后端放在一起,API 服务位于 `src/pages/api` 目录内。**
**目录简要说明**
1.
`projects`
目录下为 FastGPT 应用代码。其中
`app`
为 FastGPT 核心应用。(后续可能会引入其他应用)
2.
NextJS 框架前后端放在一起,API 服务位于
`src/pages/api`
目录内。
3.
`packages`
目录为共用代码,通过 workspace 被注入到
`projects`
中,已配置 monorepo 自动注入,无需额外打包。
**packages 目录为相关的共用包。**
### 3. 安装数据库
### 安装数据库
第一次开发,需要先部署数据库,建议本地开发可以随便找一台 2C2G 的轻量小数据库实践。数据库部署教程:
[
Docker 快速部署
](
/docs/installation/docker/
)
。部署完了,可以本地访问其数据库。
第一次开发,需要先部署数据库,建议本地开发可以随便找一台 2C2G 的轻量小数据库实践。数据库部署教程:
[
Docker 快速部署
](
/docs/installation/docker/
)
### 4. 初始配置
### 初始配置
以下文件均在
`projects/app`
路径下。
**
1.
环境变量**
**环境变量**
复制.env.template 文件,生成一个.env.local 环境变量文件夹,修改.env.local 里内容才是有效的变量。变量说明见 .env.template
复制.env.template 文件,生成一个.env.local 环境变量文件夹,修改.env.local 里内容才是有效的变量。变量说明见 .env.template
**
2.
config 配置文件**
**config 配置文件**
复制 data/config.json 文件,生成一个 data/config.local.json 配置文件
。具体的参数说明,可参考
[
config 配置说名
](
/docs/development/configuration
)
复制 data/config.json 文件,生成一个 data/config.local.json 配置文件
,具体配置参数说明,可参考
[
config 配置说明
](
/docs/development/configuration
)
**注意:json 配置文件不能包含注释,介绍中为了方便看才加入的注释**
**注意:json 配置文件不能包含注释,介绍中为了方便看才加入的注释**
...
@@ -67,23 +68,29 @@ git clone git@github.com:<github_username>/FastGPT.git
...
@@ -67,23 +68,29 @@ git clone git@github.com:<github_username>/FastGPT.git
-
`qaMaxProcess`
: QA 生成最大进程
-
`qaMaxProcess`
: QA 生成最大进程
-
`pgIvfflatProbe`
: PostgreSQL vector 搜索探针,没有添加 vector 索引时可忽略。
-
`pgIvfflatProbe`
: PostgreSQL vector 搜索探针,没有添加 vector 索引时可忽略。
### 运行
###
5.
运行
```
bash
```
bash
# 代码根目录下执行,会安装根 package、projects 和 packages 内所有依赖
pnpm i
pnpm i
cd
projects/app
# FastGPT 主程序
# 切换到应用目录
cd
projects/app
# 开发模式运行
pnpm dev
pnpm dev
```
```
### 镜像打包
###
6. 发布 -
镜像打包
```
bash
```
bash
# 根目录下执行
docker build
-t
dockername/fastgpt
--build-arg
name
=
app
.
docker build
-t
dockername/fastgpt
--build-arg
name
=
app
.
```
```
##
创建拉取请求
##
提交代码至开源仓库
在进行更改后,打开一个拉取请求(PR)。提交拉取请求后,FastGPT 团队/社区的其他人将与您一起审查它。
1.
确保你的代码是 Fork
[
FastGPT
](
https://github.com/labring/FastGPT
)
仓库
2.
尽可能少量的提交代码,每次提交仅解决一个问题。
3.
向 FastGPT 的 main 分支提交一个 PR,提交请求后,FastGPT 团队/社区的其他人将与您一起审查它。
如果遇到问题,比如合并冲突或不知道如何打开拉取请求,请查看 GitHub 的
[
拉取请求教程
](
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests
)
,了解如何解决合并冲突和其他问题。一旦您的 PR 被合并,您将自豪地被列为
[
贡献者表
](
https://github.com/labring/FastGPT/graphs/contributors
)
中的一员。
如果遇到问题,比如合并冲突或不知道如何打开拉取请求,请查看 GitHub 的
[
拉取请求教程
](
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests
)
,了解如何解决合并冲突和其他问题。一旦您的 PR 被合并,您将自豪地被列为
[
贡献者表
](
https://github.com/labring/FastGPT/graphs/contributors
)
中的一员。
...
...
docSite/content/docs/development/openApi.md
View file @
bf172fab
---
---
title
:
'
OpenAPI
使用'
title
:
'
OpenAPI
使用
(API
Key
使用)
'
description
:
'
FastGPT
OpenAPI
文档'
description
:
'
FastGPT
OpenAPI
文档'
icon
:
'
api'
icon
:
'
api'
draft
:
false
draft
:
false
...
@@ -29,19 +29,21 @@ FastGPT 的 API Key 有 2 类,一类是全局通用的 key;一类是携带
...
@@ -29,19 +29,21 @@ FastGPT 的 API Key 有 2 类,一类是全局通用的 key;一类是携带
{{% alert icon="🤖 " context="success" %}}
{{% alert icon="🤖 " context="success" %}}
该接口 API Key 需使用应用特定的 key,否则会报错。
该接口 API Key 需使用应用特定的 key,否则会报错。
有些包的 BaseUrl 需要添加
`v1`
路径,有些不需要,建议都试一下。
{{% /alert %}}
{{% /alert %}}
对话接口兼容
openai 的接口!如果你有第三方项目,可以直接通过修改 BaseUrl 和 Authorization 来访问 FastGpt 应用。缺点是你无法获取到响应的token值
。
对话接口兼容
`GPT`
的接口!如果你的项目使用的是标准的
`GPT`
官方接口,可以直接通过修改
`BaseUrl`
和
`Authorization`
来访问 FastGpt 应用
。
请求
内容
请求
参数说明
-
headers.Authorization: Bearer apikey
-
headers.Authorization: Bearer apikey
-
chatId: string | undefined 。
-
chatId: string | undefined 。
-
为 undefined 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
-
为 undefined 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
-
为非空字符串时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录
。并拼接 messages 数组最后一个内容作为完整请求。(自行确保 chatId 唯一,长度不限
)
-
为非空字符串时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录
,并使用 messages 数组最后一个内容作为用户问题。(请自行确保 chatId 唯一,长度不限制
)
-
messages:
与 openai gpt 接口
完全一致。
-
messages:
结构与
[
GPT接口
](
https://platform.openai.com/docs/api-reference/chat/object
)
完全一致。
-
detail: 是否返回详细值(模块状态,响应的完整结果),
会通过event进行区分
-
detail: 是否返回详细值(模块状态,响应的完整结果),
`stream模式`
下会通过event进行区分,
`非stream模式`
结果保存在responseData中。
-
variables: 变量
。一个对象,效果同全局变量
。
-
variables: 变量
内容,一个对象,会替换
`{{key}}`
变量。在
`HTTP`
模块中会发给接口,可作为身份凭证等标识
。
**请求示例:**
**请求示例:**
...
...
docSite/content/docs/installation/docker.md
View file @
bf172fab
...
@@ -11,17 +11,17 @@ weight: 720
...
@@ -11,17 +11,17 @@ weight: 720
### 1. 准备好代理环境(国外服务器可忽略)
### 1. 准备好代理环境(国外服务器可忽略)
确保可以访问 OpenAI,具体方案可以参考:
[
Nginx 中转
](
/docs/installation/proxy/nginx/
)
确保可以访问 OpenAI,具体方案可以参考:
[
代理方案
](
/docs/installation/proxy/
)
。或直接在 Sealos 上
[
部署 OneAPI
](
/docs/installation/one-api
)
,既解决代理问题也能实现多 Key 轮询、接入其他大模型。
### 2. 多模型支持
### 2. 多模型支持
推荐使用 one-api 项目来管理模型池,兼容 OpenAI 、Azure 和国内主流
模型等。
FastGPT 使用了 one-api 项目来管理模型池,其可以兼容 OpenAI 、Azure 、国内主流模型和本地
模型等。
具体
部署方法可参考该项目的
[
README
](
https://github.com/songquanpeng/one-api
)
,也可以直接通过以下按钮一键部署:
可选择
[
Sealos 快速部署 OneAPI
](
/docs/installation/one-api
)
,更多
部署方法可参考该项目的
[
README
](
https://github.com/songquanpeng/one-api
)
,也可以直接通过以下按钮一键部署:
[

](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Done-api)
[

](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Done-api)
## 安装 Docker 和 docker-compose
##
一、
安装 Docker 和 docker-compose
{{
<
tabs
tabTotal=
"3"
>
}}
{{
<
tabs
tabTotal=
"3"
>
}}
{{
<
tab
tabName=
"Linux"
>
}}
{{
<
tab
tabName=
"Linux"
>
}}
...
@@ -29,7 +29,7 @@ weight: 720
...
@@ -29,7 +29,7 @@ weight: 720
```
bash
```
bash
# 安装 Docker
# 安装 Docker
curl
-
sSL
https://get.daocloud.io/docker | sh
curl
-
fsSL
https://get.docker.com | bash
-s
docker
--mirror
Aliyun
systemctl
enable
--now
docker
systemctl
enable
--now
docker
# 安装 docker-compose
# 安装 docker-compose
curl
-L
https://github.com/docker/compose/releases/download/2.20.3/docker-compose-
`
uname
-s
`
-
`
uname
-m
`
-o
/usr/local/bin/docker-compose
curl
-L
https://github.com/docker/compose/releases/download/2.20.3/docker-compose-
`
uname
-s
`
-
`
uname
-m
`
-o
/usr/local/bin/docker-compose
...
@@ -37,6 +37,7 @@ chmod +x /usr/local/bin/docker-compose
...
@@ -37,6 +37,7 @@ chmod +x /usr/local/bin/docker-compose
# 验证安装
# 验证安装
docker
-v
docker
-v
docker-compose
-v
docker-compose
-v
# 如失效,自行百度~
```
```
{{
<
/
markdownify
>
}}
{{
<
/
markdownify
>
}}
...
@@ -65,93 +66,35 @@ brew install orbstack
...
@@ -65,93 +66,35 @@ brew install orbstack
{{
<
/
tab
>
}}
{{
<
/
tab
>
}}
{{
<
/
tabs
>
}}
{{
<
/
tabs
>
}}
##
创建 docker-compose.yml 文件
##
二、创建目录并下载 docker-compose.yml
先创建一个目录(例如 fastgpt)并进入该目录,创建一个 docker-compose.yml 文件:
依次执行下面命令,创建 FastGPT 文件并拉取
`docker-compose.yml`
和
`config.json`
,执行完后目录下会有 2 个文件。
非 Linux 环境,可手动创建目录,并下载这2个文件。
**注意: 配置文件中 Mongo 为 5.x,部分服务器不支持,需手动更改其镜像版本为 4.4.24**
```
bash
```
bash
mkdir fastgpt
mkdir fastgpt
cd
fastgpt
cd
fastgpt
touch docker-compose.yml
curl
-O
https://raw.githubusercontent.com/labring/FastGPT/main/files/deploy/fastgpt/docker-compose.yml
curl
-O
https://raw.githubusercontent.com/labring/FastGPT/main/projects/app/data/config.json
```
```
粘贴下面的内容,仅需把
`CHAT_API_KEY`
修改成 openai key 即可。如果需要使用中转或 oneapi 还需要修改
`OPENAI_BASE_URL`
:
```
yaml
# 非 host 版本, 不使用本机代理
version
:
'
3.3'
services
:
pg
:
image
:
ankane/pgvector:v0.4.2
# docker
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.4.2 # 阿里云
container_name
:
pg
restart
:
always
ports
:
# 生产环境建议不要暴露
-
5432:5432
networks
:
-
fastgpt
environment
:
# 这里的配置只有首次运行生效。修改后,重启镜像是不会生效的。需要把持久化数据删除再重启,才有效果
-
POSTGRES_USER=username
-
POSTGRES_PASSWORD=password
-
POSTGRES_DB=postgres
volumes
:
-
./pg/data:/var/lib/postgresql/data
mongo
:
image
:
mongo:5.0.18
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云
container_name
:
mongo
restart
:
always
ports
:
# 生产环境建议不要暴露
-
27017:27017
networks
:
-
fastgpt
environment
:
# 这里的配置只有首次运行生效。修改后,重启镜像是不会生效的。需要把持久化数据删除再重启,才有效果
-
MONGO_INITDB_ROOT_USERNAME=username
-
MONGO_INITDB_ROOT_PASSWORD=password
volumes
:
-
./mongo/data:/data/db
fastgpt
:
container_name
:
fastgpt
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest # 阿里云
image
:
ghcr.io/labring/fastgpt:latest
# github
ports
:
-
3000:3000
networks
:
-
fastgpt
depends_on
:
-
mongo
-
pg
restart
:
always
environment
:
# root 密码,用户名为: root
-
DEFAULT_ROOT_PSW=1234
# 中转地址,如果是用官方号,不需要管
-
OPENAI_BASE_URL=https://api.openai.com/v1
-
CHAT_API_KEY=sk-xxxx
-
DB_MAX_LINK=5
# database max link
-
TOKEN_KEY=any
-
ROOT_KEY=root_key
-
FILE_TOKEN_KEY=filetoken
# mongo 配置,不需要改. 如果连不上,可能需要去掉 ?authSource=admin
-
MONGODB_URI=mongodb://username:password@mongo:27017/fastgpt?authSource=admin
# pg配置. 不需要改
-
PG_URL=postgresql://username:password@pg:5432/postgres
networks
:
fastgpt
:
```
## 启动容器
## 三、启动容器
修改
`docker-compose.yml`
中的
`OPENAI_BASE_URL`
和
`CHAT_API_KEY`
即可,对应为 API 的地址和 key。
```
bash
```
bash
# 在 docker-compose.yml 同级目录下执行
# 在 docker-compose.yml 同级目录下执行
docker-compose pull
docker-compose up
-d
docker-compose up
-d
```
```
## 访问 FastGPT
##
四、
访问 FastGPT
目前可以通过
`ip:3000`
直接访问(注意防火墙)。登录用户名为
`root`
,密码为
刚刚
环境变量里设置的
`DEFAULT_ROOT_PSW`
。
目前可以通过
`ip:3000`
直接访问(注意防火墙)。登录用户名为
`root`
,密码为
`docker-compose.yml`
环境变量里设置的
`DEFAULT_ROOT_PSW`
。
如果需要域名访问,请自行安装并配置 Nginx。
如果需要域名访问,请自行安装并配置 Nginx。
...
@@ -168,28 +111,30 @@ docker-compose up -d
...
@@ -168,28 +111,30 @@ docker-compose up -d
### 如何自定义配置文件?
### 如何自定义配置文件?
需要在
`docker-compose.yml`
同级目录创建一个
`config.json`
文件,内容参考:
[
配置详解
](
/docs/development/configuration
)
修改
`config.json`
文件,并执行
`docker-compose up -d`
重起容器。具体配置,参考
[
配置详解
](
/docs/development/configuration
)
。
然后修改
`docker-compose.yml`
中的
`fastgpt`
容器内容,增加挂载选项即可:
### 如何检查自定义配置文件是否挂载
```
yaml
1.
`docker logs fastgpt`
可以查看日志,在启动容器后,第一次请求网页,会进行配置文件读取,可以看看有没有读取成功以及有无错误日志。
fastgpt
:
2.
`docker exec -it fastgpt sh`
进入 FastGPT 容器,可以通过
`ls data`
查看目录下是否成功挂载
`config.json`
文件。可通过
`cat data/config.json`
查看配置文件。
container_name
:
fastgpt
image
:
ghcr.io/labring/fastgpt:latest
# github
### 为什么无法连接 oneapi 和 本地模型镜像。
ports
:
-
3000:3000
`docker-compose.yml`
中使用了桥接的模式建立了
`fastgpt`
网络,如想通过0.0.0.0或镜像名访问其它镜像,需将其它镜像也加入到网络中。
networks
:
-
fastgpt
### 端口冲突怎么解决?
depends_on
:
-
mongo
docker-compose 端口定义为:
`映射端口:运行端口`
。
-
pg
restart
:
always
桥接模式下,容器运行端口不会有冲突,但是会有映射端口冲突,只需将映射端口修改成不同端口即可。
environment
:
...
如果
`容器1`
需要连接
`容器2`
,使用
`容器2:运行端口`
来进行连接即可。
- DEFAULT_ROOT_PSW=1234
...
(自行补习 docker 基本知识)
volumes
:
-
./config.json:/app/data/config.json
### 错误排查方式
```
遇到问题先按下面方式排查。
> 参考[配置详解](/docs/development/configuration)
1.
`docker ps -a`
查看所有容器运行状态,检查是否全部 running,如有异常,尝试
`docker logs 容器名`
查看对应日志。
2.
不懂 docker 不要瞎改端口,只需要改
`OPENAI_BASE_URL`
和
`CHAT_API_KEY`
即可。
docSite/content/docs/installation/one-api.md
View file @
bf172fab
---
---
title
:
'
部署
One
API,实现多模型支持
'
title
:
'
接入微软、ChatGLM、本地模型等
'
description
:
'
通过接入
One
API
来实现对各种大模型的支持'
description
:
'
通过接入
One
API
来实现对各种大模型的支持'
icon
:
'
Api'
icon
:
'
Api'
draft
:
false
draft
:
false
...
@@ -7,9 +7,8 @@ toc: true
...
@@ -7,9 +7,8 @@ toc: true
weight
:
730
weight
:
730
---
---
默认情况下,FastGPT 只配置了 GPT 的 3 个模型,如果你需要接入其他模型,需要进行一些额外配置。
*
默认情况下,FastGPT 只配置了 GPT 的 3 个模型,如果你需要接入其他模型,需要进行一些额外配置。
*
[
One API
](
https://github.com/songquanpeng/one-api
)
是一个 OpenAI 接口管理 & 分发系统,可以通过标准的 OpenAI API 格式访问所有的大模型,开箱即用。
[
One API
](
https://github.com/songquanpeng/one-api
)
是一个 OpenAI 接口管理 & 分发系统,可以通过标准的 OpenAI API 格式访问所有的大模型,开箱即用。
FastGPT 可以通过接入 One API 来实现对各种大模型的支持。部署方法也很简单。
FastGPT 可以通过接入 One API 来实现对各种大模型的支持。部署方法也很简单。
...
...
docSite/content/docs/installation/sealos.md
View file @
bf172fab
...
@@ -23,6 +23,22 @@ Sealos 的服务器在国外,不需要额外处理网络问题,无需服务
...
@@ -23,6 +23,22 @@ Sealos 的服务器在国外,不需要额外处理网络问题,无需服务
>
>
> 密码就是刚刚一键部署时设置的环境变量
> 密码就是刚刚一键部署时设置的环境变量
## 修改配置文件和环境变量
在 Sealos 中,你可以打开
`应用管理`
(App Launchpad)看到部署的 FastGPT,可以打开
`数据库`
(Database)看到对应的数据库。
在
`应用管理`
中,选中 FastGPT,点击变更,可以看到对应的环境变量和配置文件。
!
[
](/imgs/fastgptonsealos1.png)
{{% alert icon="🤖 " context="success" %}}
在 Sealos 上,FastGPT 一共运行了 1 个服务和 2 个数据库,如暂停和删除请注意数据库一同操作。(你可以白天启动,晚上暂停它们,省钱大法)
{{% /alert %}}
## 更新
点击重启会自动拉取最新镜像更新,请确保镜像
`tag`
正确。
## 部署架构图
## 部署架构图
!
[
](/imgs/sealos-fastgpt.webp)
!
[
](/imgs/sealos-fastgpt.webp)
\ No newline at end of file
docSite/content/docs/installation/upgrading/445.md
View file @
bf172fab
...
@@ -26,6 +26,6 @@ curl --location --request POST 'https://{{host}}/api/admin/initv445' \
...
@@ -26,6 +26,6 @@ curl --location --request POST 'https://{{host}}/api/admin/initv445' \
### Fast GPT V4.4.5
### Fast GPT V4.4.5
1.
新增 - 下一步指引选项,可以通过模型生成 3 个预测问题。
1.
新增 - 下一步指引选项,可以通过模型生成 3 个预测问题。
2.
新增 - 分享链接 hook 身份校验
。
2.
商业版新增 - 分享链接限制及 hook 身份校验(可对接已有的用户系统)
。
3.
新增 - Api Key 使用。增加别名、额度限制和过期时间。自带 appId,无需额外连接。
3.
商业版
新增 - Api Key 使用。增加别名、额度限制和过期时间。自带 appId,无需额外连接。
4.
优化 - 全局变量与开场白合并成同一模块。
4.
优化 - 全局变量与开场白合并成同一模块。
\ No newline at end of file
files/deploy/fastgpt/config.json
deleted
100644 → 0
View file @
36f5648c
{
"FeConfig"
:
{
"show_emptyChat"
:
true
,
"show_register"
:
false
,
"show_appStore"
:
false
,
"show_userDetail"
:
false
,
"show_contact"
:
true
,
"show_git"
:
true
,
"show_doc"
:
true
,
"systemTitle"
:
"FastGPT"
,
"authorText"
:
"Made by FastGPT Team."
,
"scripts"
:
[]
},
"SystemParams"
:
{
"vectorMaxProcess"
:
15
,
"qaMaxProcess"
:
15
,
"pgIvfflatProbe"
:
20
},
"ChatModels"
:
[
{
"model"
:
"gpt-3.5-turbo"
,
"name"
:
"GPT35-4k"
,
"contextMaxToken"
:
4000
,
"quoteMaxToken"
:
2000
,
"maxTemperature"
:
1.2
,
"price"
:
0
,
"defaultSystem"
:
""
},
{
"model"
:
"gpt-3.5-turbo-16k"
,
"name"
:
"GPT35-16k"
,
"contextMaxToken"
:
16000
,
"quoteMaxToken"
:
8000
,
"maxTemperature"
:
1.2
,
"price"
:
0
,
"defaultSystem"
:
""
},
{
"model"
:
"gpt-4"
,
"name"
:
"GPT4-8k"
,
"contextMaxToken"
:
8000
,
"quoteMaxToken"
:
4000
,
"maxTemperature"
:
1.2
,
"price"
:
0
,
"defaultSystem"
:
""
}
],
"VectorModels"
:
[
{
"model"
:
"text-embedding-ada-002"
,
"name"
:
"Embedding-2"
,
"price"
:
0
,
"defaultToken"
:
500
,
"maxToken"
:
3000
}
],
"QAModel"
:
{
"model"
:
"gpt-3.5-turbo-16k"
,
"name"
:
"GPT35-16k"
,
"maxToken"
:
16000
,
"price"
:
0
}
}
projects/app/.env.template
View file @
bf172fab
...
@@ -15,8 +15,9 @@ OPENAI_BASE_URL=https://api.openai.com/v1
...
@@ -15,8 +15,9 @@ OPENAI_BASE_URL=https://api.openai.com/v1
# 通用key。可以是 openai 的也可以是 oneapi 的。
# 通用key。可以是 openai 的也可以是 oneapi 的。
# 此处逻辑:优先走 ONEAPI_URL,如果填写了 ONEAPI_URL,key 也需要是 ONEAPI 的 key
# 此处逻辑:优先走 ONEAPI_URL,如果填写了 ONEAPI_URL,key 也需要是 ONEAPI 的 key
CHAT_API_KEY=sk-xxxx
CHAT_API_KEY=sk-xxxx
#
db
#
mongo 数据库连接参数
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/fastgpt?authSource=admin
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/fastgpt?authSource=admin
# PG 数据库连接参数
PG_URL=postgresql://username:password@host:port/postgres
PG_URL=postgresql://username:password@host:port/postgres
# 首页路径
# 首页路径
HOME_URL=/
HOME_URL=/
projects/app/data/config.json
View file @
bf172fab
{
{
"FeConfig"
:
{
"show_emptyChat"
:
true
,
"show_contact"
:
true
,
"show_git"
:
true
,
"show_doc"
:
true
,
"systemTitle"
:
"FastGPT"
,
"authorText"
:
"Made by FastGPT Team."
,
"limit"
:
{
"exportLimitMinutes"
:
0
},
"scripts"
:
[]
},
"SystemParams"
:
{
"SystemParams"
:
{
"vectorMaxProcess"
:
15
,
"vectorMaxProcess"
:
15
,
"qaMaxProcess"
:
15
,
"qaMaxProcess"
:
15
,
...
...
projects/app/public/locales/zh/common.json
View file @
bf172fab
...
@@ -237,7 +237,7 @@
...
@@ -237,7 +237,7 @@
"key tips"
:
"你可以使用 API 秘钥访问一些特定的接口"
"key tips"
:
"你可以使用 API 秘钥访问一些特定的接口"
},
},
"outlink"
:
{
"outlink"
:
{
"Copy Iframe"
:
"
复制嵌入
"
,
"Copy Iframe"
:
"
嵌入网页
"
,
"Copy Link"
:
"复制"
,
"Copy Link"
:
"复制"
,
"Create API Key"
:
"创建新 Key"
,
"Create API Key"
:
"创建新 Key"
,
"Create Link"
:
"创建链接"
,
"Create Link"
:
"创建链接"
,
...
...
projects/app/src/components/ChatBox/SelectDataset.tsx
View file @
bf172fab
...
@@ -31,7 +31,7 @@ const SelectDataset = ({
...
@@ -31,7 +31,7 @@ const SelectDataset = ({
setParentId=
{
setParentId
}
setParentId=
{
setParentId
}
tips=
{
t
(
'chat.Select Mark Kb Desc'
)
}
tips=
{
t
(
'chat.Select Mark Kb Desc'
)
}
>
>
<
ModalBody
flex=
{
[
'1 0 0'
,
'0 0 auto'
]
}
maxH=
{
'80vh
'
}
overflowY=
{
'auto'
}
>
<
ModalBody
flex=
{
'1 0 0
'
}
overflowY=
{
'auto'
}
>
<
Grid
<
Grid
gridTemplateColumns=
{
[
'repeat(1,1fr)'
,
'repeat(2,1fr)'
,
'repeat(3,1fr)'
]
}
gridTemplateColumns=
{
[
'repeat(1,1fr)'
,
'repeat(2,1fr)'
,
'repeat(3,1fr)'
]
}
gridGap=
{
3
}
gridGap=
{
3
}
...
...
projects/app/src/components/ChatBox/index.tsx
View file @
bf172fab
...
@@ -16,10 +16,20 @@ import {
...
@@ -16,10 +16,20 @@ import {
ExportChatType
ExportChatType
}
from
'@/types/chat'
;
}
from
'@/types/chat'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
voiceBroadcast
,
cancelBroadcast
,
hasVoiceApi
}
from
'@/utils/web/voice'
;
import
{
useAudioPlay
}
from
'@/utils/web/voice'
;
import
{
getErrText
}
from
'@/utils/tools'
;
import
{
getErrText
}
from
'@/utils/tools'
;
import
{
useCopyData
}
from
'@/hooks/useCopyData'
;
import
{
useCopyData
}
from
'@/hooks/useCopyData'
;
import
{
Box
,
Card
,
Flex
,
Input
,
Textarea
,
Button
,
useTheme
,
BoxProps
}
from
'@chakra-ui/react'
;
import
{
Box
,
Card
,
Flex
,
Input
,
Textarea
,
Button
,
useTheme
,
BoxProps
,
FlexProps
}
from
'@chakra-ui/react'
;
import
{
feConfigs
}
from
'@/store/static'
;
import
{
feConfigs
}
from
'@/store/static'
;
import
{
event
}
from
'@/utils/plugin/eventbus'
;
import
{
event
}
from
'@/utils/plugin/eventbus'
;
import
{
adaptChat2GptMessages
}
from
'@/utils/common/adapt/message'
;
import
{
adaptChat2GptMessages
}
from
'@/utils/common/adapt/message'
;
...
@@ -57,7 +67,9 @@ import { splitGuideModule } from './utils';
...
@@ -57,7 +67,9 @@ import { splitGuideModule } from './utils';
const
nanoid
=
customAlphabet
(
'abcdefghijklmnopqrstuvwxyz1234567890'
,
24
);
const
nanoid
=
customAlphabet
(
'abcdefghijklmnopqrstuvwxyz1234567890'
,
24
);
const
textareaMinH
=
'22px'
;
const
textareaMinH
=
'22px'
;
type
generatingMessageProps
=
{
text
?:
string
;
name
?:
string
;
status
?:
'running'
|
'finish'
};
type
generatingMessageProps
=
{
text
?:
string
;
name
?:
string
;
status
?:
'running'
|
'finish'
};
export
type
StartChatFnProps
=
{
export
type
StartChatFnProps
=
{
chatList
:
ChatSiteItemType
[];
chatList
:
ChatSiteItemType
[];
messages
:
MessageItemType
[];
messages
:
MessageItemType
[];
...
@@ -79,72 +91,7 @@ enum FeedbackTypeEnum {
...
@@ -79,72 +91,7 @@ enum FeedbackTypeEnum {
hidden
=
'hidden'
hidden
=
'hidden'
}
}
const
VariableLabel
=
({
type
Props
=
{
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
}
w=
{
'85%'
}
maxW=
{
'600px'
}
m=
{
'auto'
}
alignItems=
{
'center'
}
justifyContent=
{
'center'
}
>
{
/* version intro */
}
<
Card
p=
{
4
}
mb=
{
10
}
minH=
{
'200px'
}
>
<
Markdown
source=
{
versionIntro
}
/>
</
Card
>
<
Card
p=
{
4
}
minH=
{
'600px'
}
>
<
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
=
(
{
feedbackType
=
FeedbackTypeEnum
.
hidden
,
showMarkIcon
=
false
,
showVoiceIcon
=
true
,
showEmptyIntro
=
false
,
chatId
,
appAvatar
,
userAvatar
,
userGuideModule
,
active
=
true
,
onUpdateVariable
,
onStartChat
,
onDelMessage
}:
{
feedbackType
?:
`
${
FeedbackTypeEnum
}
`
;
feedbackType
?:
`
${
FeedbackTypeEnum
}
`
;
showMarkIcon
?:
boolean
;
// admin mark dataset
showMarkIcon
?:
boolean
;
// admin mark dataset
showVoiceIcon
?:
boolean
;
showVoiceIcon
?:
boolean
;
...
@@ -161,14 +108,29 @@ const ChatBox = (
...
@@ -161,14 +108,29 @@ const ChatBox = (
isNewChat
?:
boolean
;
isNewChat
?:
boolean
;
}
>
;
}
>
;
onDelMessage
?:
(
e
:
{
contentId
?:
string
;
index
:
number
})
=>
void
;
onDelMessage
?:
(
e
:
{
contentId
?:
string
;
index
:
number
})
=>
void
;
},
};
const
ChatBox
=
(
{
feedbackType
=
FeedbackTypeEnum
.
hidden
,
showMarkIcon
=
false
,
showVoiceIcon
=
true
,
showEmptyIntro
=
false
,
chatId
,
appAvatar
,
userAvatar
,
userGuideModule
,
active
=
true
,
onUpdateVariable
,
onStartChat
,
onDelMessage
}:
Props
,
ref
:
ForwardedRef
<
ComponentRef
>
ref
:
ForwardedRef
<
ComponentRef
>
)
=>
{
)
=>
{
const
ChatBoxRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
ChatBoxRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
theme
=
useTheme
();
const
theme
=
useTheme
();
const
router
=
useRouter
();
const
router
=
useRouter
();
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
{
copyData
}
=
useCopyData
();
const
{
toast
}
=
useToast
();
const
{
toast
}
=
useToast
();
const
{
isPc
}
=
useGlobalStore
();
const
{
isPc
}
=
useGlobalStore
();
const
TextareaDom
=
useRef
<
HTMLTextAreaElement
>
(
null
);
const
TextareaDom
=
useRef
<
HTMLTextAreaElement
>
(
null
);
...
@@ -252,6 +214,7 @@ const ChatBox = (
...
@@ -252,6 +214,7 @@ const ChatBox = (
);
);
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
const
generatingMessage
=
useCallback
(
const
generatingMessage
=
useCallback
(
// concat text to end of message
({
text
=
''
,
status
,
name
}:
generatingMessageProps
)
=>
{
({
text
=
''
,
status
,
name
}:
generatingMessageProps
)
=>
{
setChatHistory
((
state
)
=>
setChatHistory
((
state
)
=>
state
.
map
((
item
,
index
)
=>
{
state
.
map
((
item
,
index
)
=>
{
...
@@ -277,14 +240,6 @@ const ChatBox = (
...
@@ -277,14 +240,6 @@ const ChatBox = (
[
generatingScroll
,
setChatHistory
]
[
generatingScroll
,
setChatHistory
]
);
);
// 复制内容
const
onclickCopy
=
useCallback
(
(
value
:
string
)
=>
{
copyData
(
value
);
},
[
copyData
]
);
// 重置输入内容
// 重置输入内容
const
resetInputVal
=
useCallback
((
val
:
string
)
=>
{
const
resetInputVal
=
useCallback
((
val
:
string
)
=>
{
if
(
!
TextareaDom
.
current
)
return
;
if
(
!
TextareaDom
.
current
)
return
;
...
@@ -477,6 +432,17 @@ const ChatBox = (
...
@@ -477,6 +432,17 @@ const ChatBox = (
},
},
[
chatHistory
,
onDelMessage
,
sendPrompt
,
variables
]
[
chatHistory
,
onDelMessage
,
sendPrompt
,
variables
]
);
);
// delete one message
const
delOneMessage
=
useCallback
(
({
dataId
,
index
}:
{
dataId
?:
string
;
index
:
number
})
=>
{
setChatHistory
((
state
)
=>
state
.
filter
((
chat
)
=>
chat
.
dataId
!==
dataId
));
onDelMessage
?.({
contentId
:
dataId
,
index
});
},
[
onDelMessage
]
);
// output data
// output data
useImperativeHandle
(
ref
,
()
=>
({
useImperativeHandle
(
ref
,
()
=>
({
...
@@ -497,23 +463,7 @@ const ChatBox = (
...
@@ -497,23 +463,7 @@ const ChatBox = (
scrollToBottom
scrollToBottom
}));
}));
const
controlIconStyle
=
{
/* style start */
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'
,
pl
:
1
,
mt
:
2
};
const
MessageCardStyle
:
BoxProps
=
{
const
MessageCardStyle
:
BoxProps
=
{
px
:
4
,
px
:
4
,
py
:
3
,
py
:
3
,
...
@@ -547,6 +497,7 @@ const ChatBox = (
...
@@ -547,6 +497,7 @@ const ChatBox = (
name
:
t
(
chatContent
.
moduleName
||
'Running'
)
name
:
t
(
chatContent
.
moduleName
||
'Running'
)
};
};
},
[
chatHistory
,
isChatting
,
t
]);
},
[
chatHistory
,
isChatting
,
t
]);
/* style end */
// page change and abort request
// page change and abort request
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -556,23 +507,9 @@ const ChatBox = (
...
@@ -556,23 +507,9 @@ const ChatBox = (
if
(
!
isNewChatReplace
.
current
)
{
if
(
!
isNewChatReplace
.
current
)
{
questionGuideController
.
current
?.
abort
(
'leave'
);
questionGuideController
.
current
?.
abort
(
'leave'
);
}
}
// close voice
cancelBroadcast
();
};
};
},
[
router
.
query
]);
},
[
router
.
query
]);
// page destroy and abort request
useEffect
(()
=>
{
const
listen
=
()
=>
{
cancelBroadcast
();
};
window
.
addEventListener
(
'beforeunload'
,
listen
);
return
()
=>
{
window
.
removeEventListener
(
'beforeunload'
,
listen
);
};
},
[]);
// add guide text listener
// add guide text listener
useEffect
(()
=>
{
useEffect
(()
=>
{
event
.
on
(
'guideClick'
,
({
text
}:
{
text
:
string
})
=>
{
event
.
on
(
'guideClick'
,
({
text
}:
{
text
:
string
})
=>
{
...
@@ -678,45 +615,17 @@ const ChatBox = (
...
@@ -678,45 +615,17 @@ const ChatBox = (
<>
<>
{
/* control icon */
}
{
/* control icon */
}
<
Flex
w=
{
'100%'
}
alignItems=
{
'center'
}
justifyContent=
{
'flex-end'
}
>
<
Flex
w=
{
'100%'
}
alignItems=
{
'center'
}
justifyContent=
{
'flex-end'
}
>
<
Flex
{
...
controlContainerStyle
}
justifyContent=
{
'flex-end'
}
mr=
{
3
}
>
<
ChatController
<
MyTooltip
label=
{
t
(
'common.Copy'
)
}
>
chat=
{
item
}
<
MyIcon
onDelete=
{
{
...
controlIconStyle
}
onDelMessage
name=
{
'copy'
}
?
()
=>
{
_hover=
{
{
color
:
'myBlue.700'
}
}
delOneMessage
({
dataId
:
item
.
dataId
,
index
});
onClick=
{
()
=>
onclickCopy
(
item
.
value
)
}
}
/>
:
undefined
</
MyTooltip
>
}
{
!!
onDelMessage
&&
(
onRetry=
{
()
=>
retryInput
(
index
)
}
<
MyTooltip
label=
{
t
(
'chat.retry'
)
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'retryLight'
}
_hover=
{
{
color
:
'green.500'
}
}
onClick=
{
()
=>
retryInput
(
index
)
}
/>
</
MyTooltip
>
)
}
{
onDelMessage
&&
(
<
MyTooltip
label=
{
t
(
'common.Delete'
)
}
>
<
MyIcon
{
...
controlIconStyle
}
mr=
{
0
}
name=
{
'delete'
}
_hover=
{
{
color
:
'red.600'
}
}
onClick=
{
()
=>
{
setChatHistory
((
state
)
=>
state
.
filter
((
chat
)
=>
chat
.
dataId
!==
item
.
dataId
)
);
onDelMessage
({
contentId
:
item
.
dataId
,
index
});
}
}
/>
/>
</
MyTooltip
>
)
}
</
Flex
>
<
ChatAvatar
src=
{
userAvatar
}
type=
{
'Human'
}
/>
<
ChatAvatar
src=
{
userAvatar
}
type=
{
'Human'
}
/>
</
Flex
>
</
Flex
>
{
/* content */
}
{
/* content */
}
...
@@ -737,57 +646,23 @@ const ChatBox = (
...
@@ -737,57 +646,23 @@ const ChatBox = (
{
item
.
obj
===
'AI'
&&
(
{
item
.
obj
===
'AI'
&&
(
<>
<>
{
/* control icon */
}
{
/* control icon */
}
<
Flex
w=
{
'100%'
}
alignItems=
{
'
flex-end
'
}
>
<
Flex
w=
{
'100%'
}
alignItems=
{
'
center
'
}
>
<
ChatAvatar
src=
{
appAvatar
}
type=
{
'AI'
}
/>
<
ChatAvatar
src=
{
appAvatar
}
type=
{
'AI'
}
/>
<
Flex
<
ChatController
{
...
controlContainerStyle
}
ml=
{
2
}
ml=
{
3
}
chat=
{
item
}
display=
{
index
===
chatHistory
.
length
-
1
&&
isChatting
?
'none'
:
'flex'
}
display=
{
index
===
chatHistory
.
length
-
1
&&
isChatting
?
'none'
:
'flex'
}
>
showVoiceIcon=
{
showVoiceIcon
}
<
MyTooltip
label=
{
'复制'
}
>
onDelete=
{
<
MyIcon
onDelMessage
{
...
controlIconStyle
}
?
()
=>
{
name=
{
'copy'
}
delOneMessage
({
dataId
:
item
.
dataId
,
index
});
_hover=
{
{
color
:
'myBlue.700'
}
}
}
onClick=
{
()
=>
onclickCopy
(
item
.
value
)
}
:
undefined
/>
}
</
MyTooltip
>
onMark=
{
{
onDelMessage
&&
(
showMarkIcon
<
MyTooltip
label=
{
'删除'
}
>
?
()
=>
{
<
MyIcon
{
...
controlIconStyle
}
name=
{
'delete'
}
_hover=
{
{
color
:
'red.600'
}
}
onClick=
{
()
=>
{
setChatHistory
((
state
)
=>
state
.
filter
((
chat
)
=>
chat
.
dataId
!==
item
.
dataId
)
);
onDelMessage
({
contentId
:
item
.
dataId
,
index
});
}
}
/>
</
MyTooltip
>
)
}
{
showVoiceIcon
&&
hasVoiceApi
&&
(
<
MyTooltip
label=
{
'语音播报'
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'voice'
}
_hover=
{
{
color
:
'#E74694'
}
}
onClick=
{
()
=>
voiceBroadcast
({
text
:
item
.
value
})
}
/>
</
MyTooltip
>
)
}
{
/* admin mark icon */
}
{
showMarkIcon
&&
(
<
MyTooltip
label=
{
t
(
'chat.Mark'
)
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'markLight'
}
_hover=
{
{
color
:
'#67c13b'
}
}
onClick=
{
()
=>
{
if
(
!
item
.
dataId
)
return
;
if
(
!
item
.
dataId
)
return
;
if
(
item
.
adminFeedback
)
{
if
(
item
.
adminFeedback
)
{
setAdminMarkData
({
setAdminMarkData
({
...
@@ -804,45 +679,23 @@ const ChatBox = (
...
@@ -804,45 +679,23 @@ const ChatBox = (
a
:
item
.
value
a
:
item
.
value
});
});
}
}
}
}
}
/>
:
undefined
</
MyTooltip
>
}
)
}
onReadFeedback=
{
{
feedbackType
===
FeedbackTypeEnum
.
admin
&&
(
feedbackType
===
FeedbackTypeEnum
.
admin
<
MyTooltip
label=
{
t
(
'chat.Read User Feedback'
)
}
>
?
()
=>
<
MyIcon
display=
{
item
.
userFeedback
?
'block'
:
'none'
}
{
...
controlIconStyle
}
color=
{
'white'
}
bg=
{
'#FC9663'
}
fontWeight=
{
'bold'
}
name=
{
'badLight'
}
onClick=
{
()
=>
setReadFeedbackData
({
setReadFeedbackData
({
chatItemId
:
item
.
dataId
||
''
,
chatItemId
:
item
.
dataId
||
''
,
content
:
item
.
userFeedback
||
''
,
content
:
item
.
userFeedback
||
''
,
isMarked
:
!!
item
.
adminFeedback
isMarked
:
!!
item
.
adminFeedback
})
})
:
undefined
}
}
/>
onFeedback=
{
</
MyTooltip
>
feedbackType
===
FeedbackTypeEnum
.
user
)
}
?
item
.
userFeedback
{
feedbackType
===
FeedbackTypeEnum
.
user
&&
(
?
()
=>
{
<
MyTooltip
label=
{
item
.
userFeedback
?
`取消反馈。\n您当前反馈内容为:\n${item.userFeedback}`
:
'反馈'
}
>
<
MyIcon
{
...
controlIconStyle
}
{
...
(!!
item
.
userFeedback
?
{
color
:
'
white
',
bg
:
'#
FC9663
',
fontWeight
:
'
bold
',
onClick
:
()
=
>
{
if
(
!
item
.
dataId
)
return
;
if
(
!
item
.
dataId
)
return
;
setChatHistory
((
state
)
=>
setChatHistory
((
state
)
=>
state
.
map
((
chatItem
)
=>
state
.
map
((
chatItem
)
=>
...
@@ -855,16 +708,10 @@ const ChatBox = (
...
@@ -855,16 +708,10 @@ const ChatBox = (
userUpdateChatFeedback
({
chatItemId
:
item
.
dataId
});
userUpdateChatFeedback
({
chatItemId
:
item
.
dataId
});
}
catch
(
error
)
{}
}
catch
(
error
)
{}
}
}
:
()
=>
setFeedbackId
(
item
.
dataId
)
:
undefined
}
}
:
{
_hover
:
{
color
:
'#FB7C3C'
},
onClick
:
()
=>
setFeedbackId
(
item
.
dataId
)
}
)}
name=
{
'badLight'
}
/>
/>
</
MyTooltip
>
)
}
</
Flex
>
{
/* chatting status */
}
{
/* chatting status */
}
{
statusBoxData
&&
index
===
chatHistory
.
length
-
1
&&
(
{
statusBoxData
&&
index
===
chatHistory
.
length
-
1
&&
(
<
Flex
<
Flex
...
@@ -960,7 +807,7 @@ const ChatBox = (
...
@@ -960,7 +807,7 @@ const ChatBox = (
</
Box
>
</
Box
>
</
Box
>
</
Box
>
</
Box
>
</
Box
>
{
/* input */
}
{
/*
message
input */
}
{
onStartChat
&&
variableIsFinish
&&
active
?
(
{
onStartChat
&&
variableIsFinish
&&
active
?
(
<
Box
m=
{
[
'0 auto'
,
'10px auto'
]
}
w=
{
'100%'
}
maxW=
{
[
'auto'
,
'min(750px, 100%)'
]
}
px=
{
[
0
,
5
]
}
>
<
Box
m=
{
[
'0 auto'
,
'10px auto'
]
}
w=
{
'100%'
}
maxW=
{
[
'auto'
,
'min(750px, 100%)'
]
}
px=
{
[
0
,
5
]
}
>
<
Box
<
Box
...
@@ -999,8 +846,8 @@ const ChatBox = (
...
@@ -999,8 +846,8 @@ const ChatBox = (
textarea
.
style
.
height
=
`${textarea.scrollHeight}px`
;
textarea
.
style
.
height
=
`${textarea.scrollHeight}px`
;
}
}
}
}
onKeyDown=
{
(
e
)
=>
{
onKeyDown=
{
(
e
)
=>
{
//
触发快捷发送
//
enter send.(pc or iframe && enter and unPress shift)
if
(
isPc
&&
e
.
keyCode
===
13
&&
!
e
.
shiftKey
)
{
if
(
(
isPc
||
window
!==
parent
)
&&
e
.
keyCode
===
13
&&
!
e
.
shiftKey
)
{
handleSubmit
((
data
)
=>
sendPrompt
(
data
,
TextareaDom
.
current
?.
value
))();
handleSubmit
((
data
)
=>
sendPrompt
(
data
,
TextareaDom
.
current
?.
value
))();
e
.
preventDefault
();
e
.
preventDefault
();
}
}
...
@@ -1089,6 +936,7 @@ const ChatBox = (
...
@@ -1089,6 +936,7 @@ const ChatBox = (
}
}
}
}
/>
/>
)
}
)
}
{
/* admin mark data */
}
{
showMarkIcon
&&
(
{
showMarkIcon
&&
(
<>
<>
{
/* select one dataset to insert markData */
}
{
/* select one dataset to insert markData */
}
...
@@ -1235,3 +1083,207 @@ export const useChatBox = () => {
...
@@ -1235,3 +1083,207 @@ export const useChatBox = () => {
onExportChat
onExportChat
}
;
}
;
};
};
function VariableLabel(
{
required
=
false
,
children
}
:
{
required
?:
boolean
;
children
:
React
.
ReactNode
|
string
;
}
)
{
return
(
<
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
>
);
}
function 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
>
);
}
function Empty()
{
const
{
data
:
chatProblem
}
=
useMarkdown
({
url
:
'/chatProblem.md'
});
const
{
data
:
versionIntro
}
=
useMarkdown
({
url
:
'/versionIntro.md'
});
return
(
<
Box
pt=
{
6
}
w=
{
'85%'
}
maxW=
{
'600px'
}
m=
{
'auto'
}
alignItems=
{
'center'
}
justifyContent=
{
'center'
}
>
{
/* version intro */
}
<
Card
p=
{
4
}
mb=
{
10
}
minH=
{
'200px'
}
>
<
Markdown
source=
{
versionIntro
}
/>
</
Card
>
<
Card
p=
{
4
}
minH=
{
'600px'
}
>
<
Markdown
source=
{
chatProblem
}
/>
</
Card
>
</
Box
>
);
}
function ChatController(
{
chat
,
display
,
showVoiceIcon
,
onReadFeedback
,
onMark
,
onRetry
,
onDelete
,
onFeedback
,
ml
,
mr
}
:
{
chat
:
ChatSiteItemType
;
showVoiceIcon
?:
boolean
;
onRetry
?:
()
=>
void
;
onDelete
?:
()
=>
void
;
onMark
?:
()
=>
void
;
onReadFeedback
?:
()
=>
void
;
onFeedback
?:
()
=>
void
;
}
&
FlexProps)
{
const
theme
=
useTheme
();
const
{
t
}
=
useTranslation
();
const
{
copyData
}
=
useCopyData
();
const
{
audioLoading
,
audioPlaying
,
hasAudio
,
playAudio
,
cancelAudio
}
=
useAudioPlay
({});
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'
,
pl
:
1
};
return
(
<
Flex
{
...
controlContainerStyle
}
ml=
{
ml
}
mr=
{
mr
}
display=
{
display
}
>
<
MyTooltip
label=
{
'复制'
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'copy'
}
_hover=
{
{
color
:
'myBlue.700'
}
}
onClick=
{
()
=>
copyData
(
chat
.
value
)
}
/>
</
MyTooltip
>
{
!!
onDelete
&&
(
<>
{
onRetry
&&
(
<
MyTooltip
label=
{
t
(
'chat.retry'
)
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'retryLight'
}
_hover=
{
{
color
:
'green.500'
}
}
onClick=
{
onRetry
}
/>
</
MyTooltip
>
)
}
<
MyTooltip
label=
{
'删除'
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'delete'
}
_hover=
{
{
color
:
'red.600'
}
}
onClick=
{
onDelete
}
/>
</
MyTooltip
>
</>
)
}
{
showVoiceIcon
&&
hasAudio
&&
(
audioLoading
?
(
<
MyTooltip
label=
{
'加载中...'
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'loading'
}
/>
</
MyTooltip
>
)
:
audioPlaying
?
(
<
MyTooltip
label=
{
'终止播放'
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'pause'
}
_hover=
{
{
color
:
'#E74694'
}
}
onClick=
{
()
=>
cancelAudio
()
}
/>
</
MyTooltip
>
)
:
(
<
MyTooltip
label=
{
'语音播报'
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'voice'
}
_hover=
{
{
color
:
'#E74694'
}
}
onClick=
{
()
=>
playAudio
(
chat
.
value
)
}
/>
</
MyTooltip
>
))
}
{
!!
onMark
&&
(
<
MyTooltip
label=
{
t
(
'chat.Mark'
)
}
>
<
MyIcon
{
...
controlIconStyle
}
name=
{
'markLight'
}
_hover=
{
{
color
:
'#67c13b'
}
}
onClick=
{
onMark
}
/>
</
MyTooltip
>
)
}
{
!!
onReadFeedback
&&
(
<
MyTooltip
label=
{
t
(
'chat.Read User Feedback'
)
}
>
<
MyIcon
display=
{
chat
.
userFeedback
?
'block'
:
'none'
}
{
...
controlIconStyle
}
color=
{
'white'
}
bg=
{
'#FC9663'
}
fontWeight=
{
'bold'
}
name=
{
'badLight'
}
onClick=
{
onReadFeedback
}
/>
</
MyTooltip
>
)
}
{
!!
onFeedback
&&
(
<
MyTooltip
label=
{
chat
.
userFeedback
?
`取消反馈。\n您当前反馈内容为:\n${chat.userFeedback}`
:
'反馈'
}
>
<
MyIcon
{
...
controlIconStyle
}
{
...
(!!
chat
.
userFeedback
?
{
color
:
'
white
',
bg
:
'#
FC9663
',
fontWeight
:
'
bold
',
onClick
:
onFeedback
}
:
{
_hover
:
{
color
:
'#
FB7C3C
'
},
onClick
:
onFeedback
})}
name=
{
'badLight'
}
/>
</
MyTooltip
>
)
}
</
Flex
>
);
}
projects/app/src/components/Icon/icons/common/pause.svg
0 → 100644
View file @
bf172fab
<?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=
"1696179048209"
class=
"icon"
viewBox=
"0 0 1024 1024"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
p-id=
"4182"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"48"
height=
"48"
><path
d=
"M373.333333 85.333333H266.666667a53.393333 53.393333 0 0 0-53.333334 53.333334v746.666666a53.393333 53.393333 0 0 0 53.333334 53.333334h106.666666a53.393333 53.393333 0 0 0 53.333334-53.333334V138.666667a53.393333 53.393333 0 0 0-53.333334-53.333334z m10.666667 800a10.666667 10.666667 0 0 1-10.666667 10.666667H266.666667a10.666667 10.666667 0 0 1-10.666667-10.666667V138.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h106.666666a10.666667 10.666667 0 0 1 10.666667 10.666667z m373.333333-800H650.666667a53.393333 53.393333 0 0 0-53.333334 53.333334v746.666666a53.393333 53.393333 0 0 0 53.333334 53.333334h106.666666a53.393333 53.393333 0 0 0 53.333334-53.333334V138.666667a53.393333 53.393333 0 0 0-53.333334-53.333334z m10.666667 800a10.666667 10.666667 0 0 1-10.666667 10.666667H650.666667a10.666667 10.666667 0 0 1-10.666667-10.666667V138.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h106.666666a10.666667 10.666667 0 0 1 10.666667 10.666667z"
p-id=
"4183"
></path></svg>
\ No newline at end of file
projects/app/src/components/Icon/icons/light/loading.svg
0 → 100644
View file @
bf172fab
<?xml version="1.0" encoding="utf-8"?>
<svg
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
style=
" background: rgb(255, 255, 255); display: block; shape-rendering: auto;"
viewBox=
"0 0 100 100"
preserveAspectRatio=
"xMidYMid"
>
<g
transform=
"rotate(0 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.9166666666666666s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(30 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.8333333333333334s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(60 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.75s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(90 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.6666666666666666s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(120 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.5833333333333334s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(150 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.5s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(180 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.4166666666666667s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(210 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.3333333333333333s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(240 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.25s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(270 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.16666666666666666s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(300 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"-0.08333333333333333s"
repeatCount=
"indefinite"
></animate>
</rect>
</g><g
transform=
"rotate(330 50 50)"
>
<rect
x=
"47.5"
y=
"24"
rx=
"2.5"
ry=
"6"
width=
"5"
height=
"12"
fill=
"#3370ff"
>
<animate
attributeName=
"opacity"
values=
"1;0"
keyTimes=
"0;1"
dur=
"1s"
begin=
"0s"
repeatCount=
"indefinite"
></animate>
</rect>
</g>
<!-- [ldio] generated by https://loading.io/ -->
</svg>
\ No newline at end of file
projects/app/src/components/Icon/index.tsx
View file @
bf172fab
...
@@ -87,7 +87,9 @@ const iconPaths = {
...
@@ -87,7 +87,9 @@ const iconPaths = {
searchLight
:
()
=>
import
(
'./icons/light/search.svg'
),
searchLight
:
()
=>
import
(
'./icons/light/search.svg'
),
plusFill
:
()
=>
import
(
'./icons/fill/plus.svg'
),
plusFill
:
()
=>
import
(
'./icons/fill/plus.svg'
),
moveLight
:
()
=>
import
(
'./icons/light/move.svg'
),
moveLight
:
()
=>
import
(
'./icons/light/move.svg'
),
questionGuide
:
()
=>
import
(
'./icons/app/questionGuide.svg'
)
questionGuide
:
()
=>
import
(
'./icons/app/questionGuide.svg'
),
loading
:
()
=>
import
(
'./icons/light/loading.svg'
),
pause
:
()
=>
import
(
'./icons/common/pause.svg'
)
};
};
export
type
IconName
=
keyof
typeof
iconPaths
;
export
type
IconName
=
keyof
typeof
iconPaths
;
...
...
projects/app/src/components/Markdown/chat/Guide.tsx
View file @
bf172fab
...
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown';
...
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown';
import
RemarkGfm
from
'remark-gfm'
;
import
RemarkGfm
from
'remark-gfm'
;
import
RemarkMath
from
'remark-math'
;
import
RemarkMath
from
'remark-math'
;
import
RehypeKatex
from
'rehype-katex'
;
import
RehypeKatex
from
'rehype-katex'
;
import
RemarkBreaks
from
'remark-breaks'
;
import
{
event
}
from
'@/utils/plugin/eventbus'
;
import
{
event
}
from
'@/utils/plugin/eventbus'
;
import
'katex/dist/katex.min.css'
;
import
'katex/dist/katex.min.css'
;
...
@@ -38,12 +39,14 @@ function MyLink(e: any) {
...
@@ -38,12 +39,14 @@ function MyLink(e: any) {
}
}
const
Guide
=
({
text
}:
{
text
:
string
})
=>
{
const
Guide
=
({
text
}:
{
text
:
string
})
=>
{
const
formatText
=
useMemo
(()
=>
text
.
replace
(
/
\[(
.*
?)\](
$|
\n)
/g
,
'[$1]()\n'
),
[
text
]);
const
formatText
=
useMemo
(
()
=>
text
.
replace
(
/
\[(
.*
?)\](
$|
\n)
/g
,
'[$1]()\n'
).
replace
(
/
\\
n/g
,
'\n '
),
[
text
]
);
return
(
return
(
<
ReactMarkdown
<
ReactMarkdown
className=
{
`markdown ${styles.markdown}`
}
className=
{
`markdown ${styles.markdown}`
}
remarkPlugins=
{
[
RemarkGfm
,
RemarkMath
]
}
remarkPlugins=
{
[
RemarkGfm
,
RemarkMath
,
RemarkBreaks
]
}
rehypePlugins=
{
[
RehypeKatex
]
}
rehypePlugins=
{
[
RehypeKatex
]
}
components=
{
{
components=
{
{
a
:
MyLink
,
a
:
MyLink
,
...
...
projects/app/src/components/Markdown/index.tsx
View file @
bf172fab
...
@@ -62,6 +62,8 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
...
@@ -62,6 +62,8 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
[]
[]
);
);
const
formatSource
=
source
.
replace
(
/
\\
n/g
,
'\n '
);
return
(
return
(
<
ReactMarkdown
<
ReactMarkdown
className=
{
`markdown ${styles.markdown}
className=
{
`markdown ${styles.markdown}
...
@@ -73,9 +75,9 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
...
@@ -73,9 +75,9 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
components=
{
components
}
components=
{
components
}
linkTarget=
{
'_blank'
}
linkTarget=
{
'_blank'
}
>
>
{
s
ource
}
{
formatS
ource
}
</
ReactMarkdown
>
</
ReactMarkdown
>
);
);
};
};
export
default
Markdown
;
export
default
React
.
memo
(
Markdown
)
;
projects/app/src/components/PageContainer/index.tsx
View file @
bf172fab
...
@@ -8,7 +8,7 @@ const PageContainer = ({ children, ...props }: BoxProps) => {
...
@@ -8,7 +8,7 @@ const PageContainer = ({ children, ...props }: BoxProps) => {
<
Box
<
Box
h=
{
'100%'
}
h=
{
'100%'
}
bg=
{
'white'
}
bg=
{
'white'
}
borderRadius=
{
[
0
,
'2xl'
]
}
borderRadius=
{
props
?.
borderRadius
||
[
0
,
'2xl'
]
}
border=
{
[
'none'
,
theme
.
borders
.
lg
]
}
border=
{
[
'none'
,
theme
.
borders
.
lg
]
}
overflow=
{
'overlay'
}
overflow=
{
'overlay'
}
>
>
...
...
projects/app/src/components/core/dataset/SelectModal.tsx
View file @
bf172fab
...
@@ -33,14 +33,8 @@ const DatasetSelectContainer = ({
...
@@ -33,14 +33,8 @@ const DatasetSelectContainer = ({
const
{
isPc
}
=
useGlobalStore
();
const
{
isPc
}
=
useGlobalStore
();
return
(
return
(
<
MyModal
<
MyModal
isOpen=
{
isOpen
}
onClose=
{
onClose
}
w=
{
'100%'
}
maxW=
{
[
'90vw'
,
'900px'
]
}
isCentered
>
isOpen=
{
isOpen
}
<
Flex
flexDirection=
{
'column'
}
h=
{
'90vh'
}
>
onClose=
{
onClose
}
w=
{
'100%'
}
maxW=
{
[
'90vw'
,
'900px'
]
}
isCentered=
{
!
isPc
}
>
<
Flex
flexDirection=
{
'column'
}
h=
{
[
'90vh'
,
'auto'
]
}
>
<
ModalHeader
>
<
ModalHeader
>
{
!!
parentId
?
(
{
!!
parentId
?
(
<
Flex
<
Flex
...
...
projects/app/src/constants/flow/ModuleTemplate.ts
View file @
bf172fab
...
@@ -308,7 +308,7 @@ export const AnswerModule: FlowModuleTemplateType = {
...
@@ -308,7 +308,7 @@ export const AnswerModule: FlowModuleTemplateType = {
value: '',
value: '',
label: '回复的内容',
label: '回复的内容',
description:
description:
'可以使用 \\n 来实现
换行。也
可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容'
'可以使用 \\n 来实现
连续换行。\n\n
可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容'
}
}
],
],
outputs: [
outputs: [
...
...
projects/app/src/pages/_app.tsx
View file @
bf172fab
...
@@ -61,6 +61,12 @@ function App({ Component, pageProps }: AppProps) {
...
@@ -61,6 +61,12 @@ function App({ Component, pageProps }: AppProps) {
url
url
});
});
};
};
// log fastgpt
console
.
log
(
'%cWelcome to FastGPT'
,
'font-family:Arial; color:#3370ff ; font-size:18px; font-weight:bold;'
,
`GitHub:https://github.com/labring/FastGPT`
);
return
()
=>
{
return
()
=>
{
window
.
onerror
=
null
;
window
.
onerror
=
null
;
};
};
...
...
projects/app/src/pages/api/core/dataset/data/exportAll.ts
View file @
bf172fab
...
@@ -67,7 +67,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...
@@ -67,7 +67,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
res.end('Error connecting to database');
res.end('Error connecting to database');
return;
return;
}
}
console.log('export data');
// create pg select stream
// create pg select stream
const query = new QueryStream(
const query = new QueryStream(
...
@@ -77,16 +76,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...
@@ -77,16 +76,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
);
);
const stream = client.query(query);
const stream = client.query(query);
res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv');
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv; ');
res.write('index,content,source');
const write = responseWriteController({
const write = responseWriteController({
res,
res,
readStream: stream
readStream: stream
});
});
write('index,content,source');
// parse data every row
// parse data every row
stream.on('data', ({ q, a, source }: { q: string; a: string; source?: string }) => {
stream.on('data', ({ q, a, source }: { q: string; a: string; source?: string }) => {
if (res.closed) {
if (res.closed) {
...
...
projects/app/src/pages/chat/share.tsx
View file @
bf172fab
import
React
,
{
useCallback
,
use
Memo
,
useRef
}
from
'react'
;
import
React
,
{
useCallback
,
use
Effect
,
useMemo
,
useRef
,
useState
}
from
'react'
;
import
Head
from
'next/head'
;
import
Head
from
'next/head'
;
import
{
useRouter
}
from
'next/router'
;
import
{
useRouter
}
from
'next/router'
;
import
{
initShareChatInfo
}
from
'@/api/support/outLink'
;
import
{
initShareChatInfo
}
from
'@/api/support/outLink'
;
...
@@ -35,6 +35,7 @@ const OutLink = ({
...
@@ -35,6 +35,7 @@ const OutLink = ({
const
{
isOpen
:
isOpenSlider
,
onClose
:
onCloseSlider
,
onOpen
:
onOpenSlider
}
=
useDisclosure
();
const
{
isOpen
:
isOpenSlider
,
onClose
:
onCloseSlider
,
onOpen
:
onOpenSlider
}
=
useDisclosure
();
const
{
isPc
}
=
useGlobalStore
();
const
{
isPc
}
=
useGlobalStore
();
const
forbidRefresh
=
useRef
(
false
);
const
forbidRefresh
=
useRef
(
false
);
const
[
isEmbed
,
setIdEmbed
]
=
useState
(
true
);
const
ChatBoxRef
=
useRef
<
ComponentRef
>
(
null
);
const
ChatBoxRef
=
useRef
<
ComponentRef
>
(
null
);
...
@@ -163,8 +164,12 @@ const OutLink = ({
...
@@ -163,8 +164,12 @@ const OutLink = ({
return
loadAppInfo
(
shareId
,
chatId
,
authToken
);
return
loadAppInfo
(
shareId
,
chatId
,
authToken
);
});
});
useEffect
(()
=>
{
setIdEmbed
(
window
!==
parent
);
},
[]);
return
(
return
(
<
PageContainer
>
<
PageContainer
{
...
(
isEmbed
?
{
p
:
'0
!
important
',
borderRadius
:
'0'
}
:
{})}
>
<
Head
>
<
Head
>
<
title
>
{
shareChatData
.
app
.
name
}
</
title
>
<
title
>
{
shareChatData
.
app
.
name
}
</
title
>
</
Head
>
</
Head
>
...
...
projects/app/src/service/common/stream.ts
View file @
bf172fab
...
@@ -11,7 +11,7 @@ export function responseWriteController({
...
@@ -11,7 +11,7 @@ export function responseWriteController({
readStream
.
resume
();
readStream
.
resume
();
});
});
return
(
text
:
string
)
=>
{
return
(
text
:
string
|
Buffer
)
=>
{
const
writeResult
=
res
.
write
(
text
);
const
writeResult
=
res
.
write
(
text
);
if
(
!
writeResult
)
{
if
(
!
writeResult
)
{
readStream
.
pause
();
readStream
.
pause
();
...
...
projects/app/src/service/moduleDispatch/tools/answer.ts
View file @
bf172fab
...
@@ -23,7 +23,7 @@ export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
...
@@ -23,7 +23,7 @@ export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
res
,
res
,
event
:
detail
?
sseResponseEventEnum
.
answer
:
undefined
,
event
:
detail
?
sseResponseEventEnum
.
answer
:
undefined
,
data
:
textAdaptGptResponse
({
data
:
textAdaptGptResponse
({
text
:
text
.
replace
?.(
/
\\
n/g
,
'\n'
)
||
''
text
})
})
});
});
}
}
...
...
projects/app/src/utils/web/voice.ts
View file @
bf172fab
export
const
hasVoiceApi
=
typeof
window
!==
'undefined'
&&
'speechSynthesis'
in
window
;
import
{
useState
,
useCallback
,
useEffect
,
useMemo
}
from
'react'
;
/**
import
{
useToast
}
from
'@/hooks/useToast'
;
* voice broadcast
import
{
getErrText
}
from
'../tools'
;
*/
export
const
voiceBroadcast
=
({
text
}:
{
text
:
string
})
=>
{
export
const
useAudioPlay
=
(
props
?:
{
ttsUrl
?:
string
})
=>
{
const
{
ttsUrl
}
=
props
||
{};
const
{
toast
}
=
useToast
();
const
[
audio
,
setAudio
]
=
useState
<
HTMLAudioElement
>
();
const
[
audioLoading
,
setAudioLoading
]
=
useState
(
false
);
const
[
audioPlaying
,
setAudioPlaying
]
=
useState
(
false
);
const
hasAudio
=
useMemo
(()
=>
{
if
(
ttsUrl
)
return
true
;
const
voices
=
window
.
speechSynthesis
?.
getVoices
?.()
||
[];
// 获取语言包
const
voice
=
voices
.
find
((
item
)
=>
{
return
item
.
lang
===
'zh-CN'
;
});
return
!!
voice
;
},
[
ttsUrl
]);
const
playAudio
=
useCallback
(
async
(
text
:
string
)
=>
{
text
=
text
.
replace
(
/
\\
n/g
,
'\n'
);
try
{
if
(
audio
&&
ttsUrl
)
{
setAudioLoading
(
true
);
const
response
=
await
fetch
(
ttsUrl
,
{
method
:
'POST'
,
headers
:
{
'content-type'
:
'application/json'
},
body
:
JSON
.
stringify
({
text
})
}).
then
((
res
)
=>
res
.
blob
());
const
audioUrl
=
URL
.
createObjectURL
(
response
);
audio
.
src
=
audioUrl
;
audio
.
play
();
}
else
{
// window speech
window
.
speechSynthesis
?.
cancel
();
window
.
speechSynthesis
?.
cancel
();
const
msg
=
new
SpeechSynthesisUtterance
(
text
);
const
msg
=
new
SpeechSynthesisUtterance
(
text
);
const
voices
=
window
.
speechSynthesis
?.
getVoices
?.()
;
// 获取语言包
const
voices
=
window
.
speechSynthesis
?.
getVoices
?.()
||
[]
;
// 获取语言包
const
voice
=
voices
.
find
((
item
)
=>
{
const
voice
=
voices
.
find
((
item
)
=>
{
return
item
.
name
===
'Microsoft Yaoyao - Chinese (Simplified, PRC)
'
;
return
item
.
lang
===
'zh-CN
'
;
});
});
if
(
voice
)
{
if
(
voice
)
{
msg
.
onstart
=
()
=>
{
setAudioPlaying
(
true
);
};
msg
.
onend
=
()
=>
{
setAudioPlaying
(
false
);
msg
.
onstart
=
null
;
msg
.
onend
=
null
;
};
msg
.
voice
=
voice
;
msg
.
voice
=
voice
;
window
.
speechSynthesis
?.
speak
(
msg
);
}
}
}
}
catch
(
error
)
{
toast
({
status
:
'error'
,
title
:
getErrText
(
error
,
'语音播报异常'
)
});
}
setAudioLoading
(
false
);
},
[
audio
,
toast
,
ttsUrl
]
);
window
.
speechSynthesis
?.
speak
(
msg
);
const
cancelAudio
=
useCallback
(()
=>
{
if
(
audio
)
{
audio
.
pause
();
audio
.
src
=
''
;
}
window
.
speechSynthesis
?.
cancel
();
setAudioPlaying
(
false
);
},
[
audio
]);
useEffect
(()
=>
{
if
(
ttsUrl
)
{
setAudio
(
new
Audio
());
}
else
{
setAudio
(
undefined
);
}
},
[
ttsUrl
]);
msg
.
onerror
=
(
e
)
=>
{
useEffect
(()
=>
{
console
.
log
(
e
);
if
(
audio
)
{
audio
.
onplay
=
()
=>
{
setAudioPlaying
(
true
);
};
audio
.
onended
=
()
=>
{
setAudioPlaying
(
false
);
};
audio
.
onerror
=
()
=>
{
setAudioPlaying
(
false
);
};
}
const
listen
=
()
=>
{
cancelAudio
();
};
};
window
.
addEventListener
(
'beforeunload'
,
listen
);
return
()
=>
{
if
(
audio
)
{
audio
.
onplay
=
null
;
audio
.
onended
=
null
;
audio
.
onerror
=
null
;
}
cancelAudio
();
window
.
removeEventListener
(
'beforeunload'
,
listen
);
};
},
[
audio
,
cancelAudio
]);
useEffect
(()
=>
{
return
()
=>
{
setAudio
(
undefined
);
};
},
[]);
return
{
return
{
cancel
:
()
=>
window
.
speechSynthesis
?.
cancel
()
audioPlaying
,
audioLoading
,
hasAudio
,
playAudio
,
cancelAudio
};
};
};
};
export
const
cancelBroadcast
=
()
=>
{
window
.
speechSynthesis
?.
cancel
();
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment