Compare commits
13 Commits
v4.6.9-alp
...
v4.7-alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9501c3f3a1 | ||
|
|
5bca15f12f | ||
|
|
753b164ea2 | ||
|
|
64492b8b33 | ||
|
|
80bdbf6bce | ||
|
|
9f0b1d6bad | ||
|
|
c44b944fa6 | ||
|
|
61419a5676 | ||
|
|
47227c1b84 | ||
|
|
4d66e0f828 | ||
|
|
46d9a6461f | ||
|
|
b74dd2341b | ||
|
|
af581bc903 |
120
.github/workflows/docs-image.yml
vendored
@@ -8,82 +8,67 @@ on:
|
||||
- 'main'
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
build-fastgpt-docs-images:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get current date and time
|
||||
id: datetime
|
||||
run: echo "datetime=$(date +'%Y%m%d%H%M%S')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Set up QEMU (optional)
|
||||
uses: docker/setup-qemu-action@v2
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
${{ secrets.DOCKER_HUB_NAME }}/fastgpt-docs
|
||||
ghcr.io/${{ github.repository_owner }}/fastgpt-docs
|
||||
registry.cn-hangzhou.aliyuncs.com/${{ secrets.ALI_HUB_USERNAME }}/fastgpt-docs
|
||||
tags: |
|
||||
${{ steps.datetime.outputs.datetime }}
|
||||
flavor: latest=false
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
driver-opts: network=host
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
username: ${{ secrets.DOCKER_HUB_NAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GH_PAT }}
|
||||
- name: Set DOCKER_REPO_TAGGED based on branch or tag
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-docs:latest" >> $GITHUB_ENV
|
||||
else
|
||||
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-docs:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Build and publish image for main branch or tag push event
|
||||
env:
|
||||
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
|
||||
run: |
|
||||
docker buildx build \
|
||||
--build-arg name=app \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \
|
||||
--label "org.opencontainers.image.description=fastgpt image" \
|
||||
--label "org.opencontainers.image.licenses=Apache" \
|
||||
--push \
|
||||
--cache-from=type=local,src=/tmp/.buildx-cache \
|
||||
--cache-to=type=local,dest=/tmp/.buildx-cache \
|
||||
-t ${DOCKER_REPO_TAGGED} \
|
||||
-f docSite/Dockerfile \
|
||||
.
|
||||
push-to-docker-hub:
|
||||
needs: build-fastgpt-docs-images
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'labring/FastGPT'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_NAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
- name: Set DOCKER_REPO_TAGGED based on branch or tag
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Pull image from GitHub Container Registry
|
||||
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt-docs:${{env.IMAGE_TAG}}
|
||||
- name: Tag image with Docker Hub repository name and version tag
|
||||
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt-docs:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
|
||||
- name: Push image to Docker Hub
|
||||
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
|
||||
|
||||
- name: Login to Aliyun
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: registry.cn-hangzhou.aliyuncs.com
|
||||
username: ${{ secrets.ALI_HUB_USERNAME }}
|
||||
password: ${{ secrets.ALI_HUB_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker images to ghcr.io and DockerHub
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./docSite/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs:
|
||||
tags: ${{ steps.datetime.outputs.datetime }}
|
||||
update-docs-image:
|
||||
needs: build-fastgpt-docs-images
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -95,4 +80,9 @@ jobs:
|
||||
env:
|
||||
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
|
||||
with:
|
||||
args: rollout restart deployment fastgpt-docs
|
||||
args: set image deployment/fastgpt-docs fastgpt-docs=registry.cn-hangzhou.aliyuncs.com/${{ secrets.ALI_HUB_USERNAME }}/fastgpt-docs:${{ needs.build-fastgpt-docs-images.outputs.tags }}
|
||||
- uses: actions-hub/kubectl@master
|
||||
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
|
||||
@@ -36,8 +36,9 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
||||
|
||||
## 🛸 在线使用
|
||||
|
||||
- 🌐 国内临时可访问:[fastgpt.in](https://fastgpt.in/)
|
||||
- 🌍 海外版:[fastgpt.run](https://fastgpt.run/)
|
||||
- 🌍 海外版:[fastgpt.in](https://fastgpt.in/)
|
||||
|
||||
fastgpt.run 域名会弃用。
|
||||
|
||||
| | |
|
||||
| ---------------------------------- | ---------------------------------- |
|
||||
|
||||
@@ -9,7 +9,7 @@ weight: 1221
|
||||
|
||||
最后更新时间:2024年3月3日
|
||||
|
||||
我们非常重视您的隐私保护,在您使用FastGPT云服务时,我们将按照以下政策收集、使用、披露和保护您的个人信息。请您仔细阅读并充分理解本隐私政策。
|
||||
我们非常重视您的隐私保护,在您使用FastGPT云服务时(以下简称为“本服务”),我们将按照以下政策收集、使用、披露和保护您的个人信息。请您仔细阅读并充分理解本隐私政策。
|
||||
|
||||
**我们可能需要收集的信息**
|
||||
|
||||
@@ -39,7 +39,7 @@ weight: 1221
|
||||
2. 我们会定期对收集、存储和处理的个人信息进行安全评估,以确保个人信息安全。
|
||||
3. 在发生个人信息泄露等安全事件时,我们会立即启动应急预案,并在法律法规规定的范围内向您及时告知。
|
||||
4. 我们不会使用您的数据进行额外的备份存储或用于模型训练。
|
||||
5. 您在本服务进行的数据删除均为物理删除,不可恢复。如有有非物理删除的操作,我们会在服务中特别指出。
|
||||
5. 您在本服务进行的数据删除均为物理删除,不可恢复。如有非物理删除的操作,我们会在服务中特别指出。
|
||||
|
||||
**用户权利**
|
||||
|
||||
|
||||
@@ -20,14 +20,13 @@ llm模型全部合并
|
||||
```json
|
||||
{
|
||||
"systemEnv": {
|
||||
"openapiPrefix": "fastgpt",
|
||||
"vectorMaxProcess": 15,
|
||||
"qaMaxProcess": 15,
|
||||
"pgHNSWEfSearch": 100
|
||||
"pgHNSWEfSearch": 100 // 向量搜索参数。越大,搜索越精确,但是速度越慢。设置为100,有99%+精度。
|
||||
},
|
||||
"llmModels": [
|
||||
{
|
||||
"model": "gpt-3.5-turbo-1106", // 模型名
|
||||
"model": "gpt-3.5-turbo", // 模型名
|
||||
"name": "gpt-3.5-turbo", // 别名
|
||||
"maxContext": 16000, // 最大上下文
|
||||
"maxResponse": 4000, // 最大回复
|
||||
@@ -37,12 +36,16 @@ llm模型全部合并
|
||||
"censor": false,
|
||||
"vision": false, // 是否支持图片输入
|
||||
"datasetProcess": false, // 是否设置为知识库处理模型(QA),务必保证至少有一个为true,否则知识库会报错
|
||||
"toolChoice": true, // 是否支持工具选择
|
||||
"functionCall": false, // 是否支持函数调用
|
||||
"usedInClassify": true, // 是否用于问题分类(务必保证至少有一个为true)
|
||||
"usedInExtractFields": true, // 是否用于内容提取(务必保证至少有一个为true)
|
||||
"useInToolCall": true, // 是否用于工具调用(务必保证至少有一个为true)
|
||||
"usedInQueryExtension": true, // 是否用于问题优化(务必保证至少有一个为true)
|
||||
"toolChoice": true, // 是否支持工具选择(务必保证至少有一个为true)
|
||||
"functionCall": false, // 是否支持函数调用(特殊功能,会优先使用 toolChoice,如果为false,则使用 functionCall,如果仍为 false,则使用提示词模式)
|
||||
"customCQPrompt": "", // 自定义文本分类提示词(不支持工具和函数调用的模型
|
||||
"customExtractPrompt": "", // 自定义内容提取提示词
|
||||
"defaultSystemChatPrompt": "", // 对话默认携带的系统提示词
|
||||
"defaultConfig":{} // 对话默认配置(比如 GLM4 的 top_p
|
||||
"defaultConfig":{} // LLM默认配置,可以针对不同模型设置特殊值(比如 GLM4 的 top_p
|
||||
},
|
||||
{
|
||||
"model": "gpt-3.5-turbo-16k",
|
||||
@@ -55,6 +58,10 @@ llm模型全部合并
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"datasetProcess": true,
|
||||
"usedInClassify": true,
|
||||
"usedInExtractFields": true,
|
||||
"useInToolCall": true,
|
||||
"usedInQueryExtension": true,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"customCQPrompt": "",
|
||||
@@ -73,6 +80,10 @@ llm模型全部合并
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"datasetProcess": false,
|
||||
"usedInClassify": true,
|
||||
"usedInExtractFields": true,
|
||||
"useInToolCall": true,
|
||||
"usedInQueryExtension": true,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"customCQPrompt": "",
|
||||
@@ -91,6 +102,10 @@ llm模型全部合并
|
||||
"censor": false,
|
||||
"vision": true,
|
||||
"datasetProcess": false,
|
||||
"usedInClassify": false,
|
||||
"usedInExtractFields": false,
|
||||
"useInToolCall": false,
|
||||
"usedInQueryExtension": false,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"customCQPrompt": "",
|
||||
|
||||
@@ -54,11 +54,37 @@ ACCESS_TOKEN=mytoken
|
||||
```
|
||||
|
||||
**运行命令示例**
|
||||
|
||||
- 无需GPU环境,使用CPU运行
|
||||
```sh
|
||||
docker run -d --name reranker -p 6006:6006 -e ACCESS_TOKEN=mytoken luanshaotong/reranker:v0.1
|
||||
```
|
||||
|
||||
- 需要CUDA 11.7环境
|
||||
```sh
|
||||
docker run -d --gpus all --name reranker -p 6006:6006 -e ACCESS_TOKEN=mytoken luanshaotong/reranker:v0.1
|
||||
```
|
||||
|
||||
**docker-compose.yml示例**
|
||||
```
|
||||
version: "3"
|
||||
services:
|
||||
reranker:
|
||||
image: luanshaotong/reranker: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 变量为部署的域名。
|
||||
|
||||
@@ -108,11 +108,6 @@ curl -O https://raw.githubusercontent.com/labring/FastGPT/main/projects/app/data
|
||||
```bash
|
||||
# 进入项目目录
|
||||
cd 项目目录
|
||||
# 创建 mongo 密钥
|
||||
openssl rand -base64 756 > ./mongodb.key
|
||||
# 600不行可以用chmod 999
|
||||
chmod 600 ./mongodb.key
|
||||
chown 999:root ./mongodb.key
|
||||
# 启动容器
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
@@ -128,8 +123,8 @@ docker ps
|
||||
# 进入容器
|
||||
docker exec -it mongo bash
|
||||
|
||||
# 连接数据库
|
||||
mongo -u myname -p mypassword --authenticationDatabase admin
|
||||
# 连接数据库(这里要填Mongo的用户名和密码)
|
||||
mongo -u myusername -p mypassword --authenticationDatabase admin
|
||||
|
||||
# 初始化副本集。如果需要外网访问,mongo:27017 可以改成 ip:27017。但是需要同时修改 FastGPT 连接的参数(MONGODB_URI=mongodb://myname:mypassword@mongo:27017/fastgpt?authSource=admin => MONGODB_URI=mongodb://myname:mypassword@ip:27017/fastgpt?authSource=admin)
|
||||
rs.initiate({
|
||||
@@ -142,8 +137,58 @@ rs.initiate({
|
||||
rs.status()
|
||||
```
|
||||
|
||||
**关于 host: "mongo:27017" 说明**
|
||||
|
||||
1. mongo:27017 代表指向同一个 docker 网络的 mongo 容器的 27017 服务。因此,如果使用该参数,外网是无法访问到数据库的。
|
||||
2. ip:27017 (ip替换成公网IP):代表通过你的公网IP进行访问。如果用该方法,同时需要修改 docker-compose 中 mongo 的连接参数,因为默认是用 `mongo:27017` 进行连接。
|
||||
|
||||
## 五、访问 FastGPT
|
||||
|
||||
目前可以通过 `ip:3000` 直接访问(注意防火墙)。登录用户名为 `root`,密码为`docker-compose.yml`环境变量里设置的 `DEFAULT_ROOT_PSW`。
|
||||
|
||||
如果需要域名访问,请自行安装并配置 Nginx。
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
### Mongo 启动失败
|
||||
|
||||
docker-compose 示例优化 Mongo 副本集参数,不需要手动创建再挂载。如果无法启动,可以尝试更换下面的脚本:
|
||||
|
||||
1. 终端中执行:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 756 > ./mongodb.key
|
||||
chmod 600 ./mongodb.key
|
||||
chown 999:root ./mongodb.key
|
||||
```
|
||||
|
||||
2. 修改 docker-compose.yml:
|
||||
|
||||
```yml
|
||||
mongo:
|
||||
# image: mongo:5.0.18
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云
|
||||
container_name: mongo
|
||||
ports:
|
||||
- 27017:27017
|
||||
networks:
|
||||
- fastgpt
|
||||
command: mongod --keyFile /data/mongodb.key --replSet rs0
|
||||
environment:
|
||||
# 默认的用户名和密码,只有首次允许有效
|
||||
- MONGO_INITDB_ROOT_USERNAME=myusername
|
||||
- MONGO_INITDB_ROOT_PASSWORD=mypassword
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
- ./mongodb.key:/data/mongodb.key
|
||||
```
|
||||
|
||||
3. 重启服务
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
4. 进入容器执行副本集合初始化(看上方)
|
||||
@@ -46,9 +46,14 @@ OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并
|
||||
### 页面崩溃
|
||||
|
||||
1. 关闭翻译
|
||||
2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。(95%)
|
||||
2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。(95%情况,可以F12打开控制台,看具体的空指针情况)
|
||||
3. 某些api不兼容问题(较少)
|
||||
|
||||
### 开启内容补全后,响应速度变慢
|
||||
|
||||
1. 问题补全需要经过一轮AI生成。
|
||||
2. 会进行3~5轮的查询,如果数据库性能不足,会有明显影响。
|
||||
|
||||
## 私有部署问题
|
||||
|
||||
### 知识库索引没有进度
|
||||
@@ -80,7 +85,7 @@ OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并
|
||||
|
||||
### 如何自定义配置文件?
|
||||
|
||||
修改`config.json`文件,并执行`docker-compose up -d`重起容器。具体配置,参考[配置详解](/docs/development/configuration)。
|
||||
修改`config.json`文件,并执行`docker-compose down`再执行`docker-compose up -d`重起容器。具体配置,参考[配置详解](/docs/development/configuration)。
|
||||
|
||||
### 如何检查自定义配置文件是否挂载
|
||||
|
||||
@@ -93,6 +98,12 @@ OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并
|
||||
2. 配置文件不正确,日志中会提示`invalid json`,配置文件需要是标准的 JSON 文件。
|
||||
3. 修改后,没有`docker-compose down`再`docker-compose up -d`,restart是不会重新挂载文件的。
|
||||
|
||||
### 如何检查环境变量是否正常加载
|
||||
|
||||
1. `docker exec -it fastgpt sh` 进入 FastGPT 容器。
|
||||
2. 直接输入`env`命令查看所有环境变量。
|
||||
|
||||
|
||||
### 为什么无法连接`本地模型`镜像。
|
||||
|
||||
`docker-compose.yml`中使用了桥接的模式建立了`fastgpt`网络,如想通过0.0.0.0或镜像名访问其它镜像,需将其它镜像也加入到网络中。
|
||||
|
||||
@@ -120,6 +120,10 @@ CHAT_API_KEY=sk-xxxxxx
|
||||
"censor": false,
|
||||
"vision": false, // 是否支持图片输入
|
||||
"datasetProcess": false, // 是否设置为知识库处理模型
|
||||
"usedInClassify": true, // 是否用于问题分类
|
||||
"usedInExtractFields": true, // 是否用于字段提取
|
||||
"useInToolCall": true, // 是否用于工具调用
|
||||
"usedInQueryExtension": true, // 是否用于问题优化
|
||||
"toolChoice": true, // 是否支持工具选择
|
||||
"functionCall": false, // 是否支持函数调用
|
||||
"customCQPrompt": "", // 自定义文本分类提示词(不支持工具和函数调用的模型
|
||||
|
||||
@@ -169,6 +169,8 @@ curl --location --request POST '{{host}}/shareAuth/start' \
|
||||
|
||||
响应值与[chat 接口格式相同](/docs/development/openapi/chat/#响应),仅多了一个`token`。
|
||||
|
||||
重点关注:`totalPoints`(总消耗AI积分),`token`(Token消耗总数)
|
||||
|
||||
```bash
|
||||
curl --location --request POST '{{host}}/shareAuth/finish' \
|
||||
--header 'Content-Type: application/json' \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.9(进行中)'
|
||||
title: 'V4.6.9(需要初始化)'
|
||||
description: 'FastGPT V4.6.9更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -12,7 +12,7 @@ weight: 827
|
||||
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成自己域名
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/init/v469' \
|
||||
curl --location --request POST 'https://{{host}}/api/admin/initv469' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
@@ -20,15 +20,21 @@ curl --location --request POST 'https://{{host}}/api/init/v469' \
|
||||
1. 重置计量表。
|
||||
2. 执行脏数据清理(清理无效的文件、清理无效的图片、清理无效的知识库集合、清理无效的向量)
|
||||
|
||||
## 外部接口更新
|
||||
|
||||
1. 由于计费系统变更,[分享链接对话上报接口](/docs/development/openapi/share/#5-编写对话结果上报接口可选)需要做一些调整,price字段被totalPoints字段取代。inputToken和outputToken不再提供,只提供`token`字段(总token数量)。
|
||||
|
||||
## V4.6.9 更新说明
|
||||
|
||||
1. 商业版新增 - 知识库新增“增强处理”训练模式,可生成更多类型索引。
|
||||
2. 新增 - 完善了HTTP模块的变量提示。
|
||||
3. 新增 - HTTP模块支持OpenAI单接口导入。
|
||||
4. 优化 - 问题补全。增加英文类型。同时可以设置为单独模块,方便复用。
|
||||
5. 优化 - 重写了计量模式
|
||||
6. 优化 - Token 过滤历史记录,保持偶数条,防止部分模型报错。
|
||||
7. 优化 - 分享链接SEO,可直接展示应用名和头像。
|
||||
8. 修复 - 标注功能。
|
||||
9. 修复 - qa生成线程计数错误。
|
||||
4. 新增 - 全局变量支持增加外部变量。可通过分享链接的Query或 API 的 variables 参数传入。
|
||||
5. 新增 - 内容提取模块增加默认值。
|
||||
6. 优化 - 问题补全。增加英文类型。同时可以设置为单独模块,方便复用。
|
||||
7. 优化 - 重写了计量模式
|
||||
8. 优化 - Token 过滤历史记录,保持偶数条,防止部分模型报错。
|
||||
9. 优化 - 分享链接SEO,可直接展示应用名和头像。
|
||||
10. 修复 - 标注功能。
|
||||
11. 修复 - qa生成线程计数错误。
|
||||
12. 修复 - 问题分类连线类型错误
|
||||
|
||||
19
docSite/content/docs/development/upgrading/47.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: 'V4.7(进行中)'
|
||||
description: 'FastGPT V4.7更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 826
|
||||
---
|
||||
|
||||
## 修改配置文件
|
||||
|
||||
增加一些 Boolean 值,用于决定不同功能块可以使用哪些模型:[点击查看最新的配置文件](/docs/development/configuration/)
|
||||
|
||||
|
||||
## V4.7 更新说明
|
||||
|
||||
1. 新增 - 工具调用模块,可以让LLM模型根据用户意图,动态的选择其他模型或插件执行。
|
||||
2. 新增 - 分类和内容提取支持 functionCall 模式。部分模型支持 functionCall 不支持 ToolCall,也可以使用了。需要把 LLM 模型配置文件里的 `functionCall` 设置为 `true`, `toolChoice`设置为 `false`。如果 `toolChoice` 为 true,会走 tool 模式。
|
||||
3. 优化 - 高级编排性能
|
||||
@@ -112,7 +112,7 @@ defaultContentLanguage = 'zh-cn'
|
||||
# Link behaviour
|
||||
intLinkTooltip = true # Enable a tooltip for internal links that displays info about the destination? default false
|
||||
# extLinkNewTab = false # Open external links in a new Tab? default true
|
||||
# logoLinkURL = "" # Set a custom URL destination for the top header logo link.
|
||||
logoLinkURL = "https://fastgpt.in/" # Set a custom URL destination for the top header logo link.
|
||||
|
||||
[params.flexsearch] # Parameters for FlexSearch
|
||||
# enabled = true
|
||||
|
||||
@@ -19,21 +19,27 @@ services:
|
||||
volumes:
|
||||
- ./pg/data:/var/lib/postgresql/data
|
||||
mongo:
|
||||
image: mongo:5.0.18
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云
|
||||
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18
|
||||
container_name: mongo
|
||||
restart: always
|
||||
ports:
|
||||
- 27017:27017
|
||||
networks:
|
||||
- fastgpt
|
||||
command: mongod --keyFile /data/mongodb.key --replSet rs0
|
||||
environment:
|
||||
# 默认的用户名和密码,只有首次允许有效
|
||||
- MONGO_INITDB_ROOT_USERNAME=myname
|
||||
- MONGO_INITDB_ROOT_USERNAME=myusername
|
||||
- MONGO_INITDB_ROOT_PASSWORD=mypassword
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
- ./mongodb.key:/data/mongodb.key
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
- |
|
||||
openssl rand -base64 128 > /data/mongodb.key
|
||||
chmod 400 /data/mongodb.key
|
||||
chown 999:999 /data/mongodb.key
|
||||
exec docker-entrypoint.sh $$@
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:latest # git
|
||||
@@ -56,8 +62,8 @@ services:
|
||||
- TOKEN_KEY=any
|
||||
- ROOT_KEY=root_key
|
||||
- FILE_TOKEN_KEY=filetoken
|
||||
# mongo 配置,不需要改. 用户名myname,密码mypassword。
|
||||
- MONGODB_URI=mongodb://myname:mypassword@mongo:27017/fastgpt?authSource=admin
|
||||
# mongo 配置,不需要改. 用户名myusername,密码mypassword。
|
||||
- MONGODB_URI=mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin
|
||||
# pg配置. 不需要改
|
||||
- PG_URL=postgresql://username:password@pg:5432/postgres
|
||||
volumes:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"prepare": "husky install",
|
||||
"format-code": "prettier --config \"./.prettierrc.js\" --write \"./**/src/**/*.{ts,tsx,scss}\"",
|
||||
"format-doc": "zhlint --dir ./docSite *.md --fix",
|
||||
"gen:theme-typings": "chakra-cli tokens projects/app/src/web/styles/theme.ts --out node_modules/.pnpm/node_modules/@chakra-ui/styled-system/dist/theming.types.d.ts",
|
||||
"gen:theme-typings": "chakra-cli tokens packages/web/styles/theme.ts --out node_modules/.pnpm/node_modules/@chakra-ui/styled-system/dist/theming.types.d.ts",
|
||||
"postinstall": "sh ./scripts/postinstall.sh",
|
||||
"initIcon": "node ./scripts/icon/init.js",
|
||||
"previewIcon": "node ./scripts/icon/index.js"
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
/* Only the token of gpt-3.5-turbo is used */
|
||||
import type { ChatItemType } from '../../../core/chat/type';
|
||||
import { Tiktoken } from 'js-tiktoken/lite';
|
||||
import { adaptChat2GptMessages } from '../../../core/chat/adapt';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../../core/ai/constant';
|
||||
import { chats2GPTMessages } from '../../../core/chat/adapt';
|
||||
import encodingJson from './cl100k_base.json';
|
||||
import { ChatMessageItemType } from '../../../core/ai/type';
|
||||
import {
|
||||
ChatCompletionMessageParam,
|
||||
ChatCompletionContentPart,
|
||||
ChatCompletionCreateParams,
|
||||
ChatCompletionTool
|
||||
} from '../../../core/ai/type';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../../core/ai/constants';
|
||||
|
||||
/* init tikToken obj */
|
||||
export function getTikTokenEnc() {
|
||||
@@ -29,18 +34,25 @@ export function getTikTokenEnc() {
|
||||
|
||||
/* count one prompt tokens */
|
||||
export function countPromptTokens(
|
||||
prompt = '',
|
||||
role: '' | `${ChatCompletionRequestMessageRoleEnum}` = '',
|
||||
tools?: any
|
||||
prompt: string | ChatCompletionContentPart[] | null | undefined = '',
|
||||
role: '' | `${ChatCompletionRequestMessageRoleEnum}` = ''
|
||||
) {
|
||||
const enc = getTikTokenEnc();
|
||||
const toolText = tools
|
||||
? JSON.stringify(tools)
|
||||
.replace('"', '')
|
||||
.replace('\n', '')
|
||||
.replace(/( ){2,}/g, ' ')
|
||||
: '';
|
||||
const text = `${role}\n${prompt}\n${toolText}`.trim();
|
||||
const promptText = (() => {
|
||||
if (!prompt) return '';
|
||||
if (typeof prompt === 'string') return prompt;
|
||||
let promptText = '';
|
||||
prompt.forEach((item) => {
|
||||
if (item.type === 'text') {
|
||||
promptText += item.text;
|
||||
} else if (item.type === 'image_url') {
|
||||
promptText += item.image_url.url;
|
||||
}
|
||||
});
|
||||
return promptText;
|
||||
})();
|
||||
|
||||
const text = `${role}\n${promptText}`.trim();
|
||||
|
||||
try {
|
||||
const encodeText = enc.encode(text);
|
||||
@@ -50,15 +62,66 @@ export function countPromptTokens(
|
||||
return text.length;
|
||||
}
|
||||
}
|
||||
export const countToolsTokens = (
|
||||
tools?: ChatCompletionTool[] | ChatCompletionCreateParams.Function[]
|
||||
) => {
|
||||
if (!tools || tools.length === 0) return 0;
|
||||
|
||||
const enc = getTikTokenEnc();
|
||||
|
||||
const toolText = tools
|
||||
? JSON.stringify(tools)
|
||||
.replace('"', '')
|
||||
.replace('\n', '')
|
||||
.replace(/( ){2,}/g, ' ')
|
||||
: '';
|
||||
|
||||
return enc.encode(toolText).length;
|
||||
};
|
||||
|
||||
/* count messages tokens */
|
||||
export const countMessagesTokens = (messages: ChatItemType[], tools?: any) => {
|
||||
const adaptMessages = adaptChat2GptMessages({ messages, reserveId: true });
|
||||
export const countMessagesTokens = (messages: ChatItemType[]) => {
|
||||
const adaptMessages = chats2GPTMessages({ messages, reserveId: true });
|
||||
|
||||
return countGptMessagesTokens(adaptMessages, tools);
|
||||
return countGptMessagesTokens(adaptMessages);
|
||||
};
|
||||
export const countGptMessagesTokens = (messages: ChatMessageItemType[], tools?: any) =>
|
||||
messages.reduce((sum, item) => sum + countPromptTokens(item.content, item.role, tools), 0);
|
||||
export const countGptMessagesTokens = (
|
||||
messages: ChatCompletionMessageParam[],
|
||||
tools?: ChatCompletionTool[],
|
||||
functionCall?: ChatCompletionCreateParams.Function[]
|
||||
) =>
|
||||
messages.reduce((sum, item) => {
|
||||
// Evaluates the text of toolcall and functioncall
|
||||
const functionCallPrompt = (() => {
|
||||
let prompt = '';
|
||||
if (item.role === ChatCompletionRequestMessageRoleEnum.Assistant) {
|
||||
const toolCalls = item.tool_calls;
|
||||
prompt +=
|
||||
toolCalls
|
||||
?.map((item) => `${item?.function?.name} ${item?.function?.arguments}`.trim())
|
||||
?.join('') || '';
|
||||
|
||||
const functionCall = item.function_call;
|
||||
prompt += `${functionCall?.name} ${functionCall?.arguments}`.trim();
|
||||
}
|
||||
return prompt;
|
||||
})();
|
||||
|
||||
const contentPrompt = (() => {
|
||||
if (!item.content) return '';
|
||||
if (typeof item.content === 'string') return item.content;
|
||||
return item.content
|
||||
.map((item) => {
|
||||
if (item.type === 'text') return item.text;
|
||||
return '';
|
||||
})
|
||||
.join('');
|
||||
})();
|
||||
|
||||
return sum + countPromptTokens(`${contentPrompt}${functionCallPrompt}`, item.role);
|
||||
}, 0) +
|
||||
countToolsTokens(tools) +
|
||||
countToolsTokens(functionCall);
|
||||
|
||||
/* slice messages from top to bottom by maxTokens */
|
||||
export function sliceMessagesTB({
|
||||
@@ -68,7 +131,7 @@ export function sliceMessagesTB({
|
||||
messages: ChatItemType[];
|
||||
maxTokens: number;
|
||||
}) {
|
||||
const adaptMessages = adaptChat2GptMessages({ messages, reserveId: true });
|
||||
const adaptMessages = chats2GPTMessages({ messages, reserveId: true });
|
||||
let reduceTokens = maxTokens;
|
||||
let result: ChatItemType[] = [];
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ export type SystemEnvType = {
|
||||
vectorMaxProcess: number;
|
||||
qaMaxProcess: number;
|
||||
pgHNSWEfSearch: number;
|
||||
oneapiUrl?: string;
|
||||
chatApiKey?: string;
|
||||
};
|
||||
|
||||
// declare global {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export enum ChatCompletionRequestMessageRoleEnum {
|
||||
'System' = 'system',
|
||||
'User' = 'user',
|
||||
'Assistant' = 'assistant',
|
||||
'Function' = 'function',
|
||||
'Tool' = 'tool'
|
||||
}
|
||||
27
packages/global/core/ai/constants.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export enum ChatCompletionRequestMessageRoleEnum {
|
||||
'System' = 'system',
|
||||
'User' = 'user',
|
||||
'Assistant' = 'assistant',
|
||||
'Function' = 'function',
|
||||
'Tool' = 'tool'
|
||||
}
|
||||
|
||||
export enum ChatMessageTypeEnum {
|
||||
text = 'text',
|
||||
image_url = 'image_url'
|
||||
}
|
||||
|
||||
export enum LLMModelTypeEnum {
|
||||
all = 'all',
|
||||
classify = 'classify',
|
||||
extractFields = 'extractFields',
|
||||
toolCall = 'toolCall',
|
||||
queryExtension = 'queryExtension'
|
||||
}
|
||||
export const llmModelTypeFilterMap = {
|
||||
[LLMModelTypeEnum.all]: 'model',
|
||||
[LLMModelTypeEnum.classify]: 'usedInClassify',
|
||||
[LLMModelTypeEnum.extractFields]: 'usedInExtractFields',
|
||||
[LLMModelTypeEnum.toolCall]: 'usedInToolCall',
|
||||
[LLMModelTypeEnum.queryExtension]: 'usedInQueryExtension'
|
||||
};
|
||||
8
packages/global/core/ai/model.d.ts
vendored
@@ -10,7 +10,13 @@ export type LLMModelItemType = {
|
||||
|
||||
censor?: boolean;
|
||||
vision?: boolean;
|
||||
datasetProcess?: boolean;
|
||||
|
||||
// diff function model
|
||||
datasetProcess?: boolean; // dataset
|
||||
usedInClassify?: boolean; // classify
|
||||
usedInExtractFields?: boolean; // extract fields
|
||||
usedInToolCall?: boolean; // tool call
|
||||
usedInQueryExtension?: boolean; // query extension
|
||||
|
||||
functionCall: boolean;
|
||||
toolChoice: boolean;
|
||||
|
||||
36
packages/global/core/ai/type.d.ts
vendored
@@ -1,20 +1,33 @@
|
||||
import openai from 'openai';
|
||||
import type {
|
||||
ChatCompletion,
|
||||
ChatCompletionCreateParams,
|
||||
ChatCompletionMessageToolCall,
|
||||
ChatCompletionChunk,
|
||||
ChatCompletionMessageParam,
|
||||
ChatCompletionContentPart
|
||||
ChatCompletionToolMessageParam,
|
||||
ChatCompletionAssistantMessageParam
|
||||
} from 'openai/resources';
|
||||
import { ChatMessageTypeEnum } from './constants';
|
||||
|
||||
export type ChatCompletionContentPart = ChatCompletionContentPart;
|
||||
export type ChatCompletionCreateParams = ChatCompletionCreateParams;
|
||||
export type ChatMessageItemType = Omit<ChatCompletionMessageParam, 'name'> & {
|
||||
name?: any;
|
||||
export * from 'openai/resources';
|
||||
|
||||
export type ChatCompletionMessageParam = ChatCompletionMessageParam & {
|
||||
dataId?: string;
|
||||
content: any;
|
||||
} & any;
|
||||
};
|
||||
export type ChatCompletionToolMessageParam = ChatCompletionToolMessageParam & { name: string };
|
||||
export type ChatCompletionAssistantToolParam = {
|
||||
role: 'assistant';
|
||||
tool_calls: ChatCompletionMessageToolCall[];
|
||||
};
|
||||
|
||||
export type ChatCompletion = ChatCompletion;
|
||||
export type ChatCompletionMessageToolCall = ChatCompletionMessageToolCall & {
|
||||
toolName?: string;
|
||||
toolAvatar?: string;
|
||||
};
|
||||
export type ChatCompletionMessageFunctionCall = ChatCompletionAssistantMessageParam.FunctionCall & {
|
||||
id?: string;
|
||||
toolName?: string;
|
||||
toolAvatar?: string;
|
||||
};
|
||||
export type StreamChatType = Stream<ChatCompletionChunk>;
|
||||
|
||||
export type PromptTemplateItem = {
|
||||
@@ -22,3 +35,6 @@ export type PromptTemplateItem = {
|
||||
desc: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export default openai;
|
||||
export * from 'openai';
|
||||
|
||||
@@ -1,40 +1,298 @@
|
||||
import type { ChatItemType } from '../../core/chat/type.d';
|
||||
import { ChatRoleEnum } from '../../core/chat/constants';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../core/ai/constant';
|
||||
import type { ChatMessageItemType } from '../../core/ai/type.d';
|
||||
import type {
|
||||
ChatItemType,
|
||||
ChatItemValueItemType,
|
||||
RuntimeUserPromptType,
|
||||
UserChatItemType
|
||||
} from '../../core/chat/type.d';
|
||||
import { ChatFileTypeEnum, ChatItemValueTypeEnum, ChatRoleEnum } from '../../core/chat/constants';
|
||||
import type {
|
||||
ChatCompletionContentPart,
|
||||
ChatCompletionFunctionMessageParam,
|
||||
ChatCompletionMessageFunctionCall,
|
||||
ChatCompletionMessageParam,
|
||||
ChatCompletionMessageToolCall,
|
||||
ChatCompletionToolMessageParam
|
||||
} from '../../core/ai/type.d';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../core/ai/constants';
|
||||
|
||||
const chat2Message = {
|
||||
[ChatRoleEnum.AI]: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
[ChatRoleEnum.Human]: ChatCompletionRequestMessageRoleEnum.User,
|
||||
[ChatRoleEnum.System]: ChatCompletionRequestMessageRoleEnum.System,
|
||||
[ChatRoleEnum.Function]: ChatCompletionRequestMessageRoleEnum.Function,
|
||||
[ChatRoleEnum.Tool]: ChatCompletionRequestMessageRoleEnum.Tool
|
||||
};
|
||||
const message2Chat = {
|
||||
const GPT2Chat = {
|
||||
[ChatCompletionRequestMessageRoleEnum.System]: ChatRoleEnum.System,
|
||||
[ChatCompletionRequestMessageRoleEnum.User]: ChatRoleEnum.Human,
|
||||
[ChatCompletionRequestMessageRoleEnum.Assistant]: ChatRoleEnum.AI,
|
||||
[ChatCompletionRequestMessageRoleEnum.Function]: ChatRoleEnum.Function,
|
||||
[ChatCompletionRequestMessageRoleEnum.Tool]: ChatRoleEnum.Tool
|
||||
[ChatCompletionRequestMessageRoleEnum.Function]: ChatRoleEnum.AI,
|
||||
[ChatCompletionRequestMessageRoleEnum.Tool]: ChatRoleEnum.AI
|
||||
};
|
||||
|
||||
export function adaptRole_Chat2Message(role: `${ChatRoleEnum}`) {
|
||||
return chat2Message[role];
|
||||
}
|
||||
export function adaptRole_Message2Chat(role: `${ChatCompletionRequestMessageRoleEnum}`) {
|
||||
return message2Chat[role];
|
||||
return GPT2Chat[role];
|
||||
}
|
||||
|
||||
export const adaptChat2GptMessages = ({
|
||||
export const simpleUserContentPart = (content: ChatCompletionContentPart[]) => {
|
||||
if (content.length === 1 && content[0].type === 'text') {
|
||||
return content[0].text;
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
export const chats2GPTMessages = ({
|
||||
messages,
|
||||
reserveId
|
||||
reserveId,
|
||||
reserveTool = false
|
||||
}: {
|
||||
messages: ChatItemType[];
|
||||
reserveId: boolean;
|
||||
}): ChatMessageItemType[] => {
|
||||
return messages.map((item) => ({
|
||||
...(reserveId && { dataId: item.dataId }),
|
||||
role: chat2Message[item.obj],
|
||||
content: item.value || ''
|
||||
}));
|
||||
reserveTool?: boolean;
|
||||
}): ChatCompletionMessageParam[] => {
|
||||
let results: ChatCompletionMessageParam[] = [];
|
||||
|
||||
messages.forEach((item) => {
|
||||
const dataId = reserveId ? item.dataId : undefined;
|
||||
if (item.obj === ChatRoleEnum.Human) {
|
||||
const value = item.value
|
||||
.map((item) => {
|
||||
if (item.type === ChatItemValueTypeEnum.text) {
|
||||
return {
|
||||
type: 'text',
|
||||
text: item.text?.content || ''
|
||||
};
|
||||
}
|
||||
if (item.type === 'file' && item.file?.type === ChatFileTypeEnum.image) {
|
||||
return {
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: item.file?.url || ''
|
||||
}
|
||||
};
|
||||
}
|
||||
return;
|
||||
})
|
||||
.filter(Boolean) as ChatCompletionContentPart[];
|
||||
|
||||
results.push({
|
||||
dataId,
|
||||
role: ChatCompletionRequestMessageRoleEnum.User,
|
||||
content: simpleUserContentPart(value)
|
||||
});
|
||||
} else if (item.obj === ChatRoleEnum.System) {
|
||||
const content = item.value?.[0]?.text?.content;
|
||||
if (content) {
|
||||
results.push({
|
||||
dataId,
|
||||
role: ChatCompletionRequestMessageRoleEnum.System,
|
||||
content
|
||||
});
|
||||
}
|
||||
} else {
|
||||
item.value.forEach((value) => {
|
||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools && reserveTool) {
|
||||
const tool_calls: ChatCompletionMessageToolCall[] = [];
|
||||
const toolResponse: ChatCompletionToolMessageParam[] = [];
|
||||
value.tools.forEach((tool) => {
|
||||
tool_calls.push({
|
||||
id: tool.id,
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.functionName,
|
||||
arguments: tool.params
|
||||
}
|
||||
});
|
||||
toolResponse.push({
|
||||
tool_call_id: tool.id,
|
||||
role: ChatCompletionRequestMessageRoleEnum.Tool,
|
||||
name: tool.functionName,
|
||||
content: tool.response
|
||||
});
|
||||
});
|
||||
results = results
|
||||
.concat({
|
||||
dataId,
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
tool_calls
|
||||
})
|
||||
.concat(toolResponse);
|
||||
} else if (value.text) {
|
||||
results.push({
|
||||
dataId,
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
content: value.text.content
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
export const GPTMessages2Chats = (
|
||||
messages: ChatCompletionMessageParam[],
|
||||
reserveTool = true
|
||||
): ChatItemType[] => {
|
||||
return messages
|
||||
.map((item) => {
|
||||
const value: ChatItemType['value'] = [];
|
||||
const obj = GPT2Chat[item.role];
|
||||
|
||||
if (
|
||||
obj === ChatRoleEnum.System &&
|
||||
item.role === ChatCompletionRequestMessageRoleEnum.System
|
||||
) {
|
||||
value.push({
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: item.content
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
obj === ChatRoleEnum.Human &&
|
||||
item.role === ChatCompletionRequestMessageRoleEnum.User
|
||||
) {
|
||||
if (typeof item.content === 'string') {
|
||||
value.push({
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: item.content
|
||||
}
|
||||
});
|
||||
} else if (Array.isArray(item.content)) {
|
||||
item.content.forEach((item) => {
|
||||
if (item.type === 'text') {
|
||||
value.push({
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: item.text
|
||||
}
|
||||
});
|
||||
} else if (item.type === 'image_url') {
|
||||
value.push({
|
||||
//@ts-ignore
|
||||
type: 'file',
|
||||
file: {
|
||||
type: ChatFileTypeEnum.image,
|
||||
name: '',
|
||||
url: item.image_url.url
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// @ts-ignore
|
||||
}
|
||||
} else if (
|
||||
obj === ChatRoleEnum.AI &&
|
||||
item.role === ChatCompletionRequestMessageRoleEnum.Assistant
|
||||
) {
|
||||
if (item.content && typeof item.content === 'string') {
|
||||
value.push({
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: item.content
|
||||
}
|
||||
});
|
||||
} else if (item.tool_calls && reserveTool) {
|
||||
// save tool calls
|
||||
const toolCalls = item.tool_calls as ChatCompletionMessageToolCall[];
|
||||
value.push({
|
||||
//@ts-ignore
|
||||
type: ChatItemValueTypeEnum.tool,
|
||||
tools: toolCalls.map((tool) => {
|
||||
let toolResponse =
|
||||
messages.find(
|
||||
(msg) =>
|
||||
msg.role === ChatCompletionRequestMessageRoleEnum.Tool &&
|
||||
msg.tool_call_id === tool.id
|
||||
)?.content || '';
|
||||
toolResponse =
|
||||
typeof toolResponse === 'string' ? toolResponse : JSON.stringify(toolResponse);
|
||||
|
||||
return {
|
||||
id: tool.id,
|
||||
toolName: tool.toolName || '',
|
||||
toolAvatar: tool.toolAvatar || '',
|
||||
functionName: tool.function.name,
|
||||
params: tool.function.arguments,
|
||||
response: toolResponse as string
|
||||
};
|
||||
})
|
||||
});
|
||||
} else if (item.function_call && reserveTool) {
|
||||
const functionCall = item.function_call as ChatCompletionMessageFunctionCall;
|
||||
const functionResponse = messages.find(
|
||||
(msg) =>
|
||||
msg.role === ChatCompletionRequestMessageRoleEnum.Function &&
|
||||
msg.name === item.function_call?.name
|
||||
) as ChatCompletionFunctionMessageParam;
|
||||
|
||||
if (functionResponse) {
|
||||
value.push({
|
||||
//@ts-ignore
|
||||
type: ChatItemValueTypeEnum.tool,
|
||||
tools: [
|
||||
{
|
||||
id: functionCall.id || '',
|
||||
toolName: functionCall.toolName || '',
|
||||
toolAvatar: functionCall.toolAvatar || '',
|
||||
functionName: functionCall.name,
|
||||
params: functionCall.arguments,
|
||||
response: functionResponse.content || ''
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dataId: item.dataId,
|
||||
obj,
|
||||
value
|
||||
} as ChatItemType;
|
||||
})
|
||||
.filter((item) => item.value.length > 0);
|
||||
};
|
||||
|
||||
export const chatValue2RuntimePrompt = (value: ChatItemValueItemType[]): RuntimeUserPromptType => {
|
||||
const prompt: RuntimeUserPromptType = {
|
||||
files: [],
|
||||
text: ''
|
||||
};
|
||||
value.forEach((item) => {
|
||||
if (item.type === 'file' && item.file) {
|
||||
prompt.files?.push(item.file);
|
||||
} else if (item.text) {
|
||||
prompt.text += item.text.content;
|
||||
}
|
||||
});
|
||||
return prompt;
|
||||
};
|
||||
|
||||
export const runtimePrompt2ChatsValue = (
|
||||
prompt: RuntimeUserPromptType
|
||||
): UserChatItemType['value'] => {
|
||||
const value: UserChatItemType['value'] = [];
|
||||
if (prompt.files) {
|
||||
prompt.files.forEach((file) => {
|
||||
value.push({
|
||||
type: ChatItemValueTypeEnum.file,
|
||||
file
|
||||
});
|
||||
});
|
||||
}
|
||||
if (prompt.text) {
|
||||
value.push({
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: prompt.text
|
||||
}
|
||||
});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const getSystemPrompt = (prompt?: string): ChatItemType[] => {
|
||||
if (!prompt) return [];
|
||||
return [
|
||||
{
|
||||
obj: ChatRoleEnum.System,
|
||||
value: [{ type: ChatItemValueTypeEnum.text, text: { content: prompt } }]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
export enum ChatRoleEnum {
|
||||
System = 'System',
|
||||
Human = 'Human',
|
||||
AI = 'AI',
|
||||
Function = 'Function',
|
||||
Tool = 'Tool'
|
||||
AI = 'AI'
|
||||
}
|
||||
export const ChatRoleMap = {
|
||||
[ChatRoleEnum.System]: {
|
||||
name: '系统提示词'
|
||||
name: '系统'
|
||||
},
|
||||
[ChatRoleEnum.Human]: {
|
||||
name: '用户'
|
||||
},
|
||||
[ChatRoleEnum.AI]: {
|
||||
name: 'AI'
|
||||
},
|
||||
[ChatRoleEnum.Function]: {
|
||||
name: 'Function'
|
||||
},
|
||||
[ChatRoleEnum.Tool]: {
|
||||
name: 'Tool'
|
||||
}
|
||||
};
|
||||
|
||||
export enum ChatFileTypeEnum {
|
||||
image = 'image',
|
||||
file = 'file'
|
||||
}
|
||||
export enum ChatItemValueTypeEnum {
|
||||
text = 'text',
|
||||
file = 'file',
|
||||
tool = 'tool'
|
||||
}
|
||||
|
||||
export enum ChatSourceEnum {
|
||||
test = 'test',
|
||||
online = 'online',
|
||||
|
||||
153
packages/global/core/chat/type.d.ts
vendored
@@ -1,11 +1,20 @@
|
||||
import { ClassifyQuestionAgentItemType } from '../module/type';
|
||||
import { SearchDataResponseItemType } from '../dataset/type';
|
||||
import { ChatRoleEnum, ChatSourceEnum, ChatStatusEnum } from './constants';
|
||||
import {
|
||||
ChatFileTypeEnum,
|
||||
ChatItemValueTypeEnum,
|
||||
ChatRoleEnum,
|
||||
ChatSourceEnum,
|
||||
ChatStatusEnum
|
||||
} from './constants';
|
||||
import { FlowNodeTypeEnum } from '../module/node/constant';
|
||||
import { ModuleOutputKeyEnum } from '../module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '../module/runtime/constants';
|
||||
import { AppSchema } from '../app/type';
|
||||
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||
import { ChatBoxInputType } from '../../../../projects/app/src/components/ChatBox/type';
|
||||
import { DispatchNodeResponseType } from '../module/runtime/type.d';
|
||||
|
||||
export type ChatSchema = {
|
||||
_id: string;
|
||||
@@ -30,7 +39,53 @@ export type ChatWithAppSchema = Omit<ChatSchema, 'appId'> & {
|
||||
appId: AppSchema;
|
||||
};
|
||||
|
||||
export type ChatItemSchema = {
|
||||
export type UserChatItemValueItemType = {
|
||||
type: ChatItemValueTypeEnum.text | ChatItemValueTypeEnum.file;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
file?: {
|
||||
type: `${ChatFileTypeEnum}`;
|
||||
name?: string;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
export type UserChatItemType = {
|
||||
obj: ChatRoleEnum.Human;
|
||||
value: UserChatItemValueItemType[];
|
||||
};
|
||||
export type SystemChatItemValueItemType = {
|
||||
type: ChatItemValueTypeEnum.text;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
export type SystemChatItemType = {
|
||||
obj: ChatRoleEnum.System;
|
||||
value: SystemChatItemValueItemType[];
|
||||
};
|
||||
export type AIChatItemValueItemType = {
|
||||
type: ChatItemValueTypeEnum.text | ChatItemValueTypeEnum.tool;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
tools?: ToolModuleResponseItemType[];
|
||||
};
|
||||
export type AIChatItemType = {
|
||||
obj: ChatRoleEnum.AI;
|
||||
value: AIChatItemValueItemType[];
|
||||
userGoodFeedback?: string;
|
||||
userBadFeedback?: string;
|
||||
customFeedbacks?: string[];
|
||||
adminFeedback?: AdminFbkType;
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]?: ChatHistoryItemResType[];
|
||||
};
|
||||
export type ChatItemValueItemType =
|
||||
| UserChatItemValueItemType
|
||||
| SystemChatItemValueItemType
|
||||
| AIChatItemValueItemType;
|
||||
|
||||
export type ChatItemSchema = (UserChatItemType | SystemChatItemType | AIChatItemType) & {
|
||||
dataId: string;
|
||||
chatId: string;
|
||||
userId: string;
|
||||
@@ -38,13 +93,6 @@ export type ChatItemSchema = {
|
||||
tmbId: string;
|
||||
appId: string;
|
||||
time: Date;
|
||||
obj: `${ChatRoleEnum}`;
|
||||
value: string;
|
||||
userGoodFeedback?: string;
|
||||
userBadFeedback?: string;
|
||||
customFeedbacks?: string[];
|
||||
adminFeedback?: AdminFbkType;
|
||||
[ModuleOutputKeyEnum.responseData]?: ChatHistoryItemResType[];
|
||||
};
|
||||
|
||||
export type AdminFbkType = {
|
||||
@@ -56,22 +104,16 @@ export type AdminFbkType = {
|
||||
};
|
||||
|
||||
/* --------- chat item ---------- */
|
||||
export type ChatItemType = {
|
||||
export type ChatItemType = (UserChatItemType | SystemChatItemType | AIChatItemType) & {
|
||||
dataId?: string;
|
||||
obj: ChatItemSchema['obj'];
|
||||
value: any;
|
||||
userGoodFeedback?: string;
|
||||
userBadFeedback?: string;
|
||||
customFeedbacks?: ChatItemSchema['customFeedbacks'];
|
||||
adminFeedback?: ChatItemSchema['feedback'];
|
||||
[ModuleOutputKeyEnum.responseData]?: ChatHistoryItemResType[];
|
||||
};
|
||||
|
||||
export type ChatSiteItemType = ChatItemType & {
|
||||
export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatItemType) & {
|
||||
dataId?: string;
|
||||
status: `${ChatStatusEnum}`;
|
||||
moduleName?: string;
|
||||
ttsBuffer?: Uint8Array;
|
||||
};
|
||||
} & ChatBoxInputType;
|
||||
|
||||
/* --------- team chat --------- */
|
||||
export type ChatAppListSchema = {
|
||||
@@ -93,60 +135,25 @@ export type ChatHistoryItemType = HistoryItemType & {
|
||||
};
|
||||
|
||||
/* ------- response data ------------ */
|
||||
export type moduleDispatchResType = {
|
||||
// common
|
||||
moduleLogo?: string;
|
||||
runningTime?: number;
|
||||
query?: string;
|
||||
textOutput?: string;
|
||||
|
||||
// bill
|
||||
tokens?: number;
|
||||
model?: string;
|
||||
contextTotalLen?: number;
|
||||
totalPoints?: number;
|
||||
|
||||
// chat
|
||||
temperature?: number;
|
||||
maxToken?: number;
|
||||
quoteList?: SearchDataResponseItemType[];
|
||||
historyPreview?: ChatItemType[]; // completion context array. history will slice
|
||||
|
||||
// dataset search
|
||||
similarity?: number;
|
||||
limit?: number;
|
||||
searchMode?: `${DatasetSearchModeEnum}`;
|
||||
searchUsingReRank?: boolean;
|
||||
extensionModel?: string;
|
||||
extensionResult?: string;
|
||||
extensionTokens?: number;
|
||||
|
||||
// cq
|
||||
cqList?: ClassifyQuestionAgentItemType[];
|
||||
cqResult?: string;
|
||||
|
||||
// content extract
|
||||
extractDescription?: string;
|
||||
extractResult?: Record<string, any>;
|
||||
|
||||
// http
|
||||
params?: Record<string, any>;
|
||||
body?: Record<string, any>;
|
||||
headers?: Record<string, any>;
|
||||
httpResult?: Record<string, any>;
|
||||
|
||||
// plugin output
|
||||
pluginOutput?: Record<string, any>;
|
||||
pluginDetail?: ChatHistoryItemResType[];
|
||||
|
||||
// tf switch
|
||||
tfSwitchResult?: boolean;
|
||||
|
||||
// abandon
|
||||
tokens?: number;
|
||||
};
|
||||
|
||||
export type ChatHistoryItemResType = moduleDispatchResType & {
|
||||
export type ChatHistoryItemResType = DispatchNodeResponseType & {
|
||||
moduleType: `${FlowNodeTypeEnum}`;
|
||||
moduleName: string;
|
||||
};
|
||||
|
||||
/* One tool run response */
|
||||
export type ToolRunResponseItemType = Record<string, any> | Array;
|
||||
/* tool module response */
|
||||
export type ToolModuleResponseItemType = {
|
||||
id: string;
|
||||
toolName: string; // tool name
|
||||
toolAvatar: string;
|
||||
params: string; // tool params
|
||||
response: string;
|
||||
functionName: string;
|
||||
};
|
||||
|
||||
/* dispatch run time */
|
||||
export type RuntimeUserPromptType = {
|
||||
files?: UserChatItemValueItemType['file'][];
|
||||
text: string;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,79 @@
|
||||
import { IMG_BLOCK_KEY, FILE_BLOCK_KEY } from './constants';
|
||||
import { DispatchNodeResponseType } from '../module/runtime/type';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../module/node/constant';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from './constants';
|
||||
import { ChatHistoryItemResType, ChatItemType } from './type.d';
|
||||
|
||||
export function chatContentReplaceBlock(content: string = '') {
|
||||
const regex = new RegExp(`\`\`\`(${IMG_BLOCK_KEY})\\n([\\s\\S]*?)\`\`\``, 'g');
|
||||
return content.replace(regex, '').trim();
|
||||
}
|
||||
export const getChatTitleFromChatMessage = (message?: ChatItemType, defaultValue = '新对话') => {
|
||||
// @ts-ignore
|
||||
const textMsg = message?.value.find((item) => item.type === ChatItemValueTypeEnum.text);
|
||||
|
||||
if (textMsg?.text?.content) {
|
||||
return textMsg.text.content.slice(0, 20);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
export const getHistoryPreview = (
|
||||
completeMessages: ChatItemType[]
|
||||
): {
|
||||
obj: `${ChatRoleEnum}`;
|
||||
value: string;
|
||||
}[] => {
|
||||
return completeMessages.map((item, i) => {
|
||||
if (item.obj === ChatRoleEnum.System || i >= completeMessages.length - 2) {
|
||||
return {
|
||||
obj: item.obj,
|
||||
value: item.value?.[0]?.text?.content || ''
|
||||
};
|
||||
}
|
||||
|
||||
const content = item.value
|
||||
.map((item) => {
|
||||
if (item.text?.content) {
|
||||
const content =
|
||||
item.text.content.length > 20
|
||||
? `${item.text.content.slice(0, 20)}...`
|
||||
: item.text.content;
|
||||
return content;
|
||||
}
|
||||
return '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
return {
|
||||
obj: item.obj,
|
||||
value: content
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const filterPublicNodeResponseData = ({
|
||||
flowResponses = []
|
||||
}: {
|
||||
flowResponses?: ChatHistoryItemResType[];
|
||||
}) => {
|
||||
const filedList = ['quoteList', 'moduleType'];
|
||||
const filterModuleTypeList: any[] = [
|
||||
FlowNodeTypeEnum.pluginModule,
|
||||
FlowNodeTypeEnum.datasetSearchNode,
|
||||
FlowNodeTypeEnum.tools
|
||||
];
|
||||
|
||||
return flowResponses
|
||||
.filter((item) => filterModuleTypeList.includes(item.moduleType))
|
||||
.map((item) => {
|
||||
const obj: DispatchNodeResponseType = {};
|
||||
for (let key in item) {
|
||||
if (key === 'toolDetail' || key === 'pluginDetail') {
|
||||
// @ts-ignore
|
||||
obj[key] = filterPublicNodeResponseData({ flowResponses: item[key] });
|
||||
} else if (filedList.includes(key)) {
|
||||
// @ts-ignore
|
||||
obj[key] = item[key];
|
||||
}
|
||||
}
|
||||
return obj as ChatHistoryItemResType;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -83,17 +83,17 @@ export const TrainingTypeMap = {
|
||||
[TrainingModeEnum.chunk]: {
|
||||
label: 'core.dataset.training.Chunk mode',
|
||||
tooltip: 'core.dataset.import.Chunk Split Tip',
|
||||
isPlus: true
|
||||
openSource: true
|
||||
},
|
||||
[TrainingModeEnum.auto]: {
|
||||
label: 'core.dataset.training.Auto mode',
|
||||
tooltip: 'core.dataset.training.Auto mode Tip',
|
||||
isPlus: true
|
||||
openSource: false
|
||||
},
|
||||
[TrainingModeEnum.qa]: {
|
||||
label: 'core.dataset.training.QA mode',
|
||||
tooltip: 'core.dataset.import.QA Import Tip',
|
||||
isPlus: true
|
||||
openSource: true
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ export enum ModuleIOValueTypeEnum {
|
||||
|
||||
// plugin special type
|
||||
selectApp = 'selectApp',
|
||||
selectDataset = 'selectDataset'
|
||||
selectDataset = 'selectDataset',
|
||||
|
||||
// tool
|
||||
tools = 'tools'
|
||||
}
|
||||
|
||||
/* reg: modulename key */
|
||||
@@ -89,12 +92,10 @@ export enum ModuleInputKeyEnum {
|
||||
|
||||
export enum ModuleOutputKeyEnum {
|
||||
// common
|
||||
responseData = 'responseData',
|
||||
moduleDispatchBills = 'moduleDispatchBills',
|
||||
userChatInput = 'userChatInput',
|
||||
finish = 'finish',
|
||||
history = 'history',
|
||||
answerText = 'answerText', // answer module text key
|
||||
answerText = 'answerText', // module answer. the value will be show and save to history
|
||||
success = 'success',
|
||||
failed = 'failed',
|
||||
text = 'system_text',
|
||||
@@ -110,23 +111,41 @@ export enum ModuleOutputKeyEnum {
|
||||
|
||||
// tf switch
|
||||
resultTrue = 'system_resultTrue',
|
||||
resultFalse = 'system_resultFalse'
|
||||
resultFalse = 'system_resultFalse',
|
||||
|
||||
// tools
|
||||
selectedTools = 'selectedTools',
|
||||
|
||||
// http
|
||||
httpRawResponse = 'httpRawResponse'
|
||||
}
|
||||
|
||||
export enum VariableInputEnum {
|
||||
input = 'input',
|
||||
textarea = 'textarea',
|
||||
select = 'select'
|
||||
select = 'select',
|
||||
external = 'external'
|
||||
}
|
||||
export const variableMap = {
|
||||
[VariableInputEnum.input]: {
|
||||
icon: 'core/app/variable/input'
|
||||
icon: 'core/app/variable/input',
|
||||
title: 'core.module.variable.input type',
|
||||
desc: ''
|
||||
},
|
||||
[VariableInputEnum.textarea]: {
|
||||
icon: 'core/app/variable/textarea'
|
||||
icon: 'core/app/variable/textarea',
|
||||
title: 'core.module.variable.textarea type',
|
||||
desc: '允许用户最多输入4000字的对话框。'
|
||||
},
|
||||
[VariableInputEnum.select]: {
|
||||
icon: 'core/app/variable/select'
|
||||
icon: 'core/app/variable/select',
|
||||
title: 'core.module.variable.select type',
|
||||
desc: ''
|
||||
},
|
||||
[VariableInputEnum.external]: {
|
||||
icon: 'core/app/variable/external',
|
||||
title: 'core.module.variable.External type',
|
||||
desc: '可以通过API接口或分享链接的Query传递变量。增加该类型变量的主要目的是用于变量提示。使用例子: 你可以通过分享链接Query中拼接Token,来实现内部系统身份鉴权。'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ export enum FlowNodeTypeEnum {
|
||||
pluginModule = 'pluginModule',
|
||||
pluginInput = 'pluginInput',
|
||||
pluginOutput = 'pluginOutput',
|
||||
queryExtension = 'cfr'
|
||||
queryExtension = 'cfr',
|
||||
tools = 'tools'
|
||||
|
||||
// abandon
|
||||
}
|
||||
|
||||
10
packages/global/core/module/node/type.d.ts
vendored
@@ -2,6 +2,7 @@ import { FlowNodeInputTypeEnum, FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from
|
||||
import { ModuleIOValueTypeEnum, ModuleInputKeyEnum, ModuleOutputKeyEnum } from '../constants';
|
||||
import { SelectedDatasetType } from '../api';
|
||||
import { EditInputFieldMap, EditOutputFieldMap } from './type';
|
||||
import { LLMModelTypeEnum } from '../../ai/constants';
|
||||
|
||||
export type FlowNodeChangeProps = {
|
||||
moduleId: string;
|
||||
@@ -28,6 +29,7 @@ export type FlowNodeInputItemType = {
|
||||
label: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
toolDescription?: string; // If this field is not empty, it is entered as a tool
|
||||
|
||||
edit?: boolean; // Whether to allow editing
|
||||
editField?: EditInputFieldMap;
|
||||
@@ -49,6 +51,8 @@ export type FlowNodeInputItemType = {
|
||||
step?: number; // slider
|
||||
max?: number; // slider, number input
|
||||
min?: number; // slider, number input
|
||||
|
||||
llmModelType?: `${LLMModelTypeEnum}`;
|
||||
};
|
||||
|
||||
export type FlowNodeOutputTargetItemType = {
|
||||
@@ -62,6 +66,8 @@ export type FlowNodeOutputItemType = {
|
||||
|
||||
label?: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
defaultValue?: any;
|
||||
|
||||
edit?: boolean;
|
||||
editField?: EditOutputFieldMap;
|
||||
@@ -74,12 +80,14 @@ export type FlowNodeOutputItemType = {
|
||||
export type EditInputFieldMap = EditOutputFieldMap & {
|
||||
inputType?: boolean;
|
||||
required?: boolean;
|
||||
isToolInput?: boolean;
|
||||
};
|
||||
export type EditOutputFieldMap = {
|
||||
name?: boolean;
|
||||
key?: boolean;
|
||||
description?: boolean;
|
||||
dataType?: boolean;
|
||||
defaultValue?: boolean;
|
||||
};
|
||||
export type EditNodeFieldType = {
|
||||
inputType?: `${FlowNodeInputTypeEnum}`; // input type
|
||||
@@ -89,6 +97,8 @@ export type EditNodeFieldType = {
|
||||
label?: string;
|
||||
description?: string;
|
||||
valueType?: `${ModuleIOValueTypeEnum}`;
|
||||
isToolInput?: boolean;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
/* ------------- item type --------------- */
|
||||
|
||||
19
packages/global/core/module/runtime/constants.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export enum SseResponseEventEnum {
|
||||
error = 'error',
|
||||
answer = 'answer', // animation stream
|
||||
fastAnswer = 'fastAnswer', // direct answer text, not animation
|
||||
flowNodeStatus = 'flowNodeStatus', // update node status
|
||||
|
||||
toolCall = 'toolCall', // tool start
|
||||
toolParams = 'toolParams', // tool params return
|
||||
toolResponse = 'toolResponse', // tool response return
|
||||
flowResponses = 'flowResponses' // sse response request
|
||||
}
|
||||
|
||||
export enum DispatchNodeResponseKeyEnum {
|
||||
nodeResponse = 'responseData', // run node response
|
||||
nodeDispatchUsages = 'nodeDispatchUsages', // the node bill.
|
||||
childrenResponses = 'childrenResponses', // Some nodes make recursive calls that need to be returned
|
||||
toolResponses = 'toolResponses', // The result is passed back to the tool node for use
|
||||
assistantResponses = 'assistantResponses' // assistant response
|
||||
}
|
||||
101
packages/global/core/module/runtime/type.d.ts
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
import { ChatNodeUsageType } from '../../../support/wallet/bill/type';
|
||||
import { ChatItemValueItemType, ToolRunResponseItemType } from '../../chat/type';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType } from '../node/type';
|
||||
import { ModuleItemType } from '../type';
|
||||
import { DispatchNodeResponseKeyEnum } from './constants';
|
||||
|
||||
export type RunningModuleItemType = {
|
||||
name: ModuleItemType['name'];
|
||||
avatar: ModuleItemType['avatar'];
|
||||
intro?: ModuleItemType['intro'];
|
||||
moduleId: ModuleItemType['moduleId'];
|
||||
flowType: ModuleItemType['flowType'];
|
||||
showStatus?: ModuleItemType['showStatus'];
|
||||
isEntry?: ModuleItemType['isEntry'];
|
||||
|
||||
inputs: {
|
||||
key: string;
|
||||
value?: any;
|
||||
valueType?: FlowNodeInputItemType['valueType'];
|
||||
required?: boolean;
|
||||
toolDescription?: string;
|
||||
}[];
|
||||
outputs: {
|
||||
key: string;
|
||||
required?: boolean;
|
||||
defaultValue?: any;
|
||||
answer?: boolean;
|
||||
response?: boolean;
|
||||
value?: any;
|
||||
valueType?: FlowNodeOutputItemType['valueType'];
|
||||
targets: {
|
||||
moduleId: string;
|
||||
key: string;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export type DispatchNodeResponseType = {
|
||||
// common
|
||||
moduleLogo?: string;
|
||||
runningTime?: number;
|
||||
query?: string;
|
||||
textOutput?: string;
|
||||
|
||||
// bill
|
||||
tokens?: number;
|
||||
model?: string;
|
||||
contextTotalLen?: number;
|
||||
totalPoints?: number;
|
||||
|
||||
// chat
|
||||
temperature?: number;
|
||||
maxToken?: number;
|
||||
quoteList?: SearchDataResponseItemType[];
|
||||
historyPreview?: {
|
||||
obj: `${ChatRoleEnum}`;
|
||||
value: string;
|
||||
}[]; // completion context array. history will slice
|
||||
|
||||
// dataset search
|
||||
similarity?: number;
|
||||
limit?: number;
|
||||
searchMode?: `${DatasetSearchModeEnum}`;
|
||||
searchUsingReRank?: boolean;
|
||||
extensionModel?: string;
|
||||
extensionResult?: string;
|
||||
extensionTokens?: number;
|
||||
|
||||
// cq
|
||||
cqList?: ClassifyQuestionAgentItemType[];
|
||||
cqResult?: string;
|
||||
|
||||
// content extract
|
||||
extractDescription?: string;
|
||||
extractResult?: Record<string, any>;
|
||||
|
||||
// http
|
||||
params?: Record<string, any>;
|
||||
body?: Record<string, any>;
|
||||
headers?: Record<string, any>;
|
||||
httpResult?: Record<string, any>;
|
||||
|
||||
// plugin output
|
||||
pluginOutput?: Record<string, any>;
|
||||
pluginDetail?: ChatHistoryItemResType[];
|
||||
|
||||
// tf switch
|
||||
tfSwitchResult?: boolean;
|
||||
|
||||
// tool
|
||||
toolCallTokens?: number;
|
||||
toolDetail?: ChatHistoryItemResType[];
|
||||
};
|
||||
|
||||
export type DispatchNodeResultType<T> = {
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]?: DispatchNodeResponseType; // The node response detail
|
||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]?: ChatNodeUsageType[]; //
|
||||
[DispatchNodeResponseKeyEnum.childrenResponses]?: DispatchNodeResultType[];
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]?: ToolRunResponseItemType;
|
||||
[DispatchNodeResponseKeyEnum.assistantResponses]?: ChatItemValueItemType[];
|
||||
} & T;
|
||||
31
packages/global/core/module/runtime/utils.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../ai/constants';
|
||||
|
||||
export const textAdaptGptResponse = ({
|
||||
text,
|
||||
model = '',
|
||||
finish_reason = null,
|
||||
extraData = {}
|
||||
}: {
|
||||
model?: string;
|
||||
text: string | null;
|
||||
finish_reason?: null | 'stop';
|
||||
extraData?: Object;
|
||||
}) => {
|
||||
return JSON.stringify({
|
||||
...extraData,
|
||||
id: '',
|
||||
object: '',
|
||||
created: 0,
|
||||
model,
|
||||
choices: [
|
||||
{
|
||||
delta:
|
||||
text === null
|
||||
? {}
|
||||
: { role: ChatCompletionRequestMessageRoleEnum.Assistant, content: text },
|
||||
index: 0,
|
||||
finish_reason
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
@@ -1,26 +1,25 @@
|
||||
import { UserGuideModule } from '@fastgpt/global/core/module/template/system/userGuide';
|
||||
import { UserInputModule } from '@fastgpt/global/core/module/template/system/userInput';
|
||||
import { AiChatModule } from '@fastgpt/global/core/module/template/system/aiChat';
|
||||
import { DatasetSearchModule } from '@fastgpt/global/core/module/template/system/datasetSearch';
|
||||
import { DatasetConcatModule } from '@fastgpt/global/core/module/template/system/datasetConcat';
|
||||
import { AssignedAnswerModule } from '@fastgpt/global/core/module/template/system/assignedAnswer';
|
||||
import { ClassifyQuestionModule } from '@fastgpt/global/core/module/template/system/classifyQuestion';
|
||||
import { ContextExtractModule } from '@fastgpt/global/core/module/template/system/contextExtract';
|
||||
import { HttpModule468 } from '@fastgpt/global/core/module/template/system/http468';
|
||||
import { HttpModule } from '@fastgpt/global/core/module/template/system/abandon/http';
|
||||
import { UserGuideModule } from './system/userGuide';
|
||||
import { UserInputModule } from './system/userInput';
|
||||
import { AiChatModule } from './system/aiChat';
|
||||
import { DatasetSearchModule } from './system/datasetSearch';
|
||||
import { DatasetConcatModule } from './system/datasetConcat';
|
||||
import { AssignedAnswerModule } from './system/assignedAnswer';
|
||||
import { ClassifyQuestionModule } from './system/classifyQuestion';
|
||||
import { ContextExtractModule } from './system/contextExtract';
|
||||
import { HttpModule468 } from './system/http468';
|
||||
import { HttpModule } from './system/abandon/http';
|
||||
import { ToolModule } from './system/tools';
|
||||
|
||||
import { RunAppModule } from '@fastgpt/global/core/module/template/system/runApp';
|
||||
import { PluginInputModule } from '@fastgpt/global/core/module/template/system/pluginInput';
|
||||
import { PluginOutputModule } from '@fastgpt/global/core/module/template/system/pluginOutput';
|
||||
import { RunPluginModule } from '@fastgpt/global/core/module/template/system/runPlugin';
|
||||
import { AiQueryExtension } from '@fastgpt/global/core/module/template/system/queryExtension';
|
||||
import { RunAppModule } from './system/runApp';
|
||||
import { PluginInputModule } from './system/pluginInput';
|
||||
import { PluginOutputModule } from './system/pluginOutput';
|
||||
import { RunPluginModule } from './system/runPlugin';
|
||||
import { AiQueryExtension } from './system/queryExtension';
|
||||
|
||||
import type {
|
||||
FlowModuleTemplateType,
|
||||
moduleTemplateListType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { FlowModuleTemplateType, moduleTemplateListType } from '../../module/type.d';
|
||||
import { ModuleTemplateTypeEnum } from '../../module/constants';
|
||||
|
||||
/* app flow module templates */
|
||||
export const appSystemModuleTemplates: FlowModuleTemplateType[] = [
|
||||
UserGuideModule,
|
||||
UserInputModule,
|
||||
@@ -29,11 +28,13 @@ export const appSystemModuleTemplates: FlowModuleTemplateType[] = [
|
||||
DatasetSearchModule,
|
||||
DatasetConcatModule,
|
||||
RunAppModule,
|
||||
ToolModule,
|
||||
ClassifyQuestionModule,
|
||||
ContextExtractModule,
|
||||
HttpModule468,
|
||||
AiQueryExtension
|
||||
];
|
||||
/* plugin flow module templates */
|
||||
export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
|
||||
PluginInputModule,
|
||||
PluginOutputModule,
|
||||
@@ -42,12 +43,14 @@ export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
|
||||
DatasetSearchModule,
|
||||
DatasetConcatModule,
|
||||
RunAppModule,
|
||||
ToolModule,
|
||||
ClassifyQuestionModule,
|
||||
ContextExtractModule,
|
||||
HttpModule468,
|
||||
AiQueryExtension
|
||||
];
|
||||
|
||||
/* all module */
|
||||
export const moduleTemplatesFlat: FlowModuleTemplateType[] = [
|
||||
UserGuideModule,
|
||||
UserInputModule,
|
||||
@@ -59,6 +62,8 @@ export const moduleTemplatesFlat: FlowModuleTemplateType[] = [
|
||||
ContextExtractModule,
|
||||
HttpModule468,
|
||||
HttpModule,
|
||||
ToolModule,
|
||||
AiChatModule,
|
||||
RunAppModule,
|
||||
PluginInputModule,
|
||||
PluginOutputModule,
|
||||
@@ -2,6 +2,7 @@ import type { FlowNodeInputItemType } from '../node/type.d';
|
||||
import { DYNAMIC_INPUT_KEY, ModuleInputKeyEnum } from '../constants';
|
||||
import { FlowNodeInputTypeEnum } from '../node/constant';
|
||||
import { ModuleIOValueTypeEnum } from '../constants';
|
||||
import { chatNodeSystemPromptTip } from './tip';
|
||||
|
||||
export const Input_Template_Switch: FlowNodeInputItemType = {
|
||||
key: ModuleInputKeyEnum.switch,
|
||||
@@ -58,6 +59,28 @@ export const Input_Template_DynamicInput: FlowNodeInputItemType = {
|
||||
hideInApp: true
|
||||
};
|
||||
|
||||
export const Input_Template_AiModel: FlowNodeInputItemType = {
|
||||
key: ModuleInputKeyEnum.aiModel,
|
||||
type: FlowNodeInputTypeEnum.selectLLMModel,
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
};
|
||||
|
||||
export const Input_Template_System_Prompt: FlowNodeInputItemType = {
|
||||
key: ModuleInputKeyEnum.aiSystemPrompt,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
max: 3000,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
label: 'core.ai.Prompt',
|
||||
description: chatNodeSystemPromptTip,
|
||||
placeholder: chatNodeSystemPromptTip,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
};
|
||||
|
||||
export const Input_Template_Dataset_Quote: FlowNodeInputItemType = {
|
||||
key: ModuleInputKeyEnum.aiChatDatasetQuote,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import {
|
||||
Input_Template_AiModel,
|
||||
Input_Template_Dataset_Quote,
|
||||
Input_Template_History,
|
||||
Input_Template_Switch,
|
||||
Input_Template_System_Prompt,
|
||||
Input_Template_UserChatInput
|
||||
} from '../input';
|
||||
import { chatNodeSystemPromptTip } from '../tip';
|
||||
@@ -24,20 +26,13 @@ export const AiChatModule: FlowModuleTemplateType = {
|
||||
templateType: ModuleTemplateTypeEnum.textAnswer,
|
||||
flowType: FlowNodeTypeEnum.chatNode,
|
||||
avatar: '/imgs/module/AI.png',
|
||||
name: 'core.module.template.Ai chat',
|
||||
intro: 'core.module.template.Ai chat intro',
|
||||
name: 'AI 对话',
|
||||
intro: 'AI 大模型对话',
|
||||
showStatus: true,
|
||||
// isTool: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiModel,
|
||||
type: FlowNodeInputTypeEnum.selectLLMModel,
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
Input_Template_AiModel,
|
||||
// --- settings modal
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatTemperature,
|
||||
@@ -98,18 +93,13 @@ export const AiChatModule: FlowModuleTemplateType = {
|
||||
},
|
||||
// settings modal ---
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiSystemPrompt,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
...Input_Template_System_Prompt,
|
||||
label: 'core.ai.Prompt',
|
||||
max: 300,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
description: chatNodeSystemPromptTip,
|
||||
placeholder: chatNodeSystemPromptTip,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
placeholder: chatNodeSystemPromptTip
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput,
|
||||
{ ...Input_Template_UserChatInput, toolDescription: '用户问题' },
|
||||
Input_Template_Dataset_Quote
|
||||
],
|
||||
outputs: [
|
||||
|
||||
@@ -9,8 +9,9 @@ export const AssignedAnswerModule: FlowModuleTemplateType = {
|
||||
templateType: ModuleTemplateTypeEnum.textAnswer,
|
||||
flowType: FlowNodeTypeEnum.answerNode,
|
||||
avatar: '/imgs/module/reply.png',
|
||||
name: 'core.module.template.Assigned reply',
|
||||
intro: 'core.module.template.Assigned reply intro',
|
||||
name: '指定回复',
|
||||
intro:
|
||||
'该模块可以直接回复一段指定的内容。常用于引导、提示。非字符串内容传入时,会转成字符串进行输出。',
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
{
|
||||
|
||||
@@ -6,40 +6,34 @@ import {
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import { ModuleIOValueTypeEnum, ModuleInputKeyEnum, ModuleTemplateTypeEnum } from '../../constants';
|
||||
import {
|
||||
Input_Template_AiModel,
|
||||
Input_Template_History,
|
||||
Input_Template_Switch,
|
||||
Input_Template_UserChatInput
|
||||
} from '../input';
|
||||
import { Output_Template_UserChatInput } from '../output';
|
||||
import { Input_Template_System_Prompt } from '../input';
|
||||
import { LLMModelTypeEnum } from '../../../ai/constants';
|
||||
|
||||
export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.classifyQuestion,
|
||||
templateType: ModuleTemplateTypeEnum.functionCall,
|
||||
flowType: FlowNodeTypeEnum.classifyQuestion,
|
||||
avatar: '/imgs/module/cq.png',
|
||||
name: 'core.module.template.Classify question',
|
||||
intro: `core.module.template.Classify question intro`,
|
||||
name: '问题分类',
|
||||
intro: `根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于商品“使用”问题\n类型3: 关于商品“购买”问题\n类型4: 其他问题`,
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiModel,
|
||||
type: FlowNodeInputTypeEnum.selectLLMModel,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
label: 'core.module.input.label.Classify model',
|
||||
required: true,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
...Input_Template_AiModel,
|
||||
llmModelType: LLMModelTypeEnum.classify
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiSystemPrompt,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
...Input_Template_System_Prompt,
|
||||
label: 'core.module.input.label.Background',
|
||||
description: 'core.module.input.description.Background',
|
||||
placeholder: 'core.module.input.placeholder.Classify background',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
placeholder: 'core.module.input.placeholder.Classify background'
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput,
|
||||
|
||||
@@ -10,26 +10,23 @@ import {
|
||||
ModuleOutputKeyEnum,
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import { Input_Template_History, Input_Template_Switch } from '../input';
|
||||
import { Input_Template_AiModel, Input_Template_History, Input_Template_Switch } from '../input';
|
||||
import { LLMModelTypeEnum } from '../../../ai/constants';
|
||||
|
||||
export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.contentExtract,
|
||||
templateType: ModuleTemplateTypeEnum.functionCall,
|
||||
flowType: FlowNodeTypeEnum.contentExtract,
|
||||
avatar: '/imgs/module/extract.png',
|
||||
name: 'core.module.template.Extract field',
|
||||
intro: 'core.module.template.Extract field intro',
|
||||
name: '文本内容提取',
|
||||
intro: '可从文本中提取指定的数据,例如:sql语句、搜索关键词、代码等',
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiModel,
|
||||
type: FlowNodeInputTypeEnum.selectLLMModel,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
label: 'core.module.input.label.LLM',
|
||||
required: true,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
...Input_Template_AiModel,
|
||||
llmModelType: LLMModelTypeEnum.extractFields
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.description,
|
||||
@@ -52,12 +49,13 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
required: true,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
showTargetInPlugin: true,
|
||||
toolDescription: '需要检索的内容'
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.extractKeys,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '目标字段',
|
||||
label: '',
|
||||
valueType: ModuleIOValueTypeEnum.any,
|
||||
description: "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段",
|
||||
value: [], // {desc: string; key: string; required: boolean; enum: string[]}[]
|
||||
@@ -76,6 +74,7 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: ModuleOutputKeyEnum.failed,
|
||||
label: '提取字段缺失',
|
||||
description: '存在一个或多个字段未提取成功。尽管使用了默认值也算缺失。',
|
||||
valueType: ModuleIOValueTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
|
||||
@@ -26,7 +26,7 @@ export const DatasetConcatModule: FlowModuleTemplateType = {
|
||||
templateType: ModuleTemplateTypeEnum.tools,
|
||||
avatar: '/imgs/module/concat.svg',
|
||||
name: '知识库搜索引用合并',
|
||||
intro: 'core.module.template.Dataset search result concat intro',
|
||||
intro: '可以将多个知识库搜索结果进行合并输出。使用 RRF 的合并方式进行最终排序输出。',
|
||||
showStatus: false,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
|
||||
@@ -19,9 +19,10 @@ export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
templateType: ModuleTemplateTypeEnum.functionCall,
|
||||
flowType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
avatar: '/imgs/module/db.png',
|
||||
name: 'core.module.template.Dataset search',
|
||||
intro: 'core.module.template.Dataset search intro',
|
||||
name: '知识库搜索',
|
||||
intro: '调用知识库搜索能力,查找“有可能”与问题相关的内容',
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
{
|
||||
@@ -97,7 +98,10 @@ export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
showTargetInPlugin: false,
|
||||
value: ''
|
||||
},
|
||||
Input_Template_UserChatInput
|
||||
{
|
||||
...Input_Template_UserChatInput,
|
||||
toolDescription: '需要检索的内容'
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
Output_Template_UserChatInput,
|
||||
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
} from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type';
|
||||
import {
|
||||
DYNAMIC_INPUT_KEY,
|
||||
ModuleIOValueTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum,
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import {
|
||||
@@ -22,9 +22,10 @@ export const HttpModule468: FlowModuleTemplateType = {
|
||||
templateType: ModuleTemplateTypeEnum.externalCall,
|
||||
flowType: FlowNodeTypeEnum.httpRequest468,
|
||||
avatar: '/imgs/module/http.png',
|
||||
name: 'core.module.template.Http request',
|
||||
intro: 'core.module.template.Http request intro',
|
||||
name: 'HTTP 请求',
|
||||
intro: '可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
{
|
||||
@@ -86,7 +87,6 @@ export const HttpModule468: FlowModuleTemplateType = {
|
||||
editField: {
|
||||
key: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true
|
||||
},
|
||||
defaultEditField: {
|
||||
@@ -94,19 +94,27 @@ export const HttpModule468: FlowModuleTemplateType = {
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
}
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
Output_Template_Finish,
|
||||
{
|
||||
key: ModuleOutputKeyEnum.httpRawResponse,
|
||||
label: '原始响应',
|
||||
description: 'HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。',
|
||||
valueType: ModuleIOValueTypeEnum.any,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
...Output_Template_AddOutput,
|
||||
editField: {
|
||||
key: true,
|
||||
description: true,
|
||||
dataType: true
|
||||
dataType: true,
|
||||
defaultValue: true
|
||||
},
|
||||
defaultEditField: {
|
||||
label: '',
|
||||
|
||||
@@ -13,28 +13,26 @@ import {
|
||||
import {
|
||||
Input_Template_History,
|
||||
Input_Template_Switch,
|
||||
Input_Template_UserChatInput
|
||||
Input_Template_UserChatInput,
|
||||
Input_Template_AiModel
|
||||
} from '../input';
|
||||
import { Output_Template_UserChatInput } from '../output';
|
||||
import { LLMModelTypeEnum } from '../../../ai/constants';
|
||||
|
||||
export const AiQueryExtension: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.chatNode,
|
||||
templateType: ModuleTemplateTypeEnum.other,
|
||||
flowType: FlowNodeTypeEnum.queryExtension,
|
||||
avatar: '/imgs/module/cfr.svg',
|
||||
name: 'core.module.template.Query extension',
|
||||
intro: 'core.module.template.Query extension intro',
|
||||
name: '问题优化',
|
||||
intro:
|
||||
'使用问题优化功能,可以提高知识库连续对话时搜索的精度。使用该功能后,会先利用 AI 根据上下文构建一个或多个新的检索词,这些检索词更利于进行知识库搜索。该模块已内置在知识库搜索模块中,如果您仅进行一次知识库搜索,可直接使用知识库内置的补全功能。',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiModel,
|
||||
type: FlowNodeInputTypeEnum.selectLLMModel,
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
...Input_Template_AiModel,
|
||||
llmModelType: LLMModelTypeEnum.queryExtension
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiSystemPrompt,
|
||||
|
||||
@@ -22,8 +22,8 @@ export const RunAppModule: FlowModuleTemplateType = {
|
||||
templateType: ModuleTemplateTypeEnum.externalCall,
|
||||
flowType: FlowNodeTypeEnum.runApp,
|
||||
avatar: '/imgs/module/app.png',
|
||||
name: 'core.module.template.Running app',
|
||||
intro: 'core.module.template.Running app intro',
|
||||
name: '应用调用',
|
||||
intro: '可以选择一个其他应用进行调用',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
@@ -52,7 +52,7 @@ export const RunAppModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: ModuleOutputKeyEnum.answerText,
|
||||
label: 'AI回复',
|
||||
label: '回复的文本',
|
||||
description: '将在应用完全结束后触发',
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
|
||||
@@ -9,6 +9,7 @@ export const RunPluginModule: FlowModuleTemplateType = {
|
||||
intro: '',
|
||||
name: '',
|
||||
showStatus: false,
|
||||
isTool: true,
|
||||
inputs: [], // [{key:'pluginId'},...]
|
||||
outputs: []
|
||||
};
|
||||
|
||||
52
packages/global/core/module/template/system/tools.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import {
|
||||
ModuleIOValueTypeEnum,
|
||||
ModuleOutputKeyEnum,
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import {
|
||||
Input_Template_AiModel,
|
||||
Input_Template_History,
|
||||
Input_Template_Switch,
|
||||
Input_Template_System_Prompt,
|
||||
Input_Template_UserChatInput
|
||||
} from '../input';
|
||||
import { chatNodeSystemPromptTip } from '../tip';
|
||||
import { Output_Template_Finish, Output_Template_UserChatInput } from '../output';
|
||||
import { LLMModelTypeEnum } from '../../../ai/constants';
|
||||
|
||||
export const ToolModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.tools,
|
||||
flowType: FlowNodeTypeEnum.tools,
|
||||
templateType: ModuleTemplateTypeEnum.functionCall,
|
||||
avatar: '/imgs/module/tool.svg',
|
||||
name: '工具调用(实验)',
|
||||
intro: '通过AI模型自动选择一个或多个工具进行调用。工具可以是其他功能块或插件。',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
{
|
||||
...Input_Template_AiModel,
|
||||
llmModelType: LLMModelTypeEnum.toolCall
|
||||
},
|
||||
{
|
||||
...Input_Template_System_Prompt,
|
||||
label: 'core.ai.Prompt',
|
||||
description: chatNodeSystemPromptTip,
|
||||
placeholder: chatNodeSystemPromptTip
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput
|
||||
],
|
||||
outputs: [
|
||||
Output_Template_UserChatInput,
|
||||
{
|
||||
key: ModuleOutputKeyEnum.selectedTools,
|
||||
valueType: ModuleIOValueTypeEnum.tools,
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
@@ -8,7 +8,7 @@ export const UserGuideModule: FlowModuleTemplateType = {
|
||||
templateType: ModuleTemplateTypeEnum.userGuide,
|
||||
flowType: FlowNodeTypeEnum.userGuide,
|
||||
avatar: '/imgs/module/userGuide.png',
|
||||
name: 'core.module.template.User guide',
|
||||
name: '全局配置',
|
||||
intro: userGuideTip,
|
||||
inputs: [
|
||||
{
|
||||
|
||||
@@ -16,8 +16,8 @@ export const UserInputModule: FlowModuleTemplateType = {
|
||||
templateType: ModuleTemplateTypeEnum.systemInput,
|
||||
flowType: FlowNodeTypeEnum.questionInput,
|
||||
avatar: '/imgs/module/userChatInput.svg',
|
||||
name: 'core.module.template.Chat entrance',
|
||||
intro: 'core.module.template.Chat entrance intro',
|
||||
name: '对话入口',
|
||||
intro: '当用户发送一个内容后,流程将会从这个模块开始执行。',
|
||||
inputs: [
|
||||
{
|
||||
key: ModuleInputKeyEnum.userChatInput,
|
||||
|
||||
45
packages/global/core/module/type.d.ts
vendored
@@ -5,10 +5,16 @@ import {
|
||||
ModuleTemplateTypeEnum,
|
||||
VariableInputEnum
|
||||
} from './constants';
|
||||
import { DispatchNodeResponseKeyEnum } from './runtime/constants';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType } from './node/type';
|
||||
import { UserModelSchema } from 'support/user/type';
|
||||
import { moduleDispatchResType } from '..//chat/type';
|
||||
import { ChatModuleUsageType } from '../../support/wallet/bill/type';
|
||||
import {
|
||||
ChatItemValueItemType,
|
||||
ToolRunResponseItemType,
|
||||
UserChatItemValueItemType
|
||||
} from '../chat/type';
|
||||
import { ChatNodeUsageType } from '../../support/wallet/bill/type';
|
||||
import { RunningModuleItemType } from './runtime/type';
|
||||
|
||||
export type FlowModuleTemplateType = {
|
||||
id: string; // module id, unique
|
||||
@@ -17,6 +23,7 @@ export type FlowModuleTemplateType = {
|
||||
avatar?: string;
|
||||
name: string;
|
||||
intro: string; // template list intro
|
||||
isTool?: boolean; // can be connected by tool
|
||||
showStatus?: boolean; // chatting response step status
|
||||
inputs: FlowNodeInputItemType[];
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
@@ -44,6 +51,9 @@ export type ModuleItemType = {
|
||||
showStatus?: boolean;
|
||||
inputs: FlowNodeInputItemType[];
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
|
||||
// runTime field
|
||||
isEntry?: boolean;
|
||||
};
|
||||
|
||||
/* --------------- function type -------------------- */
|
||||
@@ -80,34 +90,11 @@ export type ContextExtractAgentItemType = {
|
||||
desc: string;
|
||||
key: string;
|
||||
required: boolean;
|
||||
defaultValue?: string;
|
||||
enum?: string;
|
||||
};
|
||||
|
||||
/* -------------- running module -------------- */
|
||||
export type RunningModuleItemType = {
|
||||
name: ModuleItemType['name'];
|
||||
moduleId: ModuleItemType['moduleId'];
|
||||
flowType: ModuleItemType['flowType'];
|
||||
showStatus?: ModuleItemType['showStatus'];
|
||||
} & {
|
||||
inputs: {
|
||||
key: string;
|
||||
value?: any;
|
||||
valueType?: `${ModuleIOValueTypeEnum}`;
|
||||
}[];
|
||||
outputs: {
|
||||
key: string;
|
||||
answer?: boolean;
|
||||
response?: boolean;
|
||||
value?: any;
|
||||
valueType?: `${ModuleIOValueTypeEnum}`;
|
||||
targets: {
|
||||
moduleId: string;
|
||||
key: string;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export type ChatDispatchProps = {
|
||||
res: NextApiResponse;
|
||||
mode: 'test' | 'chat';
|
||||
@@ -119,15 +106,13 @@ export type ChatDispatchProps = {
|
||||
responseChatItemId?: string;
|
||||
histories: ChatItemType[];
|
||||
variables: Record<string, any>;
|
||||
inputFiles?: UserChatItemValueItemType['file'][];
|
||||
stream: boolean;
|
||||
detail: boolean; // response detail
|
||||
};
|
||||
|
||||
export type ModuleDispatchProps<T> = ChatDispatchProps & {
|
||||
module: RunningModuleItemType;
|
||||
runtimeModules: RunningModuleItemType[];
|
||||
params: T;
|
||||
};
|
||||
export type ModuleDispatchResponse<T> = T & {
|
||||
[ModuleOutputKeyEnum.responseData]?: moduleDispatchResType;
|
||||
[ModuleOutputKeyEnum.moduleDispatchBills]?: ChatModuleUsageType[];
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AppTTSConfigType, ModuleItemType, VariableItemType } from './type';
|
||||
import { Input_Template_Switch } from './template/input';
|
||||
import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type';
|
||||
|
||||
/* module */
|
||||
export const getGuideModule = (modules: ModuleItemType[]) =>
|
||||
modules.find((item) => item.flowType === FlowNodeTypeEnum.userGuide);
|
||||
|
||||
@@ -57,13 +58,13 @@ export const getModuleInputUiField = (input: FlowNodeInputItemType) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export function plugin2ModuleIO(
|
||||
export const plugin2ModuleIO = (
|
||||
pluginId: string,
|
||||
modules: ModuleItemType[]
|
||||
): {
|
||||
inputs: FlowNodeInputItemType[];
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
} {
|
||||
} => {
|
||||
const pluginInput = modules.find((module) => module.flowType === FlowNodeTypeEnum.pluginInput);
|
||||
const pluginOutput = modules.find((module) => module.flowType === FlowNodeTypeEnum.pluginOutput);
|
||||
|
||||
@@ -99,7 +100,7 @@ export function plugin2ModuleIO(
|
||||
}))
|
||||
: []
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const formatEditorVariablePickerIcon = (
|
||||
variables: { key: string; label: string; type?: `${VariableInputEnum}` }[]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"dayjs": "^1.11.7",
|
||||
"encoding": "^0.1.13",
|
||||
"js-tiktoken": "^1.0.7",
|
||||
"openai": "4.23.0",
|
||||
"openai": "4.28.0",
|
||||
"nanoid": "^4.0.1",
|
||||
"timezones-list": "^3.0.2"
|
||||
},
|
||||
|
||||
2
packages/global/support/outLink/api.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import type { HistoryItemType, ChatSiteItemType } from '../../core/chat/type.d';
|
||||
import type { HistoryItemType } from '../../core/chat/type.d';
|
||||
import { OutLinkSchema } from './type.d';
|
||||
|
||||
export type AuthOutLinkInitProps = {
|
||||
|
||||
@@ -19,9 +19,10 @@ export type BillSchemaType = {
|
||||
datasetSize?: number;
|
||||
extraPoints?: number;
|
||||
};
|
||||
username: string;
|
||||
};
|
||||
|
||||
export type ChatModuleUsageType = {
|
||||
export type ChatNodeUsageType = {
|
||||
tokens?: number;
|
||||
totalPoints: number;
|
||||
moduleName: string;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export enum sseResponseEventEnum {
|
||||
error = 'error',
|
||||
answer = 'answer', // animation stream
|
||||
response = 'response', // direct response, not animation
|
||||
moduleStatus = 'moduleStatus',
|
||||
appStreamResponse = 'appStreamResponse' // sse response request
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { sseResponseEventEnum } from './constant';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { proxyError, ERROR_RESPONSE, ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
|
||||
import { addLog } from '../system/log';
|
||||
import { clearCookie } from '../../support/permission/controller';
|
||||
@@ -70,7 +70,7 @@ export const sseErrRes = (res: NextApiResponse, error: any) => {
|
||||
|
||||
return responseWrite({
|
||||
res,
|
||||
event: sseResponseEventEnum.error,
|
||||
event: SseResponseEventEnum.error,
|
||||
data: JSON.stringify(ERROR_RESPONSE[errResponseKey])
|
||||
});
|
||||
}
|
||||
@@ -90,7 +90,7 @@ export const sseErrRes = (res: NextApiResponse, error: any) => {
|
||||
|
||||
responseWrite({
|
||||
res,
|
||||
event: sseResponseEventEnum.error,
|
||||
event: SseResponseEventEnum.error,
|
||||
data: JSON.stringify({ message: replaceSensitiveText(msg) })
|
||||
});
|
||||
};
|
||||
@@ -132,3 +132,22 @@ export function responseWrite({
|
||||
event && Write(`event: ${event}\n`);
|
||||
Write(`data: ${data}\n\n`);
|
||||
}
|
||||
|
||||
export const responseWriteNodeStatus = ({
|
||||
res,
|
||||
status = 'running',
|
||||
name
|
||||
}: {
|
||||
res?: NextApiResponse;
|
||||
status?: 'running';
|
||||
name: string;
|
||||
}) => {
|
||||
responseWrite({
|
||||
res,
|
||||
event: SseResponseEventEnum.flowNodeStatus,
|
||||
data: JSON.stringify({
|
||||
status,
|
||||
name
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { delay } from '@fastgpt/global/common/system/utils';
|
||||
import { addLog } from '../../system/log';
|
||||
import { Pool } from 'pg';
|
||||
import type { QueryResultRow } from 'pg';
|
||||
|
||||
@@ -15,10 +17,13 @@ export const connectPg = async (): Promise<Pool> => {
|
||||
connectionTimeoutMillis: 20000
|
||||
});
|
||||
|
||||
global.pgClient.on('error', (err) => {
|
||||
global.pgClient.on('error', async (err) => {
|
||||
console.log(err);
|
||||
global.pgClient?.end();
|
||||
global.pgClient = null;
|
||||
|
||||
await delay(1000);
|
||||
addLog.info(`Retry connect pg`);
|
||||
connectPg();
|
||||
});
|
||||
|
||||
@@ -27,7 +32,12 @@ export const connectPg = async (): Promise<Pool> => {
|
||||
console.log('pg connected');
|
||||
return global.pgClient;
|
||||
} catch (error) {
|
||||
global.pgClient?.end();
|
||||
global.pgClient = null;
|
||||
|
||||
await delay(1000);
|
||||
addLog.info(`Retry connect pg`);
|
||||
|
||||
return connectPg();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,9 +2,6 @@ import type { UserModelSchema } from '@fastgpt/global/support/user/type';
|
||||
import OpenAI from '@fastgpt/global/core/ai';
|
||||
|
||||
export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
|
||||
export const baseUrl = process.env.ONEAPI_URL || openaiBaseUrl;
|
||||
|
||||
export const systemAIChatKey = process.env.CHAT_API_KEY || '';
|
||||
|
||||
export const getAIApi = (props?: {
|
||||
userKey?: UserModelSchema['openaiAccount'];
|
||||
@@ -12,9 +9,13 @@ export const getAIApi = (props?: {
|
||||
}) => {
|
||||
const { userKey, timeout } = props || {};
|
||||
|
||||
const baseUrl =
|
||||
userKey?.baseUrl || global?.systemEnv?.oneapiUrl || process.env.ONEAPI_URL || openaiBaseUrl;
|
||||
const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || process.env.CHAT_API_KEY || '';
|
||||
|
||||
return new OpenAI({
|
||||
apiKey: userKey?.key || systemAIChatKey,
|
||||
baseURL: userKey?.baseUrl || baseUrl,
|
||||
baseURL: baseUrl,
|
||||
apiKey,
|
||||
httpAgent: global.httpsAgent,
|
||||
timeout,
|
||||
maxRetries: 2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
|
||||
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
|
||||
import { getAIApi } from '../config';
|
||||
import { countGptMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
|
||||
@@ -8,10 +8,10 @@ export async function createQuestionGuide({
|
||||
messages,
|
||||
model
|
||||
}: {
|
||||
messages: ChatMessageItemType[];
|
||||
messages: ChatCompletionMessageParam[];
|
||||
model: string;
|
||||
}) {
|
||||
const concatMessages: ChatMessageItemType[] = [
|
||||
const concatMessages: ChatCompletionMessageParam[] = [
|
||||
...messages,
|
||||
{
|
||||
role: 'user',
|
||||
|
||||
@@ -2,6 +2,7 @@ import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
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';
|
||||
|
||||
/*
|
||||
query extension - 问题扩展
|
||||
@@ -133,7 +134,7 @@ A: ${chatBg}
|
||||
histories: concatFewShot
|
||||
})
|
||||
}
|
||||
];
|
||||
] as ChatCompletionMessageParam[];
|
||||
const result = await ai.chat.completions.create({
|
||||
model: model,
|
||||
temperature: 0.01,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { appCollectionName } from '../app/schema';
|
||||
import { userCollectionName } from '../../support/user/schema';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
|
||||
export const ChatItemCollectionName = 'chatitems';
|
||||
|
||||
@@ -54,8 +55,8 @@ const ChatItemSchema = new Schema({
|
||||
},
|
||||
value: {
|
||||
// chat content
|
||||
type: String,
|
||||
default: ''
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
userGoodFeedback: {
|
||||
type: String
|
||||
@@ -75,7 +76,7 @@ const ChatItemSchema = new Schema({
|
||||
a: String
|
||||
}
|
||||
},
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import type { ChatItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { MongoChatItem } from './chatItemSchema';
|
||||
import { addLog } from '../../common/system/log';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
|
||||
export async function getChatItems({
|
||||
appId,
|
||||
@@ -24,8 +25,27 @@ export async function getChatItems({
|
||||
|
||||
history.reverse();
|
||||
|
||||
history.forEach((item) => {
|
||||
// @ts-ignore
|
||||
item.value = adaptStringValue(item.value);
|
||||
});
|
||||
|
||||
return { history };
|
||||
}
|
||||
/* 临时适配旧的对话记录,清洗完数据后可删除(4.30刪除) */
|
||||
export const adaptStringValue = (value: any): ChatItemValueItemType[] => {
|
||||
if (typeof value === 'string') {
|
||||
return [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: value
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const addCustomFeedbacks = async ({
|
||||
appId,
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { ChatRoleEnum, IMG_BLOCK_KEY } from '@fastgpt/global/core/chat/constants';
|
||||
import { countMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import type { ChatCompletionContentPart } from '@fastgpt/global/core/ai/type.d';
|
||||
import { countGptMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import type {
|
||||
ChatCompletionContentPart,
|
||||
ChatCompletionMessageParam
|
||||
} from '@fastgpt/global/core/ai/type.d';
|
||||
import axios from 'axios';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
|
||||
/* slice chat context by tokens */
|
||||
export function ChatContextFilter({
|
||||
export function filterGPTMessageByMaxTokens({
|
||||
messages = [],
|
||||
maxTokens
|
||||
}: {
|
||||
messages: ChatItemType[];
|
||||
messages: ChatCompletionMessageParam[];
|
||||
maxTokens: number;
|
||||
}) {
|
||||
if (!Array.isArray(messages)) {
|
||||
return [];
|
||||
}
|
||||
const rawTextLen = messages.reduce((sum, item) => sum + item.value.length, 0);
|
||||
const rawTextLen = messages.reduce((sum, item) => {
|
||||
if (typeof item.content === 'string') {
|
||||
return sum + item.content.length;
|
||||
}
|
||||
if (Array.isArray(item.content)) {
|
||||
return (
|
||||
sum +
|
||||
item.content.reduce((sum, item) => {
|
||||
if (item.type === 'text') {
|
||||
return sum + item.text.length;
|
||||
}
|
||||
return sum;
|
||||
}, 0)
|
||||
);
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
// If the text length is less than half of the maximum token, no calculation is required
|
||||
if (rawTextLen < maxTokens * 0.5) {
|
||||
@@ -23,19 +42,21 @@ export function ChatContextFilter({
|
||||
}
|
||||
|
||||
// filter startWith system prompt
|
||||
const chatStartIndex = messages.findIndex((item) => item.obj !== ChatRoleEnum.System);
|
||||
const systemPrompts: ChatItemType[] = messages.slice(0, chatStartIndex);
|
||||
const chatPrompts: ChatItemType[] = messages.slice(chatStartIndex);
|
||||
const chatStartIndex = messages.findIndex(
|
||||
(item) => item.role !== ChatCompletionRequestMessageRoleEnum.System
|
||||
);
|
||||
const systemPrompts: ChatCompletionMessageParam[] = messages.slice(0, chatStartIndex);
|
||||
const chatPrompts: ChatCompletionMessageParam[] = messages.slice(chatStartIndex);
|
||||
|
||||
// reduce token of systemPrompt
|
||||
maxTokens -= countMessagesTokens(systemPrompts);
|
||||
maxTokens -= countGptMessagesTokens(systemPrompts);
|
||||
|
||||
// Save the last chat prompt(question)
|
||||
const question = chatPrompts.pop();
|
||||
if (!question) {
|
||||
return systemPrompts;
|
||||
}
|
||||
const chats: ChatItemType[] = [question];
|
||||
const chats: ChatCompletionMessageParam[] = [question];
|
||||
|
||||
// 从后往前截取对话内容, 每次需要截取2个
|
||||
while (1) {
|
||||
@@ -45,7 +66,7 @@ export function ChatContextFilter({
|
||||
break;
|
||||
}
|
||||
|
||||
const tokens = countMessagesTokens([assistant, user]);
|
||||
const tokens = countGptMessagesTokens([assistant, user]);
|
||||
maxTokens -= tokens;
|
||||
/* 整体 tokens 超出范围,截断 */
|
||||
if (maxTokens < 0) {
|
||||
@@ -62,6 +83,30 @@ export function ChatContextFilter({
|
||||
|
||||
return [...systemPrompts, ...chats];
|
||||
}
|
||||
export const formatGPTMessagesInRequestBefore = (messages: ChatCompletionMessageParam[]) => {
|
||||
return messages
|
||||
.map((item) => {
|
||||
if (!item.content) return;
|
||||
if (typeof item.content === 'string') {
|
||||
return {
|
||||
...item,
|
||||
content: item.content.trim()
|
||||
};
|
||||
}
|
||||
|
||||
// array
|
||||
if (item.content.length === 0) return;
|
||||
if (item.content.length === 1 && item.content[0].type === 'text') {
|
||||
return {
|
||||
...item,
|
||||
content: item.content[0].text
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
.filter(Boolean) as ChatCompletionMessageParam[];
|
||||
};
|
||||
|
||||
/**
|
||||
string to vision model. Follow the markdown code block rule for interception:
|
||||
@@ -175,3 +220,21 @@ export async function formatStr2ChatContent(str: string) {
|
||||
|
||||
return content ? content : null;
|
||||
}
|
||||
|
||||
export const loadChatImgToBase64 = async (content: string | ChatCompletionContentPart[]) => {
|
||||
if (typeof content === 'string') {
|
||||
return content;
|
||||
}
|
||||
return Promise.all(
|
||||
content.map(async (item) => {
|
||||
if (item.type === 'text') return item;
|
||||
// load image
|
||||
const response = await axios.get(item.image_url.url, {
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
const base64 = Buffer.from(response.data).toString('base64');
|
||||
item.image_url.url = `data:${response.headers['content-type']};base64,${base64}`;
|
||||
return item;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,12 +25,12 @@ export const pushResult2Remote = async ({
|
||||
outLinkUid,
|
||||
shareId,
|
||||
appName,
|
||||
responseData
|
||||
flowResponses
|
||||
}: {
|
||||
outLinkUid?: string; // raw id, not parse
|
||||
shareId?: string;
|
||||
appName: string;
|
||||
responseData?: ChatHistoryItemResType[];
|
||||
flowResponses?: ChatHistoryItemResType[];
|
||||
}) => {
|
||||
if (!shareId || !outLinkUid || !FastGPTProUrl) return;
|
||||
try {
|
||||
@@ -46,7 +46,7 @@ export const pushResult2Remote = async ({
|
||||
data: {
|
||||
token: outLinkUid,
|
||||
appName,
|
||||
responseData
|
||||
responseData: flowResponses
|
||||
}
|
||||
});
|
||||
} catch (error) {}
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
ModalCloseButton,
|
||||
ModalContentProps,
|
||||
Box,
|
||||
Image
|
||||
Image,
|
||||
useMediaQuery
|
||||
} from '@chakra-ui/react';
|
||||
import MyIcon from '../Icon';
|
||||
|
||||
@@ -31,12 +32,14 @@ const CustomModal = ({
|
||||
maxW = ['90vw', '600px'],
|
||||
...props
|
||||
}: MyModalProps) => {
|
||||
const [isPc] = useMediaQuery('(min-width: 900px)');
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={() => onClose && onClose()}
|
||||
autoFocus={false}
|
||||
isCentered={isCentered}
|
||||
isCentered={isPc ? isCentered : true}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
@@ -2,10 +2,10 @@ import React, { useState, useMemo, useRef } from 'react';
|
||||
import { Box, Card, Flex, useTheme, useOutsideClick, Button } from '@chakra-ui/react';
|
||||
import { addDays, format } from 'date-fns';
|
||||
import { type DateRange, DayPicker } from 'react-day-picker';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import 'react-day-picker/dist/style.css';
|
||||
import zhCN from 'date-fns/locale/zh-CN';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '../Icon';
|
||||
|
||||
const DateRangePicker = ({
|
||||
onChange,
|
||||
@@ -6,6 +6,7 @@ export const iconPaths = {
|
||||
collectionLight: () => import('./icons/collectionLight.svg'),
|
||||
collectionSolid: () => import('./icons/collectionSolid.svg'),
|
||||
'common/addCircleLight': () => import('./icons/common/addCircleLight.svg'),
|
||||
'common/addLight': () => import('./icons/common/addLight.svg'),
|
||||
'common/backFill': () => import('./icons/common/backFill.svg'),
|
||||
'common/backLight': () => import('./icons/common/backLight.svg'),
|
||||
'common/clearLight': () => import('./icons/common/clearLight.svg'),
|
||||
@@ -69,6 +70,7 @@ export const iconPaths = {
|
||||
'core/app/simpleMode/tts': () => import('./icons/core/app/simpleMode/tts.svg'),
|
||||
'core/app/simpleMode/variable': () => import('./icons/core/app/simpleMode/variable.svg'),
|
||||
'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'),
|
||||
'core/app/variable/external': () => import('./icons/core/app/variable/external.svg'),
|
||||
'core/app/variable/input': () => import('./icons/core/app/variable/input.svg'),
|
||||
'core/app/variable/select': () => import('./icons/core/app/variable/select.svg'),
|
||||
'core/app/variable/textarea': () => import('./icons/core/app/variable/textarea.svg'),
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 12" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M6.5 2C6.77614 2 7 2.22386 7 2.5V5.5H10C10.2761 5.5 10.5 5.72386 10.5 6C10.5 6.27614 10.2761 6.5 10 6.5H7V9.5C7 9.77614 6.77614 10 6.5 10C6.22386 10 6 9.77614 6 9.5V6.5H3C2.72386 6.5 2.5 6.27614 2.5 6C2.5 5.72386 2.72386 5.5 3 5.5H6V2.5C6 2.22386 6.22386 2 6.5 2Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
@@ -1,8 +1,4 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694067364830"
|
||||
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5118"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M727.950222 274.773333l-55.296-9.329777a38.741333 38.741333 0 0 0-12.856889 76.344888l193.308445 32.597334c1.991111 0.113778 1.991111 0.113778 2.844444 0 2.275556 0.227556 4.266667 0.113778 7.850667-0.284445l0.682667-0.056889a28.216889 28.216889 0 0 0 5.632-0.967111c1.080889 0 1.080889 0 3.185777-0.568889a15.530667 15.530667 0 0 0 4.039111-2.332444l1.137778-0.796444 0.796445-0.398223a28.444444 28.444444 0 0 0 4.152889-2.730666 37.091556 37.091556 0 0 0 6.542222-6.826667l0.796444-0.967111c1.080889-1.422222 1.080889-1.422222 2.161778-3.128889a37.432889 37.432889 0 0 0 3.697778-9.557333c0.568889-1.194667 0.568889-1.194667 1.137778-3.128889 0.113778-1.763556 0.113778-1.763556 0-2.503111v0.910222a36.579556 36.579556 0 0 0-0.341334-10.24l-0.113778-0.967111a22.755556 22.755556 0 0 0-0.682666-3.982222c0-1.080889 0-1.080889-0.568889-3.128889l-68.494222-183.751111a38.798222 38.798222 0 0 0-49.777778-22.755556 38.798222 38.798222 0 0 0-22.755556 49.777778l16.270223 43.804444A397.880889 397.880889 0 0 0 512 113.777778C292.408889 113.777778 113.777778 292.408889 113.777778 512s178.631111 398.222222 398.222222 398.222222 398.222222-178.631111 398.222222-398.222222a38.684444 38.684444 0 1 0-77.368889 0c0 176.924444-143.928889 320.853333-320.853333 320.853333S191.146667 688.924444 191.146667 512 335.075556 191.146667 512 191.146667c80.099556 0 157.070222 29.980444 215.950222 83.626666z"
|
||||
p-id="5119"></path>
|
||||
d="M4.82661 10.9785C3.86099 10.7071 3.07349 10.1763 2.46411 9.38585C1.85474 8.59543 1.55005 7.68544 1.55005 6.65587C1.55005 6.12256 1.63911 5.61489 1.81724 5.13286C1.99536 4.65082 2.24849 4.20883 2.57661 3.80688C2.67974 3.69461 2.8063 3.63604 2.9563 3.63117C3.1063 3.62631 3.24224 3.68488 3.36411 3.80688C3.46724 3.9098 3.52124 4.03611 3.52611 4.18581C3.53099 4.33551 3.48167 4.47586 3.37817 4.60685C3.15317 4.89689 2.97974 5.215 2.85786 5.56119C2.73599 5.90737 2.67505 6.27226 2.67505 6.65587C2.67505 7.41373 2.8978 8.08981 3.3433 8.68413C3.7888 9.27844 4.36292 9.683 5.06567 9.89782C5.18755 9.93525 5.28824 10.0054 5.36774 10.1083C5.44724 10.2113 5.48717 10.3235 5.48755 10.4452C5.48755 10.6323 5.42192 10.7797 5.29067 10.8875C5.15942 10.9953 5.00474 11.0256 4.82661 10.9785ZM7.27349 10.9785C7.09536 11.0253 6.94067 10.9925 6.80942 10.8802C6.67817 10.768 6.61255 10.6183 6.61255 10.4311C6.61255 10.3189 6.65249 10.2113 6.73236 10.1083C6.81224 10.0054 6.91292 9.93525 7.03442 9.89782C7.73755 9.67327 8.31186 9.26627 8.75736 8.67683C9.20286 8.08738 9.42542 7.41373 9.42505 6.65587C9.42505 5.72024 9.09692 4.92496 8.44067 4.27002C7.78442 3.61508 6.98755 3.28761 6.05005 3.28761H6.00786L6.23286 3.51216C6.33599 3.61508 6.38755 3.74607 6.38755 3.90512C6.38755 4.06418 6.33599 4.19517 6.23286 4.29809C6.12974 4.40101 5.99849 4.45247 5.83911 4.45247C5.67974 4.45247 5.54849 4.40101 5.44536 4.29809L4.26411 3.1192C4.20786 3.06306 4.16811 3.00224 4.14486 2.93675C4.12161 2.87126 4.1098 2.80108 4.10942 2.72623C4.10942 2.65138 4.12124 2.58121 4.14486 2.51572C4.16849 2.45022 4.20824 2.38941 4.26411 2.33327L5.44536 1.15438C5.54849 1.05146 5.67974 1 5.83911 1C5.99849 1 6.12974 1.05146 6.23286 1.15438C6.33599 1.2573 6.38755 1.38829 6.38755 1.54734C6.38755 1.7064 6.33599 1.83739 6.23286 1.94031L6.00786 2.16486H6.05005C7.3063 2.16486 8.37036 2.59992 9.24224 3.47006C10.1141 4.34019 10.55 5.40213 10.55 6.65587C10.55 7.67571 10.2454 8.58326 9.63599 9.37855C9.02661 10.1738 8.23911 10.7071 7.27349 10.9785Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1 +1,8 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689057990782" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1770" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M878.5 255.1H770V146.7c0-43.6-35.5-79.1-79.1-79.1H145.2c-43.6 0-79.1 35.5-79.1 79.1v545.8c0 43.6 35.5 79.1 79.1 79.1h108.4V880c0 43.6 35.5 79.1 79.1 79.1h545.8c43.6 0 79.1-35.5 79.1-79.1V334.2c-0.1-43.6-35.6-79.1-79.1-79.1zM145.2 707.5c-8.3 0-15.1-6.8-15.1-15.1V146.7c0-8.3 6.8-15.1 15.1-15.1H691c8.3 0 15.1 6.8 15.1 15.1v545.8c0 8.3-6.8 15.1-15.1 15.1H145.2zM893.5 880c0 8.3-6.8 15.1-15.1 15.1H332.7c-8.3 0-15.1-6.8-15.1-15.1V771.5H691c43.6 0 79.1-35.5 79.1-79.1V319.1h108.4c8.3 0 15.1 6.8 15.1 15.1V880z" p-id="1771"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1689057990782"
|
||||
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1770"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path
|
||||
d="M878.5 255.1H770V146.7c0-43.6-35.5-79.1-79.1-79.1H145.2c-43.6 0-79.1 35.5-79.1 79.1v545.8c0 43.6 35.5 79.1 79.1 79.1h108.4V880c0 43.6 35.5 79.1 79.1 79.1h545.8c43.6 0 79.1-35.5 79.1-79.1V334.2c-0.1-43.6-35.6-79.1-79.1-79.1zM145.2 707.5c-8.3 0-15.1-6.8-15.1-15.1V146.7c0-8.3 6.8-15.1 15.1-15.1H691c8.3 0 15.1 6.8 15.1 15.1v545.8c0 8.3-6.8 15.1-15.1 15.1H145.2zM893.5 880c0 8.3-6.8 15.1-15.1 15.1H332.7c-8.3 0-15.1-6.8-15.1-15.1V771.5H691c43.6 0 79.1-35.5 79.1-79.1V319.1h108.4c8.3 0 15.1 6.8 15.1 15.1V880z"
|
||||
p-id="1771"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 871 B |
@@ -0,0 +1,6 @@
|
||||
<svg t="1709524607561" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5121"
|
||||
width="128" height="128">
|
||||
<path
|
||||
d="M310.4 454.4v-118.4h435.2c44.8 0 86.4 25.6 105.6 64 16-22.4 22.4-48 22.4-73.6 0-73.6-57.6-131.2-131.2-131.2H310.4V76.8c0-16-19.2-28.8-35.2-19.2L9.6 246.4c-12.8 9.6-12.8 25.6 0 35.2l268.8 188.8c12.8 12.8 32 3.2 32-16zM1014.4 742.4l-268.8-188.8c-12.8-9.6-35.2 0-35.2 19.2v118.4H278.4c-44.8 0-86.4-25.6-105.6-64-16 22.4-22.4 48-22.4 73.6 0 73.6 57.6 131.2 131.2 131.2h432v118.4c0 16 19.2 28.8 35.2 19.2l268.8-188.8c9.6-12.8 9.6-32-3.2-38.4z"
|
||||
p-id="5122"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 645 B |
@@ -1,8 +1,4 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702264166621"
|
||||
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3126"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
|
||||
<path
|
||||
d="M743.328 31.04c71.232 0 127.008 32.96 157.568 93.632 2.784 5.504 3.872 10.944 4.928 16.256 2.24 11.04 5.6 27.776 30.432 57.536 36.32 43.52 29.984 90.08 24.896 127.488-1.312 9.76-2.656 19.456-3.232 29.024-1.856 31.648 3.968 41.184 19.488 66.656 3.136 5.216 6.592 10.848 10.272 17.184 23.68 40.896 22.752 90.912-2.464 133.792-28.96 49.248-82.624 78.624-143.488 78.624-51.552 0-108.256-1.12-141.408-1.952 8.64 50.048 25.44 154.528 25.44 210.656 0 92.64-67.04 125.504-124.448 125.504-63.68 0-113.536-58.688-113.536-133.6 0-57.92 0-65.344-22.976-97.088-60.608-83.776-122.464-142.272-150.304-142.272L117.76 612.48c-49.056 0-88.992-39.392-88.992-87.776L28.768 118.848c0-48.416 39.904-87.808 88.992-87.808l133.888 0c12.896 0 23.392 10.368 23.392 23.104l0 512.224 39.424 0c62.176 0 144.704 101.28 188.416 161.632 31.68 43.744 31.68 63.968 31.68 123.904 0 48.192 29.92 87.424 66.784 87.424 23.296 0 77.632-7.712 77.632-79.328 0-69.824-29.344-228.704-29.664-230.304-1.28-6.848 0.64-13.92 5.28-19.2 4.608-5.28 10.688-8.288 18.464-8 0.864 0 90.72 2.656 168.672 2.656 44.032 0 82.528-20.832 103.008-55.648 16.768-28.48 17.632-61.28 2.304-87.712-3.456-6.016-6.72-11.392-9.792-16.32-16.704-27.456-28.736-47.264-26.048-93.12 0.608-10.752 2.08-21.6 3.552-32.512 4.672-34.368 8.704-64.064-14.624-92.032-32.352-38.784-37.28-63.296-40.192-77.92-0.416-1.984-0.672-3.712-1.408-5.568-15.392-30.624-47.872-67.136-115.168-67.136l-316.704 0c-12.896 0-23.392-10.336-23.392-23.072 0-12.768 10.496-23.104 23.392-23.104L743.328 31.008 743.328 31.04zM117.76 566.368l110.496 0L228.256 77.248 117.76 77.248c-23.232 0-42.176 18.656-42.176 41.632L75.584 524.8C75.552 547.712 94.496 566.368 117.76 566.368L117.76 566.368zM117.76 566.368"
|
||||
p-id="3127"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M6.89916 10.544C6.75871 10.8601 6.44531 11.0637 6.09948 11.0637C5.20183 11.0637 4.47414 10.336 4.47414 9.43838V7.86912H2.91903C1.77347 7.86912 0.897458 6.84803 1.07165 5.7158L1.56312 2.5212C1.7034 1.60939 2.48796 0.936302 3.4105 0.936302H9.53785C10.3181 0.936302 10.9506 1.56881 10.9506 2.34904V5.54364C10.9506 6.32388 10.3181 6.95638 9.53785 6.95638H8.49367L6.89916 10.544ZM7.66874 1.9363H3.4105C2.98153 1.9363 2.61672 2.24928 2.55149 2.67326L2.06002 5.86786C1.97902 6.39433 2.38636 6.86912 2.91903 6.86912H4.51777C5.04596 6.86912 5.47414 7.29731 5.47414 7.8255V9.43838C5.47414 9.757 5.71243 10.02 6.02051 10.0588L7.59138 6.52433C7.61342 6.47474 7.63936 6.42768 7.66874 6.38345V1.9363ZM8.66874 5.95638V1.9363H9.53785C9.7658 1.9363 9.95059 2.12109 9.95059 2.34904V5.54364C9.95059 5.77159 9.7658 5.95638 9.53785 5.95638H8.66874Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 960 B |
@@ -1,9 +1,4 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702264142648"
|
||||
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2973"
|
||||
data-spm-anchor-id="a313x.manage_type_myprojects.0.i0.65e73a81QqWlcB" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="128" height="128">
|
||||
<path
|
||||
d="M743.328 985.536l-316.704 0c-12.896 0-23.392-10.368-23.392-23.104s10.496-23.072 23.392-23.072l316.704 0c67.296 0 99.808-36.512 115.168-67.136 0.768-1.856 1.024-3.552 1.408-5.568 2.912-14.624 7.84-39.168 40.192-77.92 23.328-27.968 19.328-57.664 14.624-92.032-1.472-10.912-2.944-21.76-3.552-32.512-2.688-45.856 9.344-65.664 26.048-93.12 3.04-4.928 6.304-10.304 9.792-16.32 15.328-26.432 14.464-59.232-2.304-87.712-20.48-34.816-59.008-55.648-103.008-55.648-77.952 0-167.808 2.656-168.672 2.656-7.776 0.288-13.888-2.72-18.464-8-4.64-5.28-6.528-12.352-5.28-19.2 0.32-1.6 29.664-160.48 29.664-230.304 0-71.616-54.336-79.328-77.632-79.328-36.832 0-66.784 39.2-66.784 87.424 0 59.936 0 80.16-31.68 123.904-43.712 60.352-126.24 161.632-188.416 161.632L275.04 450.176l0 512.224c0 12.768-10.496 23.104-23.392 23.104L117.76 985.504c-49.056 0-88.992-39.392-88.992-87.808L28.768 491.808c0-48.384 39.904-87.776 88.992-87.776l196.704 0c27.84 0 89.696-58.496 150.304-142.272 22.976-31.712 22.976-39.168 22.976-97.088 0-74.944 49.856-133.6 113.536-133.6 57.408 0 124.448 32.896 124.448 125.504 0 56.128-16.8 160.576-25.44 210.656 33.184-0.8 89.856-1.952 141.408-1.952 60.864 0 114.56 29.376 143.488 78.624 25.216 42.88 26.144 92.928 2.464 133.792-3.68 6.336-7.104 11.968-10.272 17.184-15.52 25.472-21.344 35.008-19.488 66.656 0.576 9.568 1.952 19.296 3.232 29.024 5.088 37.408 11.424 83.968-24.896 127.488-24.832 29.792-28.192 46.496-30.432 57.536-1.056 5.28-2.176 10.752-4.928 16.256C870.336 952.544 814.56 985.536 743.328 985.536L743.328 985.536zM117.76 450.208c-23.232 0-42.176 18.656-42.176 41.6l0 405.952c0 22.976 18.944 41.632 42.176 41.632l110.496 0L228.256 450.208 117.76 450.208 117.76 450.208zM117.76 450.208"
|
||||
p-id="2974"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M5.10094 1.45598C5.24139 1.13995 5.55479 0.936279 5.90063 0.936279C6.79827 0.936279 7.52596 1.66397 7.52596 2.56162V4.13088H9.08108C10.2266 4.13088 11.1026 5.15197 10.9285 6.2842L10.437 9.4788C10.2967 10.3906 9.51214 11.0637 8.5896 11.0637H2.46225C1.68202 11.0637 1.04951 10.4312 1.04951 9.65096V6.45636C1.04951 5.67612 1.68202 5.04362 2.46225 5.04362H3.50643L5.10094 1.45598ZM4.33137 10.0637H8.5896C9.01857 10.0637 9.38338 9.75072 9.44861 9.32674L9.94008 6.13214C10.0211 5.60567 9.61374 5.13088 9.08108 5.13088H7.48233C6.95414 5.13088 6.52596 4.70269 6.52596 4.1745V2.56162C6.52596 2.243 6.28768 1.98004 5.97959 1.94122L4.40873 5.47567C4.38668 5.52526 4.36075 5.57232 4.33137 5.61655V10.0637ZM3.33137 6.04362V10.0637H2.46225C2.2343 10.0637 2.04951 9.87891 2.04951 9.65096V6.45636C2.04951 6.22841 2.2343 6.04362 2.46225 6.04362H3.33137Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 969 B |
@@ -1 +1,4 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1681997838051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4520" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M898 178.7H665.3c4.3-9.8 6.7-20.6 6.7-32 0-44-36-80-80-80H432c-44 0-80 36-80 80 0 11.4 2.4 22.2 6.7 32H126c-13.2 0-24 10.8-24 24s10.8 24 24 24h772c13.2 0 24-10.8 24-24s-10.8-24-24-24z m-466 0c-8.5 0-16.5-3.4-22.6-9.4-6.1-6.1-9.4-14.1-9.4-22.6s3.4-16.5 9.4-22.6c6.1-6.1 14.1-9.4 22.6-9.4h160c8.5 0 16.5 3.4 22.6 9.4 6.1 6.1 9.4 14.1 9.4 22.6 0 8.5-3.4 16.5-9.4 22.6-6.1 6.1-14.1 9.4-22.6 9.4H432zM513 774.7c18.1 0 33-14.8 33-33v-334c0-18.1-14.9-33-33-33h-2c-18.1 0-33 14.8-33 33v334c0 18.2 14.8 33 33 33h2zM363 774.7c18.1 0 33-14.8 33-33v-334c0-18.1-14.9-33-33-33h-2c-18.1 0-33 14.8-33 33v334c0 18.2 14.8 33 33 33h2zM663 774.7c18.1 0 33-14.8 33-33v-334c0-18.1-14.9-33-33-33h-2c-18.1 0-33 14.8-33 33v334c0 18.2 14.8 33 33 33h2z" p-id="4521"></path><path d="M812 280.7c-13.3 0-24 10.7-24 24v530c0 41.9-34.1 76-76 76H312c-41.9 0-76-34.1-76-76v-530c0-13.3-10.7-24-24-24s-24 10.7-24 24v530c0 68.4 55.6 124 124 124h400c68.4 0 124-55.6 124-124v-530c0-13.2-10.7-24-24-24z" p-id="4522"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M5.61187 0.885132H6.38814C6.6304 0.885123 6.84165 0.885114 7.0161 0.899368C7.20093 0.914469 7.38803 0.948028 7.56898 1.04023C7.83672 1.17665 8.05441 1.39434 8.19083 1.66208C8.28303 1.84303 8.31659 2.03013 8.33169 2.21496C8.34371 2.362 8.34559 2.53519 8.34588 2.73106H10.1533C10.4295 2.73106 10.6533 2.95491 10.6533 3.23106C10.6533 3.5072 10.4295 3.73106 10.1533 3.73106H9.73037V8.42004C9.73038 8.79037 9.73038 9.09832 9.70985 9.34966C9.68846 9.61144 9.64232 9.85552 9.52498 10.0858C9.34431 10.4404 9.05603 10.7287 8.70145 10.9094C8.47114 11.0267 8.22706 11.0728 7.96528 11.0942C7.71394 11.1148 7.40599 11.1148 7.03568 11.1148H4.96432C4.59401 11.1148 4.28606 11.1148 4.03473 11.0942C3.77295 11.0728 3.52887 11.0267 3.29856 10.9094C2.94398 10.7287 2.65569 10.4404 2.47503 10.0858C2.35768 9.85552 2.31154 9.61144 2.29016 9.34966C2.26962 9.09832 2.26963 8.79037 2.26963 8.42005L2.26963 3.73106H1.84667C1.57053 3.73106 1.34667 3.5072 1.34667 3.23106C1.34667 2.95491 1.57053 2.73106 1.84667 2.73106H3.65413C3.65442 2.53519 3.6563 2.362 3.66831 2.21496C3.68341 2.03013 3.71697 1.84303 3.80917 1.66208C3.9456 1.39434 4.16328 1.17665 4.43103 1.04023C4.61198 0.948028 4.79907 0.914469 4.9839 0.899368C5.15836 0.885114 5.36961 0.885123 5.61187 0.885132ZM3.26963 3.73106V8.39965C3.26963 8.79558 3.27002 9.06245 3.28684 9.26823C3.30317 9.46817 3.33249 9.566 3.36603 9.63184C3.45083 9.79826 3.58613 9.93356 3.75255 10.0184C3.81839 10.0519 3.91622 10.0812 4.11616 10.0976C4.32194 10.1144 4.58881 10.1148 4.98474 10.1148H7.01526C7.41119 10.1148 7.67807 10.1144 7.88384 10.0976C8.08378 10.0812 8.18161 10.0519 8.24746 10.0184C8.41387 9.93356 8.54918 9.79826 8.63397 9.63184C8.66752 9.566 8.69683 9.46817 8.71317 9.26823C8.72998 9.06245 8.73037 8.79558 8.73037 8.39965V3.73106H3.26963ZM7.34584 2.73106H4.65416C4.65452 2.53723 4.6563 2.40278 4.66499 2.29639C4.67504 2.1734 4.69178 2.13256 4.70018 2.11607C4.74073 2.03649 4.80543 1.97178 4.88502 1.93123C4.9015 1.92283 4.94235 1.9061 5.06533 1.89605C5.19416 1.88552 5.36411 1.88513 5.63082 1.88513H6.36919C6.63589 1.88513 6.80585 1.88552 6.93467 1.89605C7.05766 1.9061 7.0985 1.92283 7.11498 1.93123C7.19457 1.97178 7.25928 2.03649 7.29983 2.11607C7.30822 2.13256 7.32496 2.1734 7.33501 2.29639C7.3437 2.40278 7.34548 2.53723 7.34584 2.73106ZM5.07704 5.2692C5.35318 5.2692 5.57704 5.49306 5.57704 5.7692V8.07661C5.57704 8.35275 5.35318 8.57661 5.07704 8.57661C4.8009 8.57661 4.57704 8.35275 4.57704 8.07661V5.7692C4.57704 5.49306 4.8009 5.2692 5.07704 5.2692ZM6.92296 5.2692C7.19911 5.2692 7.42296 5.49306 7.42296 5.7692V8.07661C7.42296 8.35275 7.19911 8.57661 6.92296 8.57661C6.64682 8.57661 6.42296 8.35275 6.42296 8.07661V5.7692C6.42296 5.49306 6.64682 5.2692 6.92296 5.2692Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -1 +1,14 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683436563791" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1062" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M162.42900021 628.38449281c-64.29851769 0-116.48887344-52.19035577-116.48887345-116.48887345s52.19035577-116.48887344 116.48887345-116.48887345 116.48887344 52.19035577 116.48887344 116.48887345S226.72751789 628.38449281 162.42900021 628.38449281z" fill="#575B66" p-id="1063"></path><path d="M511.89561936 628.38449281c-64.29851769 0-116.48887344-52.19035577-116.48887345-116.48887345s52.19035577-116.48887344 116.48887345-116.48887345 116.48887344 52.19035577 116.48887345 116.48887345S576.19413706 628.38449281 511.89561936 628.38449281z" fill="#575B66" p-id="1064"></path><path d="M861.57099979 628.38449281c-64.29851769 0-116.48887344-52.19035577-116.48887344-116.48887345s52.19035577-116.48887344 116.48887344-116.48887345 116.48887344 52.19035577 116.48887345 116.48887345S925.66075619 628.38449281 861.57099979 628.38449281z" fill="#575B66" p-id="1065"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683436563791"
|
||||
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1062"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
|
||||
<path
|
||||
d="M162.42900021 628.38449281c-64.29851769 0-116.48887344-52.19035577-116.48887345-116.48887345s52.19035577-116.48887344 116.48887345-116.48887345 116.48887344 52.19035577 116.48887344 116.48887345S226.72751789 628.38449281 162.42900021 628.38449281z"
|
||||
p-id="1063"></path>
|
||||
<path
|
||||
d="M511.89561936 628.38449281c-64.29851769 0-116.48887344-52.19035577-116.48887345-116.48887345s52.19035577-116.48887344 116.48887345-116.48887345 116.48887344 52.19035577 116.48887345 116.48887345S576.19413706 628.38449281 511.89561936 628.38449281z"
|
||||
p-id="1064"></path>
|
||||
<path
|
||||
d="M861.57099979 628.38449281c-64.29851769 0-116.48887344-52.19035577-116.48887344-116.48887345s52.19035577-116.48887344 116.48887344-116.48887345 116.48887344 52.19035577 116.48887345 116.48887345S925.66075619 628.38449281 861.57099979 628.38449281z"
|
||||
p-id="1065"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Tooltip, TooltipProps } from '@chakra-ui/react';
|
||||
import { Tooltip, TooltipProps, useMediaQuery } from '@chakra-ui/react';
|
||||
|
||||
interface Props extends TooltipProps {
|
||||
forceShow?: boolean;
|
||||
}
|
||||
|
||||
const MyTooltip = ({ children, shouldWrapChildren = true, ...props }: Props) => {
|
||||
return (
|
||||
const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ...props }: Props) => {
|
||||
const [isPc] = useMediaQuery('(min-width: 900px)');
|
||||
|
||||
return isPc || forceShow ? (
|
||||
<Tooltip
|
||||
className="tooltip"
|
||||
bg={'white'}
|
||||
@@ -25,6 +27,8 @@ const MyTooltip = ({ children, shouldWrapChildren = true, ...props }: Props) =>
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
48
packages/web/components/common/Tag/Fill.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Flex, type FlexProps } from '@chakra-ui/react';
|
||||
|
||||
interface Props extends FlexProps {
|
||||
children: React.ReactNode | React.ReactNode[];
|
||||
colorSchema?: 'blue' | 'green' | 'gray' | 'purple';
|
||||
}
|
||||
|
||||
const FillTag = ({ children, colorSchema = 'blue', ...props }: Props) => {
|
||||
const theme = useMemo(() => {
|
||||
const map = {
|
||||
blue: {
|
||||
bg: 'primary.50',
|
||||
color: 'primary.600'
|
||||
},
|
||||
green: {
|
||||
bg: 'green.50',
|
||||
color: 'green.600'
|
||||
},
|
||||
purple: {
|
||||
bg: '#F6EEFA',
|
||||
color: '#A558C9'
|
||||
},
|
||||
gray: {
|
||||
bg: 'myGray.50',
|
||||
color: 'myGray.700'
|
||||
}
|
||||
};
|
||||
return map[colorSchema];
|
||||
}, [colorSchema]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
{...theme}
|
||||
px={2}
|
||||
lineHeight={1}
|
||||
py={1}
|
||||
borderRadius={'sm'}
|
||||
fontSize={'xs'}
|
||||
alignItems={'center'}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default FillTag;
|
||||
@@ -2,7 +2,7 @@ import { Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react'
|
||||
import React, { useEffect } from 'react';
|
||||
import { editorStateToText } from './utils';
|
||||
import Editor from './Editor';
|
||||
import MyModal from '../../MyModal';
|
||||
import MyModal from '../../CustomModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { $getRoot, EditorState, type LexicalEditor } from 'lexical';
|
||||
import { EditorVariablePickerType } from './type.d';
|
||||
|
||||
129
packages/web/hooks/useEditTextarea.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import {
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Input,
|
||||
useDisclosure,
|
||||
Button,
|
||||
Box,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '../components/common/CustomModal';
|
||||
import { useToast } from './useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
export const useEditTextarea = ({
|
||||
title,
|
||||
tip,
|
||||
placeholder = '',
|
||||
canEmpty = true,
|
||||
valueRule
|
||||
}: {
|
||||
title: string;
|
||||
tip?: string;
|
||||
placeholder?: string;
|
||||
canEmpty?: boolean;
|
||||
valueRule?: (val: string) => string | void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const onSuccessCb = useRef<(content: string) => void | Promise<void>>();
|
||||
const onErrorCb = useRef<(err: any) => void>();
|
||||
const { toast } = useToast();
|
||||
const defaultValue = useRef('');
|
||||
|
||||
const onOpenModal = useCallback(
|
||||
({
|
||||
defaultVal,
|
||||
onSuccess,
|
||||
onError
|
||||
}: {
|
||||
defaultVal: string;
|
||||
onSuccess: (content: string) => any;
|
||||
onError?: (err: any) => void;
|
||||
}) => {
|
||||
onOpen();
|
||||
onSuccessCb.current = onSuccess;
|
||||
onErrorCb.current = onError;
|
||||
defaultValue.current = defaultVal;
|
||||
},
|
||||
[onOpen]
|
||||
);
|
||||
|
||||
const onclickConfirm = useCallback(async () => {
|
||||
if (!textareaRef.current || !onSuccessCb.current) return;
|
||||
const val = textareaRef.current.value;
|
||||
|
||||
if (!canEmpty && !val) {
|
||||
textareaRef.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueRule) {
|
||||
const result = valueRule(val);
|
||||
if (result) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: result
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await onSuccessCb.current(val);
|
||||
|
||||
onClose();
|
||||
} catch (err) {
|
||||
onErrorCb.current?.(err);
|
||||
}
|
||||
}, [canEmpty, onClose]);
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const EditModal = useCallback(
|
||||
({
|
||||
maxLength = 30,
|
||||
iconSrc = 'modal/edit',
|
||||
closeBtnText = t('common.Close')
|
||||
}: {
|
||||
maxLength?: number;
|
||||
iconSrc?: string;
|
||||
closeBtnText?: string;
|
||||
}) => (
|
||||
<MyModal isOpen={isOpen} onClose={onClose} iconSrc={iconSrc} title={title} maxW={'500px'}>
|
||||
<ModalBody>
|
||||
{!!tip && (
|
||||
<Box mb={2} color={'myGray.500'} fontSize={'sm'}>
|
||||
{tip}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Textarea
|
||||
ref={textareaRef}
|
||||
defaultValue={defaultValue.current}
|
||||
placeholder={placeholder}
|
||||
autoFocus
|
||||
maxLength={maxLength}
|
||||
rows={10}
|
||||
bg={'myGray.50'}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!!closeBtnText && (
|
||||
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
|
||||
{closeBtnText}
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onclickConfirm}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
),
|
||||
[isOpen, onClose, onclickConfirm, placeholder, tip, title]
|
||||
);
|
||||
|
||||
return {
|
||||
onOpenModal,
|
||||
EditModal
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import LoadingComponent from '@/components/Loading';
|
||||
import LoadingComponent from '../components/common/MyLoading';
|
||||
|
||||
export const useLoading = (props?: { defaultLoading: boolean }) => {
|
||||
const [isLoading, setIsLoading] = useState(props?.defaultLoading || false);
|
||||
@@ -1,20 +1,29 @@
|
||||
import { useRef, useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import type { PagingData } from '@/types/index.d';
|
||||
import { IconButton, Flex, Box, Input } from '@chakra-ui/react';
|
||||
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
import { throttle } from 'lodash';
|
||||
import { useToast } from './useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
const thresholdVal = 100;
|
||||
|
||||
type PagingData<T> = {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
data: T[];
|
||||
total?: number;
|
||||
};
|
||||
|
||||
export function usePagination<T = any>({
|
||||
api,
|
||||
pageSize = 10,
|
||||
params = {},
|
||||
defaultRequest = true,
|
||||
type = 'button',
|
||||
onChange
|
||||
onChange,
|
||||
elementRef
|
||||
}: {
|
||||
api: (data: any) => any;
|
||||
pageSize?: number;
|
||||
@@ -22,12 +31,18 @@ export function usePagination<T = any>({
|
||||
defaultRequest?: boolean;
|
||||
type?: 'button' | 'scroll';
|
||||
onChange?: (pageNum: number) => void;
|
||||
elementRef?: React.RefObject<HTMLDivElement>;
|
||||
}) {
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
const { toast } = useToast();
|
||||
const [pageNum, setPageNum] = useState(1);
|
||||
const pageNumRef = useRef(pageNum);
|
||||
pageNumRef.current = pageNum;
|
||||
const [total, setTotal] = useState(0);
|
||||
const totalRef = useRef(total);
|
||||
totalRef.current = total;
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
const dataLengthRef = useRef(data.length);
|
||||
dataLengthRef.current = data.length;
|
||||
const maxPage = useMemo(() => Math.ceil(total / pageSize) || 1, [pageSize, total]);
|
||||
|
||||
const { mutate, isLoading } = useMutation({
|
||||
@@ -40,11 +55,15 @@ export function usePagination<T = any>({
|
||||
});
|
||||
setPageNum(num);
|
||||
res.total !== undefined && setTotal(res.total);
|
||||
setData(res.data);
|
||||
if (type === 'scroll') {
|
||||
setData((prevData) => [...prevData, ...res.data]);
|
||||
} else {
|
||||
setData(res.data);
|
||||
}
|
||||
onChange && onChange(num);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: error?.message || '获取数据异常',
|
||||
title: getErrText(error, '获取数据异常'),
|
||||
status: 'error'
|
||||
});
|
||||
console.log(error);
|
||||
@@ -147,7 +166,7 @@ export function usePagination<T = any>({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!elementRef.current || type !== 'scroll') return;
|
||||
if (!elementRef?.current || type !== 'scroll') return;
|
||||
|
||||
const scrolling = throttle((e: Event) => {
|
||||
const element = e.target as HTMLDivElement;
|
||||
@@ -159,16 +178,23 @@ export function usePagination<T = any>({
|
||||
// 内容总高度
|
||||
const scrollHeight = element.scrollHeight;
|
||||
// 判断是否滚动到底部
|
||||
if (scrollTop + clientHeight + thresholdVal >= scrollHeight) {
|
||||
mutate(pageNum + 1);
|
||||
if (
|
||||
scrollTop + clientHeight + thresholdVal >= scrollHeight &&
|
||||
dataLengthRef.current < totalRef.current
|
||||
) {
|
||||
mutate(pageNumRef.current + 1);
|
||||
}
|
||||
}, 100);
|
||||
elementRef.current.addEventListener('scroll', scrolling);
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
elementRef.current?.removeEventListener('scroll', scrolling);
|
||||
|
||||
const handleScroll = (e: Event) => {
|
||||
scrolling(e);
|
||||
};
|
||||
}, [elementRef, mutate, pageNum, type]);
|
||||
|
||||
elementRef.current.addEventListener('scroll', handleScroll);
|
||||
return () => {
|
||||
elementRef.current?.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, [elementRef, mutate, pageNum, type, total, data.length]);
|
||||
|
||||
useEffect(() => {
|
||||
defaultRequest && mutate(1);
|
||||
@@ -179,6 +205,7 @@ export function usePagination<T = any>({
|
||||
pageSize,
|
||||
total,
|
||||
data,
|
||||
setData,
|
||||
isLoading,
|
||||
Pagination,
|
||||
ScrollData,
|
||||
@@ -1,13 +1,21 @@
|
||||
import { useToast as uToast, UseToastOptions } from '@chakra-ui/react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
export const useToast = (props?: UseToastOptions) => {
|
||||
const toast = uToast({
|
||||
position: 'top',
|
||||
duration: 2000,
|
||||
...(props && props)
|
||||
...props
|
||||
});
|
||||
|
||||
const myToast = useCallback(
|
||||
(options?: UseToastOptions) => {
|
||||
toast(options);
|
||||
},
|
||||
[props]
|
||||
);
|
||||
|
||||
return {
|
||||
toast
|
||||
toast: myToast
|
||||
};
|
||||
};
|
||||
|
||||
@@ -26,9 +26,15 @@
|
||||
"@lexical/react": "0.12.6",
|
||||
"papaparse": "^5.4.1",
|
||||
"@lexical/utils": "0.12.6",
|
||||
"@lexical/text": "0.12.6"
|
||||
"@lexical/text": "0.12.6",
|
||||
"date-fns": "2.30.0",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"lodash": "^4.17.21",
|
||||
"@tanstack/react-query": "^4.24.10",
|
||||
"dayjs": "^1.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/react": "18.2.0",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/react-dom": "18.2.0",
|
||||
|
||||
69
pnpm-lock.yaml
generated
@@ -51,8 +51,8 @@ importers:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
openai:
|
||||
specifier: 4.23.0
|
||||
version: 4.23.0(encoding@0.1.13)
|
||||
specifier: 4.28.0
|
||||
version: 4.28.0(encoding@0.1.13)
|
||||
timezones-list:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
@@ -184,6 +184,15 @@ importers:
|
||||
'@monaco-editor/react':
|
||||
specifier: ^4.6.0
|
||||
version: 4.6.0(monaco-editor@0.46.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@tanstack/react-query':
|
||||
specifier: ^4.24.10
|
||||
version: 4.24.10(react-dom@18.2.0)(react@18.2.0)
|
||||
date-fns:
|
||||
specifier: 2.30.0
|
||||
version: 2.30.0
|
||||
dayjs:
|
||||
specifier: ^1.11.7
|
||||
version: 1.11.7
|
||||
i18next:
|
||||
specifier: 23.10.0
|
||||
version: 23.10.0
|
||||
@@ -193,6 +202,9 @@ importers:
|
||||
lexical:
|
||||
specifier: 0.12.6
|
||||
version: 0.12.6
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
mammoth:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
@@ -208,6 +220,9 @@ importers:
|
||||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
react-day-picker:
|
||||
specifier: ^8.7.1
|
||||
version: 8.7.1(date-fns@2.30.0)(react@18.2.0)
|
||||
react-dom:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
@@ -218,6 +233,9 @@ importers:
|
||||
specifier: ^7.1.2
|
||||
version: 7.1.2
|
||||
devDependencies:
|
||||
'@types/lodash':
|
||||
specifier: ^4.14.191
|
||||
version: 4.14.191
|
||||
'@types/papaparse':
|
||||
specifier: ^5.3.7
|
||||
version: 5.3.7
|
||||
@@ -317,6 +335,9 @@ importers:
|
||||
jschardet:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
json5:
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
@@ -2883,6 +2904,7 @@ packages:
|
||||
|
||||
/@emotion/memoize@0.7.4:
|
||||
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -3483,6 +3505,7 @@ packages:
|
||||
/@mapbox/node-pre-gyp@1.0.11:
|
||||
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
detect-libc: 2.0.2
|
||||
https-proxy-agent: 5.0.1
|
||||
@@ -4628,6 +4651,7 @@ packages:
|
||||
|
||||
/abbrev@1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -4663,6 +4687,7 @@ packages:
|
||||
/agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
@@ -4741,12 +4766,14 @@ packages:
|
||||
|
||||
/aproba@2.0.0:
|
||||
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/are-we-there-yet@2.0.0:
|
||||
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
|
||||
engines: {node: '>=10'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
delegates: 1.0.0
|
||||
readable-stream: 3.6.2
|
||||
@@ -5128,7 +5155,7 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001591
|
||||
caniuse-lite: 1.0.30001593
|
||||
electron-to-chromium: 1.4.690
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.13(browserslist@4.23.0)
|
||||
@@ -5196,8 +5223,8 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001591:
|
||||
resolution: {integrity: sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==}
|
||||
/caniuse-lite@1.0.30001593:
|
||||
resolution: {integrity: sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==}
|
||||
|
||||
/canvas@2.11.2:
|
||||
resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==}
|
||||
@@ -5323,6 +5350,7 @@ packages:
|
||||
/chownr@2.0.0:
|
||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -5430,6 +5458,7 @@ packages:
|
||||
/color-support@1.1.3:
|
||||
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -5498,6 +5527,7 @@ packages:
|
||||
|
||||
/console-control-strings@1.1.0:
|
||||
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -6030,6 +6060,7 @@ packages:
|
||||
/decompress-response@4.2.1:
|
||||
resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==}
|
||||
engines: {node: '>=8'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
mimic-response: 2.1.0
|
||||
dev: false
|
||||
@@ -6081,6 +6112,7 @@ packages:
|
||||
|
||||
/delegates@1.0.0:
|
||||
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -6108,6 +6140,7 @@ packages:
|
||||
/detect-libc@2.0.2:
|
||||
resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
|
||||
engines: {node: '>=8'}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -7239,6 +7272,7 @@ packages:
|
||||
/fs-minipass@2.1.0:
|
||||
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
|
||||
engines: {node: '>= 8'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
dev: false
|
||||
@@ -7274,6 +7308,7 @@ packages:
|
||||
/gauge@3.0.2:
|
||||
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
|
||||
engines: {node: '>=10'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
aproba: 2.0.0
|
||||
color-support: 1.1.3
|
||||
@@ -7444,6 +7479,7 @@ packages:
|
||||
|
||||
/has-unicode@2.0.1:
|
||||
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -7593,6 +7629,7 @@ packages:
|
||||
/https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.4
|
||||
@@ -8359,6 +8396,7 @@ packages:
|
||||
/make-dir@3.1.0:
|
||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||
engines: {node: '>=8'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
semver: 6.3.1
|
||||
dev: false
|
||||
@@ -8562,6 +8600,7 @@ packages:
|
||||
|
||||
/memory-pager@1.5.0:
|
||||
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -8913,6 +8952,7 @@ packages:
|
||||
/mimic-response@2.1.0:
|
||||
resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==}
|
||||
engines: {node: '>=8'}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -8935,6 +8975,7 @@ packages:
|
||||
/minipass@3.3.6:
|
||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
||||
engines: {node: '>=8'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
@@ -8943,12 +8984,14 @@ packages:
|
||||
/minipass@5.0.0:
|
||||
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
|
||||
engines: {node: '>=8'}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/minizlib@2.1.2:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
engines: {node: '>= 8'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
yallist: 4.0.0
|
||||
@@ -8966,6 +9009,7 @@ packages:
|
||||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -9064,6 +9108,7 @@ packages:
|
||||
|
||||
/nan@2.18.0:
|
||||
resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -9124,7 +9169,7 @@ packages:
|
||||
'@next/env': 13.5.2
|
||||
'@swc/helpers': 0.5.2
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001591
|
||||
caniuse-lite: 1.0.30001593
|
||||
postcss: 8.4.14
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@@ -9224,6 +9269,7 @@ packages:
|
||||
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
abbrev: 1.1.1
|
||||
dev: false
|
||||
@@ -9242,6 +9288,7 @@ packages:
|
||||
|
||||
/npmlog@5.0.1:
|
||||
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
are-we-there-yet: 2.0.0
|
||||
console-control-strings: 1.1.0
|
||||
@@ -9359,8 +9406,8 @@ packages:
|
||||
mimic-fn: 4.0.0
|
||||
dev: true
|
||||
|
||||
/openai@4.23.0(encoding@0.1.13):
|
||||
resolution: {integrity: sha512-ey2CXh1OTcTUa0AWZWuTpgA9t5GuAG3DVU1MofCRUI7fQJij8XJ3Sr0VtgxoAE69C9wbHBMCux8Z/IQZfSwHiA==}
|
||||
/openai@4.28.0(encoding@0.1.13):
|
||||
resolution: {integrity: sha512-JM8fhcpmpGN0vrUwGquYIzdcEQHtFuom6sRCbbCM6CfzZXNuRk33G7KfeRAIfnaCxSpzrP5iHtwJzIm6biUZ2Q==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 18.19.21
|
||||
@@ -10417,6 +10464,7 @@ packages:
|
||||
|
||||
/set-blocking@2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -10486,11 +10534,13 @@ packages:
|
||||
|
||||
/simple-concat@1.0.1:
|
||||
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/simple-get@3.1.1:
|
||||
resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
decompress-response: 4.2.1
|
||||
once: 1.4.0
|
||||
@@ -10566,6 +10616,7 @@ packages:
|
||||
|
||||
/sparse-bitfield@3.0.3:
|
||||
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
memory-pager: 1.5.0
|
||||
dev: false
|
||||
@@ -10796,6 +10847,7 @@ packages:
|
||||
/tar@6.2.0:
|
||||
resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
|
||||
engines: {node: '>=10'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
chownr: 2.0.0
|
||||
fs-minipass: 2.1.0
|
||||
@@ -11526,6 +11578,7 @@ packages:
|
||||
|
||||
/wide-align@1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
dev: false
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"llmModels": [
|
||||
{
|
||||
"model": "gpt-3.5-turbo-1106",
|
||||
"model": "gpt-3.5-turbo",
|
||||
"name": "gpt-3.5-turbo",
|
||||
"maxContext": 16000,
|
||||
"maxResponse": 4000,
|
||||
@@ -17,6 +17,10 @@
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"datasetProcess": false,
|
||||
"usedInClassify": true,
|
||||
"usedInExtractFields": true,
|
||||
"useInToolCall": true,
|
||||
"usedInQueryExtension": true,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"customCQPrompt": "",
|
||||
@@ -35,6 +39,10 @@
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"datasetProcess": true,
|
||||
"usedInClassify": true,
|
||||
"usedInExtractFields": true,
|
||||
"useInToolCall": true,
|
||||
"usedInQueryExtension": true,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"customCQPrompt": "",
|
||||
@@ -53,6 +61,10 @@
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"datasetProcess": false,
|
||||
"usedInClassify": true,
|
||||
"usedInExtractFields": true,
|
||||
"useInToolCall": true,
|
||||
"usedInQueryExtension": true,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"customCQPrompt": "",
|
||||
@@ -71,6 +83,10 @@
|
||||
"censor": false,
|
||||
"vision": true,
|
||||
"datasetProcess": false,
|
||||
"usedInClassify": false,
|
||||
"usedInExtractFields": false,
|
||||
"useInToolCall": false,
|
||||
"usedInQueryExtension": false,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"customCQPrompt": "",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.6.9",
|
||||
"version": "4.7",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -37,6 +37,7 @@
|
||||
"immer": "^9.0.19",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jschardet": "^3.0.0",
|
||||
"json5": "^2.2.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mermaid": "^10.2.3",
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
### Fast GPT V4.6.8
|
||||
### FastGPT V4.7
|
||||
|
||||
1. 新增 - 知识库新增“增强处理”训练模式,可生成更多类型索引。
|
||||
2. 新增 - 完善了HTTP模块的变量提示。
|
||||
3. 新增 - HTTP模块支持OpenAI单接口导入。
|
||||
4. 优化 - 问题补全。增加英文类型。同时可以设置为单独模块,方便复用。
|
||||
5. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
|
||||
6. [使用文档](https://doc.fastgpt.in/docs/intro/)
|
||||
7. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)
|
||||
1. 新增 - 工具调用模块,可以让LLM模型根据用户意图,动态的选择其他模型或插件执行。
|
||||
2. 优化 - 高级编排性能
|
||||
3. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
|
||||
4. [使用文档](https://doc.fastgpt.in/docs/intro/)
|
||||
5. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)
|
||||
12
projects/app/public/imgs/module/tool.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M7.16966 2.66659H8.98677C9.57588 2.66659 10.0534 3.14415 10.0534 3.73325V3.9589H11.3332C11.3512 3.9589 11.3691 3.9589 11.3868 3.9589V3.73325C11.3868 2.40777 10.3123 1.33325 8.98677 1.33325H7.16966C5.84418 1.33325 4.76966 2.40777 4.76966 3.73325V3.9589H6.10299V3.73325C6.10299 3.14415 6.58056 2.66659 7.16966 2.66659Z"
|
||||
fill="#6F5DD7" />
|
||||
<path
|
||||
d="M14.9573 6.83844H1.04263C1.05186 6.04556 1.09409 5.57531 1.28709 5.19652C1.5044 4.77002 1.85116 4.42327 2.27766 4.20596C2.76253 3.9589 3.39725 3.9589 4.66671 3.9589H11.3332C12.6027 3.9589 13.2374 3.9589 13.7223 4.20596C14.1488 4.42327 14.4955 4.77002 14.7129 5.19652C14.9059 5.57531 14.9481 6.04556 14.9573 6.83844Z"
|
||||
fill="#6F5DD7" />
|
||||
<path
|
||||
d="M9.75703 8.02511H14.9599V11.04C14.9599 12.3094 14.9599 12.9442 14.7129 13.429C14.4955 13.8555 14.1488 14.2023 13.7223 14.4196C13.2374 14.6666 12.6027 14.6666 11.3332 14.6666H4.6667C3.39725 14.6666 2.76253 14.6666 2.27766 14.4196C1.85116 14.2023 1.5044 13.8555 1.28709 13.429C1.04004 12.9442 1.04004 12.3094 1.04004 11.04V8.02511H6.24291V10.2151C6.24291 10.436 6.422 10.6151 6.64291 10.6151H9.35703C9.57794 10.6151 9.75703 10.436 9.75703 10.2151V8.02511Z"
|
||||
fill="#6F5DD7" />
|
||||
<path d="M7.30958 8.02511H8.69036V9.54843H7.30958V8.02511Z" fill="#6F5DD7" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -89,6 +89,7 @@
|
||||
"Edit": "Edit",
|
||||
"Exit": "Exit",
|
||||
"Expired Time": "Expired",
|
||||
"Field": "Field",
|
||||
"File": "File",
|
||||
"Filed is repeat": "Filed is repeated",
|
||||
"Filed is repeated": "",
|
||||
@@ -409,7 +410,7 @@
|
||||
"Speaking": "I'm listening...",
|
||||
"Start Chat": "Start Chat",
|
||||
"Stop Speak": "Stop Speak",
|
||||
"Type a message": "Input problem",
|
||||
"Type a message": "Enter your question here",
|
||||
"Unpin": "Unpin",
|
||||
"You need to a chat app": "You don't have apps available",
|
||||
"error": {
|
||||
@@ -444,9 +445,11 @@
|
||||
"response": {
|
||||
"Complete Response": "Complete Response",
|
||||
"Extension model": "Extension model",
|
||||
"Plugin Resonse Detail": "Plugin Detail",
|
||||
"Plugin response detail": "Plugin Detail",
|
||||
"Read complete response": "Read Detail",
|
||||
"Read complete response tips": "Click to see the detailed process",
|
||||
"Tool call response detail": "Tool call detail",
|
||||
"Tool call tokens": "Tool call tokens",
|
||||
"context total length": "Context Length",
|
||||
"module cq": "Question classification list",
|
||||
"module cq result": "Classification Result",
|
||||
@@ -774,6 +777,9 @@
|
||||
"Input description": "",
|
||||
"label": "Dataset quote"
|
||||
},
|
||||
"Default value": "Default ",
|
||||
"Default value placeholder": "Null characters are returned by default",
|
||||
"Edit intro": "Edit",
|
||||
"Field Description": "Description",
|
||||
"Field Name": "Name",
|
||||
"Field Type": "Type",
|
||||
@@ -795,10 +801,14 @@
|
||||
"Field Edit": "Field Edit"
|
||||
},
|
||||
"extract": {
|
||||
"Add field": "Add",
|
||||
"Enum Description": "Lists the possible values for the field, one per row",
|
||||
"Enum Value": "Enum",
|
||||
"Field Description Placeholder": "Name/age /sql statement......",
|
||||
"Field Setting Title": "Extract field configuration"
|
||||
"Field Setting Title": "Extract field configuration",
|
||||
"Required": "Required",
|
||||
"Required Description": "Even if the field cannot be extracted, it is returned with the default value",
|
||||
"Target field": "Field"
|
||||
},
|
||||
"http": {
|
||||
"Add props": "Add props",
|
||||
@@ -927,6 +937,9 @@
|
||||
"textEditor": {
|
||||
"Text Edit": "Text Edit"
|
||||
},
|
||||
"tool": {
|
||||
"Tool input": "Tool input"
|
||||
},
|
||||
"valueType": {
|
||||
"any": "Any",
|
||||
"boolean": "Boolean",
|
||||
@@ -936,9 +949,11 @@
|
||||
"number": "Number",
|
||||
"selectApp": "Select App",
|
||||
"selectDataset": "Select Dataset",
|
||||
"string": "String"
|
||||
"string": "String",
|
||||
"tools": "tools"
|
||||
},
|
||||
"variable": {
|
||||
"External type": "External",
|
||||
"add option": "Add Option",
|
||||
"input type": "Text",
|
||||
"key": "Key",
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
"Edit": "编辑",
|
||||
"Exit": "退出",
|
||||
"Expired Time": "过期时间",
|
||||
"Field": "字段",
|
||||
"File": "文件",
|
||||
"Filed is repeat": "",
|
||||
"Filed is repeated": "字段重复了",
|
||||
@@ -444,9 +445,11 @@
|
||||
"response": {
|
||||
"Complete Response": "完整响应",
|
||||
"Extension model": "问题优化模型",
|
||||
"Plugin Resonse Detail": "插件详情",
|
||||
"Plugin response detail": "插件详情",
|
||||
"Read complete response": "查看详情",
|
||||
"Read complete response tips": "点击查看详细流程",
|
||||
"Tool call response detail": "工具运行详情",
|
||||
"Tool call tokens": "工具调用Tokens消耗",
|
||||
"context total length": "上下文总长度",
|
||||
"module cq": "问题分类列表",
|
||||
"module cq result": "分类结果",
|
||||
@@ -776,10 +779,13 @@
|
||||
"Input description": "可接收知识库搜索的结果。",
|
||||
"label": "知识库引用"
|
||||
},
|
||||
"Default value": "默认值",
|
||||
"Default value placeholder": "不填则默认返回空字符",
|
||||
"Edit intro": "编辑描述",
|
||||
"Field Description": "字段描述",
|
||||
"Field Name": "字段名",
|
||||
"Field Type": "字段类型",
|
||||
"Field key": "字段 Key",
|
||||
"Field key": "字段Key",
|
||||
"Http request props": "请求参数",
|
||||
"Http request settings": "请求配置",
|
||||
"Input Type": "输入类型",
|
||||
@@ -797,10 +803,14 @@
|
||||
"Field Edit": "字段编辑"
|
||||
},
|
||||
"extract": {
|
||||
"Add field": "新增字段",
|
||||
"Enum Description": "列举出该字段可能的值,每行一个",
|
||||
"Enum Value": "枚举值",
|
||||
"Field Description Placeholder": "姓名/年龄/sql语句……",
|
||||
"Field Setting Title": "提取字段配置"
|
||||
"Field Setting Title": "提取字段配置",
|
||||
"Required": "必须返回",
|
||||
"Required Description": "即使无法提取该字段,也会使用默认值进行返回",
|
||||
"Target field": "目标字段"
|
||||
},
|
||||
"http": {
|
||||
"Add props": "添加参数",
|
||||
@@ -929,6 +939,9 @@
|
||||
"textEditor": {
|
||||
"Text Edit": "文本加工"
|
||||
},
|
||||
"tool": {
|
||||
"Tool input": "工具输入"
|
||||
},
|
||||
"valueType": {
|
||||
"any": "任意",
|
||||
"boolean": "布尔",
|
||||
@@ -938,9 +951,11 @@
|
||||
"number": "数字",
|
||||
"selectApp": "应用选择",
|
||||
"selectDataset": "知识库选择",
|
||||
"string": "字符串"
|
||||
"string": "字符串",
|
||||
"tools": "工具调用"
|
||||
},
|
||||
"variable": {
|
||||
"External type": "外部传入",
|
||||
"add option": "添加选项",
|
||||
"input type": "文本",
|
||||
"key": "变量 key",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { ModalBody, Box, useTheme, Flex, Image } from '@chakra-ui/react';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
|
||||
import MyModal from '../MyModal';
|
||||
import { DispatchNodeResponseType } from '@fastgpt/global/core/module/runtime/type.d';
|
||||
|
||||
const ContextModal = ({
|
||||
context = [],
|
||||
onClose
|
||||
}: {
|
||||
context: ChatItemType[];
|
||||
context: DispatchNodeResponseType['historyPreview'];
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
@@ -17,7 +17,7 @@ const ContextModal = ({
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/chatHistory.svg"
|
||||
title={`完整对话记录(${context.length}条)`}
|
||||
title={`上下文预览(${context.length}条)`}
|
||||
h={['90vh', '80vh']}
|
||||
minW={['90vw', '600px']}
|
||||
isCentered
|
||||
|
||||
@@ -1,36 +1,24 @@
|
||||
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, useState, useTransition } from 'react';
|
||||
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { IMG_BLOCK_KEY } from '@fastgpt/global/core/chat/constants';
|
||||
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { addDays } from 'date-fns';
|
||||
import { useRequest } from '@/web/common/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';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
enum FileTypeEnum {
|
||||
image = 'image',
|
||||
file = 'file'
|
||||
}
|
||||
type FileItemType = {
|
||||
id: string;
|
||||
rawFile: File;
|
||||
type: `${FileTypeEnum}`;
|
||||
name: string;
|
||||
icon: string; // img is base64
|
||||
src?: string;
|
||||
};
|
||||
|
||||
const MessageInput = ({
|
||||
onChange,
|
||||
onSendMessage,
|
||||
onStop,
|
||||
isChatting,
|
||||
@@ -40,17 +28,29 @@ const MessageInput = ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
teamToken,
|
||||
chatForm
|
||||
}: OutLinkChatAuthProps & {
|
||||
onChange?: (e: string) => void;
|
||||
onSendMessage: (e: string) => void;
|
||||
onSendMessage: (val: ChatBoxInputType) => void;
|
||||
onStop: () => void;
|
||||
isChatting: boolean;
|
||||
showFileSelector?: boolean;
|
||||
TextareaDom: React.MutableRefObject<HTMLTextAreaElement | null>;
|
||||
resetInputVal: (val: string) => void;
|
||||
resetInputVal: (val: ChatBoxInputType) => void;
|
||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||
}) => {
|
||||
const [, startSts] = useTransition();
|
||||
const { setValue, watch, control } = chatForm;
|
||||
const inputValue = watch('input');
|
||||
const {
|
||||
update: updateFile,
|
||||
remove: removeFile,
|
||||
fields: fileList,
|
||||
append: appendFile,
|
||||
replace: replaceFile
|
||||
} = useFieldArray({
|
||||
control,
|
||||
name: 'files'
|
||||
});
|
||||
|
||||
const {
|
||||
isSpeaking,
|
||||
@@ -64,45 +64,38 @@ const MessageInput = ({
|
||||
const { isPc } = useSystemStore();
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const textareaMinH = '22px';
|
||||
const [fileList, setFileList] = useState<FileItemType[]>([]);
|
||||
const havInput = !!TextareaDom.current?.value || fileList.length > 0;
|
||||
|
||||
const havInput = !!inputValue || fileList.length > 0;
|
||||
|
||||
/* file selector and upload */
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: 'image/*',
|
||||
multiple: true,
|
||||
maxCount: 10
|
||||
});
|
||||
|
||||
const { mutate: uploadFile } = useRequest({
|
||||
mutationFn: async (file: FileItemType) => {
|
||||
if (file.type === FileTypeEnum.image) {
|
||||
mutationFn: async ({ file, fileIndex }: { file: UserInputFileItemType; fileIndex: number }) => {
|
||||
if (file.type === ChatFileTypeEnum.image && file.rawFile) {
|
||||
try {
|
||||
const src = await compressImgFileAndUpload({
|
||||
const url = await compressImgFileAndUpload({
|
||||
type: MongoImageTypeEnum.chatImage,
|
||||
file: file.rawFile,
|
||||
maxW: 4329,
|
||||
maxH: 4329,
|
||||
maxSize: 1024 * 1024 * 5,
|
||||
// 30 day expired.
|
||||
// 7 day expired.
|
||||
expiredTime: addDays(new Date(), 7),
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
setFileList((state) =>
|
||||
state.map((item) =>
|
||||
item.id === file.id
|
||||
? {
|
||||
...item,
|
||||
src: `${location.origin}${src}`
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
updateFile(fileIndex, {
|
||||
...file,
|
||||
url: `${location.origin}${url}`
|
||||
});
|
||||
} catch (error) {
|
||||
setFileList((state) => state.filter((item) => item.id !== file.id));
|
||||
removeFile(fileIndex);
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@@ -110,7 +103,6 @@ const MessageInput = ({
|
||||
},
|
||||
errorToast: t('common.Upload File Failed')
|
||||
});
|
||||
|
||||
const onSelectFile = useCallback(
|
||||
async (files: File[]) => {
|
||||
if (!files || files.length === 0) {
|
||||
@@ -119,7 +111,7 @@ const MessageInput = ({
|
||||
const loadFiles = await Promise.all(
|
||||
files.map(
|
||||
(file) =>
|
||||
new Promise<FileItemType>((resolve, reject) => {
|
||||
new Promise<UserInputFileItemType>((resolve, reject) => {
|
||||
if (file.type.includes('image')) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
@@ -127,11 +119,10 @@ const MessageInput = ({
|
||||
const item = {
|
||||
id: nanoid(),
|
||||
rawFile: file,
|
||||
type: FileTypeEnum.image,
|
||||
type: ChatFileTypeEnum.image,
|
||||
name: file.name,
|
||||
icon: reader.result as string
|
||||
};
|
||||
uploadFile(item);
|
||||
resolve(item);
|
||||
};
|
||||
reader.onerror = () => {
|
||||
@@ -141,7 +132,7 @@ const MessageInput = ({
|
||||
resolve({
|
||||
id: nanoid(),
|
||||
rawFile: file,
|
||||
type: FileTypeEnum.file,
|
||||
type: ChatFileTypeEnum.file,
|
||||
name: file.name,
|
||||
icon: 'file/pdf'
|
||||
});
|
||||
@@ -149,29 +140,28 @@ const MessageInput = ({
|
||||
})
|
||||
)
|
||||
);
|
||||
appendFile(loadFiles);
|
||||
|
||||
setFileList((state) => [...state, ...loadFiles]);
|
||||
loadFiles.forEach((file, i) =>
|
||||
uploadFile({
|
||||
file,
|
||||
fileIndex: i + fileList.length
|
||||
})
|
||||
);
|
||||
},
|
||||
[uploadFile]
|
||||
[appendFile, fileList.length, uploadFile]
|
||||
);
|
||||
|
||||
/* on send */
|
||||
const handleSend = useCallback(async () => {
|
||||
const textareaValue = TextareaDom.current?.value || '';
|
||||
|
||||
const images = fileList.filter((item) => item.type === FileTypeEnum.image);
|
||||
const imagesText =
|
||||
images.length === 0
|
||||
? ''
|
||||
: `\`\`\`${IMG_BLOCK_KEY}
|
||||
${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
const inputMessage = `${imagesText}${textareaValue}`;
|
||||
|
||||
onSendMessage(inputMessage);
|
||||
setFileList([]);
|
||||
}, [TextareaDom, fileList, onSendMessage]);
|
||||
onSendMessage({
|
||||
text: textareaValue.trim(),
|
||||
files: fileList
|
||||
});
|
||||
replaceFile([]);
|
||||
}, [TextareaDom, fileList, onSendMessage, replaceFile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!stream) {
|
||||
@@ -231,7 +221,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
|
||||
{/* file preview */}
|
||||
<Flex wrap={'wrap'} px={[2, 4]} userSelect={'none'}>
|
||||
{fileList.map((item) => (
|
||||
{fileList.map((item, index) => (
|
||||
<Box
|
||||
key={item.id}
|
||||
border={'1px solid rgba(0,0,0,0.12)'}
|
||||
@@ -240,11 +230,11 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
rounded={'md'}
|
||||
position={'relative'}
|
||||
_hover={{
|
||||
'.close-icon': { display: item.src ? 'block' : 'none' }
|
||||
'.close-icon': { display: item.url ? 'block' : 'none' }
|
||||
}}
|
||||
>
|
||||
{/* uploading */}
|
||||
{!item.src && (
|
||||
{!item.url && (
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
alignItems={'center'}
|
||||
@@ -272,12 +262,12 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
right={'-8px'}
|
||||
top={'-8px'}
|
||||
onClick={() => {
|
||||
setFileList((state) => state.filter((file) => file.id !== item.id));
|
||||
removeFile(index);
|
||||
}}
|
||||
className="close-icon"
|
||||
display={['', 'none']}
|
||||
/>
|
||||
{item.type === FileTypeEnum.image && (
|
||||
{item.type === ChatFileTypeEnum.image && (
|
||||
<Image
|
||||
alt={'img'}
|
||||
src={item.icon}
|
||||
@@ -335,14 +325,12 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
boxShadow={'none !important'}
|
||||
color={'myGray.900'}
|
||||
isDisabled={isSpeaking}
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
const textarea = e.target;
|
||||
textarea.style.height = textareaMinH;
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
|
||||
startSts(() => {
|
||||
onChange?.(textarea.value);
|
||||
});
|
||||
setValue('input', textarea.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// enter send.(pc or iframe && enter and unPress shift)
|
||||
@@ -406,7 +394,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
if (isSpeaking) {
|
||||
return stopSpeak();
|
||||
}
|
||||
startSpeak(resetInputVal);
|
||||
startSpeak((text) => resetInputVal({ text }));
|
||||
}}
|
||||
>
|
||||
<MyTooltip label={isSpeaking ? t('core.chat.Stop Speak') : t('core.chat.Record')}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { type ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { DispatchNodeResponseType } from '@fastgpt/global/core/module/runtime/type.d';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { Flex, BoxProps, useDisclosure, useTheme, Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -14,15 +15,18 @@ import ChatBoxDivider from '@/components/core/chat/Divider';
|
||||
import { strIsLink } from '@fastgpt/global/common/string/tools';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
|
||||
const ContextModal = dynamic(() => import('./ContextModal'), { ssr: false });
|
||||
const WholeResponseModal = dynamic(() => import('./WholeResponseModal'), { ssr: false });
|
||||
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
||||
const ContextModal = dynamic(() => import('./ContextModal'));
|
||||
const WholeResponseModal = dynamic(() => import('./WholeResponseModal'));
|
||||
|
||||
const isLLMNode = (item: ChatHistoryItemResType) =>
|
||||
item.moduleType === FlowNodeTypeEnum.chatNode || item.moduleType === FlowNodeTypeEnum.tools;
|
||||
|
||||
const ResponseTags = ({
|
||||
responseData = [],
|
||||
flowResponses = [],
|
||||
showDetail
|
||||
}: {
|
||||
responseData?: ChatHistoryItemResType[];
|
||||
flowResponses?: ChatHistoryItemResType[];
|
||||
showDetail: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
@@ -36,7 +40,8 @@ const ResponseTags = ({
|
||||
sourceName: string;
|
||||
};
|
||||
}>();
|
||||
const [contextModalData, setContextModalData] = useState<ChatItemType[]>();
|
||||
const [contextModalData, setContextModalData] =
|
||||
useState<DispatchNodeResponseType['historyPreview']>();
|
||||
const {
|
||||
isOpen: isOpenWholeModal,
|
||||
onOpen: onOpenWholeModal,
|
||||
@@ -44,18 +49,29 @@ const ResponseTags = ({
|
||||
} = useDisclosure();
|
||||
|
||||
const {
|
||||
chatAccount,
|
||||
llmModuleAccount,
|
||||
quoteList = [],
|
||||
sourceList = [],
|
||||
historyPreview = [],
|
||||
runningTime = 0
|
||||
} = useMemo(() => {
|
||||
const chatData = responseData.find((item) => item.moduleType === FlowNodeTypeEnum.chatNode);
|
||||
const quoteList = responseData
|
||||
.filter((item) => item.moduleType === FlowNodeTypeEnum.chatNode)
|
||||
const flatResponse = flowResponses
|
||||
.map((item) => {
|
||||
if (item.pluginDetail || item.toolDetail) {
|
||||
return [item, ...(item.pluginDetail || []), ...(item.toolDetail || [])];
|
||||
}
|
||||
return item;
|
||||
})
|
||||
.flat();
|
||||
|
||||
const chatData = flatResponse.find(isLLMNode);
|
||||
|
||||
const quoteList = flatResponse
|
||||
.filter((item) => item.moduleType === FlowNodeTypeEnum.datasetSearchNode)
|
||||
.map((item) => item.quoteList)
|
||||
.flat()
|
||||
.filter(Boolean) as SearchDataResponseItemType[];
|
||||
|
||||
const sourceList = quoteList.reduce(
|
||||
(acc: Record<string, SearchDataResponseItemType[]>, cur) => {
|
||||
if (!acc[cur.collectionId]) {
|
||||
@@ -67,8 +83,7 @@ const ResponseTags = ({
|
||||
);
|
||||
|
||||
return {
|
||||
chatAccount: responseData.filter((item) => item.moduleType === FlowNodeTypeEnum.chatNode)
|
||||
.length,
|
||||
llmModuleAccount: flatResponse.filter(isLLMNode).length,
|
||||
quoteList,
|
||||
sourceList: Object.values(sourceList)
|
||||
.flat()
|
||||
@@ -80,16 +95,16 @@ const ResponseTags = ({
|
||||
collectionId: item.collectionId
|
||||
})),
|
||||
historyPreview: chatData?.historyPreview,
|
||||
runningTime: +responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0).toFixed(2)
|
||||
runningTime: +flowResponses.reduce((sum, item) => sum + (item.runningTime || 0), 0).toFixed(2)
|
||||
};
|
||||
}, [showDetail, responseData]);
|
||||
}, [showDetail, flowResponses]);
|
||||
|
||||
const TagStyles: BoxProps = {
|
||||
mr: 2,
|
||||
bg: 'transparent'
|
||||
};
|
||||
|
||||
return responseData.length === 0 ? null : (
|
||||
return flowResponses.length === 0 ? null : (
|
||||
<>
|
||||
{sourceList.length > 0 && (
|
||||
<>
|
||||
@@ -148,10 +163,10 @@ const ResponseTags = ({
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{chatAccount === 1 && (
|
||||
{llmModuleAccount === 1 && (
|
||||
<>
|
||||
{historyPreview.length > 0 && (
|
||||
<MyTooltip label={'点击查看完整对话记录'}>
|
||||
<MyTooltip label={'点击查看上下文预览'}>
|
||||
<Tag
|
||||
colorSchema="green"
|
||||
cursor={'pointer'}
|
||||
@@ -164,7 +179,7 @@ const ResponseTags = ({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{chatAccount > 1 && (
|
||||
{llmModuleAccount > 1 && (
|
||||
<Tag colorSchema="blue" {...TagStyles}>
|
||||
多组 AI 对话
|
||||
</Tag>
|
||||
@@ -196,7 +211,7 @@ const ResponseTags = ({
|
||||
)}
|
||||
{isOpenWholeModal && (
|
||||
<WholeResponseModal
|
||||
response={responseData}
|
||||
response={flowResponses}
|
||||
showDetail={showDetail}
|
||||
onClose={onCloseWholeModal}
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useMemo, useState } from 'react';
|
||||
import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
|
||||
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { moduleTemplatesFlat } from '@/web/core/modules/template/system';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/module/template/constants';
|
||||
|
||||
import Tabs from '../Tabs';
|
||||
import MyModal from '../MyModal';
|
||||
@@ -143,6 +143,11 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
/>
|
||||
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||
<Row
|
||||
label={t('core.chat.response.Tool call tokens')}
|
||||
value={`${activeModule?.toolCallTokens}`}
|
||||
/>
|
||||
|
||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||
<Row
|
||||
label={t('core.chat.response.context total length')}
|
||||
@@ -182,12 +187,6 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
)
|
||||
}
|
||||
/>
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.module quoteList')}
|
||||
rawDom={<QuoteList showDetail={showDetail} rawSearch={activeModule.quoteList} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* dataset search */}
|
||||
@@ -213,6 +212,12 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
label={t('support.wallet.usage.Extension result')}
|
||||
value={`${activeModule?.extensionResult}`}
|
||||
/>
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.module quoteList')}
|
||||
rawDom={<QuoteList showDetail={showDetail} rawSearch={activeModule.quoteList} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* classify question */}
|
||||
@@ -276,7 +281,7 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
)}
|
||||
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Plugin Resonse Detail')}
|
||||
label={t('core.chat.response.Plugin response detail')}
|
||||
rawDom={<ResponseBox response={activeModule.pluginDetail} showDetail={showDetail} />}
|
||||
/>
|
||||
)}
|
||||
@@ -284,6 +289,14 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
|
||||
{/* text output */}
|
||||
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
|
||||
|
||||
{/* tool call */}
|
||||
{activeModule?.toolDetail && activeModule?.toolDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Tool call response detail')}
|
||||
rawDom={<ResponseBox response={activeModule.toolDetail} showDetail={showDetail} />}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useTheme } from '@chakra-ui/system';
|
||||
import React from 'react';
|
||||
|
||||
const ChatAvatar = ({ src, type }: { src?: string; type: 'Human' | 'AI' }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
w={['28px', '34px']}
|
||||
h={['28px', '34px']}
|
||||
p={'2px'}
|
||||
borderRadius={'sm'}
|
||||
border={theme.borders.base}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
bg={type === 'Human' ? 'white' : 'primary.50'}
|
||||
>
|
||||
<Avatar src={src} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatAvatar);
|
||||
@@ -0,0 +1,236 @@
|
||||
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 { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { formatChatValue2InputType } from '../utils';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
|
||||
export type ChatControllerProps = {
|
||||
isChatting: boolean;
|
||||
chat: ChatSiteItemType;
|
||||
setChatHistories?: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
showVoiceIcon?: boolean;
|
||||
ttsConfig?: AppTTSConfigType;
|
||||
onRetry?: () => void;
|
||||
onDelete?: () => void;
|
||||
onMark?: () => void;
|
||||
onReadUserDislike?: () => void;
|
||||
onCloseUserLike?: () => void;
|
||||
onAddUserLike?: () => void;
|
||||
onAddUserDislike?: () => void;
|
||||
};
|
||||
|
||||
const ChatController = ({
|
||||
isChatting,
|
||||
chat,
|
||||
setChatHistories,
|
||||
showVoiceIcon,
|
||||
ttsConfig,
|
||||
onReadUserDislike,
|
||||
onCloseUserLike,
|
||||
onMark,
|
||||
onRetry,
|
||||
onDelete,
|
||||
onAddUserDislike,
|
||||
onAddUserLike,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}: OutLinkChatAuthProps & ChatControllerProps & FlexProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({
|
||||
ttsConfig,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
const controlIconStyle = {
|
||||
w: '14px',
|
||||
cursor: 'pointer',
|
||||
p: '5px',
|
||||
bg: 'white',
|
||||
borderRight: theme.borders.base
|
||||
};
|
||||
const controlContainerStyle = {
|
||||
className: 'control',
|
||||
color: 'myGray.400',
|
||||
display: 'flex'
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
{...controlContainerStyle}
|
||||
borderRadius={'sm'}
|
||||
overflow={'hidden'}
|
||||
border={theme.borders.base}
|
||||
// 最后一个子元素,没有border
|
||||
css={css({
|
||||
'& > *:last-child, & > *:last-child svg': {
|
||||
borderRight: 'none',
|
||||
borderRadius: 'md'
|
||||
}
|
||||
})}
|
||||
>
|
||||
<MyTooltip label={t('common.Copy')}>
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
name={'copy'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => copyData(formatChatValue2InputType(chat.value).text || '')}
|
||||
/>
|
||||
</MyTooltip>
|
||||
{!!onDelete && !isChatting && (
|
||||
<>
|
||||
{onRetry && (
|
||||
<MyTooltip label={t('core.chat.retry')}>
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
name={'common/retryLight'}
|
||||
_hover={{ color: 'green.500' }}
|
||||
onClick={onRetry}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip label={t('common.Delete')}>
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
name={'delete'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
{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')}>
|
||||
<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>
|
||||
) : (
|
||||
<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
|
||||
{...controlIconStyle}
|
||||
name={'core/app/markLight'}
|
||||
_hover={{ color: '#67c13b' }}
|
||||
onClick={onMark}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{chat.obj === ChatRoleEnum.AI && (
|
||||
<>
|
||||
{!!onCloseUserLike && chat.userGoodFeedback && (
|
||||
<MyTooltip label={t('core.chat.feedback.Close User Like')}>
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
color={'white'}
|
||||
bg={'green.500'}
|
||||
fontWeight={'bold'}
|
||||
name={'core/chat/feedback/goodLight'}
|
||||
onClick={onCloseUserLike}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{!!onReadUserDislike && chat.userBadFeedback && (
|
||||
<MyTooltip label={t('core.chat.feedback.Read User dislike')}>
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
color={'white'}
|
||||
bg={'#FC9663'}
|
||||
fontWeight={'bold'}
|
||||
name={'core/chat/feedback/badLight'}
|
||||
onClick={onReadUserDislike}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{!!onAddUserLike && (
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
{...(!!chat.userGoodFeedback
|
||||
? {
|
||||
color: 'white',
|
||||
bg: 'green.500',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {
|
||||
_hover: { color: 'green.600' }
|
||||
})}
|
||||
name={'core/chat/feedback/goodLight'}
|
||||
onClick={onAddUserLike}
|
||||
/>
|
||||
)}
|
||||
{!!onAddUserDislike && (
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
{...(!!chat.userBadFeedback
|
||||
? {
|
||||
color: 'white',
|
||||
bg: '#FC9663',
|
||||
fontWeight: 'bold',
|
||||
onClick: onAddUserDislike
|
||||
}
|
||||
: {
|
||||
_hover: { color: '#FB7C3C' },
|
||||
onClick: onAddUserDislike
|
||||
})}
|
||||
name={'core/chat/feedback/badLight'}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatController);
|
||||
236
projects/app/src/components/ChatBox/components/ChatItem.tsx
Normal file
@@ -0,0 +1,236 @@
|
||||
import {
|
||||
Box,
|
||||
BoxProps,
|
||||
Card,
|
||||
Flex,
|
||||
useTheme,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionPanel,
|
||||
AccordionIcon,
|
||||
Button,
|
||||
Image,
|
||||
Grid
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import ChatController, { type ChatControllerProps } from './ChatController';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { formatChatValue2InputType } from '../utils';
|
||||
import Markdown, { CodeClassName } from '@/components/Markdown';
|
||||
import styles from '../index.module.scss';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import {
|
||||
ChatItemValueTypeEnum,
|
||||
ChatRoleEnum,
|
||||
ChatStatusEnum
|
||||
} from '@fastgpt/global/core/chat/constants';
|
||||
import FilesBlock from './FilesBox';
|
||||
|
||||
const colorMap = {
|
||||
[ChatStatusEnum.loading]: {
|
||||
bg: 'myGray.100',
|
||||
color: 'myGray.600'
|
||||
},
|
||||
[ChatStatusEnum.running]: {
|
||||
bg: 'green.50',
|
||||
color: 'green.700'
|
||||
},
|
||||
[ChatStatusEnum.finish]: {
|
||||
bg: 'green.50',
|
||||
color: 'green.700'
|
||||
}
|
||||
};
|
||||
|
||||
const ChatItem = ({
|
||||
type,
|
||||
avatar,
|
||||
statusBoxData,
|
||||
children,
|
||||
isLastChild,
|
||||
questionGuides = [],
|
||||
...chatControllerProps
|
||||
}: {
|
||||
type: ChatRoleEnum.Human | ChatRoleEnum.AI;
|
||||
avatar?: string;
|
||||
statusBoxData?: {
|
||||
status: `${ChatStatusEnum}`;
|
||||
name: string;
|
||||
};
|
||||
isLastChild?: boolean;
|
||||
questionGuides?: string[];
|
||||
children?: React.ReactNode;
|
||||
} & ChatControllerProps) => {
|
||||
const theme = useTheme();
|
||||
const styleMap: BoxProps =
|
||||
type === ChatRoleEnum.Human
|
||||
? {
|
||||
order: 0,
|
||||
borderRadius: '8px 0 8px 8px',
|
||||
justifyContent: 'flex-end',
|
||||
textAlign: 'right',
|
||||
bg: 'primary.100'
|
||||
}
|
||||
: {
|
||||
order: 1,
|
||||
borderRadius: '0 8px 8px 8px',
|
||||
justifyContent: 'flex-start',
|
||||
textAlign: 'left',
|
||||
bg: 'myGray.50'
|
||||
};
|
||||
const { chat, isChatting } = chatControllerProps;
|
||||
|
||||
const ContentCard = useMemo(() => {
|
||||
if (type === 'Human') {
|
||||
const { text, files = [] } = formatChatValue2InputType(chat.value);
|
||||
|
||||
return (
|
||||
<>
|
||||
{files.length > 0 && <FilesBlock files={files} />}
|
||||
<Markdown source={text} isChatting={false} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
/* AI */
|
||||
return (
|
||||
<Flex flexDirection={'column'} gap={2}>
|
||||
{chat.value.map((value, i) => {
|
||||
const key = `${chat.dataId}-ai-${i}`;
|
||||
if (value.text) {
|
||||
let source = value.text?.content || '';
|
||||
|
||||
if (isLastChild && !isChatting && questionGuides.length > 0) {
|
||||
source = `${source}
|
||||
\`\`\`${CodeClassName.questionGuide}
|
||||
${JSON.stringify(questionGuides)}`;
|
||||
}
|
||||
|
||||
return <Markdown key={key} source={source} isChatting={isLastChild && isChatting} />;
|
||||
}
|
||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
|
||||
return (
|
||||
<Box key={key}>
|
||||
{value.tools.map((tool) => {
|
||||
const toolParams = (() => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(tool.params), null, 2);
|
||||
} catch (error) {
|
||||
return tool.params;
|
||||
}
|
||||
})();
|
||||
const toolResponse = (() => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(tool.response), null, 2);
|
||||
} catch (error) {
|
||||
return tool.response;
|
||||
}
|
||||
})();
|
||||
return (
|
||||
<Box key={tool.id}>
|
||||
<Accordion allowToggle>
|
||||
<AccordionItem borderTop={'none'} borderBottom={'none'}>
|
||||
<AccordionButton
|
||||
w={'auto'}
|
||||
bg={'white'}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.200'}
|
||||
boxShadow={'1'}
|
||||
_hover={{
|
||||
bg: 'auto',
|
||||
color: 'primary.600'
|
||||
}}
|
||||
>
|
||||
<Image src={tool.toolAvatar} alt={''} w={'14px'} mr={2} />
|
||||
<Box mr={1}>{tool.toolName}</Box>
|
||||
{isChatting && !tool.response && (
|
||||
<MyIcon name={'common/loading'} w={'14px'} />
|
||||
)}
|
||||
<AccordionIcon color={'myGray.600'} ml={5} />
|
||||
</AccordionButton>
|
||||
<AccordionPanel
|
||||
py={0}
|
||||
px={0}
|
||||
mt={0}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
maxH={'500px'}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{toolParams && (
|
||||
<Markdown
|
||||
source={`~~~json#Input
|
||||
${toolParams}`}
|
||||
/>
|
||||
)}
|
||||
{toolResponse && (
|
||||
<Markdown
|
||||
source={`~~~json#Response
|
||||
${toolResponse}`}
|
||||
/>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
}, [chat.dataId, chat.value, isChatting, isLastChild, questionGuides, type]);
|
||||
|
||||
const chatStatusMap = useMemo(() => {
|
||||
if (!statusBoxData?.status) return;
|
||||
return colorMap[statusBoxData.status];
|
||||
}, [statusBoxData?.status]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* control icon */}
|
||||
<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} />
|
||||
</Box>
|
||||
)}
|
||||
<ChatAvatar src={avatar} type={type} />
|
||||
|
||||
{!!chatStatusMap && statusBoxData && isLastChild && (
|
||||
<Flex alignItems={'center'} px={3} py={'1.5px'} borderRadius="md" bg={chatStatusMap.bg}>
|
||||
<Box
|
||||
className={styles.statusAnimation}
|
||||
bg={chatStatusMap.color}
|
||||
w="8px"
|
||||
h="8px"
|
||||
borderRadius={'50%'}
|
||||
mt={'1px'}
|
||||
/>
|
||||
<Box ml={2} color={'myGray.600'}>
|
||||
{statusBoxData.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{/* content */}
|
||||
<Box mt={['6px', 2]} textAlign={styleMap.textAlign}>
|
||||
<Card
|
||||
className="markdown"
|
||||
{...MessageCardStyle}
|
||||
bg={styleMap.bg}
|
||||
borderRadius={styleMap.borderRadius}
|
||||
textAlign={'left'}
|
||||
>
|
||||
{ContentCard}
|
||||
{children}
|
||||
</Card>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatItem;
|
||||
23
projects/app/src/components/ChatBox/components/Empty.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { useMarkdown } from '@/web/common/hooks/useMarkdown';
|
||||
import { Box, Card } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
const Empty = () => {
|
||||
const { data: chatProblem } = useMarkdown({ url: '/chatProblem.md' });
|
||||
const { data: versionIntro } = useMarkdown({ url: '/versionIntro.md' });
|
||||
|
||||
return (
|
||||
<Box pt={6} w={'85%'} maxW={'600px'} m={'auto'} alignItems={'center'} justifyContent={'center'}>
|
||||
{/* version intro */}
|
||||
<Card p={4} mb={10} minH={'200px'}>
|
||||
<Markdown source={versionIntro} />
|
||||
</Card>
|
||||
<Card p={4} minH={'600px'}>
|
||||
<Markdown source={chatProblem} />
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Empty);
|
||||
22
projects/app/src/components/ChatBox/components/FilesBox.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Box, Flex, Grid } from '@chakra-ui/react';
|
||||
import MdImage from '@/components/Markdown/img/Image';
|
||||
import { UserInputFileItemType } from '@/components/ChatBox/type';
|
||||
|
||||
const FilesBlock = ({ files }: { files: UserInputFileItemType[] }) => {
|
||||
return (
|
||||
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gap={4}>
|
||||
{files.map(({ id, type, name, url }, i) => {
|
||||
if (type === 'image') {
|
||||
return (
|
||||
<Box key={i} rounded={'md'} flex={'1 0 0'} minW={'120px'}>
|
||||
<MdImage src={url} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilesBlock;
|
||||
119
projects/app/src/components/ChatBox/components/VariableInput.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { VariableItemType } from '@fastgpt/global/core/module/type';
|
||||
import React, { useState } from 'react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Card, Input, Textarea } from '@chakra-ui/react';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/module/constants';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ChatBoxInputFormType } from '../type.d';
|
||||
|
||||
const VariableInput = ({
|
||||
appAvatar,
|
||||
variableModules,
|
||||
variableIsFinish,
|
||||
chatForm,
|
||||
onSubmitVariables
|
||||
}: {
|
||||
appAvatar?: string;
|
||||
variableModules: VariableItemType[];
|
||||
variableIsFinish: boolean;
|
||||
onSubmitVariables: (e: Record<string, any>) => void;
|
||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { register, setValue, handleSubmit: handleSubmitChat, watch } = chatForm;
|
||||
const variables = watch('variables');
|
||||
|
||||
return (
|
||||
<Box py={3}>
|
||||
{/* avatar */}
|
||||
<ChatAvatar src={appAvatar} type={'AI'} />
|
||||
{/* message */}
|
||||
<Box textAlign={'left'}>
|
||||
<Card
|
||||
order={2}
|
||||
mt={2}
|
||||
w={'400px'}
|
||||
{...MessageCardStyle}
|
||||
bg={'white'}
|
||||
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
|
||||
>
|
||||
{variableModules.map((item) => (
|
||||
<Box key={item.id} mb={4}>
|
||||
<Box as={'label'} display={'inline-block'} position={'relative'} mb={1}>
|
||||
{item.label}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
right={'-10px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{item.type === VariableInputEnum.input && (
|
||||
<Input
|
||||
isDisabled={variableIsFinish}
|
||||
bg={'myWhite.400'}
|
||||
{...register(`variables.${item.key}`, {
|
||||
required: item.required
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{item.type === VariableInputEnum.textarea && (
|
||||
<Textarea
|
||||
isDisabled={variableIsFinish}
|
||||
bg={'myWhite.400'}
|
||||
{...register(`variables.${item.key}`, {
|
||||
required: item.required
|
||||
})}
|
||||
rows={5}
|
||||
maxLength={4000}
|
||||
/>
|
||||
)}
|
||||
{item.type === VariableInputEnum.select && (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
isDisabled={variableIsFinish}
|
||||
list={(item.enums || []).map((item) => ({
|
||||
label: item.value,
|
||||
value: item.value
|
||||
}))}
|
||||
{...register(`variables.${item.key}`, {
|
||||
required: item.required
|
||||
})}
|
||||
value={variables[item.key]}
|
||||
onchange={(e) => {
|
||||
setValue(`variables.${item.key}`, e);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
{!variableIsFinish && (
|
||||
<Button
|
||||
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
|
||||
size={'sm'}
|
||||
maxW={'100px'}
|
||||
onClick={handleSubmitChat((data) => {
|
||||
onSubmitVariables(data);
|
||||
})}
|
||||
>
|
||||
{t('core.chat.Start Chat')}
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(VariableInput);
|
||||