Compare commits
7 Commits
v4.7.1-alp
...
v4.7.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2991c07467 | ||
|
|
adfad8ff7f | ||
|
|
1fbc407ecf | ||
|
|
3b0b2d68cc | ||
|
|
64db0e4f25 | ||
|
|
5cfa43287f | ||
|
|
a01b945bc9 |
34
.github/imgs/logo.svg
vendored
@@ -1,14 +1,20 @@
|
||||
<svg width="32" height="32" viewBox="0 0 1041 1348" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M340.837 0.33933L681.068 0.338989V0.455643C684.032 0.378397 686.999 0.339702 689.967 0.339702C735.961 0.3397 781.504 9.62899 823.997 27.6772C866.49 45.7254 905.099 72.1791 937.622 105.528C970.144 138.877 995.942 178.467 1013.54 222.04C1031.14 265.612 1040.2 312.312 1040.2 359.474L340.836 359.474L340.836 1347.84C296.157 1347.84 251.914 1338.55 210.636 1320.49C169.357 1302.43 131.85 1275.95 100.257 1242.58C68.6636 1209.21 43.6023 1169.59 26.5041 1125.99C11.3834 1087.43 2.75216 1046.42 0.957956 1004.81H0.605869L0.605897 368.098H0.70363C0.105752 341.831 2.23741 315.443 7.14306 289.411C20.2709 219.745 52.6748 155.754 100.257 105.528C147.839 55.3017 208.462 21.0975 274.461 7.24017C296.426 2.62833 318.657 0.339101 340.837 0.33933Z" fill="url(#paint0_linear_1172_228)"/>
|
||||
<path d="M633.639 904.645H513.029V576.37H635.422V576.377C678.161 576.607 720.454 585.093 759.951 601.37C799.997 617.874 836.384 642.064 867.033 672.559C897.683 703.054 921.996 739.257 938.583 779.101C955.171 818.944 963.709 861.648 963.709 904.775H633.639V904.645Z" fill="url(#paint1_linear_1172_228)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1172_228" x1="520.404" y1="0.338989" x2="520.404" y2="1347.84" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#326DFF"/>
|
||||
<stop offset="1" stop-color="#8EAEFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1172_228" x1="738.369" y1="576.37" x2="738.369" y2="904.775" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#326DFF"/>
|
||||
<stop offset="1" stop-color="#8EAEFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<svg width="49" height="48" viewBox="0 0 49 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M20.3692 7.00001L28.9536 7V7.00294C29.0284 7.00099 29.1033 7.00002 29.1782 7.00002C30.3387 7.00002 31.4878 7.2344 32.5599 7.68979C33.6321 8.14518 34.6062 8.81265 35.4268 9.6541C36.2474 10.4956 36.8983 11.4945 37.3424 12.5939C37.7865 13.6933 38.0151 14.8716 38.0151 16.0616L20.3691 16.0616L20.3691 41C19.2418 41 18.1255 40.7655 17.084 40.3097C16.0425 39.854 15.0961 39.1861 14.299 38.344C13.5018 37.502 12.8695 36.5024 12.4381 35.4022C12.0566 34.4292 11.8388 33.3945 11.7935 32.3446H11.7846L11.7846 16.2792H11.7871C11.772 15.6165 11.8258 14.9506 11.9496 14.2938C12.2808 12.536 13.0984 10.9214 14.299 9.6541C15.4995 8.38681 17.0291 7.52377 18.6944 7.17413C19.2486 7.05776 19.8095 7 20.3692 7.00001Z"
|
||||
fill="url(#paint0_linear_1008_3495)" />
|
||||
<path
|
||||
d="M27.7569 29.8173H24.7138V21.5343H27.8019V21.5345C28.8803 21.5403 29.9474 21.7544 30.944 22.1651C31.9544 22.5815 32.8725 23.1919 33.6458 23.9613C34.4191 24.7308 35.0326 25.6442 35.4511 26.6496C35.8696 27.6549 36.085 28.7324 36.085 29.8205H27.7569V29.8173Z"
|
||||
fill="url(#paint1_linear_1008_3495)" />
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1008_3495" x1="24.8999" y1="7" x2="24.8999" y2="41"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#326DFF" />
|
||||
<stop offset="1" stop-color="#8EAEFF" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1008_3495" x1="30.3994" y1="21.5343" x2="30.3994" y2="29.8205"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#326DFF" />
|
||||
<stop offset="1" stop-color="#8EAEFF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,4 +1,4 @@
|
||||
name: Build docs images and copy image to docker hub
|
||||
name: Deploy image by kubeconfig
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs:
|
||||
tags: ${{ steps.datetime.outputs.datetime }}
|
||||
tags: ${{ steps.datetime.outputs.datetime }}
|
||||
update-docs-image:
|
||||
needs: build-fastgpt-docs-images
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -85,4 +85,4 @@ jobs:
|
||||
env:
|
||||
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
|
||||
with:
|
||||
args: annotate deployment/fastgpt-docs originImageName="registry.cn-hangzhou.aliyuncs.com/${{ secrets.ALI_HUB_USERNAME }}/fastgpt-docs:${{ needs.build-fastgpt-docs-images.outputs.tags }}" --overwrite
|
||||
args: annotate deployment/fastgpt-docs originImageName="registry.cn-hangzhou.aliyuncs.com/${{ secrets.ALI_HUB_USERNAME }}/fastgpt-docs:${{ needs.build-fastgpt-docs-images.outputs.tags }}" --overwrite
|
||||
@@ -1,4 +1,4 @@
|
||||
name: deploy-docs
|
||||
name: Deploy image to vercel
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
- name: Add cdn for images
|
||||
run: |
|
||||
sed -i "s#\](/imgs/#\](https://cdn.jsdelivr.us/gh/yangchuansheng/fastgpt-imgs@main/imgs/#g" $(grep -rl "\](/imgs/" docSite/content/docs)
|
||||
sed -i "s#\](/imgs/#\](https://cdn.jsdelivr.net/gh/yangchuansheng/fastgpt-imgs@main/imgs/#g" $(grep -rl "\](/imgs/" docSite/content/docs)
|
||||
|
||||
# Step 3 - Install Hugo (specific version)
|
||||
- name: Install Hugo
|
||||
4
.github/workflows/docs-preview.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: preview-docs
|
||||
name: Preview FastGPT docs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
- name: Add cdn for images
|
||||
run: |
|
||||
sed -i "s#\](/imgs/#\](https://cdn.jsdelivr.us/gh/yangchuansheng/fastgpt-imgs@main/imgs/#g" $(grep -rl "\](/imgs/" docSite/content/docs)
|
||||
sed -i "s#\](/imgs/#\](https://cdn.jsdelivr.net/gh/yangchuansheng/fastgpt-imgs@main/imgs/#g" $(grep -rl "\](/imgs/" docSite/content/docs)
|
||||
|
||||
# Step 3 - Install Hugo (specific version)
|
||||
- name: Install Hugo
|
||||
|
||||
2
.github/workflows/helm-release.yaml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Release
|
||||
name: Release helm chart
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
16
README.md
@@ -38,8 +38,6 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
||||
|
||||
- 🌍 海外版:[fastgpt.in](https://fastgpt.in/)
|
||||
|
||||
fastgpt.run 域名会弃用。
|
||||
|
||||
| | |
|
||||
| ---------------------------------- | ---------------------------------- |
|
||||
|  |  |
|
||||
@@ -53,23 +51,21 @@ fastgpt.run 域名会弃用。
|
||||
|
||||
`1` 应用编排能力
|
||||
- [x] 提供简易模式,无需操作编排
|
||||
- [x] 对话下一步指引
|
||||
- [x] 工作流编排
|
||||
- [x] 源文件引用追踪
|
||||
- [x] 模块封装,实现多级复用
|
||||
- [x] 混合检索 & 重排
|
||||
- [x] Tool 模块
|
||||
- [ ] 嵌入 [Laf](https://github.com/labring/laf),实现在线编写 HTTP 模块
|
||||
- [ ] 嵌入 [Laf](https://github.com/labring/laf),实现在线编写 HTTP 模块。初版已完成。
|
||||
- [ ] 插件封装功能,支持低代码渲染
|
||||
|
||||
`2` 知识库能力
|
||||
- [x] 多库复用,混用
|
||||
- [x] chunk 记录修改和删除
|
||||
- [x] 支持知识库单独设置向量模型
|
||||
- [x] 源文件存储
|
||||
- [x] 支持手动输入,直接分段,QA 拆分导入
|
||||
- [x] 支持。txt, 。md, 。html, 。pdf, 。docx,pptx, 。csv, 。xlsx (有需要更多可 PR file loader)
|
||||
- [x] 支持 txt,md,html,pdf,docx,pptx,csv,xlsx (有需要更多可 PR file loader)
|
||||
- [x] 支持 url 读取、CSV 批量导入
|
||||
- [x] 混合检索 & 重排
|
||||
- [ ] 支持文件阅读器
|
||||
- [ ] 更多的数据预处理方案
|
||||
|
||||
@@ -90,6 +86,8 @@ fastgpt.run 域名会弃用。
|
||||
- [x] Iframe 一键嵌入
|
||||
- [x] 聊天窗口嵌入支持自定义 Icon,默认打开,拖拽等功能
|
||||
- [x] 统一查阅对话记录,并对数据进行标注
|
||||
`6` 其他
|
||||
- [x] 支持语音输入和输出 (可配置语音输入语音回答)
|
||||
|
||||
<a href="#readme">
|
||||
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">
|
||||
@@ -103,7 +101,7 @@ fastgpt.run 域名会弃用。
|
||||
|
||||
> [Sealos](https://sealos.io) 的服务器在国外,不需要额外处理网络问题,无需服务器、无需魔法、无需域名,支持高并发 & 动态伸缩。点击以下按钮即可一键部署 👇
|
||||
|
||||
[](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
|
||||
[](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
|
||||
|
||||
由于需要部署数据库,部署完后需要等待 2~4 分钟才能正常访问。默认用了最低配置,首次访问时会有些慢。相关使用教程可查看:[Sealos 部署 FastGPT](https://doc.fastgpt.in/docs/development/sealos/)
|
||||
|
||||
@@ -123,7 +121,7 @@ fastgpt.run 域名会弃用。
|
||||
|
||||
wx 扫一下加入:
|
||||
|
||||

|
||||

|
||||
|
||||
<a href="#readme">
|
||||
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">
|
||||
|
||||
@@ -106,7 +106,7 @@ Project tech stack: NextJs + TS + ChakraUI + Mongo + Postgres (Vector plugin)
|
||||
|
||||
- **⚡ Deployment**
|
||||
|
||||
[](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
|
||||
[](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
|
||||
|
||||
Give it a 2-4 minute wait after deployment as it sets up the database. Initially, it might be a tad slow since we're using the basic settings.
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
||||
|
||||
- **⚡ デプロイ**
|
||||
|
||||
[](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
|
||||
[](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
|
||||
|
||||
デプロイ 後、データベースをセットアップするので、2~4分待 ってください。基本設定 を 使 っているので、最初 は 少 し 遅 いかもしれません。
|
||||
|
||||
|
||||
BIN
docSite/assets/imgs/laf1.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docSite/assets/imgs/laf2.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docSite/assets/imgs/laf3.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
docSite/assets/imgs/laf4.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
docSite/assets/imgs/rerank1.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
@@ -20,7 +20,7 @@ llm模型全部合并
|
||||
```json
|
||||
{
|
||||
"feConfigs": {
|
||||
"lafEnv": "https://laf.dev" // laf环境
|
||||
"lafEnv": "https://laf.dev" // laf环境。 https://laf.run (杭州阿里云) ,或者私有化的laf环境。如果使用 Laf openapi 功能,需要最新版的 laf 。
|
||||
},
|
||||
"systemEnv": {
|
||||
"vectorMaxProcess": 15,
|
||||
@@ -156,7 +156,7 @@ llm模型全部合并
|
||||
|
||||
请使用 4.6.6-alpha 以上版本,配置文件中的 `reRankModels` 为重排模型,虽然是数组,不过目前仅有第1个生效。
|
||||
|
||||
1. [部署 ReRank 模型](/docs/development/custom-models/reranker/)
|
||||
1. [部署 ReRank 模型](/docs/development/custom-models/bge-rerank/)
|
||||
1. 找到 FastGPT 的配置文件中的 `reRankModels`, 4.6.6 以前是 `ReRankModels`。
|
||||
2. 修改对应的值:(记得去掉注释)
|
||||
|
||||
|
||||
121
docSite/content/docs/development/custom-models/bge-rerank.md
Normal file
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: '接入 bge-rerank 重排模型'
|
||||
description: '接入 bge-rerank 重排模型'
|
||||
icon: 'sort'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 910
|
||||
---
|
||||
|
||||
## 不同模型推荐配置
|
||||
|
||||
推荐配置如下:
|
||||
|
||||
{{< table "table-hover table-striped-columns" >}}
|
||||
| 模型名 | 内存 | 显存 | 硬盘空间 | 启动命令 |
|
||||
|------|---------|---------|----------|--------------------------|
|
||||
| bge-rerank-base | >=4GB | >=4GB | >=8GB | python app.py |
|
||||
| bge-rerank-large | >=8GB | >=8GB | >=8GB | python app.py |
|
||||
| bge-rerank-v2-m3 | >=8GB | >=8GB | >=8GB | python app.py |
|
||||
{{< /table >}}
|
||||
|
||||
## 源码部署
|
||||
|
||||
### 1. 安装环境
|
||||
|
||||
- Python 3.9, 3.10
|
||||
- CUDA 11.7
|
||||
- 科学上网环境
|
||||
|
||||
### 2. 下载代码
|
||||
|
||||
3 个模型代码分别为:
|
||||
|
||||
1. [https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-base](https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-base)
|
||||
2. [https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-large](https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-reranker-large)
|
||||
3. [https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-rerank-v2-m3](https://github.com/labring/FastGPT/tree/main/python/bge-rerank/bge-rerank-v2-m3)
|
||||
|
||||
### 3. 安装依赖
|
||||
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 4. 下载模型
|
||||
|
||||
3个模型的 huggingface 仓库地址如下:
|
||||
|
||||
1. [https://huggingface.co/BAAI/bge-reranker-base](https://huggingface.co/BAAI/bge-reranker-base)
|
||||
2. [https://huggingface.co/BAAI/bge-reranker-large](https://huggingface.co/BAAI/bge-reranker-large)
|
||||
3. [https://huggingface.co/BAAI/bge-rerank-v2-m3](https://huggingface.co/BAAI/bge-rerank-v2-m3)
|
||||
|
||||
在对应代码目录下 clone 模型。目录结构:
|
||||
|
||||
```
|
||||
bge-reranker-base/
|
||||
app.py
|
||||
Dockerfile
|
||||
requirements.txt
|
||||
```
|
||||
|
||||
### 5. 运行代码
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
启动成功后应该会显示如下地址:
|
||||
|
||||

|
||||
|
||||
> 这里的 `http://0.0.0.0:6006` 就是连接地址。
|
||||
|
||||
## docker 部署
|
||||
|
||||
**镜像名分别为:**
|
||||
|
||||
1. registry.cn-hangzhou.aliyuncs.com/fastgpt/bge-rerank-base:v0.1 (4 GB+)
|
||||
2. registry.cn-hangzhou.aliyuncs.com/fastgpt/bge-rerank-large:v0.1 (5 GB+)
|
||||
3. registry.cn-hangzhou.aliyuncs.com/fastgpt/bge-rerank-v2-m3:v0.1 (5 GB+)
|
||||
|
||||
**端口**
|
||||
|
||||
6006
|
||||
|
||||
**环境变量**
|
||||
|
||||
```
|
||||
ACCESS_TOKEN=访问安全凭证,请求时,Authorization: Bearer ${ACCESS_TOKEN}
|
||||
```
|
||||
|
||||
**运行命令示例**
|
||||
|
||||
```sh
|
||||
# auth token 为mytoken
|
||||
docker run -d --name reranker -p 6006:6006 -e ACCESS_TOKEN=mytoken --gpus all registry.cn-hangzhou.aliyuncs.com/fastgpt/bge-rerank-base:v0.1
|
||||
```
|
||||
|
||||
**docker-compose.yml示例**
|
||||
```
|
||||
version: "3"
|
||||
services:
|
||||
reranker:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/bge-rerank-base:v0.1
|
||||
container_name: reranker
|
||||
# GPU运行环境,如果宿主机未安装,将deploy配置隐藏即可
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
ports:
|
||||
- 6006:6006
|
||||
environment:
|
||||
- ACCESS_TOKEN=mytoken
|
||||
|
||||
```
|
||||
## 接入 FastGPT
|
||||
|
||||
参考 [ReRank模型接入](/docs/development/configuration/#rerank-接入),host 变量为部署的域名。
|
||||
@@ -1,90 +0,0 @@
|
||||
---
|
||||
title: '接入 ReRank 重排模型'
|
||||
description: '接入 ReRank 重排模型'
|
||||
icon: 'sort'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 910
|
||||
---
|
||||
|
||||
## 推荐配置
|
||||
|
||||
推荐配置如下:
|
||||
|
||||
{{< table "table-hover table-striped-columns" >}}
|
||||
| 类型 | 内存 | 显存 | 硬盘空间 | 启动命令 |
|
||||
|------|---------|---------|----------|--------------------------|
|
||||
| base | >=4GB | >=3GB | >=8GB | python app.py |
|
||||
{{< /table >}}
|
||||
|
||||
## 部署
|
||||
|
||||
### 环境要求
|
||||
|
||||
- Python 3.10.11
|
||||
- CUDA 11.7
|
||||
- 科学上网环境
|
||||
|
||||
### 源码部署
|
||||
|
||||
1. 根据上面的环境配置配置好环境,具体教程自行 GPT;
|
||||
2. 下载 [python 文件](https://github.com/labring/FastGPT/tree/main/python/reranker/bge-reranker-base)
|
||||
3. 在命令行输入命令 `pip install -r requirements.txt`;
|
||||
4. 按照[https://huggingface.co/BAAI/bge-reranker-base](https://huggingface.co/BAAI/bge-reranker-base)下载模型仓库到app.py同级目录
|
||||
5. 添加环境变量 `export ACCESS_TOKEN=XXXXXX` 配置 token,这里的 token 只是加一层验证,防止接口被人盗用,默认值为 `ACCESS_TOKEN` ;
|
||||
6. 执行命令 `python app.py`。
|
||||
|
||||
然后等待模型下载,直到模型加载完毕为止。如果出现报错先问 GPT。
|
||||
|
||||
启动成功后应该会显示如下地址:
|
||||
|
||||

|
||||
|
||||
> 这里的 `http://0.0.0.0:6006` 就是连接地址。
|
||||
|
||||
### docker 部署
|
||||
|
||||
+ 镜像名: `registry.cn-hangzhou.aliyuncs.com/fastgpt/rerank:v0.2`
|
||||
+ 端口号: 6006
|
||||
+ 大小:约8GB
|
||||
|
||||
**设置安全凭证(即oneapi中的渠道密钥)**
|
||||
```
|
||||
ACCESS_TOKEN=mytoken
|
||||
```
|
||||
|
||||
**运行命令示例**
|
||||
- 无需GPU环境,使用CPU运行
|
||||
```sh
|
||||
docker run -d --name reranker -p 6006:6006 -e ACCESS_TOKEN=mytoken registry.cn-hangzhou.aliyuncs.com/fastgpt/rerank:v0.2
|
||||
```
|
||||
|
||||
- 需要CUDA 11.7环境
|
||||
```sh
|
||||
docker run -d --gpus all --name reranker -p 6006:6006 -e ACCESS_TOKEN=mytoken registry.cn-hangzhou.aliyuncs.com/fastgpt/rerank:v0.2
|
||||
```
|
||||
|
||||
**docker-compose.yml示例**
|
||||
```
|
||||
version: "3"
|
||||
services:
|
||||
reranker:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/rerank:v0.2
|
||||
container_name: reranker
|
||||
# GPU运行环境,如果宿主机未安装,将deploy配置隐藏即可
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
ports:
|
||||
- 6006:6006
|
||||
environment:
|
||||
- ACCESS_TOKEN=mytoken
|
||||
|
||||
```
|
||||
## 接入 FastGPT
|
||||
|
||||
参考 [ReRank模型接入](/docs/development/configuration/#rerank-接入),host 变量为部署的域名。
|
||||
@@ -32,7 +32,7 @@ FastGPT 使用了 one-api 项目来管理模型池,其可以兼容 OpenAI 、A
|
||||
|
||||
可选择 [Sealos 快速部署 OneAPI](/docs/development/one-api),更多部署方法可参考该项目的 [README](https://github.com/songquanpeng/one-api),也可以直接通过以下按钮一键部署:
|
||||
|
||||
<a href="https://template.cloud.sealos.io/deploy?templateName=one-api" rel="external" target="_blank"><img src="https://cdn.jsdelivr.us/gh/labring-actions/templates@main/Deploy-on-Sealos.svg" alt="Deploy on Sealos"/></a>
|
||||
<a href="https://template.cloud.sealos.io/deploy?templateName=one-api" rel="external" target="_blank"><img src="https://cdn.jsdelivr.net/gh/labring-actions/templates@main/Deploy-on-Sealos.svg" alt="Deploy on Sealos"/></a>
|
||||
|
||||
## 一、安装 Docker 和 docker-compose
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ MySQL 版本支持多实例,高并发。
|
||||
|
||||
直接点击以下按钮即可一键部署 👇
|
||||
|
||||
<a href="https://template.cloud.sealos.io/deploy?templateName=one-api" rel="external" target="_blank"><img src="https://cdn.jsdelivr.us/gh/labring-actions/templates@main/Deploy-on-Sealos.svg" alt="Deploy on Sealos"/></a>
|
||||
<a href="https://template.cloud.sealos.io/deploy?templateName=one-api" rel="external" target="_blank"><img src="https://cdn.jsdelivr.net/gh/labring-actions/templates@main/Deploy-on-Sealos.svg" alt="Deploy on Sealos"/></a>
|
||||
|
||||
部署完后会跳转「应用管理」,数据库在另一个应用「数据库」中。需要等待 1~3 分钟数据库运行后才能访问成功。
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ FastGPT 使用了 one-api 项目来管理模型池,其可以兼容 OpenAI 、A
|
||||
## 一键部署
|
||||
Sealos 的服务器在国外,不需要额外处理网络问题,无需服务器、无需魔法、无需域名,支持高并发 & 动态伸缩。点击以下按钮即可一键部署 👇
|
||||
|
||||
<a href="https://template.cloud.sealos.io/deploy?templateName=fastgpt" rel="external" target="_blank"><img src="https://cdn.jsdelivr.us/gh/labring-actions/templates@main/Deploy-on-Sealos.svg" alt="Deploy on Sealos"/></a>
|
||||
<a href="https://template.cloud.sealos.io/deploy?templateName=fastgpt" rel="external" target="_blank"><img src="https://cdn.jsdelivr.net/gh/labring-actions/templates@main/Deploy-on-Sealos.svg" alt="Deploy on Sealos"/></a>
|
||||
|
||||
由于需要部署数据库,部署完后需要等待 2~4 分钟才能正常访问。默认用了最低配置,首次访问时会有些慢。
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.7'
|
||||
title: 'V4.7(需要初始化)'
|
||||
description: 'FastGPT V4.7更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -26,7 +26,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv47' \
|
||||
|
||||
## 3. 升级 ReRank 模型
|
||||
|
||||
4.7对ReRank模型进行了格式变动,兼容 cohere 的格式,可以直接使用 cohere 提供的 API。如果是本地的 ReRank 模型,需要修改镜像为:`registry.cn-hangzhou.aliyuncs.com/fastgpt/rerank:v0.2` 。
|
||||
4.7对ReRank模型进行了格式变动,兼容 cohere 的格式,可以直接使用 cohere 提供的 API。如果是本地的 ReRank 模型,需要修改镜像为:`registry.cn-hangzhou.aliyuncs.com/fastgpt/bge-rerank-base:v0.1` 。
|
||||
|
||||
cohere的重排模型对中文不是很好,感觉不如 bge 的好用,接入教程如下:
|
||||
|
||||
|
||||
@@ -19,13 +19,21 @@ curl --location --request POST 'https://{{host}}/api/admin/clearInvalidData' \
|
||||
|
||||
该请求会执行脏数据清理(清理无效的文件、清理无效的图片、清理无效的知识库集合、清理无效的向量)
|
||||
|
||||
|
||||
## 修改配置文件
|
||||
|
||||
增加了Laf环境配置:[点击查看最新的配置文件](/docs/development/configuration/)
|
||||
|
||||
|
||||
## V4.7.1 更新说明
|
||||
|
||||
1. 新增 - Pptx 和 xlsx 文件读取。但所有文件读取都放服务端,会消耗更多的服务器资源,以及无法在上传时预览更多内容。
|
||||
2. 新增 - 集成 Laf 云函数,可以读取 Laf 账号中的云函数作为 HTTP 模块。
|
||||
3. 新增 - 定时器,清理垃圾数据。(采用小范围清理,会清理最近n个小时的,所以请保证服务持续运行,长时间不允许,可以继续执行 clearInvalidData 的接口进行全量清理。)
|
||||
4. 商业版新增 - 后台配置系统通知。
|
||||
5. 修改 - csv导入模板,取消 header 校验,自动获取前两列。
|
||||
6. 修复 - 工具调用模块连线数据类型校验错误。
|
||||
7. 修复 - 自定义索引输入时,解构数据失败。
|
||||
8. 修复 - rerank 模型数据格式。
|
||||
1. 新增 - 语音输入完整配置。支持选择是否打开语音输入(包括分享页面),支持语音输入后自动发送,支持语音输入后自动语音播放(流式)。
|
||||
2. 新增 - pptx 和 xlsx 文件读取。但所有文件读取都放服务端,会消耗更多的服务器资源,以及无法在上传时预览更多内容。
|
||||
3. 新增 - 集成 Laf 云函数,可以读取 Laf 账号中的云函数作为 HTTP 模块。
|
||||
4. 新增 - 定时器,清理垃圾数据。(采用小范围清理,会清理最近n个小时的,所以请保证服务持续运行,长时间不允许,可以继续执行 clearInvalidData 的接口进行全量清理。)
|
||||
5. 商业版新增 - 后台配置系统通知。
|
||||
6. 修改 - csv导入模板,取消 header 校验,自动获取前两列。
|
||||
7. 修复 - 工具调用模块连线数据类型校验错误。
|
||||
8. 修复 - 自定义索引输入时,解构数据失败。
|
||||
9. 修复 - rerank 模型数据格式。
|
||||
10. 修复 - 问题补全历史记录BUG
|
||||
@@ -88,7 +88,7 @@ Response:
|
||||
[
|
||||
{
|
||||
"moduleId": "userGuide",
|
||||
"name": "core.module.template.User guide",
|
||||
"name": "core.module.template.App system setting",
|
||||
"flowType": "userGuide",
|
||||
"position": {
|
||||
"x": 454.98510354678695,
|
||||
|
||||
@@ -27,7 +27,7 @@ weight: 404
|
||||
[
|
||||
{
|
||||
"moduleId": "userGuide",
|
||||
"name": "core.module.template.User guide",
|
||||
"name": "core.module.template.App system setting",
|
||||
"intro": "core.app.tip.userGuideTip",
|
||||
"avatar": "/imgs/module/userGuide.png",
|
||||
"flowType": "userGuide",
|
||||
|
||||
@@ -84,7 +84,7 @@ export default async function (ctx: FunctionContext) {
|
||||
[
|
||||
{
|
||||
"moduleId": "userGuide",
|
||||
"name": "core.module.template.User guide",
|
||||
"name": "core.module.template.App system setting",
|
||||
"intro": "core.app.tip.userGuideTip",
|
||||
"avatar": "/imgs/module/userGuide.png",
|
||||
"flowType": "userGuide",
|
||||
|
||||
98
docSite/content/docs/workflow/modules/laf.md
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
title: "Laf 函数调用"
|
||||
description: "FastGPT Laf 函数调用模块介绍"
|
||||
icon: "code"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 355
|
||||
---
|
||||
|
||||
|
||||

|
||||
|
||||
## 介绍
|
||||
|
||||
`Laf 函数调用`模块可以调用 Laf 账号下的云函数,其工作原理与 HTTP 模块相同,有以下特殊特征:
|
||||
|
||||
- 只能使用 POST 请求
|
||||
- 请求自带系统参数 systemParams,无需通过变量传递。
|
||||
|
||||
## 绑定 Laf 账号
|
||||
|
||||
要调用 Laf 云函数,首先需要绑定 Laf 账号和应用,并且在应用中创建云函数。
|
||||
|
||||
Laf 提供了 PAT(访问凭证) 来实现 Laf 平台外的快捷登录,可以访问 [Laf 文档](https://doc.Laf.run/zh/cli/#%E7%99%BB%E5%BD%95)查看详细如何获取 PAT。
|
||||
|
||||
在获取到 PAT 后,我们可以进入 FastGPT 的`账号页`或是在高级编排中的 `Laf模块` 对 Laf 账号进行绑定。Laf 账号是团队共享的,仅团队管理员可配置。
|
||||
|
||||
填入 PAT 验证后,选择需要绑定的应用(应用需要是 Running 状态),即可调用该应用下的云函数。
|
||||
|
||||

|
||||
|
||||
## 编写云函数
|
||||
|
||||
Laf 云函数拥有根据 interface 自动生成 OpenAPI 的能力,可以参照下面的代码编写云函数,以便自动生成 OpenAPI 文档。
|
||||
|
||||
`Laf模块`可以根据 OpenAPI 文档,自动识别出入参,无需手动添加数据类型。如果不会写 TS,可忽略,手动在 FastGPT 中添加参数即可。
|
||||
|
||||
```ts
|
||||
import cloud from '@lafjs/cloud'
|
||||
|
||||
interface IRequestBody { // 自定义入参,FastGPT 传入的均为POST请求。
|
||||
data1: string // 必填参数
|
||||
data2?: string // 可选参数
|
||||
}
|
||||
|
||||
interface RequestProps extends IRequestBody { // 完整入参,这个无需改动。
|
||||
systemParams: { // 这是FastGPT默认会传递过来的参数
|
||||
appId: string,
|
||||
variables: string,
|
||||
histories: string,
|
||||
cTime: string,
|
||||
chatId: string,
|
||||
responseChatItemId: string
|
||||
}
|
||||
}
|
||||
|
||||
interface IResponse { // 响应内容
|
||||
message: string // 必返回的参数
|
||||
msg?: string; // 可选的返回参数
|
||||
}
|
||||
|
||||
export default async function (ctx: FunctionContext): Promise<IResponse> {
|
||||
const {
|
||||
data1,
|
||||
data2,
|
||||
systemParams
|
||||
}: RequestProps = ctx.body;
|
||||
|
||||
console.log({
|
||||
data1,
|
||||
data2,
|
||||
systemParams
|
||||
});
|
||||
|
||||
return {
|
||||
message: 'ok',
|
||||
msg: 'msg'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
当然,你也可以在 Laf 平台上选择 fastgpt_template,快速生成该函数模板。
|
||||
|
||||
具体操作可以是,进入 Laf 的函数页面,新建函数(注意 fastgpt 只会调用 post 请求的函数),然后复制上面的代码或者点击更多模板搜索“fastgpt”,使用下面的模板
|
||||
|
||||

|
||||
|
||||
## FastGPT 中使用
|
||||
|
||||
在选择函数后,可以通过点击“同步参数”,自动同步云函数的参数到 FastGPT 中。当然也可以手动添加,手动修改后的参数不会被“同步参数”修改。
|
||||
|
||||

|
||||
|
||||
## 使用注意事项
|
||||
|
||||
### 调用报错
|
||||
|
||||
先在 laf 中调试函数,看是否正常调用。可以通过 console.log,打印入参,将入参放在 Laf 测试页面的 Body 中进行测试。
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<!-- change -->
|
||||
<script
|
||||
src="https://cdn.jsdelivr.us/npm/medium-zoom/dist/medium-zoom.min.js"
|
||||
src="https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.min.js"
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<head>
|
||||
<script defer type="text/javascript" src="{{ "js/jsdelivr-auto-fallback.js" | absURL }}"></script>
|
||||
<!-- <script defer type="text/javascript" src="{{ "js/jsdelivr-auto-fallback.js" | absURL }}"></script> -->
|
||||
<meta charset="utf-8" />
|
||||
<title>
|
||||
{{- $url := replace .Permalink ( printf "%s" .Site.BaseURL) "" }}
|
||||
@@ -106,6 +106,6 @@
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<!-- change -->
|
||||
<link rel="preload" href="https://cdn.jsdelivr.us/npm/lxgw-wenkai-screen-webfont@1.1.0/style.css" as="style" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.us/npm/lxgw-wenkai-screen-webfont@1.1.0/style.css" />
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/lxgw-wenkai-screen-webfont@1.1.0/style.css" as="style" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lxgw-wenkai-screen-webfont@1.1.0/style.css" />
|
||||
</head>
|
||||
@@ -4,7 +4,7 @@
|
||||
let failed;
|
||||
let isRunning;
|
||||
const DEST_LIST = [
|
||||
'cdn.jsdelivr.us',
|
||||
'cdn.jsdelivr.net',
|
||||
'jsd.cdn.zzko.cn',
|
||||
'jsd.onmicrosoft.cn'
|
||||
];
|
||||
|
||||
@@ -51,7 +51,7 @@ services:
|
||||
})
|
||||
}' > /data/initReplicaSet.js
|
||||
# 启动MongoDB服务
|
||||
exec docker-entrypoint.sh "$@" &
|
||||
exec docker-entrypoint.sh "$$@" &
|
||||
|
||||
# 等待MongoDB服务启动
|
||||
until mongo -u myusername -p mypassword --authenticationDatabase admin --eval "print('waited for connection')" > /dev/null 2>&1; do
|
||||
@@ -63,7 +63,7 @@ services:
|
||||
mongo -u myusername -p mypassword --authenticationDatabase admin /data/initReplicaSet.js
|
||||
|
||||
# 等待docker-entrypoint.sh脚本执行的MongoDB服务进程
|
||||
wait $!
|
||||
wait $$!
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.7 # git
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getErrText } from '../error/utils';
|
||||
import { countPromptTokens } from './tiktoken';
|
||||
import { replaceRegChars } from './tools';
|
||||
|
||||
/**
|
||||
* text split into chunks
|
||||
@@ -31,7 +31,7 @@ export const splitText2Chunks = (props: {
|
||||
// The larger maxLen is, the next sentence is less likely to trigger splitting
|
||||
const stepReges: { reg: RegExp; maxLen: number }[] = [
|
||||
...customReg.map((text) => ({
|
||||
reg: new RegExp(`(${text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'g'),
|
||||
reg: new RegExp(`(${replaceRegChars(text)})`, 'g'),
|
||||
maxLen: chunkLen * 1.4
|
||||
})),
|
||||
{ reg: /^(#\s[^\n]+)\n/gm, maxLen: chunkLen * 1.2 },
|
||||
|
||||
@@ -51,3 +51,5 @@ export const replaceSensitiveText = (text: string) => {
|
||||
export const getNanoid = (size = 12) => {
|
||||
return customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', size)();
|
||||
};
|
||||
|
||||
export const replaceRegChars = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
1
packages/global/common/system/api.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export type AuthGoogleTokenProps = { googleToken: string; remoteip?: string | null };
|
||||
2
packages/global/core/app/api.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import type { LLMModelItemType } from '../ai/model.d';
|
||||
import { AppTypeEnum } from './constants';
|
||||
import { AppSchema, AppSimpleEditFormType } from './type';
|
||||
import { AppSchema } from './type';
|
||||
|
||||
export type CreateAppParams = {
|
||||
name?: string;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AppWhisperConfigType } from './type';
|
||||
|
||||
export enum AppTypeEnum {
|
||||
simple = 'simple',
|
||||
advanced = 'advanced'
|
||||
@@ -10,3 +12,9 @@ export const AppTypeMap = {
|
||||
label: 'advanced'
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultWhisperConfig: AppWhisperConfigType = {
|
||||
open: false,
|
||||
autoSend: false,
|
||||
autoTTSResponse: false
|
||||
};
|
||||
|
||||
34
packages/global/core/app/type.d.ts
vendored
@@ -1,9 +1,5 @@
|
||||
import type {
|
||||
AppTTSConfigType,
|
||||
FlowNodeTemplateType,
|
||||
ModuleItemType,
|
||||
VariableItemType
|
||||
} from '../module/type.d';
|
||||
import type { FlowNodeTemplateType, ModuleItemType } from '../module/type.d';
|
||||
|
||||
import { AppTypeEnum } from './constants';
|
||||
import { PermissionTypeEnum } from '../../support/permission/constant';
|
||||
import type { DatasetModuleProps } from '../module/node/type.d';
|
||||
@@ -82,5 +78,31 @@ export type AppSimpleEditFormType = {
|
||||
voice?: string | undefined;
|
||||
speed?: number | undefined;
|
||||
};
|
||||
whisper: AppWhisperConfigType;
|
||||
};
|
||||
};
|
||||
|
||||
/* app function config */
|
||||
// variable
|
||||
export type VariableItemType = {
|
||||
id: string;
|
||||
key: string;
|
||||
label: string;
|
||||
type: `${VariableInputEnum}`;
|
||||
required: boolean;
|
||||
maxLen: number;
|
||||
enums: { value: string }[];
|
||||
};
|
||||
// tts
|
||||
export type AppTTSConfigType = {
|
||||
type: 'none' | 'web' | 'model';
|
||||
model?: string;
|
||||
voice?: string;
|
||||
speed?: number;
|
||||
};
|
||||
// whisper
|
||||
export type AppWhisperConfigType = {
|
||||
open: boolean;
|
||||
autoSend: boolean;
|
||||
autoTTSResponse: boolean;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { FlowNodeInputItemType } from '../module/node/type.d';
|
||||
import { getGuideModule, splitGuideModule } from '../module/utils';
|
||||
import { ModuleItemType } from '../module/type.d';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||
import { defaultWhisperConfig } from './constants';
|
||||
|
||||
export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
||||
return {
|
||||
@@ -36,7 +37,8 @@ export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
||||
questionGuide: false,
|
||||
tts: {
|
||||
type: 'web'
|
||||
}
|
||||
},
|
||||
whisper: defaultWhisperConfig
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -107,14 +109,15 @@ export const appModules2Form = ({ modules }: { modules: ModuleItemType[] }) => {
|
||||
ModuleInputKeyEnum.datasetSearchExtensionBg
|
||||
);
|
||||
} else if (module.flowType === FlowNodeTypeEnum.userGuide) {
|
||||
const { welcomeText, variableModules, questionGuide, ttsConfig } = splitGuideModule(
|
||||
getGuideModule(modules)
|
||||
);
|
||||
const { welcomeText, variableModules, questionGuide, ttsConfig, whisperConfig } =
|
||||
splitGuideModule(getGuideModule(modules));
|
||||
|
||||
defaultAppForm.userGuide = {
|
||||
welcomeText: welcomeText,
|
||||
variables: variableModules,
|
||||
questionGuide: questionGuide,
|
||||
tts: ttsConfig
|
||||
tts: ttsConfig,
|
||||
whisper: whisperConfig
|
||||
};
|
||||
} else if (module.flowType === FlowNodeTypeEnum.pluginModule) {
|
||||
defaultAppForm.selectedTools.push({
|
||||
|
||||
2
packages/global/core/chat/type.d.ts
vendored
@@ -109,7 +109,7 @@ export type ChatItemType = (UserChatItemType | SystemChatItemType | AIChatItemTy
|
||||
};
|
||||
|
||||
export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatItemType) & {
|
||||
dataId?: string;
|
||||
dataId: string;
|
||||
status: `${ChatStatusEnum}`;
|
||||
moduleName?: string;
|
||||
ttsBuffer?: Uint8Array;
|
||||
|
||||
@@ -37,6 +37,7 @@ export enum ModuleInputKeyEnum {
|
||||
userChatInput = 'userChatInput',
|
||||
questionGuide = 'questionGuide',
|
||||
tts = 'tts',
|
||||
whisper = 'whisper',
|
||||
answerText = 'text',
|
||||
agents = 'agents', // cq agent key
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowNodeTemplateType } from '../../type.d';
|
||||
import { userGuideTip } from '../tip';
|
||||
import {
|
||||
ModuleIOValueTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
@@ -12,8 +11,8 @@ export const UserGuideModule: FlowNodeTemplateType = {
|
||||
templateType: FlowNodeTemplateTypeEnum.userGuide,
|
||||
flowType: FlowNodeTypeEnum.userGuide,
|
||||
avatar: '/imgs/module/userGuide.png',
|
||||
name: '全局配置',
|
||||
intro: userGuideTip,
|
||||
name: '系统配置',
|
||||
intro: '可以配置应用的系统参数。',
|
||||
inputs: [
|
||||
{
|
||||
key: ModuleInputKeyEnum.welcomeText,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export const chatNodeSystemPromptTip = 'core.app.tip.chatNodeSystemPromptTip';
|
||||
export const userGuideTip = 'core.app.tip.userGuideTip';
|
||||
export const welcomeTextTip = 'core.app.tip.welcomeTextTip';
|
||||
export const variableTip = 'core.app.tip.variableTip';
|
||||
|
||||
18
packages/global/core/module/type.d.ts
vendored
@@ -63,24 +63,6 @@ export type ModuleItemType = {
|
||||
};
|
||||
|
||||
/* --------------- function type -------------------- */
|
||||
// variable
|
||||
export type VariableItemType = {
|
||||
id: string;
|
||||
key: string;
|
||||
label: string;
|
||||
type: `${VariableInputEnum}`;
|
||||
required: boolean;
|
||||
maxLen: number;
|
||||
enums: { value: string }[];
|
||||
};
|
||||
// tts
|
||||
export type AppTTSConfigType = {
|
||||
type: 'none' | 'web' | 'model';
|
||||
model?: string;
|
||||
voice?: string;
|
||||
speed?: number;
|
||||
};
|
||||
|
||||
export type SelectAppItemType = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -6,10 +6,12 @@ import {
|
||||
variableMap
|
||||
} from './constants';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType } from './node/type';
|
||||
import { AppTTSConfigType, ModuleItemType, VariableItemType } from './type';
|
||||
import { ModuleItemType } from './type';
|
||||
import type { VariableItemType, AppTTSConfigType, AppWhisperConfigType } from '../app/type';
|
||||
import { Input_Template_Switch } from './template/input';
|
||||
import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type';
|
||||
import { Output_Template_Finish } from './template/output';
|
||||
import { defaultWhisperConfig } from '../app/constants';
|
||||
|
||||
/* module */
|
||||
export const getGuideModule = (modules: ModuleItemType[]) =>
|
||||
@@ -30,11 +32,16 @@ export const splitGuideModule = (guideModules?: ModuleItemType) => {
|
||||
(item) => item.key === ModuleInputKeyEnum.tts
|
||||
)?.value || { type: 'web' };
|
||||
|
||||
const whisperConfig: AppWhisperConfigType =
|
||||
guideModules?.inputs?.find((item) => item.key === ModuleInputKeyEnum.whisper)?.value ||
|
||||
defaultWhisperConfig;
|
||||
|
||||
return {
|
||||
welcomeText,
|
||||
variableModules,
|
||||
questionGuide,
|
||||
ttsConfig
|
||||
ttsConfig,
|
||||
whisperConfig
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ export type PathDataType = {
|
||||
path: string;
|
||||
params: any[];
|
||||
request: any;
|
||||
response: any;
|
||||
};
|
||||
|
||||
export type OpenApiJsonSchema = {
|
||||
|
||||
@@ -43,7 +43,8 @@ export const str2OpenApiSchema = async (yamlStr = ''): Promise<OpenApiJsonSchema
|
||||
name: methodInfo.operationId || path,
|
||||
description: methodInfo.description || methodInfo.summary,
|
||||
params: methodInfo.parameters,
|
||||
request: methodInfo?.requestBody
|
||||
request: methodInfo?.requestBody,
|
||||
response: methodInfo.responses
|
||||
};
|
||||
return result;
|
||||
});
|
||||
|
||||
1
packages/global/support/user/team/type.d.ts
vendored
@@ -81,4 +81,5 @@ export type TeamTagItemType = {
|
||||
export type LafAccountType = {
|
||||
token: string;
|
||||
appid: string;
|
||||
pat: string;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ export type BillSchemaType = {
|
||||
month?: number;
|
||||
datasetSize?: number;
|
||||
extraPoints?: number;
|
||||
invoice: boolean;
|
||||
};
|
||||
username: string;
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ try {
|
||||
ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 });
|
||||
ImageSchema.index({ type: 1 });
|
||||
ImageSchema.index({ createTime: 1 });
|
||||
// delete related img
|
||||
ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
export type DeleteDatasetVectorProps = {
|
||||
export type DeleteDatasetVectorProps = (
|
||||
| { id: string }
|
||||
| { datasetIds: string[]; collectionIds?: string[] }
|
||||
| { idList: string[] }
|
||||
) & {
|
||||
teamId: string;
|
||||
|
||||
id?: string;
|
||||
datasetIds?: string[];
|
||||
collectionIds?: string[];
|
||||
idList?: string[];
|
||||
};
|
||||
|
||||
export type InsertVectorProps = {
|
||||
|
||||
@@ -26,13 +26,7 @@ export async function initPg() {
|
||||
`CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 64);`
|
||||
);
|
||||
await PgClient.query(
|
||||
`CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_index ON ${PgDatasetTableName} USING btree(team_id, dataset_id);`
|
||||
);
|
||||
await PgClient.query(
|
||||
` CREATE INDEX CONCURRENTLY IF NOT EXISTS team_collection_index ON ${PgDatasetTableName} USING btree(team_id, collection_id);`
|
||||
);
|
||||
await PgClient.query(
|
||||
`CREATE INDEX CONCURRENTLY IF NOT EXISTS team_id_index ON ${PgDatasetTableName} USING btree(team_id, id);`
|
||||
`CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_collection_index ON ${PgDatasetTableName} USING btree(team_id, dataset_id, collection_id);`
|
||||
);
|
||||
await PgClient.query(
|
||||
`CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${PgDatasetTableName} USING btree(createtime);`
|
||||
@@ -83,31 +77,33 @@ export const deleteDatasetDataVector = async (
|
||||
retry?: number;
|
||||
}
|
||||
): Promise<any> => {
|
||||
const { teamId, id, datasetIds, collectionIds, idList, retry = 2 } = props;
|
||||
const { teamId, retry = 2 } = props;
|
||||
|
||||
const teamIdWhere = `team_id='${String(teamId)}' AND`;
|
||||
|
||||
const where = await (() => {
|
||||
if (id) return `${teamIdWhere} id=${id}`;
|
||||
if ('id' in props && props.id) return `${teamIdWhere} id=${props.id}`;
|
||||
|
||||
if (datasetIds) {
|
||||
return `${teamIdWhere} dataset_id IN (${datasetIds
|
||||
if ('datasetIds' in props && props.datasetIds) {
|
||||
const datasetIdWhere = `dataset_id IN (${props.datasetIds
|
||||
.map((id) => `'${String(id)}'`)
|
||||
.join(',')})`;
|
||||
|
||||
if ('collectionIds' in props && props.collectionIds) {
|
||||
return `${teamIdWhere} ${datasetIdWhere} AND collection_id IN (${props.collectionIds
|
||||
.map((id) => `'${String(id)}'`)
|
||||
.join(',')})`;
|
||||
}
|
||||
|
||||
return `${teamIdWhere} ${datasetIdWhere}`;
|
||||
}
|
||||
|
||||
if (collectionIds) {
|
||||
return `${teamIdWhere} collection_id IN (${collectionIds
|
||||
.map((id) => `'${String(id)}'`)
|
||||
.join(',')})`;
|
||||
}
|
||||
|
||||
if (idList) {
|
||||
return `${teamIdWhere} id IN (${idList.map((id) => `'${String(id)}'`).join(',')})`;
|
||||
if ('idList' in props && props.idList) {
|
||||
return `${teamIdWhere} id IN (${props.idList.map((id) => `'${String(id)}'`).join(',')})`;
|
||||
}
|
||||
return Promise.reject('deleteDatasetData: no where');
|
||||
})();
|
||||
|
||||
console.log(where, '===');
|
||||
try {
|
||||
await PgClient.delete(PgDatasetTableName, {
|
||||
where: [where]
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getAIApi } from '../config';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { countGptMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||
|
||||
/*
|
||||
query extension - 问题扩展
|
||||
@@ -117,7 +118,7 @@ A: ${chatBg}
|
||||
const historyFewShot = histories
|
||||
.map((item) => {
|
||||
const role = item.obj === 'Human' ? 'Q' : 'A';
|
||||
return `${role}: ${item.value}`;
|
||||
return `${role}: ${chatValue2RuntimePrompt(item.value).text}`;
|
||||
})
|
||||
.join('\n');
|
||||
const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
|
||||
|
||||
@@ -118,6 +118,37 @@ export function createDefaultCollection({
|
||||
);
|
||||
}
|
||||
|
||||
/* delete collection related images/files */
|
||||
export const delCollectionRelatedSource = async ({
|
||||
collections,
|
||||
session
|
||||
}: {
|
||||
collections: (CollectionWithDatasetType | DatasetCollectionSchemaType)[];
|
||||
session: ClientSession;
|
||||
}) => {
|
||||
if (collections.length === 0) return;
|
||||
|
||||
const teamId = collections[0].teamId;
|
||||
|
||||
if (!teamId) return Promise.reject('teamId is not exist');
|
||||
|
||||
const fileIdList = collections.map((item) => item?.fileId || '').filter(Boolean);
|
||||
const relatedImageIds = collections
|
||||
.map((item) => item?.metadata?.relatedImgId || '')
|
||||
.filter(Boolean);
|
||||
|
||||
// delete images
|
||||
await delImgByRelatedId({
|
||||
teamId,
|
||||
relateIds: relatedImageIds,
|
||||
session
|
||||
});
|
||||
// delete files
|
||||
await delFileByFileIdList({
|
||||
bucketName: BucketNameEnum.dataset,
|
||||
fileIdList
|
||||
});
|
||||
};
|
||||
/**
|
||||
* delete collection and it related data
|
||||
*/
|
||||
@@ -134,26 +165,32 @@ export async function delCollectionAndRelatedSources({
|
||||
|
||||
if (!teamId) return Promise.reject('teamId is not exist');
|
||||
|
||||
const datasetIds = Array.from(
|
||||
new Set(
|
||||
collections.map((item) => {
|
||||
if (typeof item.datasetId === 'string') {
|
||||
return String(item.datasetId);
|
||||
}
|
||||
return String(item.datasetId._id);
|
||||
})
|
||||
)
|
||||
);
|
||||
const collectionIds = collections.map((item) => String(item._id));
|
||||
const fileIdList = collections.map((item) => item?.fileId || '').filter(Boolean);
|
||||
const relatedImageIds = collections
|
||||
.map((item) => item?.metadata?.relatedImgId || '')
|
||||
.filter(Boolean);
|
||||
|
||||
await delCollectionRelatedSource({ collections, session });
|
||||
|
||||
// delete training data
|
||||
await MongoDatasetTraining.deleteMany({
|
||||
teamId,
|
||||
datasetIds: { $in: datasetIds },
|
||||
collectionId: { $in: collectionIds }
|
||||
});
|
||||
|
||||
// delete dataset.datas
|
||||
await MongoDatasetData.deleteMany({ teamId, collectionId: { $in: collectionIds } }, { session });
|
||||
// delete imgs
|
||||
await delImgByRelatedId({
|
||||
teamId,
|
||||
relateIds: relatedImageIds,
|
||||
session
|
||||
});
|
||||
await MongoDatasetData.deleteMany(
|
||||
{ teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
|
||||
{ session }
|
||||
);
|
||||
|
||||
// delete collections
|
||||
await MongoDatasetCollection.deleteMany(
|
||||
{
|
||||
@@ -163,9 +200,5 @@ export async function delCollectionAndRelatedSources({
|
||||
);
|
||||
|
||||
// no session delete: delete files, vector data
|
||||
await deleteDatasetDataVector({ teamId, collectionIds });
|
||||
await delFileByFileIdList({
|
||||
bucketName: BucketNameEnum.dataset,
|
||||
fileIdList
|
||||
});
|
||||
await deleteDatasetDataVector({ teamId, datasetIds, collectionIds });
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { CollectionWithDatasetType, DatasetSchemaType } from '@fastgpt/global/core/dataset/type';
|
||||
import { MongoDatasetCollection } from './collection/schema';
|
||||
import { MongoDataset } from './schema';
|
||||
import { delCollectionAndRelatedSources } from './collection/controller';
|
||||
import { delCollectionRelatedSource } from './collection/controller';
|
||||
import { ClientSession } from '../../common/mongo';
|
||||
import { MongoDatasetTraining } from './training/schema';
|
||||
import { MongoDatasetData } from './data/schema';
|
||||
import { deleteDatasetDataVector } from '../../common/vectorStore/controller';
|
||||
|
||||
/* ============= dataset ========== */
|
||||
/* find all datasetId by top datasetId */
|
||||
@@ -82,5 +85,26 @@ export async function delDatasetRelevantData({
|
||||
'_id teamId fileId metadata'
|
||||
).lean();
|
||||
|
||||
await delCollectionAndRelatedSources({ collections, session });
|
||||
// image and file
|
||||
await delCollectionRelatedSource({ collections, session });
|
||||
|
||||
// delete training data
|
||||
await MongoDatasetTraining.deleteMany({
|
||||
teamId,
|
||||
datasetId: { $in: datasetIds }
|
||||
});
|
||||
// delete dataset.datas
|
||||
await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }, { session });
|
||||
|
||||
// delete collections
|
||||
await MongoDatasetCollection.deleteMany(
|
||||
{
|
||||
teamId,
|
||||
datasetId: { $in: datasetIds }
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
|
||||
// no session delete: delete files, vector data
|
||||
await deleteDatasetDataVector({ teamId, datasetIds });
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
import { MongoDatasetData } from './schema';
|
||||
import { deleteDatasetDataVector } from '../../../common/vectorStore/controller';
|
||||
@@ -77,17 +77,18 @@ const DatasetDataSchema = new Schema({
|
||||
});
|
||||
|
||||
try {
|
||||
// list collection and count data; list data
|
||||
// list collection and count data; list data; delete collection(relate data)
|
||||
DatasetDataSchema.index(
|
||||
{ teamId: 1, datasetId: 1, collectionId: 1, chunkIndex: 1, updateTime: -1 },
|
||||
{ background: true }
|
||||
);
|
||||
// same data check
|
||||
DatasetDataSchema.index({ teamId: 1, collectionId: 1, q: 1, a: 1 }, { background: true });
|
||||
// full text index
|
||||
DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' }, { background: true });
|
||||
// Recall vectors after data matching
|
||||
DatasetDataSchema.index({ teamId: 1, datasetId: 1, 'indexes.dataId': 1 }, { background: true });
|
||||
DatasetDataSchema.index(
|
||||
{ teamId: 1, datasetId: 1, collectionId: 1, 'indexes.dataId': 1 },
|
||||
{ background: true }
|
||||
);
|
||||
DatasetDataSchema.index({ updateTime: 1 }, { background: true });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -93,6 +93,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
{
|
||||
teamId,
|
||||
datasetId: { $in: datasetIds },
|
||||
collectionId: { $in: results.map((item) => item.collectionId) },
|
||||
'indexes.dataId': { $in: results.map((item) => item.id?.trim()) }
|
||||
},
|
||||
'datasetId collectionId q a chunkIndex indexes'
|
||||
|
||||
@@ -6,7 +6,6 @@ import type {
|
||||
} from '@fastgpt/global/core/dataset/api.d';
|
||||
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { simpleText } from '@fastgpt/global/common/string/tools';
|
||||
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { ClientSession } from '../../../common/mongo';
|
||||
import { getLLMModel, getVectorModel } from '../../ai/model';
|
||||
import { addLog } from '../../../common/system/log';
|
||||
|
||||
@@ -92,8 +92,8 @@ const TrainingDataSchema = new Schema({
|
||||
});
|
||||
|
||||
try {
|
||||
// lock training data; delete training data
|
||||
TrainingDataSchema.index({ teamId: 1, collectionId: 1 });
|
||||
// lock training data(teamId); delete training data
|
||||
TrainingDataSchema.index({ teamId: 1, datasetId: 1 });
|
||||
// get training data and sort
|
||||
TrainingDataSchema.index({ mode: 1, lockTime: 1, weight: -1 });
|
||||
TrainingDataSchema.index({ expireAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 * 60 }); // 7 days
|
||||
|
||||
@@ -42,6 +42,9 @@ const TeamSchema = new Schema({
|
||||
},
|
||||
appid: {
|
||||
type: String
|
||||
},
|
||||
pat: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,3 +7,7 @@ export const getUserFingerprint = async () => {
|
||||
const result = await fp.get();
|
||||
console.log(result.visitorId);
|
||||
};
|
||||
|
||||
export const hasHttps = () => {
|
||||
return window.location.protocol === 'https:';
|
||||
};
|
||||
|
||||
@@ -70,6 +70,7 @@ export const iconPaths = {
|
||||
'core/app/simpleMode/template': () => import('./icons/core/app/simpleMode/template.svg'),
|
||||
'core/app/simpleMode/tts': () => import('./icons/core/app/simpleMode/tts.svg'),
|
||||
'core/app/simpleMode/variable': () => import('./icons/core/app/simpleMode/variable.svg'),
|
||||
'core/app/simpleMode/whisper': () => import('./icons/core/app/simpleMode/whisper.svg'),
|
||||
'core/app/toolCall': () => import('./icons/core/app/toolCall.svg'),
|
||||
'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'),
|
||||
'core/app/variable/external': () => import('./icons/core/app/variable/external.svg'),
|
||||
@@ -77,12 +78,14 @@ export const iconPaths = {
|
||||
'core/app/variable/select': () => import('./icons/core/app/variable/select.svg'),
|
||||
'core/app/variable/textarea': () => import('./icons/core/app/variable/textarea.svg'),
|
||||
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg'),
|
||||
'core/chat/cancelSpeak': () => import('./icons/core/chat/cancelSpeak.svg'),
|
||||
'core/chat/chatFill': () => import('./icons/core/chat/chatFill.svg'),
|
||||
'core/chat/chatLight': () => import('./icons/core/chat/chatLight.svg'),
|
||||
'core/chat/chatModelTag': () => import('./icons/core/chat/chatModelTag.svg'),
|
||||
'core/chat/feedback/badLight': () => import('./icons/core/chat/feedback/badLight.svg'),
|
||||
'core/chat/feedback/goodLight': () => import('./icons/core/chat/feedback/goodLight.svg'),
|
||||
'core/chat/fileSelect': () => import('./icons/core/chat/fileSelect.svg'),
|
||||
'core/chat/finishSpeak': () => import('./icons/core/chat/finishSpeak.svg'),
|
||||
'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'),
|
||||
'core/chat/quoteSign': () => import('./icons/core/chat/quoteSign.svg'),
|
||||
'core/chat/recordFill': () => import('./icons/core/chat/recordFill.svg'),
|
||||
@@ -91,7 +94,6 @@ export const iconPaths = {
|
||||
'core/chat/setTopLight': () => import('./icons/core/chat/setTopLight.svg'),
|
||||
'core/chat/speaking': () => import('./icons/core/chat/speaking.svg'),
|
||||
'core/chat/stopSpeech': () => import('./icons/core/chat/stopSpeech.svg'),
|
||||
'core/chat/stopSpeechFill': () => import('./icons/core/chat/stopSpeechFill.svg'),
|
||||
'core/dataset/commonDataset': () => import('./icons/core/dataset/commonDataset.svg'),
|
||||
'core/dataset/datasetFill': () => import('./icons/core/dataset/datasetFill.svg'),
|
||||
'core/dataset/datasetLight': () => import('./icons/core/dataset/datasetLight.svg'),
|
||||
@@ -105,6 +107,7 @@ export const iconPaths = {
|
||||
'core/dataset/tableCollection': () => import('./icons/core/dataset/tableCollection.svg'),
|
||||
'core/dataset/websiteDataset': () => import('./icons/core/dataset/websiteDataset.svg'),
|
||||
'core/modules/basicNode': () => import('./icons/core/modules/basicNode.svg'),
|
||||
'core/modules/fixview': () => import('./icons/core/modules/fixview.svg'),
|
||||
'core/modules/flowLight': () => import('./icons/core/modules/flowLight.svg'),
|
||||
'core/modules/previewLight': () => import('./icons/core/modules/previewLight.svg'),
|
||||
'core/modules/systemPlugin': () => import('./icons/core/modules/systemPlugin.svg'),
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg t="1712207338160" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6114"
|
||||
width="128" height="128">
|
||||
<path
|
||||
d="M370.569846 945.230769c-18.825846 0.787692-34.658462-14.808615-35.446154-34.776615 0.787692-19.968 16.620308-35.524923 35.446154-34.776616h106.180923v-106.338461c-138.358154-10.436923-252.888615-118.153846-279.394461-262.774154a36.745846 36.745846 0 0 1 6.852923-26.545231 32.649846 32.649846 0 0 1 22.803692-13.154461c18.628923-3.426462 36.470154 9.412923 40.369231 29.065846 24.260923 122.249846 127.133538 208.817231 244.775384 205.902769 117.563077 2.875077 220.396308-83.613538 244.736-205.824 3.938462-19.613538 21.740308-32.374154 40.329847-28.987077a32.649846 32.649846 0 0 1 22.803692 13.115077c5.592615 7.483077 8.073846 17.092923 6.892308 26.545231-26.505846 144.580923-141.075692 252.297846-279.433847 262.656v106.338461h106.220308c18.786462-0.787692 34.619077 14.808615 35.367385 34.776616a37.179077 37.179077 0 0 1-10.909539 25.206154 32.964923 32.964923 0 0 1-24.457846 9.570461h-283.175384z m-36.076308-483.958154v-208.738461C338.628923 152.891077 417.595077 75.342769 511.488 78.769231c93.892923-3.426462 172.898462 74.161231 176.955077 173.883077v208.738461c-4.056615 99.721846-83.062154 177.309538-176.955077 173.883077-93.971692 3.426462-172.977231-74.24-176.994462-174.001231z"
|
||||
fill="#F06E23" p-id="6115"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -2,7 +2,7 @@
|
||||
<g clip-path="url(#clip0_74_2)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M10 2.49999C5.85791 2.49999 2.50004 5.85786 2.50004 10C2.50004 14.1421 5.85791 17.5 10 17.5C14.1422 17.5 17.5 14.1421 17.5 10C17.5 5.85786 14.1422 2.49999 10 2.49999ZM0.833374 10C0.833374 4.93739 4.93743 0.833328 10 0.833328C15.0627 0.833328 19.1667 4.93739 19.1667 10C19.1667 15.0626 15.0627 19.1667 10 19.1667C4.93743 19.1667 0.833374 15.0626 0.833374 10ZM6.66671 7.5C6.66671 7.03976 7.0398 6.66666 7.50004 6.66666H12.5C12.9603 6.66666 13.3334 7.03976 13.3334 7.5V12.5C13.3334 12.9602 12.9603 13.3333 12.5 13.3333H7.50004C7.0398 13.3333 6.66671 12.9602 6.66671 12.5V7.5ZM8.33337 8.33333V11.6667H11.6667V8.33333H8.33337Z"
|
||||
fill="#3370FF" />
|
||||
fill="#fd853a" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_74_2">
|
||||
|
Before Width: | Height: | Size: 944 B After Width: | Height: | Size: 944 B |
@@ -0,0 +1,6 @@
|
||||
<svg t="1712578349044" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1183"
|
||||
width="128" height="128">
|
||||
<path
|
||||
d="M512 105.472c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z m254.976 296.96l-331.776 331.776-129.024-129.024-53.248 53.248 155.648 155.648 26.624 25.6 26.624-25.6 358.4-358.4-53.248-53.248z"
|
||||
p-id="1184" fill="#039855"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 637 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
|
||||
<path
|
||||
d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z">
|
||||
</path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 482 B |
@@ -205,7 +205,7 @@ const Button = defineStyleConfig({
|
||||
bg: 'primary.50'
|
||||
},
|
||||
_disabled: {
|
||||
bg: 'myGray.50'
|
||||
bg: 'myGray.50 !important'
|
||||
}
|
||||
},
|
||||
grayDanger: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.7",
|
||||
"version": "4.7.1",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
### FastGPT V4.7
|
||||
### FastGPT V4.7.1
|
||||
|
||||
1. 新增 - 工具调用模块,可以让LLM模型根据用户意图,动态的选择其他模型或插件执行。
|
||||
2. 新增 - 分类和内容提取支持 functionCall 模式。部分模型支持 functionCall 不支持 ToolCall,也可以使用了。需要把 LLM 模型配置文件里的 `functionCall` 设置为 `true`, `toolChoice`设置为 `false`。如果 `toolChoice` 为 true,会走 tool 模式。
|
||||
3. 新增 - HTTP插件,可实现OpenAPI快速生成插件。
|
||||
4. 优化 - 高级编排性能。
|
||||
5. 优化 - AI模型选择。
|
||||
6. 优化 - 手动输入知识库弹窗。
|
||||
7. 优化 - 变量输入弹窗。
|
||||
8. 优化 - 浏览器读取文件自动推断编码,减少乱码情况。
|
||||
9. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
|
||||
10. [使用文档](https://doc.fastgpt.in/docs/intro/)
|
||||
11. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)
|
||||
1. 新增 - 语音输入完整配置。支持选择是否打开语音输入(包括分享页面),支持语音输入后自动发送,支持语音输入后自动语音播放(流式)。
|
||||
2. 新增 - Pptx 和 xlsx 文件读取。但所有文件读取都放服务端,会消耗更多的服务器资源,以及无法在上传时预览更多内容。
|
||||
3. 新增 - 集成 Laf 云函数,可以读取 Laf 账号中的云函数作为 HTTP 模块。
|
||||
4. 修改 - csv导入模板,取消 header 校验,自动获取前两列。
|
||||
5. 修复 - 问题补全历史记录BUG
|
||||
6. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
|
||||
7. [使用文档](https://doc.fastgpt.in/docs/intro/)
|
||||
8. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)
|
||||
@@ -33,7 +33,7 @@ function embedChatbot() {
|
||||
ChatBtnDiv.draggable = false;
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.allow = 'fullscreen;microphone';
|
||||
iframe.allow = '*';
|
||||
iframe.referrerPolicy = 'no-referrer';
|
||||
iframe.title = 'FastGPT Chat Window';
|
||||
iframe.id = chatWindowId;
|
||||
|
||||
@@ -230,7 +230,8 @@
|
||||
"Amount": "{{amount}}{{unit}}"
|
||||
},
|
||||
"speech": {
|
||||
"error tip": "Speech Failed"
|
||||
"error tip": "Speech Failed",
|
||||
"not support": "Your browser does not support voice input"
|
||||
},
|
||||
"system": {
|
||||
"Commercial version function": "Commercial version special function",
|
||||
@@ -250,7 +251,7 @@
|
||||
},
|
||||
"core": {
|
||||
"Chat": "Chat",
|
||||
"Chat test": "Chat test",
|
||||
"Chat test": "Test",
|
||||
"Max Token": "MaxTokens",
|
||||
"Start chat": "Start chat",
|
||||
"Total chars": "Total chars: {{total}}",
|
||||
@@ -275,6 +276,7 @@
|
||||
"App intro": "App intro",
|
||||
"App params config": "App Config",
|
||||
"Chat Variable": "",
|
||||
"Config whisper": "Config whisper",
|
||||
"External using": "External use",
|
||||
"Make a brief introduction of your app": "Make a brief introduction of your app",
|
||||
"Max histories": "Dialog round",
|
||||
@@ -297,6 +299,7 @@
|
||||
"Simple Config Tip": "Only basic functions are included. For complex agent functions, use advanced orchestration.",
|
||||
"TTS": "Audio Speech",
|
||||
"TTS Tip": "After this function is enabled, the voice playback function can be used after each conversation. Use of this feature may incur additional charges.",
|
||||
"TTS start": "Reading content",
|
||||
"Team tags": "Team tags",
|
||||
"Temperature": "Temperature",
|
||||
"Tool call": "Tool call",
|
||||
@@ -309,6 +312,9 @@
|
||||
"This plugin cannot be called as a tool": "This tool cannot be used in easy mode"
|
||||
},
|
||||
"Welcome Text": "Welcome Text",
|
||||
"Whisper": "Whisper",
|
||||
"Whisper Tip": "",
|
||||
"Whisper config": "Whisper config",
|
||||
"create app": "Create App",
|
||||
"deterministic": "Deterministic",
|
||||
"edit": {
|
||||
@@ -395,17 +401,30 @@
|
||||
"Test Listen": "Test",
|
||||
"Test Listen Text": "Hello, this is a voice test, if you can hear this sentence, it means that the voice playback function is normal",
|
||||
"Web": "Browser (free)"
|
||||
},
|
||||
"whisper": {
|
||||
"Auto send": "Auto send",
|
||||
"Auto send tip": "After the voice input is completed, you can send it directly, without manually clicking the send button",
|
||||
"Auto tts response": "Auto tts response",
|
||||
"Auto tts response tip": "Questions sent through voice input will be answered directly in the form of voice. Please ensure that the voice broadcast function is enabled.",
|
||||
"Close": "Close",
|
||||
"Not tts tip": "You have not turned on Voice playback and the feature is not available",
|
||||
"Open": "Open",
|
||||
"Switch": "Open whisper"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"Admin Mark Content": "Corrected response",
|
||||
"Audio Speech Error": "Audio Speech Error",
|
||||
"Cancel Speak": "Cancel speak",
|
||||
"Canceled Speak": "Voice input has been cancelled",
|
||||
"Chat API is error or undefined": "The session interface reported an error or returned null",
|
||||
"Confirm to clear history": "Confirm to clear history?",
|
||||
"Confirm to clear share chat history": " Are you sure to delete all chats?",
|
||||
"Converting to text": "Converting to text...",
|
||||
"Custom History Title": "Custom history title",
|
||||
"Custom History Title Description": "If set to empty, chat history will be followed automatically.",
|
||||
"Debug test": "Test",
|
||||
"Exit Chat": "Exit",
|
||||
"Failed to initialize chat": "Failed to initialize chat",
|
||||
"Feedback Failed": "Feedback Failed",
|
||||
@@ -415,6 +434,7 @@
|
||||
"Feedback Submit": "Submit",
|
||||
"Feedback Success": "Feedback Success",
|
||||
"Feedback Update Failed": "Feedback Update Failed",
|
||||
"Finish Speak": "Finish speak",
|
||||
"History": "History",
|
||||
"History Amount": "{{amount}} records",
|
||||
"Mark": "Mark",
|
||||
|
||||
@@ -230,7 +230,8 @@
|
||||
"Amount": "{{amount}}{{unit}}"
|
||||
},
|
||||
"speech": {
|
||||
"error tip": "语音转文字失败"
|
||||
"error tip": "语音转文字失败",
|
||||
"not support": "您的浏览器不支持语音输入"
|
||||
},
|
||||
"system": {
|
||||
"Commercial version function": "商业版特有功能",
|
||||
@@ -250,7 +251,7 @@
|
||||
},
|
||||
"core": {
|
||||
"Chat": "对话",
|
||||
"Chat test": "测试对话",
|
||||
"Chat test": "测试",
|
||||
"Max Token": "单条数据上限",
|
||||
"Start chat": "立即对话",
|
||||
"Total chars": "总字数: {{total}}",
|
||||
@@ -275,6 +276,7 @@
|
||||
"App intro": "应用介绍",
|
||||
"App params config": "应用配置",
|
||||
"Chat Variable": "对话框变量",
|
||||
"Config whisper": "配置语音输入",
|
||||
"External using": "外部使用途径",
|
||||
"Make a brief introduction of your app": "给你的 AI 应用一个介绍",
|
||||
"Max histories": "聊天记录数量",
|
||||
@@ -295,8 +297,9 @@
|
||||
"Share link desc": "分享链接给其他用户,无需登录即可直接进行使用",
|
||||
"Share link desc detail": "可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的余额,请保管好链接!",
|
||||
"Simple Config Tip": "仅包含基础功能,复杂 agent 功能请使用高级编排。",
|
||||
"TTS": "语音播报",
|
||||
"TTS": "语音播放",
|
||||
"TTS Tip": "开启后,每次对话后可使用语音播放功能。使用该功能可能产生额外费用。",
|
||||
"TTS start": "朗读内容",
|
||||
"Team tags": "团队标签",
|
||||
"Temperature": "温度",
|
||||
"Tool call": "工具调用",
|
||||
@@ -309,6 +312,9 @@
|
||||
"This plugin cannot be called as a tool": "该工具无法在简易模式中使用"
|
||||
},
|
||||
"Welcome Text": "对话开场白",
|
||||
"Whisper": "语音输入",
|
||||
"Whisper Tip": "配置语音输入相关参数",
|
||||
"Whisper config": "语音输入配置",
|
||||
"create app": "创建属于你的 AI 应用",
|
||||
"deterministic": "严谨",
|
||||
"edit": {
|
||||
@@ -395,17 +401,30 @@
|
||||
"Test Listen": "试听",
|
||||
"Test Listen Text": "你好,这是语音测试,如果你能听到这句话,说明语音播放功能正常",
|
||||
"Web": "浏览器自带(免费)"
|
||||
},
|
||||
"whisper": {
|
||||
"Auto send": "自动发送",
|
||||
"Auto send tip": "语音输入完毕后直接发送,不需要再手动点击发送按键",
|
||||
"Auto tts response": "自动语音回复",
|
||||
"Auto tts response tip": "通过语音输入发送的问题,会直接以语音的形式响应,请确保打开了语音播报功能。",
|
||||
"Close": "关闭",
|
||||
"Not tts tip": "你没有开启语音播放,该功能无法使用",
|
||||
"Open": "开启",
|
||||
"Switch": "开启语音输入"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"Admin Mark Content": "纠正后的回复",
|
||||
"Audio Speech Error": "语音播报异常",
|
||||
"Cancel Speak": "取消语音输入",
|
||||
"Canceled Speak": "语音输入已取消",
|
||||
"Chat API is error or undefined": "对话接口报错或返回为空",
|
||||
"Confirm to clear history": "确认清空该应用的在线聊天记录?分享和 API 调用的记录不会被清空。",
|
||||
"Confirm to clear share chat history": "确认删除所有聊天记录?",
|
||||
"Converting to text": "正在转换为文本...",
|
||||
"Custom History Title": "自定义历史记录标题",
|
||||
"Custom History Title Description": "如果设置为空,会自动跟随聊天记录。",
|
||||
"Debug test": "调试预览",
|
||||
"Exit Chat": "退出聊天",
|
||||
"Failed to initialize chat": "初始化聊天失败",
|
||||
"Feedback Failed": "提交反馈异常",
|
||||
@@ -415,6 +434,7 @@
|
||||
"Feedback Submit": "提交反馈",
|
||||
"Feedback Success": "反馈成功!",
|
||||
"Feedback Update Failed": "更新反馈状态失败",
|
||||
"Finish Speak": "语音输入完成",
|
||||
"History": "记录",
|
||||
"History Amount": "{{amount}}条记录",
|
||||
"Mark": "标注预期回答",
|
||||
@@ -590,8 +610,7 @@
|
||||
"success": "开始同步"
|
||||
}
|
||||
},
|
||||
"training": {
|
||||
}
|
||||
"training": {}
|
||||
},
|
||||
"data": {
|
||||
"Auxiliary Data": "辅助数据",
|
||||
@@ -978,6 +997,7 @@
|
||||
"Tool module": "工具",
|
||||
"UnKnow Module": "未知模块",
|
||||
"User guide": "用户引导",
|
||||
"App system setting": "系统配置",
|
||||
"http body placeholder": "与APIFox相同的语法",
|
||||
"textEditor": "文本加工",
|
||||
"textEditor intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。"
|
||||
@@ -1473,7 +1493,7 @@
|
||||
"usage": {
|
||||
"Ai model": "AI模型",
|
||||
"App name": "应用名",
|
||||
"Audio Speech": "语音播报",
|
||||
"Audio Speech": "语音播放",
|
||||
"Bill Module": "扣费模块",
|
||||
"Chars length": "文本长度",
|
||||
"Data Length": "数据长度",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useSpeech } from '@/web/common/hooks/useSpeech';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box, Flex, Image, Spinner, Textarea } from '@chakra-ui/react';
|
||||
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import React, { useRef, useEffect, useCallback, useTransition } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -12,32 +12,28 @@ import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { addDays } from 'date-fns';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { ChatBoxInputFormType, ChatBoxInputType, UserInputFileItemType } from './type';
|
||||
import { textareaMinH } from './constants';
|
||||
import { UseFormReturn, useFieldArray } from 'react-hook-form';
|
||||
import { useChatProviderStore } from './Provider';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const MessageInput = ({
|
||||
onSendMessage,
|
||||
onStop,
|
||||
isChatting,
|
||||
TextareaDom,
|
||||
showFileSelector = false,
|
||||
resetInputVal,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
chatForm
|
||||
}: OutLinkChatAuthProps & {
|
||||
onSendMessage: (val: ChatBoxInputType) => void;
|
||||
chatForm,
|
||||
appId
|
||||
}: {
|
||||
onSendMessage: (val: ChatBoxInputType & { autoTTSResponse?: boolean }) => void;
|
||||
onStop: () => void;
|
||||
isChatting: boolean;
|
||||
showFileSelector?: boolean;
|
||||
TextareaDom: React.MutableRefObject<HTMLTextAreaElement | null>;
|
||||
resetInputVal: (val: ChatBoxInputType) => void;
|
||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||
appId?: string;
|
||||
}) => {
|
||||
const { setValue, watch, control } = chatForm;
|
||||
const inputValue = watch('input');
|
||||
@@ -52,15 +48,8 @@ const MessageInput = ({
|
||||
name: 'files'
|
||||
});
|
||||
|
||||
const {
|
||||
isSpeaking,
|
||||
isTransCription,
|
||||
stopSpeak,
|
||||
startSpeak,
|
||||
speakingTimeString,
|
||||
renderAudioGraph,
|
||||
stream
|
||||
} = useSpeech({ shareId, outLinkUid, teamId, teamToken });
|
||||
const { shareId, outLinkUid, teamId, teamToken, isChatting, whisperConfig, autoTTSResponse } =
|
||||
useChatProviderStore();
|
||||
const { isPc, whisperModel } = useSystemStore();
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const { t } = useTranslation();
|
||||
@@ -163,6 +152,16 @@ const MessageInput = ({
|
||||
replaceFile([]);
|
||||
}, [TextareaDom, fileList, onSendMessage, replaceFile]);
|
||||
|
||||
/* whisper init */
|
||||
const {
|
||||
isSpeaking,
|
||||
isTransCription,
|
||||
stopSpeak,
|
||||
startSpeak,
|
||||
speakingTimeString,
|
||||
renderAudioGraph,
|
||||
stream
|
||||
} = useSpeech({ appId, shareId, outLinkUid, teamId, teamToken });
|
||||
useEffect(() => {
|
||||
if (!stream) {
|
||||
return;
|
||||
@@ -180,6 +179,28 @@ const MessageInput = ({
|
||||
};
|
||||
renderCurve();
|
||||
}, [renderAudioGraph, stream]);
|
||||
const finishWhisperTranscription = useCallback(
|
||||
(text: string) => {
|
||||
if (!text) return;
|
||||
if (whisperConfig?.autoSend) {
|
||||
onSendMessage({
|
||||
text,
|
||||
files: fileList,
|
||||
autoTTSResponse
|
||||
});
|
||||
replaceFile([]);
|
||||
} else {
|
||||
resetInputVal({ text });
|
||||
}
|
||||
},
|
||||
[autoTTSResponse, fileList, onSendMessage, replaceFile, resetInputVal, whisperConfig?.autoSend]
|
||||
);
|
||||
const onWhisperRecord = useCallback(() => {
|
||||
if (isSpeaking) {
|
||||
return stopSpeak();
|
||||
}
|
||||
startSpeak(finishWhisperTranscription);
|
||||
}, [finishWhisperTranscription, isSpeaking, startSpeak, stopSpeak]);
|
||||
|
||||
return (
|
||||
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(800px, 100%)']} px={[0, 5]}>
|
||||
@@ -369,7 +390,7 @@ const MessageInput = ({
|
||||
bottom={['10px', '12px']}
|
||||
>
|
||||
{/* voice-input */}
|
||||
{!shareId && !havInput && !isChatting && !!whisperModel && (
|
||||
{whisperConfig.open && !havInput && !isChatting && !!whisperModel && (
|
||||
<>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
@@ -380,32 +401,49 @@ const MessageInput = ({
|
||||
zIndex: 0
|
||||
}}
|
||||
/>
|
||||
<Flex
|
||||
mr={2}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['26px', '32px']}
|
||||
w={['26px', '32px']}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#F5F5F8' }}
|
||||
onClick={() => {
|
||||
if (isSpeaking) {
|
||||
return stopSpeak();
|
||||
}
|
||||
startSpeak((text) => resetInputVal({ text }));
|
||||
}}
|
||||
>
|
||||
<MyTooltip label={isSpeaking ? t('core.chat.Stop Speak') : t('core.chat.Record')}>
|
||||
{isSpeaking && (
|
||||
<MyTooltip label={t('core.chat.Cancel Speak')}>
|
||||
<Flex
|
||||
mr={2}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['26px', '32px']}
|
||||
w={['26px', '32px']}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#F5F5F8' }}
|
||||
onClick={() => stopSpeak(true)}
|
||||
>
|
||||
<MyIcon
|
||||
name={'core/chat/cancelSpeak'}
|
||||
width={['20px', '22px']}
|
||||
height={['20px', '22px']}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip label={isSpeaking ? t('core.chat.Finish Speak') : t('core.chat.Record')}>
|
||||
<Flex
|
||||
mr={2}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['26px', '32px']}
|
||||
w={['26px', '32px']}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#F5F5F8' }}
|
||||
onClick={onWhisperRecord}
|
||||
>
|
||||
<MyIcon
|
||||
name={isSpeaking ? 'core/chat/stopSpeechFill' : 'core/chat/recordFill'}
|
||||
name={isSpeaking ? 'core/chat/finishSpeak' : 'core/chat/recordFill'}
|
||||
width={['20px', '22px']}
|
||||
height={['20px', '22px']}
|
||||
color={isSpeaking ? 'primary.500' : 'myGray.600'}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
{/* send and stop icon */}
|
||||
|
||||
176
projects/app/src/components/ChatBox/Provider.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import React, { useContext, createContext, useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import {
|
||||
AppTTSConfigType,
|
||||
AppWhisperConfigType,
|
||||
VariableItemType
|
||||
} from '@fastgpt/global/core/app/type';
|
||||
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
|
||||
type useChatStoreType = OutLinkChatAuthProps & {
|
||||
welcomeText: string;
|
||||
variableModules: VariableItemType[];
|
||||
questionGuide: boolean;
|
||||
ttsConfig: AppTTSConfigType;
|
||||
whisperConfig: AppWhisperConfigType;
|
||||
autoTTSResponse: boolean;
|
||||
startSegmentedAudio: () => Promise<any>;
|
||||
splitText2Audio: (text: string, done?: boolean | undefined) => void;
|
||||
finishSegmentedAudio: () => void;
|
||||
audioLoading: boolean;
|
||||
audioPlaying: boolean;
|
||||
hasAudio: boolean;
|
||||
playAudioByText: ({
|
||||
text,
|
||||
buffer
|
||||
}: {
|
||||
text: string;
|
||||
buffer?: Uint8Array | undefined;
|
||||
}) => Promise<{
|
||||
buffer?: Uint8Array | undefined;
|
||||
}>;
|
||||
cancelAudio: () => void;
|
||||
audioPlayingChatId: string | undefined;
|
||||
setAudioPlayingChatId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
chatHistories: ChatSiteItemType[];
|
||||
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
isChatting: boolean;
|
||||
};
|
||||
const StateContext = createContext<useChatStoreType>({
|
||||
welcomeText: '',
|
||||
variableModules: [],
|
||||
questionGuide: false,
|
||||
ttsConfig: {
|
||||
type: 'none',
|
||||
model: undefined,
|
||||
voice: undefined,
|
||||
speed: undefined
|
||||
},
|
||||
whisperConfig: {
|
||||
open: false,
|
||||
autoSend: false,
|
||||
autoTTSResponse: false
|
||||
},
|
||||
autoTTSResponse: false,
|
||||
startSegmentedAudio: function (): Promise<any> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
splitText2Audio: function (text: string, done?: boolean | undefined): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
chatHistories: [],
|
||||
setChatHistories: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isChatting: false,
|
||||
audioLoading: false,
|
||||
audioPlaying: false,
|
||||
hasAudio: false,
|
||||
playAudioByText: function ({
|
||||
text,
|
||||
buffer
|
||||
}: {
|
||||
text: string;
|
||||
buffer?: Uint8Array | undefined;
|
||||
}): Promise<{ buffer?: Uint8Array | undefined }> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
cancelAudio: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
audioPlayingChatId: undefined,
|
||||
setAudioPlayingChatId: function (value: React.SetStateAction<string | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
finishSegmentedAudio: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
userGuideModule?: ModuleItemType;
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const useChatProviderStore = () => useContext(StateContext);
|
||||
|
||||
const Provider = ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
userGuideModule,
|
||||
children
|
||||
}: ChatProviderProps) => {
|
||||
const [chatHistories, setChatHistories] = useState<ChatSiteItemType[]>([]);
|
||||
|
||||
const { welcomeText, variableModules, questionGuide, ttsConfig, whisperConfig } = useMemo(
|
||||
() => splitGuideModule(userGuideModule),
|
||||
[userGuideModule]
|
||||
);
|
||||
|
||||
// segment audio
|
||||
const [audioPlayingChatId, setAudioPlayingChatId] = useState<string>();
|
||||
const {
|
||||
audioLoading,
|
||||
audioPlaying,
|
||||
hasAudio,
|
||||
playAudioByText,
|
||||
cancelAudio,
|
||||
startSegmentedAudio,
|
||||
finishSegmentedAudio,
|
||||
splitText2Audio
|
||||
} = useAudioPlay({
|
||||
ttsConfig,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
|
||||
const autoTTSResponse =
|
||||
whisperConfig?.open && whisperConfig?.autoSend && whisperConfig?.autoTTSResponse && hasAudio;
|
||||
|
||||
const isChatting = useMemo(
|
||||
() =>
|
||||
chatHistories[chatHistories.length - 1] &&
|
||||
chatHistories[chatHistories.length - 1]?.status !== 'finish',
|
||||
[chatHistories]
|
||||
);
|
||||
|
||||
const value: useChatStoreType = {
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
welcomeText,
|
||||
variableModules,
|
||||
questionGuide,
|
||||
ttsConfig,
|
||||
whisperConfig,
|
||||
autoTTSResponse,
|
||||
startSegmentedAudio,
|
||||
finishSegmentedAudio,
|
||||
splitText2Audio,
|
||||
audioLoading,
|
||||
audioPlaying,
|
||||
hasAudio,
|
||||
playAudioByText,
|
||||
cancelAudio,
|
||||
audioPlayingChatId,
|
||||
setAudioPlayingChatId,
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
isChatting
|
||||
};
|
||||
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
|
||||
};
|
||||
|
||||
export default React.memo(Provider);
|
||||
@@ -2,21 +2,18 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { Flex, FlexProps, Image, css, useTheme } from '@chakra-ui/react';
|
||||
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { AppTTSConfigType } from '@fastgpt/global/core/module/type';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { formatChatValue2InputType } from '../utils';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useChatProviderStore } from '../Provider';
|
||||
|
||||
export type ChatControllerProps = {
|
||||
isChatting: boolean;
|
||||
isLastChild: boolean;
|
||||
chat: ChatSiteItemType;
|
||||
setChatHistories?: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
showVoiceIcon?: boolean;
|
||||
ttsConfig?: AppTTSConfigType;
|
||||
onRetry?: () => void;
|
||||
onDelete?: () => void;
|
||||
onMark?: () => void;
|
||||
@@ -27,33 +24,29 @@ export type ChatControllerProps = {
|
||||
};
|
||||
|
||||
const ChatController = ({
|
||||
isChatting,
|
||||
chat,
|
||||
setChatHistories,
|
||||
isLastChild,
|
||||
showVoiceIcon,
|
||||
ttsConfig,
|
||||
onReadUserDislike,
|
||||
onCloseUserLike,
|
||||
onMark,
|
||||
onRetry,
|
||||
onDelete,
|
||||
onAddUserDislike,
|
||||
onAddUserLike,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}: OutLinkChatAuthProps & ChatControllerProps & FlexProps) => {
|
||||
onAddUserLike
|
||||
}: ChatControllerProps & FlexProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({
|
||||
ttsConfig,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
const {
|
||||
isChatting,
|
||||
setChatHistories,
|
||||
audioLoading,
|
||||
audioPlaying,
|
||||
hasAudio,
|
||||
playAudioByText,
|
||||
cancelAudio,
|
||||
audioPlayingChatId,
|
||||
setAudioPlayingChatId
|
||||
} = useChatProviderStore();
|
||||
const controlIconStyle = {
|
||||
w: '14px',
|
||||
cursor: 'pointer',
|
||||
@@ -67,6 +60,11 @@ const ChatController = ({
|
||||
display: 'flex'
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const chatText = useMemo(() => formatChatValue2InputType(chat.value).text || '', [chat.value]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
{...controlContainerStyle}
|
||||
@@ -86,7 +84,7 @@ const ChatController = ({
|
||||
{...controlIconStyle}
|
||||
name={'copy'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => copyData(formatChatValue2InputType(chat.value).text || '')}
|
||||
onClick={() => copyData(chatText)}
|
||||
/>
|
||||
</MyTooltip>
|
||||
{!!onDelete && !isChatting && (
|
||||
@@ -113,51 +111,65 @@ const ChatController = ({
|
||||
)}
|
||||
{showVoiceIcon &&
|
||||
hasAudio &&
|
||||
(audioLoading ? (
|
||||
<MyTooltip label={t('common.Loading')}>
|
||||
<MyIcon {...controlIconStyle} name={'common/loading'} />
|
||||
</MyTooltip>
|
||||
) : audioPlaying ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTooltip label={t('core.chat.tts.Stop Speech')}>
|
||||
(() => {
|
||||
const isPlayingChat = chat.dataId === audioPlayingChatId;
|
||||
if (isPlayingChat && audioPlaying) {
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTooltip label={t('core.chat.tts.Stop Speech')}>
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
borderRight={'none'}
|
||||
name={'core/chat/stopSpeech'}
|
||||
color={'#E74694'}
|
||||
onClick={cancelAudio}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<Image
|
||||
src="/icon/speaking.gif"
|
||||
w={'23px'}
|
||||
alt={''}
|
||||
borderRight={theme.borders.base}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
if (isPlayingChat && audioLoading) {
|
||||
return (
|
||||
<MyTooltip label={t('common.Loading')}>
|
||||
<MyIcon {...controlIconStyle} name={'common/loading'} />
|
||||
</MyTooltip>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MyTooltip label={t('core.app.TTS start')}>
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
borderRight={'none'}
|
||||
name={'core/chat/stopSpeech'}
|
||||
color={'#E74694'}
|
||||
onClick={() => cancelAudio()}
|
||||
name={'common/voiceLight'}
|
||||
_hover={{ color: '#E74694' }}
|
||||
onClick={async () => {
|
||||
setAudioPlayingChatId(chat.dataId);
|
||||
const response = await playAudioByText({
|
||||
buffer: chat.ttsBuffer,
|
||||
text: chatText
|
||||
});
|
||||
|
||||
if (!setChatHistories || !response.buffer) return;
|
||||
setChatHistories((state) =>
|
||||
state.map((item) =>
|
||||
item.dataId === chat.dataId
|
||||
? {
|
||||
...item,
|
||||
ttsBuffer: response.buffer
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<Image src="/icon/speaking.gif" w={'23px'} alt={''} borderRight={theme.borders.base} />
|
||||
</Flex>
|
||||
) : (
|
||||
<MyTooltip label={t('core.app.TTS')}>
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
name={'common/voiceLight'}
|
||||
_hover={{ color: '#E74694' }}
|
||||
onClick={async () => {
|
||||
const response = await playAudio({
|
||||
buffer: chat.ttsBuffer,
|
||||
chatItemId: chat.dataId,
|
||||
text: formatChatValue2InputType(chat.value).text || ''
|
||||
});
|
||||
|
||||
if (!setChatHistories || !response.buffer) return;
|
||||
setChatHistories((state) =>
|
||||
state.map((item) =>
|
||||
item.dataId === chat.dataId
|
||||
? {
|
||||
...item,
|
||||
ttsBuffer: response.buffer
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
))}
|
||||
);
|
||||
})()}
|
||||
{!!onMark && (
|
||||
<MyTooltip label={t('core.chat.Mark')}>
|
||||
<MyIcon
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ChatStatusEnum
|
||||
} from '@fastgpt/global/core/chat/constants';
|
||||
import FilesBlock from './FilesBox';
|
||||
import { useChatProviderStore } from '../Provider';
|
||||
|
||||
const colorMap = {
|
||||
[ChatStatusEnum.loading]: {
|
||||
@@ -56,11 +57,9 @@ const ChatItem = ({
|
||||
status: `${ChatStatusEnum}`;
|
||||
name: string;
|
||||
};
|
||||
isLastChild?: boolean;
|
||||
questionGuides?: string[];
|
||||
children?: React.ReactNode;
|
||||
} & ChatControllerProps) => {
|
||||
const theme = useTheme();
|
||||
const styleMap: BoxProps =
|
||||
type === ChatRoleEnum.Human
|
||||
? {
|
||||
@@ -77,7 +76,9 @@ const ChatItem = ({
|
||||
textAlign: 'left',
|
||||
bg: 'myGray.50'
|
||||
};
|
||||
const { chat, isChatting } = chatControllerProps;
|
||||
|
||||
const { isChatting } = useChatProviderStore();
|
||||
const { chat } = chatControllerProps;
|
||||
|
||||
const ContentCard = useMemo(() => {
|
||||
if (type === 'Human') {
|
||||
@@ -93,7 +94,7 @@ const ChatItem = ({
|
||||
|
||||
/* AI */
|
||||
return (
|
||||
<Flex flexDirection={'column'} gap={2}>
|
||||
<Flex flexDirection={'column'} key={chat.dataId} gap={2}>
|
||||
{chat.value.map((value, i) => {
|
||||
const key = `${chat.dataId}-ai-${i}`;
|
||||
if (value.text) {
|
||||
@@ -209,7 +210,7 @@ ${toolResponse}`}
|
||||
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
|
||||
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
|
||||
<Box order={styleMap.order} ml={styleMap.ml}>
|
||||
<ChatController {...chatControllerProps} />
|
||||
<ChatController {...chatControllerProps} isLastChild={isLastChild} />
|
||||
</Box>
|
||||
)}
|
||||
<ChatAvatar src={avatar} type={type} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VariableItemType } from '@fastgpt/global/core/module/type';
|
||||
import { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
||||
import React, { useState } from 'react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
@@ -11,3 +11,9 @@ export const MessageCardStyle: BoxProps = {
|
||||
maxW: ['calc(100% - 25px)', 'calc(100% - 40px)'],
|
||||
color: 'myGray.900'
|
||||
};
|
||||
|
||||
export enum FeedbackTypeEnum {
|
||||
user = 'user',
|
||||
admin = 'admin',
|
||||
hidden = 'hidden'
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import React, {
|
||||
import Script from 'next/script';
|
||||
import { throttle } from 'lodash';
|
||||
import type {
|
||||
AIChatItemType,
|
||||
AIChatItemValueItemType,
|
||||
ChatSiteItemType,
|
||||
UserChatItemValueItemType
|
||||
@@ -39,7 +38,6 @@ import type { AdminMarkType } from './SelectMarkCollection';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
|
||||
import { postQuestionGuide } from '@/web/core/ai/api';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import type {
|
||||
generatingMessageProps,
|
||||
StartChatFnProps,
|
||||
@@ -55,6 +53,8 @@ import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/c
|
||||
import { formatChatValue2InputType } from './utils';
|
||||
import { textareaMinH } from './constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import ChatProvider, { useChatProviderStore } from './Provider';
|
||||
|
||||
import ChatItem from './components/ChatItem';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
@@ -82,9 +82,9 @@ type Props = OutLinkChatAuthProps & {
|
||||
userGuideModule?: ModuleItemType;
|
||||
showFileSelector?: boolean;
|
||||
active?: boolean; // can use
|
||||
appId: string;
|
||||
|
||||
// not chat test params
|
||||
appId?: string;
|
||||
chatId?: string;
|
||||
|
||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
||||
@@ -112,7 +112,6 @@ const ChatBox = (
|
||||
showEmptyIntro = false,
|
||||
appAvatar,
|
||||
userAvatar,
|
||||
userGuideModule,
|
||||
showFileSelector,
|
||||
active = true,
|
||||
appId,
|
||||
@@ -137,7 +136,6 @@ const ChatBox = (
|
||||
const questionGuideController = useRef(new AbortController());
|
||||
const isNewChatReplace = useRef(false);
|
||||
|
||||
const [chatHistories, setChatHistories] = useState<ChatSiteItemType[]>([]);
|
||||
const [feedbackId, setFeedbackId] = useState<string>();
|
||||
const [readFeedbackData, setReadFeedbackData] = useState<{
|
||||
chatItemId: string;
|
||||
@@ -146,17 +144,20 @@ const ChatBox = (
|
||||
const [adminMarkData, setAdminMarkData] = useState<AdminMarkType & { chatItemId: string }>();
|
||||
const [questionGuides, setQuestionGuide] = useState<string[]>([]);
|
||||
|
||||
const isChatting = useMemo(
|
||||
() =>
|
||||
chatHistories[chatHistories.length - 1] &&
|
||||
chatHistories[chatHistories.length - 1]?.status !== 'finish',
|
||||
[chatHistories]
|
||||
);
|
||||
const {
|
||||
welcomeText,
|
||||
variableModules,
|
||||
questionGuide,
|
||||
startSegmentedAudio,
|
||||
finishSegmentedAudio,
|
||||
setAudioPlayingChatId,
|
||||
splitText2Audio,
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
isChatting
|
||||
} = useChatProviderStore();
|
||||
|
||||
const { welcomeText, variableModules, questionGuide, ttsConfig } = useMemo(
|
||||
() => splitGuideModule(userGuideModule),
|
||||
[userGuideModule]
|
||||
);
|
||||
/* variable */
|
||||
const filterVariableModules = useMemo(
|
||||
() => variableModules.filter((item) => item.type !== VariableInputEnum.external),
|
||||
[variableModules]
|
||||
@@ -171,10 +172,9 @@ const ChatBox = (
|
||||
chatStarted: false
|
||||
}
|
||||
});
|
||||
const { setValue, watch, handleSubmit, control } = chatForm;
|
||||
const { setValue, watch, handleSubmit } = chatForm;
|
||||
const variables = watch('variables');
|
||||
const chatStarted = watch('chatStarted');
|
||||
|
||||
const variableIsFinish = useMemo(() => {
|
||||
if (!filterVariableModules || filterVariableModules.length === 0 || chatHistories.length > 0)
|
||||
return true;
|
||||
@@ -212,12 +212,21 @@ const ChatBox = (
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const generatingMessage = useCallback(
|
||||
({ event, text = '', status, name, tool }: generatingMessageProps) => {
|
||||
({
|
||||
event,
|
||||
text = '',
|
||||
status,
|
||||
name,
|
||||
tool,
|
||||
autoTTSResponse
|
||||
}: generatingMessageProps & { autoTTSResponse?: boolean }) => {
|
||||
setChatHistories((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
if (item.obj !== ChatRoleEnum.AI) return item;
|
||||
|
||||
autoTTSResponse && splitText2Audio(formatChatValue2InputType(item.value).text || '');
|
||||
|
||||
const lastValue: AIChatItemValueItemType = JSON.parse(
|
||||
JSON.stringify(item.value[item.value.length - 1])
|
||||
);
|
||||
@@ -299,7 +308,7 @@ const ChatBox = (
|
||||
);
|
||||
generatingScroll();
|
||||
},
|
||||
[generatingScroll]
|
||||
[generatingScroll, setChatHistories, splitText2Audio]
|
||||
);
|
||||
|
||||
// 重置输入内容
|
||||
@@ -357,8 +366,10 @@ const ChatBox = (
|
||||
({
|
||||
text = '',
|
||||
files = [],
|
||||
history = chatHistories
|
||||
history = chatHistories,
|
||||
autoTTSResponse = false
|
||||
}: ChatBoxInputType & {
|
||||
autoTTSResponse?: boolean;
|
||||
history?: ChatSiteItemType[];
|
||||
}) => {
|
||||
handleSubmit(async ({ variables }) => {
|
||||
@@ -370,7 +381,7 @@ const ChatBox = (
|
||||
});
|
||||
return;
|
||||
}
|
||||
questionGuideController.current?.abort('stop');
|
||||
|
||||
text = text.trim();
|
||||
|
||||
if (!text && files.length === 0) {
|
||||
@@ -381,6 +392,15 @@ const ChatBox = (
|
||||
return;
|
||||
}
|
||||
|
||||
const responseChatId = getNanoid(24);
|
||||
questionGuideController.current?.abort('stop');
|
||||
|
||||
// set auto audio playing
|
||||
if (autoTTSResponse) {
|
||||
await startSegmentedAudio();
|
||||
setAudioPlayingChatId(responseChatId);
|
||||
}
|
||||
|
||||
const newChatList: ChatSiteItemType[] = [
|
||||
...history,
|
||||
{
|
||||
@@ -409,7 +429,7 @@ const ChatBox = (
|
||||
status: 'finish'
|
||||
},
|
||||
{
|
||||
dataId: getNanoid(24),
|
||||
dataId: responseChatId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
@@ -447,7 +467,7 @@ const ChatBox = (
|
||||
chatList: newChatList,
|
||||
messages,
|
||||
controller: abortSignal,
|
||||
generatingMessage,
|
||||
generatingMessage: (e) => generatingMessage({ ...e, autoTTSResponse }),
|
||||
variables
|
||||
});
|
||||
|
||||
@@ -485,6 +505,9 @@ const ChatBox = (
|
||||
generatingScroll();
|
||||
isPc && TextareaDom.current?.focus();
|
||||
}, 100);
|
||||
|
||||
// tts audio
|
||||
autoTTSResponse && splitText2Audio(responseText, true);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: t(getErrText(err, 'core.chat.error.Chat error')),
|
||||
@@ -509,11 +532,14 @@ const ChatBox = (
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
autoTTSResponse && finishSegmentedAudio();
|
||||
})();
|
||||
},
|
||||
[
|
||||
chatHistories,
|
||||
createQuestionGuide,
|
||||
finishSegmentedAudio,
|
||||
generatingMessage,
|
||||
generatingScroll,
|
||||
handleSubmit,
|
||||
@@ -521,6 +547,10 @@ const ChatBox = (
|
||||
isPc,
|
||||
onStartChat,
|
||||
resetInputVal,
|
||||
setAudioPlayingChatId,
|
||||
setChatHistories,
|
||||
splitText2Audio,
|
||||
startSegmentedAudio,
|
||||
t,
|
||||
toast
|
||||
]
|
||||
@@ -875,9 +905,9 @@ const ChatBox = (
|
||||
type={item.obj}
|
||||
avatar={item.obj === 'Human' ? userAvatar : appAvatar}
|
||||
chat={item}
|
||||
isChatting={isChatting}
|
||||
onRetry={retryInput(item.dataId)}
|
||||
onDelete={delOneMessage(item.dataId)}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
/>
|
||||
)}
|
||||
{item.obj === 'AI' && (
|
||||
@@ -886,17 +916,14 @@ const ChatBox = (
|
||||
type={item.obj}
|
||||
avatar={appAvatar}
|
||||
chat={item}
|
||||
isChatting={isChatting}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
{...(item.obj === 'AI' && {
|
||||
setChatHistories,
|
||||
showVoiceIcon,
|
||||
ttsConfig,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
statusBoxData,
|
||||
isLastChild: index === chatHistories.length - 1,
|
||||
questionGuides,
|
||||
onMark: onMark(
|
||||
item,
|
||||
@@ -957,15 +984,11 @@ const ChatBox = (
|
||||
<MessageInput
|
||||
onSendMessage={sendPrompt}
|
||||
onStop={() => chatController.current?.abort('stop')}
|
||||
isChatting={isChatting}
|
||||
TextareaDom={TextareaDom}
|
||||
resetInputVal={resetInputVal}
|
||||
showFileSelector={showFileSelector}
|
||||
shareId={shareId}
|
||||
outLinkUid={outLinkUid}
|
||||
teamId={teamId}
|
||||
teamToken={teamToken}
|
||||
chatForm={chatForm}
|
||||
appId={appId}
|
||||
/>
|
||||
)}
|
||||
{/* user feedback modal */}
|
||||
@@ -1063,5 +1086,14 @@ const ChatBox = (
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
const ForwardChatBox = forwardRef(ChatBox);
|
||||
|
||||
export default React.memo(forwardRef(ChatBox));
|
||||
const ChatBoxContainer = (props: Props, ref: ForwardedRef<ComponentRef>) => {
|
||||
return (
|
||||
<ChatProvider {...props}>
|
||||
<ForwardChatBox {...props} ref={ref} />
|
||||
</ChatProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(forwardRef(ChatBoxContainer));
|
||||
|
||||
@@ -55,7 +55,7 @@ const SettingLLMModel = ({ llmModelType = LLMModelTypeEnum.all, defaultData, onC
|
||||
leftIcon={
|
||||
<Avatar
|
||||
borderRadius={'0'}
|
||||
src={selectedModel.avatar || HUGGING_FACE_ICON}
|
||||
src={selectedModel?.avatar || HUGGING_FACE_ICON}
|
||||
fallbackSrc={HUGGING_FACE_ICON}
|
||||
w={'18px'}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Box, Button, Flex, ModalBody, useDisclosure, Image } from '@chakra-ui/r
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TTSTypeEnum } from '@/constants/app';
|
||||
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
|
||||
import type { AppTTSConfigType } from '@fastgpt/global/core/app/type.d';
|
||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
@@ -46,7 +46,9 @@ const TTSSelect = ({
|
||||
[formatValue, list, t]
|
||||
);
|
||||
|
||||
const { playAudio, cancelAudio, audioLoading, audioPlaying } = useAudioPlay({ ttsConfig: value });
|
||||
const { playAudioByText, cancelAudio, audioLoading, audioPlaying } = useAudioPlay({
|
||||
ttsConfig: value
|
||||
});
|
||||
|
||||
const onclickChange = useCallback(
|
||||
(e: string) => {
|
||||
@@ -70,6 +72,11 @@ const TTSSelect = ({
|
||||
[audioSpeechModelList, onChange, value]
|
||||
);
|
||||
|
||||
const onCloseTTSModal = useCallback(() => {
|
||||
cancelAudio();
|
||||
onClose();
|
||||
}, [cancelAudio, onClose]);
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/tts'} mr={2} w={'20px'} />
|
||||
@@ -98,7 +105,7 @@ const TTSSelect = ({
|
||||
</>
|
||||
}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onClose={onCloseTTSModal}
|
||||
w={'500px'}
|
||||
>
|
||||
<ModalBody px={[5, 16]} py={[4, 8]}>
|
||||
@@ -137,9 +144,7 @@ const TTSSelect = ({
|
||||
color={'primary.600'}
|
||||
isLoading={audioLoading}
|
||||
leftIcon={<MyIcon name={'core/chat/stopSpeech'} w={'16px'} />}
|
||||
onClick={() => {
|
||||
cancelAudio();
|
||||
}}
|
||||
onClick={cancelAudio}
|
||||
>
|
||||
{t('core.chat.tts.Stop Speech')}
|
||||
</Button>
|
||||
@@ -149,7 +154,7 @@ const TTSSelect = ({
|
||||
isLoading={audioLoading}
|
||||
leftIcon={<MyIcon name={'core/app/headphones'} w={'16px'} />}
|
||||
onClick={() => {
|
||||
playAudio({
|
||||
playAudioByText({
|
||||
text: t('core.app.tts.Test Listen Text')
|
||||
});
|
||||
}}
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { VariableInputEnum, variableMap } from '@fastgpt/global/core/module/constants';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useFieldArray } from 'react-hook-form';
|
||||
116
projects/app/src/components/core/app/WhisperConfig.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { Box, Button, Flex, ModalBody, useDisclosure, Switch } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { AppWhisperConfigType } from '@fastgpt/global/core/app/type.d';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
const WhisperConfig = ({
|
||||
isOpenAudio,
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
isOpenAudio: boolean;
|
||||
value: AppWhisperConfigType;
|
||||
onChange: (e: AppWhisperConfigType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const isOpenWhisper = value.open;
|
||||
const isAutoSend = value.autoSend;
|
||||
|
||||
const formLabel = useMemo(() => {
|
||||
if (!isOpenWhisper) {
|
||||
return t('core.app.whisper.Close');
|
||||
}
|
||||
return t('core.app.whisper.Open');
|
||||
}, [t, isOpenWhisper]);
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/whisper'} mr={2} w={'20px'} />
|
||||
<Box>{t('core.app.Whisper')}</Box>
|
||||
<Box flex={1} />
|
||||
<MyTooltip label={t('core.app.Config whisper')}>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
fontSize={'md'}
|
||||
mr={'-5px'}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{formLabel}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
<MyModal
|
||||
title={t('core.app.Whisper config')}
|
||||
iconSrc="core/app/simpleMode/whisper"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody px={[5, 16]} py={[4, 8]}>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
{t('core.app.whisper.Switch')}
|
||||
<Switch
|
||||
isChecked={isOpenWhisper}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
open: e.target.checked
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{isOpenWhisper && (
|
||||
<Flex mt={8} alignItems={'center'}>
|
||||
{t('core.app.whisper.Auto send')}
|
||||
<QuestionTip label={t('core.app.whisper.Auto send tip')} />
|
||||
<Box flex={'1 0 0'} />
|
||||
<Switch
|
||||
isChecked={value.autoSend}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
autoSend: e.target.checked
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{isOpenWhisper && isAutoSend && (
|
||||
<>
|
||||
<Flex mt={8} alignItems={'center'}>
|
||||
{t('core.app.whisper.Auto tts response')}
|
||||
<QuestionTip label={t('core.app.whisper.Auto tts response tip')} />
|
||||
<Box flex={'1 0 0'} />
|
||||
<Switch
|
||||
isChecked={value.autoTTSResponse}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
autoTTSResponse: e.target.checked
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{!isOpenAudio && (
|
||||
<Box mt={1} color={'myGray.600'} fontSize={'sm'}>
|
||||
{t('core.app.whisper.Not tts tip')}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(WhisperConfig);
|
||||
@@ -8,6 +8,7 @@ import React, {
|
||||
useImperativeHandle,
|
||||
ForwardedRef
|
||||
} from 'react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
@@ -18,6 +19,7 @@ import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
export type ChatTestComponentRef = {
|
||||
resetChatTest: () => void;
|
||||
@@ -35,6 +37,7 @@ const ChatTest = (
|
||||
},
|
||||
ref: ForwardedRef<ChatTestComponentRef>
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const { userInfo } = useUserStore();
|
||||
const isOpen = useMemo(() => modules && modules.length > 0, [modules]);
|
||||
@@ -100,9 +103,9 @@ const ChatTest = (
|
||||
>
|
||||
<Flex py={4} px={5} whiteSpace={'nowrap'}>
|
||||
<Box fontSize={'xl'} fontWeight={'bold'} flex={1}>
|
||||
调试预览
|
||||
{t('core.chat.Debug test')}
|
||||
</Box>
|
||||
<MyTooltip label={'重置'}>
|
||||
<MyTooltip label={t('core.chat.Restart')}>
|
||||
<IconButton
|
||||
className="chat"
|
||||
size={'smSquare'}
|
||||
@@ -117,10 +120,21 @@ const ChatTest = (
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={t('common.Close')}>
|
||||
<IconButton
|
||||
ml={[3, 6]}
|
||||
icon={<SmallCloseIcon fontSize={'22px'} />}
|
||||
variant={'grayBase'}
|
||||
size={'smSquare'}
|
||||
aria-label={''}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box flex={1}>
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
appId={app._id}
|
||||
appAvatar={app.avatar}
|
||||
userAvatar={userInfo?.avatar}
|
||||
showMarkIcon
|
||||
@@ -131,7 +145,7 @@ const ChatTest = (
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box
|
||||
{/* <Box
|
||||
zIndex={2}
|
||||
display={isOpen ? 'block' : 'none'}
|
||||
position={'fixed'}
|
||||
@@ -140,7 +154,7 @@ const ChatTest = (
|
||||
bottom={0}
|
||||
right={0}
|
||||
onClick={onClose}
|
||||
/>
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -166,7 +166,7 @@ export const FlowProvider = ({
|
||||
}, [nodes]);
|
||||
|
||||
const onFixView = useCallback(() => {
|
||||
const btn = document.querySelector('.react-flow__controls-fitview') as HTMLButtonElement;
|
||||
const btn = document.querySelector('.custom-workflow-fix_view') as HTMLButtonElement;
|
||||
|
||||
setTimeout(() => {
|
||||
btn && btn.click();
|
||||
|
||||
@@ -16,13 +16,17 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { ChevronRightIcon } from '@chakra-ui/icons';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import Divider from '../modules/Divider';
|
||||
import RenderToolInput from '../render/RenderToolInput';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal'));
|
||||
|
||||
@@ -31,7 +35,7 @@ const NodeLaf = (props: NodeProps<FlowModuleItemType>) => {
|
||||
const { toast } = useToast();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { data, selected } = props;
|
||||
const { moduleId, inputs } = data;
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
const requestUrl = inputs.find((item) => item.key === ModuleInputKeyEnum.httpReqUrl);
|
||||
|
||||
@@ -49,7 +53,11 @@ const NodeLaf = (props: NodeProps<FlowModuleItemType>) => {
|
||||
);
|
||||
}
|
||||
|
||||
const { data: lafData, isLoading: isLoadingFunctions } = useQuery(
|
||||
const {
|
||||
data: lafData,
|
||||
isLoading: isLoadingFunctions,
|
||||
refetch: refetchFunction
|
||||
} = useQuery(
|
||||
['getLafFunctionList'],
|
||||
async () => {
|
||||
// load laf app detail
|
||||
@@ -94,61 +102,99 @@ const NodeLaf = (props: NodeProps<FlowModuleItemType>) => {
|
||||
[lafFunctionSelectList, requestUrl?.value]
|
||||
);
|
||||
|
||||
const onSyncParams = useCallback(() => {
|
||||
const lafFunction = lafData?.lafFunctions.find((item) => item.requestUrl === selectedFunction);
|
||||
const { mutate: onSyncParams, isLoading: isSyncing } = useRequest({
|
||||
mutationFn: async () => {
|
||||
await refetchFunction();
|
||||
const lafFunction = lafData?.lafFunctions.find(
|
||||
(item) => item.requestUrl === selectedFunction
|
||||
);
|
||||
|
||||
if (!lafFunction) return;
|
||||
if (!lafFunction) return;
|
||||
|
||||
const bodyParams =
|
||||
lafFunction?.request?.content?.['application/json']?.schema?.properties || {};
|
||||
const bodyParams =
|
||||
lafFunction?.request?.content?.['application/json']?.schema?.properties || {};
|
||||
|
||||
const requiredParams =
|
||||
lafFunction?.request?.content?.['application/json']?.schema?.required || [];
|
||||
const requiredParams =
|
||||
lafFunction?.request?.content?.['application/json']?.schema?.required || [];
|
||||
|
||||
const allParams = [
|
||||
...Object.keys(bodyParams).map((key) => ({
|
||||
name: key,
|
||||
desc: bodyParams[key].description,
|
||||
required: requiredParams?.includes(key) || false,
|
||||
value: `{{${key}}}`,
|
||||
type: 'string'
|
||||
}))
|
||||
].filter((item) => !inputs.find((input) => input.key === item.name));
|
||||
const allParams = [
|
||||
...Object.keys(bodyParams).map((key) => ({
|
||||
name: key,
|
||||
desc: bodyParams[key].description,
|
||||
required: requiredParams?.includes(key) || false,
|
||||
value: `{{${key}}}`,
|
||||
type: 'string'
|
||||
}))
|
||||
].filter((item) => !inputs.find((input) => input.key === item.name));
|
||||
|
||||
// add params
|
||||
allParams.forEach((param) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: param.name,
|
||||
value: {
|
||||
// add params
|
||||
allParams.forEach((param) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: param.name,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
label: param.name,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
required: param.required,
|
||||
description: param.desc || '',
|
||||
toolDescription: param.desc || '未设置参数描述',
|
||||
edit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: true,
|
||||
isToolInput: true
|
||||
},
|
||||
connected: false
|
||||
}
|
||||
value: {
|
||||
key: param.name,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
label: param.name,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
required: param.required,
|
||||
description: param.desc || '',
|
||||
toolDescription: param.desc || '未设置参数描述',
|
||||
edit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: true,
|
||||
isToolInput: true
|
||||
},
|
||||
connected: false
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('common.Sync success')
|
||||
});
|
||||
}, [inputs, lafData?.lafFunctions, moduleId, selectedFunction, t, toast]);
|
||||
const responseParams =
|
||||
lafFunction?.response?.default.content?.['application/json'].schema.properties || {};
|
||||
const requiredResponseParams =
|
||||
lafFunction?.response?.default.content?.['application/json'].schema.required || [];
|
||||
|
||||
const allResponseParams = [
|
||||
...Object.keys(responseParams).map((key) => ({
|
||||
valueType: responseParams[key].type,
|
||||
name: key,
|
||||
desc: responseParams[key].description,
|
||||
required: requiredResponseParams?.includes(key) || false
|
||||
}))
|
||||
].filter((item) => !outputs.find((output) => output.key === item.name));
|
||||
allResponseParams.forEach((param) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
key: param.name,
|
||||
value: {
|
||||
key: param.name,
|
||||
valueType: param.valueType,
|
||||
label: param.name,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
required: param.required,
|
||||
description: param.desc || '',
|
||||
edit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
description: true,
|
||||
dataType: true,
|
||||
defaultValue: true
|
||||
},
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
successToast: t('common.Sync success')
|
||||
});
|
||||
|
||||
return (
|
||||
<NodeCard minW={'350px'} selected={selected} {...data}>
|
||||
@@ -174,9 +220,9 @@ const NodeLaf = (props: NodeProps<FlowModuleItemType>) => {
|
||||
{/* auto set params and go to edit */}
|
||||
{!!selectedFunction && (
|
||||
<Flex justifyContent={'flex-end'} mt={2} gap={2}>
|
||||
{/* <Button variant={'whiteBase'} size={'sm'} onClick={onSyncParams}>
|
||||
<Button isLoading={isSyncing} variant={'grayBase'} size={'sm'} onClick={onSyncParams}>
|
||||
{t('core.module.Laf sync params')}
|
||||
</Button> */}
|
||||
</Button>
|
||||
<Button
|
||||
variant={'grayBase'}
|
||||
size={'sm'}
|
||||
@@ -186,7 +232,7 @@ const NodeLaf = (props: NodeProps<FlowModuleItemType>) => {
|
||||
);
|
||||
|
||||
if (!lafFunction) return;
|
||||
const url = `${feConfigs.lafEnv}/app/${lafData?.lafApp?.appid}/function${lafFunction?.path}`;
|
||||
const url = `${feConfigs.lafEnv}/app/${lafData?.lafApp?.appid}/function${lafFunction?.path}?templateid=fastgptflow`;
|
||||
window.open(url, '_blank');
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -7,16 +7,18 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { welcomeTextTip } from '@fastgpt/global/core/module/template/tip';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import VariableEdit from '../modules/VariableEdit';
|
||||
import VariableEdit from '../../../../app/VariableEdit';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import Container from '../modules/Container';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
|
||||
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
||||
import QGSwitch from '@/components/core/app/QGSwitch';
|
||||
import TTSSelect from '@/components/core/app/TTSSelect';
|
||||
import WhisperConfig from '@/components/core/app/WhisperConfig';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TTSTypeEnum } from '@/constants/app';
|
||||
|
||||
const NodeUserGuide = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const theme = useTheme();
|
||||
@@ -31,6 +33,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
<Box pt={3} borderTop={theme.borders.base}>
|
||||
<TTSGuide data={data} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||
<WhisperGuide data={data} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||
<QuestionGuide data={data} />
|
||||
</Box>
|
||||
@@ -164,3 +169,26 @@ function TTSGuide({ data }: { data: FlowModuleItemType }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function WhisperGuide({ data }: { data: FlowModuleItemType }) {
|
||||
const { inputs, moduleId } = data;
|
||||
const { ttsConfig, whisperConfig } = splitGuideModule({ inputs } as ModuleItemType);
|
||||
|
||||
return (
|
||||
<WhisperConfig
|
||||
isOpenAudio={ttsConfig.type !== TTSTypeEnum.none}
|
||||
value={whisperConfig}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: ModuleInputKeyEnum.whisper,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.whisper),
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ const NodeCard = (props: Props) => {
|
||||
}}
|
||||
>
|
||||
{Header}
|
||||
{children}
|
||||
<Box className="nowheel">{children}</Box>
|
||||
{RenderModal}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -3,8 +3,11 @@ import ReactFlow, {
|
||||
Background,
|
||||
Connection,
|
||||
Controls,
|
||||
ControlButton,
|
||||
MiniMap,
|
||||
NodeProps,
|
||||
ReactFlowProvider
|
||||
ReactFlowProvider,
|
||||
useReactFlow
|
||||
} from 'reactflow';
|
||||
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
@@ -20,6 +23,8 @@ import 'reactflow/dist/style.css';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
|
||||
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
|
||||
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
|
||||
@@ -54,19 +59,10 @@ const edgeTypes = {
|
||||
const Container = React.memo(function Container() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect } =
|
||||
useFlowProviderStore();
|
||||
|
||||
const memoRenderTools = useMemo(
|
||||
() => (
|
||||
<>
|
||||
<Background />
|
||||
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
|
||||
</>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const customOnConnect = useCallback(
|
||||
(connect: Connection) => {
|
||||
if (!connect.sourceHandle || !connect.targetHandle) {
|
||||
@@ -105,7 +101,7 @@ const Container = React.memo(function Container() {
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={customOnConnect}
|
||||
>
|
||||
{memoRenderTools}
|
||||
<FlowController />
|
||||
</ReactFlow>
|
||||
);
|
||||
});
|
||||
@@ -168,3 +164,40 @@ const Flow = ({ Header, ...data }: { Header: React.ReactNode }) => {
|
||||
};
|
||||
|
||||
export default React.memo(Flow);
|
||||
|
||||
const FlowController = React.memo(function FlowController() {
|
||||
const { fitView } = useReactFlow();
|
||||
return (
|
||||
<>
|
||||
<MiniMap
|
||||
style={{
|
||||
height: 78,
|
||||
width: 126,
|
||||
marginBottom: 35
|
||||
}}
|
||||
pannable
|
||||
/>
|
||||
<Controls
|
||||
position={'bottom-right'}
|
||||
style={{
|
||||
display: 'flex',
|
||||
marginBottom: 5,
|
||||
background: 'white',
|
||||
borderRadius: '6px',
|
||||
overflow: 'hidden',
|
||||
boxShadow:
|
||||
'0px 0px 1px 0px rgba(19, 51, 107, 0.20), 0px 12px 16px -4px rgba(19, 51, 107, 0.20)'
|
||||
}}
|
||||
showInteractive={false}
|
||||
showFitView={false}
|
||||
>
|
||||
<MyTooltip label={'页面居中'}>
|
||||
<ControlButton className="custom-workflow-fix_view" onClick={() => fitView()}>
|
||||
<MyIcon name={'core/modules/fixview'} w={'14px'} />
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
</Controls>
|
||||
<Background />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,11 +13,13 @@ import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import type { LafAccountType } from '@fastgpt/global/support/user/team/type.d';
|
||||
import { postLafPat2Token, getLafApplications } from '@/web/support/laf/api';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
|
||||
const LafAccountModal = ({
|
||||
defaultData = {
|
||||
token: '',
|
||||
appid: ''
|
||||
appid: '',
|
||||
pat: ''
|
||||
},
|
||||
onClose
|
||||
}: {
|
||||
@@ -100,7 +102,7 @@ const LafAccountModal = ({
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
<Box>{t('support.user.Laf account intro')}</Box>
|
||||
<Box textDecoration={'underline'}>
|
||||
<Link href={`https://doc.laf.run/zh/`} isExternal>
|
||||
<Link href={getDocPath('/docs/workflow/modules/laf/')} isExternal>
|
||||
{t('support.user.Laf account course')}
|
||||
</Link>
|
||||
</Box>
|
||||
@@ -139,7 +141,7 @@ const LafAccountModal = ({
|
||||
onResetForm();
|
||||
putUpdateTeam({
|
||||
teamId: userInfo?.team.teamId || '',
|
||||
lafAccount: { token: '', appid: '' }
|
||||
lafAccount: { token: '', appid: '', pat: '' }
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -171,12 +173,14 @@ const LafAccountModal = ({
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button isLoading={isUpdating} onClick={handleSubmit((data) => onSubmit(data))}>
|
||||
{t('common.Update')}
|
||||
</Button>
|
||||
{appid && (
|
||||
<Button ml={3} isLoading={isUpdating} onClick={handleSubmit((data) => onSubmit(data))}>
|
||||
{t('common.Update')}
|
||||
</Button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
|
||||
2
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
|
||||
import type { AppTTSConfigType } from '@fastgpt/global/core/app/type.d';
|
||||
import { ModuleItemType } from '../module/type';
|
||||
import { AdminFbkType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
||||
|
||||
29
projects/app/src/pages/api/admin/initv471.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
|
||||
|
||||
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
await authCert({ req, authRoot: true });
|
||||
|
||||
// 删除索引
|
||||
await PgClient.query(`DROP INDEX IF EXISTS team_dataset_index;`);
|
||||
await PgClient.query(`DROP INDEX IF EXISTS team_collection_index;`);
|
||||
await PgClient.query(`DROP INDEX IF EXISTS team_id_index;`);
|
||||
|
||||
jsonRes(res, {
|
||||
message: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
|
||||
/*
|
||||
1. get tts from chatItem store
|
||||
2. get tts from ai
|
||||
3. save tts to chatItem store if chatItemId is provided
|
||||
4. push bill
|
||||
*/
|
||||
|
||||
@@ -34,6 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
throw new Error('voice not found');
|
||||
}
|
||||
|
||||
/* get audio from buffer */
|
||||
const ttsBuffer = await MongoTTSBuffer.findOne(
|
||||
{
|
||||
bufferId: voiceData.bufferId,
|
||||
@@ -46,6 +46,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.end(new Uint8Array(ttsBuffer.buffer.buffer));
|
||||
}
|
||||
|
||||
/* request audio */
|
||||
await text2Speech({
|
||||
res,
|
||||
input,
|
||||
@@ -54,6 +55,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
speed: ttsConfig.speed,
|
||||
onSuccess: async ({ model, buffer }) => {
|
||||
try {
|
||||
/* bill */
|
||||
pushAudioSpeechUsage({
|
||||
model: model,
|
||||
charsLength: input.length,
|
||||
@@ -62,6 +64,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
source: authType2UsageSource({ authType })
|
||||
});
|
||||
|
||||
/* create buffer */
|
||||
await MongoTTSBuffer.create({
|
||||
bufferId: voiceData.bufferId,
|
||||
text: JSON.stringify({ text: input, speed: ttsConfig.speed }),
|
||||
|
||||
@@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
teamId,
|
||||
datasetId: collection.datasetId._id,
|
||||
collectionId,
|
||||
fields: '_id teamId fileId metadata'
|
||||
fields: '_id teamId datasetId fileId metadata'
|
||||
});
|
||||
|
||||
// delete
|
||||
|
||||
@@ -70,6 +70,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
// Duplicate data check
|
||||
await hasSameValue({
|
||||
teamId,
|
||||
datasetId,
|
||||
collectionId,
|
||||
q: formatQ,
|
||||
a: formatA
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { GetDatasetDataListProps } from '@/global/core/api/datasetReq';
|
||||
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { PagingData } from '@/types';
|
||||
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -28,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
per: 'r'
|
||||
});
|
||||
|
||||
searchText = searchText.replace(/'/g, '');
|
||||
searchText = replaceRegChars(searchText).replace(/'/g, '');
|
||||
|
||||
const match = {
|
||||
teamId,
|
||||
|
||||
@@ -7,6 +7,9 @@ import fs from 'fs';
|
||||
import { getAIApi } from '@fastgpt/service/core/ai/config';
|
||||
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
|
||||
const upload = getUploadModel({
|
||||
maxSize: 2
|
||||
@@ -18,21 +21,21 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
try {
|
||||
const {
|
||||
file,
|
||||
data: { duration, teamId: spaceTeamId, teamToken }
|
||||
} = await upload.doUpload<{
|
||||
duration: number;
|
||||
shareId?: string;
|
||||
teamId?: string;
|
||||
teamToken?: string;
|
||||
}>(req, res);
|
||||
data: { appId, duration, shareId, outLinkUid, teamId: spaceTeamId, teamToken }
|
||||
} = await upload.doUpload<
|
||||
OutLinkChatAuthProps & {
|
||||
appId: string;
|
||||
duration: number;
|
||||
}
|
||||
>(req, res);
|
||||
|
||||
req.body.shareId = shareId;
|
||||
req.body.outLinkUid = outLinkUid;
|
||||
req.body.teamId = spaceTeamId;
|
||||
req.body.teamToken = teamToken;
|
||||
|
||||
filePaths = [file.path];
|
||||
|
||||
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
|
||||
|
||||
if (!global.whisperModel) {
|
||||
throw new Error('whisper model not found');
|
||||
}
|
||||
@@ -41,6 +44,18 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
throw new Error('file not found');
|
||||
}
|
||||
|
||||
// auth role
|
||||
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
|
||||
// auth app
|
||||
const app = await MongoApp.findById(appId, 'modules').lean();
|
||||
if (!app) {
|
||||
throw new Error('app not found');
|
||||
}
|
||||
const { whisperConfig } = splitGuideModule(getGuideModule(app?.modules));
|
||||
if (!whisperConfig?.open) {
|
||||
throw new Error('Whisper is not open in the app');
|
||||
}
|
||||
|
||||
const ai = getAIApi();
|
||||
|
||||
const result = await ai.audio.transcriptions.create({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
@@ -18,6 +17,7 @@ import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import MyMenu from '@/components/MyMenu';
|
||||
|
||||
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
|
||||
|
||||
@@ -143,73 +143,61 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
{app.name}
|
||||
</Box>
|
||||
|
||||
<MyTooltip label={t('app.Import Configs')}>
|
||||
<IconButton
|
||||
mr={[3, 6]}
|
||||
size={'smSquare'}
|
||||
icon={<MyIcon name={'common/importLight'} w={['14px', '16px']} />}
|
||||
variant={'whitePrimary'}
|
||||
aria-label={'save'}
|
||||
onClick={onOpenImport}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={t('app.Export Configs')}>
|
||||
<IconButton
|
||||
mr={[3, 6]}
|
||||
icon={<MyIcon name={'export'} w={['14px', '16px']} />}
|
||||
size={'smSquare'}
|
||||
variant={'whitePrimary'}
|
||||
aria-label={'save'}
|
||||
onClick={async () => {
|
||||
const modules = await flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
copyData(filterExportModules(modules), t('app.Export Config Successful'));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
|
||||
{testModules ? (
|
||||
<IconButton
|
||||
mr={[3, 6]}
|
||||
icon={<SmallCloseIcon fontSize={'25px'} />}
|
||||
variant={'whitePrimary'}
|
||||
size={'smSquare'}
|
||||
aria-label={''}
|
||||
onClick={() => setTestModules(undefined)}
|
||||
/>
|
||||
) : (
|
||||
<MyTooltip label={t('core.Chat test')}>
|
||||
<MyMenu
|
||||
Button={
|
||||
<IconButton
|
||||
mr={[3, 6]}
|
||||
icon={<MyIcon name={'core/chat/chatLight'} w={['14px', '16px']} />}
|
||||
size={'smSquare'}
|
||||
aria-label={'save'}
|
||||
mr={[3, 5]}
|
||||
icon={<MyIcon name={'more'} w={'14px'} p={2} />}
|
||||
aria-label={''}
|
||||
size={'sm'}
|
||||
variant={'whitePrimary'}
|
||||
onClick={async () => {
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
{ label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport },
|
||||
{
|
||||
label: t('app.Export Configs'),
|
||||
icon: 'export',
|
||||
onClick: async () => {
|
||||
const modules = await flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
setTestModules(modules);
|
||||
copyData(filterExportModules(modules), t('app.Export Config Successful'));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<MyTooltip label={t('common.Save')}>
|
||||
<IconButton
|
||||
icon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
|
||||
size={'smSquare'}
|
||||
isLoading={isSaving}
|
||||
aria-label={'save'}
|
||||
{!testModules && (
|
||||
<Button
|
||||
mr={[3, 5]}
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'core/chat/chatLight'} w={['14px', '16px']} />}
|
||||
variant={'whitePrimary'}
|
||||
onClick={async () => {
|
||||
const modules = await flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
onclickSave(modules);
|
||||
setTestModules(modules);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
>
|
||||
{t('core.Chat test')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
size={'sm'}
|
||||
isLoading={isSaving}
|
||||
leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
|
||||
onClick={async () => {
|
||||
const modules = await flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
onclickSave(modules);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('common.Save')}
|
||||
</Button>
|
||||
</Flex>
|
||||
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
|
||||
<ConfirmModal
|
||||
|
||||
@@ -32,6 +32,7 @@ import MyBox from '@/components/common/MyBox';
|
||||
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
|
||||
import { formatChatValue2InputType } from '@/components/ChatBox/utils';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
const Logs = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -234,6 +235,7 @@ const DetailLogsModal = ({
|
||||
onSuccess(res) {
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || getNanoid(),
|
||||
status: 'finish' as any
|
||||
}));
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
|
||||
@@ -88,7 +88,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
|
||||
src="${linkUrl}"
|
||||
style="width: 100%; height: 100%;"
|
||||
frameborder="0"
|
||||
allow="microphone"
|
||||
allow="*"
|
||||
/>`
|
||||
},
|
||||
[UsingWayEnum.script]: {
|
||||
@@ -102,7 +102,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
|
||||
data-open-icon="${getValues('scriptOpenIcon')}"
|
||||
data-close-icon="${getValues('scriptCloseIcon')}"
|
||||
defer
|
||||
/>
|
||||
></script>
|
||||
<script>
|
||||
console.log("Chat box loaded")
|
||||
</script>`
|
||||
|
||||
@@ -99,6 +99,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
|
||||
<Box flex={1}>
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
appId={appDetail._id}
|
||||
appAvatar={appDetail.avatar}
|
||||
userAvatar={userInfo?.avatar}
|
||||
showMarkIcon
|
||||
|
||||